DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

knxi /

knxi/dsh-grsai-canvas

Verified

DSH 右侧栏「画布」插件:无限画布 + 直接在图上标注 + 接入 GrsAI(gpt-image-2 / nano-banana) 按标注改图,结果放在原图旁边对比,画布自动存到工作区。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@2d5a156f

dsh-grsai-canvas · DSH 右侧栏「画布」插件

在 DSH 网页 GUI 的右侧栏里加一块无限画布,用来跟 AI 一起改图:把图片摆上画布,直接在图上画箭头/框/文字, 一键「按标注改图」,结果放在原图旁边方便对比,继续迭代;画布自动存在工作区里,刷新不丢。

交互模型参考 Cowart(tldraw 无限画布 + 标注驱动改图)。

后端用的是 image.grsai.ai 页面自己在用的接口:

用途 请求
建任务 POST https://grsai.dakka.com.cn/v1/api/generate,Authorization: Bearer <APIKEY>,body {model, prompt, images[], aspectRatio, replyType:"async"} → {id}
查结果 GET https://grsai.dakka.com.cn/v1/api/result?id=<id> → {status, progress, results:[{url}]},约 3 秒轮询一次

浏览器不直连上游:全部经插件在宿主的 /grsai-canvas/* 代理转发 —— 绕开 CORS,结果图也不会跨域污染画布 (所以合成图能安全 toDataURL() 保存)。

画布怎么用

摆图:+图片 选文件 / 把图拖进来 / Ctrl+V 粘贴 / 工作区 从会话工作区挑。图片是画布上的对象: 选择工具下可拖动位置、拉四角缩放(默认锁比例,按住 Shift 自由缩放)。

看:滚轮以光标为中心缩放;空格拖动、中键拖动或右键拖动平移;适应 把所有图缩到可见;100% 原大;0/1 快捷键。 底纹点阵随缩放变化,便于判断位置。

标注(画在图片上,按图片归一化坐标粘住,不烧进像素):

工具 键 说明
选择/移动 V 选中、拖动、拉角缩放
画笔 B 自由涂抹,左侧可调颜色和粗细
箭头 A 从起点拖到终点
矩形框 R 框住要改的区域
椭圆 O 圈出要改的对象
蒙版 K 涂哪改哪:提交时整张图压暗、涂过的地方保持原亮
橡皮 E 擦掉点到的标注

文字不在工具栏里(0.21.0 起):它是文字图层 —— 选中图片后在右侧属性区点 + 文字, 用本机字体矢量绘制,所以导出/放大都清晰(见下面的「文字图层」)。

Ctrl+Z 撤销 / Ctrl+Shift+Z(或 Ctrl+Y)重做;Ctrl+C 把选中的图复制到系统剪贴板;Delete 删除选中的图;Esc 取消选中; 左侧工具栏的垃圾桶按钮清空这张图上的标注。快捷键只在鼠标位于画布上时生效,不会抢走对话输入框的按键。

点一下图片:快捷工具条(0.26.0):用选择工具点中一张图,它上方浮出一条工具条(图贴着屏幕上沿时自动翻到下方;点空白就收), 八颗按钮点一下直接干活 —— ✎ 编辑(切画笔)、✦ 引用生成(设为参考图并聚焦提示词框)、≡ 分图层(交给 Agent 拆层出 PSD)、 ⛶ 去背景、⊗ 去水印、⛶ 扩图、◐ 变清晰(中间这几颗走 Skills 模板、真的发上游,模板用完会自动取消选中)、↓ 下载。 其中 ⛶ 扩图 是拉框:点完图外围出现一个蓝框 + 8 个手柄,拉多大就扩多大,再点 ▶ 生成扩图, 结果正好盖在原图那个位置上(不是另起一行)。选中生成框后按 Delete 可以删掉它。 模型菜单里还多了一颗 ⟳ 获取新模型(读工作区的 画布/models.json;没有就交给 DSH 里的 Agent 去查一份,写好会自动载入)。

生成:写提示词 → 按标注改图(选中图时,提交「原图 + 标注」合成图)或 生成新图(没选图时文生图)。 结果会自动落到原图右边并选中,原图和标注都保留,可以继续改。勾选「只提交标注范围(裁剪)」则只把标注框内的区域发出去; 勾选「附加「按标注改」说明」会自动补一句「按图中标注修改,其余保持原样」。 连着点,排队出图(0.23.0):右下角那个按钮不会再变成「打断」—— 生成中也能接着点,点几下就排几个任务; 最多同时跑 2 张(多出来的排队等,常量 MAX_CONCURRENT_JOBS 可调),出好的图顺着往下摆、不重叠。 想停某一张,就点画布上那个框右上角的红色「⏹ 打断」:只停这一张,别的接着跑;排队中的那张点了等于撤回 (还没提交、不扣费),跑着的点了结果不再取回。右下角卡片会列出正在跑/在排队的任务。

生成中的占位框(0.22.0 起):点了生成,画布上立刻出现一个和成品同尺寸的框,位置就是这张图待会儿要落的地方 —— 框里是进度条、百分比(上游给了才有)与已耗时;上游不报进度时进度条变成一条来回跑的色块,让你知道它还在跑。 有生成框或填了成品尺寸时框和最终落图完全一致,只有比例时高度是估的(宁可差一点,也不把成品拉伸去填框)。 0.23.0 起每个任务一个框:排队的框是灰色写「排队中…」,跑起来的是蓝色、写这一张自己的百分比与耗时(不再共用一个)。 出图、失败、打断、续查结束框都会消失;重开画布「续查」找回未取回任务时也有框(标签 续查)。 这个框不进存档、不进撤销栈、不出现在图层面板,也不会被导出。

参考图:选中某张图点 参考,它右上角会显示「参考图」角标,提交时会作为参考图一起发给模型(不计入要修改的主图)。

多图:画布上可以并存任意多张图,自由摆放;生成结果也当作普通图片对象。

生成记录(0.21.0):每一次成功的出图都会自动存成一条持久记录(正常出图和「续查」走同一条路),右侧停靠区的 记录 标签里能看到缩略图、时间、模型、尺寸和提示词摘要。每条记录可以:看完整提示词、复制、 恢复参数(只把模型/比例/质量/背景/尺寸/提示词回填到生成区,绝不自动重发,不会偷偷花钱)、放入画布、下载、 收藏(★)、删除。上面还有关键词筛选、清空(二次确认)和「只看到收藏」。 落盘是清单 画布/history.json + 图片 画布/results/<id>.png 分开存 —— 图片是工作区里的真文件,清单只记 参数与引用,不把 base64 塞进 state.json;清单用「临时文件 + 改名」原子写入,写坏了也不会把旧记录冲成空。 按工作区隔离,刷新页面回来还在,换工作区互不影响;元数据失败时只提示、不重发 POST /generate(不会重复扣费)。

上游任务的原始输入和最终提交的提示词是分开存的(模板文字不会越叠越多),还带一份模板当时的内容快照。

对比视图(0.21.0):想细看两张图的差别,选中两张 → 属性区点 对比:左右并排(独立缩放/平移,两边同步)或 滑杆叠加(拖动分割线,左边看 A、右边看 B),随时交换 A/B、适应视口、100% 看像素。可以 导出当前结果。 Esc 关闭,不动画布、不进撤销栈。它是「看」的工具:不做像素配准、也不谎称能对齐不同尺寸的图(尺寸不同会明说)。

图层面板(0.21.0):停靠区 图层 标签是一张紧凑列表:缩略图 + 名称 + 参考标记 + 显/隐 + 锁。 点行 = 选中(和画布同步,Ctrl/Shift 多选);双击改名;拖动排序(面板顺序就是叠放顺序,首行在最底层)。 隐藏的图不绘制、点不到、也不会被当成参考图;想拿它当主图要先点 显。 锁定的图正常显示、能选中查看,但不能拖、不能缩放、不能标注/改名/排序;普通删除会跳过并告诉你跳过几张, 要删就先用 锁 解开。清空画布(在设置面板)会二次确认,并明确告诉你有几张隐藏/几张锁定。

界面重排(0.21.0):整块界面按「经常用的放眼前、偶尔用的收起来」重排 —— 顶部一条紧凑导航(+图片 / 工作区 / Skills / 记录 / 分层 / 放大 / ⤢ / 设置), 左边仍是编辑工具,左下角是视图控制(− / 缩放值 / + / 适应 / 100%), 右侧自上而下是选中对象属性区(只在选中时出现:图片信息、参考标记、对齐、文字图层、对比/导出/删除) 和生成区(只放生成流程:主图与参考摘要、提示词、模型、尺寸、预检、主按钮); 记录 / 图层 / 工作区 / Skills 收进右侧停靠区(四个标签互斥,再点一次收起); 质量、背景、裁剪、批量尺寸这些高级项默认收起,展开看,收起不会重置已经设好的值。 顺手改了几个容易误会的名字:分层 → 导出底图与文字(和 AI 拆层区分开)、存 → 存工作区、跨平台的清空 → 取消勾选。 无障碍也补了一轮:38 个表单控件补上 id/name,缩略图补 width/height 与替代文本。

界面微调(0.24.0):①画笔 / 蒙版 / 箭头 / 矩形 / 椭圆的鼠标指针换成跟你设的粗细一样大的圆点(黑白双圈,深底浅底都看得见;拖粗细滑杆实时变大变小)—— 落笔前就知道会画多宽,选择 / 场景框 / 橡皮不换; ②左下角那条视图控制搬到顶部右上角,数值改成百分比(点一下回 100%,原来的「100%」按钮去掉了;顺带修掉以前显示 1/1.5 倍数而不是百分比的老毛病); ③顶部那排按钮改成居中排,并且鼠标停上去就自动展开右边对应的面板(贴着按钮正下方弹出),移开 160ms 自动收起;在面板里打字 / 改名时不会被收走,点一下那个按钮=钉住,停靠区标签行右侧另有 ✕ 一键收起。「+图片」和「↗ 弹成浮窗」不参与悬停。

画布右键菜单 + 文字编辑(0.25.0):①画布上点右键弹出白色卡片菜单 —— 点在图片上有「复制(到剪贴板)/ 下载这张图 / 粘贴(从剪贴板)」加图层顺序四项(置顶 / 上移一层 / 下移一层 / 置底),点在空白处只有「粘贴」;右键按下拖动仍是平移画布(放手不弹菜单,靠有无位移判定),Esc 或点别处收起;「粘贴」读系统剪贴板(图片落图、图片直链按网址取图,读不到就提示按 Ctrl+V); ②双击画布上的文字就地改字:编辑框在原位冒出,字号 / 字体 / 对齐按画出来的样子,支持换行,Esc / 点别处结束并自动记一次历史(点空白或拖动画布会自动收掉,免得跟画面错位); ③选中文字后右下角出现蓝色方块,拖动即可拉字号(往外变大、往里收变小;存的是占图高的比例,改尺寸 / 导出 / 做图都不跑位,松手才提交一次)。

粘贴修复(0.25.1):焦点在输入框 / 文本域 / 可编辑区里时,画布的粘贴监听一律不接管,原样交给浏览器 —— 以前在提示词框粘贴一条裸链接(或粘截图)会被当成"图片地址 / 要落的图"抢走并 preventDefault,文字根本粘不进去。在画布本身上粘贴不受影响(截图落图、图片直链拉图照旧)。

批量操作(0.20.0):多选(空白处拖框选、Shift 点选加/减)之后 —— ①图片操作行变成 存 N 张 / 下载 N 张,按图层顺序逐个导出,文件名自动带序号(不再同秒互相覆盖);②左工具栏多了图层顺序 4 键(置顶 / 上移 / 下移 / 置底),③选中 2 张以上时操作行出现对齐 / 分布条(左 / 中 / 右、上 / 中 / 下 + 横竖等距)。图层顺序就是 doc.images 的数组顺序,没有新增字段,老存档照旧。

拖 / 粘贴图片网址(0.20.0):网页上的图直接拖进画布,或 Ctrl+V 粘一个图片直链,都能落图(落点 = 鼠标放开 / 光标位置)。只认 http/https 直链(≤2048 字符),取图走宿主 /image 代理(拒内网地址、最多 5 跳重定向);拿回来的不是图片会明说,而不是静默失败。

跨平台一次出图(0.20.0):生成卡片上勾几个平台尺寸(小红书 1080x1440 / 朋友圈 1080x1350 / 视频号 1080x1920 / 方图 / 横图)→ 同一个提示词、同一份参考图串行出多张,自动按序号错开摆放,随时在框右上角点「⏹ 打断」(只停这一张,别的接着跑),已经出来的留在画布上。每张的参数在开始前就冻结成快照(runOne(cfg)),不会因为出图中途界面重渲染而串味。

帮我写提示词(0.20.0):Skills 面板里的 ✎ 帮我写提示词 把当前图(含标注)经对话附件(createDrafts)送进会话,让 AI 给一条 120 字内的中文提示词 —— 只走对话,不发出图任务、不花额度。

蒙版(0.20.0):工具栏 蒙版(K)涂哪改哪。提交时合成图整张压暗,涂过的地方用 destination-out 抠回原亮度,再配一句「只有这一片是唯一要改的地方」的说明。上游不支持独立 mask 字段(请求体与宿主都是纯透传),所以这是当前最稳的近似做法;有蒙版时自动按整图提交(跳过裁剪,否则压暗罩会错位),橡皮可整条擦掉。

Skills 模板(可选套路):右上角 Skills 按钮里有一组内置套路,共 22 个(证件照·白底 / 证件照·蓝底 / 证件照·红底 / 抠图去背景 / 换背景 / 换天空 / 去水印 / 局部修复 / 局部重绘 / 高清修复 / 去网纹 / 商品白底图 / 商品场景图 / 换装·职业装 / 老照片修复上色 / 人脸修复 / 转插画风格 / 转风格 / 线稿上色 / 留白配图(本地加字) / 扩图补全 / 重构构图)。选中才生效,不选就是普通改图;选中后模板文字排在提示词最前面,模板自带的参数(ratio/background/quality)一并套上。自定义模板存在工作区的 画布/skills.json(随项目走),也可以在面板里新建/删除。 字段规范、取值域、提示词写法规范、自检清单和坑,单独一篇 → Skills模板规范.md。

大图:提交前会把合成图(以及参考图)等比缩到长边不超过 SUBMIT_MAX_SIDE(2048), 按钮边上显示实际提交尺寸。这一步是必须的:画布上完全可能出现 9419×15000 这种图,按原始分辨率合成会做出 上百 MB 的 PNG,浏览器 canvas 内存与请求体都撑不住(表现是 Failed to fetch);上游模型也吃不下。 标注按归一化坐标等比跟随,缩小不改变位置关系。整包超过 MAX_SUBMIT_BYTES(24 MB)时会就地拦下并给出建议。

尺寸按模型换算(0.3.0):上游的 aspectRatio 同一个字段,不同模型吃的格式不同 —— gpt-image-2 / -vip / 2.5 系列要像素值(1024x1024),nano-banana* 要比例(3:4),minimax-h3 要方向词(portrait/landscape/square,且没有 auto)。给 vip 发 3:4,上游直接判 failed:gpt-image-2-vip model does not support 3:4 parameter, please use pixel value input, for example: 1024x1024。 实现:上游站点的尺寸表整张抄进 MODEL_SIZES(14 个型号)+ SIZE_LABELS(官方标注如 864x1152 (3:4, 1K)),「尺寸」框的候选值随模型变;nearestSize() 做比例 ↔ 像素就近换算(同比例取 1K 那档),buildGenerateBody() 是唯一拼请求体的地方、提交前必定 normalizeSize() 一次;换模型与套模板都走同一套换算(证件照模板的 3:4 在 vip 上 → 864x1152,在 nano-banana 上仍是 3:4);上游若仍回「不支持这个尺寸」,sizeErrorFix() 自动改成合法值并给中文提示。认不出的模型名一律原样发,不擅自改用户填的值。

模型菜单(0.3.1):模型那一格是「文本框 + ▾」。点 ▾ 弹出 MODEL_CHOICES 菜单,一次列全 17 个型号(含 gpt-image-2.5* 别名),每项标出它认的尺寸格式,当前项打勾,点一行即切换、尺寸自动换算。刻意不用 <datalist>:浏览器会按输入框里已有的文字过滤候选,框里写着 gpt-image-2-vip 时下拉就只剩这一条(用户反馈「模型怎么只剩一个了」),而且 datalist 没法标格式。文本框仍可自由输入任意模型名。

原图比例(0.3.4):「尺寸」默认是**「原图比例」(内部值 SOURCE_SIZE = 'source',显示中文),提交时按实际要发出去那张图**的宽高比就近换算 —— 有主图用主图、勾了裁剪用裁出来那块、只发参考图用第一张参考图,都没有则退到模型兜底值。此前默认 auto(上游爱给 1:1 方图)、且套过模板后那个具体尺寸会被写进 prefs 一直粘着,竖图进去方图出来就是这么来的。

尺寸菜单与成品尺寸(0.5.0):「尺寸」框右边的 ▾ 弹出按比例分组的全量档位菜单(刻意不用原生 <datalist>:它会被框里已有的字过滤成一个,看着像"尺寸被固定了")。菜单底部是一排常用成品尺寸快捷值,顶部是「原图比例」与 auto。 「成品尺寸」(customSize)优先级最高:你明确要 1080x1440,插件就让上游按最接近的档位渲染、回来后在本地精确缩放到你要的尺寸(resizeDataUrl(),imageSmoothingQuality='high')—— 交付出去的文件尺寸就是你要的那个。

文字图层(0.6.0):图像模型"写"出来的是像素,放大必糊。正解是让它出干净底图(Skills 里有「留白配图」),文字用本机字体在本地矢量绘制:数据挂在图片对象上(image.texts,归一化中心坐标 + 占图高比例的字号 → 换尺寸/导出都不跑位),drawImageTexts() 同一份代码服务屏幕预览与导出,存/下载 时(renderExportDataUrl())按原图自然分辨率重绘,所以字永远清晰。支持本机字体检测(isFontAvailable/availableFonts)与「导入字体」(.ttf/.otf/.woff2 → FontFace,仅本次会话有效)。提交给上游默认不带文字(免得模型去重画),想带上就勾选。

分层导出(0.9.0):底图是位图、文字是矢量,烧在一起就只剩一张成品图有用了。文字面板里新增 导出尺寸 与 分层导出,一次落盘三个文件 —— layerPaths() 从保存路径派生出 成品 / <名>-底图.png / <名>.texts.json;renderExportDataUrl(image, { onlyBase, size }) 是唯一的合成出口:onlyBase 不画文字(尺寸又没变时直接返回原 dataUrl,连一次编解码都省),size 按目标分辨率重画(drawImageTexts 用的是 h × size 与 nx × w,所以底图放大必糊、文字不会)。parseExportSize() 复用 parseHumanSize 但只收 mode === 'pixel' 的写法(比例/方向词定不出像素,一律当留空),再走 fitScale() 兜住浏览器画布上限。textLayerManifest() 输出的是参数而非像素(归一化坐标 + 占图高比例字号 + 字体/字重/对齐/颜色/描边/行高),并记下导出尺寸与每个字体在本机是否可用(isFontAvailable)—— 换台机器重出时一眼看得出会 fallback 成什么。落盘仍走既有的 /ws-save 宿主路由,JSON 用 data:application/json,<encodeURIComponent>(宿主 decodeDataUrl 的非 base64 分支走 decodeURIComponent,中文不坏)。

复制到剪贴板(0.12.0):选中图按 Ctrl+C(或点图片那一行的 复制),图直接进系统剪贴板,粘到聊天/PPT/PS 就能用。写的是 navigator.clipboard.write([new ClipboardItem({ 'image/png': … })]),三个坑都在这里踩平了:① 剪贴板里的图只认 PNG,原图是 jpg/webp 时先按原始像素重画成 PNG(clipboardPngBlob())—— 声明类型和 Blob 实际类型不符,浏览器会直接拒收;② 大图编码要几百毫秒,等编码完再写就过了"用户手势",所以塞进 ClipboardItem 的是 Promise(少数不收 Promise 的实现,再等编码完写一次兜底);③ Ctrl+C 只在鼠标位于画布上、且焦点不在输入框时才接管 —— 否则会把"复制对话里的文字"一起抢走。浏览器不给写时明说「用 下载」,不静默失败。

尺寸可以写人话(0.7.0):parseHumanSize() 认 像素(1080x1440 / 1080×1440 / 1080*1440 / 1080 1440)、比例(3:4 / 3/4)、方向词(竖版/竖屏/横版/横屏/方图)、常用名(小红书/朋友圈/视频号/公众号/头像/壁纸/A4/封面/证件照…)与单边数字(按方图);convertSizeForModel() 把它换算成当前模型认识的值并给出人话提示。

坑:数字解析里的分隔符不能写成可选 —— 早先写成 [x×*]? 时 1024 被拆成 10×24。

实物尺寸与 DPI(0.8.0):做印刷/实物交付的人可以直接写带单位的尺寸 —— 210x297mm / 21x29.7cm / 8.5x11in / 210×297 毫米(大小写、前后空格都容忍),由 parseUnitSize() 按 DPI 折成像素(默认 300,在「设置」里改,范围 36–2400)。 不写单位仍然是像素(210x297 = 210×297 像素),这是刻意的:不破坏老用法;裸数字偏小时框下面会提醒「要按毫米算请写 210x297mm」。另外,若上游档位表里没有比例正好吻合的值(A4 是 1:1.414,最近的是 2:3),提示里会写明本地那次缩放会有约百分之几的拉伸,而不是让人拿到变形的成品还不知道原因。 尺寸超出浏览器画布上限时自动换算,而不是报错拦下(fitScale() + MAX_OUTPUT_SIDE = 12000 / MAX_OUTPUT_PIXELS = 60M,超限的图根本建不出 canvas,以前要等提交完才发现导不出来):实物尺寸降 DPI(海报 800x2000mm @300DPI 的 9449×23622 超限 → 自动降到 150 DPI = 4724×11811 像素,成品实际尺寸不变),裸像素则等比缩小并说明缩到了多少。

插件自我更新(0.4.0,0.4.3 起自动检测):内置默认更新源 DEFAULT_UPDATE_URL = https://b.mumu.bio/huabu/version.json —— 「更新地址」留空就用它,装好后打开画布约 2.5 秒自动检查一次,有新版本时设置按钮变成「设置 ⬆x.y.z」并在状态栏留一句(只检查,从不自动安装;不想联网就勾掉「启动时自动检查」)。也可以填自己的地址,或走手动流程:「③ 插件更新」→「检查更新 / 立即更新 / 回滚上一版」。发布侧用 deploy\make-update-manifest.ps1 生成带 sha256 的 version.json(或用 deploy\update-site.html 拖 zip 生成),把 zip 与它一起传到任意静态站 —— 发布步骤见 更新发布说明.md。宿主侧 /update/status|check|apply|rollback 路由 + lib/update.js(零依赖 zip 解压、sha256 必校验、路径越界拒绝、只写插件目录、先备份可回滚);更新后客户端刷新页面生效、宿主需重启 DSH。

让模型听懂标注:grsai 是图像模型,不是会看图的 agent —— 只给它一根红箭头,它大概率当装饰。所以提交时会自动附上 describeMarks() 生成的文字说明:每条标注被翻译成「什么形状 + 在画面的哪个位置 + 什么含义」(箭头点明"所指处即目标"), 图上用文字工具写的内容会被原样抬进提示词当指令,并要求"只改标注处、其余保持原样、输出不留任何标注痕迹"。 另外提交图里的标注会带一层深色底衬,避免在浅色/深色背景上看不清。实践建议:箭头指位置 + 文字说改成什么,两条一起给。

存:画布自动保存到 <会话工作区>/画布/state.json(改动后约 1.2 秒落盘,状态栏显示时间)。单张图可以 存工作区(写进工作区,默认 画布输出/…)、下载,或 复制(直接进系统剪贴板,等同 Ctrl+C)。 清空画布 在「设置」面板底部的低频维护里(0.21.0 起从主界面挪走),会二次确认、并提示有几张隐藏/锁定,清空本身可撤销。

大屏:右上角 ⤢ 把标签页弹成浮动面板(可拖大小),再配合 DSH 右侧栏自带的展开/全屏按钮; 生成面板右上角的 ▾ 可以把它收起来,把画布让出来。

提交前自检(0.8.0):点提交前,preflightChecks() 会按当前表单列一遍最容易忘写的要求,每条都能一键补进提示词: 「保持主体(五官/姿态/构图/颜色)完全不变」「输出清晰锐利、不要模糊/拉伸变形」「不要添加文字/水印/logo/边框」, 以及场景化的几条:带了参考图没说明用途、勾了裁剪(标注痕迹与位置都不会传过去)、画了箭头却没写提示词、 图上有本地文字图层却没告诉模型"别画字"(这条会直接给一个「套用留白配图」按钮)。已经写过的(含 Skills 模板里的原话)不会重复提醒。

把提示词同步到工作区(0.8.0):画布会把「正在编辑的提示词 + 文字图层 + 标注类型」防抖写进 <工作区>/画布/draft.json (只有文字,不含图片数据),这样对话里的 AI 能读到你在写什么、帮你检查漏了哪些要求。默认开启,「设置」里可关。

设置(APIKEY / 接口地址 / 模型)

没填 KEY 时,右上角按钮显示为**「填 APIKEY」**并高亮,画布右下也有「⚠ 还没填 APIKEY」的入口,首次打开会自动弹出设置面板。

字段 说明 存储
① GrsAI APIKEY grsai.com 后台的 KEY,每次请求放在 x-grsai-key 头里 浏览器 localStorage(dsh.grsai-canvas.apikey)
② 接口地址 默认 https://grsai.dakka.com.cn;只填域名/路径前缀,插件拼 /v1/api/generate、/v1/api/result。客户端用 x-grsai-base 头告诉宿主,宿主只接受 http/https 浏览器 localStorage(prefs 里的 apiBase)
模型 可直接输入任意模型名,下拉里是常用值 prefs

两项都只存在本机浏览器,不写进配置文件、不进日志;每台机器第一次用都要填一次。

两种装载方式(只能选一种)

DSH 的 profile 有两种把插件接进去的办法,本插件都支持,但同时用会被 DSH 拒绝(同一个包不能来自两个装载源):

A. 补丁层(本机现在用的):在 <DSH_HOME>\profiles\web\cordis.patch.yml 里写一行绝对路径。

- insert:
    - id: grsai-canvas
      name: '<绝对路径>/dsh-grsai-canvas/lib/index.js'
  • 优点:零拷贝、改源码立刻生效(客户端半边刷页即换);缺点:路径写死在这台机器上。
  • 停用/卸载:把该文件改回 [](或删掉那两行),重启 DSH,不留其他痕迹。

B. 装成 bundle(deploy\install.cmd,推荐给别人用):把包拷进 DSH home,并按包名挂载。

  • 优点:没有绝对路径,目录随便放、装完就能删下载目录;缺点:需要重启 DSH。
  • 安装位置(默认,可用 -ProfileLocal 改成后者):
    • <DSH_HOME>\profiles\node_modules\dsh-grsai-canvas ← 默认
    • <DSH_HOME>\profiles\web\node_modules\dsh-grsai-canvas ← -ProfileLocal

为什么默认放上级的 profiles\node_modules:DSH 解析 bundle 名时,安装锚点找不到就从 profile 目录往上走 Node 的查找路径, 所以放上一级照样能解析到(已实测);而 pnpm 只在 profile 目录里干活,碰不到上级那个目录 —— 万一以后有人在那台机器上装了 pnpm 并跑 dsh plugin … install,profile 自己的 node_modules 可能被当作多余依赖清掉,而 bundle 列表里还留着这一行, DSH 就会因为解析不到而起不来。放上一级可以彻底避开这个坑。

宿主半边(lib/index.js)改动后需要重启 DSH 才生效;客户端半边(lib/client.js)由 DSH 的模块系统按内容哈希发版, 也建议一起重启最稳。

版本不一致会怎样

客户端启动时会先调 GET /ping 拿宿主的 version 与 features(能力清单):

  • 宿主够新 → 正常读写画布存档、自动保存;features 里有 skills 时才启用自定义模板(/debug 同理);
  • 宿主缺某项能力(客户端被 HMR 更新、宿主没重启时是常态)→ 缺哪项就禁用哪项并提示"重启一次 DSH",不会抛"未知路由";
  • 宿主偏旧(典型场景:DSH 的客户端 HMR 先把浏览器半边换成新版,Node 半边还在跑旧代码)→ 自动保存与恢复静默关闭, 状态栏显示「宿主旧版:没存」,生成面板里显示一条 ⚠ 提示,其余功能(加图、标注、改图、存单图)照常。重启 DSH 后两边一致。

装到别的电脑

插件是零依赖的纯 JS(宿主 + 浏览器两半都手写成成品:没有构建步骤、不联网、不需要 pnpm), 搬过去只要「放对位置 + 让 DSH 知道它」。

前提:目标机器上 DSH 已经能用(dsh web 至少跑过一次,%USERPROFILE%\.dsh\profiles\web 已存在)。

方式 A:一键安装(推荐)

把整个 dsh-grsai-canvas 目录(或发布包 dsh-grsai-canvas-vX.Y.Z.zip)拷到目标机器 —— 放哪、叫什么名字都行 —— 然后:

双击 dsh-grsai-canvas\install.cmd        (根目录那个;deploy\install.cmd 作用相同)

或者命令行:

powershell -ExecutionPolicy Bypass -File deploy\install.ps1
powershell -ExecutionPolicy Bypass -File deploy\install.ps1 -DshHome D:\dsh-home -Profile web
powershell -ExecutionPolicy Bypass -File deploy\install.ps1 -ProfileLocal
powershell -ExecutionPolicy Bypass -File deploy\install.ps1 -UsePnpm   # 有 pnpm 时走官方 dsh plugin add

脚本做的事(全部可逆):

  1. 把包拷到 <DSH_HOME>\profiles\node_modules\dsh-grsai-canvas(默认;-ProfileLocal 则放 profile 自己的 node_modules);profile 不存在时会先让 dsh 自己初始化出来;
  2. 把 dsh-grsai-canvas 追加到 <profile>\package.json 的 dsh.profile.bundles(改前自动备份 package.json.bak, 写成 UTF-8 无 BOM —— 带 BOM 会让 dsh 的 JSON.parse 拒绝启动);
  3. 顺手把 deploy\ 一起拷进目标目录,所以下载目录删掉也能卸载;
  4. 校验文件齐不齐;
  5. 如果该 profile 的 cordis.patch.yml 里已经有 grsai-canvas 行,会拒绝安装并提示先删掉(见上面「两种装载方式」)。

装完重启 DSH 即可。卸载:双击安装目录里的 deploy\uninstall.cmd(或原目录里的那份)。

  • 卸载会把两处可能的安装位置(profiles\node_modules 和 <profile>\node_modules)都清掉,不用记当初装在哪;
  • 从安装目录里运行时,脚本会把"删除自己所在目录"这件事交给一个退出后启动的外部进程,避免 cmd.exe 读不到自己的批处理;
  • 源码目录的名字、放在哪都不影响安装(校验的是 package.json 里的包名),装完可以删掉源目录。

方式 B:手动(cordis.patch.yml 一行)

见上面「两种装载方式」的 A。两种方式只能选一种;切换时先把另一种撤掉再重启。

搬过去之后

APIKEY 不随插件走(只存在每台机器的浏览器 localStorage 里),所以每台电脑第一次用都要在「设置」里填一次。 画布存档在各自的会话工作区里,也不跟着走。

结构

dsh-grsai-canvas/
├── 交接文档.md            # 交接文档:架构、数据模型、怎么改怎么验、坑、变更历史
├── 画布插件说明.md         # 使用者视角的完整说明(尺寸换算表、文字图层、一键更新)
├── Skills模板规范.md      # Skills 模板的字段规范 + 提示词写法 + 自检清单
├── 更新发布说明.md         # 发布方:更新站怎么部署、清单怎么生成、30 秒自检、排错
├── install.cmd           # 一键安装(双击这个)
├── uninstall.cmd         # 一键卸载
├── README-FIRST.txt      # 给第一次用的人看的 3 步速查
├── package.json          # dsh.bundle.patch + dsh.client(platform: web, 导出 ./client)
├── cordis.patch.yml      # 作为独立 bundle 安装时用的入口行
├── deploy/
│   ├── install.ps1       # 一键安装(拷包 + 注册 bundle)
│   ├── install.cmd       # 双击用
│   ├── make-update-manifest.ps1  # 发布用:算 sha256 生成 version.json
│   ├── update-site.html          # 发布页:给人看 + 本机拖 zip 生成清单(单文件零依赖)
│   ├── uninstall.ps1
│   └── uninstall.cmd
├── tools/
│   ├── build-client.mjs   # 装配:把 lib/parts/*.js 拼成 lib/client.js(零依赖、纯字符串拼接)
│   ├── check-deps.mjs     # 自检:界面零件"用到的自由标识符 == 调用处传的依赖清单"
│   └── verify-package.mjs # 发布前自检:读 zip 核对关键文件 / 产物是否最新 / sha256
├── lib/
│   ├── index.js          # 宿主半边:/grsai-canvas/* 路由(上游代理、工作区读写、画布存档、更新)
│   ├── update.js         # 自我更新:零依赖 zip 解压 + 校验 + 备份 + 回滚
│   ├── client.js         # 浏览器半边(**装配产物**,别直接改):window.__ModuleLoader__ 成品 bundle
│   └── parts/            # 浏览器半边的**源码**(29 个零件,按职责分文件)
│       ├── 00-runtime.js        # 零件机:definePart / rt.xxx / deps() 缺依赖守卫
│       ├── 01…11-*.js           # 常量、模型尺寸表、尺寸换算、自检、Skills、工具、文字图层、
│       │                        # 几何、标注渲染、未取回任务、样式表(共享作用域的库零件)
│       ├── 12-canvas-tab.js     # 画布标签页本体:状态、绘制、命中、指针交互、组装各零件
│       ├── 20-toolbar.js        # 左侧工具栏
│       ├── 21-topright.js       # 右上角那一排(加图/工作区/Skills/适应/缩放/记录/设置)
│       ├── 22-statusbar.js      # 左下角状态栏
│       ├── 23-model-panel.js    # 模型菜单      24-size-panel.js  尺寸菜单
│       ├── 25-gen-card.js       # 右下角生成/发送区块(主操作区)
│       ├── 26-skills-panel.js   # Skills 面板   27-settings-panel.js 右上角设置面板
│       ├── 28-history-panel.js  # 生成记录      29-workspace-panel.js 工作区图片
│       ├── 30-text-input.js     # 图上文字输入框 31-frame-input.js 场景框放置弹窗
│       ├── 40-update-center.js  # 插件自我更新(检查/更新/回滚)
│       ├── 41-generate.js       # 提交与生成(合成 → 建任务 → 轮询 → 落图)
│       ├── 42-images.js         # 图片操作(存/下载/复制到剪贴板/分层导出/工作区读取)
│       └── 99-entry.js          # apply():注册 tab 类型 / 面板体 / 标题 / 侧栏入口
└── README.md

改客户端代码:改 lib/parts/*.js(别改 lib/client.js,它是产物),然后

cd dsh-grsai-canvas
node tools/build-client.mjs     # 重新装配(支持 --check 只校验 / --list 列零件)
node tools/check-deps.mjs       # 改了界面零件就顺手跑:谁漏传/多传依赖一眼看到
node --check lib/client.js      # 语法自检

dsh.client 只认一个 exports["./client"] bundle —— 所以"分文件"只能靠装配:构建脚本把零件原样拼 进 window.__ModuleLoader__.load 的外壳里(不解析模块、不压缩),产物里每个零件前留一行分割注释。 忘了装配会被 _test\client-bundle.mjs 抓住(它比对"产物 == 零件重新装配的结果")。

宿主路由一览:

路由 作用
GET /grsai-canvas/ping 自检:插件名、版本、能力清单 features、当前解析到的工作区根(客户端据此优雅降级)
GET /grsai-canvas/skills 读画布「Skills 模板」{ok, path, presets}
POST /grsai-canvas/skills 写模板(过滤掉缺 id/名称/正文的条目,上限 1 MB)
GET /grsai-canvas/debug 排错:最近 40 条上游交互(状态码 + 响应摘要,不含 KEY)
GET /grsai-canvas/state 读画布存档 {ok, path, state},没有就是 state: null
POST /grsai-canvas/state 写画布存档(JSON,上限 48 MB,写前校验)
GET /grsai-canvas/ws-list?session= 列出工作区里的图片(深度 4、最多 800 个,跳过点目录与 node_modules)
GET /grsai-canvas/ws-file?path=&session= 读一张图(限制在工作区内)
POST /grsai-canvas/ws-save {path, dataUrl} 写回工作区
GET /grsai-canvas/update/status 更新用:本机现状 {loaded, disk, name, dir, backups}(不联网)
POST /grsai-canvas/update/check 拉清单比对版本 {current, version, notes, sha256, newer}(清单必须带 sha256)
POST /grsai-canvas/update/apply 下载 → 校验 → 解压 → 先备份再覆盖 {from, to, files, backup, restartNeeded}
POST /grsai-canvas/update/rollback 从最近一次备份恢复
POST /grsai-canvas/generate 转发建任务(KEY 走 x-grsai-key 头)
GET /grsai-canvas/result?id= 转发查结果
GET /grsai-canvas/image?url= 把远程结果图取回为字节,避免画布跨域污染

工作区根的解析优先级:环境变量 DSH_GRSAI_CANVAS_DIR → 该会话 header 的 cwd → 沙箱策略工作区根 → 进程工作目录。

依赖面:宿主只用 node:fs / node:path 和全局 fetch(Node 18+);客户端只 require("react"),其余全是标准浏览器 API。

界面挂载点

  • sidebar.right.pane.tab / sidebar.right.pane.tab.title:画布标签页本体与标题(key = 包名)。
  • sidebar.footer.action:侧栏左下角「画布」按钮,点一下打开标签页。
  • tab 类型 grsai-canvas(注册进 ctx.sidebarRightTabs),带一个引导页入口;⤢ 通过 ctx.sidebarRight.float(tabId) 弹成浮动面板。

对端 DSH 版本差异时降级而不是崩:座位不存在就不激活(只影响界面),float 不存在会被 try/catch 吃掉。

自测

_test/ 下有 11 套离线测试(装配产物、宿主路由、客户端渲染与分支、画布几何/标注绘制、指针交互、尺寸换算、 更新逻辑与路由、发布页、profile 组装),直接 node 跑即可,详见 _test/README.md。 其中客户端三套要真 React(装在 <工作区>\.dshtools);compose-check.mjs 会拿 _test/profile/cordis.patch.yml 里的挂载行去比对当前插件目录,所以它要在源码所在的工作区原位跑。

开源

  • 许可: MIT —— 可自由使用、修改、分发,保留版权声明即可。
  • 仓库: https://github.com/knxi/dsh-grsai-canvas
  • 上游服务(GrsAI / APIMart / Recraft)需要你自己申请 API 密钥,额度、费用与使用条款归各服务商。
—/ 5

No ratings yet

Verified DSH bundle

Commit 2d5a156f88ec

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout