@anionex/dsh-vision-toolkit 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +18 -17
- package/README.zh.md +16 -7
- package/assets/community-group-qr.png +0 -0
- package/assets/vision-model-test.png +0 -0
- package/docs/requirements-traceability/README.i18n.yaml +2 -2
- package/docs/requirements-traceability/README.md +2 -2
- package/docs/requirements-traceability/README.zh.md +2 -2
- package/lib/client.js +55 -12
- package/lib/client.js.map +1 -1
- package/lib/runtime.js +37 -3
- package/lib/runtime.js.map +1 -1
- package/lib/types/client/index.d.ts +16 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +5 -2
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web.js +7 -2
- package/lib/web.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.tsx +53 -14
- package/src/runtime.ts +42 -3
- package/src/web.ts +6 -2
package/src/client/index.tsx
CHANGED
|
@@ -73,9 +73,11 @@ const en = {
|
|
|
73
73
|
sourceFile: 'Credential file',
|
|
74
74
|
health: 'Health',
|
|
75
75
|
runHealth: 'Run health check',
|
|
76
|
-
testConnection: 'Test connection',
|
|
76
|
+
testConnection: 'Test API connection',
|
|
77
|
+
testModel: 'Test vision model',
|
|
77
78
|
testing: 'Checking…',
|
|
78
|
-
|
|
79
|
+
testingModel: 'Testing model…',
|
|
80
|
+
connectionHint: 'The API connection test only queries GET /models. The vision model test sends the bundled diagnostic image and verifies one real multimodal request.',
|
|
79
81
|
saveBeforeTesting: 'Save service changes before testing the connection.',
|
|
80
82
|
advanced: 'Advanced settings',
|
|
81
83
|
advancedHint: 'Credential name, provider compatibility, output language, resource limits, runtime source, Python, and additional readable directories.',
|
|
@@ -140,6 +142,7 @@ const en = {
|
|
|
140
142
|
healthArtifactDirectory: 'Artifact directory',
|
|
141
143
|
healthTempDirectory: 'Temporary directory',
|
|
142
144
|
healthService: 'Vision service',
|
|
145
|
+
healthModel: 'Vision model',
|
|
143
146
|
statusOk: 'OK',
|
|
144
147
|
statusWarning: 'Warning',
|
|
145
148
|
statusError: 'Error',
|
|
@@ -154,7 +157,7 @@ const en = {
|
|
|
154
157
|
healthDirectoryWritable: '{directory} is writable: {path}',
|
|
155
158
|
healthDirectoryNotWritable: '{directory} is not writable: {path}',
|
|
156
159
|
healthArtifactDirectoryFailed: 'Could not prepare the artifact directory.',
|
|
157
|
-
healthConnectionNotTested: '
|
|
160
|
+
healthConnectionNotTested: 'API connection not tested. Use Test API connection to query /models.',
|
|
158
161
|
healthConnectionCredentialMissing: 'Connection test skipped because the credential is unavailable.',
|
|
159
162
|
healthServiceResponded: 'Service responded at {endpoint} (HTTP {status}).',
|
|
160
163
|
healthServiceRejectedCredential: 'Service rejected the configured credential (HTTP {status}).',
|
|
@@ -162,6 +165,13 @@ const en = {
|
|
|
162
165
|
healthServiceRateLimited: 'Service is reachable, but the connection test was rate-limited (HTTP 429).',
|
|
163
166
|
healthServiceHttpFailed: 'Connection test failed with HTTP {status}.',
|
|
164
167
|
healthServiceUnreachable: 'Could not reach {endpoint}.',
|
|
168
|
+
healthModelNotTested: 'Vision model not tested. Run Test vision model to make one real multimodal request.',
|
|
169
|
+
healthModelCredentialMissing: 'Vision model test skipped because the credential is unavailable.',
|
|
170
|
+
healthModelReady: 'Model {model} completed a real multimodal request.',
|
|
171
|
+
healthModelFailed: 'Real multimodal request failed: {detail}',
|
|
172
|
+
modelTestVerifiedTag: 'Verified',
|
|
173
|
+
modelTestNotRunTag: 'Not tested',
|
|
174
|
+
modelTestFailedTag: 'Test failed',
|
|
165
175
|
} as const
|
|
166
176
|
|
|
167
177
|
type LocaleKey = keyof typeof en
|
|
@@ -213,10 +223,12 @@ const zh: Record<LocaleKey, string> = {
|
|
|
213
223
|
sourceFile: '凭据文件',
|
|
214
224
|
health: '运行检查',
|
|
215
225
|
runHealth: '检查本地环境',
|
|
216
|
-
testConnection: '
|
|
226
|
+
testConnection: '测试 API 连接',
|
|
227
|
+
testModel: '测试视觉模型',
|
|
217
228
|
testing: '检查中…',
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
testingModel: '正在测试模型…',
|
|
230
|
+
connectionHint: '“测试 API 连接”只请求 GET /models;“测试视觉模型”会发送插件自带的诊断图片,验证一次真实多模态调用。',
|
|
231
|
+
saveBeforeTesting: '修改服务配置后,请先保存,再执行 API 或视觉模型测试。',
|
|
220
232
|
advanced: '高级设置',
|
|
221
233
|
advancedHint: '凭据名称、服务兼容参数、结果语言、资源限制、运行环境来源、Python 和额外可读目录。一般无需修改。',
|
|
222
234
|
pluginVersion: '插件版本',
|
|
@@ -280,6 +292,7 @@ const zh: Record<LocaleKey, string> = {
|
|
|
280
292
|
healthArtifactDirectory: '输出目录',
|
|
281
293
|
healthTempDirectory: '临时目录',
|
|
282
294
|
healthService: '视觉服务',
|
|
295
|
+
healthModel: '视觉模型',
|
|
283
296
|
statusOk: '正常',
|
|
284
297
|
statusWarning: '注意',
|
|
285
298
|
statusError: '异常',
|
|
@@ -294,7 +307,7 @@ const zh: Record<LocaleKey, string> = {
|
|
|
294
307
|
healthDirectoryWritable: '{directory}可写:{path}',
|
|
295
308
|
healthDirectoryNotWritable: '{directory}不可写:{path}',
|
|
296
309
|
healthArtifactDirectoryFailed: '无法准备输出目录。',
|
|
297
|
-
healthConnectionNotTested: '
|
|
310
|
+
healthConnectionNotTested: '尚未测试 API 连接。点击“测试 API 连接”可请求 /models。',
|
|
298
311
|
healthConnectionCredentialMissing: 'API 密钥不可用,未执行连接测试。',
|
|
299
312
|
healthServiceResponded: '服务已响应:{endpoint}(HTTP {status})。',
|
|
300
313
|
healthServiceRejectedCredential: '服务拒绝了当前 API 密钥(HTTP {status})。',
|
|
@@ -302,6 +315,13 @@ const zh: Record<LocaleKey, string> = {
|
|
|
302
315
|
healthServiceRateLimited: '服务可以访问,但本次连接测试触发了限流(HTTP 429)。',
|
|
303
316
|
healthServiceHttpFailed: '连接测试失败(HTTP {status})。',
|
|
304
317
|
healthServiceUnreachable: '无法连接到 {endpoint}。',
|
|
318
|
+
healthModelNotTested: '尚未测试视觉模型。点击“测试视觉模型”可执行一次真实多模态请求。',
|
|
319
|
+
healthModelCredentialMissing: '视觉模型测试已跳过,因为当前 API 密钥不可用。',
|
|
320
|
+
healthModelReady: '模型 {model} 已完成一次真实多模态请求。',
|
|
321
|
+
healthModelFailed: '真实多模态请求失败:{detail}',
|
|
322
|
+
modelTestVerifiedTag: '已实测',
|
|
323
|
+
modelTestNotRunTag: '未测试',
|
|
324
|
+
modelTestFailedTag: '测试失败',
|
|
305
325
|
}
|
|
306
326
|
|
|
307
327
|
type Translate = (key: LocaleKey, params?: Record<string, unknown>) => string
|
|
@@ -356,6 +376,7 @@ interface HealthResult {
|
|
|
356
376
|
checks: Record<string, HealthCheck>
|
|
357
377
|
healthy: boolean
|
|
358
378
|
connectionTested: boolean
|
|
379
|
+
modelTested: boolean
|
|
359
380
|
}
|
|
360
381
|
|
|
361
382
|
interface SettingsValue {
|
|
@@ -754,7 +775,7 @@ interface SettingsState {
|
|
|
754
775
|
status: 'idle' | 'loading' | 'ready' | 'error'
|
|
755
776
|
snapshot?: SettingsSnapshot | undefined
|
|
756
777
|
health?: HealthResult | undefined
|
|
757
|
-
action?: 'save' | 'health' | 'connection' | undefined
|
|
778
|
+
action?: 'save' | 'health' | 'connection' | 'model' | undefined
|
|
758
779
|
message?: string | undefined
|
|
759
780
|
error?: string | undefined
|
|
760
781
|
}
|
|
@@ -842,13 +863,15 @@ export class VisionSettingsController {
|
|
|
842
863
|
}
|
|
843
864
|
}
|
|
844
865
|
|
|
845
|
-
async runHealth(
|
|
846
|
-
|
|
866
|
+
async runHealth(mode: 'health' | 'connection' | 'model'): Promise<void> {
|
|
867
|
+
const testConnection = mode !== 'health'
|
|
868
|
+
const testModel = mode === 'model'
|
|
869
|
+
this.set({ ...this.state, action: mode, error: undefined, message: undefined })
|
|
847
870
|
try {
|
|
848
871
|
const health = await apiRequest<HealthResult>({
|
|
849
872
|
method: 'POST',
|
|
850
873
|
headers: { 'Content-Type': 'application/json' },
|
|
851
|
-
body: JSON.stringify({ action: 'health', testConnection }),
|
|
874
|
+
body: JSON.stringify({ action: 'health', testConnection, testModel }),
|
|
852
875
|
})
|
|
853
876
|
this.set({ ...this.state, action: undefined, health })
|
|
854
877
|
} catch (error) {
|
|
@@ -959,6 +982,7 @@ const HEALTH_NAME_KEYS: Record<string, LocaleKey> = {
|
|
|
959
982
|
artifactDirectory: 'healthArtifactDirectory',
|
|
960
983
|
tempDirectory: 'healthTempDirectory',
|
|
961
984
|
service: 'healthService',
|
|
985
|
+
model: 'healthModel',
|
|
962
986
|
}
|
|
963
987
|
|
|
964
988
|
const HEALTH_STATUS_KEYS: Record<HealthCheck['status'], LocaleKey> = {
|
|
@@ -1005,9 +1029,21 @@ function healthDetail(name: string, detail: string, t: Translate): string {
|
|
|
1005
1029
|
if (match !== null) return t('healthServiceHttpFailed', { status: match[1] })
|
|
1006
1030
|
match = /^Service could not be reached at (.+)$/u.exec(detail)
|
|
1007
1031
|
if (match !== null) return t('healthServiceUnreachable', { endpoint: match[1] })
|
|
1032
|
+
if (detail === 'Vision model was not tested; run an explicit model test to send the bundled diagnostic image') return t('healthModelNotTested')
|
|
1033
|
+
if (detail === 'Vision model test skipped because the configured credential is unavailable') return t('healthModelCredentialMissing')
|
|
1034
|
+
match = /^Vision model (.+) completed a multimodal request$/u.exec(detail)
|
|
1035
|
+
if (match !== null) return t('healthModelReady', { model: match[1] })
|
|
1036
|
+
match = /^Vision model test failed: (.+)$/u.exec(detail)
|
|
1037
|
+
if (match !== null) return t('healthModelFailed', { detail: match[1] })
|
|
1008
1038
|
return detail
|
|
1009
1039
|
}
|
|
1010
1040
|
|
|
1041
|
+
function modelTestTag(health: HealthResult, check: HealthCheck): { status: 'ok' | 'warning' | 'error'; label: LocaleKey } {
|
|
1042
|
+
if (!health.modelTested) return { status: 'warning', label: 'modelTestNotRunTag' }
|
|
1043
|
+
if (check.status === 'ok') return { status: 'ok', label: 'modelTestVerifiedTag' }
|
|
1044
|
+
return { status: 'error', label: 'modelTestFailedTag' }
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1011
1047
|
function credentialSource(source: string, t: Translate): string {
|
|
1012
1048
|
if (source === 'env') return t('sourceEnv')
|
|
1013
1049
|
if (source === 'file') return t('sourceFile')
|
|
@@ -1079,9 +1115,12 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1079
1115
|
|
|
1080
1116
|
<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>
|
|
1081
1117
|
|
|
1082
|
-
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('health')}</h3><p>{t('connectionHint')}</p></div><div className="dvt-actions"><Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth(
|
|
1118
|
+
<section className="dvt-panel"><div className="dvt-panel-title"><div><h3>{t('health')}</h3><p>{t('connectionHint')}</p></div><div className="dvt-actions"><Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('health') }}>{state.action === 'health' ? t('testing') : t('runHealth')}</Button><Button size="sm" variant="outline" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('connection') }}>{state.action === 'connection' ? t('testing') : t('testConnection')}</Button><Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth('model') }}>{state.action === 'model' ? t('testingModel') : t('testModel')}</Button></div></div>
|
|
1083
1119
|
<p className="dvt-muted">{t('saveBeforeTesting')}</p>
|
|
1084
|
-
{state.health === undefined ? <p className="dvt-muted">{t('notTested')}</p> : <div className="dvt-health-grid">{Object.entries(state.health.checks).map(([name, check]) =>
|
|
1120
|
+
{state.health === undefined ? <p className="dvt-muted">{t('notTested')}</p> : <div className="dvt-health-grid">{Object.entries(state.health.checks).map(([name, check]) => {
|
|
1121
|
+
const testTag = name === 'model' ? modelTestTag(state.health as HealthResult, check) : undefined
|
|
1122
|
+
return <div key={name} data-status={check.status}><span>{t(HEALTH_NAME_KEYS[name] ?? 'health')}</span>{testTag === undefined ? null : <em className="dvt-health-test-tag" data-status={testTag.status}>{t(testTag.label)}</em>}<strong>{t(HEALTH_STATUS_KEYS[check.status])}</strong><p>{healthDetail(name, check.detail, t)}</p></div>
|
|
1123
|
+
})}</div>}
|
|
1085
1124
|
</section>
|
|
1086
1125
|
|
|
1087
1126
|
<details className="dvt-advanced">
|
|
@@ -1128,7 +1167,7 @@ const CSS = `
|
|
|
1128
1167
|
.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)}
|
|
1129
1168
|
.dvt-settings{display:grid;gap:14px;max-width:900px;padding:8px 2px 32px;color:var(--dsw-alias-label-primary)}.dvt-settings-footer{display:flex;justify-content:space-between;gap:20px;align-items:flex-start;padding:8px 2px}.dvt-settings-footer h2{font-size:25px;letter-spacing:-.025em;margin:3px 0 6px}.dvt-settings-footer p{max-width:620px;margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.55}.dvt-kicker{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--dsw-alias-state-business-primary);font-weight:700}.dvt-release{display:grid;gap:4px;min-width:170px;padding:9px 11px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);font-size:10px;color:var(--dsw-alias-label-secondary)}.dvt-release span{display:flex;justify-content:space-between;gap:12px}.dvt-release strong{color:var(--dsw-alias-label-primary)}.dvt-alert{padding:10px 12px;border-radius:10px;font-size:12px;line-height:1.5;display:grid;gap:3px}.dvt-alert.notice{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}.dvt-alert.warning{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 12%,transparent);color:var(--dsw-alias-state-warn-label)}.dvt-alert.error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}.dvt-alert.success{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent);color:var(--dsw-alias-state-success-primary)}.dvt-panel{display:grid;gap:12px;padding:15px;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv1)}.dvt-panel-title{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.dvt-panel-title h3{font-size:14px;margin:0}.dvt-panel-title p{font-size:11px;line-height:1.45;color:var(--dsw-alias-label-secondary);margin:4px 0 0;max-width:620px}.dvt-badge{font-size:10px;padding:3px 7px;border-radius:999px;font-weight:650}.dvt-badge.ok{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary)}.dvt-badge.error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}.dvt-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.dvt-field{display:grid;gap:6px;align-content:start}.dvt-field>span{font-size:11px;font-weight:600}.dvt-field>small{font-size:10px;color:var(--dsw-alias-label-secondary);line-height:1.4}.dvt-field select,.dvt-field textarea{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);border-radius:9px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:8px 10px}.dvt-field select{height:36px}.dvt-field textarea{resize:vertical;min-height:76px}.dvt-runtime-facts{display:grid;gap:4px;padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);overflow:auto}.dvt-runtime-facts code{font-size:10px;white-space:nowrap;color:var(--dsw-alias-label-secondary)}.dvt-save-row{display:flex;gap:8px;padding:2px 0}
|
|
1130
1169
|
.dvt-settings-footer{margin-top:8px;padding:20px 2px 4px;border-top:1px solid var(--dsw-alias-border-l1);opacity:.82}.dvt-settings-footer h2{font-size:18px;letter-spacing:-.015em;margin:3px 0 5px}.dvt-settings-footer p{font-size:11px;line-height:1.5}.dvt-release{min-width:220px}.dvt-release span{white-space:nowrap}.dvt-essential{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,var(--dsw-alias-border-l1));box-shadow:var(--dsw-shadow-lv1),0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 5%,transparent)}.dvt-advanced{border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}.dvt-advanced>summary{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 15px;cursor:pointer;list-style:none}.dvt-advanced>summary::-webkit-details-marker{display:none}.dvt-advanced>summary>span:first-child{display:grid;gap:3px}.dvt-advanced>summary strong{font-size:13px}.dvt-advanced>summary small{font-size:10px;line-height:1.45;color:var(--dsw-alias-label-secondary);font-weight:400}.dvt-details-chevron{font-size:15px;opacity:.55;transition:transform .16s ease}.dvt-advanced[open] .dvt-details-chevron{transform:rotate(180deg)}.dvt-advanced-body{display:grid;gap:12px;padding:0 12px 12px}.dvt-advanced-body>.dvt-panel{box-shadow:none}
|
|
1131
|
-
.dvt-health-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.dvt-health-grid>div{padding:9px 10px;border-radius:9px;background:var(--dsw-alias-bg-layer-2);border-left:3px solid var(--dsw-alias-border-l4)}.dvt-health-grid>div[data-status=ok]{border-left-color:var(--dsw-alias-state-success-primary)}.dvt-health-grid>div[data-status=warning],.dvt-health-grid>div[data-status=not_tested]{border-left-color:var(--dsw-alias-state-warn-primary)}.dvt-health-grid>div[data-status=error]{border-left-color:var(--dsw-alias-state-error-primary)}.dvt-health-grid span{font-size:10px;text-transform:capitalize}.dvt-health-grid strong{float:right;font-size:9px;text-transform:uppercase;color:var(--dsw-alias-label-secondary)}.dvt-health-grid p{clear:both;margin:5px 0 0;font-size:10px;line-height:1.4;color:var(--dsw-alias-label-secondary)}.dvt-loading{padding:24px;border-radius:12px;background:var(--dsw-alias-bg-layer-2);font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
1170
|
+
.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)}
|
|
1132
1171
|
.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}
|
|
1133
1172
|
@media(max-width:720px){.dvt-settings-footer{display:grid}.dvt-release{width:auto}.dvt-form-grid{grid-template-columns:1fr}.dvt-metrics{grid-template-columns:1fr}.dvt-artifact-meta{align-items:flex-start;flex-direction:column}.dvt-panel-title{flex-direction:column}}
|
|
1134
1173
|
`
|
package/src/runtime.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { createHash, randomUUID } from 'node:crypto'
|
|
10
10
|
import { readFile, rm, stat, writeFile } from 'node:fs/promises'
|
|
11
11
|
import { basename, extname, join } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
12
13
|
import type { Context } from '@deepseek-ai/cordis'
|
|
13
14
|
import type { ResolvedCredential } from '@deepseek-ai/dsh-credentials'
|
|
14
15
|
import { SaxesParser } from 'saxes'
|
|
@@ -49,6 +50,8 @@ import {
|
|
|
49
50
|
import { PLUGIN_VERSION } from './version.ts'
|
|
50
51
|
|
|
51
52
|
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
|
|
53
|
+
const VISION_MODEL_TEST_IMAGE = fileURLToPath(new URL('../assets/vision-model-test.png', import.meta.url))
|
|
54
|
+
const VISION_MODEL_TEST_PROMPT = 'This is an explicit service readiness test. Reply with one short sentence confirming that you received the image.'
|
|
52
55
|
|
|
53
56
|
function svgDocumentPathCount(svg: string): number | undefined {
|
|
54
57
|
const parser = new SaxesParser({ xmlns: true })
|
|
@@ -444,9 +447,11 @@ export interface VisionToolkitHealthResult {
|
|
|
444
447
|
artifactDirectory: HealthCheck
|
|
445
448
|
tempDirectory: HealthCheck
|
|
446
449
|
service: HealthCheck
|
|
450
|
+
model: HealthCheck
|
|
447
451
|
}
|
|
448
452
|
healthy: boolean
|
|
449
453
|
connectionTested: boolean
|
|
454
|
+
modelTested: boolean
|
|
450
455
|
}
|
|
451
456
|
|
|
452
457
|
/** Shared per-call execution options. */
|
|
@@ -764,6 +769,10 @@ export class VisionToolkitRuntime {
|
|
|
764
769
|
`credential ${this.config.provider.credential} is not configured; set it through DSH credentials`,
|
|
765
770
|
)
|
|
766
771
|
}
|
|
772
|
+
return this.visionEnv(resolved)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private visionEnv(resolved: ResolvedCredential): UpstreamEnvironment {
|
|
767
776
|
return {
|
|
768
777
|
VISION_API_KEY: resolved.value,
|
|
769
778
|
VISION_BASE_URL: this.config.provider.baseUrl,
|
|
@@ -1690,8 +1699,8 @@ export class VisionToolkitRuntime {
|
|
|
1690
1699
|
}
|
|
1691
1700
|
}
|
|
1692
1701
|
|
|
1693
|
-
/**
|
|
1694
|
-
async health(testConnection: boolean, options: ToolCallOptions): Promise<VisionToolkitHealthResult> {
|
|
1702
|
+
/** Health: inspect local readiness, optionally probe `/models`, and explicitly test one real multimodal request. */
|
|
1703
|
+
async health(testConnection: boolean, options: ToolCallOptions, testModel = false): Promise<VisionToolkitHealthResult> {
|
|
1695
1704
|
return this.runOperation('vision_toolkit_health', options, async (operation) => {
|
|
1696
1705
|
const info = this.upstreamVersion
|
|
1697
1706
|
const python: HealthCheck = { status: 'ok', detail: `${info.pythonVersion} via ${info.python}` }
|
|
@@ -1733,6 +1742,10 @@ export class VisionToolkitRuntime {
|
|
|
1733
1742
|
status: 'not_tested',
|
|
1734
1743
|
detail: 'Connection was not tested; pass testConnection=true to query the configured /models endpoint',
|
|
1735
1744
|
}
|
|
1745
|
+
let model: HealthCheck = {
|
|
1746
|
+
status: 'not_tested',
|
|
1747
|
+
detail: 'Vision model was not tested; run an explicit model test to send the bundled diagnostic image',
|
|
1748
|
+
}
|
|
1736
1749
|
if (testConnection) {
|
|
1737
1750
|
if (resolvedCredential === undefined) {
|
|
1738
1751
|
service = { status: 'error', detail: 'Connection test skipped because the configured credential is unavailable' }
|
|
@@ -1775,7 +1788,32 @@ export class VisionToolkitRuntime {
|
|
|
1775
1788
|
}
|
|
1776
1789
|
}
|
|
1777
1790
|
}
|
|
1778
|
-
|
|
1791
|
+
if (testModel) {
|
|
1792
|
+
if (resolvedCredential === undefined) {
|
|
1793
|
+
model = { status: 'error', detail: 'Vision model test skipped because the configured credential is unavailable' }
|
|
1794
|
+
} else {
|
|
1795
|
+
try {
|
|
1796
|
+
const result = await this.runUpstream(
|
|
1797
|
+
'glance',
|
|
1798
|
+
[VISION_MODEL_TEST_IMAGE, '-q', VISION_MODEL_TEST_PROMPT],
|
|
1799
|
+
operation,
|
|
1800
|
+
this.visionEnv(resolvedCredential),
|
|
1801
|
+
)
|
|
1802
|
+
if (result.stdout.trim().length === 0) {
|
|
1803
|
+
throw new VisionToolkitError('output', 'glance: vision API returned an empty description')
|
|
1804
|
+
}
|
|
1805
|
+
model = {
|
|
1806
|
+
status: 'ok',
|
|
1807
|
+
detail: `Vision model ${this.config.provider.model} completed a multimodal request`,
|
|
1808
|
+
}
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
if (operation.signal.aborted) throw error
|
|
1811
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
1812
|
+
model = { status: 'error', detail: `Vision model test failed: ${detail.slice(0, 600)}` }
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const checks = { python, dependencies, chrome, credential, artifactDirectory, tempDirectory, service, model }
|
|
1779
1817
|
const healthy = Object.values(checks).every(check => check.status !== 'error')
|
|
1780
1818
|
return {
|
|
1781
1819
|
pluginVersion: PLUGIN_VERSION,
|
|
@@ -1783,6 +1821,7 @@ export class VisionToolkitRuntime {
|
|
|
1783
1821
|
checks,
|
|
1784
1822
|
healthy,
|
|
1785
1823
|
connectionTested: testConnection,
|
|
1824
|
+
modelTested: testModel,
|
|
1786
1825
|
}
|
|
1787
1826
|
})
|
|
1788
1827
|
}
|
package/src/web.ts
CHANGED
|
@@ -74,6 +74,7 @@ interface SaveRequest {
|
|
|
74
74
|
interface HealthRequest {
|
|
75
75
|
action: 'health'
|
|
76
76
|
testConnection: boolean
|
|
77
|
+
testModel: boolean
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
interface CredentialRequest {
|
|
@@ -156,7 +157,10 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
156
157
|
if (!isRecord(value) || typeof value.action !== 'string') throw new TypeError('request action is required')
|
|
157
158
|
if (value.action === 'health') {
|
|
158
159
|
if (typeof value.testConnection !== 'boolean') throw new TypeError('health.testConnection must be boolean')
|
|
159
|
-
|
|
160
|
+
const testModel = value.testModel === undefined ? false : value.testModel
|
|
161
|
+
if (typeof testModel !== 'boolean') throw new TypeError('health.testModel must be boolean')
|
|
162
|
+
if (testModel && !value.testConnection) throw new TypeError('health.testModel requires health.testConnection')
|
|
163
|
+
return { action: 'health', testConnection: value.testConnection, testModel }
|
|
160
164
|
}
|
|
161
165
|
if (value.action === 'save') {
|
|
162
166
|
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
@@ -294,7 +298,7 @@ export class VisionToolkitWebBackend {
|
|
|
294
298
|
signal: controller.signal,
|
|
295
299
|
workspace: process.cwd(),
|
|
296
300
|
sessionId: 'vision-toolkit-settings',
|
|
297
|
-
})
|
|
301
|
+
}, request.testModel)
|
|
298
302
|
} finally {
|
|
299
303
|
req.off('aborted', abort)
|
|
300
304
|
req.socket.off('close', abort)
|