@mengruo/dsh-vision-toolkit 0.1.3 → 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 (101) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +15 -70
  3. package/README.zh.md +15 -69
  4. package/docs/requirements-traceability/README.i18n.yaml +2 -2
  5. package/docs/requirements-traceability/README.md +1 -1
  6. package/docs/requirements-traceability/README.zh.md +1 -1
  7. package/lib/artifact-access.js +20 -2
  8. package/lib/artifact-access.js.map +1 -1
  9. package/lib/client.js +128 -12
  10. package/lib/client.js.map +1 -1
  11. package/lib/config.js +69 -0
  12. package/lib/config.js.map +1 -1
  13. package/lib/errors.js +25 -2
  14. package/lib/errors.js.map +1 -1
  15. package/lib/evidence-cache.js +2 -1
  16. package/lib/evidence-cache.js.map +1 -1
  17. package/lib/exposure.js +14 -1
  18. package/lib/exposure.js.map +1 -1
  19. package/lib/image-input-variants.js +22 -12
  20. package/lib/image-input-variants.js.map +1 -1
  21. package/lib/index.js +53 -6
  22. package/lib/index.js.map +1 -1
  23. package/lib/object-storage.js +141 -0
  24. package/lib/object-storage.js.map +1 -0
  25. package/lib/paste-images.js +67 -19
  26. package/lib/paste-images.js.map +1 -1
  27. package/lib/paths.js +214 -28
  28. package/lib/paths.js.map +1 -1
  29. package/lib/runtime-manager.js +76 -10
  30. package/lib/runtime-manager.js.map +1 -1
  31. package/lib/runtime.js +219 -38
  32. package/lib/runtime.js.map +1 -1
  33. package/lib/storage-history.js +154 -0
  34. package/lib/storage-history.js.map +1 -0
  35. package/lib/types/artifact-access.d.ts.map +1 -1
  36. package/lib/types/client/index.d.ts +45 -2
  37. package/lib/types/client/index.d.ts.map +1 -1
  38. package/lib/types/client/paste-images.d.ts +2 -0
  39. package/lib/types/client/paste-images.d.ts.map +1 -1
  40. package/lib/types/config.d.ts +55 -0
  41. package/lib/types/config.d.ts.map +1 -1
  42. package/lib/types/errors.d.ts +18 -2
  43. package/lib/types/errors.d.ts.map +1 -1
  44. package/lib/types/evidence-cache.d.ts +1 -1
  45. package/lib/types/evidence-cache.d.ts.map +1 -1
  46. package/lib/types/exposure.d.ts.map +1 -1
  47. package/lib/types/image-input-variants.d.ts +5 -3
  48. package/lib/types/image-input-variants.d.ts.map +1 -1
  49. package/lib/types/index.d.ts.map +1 -1
  50. package/lib/types/object-storage.d.ts +54 -0
  51. package/lib/types/object-storage.d.ts.map +1 -0
  52. package/lib/types/paste-images.d.ts +12 -4
  53. package/lib/types/paste-images.d.ts.map +1 -1
  54. package/lib/types/paths.d.ts +31 -5
  55. package/lib/types/paths.d.ts.map +1 -1
  56. package/lib/types/runtime-manager.d.ts +28 -4
  57. package/lib/types/runtime-manager.d.ts.map +1 -1
  58. package/lib/types/runtime.d.ts +31 -1
  59. package/lib/types/runtime.d.ts.map +1 -1
  60. package/lib/types/storage-history.d.ts +63 -0
  61. package/lib/types/storage-history.d.ts.map +1 -0
  62. package/lib/types/upstream.d.ts +1 -0
  63. package/lib/types/upstream.d.ts.map +1 -1
  64. package/lib/types/web.d.ts +7 -0
  65. package/lib/types/web.d.ts.map +1 -1
  66. package/lib/upstream.js +34 -8
  67. package/lib/upstream.js.map +1 -1
  68. package/lib/web.js +44 -9
  69. package/lib/web.js.map +1 -1
  70. package/package.json +3 -1
  71. package/src/artifact-access.ts +22 -2
  72. package/src/client/index.tsx +169 -9
  73. package/src/client/paste-images.tsx +14 -4
  74. package/src/config.ts +128 -0
  75. package/src/errors.ts +25 -2
  76. package/src/evidence-cache.ts +2 -1
  77. package/src/exposure.ts +16 -2
  78. package/src/image-input-variants.ts +21 -6
  79. package/src/index.ts +65 -6
  80. package/src/object-storage.ts +174 -0
  81. package/src/paste-images.ts +81 -19
  82. package/src/paths.ts +249 -28
  83. package/src/runtime-manager.ts +93 -10
  84. package/src/runtime.ts +215 -36
  85. package/src/storage-history.ts +172 -0
  86. package/src/upstream.ts +36 -7
  87. package/src/web.ts +54 -9
  88. package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +11 -11
  89. package/vendor/agent-vision-toolkit/__pycache__/detect.cpython-314.pyc +0 -0
  90. package/vendor/agent-vision-toolkit/__pycache__/ground.cpython-314.pyc +0 -0
  91. package/vendor/agent-vision-toolkit/__pycache__/vision_client.cpython-314.pyc +0 -0
  92. package/vendor/agent-vision-toolkit/bin/__pycache__/glancecpython-314.pyc +0 -0
  93. package/vendor/agent-vision-toolkit/bin/glance +8 -1
  94. package/vendor/agent-vision-toolkit/detect.py +13 -7
  95. package/vendor/agent-vision-toolkit/ground.py +43 -18
  96. package/vendor/agent-vision-toolkit/tests/test_vision_client.py +88 -0
  97. package/vendor/agent-vision-toolkit/vision_client.py +84 -6
  98. package/assets/community-group-qr.png +0 -0
  99. package/assets/logo_aihubmix.png +0 -0
  100. package/assets/logo_eapi_dark.png +0 -0
  101. package/assets/wechat-reward.png +0 -0
package/lib/client.js CHANGED
@@ -87,6 +87,8 @@ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKi
87
87
  const BUILT_IN_FREE_VISION_BASE_URL = 'https://vision.anionex.me/v1';
88
88
  const BUILT_IN_FREE_VISION_CREDENTIAL = 'ANIONEX_FREE_VISION';
89
89
  const BUILT_IN_FREE_VISION_MODEL = 'gemini-3.7-flash';
