@anionex/dsh-vision-toolkit 0.1.5

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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +383 -0
  4. package/README.zh.md +383 -0
  5. package/assets/dsh-conversation-artifact.png +0 -0
  6. package/assets/dsh-conversation-image-qa-top.png +0 -0
  7. package/assets/dsh-conversation-image-qa.png +0 -0
  8. package/assets/dsh-conversation-pixel-diff.png +0 -0
  9. package/assets/dsh-conversation-screenshot-debugging-top.png +0 -0
  10. package/assets/dsh-conversation-screenshot-debugging.png +0 -0
  11. package/assets/dsh-conversation-tool-call.png +0 -0
  12. package/assets/dsh-conversation-vision-trace.png +0 -0
  13. package/assets/hero.png +0 -0
  14. package/assets/social-preview.png +0 -0
  15. package/assets/upstream/README.md +16 -0
  16. package/assets/upstream/image-qa.webp +0 -0
  17. package/assets/upstream/infographic-reference.webp +0 -0
  18. package/assets/upstream/infographic-result.webp +0 -0
  19. package/assets/upstream/screenshot-debugging.webp +0 -0
  20. package/assets/upstream/ui-result.webp +0 -0
  21. package/assets/upstream/ui-sketch.webp +0 -0
  22. package/assets/vision-settings.png +0 -0
  23. package/cordis.patch.yml +6 -0
  24. package/docs/assets/vision-settings.png +0 -0
  25. package/docs/requirements-traceability/README.i18n.yaml +6 -0
  26. package/docs/requirements-traceability/README.md +75 -0
  27. package/docs/requirements-traceability/README.zh.md +75 -0
  28. package/examples/ui-restoration/README.i18n.yaml +6 -0
  29. package/examples/ui-restoration/README.md +70 -0
  30. package/examples/ui-restoration/README.zh.md +70 -0
  31. package/examples/ui-restoration/assets/final-heatmap.png +0 -0
  32. package/examples/ui-restoration/assets/final-report.json +83 -0
  33. package/examples/ui-restoration/assets/implementation.png +0 -0
  34. package/examples/ui-restoration/assets/initial-heatmap.png +0 -0
  35. package/examples/ui-restoration/assets/initial-report.json +83 -0
  36. package/examples/ui-restoration/assets/initial.png +0 -0
  37. package/examples/ui-restoration/assets/metrics.json +12 -0
  38. package/examples/ui-restoration/assets/reference.png +0 -0
  39. package/examples/ui-restoration/implementation.html +94 -0
  40. package/examples/ui-restoration/initial.html +57 -0
  41. package/lib/artifact-access.js +369 -0
  42. package/lib/artifact-access.js.map +1 -0
  43. package/lib/artifacts.js +56 -0
  44. package/lib/artifacts.js.map +1 -0
  45. package/lib/client.js +952 -0
  46. package/lib/client.js.map +1 -0
  47. package/lib/config.js +117 -0
  48. package/lib/config.js.map +1 -0
  49. package/lib/errors.js +56 -0
  50. package/lib/errors.js.map +1 -0
  51. package/lib/exposure.js +213 -0
  52. package/lib/exposure.js.map +1 -0
  53. package/lib/index.js +97 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/paste-images.js +199 -0
  56. package/lib/paste-images.js.map +1 -0
  57. package/lib/paths.js +325 -0
  58. package/lib/paths.js.map +1 -0
  59. package/lib/runtime-install.js +601 -0
  60. package/lib/runtime-install.js.map +1 -0
  61. package/lib/runtime-manager.js +126 -0
  62. package/lib/runtime-manager.js.map +1 -0
  63. package/lib/runtime.js +1344 -0
  64. package/lib/runtime.js.map +1 -0
  65. package/lib/skill.js +139 -0
  66. package/lib/skill.js.map +1 -0
  67. package/lib/tools.js +528 -0
  68. package/lib/tools.js.map +1 -0
  69. package/lib/types/artifact-access.d.ts +61 -0
  70. package/lib/types/artifact-access.d.ts.map +1 -0
  71. package/lib/types/artifacts.d.ts +42 -0
  72. package/lib/types/artifacts.d.ts.map +1 -0
  73. package/lib/types/client/index.d.ts +179 -0
  74. package/lib/types/client/index.d.ts.map +1 -0
  75. package/lib/types/client/paste-images.d.ts +57 -0
  76. package/lib/types/client/paste-images.d.ts.map +1 -0
  77. package/lib/types/config.d.ts +73 -0
  78. package/lib/types/config.d.ts.map +1 -0
  79. package/lib/types/errors.d.ts +35 -0
  80. package/lib/types/errors.d.ts.map +1 -0
  81. package/lib/types/exposure.d.ts +40 -0
  82. package/lib/types/exposure.d.ts.map +1 -0
  83. package/lib/types/index.d.ts +18 -0
  84. package/lib/types/index.d.ts.map +1 -0
  85. package/lib/types/paste-images.d.ts +21 -0
  86. package/lib/types/paste-images.d.ts.map +1 -0
  87. package/lib/types/paths.d.ts +107 -0
  88. package/lib/types/paths.d.ts.map +1 -0
  89. package/lib/types/runtime-install.d.ts +49 -0
  90. package/lib/types/runtime-install.d.ts.map +1 -0
  91. package/lib/types/runtime-manager.d.ts +60 -0
  92. package/lib/types/runtime-manager.d.ts.map +1 -0
  93. package/lib/types/runtime.d.ts +389 -0
  94. package/lib/types/runtime.d.ts.map +1 -0
  95. package/lib/types/skill.d.ts +15 -0
  96. package/lib/types/skill.d.ts.map +1 -0
  97. package/lib/types/tools.d.ts +22 -0
  98. package/lib/types/tools.d.ts.map +1 -0
  99. package/lib/types/upstream.d.ts +207 -0
  100. package/lib/types/upstream.d.ts.map +1 -0
  101. package/lib/types/version.d.ts +15 -0
  102. package/lib/types/version.d.ts.map +1 -0
  103. package/lib/types/web-request.d.ts +4 -0
  104. package/lib/types/web-request.d.ts.map +1 -0
  105. package/lib/types/web.d.ts +74 -0
  106. package/lib/types/web.d.ts.map +1 -0
  107. package/lib/upstream.js +675 -0
  108. package/lib/upstream.js.map +1 -0
  109. package/lib/version.js +18 -0
  110. package/lib/version.js.map +1 -0
  111. package/lib/web-request.js +20 -0
  112. package/lib/web-request.js.map +1 -0
  113. package/lib/web.js +244 -0
  114. package/lib/web.js.map +1 -0
  115. package/package.json +139 -0
  116. package/runtime/requirements.lock +3 -0
  117. package/src/artifact-access.ts +386 -0
  118. package/src/artifacts.ts +85 -0
  119. package/src/client/index.tsx +866 -0
  120. package/src/client/paste-images.tsx +426 -0
  121. package/src/config.ts +177 -0
  122. package/src/errors.ts +62 -0
  123. package/src/exposure.ts +227 -0
  124. package/src/index.ts +122 -0
  125. package/src/paste-images.ts +234 -0
  126. package/src/paths.ts +348 -0
  127. package/src/runtime-install.ts +723 -0
  128. package/src/runtime-manager.ts +166 -0
  129. package/src/runtime.ts +1783 -0
  130. package/src/skill.ts +143 -0
  131. package/src/tools.ts +668 -0
  132. package/src/upstream.ts +861 -0
  133. package/src/version.ts +37 -0
  134. package/src/web-request.ts +17 -0
  135. package/src/web.ts +329 -0
  136. package/vendor/agent-vision-toolkit/CHANGELOG.md +16 -0
  137. package/vendor/agent-vision-toolkit/LICENSE +21 -0
  138. package/vendor/agent-vision-toolkit/README.md +399 -0
  139. package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +89 -0
  140. package/vendor/agent-vision-toolkit/bin/crop +90 -0
  141. package/vendor/agent-vision-toolkit/bin/detect +13 -0
  142. package/vendor/agent-vision-toolkit/bin/glance +93 -0
  143. package/vendor/agent-vision-toolkit/bin/ground +13 -0
  144. package/vendor/agent-vision-toolkit/bin/trace +129 -0
  145. package/vendor/agent-vision-toolkit/detect.py +56 -0
  146. package/vendor/agent-vision-toolkit/ground.py +216 -0
  147. package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/dominant_colors.py +224 -0
  148. package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/extract_fg.py +278 -0
  149. package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/html_shot.py +108 -0
  150. package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/long_screenshot_ocr.py +1245 -0
  151. package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/pixel_diff.py +88 -0
  152. package/vendor/agent-vision-toolkit/vision_client.py +156 -0
