@mengruo/dsh-vision-toolkit 0.0.1 → 0.1.1

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 (51) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +17 -42
  3. package/README.zh.md +16 -41
  4. package/assets/logo_eapi_dark.png +0 -0
  5. package/docs/aihubmix-gemini-vision.i18n.yaml +2 -2
  6. package/docs/aihubmix-gemini-vision.md +2 -2
  7. package/docs/aihubmix-gemini-vision.zh.md +2 -2
  8. package/lib/client.js +246 -68
  9. package/lib/client.js.map +1 -1
  10. package/lib/config.js +143 -24
  11. package/lib/config.js.map +1 -1
  12. package/lib/evidence-cache.js +14 -0
  13. package/lib/evidence-cache.js.map +1 -1
  14. package/lib/image-input-variants.js +48 -24
  15. package/lib/image-input-variants.js.map +1 -1
  16. package/lib/runtime-install.js +204 -19
  17. package/lib/runtime-install.js.map +1 -1
  18. package/lib/runtime.js +310 -154
  19. package/lib/runtime.js.map +1 -1
  20. package/lib/tools.js +1 -0
  21. package/lib/tools.js.map +1 -1
  22. package/lib/types/client/index.d.ts +45 -3
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/config.d.ts +53 -0
  25. package/lib/types/config.d.ts.map +1 -1
  26. package/lib/types/evidence-cache.d.ts.map +1 -1
  27. package/lib/types/image-input-variants.d.ts +9 -4
  28. package/lib/types/image-input-variants.d.ts.map +1 -1
  29. package/lib/types/runtime-install.d.ts +21 -0
  30. package/lib/types/runtime-install.d.ts.map +1 -1
  31. package/lib/types/runtime.d.ts +45 -10
  32. package/lib/types/runtime.d.ts.map +1 -1
  33. package/lib/types/tools.d.ts.map +1 -1
  34. package/lib/types/upstream.d.ts +3 -0
  35. package/lib/types/upstream.d.ts.map +1 -1
  36. package/lib/types/web.d.ts +8 -0
  37. package/lib/types/web.d.ts.map +1 -1
  38. package/lib/upstream.js +11 -6
  39. package/lib/upstream.js.map +1 -1
  40. package/lib/web.js +50 -7
  41. package/lib/web.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/client/index.tsx +331 -88
  44. package/src/config.ts +210 -28
  45. package/src/evidence-cache.ts +14 -0
  46. package/src/image-input-variants.ts +54 -22
  47. package/src/runtime-install.ts +231 -20
  48. package/src/runtime.ts +333 -180
  49. package/src/tools.ts +1 -0
  50. package/src/upstream.ts +15 -8
  51. package/src/web.ts +67 -8