90
+ // Fixed credential reference holding the combined "accessKeyId:secretAccessKey".
91
+ const OBJECT_STORAGE_CREDENTIAL = 'VISION_OBJECT_STORAGE';
90
92
  const en = {
91
93
  nav: 'Vision',
92
94
  settingsTitle: 'Vision Toolkit',
@@ -97,6 +99,7 @@ const en = {
97
99
  providers: 'Vision providers',
98
100
  name: 'Label',
99
101
  enabled: 'Enabled',
102
+ disabled: 'Disabled',
100
103
  priority: 'Priority {index}',
101
104
  attempts: 'Attempts',
102
105
  attemptsHint: 'Total attempts against this provider before failing over to the next one.',
@@ -125,6 +128,10 @@ const en = {
125
128
  anthropicThinking: 'Anthropic thinking',
126
129
  anthropicThinkingHint: 'omit has the broadest compatibility. Use disabled or adaptive only when the selected model documents that mode; restore omit first after HTTP 400.',
127
130
  userAgent: 'User-Agent',
131
+ stream: 'Streaming',
132
+ streamHint: 'Request a streamed (SSE) completion instead of one JSON response. Use it for endpoints with weak non-streaming support or that time out on long outputs.',
133
+ uploadViaUrl: 'Transfer images via URL',
134
+ uploadViaUrlHint: 'Upload each image to the configured object storage and send the model a URL instead of base64. Requires object storage (below); region crops fall back to base64.',
128
135
  language: 'Output language',
129
136
  limits: 'Timeout and concurrency',
130
137
  timeout: 'Request timeout (ms)',
@@ -145,12 +152,29 @@ const en = {
145
152
  runtimeMode: 'Runtime mode',
146
153
  toolkitPath: 'Pinned checkout path',
147
154
  python: 'Python override',
155
+ storage: 'Local files',
156
+ storageDir: 'Default save directory',
157
+ storageDirHint: 'Leave blank to keep .dsh-vision-toolkit in each workspace. On POSIX systems, set an absolute shared root such as /tmp/dsh-vision-toolkit to store artifacts, pasted images, and caches in an automatically generated per-user, workspace-specific child directory. Windows shared roots remain disabled until their ACLs can be verified safely.',
148
158
  allowedDirs: 'Additional allowed directories',
149
159
  allowedDirsHint: 'One path per line. The session workspace is always allowed.',
160
+ objectStorage: 'Object storage',
161
+ objectStorageHint: 'S3-compatible storage used when a provider enables URL image transfer. The access key is stored as a DSH credential and never shown again after saving.',
162
+ storageEndpoint: 'Endpoint',
163
+ storageBucket: 'Bucket',
164
+ storageAccessKeyId: 'Access Key ID',
165
+ storageSecretAccessKey: 'Secret Access Key',
166
+ storagePublicBase: 'Public base URL (optional)',
167
+ storagePublicBaseHint: 'Custom domain or r2.dev URL for permanent public links. Leave blank to use temporary presigned URLs.',
168
+ testStorage: 'Test object storage',
169
+ testingStorage: 'Testing…',
170
+ storageKeyPair: 'Access Key ID and Secret Access Key must both be filled together.',
150
171
  save: 'Save and apply',
151
172
  saving: 'Validating runtime…',
152
173
  reload: 'Reload',
153
174
  saved: 'Settings validated and applied.',
175
+ unsavedChanges: 'Changed but not saved',
176
+ discardChanges: 'Discard changes',
177
+ noUnsavedChanges: 'Settings unchanged',
154
178
  readOnly: 'Service settings are read-only. A writable API key can still be saved.',
155
179
  configured: 'Configured',
156
180
  missing: 'Missing',
@@ -167,7 +191,7 @@ const en = {
167
191
  connectionHint: 'The API connection test only queries GET /models. The vision model test sends the bundled diagnostic image and verifies one real multimodal request.',
168
192
  saveBeforeTesting: 'Save service changes before testing the connection.',
169
193
  advanced: 'Advanced settings',
170
- advancedHint: 'Credential name, provider compatibility, output language, resource limits, runtime source, Python, and additional readable directories.',
194
+ advancedHint: 'Credential name, provider compatibility, output language, resource limits, default save directory, runtime source, Python, and additional readable directories.',
171
195
  imageInput: 'Image input',
172
196
  hiddenVariants: 'Transparent variant routing',
173
197
  hiddenVariantsLabel: 'Keep the original model names and enable images automatically',
@@ -307,6 +331,7 @@ const zh = {
307
331
  providers: '视觉服务列表',
308
332
  name: '名称',
309
333
  enabled: '启用',
334
+ disabled: '禁用',
310
335
  priority: '优先级 {index}',
311
336
  attempts: '尝试次数',
312
337
  attemptsHint: '该服务在故障转移到下一个之前最多尝试的次数。',
@@ -335,6 +360,10 @@ const zh = {
335
360
  anthropicThinking: 'Anthropic thinking',
336
361
  anthropicThinkingHint: 'omit 兼容性最好。仅当所选模型明确支持时使用 disabled 或 adaptive;遇到 HTTP 400 时先恢复 omit。',
337
362
  userAgent: 'User-Agent',
363
+ stream: '流式请求',
364
+ streamHint: '以流式(SSE)方式请求补全,而非一次性返回 JSON。适合对非流式支持不佳、长输出易连接超时的端点。',
365
+ uploadViaUrl: '通过 URL 传输图片',
366
+ uploadViaUrlHint: '把每张图片上传到下方配置的对象存储,将 URL 传给模型而不是 base64。需要先配置对象存储;使用 region 裁剪时回退为 base64。',
338
367
  language: '结果语言',
339
368
  limits: '超时与并发限制',
340
369
  timeout: '单次请求超时(毫秒)',
@@ -355,12 +384,29 @@ const zh = {
355
384
  runtimeMode: '环境来源',
356
385
  toolkitPath: 'agent-vision-toolkit 目录',
357
386
  python: 'Python 解释器(可选)',
387
+ storage: '本地文件',
388
+ storageDir: '默认保存目录',
389
+ storageDirHint: '留空时继续在各工作区创建 .dsh-vision-toolkit。在 POSIX 系统上填写绝对路径共享根目录(例如 /tmp/dsh-vision-toolkit)后,产物、粘贴图片和缓存会保存到自动生成的当前用户、工作区专属子目录中。Windows 共享目录会保持禁用,直到能够安全校验 ACL 权限。',
358
390
  allowedDirs: '允许读取的其他目录',
359
391
  allowedDirsHint: '每行填写一个目录。当前会话的工作目录始终可以读取,无需重复填写。',
392
+ objectStorage: '对象存储',
393
+ objectStorageHint: '用于「通过 URL 传输图片」的 S3 兼容存储。访问密钥以 DSH 凭据保存,保存后不再显示。',
394
+ storageEndpoint: 'Endpoint',
395
+ storageBucket: 'Bucket',
396
+ storageAccessKeyId: 'Access Key ID',
397
+ storageSecretAccessKey: 'Secret Access Key',
398
+ storagePublicBase: '公网 URL 前缀(可选)',
399
+ storagePublicBaseHint: '自定义域名或 r2.dev 地址,用于生成永久公网链接。留空则使用临时预签名 URL。',
400
+ testStorage: '测试对象存储',
401
+ testingStorage: '测试中…',
402
+ storageKeyPair: 'Access Key ID 与 Secret Access Key 必须同时填写。',
360
403
  save: '保存设置',
361
404
  saving: '正在检查并应用…',
362
405
  reload: '重新加载',
363
406
  saved: '设置已保存并生效。',
407
+ unsavedChanges: '已更改但尚未保存',
408
+ discardChanges: '丢弃更改',
409
+ noUnsavedChanges: '设置项未变更',
364
410
  readOnly: '服务设置来自只读配置;如果 API 密钥可写,仍可在此保存密钥。',
365
411
  configured: '已就绪',
366
412
  missing: '未配置',
@@ -377,7 +423,7 @@ const zh = {
377
423
  connectionHint: '“测试 API 连接”只请求 GET /models;“测试视觉模型”会发送插件自带的诊断图片,验证一次真实多模态调用。',
378
424
  saveBeforeTesting: '修改服务配置后,请先保存,再执行 API 或视觉模型测试。',
379
425
  advanced: '高级设置',
380
- advancedHint: '凭据名称、服务兼容参数、结果语言、资源限制、运行环境来源、Python 和额外可读目录。一般无需修改。',
426
+ advancedHint: '凭据名称、服务兼容参数、结果语言、资源限制、默认保存目录、运行环境来源、Python 和额外可读目录。一般无需修改。',
381
427
  imageInput: '图片输入',
382
428
  hiddenVariants: '透明变体路由',
383
429
  hiddenVariantsLabel: '保留原模型名并自动启用图片能力',
@@ -621,6 +667,9 @@ function VisionIcon({ kind = 'scan' }) {
621
667
  : 'M3 5V3h2M11 3h2v2M13 11v2h-2M5 13H3v-2M5 8h6';
622
668
  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 }) }));
623
669
  }
670
+ function ReloadIcon() {
671
+ return ((0, jsx_runtime_1.jsxs)("svg", { viewBox: "0 0 24 24", width: "14", height: "14", "aria-hidden": "true", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }), (0, jsx_runtime_1.jsx)("path", { d: "M21 3v5h-5" })] }));
672
+ }
624
673
  function ToolShell({ block, title, summary, icon, children, t, }) {
625
674
  const [open, setOpen] = (0, react_1.useState)(true);
626
675
  const status = statusText(block, t);
@@ -865,6 +914,20 @@ class VisionSettingsController {
865
914
  this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
866
915
  }
867
916
  }
917
+ async testStorage() {
918
+ this.set({ ...this.state, action: 'test-storage', error: undefined, message: undefined });
919
+ try {
920
+ const result = await apiRequest({
921
+ method: 'POST',
922
+ headers: { 'Content-Type': 'application/json' },
923
+ body: JSON.stringify({ action: 'test-storage' }),
924
+ });
925
+ this.set({ ...this.state, action: undefined, storageTest: result });
926
+ }
927
+ catch (error) {
928
+ this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
929
+ }
930
+ }
868
931
  async checkUpdate() {
869
932
  this.set({ ...this.state, action: 'check-update', error: undefined, message: undefined });
870
933
  try {
@@ -929,6 +992,8 @@ function emptyProviderDraft(defaults) {
929
992
  protocol: 'openai',
930
993
  anthropicThinking: 'omit',
931
994
  userAgent: DEFAULT_USER_AGENT,
995
+ stream: false,
996
+ uploadViaUrl: false,
932
997
  t1Seconds: '90',
933
998
  t2Seconds: '90',
934
999
  maxImageBytes: String(defaults.maxImageBytes),
@@ -950,6 +1015,8 @@ function providerDraftOf(value, fallback, defaults) {
950
1015
  protocol: source?.protocol ?? 'openai',
951
1016
  anthropicThinking: source?.anthropicThinking ?? 'omit',
952
1017
  userAgent: source?.userAgent ?? DEFAULT_USER_AGENT,
1018
+ stream: source?.stream === true,
1019
+ uploadViaUrl: source?.uploadViaUrl === true,
953
1020
  t1Seconds: String(source?.t1Seconds ?? 90),
954
1021
  t2Seconds: String(source?.t2Seconds ?? 90),
955
1022
  maxImageBytes: String(source?.maxImageBytes ?? defaults.maxImageBytes),
@@ -977,9 +1044,13 @@ function draftOf(value) {
977
1044
  maxImageBytes: String(globalDefaults.maxImageBytes),
978
1045
  maxImagePixels: String(globalDefaults.maxImagePixels),
979
1046
  concurrency: String(globalDefaults.concurrency),
1047
+ storageEndpoint: value.objectStorage?.endpoint ?? '',
1048
+ storageBucket: value.objectStorage?.bucket ?? '',
1049
+ storagePublicBase: value.objectStorage?.publicBase ?? '',
980
1050
  runtimeMode: value.runtime?.mode ?? 'managed',
981
1051
  toolkitPath: value.runtime?.agentVisionToolkitPath ?? '',
982
1052
  python: value.runtime?.python ?? '',
1053
+ storageDir: value.storageDir ?? '',
983
1054
  allowedDirs: (value.allowedDirs ?? []).join('\n'),
984
1055
  hiddenVariants: value.imageInputVariants?.hidden ?? true,
985
1056
  variantEnabled: value.imageInputVariants?.enabled ?? true,
@@ -1016,6 +1087,8 @@ function valueOf(draft, t) {
1016
1087
  protocol: provider.protocol,
1017
1088
  anthropicThinking: provider.anthropicThinking,
1018
1089
  ...(provider.userAgent.trim() === DEFAULT_USER_AGENT ? {} : { userAgent: provider.userAgent.trim() }),
1090
+ ...(provider.stream ? { stream: true } : {}),
1091
+ ...(provider.uploadViaUrl ? { uploadViaUrl: true } : {}),
1019
1092
  ...(provider.t1Seconds.trim().length === 0 ? {} : { t1Seconds: positiveInteger(provider.t1Seconds, t('t1'), t) }),
1020
1093
  ...(provider.t2Seconds.trim().length === 0 ? {} : { t2Seconds: positiveInteger(provider.t2Seconds, t('t2'), t) }),
1021
1094
  ...(provider.maxImageBytes.trim().length === 0 ? {} : { maxImageBytes: positiveInteger(provider.maxImageBytes, t('maxBytes'), t) }),
@@ -1032,6 +1105,8 @@ function valueOf(draft, t) {
1032
1105
  protocol: primary?.protocol ?? 'openai',
1033
1106
  anthropicThinking: primary?.anthropicThinking ?? 'omit',
1034
1107
  userAgent: primary?.userAgent.trim() || DEFAULT_USER_AGENT,
1108
+ stream: primary?.stream === true,
1109
+ uploadViaUrl: primary?.uploadViaUrl === true,
1035
1110
  },
1036
1111
  providers,
1037
1112
  language: draft.language,
@@ -1041,11 +1116,22 @@ function valueOf(draft, t) {
1041
1116
  maxImageBytes: positiveInteger(draft.maxImageBytes, t('maxBytes'), t),
1042
1117
  maxImagePixels: positiveInteger(draft.maxImagePixels, t('maxPixels'), t),
1043
1118
  concurrency: positiveInteger(draft.concurrency, t('concurrency'), t),
1119
+ ...(draft.storageEndpoint.trim().length === 0 && draft.storageBucket.trim().length === 0
1120
+ ? {}
1121
+ : {
1122
+ objectStorage: {
1123
+ endpoint: draft.storageEndpoint.trim(),
1124
+ bucket: draft.storageBucket.trim(),
1125
+ credential: OBJECT_STORAGE_CREDENTIAL,
1126
+ ...(draft.storagePublicBase.trim().length === 0 ? {} : { publicBase: draft.storagePublicBase.trim() }),
1127
+ },
1128
+ }),
1044
1129
  runtime: {
1045
1130
  mode: draft.runtimeMode,
1046
1131
  ...(draft.runtimeMode === 'external' ? { agentVisionToolkitPath: draft.toolkitPath.trim() } : {}),
1047
1132
  ...(draft.python.trim().length === 0 ? {} : { python: draft.python.trim() }),
1048
1133
  },
1134
+ ...(draft.storageDir.trim().length === 0 ? {} : { storageDir: draft.storageDir.trim() }),
1049
1135
  allowedDirs: draft.allowedDirs.split(/\r?\n/).map(entry => entry.trim()).filter(Boolean),
1050
1136
  imageInputVariants: {
1051
1137
  ...(draft.variantEnabled ? {} : { enabled: false }),
@@ -1177,6 +1263,7 @@ function LoadedSettings({ controller, t }) {
1177
1263
  const [draft, setDraft] = (0, react_1.useState)(undefined);
1178
1264
  const [selectedIndex, setSelectedIndex] = (0, react_1.useState)(0);
1179
1265
  const [keys, setKeys] = (0, react_1.useState)({});
1266
+ const [storageKeys, setStorageKeys] = (0, react_1.useState)({ accessKeyId: '', secretAccessKey: '' });
1180
1267
  const [draftError, setDraftError] = (0, react_1.useState)(undefined);
1181
1268
  const [copiedCommand, setCopiedCommand] = (0, react_1.useState)(false);
1182
1269
  (0, react_1.useEffect)(() => { if (state.status === 'idle')
@@ -1296,14 +1383,25 @@ function LoadedSettings({ controller, t }) {
1296
1383
  return;
1297
1384
  }
1298
1385
  }
1386
+ const accessKeyId = storageKeys.accessKeyId.trim();
1387
+ const secretAccessKey = storageKeys.secretAccessKey.trim();
1388
+ if ((accessKeyId.length === 0) !== (secretAccessKey.length === 0)) {
1389
+ setDraftError(t('storageKeyPair'));
1390
+ return;
1391
+ }
1299
1392
  const credentials = Object.entries(keys)
1300
1393
  .filter(([, value]) => value.trim().length > 0)
1301
1394
  .map(([ref, value]) => ({ ref, value: value.trim() }));
1395
+ if (accessKeyId.length > 0 && (draft.storageEndpoint.trim().length > 0 || draft.storageBucket.trim().length > 0)) {
1396
+ credentials.push({ ref: OBJECT_STORAGE_CREDENTIAL, value: `${accessKeyId}:${secretAccessKey}` });
1397
+ }
1302
1398
  const savedRefs = new Set(credentials.map(credential => credential.ref));
1303
1399
  setDraftError(undefined);
1304
1400
  void controller.save(valueOf(draft, t), snapshot.settings.revision, credentials, snapshot.writable).then(saved => {
1305
1401
  if (saved) {
1306
1402
  setKeys(current => Object.fromEntries(Object.entries(current).filter(([ref]) => !savedRefs.has(ref))));
1403
+ if (savedRefs.has(OBJECT_STORAGE_CREDENTIAL))
1404
+ setStorageKeys({ accessKeyId: '', secretAccessKey: '' });
1307
1405
  }
1308
1406
  });
1309
1407
  }
@@ -1311,6 +1409,12 @@ function LoadedSettings({ controller, t }) {
1311
1409
  setDraftError(error instanceof Error ? error.message : String(error));
1312
1410
  }
1313
1411
  };
1412
+ const discard = () => {
1413
+ setDraft(draftOf(snapshot.settings.value));
1414
+ setKeys({});
1415
+ setStorageKeys({ accessKeyId: '', secretAccessKey: '' });
1416
+ setDraftError(undefined);
1417
+ };
1314
1418
  const busy = state.action !== undefined;
1315
1419
  const selectedProvider = draft.providers[selectedIndex];
1316
1420
  const providerCredentials = snapshot.credentials ?? [];
@@ -1318,14 +1422,18 @@ function LoadedSettings({ controller, t }) {
1318
1422
  const selectedKey = selectedProvider === undefined ? '' : (keys[selectedProvider.credential] ?? '');
1319
1423
  const selectedKeyLocked = selectedCredential !== undefined && !selectedCredential.writable;
1320
1424
  const selectedBuiltInFree = selectedCredential?.source === 'built-in-free';
1321
- const canSave = snapshot.writable || Object.values(keys).some(value => value.trim().length > 0);
1425
+ const objectStorageCredential = snapshot.objectStorageCredential ?? { ref: '', configured: false, writable: false };
1426
+ const canSave = snapshot.writable || Object.values(keys).some(value => value.trim().length > 0) || storageKeys.accessKeyId.trim().length > 0 || storageKeys.secretAccessKey.trim().length > 0;
1322
1427
  const runtimeErrorTitle = snapshot.runtime.ready ? t('runtimeCandidateRejected') : t('runtimeUnavailable');
1323
1428
  const pluginUpdate = state.update;
1324
1429
  const updateCapability = pluginUpdate ?? snapshot.release.update;
1325
1430
  const latestVersion = pluginUpdate?.latestVersion;
1326
1431
  const updateReason = updateCapability.reason === undefined ? undefined : t(UPDATE_REASON_KEYS[updateCapability.reason]);
1327
1432
  const updateCheckSupported = updateCapability.checkSupported ?? updateCapability.supported;
1328
- const updateHasUnsavedChanges = Object.values(keys).some(value => value.trim().length > 0) || settingsDraftChanged(draft, snapshot.settings.value, t);
1433
+ const updateHasUnsavedChanges = Object.values(keys).some(value => value.length > 0)
1434
+ || storageKeys.accessKeyId.length > 0
1435
+ || storageKeys.secretAccessKey.length > 0
1436
+ || settingsDraftChanged(draft, snapshot.settings.value, t);
1329
1437
  const manualUpdateProfile = updateCapability.profile ?? 'web';
1330
1438
  const manualUpdateCommand = `dsh plugin --profile ${manualUpdateProfile} add @mengruo/dsh-vision-toolkit@latest --registry=https://registry.npmjs.org/`;
1331
1439
  const copyManualUpdate = () => {
@@ -1344,28 +1452,29 @@ function LoadedSettings({ controller, t }) {
1344
1452
  void controller.applyUpdate(latestVersion);
1345
1453
  };
1346
1454
  const providerHealth = state.providerHealth?.[selectedIndex];
1347
- 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, state.message === 'restarting' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('restarting', { version: state.restart.toVersion }) }) : null, state.message === 'manual-restart-required' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('manualRestartRequired', { version: state.restart.toVersion }) }) : 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.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('providers') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-select", children: [(0, jsx_runtime_1.jsx)("select", { "aria-label": t('providers'), disabled: !snapshot.writable || busy, value: selectedIndex, onChange: (event) => { setSelectedIndex(Number(event.target.value)); }, children: draft.providers.map((provider, index) => ((0, jsx_runtime_1.jsxs)("option", { value: index, children: [index + 1, ". ", provider.name.trim() || provider.model.trim() || t('provider')] }, index))) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: !snapshot.writable || busy || draft.providers.length >= 32, onClick: addProvider, children: t('addProvider') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: !snapshot.writable || busy || draft.providers.length <= 1, onClick: () => { removeProvider(selectedIndex); }, children: t('removeProvider') })] }), selectedProvider === undefined ? null : ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-head", children: [(0, jsx_runtime_1.jsxs)("label", { className: "dvt-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: selectedProvider.enabled, disabled: !snapshot.writable || busy, onChange: (event) => { updateProvider(selectedIndex, 'enabled', event.target.checked); } }), (0, jsx_runtime_1.jsx)("span", { children: t('enabled') })] }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-provider-priority", children: t('priority', { index: selectedIndex + 1 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('name'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('name'), value: selectedProvider.name, onChange: (event) => { updateProvider(selectedIndex, 'name', event.target.value); }, placeholder: selectedProvider.model || t('model') }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('model'), disabled: !snapshot.writable || busy, value: selectedProvider.model, onChange: (event) => { updateProvider(selectedIndex, 'model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('baseUrl'), disabled: !snapshot.writable || busy, value: selectedProvider.baseUrl, onChange: (event) => { updateProvider(selectedIndex, 'baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('protocol'), disabled: !snapshot.writable || busy, value: selectedProvider.protocol, onChange: (event) => { updateProvider(selectedIndex, '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" })] }) }), selectedProvider.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: selectedProvider.anthropicThinking, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('userAgent'), value: selectedProvider.userAgent, onChange: (event) => { updateProvider(selectedIndex, 'userAgent', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('t1'), hint: t('t1Hint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('t1'), inputMode: "numeric", value: selectedProvider.t1Seconds, onChange: (event) => { updateProvider(selectedIndex, 't1Seconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('t2'), hint: t('t2Hint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('t2'), inputMode: "numeric", value: selectedProvider.t2Seconds, onChange: (event) => { updateProvider(selectedIndex, 't2Seconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('maxBytes'), inputMode: "numeric", value: selectedProvider.maxImageBytes, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('maxPixels'), inputMode: "numeric", value: selectedProvider.maxImagePixels, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('concurrency'), inputMode: "numeric", value: selectedProvider.concurrency, onChange: (event) => { updateProvider(selectedIndex, 'concurrency', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('attempts'), hint: t('attemptsHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('attempts'), inputMode: "numeric", value: selectedProvider.attempts, onChange: (event) => { updateProvider(selectedIndex, 'attempts', event.target.value); } }) })] }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: selectedBuiltInFree ? t('apiKeyBuiltInFree') : selectedKeyLocked ? t('apiKeyLocked') : t('apiKeyHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || selectedKeyLocked || selectedBuiltInFree, placeholder: selectedCredential?.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: selectedKey, onChange: (event) => { setKeys(current => ({ ...current, [selectedProvider.credential]: event.target.value })); setDraftError(undefined); } }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-tests", 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('connection', selectedIndex); }, children: state.action === 'connection' ? t('testing') : t('testConnection') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('model', selectedIndex); }, children: state.action === 'model' ? t('testingModel') : t('testModel') })] }), providerHealth === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-provider-test-result", children: ['service', 'model'].map(name => {
1455
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-save-dock", "data-dirty": updateHasUnsavedChanges ? 'true' : undefined, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "dvt-save-dock-reload", "aria-label": t('reload'), title: t('reload'), disabled: busy, onClick: () => { void controller.load(); }, children: (0, jsx_runtime_1.jsx)(ReloadIcon, {}) }), updateHasUnsavedChanges ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-save-dock-text", children: t('unsavedChanges') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy, onClick: discard, children: t('discardChanges') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: !canSave || busy, onClick: save, children: state.action === 'save' ? t('saving') : t('save') })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "dvt-save-dock-text", children: t('noUnsavedChanges') }))] }), (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, state.message === 'restarting' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('restarting', { version: state.restart.toVersion }) }) : null, state.message === 'manual-restart-required' && state.restart !== undefined ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert success", children: t('manualRestartRequired', { version: state.restart.toVersion }) }) : 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.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('providers') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-select", children: [(0, jsx_runtime_1.jsx)("select", { "aria-label": t('providers'), disabled: !snapshot.writable || busy, value: selectedIndex, onChange: (event) => { setSelectedIndex(Number(event.target.value)); }, children: draft.providers.map((provider, index) => ((0, jsx_runtime_1.jsxs)("option", { value: index, children: [index + 1, ". ", provider.name.trim() || provider.model.trim() || t('provider')] }, index))) }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: !snapshot.writable || busy || draft.providers.length >= 32, onClick: addProvider, children: t('addProvider') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: !snapshot.writable || busy || draft.providers.length <= 1, onClick: () => { removeProvider(selectedIndex); }, children: t('removeProvider') })] }), selectedProvider === undefined ? null : ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-card", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-head", children: [(0, jsx_runtime_1.jsxs)("label", { className: "dvt-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: selectedProvider.enabled, disabled: !snapshot.writable || busy, onChange: (event) => { updateProvider(selectedIndex, 'enabled', event.target.checked); } }), (0, jsx_runtime_1.jsx)("span", { children: t('enabled') })] }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-provider-priority", children: t('priority', { index: selectedIndex + 1 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('name'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('name'), value: selectedProvider.name, onChange: (event) => { updateProvider(selectedIndex, 'name', event.target.value); }, placeholder: selectedProvider.model || t('model') }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('model'), disabled: !snapshot.writable || busy, value: selectedProvider.model, onChange: (event) => { updateProvider(selectedIndex, 'model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('baseUrl'), disabled: !snapshot.writable || busy, value: selectedProvider.baseUrl, onChange: (event) => { updateProvider(selectedIndex, 'baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('protocol'), disabled: !snapshot.writable || busy, value: selectedProvider.protocol, onChange: (event) => { updateProvider(selectedIndex, '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" })] }) }), selectedProvider.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: selectedProvider.anthropicThinking, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('userAgent'), value: selectedProvider.userAgent, onChange: (event) => { updateProvider(selectedIndex, 'userAgent', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('stream'), hint: t('streamHint'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('stream'), disabled: !snapshot.writable || busy, value: selectedProvider.stream ? 'enabled' : 'disabled', onChange: (event) => { updateProvider(selectedIndex, 'stream', event.target.value === 'enabled'); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "disabled", children: t('disabled') }), (0, jsx_runtime_1.jsx)("option", { value: "enabled", children: t('enabled') })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('uploadViaUrl'), hint: t('uploadViaUrlHint'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('uploadViaUrl'), disabled: !snapshot.writable || busy, value: selectedProvider.uploadViaUrl ? 'enabled' : 'disabled', onChange: (event) => { updateProvider(selectedIndex, 'uploadViaUrl', event.target.value === 'enabled'); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "disabled", children: t('disabled') }), (0, jsx_runtime_1.jsx)("option", { value: "enabled", children: t('enabled') })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('t1'), hint: t('t1Hint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('t1'), inputMode: "numeric", value: selectedProvider.t1Seconds, onChange: (event) => { updateProvider(selectedIndex, 't1Seconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('t2'), hint: t('t2Hint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('t2'), inputMode: "numeric", value: selectedProvider.t2Seconds, onChange: (event) => { updateProvider(selectedIndex, 't2Seconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('maxBytes'), inputMode: "numeric", value: selectedProvider.maxImageBytes, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('maxPixels'), inputMode: "numeric", value: selectedProvider.maxImagePixels, onChange: (event) => { updateProvider(selectedIndex, '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, { "aria-label": t('concurrency'), inputMode: "numeric", value: selectedProvider.concurrency, onChange: (event) => { updateProvider(selectedIndex, 'concurrency', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('attempts'), hint: t('attemptsHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('attempts'), inputMode: "numeric", value: selectedProvider.attempts, onChange: (event) => { updateProvider(selectedIndex, 'attempts', event.target.value); } }) })] }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: selectedBuiltInFree ? t('apiKeyBuiltInFree') : selectedKeyLocked ? t('apiKeyLocked') : t('apiKeyHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || selectedKeyLocked || selectedBuiltInFree, placeholder: selectedCredential?.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: selectedKey, onChange: (event) => { setKeys(current => ({ ...current, [selectedProvider.credential]: event.target.value })); setDraftError(undefined); } }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-tests", 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('connection', selectedIndex); }, children: state.action === 'connection' ? t('testing') : t('testConnection') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "primary", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('model', selectedIndex); }, children: state.action === 'model' ? t('testingModel') : t('testModel') })] }), providerHealth === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-provider-test-result", children: ['service', 'model'].map(name => {
1348
1456
  const check = providerHealth.checks[name];
1349
1457
  if (check === undefined)
1350
1458
  return null;
1351
1459
  return (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);
1352
- }) })] }))] }), (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.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('prioritySort') }), (0, jsx_runtime_1.jsx)("p", { children: t('prioritySortHint') })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dvt-sorter", children: draft.providers.map((provider, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-sorter-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-sorter-index", children: index + 1 }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-sorter-name", children: provider.name.trim() || provider.model.trim() || t('provider') }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": t('moveUp'), title: t('moveUp'), disabled: !snapshot.writable || busy || index === 0, onClick: () => { moveProvider(index, -1); }, children: "\u2191" }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": t('moveDown'), title: t('moveDown'), disabled: !snapshot.writable || busy || index === draft.providers.length - 1, onClick: () => { moveProvider(index, 1); }, children: "\u2193" })] })] }, index))) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel dvt-update-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('runtimeCheck') }), (0, jsx_runtime_1.jsx)("p", { children: t('runtimeCheckHint') })] }), (0, jsx_runtime_1.jsx)("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('health'); }, children: state.action === 'health' ? t('testing') : t('runHealth') }) })] }), 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: ['python', 'dependencies', 'chrome'].map(name => {
1460
+ }) })] }))] }), (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.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('prioritySort') }), (0, jsx_runtime_1.jsx)("p", { children: t('prioritySortHint') })] }) }), (0, jsx_runtime_1.jsx)("div", { className: "dvt-sorter", children: draft.providers.map((provider, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "dvt-sorter-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "dvt-sorter-index", children: index + 1 }), (0, jsx_runtime_1.jsx)("span", { className: "dvt-sorter-name", children: provider.name.trim() || provider.model.trim() || t('provider') }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-provider-actions", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": t('moveUp'), title: t('moveUp'), disabled: !snapshot.writable || busy || index === 0, onClick: () => { moveProvider(index, -1); }, children: "\u2191" }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": t('moveDown'), title: t('moveDown'), disabled: !snapshot.writable || busy || index === draft.providers.length - 1, onClick: () => { moveProvider(index, 1); }, children: "\u2193" })] })] }, index))) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel dvt-update-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('runtimeCheck') }), (0, jsx_runtime_1.jsx)("p", { children: t('runtimeCheckHint') })] }), (0, jsx_runtime_1.jsx)("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('health'); }, children: state.action === 'health' ? t('testing') : t('runHealth') }) })] }), 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: ['python', 'dependencies', 'chrome'].map(name => {
1353
1461
  const check = state.health?.checks[name];
1354
1462
  if (check === undefined)
1355
1463
  return null;
1356
1464
  return (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);
1357
- }) }), (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('updates') }), (0, jsx_runtime_1.jsx)("p", { children: t('updatesHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${pluginUpdate?.updateAvailable ? 'warning' : pluginUpdate !== undefined && pluginUpdate.supported ? 'ok' : ''}`, children: pluginUpdate?.updateAvailable ? t('updateAvailable') : pluginUpdate !== undefined && pluginUpdate.supported ? t('upToDate') : t('pluginVersion') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-update-grid", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateInstalled') }), (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.pluginVersion })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateLatest') }), (0, jsx_runtime_1.jsx)("strong", { children: latestVersion ?? '—' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateProfile') }), (0, jsx_runtime_1.jsx)("strong", { children: updateCapability.profile ?? '—' })] })] }), !updateCapability.supported ? (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert warning", children: [(0, jsx_runtime_1.jsx)("strong", { children: t('updateUnsupported') }), (0, jsx_runtime_1.jsx)("span", { children: updateReason })] }) : null, updateCapability.supported && updateHasUnsavedChanges ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('updateSaveFirst') }) : null, pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('updateAvailableDetail', { version: latestVersion }) }) : null, pluginUpdate?.supported && !pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('upToDateDetail', { version: latestVersion }) }) : null, (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('manualUpdateHint') }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-manual-update", children: [(0, jsx_runtime_1.jsx)("code", { children: manualUpdateCommand }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", onClick: copyManualUpdate, children: copiedCommand ? t('copied') : t('copy') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy || !updateCheckSupported || state.restart !== undefined, onClick: () => { void controller.checkUpdate(); }, children: state.action === 'check-update' ? t('checkingUpdate') : t('checkUpdate') }), pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: busy || state.restart !== undefined || updateHasUnsavedChanges, onClick: applyUpdate, children: state.action === 'apply-update' ? t('updatingPlugin') : t('updateNow') }) : null] })] }), (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('language') }) }), (0, jsx_runtime_1.jsx)("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.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('hardTimeout'), hint: t('hardTimeoutHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('hardTimeout'), inputMode: "numeric", value: draft.hardTimeoutSeconds, onChange: (event) => { update('hardTimeoutSeconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('sessionConcurrency'), hint: t('sessionConcurrencyHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('sessionConcurrency'), inputMode: "numeric", value: draft.sessionMaxConcurrency, onChange: (event) => { update('sessionMaxConcurrency', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('minAvailable'), hint: t('minAvailableHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('minAvailable'), inputMode: "numeric", value: draft.minAvailableSeconds, onChange: (event) => { update('minAvailableSeconds', 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('imageInput') }) }), (0, jsx_runtime_1.jsxs)("label", { className: "dvt-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: draft.hiddenVariants, disabled: !snapshot.writable || busy, onChange: (event) => { update('hiddenVariants', event.target.checked); } }), (0, jsx_runtime_1.jsx)("span", { children: t('hiddenVariantsLabel') }), (0, jsx_runtime_1.jsx)("small", { children: t('hiddenVariantsHint') })] })] }), (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 }) })] })] })] })] }));
1465
+ }) }), (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('updates') }), (0, jsx_runtime_1.jsx)("p", { children: t('updatesHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${pluginUpdate?.updateAvailable ? 'warning' : pluginUpdate !== undefined && pluginUpdate.supported ? 'ok' : ''}`, children: pluginUpdate?.updateAvailable ? t('updateAvailable') : pluginUpdate !== undefined && pluginUpdate.supported ? t('upToDate') : t('pluginVersion') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-update-grid", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateInstalled') }), (0, jsx_runtime_1.jsx)("strong", { children: snapshot.release.pluginVersion })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateLatest') }), (0, jsx_runtime_1.jsx)("strong", { children: latestVersion ?? '—' })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { children: t('updateProfile') }), (0, jsx_runtime_1.jsx)("strong", { children: updateCapability.profile ?? '—' })] })] }), !updateCapability.supported ? (0, jsx_runtime_1.jsxs)("div", { className: "dvt-alert warning", children: [(0, jsx_runtime_1.jsx)("strong", { children: t('updateUnsupported') }), (0, jsx_runtime_1.jsx)("span", { children: updateReason })] }) : null, updateCapability.supported && updateHasUnsavedChanges ? (0, jsx_runtime_1.jsx)("div", { className: "dvt-alert warning", children: t('updateSaveFirst') }) : null, pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('updateAvailableDetail', { version: latestVersion }) }) : null, pluginUpdate?.supported && !pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('upToDateDetail', { version: latestVersion }) }) : null, (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('manualUpdateHint') }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-manual-update", children: [(0, jsx_runtime_1.jsx)("code", { children: manualUpdateCommand }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", onClick: copyManualUpdate, children: copiedCommand ? t('copied') : t('copy') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy || !updateCheckSupported || state.restart !== undefined, onClick: () => { void controller.checkUpdate(); }, children: state.action === 'check-update' ? t('checkingUpdate') : t('checkUpdate') }), pluginUpdate?.supported && pluginUpdate.updateAvailable && latestVersion !== undefined ? (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: busy || state.restart !== undefined || updateHasUnsavedChanges, onClick: applyUpdate, children: state.action === 'apply-update' ? t('updatingPlugin') : t('updateNow') }) : null] })] }), (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('language') }) }), (0, jsx_runtime_1.jsx)("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.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('hardTimeout'), hint: t('hardTimeoutHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('hardTimeout'), inputMode: "numeric", value: draft.hardTimeoutSeconds, onChange: (event) => { update('hardTimeoutSeconds', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('sessionConcurrency'), hint: t('sessionConcurrencyHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('sessionConcurrency'), inputMode: "numeric", value: draft.sessionMaxConcurrency, onChange: (event) => { update('sessionMaxConcurrency', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('minAvailable'), hint: t('minAvailableHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('minAvailable'), inputMode: "numeric", value: draft.minAvailableSeconds, onChange: (event) => { update('minAvailableSeconds', 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('imageInput') }) }), (0, jsx_runtime_1.jsxs)("label", { className: "dvt-check", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: draft.hiddenVariants, disabled: !snapshot.writable || busy, onChange: (event) => { update('hiddenVariants', event.target.checked); } }), (0, jsx_runtime_1.jsx)("span", { children: t('hiddenVariantsLabel') }), (0, jsx_runtime_1.jsx)("small", { children: t('hiddenVariantsHint') })] })] }), (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('storage') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('storageDir'), hint: t('storageDirHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storageDir'), placeholder: "/tmp/dsh-vision-toolkit", value: draft.storageDir, onChange: (event) => { update('storageDir', 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); } }) })] })] }), (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('objectStorage') }), (0, jsx_runtime_1.jsx)("p", { children: t('objectStorageHint') })] }), (0, jsx_runtime_1.jsx)("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.testStorage(); }, children: state.action === 'test-storage' ? t('testingStorage') : t('testStorage') }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('storageEndpoint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storageEndpoint'), disabled: !snapshot.writable || busy, value: draft.storageEndpoint, onChange: (event) => { update('storageEndpoint', event.target.value); }, placeholder: "https://<account>.r2.cloudflarestorage.com" }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('storageBucket'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storageBucket'), disabled: !snapshot.writable || busy, value: draft.storageBucket, onChange: (event) => { update('storageBucket', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('storageAccessKeyId'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storageAccessKeyId'), type: "password", autoComplete: "new-password", disabled: busy || !snapshot.writable, placeholder: objectStorageCredential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: storageKeys.accessKeyId, onChange: (event) => { setStorageKeys(current => ({ ...current, accessKeyId: event.target.value })); setDraftError(undefined); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('storageSecretAccessKey'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storageSecretAccessKey'), type: "password", autoComplete: "new-password", disabled: busy || !snapshot.writable, placeholder: objectStorageCredential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: storageKeys.secretAccessKey, onChange: (event) => { setStorageKeys(current => ({ ...current, secretAccessKey: event.target.value })); setDraftError(undefined); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('storagePublicBase'), hint: t('storagePublicBaseHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('storagePublicBase'), disabled: !snapshot.writable || busy, value: draft.storagePublicBase, onChange: (event) => { update('storagePublicBase', event.target.value); }, placeholder: "https://cdn.example.com" }) })] }), state.storageTest === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dvt-provider-test-result", children: (0, jsx_runtime_1.jsxs)("div", { "data-status": "ok", children: [(0, jsx_runtime_1.jsx)("span", { children: t('objectStorage') }), (0, jsx_runtime_1.jsx)("strong", { children: t('statusOk') }), (0, jsx_runtime_1.jsx)("p", { children: state.storageTest.detail })] }) })] }), (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); } }) })] }), 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 }) })] })] })] })] }));
1358
1466
  }