package/lib/client.js ADDED
@@ -0,0 +1,952 @@
1
+ window.__ModuleLoader__.load({ id: "@anionex/dsh-vision-toolkit", factory: (require) => {
2
+ var __modules = Object.create(null); var __cache = Object.create(null);
3
+ __modules["./index.js"] = function(module, exports, require, __load_) {
4
+ "use strict";
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.inject = exports.VisionSettingsController = void 0;
7
+ exports.decodeVisionResult = decodeVisionResult;
8
+ exports.apply = apply;
9
+ const jsx_runtime_1 = require("react/jsx-runtime");
10
+ /**
11
+ * DSH Vision Toolkit browser plugin: dedicated Tool cards plus the Settings,
12
+ * health, connection-test, and safe Artifact preview experience.
13
+ */
14
+ const react_1 = require("react");
15
+ const dsh_client_ui_primitives_1 = require("@deepseek-ai/dsh-client-ui-primitives");
16
+ const paste_images_tsx_1 = __load_("./paste-images.js");
17
+ const NS = 'vision-toolkit';
18
+ const SETTINGS_ROUTE = '/_dsh/vision-toolkit/settings';
19
+ const PRESENTATION_META_KEY = '$dshVisionToolkit';
20
+ const en = {
21
+ nav: 'Vision',
22
+ settingsTitle: 'Vision Toolkit',
23
+ settingsIntro: 'Configure the pinned visual engineering runtime, its external vision endpoint, and local safety limits.',
24
+ externalNotice: 'Remote tools send the selected image bytes to the configured external vision API. Local crop, trace, pixel diff, palette, foreground extraction, and HTML rendering do not upload images.',
25
+ provider: 'Vision service',
26
+ baseUrl: 'Base URL',
27
+ credential: 'Credential reference',
28
+ model: 'Model',
29
+ language: 'Output language',
30
+ limits: 'Limits',
31
+ timeout: 'Request timeout (ms)',
32
+ maxBytes: 'Maximum image bytes',
33
+ maxPixels: 'Maximum image pixels',
34
+ concurrency: 'Concurrent calls per session',
35
+ runtime: 'Runtime',
36
+ runtimeMode: 'Runtime mode',
37
+ toolkitPath: 'Pinned checkout path',
38
+ python: 'Python override',
39
+ allowedDirs: 'Additional allowed directories',
40
+ allowedDirsHint: 'One path per line. The session workspace is always allowed.',
41
+ save: 'Save and apply',
42
+ saving: 'Validating runtime…',
43
+ reload: 'Reload',
44
+ saved: 'Settings validated and applied.',
45
+ readOnly: 'The active Settings provider is read-only.',
46
+ configured: 'Configured',
47
+ missing: 'Missing',
48
+ source: 'Source',
49
+ health: 'Health',
50
+ runHealth: 'Run health check',
51
+ testConnection: 'Test connection',
52
+ testing: 'Checking…',
53
+ connectionHint: 'Connection testing explicitly sends the configured credential to GET /models. It uploads no image and creates no completion.',
54
+ pluginVersion: 'Plugin',
55
+ upstreamVersion: 'Upstream',
56
+ activeGeneration: 'Runtime generation',
57
+ runtimeUnavailable: 'Runtime unavailable',
58
+ runtimeCandidateRejected: 'Last runtime candidate was rejected; the active generation remains available.',
59
+ retry: 'Retry',
60
+ open: 'Open file',
61
+ download: 'Download',
62
+ previewUnavailable: 'HTTP preview is unavailable in this host; use Open file.',
63
+ running: 'Running…',
64
+ failed: 'Failed',
65
+ matches: 'matches',
66
+ elements: 'elements',
67
+ dimensions: 'Dimensions',
68
+ coordinates: 'Coordinates',
69
+ artifact: 'Artifact',
70
+ artifacts: 'Artifacts',
71
+ difference: 'Overall difference',
72
+ worstRegions: 'Worst regions',
73
+ colors: 'Dominant colors',
74
+ noResult: 'Structured result unavailable; inspect the raw Tool result.',
75
+ healthy: 'Healthy',
76
+ degraded: 'Needs attention',
77
+ notTested: 'Not tested',
78
+ };
79
+ const zh = {
80
+ nav: '视觉工具',
81
+ settingsTitle: 'Vision Toolkit',
82
+ settingsIntro: '配置固定版本的视觉工程运行时、外部视觉服务与本地安全限制。',
83
+ externalNotice: '远程工具会把选中的图片字节发送到已配置的外部视觉 API。本地裁剪、几何恢复、像素对比、调色板、前景提取和 HTML 渲染不会上传图片。',
84
+ provider: '视觉服务',
85
+ baseUrl: '服务地址',
86
+ credential: 'Credential 引用',
87
+ model: '模型',
88
+ language: '输出语言',
89
+ limits: '限制',
90
+ timeout: '请求超时(毫秒)',
91
+ maxBytes: '最大图片字节数',
92
+ maxPixels: '最大图片像素数',
93
+ concurrency: '每个 Session 并发数',
94
+ runtime: '运行时',
95
+ runtimeMode: '运行模式',
96
+ toolkitPath: '固定上游 checkout 路径',
97
+ python: 'Python 覆盖',
98
+ allowedDirs: '额外允许目录',
99
+ allowedDirsHint: '每行一个路径;Session 工作区始终允许。',
100
+ save: '保存并应用',
101
+ saving: '正在验证运行时…',
102
+ reload: '重新加载',
103
+ saved: '设置已验证并生效。',
104
+ readOnly: '当前 Settings 提供方为只读。',
105
+ configured: '已配置',
106
+ missing: '缺失',
107
+ source: '来源',
108
+ health: '健康检查',
109
+ runHealth: '运行健康检查',
110
+ testConnection: '测试连接',
111
+ testing: '检查中…',
112
+ connectionHint: '连接测试会显式把已配置 Credential 发送到 GET /models;不会上传图片,也不会创建 completion。',
113
+ pluginVersion: '插件',
114
+ upstreamVersion: '上游',
115
+ activeGeneration: '运行时世代',
116
+ runtimeUnavailable: '运行时不可用',
117
+ runtimeCandidateRejected: '最近的运行时候选已被拒绝;当前世代仍可用。',
118
+ retry: '重试',
119
+ open: '打开文件',
120
+ download: '下载',
121
+ previewUnavailable: '当前宿主不提供 HTTP 预览,请使用“打开文件”。',
122
+ running: '运行中…',
123
+ failed: '失败',
124
+ matches: '个匹配',
125
+ elements: '个元素',
126
+ dimensions: '尺寸',
127
+ coordinates: '坐标',
128
+ artifact: '交付文件',
129
+ artifacts: '交付文件',
130
+ difference: '总体差异',
131
+ worstRegions: '差异最大区域',
132
+ colors: '主色',
133
+ noResult: '结构化结果不可用,请检查原始 Tool 结果。',
134
+ healthy: '健康',
135
+ degraded: '需要处理',
136
+ notTested: '未测试',
137
+ };
138
+ function isRecord(value) {
139
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
140
+ }
141
+ function textOfContent(block) {
142
+ if (!('kind' in block))
143
+ return '';
144
+ return block.content
145
+ .filter((entry) => entry.type === 'text')
146
+ .map(entry => entry.text)
147
+ .join('\n');
148
+ }
149
+ /** Decode canonical presentation metadata with a JSON-text fallback. */
150
+ function decodeVisionResult(block) {
151
+ if (!('kind' in block) || block.isError)
152
+ return undefined;
153
+ if (isRecord(block.meta))
154
+ return block.meta;
155
+ const text = textOfContent(block).trim();
156
+ if (text.length === 0)
157
+ return undefined;
158
+ try {
159
+ const parsed = JSON.parse(text);
160
+ return isRecord(parsed) ? parsed : undefined;
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ }
166
+ function accessMap(value) {
167
+ const map = new Map();
168
+ if (value === undefined)
169
+ return map;
170
+ const envelope = value[PRESENTATION_META_KEY];
171
+ if (!isRecord(envelope) || envelope.schemaVersion !== 1 || !Array.isArray(envelope.artifacts))
172
+ return map;
173
+ for (const entry of envelope.artifacts) {
174
+ if (!isRecord(entry) || typeof entry.path !== 'string' || typeof entry.previewUrl !== 'string' || typeof entry.downloadUrl !== 'string')
175
+ continue;
176
+ map.set(entry.path, entry);
177
+ }
178
+ return map;
179
+ }
180
+ function artifactFrom(value) {
181
+ if (!isRecord(value))
182
+ return undefined;
183
+ if (typeof value.path !== 'string'
184
+ || typeof value.filename !== 'string'
185
+ || typeof value.mimeType !== 'string'
186
+ || (value.kind !== 'image' && value.kind !== 'svg' && value.kind !== 'markdown' && value.kind !== 'json')
187
+ || typeof value.description !== 'string'
188
+ || typeof value.sourceTool !== 'string'
189
+ || (value.previewIntent !== 'image' && value.previewIntent !== 'svg' && value.previewIntent !== 'text' && value.previewIntent !== 'download')
190
+ || typeof value.bytes !== 'number')
191
+ return undefined;
192
+ return value;
193
+ }
194
+ function collectArtifacts(value, found = new Map(), depth = 0) {
195
+ if (depth > 16)
196
+ return [...found.values()];
197
+ const artifact = artifactFrom(value);
198
+ if (artifact !== undefined) {
199
+ found.set(artifact.path, artifact);
200
+ return [...found.values()];
201
+ }
202
+ if (Array.isArray(value)) {
203
+ for (const entry of value)
204
+ collectArtifacts(entry, found, depth + 1);
205
+ }
206
+ else if (isRecord(value)) {
207
+ for (const entry of Object.values(value))
208
+ collectArtifacts(entry, found, depth + 1);
209
+ }
210
+ return [...found.values()];
211
+ }
212
+ function numberOf(value) {
213
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
214
+ }
215
+ function stringOf(value) {
216
+ return typeof value === 'string' ? value : undefined;
217
+ }
218
+ function formatBytes(bytes) {
219
+ if (bytes < 1024)
220
+ return `${bytes} B`;
221
+ if (bytes < 1024 * 1024)
222
+ return `${(bytes / 1024).toFixed(1)} KB`;
223
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
224
+ }
225
+ function boxText(value) {
226
+ if (!isRecord(value))
227
+ return '—';
228
+ const parts = ['x1', 'y1', 'x2', 'y2'].map(key => numberOf(value[key]));
229
+ return parts.every(part => part !== undefined) ? parts.join(', ') : '—';
230
+ }
231
+ function statusText(block, t) {
232
+ if (!('kind' in block))
233
+ return t('running');
234
+ if (block.isError)
235
+ return textOfContent(block).split('\n')[0] || t('failed');
236
+ return undefined;
237
+ }
238
+ function VisionIcon({ kind = 'scan' }) {
239
+ const path = kind === 'target'
240
+ ? 'M8 2v2m0 8v2M2 8h2m8 0h2M5 5h6v6H5z'
241
+ : kind === 'layers'
242
+ ? 'm3 6 5-3 5 3-5 3-5-3Zm0 3 5 3 5-3M3 12l5 3 5-3'
243
+ : kind === 'shape'
244
+ ? 'M3 12 6 4l7-1-1 7-9 2Zm3-8 6 6'
245
+ : kind === 'diff'
246
+ ? 'M3 3h4v4H3V3Zm6 6h4v4H9V9Zm0-6h4M3 11h4'
247
+ : kind === 'palette'
248
+ ? 'M8 2a6 6 0 1 0 0 12h1.2a1.3 1.3 0 0 0 0-2.6H8a1.5 1.5 0 0 1 0-3h3.5A2.5 2.5 0 0 0 14 5.9C13.2 3.6 10.9 2 8 2Z'
249
+ : 'M3 5V3h2M11 3h2v2M13 11v2h-2M5 13H3v-2M5 8h6';
250
+ return ((0, jsx_runtime_1.jsx)("svg", { viewBox: "0 0 16 16", width: "16", height: "16", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.35", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)("path", { d: path }) }));
251
+ }
252
+ function ToolShell({ block, title, summary, icon, children, t, }) {
253
+ const [open, setOpen] = (0, react_1.useState)(true);
254
+ const status = statusText(block, t);
255
+ const expandable = children !== undefined && children !== null;
256
+ return ((0, jsx_runtime_1.jsxs)("section", { className: "dvt-tool", "data-state": !('kind' in block) ? 'running' : block.isError ? 'error' : 'success', children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "dvt-tool-head", onClick: () => { if (expandable)
257
+ setOpen(value => !value); }, "aria-expanded": expandable ? open : undefined, children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-tool-icon", children: icon }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-tool-title", children: title }), summary !== undefined && summary.length > 0 ? (0, jsx_runtime_1.jsx)("span", { className: "dvt-tool-sep", "aria-hidden": "true", children: "\u00B7" }) : null, summary !== undefined ? (0, jsx_runtime_1.jsx)("span", { className: "dvt-tool-summary", children: summary }) : null, status !== undefined ? (0, jsx_runtime_1.jsx)("span", { className: "dvt-tool-status", children: status }) : null, expandable ? (0, jsx_runtime_1.jsx)("span", { className: "dvt-chevron", "data-open": open || undefined, children: "\u2304" }) : null] }), expandable && open ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-tool-body", children: children }) : null] }));
258
+ }
259
+ function ArtifactActions({ artifact, grant, openFile, t }) {
260
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", onClick: () => { openFile(artifact.path); }, children: t('open') }), grant === undefined ? null : (0, jsx_runtime_1.jsx)("a", { className: "dvt-download", href: grant.downloadUrl, download: artifact.filename, children: t('download') })] }));
261
+ }
262
+ function ArtifactPreview({ artifact, grant, openFile, t }) {
263
+ const canPreview = grant !== undefined && (artifact.kind === 'image' || artifact.kind === 'svg');
264
+ return ((0, jsx_runtime_1.jsxs)("article", { className: "dvt-artifact", children: [canPreview
265
+ ? artifact.kind === 'svg'
266
+ ? (0, jsx_runtime_1.jsx)("iframe", { className: "dvt-preview dvt-svg", sandbox: "", src: grant.previewUrl, title: artifact.description })
267
+ : (0, jsx_runtime_1.jsx)("img", { className: "dvt-preview", src: grant.previewUrl, alt: artifact.description, loading: "lazy" })
268
+ : null, (0, jsx_runtime_1.jsxs)("div", { className: "dvt-artifact-meta", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: artifact.filename }), (0, jsx_runtime_1.jsx)("span", { children: artifact.description }), (0, jsx_runtime_1.jsxs)("small", { children: [artifact.mimeType, " \u00B7 ", formatBytes(artifact.bytes)] })] }), (0, jsx_runtime_1.jsx)(ArtifactActions, { artifact: artifact, grant: grant, openFile: openFile, t: t })] }), !canPreview && grant === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('previewUnavailable') }) : null] }));
269
+ }
270
+ function GroundView({ block, openFile, t = key => en[key] }) {
271
+ const value = decodeVisionResult(block);
272
+ const matches = Array.isArray(value?.matches) ? value.matches.filter(isRecord) : [];
273
+ const target = stringOf(value?.target) ?? 'Ground';
274
+ const width = numberOf(value?.imageWidth);
275
+ const height = numberOf(value?.imageHeight);
276
+ const preview = artifactFrom(value?.preview);
277
+ const grants = accessMap(value);
278
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: "Ground", summary: matches.length > 0 ? `${target} · ${matches.length} ${t('matches')}` : target, icon: (0, jsx_runtime_1.jsx)(VisionIcon, { kind: "target" }), t: t, children: value === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-stack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-metrics", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('dimensions') }), (0, jsx_runtime_1.jsxs)("strong", { children: [width ?? '—', " \u00D7 ", height ?? '—'] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('coordinates') }), (0, jsx_runtime_1.jsx)("strong", { children: matches[0] === undefined ? '—' : boxText(matches[0].box) })] })] }), matches.length > 1 ? ((0, jsx_runtime_1.jsx)("ol", { className: "dvt-list", children: matches.map((match, index) => (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("span", { children: stringOf(match.label) ?? `#${index + 1}` }), (0, jsx_runtime_1.jsx)("code", { children: boxText(match.box) })] }, index)) })) : null, preview === undefined ? null : (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: preview, grant: grants.get(preview.path), openFile: openFile, t: t })] })) }));
279
+ }
280
+ function DetectView({ block, openFile, t = key => en[key] }) {
281
+ const value = decodeVisionResult(block);
282
+ const elements = Array.isArray(value?.elements) ? value.elements.filter(isRecord) : [];
283
+ const width = numberOf(value?.imageWidth);
284
+ const height = numberOf(value?.imageHeight);
285
+ const preview = artifactFrom(value?.preview);
286
+ const grants = accessMap(value);
287
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: "Detect", summary: `${elements.length} ${t('elements')}`, icon: (0, jsx_runtime_1.jsx)(VisionIcon, { kind: "layers" }), t: t, children: value === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-stack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-metrics", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('dimensions') }), (0, jsx_runtime_1.jsxs)("strong", { children: [width ?? '—', " \u00D7 ", height ?? '—'] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('elements') }), (0, jsx_runtime_1.jsx)("strong", { children: elements.length })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "dvt-table-wrap", children: (0, jsx_runtime_1.jsxs)("table", { className: "dvt-table", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "#" }), (0, jsx_runtime_1.jsx)("th", { children: "Label" }), (0, jsx_runtime_1.jsx)("th", { children: t('coordinates') })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: elements.map((element, index) => (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: numberOf(element.index) ?? index + 1 }), (0, jsx_runtime_1.jsx)("td", { children: stringOf(element.label) ?? '—' }), (0, jsx_runtime_1.jsx)("td", { children: (0, jsx_runtime_1.jsx)("code", { children: boxText(element.box) }) })] }, index)) })] }) }), preview === undefined ? null : (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: preview, grant: grants.get(preview.path), openFile: openFile, t: t })] })) }));
288
+ }
289
+ function TraceView({ block, openFile, t = key => en[key] }) {
290
+ const value = decodeVisionResult(block);
291
+ const artifact = artifactFrom(value?.artifact);
292
+ const geometry = isRecord(value?.geometry) ? value.geometry : undefined;
293
+ const summary = geometry === undefined ? undefined : `${numberOf(geometry.pathCount) ?? 0} paths · ${formatBytes(numberOf(geometry.bytes) ?? 0)}`;
294
+ const grants = accessMap(value);
295
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: "Trace SVG", summary: summary, icon: (0, jsx_runtime_1.jsx)(VisionIcon, { kind: "shape" }), t: t, children: artifact === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: artifact, grant: grants.get(artifact.path), openFile: openFile, t: t }) }));
296
+ }
297
+ function PixelDiffView({ block, openFile, t = key => en[key] }) {
298
+ const value = decodeVisionResult(block);
299
+ const pct = numberOf(value?.overallDifferencePct);
300
+ const regions = Array.isArray(value?.worstRegions) ? value.worstRegions.filter(isRecord) : [];
301
+ const heatmap = artifactFrom(value?.heatmap);
302
+ const report = artifactFrom(value?.report);
303
+ const grants = accessMap(value);
304
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: "Pixel Diff", summary: pct === undefined ? undefined : `${pct.toFixed(3)}%`, icon: (0, jsx_runtime_1.jsx)(VisionIcon, { kind: "diff" }), t: t, children: value === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-stack", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-diff-score", children: [(0, jsx_runtime_1.jsx)("span", { children: t('difference') }), (0, jsx_runtime_1.jsxs)("strong", { children: [pct?.toFixed(4) ?? '—', "%"] }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("i", { style: { width: `${Math.min(100, Math.max(0, pct ?? 0))}%` } }) })] }), regions.length === 0 ? null : (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { children: t('worstRegions') }), (0, jsx_runtime_1.jsx)("ol", { className: "dvt-list", children: regions.map((region, index) => (0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsxs)("span", { children: [(numberOf(region.differencePct) ?? 0).toFixed(3), "%"] }), (0, jsx_runtime_1.jsx)("code", { children: boxText(region.box) })] }, index)) })] }), heatmap === undefined ? null : (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: heatmap, grant: grants.get(heatmap.path), openFile: openFile, t: t }), report === undefined ? null : (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: report, grant: grants.get(report.path), openFile: openFile, t: t })] })) }));
305
+ }
306
+ function ArtifactView({ block, openFile, toolName, t = key => en[key] }) {
307
+ const value = decodeVisionResult(block);
308
+ const artifacts = collectArtifacts(value);
309
+ const grants = accessMap(value);
310
+ const title = toolName === 'vision_crop' ? 'Crop'
311
+ : toolName === 'vision_long_screenshot_ocr' ? 'Long OCR'
312
+ : toolName === 'vision_extract_foreground' ? 'Extract Foreground'
313
+ : toolName === 'vision_html_screenshot' ? 'HTML Screenshot'
314
+ : 'Vision Artifact';
315
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: title, summary: artifacts.length > 0 ? `${artifacts.length} ${t('artifacts')}` : undefined, icon: (0, jsx_runtime_1.jsx)(VisionIcon, {}), t: t, children: artifacts.length === 0 ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-stack", children: artifacts.map(artifact => (0, jsx_runtime_1.jsx)(ArtifactPreview, { artifact: artifact, grant: grants.get(artifact.path), openFile: openFile, t: t }, artifact.path)) }) }));
316
+ }
317
+ function PaletteView({ block, t = key => en[key] }) {
318
+ const value = decodeVisionResult(block);
319
+ const analysis = isRecord(value?.analysis) ? value.analysis : undefined;
320
+ const colors = Array.isArray(analysis?.colors) ? analysis.colors.filter(isRecord) : [];
321
+ return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: "Dominant Colors", summary: `${colors.length} ${t('colors')}`, icon: (0, jsx_runtime_1.jsx)(VisionIcon, { kind: "palette" }), t: t, children: colors.length === 0 ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('noResult') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-palette", children: colors.map((color, index) => {
322
+ const hex = stringOf(color.color) ?? '#000000';
323
+ const share = numberOf(color.sharePct);
324
+ return (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("i", { style: { background: hex } }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("strong", { children: hex }), (0, jsx_runtime_1.jsx)("small", { children: share === undefined ? '' : `${share.toFixed(2)}%` })] })] }, `${hex}-${index}`);
325
+ }) }) }));
326
+ }
327
+ async function apiRequest(init) {
328
+ const response = await fetch(SETTINGS_ROUTE, { credentials: 'same-origin', ...init });
329
+ const body = await response.json();
330
+ if (!response.ok || !body.ok) {
331
+ const failure = body;
332
+ throw new Error(failure.error?.message ?? `Vision Toolkit request failed with HTTP ${response.status}`);
333
+ }
334
+ return body.value;
335
+ }
336
+ /** Small external store shared by the Settings route and pushed invalidations. */
337
+ class VisionSettingsController {
338
+ state = { status: 'idle' };
339
+ listeners = new Set();
340
+ generation = 0;
341
+ subscribe = (listener) => {
342
+ this.listeners.add(listener);
343
+ return () => { this.listeners.delete(listener); };
344
+ };
345
+ snapshot = () => this.state;
346
+ set(next) {
347
+ this.state = next;
348
+ for (const listener of this.listeners)
349
+ listener();
350
+ }
351
+ async load() {
352
+ const generation = ++this.generation;
353
+ this.set({ ...this.state, status: 'loading', error: undefined, message: undefined });
354
+ try {
355
+ const snapshot = await apiRequest();
356
+ if (generation !== this.generation)
357
+ return;
358
+ this.set({ status: 'ready', snapshot, health: this.state.health });
359
+ }
360
+ catch (error) {
361
+ if (generation !== this.generation)
362
+ return;
363
+ this.set({ ...this.state, status: 'error', error: error instanceof Error ? error.message : String(error) });
364
+ }
365
+ }
366
+ refreshIfLoaded() {
367
+ if (this.state.status === 'idle' || this.state.action === 'save')
368
+ return;
369
+ void this.load();
370
+ }
371
+ async save(value, expectedRevision) {
372
+ this.set({ ...this.state, action: 'save', error: undefined, message: undefined });
373
+ try {
374
+ const snapshot = await apiRequest({
375
+ method: 'POST',
376
+ headers: { 'Content-Type': 'application/json' },
377
+ body: JSON.stringify({ action: 'save', expectedRevision, value }),
378
+ });
379
+ this.set({ status: 'ready', snapshot, health: this.state.health, message: 'saved' });
380
+ }
381
+ catch (error) {
382
+ this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
383
+ }
384
+ }
385
+ async runHealth(testConnection) {
386
+ this.set({ ...this.state, action: testConnection ? 'connection' : 'health', error: undefined, message: undefined });
387
+ try {
388
+ const health = await apiRequest({
389
+ method: 'POST',
390
+ headers: { 'Content-Type': 'application/json' },
391
+ body: JSON.stringify({ action: 'health', testConnection }),
392
+ });
393
+ this.set({ ...this.state, action: undefined, health });
394
+ }
395
+ catch (error) {
396
+ this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
397
+ }
398
+ }
399
+ }
400
+ exports.VisionSettingsController = VisionSettingsController;
401
+ function draftOf(value) {
402
+ return {
403
+ baseUrl: value.provider?.baseUrl ?? 'https://api.inferera.com/v1',
404
+ credential: value.provider?.credential ?? 'VISION_API_KEY',
405
+ model: value.provider?.model ?? 'gemini-3.6-flash',
406
+ language: value.language ?? 'zh',
407
+ timeoutMs: String(value.timeoutMs ?? 60000),
408
+ maxImageBytes: String(value.maxImageBytes ?? 10485760),
409
+ maxImagePixels: String(value.maxImagePixels ?? 40000000),
410
+ concurrency: String(value.concurrency ?? 4),
411
+ runtimeMode: value.runtime?.mode ?? 'managed',
412
+ toolkitPath: value.runtime?.agentVisionToolkitPath ?? '',
413
+ python: value.runtime?.python ?? '',
414
+ allowedDirs: (value.allowedDirs ?? []).join('\n'),
415
+ };
416
+ }
417
+ function positiveInteger(raw, label) {
418
+ const value = Number(raw);
419
+ if (!Number.isSafeInteger(value) || value <= 0)
420
+ throw new Error(`${label} must be a positive integer`);
421
+ return value;
422
+ }
423
+ function valueOf(draft) {
424
+ return {
425
+ provider: { baseUrl: draft.baseUrl.trim(), credential: draft.credential.trim(), model: draft.model.trim() },
426
+ language: draft.language,
427
+ timeoutMs: positiveInteger(draft.timeoutMs, 'timeoutMs'),
428
+ maxImageBytes: positiveInteger(draft.maxImageBytes, 'maxImageBytes'),
429
+ maxImagePixels: positiveInteger(draft.maxImagePixels, 'maxImagePixels'),
430
+ concurrency: positiveInteger(draft.concurrency, 'concurrency'),
431
+ runtime: {
432
+ mode: draft.runtimeMode,
433
+ ...(draft.runtimeMode === 'external' ? { agentVisionToolkitPath: draft.toolkitPath.trim() } : {}),
434
+ ...(draft.python.trim().length === 0 ? {} : { python: draft.python.trim() }),
435
+ },
436
+ allowedDirs: draft.allowedDirs.split(/\r?\n/).map(entry => entry.trim()).filter(Boolean),
437
+ };
438
+ }
439
+ function Field({ label, children, hint }) {
440
+ return (0, jsx_runtime_1.jsxs)("label", { className: "dvt-field", children: [(0, jsx_runtime_1.jsx)("span", { children: label }), children, hint === undefined ? null : (0, jsx_runtime_1.jsx)("small", { children: hint })] });
441
+ }
442
+ function SettingsSection({ controller, t }) {
443
+ if (controller === undefined || t === undefined)
444
+ return null;
445
+ return (0, jsx_runtime_1.jsx)(LoadedSettings, { controller: controller, t: t });
446
+ }
447
+ function LoadedSettings({ controller, t }) {
448
+ const state = (0, react_1.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
449
+ const snapshot = state.snapshot;
450
+ const [draft, setDraft] = (0, react_1.useState)(undefined);
451
+ const [draftError, setDraftError] = (0, react_1.useState)(undefined);
452
+ (0, react_1.useEffect)(() => { if (state.status === 'idle')
453
+ void controller.load(); }, [controller, state.status]);
454
+ (0, react_1.useEffect)(() => {
455
+ if (snapshot !== undefined)
456
+ setDraft(draftOf(snapshot.settings.value));
457
+ }, [snapshot]);
458
+ if (state.status === 'idle' || (state.status === 'loading' && snapshot === undefined)) {
459
+ return (0, jsx_runtime_1.jsx)("div", { className: "dvt-settings", children: (0, jsx_runtime_1.jsx)("div", { className: "dvt-loading", children: t('testing') }) });
460
+ }
461
+ if (snapshot === undefined || draft === undefined) {
462
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: state.error ?? t('runtimeUnavailable') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: () => { void controller.load(); }, children: t('retry') })] });
463
+ }
464
+ const update = (key, value) => setDraft(current => current === undefined ? current : { ...current, [key]: value });
465
+ const save = () => {
466
+ try {
467
+ setDraftError(undefined);
468
+ void controller.save(valueOf(draft), snapshot.settings.revision);
469
+ }
470
+ catch (error) {
471
+ setDraftError(error instanceof Error ? error.message : String(error));
472
+ }
473
+ };
474
+ const busy = state.action !== undefined;
475
+ const runtimeErrorTitle = snapshot.runtime.ready ? t('runtimeCandidateRejected') : t('runtimeUnavailable');
476
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsxs)("header", { className: "dvt-settings-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-kicker", children: "DSH native plugin" }), (0, jsx_runtime_1.jsx)("h2", { children: t('settingsTitle') }), (0, jsx_runtime_1.jsx)("p", { children: t('settingsIntro') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-release", children: [(0, jsx_runtime_1.jsxs)("span", { children: [t('pluginVersion'), " ", (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.pluginVersion })] }), (0, jsx_runtime_1.jsxs)("span", { children: [t('upstreamVersion'), " ", (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.upstreamVersion })] }), (0, jsx_runtime_1.jsxs)("span", { children: [t('activeGeneration'), " ", (0, jsx_runtime_1.jsx)("strong", { children: snapshot.runtime.generation })] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert notice", children: t('externalNotice') }), !snapshot.writable ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('readOnly') }) : null, draftError === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: draftError }), state.error === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: state.error }), state.message === 'saved' ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('saved') }) : null, snapshot.runtime.lastError === undefined ? null : (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert error", children: [(0, jsx_runtime_1.jsx)("strong", { children: runtimeErrorTitle }), (0, jsx_runtime_1.jsx)("span", { children: snapshot.runtime.lastError })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsx)("h3", { children: t('provider') }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.credential.configured ? 'ok' : 'error'}`, children: snapshot.credential.configured ? t('configured') : t('missing') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.baseUrl, onChange: (event) => { update('baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.model, onChange: (event) => { update('model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('credential'), hint: snapshot.credential.source === undefined ? undefined : `${t('source')}: ${snapshot.credential.source}`, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.credential, onChange: (event) => { update('credential', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('language'), children: (0, jsx_runtime_1.jsxs)("select", { value: draft.language, onChange: (event) => { update('language', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "zh", children: "\u4E2D\u6587" }), (0, jsx_runtime_1.jsx)("option", { value: "en", children: "English" })] }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsx)("h3", { children: t('limits') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('timeout'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.timeoutMs, onChange: (event) => { update('timeoutMs', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImageBytes, onChange: (event) => { update('maxImageBytes', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxPixels'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImagePixels, onChange: (event) => { update('maxImagePixels', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('concurrency'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.concurrency, onChange: (event) => { update('concurrency', event.target.value); } }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsx)("h3", { children: t('runtime') }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.runtime.ready ? 'ok' : 'error'}`, children: snapshot.runtime.ready ? snapshot.runtime.upstream?.source ?? 'ready' : t('runtimeUnavailable') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('runtimeMode'), children: (0, jsx_runtime_1.jsxs)("select", { value: draft.runtimeMode, onChange: (event) => { update('runtimeMode', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "managed", children: "managed" }), (0, jsx_runtime_1.jsx)("option", { value: "external", children: "external" })] }) }), draft.runtimeMode === 'external' ? (0, jsx_runtime_1.jsx)(Field, { label: t('toolkitPath'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.toolkitPath, onChange: (event) => { update('toolkitPath', event.target.value); } }) }) : null, (0, jsx_runtime_1.jsx)(Field, { label: t('python'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { placeholder: "python3", value: draft.python, onChange: (event) => { update('python', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('allowedDirs'), hint: t('allowedDirsHint'), children: (0, jsx_runtime_1.jsx)("textarea", { rows: 3, value: draft.allowedDirs, onChange: (event) => { update('allowedDirs', event.target.value); } }) })] }), snapshot.runtime.upstream === undefined ? null : (0, jsx_runtime_1.jsxs)("div", { className: "dvt-runtime-facts", children: [(0, jsx_runtime_1.jsx)("code", { children: snapshot.runtime.upstream.path }), (0, jsx_runtime_1.jsxs)("code", { children: [snapshot.runtime.upstream.python, " \u00B7 ", snapshot.runtime.upstream.pythonVersion] }), (0, jsx_runtime_1.jsx)("code", { children: snapshot.runtime.upstream.runtimeHome })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-save-row", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: !snapshot.writable || busy, onClick: save, children: state.action === 'save' ? t('saving') : t('save') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy, onClick: () => { void controller.load(); }, children: t('reload') })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('health') }), (0, jsx_runtime_1.jsx)("p", { children: t('connectionHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth(false); }, children: state.action === 'health' ? t('testing') : t('runHealth') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth(true); }, children: state.action === 'connection' ? t('testing') : t('testConnection') })] })] }), state.health === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('notTested') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-health-grid", children: Object.entries(state.health.checks).map(([name, check]) => (0, jsx_runtime_1.jsxs)("div", { "data-status": check.status, children: [(0, jsx_runtime_1.jsx)("span", { children: name }), (0, jsx_runtime_1.jsx)("strong", { children: check.status }), (0, jsx_runtime_1.jsx)("p", { children: check.detail })] }, name)) })] })] }));
477
+ }
478
+ const CSS = `
479
+ .dvt-tool{margin:4px 0;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;box-shadow:var(--dsw-shadow-lv1)}
480
+ .dvt-tool-head{width:100%;min-height:38px;display:flex;align-items:center;gap:7px;padding:8px 10px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer;font:inherit}.dvt-tool-head:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.dvt-tool-icon{width:20px;height:20px;display:grid;place-items:center;border-radius:6px;color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);flex:none}.dvt-tool-title{font-size:12px;font-weight:650;white-space:nowrap}.dvt-tool-sep{opacity:.35}.dvt-tool-summary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--dsw-alias-label-secondary)}.dvt-tool-status{margin-left:auto;font-size:11px;color:var(--dsw-alias-label-secondary);max-width:45%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvt-tool[data-state=error] .dvt-tool-status{color:var(--dsw-alias-state-error-primary)}.dvt-chevron{margin-left:auto;transition:transform .16s ease;opacity:.55}.dvt-chevron[data-open=true]{transform:rotate(180deg)}.dvt-tool-body{padding:0 10px 10px}.dvt-stack{display:grid;gap:10px}.dvt-muted{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}
481
+ .dvt-metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.dvt-metrics>div,.dvt-diff-score{padding:10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);display:grid;gap:4px}.dvt-metrics span,.dvt-diff-score span{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--dsw-alias-label-secondary)}.dvt-metrics strong,.dvt-diff-score strong{font-size:13px}.dvt-list{list-style:none;margin:0;padding:0;display:grid;gap:4px;max-height:160px;overflow:auto}.dvt-list li{display:flex;justify-content:space-between;gap:12px;padding:6px 8px;border-radius:7px;background:var(--dsw-alias-bg-layer-2);font-size:11px}.dvt-list code{color:var(--dsw-alias-state-business-primary)}.dvt-table-wrap{max-height:220px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:9px}.dvt-table{width:100%;border-collapse:collapse;font-size:11px}.dvt-table th,.dvt-table td{padding:7px 8px;text-align:left;border-bottom:1px solid var(--dsw-alias-border-l1)}.dvt-table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-2);font-size:10px;text-transform:uppercase;letter-spacing:.05em}.dvt-table tr:last-child td{border-bottom:0}
482
+ .dvt-artifact{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-1)}.dvt-preview{display:block;width:100%;max-height:360px;object-fit:contain;background:repeating-conic-gradient(var(--dsw-alias-bg-module-platform) 0 25%,var(--dsw-alias-bg-layer-1) 0 50%) 50%/18px 18px;border:0}.dvt-svg{height:280px}.dvt-artifact-meta{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 10px}.dvt-artifact-meta>div:first-child{min-width:0;display:grid;gap:2px}.dvt-artifact-meta strong{font-size:12px;overflow:hidden;text-overflow:ellipsis}.dvt-artifact-meta span,.dvt-artifact-meta small{font-size:10px;color:var(--dsw-alias-label-secondary)}.dvt-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.dvt-download{display:inline-flex;align-items:center;height:28px;padding:0 12px;border-radius:999px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);text-decoration:none;font-size:12px;font-weight:600}.dvt-download:hover{background:var(--dsw-alias-button-primary-hover)}.dvt-download:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.dvt-artifact>.dvt-muted{padding:0 10px 10px}.dvt-diff-score>div{height:5px;border-radius:99px;background:var(--dsw-alias-border-l2);overflow:hidden}.dvt-diff-score i{display:block;height:100%;min-width:2px;background:linear-gradient(90deg,var(--dsw-alias-state-warn-primary),var(--dsw-alias-state-error-primary));border-radius:99px}.dvt-tool h4{font-size:11px;margin:0 0 6px}.dvt-palette{display:grid;grid-template-columns:repeat(auto-fit,minmax(112px,1fr));gap:7px}.dvt-palette>div{display:flex;align-items:center;gap:8px;padding:7px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px}.dvt-palette i{width:28px;height:28px;border-radius:7px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2)}.dvt-palette span{display:grid}.dvt-palette strong{font-size:11px}.dvt-palette small{font-size:10px;color:var(--dsw-alias-label-secondary)}
483
+ .dvt-settings{display:grid;gap:14px;max-width:900px;padding:8px 2px 32px;color:var(--dsw-alias-label-primary)}.dvt-settings-header{display:flex;justify-content:space-between;gap:20px;align-items:flex-start;padding:8px 2px}.dvt-settings-header h2{font-size:25px;letter-spacing:-.025em;margin:3px 0 6px}.dvt-settings-header p{max-width:620px;margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.55}.dvt-kicker{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--dsw-alias-state-business-primary);font-weight:700}.dvt-release{display:grid;gap:4px;min-width:170px;padding:9px 11px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);font-size:10px;color:var(--dsw-alias-label-secondary)}.dvt-release span{display:flex;justify-content:space-between;gap:12px}.dvt-release strong{color:var(--dsw-alias-label-primary)}.dvt-alert{padding:10px 12px;border-radius:10px;font-size:12px;line-height:1.5;display:grid;gap:3px}.dvt-alert.notice{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}.dvt-alert.warning{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}.dvt-alert.error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}.dvt-alert.success{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent);color:var(--dsw-alias-state-success-primary)}.dvt-panel{display:grid;gap:12px;padding:15px;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv1)}.dvt-panel-title{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dvt-panel-title h3{font-size:14px;margin:0}.dvt-panel-title p{font-size:11px;line-height:1.45;color:var(--dsw-alias-label-secondary);margin:4px 0 0;max-width:620px}.dvt-badge{font-size:10px;padding:3px 7px;border-radius:999px;font-weight:650}.dvt-badge.ok{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary)}.dvt-badge.error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}.dvt-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.dvt-field{display:grid;gap:6px;align-content:start}.dvt-field>span{font-size:11px;font-weight:600}.dvt-field>small{font-size:10px;color:var(--dsw-alias-label-secondary);line-height:1.4}.dvt-field select,.dvt-field textarea{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:8px 10px}.dvt-field select{height:36px}.dvt-field textarea{resize:vertical;min-height:76px}.dvt-runtime-facts{display:grid;gap:4px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);overflow:auto}.dvt-runtime-facts code{font-size:10px;white-space:nowrap;color:var(--dsw-alias-label-secondary)}.dvt-save-row{display:flex;gap:8px;padding:2px 0}
484
+ .dvt-health-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.dvt-health-grid>div{padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border-left:3px solid var(--dsw-alias-border-l4)}.dvt-health-grid>div[data-status=ok]{border-left-color:var(--dsw-alias-state-success-primary)}.dvt-health-grid>div[data-status=warning],.dvt-health-grid>div[data-status=not_tested]{border-left-color:var(--dsw-alias-state-warn-primary)}.dvt-health-grid>div[data-status=error]{border-left-color:var(--dsw-alias-state-error-primary)}.dvt-health-grid span{font-size:10px;text-transform:capitalize}.dvt-health-grid strong{float:right;font-size:9px;text-transform:uppercase;color:var(--dsw-alias-label-secondary)}.dvt-health-grid p{clear:both;margin:5px 0 0;font-size:10px;line-height:1.4;color:var(--dsw-alias-label-secondary)}.dvt-loading{padding:24px;border-radius:12px;background:var(--dsw-alias-bg-layer-2);font-size:12px;color:var(--dsw-alias-label-secondary)}
485
+ .dvt-paste-dock{box-sizing:border-box;width:calc(100% - 32px);max-width:var(--dsh-composer-card-max-width,960px);margin:0 auto;display:flex;flex-wrap:wrap;gap:6px;padding:0 2px 6px}.dvt-paste-chip{max-width:100%;height:32px;box-sizing:border-box;display:flex;align-items:center;gap:7px;padding:0 6px 0 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-specific-tip);font-size:12px}.dvt-paste-chip[data-status=copying]{border-color:var(--dsw-alias-state-business-primary)}.dvt-paste-chip[data-status=error]{border-color:var(--dsw-alias-state-error-primary)}.dvt-paste-name{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvt-paste-detail{color:var(--dsw-alias-label-caption);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvt-paste-chip[data-status=error] .dvt-paste-detail{color:var(--dsw-alias-state-error-primary)}.dvt-paste-chip button{width:20px;height:20px;display:grid;place-items:center;border:0;border-radius:50%;padding:0;background:transparent;color:var(--dsw-alias-label-caption);font:inherit;font-size:16px;cursor:pointer}.dvt-paste-chip button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dvt-paste-chip button:disabled{opacity:.4;cursor:default}
486
+ @media(max-width:720px){.dvt-settings-header{display:grid}.dvt-release{width:auto}.dvt-form-grid{grid-template-columns:1fr}.dvt-metrics{grid-template-columns:1fr}.dvt-artifact-meta{align-items:flex-start;flex-direction:column}.dvt-panel-title{flex-direction:column}}
487
+ `;
488
+ function installStyles() {
489
+ const id = '@anionex/dsh-vision-toolkit/client';
490
+ const existing = document.querySelector(`style[data-plugin-css="${id}"]`);
491
+ if (existing !== null)
492
+ return () => { };
493
+ const style = document.createElement('style');
494
+ style.dataset.plugin = '@anionex/dsh-vision-toolkit';
495
+ style.dataset.pluginCss = id;
496
+ style.textContent = CSS;
497
+ document.head.appendChild(style);
498
+ return () => { style.remove(); };
499
+ }
500
+ /** Required client services. The pasted-image codec attaches to either trigger-service generation after load. */
501
+ exports.inject = ['slots', 'locale', 'remote', 'conversation', 'sessions'];
502
+ /** Register dedicated Tool views and the Vision Settings section. */
503
+ function apply(ctx) {
504
+ ctx.effect(installStyles, 'dsh-vision-toolkit: styles');
505
+ ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-vision-toolkit: locale');
506
+ (0, paste_images_tsx_1.installPasteImages)(ctx);
507
+ const t = ctx.locale.bind(NS);
508
+ const injected = () => ({ t });
509
+ const entries = [
510
+ ['vision_ground', GroundView],
511
+ ['vision_detect', DetectView],
512
+ ['vision_trace', TraceView],
513
+ ['vision_pixel_diff', PixelDiffView],
514
+ ['vision_crop', ArtifactView],
515
+ ['vision_long_screenshot_ocr', ArtifactView],
516
+ ['vision_extract_foreground', ArtifactView],
517
+ ['vision_html_screenshot', ArtifactView],
518
+ ['vision_dominant_colors', PaletteView],
519
+ ];
520
+ ctx.slots.inject('tool.call.toolview', function* () {
521
+ for (const [key, component] of entries) {
522
+ yield ctx.slots.register({ name: 'tool.call.toolview', key, inject: injected }, component);
523
+ }
524
+ });
525
+ const controller = new VisionSettingsController();
526
+ ctx.effect(() => {
527
+ const refreshSettings = (namespace) => {
528
+ if (namespace === 'vision-toolkit')
529
+ controller.refreshIfLoaded();
530
+ };
531
+ const refreshCredential = (ref) => {
532
+ const current = controller.snapshot().snapshot;
533
+ if (current?.credential.ref === ref)
534
+ controller.refreshIfLoaded();
535
+ };
536
+ const legacyRemote = ctx.remote;
537
+ const currentEvents = ctx;
538
+ const disposers = typeof legacyRemote.$on === 'function'
539
+ ? [
540
+ legacyRemote.$on('settings/document-updated', refreshSettings),
541
+ legacyRemote.$on('credentials/updated', refreshCredential),
542
+ ]
543
+ : [
544
+ currentEvents.on('settings/changed', (namespace) => {
545
+ refreshSettings(namespace);
546
+ }),
547
+ currentEvents.on('credentials/changed', (ref) => {
548
+ refreshCredential(ref);
549
+ }),
550
+ ];
551
+ disposers.push(ctx.on('connection/reset', () => { controller.refreshIfLoaded(); }));
552
+ return () => { for (const dispose of disposers)
553
+ dispose(); };
554
+ }, 'dsh-vision-toolkit: Settings invalidations');
555
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
556
+ name: 'settings.section',
557
+ id: 'vision-toolkit',
558
+ order: 30,
559
+ label: () => t('nav'),
560
+ inject: () => ({ controller, t }),
561
+ }, SettingsSection));
562
+ }
563
+ };
564
+ __modules["./paste-images.js"] = function(module, exports, require, __load_) {
565
+ "use strict";
566
+ Object.defineProperty(exports, "__esModule", { value: true });
567
+ exports.PasteImageController = exports.PASTE_IMAGES_ROUTE = void 0;
568
+ exports.PasteImageDock = PasteImageDock;
569
+ exports.installPasteImages = installPasteImages;
570
+ const jsx_runtime_1 = require("react/jsx-runtime");
571
+ /** Clipboard-only multi-image input for DSH Web. */
572
+ const react_1 = require("react");
573
+ const SOURCE = 'vision-toolkit-pasted-image';
574
+ exports.PASTE_IMAGES_ROUTE = '/_dsh/vision-toolkit/paste-images';
575
+ const MAX_IMAGES = 20;
576
+ const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
577
+ const MAX_BATCH_BYTES = 80 * 1024 * 1024;
578
+ const CORDIS_ORIGINAL = Symbol.for('cordis.original');
579
+ function registryIdentity(registry) {
580
+ let current = registry;
581
+ while (true) {
582
+ const original = current[CORDIS_ORIGINAL];
583
+ if ((typeof original !== 'object' && typeof original !== 'function') || original === null || original === current) {
584
+ return current;
585
+ }
586
+ current = original;
587
+ }
588
+ }
589
+ let fallbackId = 0;
590
+ function id() {
591
+ if (typeof globalThis.crypto?.randomUUID === 'function')
592
+ return globalThis.crypto.randomUUID();
593
+ fallbackId += 1;
594
+ return `paste-${Date.now()}-${fallbackId}`;
595
+ }
596
+ function humanBytes(bytes) {
597
+ if (bytes < 1024)
598
+ return `${bytes} B`;
599
+ if (bytes < 1024 ** 2)
600
+ return `${(bytes / 1024).toFixed(1)} KB`;
601
+ return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
602
+ }
603
+ function message(error) {
604
+ return error instanceof Error ? error.message : String(error);
605
+ }
606
+ function imageFiles(data) {
607
+ if (data === null)
608
+ return [];
609
+ const itemFiles = Array.from(data.items)
610
+ .filter(item => item.kind === 'file')
611
+ .map(item => item.getAsFile())
612
+ .filter((file) => file !== null);
613
+ const candidates = itemFiles.length > 0 ? itemFiles : Array.from(data.files);
614
+ return candidates.filter(file => file.type.toLowerCase().startsWith('image/'));
615
+ }
616
+ function validateImages(files) {
617
+ if (files.length > MAX_IMAGES)
618
+ throw new Error(`Paste at most ${MAX_IMAGES} images at a time`);
619
+ let total = 0;
620
+ for (const file of files) {
621
+ if (!file.type.toLowerCase().startsWith('image/'))
622
+ throw new Error(`${file.name || 'clipboard item'} is not an image`);
623
+ if (file.size <= 0)
624
+ throw new Error(`${file.name || 'clipboard image'} is empty`);
625
+ if (file.size > MAX_IMAGE_BYTES)
626
+ throw new Error(`${file.name || 'clipboard image'} exceeds ${humanBytes(MAX_IMAGE_BYTES)}`);
627
+ total += file.size;
628
+ }
629
+ if (total > MAX_BATCH_BYTES)
630
+ throw new Error(`Pasted images exceed ${humanBytes(MAX_BATCH_BYTES)} in total`);
631
+ }
632
+ async function responseJson(response) {
633
+ const body = await response.json();
634
+ if (!response.ok || body.ok !== true)
635
+ throw new Error(body.error?.message ?? `Image copy failed (${response.status})`);
636
+ return body;
637
+ }
638
+ function pasteLabel(file, index) {
639
+ return file.name.trim() || `clipboard-image-${index + 1}`;
640
+ }
641
+ /** Owns browser File objects until DSH serializes the corresponding text references. */
642
+ class PasteImageController {
643
+ ctx;
644
+ records = new Map();
645
+ listeners = new Set();
646
+ revision = 0;
647
+ constructor(ctx) {
648
+ this.ctx = ctx;
649
+ }
650
+ subscribe = (listener) => {
651
+ this.listeners.add(listener);
652
+ return () => { this.listeners.delete(listener); };
653
+ };
654
+ snapshot = () => this.revision;
655
+ changed() {
656
+ this.revision += 1;
657
+ for (const listener of this.listeners)
658
+ listener();
659
+ }
660
+ source() {
661
+ return {
662
+ trigger: '@',
663
+ name: SOURCE,
664
+ order: 1000,
665
+ candidates: () => Promise.resolve([]),
666
+ onPick: () => undefined,
667
+ codec: {
668
+ clipboardText: ref => `[pasted image: ${this.records.get(ref)?.file.name ?? ref}]`,
669
+ serialize: (ref, signal) => this.serialize(ref, signal),
670
+ },
671
+ };
672
+ }
673
+ recordsFor(occurrences) {
674
+ return occurrences
675
+ .filter(occurrence => occurrence.source === SOURCE)
676
+ .map(occurrence => this.records.get(occurrence.ref))
677
+ .filter((record) => record !== undefined);
678
+ }
679
+ inputFor(sessionId) {
680
+ const actx = this.ctx.sessions.scope(sessionId);
681
+ if (actx === undefined)
682
+ throw new Error('Open a live session before pasting images');
683
+ return this.ctx.conversation.input.for(actx);
684
+ }
685
+ insertText(input, text, start, end = start) {
686
+ if (text === '')
687
+ return start;
688
+ const snapshot = input.state.getSnapshot();
689
+ input.setDraft(snapshot.draft.slice(0, start) + text + snapshot.draft.slice(end));
690
+ return start + text.length;
691
+ }
692
+ insertRecords(sessionId, input, files, cursor) {
693
+ const batch = { sessionId, records: [] };
694
+ const draftBeforeReferences = input.state.getSnapshot().draft;
695
+ try {
696
+ const before = input.state.getSnapshot().draft.slice(0, cursor);
697
+ if (before !== '' && !/\s$/u.test(before))
698
+ cursor = this.insertText(input, ' ', cursor);
699
+ for (const [index, file] of files.entries()) {
700
+ const ref = id();
701
+ const record = { ref, file, batch, status: 'ready' };
702
+ batch.records.push(record);
703
+ this.records.set(ref, record);
704
+ const snapshot = input.state.getSnapshot();
705
+ const accepted = input.insertReference({
706
+ source: SOURCE,
707
+ ref,
708
+ label: pasteLabel(file, index),
709
+ clipboardText: `[pasted image: ${pasteLabel(file, index)}]`,
710
+ }, { start: cursor, end: cursor, draftRev: snapshot.draftRev });
711
+ if (!accepted)
712
+ throw new Error('The composer changed before pasted images could be inserted');
713
+ cursor += 1;
714
+ const hasNext = index + 1 < files.length;
715
+ const suffix = input.state.getSnapshot().draft.slice(cursor);
716
+ if (hasNext || (suffix !== '' && !/^\s/u.test(suffix)))
717
+ cursor = this.insertText(input, ' ', cursor);
718
+ }
719
+ batch.unsubscribe = input.state.subscribe(() => {
720
+ const alive = new Set(input.state.getSnapshot().occurrences
721
+ .filter(occurrence => occurrence.source === SOURCE)
722
+ .map(occurrence => occurrence.ref));
723
+ let changed = false;
724
+ for (const record of batch.records) {
725
+ if (alive.has(record.ref) || record.batch.inflight !== undefined)
726
+ continue;
727
+ changed = this.records.delete(record.ref) || changed;
728
+ }
729
+ if (batch.records.every(record => !this.records.has(record.ref)) && batch.inflight === undefined) {
730
+ batch.unsubscribe?.();
731
+ batch.unsubscribe = undefined;
732
+ }
733
+ if (changed)
734
+ this.changed();
735
+ });
736
+ this.changed();
737
+ return cursor;
738
+ }
739
+ catch (error) {
740
+ input.setDraft(draftBeforeReferences);
741
+ for (const record of batch.records)
742
+ this.records.delete(record.ref);
743
+ throw error;
744
+ }
745
+ }
746
+ handlePaste(event) {
747
+ const files = imageFiles(event.clipboardData);
748
+ if (files.length === 0)
749
+ return false;
750
+ const target = event.target;
751
+ if (!(target instanceof HTMLTextAreaElement) || target.closest('[data-composer-card]') === null)
752
+ return false;
753
+ event.preventDefault();
754
+ event.stopPropagation();
755
+ event.stopImmediatePropagation();
756
+ const sessionId = this.ctx.sessions.list.getSnapshot().current;
757
+ if (sessionId === undefined)
758
+ return true;
759
+ const input = this.inputFor(sessionId);
760
+ const snapshot = input.state.getSnapshot();
761
+ if (snapshot.phase !== 'plain')
762
+ return true;
763
+ const start = Math.max(0, Math.min(target.selectionStart ?? snapshot.draft.length, snapshot.draft.length));
764
+ const end = Math.max(start, Math.min(target.selectionEnd ?? start, snapshot.draft.length));
765
+ const text = (event.clipboardData?.getData('text/plain') ?? '').replaceAll('\uFFFC', '');
766
+ try {
767
+ let cursor = this.insertText(input, text, start, end);
768
+ validateImages(files);
769
+ cursor = this.insertRecords(String(sessionId), input, files, cursor);
770
+ requestAnimationFrame(() => {
771
+ target.focus({ preventScroll: true });
772
+ target.setSelectionRange(cursor, cursor);
773
+ });
774
+ }
775
+ catch (error) {
776
+ input.notify('error', message(error));
777
+ }
778
+ return true;
779
+ }
780
+ remove(sessionId, occurrence) {
781
+ const record = this.records.get(occurrence.ref);
782
+ if (record?.batch.inflight !== undefined)
783
+ return;
784
+ const input = this.inputFor(sessionId);
785
+ const snapshot = input.state.getSnapshot();
786
+ if (snapshot.phase !== 'plain')
787
+ return;
788
+ const current = snapshot.occurrences.find(candidate => candidate.source === SOURCE
789
+ && candidate.occurrenceId === occurrence.occurrenceId
790
+ && candidate.ref === occurrence.ref);
791
+ if (current === undefined)
792
+ return;
793
+ const accepted = input.insertText('', {
794
+ start: current.offset,
795
+ end: current.offset + 1,
796
+ draftRev: snapshot.draftRev,
797
+ });
798
+ if (!accepted)
799
+ return;
800
+ this.records.delete(occurrence.ref);
801
+ this.changed();
802
+ }
803
+ async upload(batch, signal) {
804
+ if (batch.inflight !== undefined)
805
+ return batch.inflight;
806
+ const active = batch.records.filter(record => this.records.get(record.ref) === record);
807
+ if (active.length === 0)
808
+ throw new Error('Pasted images were removed before sending');
809
+ const pending = active.filter(record => record.absolutePath === undefined);
810
+ if (pending.length === 0)
811
+ return;
812
+ const task = (async () => {
813
+ for (const record of pending) {
814
+ record.status = 'copying';
815
+ record.error = undefined;
816
+ }
817
+ this.changed();
818
+ try {
819
+ const failures = await Promise.all(pending.map(async (record) => {
820
+ try {
821
+ if (signal.aborted)
822
+ throw signal.reason ?? new DOMException('Aborted', 'AbortError');
823
+ const query = new URLSearchParams({
824
+ sessionId: batch.sessionId,
825
+ name: record.file.name || 'clipboard-image',
826
+ size: String(record.file.size),
827
+ });
828
+ const body = await responseJson(await fetch(`${exports.PASTE_IMAGES_ROUTE}?${query.toString()}`, {
829
+ method: 'POST',
830
+ headers: { 'Content-Type': record.file.type },
831
+ body: record.file,
832
+ signal,
833
+ }));
834
+ const absolutePath = body.value?.absolutePath;
835
+ if (typeof absolutePath !== 'string' || absolutePath === '') {
836
+ throw new Error('Image copy response contained an invalid path');
837
+ }
838
+ record.absolutePath = absolutePath;
839
+ record.status = 'copied';
840
+ record.error = undefined;
841
+ return undefined;
842
+ }
843
+ catch (error) {
844
+ const failure = error instanceof Error ? error : new Error(message(error));
845
+ record.status = 'error';
846
+ record.error = failure.message;
847
+ return failure;
848
+ }
849
+ }));
850
+ this.changed();
851
+ const failure = failures.find((error) => error !== undefined);
852
+ if (failure !== undefined)
853
+ throw failure;
854
+ }
855
+ finally {
856
+ batch.inflight = undefined;
857
+ this.changed();
858
+ }
859
+ })();
860
+ batch.inflight = task;
861
+ return task;
862
+ }
863
+ async serialize(ref, signal) {
864
+ const record = this.records.get(ref);
865
+ if (record === undefined)
866
+ throw new Error('Pasted image is no longer available in this browser tab');
867
+ await this.upload(record.batch, signal);
868
+ if (record.absolutePath === undefined)
869
+ throw new Error('Pasted image was not copied into the workspace');
870
+ return `[Pasted image available at absolute path: ${JSON.stringify(record.absolutePath)}]`;
871
+ }
872
+ }
873
+ exports.PasteImageController = PasteImageController;
874
+ /** Minimal per-image progress, failure, and removal feedback above the composer. */
875
+ function PasteImageDock(props) {
876
+ (0, react_1.useSyncExternalStore)(props.controller.subscribe, props.controller.snapshot);
877
+ const occurrences = props.input.occurrences.filter(occurrence => occurrence.source === SOURCE);
878
+ const records = props.controller.recordsFor(occurrences);
879
+ if (records.length === 0)
880
+ return null;
881
+ return (0, jsx_runtime_1.jsx)("div", { className: "dvt-paste-dock", role: "status", "aria-label": "Pasted images", children: occurrences.map((occurrence) => {
882
+ const record = props.controller.recordsFor([occurrence])[0];
883
+ if (record === undefined)
884
+ return null;
885
+ const detail = record.status === 'copying' ? 'copying…'
886
+ : record.status === 'copied' ? 'copied'
887
+ : record.status === 'error' ? record.error ?? 'copy failed'
888
+ : humanBytes(record.file.size);
889
+ return (0, jsx_runtime_1.jsxs)("div", { className: "dvt-paste-chip", "data-status": record.status, children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-paste-name", title: record.file.name, children: record.file.name || 'clipboard image' }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-paste-detail", title: record.error, children: detail }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": `Remove ${record.file.name || 'clipboard image'}`, disabled: props.input.phase !== 'plain' || record.status === 'copying', onClick: () => { props.remove(occurrence); }, children: "\u00D7" })] }, occurrence.occurrenceId);
890
+ }) });
891
+ }
892
+ /** Install capture interception, the text-reference codec, and composer feedback. */
893
+ function installPasteImages(ctx) {
894
+ const controller = new PasteImageController(ctx);
895
+ const registered = new WeakMap();
896
+ const register = (scope, registry) => {
897
+ scope.effect(() => {
898
+ const identity = registryIdentity(registry);
899
+ let registration = registered.get(identity);
900
+ if (registration === undefined) {
901
+ registration = { dispose: registry.registerSource(controller.source()), owners: 0 };
902
+ registered.set(identity, registration);
903
+ }
904
+ registration.owners += 1;
905
+ return () => {
906
+ if (registered.get(identity) !== registration)
907
+ return;
908
+ registration.owners -= 1;
909
+ if (registration.owners > 0)
910
+ return;
911
+ registered.delete(identity);
912
+ registration.dispose();
913
+ };
914
+ }, 'dsh-vision-toolkit: pasted image reference codec');
915
+ };
916
+ ctx.inject(['slash'], (scope) => {
917
+ register(scope, scope.slash);
918
+ });
919
+ ctx.inject(['inputTriggers'], (scope) => {
920
+ register(scope, scope.inputTriggers);
921
+ });
922
+ ctx.effect(() => {
923
+ const listener = (event) => { controller.handlePaste(event); };
924
+ document.addEventListener('paste', listener, true);
925
+ return () => { document.removeEventListener('paste', listener, true); };
926
+ }, 'dsh-vision-toolkit: clipboard image capture');
927
+ ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
928
+ name: 'conversation.input.dock',
929
+ id: 'vision-toolkit-pasted-images',
930
+ order: 6,
931
+ inject: sessionId => ({
932
+ controller,
933
+ remove: (occurrence) => { controller.remove(String(sessionId), occurrence); },
934
+ }),
935
+ }, PasteImageDock));
936
+ }
937
+ };
938
+ function __resolve(from, request) {
939
+ if (!request.startsWith(".")) return request;
940
+ var parts = from.slice(2).split("/"); parts.pop();
941
+ for (var part of request.split("/")) { if (part === "." || part === "") continue; if (part === "..") parts.pop(); else parts.push(part); }
942
+ return "./" + parts.join("/");
943
+ }
944
+ function __load(id) {
945
+ if (__modules[id] === undefined) return require(id);
946
+ if (__cache[id] !== undefined) return __cache[id].exports;
947
+ var module = __cache[id] = { exports: {} };
948
+ __modules[id](module, module.exports, require, function(request) { var resolved = __resolve(id, request); return __modules[resolved] === undefined ? require(request) : __load(resolved); });
949
+ return module.exports;
950
+ }
951
+ return __load("./index.js"); } });
952
+ //# sourceMappingURL=client.js.map