dsh-image-gen 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -122,11 +122,12 @@ Click the **`[Gallery]`** Tab in the top navigation bar to browse and search all
122
122
  ## ✨ Key Features
123
123
 
124
124
  - 💬 **In-Chat Image Generation**: Tell your Agent what you want to draw without switching tabs or tools.
125
- - 🖼️ **Native Image Gallery**: Dedicated "Gallery" tab automatically collecting all generated images with keyword search, provider filtering, and quick copy/download.
125
+ - 🖼️ **Native Image Gallery**: Dedicated "Gallery" tab automatically collecting all generated images with keyword search, provider filtering, single-item deletion (with confirmation and persistent tombstones), and quick copy/download.
126
126
  - 🔍 **Interactive Image Tools**: High-res fullscreen preview, one-click copy to clipboard, local download, and open in new tab.
127
127
  - 🎨 **Multi-Provider Support**: Supports Google Gemini, OpenAI Images, OpenAI Compatible API, ByteDance Seedream / Volcengine Ark, and Aliyun DashScope (Wanx / Qwen-Image).
128
128
  - 🔑 **BYOK (Bring Your Own Key)**: Uses your own API keys managed securely by DSH credentials service with write-only protection.
129
129
  - 🖼️ **Durable Session Persistence**: Images integrate with DSH Attachment and conversation lifecycle, preserved across reloads.
130
+ - 💾 **Workspace File Output**: By default each generated image is also written as a file into the current session workspace (`dsh-image-gen/` subfolder); the tool result carries the absolute file path. Can be disabled or re-pointed in settings.
130
131
  - ⚙️ **Native Web Settings**: Configure providers, keys, models, and endpoints directly in DSH settings.
131
132
 
132
133
  ---
package/README.md CHANGED
@@ -122,11 +122,12 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
122
122
  ## ✨ 主要能力
123
123
 
124
124
  - 💬 **对话中直接生图**:不需要切换到其他网站,也不需要手动复制 Prompt,直接告诉 Agent 你想画什么即可。
125
- - 🖼️ **历史生图画廊**:顶栏自带「画廊」Tab,自动汇总所有历史生成的图片,支持关键词搜索、厂商筛选与一键复制/下载。
125
+ - 🖼️ **历史生图画廊**:顶栏自带「画廊」Tab,自动汇总所有历史生成的图片,支持关键词搜索、厂商筛选、单张删除(带防误触确认与持久化墓碑)与一键复制/下载。
126
126
  - 🔍 **交互式图片工具**:支持点击全屏大图预览、一键复制图片到剪贴板、本地下载与新标签页打开。
127
127
  - 🎨 **多 Provider 支持**:支持 Google Gemini、OpenAI Images、OpenAI Compatible API、ByteDance Seedream / 火山方舟以及阿里云 DashScope(通义万相 / Qwen-Image)。Provider、模型和 Endpoint 均可在设置中自由定制。
128
128
  - 🔑 **BYOK (自带 Key)**:插件使用你自己的 API Key。API Key 通过 DeepSeek Harness 的 `credentials` 服务管理,采用写保护隔离,不需要写进项目源码或配置文件,前端不存明文。
129
129
  - 🖼️ **图片跟随会话保存**:生成结果会接入 DeepSeek Harness 的 Attachment / Conversation 体系,重新打开历史会话后,仍然可以看到之前生成的图片。
130
+ - 💾 **生成图片自动落盘到工作区**:默认在每次生成后把图片文件保存到当前会话工作区(`dsh-image-gen/` 子目录),工具结果会返回文件的绝对路径;可在设置中关闭或修改保存目录。
130
131
  - ⚙️ **原生设置界面**:Provider、API Key、模型和 Endpoint 都可以直接在 DSH Web 设置中修改,不需要手动编辑配置文件。
131
132
 
132
133
  ---
package/README.zh-CN.md CHANGED
@@ -122,11 +122,12 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
122
122
  ## ✨ 主要能力
123
123
 
124
124
  - 💬 **对话中直接生图**:不需要切换到其他网站,也不需要手动复制 Prompt,直接告诉 Agent 你想画什么即可。
