@anionex/dsh-vision-toolkit 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +6 -10
- package/README.zh.md +6 -10
- package/lib/client.js +85 -16
- package/lib/client.js.map +1 -1
- package/lib/types/client/index.d.ts +13 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/web.d.ts +1 -0
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web.js +61 -8
- package/lib/web.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.tsx +95 -20
- package/src/web.ts +74 -9
package/src/client/index.tsx
CHANGED
|
@@ -32,9 +32,17 @@ const en = {
|
|
|
32
32
|
settingsIntro: 'Configure the pinned visual engineering runtime, its external vision endpoint, and local safety limits.',
|
|
33
33
|
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.',
|
|
34
34
|
provider: 'Vision service',
|
|
35
|
-
providerHint: 'Choose the API protocol, then provide the service address, model, and
|
|
35
|
+
providerHint: 'Choose the API protocol, then provide the service address, model, and API key used by online vision features.',
|
|
36
36
|
baseUrl: 'Base URL',
|
|
37
|
-
|
|
37
|
+
apiKey: 'API key',
|
|
38
|
+
apiKeyPlaceholderMissing: 'Paste the API key',
|
|
39
|
+
apiKeyPlaceholderConfigured: 'Saved; leave blank to keep it',
|
|
40
|
+
apiKeyHint: 'The key is stored in DSH Credentials and is never shown again after saving.',
|
|
41
|
+
apiKeyLocked: 'The current key comes from a read-only source and cannot be replaced here.',
|
|
42
|
+
apiKeyBlank: 'The API key cannot contain only spaces.',
|
|
43
|
+
apiKeyInvalid: 'Paste only the key, without a variable name, quotes, spaces, or line breaks.',
|
|
44
|
+
credential: 'Credential name',
|
|
45
|
+
credentialHint: 'Advanced: the API key is stored under this name. Keep VISION_API_KEY unless another plugin configuration requires a different reference.',
|
|
38
46
|
model: 'Model',
|
|
39
47
|
protocol: 'API protocol',
|
|
40
48
|
anthropicThinking: 'Anthropic thinking',
|
|
@@ -56,7 +64,7 @@ const en = {
|
|
|
56
64
|
saving: 'Validating runtime…',
|
|
57
65
|
reload: 'Reload',
|
|
58
66
|
saved: 'Settings validated and applied.',
|
|
59
|
-
readOnly: '
|
|
67
|
+
readOnly: 'Service settings are read-only. A writable API key can still be saved.',
|
|
60
68
|
configured: 'Configured',
|
|
61
69
|
missing: 'Missing',
|
|
62
70
|
source: 'Source',
|
|
@@ -70,7 +78,7 @@ const en = {
|
|
|
70
78
|
connectionHint: 'Connection testing explicitly sends the configured credential to GET /models. It uploads no image and creates no completion.',
|
|
71
79
|
saveBeforeTesting: 'Save service changes before testing the connection.',
|
|
72
80
|
advanced: 'Advanced settings',
|
|
73
|
-
advancedHint: '
|
|
81
|
+
advancedHint: 'Credential name, provider compatibility, output language, resource limits, runtime source, Python, and additional readable directories.',
|
|
74
82
|
pluginVersion: 'Plugin',
|
|
75
83
|
upstreamVersion: 'Upstream',
|
|
76
84
|
activeGeneration: 'Runtime generation',
|
|
@@ -164,9 +172,17 @@ const zh: Record<LocaleKey, string> = {
|
|
|
164
172
|
settingsIntro: '在这里设置视觉模型服务、工具运行环境,以及图片和文件的本地访问范围。',
|
|
165
173
|
externalNotice: '使用图像理解、目标定位、界面检测或文字识别等在线功能时,所选图片会发送到下方配置的视觉服务。图片裁剪、轮廓描摹、像素对比、主色提取、前景提取和网页截图均在本机完成,不会上传图片。',
|
|
166
174
|
provider: '在线视觉服务',
|
|
167
|
-
providerHint: '选择接口协议后,填写在线视觉功能使用的 API
|
|
175
|
+
providerHint: '选择接口协议后,填写在线视觉功能使用的 API 地址、模型名称和 API 密钥。',
|
|
168
176
|
baseUrl: 'API 地址',
|
|
169
|
-
|
|
177
|
+
apiKey: 'API 密钥',
|
|
178
|
+
apiKeyPlaceholderMissing: '粘贴 API 密钥',
|
|
179
|
+
apiKeyPlaceholderConfigured: '已保存;留空表示不修改',
|
|
180
|
+
apiKeyHint: '密钥会保存到 DSH 凭据存储,保存后不会在页面中回显。',
|
|
181
|
+
apiKeyLocked: '当前密钥来自只读配置,无法在此替换。',
|
|
182
|
+
apiKeyBlank: 'API 密钥不能只包含空格。',
|
|
183
|
+
apiKeyInvalid: '请只粘贴密钥本身,不要包含变量名、引号、空格或换行。',
|
|
184
|
+
credential: '凭据名称',
|
|
185
|
+
credentialHint: '高级用法:API 密钥会按此名称保存。除非其他插件配置要求不同名称,否则保持 VISION_API_KEY。',
|
|
170
186
|
model: '模型名称',
|
|
171
187
|
protocol: 'API 协议',
|
|
172
188
|
anthropicThinking: 'Anthropic thinking',
|
|
@@ -188,7 +204,7 @@ const zh: Record<LocaleKey, string> = {
|
|
|
188
204
|
saving: '正在检查并应用…',
|
|
189
205
|
reload: '重新加载',
|
|
190
206
|
saved: '设置已保存并生效。',
|
|
191
|
-
readOnly: '
|
|
207
|
+
readOnly: '服务设置来自只读配置;如果 API 密钥可写,仍可在此保存密钥。',
|
|
192
208
|
configured: '已就绪',
|
|
193
209
|
missing: '未配置',
|
|
194
210
|
source: '配置来源',
|
|
@@ -202,7 +218,7 @@ const zh: Record<LocaleKey, string> = {
|
|
|
202
218
|
connectionHint: '“检查本地环境”只检查本机依赖和目录;“测试服务连接”会携带已配置的 API 密钥请求 /models,但不会上传图片或调用模型。',
|
|
203
219
|
saveBeforeTesting: '修改服务配置后,请先保存,再测试连接。',
|
|
204
220
|
advanced: '高级设置',
|
|
205
|
-
advancedHint: '
|
|
221
|
+
advancedHint: '凭据名称、服务兼容参数、结果语言、资源限制、运行环境来源、Python 和额外可读目录。一般无需修改。',
|
|
206
222
|
pluginVersion: '插件版本',
|
|
207
223
|
upstreamVersion: '工具包版本',
|
|
208
224
|
activeGeneration: '本次运行已应用',
|
|
@@ -779,17 +795,50 @@ export class VisionSettingsController {
|
|
|
779
795
|
void this.load()
|
|
780
796
|
}
|
|
781
797
|
|
|
782
|
-
async save(
|
|
798
|
+
async save(
|
|
799
|
+
value: SettingsValue,
|
|
800
|
+
expectedRevision: number,
|
|
801
|
+
credentialValue: string | undefined,
|
|
802
|
+
writeSettings: boolean,
|
|
803
|
+
): Promise<boolean> {
|
|
783
804
|
this.set({ ...this.state, action: 'save', error: undefined, message: undefined })
|
|
805
|
+
let snapshot = this.state.snapshot
|
|
784
806
|
try {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
807
|
+
if (writeSettings) {
|
|
808
|
+
snapshot = await apiRequest<SettingsSnapshot>({
|
|
809
|
+
method: 'POST',
|
|
810
|
+
headers: { 'Content-Type': 'application/json' },
|
|
811
|
+
body: JSON.stringify({ action: 'save', expectedRevision, value }),
|
|
812
|
+
})
|
|
813
|
+
}
|
|
814
|
+
if (snapshot === undefined) throw new Error('Vision Toolkit Settings are unavailable')
|
|
815
|
+
if (credentialValue !== undefined) {
|
|
816
|
+
try {
|
|
817
|
+
snapshot = await apiRequest<SettingsSnapshot>({
|
|
818
|
+
method: 'POST',
|
|
819
|
+
headers: { 'Content-Type': 'application/json' },
|
|
820
|
+
body: JSON.stringify({
|
|
821
|
+
action: 'credential',
|
|
822
|
+
expectedRevision: snapshot.settings.revision,
|
|
823
|
+
ref: snapshot.credential.ref,
|
|
824
|
+
value: credentialValue,
|
|
825
|
+
}),
|
|
826
|
+
})
|
|
827
|
+
} catch (error) {
|
|
828
|
+
this.set({
|
|
829
|
+
status: 'ready',
|
|
830
|
+
snapshot,
|
|
831
|
+
health: this.state.health,
|
|
832
|
+
error: error instanceof Error ? error.message : String(error),
|
|
833
|
+
})
|
|
834
|
+
return false
|
|
835
|
+
}
|
|
836
|
+
}
|
|
790
837
|
this.set({ status: 'ready', snapshot, health: this.state.health, message: 'saved' })
|
|
838
|
+
return true
|
|
791
839
|
} catch (error) {
|
|
792
840
|
this.set({ ...this.state, action: undefined, error: error instanceof Error ? error.message : String(error) })
|
|
841
|
+
return false
|
|
793
842
|
}
|
|
794
843
|
}
|
|
795
844
|
|
|
@@ -852,6 +901,16 @@ function positiveInteger(raw: string, label: string, t: Translate): number {
|
|
|
852
901
|
return value
|
|
853
902
|
}
|
|
854
903
|
|
|
904
|
+
function apiKeyFailure(value: string, t: Translate): string | undefined {
|
|
905
|
+
if (value.length === 0) return undefined
|
|
906
|
+
const trimmed = value.trim()
|
|
907
|
+
if (trimmed.length === 0) return t('apiKeyBlank')
|
|
908
|
+
const quoted = trimmed.length > 1 && ['"', '\'', '`'].includes(trimmed[0] ?? '') && trimmed.endsWith(trimmed[0] ?? '')
|
|
909
|
+
const environmentLine = /^[A-Z][A-Z0-9_]*=[^=]/u.test(trimmed)
|
|
910
|
+
if (quoted || environmentLine || !/^[\x21-\x7E]+$/u.test(trimmed)) return t('apiKeyInvalid')
|
|
911
|
+
return undefined
|
|
912
|
+
}
|
|
913
|
+
|
|
855
914
|
function valueOf(draft: Draft, t: Translate): SettingsValue {
|
|
856
915
|
return {
|
|
857
916
|
provider: {
|
|
@@ -959,6 +1018,7 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
959
1018
|
const state = useSyncExternalStore(controller.subscribe, controller.snapshot, controller.snapshot)
|
|
960
1019
|
const snapshot = state.snapshot
|
|
961
1020
|
const [draft, setDraft] = useState<Draft | undefined>(undefined)
|
|
1021
|
+
const [apiKey, setApiKey] = useState('')
|
|
962
1022
|
const [draftError, setDraftError] = useState<string | undefined>(undefined)
|
|
963
1023
|
|
|
964
1024
|
useEffect(() => { if (state.status === 'idle') void controller.load() }, [controller, state.status])
|
|
@@ -976,13 +1036,27 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
976
1036
|
const update = <K extends keyof Draft>(key: K, value: Draft[K]): void => setDraft(current => current === undefined ? current : { ...current, [key]: value })
|
|
977
1037
|
const save = (): void => {
|
|
978
1038
|
try {
|
|
1039
|
+
const keyFailure = apiKeyFailure(apiKey, t)
|
|
1040
|
+
if (keyFailure !== undefined) {
|
|
1041
|
+
setDraftError(keyFailure)
|
|
1042
|
+
return
|
|
1043
|
+
}
|
|
1044
|
+
const credentialValue = apiKey.length === 0 ? undefined : apiKey.trim()
|
|
979
1045
|
setDraftError(undefined)
|
|
980
|
-
void controller.save(
|
|
1046
|
+
void controller.save(
|
|
1047
|
+
valueOf(draft, t),
|
|
1048
|
+
snapshot.settings.revision,
|
|
1049
|
+
credentialValue,
|
|
1050
|
+
snapshot.writable,
|
|
1051
|
+
).then(saved => { if (saved) setApiKey('') })
|
|
981
1052
|
} catch (error) {
|
|
982
1053
|
setDraftError(error instanceof Error ? error.message : String(error))
|
|
983
1054
|
}
|
|
984
1055
|
}
|
|
985
1056
|
const busy = state.action !== undefined
|
|
1057
|
+
const credentialMatchesSnapshot = draft.credential.trim() === snapshot.credential.ref
|
|
1058
|
+
const keyLocked = credentialMatchesSnapshot && !snapshot.credential.writable
|
|
1059
|
+
const canSave = snapshot.writable || (apiKey.length > 0 && !keyLocked)
|
|
986
1060
|
const runtimeErrorTitle = snapshot.runtime.ready ? t('runtimeCandidateRejected') : t('runtimeUnavailable')
|
|
987
1061
|
|
|
988
1062
|
return (
|
|
@@ -996,14 +1070,14 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
996
1070
|
|
|
997
1071
|
<section className="dvt-panel dvt-essential"><div className="dvt-panel-title"><div><h3>{t('provider')}</h3><p>{t('providerHint')}</p></div><span className={`dvt-badge ${snapshot.credential.configured ? 'ok' : 'error'}`}>{snapshot.credential.configured ? t('configured') : t('missing')}</span></div>
|
|
998
1072
|
<div className="dvt-form-grid">
|
|
999
|
-
<Field label={t('protocol')}><select value={draft.protocol} onChange={(event) => { update('protocol', event.target.value as 'openai' | 'anthropic') }}><option value="openai">OpenAI Chat Completions</option><option value="anthropic">Anthropic Messages</option></select></Field>
|
|
1000
|
-
<Field label={t('baseUrl')}><Input value={draft.baseUrl} onChange={(event) => { update('baseUrl', event.target.value) }} /></Field>
|
|
1001
|
-
<Field label={t('model')}><Input value={draft.model} onChange={(event) => { update('model', event.target.value) }} /></Field>
|
|
1002
|
-
<Field label={t('
|
|
1073
|
+
<Field label={t('protocol')}><select disabled={!snapshot.writable || busy} value={draft.protocol} onChange={(event) => { update('protocol', event.target.value as 'openai' | 'anthropic') }}><option value="openai">OpenAI Chat Completions</option><option value="anthropic">Anthropic Messages</option></select></Field>
|
|
1074
|
+
<Field label={t('baseUrl')}><Input disabled={!snapshot.writable || busy} value={draft.baseUrl} onChange={(event) => { update('baseUrl', event.target.value) }} /></Field>
|
|
1075
|
+
<Field label={t('model')}><Input disabled={!snapshot.writable || busy} value={draft.model} onChange={(event) => { update('model', event.target.value) }} /></Field>
|
|
1076
|
+
<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) })}`}><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) }} /></Field>
|
|
1003
1077
|
</div>
|
|
1004
1078
|
</section>
|
|
1005
1079
|
|
|
1006
|
-
<div className="dvt-save-row"><Button variant="primary" disabled={!
|
|
1080
|
+
<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>
|
|
1007
1081
|
|
|
1008
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(false) }}>{state.action === 'health' ? t('testing') : t('runHealth')}</Button><Button size="sm" variant="primary" disabled={busy || !snapshot.runtime.ready} onClick={() => { void controller.runHealth(true) }}>{state.action === 'connection' ? t('testing') : t('testConnection')}</Button></div></div>
|
|
1009
1083
|
<p className="dvt-muted">{t('saveBeforeTesting')}</p>
|
|
@@ -1014,6 +1088,7 @@ function LoadedSettings({ controller, t }: SettingsInjected) {
|
|
|
1014
1088
|
<summary><span><strong>{t('advanced')}</strong><small>{t('advancedHint')}</small></span><span className="dvt-details-chevron" aria-hidden="true">⌄</span></summary>
|
|
1015
1089
|
<div className="dvt-advanced-body">
|
|
1016
1090
|
<section className="dvt-panel"><div className="dvt-panel-title"><h3>{t('provider')}</h3></div><div className="dvt-form-grid">
|
|
1091
|
+
<Field label={t('credential')} hint={t('credentialHint')}><Input aria-label={t('credential')} disabled={!snapshot.writable || busy} value={draft.credential} onChange={(event) => { update('credential', event.target.value) }} /></Field>
|
|
1017
1092
|
{draft.protocol === 'anthropic' ? <Field label={t('anthropicThinking')} hint={t('anthropicThinkingHint')}><select aria-label={t('anthropicThinking')} value={draft.anthropicThinking} onChange={(event) => { update('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}
|
|
1018
1093
|
<Field label={t('userAgent')}><Input value={draft.userAgent} onChange={(event) => { update('userAgent', event.target.value) }} /></Field>
|
|
1019
1094
|
</div></section>
|
package/src/web.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
9
9
|
import type { Context } from '@deepseek-ai/cordis'
|
|
10
|
-
import type { CredentialInfo } from '@deepseek-ai/dsh-credentials'
|
|
10
|
+
import type { CredentialInfo, CredentialRef } from '@deepseek-ai/dsh-credentials'
|
|
11
11
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
12
12
|
import { SettingsConflictError, type SettingsDescriptor } from '@deepseek-ai/dsh-settings'
|
|
13
13
|
// Type-only import activates the optional webServer Context declaration.
|
|
@@ -70,7 +70,14 @@ interface HealthRequest {
|
|
|
70
70
|
testConnection: boolean
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
interface CredentialRequest {
|
|
74
|
+
action: 'credential'
|
|
75
|
+
expectedRevision: number
|
|
76
|
+
ref: CredentialRef
|
|
77
|
+
value: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type SettingsRequest = SaveRequest | HealthRequest | CredentialRequest
|
|
74
81
|
|
|
75
82
|
interface JsonError {
|
|
76
83
|
ok: false
|
|
@@ -101,6 +108,8 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
101
108
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
class CredentialReferenceConflictError extends Error {}
|
|
112
|
+
|
|
104
113
|
function descriptorOf(ctx: Context): SettingsDescriptor {
|
|
105
114
|
const descriptor = ctx.settings.describe().find(row => row.ns === VISION_TOOLKIT_SETTINGS_NAMESPACE)
|
|
106
115
|
if (descriptor === undefined) throw new Error('vision-toolkit Settings namespace is not registered')
|
|
@@ -154,6 +163,27 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
154
163
|
value: value.value as VisionToolkitConfig,
|
|
155
164
|
}
|
|
156
165
|
}
|
|
166
|
+
if (value.action === 'credential') {
|
|
167
|
+
if (!Number.isSafeInteger(value.expectedRevision) || (value.expectedRevision as number) < 0) {
|
|
168
|
+
throw new TypeError('credential.expectedRevision must be a non-negative integer')
|
|
169
|
+
}
|
|
170
|
+
if (typeof value.ref !== 'string') throw new TypeError('credential.ref must be a string')
|
|
171
|
+
if (typeof value.value !== 'string') throw new TypeError('credential.value must be a string')
|
|
172
|
+
const secret = value.value.trim()
|
|
173
|
+
if (secret.length === 0) throw new TypeError('API key cannot be blank')
|
|
174
|
+
const first = secret[0]
|
|
175
|
+
const quoted = secret.length > 1 && (first === '"' || first === '\'' || first === '`') && secret.endsWith(first)
|
|
176
|
+
const environmentLine = /^[A-Z][A-Z0-9_]*=[^=]/u.test(secret)
|
|
177
|
+
if (quoted || environmentLine || !/^[\x21-\x7E]+$/u.test(secret)) {
|
|
178
|
+
throw new TypeError('paste only the API key, without a variable name, quotes, spaces, or line breaks')
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
action: 'credential',
|
|
182
|
+
expectedRevision: value.expectedRevision as number,
|
|
183
|
+
ref: credentialRef(value.ref),
|
|
184
|
+
value: secret,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
157
187
|
throw new TypeError(`unsupported action: ${value.action}`)
|
|
158
188
|
}
|
|
159
189
|
|
|
@@ -227,6 +257,26 @@ export class VisionToolkitWebBackend {
|
|
|
227
257
|
return this.snapshot()
|
|
228
258
|
}
|
|
229
259
|
|
|
260
|
+
private async saveCredential(request: CredentialRequest): Promise<VisionToolkitSettingsSnapshot> {
|
|
261
|
+
const descriptor = descriptorOf(this.ctx)
|
|
262
|
+
if (descriptor.revision !== request.expectedRevision) {
|
|
263
|
+
throw new SettingsConflictError(
|
|
264
|
+
VISION_TOOLKIT_SETTINGS_NAMESPACE,
|
|
265
|
+
request.expectedRevision,
|
|
266
|
+
descriptor.revision,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
const resolved = resolveConfig(descriptor.value as VisionToolkitConfig)
|
|
270
|
+
const currentRef = credentialRef(String(resolved.provider.credential))
|
|
271
|
+
if (currentRef !== request.ref) {
|
|
272
|
+
throw new CredentialReferenceConflictError(
|
|
273
|
+
`credential reference changed from "${request.ref}" to "${currentRef}"; reload Settings and try again`,
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
await this.ctx.credentials.set(currentRef, request.value)
|
|
277
|
+
return this.snapshot()
|
|
278
|
+
}
|
|
279
|
+
|
|
230
280
|
private async health(request: HealthRequest, req: IncomingMessage): Promise<VisionToolkitHealthResult> {
|
|
231
281
|
if (!this.manager.ready) throw new Error('runtime is not ready; fix Settings and save a valid configuration first')
|
|
232
282
|
const controller = new AbortController()
|
|
@@ -273,15 +323,30 @@ export class VisionToolkitWebBackend {
|
|
|
273
323
|
return
|
|
274
324
|
}
|
|
275
325
|
try {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
326
|
+
switch (parsed.action) {
|
|
327
|
+
case 'health':
|
|
328
|
+
responseJson(res, 200, { ok: true, value: await this.health(parsed, req) })
|
|
329
|
+
break
|
|
330
|
+
case 'save':
|
|
331
|
+
responseJson(res, 200, { ok: true, value: await this.save(parsed) })
|
|
332
|
+
break
|
|
333
|
+
case 'credential':
|
|
334
|
+
responseJson(res, 200, { ok: true, value: await this.saveCredential(parsed) })
|
|
335
|
+
break
|
|
280
336
|
}
|
|
281
337
|
} catch (error) {
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
338
|
+
const settingsConflict = error instanceof SettingsConflictError
|
|
339
|
+
const credentialConflict = error instanceof CredentialReferenceConflictError
|
|
340
|
+
const code = settingsConflict
|
|
341
|
+
? 'settings-conflict'
|
|
342
|
+
: credentialConflict
|
|
343
|
+
? 'credential-conflict'
|
|
344
|
+
: parsed.action === 'health'
|
|
345
|
+
? 'health-failed'
|
|
346
|
+
: parsed.action === 'credential'
|
|
347
|
+
? 'credential-rejected'
|
|
348
|
+
: 'settings-rejected'
|
|
349
|
+
const status = settingsConflict || credentialConflict ? 409 : parsed.action === 'health' ? 503 : 400
|
|
285
350
|
this.ctx.logger.warn('dsh-vision-toolkit Web action=%s failed: %s', parsed.action, publicMessage(error))
|
|
286
351
|
requestError(res, status, code, publicMessage(error))
|
|
287
352
|
}
|