1359
1467
  const CSS = `
1360
1468
  .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)}
1361
1469
  .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}
1362
1470
  .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}
1363
1471
  .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)}
1364
- .dvt-tutorial-link{margin:0;font-size:12px;line-height:1.5}.dvt-tutorial-link a{color:var(--dsw-alias-state-business-primary);text-decoration:none;font-weight:600}.dvt-tutorial-link a:hover{text-decoration:underline}.dvt-manual-update{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2)}.dvt-manual-update code{flex:1;min-width:0;overflow:auto;white-space:nowrap;font-size:11px;color:var(--dsw-alias-label-primary)}.dvt-settings{display:grid;grid-template-columns:minmax(0,1fr);width:100%;max-width:900px;min-width:0;box-sizing:border-box;gap:14px;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;grid-template-columns:minmax(0,1fr);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;flex-wrap:wrap;gap:12px}.dvt-panel-title>div:first-child{flex:1 1 320px;min-width:0}.dvt-panel-title>.dvt-actions{margin-left:auto;justify-content:flex-end}.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.warning{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}.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;min-width:0;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-check{display:grid;gap:6px;cursor:pointer}.dvt-check input{width:auto}.dvt-check>span{font-size:12px;font-weight:600}.dvt-check>small{font-size:10px;color:var(--dsw-alias-label-secondary);line-height:1.4}.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}.dvt-update-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.dvt-update-grid>div{display:grid;gap:3px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2)}.dvt-update-grid span{font-size:9px;text-transform:uppercase;letter-spacing:.04em;color:var(--dsw-alias-label-caption)}.dvt-update-grid strong{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1472
+ .dvt-tutorial-link{margin:0;font-size:12px;line-height:1.5}.dvt-tutorial-link a{color:var(--dsw-alias-state-business-primary);text-decoration:none;font-weight:600}.dvt-tutorial-link a:hover{text-decoration:underline}.dvt-manual-update{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2)}.dvt-manual-update code{flex:1;min-width:0;overflow:auto;white-space:nowrap;font-size:11px;color:var(--dsw-alias-label-primary)}.dvt-settings{display:flex;flex-direction:column;width:100%;max-width:900px;min-width:0;box-sizing:border-box;gap:14px;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;grid-template-columns:minmax(0,1fr);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;flex-wrap:wrap;gap:12px}.dvt-panel-title>div:first-child{flex:1 1 320px;min-width:0}.dvt-panel-title>.dvt-actions{margin-left:auto;justify-content:flex-end}.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.warning{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}.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;min-width:0;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-check{display:grid;gap:6px;cursor:pointer}.dvt-check input{width:auto}.dvt-check>span{font-size:12px;font-weight:600}.dvt-check>small{font-size:10px;color:var(--dsw-alias-label-secondary);line-height:1.4}.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-update-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.dvt-update-grid>div{display:grid;gap:3px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2)}.dvt-update-grid span{font-size:9px;text-transform:uppercase;letter-spacing:.04em;color:var(--dsw-alias-label-caption)}.dvt-update-grid strong{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1365
1473
  .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;grid-template-columns:minmax(0,1fr);gap:12px;padding:0 12px 12px}.dvt-advanced-body>.dvt-panel{box-shadow:none}
1366
1474
  .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-test-tag{display:inline-flex;margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);font-size:9px;font-style:normal;font-weight:600;color:var(--dsw-alias-label-secondary)}.dvt-health-test-tag[data-status=ok]{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary)}.dvt-health-test-tag[data-status=warning]{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}.dvt-health-test-tag[data-status=error]{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);color:var(--dsw-alias-state-error-primary)}.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)}
1367
1475
  .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}
1368
1476
  .dvt-provider-card{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:9px 10px;display:grid;gap:8px;background:var(--dsw-alias-bg-layer-2)}.dvt-provider-head{display:flex;align-items:center;gap:10px}.dvt-provider-priority{margin-left:auto;font-size:11px;color:var(--dsw-alias-label-secondary);white-space:nowrap}.dvt-provider-actions{display:flex;gap:4px}.dvt-provider-actions button{width:26px;height:26px;display:grid;place-items:center;border:1px solid var(--dsw-alias-border-l1);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;font-size:14px;line-height:1}.dvt-provider-actions button:hover{background:var(--dsw-alias-interactive-bg-hover)}.dvt-provider-actions button:disabled{opacity:.35;cursor:default}.dvt-provider-select{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.dvt-provider-select select{flex:1;min-width:180px;height:36px;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:0 10px}.dvt-provider-tests{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.dvt-provider-test-result{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.dvt-provider-test-result>div{padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border-left:3px solid var(--dsw-alias-border-l4)}.dvt-provider-test-result>div[data-status=ok]{border-left-color:var(--dsw-alias-state-success-primary)}.dvt-provider-test-result>div[data-status=warning],.dvt-provider-test-result>div[data-status=not_tested]{border-left-color:var(--dsw-alias-state-warn-primary)}.dvt-provider-test-result>div[data-status=error]{border-left-color:var(--dsw-alias-state-error-primary)}.dvt-provider-test-result span{font-size:10px;text-transform:capitalize}.dvt-provider-test-result strong{float:right;font-size:9px;text-transform:uppercase;color:var(--dsw-alias-label-secondary)}.dvt-provider-test-result p{clear:both;margin:5px 0 0;font-size:10px;line-height:1.4;color:var(--dsw-alias-label-secondary)}.dvt-sorter{display:grid;gap:6px}.dvt-sorter-row{display:flex;align-items:center;gap:10px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}.dvt-sorter-index{flex:none;width:24px;height:24px;display:grid;place-items:center;border-radius:6px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-state-business-primary);font-size:11px;font-weight:700}.dvt-sorter-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}
1477
+ .dvt-save-dock{position:sticky;top:8px;align-self:flex-end;z-index:50;display:flex;align-items:center;gap:10px;max-width:calc(100% - 16px);padding:11px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv1)}.dvt-save-dock[data-dirty]{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 45%,var(--dsw-alias-border-l1))}.dvt-save-dock-text{font-size:12px;color:var(--dsw-alias-label-secondary)}.dvt-save-dock[data-dirty] .dvt-save-dock-text{color:var(--dsw-alias-state-warn-label);font-weight:600}.dvt-save-dock-reload{flex:none;display:grid;place-items:center;width:26px;height:26px;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:0}.dvt-save-dock-reload:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dvt-save-dock-reload:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.dvt-save-dock-reload:disabled{opacity:.4;cursor:default}
1369
1478
  @media(max-width:720px){.dvt-settings-footer{display:grid}.dvt-release{width:auto}.dvt-form-grid,.dvt-update-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}}
1370
1479
  `;