125
- - 🖼️ **历史生图画廊**:顶栏自带「画廊」Tab,自动汇总所有历史生成的图片,支持关键词搜索、厂商筛选与一键复制/下载。
125
+ - 🖼️ **历史生图画廊**:顶栏自带「画廊」Tab,自动汇总所有历史生成的图片,支持关键词搜索、厂商筛选、单张删除(带防误触确认与持久化墓碑)与一键复制/下载。
126
126
  - 🔍 **交互式图片工具**:支持点击全屏大图预览、一键复制图片到剪贴板、本地下载与新标签页打开。
127
127
  - 🎨 **多 Provider 支持**:支持 Google Gemini、OpenAI Images、OpenAI Compatible API、ByteDance Seedream / 火山方舟以及阿里云 DashScope(通义万相 / Qwen-Image)。Provider、模型和 Endpoint 均可在设置中自由定制。
128
128
  - 🔑 **BYOK (自带 Key)**:插件使用你自己的 API Key。API Key 通过 DeepSeek Harness 的 `credentials` 服务管理,采用写保护隔离,不需要写进项目源码或配置文件,前端不存明文。
129
129
  - 🖼️ **图片跟随会话保存**:生成结果会接入 DeepSeek Harness 的 Attachment / Conversation 体系,重新打开历史会话后,仍然可以看到之前生成的图片。
130
+ - 💾 **生成图片自动落盘到工作区**:默认在每次生成后把图片文件保存到当前会话工作区(`dsh-image-gen/` 子目录),工具结果会返回文件的绝对路径;可在设置中关闭或修改保存目录。
130
131
  - ⚙️ **原生设置界面**:Provider、API Key、模型和 Endpoint 都可以直接在 DSH Web 设置中修改,不需要手动编辑配置文件。
131
132
 
132
133
  ---
