@mengruo/dsh-vision-toolkit 0.1.4 → 0.1.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -70
- package/README.zh.md +11 -69
- package/assets/1.mp4 +0 -0
- package/assets/skill/SKILL.md +37 -5
- package/docs/plan-per-tool-visibility.md +82 -0
- package/lib/client.js +157 -6
- package/lib/client.js.map +1 -1
- package/lib/config.js +52 -0
- package/lib/config.js.map +1 -1
- package/lib/exposure.js +35 -8
- package/lib/exposure.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/object-storage.js +141 -0
- package/lib/object-storage.js.map +1 -0
- package/lib/paths.js +9 -0
- package/lib/paths.js.map +1 -1
- package/lib/runtime.js +314 -27
- package/lib/runtime.js.map +1 -1
- package/lib/tools.js +116 -4
- package/lib/tools.js.map +1 -1
- package/lib/types/client/index.d.ts +62 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/config.d.ts +59 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/exposure.d.ts +14 -2
- package/lib/types/exposure.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/object-storage.d.ts +54 -0
- package/lib/types/object-storage.d.ts.map +1 -0
- package/lib/types/paths.d.ts +7 -0
- package/lib/types/paths.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +50 -0
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/tools.d.ts +22 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/upstream.d.ts +1 -0
- package/lib/types/upstream.d.ts.map +1 -1
- package/lib/types/video.d.ts +78 -0
- package/lib/types/video.d.ts.map +1 -0
- package/lib/types/web.d.ts +8 -0
- package/lib/types/web.d.ts.map +1 -1
- package/lib/upstream.js +3 -0
- package/lib/upstream.js.map +1 -1
- package/lib/video.js +169 -0
- package/lib/video.js.map +1 -0
- package/lib/web.js +78 -6
- package/lib/web.js.map +1 -1
- package/package.json +4 -1
- package/src/client/index.tsx +222 -6
- package/src/config.ts +110 -0
- package/src/exposure.ts +34 -9
- package/src/index.ts +10 -5
- package/src/object-storage.ts +174 -0
- package/src/paths.ts +11 -0
- package/src/runtime.ts +332 -25
- package/src/tools.ts +144 -3
- package/src/upstream.ts +4 -0
- package/src/video.ts +222 -0
- package/src/web.ts +93 -7
- package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +11 -11
- package/vendor/agent-vision-toolkit/__pycache__/detect.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/__pycache__/ground.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/__pycache__/vision_client.cpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/bin/__pycache__/glancecpython-314.pyc +0 -0
- package/vendor/agent-vision-toolkit/bin/glance +8 -1
- package/vendor/agent-vision-toolkit/detect.py +13 -7
- package/vendor/agent-vision-toolkit/ground.py +43 -18
- package/vendor/agent-vision-toolkit/tests/test_vision_client.py +88 -0
- package/vendor/agent-vision-toolkit/vision_client.py +84 -6
- package/assets/community-group-qr.png +0 -0
- package/assets/logo_aihubmix.png +0 -0
- package/assets/logo_eapi_dark.png +0 -0
- package/assets/wechat-reward.png +0 -0
package/src/client/index.tsx
CHANGED
|
@@ -31,6 +31,8 @@ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKi
|
|
|
31
31
|
const BUILT_IN_FREE_VISION_BASE_URL = 'https://vision.anionex.me/v1'
|
|
32
32
|
const BUILT_IN_FREE_VISION_CREDENTIAL = 'ANIONEX_FREE_VISION'
|
|
33
33
|
const BUILT_IN_FREE_VISION_MODEL = 'gemini-3.7-flash'
|
|
34
|
+
// Fixed credential reference holding the combined "accessKeyId:secretAccessKey".
|
|
35
|
+
const OBJECT_STORAGE_CREDENTIAL = 'VISION_OBJECT_STORAGE'
|
|
34
36
|
|
|
35
37
|
const en = {
|
|
36
38
|
nav: 'Vision',
|
|
@@ -42,6 +44,7 @@ const en = {
|
|
|
42
44
|
providers: 'Vision providers',
|
|
43
45
|
name: 'Label',
|
|
44
46
|
enabled: 'Enabled',
|
|
47
|
+
disabled: 'Disabled',
|
|
45
48
|
priority: 'Priority {index}',
|
|
46
49
|
attempts: 'Attempts',
|
|
47
50
|
attemptsHint: 'Total attempts against this provider before failing over to the next one.',
|
|
@@ -70,6 +73,12 @@ const en = {
|
|
|
70
73
|
anthropicThinking: 'Anthropic thinking',
|
|
71
74
|
anthropicThinkingHint: 'omit has the broadest compatibility. Use disabled or adaptive only when the selected model documents that mode; restore omit first after HTTP 400.',
|
|
72
75
|
userAgent: 'User-Agent',
|
|
76
|
+
stream: 'Streaming',
|
|
77
|
+
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.',
|
|
78
|
+
uploadViaUrl: 'Transfer images via URL',
|
|
79
|
+
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.',
|
|
80
|
+
videoSupport: 'Enable video support',
|
|
81
|
+
videoSupportHint: 'OpenAI-compatible video understanding. This setting is hidden for Anthropic providers and is currently a placeholder with no effect yet.',
|
|
73
82
|
language: 'Output language',
|
|
74
83
|
limits: 'Timeout and concurrency',
|
|
75
84
|
timeout: 'Request timeout (ms)',
|
|
@@ -95,10 +104,24 @@ const en = {
|
|
|
95
104
|
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.',
|
|
96
105
|
allowedDirs: 'Additional allowed directories',
|
|
97
106
|
allowedDirsHint: 'One path per line. The session workspace is always allowed.',
|
|
107
|
+
objectStorage: 'Object storage',
|
|
108
|
+
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.',
|
|
109
|
+
storageEndpoint: 'Endpoint',
|
|
110
|
+
storageBucket: 'Bucket',
|
|
111
|
+
storageAccessKeyId: 'Access Key ID',
|
|
112
|
+
storageSecretAccessKey: 'Secret Access Key',
|
|
113
|
+
storagePublicBase: 'Public base URL (optional)',
|
|
114
|
+
storagePublicBaseHint: 'Custom domain or r2.dev URL for permanent public links. Leave blank to use temporary presigned URLs.',
|
|
115
|
+
testStorage: 'Test object storage',
|
|
116
|
+
testingStorage: 'Testing…',
|
|
117
|
+
storageKeyPair: 'Access Key ID and Secret Access Key must both be filled together.',
|
|
98
118
|
save: 'Save and apply',
|
|
99
119
|
saving: 'Validating runtime…',
|
|
100
120
|
reload: 'Reload',
|
|
101
121
|
saved: 'Settings validated and applied.',
|
|
122
|
+
unsavedChanges: 'Changed but not saved',
|
|
123
|
+
discardChanges: 'Discard changes',
|
|
124
|
+
noUnsavedChanges: 'Settings unchanged',
|
|
102
125
|
readOnly: 'Service settings are read-only. A writable API key can still be saved.',
|
|
103
126
|
configured: 'Configured',
|
|
104
127
|
missing: 'Missing',
|
|
@@ -110,8 +133,10 @@ const en = {
|
|
|
110
133
|
runHealth: 'Run health check',
|
|
111
134
|
testConnection: 'Test API connection',
|
|
112
135
|
testModel: 'Test vision model',
|
|
136
|
+
testVideo: 'Test video call',
|
|
113
137
|
testing: 'Checking…',
|
|
114
138
|
testingModel: 'Testing model…',
|
|
139
|
+
testingVideo: 'Testing video…',
|
|
115
140
|
connectionHint: 'The API connection test only queries GET /models. The vision model test sends the bundled diagnostic image and verifies one real multimodal request.',
|
|
116
141
|
saveBeforeTesting: 'Save service changes before testing the connection.',
|
|
117
142
|
advanced: 'Advanced settings',
|
|
@@ -120,6 +145,14 @@ const en = {
|
|
|
120
145
|
hiddenVariants: 'Transparent variant routing',
|
|
121
146
|
hiddenVariantsLabel: 'Keep the original model names and enable images automatically',
|
|
122
147
|
hiddenVariantsHint: 'Text-only models keep one model-selector entry with the original name while the session runs on the image-capable variant. Pasted images, image history, and the built-in read_image tool keep working; disable to restore the explicit (Vision Toolkit) entries.',
|
|
148
|
+
toolVisibility: 'Tool visibility',
|
|
149
|
+
toolVisibilityHint: 'Which groups of vision tools an Agent sees. Applies to the next Agent conversation: an already-active Agent keeps its activation-time set until it is reloaded.',
|
|
150
|
+
toolVisibilityLocal: 'Local tools',
|
|
151
|
+
toolVisibilityLocalHint: 'trace / crop / pixel diff / foreground / colors / HTML screenshot / video info. Local processing, no on-line parallel limits.',
|
|
152
|
+
toolVisibilityOnline: 'Online image tools',
|
|
153
|
+
toolVisibilityOnlineHint: 'glance / ground / detect / long-screenshot OCR, plus the concurrency/status probe. On-line service calls share the session concurrency budget.',
|
|
154
|
+
toolVisibilityVideo: 'Video tools',
|
|
155
|
+
toolVisibilityVideoHint: 'video understanding (experimental). Calling it without an enabled video-capable service returns "video understanding unavailable".',
|
|
123
156
|
pluginVersion: 'Plugin',
|
|
124
157
|
upstreamVersion: 'Upstream',
|
|
125
158
|
activeGeneration: 'Runtime generation',
|
|
@@ -258,6 +291,7 @@ const zh: Record<LocaleKey, string> = {
|
|
|
258
291
|
providers: '视觉服务列表',
|
|
259
292
|
name: '名称',
|
|
260
293
|
enabled: '启用',
|
|
294
|
+
disabled: '禁用',
|
|
261
295
|
priority: '优先级 {index}',
|
|
262
296
|
attempts: '尝试次数',
|
|
263
297
|
attemptsHint: '该服务在故障转移到下一个之前最多尝试的次数。',
|
|
@@ -286,6 +320,12 @@ const zh: Record<LocaleKey, string> = {
|
|
|
286
320
|
anthropicThinking: 'Anthropic thinking',
|
|
287
321
|
anthropicThinkingHint: 'omit 兼容性最好。仅当所选模型明确支持时使用 disabled 或 adaptive;遇到 HTTP 400 时先恢复 omit。',
|
|
288
322
|
userAgent: 'User-Agent',
|
|
323
|
+
stream: '流式请求',
|
|
324
|
+
streamHint: '以流式(SSE)方式请求补全,而非一次性返回 JSON。适合对非流式支持不佳、长输出易连接超时的端点。',
|
|
325
|
+
uploadViaUrl: '通过 URL 传输图片',
|
|
326
|
+
uploadViaUrlHint: '把每张图片上传到下方配置的对象存储,将 URL 传给模型而不是 base64。需要先配置对象存储;使用 region 裁剪时回退为 base64。',
|
|
327
|
+
videoSupport: '启用视频支持',
|
|
328
|
+
videoSupportHint: 'OpenAI 兼容接口的视频理解能力。Anthropic 服务不显示此项;当前为占位设置,尚未生效。',
|
|
289
329
|
language: '结果语言',
|
|
290
330
|
limits: '超时与并发限制',
|
|
291
331
|
timeout: '单次请求超时(毫秒)',
|
|
@@ -311,10 +351,24 @@ const zh: Record<LocaleKey, string> = {
|
|
|
311
351
|
storageDirHint: '留空时继续在各工作区创建 .dsh-vision-toolkit。在 POSIX 系统上填写绝对路径共享根目录(例如 /tmp/dsh-vision-toolkit)后,产物、粘贴图片和缓存会保存到自动生成的当前用户、工作区专属子目录中。Windows 共享目录会保持禁用,直到能够安全校验 ACL 权限。',
|
|
312
352
|
allowedDirs: '允许读取的其他目录',
|
|
313
353
|
allowedDirsHint: '每行填写一个目录。当前会话的工作目录始终可以读取,无需重复填写。',
|
|
354
|
+
objectStorage: '对象存储',
|
|
355
|
+
objectStorageHint: '用于「通过 URL 传输图片」的 S3 兼容存储。访问密钥以 DSH 凭据保存,保存后不再显示。',
|
|
356
|
+
storageEndpoint: 'Endpoint',
|
|
357
|
+
storageBucket: 'Bucket',
|
|
358
|
+
storageAccessKeyId: 'Access Key ID',
|
|
359
|
+
storageSecretAccessKey: 'Secret Access Key',
|
|
360
|
+
storagePublicBase: '公网 URL 前缀(可选)',
|
|
361
|
+
storagePublicBaseHint: '自定义域名或 r2.dev 地址,用于生成永久公网链接。留空则使用临时预签名 URL。',
|
|
362
|
+
testStorage: '测试对象存储',
|
|
363
|
+
testingStorage: '测试中…',
|
|
364
|
+
storageKeyPair: 'Access Key ID 与 Secret Access Key 必须同时填写。',
|
|
314
365
|
save: '保存设置',
|
|
315
366
|
saving: '正在检查并应用…',
|
|
316
367
|
reload: '重新加载',
|
|
317
368
|
saved: '设置已保存并生效。',
|
|
369
|
+
unsavedChanges: '已更改但尚未保存',
|
|
370
|
+
discardChanges: '丢弃更改',
|
|
371
|
+
noUnsavedChanges: '设置项未变更',
|
|
318
372
|
readOnly: '服务设置来自只读配置;如果 API 密钥可写,仍可在此保存密钥。',
|
|
319
373
|
configured: '已就绪',
|
|
320
374
|
missing: '未配置',
|
|
@@ -326,8 +380,10 @@ const zh: Record<LocaleKey, string> = {
|
|
|
326
380
|
runHealth: '检查本地环境',
|
|
327
381
|
testConnection: '测试 API 连接',
|
|
328
382
|
testModel: '测试视觉模型',
|
|
383
|
+
testVideo: '测试视频调用',
|
|
329
384
|
testing: '检查中…',
|
|
330
385
|
testingModel: '正在测试模型…',
|
|
386
|
+
testingVideo: '正在测试视频…',
|
|
331
387
|
connectionHint: '“测试 API 连接”只请求 GET /models;“测试视觉模型”会发送插件自带的诊断图片,验证一次真实多模态调用。',
|
|
332
388
|
saveBeforeTesting: '修改服务配置后,请先保存,再执行 API 或视觉模型测试。',
|
|
333
389
|
advanced: '高级设置',
|
|
@@ -336,6 +392,14 @@ const zh: Record<LocaleKey, string> = {
|
|
|
336
392
|
hiddenVariants: '透明变体路由',
|
|
337
393
|
hiddenVariantsLabel: '保留原模型名并自动启用图片能力',
|
|
338
394
|
hiddenVariantsHint: '文本模型在模型列表中只显示原名称,会话实际运行在支持图片的变体路由上:粘贴图片、历史图片和内置 read_image 工具均可正常使用。关闭后恢复显示显式的(Vision Toolkit)条目。',
|
|
395
|
+
toolVisibility: '工具可见性',
|
|
396
|
+
toolVisibilityHint: 'Agent 可见的视觉工具分组。仅在下一个 agent 对话中生效:已激活的 agent 保持激活时的工具集,直到重载。',
|
|
397
|
+
toolVisibilityLocal: '本地工具',
|
|
398
|
+
toolVisibilityLocalHint: 'trace / crop / 像素对比 / 前景提取 / 取色 / HTML 截图 / 视频信息。本地处理,不受联机并发限制。',
|
|
399
|
+
toolVisibilityOnline: '联机图片工具',
|
|
400
|
+
toolVisibilityOnlineHint: 'glance / ground / detect / 长截图 OCR,以及并发/状态查询。联机调用共享会话并发额度。',
|
|
401
|
+
toolVisibilityVideo: '视频工具',
|
|
402
|
+
toolVisibilityVideoHint: '视频理解(测试)。调用时若没有已启用且支持视频的服务,将返回“视频理解不可用”。',
|
|
339
403
|
pluginVersion: '插件版本',
|
|
340
404
|
upstreamVersion: '工具包版本',
|
|
341
405
|
activeGeneration: '本次运行已应用',
|
|
@@ -527,6 +591,9 @@ interface ProviderValue {
|
|
|
527
591
|
protocol?: 'openai' | 'anthropic'
|
|
528
592
|
anthropicThinking?: 'omit' | 'disabled' | 'adaptive'
|
|
529
593
|
userAgent?: string
|
|
594
|
+
stream?: boolean
|
|
595
|
+
uploadViaUrl?: boolean
|
|
596
|
+
videoSupport?: boolean
|
|
530
597
|
t1Seconds?: number
|
|
531
598
|
t2Seconds?: number
|
|
532
599
|
maxImageBytes?: number
|
|
@@ -543,6 +610,9 @@ interface SettingsValue {
|
|
|
543
610
|
protocol?: 'openai' | 'anthropic'
|
|
544
611
|
anthropicThinking?: 'omit' | 'disabled' | 'adaptive'
|
|
545
612
|
userAgent?: string
|
|
613
|
+
stream?: boolean
|
|
614
|
+
uploadViaUrl?: boolean
|
|
615
|
+
videoSupport?: boolean
|
|
546
616
|
}
|
|
547
617
|
providers?: ProviderValue[]
|
|
548
618
|
language?: 'zh' | 'en'
|
|
@@ -552,6 +622,12 @@ interface SettingsValue {
|
|
|
552
622
|
maxImageBytes?: number
|
|
553
623
|
maxImagePixels?: number
|
|
554
624
|
concurrency?: number
|
|
625
|
+
objectStorage?: {
|
|
626
|
+
endpoint?: string
|
|
627
|
+
bucket?: string
|
|
628
|
+
credential?: string
|
|
629
|
+
publicBase?: string
|
|
630
|
+
}
|
|
555
631
|
runtime?: { mode?: 'managed' | 'external'; agentVisionToolkitPath?: string; python?: string }
|
|
556
632
|
storageDir?: string
|
|
557
633
|
storageHistory?: string[]
|
|
@@ -562,6 +638,11 @@ interface SettingsValue {
|
|
|
562
638
|
autoSwitch?: boolean
|
|
563
639
|
hidden?: boolean
|
|
564
640
|
}
|
|
641
|
+
toolVisibility?: {
|
|
642
|
+
local?: boolean
|
|
643
|
+
online?: boolean
|
|
644
|
+
video?: boolean
|
|
645
|
+
}
|
|
565
646
|
}
|
|
566
647
|
|
|
567
648
|
type PluginUpdateUnavailableReason =
|
|
@@ -611,6 +692,7 @@ interface SettingsSnapshot {
|
|
|
611
692
|
settings: { value: SettingsValue; revision: number; applies: 'live' }
|
|
612
693
|
credential: { ref: string; configured: boolean; source?: string; writable: boolean }
|
|
613
694
|
credentials: Array<{ ref: string; configured: boolean; source?: string; writable: boolean }>
|
|
695
|
+
objectStorageCredential: { ref: string; configured: boolean; writable: boolean }
|
|
614
696
|
runtime: {
|
|
615
697
|
ready: boolean
|
|
616
698
|
generation: number
|
|
@@ -750,6 +832,15 @@ function VisionIcon({ kind = 'scan' }: { kind?: 'scan' | 'target' | 'layers' | '
|
|
|
750
832
|
)
|
|
751
833
|
}
|
|
752
834
|
|
|
835
|
+
function ReloadIcon() {
|
|
836
|
+
return (
|
|
837
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
838
|
+
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
|
839
|
+
<path d="M21 3v5h-5" />
|
|
840
|
+
</svg>
|
|
841
|
+
)
|
|
842
|
+
}
|
|
843
|
+
|
|
753
844
|
function ToolShell({
|
|
754
845
|
block, title, summary, icon, children, t,
|
|
755
846
|
}: {
|
|
@@ -988,7 +1079,9 @@ interface SettingsState {
|
|
|
988
1079
|
providerHealth?: Record<number, HealthResult> | undefined
|
|
989
1080
|
update?: PluginUpdateCheck | undefined
|
|
990
1081
|
restart?: PluginUpdateResult | undefined
|
|
991
|
-
|
|
1082
|
+
storageTest?: { detail: string } | undefined
|
|
1083
|
+
videoTest?: { detail: string } | undefined
|
|
1084
|
+
action?: 'save' | 'health' | 'connection' | 'model' | 'check-update' | 'apply-update' | 'test-storage' | 'test-video' | undefined
|
|
992
1085
|
message?: string | undefined
|
|
993
1086
|
error?: string | undefined
|
|
994
1087
|
}
|
|
@@ -1123,6 +1216,34 @@ export class VisionSettingsController {
|
|
|
1123
1216
|
}
|
|
1124
1217
|
}
|
|
1125
1218
|
|
|
1219
|
+
async testStorage(): Promise<void> {
|
|
1220
|
+
this.set({ ...this.state, action: 'test-storage', error: undefined, message: undefined })
|
|
1221
|
+
try {
|
|
1222
|
+
const result = await apiRequest<{ detail: string }>({
|
|
1223
|
+
method: 'POST',
|
|
1224
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1225
|
+
body: JSON.stringify({ action: 'test-storage' }),
|
|
1226
|
+
})
|
|
1227
|
+
this.set({ ...this.state, action: undefined, storageTest: result })
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) })
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async runVideoTest(providerIndex?: number): Promise<void> {
|
|
1234
|
+
this.set({ ...this.state, action: 'test-video', error: undefined, message: undefined })
|
|
1235
|
+
try {
|
|
1236
|
+
const result = await apiRequest<{ detail: string }>({
|
|
1237
|
+
method: 'POST',
|
|
1238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1239
|
+
body: JSON.stringify({ action: 'test-video', ...(providerIndex === undefined ? {} : { providerIndex }) }),
|
|
1240
|
+
})
|
|
1241
|
+
this.set({ ...this.state, action: undefined, videoTest: result })
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) })
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1126
1247
|
async checkUpdate(): Promise<void> {
|
|
1127
1248
|
this.set({ ...this.state, action: 'check-update', error: undefined, message: undefined })
|
|
1128
1249
|
try {
|
|
@@ -1171,6 +1292,9 @@ interface ProviderDraft {
|
|
|
1171
1292
|
protocol: 'openai' | 'anthropic'
|
|
1172
1293
|
anthropicThinking: 'omit' | 'disabled' | 'adaptive'
|
|
1173
1294
|
userAgent: string
|
|
1295
|
+
stream: boolean
|
|
1296
|
+
uploadViaUrl: boolean
|
|
1297
|
+
videoSupport: boolean
|
|
1174
1298
|
t1Seconds: string
|
|
1175
1299
|
t2Seconds: string
|
|
1176
1300
|
maxImageBytes: string
|
|
@@ -1188,6 +1312,9 @@ interface Draft {
|
|
|
1188
1312
|
maxImageBytes: string
|
|
1189
1313
|
maxImagePixels: string
|
|
1190
1314
|
concurrency: string
|
|
1315
|
+
storageEndpoint: string
|
|
1316
|
+
storageBucket: string
|
|
1317
|
+
storagePublicBase: string
|
|
1191
1318
|
runtimeMode: 'managed' | 'external'
|
|
1192
1319
|
toolkitPath: string
|
|
1193
1320
|
python: string
|
|
@@ -1197,6 +1324,9 @@ interface Draft {
|
|
|
1197
1324
|
variantEnabled: boolean
|
|
1198
1325
|
variantProviders: string
|
|
1199
1326
|
variantAutoSwitch: boolean
|
|
1327
|
+
toolVisibilityLocal: boolean
|
|
1328
|
+
toolVisibilityOnline: boolean
|
|
1329
|
+
toolVisibilityVideo: boolean
|
|
1200
1330
|
}
|
|
1201
1331
|
|
|
1202
1332
|
function randomProviderId(): string {
|
|
@@ -1231,6 +1361,9 @@ function emptyProviderDraft(defaults: ProviderDefaults): ProviderDraft {
|
|
|
1231
1361
|
protocol: 'openai',
|
|
1232
1362
|
anthropicThinking: 'omit',
|
|
1233
1363
|
userAgent: DEFAULT_USER_AGENT,
|
|
1364
|
+
stream: false,
|
|
1365
|
+
uploadViaUrl: false,
|
|
1366
|
+
videoSupport: false,
|
|
1234
1367
|
t1Seconds: '90',
|
|
1235
1368
|
t2Seconds: '90',
|
|
1236
1369
|
maxImageBytes: String(defaults.maxImageBytes),
|
|
@@ -1253,6 +1386,9 @@ function providerDraftOf(value: ProviderValue | undefined, fallback: ProviderVal
|
|
|
1253
1386
|
protocol: source?.protocol ?? 'openai',
|
|
1254
1387
|
anthropicThinking: source?.anthropicThinking ?? 'omit',
|
|
1255
1388
|
userAgent: source?.userAgent ?? DEFAULT_USER_AGENT,
|
|
1389
|
+
stream: source?.stream === true,
|
|
1390
|
+
uploadViaUrl: source?.uploadViaUrl === true,
|
|
1391
|
+
videoSupport: source?.videoSupport === true,
|
|
1256
1392
|
t1Seconds: String(source?.t1Seconds ?? 90),
|
|
1257
1393
|
t2Seconds: String(source?.t2Seconds ?? 90),
|
|
1258
1394
|
maxImageBytes: String(source?.maxImageBytes ?? defaults.maxImageBytes),
|
|
@@ -1281,6 +1417,9 @@ function draftOf(value: SettingsValue): Draft {
|
|
|
1281
1417
|
maxImageBytes: String(globalDefaults.maxImageBytes),
|
|
1282
1418
|
maxImagePixels: String(globalDefaults.maxImagePixels),
|
|
1283
1419
|
concurrency: String(globalDefaults.concurrency),
|
|
1420
|
+
storageEndpoint: value.objectStorage?.endpoint ?? '',
|
|
1421
|
+
storageBucket: value.objectStorage?.bucket ?? '',
|
|
1422
|
+
storagePublicBase: value.objectStorage?.publicBase ?? '',
|
|
1284
1423
|
runtimeMode: value.runtime?.mode ?? 'managed',
|
|
1285
1424
|
toolkitPath: value.runtime?.agentVisionToolkitPath ?? '',
|
|
1286
1425
|
python: value.runtime?.python ?? '',
|
|
@@ -1290,6 +1429,9 @@ function draftOf(value: SettingsValue): Draft {
|
|
|
1290
1429
|
variantEnabled: value.imageInputVariants?.enabled ?? true,
|
|
1291
1430
|
variantProviders: (value.imageInputVariants?.providers ?? []).join('\n'),
|
|
1292
1431
|
variantAutoSwitch: value.imageInputVariants?.autoSwitch ?? true,
|
|
1432
|
+
toolVisibilityLocal: value.toolVisibility?.local ?? true,
|
|
1433
|
+
toolVisibilityOnline: value.toolVisibility?.online ?? true,
|
|
1434
|
+
toolVisibilityVideo: value.toolVisibility?.video ?? false,
|
|
1293
1435
|
}
|
|
1294
1436
|
}
|
|
1295
1437
|
|
|
@@ -1320,6 +1462,9 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1320
1462
|
protocol: provider.protocol,
|
|
1321
1463
|
anthropicThinking: provider.anthropicThinking,
|
|
1322
1464
|
...(provider.userAgent.trim() === DEFAULT_USER_AGENT ? {} : { userAgent: provider.userAgent.trim() }),
|
|
1465
|
+
...(provider.stream ? { stream: true } : {}),
|
|
1466
|
+
...(provider.uploadViaUrl ? { uploadViaUrl: true } : {}),
|
|
1467
|
+
...(provider.videoSupport ? { videoSupport: true } : {}),
|
|
1323
1468
|
...(provider.t1Seconds.trim().length === 0 ? {} : { t1Seconds: positiveInteger(provider.t1Seconds, t('t1'), t) }),
|
|
1324
1469
|
...(provider.t2Seconds.trim().length === 0 ? {} : { t2Seconds: positiveInteger(provider.t2Seconds, t('t2'), t) }),
|
|
1325
1470
|
...(provider.maxImageBytes.trim().length === 0 ? {} : { maxImageBytes: positiveInteger(provider.maxImageBytes, t('maxBytes'), t) }),
|
|
@@ -1336,6 +1481,9 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1336
1481
|
protocol: primary?.protocol ?? 'openai',
|
|
1337
1482
|
anthropicThinking: primary?.anthropicThinking ?? 'omit',
|
|
1338
1483
|
userAgent: primary?.userAgent.trim() || DEFAULT_USER_AGENT,
|
|
1484
|
+
stream: primary?.stream === true,
|
|
1485
|
+
uploadViaUrl: primary?.uploadViaUrl === true,
|
|
1486
|
+
videoSupport: primary?.videoSupport === true,
|
|
1339
1487
|
},
|
|
1340
1488
|
providers,
|
|
1341
1489
|
language: draft.language,
|
|
@@ -1345,6 +1493,16 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1345
1493
|
maxImageBytes: positiveInteger(draft.maxImageBytes, t('maxBytes'), t),
|
|
1346
1494
|
maxImagePixels: positiveInteger(draft.maxImagePixels, t('maxPixels'), t),
|
|
1347
1495
|
concurrency: positiveInteger(draft.concurrency, t('concurrency'), t),
|
|
1496
|
+
...(draft.storageEndpoint.trim().length === 0 && draft.storageBucket.trim().length === 0
|
|
1497
|
+
? {}
|
|
1498
|
+
: {
|
|
1499
|
+
objectStorage: {
|
|
1500
|
+
endpoint: draft.storageEndpoint.trim(),
|
|
1501
|
+
bucket: draft.storageBucket.trim(),
|
|
1502
|
+
credential: OBJECT_STORAGE_CREDENTIAL,
|
|
1503
|
+
...(draft.storagePublicBase.trim().length === 0 ? {} : { publicBase: draft.storagePublicBase.trim() }),
|
|
1504
|
+
},
|
|
1505
|
+
}),
|
|
1348
1506
|
runtime: {
|
|
1349
1507
|
mode: draft.runtimeMode,
|
|
1350
1508
|
...(draft.runtimeMode === 'external' ? { agentVisionToolkitPath: draft.toolkitPath.trim() } : {}),
|
|
@@ -1360,6 +1518,11 @@ function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
|
1360
1518
|
...(draft.variantAutoSwitch ? {} : { autoSwitch: false }),
|
|
1361
1519
|
hidden: draft.hiddenVariants,
|
|
1362
1520
|
},
|
|
1521
|
+
toolVisibility: {
|
|
1522
|
+
local: draft.toolVisibilityLocal,
|
|
1523
|
+
online: draft.toolVisibilityOnline,
|
|
1524
|
+
video: draft.toolVisibilityVideo,
|
|
1525
|
+
},
|
|
1363
1526
|
}
|
|
1364
1527
|
}
|
|
1365
1528
|
|
|
@@ -1474,6 +1637,7 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1474
1637
|
const [draft, setDraft] = useState<Draft | undefined>(undefined)
|
|
1475
1638
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
1476
1639
|
const [keys, setKeys] = useState<Record<string, string>>({})
|
|
1640
|
+
const [storageKeys, setStorageKeys] = useState<{ accessKeyId: string; secretAccessKey: string }>({ accessKeyId: '', secretAccessKey: '' })
|
|
1477
1641
|
const [draftError, setDraftError] = useState<string | undefined>(undefined)
|
|
1478
1642
|
const [copiedCommand, setCopiedCommand] = useState(false)
|
|
1479
1643
|
|
|
@@ -1582,9 +1746,18 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1582
1746
|
return
|
|
1583
1747
|
}
|
|
1584
1748
|
}
|
|
1749
|
+
const accessKeyId = storageKeys.accessKeyId.trim()
|
|
1750
|
+
const secretAccessKey = storageKeys.secretAccessKey.trim()
|
|
1751
|
+
if ((accessKeyId.length === 0) !== (secretAccessKey.length === 0)) {
|
|
1752
|
+
setDraftError(t('storageKeyPair'))
|
|
1753
|
+
return
|
|
1754
|
+
}
|
|
1585
1755
|
const credentials = Object.entries(keys)
|
|
1586
1756
|
.filter(([, value]) => value.trim().length > 0)
|
|
1587
1757
|
.map(([ref, value]) => ({ ref, value: value.trim() }))
|
|
1758
|
+
if (accessKeyId.length > 0 && (draft.storageEndpoint.trim().length > 0 || draft.storageBucket.trim().length > 0)) {
|
|
1759
|
+
credentials.push({ ref: OBJECT_STORAGE_CREDENTIAL, value: `${accessKeyId}:${secretAccessKey}` })
|
|
1760
|
+
}
|
|
1588
1761
|
const savedRefs = new Set(credentials.map(credential => credential.ref))
|
|
1589
1762
|
setDraftError(undefined)
|
|
1590
1763
|
void controller.save(
|
|
@@ -1595,12 +1768,19 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1595
1768
|
).then(saved => {
|
|
1596
1769
|
if (saved) {
|
|
1597
1770
|
setKeys(current => Object.fromEntries(Object.entries(current).filter(([ref]) => !savedRefs.has(ref))))
|
|
1771
|
+
if (savedRefs.has(OBJECT_STORAGE_CREDENTIAL)) setStorageKeys({ accessKeyId: '', secretAccessKey: '' })
|
|
1598
1772
|
}
|
|
1599
1773
|
})
|
|
1600
1774
|
} catch (error) {
|
|
1601
1775
|
setDraftError(error instanceof Error ? error.message : String(error))
|
|
1602
1776
|
}
|
|
1603
1777
|
}
|
|
1778
|
+
const discard = (): void => {
|
|
1779
|
+
setDraft(draftOf(snapshot.settings.value))
|
|
1780
|
+
setKeys({})
|
|
1781
|
+
setStorageKeys({ accessKeyId: '', secretAccessKey: '' })
|
|
1782
|
+
setDraftError(undefined)
|
|
1783
|
+
}
|
|
1604
1784
|
const busy = state.action !== undefined
|
|
1605
1785
|
const selectedProvider = draft.providers[selectedIndex]
|
|
1606
1786
|
const providerCredentials = snapshot.credentials ?? []
|
|
@@ -1608,14 +1788,18 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1608
1788
|
const selectedKey = selectedProvider === undefined ? '' : (keys[selectedProvider.credential] ?? '')
|
|
1609
1789
|
const selectedKeyLocked = selectedCredential !== undefined && !selectedCredential.writable
|
|
1610
1790
|
const selectedBuiltInFree = selectedCredential?.source === 'built-in-free'
|
|
1611
|
-
const
|
|
1791
|
+
const objectStorageCredential = snapshot.objectStorageCredential ?? { ref: '', configured: false, writable: false }
|
|
1792
|
+
const canSave = snapshot.writable || Object.values(keys).some(value => value.trim().length > 0) || storageKeys.accessKeyId.trim().length > 0 || storageKeys.secretAccessKey.trim().length > 0
|
|
1612
1793
|
const runtimeErrorTitle = snapshot.runtime.ready ? t('runtimeCandidateRejected') : t('runtimeUnavailable')
|
|
1613
1794
|
const pluginUpdate = state.update
|
|
1614
1795
|
const updateCapability = pluginUpdate ?? snapshot.release.update
|
|
1615
1796
|
const latestVersion = pluginUpdate?.latestVersion
|
|
1616
1797
|
const updateReason = updateCapability.reason === undefined ? undefined : t(UPDATE_REASON_KEYS[updateCapability.reason])
|
|
1617
1798
|
const updateCheckSupported = updateCapability.checkSupported ?? updateCapability.supported
|
|
1618
|
-
const updateHasUnsavedChanges = Object.values(keys).some(value => value.
|
|
1799
|
+
const updateHasUnsavedChanges = Object.values(keys).some(value => value.length > 0)
|
|
1800
|
+
|| storageKeys.accessKeyId.length > 0
|
|
1801
|
+
|| storageKeys.secretAccessKey.length > 0
|
|
1802
|
+
|| settingsDraftChanged(draft, snapshot.settings.value, t)
|
|
1619
1803
|
const manualUpdateProfile = updateCapability.profile ?? 'web'
|
|
1620
1804
|
const manualUpdateCommand = `dsh plugin --profile ${manualUpdateProfile} add @mengruo/dsh-vision-toolkit@latest --registry=https://registry.npmjs.org/`
|
|
1621
1805
|
const copyManualUpdate = (): void => {
|
|
@@ -1635,6 +1819,18 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1635
1819
|
|
|
1636
1820
|
return (
|
|
1637
1821
|
<div className="dvt-settings">
|
|
1822
|
+
<div className="dvt-save-dock" data-dirty={updateHasUnsavedChanges ? 'true' : undefined}>
|
|
1823
|
+
<button type="button" className="dvt-save-dock-reload" aria-label={t('reload')} title={t('reload')} disabled={busy} onClick={() => { void controller.load() }}><ReloadIcon /></button>
|
|
1824
|
+
{updateHasUnsavedChanges ? (
|
|
1825
|
+
<>
|
|
1826
|
+
<span className="dvt-save-dock-text">{t('unsavedChanges')}</span>
|
|
1827
|
+
<Button size="sm" variant="outline" disabled={busy} onClick={discard}>{t('discardChanges')}</Button>
|
|
1828
|
+
<Button size="sm" variant="primary" disabled={!canSave || busy} onClick={save}>{state.action === 'save' ? t('saving') : t('save')}</Button>
|
|
1829
|
+
</>
|
|
1830
|
+
) : (
|
|
1831
|
+
<span className="dvt-save-dock-text">{t('noUnsavedChanges')}</span>
|
|
1832
|
+
)}
|
|
1833
|
+
</div>
|
|
1638
1834
|
<div className="dvt-alert notice">{t('externalNotice')}</div>
|
|
1639
1835
|
{!snapshot.writable ? <div className="dvt-alert warning">{t('readOnly')}</div> : null}
|
|
1640
1836
|
{draftError === undefined ? null : <div className="dvt-alert error">{draftError}</div>}
|
|
@@ -1667,6 +1863,9 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1667
1863
|
<Field label={t('protocol')}><select aria-label={t('protocol')} disabled={!snapshot.writable || busy} value={selectedProvider.protocol} onChange={(event) => { updateProvider(selectedIndex, 'protocol', event.target.value as 'openai' | 'anthropic') }}><option value="openai">OpenAI Chat Completions</option><option value="anthropic">Anthropic Messages</option></select></Field>
|
|
1668
1864
|
{selectedProvider.protocol === 'anthropic' ? <Field label={t('anthropicThinking')} hint={t('anthropicThinkingHint')}><select aria-label={t('anthropicThinking')} value={selectedProvider.anthropicThinking} onChange={(event) => { updateProvider(selectedIndex, 'anthropicThinking', event.target.value as 'omit' | 'disabled' | 'adaptive') }}><option value="omit">omit (widest compatibility)</option><option value="disabled">disabled (model support required)</option><option value="adaptive">adaptive (model support required)</option></select></Field> : null}
|
|
1669
1865
|
<Field label={t('userAgent')}><Input aria-label={t('userAgent')} value={selectedProvider.userAgent} onChange={(event) => { updateProvider(selectedIndex, 'userAgent', event.target.value) }} /></Field>
|
|
1866
|
+
<Field label={t('stream')} hint={t('streamHint')}><select aria-label={t('stream')} disabled={!snapshot.writable || busy} value={selectedProvider.stream ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'stream', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field>
|
|
1867
|
+
<Field label={t('uploadViaUrl')} hint={t('uploadViaUrlHint')}><select aria-label={t('uploadViaUrl')} disabled={!snapshot.writable || busy} value={selectedProvider.uploadViaUrl ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'uploadViaUrl', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field>
|
|
1868
|
+
{selectedProvider.protocol === 'openai' ? <Field label={t('videoSupport')} hint={t('videoSupportHint')}><select aria-label={t('videoSupport')} disabled={!snapshot.writable || busy} value={selectedProvider.videoSupport ? 'enabled' : 'disabled'} onChange={(event) => { updateProvider(selectedIndex, 'videoSupport', event.target.value === 'enabled') }}><option value="disabled">{t('disabled')}</option><option value="enabled">{t('enabled')}</option></select></Field> : null}
|
|
1670
1869
|
<Field label={t('t1')} hint={t('t1Hint')}><Input aria-label={t('t1')} inputMode="numeric" value={selectedProvider.t1Seconds} onChange={(event) => { updateProvider(selectedIndex, 't1Seconds', event.target.value) }} /></Field>
|
|
1671
1870
|
<Field label={t('t2')} hint={t('t2Hint')}><Input aria-label={t('t2')} inputMode="numeric" value={selectedProvider.t2Seconds} onChange={(event) => { updateProvider(selectedIndex, 't2Seconds', event.target.value) }} /></Field>
|
|
1672
1871
|
<Field label={t('maxBytes')}><Input aria-label={t('maxBytes')} inputMode="numeric" value={selectedProvider.maxImageBytes} onChange={(event) => { updateProvider(selectedIndex, 'maxImageBytes', event.target.value) }} /></Field>
|
|
@@ -1678,18 +1877,18 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1678
1877
|
<div className="dvt-provider-tests">
|
|
1679
1878
|
<Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('connection', selectedIndex) }}>{state.action === 'connection' ? t('testing') : t('testConnection')}</Button>
|
|
1680
1879
|
<Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('model', selectedIndex) }}>{state.action === 'model' ? t('testingModel') : t('testModel')}</Button>
|
|
1880
|
+
{selectedProvider.protocol === 'openai' ? <Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runVideoTest(selectedIndex) }}>{state.action === 'test-video' ? t('testingVideo') : t('testVideo')}</Button> : null}
|
|
1681
1881
|
</div>
|
|
1682
1882
|
{providerHealth === undefined ? null : <div className="dvt-provider-test-result">{(['service', 'model'] as const).map(name => {
|
|
1683
1883
|
const check = providerHealth.checks[name]
|
|
1684
1884
|
if (check === undefined) return null
|
|
1685
1885
|
return <div key={name} data-status={check.status}><span>{t(HEALTH_NAME_KEYS[name] ?? 'health')}</span><strong>{t(HEALTH_STATUS_KEYS[check.status])}</strong><p>{healthDetail(name, check.detail, t)}</p></div>
|
|
1686
1886
|
})}</div>}
|
|
1887
|
+
{state.videoTest === undefined ? null : <div className="dvt-provider-test-result"><div data-status="ok"><span>{t('videoSupport')}</span><strong>{t('statusOk')}</strong><p>{state.videoTest.detail}</p></div></div>}
|
|
1687
1888
|
</div>
|
|
1688
1889
|
)}
|
|
1689
1890
|
</section>
|
|
1690
1891
|
|
|
1691
|
-
<div className="dvt-save-row"><Button variant="primary" disabled={!canSave || busy} onClick={save}>{state.action === 'save' ? t('saving') : t('save')}</Button><Button variant="outline" disabled={busy} onClick={() => { void controller.load() }}>{t('reload')}</Button></div>
|
|
1692
|
-
|
|
1693
1892
|
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('prioritySort')}</h3><p>{t('prioritySortHint')}</p></div></div>
|
|
1694
1893
|
<div className="dvt-sorter">
|
|
1695
1894
|
{draft.providers.map((provider, index) => (
|
|
@@ -1753,11 +1952,27 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1753
1952
|
|
|
1754
1953
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('imageInput')}</h3></div><label className="dvt-check"><input type="checkbox" checked={draft.hiddenVariants} disabled={!snapshot.writable || busy} onChange={(event) => { update('hiddenVariants', event.target.checked) }} /><span>{t('hiddenVariantsLabel')}</span><small>{t('hiddenVariantsHint')}</small></label></section>
|
|
1755
1954
|
|
|
1955
|
+
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('toolVisibility')}</h3><p>{t('toolVisibilityHint')}</p></div></div>
|
|
1956
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityLocal} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityLocal', event.target.checked) }} /><span>{t('toolVisibilityLocal')}</span><small>{t('toolVisibilityLocalHint')}</small></label>
|
|
1957
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityOnline} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityOnline', event.target.checked) }} /><span>{t('toolVisibilityOnline')}</span><small>{t('toolVisibilityOnlineHint')}</small></label>
|
|
1958
|
+
<label className="dvt-check"><input type="checkbox" checked={draft.toolVisibilityVideo} disabled={!snapshot.writable || busy} onChange={(event) => { update('toolVisibilityVideo', event.target.checked) }} /><span>{t('toolVisibilityVideo')} <em>(测试)</em></span><small>{t('toolVisibilityVideoHint')}</small></label>
|
|
1959
|
+
</section>
|
|
1960
|
+
|
|
1756
1961
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('storage')}</h3></div><div className="dvt-form-grid">
|
|
1757
1962
|
<Field label={t('storageDir')} hint={t('storageDirHint')}><Input aria-label={t('storageDir')} placeholder="/tmp/dsh-vision-toolkit" value={draft.storageDir} onChange={(event) => { update('storageDir', event.target.value) }} /></Field>
|
|
1758
1963
|
<Field label={t('allowedDirs')} hint={t('allowedDirsHint')}><textarea rows={3} value={draft.allowedDirs} onChange={(event) => { update('allowedDirs', event.target.value) }} /></Field>
|
|
1759
1964
|
</div></section>
|
|
1760
1965
|
|
|
1966
|
+
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('objectStorage')}</h3><p>{t('objectStorageHint')}</p></div><div className="dvt-actions"><Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.testStorage() }}>{state.action === 'test-storage' ? t('testingStorage') : t('testStorage')}</Button></div></div><div className="dvt-form-grid">
|
|
1967
|
+
<Field label={t('storageEndpoint')}><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" /></Field>
|
|
1968
|
+
<Field label={t('storageBucket')}><Input aria-label={t('storageBucket')} disabled={!snapshot.writable || busy} value={draft.storageBucket} onChange={(event) => { update('storageBucket', event.target.value) }} /></Field>
|
|
1969
|
+
<Field label={t('storageAccessKeyId')}><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) }} /></Field>
|
|
1970
|
+
<Field label={t('storageSecretAccessKey')}><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) }} /></Field>
|
|
1971
|
+
<Field label={t('storagePublicBase')} hint={t('storagePublicBaseHint')}><Input aria-label={t('storagePublicBase')} disabled={!snapshot.writable || busy} value={draft.storagePublicBase} onChange={(event) => { update('storagePublicBase', event.target.value) }} placeholder="https://cdn.example.com" /></Field>
|
|
1972
|
+
</div>
|
|
1973
|
+
{state.storageTest === undefined ? null : <div className="dvt-provider-test-result"><div data-status="ok"><span>{t('objectStorage')}</span><strong>{t('statusOk')}</strong><p>{state.storageTest.detail}</p></div></div>}
|
|
1974
|
+
</section>
|
|
1975
|
+
|
|
1761
1976
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('runtime')}</h3><span className={`dvt-badge ${snapshot.runtime.ready ? 'ok' : 'error'}`}>{snapshot.runtime.ready ? snapshot.runtime.upstream?.source === 'managed' ? t('runtimeManaged') : snapshot.runtime.upstream?.source === 'external' ? t('runtimeExternal') : t('runtimeReady') : t('runtimeUnavailable')}</span></div><div className="dvt-form-grid">
|
|
1762
1977
|
<Field label={t('runtimeMode')}><select value={draft.runtimeMode} onChange={(event) => { update('runtimeMode', event.target.value as 'managed' | 'external') }}><option value="managed">{t('runtimeManaged')}</option><option value="external">{t('runtimeExternal')}</option></select></Field>
|
|
1763
1978
|
{draft.runtimeMode === 'external' ? <Field label={t('toolkitPath')}><Input value={draft.toolkitPath} onChange={(event) => { update('toolkitPath', event.target.value) }} /></Field> : null}
|
|
@@ -1782,11 +1997,12 @@ const CSS = `
|
|
|
1782
1997
|
.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}
|
|
1783
1998
|
.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}
|
|
1784
1999
|
.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)}
|
|
1785
|
-
.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:
|
|
2000
|
+
.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}
|
|
1786
2001
|
.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}
|
|
1787
2002
|
.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)}
|
|
1788
2003
|
.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}
|
|
1789
2004
|
.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}
|
|
2005
|
+
.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}
|
|
1790
2006
|
@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}}
|
|
1791
2007
|
`
|
|
1792
2008
|
|