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
脚本做的事(全部可逆):
- 把包拷到
<DSH_HOME>\profiles\node_modules\dsh-grsai-canvas(默认;-ProfileLocal则放 profile 自己的node_modules);profile 不存在时会先让dsh自己初始化出来; - 把
dsh-grsai-canvas追加到<profile>\package.json的dsh.profile.bundles(改前自动备份package.json.bak, 写成 UTF-8 无 BOM —— 带 BOM 会让 dsh 的JSON.parse拒绝启动); - 顺手把
deploy\一起拷进目标目录,所以下载目录删掉也能卸载; - 校验文件齐不齐;
- 如果该 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 密钥,额度、费用与使用条款归各服务商。
No comments yet. Be the first to write one.