package/lib/client.js CHANGED
@@ -32,9 +32,11 @@ window.__ModuleLoader__.load({
32
32
  //#endregion
33
33
  //#region lib/types/client/gallery-store.js
34
34
  const DB_NAME = "dsh_image_gen_db";
35
- const DB_VERSION = 1;
35
+ const DB_VERSION = 2;
36
36
  const STORE_NAME = "gallery_history";
37
+ const TOMBSTONE_STORE = "gallery_tombstones";
37
38
  let dbPromise = null;
39
+ let tombstonesCache = null;
38
40
  function getDB() {
39
41
  if (dbPromise) return dbPromise;
40
42
  dbPromise = new Promise((resolve, reject) => {
@@ -46,6 +48,7 @@ window.__ModuleLoader__.load({
46
48
  request.onupgradeneeded = (event) => {
47
49
  const db = event.target.result;
48
50
  if (!db.objectStoreNames.contains(STORE_NAME)) db.createObjectStore(STORE_NAME, { keyPath: "id" }).createIndex("createdAt", "createdAt", { unique: false });
51
+ if (!db.objectStoreNames.contains(TOMBSTONE_STORE)) db.createObjectStore(TOMBSTONE_STORE, { keyPath: "id" });
49
52
  };
50
53
  request.onsuccess = () => {
51
54
  resolve(request.result);
@@ -56,6 +59,30 @@ window.__ModuleLoader__.load({
56
59
  });
57
60
  return dbPromise;
58
61
  }
62
+ async function loadTombstones(db) {
63
+ if (tombstonesCache) return tombstonesCache;
64
+ return new Promise((resolve) => {
65
+ if (!db.objectStoreNames.contains(TOMBSTONE_STORE)) {
66
+ tombstonesCache = /* @__PURE__ */ new Set();
67
+ resolve(tombstonesCache);
68
+ return;
69
+ }
70
+ try {
71
+ const req = db.transaction(TOMBSTONE_STORE, "readonly").objectStore(TOMBSTONE_STORE).getAllKeys();
72
+ req.onsuccess = () => {
73
+ tombstonesCache = new Set(req.result.map(String));
74
+ resolve(tombstonesCache);
75
+ };
76
+ req.onerror = () => {
77
+ tombstonesCache = /* @__PURE__ */ new Set();
78
+ resolve(tombstonesCache);
79
+ };
80
+ } catch {
81
+ tombstonesCache = /* @__PURE__ */ new Set();
82
+ resolve(tombstonesCache);
83
+ }
84
+ });
85
+ }
59
86
  const listeners = /* @__PURE__ */ new Set();
60
87
  function notifyListeners() {
61
88
  for (const listener of listeners) try {
@@ -75,10 +102,12 @@ window.__ModuleLoader__.load({
75
102
  }
76
103
  /**
77
104
  * Save or update a gallery record by attachmentId.
105
+ * Skipped if the item was previously deleted (tombstoned).
78
106
  */
79
107
  async function saveGalleryItem(item) {
80
108
  try {
81
109
  const db = await getDB();
110
+ if ((await loadTombstones(db)).has(item.id)) return;
82
111
  const record = {
83
112
  ...item,
84
113
  createdAt: item.createdAt ?? Date.now()
@@ -116,6 +145,30 @@ window.__ModuleLoader__.load({
116
145
  return [];
117
146
  }
118
147
  }
148
+ /**
149
+ * Delete a single gallery record by ID and record a tombstone.
150
+ */
151
+ async function deleteGalleryItem(id) {
152
+ try {
153
+ const db = await getDB();
154
+ (await loadTombstones(db)).add(id);
155
+ await new Promise((resolve, reject) => {
156
+ const tx = db.transaction([STORE_NAME, TOMBSTONE_STORE], "readwrite");
157
+ const store = tx.objectStore(STORE_NAME);
158
+ const tombstoneStore = tx.objectStore(TOMBSTONE_STORE);
159
+ store.delete(id);
160
+ tombstoneStore.put({
161
+ id,
162
+ deletedAt: Date.now()
163
+ });
164
+ tx.oncomplete = () => resolve();
165
+ tx.onerror = () => reject(tx.error);
166
+ });
167
+ notifyListeners();
168
+ } catch (err) {
169
+ console.warn("[dsh-image-gen] Failed to delete gallery item from IndexedDB:", err);
170
+ }
171
+ }
119
172
  //#endregion
120
173
  //#region lib/types/client/gallery-view.js
121
174
  /**
@@ -130,6 +183,7 @@ window.__ModuleLoader__.load({
130
183
  filterGoogle: "Google Gemini",
131
184
  filterOpenAI: "OpenAI / 中转站",
132
185
  filterSeedream: "字节 Seedream",
186
+ filterDashScope: "阿里 DashScope",
133
187
  emptyTitle: "暂无生图记录",
134
188
  emptyDesc: "在对话中让 Agent 生图后,生成的图片会自动收录到这里。",
135
189
  noMatchTitle: "未找到匹配结果",
@@ -141,6 +195,9 @@ window.__ModuleLoader__.load({
141
195
  download: "下载图片",
142
196
  copyImg: "复制图片",
143
197
  copyPpt: "复制 Prompt",
198
+ delete: "从画廊删除",
199
+ confirmDelete: "确定要从画廊中删除这张图片吗?(不会影响原聊天记录)",
200
+ deleted: "已从画廊删除",
144
201
  model: "模型",
145
202
  prompt: "Prompt",
146
203
  close: "关闭 (Esc)"
@@ -152,6 +209,7 @@ window.__ModuleLoader__.load({
152
209
  filterGoogle: "Google Gemini",
153
210
  filterOpenAI: "OpenAI / Relay",
154
211
  filterSeedream: "ByteDance Seedream",
212
+ filterDashScope: "Aliyun DashScope",
155
213
  emptyTitle: "No images generated yet",
156
214
  emptyDesc: "Images generated during conversations will automatically appear here.",
157
215
  noMatchTitle: "No matching images",
@@ -163,6 +221,9 @@ window.__ModuleLoader__.load({
163
221
  download: "Download",
164
222
  copyImg: "Copy Image",
165
223
  copyPpt: "Copy Prompt",
224
+ delete: "Delete from gallery",
225
+ confirmDelete: "Are you sure you want to remove this image from the gallery? (Chat history will not be affected)",
226
+ deleted: "Deleted from gallery",
166
227
  model: "Model",
167
228
  prompt: "Prompt",
168
229
  close: "Close (Esc)"
@@ -312,6 +373,10 @@ window.__ModuleLoader__.load({
312
373
  (0, react_jsx_runtime.jsx)("option", {
313
374
  value: "seedream",
314
375
  children: t("filterSeedream")
376
+ }),
377
+ (0, react_jsx_runtime.jsx)("option", {
378
+ value: "dashscope",
379
+ children: t("filterDashScope")
315
380
  })
316
381
  ]
317
382
  })]
@@ -527,6 +592,45 @@ window.__ModuleLoader__.load({
527
592
  })
528
593
  ]
529
594
  }), (0, react_jsx_runtime.jsx)("span", { children: t("download") })]
595
+ }),
596
+ (0, react_jsx_runtime.jsxs)("button", {
597
+ type: "button",
598
+ className: "dsh-ig-lightbox-btn dsh-ig-lightbox-btn-danger",
599
+ title: t("delete"),
600
+ onClick: async () => {
601
+ if (!window.confirm(t("confirmDelete"))) return;
602
+ await deleteGalleryItem(previewItem.id);
603
+ setPreviewItem(null);
604
+ setPreviewUrl(null);
605
+ setPreviewBlob(null);
606
+ showToast(t("deleted"));
607
+ },
608
+ children: [(0, react_jsx_runtime.jsxs)("svg", {
609
+ width: "14",
610
+ height: "14",
611
+ viewBox: "0 0 24 24",
612
+ fill: "none",
613
+ stroke: "currentColor",
614
+ strokeWidth: "2",
615
+ strokeLinecap: "round",
616
+ strokeLinejoin: "round",
617
+ children: [
618
+ (0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
619
+ (0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
620
+ (0, react_jsx_runtime.jsx)("line", {
621
+ x1: "10",
622
+ y1: "11",
623
+ x2: "10",
624
+ y2: "17"
625
+ }),
626
+ (0, react_jsx_runtime.jsx)("line", {
627
+ x1: "14",
628
+ y1: "11",
629
+ x2: "14",
630
+ y2: "17"
631
+ })
632
+ ]
633
+ }), (0, react_jsx_runtime.jsx)("span", { children: t("delete") })]
530
634
  })
531
635
  ]
532
636
  })]
@@ -694,6 +798,43 @@ window.__ModuleLoader__.load({
694
798
  ry: "1"
695
799
  })]
696
800
  })
801
+ }),
802
+ (0, react_jsx_runtime.jsx)("button", {
803
+ type: "button",
804
+ className: "dsh-ig-tool-btn dsh-ig-tool-btn-danger",
805
+ title: t("delete"),
806
+ onClick: async (e) => {
807
+ e.stopPropagation();
808
+ if (!window.confirm(t("confirmDelete"))) return;
809
+ await deleteGalleryItem(item.id);
810
+ onToast(t("deleted"));
811
+ },
812
+ children: (0, react_jsx_runtime.jsxs)("svg", {
813
+ width: "14",
814
+ height: "14",
815
+ viewBox: "0 0 24 24",
816
+ fill: "none",
817
+ stroke: "currentColor",
818
+ strokeWidth: "2",
819
+ strokeLinecap: "round",
820
+ strokeLinejoin: "round",
821
+ children: [
822
+ (0, react_jsx_runtime.jsx)("polyline", { points: "3 6 5 6 21 6" }),
823
+ (0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }),
824
+ (0, react_jsx_runtime.jsx)("line", {
825
+ x1: "10",
826
+ y1: "11",
827
+ x2: "10",
828
+ y2: "17"
829
+ }),
830
+ (0, react_jsx_runtime.jsx)("line", {
831
+ x1: "14",
832
+ y1: "11",
833
+ x2: "14",
834
+ y2: "17"
835
+ })
836
+ ]
837
+ })
697
838
  })
698
839
  ]
699
840
  })
@@ -776,9 +917,14 @@ window.__ModuleLoader__.load({
776
917
  endpointHintSeedream: "火山方舟兼容的 /api/v3 地址。",
777
918
  endpointHintDashScope: "阿里云百炼 DashScope 官方接口地址。",
778
919
  model: "模型",
920
+ saveToWorkspace: "保存到工作区",
921
+ saveToWorkspaceHint: "每次生成后,把图片文件保存到当前会话工作区。",
922
+ folder: "工作区文件夹",
923
+ folderHint: "相对当前会话工作区的子目录;留空表示工作区根目录。",
779
924
  saving: "保存中…",
780
925
  save: "保存",
781
926
  saved: "已保存",
927
+ savedToPath: "已保存到",
782
928
  checkingKey: "正在检查 API Key…",
783
929
  keyConfigured: "已配置 API Key",
784
930
  keyNotConfigured: "尚未配置 API Key",
@@ -811,9 +957,14 @@ window.__ModuleLoader__.load({
811
957
  endpointHintSeedream: "Volcengine Ark compatible /api/v3 base URL.",
812
958
  endpointHintDashScope: "Official Aliyun DashScope endpoint.",
813
959
  model: "Model",
960
+ saveToWorkspace: "Save to workspace",
961
+ saveToWorkspaceHint: "Write each generated image as a file into the session workspace.",
962
+ folder: "Workspace folder",
963
+ folderHint: "Subdirectory of the session workspace; empty means the workspace root.",
814
964
  saving: "Saving…",
815
965
  save: "Save",
816
966
  saved: "Saved",
967
+ savedToPath: "Saved to",
817
968
  checkingKey: "Checking API Key…",
818
969
  keyConfigured: "API Key configured",
819
970
  keyNotConfigured: "API Key not configured",
@@ -849,6 +1000,9 @@ window.__ModuleLoader__.load({
849
1000
  .dsh-ig-btn-reset:hover{background:var(--dsw-alias-bg-layer-2,#edf0f3);border-color:var(--dsw-alias-label-dimmed,#9ca3af)}
850
1001
  .dsh-ig-hint,.dsh-ig-status{margin:0;color:var(--dsw-alias-label-tertiary,#7b818b);font-size:12px;line-height:1.4}
851
1002
  .dsh-ig-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}
1003
+ .dsh-ig-check-row{display:flex;align-items:center;gap:8px;cursor:pointer}
1004
+ .dsh-ig-check-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--dsw-alias-brand-primary,#4c78ff);margin:0}
1005
+ .dsh-ig-savedto{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#7b818b);word-break:break-all}
852
1006
  .dsh-ig-save{appearance:none;border:0;border-radius:8px;padding:6px 16px;background:var(--dsw-alias-label-primary,#111827);color:var(--dsw-alias-bg-layer-3,#fff);font:inherit;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
853
1007
  .dsh-ig-save:disabled{opacity:.4;cursor:default}
854
1008
 
@@ -857,8 +1011,9 @@ window.__ModuleLoader__.load({
857
1011
  .dsh-ig-container{position:relative;display:inline-block;width:fit-content;max-width:100%;justify-self:start;border-radius:12px;overflow:hidden;line-height:0}
858
1012
  .dsh-ig-container:hover .dsh-ig-toolbar{opacity:1;pointer-events:auto}
859
1013
  .dsh-ig-toolbar{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:8px;background:rgba(0,0,0,0.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);opacity:0;pointer-events:none;transition:opacity .18s ease;z-index:10;line-height:1}
860
- .dsh-ig-tool-btn{appearance:none;border:0;background:transparent;color:#fff;padding:5px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s}
1014
+ .dsh-ig-tool-btn{appearance:none;border:0;background:transparent;color:#fff;padding:5px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:background .15s,color .15s}
861
1015
  .dsh-ig-tool-btn:hover{background:rgba(255,255,255,0.25)}
1016
+ .dsh-ig-tool-btn-danger:hover{background:rgba(239,68,68,0.75)!important;color:#fff!important}
862
1017
  .dsh-ig-toast{position:absolute;top:100%;left:0;margin-top:5px;padding:3px 8px;border-radius:6px;background:rgba(0,0,0,0.85);color:#fff;font-size:11px;white-space:nowrap;pointer-events:none;z-index:20}
863
1018
  .dsh-ig-image{display:block;max-width:100%;max-height:520px;border-radius:12px;background:#f2f3f5;cursor:pointer}
864
1019
  @keyframes dsh-ig-fade{from{opacity:0}to{opacity:1}}
@@ -911,8 +1066,10 @@ window.__ModuleLoader__.load({
911
1066
  .dsh-ig-lightbox-bottombar{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);max-width:min(90vw,640px);background:rgba(20,22,26,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.15);border-radius:14px;padding:10px 16px;display:flex;flex-direction:column;gap:8px;color:#fff;box-shadow:0 16px 40px rgba(0,0,0,0.5);cursor:default}
912
1067
  .dsh-ig-lightbox-prompt-text{font-size:13px;line-height:1.4;color:rgba(255,255,255,0.92);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
913
1068
  .dsh-ig-lightbox-actions{display:flex;align-items:center;gap:8px;justify-content:flex-end;border-top:1px solid rgba(255,255,255,0.1);padding-top:8px}
914
- .dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s}
1069
+ .dsh-ig-lightbox-btn{appearance:none;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.08);color:#fff;border-radius:6px;padding:5px 10px;font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:background .15s,border-color .15s,color .15s}
915
1070
  .dsh-ig-lightbox-btn:hover{background:rgba(255,255,255,0.22)}
1071
+ .dsh-ig-lightbox-btn-danger{border-color:rgba(239,68,68,0.4);color:#fca5a5}
1072
+ .dsh-ig-lightbox-btn-danger:hover{background:rgba(239,68,68,0.35)!important;color:#fff!important;border-color:rgba(239,68,68,0.7)!important}
916
1073
  .dsh-ig-gallery-page-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);color:#fff;padding:6px 14px;border-radius:8px;font-size:13px;z-index:99999;animation:dsh-ig-fade .15s}
917
1074
 
918
1075
  /* Hide floating chat composer when gallery page is active */
@@ -973,6 +1130,8 @@ window.__ModuleLoader__.load({
973
1130
  const [provider, setProvider] = (0, react.useState)("google");
974
1131
  const [model, setModel] = (0, react.useState)("");
975
1132
  const [baseURL, setBaseURL] = (0, react.useState)("");
1133
+ const [saveToWorkspace, setSaveToWorkspace] = (0, react.useState)(true);
1134
+ const [workspaceFolder, setWorkspaceFolder] = (0, react.useState)("dsh-image-gen");
976
1135
  const [key, setKey] = (0, react.useState)("");
977
1136
  const [configured, setConfigured] = (0, react.useState)();
978
1137
  const [saving, setSaving] = (0, react.useState)(false);
@@ -1002,6 +1161,8 @@ window.__ModuleLoader__.load({
1002
1161
  setProvider(next);
1003
1162
  setModel(modelOf(next, value));
1004
1163
  setBaseURL(baseURLOf(next, value));
1164
+ setSaveToWorkspace(value?.saveToWorkspace ?? true);
1165
+ setWorkspaceFolder(value?.workspaceFolder ?? "dsh-image-gen");
1005
1166
  }, [snapshot]);
1006
1167
  (0, react.useEffect)(() => {
1007
1168
  let active = true;
@@ -1022,6 +1183,8 @@ window.__ModuleLoader__.load({
1022
1183
  await props.scope.set("provider", provider);
1023
1184
  await props.scope.set(provider === "google" ? "googleModel" : provider === "openai" ? "openaiModel" : provider === "seedream" ? "seedreamModel" : "dashscopeModel", model);
1024
1185
  await props.scope.set(provider === "google" ? "googleEndpoint" : provider === "openai" ? "openaiBaseURL" : provider === "seedream" ? "seedreamBaseURL" : "dashscopeEndpoint", baseURL);
1186
+ await props.scope.set("saveToWorkspace", saveToWorkspace);
1187
+ await props.scope.set("workspaceFolder", workspaceFolder.trim());
1025
1188
  if (key.trim().length > 0) {
1026
1189
  const response = await props.credentials.set({
1027
1190
  ref: KEY_REF[provider],
@@ -1190,6 +1353,46 @@ window.__ModuleLoader__.load({
1190
1353
  required: true
1191
1354
  })]
1192
1355
  }),
1356
+ (0, react_jsx_runtime.jsxs)("div", {
1357
+ className: "dsh-ig-field",
1358
+ children: [(0, react_jsx_runtime.jsxs)("label", {
1359
+ className: "dsh-ig-check-row",
1360
+ children: [(0, react_jsx_runtime.jsx)("input", {
1361
+ type: "checkbox",
1362
+ checked: saveToWorkspace,
1363
+ onChange: (event) => {
1364
+ setSaveToWorkspace(event.target.checked);
1365
+ }
1366
+ }), (0, react_jsx_runtime.jsx)("span", {
1367
+ className: "dsh-ig-label",
1368
+ children: t("saveToWorkspace")
1369
+ })]
1370
+ }), (0, react_jsx_runtime.jsx)("span", {
1371
+ className: "dsh-ig-hint",
1372
+ children: t("saveToWorkspaceHint")
1373
+ })]
1374
+ }),
1375
+ saveToWorkspace ? (0, react_jsx_runtime.jsxs)("label", {
1376
+ className: "dsh-ig-field",
1377
+ children: [
1378
+ (0, react_jsx_runtime.jsx)("span", {
1379
+ className: "dsh-ig-label",
1380
+ children: t("folder")
1381
+ }),
1382
+ (0, react_jsx_runtime.jsx)("input", {
1383
+ className: "dsh-ig-input",
1384
+ value: workspaceFolder,
1385
+ onChange: (event) => {
1386
+ setWorkspaceFolder(event.target.value);
1387
+ },
1388
+ placeholder: "dsh-image-gen"
1389
+ }),
1390
+ (0, react_jsx_runtime.jsx)("span", {
1391
+ className: "dsh-ig-hint",
1392
+ children: t("folderHint")
1393
+ })
1394
+ ]
1395
+ }) : null,
1193
1396
  (0, react_jsx_runtime.jsxs)("div", {
1194
1397
  className: "dsh-ig-actions",
1195
1398
  children: [(0, react_jsx_runtime.jsx)("p", {
@@ -1210,6 +1413,7 @@ window.__ModuleLoader__.load({
1210
1413
  /** Render the durable attachment referenced by a completed image tool call. */
1211
1414
  function GeneratedImageCard(props) {
1212
1415
  const attachment = imageRef(props.block);
1416
+ const savedTo = imageSavedTo(props.block);
1213
1417
  const [url, setUrl] = (0, react.useState)();
1214
1418
  const [blob, setBlob] = (0, react.useState)();
1215
1419
  const [error, setError] = (0, react.useState)();
@@ -1313,6 +1517,14 @@ window.__ModuleLoader__.load({
1313
1517
  className: "dsh-ig-result-title",
1314
1518
  children: t("generatedTitle")
1315
1519
  }),
1520
+ savedTo !== void 0 ? (0, react_jsx_runtime.jsxs)("div", {
1521
+ className: "dsh-ig-savedto",
1522
+ children: [
1523
+ t("savedToPath"),
1524
+ ": ",
1525
+ savedTo
1526
+ ]
1527
+ }) : null,
1316
1528
  error !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
1317
1529
  className: "dsh-ig-error",
1318
1530
  children: error
@@ -1451,6 +1663,12 @@ window.__ModuleLoader__.load({
1451
1663
  const image = block.resultView.content?.find((item) => item.type === "image");
1452
1664
  return image?.type === "image" ? image.attachment : void 0;
1453
1665
  }
1666
+ /** The workspace file path a completed image call saved, when the result meta carries one. */
1667
+ function imageSavedTo(block) {
1668
+ if (!("kind" in block)) return void 0;
1669
+ const meta = block.meta ?? block.resultView?.meta;
1670
+ return typeof meta?.savedTo === "string" ? meta.savedTo : void 0;
1671
+ }
1454
1672
  //#endregion
1455
1673
  exports.GeneratedImageCard = GeneratedImageCard;
1456
1674
  exports.ImageGenerationSettingsCard = ImageGenerationSettingsCard;