@anionex/dsh-vision-toolkit 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.i18n.yaml +3 -3
- package/README.md +26 -15
- package/README.zh.md +26 -15
- package/docs/assets/vision-settings.png +0 -0
- package/docs/requirements-traceability/README.md +1 -1
- package/docs/requirements-traceability/README.zh.md +1 -1
- package/lib/client.js +415 -84
- package/lib/client.js.map +1 -1
- package/lib/config.js +18 -1
- package/lib/config.js.map +1 -1
- package/lib/runtime.js +18 -1
- package/lib/runtime.js.map +1 -1
- package/lib/types/client/index.d.ts +84 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/config.d.ts +12 -1
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/upstream.d.ts +3 -0
- package/lib/types/upstream.d.ts.map +1 -1
- package/lib/types/web.d.ts +2 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/upstream.js +3 -0
- package/lib/upstream.js.map +1 -1
- package/lib/web.js +62 -9
- package/lib/web.js.map +1 -1
- package/package.json +4 -4
- package/src/client/index.tsx +461 -113
- package/src/config.ts +29 -2
- package/src/runtime.ts +17 -1
- package/src/upstream.ts +6 -0
- package/src/web.ts +75 -10
- package/vendor/agent-vision-toolkit/CHANGELOG.md +13 -0
- package/vendor/agent-vision-toolkit/README.md +11 -6
- package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +14 -9
- package/vendor/agent-vision-toolkit/tests/test_vision_client.py +396 -0
- package/vendor/agent-vision-toolkit/vision_client.py +134 -18
package/lib/client.js
CHANGED
|
@@ -17,15 +17,29 @@ const paste_images_tsx_1 = __load_("./paste-images.js");
|
|
|
17
17
|
const NS = 'vision-toolkit';
|
|
18
18
|
const SETTINGS_ROUTE = '/_dsh/vision-toolkit/settings';
|
|
19
19
|
const PRESENTATION_META_KEY = '$dshVisionToolkit';
|
|
20
|
+
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
|
|
20
21
|
const en = {
|
|
21
22
|
nav: 'Vision',
|
|
22
23
|
settingsTitle: 'Vision Toolkit',
|
|
23
24
|
settingsIntro: 'Configure the pinned visual engineering runtime, its external vision endpoint, and local safety limits.',
|
|
24
25
|
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
26
|
provider: 'Vision service',
|
|
27
|
+
providerHint: 'Choose the API protocol, then provide the service address, model, and API key used by online vision features.',
|
|
26
28
|
baseUrl: 'Base URL',
|
|
27
|
-
|
|
29
|
+
apiKey: 'API key',
|
|
30
|
+
apiKeyPlaceholderMissing: 'Paste the API key',
|
|
31
|
+
apiKeyPlaceholderConfigured: 'Saved; leave blank to keep it',
|
|
32
|
+
apiKeyHint: 'The key is stored in DSH Credentials and is never shown again after saving.',
|
|
33
|
+
apiKeyLocked: 'The current key comes from a read-only source and cannot be replaced here.',
|
|
34
|
+
apiKeyBlank: 'The API key cannot contain only spaces.',
|
|
35
|
+
apiKeyInvalid: 'Paste only the key, without a variable name, quotes, spaces, or line breaks.',
|
|
36
|
+
credential: 'Credential name',
|
|
37
|
+
credentialHint: 'Advanced: the API key is stored under this name. Keep VISION_API_KEY unless another plugin configuration requires a different reference.',
|
|
28
38
|
model: 'Model',
|
|
39
|
+
protocol: 'API protocol',
|
|
40
|
+
anthropicThinking: 'Anthropic thinking',
|
|
41
|
+
anthropicThinkingHint: 'omit has the broadest compatibility. Use disabled or adaptive only when the selected model documents that mode; restore omit first after HTTP 400.',
|
|
42
|
+
userAgent: 'User-Agent',
|
|
29
43
|
language: 'Output language',
|
|
30
44
|
limits: 'Limits',
|
|
31
45
|
timeout: 'Request timeout (ms)',
|
|
@@ -42,20 +56,31 @@ const en = {
|
|
|
42
56
|
saving: 'Validating runtime…',
|
|
43
57
|
reload: 'Reload',
|
|
44
58
|
saved: 'Settings validated and applied.',
|
|
45
|
-
readOnly: '
|
|
59
|
+
readOnly: 'Service settings are read-only. A writable API key can still be saved.',
|
|
46
60
|
configured: 'Configured',
|
|
47
61
|
missing: 'Missing',
|
|
48
62
|
source: 'Source',
|
|
63
|
+
sourceHint: '{source}: {value}',
|
|
64
|
+
sourceEnv: 'Environment variable',
|
|
65
|
+
sourceFile: 'Credential file',
|
|
49
66
|
health: 'Health',
|
|
50
67
|
runHealth: 'Run health check',
|
|
51
68
|
testConnection: 'Test connection',
|
|
52
69
|
testing: 'Checking…',
|
|
53
70
|
connectionHint: 'Connection testing explicitly sends the configured credential to GET /models. It uploads no image and creates no completion.',
|
|
71
|
+
saveBeforeTesting: 'Save service changes before testing the connection.',
|
|
72
|
+
advanced: 'Advanced settings',
|
|
73
|
+
advancedHint: 'Credential name, provider compatibility, output language, resource limits, runtime source, Python, and additional readable directories.',
|
|
54
74
|
pluginVersion: 'Plugin',
|
|
55
75
|
upstreamVersion: 'Upstream',
|
|
56
76
|
activeGeneration: 'Runtime generation',
|
|
77
|
+
activeGenerationValue: 'Generation {generation}',
|
|
78
|
+
pluginKind: 'DSH native plugin',
|
|
57
79
|
runtimeUnavailable: 'Runtime unavailable',
|
|
58
80
|
runtimeCandidateRejected: 'Last runtime candidate was rejected; the active generation remains available.',
|
|
81
|
+
runtimeReady: 'Ready',
|
|
82
|
+
runtimeManaged: 'Managed',
|
|
83
|
+
runtimeExternal: 'External checkout',
|
|
59
84
|
retry: 'Retry',
|
|
60
85
|
open: 'Open file',
|
|
61
86
|
download: 'Download',
|
|
@@ -75,65 +100,197 @@ const en = {
|
|
|
75
100
|
healthy: 'Healthy',
|
|
76
101
|
degraded: 'Needs attention',
|
|
77
102
|
notTested: 'Not tested',
|
|
103
|
+
groundTitle: 'Ground',
|
|
104
|
+
detectTitle: 'Detect',
|
|
105
|
+
traceTitle: 'Trace SVG',
|
|
106
|
+
pixelDiffTitle: 'Pixel Diff',
|
|
107
|
+
cropTitle: 'Crop',
|
|
108
|
+
longOcrTitle: 'Long OCR',
|
|
109
|
+
extractForegroundTitle: 'Extract Foreground',
|
|
110
|
+
htmlScreenshotTitle: 'HTML Screenshot',
|
|
111
|
+
artifactTitle: 'Vision Artifact',
|
|
112
|
+
dominantColorsTitle: 'Dominant Colors',
|
|
113
|
+
artifactGroundPreview: 'Grounding bounding-box preview',
|
|
114
|
+
artifactDetectPreview: 'Detected-element bounding-box preview',
|
|
115
|
+
artifactCrop: 'Cropped image region',
|
|
116
|
+
artifactTrace: 'Traced vector geometry',
|
|
117
|
+
artifactDiffHeatmap: 'Pixel-difference heatmap',
|
|
118
|
+
artifactDiffReport: 'Structured pixel-difference report',
|
|
119
|
+
artifactLongManifest: 'Long-screenshot split and merge manifest',
|
|
120
|
+
artifactLongTranscript: 'Merged long-screenshot OCR transcript',
|
|
121
|
+
artifactLongAudit: 'Long-screenshot OCR boundary audit',
|
|
122
|
+
artifactLongChunk: 'Long-screenshot OCR chunk {index}',
|
|
123
|
+
artifactOcrSidecar: 'OCR sidecar for chunk {index}',
|
|
124
|
+
artifactForeground: 'Extracted transparent foreground',
|
|
125
|
+
artifactHtmlScreenshot: 'Headless browser screenshot of local HTML',
|
|
126
|
+
label: 'Label',
|
|
127
|
+
paths: 'paths',
|
|
128
|
+
healthPython: 'Python',
|
|
129
|
+
healthDependencies: 'Dependencies',
|
|
130
|
+
healthChrome: 'Browser',
|
|
131
|
+
healthCredential: 'Credential',
|
|
132
|
+
healthArtifactDirectory: 'Artifact directory',
|
|
133
|
+
healthTempDirectory: 'Temporary directory',
|
|
134
|
+
healthService: 'Vision service',
|
|
135
|
+
statusOk: 'OK',
|
|
136
|
+
statusWarning: 'Warning',
|
|
137
|
+
statusError: 'Error',
|
|
138
|
+
statusNotTested: 'Not tested',
|
|
139
|
+
positiveInteger: '{field} must be a positive integer.',
|
|
140
|
+
healthPythonDetail: '{version} via {path}',
|
|
141
|
+
healthChromeMissing: 'Chrome, Chromium, or Edge was not found; HTML Screenshot is unavailable.',
|
|
142
|
+
healthChromeProbeFailed: 'Could not check whether a supported browser is available.',
|
|
143
|
+
healthCredentialMissing: 'Credential {credential} is not configured.',
|
|
144
|
+
healthCredentialReady: 'Credential {credential} is available.',
|
|
145
|
+
healthCredentialFailed: 'Could not read credential {credential}.',
|
|
146
|
+
healthDirectoryWritable: '{directory} is writable: {path}',
|
|
147
|
+
healthDirectoryNotWritable: '{directory} is not writable: {path}',
|
|
148
|
+
healthArtifactDirectoryFailed: 'Could not prepare the artifact directory.',
|
|
149
|
+
healthConnectionNotTested: 'Connection not tested. Use Test connection to query /models.',
|
|
150
|
+
healthConnectionCredentialMissing: 'Connection test skipped because the credential is unavailable.',
|
|
151
|
+
healthServiceResponded: 'Service responded at {endpoint} (HTTP {status}).',
|
|
152
|
+
healthServiceRejectedCredential: 'Service rejected the configured credential (HTTP {status}).',
|
|
153
|
+
healthServiceNoModels: 'Service is reachable but does not support GET /models (HTTP {status}).',
|
|
154
|
+
healthServiceRateLimited: 'Service is reachable, but the connection test was rate-limited (HTTP 429).',
|
|
155
|
+
healthServiceHttpFailed: 'Connection test failed with HTTP {status}.',
|
|
156
|
+
healthServiceUnreachable: 'Could not reach {endpoint}.',
|
|
78
157
|
};
|
|
79
158
|
const zh = {
|
|
80
159
|
nav: '视觉工具',
|
|
81
|
-
settingsTitle: '
|
|
82
|
-
settingsIntro: '
|
|
83
|
-
externalNotice: '
|
|
84
|
-
provider: '
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
settingsTitle: '视觉工具箱',
|
|
161
|
+
settingsIntro: '在这里设置视觉模型服务、工具运行环境,以及图片和文件的本地访问范围。',
|
|
162
|
+
externalNotice: '使用图像理解、目标定位、界面检测或文字识别等在线功能时,所选图片会发送到下方配置的视觉服务。图片裁剪、轮廓描摹、像素对比、主色提取、前景提取和网页截图均在本机完成,不会上传图片。',
|
|
163
|
+
provider: '在线视觉服务',
|
|
164
|
+
providerHint: '选择接口协议后,填写在线视觉功能使用的 API 地址、模型名称和 API 密钥。',
|
|
165
|
+
baseUrl: 'API 地址',
|
|
166
|
+
apiKey: 'API 密钥',
|
|
167
|
+
apiKeyPlaceholderMissing: '粘贴 API 密钥',
|
|
168
|
+
apiKeyPlaceholderConfigured: '已保存;留空表示不修改',
|
|
169
|
+
apiKeyHint: '密钥会保存到 DSH 凭据存储,保存后不会在页面中回显。',
|
|
170
|
+
apiKeyLocked: '当前密钥来自只读配置,无法在此替换。',
|
|
171
|
+
apiKeyBlank: 'API 密钥不能只包含空格。',
|
|
172
|
+
apiKeyInvalid: '请只粘贴密钥本身,不要包含变量名、引号、空格或换行。',
|
|
173
|
+
credential: '凭据名称',
|
|
174
|
+
credentialHint: '高级用法:API 密钥会按此名称保存。除非其他插件配置要求不同名称,否则保持 VISION_API_KEY。',
|
|
175
|
+
model: '模型名称',
|
|
176
|
+
protocol: 'API 协议',
|
|
177
|
+
anthropicThinking: 'Anthropic thinking',
|
|
178
|
+
anthropicThinkingHint: 'omit 兼容性最好。仅当所选模型明确支持时使用 disabled 或 adaptive;遇到 HTTP 400 时先恢复 omit。',
|
|
179
|
+
userAgent: 'User-Agent',
|
|
180
|
+
language: '结果语言',
|
|
181
|
+
limits: '资源与并发限制',
|
|
182
|
+
timeout: '单次请求超时(毫秒)',
|
|
183
|
+
maxBytes: '单张图片大小上限(字节)',
|
|
184
|
+
maxPixels: '单张图片最大像素数',
|
|
185
|
+
concurrency: '单个会话最多并发任务数',
|
|
186
|
+
runtime: '工具运行环境',
|
|
187
|
+
runtimeMode: '环境来源',
|
|
188
|
+
toolkitPath: 'agent-vision-toolkit 目录',
|
|
189
|
+
python: 'Python 解释器(可选)',
|
|
190
|
+
allowedDirs: '允许读取的其他目录',
|
|
191
|
+
allowedDirsHint: '每行填写一个目录。当前会话的工作目录始终可以读取,无需重复填写。',
|
|
192
|
+
save: '保存设置',
|
|
193
|
+
saving: '正在检查并应用…',
|
|
102
194
|
reload: '重新加载',
|
|
103
|
-
saved: '
|
|
104
|
-
readOnly: '
|
|
105
|
-
configured: '
|
|
106
|
-
missing: '
|
|
107
|
-
source: '
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
195
|
+
saved: '设置已保存并生效。',
|
|
196
|
+
readOnly: '服务设置来自只读配置;如果 API 密钥可写,仍可在此保存密钥。',
|
|
197
|
+
configured: '已就绪',
|
|
198
|
+
missing: '未配置',
|
|
199
|
+
source: '配置来源',
|
|
200
|
+
sourceHint: '{source}:{value}',
|
|
201
|
+
sourceEnv: '环境变量',
|
|
202
|
+
sourceFile: '凭据文件',
|
|
203
|
+
health: '运行检查',
|
|
204
|
+
runHealth: '检查本地环境',
|
|
205
|
+
testConnection: '测试服务连接',
|
|
111
206
|
testing: '检查中…',
|
|
112
|
-
connectionHint: '
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
207
|
+
connectionHint: '“检查本地环境”只检查本机依赖和目录;“测试服务连接”会携带已配置的 API 密钥请求 /models,但不会上传图片或调用模型。',
|
|
208
|
+
saveBeforeTesting: '修改服务配置后,请先保存,再测试连接。',
|
|
209
|
+
advanced: '高级设置',
|
|
210
|
+
advancedHint: '凭据名称、服务兼容参数、结果语言、资源限制、运行环境来源、Python 和额外可读目录。一般无需修改。',
|
|
211
|
+
pluginVersion: '插件版本',
|
|
212
|
+
upstreamVersion: '工具包版本',
|
|
213
|
+
activeGeneration: '本次运行已应用',
|
|
214
|
+
activeGenerationValue: '{generation} 次',
|
|
215
|
+
pluginKind: 'DSH 原生插件',
|
|
216
|
+
runtimeUnavailable: '运行环境尚未就绪',
|
|
217
|
+
runtimeCandidateRejected: '新设置未能生效,仍在使用上一次可用的设置。',
|
|
218
|
+
runtimeReady: '已就绪',
|
|
219
|
+
runtimeManaged: '自动安装',
|
|
220
|
+
runtimeExternal: '本地源码',
|
|
118
221
|
retry: '重试',
|
|
119
|
-
open: '
|
|
222
|
+
open: '在工作区中打开',
|
|
120
223
|
download: '下载',
|
|
121
|
-
previewUnavailable: '
|
|
224
|
+
previewUnavailable: '此页面无法直接预览该文件,请在工作区中打开。',
|
|
122
225
|
running: '运行中…',
|
|
123
|
-
failed: '
|
|
124
|
-
matches: '
|
|
226
|
+
failed: '运行失败',
|
|
227
|
+
matches: '处匹配',
|
|
125
228
|
elements: '个元素',
|
|
126
|
-
dimensions: '
|
|
229
|
+
dimensions: '图片尺寸',
|
|
127
230
|
coordinates: '坐标',
|
|
128
|
-
artifact: '
|
|
129
|
-
artifacts: '
|
|
130
|
-
difference: '
|
|
131
|
-
worstRegions: '
|
|
132
|
-
colors: '
|
|
133
|
-
noResult: '
|
|
134
|
-
healthy: '
|
|
135
|
-
degraded: '
|
|
136
|
-
notTested: '
|
|
231
|
+
artifact: '生成文件',
|
|
232
|
+
artifacts: '个生成文件',
|
|
233
|
+
difference: '像素差异',
|
|
234
|
+
worstRegions: '差异最大的区域',
|
|
235
|
+
colors: '种颜色',
|
|
236
|
+
noResult: '未能读取结果,请查看工具的原始输出。',
|
|
237
|
+
healthy: '一切正常',
|
|
238
|
+
degraded: '有项目需要处理',
|
|
239
|
+
notTested: '尚未检查',
|
|
240
|
+
groundTitle: '目标定位',
|
|
241
|
+
detectTitle: '界面元素识别',
|
|
242
|
+
traceTitle: '描摹为 SVG',
|
|
243
|
+
pixelDiffTitle: '像素对比',
|
|
244
|
+
cropTitle: '裁剪图片',
|
|
245
|
+
longOcrTitle: '长图文字识别',
|
|
246
|
+
extractForegroundTitle: '提取前景',
|
|
247
|
+
htmlScreenshotTitle: '网页截图',
|
|
248
|
+
artifactTitle: '视觉处理结果',
|
|
249
|
+
dominantColorsTitle: '主色提取',
|
|
250
|
+
artifactGroundPreview: '目标定位框预览',
|
|
251
|
+
artifactDetectPreview: '界面元素标注预览',
|
|
252
|
+
artifactCrop: '裁剪后的图片',
|
|
253
|
+
artifactTrace: '描摹得到的矢量图',
|
|
254
|
+
artifactDiffHeatmap: '像素差异热力图',
|
|
255
|
+
artifactDiffReport: '像素差异详细报告',
|
|
256
|
+
artifactLongManifest: '长图切分与合并记录',
|
|
257
|
+
artifactLongTranscript: '长图文字识别结果',
|
|
258
|
+
artifactLongAudit: '长图分块边界检查记录',
|
|
259
|
+
artifactLongChunk: '长图文字识别分块 {index}',
|
|
260
|
+
artifactOcrSidecar: '分块 {index} 的文字识别记录',
|
|
261
|
+
artifactForeground: '提取后的透明背景前景图',
|
|
262
|
+
artifactHtmlScreenshot: '本地网页截图',
|
|
263
|
+
label: '名称',
|
|
264
|
+
paths: '条路径',
|
|
265
|
+
healthPython: 'Python',
|
|
266
|
+
healthDependencies: 'Python 依赖',
|
|
267
|
+
healthChrome: '浏览器',
|
|
268
|
+
healthCredential: 'API 密钥',
|
|
269
|
+
healthArtifactDirectory: '输出目录',
|
|
270
|
+
healthTempDirectory: '临时目录',
|
|
271
|
+
healthService: '视觉服务',
|
|
272
|
+
statusOk: '正常',
|
|
273
|
+
statusWarning: '注意',
|
|
274
|
+
statusError: '异常',
|
|
275
|
+
statusNotTested: '未检查',
|
|
276
|
+
positiveInteger: '{field}必须填写正整数。',
|
|
277
|
+
healthPythonDetail: '版本 {version};解释器:{path}',
|
|
278
|
+
healthChromeMissing: '未找到 Chrome、Chromium 或 Edge,网页截图功能暂不可用。',
|
|
279
|
+
healthChromeProbeFailed: '无法检查浏览器是否可用。',
|
|
280
|
+
healthCredentialMissing: '尚未配置凭据 {credential}。',
|
|
281
|
+
healthCredentialReady: '已找到凭据 {credential}。',
|
|
282
|
+
healthCredentialFailed: '无法读取凭据 {credential}。',
|
|
283
|
+
healthDirectoryWritable: '{directory}可写:{path}',
|
|
284
|
+
healthDirectoryNotWritable: '{directory}不可写:{path}',
|
|
285
|
+
healthArtifactDirectoryFailed: '无法准备输出目录。',
|
|
286
|
+
healthConnectionNotTested: '尚未测试服务连接。点击“测试服务连接”可请求 /models。',
|
|
287
|
+
healthConnectionCredentialMissing: 'API 密钥不可用,未执行连接测试。',
|
|
288
|
+
healthServiceResponded: '服务已响应:{endpoint}(HTTP {status})。',
|
|
289
|
+
healthServiceRejectedCredential: '服务拒绝了当前 API 密钥(HTTP {status})。',
|
|
290
|
+
healthServiceNoModels: '服务可以访问,但不支持 GET /models(HTTP {status})。',
|
|
291
|
+
healthServiceRateLimited: '服务可以访问,但本次连接测试触发了限流(HTTP 429)。',
|
|
292
|
+
healthServiceHttpFailed: '连接测试失败(HTTP {status})。',
|
|
293
|
+
healthServiceUnreachable: '无法连接到 {endpoint}。',
|
|
137
294
|
};
|
|
138
295
|
function isRecord(value) {
|
|
139
296
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -261,21 +418,53 @@ function ArtifactActions({ artifact, grant, openFile, t }) {
|
|
|
261
418
|
}
|
|
262
419
|
function ArtifactPreview({ artifact, grant, openFile, t }) {
|
|
263
420
|
const canPreview = grant !== undefined && (artifact.kind === 'image' || artifact.kind === 'svg');
|
|
421
|
+
const description = artifactDescription(artifact.description, t);
|
|
264
422
|
return ((0, jsx_runtime_1.jsxs)("article", { className: "dvt-artifact", children: [canPreview
|
|
265
423
|
? artifact.kind === 'svg'
|
|
266
|
-
? (0, jsx_runtime_1.jsx)("iframe", { className: "dvt-preview dvt-svg", sandbox: "", src: grant.previewUrl, title:
|
|
267
|
-
: (0, jsx_runtime_1.jsx)("img", { className: "dvt-preview", src: grant.previewUrl, alt:
|
|
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:
|
|
424
|
+
? (0, jsx_runtime_1.jsx)("iframe", { className: "dvt-preview dvt-svg", sandbox: "", src: grant.previewUrl, title: description })
|
|
425
|
+
: (0, jsx_runtime_1.jsx)("img", { className: "dvt-preview", src: grant.previewUrl, alt: description, loading: "lazy" })
|
|
426
|
+
: 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: 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] }));
|
|
427
|
+
}
|
|
428
|
+
const ARTIFACT_DESCRIPTION_KEYS = {
|
|
429
|
+
'Grounding bounding-box preview': 'artifactGroundPreview',
|
|
430
|
+
'Detected-element bounding-box preview': 'artifactDetectPreview',
|
|
431
|
+
'Cropped image region': 'artifactCrop',
|
|
432
|
+
'Traced vector geometry': 'artifactTrace',
|
|
433
|
+
'Pixel-difference heatmap': 'artifactDiffHeatmap',
|
|
434
|
+
'Structured pixel-difference report': 'artifactDiffReport',
|
|
435
|
+
'Long-screenshot split and merge manifest': 'artifactLongManifest',
|
|
436
|
+
'Merged long-screenshot OCR transcript': 'artifactLongTranscript',
|
|
437
|
+
'Long-screenshot OCR boundary audit': 'artifactLongAudit',
|
|
438
|
+
'Extracted transparent foreground': 'artifactForeground',
|
|
439
|
+
'Headless browser screenshot of local HTML': 'artifactHtmlScreenshot',
|
|
440
|
+
};
|
|
441
|
+
function artifactDescription(description, t) {
|
|
442
|
+
const key = ARTIFACT_DESCRIPTION_KEYS[description];
|
|
443
|
+
if (key !== undefined) {
|
|
444
|
+
const translated = t(key);
|
|
445
|
+
return translated === key ? description : translated;
|
|
446
|
+
}
|
|
447
|
+
let match = /^Long-screenshot OCR chunk (\d+)$/u.exec(description);
|
|
448
|
+
if (match !== null) {
|
|
449
|
+
const translated = t('artifactLongChunk', { index: match[1] });
|
|
450
|
+
return translated === 'artifactLongChunk' ? description : translated;
|
|
451
|
+
}
|
|
452
|
+
match = /^OCR sidecar for chunk (\d+)$/u.exec(description);
|
|
453
|
+
if (match !== null) {
|
|
454
|
+
const translated = t('artifactOcrSidecar', { index: match[1] });
|
|
455
|
+
return translated === 'artifactOcrSidecar' ? description : translated;
|
|
456
|
+
}
|
|
457
|
+
return description;
|
|
269
458
|
}
|
|
270
459
|
function GroundView({ block, openFile, t = key => en[key] }) {
|
|
271
460
|
const value = decodeVisionResult(block);
|
|
272
461
|
const matches = Array.isArray(value?.matches) ? value.matches.filter(isRecord) : [];
|
|
273
|
-
const target = stringOf(value?.target) ?? '
|
|
462
|
+
const target = stringOf(value?.target) ?? t('groundTitle');
|
|
274
463
|
const width = numberOf(value?.imageWidth);
|
|
275
464
|
const height = numberOf(value?.imageHeight);
|
|
276
465
|
const preview = artifactFrom(value?.preview);
|
|
277
466
|
const grants = accessMap(value);
|
|
278
|
-
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title:
|
|
467
|
+
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: t('groundTitle'), 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
468
|
}
|
|
280
469
|
function DetectView({ block, openFile, t = key => en[key] }) {
|
|
281
470
|
const value = decodeVisionResult(block);
|
|
@@ -284,15 +473,15 @@ function DetectView({ block, openFile, t = key => en[key] }) {
|
|
|
284
473
|
const height = numberOf(value?.imageHeight);
|
|
285
474
|
const preview = artifactFrom(value?.preview);
|
|
286
475
|
const grants = accessMap(value);
|
|
287
|
-
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title:
|
|
476
|
+
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: t('detectTitle'), 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: t('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
477
|
}
|
|
289
478
|
function TraceView({ block, openFile, t = key => en[key] }) {
|
|
290
479
|
const value = decodeVisionResult(block);
|
|
291
480
|
const artifact = artifactFrom(value?.artifact);
|
|
292
481
|
const geometry = isRecord(value?.geometry) ? value.geometry : undefined;
|
|
293
|
-
const summary = geometry === undefined ? undefined : `${numberOf(geometry.pathCount) ?? 0} paths · ${formatBytes(numberOf(geometry.bytes) ?? 0)}`;
|
|
482
|
+
const summary = geometry === undefined ? undefined : `${numberOf(geometry.pathCount) ?? 0} ${t('paths')} · ${formatBytes(numberOf(geometry.bytes) ?? 0)}`;
|
|
294
483
|
const grants = accessMap(value);
|
|
295
|
-
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title:
|
|
484
|
+
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: t('traceTitle'), 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
485
|
}
|
|
297
486
|
function PixelDiffView({ block, openFile, t = key => en[key] }) {
|
|
298
487
|
const value = decodeVisionResult(block);
|
|
@@ -301,24 +490,24 @@ function PixelDiffView({ block, openFile, t = key => en[key] }) {
|
|
|
301
490
|
const heatmap = artifactFrom(value?.heatmap);
|
|
302
491
|
const report = artifactFrom(value?.report);
|
|
303
492
|
const grants = accessMap(value);
|
|
304
|
-
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title:
|
|
493
|
+
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: t('pixelDiffTitle'), 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
494
|
}
|
|
306
495
|
function ArtifactView({ block, openFile, toolName, t = key => en[key] }) {
|
|
307
496
|
const value = decodeVisionResult(block);
|
|
308
497
|
const artifacts = collectArtifacts(value);
|
|
309
498
|
const grants = accessMap(value);
|
|
310
|
-
const title = toolName === 'vision_crop' ? '
|
|
311
|
-
: toolName === 'vision_long_screenshot_ocr' ? '
|
|
312
|
-
: toolName === 'vision_extract_foreground' ? '
|
|
313
|
-
: toolName === 'vision_html_screenshot' ? '
|
|
314
|
-
: '
|
|
499
|
+
const title = toolName === 'vision_crop' ? t('cropTitle')
|
|
500
|
+
: toolName === 'vision_long_screenshot_ocr' ? t('longOcrTitle')
|
|
501
|
+
: toolName === 'vision_extract_foreground' ? t('extractForegroundTitle')
|
|
502
|
+
: toolName === 'vision_html_screenshot' ? t('htmlScreenshotTitle')
|
|
503
|
+
: t('artifactTitle');
|
|
315
504
|
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
505
|
}
|
|
317
506
|
function PaletteView({ block, t = key => en[key] }) {
|
|
318
507
|
const value = decodeVisionResult(block);
|
|
319
508
|
const analysis = isRecord(value?.analysis) ? value.analysis : undefined;
|
|
320
509
|
const colors = Array.isArray(analysis?.colors) ? analysis.colors.filter(isRecord) : [];
|
|
321
|
-
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title:
|
|
510
|
+
return ((0, jsx_runtime_1.jsx)(ToolShell, { block: block, title: t('dominantColorsTitle'), 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
511
|
const hex = stringOf(color.color) ?? '#000000';
|
|
323
512
|
const share = numberOf(color.sharePct);
|
|
324
513
|
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}`);
|
|
@@ -368,18 +557,48 @@ class VisionSettingsController {
|
|
|
368
557
|
return;
|
|
369
558
|
void this.load();
|
|
370
559
|
}
|
|
371
|
-
async save(value, expectedRevision) {
|
|
560
|
+
async save(value, expectedRevision, credentialValue, writeSettings) {
|
|
372
561
|
this.set({ ...this.state, action: 'save', error: undefined, message: undefined });
|
|
562
|
+
let snapshot = this.state.snapshot;
|
|
373
563
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
564
|
+
if (writeSettings) {
|
|
565
|
+
snapshot = await apiRequest({
|
|
566
|
+
method: 'POST',
|
|
567
|
+
headers: { 'Content-Type': 'application/json' },
|
|
568
|
+
body: JSON.stringify({ action: 'save', expectedRevision, value }),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if (snapshot === undefined)
|
|
572
|
+
throw new Error('Vision Toolkit Settings are unavailable');
|
|
573
|
+
if (credentialValue !== undefined) {
|
|
574
|
+
try {
|
|
575
|
+
snapshot = await apiRequest({
|
|
576
|
+
method: 'POST',
|
|
577
|
+
headers: { 'Content-Type': 'application/json' },
|
|
578
|
+
body: JSON.stringify({
|
|
579
|
+
action: 'credential',
|
|
580
|
+
expectedRevision: snapshot.settings.revision,
|
|
581
|
+
ref: snapshot.credential.ref,
|
|
582
|
+
value: credentialValue,
|
|
583
|
+
}),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
this.set({
|
|
588
|
+
status: 'ready',
|
|
589
|
+
snapshot,
|
|
590
|
+
health: this.state.health,
|
|
591
|
+
error: error instanceof Error ? error.message : String(error),
|
|
592
|
+
});
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
379
596
|
this.set({ status: 'ready', snapshot, health: this.state.health, message: 'saved' });
|
|
597
|
+
return true;
|
|
380
598
|
}
|
|
381
599
|
catch (error) {
|
|
382
600
|
this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
|
|
601
|
+
return false;
|
|
383
602
|
}
|
|
384
603
|
}
|
|
385
604
|
async runHealth(testConnection) {
|
|
@@ -403,6 +622,9 @@ function draftOf(value) {
|
|
|
403
622
|
baseUrl: value.provider?.baseUrl ?? 'https://api.inferera.com/v1',
|
|
404
623
|
credential: value.provider?.credential ?? 'VISION_API_KEY',
|
|
405
624
|
model: value.provider?.model ?? 'gemini-3.6-flash',
|
|
625
|
+
protocol: value.provider?.protocol ?? 'openai',
|
|
626
|
+
anthropicThinking: value.provider?.anthropicThinking ?? 'omit',
|
|
627
|
+
userAgent: value.provider?.userAgent ?? DEFAULT_USER_AGENT,
|
|
406
628
|
language: value.language ?? 'zh',
|
|
407
629
|
timeoutMs: String(value.timeoutMs ?? 60000),
|
|
408
630
|
maxImageBytes: String(value.maxImageBytes ?? 10485760),
|
|
@@ -414,20 +636,39 @@ function draftOf(value) {
|
|
|
414
636
|
allowedDirs: (value.allowedDirs ?? []).join('\n'),
|
|
415
637
|
};
|
|
416
638
|
}
|
|
417
|
-
function positiveInteger(raw, label) {
|
|
639
|
+
function positiveInteger(raw, label, t) {
|
|
418
640
|
const value = Number(raw);
|
|
419
641
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
420
|
-
throw new Error(
|
|
642
|
+
throw new Error(t('positiveInteger', { field: label }));
|
|
421
643
|
return value;
|
|
422
644
|
}
|
|
423
|
-
function
|
|
645
|
+
function apiKeyFailure(value, t) {
|
|
646
|
+
if (value.length === 0)
|
|
647
|
+
return undefined;
|
|
648
|
+
const trimmed = value.trim();
|
|
649
|
+
if (trimmed.length === 0)
|
|
650
|
+
return t('apiKeyBlank');
|
|
651
|
+
const quoted = trimmed.length > 1 && ['"', '\'', '`'].includes(trimmed[0] ?? '') && trimmed.endsWith(trimmed[0] ?? '');
|
|
652
|
+
const environmentLine = /^[A-Z][A-Z0-9_]*=[^=]/u.test(trimmed);
|
|
653
|
+
if (quoted || environmentLine || !/^[\x21-\x7E]+$/u.test(trimmed))
|
|
654
|
+
return t('apiKeyInvalid');
|
|
655
|
+
return undefined;
|
|
656
|
+
}
|
|
657
|
+
function valueOf(draft, t) {
|
|
424
658
|
return {
|
|
425
|
-
provider: {
|
|
659
|
+
provider: {
|
|
660
|
+
baseUrl: draft.baseUrl.trim(),
|
|
661
|
+
credential: draft.credential.trim(),
|
|
662
|
+
model: draft.model.trim(),
|
|
663
|
+
protocol: draft.protocol,
|
|
664
|
+
anthropicThinking: draft.anthropicThinking,
|
|
665
|
+
userAgent: draft.userAgent.trim(),
|
|
666
|
+
},
|
|
426
667
|
language: draft.language,
|
|
427
|
-
timeoutMs: positiveInteger(draft.timeoutMs, '
|
|
428
|
-
maxImageBytes: positiveInteger(draft.maxImageBytes, '
|
|
429
|
-
maxImagePixels: positiveInteger(draft.maxImagePixels, '
|
|
430
|
-
concurrency: positiveInteger(draft.concurrency, 'concurrency'),
|
|
668
|
+
timeoutMs: positiveInteger(draft.timeoutMs, t('timeout'), t),
|
|
669
|
+
maxImageBytes: positiveInteger(draft.maxImageBytes, t('maxBytes'), t),
|
|
670
|
+
maxImagePixels: positiveInteger(draft.maxImagePixels, t('maxPixels'), t),
|
|
671
|
+
concurrency: positiveInteger(draft.concurrency, t('concurrency'), t),
|
|
431
672
|
runtime: {
|
|
432
673
|
mode: draft.runtimeMode,
|
|
433
674
|
...(draft.runtimeMode === 'external' ? { agentVisionToolkitPath: draft.toolkitPath.trim() } : {}),
|
|
@@ -444,10 +685,89 @@ function SettingsSection({ controller, t }) {
|
|
|
444
685
|
return null;
|
|
445
686
|
return (0, jsx_runtime_1.jsx)(LoadedSettings, { controller: controller, t: t });
|
|
446
687
|
}
|
|
688
|
+
const HEALTH_NAME_KEYS = {
|
|
689
|
+
python: 'healthPython',
|
|
690
|
+
dependencies: 'healthDependencies',
|
|
691
|
+
chrome: 'healthChrome',
|
|
692
|
+
credential: 'healthCredential',
|
|
693
|
+
artifactDirectory: 'healthArtifactDirectory',
|
|
694
|
+
tempDirectory: 'healthTempDirectory',
|
|
695
|
+
service: 'healthService',
|
|
696
|
+
};
|
|
697
|
+
const HEALTH_STATUS_KEYS = {
|
|
698
|
+
ok: 'statusOk',
|
|
699
|
+
warning: 'statusWarning',
|
|
700
|
+
error: 'statusError',
|
|
701
|
+
not_tested: 'statusNotTested',
|
|
702
|
+
};
|
|
703
|
+
function healthDetail(name, detail, t) {
|
|
704
|
+
if (name === 'python') {
|
|
705
|
+
const match = /^(.+) via (.+)$/u.exec(detail);
|
|
706
|
+
if (match !== null)
|
|
707
|
+
return t('healthPythonDetail', { version: match[1], path: match[2] });
|
|
708
|
+
}
|
|
709
|
+
if (detail === 'Chrome/Chromium/Edge was not found; vision_html_screenshot is unavailable')
|
|
710
|
+
return t('healthChromeMissing');
|
|
711
|
+
if (detail === 'Chrome availability probe failed')
|
|
712
|
+
return t('healthChromeProbeFailed');
|
|
713
|
+
let match = /^credential (.+) is not configured$/u.exec(detail);
|
|
714
|
+
if (match !== null)
|
|
715
|
+
return t('healthCredentialMissing', { credential: match[1] });
|
|
716
|
+
match = /^credential (.+) is resolvable$/u.exec(detail);
|
|
717
|
+
if (match !== null)
|
|
718
|
+
return t('healthCredentialReady', { credential: match[1] });
|
|
719
|
+
match = /^credential (.+) could not be resolved$/u.exec(detail);
|
|
720
|
+
if (match !== null)
|
|
721
|
+
return t('healthCredentialFailed', { credential: match[1] });
|
|
722
|
+
match = /^(Artifact directory|Runtime temp directory) is writable: (.+)$/u.exec(detail);
|
|
723
|
+
if (match !== null)
|
|
724
|
+
return t('healthDirectoryWritable', {
|
|
725
|
+
directory: match[1] === 'Artifact directory' ? t('healthArtifactDirectory') : t('healthTempDirectory'),
|
|
726
|
+
path: match[2],
|
|
727
|
+
});
|
|
728
|
+
match = /^(Artifact directory|Runtime temp directory) is not writable: (.+)$/u.exec(detail);
|
|
729
|
+
if (match !== null)
|
|
730
|
+
return t('healthDirectoryNotWritable', {
|
|
731
|
+
directory: match[1] === 'Artifact directory' ? t('healthArtifactDirectory') : t('healthTempDirectory'),
|
|
732
|
+
path: match[2],
|
|
733
|
+
});
|
|
734
|
+
if (detail === 'Artifact directory could not be prepared')
|
|
735
|
+
return t('healthArtifactDirectoryFailed');
|
|
736
|
+
if (detail === 'Connection was not tested; pass testConnection=true to query the configured /models endpoint')
|
|
737
|
+
return t('healthConnectionNotTested');
|
|
738
|
+
if (detail === 'Connection test skipped because the configured credential is unavailable')
|
|
739
|
+
return t('healthConnectionCredentialMissing');
|
|
740
|
+
match = /^Service responded at (.+) \(HTTP (\d+)\)$/u.exec(detail);
|
|
741
|
+
if (match !== null)
|
|
742
|
+
return t('healthServiceResponded', { endpoint: match[1], status: match[2] });
|
|
743
|
+
match = /^Service rejected the configured credential \(HTTP (\d+)\)$/u.exec(detail);
|
|
744
|
+
if (match !== null)
|
|
745
|
+
return t('healthServiceRejectedCredential', { status: match[1] });
|
|
746
|
+
match = /^Service is reachable but does not expose GET \/models \(HTTP (\d+)\)$/u.exec(detail);
|
|
747
|
+
if (match !== null)
|
|
748
|
+
return t('healthServiceNoModels', { status: match[1] });
|
|
749
|
+
if (detail === 'Service is reachable but rate-limited the connection test (HTTP 429)')
|
|
750
|
+
return t('healthServiceRateLimited');
|
|
751
|
+
match = /^Service connection test failed with HTTP (\d+)$/u.exec(detail);
|
|
752
|
+
if (match !== null)
|
|
753
|
+
return t('healthServiceHttpFailed', { status: match[1] });
|
|
754
|
+
match = /^Service could not be reached at (.+)$/u.exec(detail);
|
|
755
|
+
if (match !== null)
|
|
756
|
+
return t('healthServiceUnreachable', { endpoint: match[1] });
|
|
757
|
+
return detail;
|
|
758
|
+
}
|
|
759
|
+
function credentialSource(source, t) {
|
|
760
|
+
if (source === 'env')
|
|
761
|
+
return t('sourceEnv');
|
|
762
|
+
if (source === 'file')
|
|
763
|
+
return t('sourceFile');
|
|
764
|
+
return source;
|
|
765
|
+
}
|
|
447
766
|
function LoadedSettings({ controller, t }) {
|
|
448
767
|
const state = (0, react_1.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
|
|
449
768
|
const snapshot = state.snapshot;
|
|
450
769
|
const [draft, setDraft] = (0, react_1.useState)(undefined);
|
|
770
|
+
const [apiKey, setApiKey] = (0, react_1.useState)('');
|
|
451
771
|
const [draftError, setDraftError] = (0, react_1.useState)(undefined);
|
|
452
772
|
(0, react_1.useEffect)(() => { if (state.status === 'idle')
|
|
453
773
|
void controller.load(); }, [controller, state.status]);
|
|
@@ -464,26 +784,37 @@ function LoadedSettings({ controller, t }) {
|
|
|
464
784
|
const update = (key, value) => setDraft(current => current === undefined ? current : { ...current, [key]: value });
|
|
465
785
|
const save = () => {
|
|
466
786
|
try {
|
|
787
|
+
const keyFailure = apiKeyFailure(apiKey, t);
|
|
788
|
+
if (keyFailure !== undefined) {
|
|
789
|
+
setDraftError(keyFailure);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const credentialValue = apiKey.length === 0 ? undefined : apiKey.trim();
|
|
467
793
|
setDraftError(undefined);
|
|
468
|
-
void controller.save(valueOf(draft), snapshot.settings.revision)
|
|
794
|
+
void controller.save(valueOf(draft, t), snapshot.settings.revision, credentialValue, snapshot.writable).then(saved => { if (saved)
|
|
795
|
+
setApiKey(''); });
|
|
469
796
|
}
|
|
470
797
|
catch (error) {
|
|
471
798
|
setDraftError(error instanceof Error ? error.message : String(error));
|
|
472
799
|
}
|
|
473
800
|
};
|
|
474
801
|
const busy = state.action !== undefined;
|
|
802
|
+
const credentialMatchesSnapshot = draft.credential.trim() === snapshot.credential.ref;
|
|
803
|
+
const keyLocked = credentialMatchesSnapshot && !snapshot.credential.writable;
|
|
804
|
+
const canSave = snapshot.writable || (apiKey.length > 0 && !keyLocked);
|
|
475
805
|
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)) })] })] }));
|
|
806
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(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 dvt-essential", 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('provider') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }), (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('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { disabled: !snapshot.writable || busy, value: draft.protocol, onChange: (event) => { update('protocol', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "openai", children: "OpenAI Chat Completions" }), (0, jsx_runtime_1.jsx)("option", { value: "anthropic", children: "Anthropic Messages" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, 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, { disabled: !snapshot.writable || busy, value: draft.model, onChange: (event) => { update('model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: keyLocked ? t('apiKeyLocked') : snapshot.credential.source === undefined ? t('apiKeyHint') : `${t('apiKeyHint')} ${t('sourceHint', { source: t('source'), value: credentialSource(snapshot.credential.source, t) })}`, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || keyLocked, placeholder: snapshot.credential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: apiKey, onChange: (event) => { setApiKey(event.target.value); setDraftError(undefined); } }) })] })] }), (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: !canSave || 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') })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('saveBeforeTesting') }), 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: t(HEALTH_NAME_KEYS[name] ?? 'health') }), (0, jsx_runtime_1.jsx)("strong", { children: t(HEALTH_STATUS_KEYS[check.status]) }), (0, jsx_runtime_1.jsx)("p", { children: healthDetail(name, check.detail, t) })] }, name)) })] }), (0, jsx_runtime_1.jsxs)("details", { className: "dvt-advanced", children: [(0, jsx_runtime_1.jsxs)("summary", { children: [(0, jsx_runtime_1.jsxs)("span", { children: [(0, jsx_runtime_1.jsx)("strong", { children: t('advanced') }), (0, jsx_runtime_1.jsx)("small", { children: t('advancedHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-details-chevron", "aria-hidden": "true", children: "\u2304" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-advanced-body", children: [(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('provider') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('credential'), hint: t('credentialHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('credential'), disabled: !snapshot.writable || busy, value: draft.credential, onChange: (event) => { update('credential', event.target.value); } }) }), draft.protocol === 'anthropic' ? (0, jsx_runtime_1.jsx)(Field, { label: t('anthropicThinking'), hint: t('anthropicThinkingHint'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('anthropicThinking'), value: draft.anthropicThinking, onChange: (event) => { update('anthropicThinking', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "omit", children: "omit (widest compatibility)" }), (0, jsx_runtime_1.jsx)("option", { value: "disabled", children: "disabled (model support required)" }), (0, jsx_runtime_1.jsx)("option", { value: "adaptive", children: "adaptive (model support required)" })] }) }) : null, (0, jsx_runtime_1.jsx)(Field, { label: t('userAgent'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.userAgent, onChange: (event) => { update('userAgent', event.target.value); } }) })] })] }), (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('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.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 === 'managed' ? t('runtimeManaged') : snapshot.runtime.upstream?.source === 'external' ? t('runtimeExternal') : t('runtimeReady') : 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: t('runtimeManaged') }), (0, jsx_runtime_1.jsx)("option", { value: "external", children: t('runtimeExternal') })] }) }), 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)("footer", { className: "dvt-settings-footer", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-kicker", children: t('pluginKind') }), (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: t('activeGenerationValue', { generation: snapshot.runtime.generation }) })] })] })] })] }));
|
|
477
807
|
}
|
|
478
808
|
const CSS = `
|
|
479
809
|
.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
810
|
.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
811
|
.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
812
|
.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-
|
|
813
|
+
.dvt-settings{display:grid;gap:14px;max-width:900px;padding:8px 2px 32px;color:var(--dsw-alias-label-primary)}.dvt-settings-footer{display:flex;justify-content:space-between;gap:20px;align-items:flex-start;padding:8px 2px}.dvt-settings-footer h2{font-size:25px;letter-spacing:-.025em;margin:3px 0 6px}.dvt-settings-footer 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}
|
|
814
|
+
.dvt-settings-footer{margin-top:8px;padding:20px 2px 4px;border-top:1px solid var(--dsw-alias-border-l1);opacity:.82}.dvt-settings-footer h2{font-size:18px;letter-spacing:-.015em;margin:3px 0 5px}.dvt-settings-footer p{font-size:11px;line-height:1.5}.dvt-release{min-width:220px}.dvt-release span{white-space:nowrap}.dvt-essential{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,var(--dsw-alias-border-l1));box-shadow:var(--dsw-shadow-lv1),0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 5%,transparent)}.dvt-advanced{border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}.dvt-advanced>summary{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 15px;cursor:pointer;list-style:none}.dvt-advanced>summary::-webkit-details-marker{display:none}.dvt-advanced>summary>span:first-child{display:grid;gap:3px}.dvt-advanced>summary strong{font-size:13px}.dvt-advanced>summary small{font-size:10px;line-height:1.45;color:var(--dsw-alias-label-secondary);font-weight:400}.dvt-details-chevron{font-size:15px;opacity:.55;transition:transform .16s ease}.dvt-advanced[open] .dvt-details-chevron{transform:rotate(180deg)}.dvt-advanced-body{display:grid;gap:12px;padding:0 12px 12px}.dvt-advanced-body>.dvt-panel{box-shadow:none}
|
|
484
815
|
.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
816
|
.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-
|
|
817
|
+
@media(max-width:720px){.dvt-settings-footer{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
818
|
`;
|
|
488
819
|
function installStyles() {
|
|
489
820
|
const id = '@anionex/dsh-vision-toolkit/client';
|