package/lib/client.js CHANGED
@@ -87,15 +87,28 @@ 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
- const AIHUBMIX_TUTORIAL_URL_EN = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/aihubmix-gemini-vision.md';
91
- const AIHUBMIX_TUTORIAL_URL_ZH = 'https://github.com/Anionex/dsh-vision-toolkit/blob/main/docs/aihubmix-gemini-vision.zh.md';
92
90
  const en = {
93
91
  nav: 'Vision',
94
92
  settingsTitle: 'Vision Toolkit',
95
93
  settingsIntro: 'Configure the pinned visual engineering runtime, its external vision endpoint, and local safety limits.',
96
94
  externalNotice: 'Remote tools send the selected image bytes to the configured external vision API. Local crop, trace, pixel diff, palette, foreground extraction, and HTML rendering do not upload images.',
97
95
  provider: 'Vision service',
98
- providerHint: 'Choose the API protocol, then provide the service address, model, and API key used by online vision features.',
96
+ providerHint: 'Add multiple API endpoints and order them by failover priority. A failed or rate-limited request automatically moves to the next provider; when every provider fails, the request reports an error.',
97
+ providers: 'Vision providers',
98
+ name: 'Label',
99
+ enabled: 'Enabled',
100
+ priority: 'Priority {index}',
101
+ attempts: 'Attempts',
102
+ attemptsHint: 'Total attempts against this provider before failing over to the next one.',
103
+ addProvider: 'Add provider',
104
+ moveUp: 'Move up',
105
+ moveDown: 'Move down',
106
+ removeProvider: 'Remove',
107
+ prioritySort: 'Provider priority',
108
+ prioritySortHint: 'Order the providers by failover priority. The top one is tried first; request order here is the priority order.',
109
+ runtimeCheck: 'Runtime check',
110
+ runtimeCheckHint: 'Checks the local Python environment (with its libraries) and the browser used for HTML screenshots.',
111
+ apiKeyBuiltInFree: 'The built-in free provider needs no user key.',
99
112
  aihubmixTutorial: 'Need an AIHubMix key for free Gemini 3.7 Flash vision? Follow the signup guide →',
100
113
  baseUrl: 'Base URL',
101
114
  apiKey: 'API key',
@@ -280,7 +293,22 @@ const zh = {
280
293
  settingsIntro: '在这里设置视觉模型服务、工具运行环境,以及图片和文件的本地访问范围。',
281
294
  externalNotice: '使用图像理解、目标定位、界面检测或文字识别等在线功能时,所选图片会发送到下方配置的视觉服务。图片裁剪、轮廓描摹、像素对比、主色提取、前景提取和网页截图均在本机完成,不会上传图片。',
282
295
  provider: '在线视觉服务',
283
- providerHint: '选择接口协议后,填写在线视觉功能使用的 API 地址、模型名称和 API 密钥。',
296
+ providerHint: '添加多个 API 并按故障转移优先级排序;某个 API 失败或限流后自动切换到下一个,全部失败才返回错误。',
297
+ providers: '视觉服务列表',
298
+ name: '名称',
299
+ enabled: '启用',
300
+ priority: '优先级 {index}',
301
+ attempts: '尝试次数',
302
+ attemptsHint: '该服务在故障转移到下一个之前最多尝试的次数。',
303
+ addProvider: '添加服务',
304
+ moveUp: '上移',
305
+ moveDown: '下移',
306
+ removeProvider: '移除',
307
+ prioritySort: '服务优先级',
308
+ prioritySortHint: '按故障转移优先级排列服务;排在最上面的最先尝试,这里的顺序就是优先级顺序。',
309
+ runtimeCheck: '运行检查',
310
+ runtimeCheckHint: '检查本地 Python 环境(含依赖库)和用于网页截图的浏览器。',
311
+ apiKeyBuiltInFree: '内置免费服务无需填写密钥。',
284
312
  aihubmixTutorial: '想申请 AIHubMix Key,并用免费 Gemini 3.7 Flash 识图?看这篇图文教程 →',
285
313
  baseUrl: 'API 地址',
286
314
  apiKey: 'API 密钥',
@@ -730,7 +758,7 @@ class VisionSettingsController {
730
758
  return;
731
759
  void this.load();
732
760
  }
733
- async save(value, expectedRevision, credentialValue, writeSettings) {
761
+ async save(value, expectedRevision, credentials, writeSettings) {
734
762
  this.set({ ...this.state, action: 'save', error: undefined, message: undefined });
735
763
  let snapshot = this.state.snapshot;
736
764
  try {
@@ -743,7 +771,7 @@ class VisionSettingsController {
743
771
  }
744
772
  if (snapshot === undefined)
745
773
  throw new Error('Vision Toolkit Settings are unavailable');
746
- if (credentialValue !== undefined) {
774
+ for (const credential of credentials) {
747
775
  try {
748
776
  snapshot = await apiRequest({
749
777
  method: 'POST',
@@ -751,8 +779,8 @@ class VisionSettingsController {
751
779
  body: JSON.stringify({
752
780
  action: 'credential',
753
781
  expectedRevision: snapshot.settings.revision,
754
- ref: snapshot.credential.ref,
755
- value: credentialValue,
782
+ ref: credential.ref,
783
+ value: credential.value,
756
784
  }),
757
785
  });
758
786
  }
@@ -761,6 +789,7 @@ class VisionSettingsController {
761
789
  status: 'ready',
762
790
  snapshot,
763
791
  health: this.state.health,
792
+ providerHealth: this.state.providerHealth,
764
793
  update: this.state.update,
765
794
  restart: this.state.restart,
766
795
  error: error instanceof Error ? error.message : String(error),
@@ -772,6 +801,7 @@ class VisionSettingsController {
772
801
  status: 'ready',
773
802
  snapshot,
774
803
  health: this.state.health,
804
+ providerHealth: this.state.providerHealth,
775
805
  update: this.state.update,
776
806
  restart: this.state.restart,
777
807
  message: 'saved',
@@ -788,7 +818,7 @@ class VisionSettingsController {
788
818
  (0, display_config_ts_1.resetDisplayConfigCache)();
789
819
  }
790
820
  }
791
- async runHealth(mode) {
821
+ async runHealth(mode, providerIndex) {
792
822
  const testConnection = mode !== 'health';
793
823
  const testModel = mode === 'model';
794
824
  this.set({ ...this.state, action: mode, error: undefined, message: undefined });
@@ -796,9 +826,20 @@ class VisionSettingsController {
796
826
  const health = await apiRequest({
797
827
  method: 'POST',
798
828
  headers: { 'Content-Type': 'application/json' },
799
- body: JSON.stringify({ action: 'health', testConnection, testModel }),
829
+ body: JSON.stringify({
830
+ action: 'health',
831
+ testConnection,
832
+ testModel,
833
+ ...(providerIndex === undefined ? {} : { providerIndex }),
834
+ }),
800
835
  });
801
- this.set({ ...this.state, action: undefined, health });
836
+ if (providerIndex === undefined) {
837
+ this.set({ ...this.state, action: undefined, health });
838
+ }
839
+ else {
840
+ const providerHealth = { ...(this.state.providerHealth ?? {}), [providerIndex]: health };
841
+ this.set({ ...this.state, action: undefined, providerHealth });
842
+ }
802
843
  }
803
844
  catch (error) {
804
845
  this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) });
@@ -842,19 +883,77 @@ class VisionSettingsController {
842
883
  }
843
884
  }
844
885
  exports.VisionSettingsController = VisionSettingsController;
886
+ function randomProviderId() {
887
+ const bytes = new Uint8Array(6);
888
+ if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
889
+ crypto.getRandomValues(bytes);
890
+ }
891
+ else {
892
+ for (let i = 0; i < bytes.length; i++)
893
+ bytes[i] = Math.floor(Math.random() * 256);
894
+ }
895
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('').toUpperCase();
896
+ }
897
+ function autoCredentialName(id) {
898
+ return `VISION_PROVIDER_${id}`;
899
+ }
900
+ function emptyProviderDraft(defaults) {
901
+ const id = randomProviderId();
902
+ return {
903
+ id,
904
+ name: '',
905
+ enabled: true,
906
+ baseUrl: '',
907
+ credential: autoCredentialName(id),
908
+ model: '',
909
+ protocol: 'openai',
910
+ anthropicThinking: 'omit',
911
+ userAgent: DEFAULT_USER_AGENT,
912
+ timeoutMs: String(defaults.timeoutMs),
913
+ maxImageBytes: String(defaults.maxImageBytes),
914
+ maxImagePixels: String(defaults.maxImagePixels),
915
+ concurrency: String(defaults.concurrency),
916
+ attempts: '3',
917
+ };
918
+ }
919
+ function providerDraftOf(value, fallback, defaults) {
920
+ const source = value ?? fallback;
921
+ const id = source?.id ?? '';
922
+ return {
923
+ id,
924
+ name: source?.name ?? '',
925
+ enabled: source?.enabled !== false,
926
+ baseUrl: source?.baseUrl ?? '',
927
+ credential: source?.credential ?? autoCredentialName(id),
928
+ model: source?.model ?? '',
929
+ protocol: source?.protocol ?? 'openai',
930
+ anthropicThinking: source?.anthropicThinking ?? 'omit',
931
+ userAgent: source?.userAgent ?? DEFAULT_USER_AGENT,
932
+ timeoutMs: String(source?.timeoutMs ?? defaults.timeoutMs),
933
+ maxImageBytes: String(source?.maxImageBytes ?? defaults.maxImageBytes),
934
+ maxImagePixels: String(source?.maxImagePixels ?? defaults.maxImagePixels),
935
+ concurrency: String(source?.concurrency ?? defaults.concurrency),
936
+ attempts: String(source?.attempts ?? 3),
937
+ };
938
+ }
845
939
  function draftOf(value) {
940
+ const globalDefaults = {
941
+ timeoutMs: value.timeoutMs ?? 30000,
942
+ maxImageBytes: value.maxImageBytes ?? 4194304,
943
+ maxImagePixels: value.maxImagePixels ?? 20000000,
944
+ concurrency: value.concurrency ?? 4,
945
+ };
946
+ const configured = value.providers ?? [];
947
+ const providers = configured.length > 0
948
+ ? configured.map(entry => providerDraftOf(entry, undefined, globalDefaults))
949
+ : [providerDraftOf(undefined, value.provider, globalDefaults)];
846
950
  return {
847
- baseUrl: value.provider?.baseUrl ?? BUILT_IN_FREE_VISION_BASE_URL,
848
- credential: value.provider?.credential ?? BUILT_IN_FREE_VISION_CREDENTIAL,
849
- model: value.provider?.model ?? BUILT_IN_FREE_VISION_MODEL,
850
- protocol: value.provider?.protocol ?? 'openai',
851
- anthropicThinking: value.provider?.anthropicThinking ?? 'omit',
852
- userAgent: value.provider?.userAgent ?? DEFAULT_USER_AGENT,
951
+ providers,
853
952
  language: value.language ?? 'zh',
854
- timeoutMs: String(value.timeoutMs ?? 30000),
855
- maxImageBytes: String(value.maxImageBytes ?? 4194304),
856
- maxImagePixels: String(value.maxImagePixels ?? 20000000),
857
- concurrency: String(value.concurrency ?? 4),
953
+ timeoutMs: String(globalDefaults.timeoutMs),
954
+ maxImageBytes: String(globalDefaults.maxImageBytes),
955
+ maxImagePixels: String(globalDefaults.maxImagePixels),
956
+ concurrency: String(globalDefaults.concurrency),
858
957
  runtimeMode: value.runtime?.mode ?? 'managed',
859
958
  toolkitPath: value.runtime?.agentVisionToolkitPath ?? '',
860
959
  python: value.runtime?.python ?? '',
@@ -884,15 +983,33 @@ function apiKeyFailure(value, t) {
884
983
  return undefined;
885
984
  }
886
985
  function valueOf(draft, t) {
986
+ const providers = draft.providers.map(provider => ({
987
+ ...(provider.id.length === 0 ? {} : { id: provider.id }),
988
+ ...(provider.name.trim().length === 0 ? {} : { name: provider.name.trim() }),
989
+ ...(provider.enabled ? {} : { enabled: false }),
990
+ ...(provider.baseUrl.trim().length === 0 ? {} : { baseUrl: provider.baseUrl.trim() }),
991
+ ...(provider.credential.trim().length === 0 ? {} : { credential: provider.credential.trim() }),
992
+ ...(provider.model.trim().length === 0 ? {} : { model: provider.model.trim() }),
993
+ protocol: provider.protocol,
994
+ anthropicThinking: provider.anthropicThinking,
995
+ ...(provider.userAgent.trim() === DEFAULT_USER_AGENT ? {} : { userAgent: provider.userAgent.trim() }),
996
+ ...(provider.timeoutMs.trim().length === 0 ? {} : { timeoutMs: positiveInteger(provider.timeoutMs, t('timeout'), t) }),
997
+ ...(provider.maxImageBytes.trim().length === 0 ? {} : { maxImageBytes: positiveInteger(provider.maxImageBytes, t('maxBytes'), t) }),
998
+ ...(provider.maxImagePixels.trim().length === 0 ? {} : { maxImagePixels: positiveInteger(provider.maxImagePixels, t('maxPixels'), t) }),
999
+ ...(provider.concurrency.trim().length === 0 ? {} : { concurrency: positiveInteger(provider.concurrency, t('concurrency'), t) }),
1000
+ ...(provider.attempts.trim().length === 0 ? {} : { attempts: positiveInteger(provider.attempts, t('attempts'), t) }),
1001
+ }));
1002
+ const primary = draft.providers[0];
887
1003
  return {
888
1004
  provider: {
889
- baseUrl: draft.baseUrl.trim(),
890
- credential: draft.credential.trim(),
891
- model: draft.model.trim(),
892
- protocol: draft.protocol,
893
- anthropicThinking: draft.anthropicThinking,
894
- userAgent: draft.userAgent.trim(),
1005
+ baseUrl: primary?.baseUrl.trim() || BUILT_IN_FREE_VISION_BASE_URL,
1006
+ credential: primary?.credential.trim() || BUILT_IN_FREE_VISION_CREDENTIAL,
1007
+ model: primary?.model.trim() || BUILT_IN_FREE_VISION_MODEL,
1008
+ protocol: primary?.protocol ?? 'openai',
1009
+ anthropicThinking: primary?.anthropicThinking ?? 'omit',
1010
+ userAgent: primary?.userAgent.trim() || DEFAULT_USER_AGENT,
895
1011
  },
1012
+ providers,
896
1013
  language: draft.language,
897
1014
  timeoutMs: positiveInteger(draft.timeoutMs, t('timeout'), t),
898
1015
  maxImageBytes: positiveInteger(draft.maxImageBytes, t('maxBytes'), t),
@@ -922,12 +1039,6 @@ function settingsDraftChanged(draft, saved, t) {
922
1039
  return true;
923
1040
  }
924
1041
  }
925
- function isBuiltInFreeVisionDraft(draft) {
926
- return draft.baseUrl.trim().replace(/\/+$/, '') === BUILT_IN_FREE_VISION_BASE_URL
927
- && draft.credential.trim() === BUILT_IN_FREE_VISION_CREDENTIAL
928
- && draft.model.trim() === BUILT_IN_FREE_VISION_MODEL
929
- && draft.protocol === 'openai';
930
- }
931
1042
  function Field({ label, children, hint }) {
932
1043
  return (0, jsx_runtime_1.jsxs)("label", { className: "dvt-field", children: [(0, jsx_runtime_1.jsx)("span", { children: label }), children, hint === undefined ? null : (0, jsx_runtime_1.jsx)("small", { children: hint })] });
933
1044
  }
@@ -1021,20 +1132,6 @@ function healthDetail(name, detail, t) {
1021
1132
  return t('healthModelFailed', { detail: match[1] });
1022
1133
  return detail;
1023
1134
  }
1024
- function modelTestTag(health, check) {
1025
- if (!health.modelTested)
1026
- return { status: 'warning', label: 'modelTestNotRunTag' };
1027
- if (check.status === 'ok')
1028
- return { status: 'ok', label: 'modelTestVerifiedTag' };
1029
- return { status: 'error', label: 'modelTestFailedTag' };
1030
- }
1031
- function credentialSource(source, t) {
1032
- if (source === 'env')
1033
- return t('sourceEnv');
1034
- if (source === 'file')
1035
- return t('sourceFile');
1036
- return source;
1037
- }
1038
1135
  const UPDATE_REASON_KEYS = {
1039
1136
  'profile-not-found': 'updateReasonProfileNotFound',
1040
1137
  'not-direct-dependency': 'updateReasonNotDependency',
@@ -1052,7 +1149,8 @@ function LoadedSettings({ controller, t }) {
1052
1149
  const state = (0, react_1.useSyncExternalStore)(controller.subscribe, controller.snapshot, controller.snapshot);
1053
1150
  const snapshot = state.snapshot;
1054
1151
  const [draft, setDraft] = (0, react_1.useState)(undefined);
1055
- const [apiKey, setApiKey] = (0, react_1.useState)('');
1152
+ const [selectedIndex, setSelectedIndex] = (0, react_1.useState)(0);
1153
+ const [keys, setKeys] = (0, react_1.useState)({});
1056
1154
  const [draftError, setDraftError] = (0, react_1.useState)(undefined);
1057
1155
  const [copiedCommand, setCopiedCommand] = (0, react_1.useState)(false);
1058
1156
  (0, react_1.useEffect)(() => { if (state.status === 'idle')
@@ -1100,40 +1198,111 @@ function LoadedSettings({ controller, t }) {
1100
1198
  return (0, jsx_runtime_1.jsxs)("div", { className: "dvt-settings", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-alert error", children: state.error ?? t('runtimeUnavailable') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", onClick: () => { void controller.load(); }, children: t('retry') })] });
1101
1199
  }
1102
1200
  const update = (key, value) => setDraft(current => current === undefined ? current : { ...current, [key]: value });
1201
+ const updateProvider = (index, key, value) => {
1202
+ setDraft(current => {
1203
+ if (current === undefined)
1204
+ return current;
1205
+ const providers = current.providers.map((provider, i) => i === index ? { ...provider, [key]: value } : provider);
1206
+ return { ...current, providers };
1207
+ });
1208
+ };
1209
+ const addProvider = () => {
1210
+ setDraft(current => current === undefined ? current : {
1211
+ ...current,
1212
+ providers: [...current.providers, emptyProviderDraft({
1213
+ timeoutMs: Number(current.timeoutMs) || 30000,
1214
+ maxImageBytes: Number(current.maxImageBytes) || 4194304,
1215
+ maxImagePixels: Number(current.maxImagePixels) || 20000000,
1216
+ concurrency: Number(current.concurrency) || 4,
1217
+ })],
1218
+ });
1219
+ setSelectedIndex(draft.providers.length);
1220
+ };
1221
+ const removeProvider = (index) => {
1222
+ const removed = draft.providers[index];
1223
+ if (removed !== undefined) {
1224
+ // Deleting a provider also deletes its stored credential.
1225
+ void apiRequest({
1226
+ method: 'POST',
1227
+ headers: { 'Content-Type': 'application/json' },
1228
+ body: JSON.stringify({ action: 'delete-credential', ref: removed.credential }),
1229
+ }).catch(() => { });
1230
+ }
1231
+ setDraft(current => current === undefined ? current : { ...current, providers: current.providers.filter((_, i) => i !== index) });
1232
+ setSelectedIndex(prev => {
1233
+ if (index < prev)
1234
+ return prev - 1;
1235
+ if (prev >= draft.providers.length - 1)
1236
+ return Math.max(0, draft.providers.length - 2);
1237
+ return prev;
1238
+ });
1239
+ };
1240
+ const moveProvider = (index, delta) => {
1241
+ setDraft(current => {
1242
+ if (current === undefined)
1243
+ return current;
1244
+ const target = index + delta;
1245
+ if (target < 0 || target >= current.providers.length)
1246
+ return current;
1247
+ const providers = [...current.providers];
1248
+ const [entry] = providers.splice(index, 1);
1249
+ if (entry === undefined)
1250
+ return current;
1251
+ providers.splice(target, 0, entry);
1252
+ return { ...current, providers };
1253
+ });
1254
+ setSelectedIndex(prev => {
1255
+ const target = index + delta;
1256
+ if (prev === index)
1257
+ return target;
1258
+ if (target < index && prev >= target && prev < index)
1259
+ return prev + 1;
1260
+ if (target > index && prev > index && prev <= target)
1261
+ return prev - 1;
1262
+ return prev;
1263
+ });
1264
+ };
1103
1265
  const save = () => {
1104
1266
  try {
1105
- const keyFailure = apiKeyFailure(apiKey, t);
1106
- if (keyFailure !== undefined) {
1107
- setDraftError(keyFailure);
1108
- return;
1267
+ for (const value of Object.values(keys)) {
1268
+ const failure = apiKeyFailure(value, t);
1269
+ if (failure !== undefined) {
1270
+ setDraftError(failure);
1271
+ return;
1272
+ }
1109
1273
  }
1110
- const credentialValue = apiKey.length === 0 ? undefined : apiKey.trim();
1274
+ const credentials = Object.entries(keys)
1275
+ .filter(([, value]) => value.trim().length > 0)
1276
+ .map(([ref, value]) => ({ ref, value: value.trim() }));
1277
+ const savedRefs = new Set(credentials.map(credential => credential.ref));
1111
1278
  setDraftError(undefined);
1112
- void controller.save(valueOf(draft, t), snapshot.settings.revision, credentialValue, snapshot.writable).then(saved => { if (saved)
1113
- setApiKey(''); });
1279
+ void controller.save(valueOf(draft, t), snapshot.settings.revision, credentials, snapshot.writable).then(saved => {
1280
+ if (saved) {
1281
+ setKeys(current => Object.fromEntries(Object.entries(current).filter(([ref]) => !savedRefs.has(ref))));
1282
+ }
1283
+ });
1114
1284
  }
1115
1285
  catch (error) {
1116
1286
  setDraftError(error instanceof Error ? error.message : String(error));
1117
1287
  }
1118
1288
  };
1119
1289
  const busy = state.action !== undefined;
1120
- const credentialMatchesSnapshot = draft.credential.trim() === snapshot.credential.ref;
1121
- const builtInCredentialChangedProvider = snapshot.credential.source === 'built-in-free'
1122
- && !isBuiltInFreeVisionDraft(draft);
1123
- const keyLocked = credentialMatchesSnapshot
1124
- && !snapshot.credential.writable
1125
- && !builtInCredentialChangedProvider;
1126
- const canSave = snapshot.writable || (apiKey.length > 0 && !keyLocked);
1290
+ const selectedProvider = draft.providers[selectedIndex];
1291
+ const providerCredentials = snapshot.credentials ?? [];
1292
+ const selectedCredential = providerCredentials[selectedIndex];
1293
+ const selectedKey = selectedProvider === undefined ? '' : (keys[selectedProvider.credential] ?? '');
1294
+ const selectedKeyLocked = selectedCredential !== undefined && !selectedCredential.writable;
1295
+ const selectedBuiltInFree = selectedCredential?.source === 'built-in-free';
1296
+ const canSave = snapshot.writable || Object.values(keys).some(value => value.trim().length > 0);
1127
1297
  const runtimeErrorTitle = snapshot.runtime.ready ? t('runtimeCandidateRejected') : t('runtimeUnavailable');
1128
1298
  const pluginUpdate = state.update;
1129
1299
  const updateCapability = pluginUpdate ?? snapshot.release.update;
1130
1300
  const latestVersion = pluginUpdate?.latestVersion;
1131
1301
  const updateReason = updateCapability.reason === undefined ? undefined : t(UPDATE_REASON_KEYS[updateCapability.reason]);
1132
1302
  const updateCheckSupported = updateCapability.checkSupported ?? updateCapability.supported;
1133
- const updateHasUnsavedChanges = apiKey.length > 0 || settingsDraftChanged(draft, snapshot.settings.value, t);
1303
+ const updateHasUnsavedChanges = Object.values(keys).some(value => value.trim().length > 0) || settingsDraftChanged(draft, snapshot.settings.value, t);
1134
1304
  const manualUpdateProfile = updateCapability.profile ?? 'web';
1135
1305
  const manualUpdateCommand = `dsh plugin --profile ${manualUpdateProfile} add @mengruo/dsh-vision-toolkit@latest --registry=https://registry.npmjs.org/`;
1136
- const aihubmixTutorialUrl = draft?.language === 'en' ? AIHUBMIX_TUTORIAL_URL_EN : AIHUBMIX_TUTORIAL_URL_ZH;
1137
1306
  const copyManualUpdate = () => {
1138
1307
  void navigator.clipboard?.writeText(manualUpdateCommand)
1139
1308
  .then(() => {
@@ -1149,10 +1318,18 @@ function LoadedSettings({ controller, t }) {
1149
1318
  return;
1150
1319
  void controller.applyUpdate(latestVersion);
1151
1320
  };
1152
- 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.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('provider') }), (0, jsx_runtime_1.jsx)("p", { children: t('providerHint') })] }), (0, jsx_runtime_1.jsx)("span", { className: `dvt-badge ${snapshot.credential.configured ? 'ok' : 'error'}`, children: snapshot.credential.configured ? t('configured') : t('missing') })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-tutorial-link", children: (0, jsx_runtime_1.jsx)("a", { href: aihubmixTutorialUrl, target: "_blank", rel: "noreferrer", children: t('aihubmixTutorial') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('protocol'), children: (0, jsx_runtime_1.jsxs)("select", { disabled: !snapshot.writable || busy, value: draft.protocol, onChange: (event) => { update('protocol', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "openai", children: "OpenAI Chat Completions" }), (0, jsx_runtime_1.jsx)("option", { value: "anthropic", children: "Anthropic Messages" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('baseUrl'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.baseUrl, onChange: (event) => { update('baseUrl', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('model'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { disabled: !snapshot.writable || busy, value: draft.model, onChange: (event) => { update('model', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('apiKey'), hint: keyLocked ? t('apiKeyLocked') : snapshot.credential.source === undefined ? t('apiKeyHint') : `${t('apiKeyHint')} ${t('sourceHint', { source: t('source'), value: credentialSource(snapshot.credential.source, t) })}`, children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('apiKey'), type: "password", autoComplete: "new-password", disabled: busy || keyLocked, placeholder: snapshot.credential.configured ? t('apiKeyPlaceholderConfigured') : t('apiKeyPlaceholderMissing'), value: apiKey, onChange: (event) => { setApiKey(event.target.value); setDraftError(undefined); } }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-save-row", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "primary", disabled: !canSave || busy, onClick: save, children: state.action === 'save' ? t('saving') : t('save') }), (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { variant: "outline", disabled: busy, onClick: () => { void controller.load(); }, children: t('reload') })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dvt-panel-title", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: t('health') }), (0, jsx_runtime_1.jsx)("p", { children: t('connectionHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-actions", children: [(0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Button, { size: "sm", variant: "outline", disabled: busy || !snapshot.runtime.ready, onClick: () => { void controller.runHealth('health'); }, children: state.action === 'health' ? t('testing') : t('runHealth') }), (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'); }, 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'); }, children: state.action === 'model' ? t('testingModel') : t('testModel') })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('saveBeforeTesting') }), state.health === undefined ? (0, jsx_runtime_1.jsx)("p", { className: "dvt-muted", children: t('notTested') }) : (0, jsx_runtime_1.jsx)("div", { className: "dvt-health-grid", children: Object.entries(state.health.checks).map(([name, check]) => {
1153
- const testTag = name === 'model' ? modelTestTag(state.health, check) : undefined;
1154
- 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') }), testTag === undefined ? null : (0, jsx_runtime_1.jsx)("em", { className: "dvt-health-test-tag", "data-status": testTag.status, children: t(testTag.label) }), (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);
1155
- }) })] }), (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('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('provider') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('credential'), hint: t('credentialHint'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('credential'), disabled: !snapshot.writable || busy, value: draft.credential, onChange: (event) => { update('credential', event.target.value); } }) }), draft.protocol === 'anthropic' ? (0, jsx_runtime_1.jsx)(Field, { label: t('anthropicThinking'), hint: t('anthropicThinkingHint'), children: (0, jsx_runtime_1.jsxs)("select", { "aria-label": t('anthropicThinking'), value: draft.anthropicThinking, onChange: (event) => { update('anthropicThinking', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "omit", children: "omit (widest compatibility)" }), (0, jsx_runtime_1.jsx)("option", { value: "disabled", children: "disabled (model support required)" }), (0, jsx_runtime_1.jsx)("option", { value: "adaptive", children: "adaptive (model support required)" })] }) }) : null, (0, jsx_runtime_1.jsx)(Field, { label: t('userAgent'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { value: draft.userAgent, onChange: (event) => { update('userAgent', event.target.value); } }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dvt-panel-title", children: (0, jsx_runtime_1.jsx)("h3", { children: t('limits') }) }), (0, jsx_runtime_1.jsxs)("div", { className: "dvt-form-grid", children: [(0, jsx_runtime_1.jsx)(Field, { label: t('language'), children: (0, jsx_runtime_1.jsxs)("select", { value: draft.language, onChange: (event) => { update('language', event.target.value); }, children: [(0, jsx_runtime_1.jsx)("option", { value: "zh", children: "\u4E2D\u6587" }), (0, jsx_runtime_1.jsx)("option", { value: "en", children: "English" })] }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('timeout'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.timeoutMs, onChange: (event) => { update('timeoutMs', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImageBytes, onChange: (event) => { update('maxImageBytes', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxPixels'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.maxImagePixels, onChange: (event) => { update('maxImagePixels', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('concurrency'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { inputMode: "numeric", value: draft.concurrency, onChange: (event) => { update('concurrency', event.target.value); } }) })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dvt-panel", children: [(0, jsx_runtime_1.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 }) })] })] })] })] }));
1321
+ const providerHealth = state.providerHealth?.[selectedIndex];
1322
+ 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('timeout'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "aria-label": t('timeout'), inputMode: "numeric", value: selectedProvider.timeoutMs, onChange: (event) => { updateProvider(selectedIndex, 'timeoutMs', event.target.value); } }) }), (0, jsx_runtime_1.jsx)(Field, { label: t('maxBytes'), children: (0, jsx_runtime_1.jsx)(dsh_client_ui_primitives_1.Input, { "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 => {
1323
+ const check = providerHealth.checks[name];
1324
+ if (check === undefined)
1325
+ return null;
1326
+ 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);
1327
+ }) })] }))] }), (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 => {
1328
+ const check = state.health?.checks[name];
1329
+ if (check === undefined)
1330
+ return null;
1331
+ 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);
1332
+ }) }), (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('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 }) })] })] })] })] }));
1156
1333
  }
1157
1334
  const CSS = `
1158
1335
  .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)}
@@ -1163,6 +1340,7 @@ const CSS = `
1163
1340
  .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}
1164
1341
  .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)}
1165
1342
  .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}
1343
+ .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}
1166
1344
  @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}}
1167
1345
  `;
1168
1346
  function installStyles() {