1371
1480
  function installStyles() {
@@ -1717,6 +1826,9 @@ async function responseJson(response) {
1717
1826
  function pasteLabel(file, index) {
1718
1827
  return file.name.trim() || `clipboard-image-${index + 1}`;
1719
1828
  }
1829
+ function occurrenceEnd(occurrence) {
1830
+ return occurrence.offset + (occurrence.length ?? 1);
1831
+ }
1720
1832
  /** Owns browser File objects until DSH serializes the corresponding text references. */
1721
1833
  class PasteImageController {
1722
1834
  ctx;
@@ -1780,6 +1892,7 @@ class PasteImageController {
1780
1892
  cursor = this.insertText(input, ' ', cursor);
1781
1893
  for (const [index, file] of files.entries()) {
1782
1894
  const ref = id();
1895
+ const label = pasteLabel(file, index);
1783
1896
  const record = { ref, file, batch, status: 'ready' };
1784
1897
  batch.records.push(record);
1785
1898
  this.records.set(ref, record);
@@ -1787,12 +1900,15 @@ class PasteImageController {
1787
1900
  const accepted = input.insertReference({
1788
1901
  source: SOURCE,
1789
1902
  ref,
1790
- label: pasteLabel(file, index),
1791
- clipboardText: `[pasted image: ${pasteLabel(file, index)}]`,
1903
+ label,
1904
+ clipboardText: `[pasted image: ${label}]`,
1792
1905
  }, { start: cursor, end: cursor, draftRev: snapshot.draftRev });
1793
1906
  if (!accepted)
1794
1907
  throw new Error('The composer changed before pasted images could be inserted');
1795
- cursor += 1;
1908
+ const inserted = input.state.getSnapshot().occurrences.find(occurrence => occurrence.source === SOURCE && occurrence.ref === ref);
1909
+ if (inserted === undefined)
1910
+ throw new Error('The pasted image reference was not present after insertion');
1911
+ cursor = occurrenceEnd(inserted);
1796
1912
  const hasNext = index + 1 < files.length;
1797
1913
  const suffix = input.state.getSnapshot().draft.slice(cursor);
1798
1914
  if (hasNext || (suffix !== '' && !/^\s/u.test(suffix)))
@@ -2132,7 +2248,7 @@ class PasteImageController {
2132
2248
  return;
2133
2249
  const accepted = input.insertText('', {
2134
2250
  start: current.offset,
2135
- end: current.offset + 1,
2251
+ end: occurrenceEnd(current),
2136
2252
  draftRev: snapshot.draftRev,
2137
2253
  });
2138
2254
  if (!accepted)