dsh-vision-router 2.2.1 → 2.2.3

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.
@@ -0,0 +1,331 @@
1
+ import { htmlHasScriptMarker } from './html-script-marker.js'
2
+ import { LOCAL_SETTINGS_PATH } from './dsh-settings-017-compat.js'
3
+
4
+ const SETTINGS_017_MARK = 'data-vision-router-settings-017-compat'
5
+
6
+ export const SETTINGS_017_CLIENT_PRELUDE = String.raw`(function(){
7
+ 'use strict';
8
+ var TARGET = 'dsh-vision-router';
9
+ var ENDPOINT = '${LOCAL_SETTINGS_PATH}';
10
+ var contextCache = typeof WeakMap === 'function' ? new WeakMap() : undefined;
11
+ var scopeCache = typeof WeakMap === 'function' ? new WeakMap() : undefined;
12
+ var binderCache = typeof WeakMap === 'function' ? new WeakMap() : undefined;
13
+ var connectionCache = typeof WeakMap === 'function' ? new WeakMap() : undefined;
14
+
15
+ function safeGet(ctx, name) {
16
+ if (!ctx) return undefined;
17
+ try {
18
+ if (typeof ctx.get === 'function') {
19
+ var value = ctx.get(name);
20
+ if (value !== undefined && value !== null) return value;
21
+ }
22
+ } catch (_) {}
23
+ try { return ctx[name]; } catch (_) { return undefined; }
24
+ }
25
+
26
+ function isLoopbackLocation(locationLike) {
27
+ var hostname = locationLike && typeof locationLike.hostname === 'string'
28
+ ? locationLike.hostname.toLowerCase().replace(/^\[|\]$/g, '')
29
+ : '';
30
+ if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return true;
31
+ return /^127(?:\.\d{1,3}){3}$/.test(hostname);
32
+ }
33
+
34
+ function normalizeConnection(connection) {
35
+ if (!connection || typeof connection !== 'object') return connection;
36
+ var locationLike;
37
+ try { locationLike = window && window.location; } catch (_) { locationLike = undefined; }
38
+ if (!isLoopbackLocation(locationLike) || connection.isLoopback !== false) return connection;
39
+ if (connectionCache && connectionCache.has(connection)) return connectionCache.get(connection);
40
+ var wrapped = new Proxy(connection, {
41
+ get: function(target, property) {
42
+ if (property === 'isLoopback') return true;
43
+ var value = Reflect.get(target, property, target);
44
+ return typeof value === 'function' ? value.bind(target) : value;
45
+ }
46
+ });
47
+ if (connectionCache) connectionCache.set(connection, wrapped);
48
+ return wrapped;
49
+ }
50
+
51
+ function errorOf(value, fallback) {
52
+ var detail = value && value.error;
53
+ var error = new Error(detail && detail.message ? detail.message : fallback);
54
+ if (detail && detail.code) error.code = detail.code;
55
+ if (detail && detail.details !== undefined) error.details = detail.details;
56
+ return error;
57
+ }
58
+
59
+ function createScope() {
60
+ var listeners = new Set();
61
+ var writeTail = Promise.resolve();
62
+ var inFlight;
63
+ var snapshot = Object.freeze({
64
+ status: 'loading', value: undefined, base: undefined, user: undefined,
65
+ revision: undefined, writable: false, mode: 'host'
66
+ });
67
+ function publish(next) {
68
+ snapshot = Object.freeze(next);
69
+ listeners.forEach(function(listener){ try { listener(); } catch (_) {} });
70
+ }
71
+ function accept(view) {
72
+ if (!view || !view.value || typeof view.value !== 'object' || Array.isArray(view.value)
73
+ || !Number.isInteger(view.revision) || view.revision < 0) {
74
+ throw new Error('Vision Router local settings returned an invalid view');
75
+ }
76
+ publish({
77
+ status: 'ready', value: view.value, base: view.base, user: view.user,
78
+ revision: view.revision, writable: view.writable === true, mode: 'host'
79
+ });
80
+ }
81
+ async function request(method, payload) {
82
+ if (typeof fetch !== 'function') throw new Error('Vision Router local settings transport is unavailable');
83
+ var response = await fetch(ENDPOINT, {
84
+ method: method,
85
+ headers: { accept: 'application/json', ...(method === 'POST' ? { 'content-type': 'application/json' } : {}) },
86
+ cache: 'no-store',
87
+ credentials: 'same-origin',
88
+ ...(method === 'POST' ? { body: JSON.stringify(payload) } : {})
89
+ });
90
+ var body;
91
+ try { body = await response.json(); } catch (_) { body = undefined; }
92
+ if (!response.ok || !body || body.ok !== true) throw errorOf(body, 'Vision Router local settings request failed');
93
+ return body.value;
94
+ }
95
+ function load(restart) {
96
+ if (!restart && inFlight) return inFlight;
97
+ var task = request('GET').then(accept, function(error){
98
+ publish({
99
+ status: 'unavailable', value: undefined, base: undefined, user: undefined,
100
+ revision: undefined, writable: false, mode: 'host', error: error && error.message ? error.message : String(error)
101
+ });
102
+ throw error;
103
+ });
104
+ var held = task.finally(function(){ if (inFlight === held) inFlight = undefined; });
105
+ inFlight = held;
106
+ return held;
107
+ }
108
+ function writeOps(ops) {
109
+ if (!Array.isArray(ops) || ops.length === 0) return Promise.reject(new TypeError('settings operations must be a non-empty array'));
110
+ var task = writeTail.then(async function(){
111
+ if (snapshot.status !== 'ready' || !Number.isInteger(snapshot.revision)) await load(true);
112
+ if (snapshot.status !== 'ready' || !Number.isInteger(snapshot.revision)) throw new Error('Vision Router local settings are not ready');
113
+ if (!snapshot.writable) throw new Error('Vision Router settings provider is read-only');
114
+ try {
115
+ // One ConfigEditor edit may reload DVR. Keep all fields from one UI
116
+ // Save inside that single Host transaction so a second request cannot
117
+ // get stranded between plugin generations.
118
+ var view = await request('POST', { ops: ops, expectedRevision: snapshot.revision });
119
+ accept(view);
120
+ } catch (error) {
121
+ try { await load(true); } catch (_) {}
122
+ throw error;
123
+ }
124
+ });
125
+ writeTail = task.catch(function(){});
126
+ return task;
127
+ }
128
+ function planOps(items) {
129
+ if (!Array.isArray(items) || items.length === 0) throw new TypeError('settings plan must be a non-empty array');
130
+ return items.map(function(item){
131
+ if (!item || typeof item.key !== 'string' || item.key.length === 0 || !item.run) {
132
+ throw new TypeError('settings plan item is invalid');
133
+ }
134
+ return item.run.clear
135
+ ? { op: 'unset', path: [item.key] }
136
+ : { op: 'set', path: [item.key], value: item.run.value };
137
+ });
138
+ }
139
+ var scope = {
140
+ getSnapshot: function(){ return snapshot; },
141
+ subscribe: function(listener){
142
+ if (typeof listener !== 'function') return function(){};
143
+ listeners.add(listener);
144
+ return function(){ listeners.delete(listener); };
145
+ },
146
+ load: function(){ return load(false); },
147
+ reload: function(){ return load(true); },
148
+ set: function(field, value){ return writeOps([{ op: 'set', path: [field], value: value }]); },
149
+ unset: function(field){ return writeOps([{ op: 'unset', path: [field] }]); },
150
+ __visionRouterWritePlan: function(items){ return writeOps(planOps(items)); },
151
+ dispose: async function(){ listeners.clear(); await Promise.allSettled([inFlight, writeTail].filter(Boolean)); }
152
+ };
153
+ void load(false).catch(function(){});
154
+ return scope;
155
+ }
156
+
157
+ function scopeFor(ctx) {
158
+ if (scopeCache && scopeCache.has(ctx)) return scopeCache.get(ctx);
159
+ var scope = createScope();
160
+ if (scopeCache) scopeCache.set(ctx, scope);
161
+ return scope;
162
+ }
163
+
164
+ function syntheticBinder(ctx, original) {
165
+ return {
166
+ bind: function(spec) {
167
+ if (spec && spec.namespace === 'vision-router') return scopeFor(ctx);
168
+ if (original && typeof original.bind === 'function') return original.bind(spec);
169
+ throw new Error('Settings scope ' + String(spec && spec.namespace) + ' is unavailable');
170
+ }
171
+ };
172
+ }
173
+
174
+ function configFormsBinder(ctx) {
175
+ var forms = safeGet(ctx, 'configForms');
176
+ if (!forms || typeof forms.get !== 'function') return undefined;
177
+ if (binderCache && binderCache.has(forms)) return binderCache.get(forms);
178
+ var binder = {
179
+ bind: function(spec) {
180
+ var namespace = spec && spec.namespace;
181
+ if (typeof namespace !== 'string' || namespace.length === 0) {
182
+ throw new TypeError('settings namespace must be a non-empty string');
183
+ }
184
+ return forms.get(namespace);
185
+ }
186
+ };
187
+ if (binderCache) binderCache.set(forms, binder);
188
+ return binder;
189
+ }
190
+
191
+ function wrapContext(ctx) {
192
+ if (!ctx || typeof ctx !== 'object') return ctx;
193
+ if (contextCache && contextCache.has(ctx)) return contextCache.get(ctx);
194
+ var wrapped = new Proxy(ctx, {
195
+ get: function(target, property) {
196
+ if (property === 'settingsScope') {
197
+ var original = safeGet(target, 'settingsScope');
198
+ if (original && typeof original.bind === 'function') return original;
199
+ // DSH 0.1.7 configForms only mirrors Config fields declared volatile.
200
+ // DVR must keep its public Config ordinary for the older supported Host
201
+ // window, so its namespace intentionally uses the local-only ConfigEditor
202
+ // bridge while other namespaces may still delegate to native configForms.
203
+ var official = configFormsBinder(target);
204
+ return syntheticBinder(ctx, official);
205
+ }
206
+ if (property === 'get') {
207
+ var getter = Reflect.get(target, property, target);
208
+ if (typeof getter !== 'function') return getter;
209
+ return function(name) {
210
+ var value = getter.call(target, name);
211
+ return name === 'connection' ? normalizeConnection(value) : value;
212
+ };
213
+ }
214
+ var value = Reflect.get(target, property, target);
215
+ return typeof value === 'function' ? value.bind(target) : value;
216
+ }
217
+ });
218
+ if (contextCache) contextCache.set(ctx, wrapped);
219
+ return wrapped;
220
+ }
221
+
222
+ function requireConfigForms(plugin) {
223
+ if (!plugin || !Array.isArray(plugin.inject)) return plugin;
224
+ var inject = [];
225
+ var inserted = false;
226
+ for (var index = 0; index < plugin.inject.length; index += 1) {
227
+ var service = plugin.inject[index];
228
+ if (service === 'settingsScope') {
229
+ if (!inserted) {
230
+ inject.push('configForms');
231
+ inserted = true;
232
+ }
233
+ continue;
234
+ }
235
+ if (service === 'configForms') inserted = true;
236
+ if (inject.indexOf(service) === -1) inject.push(service);
237
+ }
238
+ // Another loader compatibility layer may already have removed the legacy
239
+ // dependency before this factory runs. DSH 0.1.7 still requires the native
240
+ // configForms service to be activation-ready before Vision Router applies.
241
+ if (!inserted) inject.unshift('configForms');
242
+ try {
243
+ plugin.inject = inject;
244
+ return plugin;
245
+ } catch (_) {
246
+ return Object.assign({}, plugin, { inject: inject });
247
+ }
248
+ }
249
+
250
+ function patchLoader(loader) {
251
+ if (!loader || typeof loader.load !== 'function' || loader.load.__visionRouterSettings017Compat) return;
252
+ var original = loader.load;
253
+ function load(spec) {
254
+ if (spec && spec.id === TARGET && typeof spec.factory === 'function') {
255
+ var factory = spec.factory;
256
+ spec = Object.assign({}, spec, {
257
+ factory: function(require) {
258
+ var plugin = requireConfigForms(factory(require));
259
+ if (plugin && typeof plugin.apply === 'function' && !plugin.apply.__visionRouterSettings017Compat) {
260
+ var apply = plugin.apply;
261
+ var wrappedApply = function(ctx) {
262
+ var rest = Array.prototype.slice.call(arguments, 1);
263
+ return apply.apply(plugin, [wrapContext(ctx)].concat(rest));
264
+ };
265
+ Object.defineProperty(wrappedApply, '__visionRouterSettings017Compat', { value: true });
266
+ plugin.apply = wrappedApply;
267
+ }
268
+ return plugin;
269
+ }
270
+ });
271
+ }
272
+ return original.call(this, spec);
273
+ }
274
+ Object.defineProperty(load, '__visionRouterSettings017Compat', { value: true });
275
+ loader.load = load;
276
+ }
277
+
278
+ function patchCreate(loader) {
279
+ if (!loader || typeof loader.create !== 'function' || loader.create.__visionRouterSettings017Compat) return;
280
+ var original = loader.create;
281
+ function create() {
282
+ var result = original.apply(this, arguments);
283
+ patchLoader(loader);
284
+ if (result && result !== loader) patchLoader(result);
285
+ return result;
286
+ }
287
+ Object.defineProperty(create, '__visionRouterSettings017Compat', { value: true });
288
+ loader.create = create;
289
+ if (loader.mode === 'live') patchLoader(loader);
290
+ }
291
+
292
+ function install() {
293
+ var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
294
+ var stored = window.__ModuleLoader__;
295
+ if (stored) patchCreate(stored);
296
+ if (descriptor && descriptor.configurable === false) return;
297
+ var previousGet = descriptor && descriptor.get;
298
+ var previousSet = descriptor && descriptor.set;
299
+ Object.defineProperty(window, '__ModuleLoader__', {
300
+ configurable: true,
301
+ enumerable: !descriptor || descriptor.enumerable !== false,
302
+ get: function(){ return previousGet ? previousGet.call(window) : stored; },
303
+ set: function(value) {
304
+ if (previousSet) previousSet.call(window, value); else stored = value;
305
+ try { patchCreate(previousGet ? previousGet.call(window) : value); } catch (_) {}
306
+ }
307
+ });
308
+ }
309
+
310
+ try { install(); } catch (_) {}
311
+ })();`
312
+
313
+ export function injectSettings017ClientPrelude(html) {
314
+ if (typeof html !== 'string' || htmlHasScriptMarker(html, SETTINGS_017_MARK)) return html
315
+ const safe = SETTINGS_017_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')
316
+ const script = `<script ${SETTINGS_017_MARK}>${safe}</script>`
317
+ const closeHead = html.indexOf('</head>')
318
+ return closeHead === -1 ? `${html}${script}` : `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
319
+ }
320
+
321
+ export function installSettings017ClientCompatibility(ctx) {
322
+ if (!ctx || typeof ctx.inject !== 'function') return
323
+ // ConfigEditor is the generation fence: old supported Hosts keep their native
324
+ // settingsScope activation contract and never see this client shim.
325
+ ctx.inject(['configEditor', 'webServer'], (webCtx) => {
326
+ webCtx.effect(
327
+ () => webCtx.webServer.tapIndex(injectSettings017ClientPrelude),
328
+ 'vision-router: DSH 0.1.7 settings client compatibility',
329
+ )
330
+ })
331
+ }
@@ -31,6 +31,14 @@ export const SETTINGS_RC8_CLIENT_PRELUDE = String.raw`(function(){
31
31
  return undefined;
32
32
  }
33
33
 
34
+ function isLoopbackLocation(locationLike) {
35
+ var hostname = locationLike && typeof locationLike.hostname === 'string'
36
+ ? locationLike.hostname.toLowerCase().replace(/^\[|\]$/g, '')
37
+ : '';
38
+ if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return true;
39
+ return /^127(?:\.\d{1,3}){3}$/.test(hostname);
40
+ }
41
+
34
42
  async function writePermission(operation, value, revision) {
35
43
  if (typeof fetch !== 'function') throw new Error('Vision Router local settings transport is unavailable');
36
44
  var payload = { operation: operation };
@@ -269,6 +277,11 @@ export const SETTINGS_RC8_CLIENT_PRELUDE = String.raw`(function(){
269
277
  if (connectionCache && connectionCache.has(connection)) return connectionCache.get(connection);
270
278
  var wrapped = new Proxy(connection, {
271
279
  get: function(target, property) {
280
+ if (property === 'isLoopback') {
281
+ var locationLike;
282
+ try { locationLike = window && window.location; } catch (_) { locationLike = undefined; }
283
+ if (isLoopbackLocation(locationLike)) return true;
284
+ }
272
285
  if (property === 'rpc') return wrapRpc(Reflect.get(target, property, target));
273
286
  var value = Reflect.get(target, property, target);
274
287
  return typeof value === 'function' ? value.bind(target) : value;
@@ -73,7 +73,7 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
73
73
  function makeComponent(React,helpers){
74
74
  helpers=helpers&&typeof helpers==='object'?helpers:{};
75
75
  var commitPlan=typeof helpers.commitSettingsPlan==='function'?function(scope,plan,drafts){return helpers.commitSettingsPlan(scope,plan,drafts);}:function(scope,plan){return fallbackCommit(scope,plan);};
76
- var parseLocal=typeof helpers.parseLocalProviderDraft==='function'?helpers.parseLocalProviderDraft:function(value,defaults){var input=obj(value),temperature=typeof input.temperature==='number'&&Number.isFinite(input.temperature)?Math.min(2,Math.max(0,input.temperature)):undefined,topP=typeof input.top_p==='number'&&Number.isFinite(input.top_p)?Math.min(1,Math.max(0,input.top_p)):undefined;return Object.assign({enabled:input.enabled===true,baseURL:typeof input.baseURL==='string'&&input.baseURL.trim()?input.baseURL.trim():defaults.baseURL,model:typeof input.model==='string'&&input.model.trim()?input.model.trim():defaults.model,format:input.format==='anthropic'?'anthropic':'openai'},temperature===undefined?{}:{temperature:temperature},topP===undefined?{}:{top_p:topP});};
76
+ var parseLocal=typeof helpers.parseLocalProviderDraft==='function'?helpers.parseLocalProviderDraft:function(value,defaults){var input=obj(value),temperature=typeof input.temperature==='number'&&Number.isFinite(input.temperature)?Math.min(2,Math.max(0,input.temperature)):undefined,topP=typeof input.top_p==='number'&&Number.isFinite(input.top_p)?Math.min(1,Math.max(0,input.top_p)):undefined,maxTokens=Number.isInteger(input.maxTokens)?Math.min(32768,Math.max(256,input.maxTokens)):defaults.maxTokens,reasoningEffort=['provider_default','none','low','medium','high','max'].includes(input.reasoningEffort)?input.reasoningEffort:defaults.reasoningEffort;return Object.assign({enabled:input.enabled===true,baseURL:typeof input.baseURL==='string'&&input.baseURL.trim()?input.baseURL.trim():defaults.baseURL,model:typeof input.model==='string'&&input.model.trim()?input.model.trim():defaults.model,format:input.format==='anthropic'?'anthropic':input.format==='lmstudio'?'lmstudio':'openai',maxTokens:maxTokens,reasoningEffort:reasoningEffort},temperature===undefined?{}:{temperature:temperature},topP===undefined?{}:{top_p:topP});};
77
77
  return function VisionRouterSettingsIA(props){
78
78
  var scope=props&&props.scope;
79
79
  var subscribe=React.useMemo(function(){return scope&&typeof scope.subscribe==='function'?scope.subscribe.bind(scope):function(){return function(){};};},[scope]);
@@ -142,9 +142,9 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
142
142
  function draftRun(key,raw){
143
143
  if(boolKeys.has(key)){var next=bool(raw);return {value:key==='allowRemoteSettings'&&snapshot.mode==='host'?(next?'true':'false'):next};}
144
144
  if(numberMeta[key])return numberRun(key,raw);
145
- if(key==='visionDepth')return raw==='fast'||raw==='standard'||raw==='deep'?{value:raw}:undefined;
145
+ if(key==='visionDepth')return raw==='fast'||raw==='standard'||raw==='deep'?{value:raw}:undefined;if(key==='ocrEngine')return raw==='auto'||raw==='tesseract'||raw==='vision'?{value:raw}:undefined;
146
146
  if(key==='textProvider'){var pair=obj(raw),p=String(pair.provider||'').trim(),m=String(pair.model||'').trim();if(!p&&!m)return {clear:true};return p&&m?{value:{provider:p,model:m}}:undefined;}
147
- if(key==='localOllama'||key==='localLmStudio'){var defaults=key==='localOllama'?{baseURL:'http://127.0.0.1:11434/v1',model:'qwen2.5vl'}:{baseURL:'http://localhost:1234/v1',model:''};var parsed=parseLocal(raw,defaults);if(key==='localLmStudio'&&parsed.enabled===true&&String(parsed.model||'').trim()==='')return undefined;return {value:parsed};}
147
+ if(key==='localOllama'||key==='localLmStudio'){var defaults=key==='localOllama'?{baseURL:'http://127.0.0.1:11434/v1',model:'qwen2.5vl',maxTokens:4096,reasoningEffort:'none'}:{baseURL:'http://localhost:1234/v1',model:'',maxTokens:4096,reasoningEffort:'none'};var parsed=parseLocal(raw,defaults);if(key==='localLmStudio'&&parsed.enabled===true&&String(parsed.model||'').trim()==='')return undefined;return {value:parsed};}
148
148
  if(key==='proxyHosts'||key==='extraVisionModels'){var list=arr(raw).map(function(x){return String(x||'').trim();}).filter(Boolean);return list.length?{value:list}:{clear:true};}
149
149
  if(key==='proxy'||key==='wrapperRoute'||key==='chainRoute'){var text=String(raw||'').trim();return text?{value:text}:{clear:true};}
150
150
  return {value:raw};
@@ -254,11 +254,11 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
254
254
  ])
255
255
  );
256
256
  }
257
- function localProviderCard(key,label,defaults,which){var current=Object.assign({},defaults,obj(value(key,{}))),opened=which==='ollama'?open.ollama:open.lmstudio;function update(patch){setValue(key,Object.assign({},current,patch));}function sample(name,min,max){return h('div',{className:'vr-field'},h('span',{className:'vr-label'},name),h('input',{className:'vr-input',type:'number',step:'0.1',min:min,max:max,value:current[name]===undefined?'':current[name],disabled:!writable||saving,onChange:function(event){var patch={};patch[name]=event.target.value===''?undefined:Number(event.target.value);update(patch);}}));}return card([h('div',{className:'vr-ia-backend-head',key:'head'},h('div',null,h('div',{className:'vr-ia-field-head'},h('strong',null,label),override(key)),h('p',{className:'vr-hint'},current.enabled?tx('已启用','Enabled'):tx('未启用','Disabled'))),h('input',{type:'checkbox',checked:current.enabled===true,disabled:!writable||saving,onChange:function(event){update({enabled:event.target.checked});}})),h('div',{className:'vr-field',key:'model'},h('span',{className:'vr-label'},tx('模型','Model')),h('input',{className:'vr-input',value:String(current.model||''),disabled:!writable||saving,onChange:function(event){update({model:event.target.value});}}),key==='localLmStudio'&&invalidKeys.includes(key)?h('p',{className:'vr-failed'},tx('启用 LM Studio 时必须填写真实模型标识。','A real model id is required when LM Studio is enabled.')):null),h('div',{className:'vr-field',key:'url'},h('span',{className:'vr-label'},tx('服务地址','Service URL')),h('input',{className:'vr-input',value:String(current.baseURL||''),disabled:!writable||saving,onChange:function(event){update({baseURL:event.target.value});}})),h('button',{type:'button',className:'vr-btn',onClick:function(){setOpen(Object.assign({},open,which==='ollama'?{ollama:!opened}:{lmstudio:!opened}));}},opened?tx('收起高级连接设置','Hide advanced connection settings'):tx('高级连接设置','Advanced connection settings')),opened?h('div',{className:'vr-ia-subgrid',key:'advanced'},h('div',{className:'vr-field'},h('span',{className:'vr-label'},tx('请求协议','Request protocol')),h('select',{className:'vr-input',value:current.format==='anthropic'?'anthropic':'openai',disabled:!writable||saving,onChange:function(event){update({format:event.target.value});}},h('option',{value:'openai'},'OpenAI'),h('option',{value:'anthropic'},'Anthropic'))),sample('temperature',0,2),sample('top_p',0,1)):null]);}
258
- function localPageContent(){if(!local)return h(React.Fragment,null,title(tx('本地与设备','Local & device')),card([h('p',{className:'vr-hint',key:'remote'},tx('本地视觉后端和桌面截图只能在运行 DSH 的机器上配置。','Local vision backends and desktop capture can only be configured on the DSH machine.'))]));return h(React.Fragment,null,title(tx('本地与设备','Local & device'),tx('本地运行视觉模型可减少 API 费用和图片上传。','Run vision locally to reduce API cost and image uploads.')),localProviderCard('localOllama','Ollama',{enabled:false,baseURL:'http://127.0.0.1:11434/v1',model:'qwen2.5vl',format:'openai'},'ollama'),localProviderCard('localLmStudio','LM Studio',{enabled:false,baseURL:'http://localhost:1234/v1',model:'',format:'openai'},'lmstudio'),card([toggle('desktopScreenshot',tx('允许 Agent 读取桌面截图','Allow the agent to capture the desktop'),tx('这是独立的隐私权限;macOS 上保存为开启后会立即触发屏幕录制权限检查。','This is a separate privacy permission; on macOS saving it enabled immediately triggers the screen-recording permission check.'))]));}
257
+ function localProviderCard(key,label,defaults,which){var current=Object.assign({},defaults,obj(value(key,{}))),opened=which==='ollama'?open.ollama:open.lmstudio;function update(patch){setValue(key,Object.assign({},current,patch));}function sample(name,min,max){return h('div',{className:'vr-field'},h('span',{className:'vr-label'},name),h('input',{className:'vr-input',type:'number',step:'0.1',min:min,max:max,value:current[name]===undefined?'':current[name],disabled:!writable||saving,onChange:function(event){var patch={};patch[name]=event.target.value===''?undefined:Number(event.target.value);update(patch);}}));}return card([h('div',{className:'vr-ia-backend-head',key:'head'},h('div',null,h('div',{className:'vr-ia-field-head'},h('strong',null,label),override(key)),h('p',{className:'vr-hint'},current.enabled?tx('已启用','Enabled'):tx('未启用','Disabled'))),h('input',{type:'checkbox',checked:current.enabled===true,disabled:!writable||saving,onChange:function(event){update({enabled:event.target.checked});}})),h('div',{className:'vr-field',key:'model'},h('span',{className:'vr-label'},tx('模型','Model')),h('input',{className:'vr-input',value:String(current.model||''),disabled:!writable||saving,onChange:function(event){update({model:event.target.value});}}),key==='localLmStudio'&&invalidKeys.includes(key)?h('p',{className:'vr-failed'},tx('启用 LM Studio 时必须填写真实模型标识。','A real model id is required when LM Studio is enabled.')):null),h('div',{className:'vr-field',key:'url'},h('span',{className:'vr-label'},tx('服务地址','Service URL')),h('input',{className:'vr-input',value:String(current.baseURL||''),disabled:!writable||saving,onChange:function(event){update({baseURL:event.target.value});}})),h('button',{type:'button',className:'vr-btn',onClick:function(){setOpen(Object.assign({},open,which==='ollama'?{ollama:!opened}:{lmstudio:!opened}));}},opened?tx('收起高级连接设置','Hide advanced connection settings'):tx('高级连接设置','Advanced connection settings')),opened?h('div',{className:'vr-ia-subgrid',key:'advanced'},h('div',{className:'vr-field'},h('span',{className:'vr-label'},tx('请求协议','Request protocol')),h('select',{className:'vr-input',value:current.format==='anthropic'?'anthropic':current.format==='lmstudio'?'lmstudio':'openai',disabled:!writable||saving,onChange:function(event){update({format:event.target.value});}},h('option',{value:'openai'},'OpenAI'),h('option',{value:'anthropic'},'Anthropic'),which==='lmstudio'?h('option',{value:'lmstudio'},tx('LM Studio 原生','LM Studio native')):null)),h('div',{className:'vr-field'},h('span',{className:'vr-label'},tx('最大输出 token','Max output tokens')),h('input',{className:'vr-input',type:'number',step:'256',min:'256',max:'32768',value:String(current.maxTokens||4096),disabled:!writable||saving,onChange:function(event){update({maxTokens:Number(event.target.value)});}})),h('div',{className:'vr-field'},h('span',{className:'vr-label'},tx('推理强度','Reasoning effort')),h('select',{className:'vr-input',value:String(current.reasoningEffort||defaults.reasoningEffort||'provider_default'),disabled:!writable||saving||(which==='lmstudio'?current.format!=='lmstudio':current.format==='anthropic'),onChange:function(event){update({reasoningEffort:event.target.value});}},h('option',{value:'provider_default'},tx('服务端默认','Provider default')),h('option',{value:'none'},tx('关闭推理','Disable reasoning')),h('option',{value:'low'},'low'),h('option',{value:'medium'},'medium'),h('option',{value:'high'},'high'),h('option',{value:'max'},'max'))),sample('temperature',0,2),sample('top_p',0,1)):null]);}
258
+ function localPageContent(){if(!local)return h(React.Fragment,null,title(tx('本地与设备','Local & device')),card([h('p',{className:'vr-hint',key:'remote'},tx('本地视觉后端和桌面截图只能在运行 DSH 的机器上配置。','Local vision backends and desktop capture can only be configured on the DSH machine.'))]));return h(React.Fragment,null,title(tx('本地与设备','Local & device'),tx('本地运行视觉模型可减少 API 费用和图片上传。','Run vision locally to reduce API cost and image uploads.')),localProviderCard('localOllama','Ollama',{enabled:false,baseURL:'http://127.0.0.1:11434/v1',model:'qwen2.5vl',format:'openai',maxTokens:4096,reasoningEffort:'none'},'ollama'),localProviderCard('localLmStudio','LM Studio',{enabled:false,baseURL:'http://localhost:1234/v1',model:'',format:'openai',maxTokens:4096,reasoningEffort:'none'},'lmstudio'),card([toggle('desktopScreenshot',tx('允许 Agent 读取桌面截图','Allow the agent to capture the desktop'),tx('这是独立的隐私权限;macOS 上保存为开启后会立即触发屏幕录制权限检查。','This is a separate privacy permission; on macOS saving it enabled immediately triggers the screen-recording permission check.'))]));}
259
259
  function wrappersEditor(){var rows=wrapperRows();return h('div',{className:'vr-field'},fieldHead('wrappedProviders',tx('哪些聊天模型可以开启识图','Which chat models can use Vision mode')),h('p',{className:'vr-hint'},tx('通常无需修改;模型留空表示整个 Provider。','Usually leave this alone; an empty model means the whole provider.')),rows.map(function(row,index){return modelRow(row,index,rows,setWrapperDraft,true);}),h('button',{type:'button',className:'vr-btn',disabled:!writable||saving,onClick:function(){setWrapperDraft(rows.concat([{provider:'',model:''}]))}},tx('+ 添加范围','+ Add scope')));}
260
260
  function textProviderEditor(){var current=Object.assign({provider:'',model:''},obj(value('textProvider',{}))),ready=groups.length>0;return h('div',{className:'vr-field'},fieldHead('textProvider',tx('文字回退模型','Text fallback model')),h('div',{className:'vr-chain-row'},ready?h('select',{className:'vr-input',value:current.provider||'',disabled:!writable||saving,onChange:function(event){setValue('textProvider',{provider:event.target.value,model:''});}},providerOptions(current.provider||'')):h('input',{className:'vr-input',value:current.provider||'',disabled:!writable||saving,onChange:function(event){setValue('textProvider',{provider:event.target.value,model:current.model||''});}}),ready?h('select',{className:'vr-input',value:current.model||'',disabled:!writable||saving||!current.provider,onChange:function(event){setValue('textProvider',{provider:current.provider||'',model:event.target.value});}},modelOptions(current.provider||'',current.model||'',false)):h('input',{className:'vr-input',value:current.model||'',disabled:!writable||saving,onChange:function(event){setValue('textProvider',{provider:current.provider||'',model:event.target.value});}})),invalidKeys.includes('textProvider')?h('p',{className:'vr-failed'},tx('Provider 和 model 必须同时填写,或同时留空恢复默认。','Provider and model must both be filled, or both left empty to restore the default.')):null);}
261
- function advancedPage(){var routing=toggleValue('routing',false),turnBudget=Number(value('visionTurnBudgetMs',0))||0,customBudget=turnBudget>0;return h(React.Fragment,null,title(tx('高级','Advanced'),tx('这些默认值对大多数用户已经合适。','Defaults are suitable for most users.')),card([h('h4',{className:'vr-ia-subtitle',key:'p'},tx('性能与稳定性','Performance & stability')),toggle('downscale',tx('自动缩放','Auto downscale')),numberField('downscaleMaxPixels',tx('图片像素上限','Image pixel limit'),null,1000,100000000),toggle('cache',tx('识图缓存','Vision answer cache')),numberField('cacheTtlSeconds',tx('缓存有效期(秒)','Cache TTL (seconds)'),null,0,31536000),numberField('cacheMaxEntries',tx('最大缓存数量','Maximum cached answers'),null,1,100000),h('h4',{className:'vr-ia-subtitle',key:'t'},tx('超时','Timeouts')),numberField('timeoutMs',tx('单次模型请求','Single model request'),tx('毫秒。','Milliseconds.'),1000,600000),numberField('visionTaskTimeoutMs',tx('单个视觉任务','Single visual task'),tx('包含该任务内部的重试和备用模型;不是每个后端各自一份。','Includes retries and fallbacks inside the task; it is not a fresh budget per backend.'),1000,180000),numberField('ocrTimeoutMs',tx('OCR 任务','OCR task'),null,1000,120000),h('div',{className:'vr-field',key:'budget'},fieldHead('visionTurnBudgetMs',tx('整轮视觉工具上限','Whole-turn vision-tool limit')),h('select',{className:'vr-input',value:customBudget?'custom':'unlimited',disabled:!writable||saving,onChange:function(event){setValue('visionTurnBudgetMs',event.target.value==='unlimited'?0:(turnBudget>0?turnBudget:180000));}},h('option',{value:'unlimited'},tx('不限制(推荐)','Unlimited (recommended)')),h('option',{value:'custom'},tx('自定义','Custom'))),customBudget?numberField('visionTurnBudgetMs',tx('上限(毫秒)','Limit (ms)'),null,10000,600000):null)]),card([h('h4',{className:'vr-ia-subtitle',key:'cost'},tx('模型顺序与成本','Model order & cost')),toggle('freeCloudFirst',tx('免费云模型优先','Try free cloud models first'))]),card([h('h4',{className:'vr-ia-subtitle',key:'scope'},tx('识图模式范围','Vision mode scope')),toggle('autoWrapProviders',tx('自动允许已启用模型使用识图','Automatically allow enabled models to use Vision mode')),wrappersEditor()]),local?card([h('h4',{className:'vr-ia-subtitle',key:'network'},tx('网络与远程','Network & remote')),h('p',{className:'vr-hint',key:'network-authority'},tx('默认沿用 DSH/Host 的网络出口;只有需要让视觉请求走不同出口时才填写下方覆盖。','By default Vision Router follows the DSH/Host network path. Use the override below only when vision requests need a different egress route.')),toggle('allowRemoteSettings',tx('允许可信 Host 远程修改设置','Allow trusted-host remote settings'),tx('默认关闭;trustedHosts 不是身份认证。','Off by default; trustedHosts is not authentication.')),textField('proxy',tx('视觉专用代理覆盖','Vision-only proxy override'),tx('留空即沿用 DSH/Host;支持 http(s)://、socks5://,并兼容旧 socks5h://。','Leave empty to follow DSH/Host; supports http(s):// and socks5://, with legacy socks5h:// compatibility.')),textareaArray('proxyHosts',tx('视觉代理覆盖域名','Vision proxy override hosts'),tx('仅在上方覆盖非空时生效;每行一个,其余请求继续沿用 DSH/Host。','Used only when the override above is non-empty; one host per line. All other requests keep the DSH/Host path.'))]):null,card([h('h4',{className:'vr-ia-subtitle',key:'compat'},tx('兼容模式','Compatibility')),toggle('rewriteImages',tx('保护纯文本模型','Protect text-only models')),toggle('routing',tx('整轮视觉路由(旧工作流)','Whole-turn vision routing (legacy workflow)')),routing?toggle('reverseRouting',tx('纯文字消息继续使用聊天模型','Keep text-only messages on the chat model')):null,routing?textProviderEditor():null]),card([h('button',{type:'button',className:'vr-btn',key:'dev',onClick:function(){setOpen(Object.assign({},open,{developer:!open.developer}));}},open.developer?tx('隐藏开发者设置','Hide developer settings'):tx('显示开发者设置','Show developer settings')),open.developer?h('div',{className:'vr-ia-dev',key:'body'},toggle('progressiveTools',tx('渐进式工具暴露','Progressive tool exposure')),h('p',{className:'vr-hint',key:'progressive-restart'},tx('保存后需重启 DSH 才生效。','Restart DSH after saving for this change to take effect.')),local?toggle('stealth','Stealth'):null,local?textField('wrapperRoute',tx('包装路由名','Wrapper route name')):null,local?textField('chainRoute',tx('视觉链路由名','Vision chain route name')):null,textareaArray('extraVisionModels',tx('额外视觉能力标记','Extra vision capability labels'),tx('只有诊断发现模型未声明图片能力时才需要。','Needed only when diagnostics show missing image-capability metadata.'))):null]));}
261
+ function advancedPage(){var routing=toggleValue('routing',false),turnBudget=Number(value('visionTurnBudgetMs',0))||0,customBudget=turnBudget>0;return h(React.Fragment,null,title(tx('高级','Advanced'),tx('这些默认值对大多数用户已经合适。','Defaults are suitable for most users.')),card([h('h4',{className:'vr-ia-subtitle',key:'p'},tx('性能与稳定性','Performance & stability')),toggle('downscale',tx('自动缩放','Auto downscale')),numberField('downscaleMaxPixels',tx('图片像素上限','Image pixel limit'),null,1000,100000000),toggle('cache',tx('识图缓存','Vision answer cache')),numberField('cacheTtlSeconds',tx('缓存有效期(秒)','Cache TTL (seconds)'),null,0,31536000),numberField('cacheMaxEntries',tx('最大缓存数量','Maximum cached answers'),null,1,100000),h('h4',{className:'vr-ia-subtitle',key:'t'},tx('超时','Timeouts')),numberField('timeoutMs',tx('单次模型请求','Single model request'),tx('毫秒。','Milliseconds.'),1000,600000),numberField('visionTaskTimeoutMs',tx('单个视觉任务','Single visual task'),tx('包含该任务内部的重试和备用模型;不是每个后端各自一份。','Includes retries and fallbacks inside the task; it is not a fresh budget per backend.'),1000,180000),numberField('ocrTimeoutMs',tx('OCR 任务','OCR task'),null,1000,120000),h('div',{className:'vr-field',key:'ocr-engine'},fieldHead('ocrEngine',tx('OCR 默认引擎','Default OCR engine')),h('select',{className:'vr-input',value:String(value('ocrEngine','auto')||'auto'),disabled:!writable||saving,onChange:function(event){setValue('ocrEngine',event.target.value);}},h('option',{value:'auto'},tx('自动(Tesseract → 视觉模型)','Auto (Tesseract → vision)')),h('option',{value:'tesseract'},tx('仅 Tesseract','Tesseract only')),h('option',{value:'vision'},tx('仅视觉模型','Vision model only'))),h('p',{className:'vr-hint'},tx('vision_ocr 未显式指定 engine 时使用;单次调用的 engine=tesseract/vision 始终优先。','Used when vision_ocr does not explicitly choose an engine; per-call engine=tesseract/vision always wins.'))),h('div',{className:'vr-field',key:'budget'},fieldHead('visionTurnBudgetMs',tx('整轮视觉工具上限','Whole-turn vision-tool limit')),h('select',{className:'vr-input',value:customBudget?'custom':'unlimited',disabled:!writable||saving,onChange:function(event){setValue('visionTurnBudgetMs',event.target.value==='unlimited'?0:(turnBudget>0?turnBudget:180000));}},h('option',{value:'unlimited'},tx('不限制(推荐)','Unlimited (recommended)')),h('option',{value:'custom'},tx('自定义','Custom'))),customBudget?numberField('visionTurnBudgetMs',tx('上限(毫秒)','Limit (ms)'),null,10000,600000):null)]),card([h('h4',{className:'vr-ia-subtitle',key:'cost'},tx('模型顺序与成本','Model order & cost')),toggle('freeCloudFirst',tx('免费云模型优先','Try free cloud models first'))]),card([h('h4',{className:'vr-ia-subtitle',key:'scope'},tx('识图模式范围','Vision mode scope')),toggle('autoWrapProviders',tx('自动允许已启用模型使用识图','Automatically allow enabled models to use Vision mode')),wrappersEditor()]),local?card([h('h4',{className:'vr-ia-subtitle',key:'network'},tx('网络与远程','Network & remote')),h('p',{className:'vr-hint',key:'network-authority'},tx('默认沿用 DSH/Host 的网络出口;只有需要让视觉请求走不同出口时才填写下方覆盖。','By default Vision Router follows the DSH/Host network path. Use the override below only when vision requests need a different egress route.')),toggle('allowRemoteSettings',tx('允许可信 Host 远程修改设置','Allow trusted-host remote settings'),tx('默认关闭;trustedHosts 不是身份认证。','Off by default; trustedHosts is not authentication.')),textField('proxy',tx('视觉专用代理覆盖','Vision-only proxy override'),tx('留空即沿用 DSH/Host;支持 http(s)://、socks5://,并兼容旧 socks5h://。','Leave empty to follow DSH/Host; supports http(s):// and socks5://, with legacy socks5h:// compatibility.')),textareaArray('proxyHosts',tx('视觉代理覆盖域名','Vision proxy override hosts'),tx('仅在上方覆盖非空时生效;每行一个,其余请求继续沿用 DSH/Host。','Used only when the override above is non-empty; one host per line. All other requests keep the DSH/Host path.'))]):null,card([h('h4',{className:'vr-ia-subtitle',key:'compat'},tx('兼容模式','Compatibility')),toggle('rewriteImages',tx('保护纯文本模型','Protect text-only models')),toggle('routing',tx('整轮视觉路由(旧工作流)','Whole-turn vision routing (legacy workflow)')),routing?toggle('reverseRouting',tx('纯文字消息继续使用聊天模型','Keep text-only messages on the chat model')):null,routing?textProviderEditor():null]),card([h('button',{type:'button',className:'vr-btn',key:'dev',onClick:function(){setOpen(Object.assign({},open,{developer:!open.developer}));}},open.developer?tx('隐藏开发者设置','Hide developer settings'):tx('显示开发者设置','Show developer settings')),open.developer?h('div',{className:'vr-ia-dev',key:'body'},toggle('progressiveTools',tx('渐进式工具暴露','Progressive tool exposure')),h('p',{className:'vr-hint',key:'progressive-restart'},tx('保存后需重启 DSH 才生效。','Restart DSH after saving for this change to take effect.')),local?toggle('stealth','Stealth'):null,local?textField('wrapperRoute',tx('包装路由名','Wrapper route name')):null,local?textField('chainRoute',tx('视觉链路由名','Vision chain route name')):null,textareaArray('extraVisionModels',tx('额外视觉能力标记','Extra vision capability labels'),tx('只有诊断发现模型未声明图片能力时才需要。','Needed only when diagnostics show missing image-capability metadata.'))):null]));}
262
262
  function diagnosticValue(label,valueText){return h('div',{className:'vr-ia-diag-row',key:label},h('span',null,label),h('strong',null,valueText));}
263
263
  function updatePanel(){if(!local)return null;var result=updateState.result,auto=result&&result.autoUpdate,current=result&&result.currentVersion?result.currentVersion:tx('检测中','Checking'),latest=result&&result.latestVersion?result.latestVersion:'—',available=result&&result.ok===true&&result.updateAvailable===true,profile=auto&&auto.profile?auto.profile:'web',manualVersion=result&&result.latestVersion?result.latestVersion:'<version>',spec='dsh-vision-router@'+manualVersion,pnpm='pnpm dsh plugin --profile '+profile+' add '+spec,npx='npx @deepseek-ai/dsh plugin --profile '+profile+' add '+spec;return card([h('h4',{className:'vr-ia-subtitle',key:'title'},tx('版本更新','Updates')),h('p',{className:'vr-hint',key:'status'},updateState.status==='running'?tx('正在检查更新…','Checking for updates…'):result&&result.ok===false?tx('更新检查失败:','Update check failed: ')+String(result.error||'unknown'):available?tx('发现新版本:v','Update available: v')+latest+tx('(当前 v',' (current v')+current+')':result&&result.ok===true?tx('已是最新版本 v','Up to date: v')+current:tx('尚未检查','Not checked yet')),h('div',{className:'vr-ia-actions',key:'actions'},h('button',{type:'button',className:'vr-btn',disabled:updateState.status==='running',onClick:function(){void runUpdateCheck(true);}},tx('检查更新','Check for updates')),available&&auto&&auto.supported===true&&auto.token?h('button',{type:'button',className:'vr-btn vr-btn-save',disabled:selfUpdateState.status==='running',onClick:function(){void runSelfUpdate();}},selfUpdateState.status==='running'?tx('更新中…','Updating…'):tx('一键更新','Update now')):null),selfUpdateState.status==='done'&&selfUpdateState.result?h('p',{className:'vr-hint',key:'updated'},tx('更新完成,请重启 DSH。','Update complete. Restart DSH.')):selfUpdateState.error?h('p',{className:'vr-failed',key:'uerr'},String(selfUpdateState.error)):null,available&&(!auto||auto.supported!==true)?h('div',{className:'vr-ia-manual',key:'manual'},h('p',{className:'vr-hint'},tx('当前安装方式不支持安全的一键更新,请使用与你当前 DSH 安装方式一致的命令:','This install method cannot be safely auto-updated. Use the command matching your DSH installation:')),h('code',{className:'vr-ia-code'},pnpm),h('code',{className:'vr-ia-code'},npx)):null]);}
264
264
  function diagnosticsPage(){var providerCount=groups.length,modelCount=groups.reduce(function(total,group){return total+arr(group&&group.models).length;},0),configured=chainRows().filter(function(row){return row&&row.provider&&row.model;}).length,ollama=obj(value('localOllama',{})),lm=obj(value('localLmStudio',{})),testText=!local?tx('仅本机可见','Local only'):testState.status==='running'?tx('检测中','Checking'):testState.status==='done'&&testState.result&&testState.result.ok===true?tx('连接正常','Connected'):testState.status==='done'?tx('连接失败','Failed'):tx('未检测','Not checked'),capsText=caps.status==='ready'?tx('正常','Ready'):caps.status==='loading'?tx('检测中','Checking'):caps.status==='error'?tx('不可用','Unavailable'):tx('未检测','Not checked');var rows=[[tx('设置协议','Settings contract'),String(value('settingsContractRevision','—'))],[tx('模型目录','Model catalog'),catalog.status==='ready'?tx('正常','Ready'):catalog.status==='loading'?tx('检测中','Checking'):tx('不可用','Unavailable')],[tx('图片能力元数据','Image capability metadata'),capsText],[tx('可选 Provider','Selectable providers'),String(providerCount)],[tx('可选模型','Selectable models'),String(modelCount)],[tx('已配置识图模型','Configured vision models'),String(configured)],[tx('内置免费兜底','Built-in free fallback'),toggleValue('freeFallback',true)?(caps.builtinFallback.length?tx('已启用,','Enabled, ')+caps.builtinFallback.length+tx(' 个模型',' models'):tx('已启用','Enabled')):tx('已关闭','Disabled')],[tx('后端连接','Backend connection'),testText],[tx('Ollama','Ollama'),local?(ollama.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('LM Studio','LM Studio'),local?(lm.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('桌面截图','Desktop capture'),local?(toggleValue('desktopScreenshot',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('网络出口','Network egress'),local?(String(value('proxy','')).trim()?tx('视觉专用覆盖','Vision-only override'):tx('DSH/Host(默认)','DSH/Host (default)')):tx('仅本机可见','Local only')],[tx('远程设置','Remote settings'),local?(toggleValue('allowRemoteSettings',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('当前为远程安全视图','Remote safe view')]];function reportText(){return ['Vision Router diagnostics'].concat(rows.map(function(row){return row[0]+': '+row[1];}),['doctor: dsh-vision-router doctor']).join('\n');}async function copyReport(){try{if(typeof navigator!=='undefined'&&navigator.clipboard&&typeof navigator.clipboard.writeText==='function')await navigator.clipboard.writeText(reportText());setActionState({status:'copied'});}catch(error){setActionState({status:'error',error:error&&error.message?error.message:String(error)});}}return h(React.Fragment,null,title(tx('诊断','Diagnostics'),tx('状态页不会修改你的模型/路由设置;连接测试和更新操作会明确由按钮触发。','The status page does not change model or routing settings; connection tests and updates are explicit actions.')),card(rows.map(function(row){return diagnosticValue(row[0],row[1]);})),card([h('div',{className:'vr-ia-actions',key:'actions'},local?h('button',{type:'button',className:'vr-btn',disabled:testState.status==='running',onClick:function(){void runTestConnection();}},tx('测试连接','Test connection')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){invalidateCatalog();}},tx('重新检测模型','Re-detect models')),local?h('button',{type:'button',className:'vr-btn',onClick:function(){void openLogs();}},tx('打开日志文件夹','Open logs folder')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){void copyReport();}},tx('复制诊断信息','Copy diagnostics'))),h('p',{className:'vr-hint',key:'doctor'},tx('需要完整 DSH 版本、安装、Profile、运行时路由和会话诊断时执行:dsh-vision-router doctor','For the full DSH version, installation, profile, runtime-route, and session report, run: dsh-vision-router doctor')),testState.status==='done'&&testState.result&&testState.result.ok!==true?h('p',{className:'vr-failed',key:'testerr'},String(testState.result.error||tx('连接测试失败','Connection test failed'))):null,actionState.status==='error'?h('p',{className:'vr-failed',key:'aerr'},String(actionState.error||'unknown')):null]),updatePanel());}
@@ -83,7 +83,7 @@ export function transformSettingsIaToNativeCards(source = SETTINGS_IA_CLIENT_PRE
83
83
  general:new Set(['providers','freeFallback']),
84
84
  strategy:new Set(['tool','structuredVisionBootstrap','visionDepth','visionDepthMaxCalls','guidanceOverrides']),
85
85
  local:new Set(['localOllama','localLmStudio','desktopScreenshot']),
86
- advanced:new Set(['wrappedProviders','downscale','downscaleMaxPixels','cache','cacheTtlSeconds','cacheMaxEntries','timeoutMs','visionTaskTimeoutMs','ocrTimeoutMs','visionTurnBudgetMs','freeCloudFirst','autoWrapProviders','allowRemoteSettings','proxy','proxyHosts','rewriteImages','routing','reverseRouting','textProvider','progressiveTools','stealth','wrapperRoute','chainRoute','extraVisionModels']),
86
+ advanced:new Set(['wrappedProviders','downscale','downscaleMaxPixels','cache','cacheTtlSeconds','cacheMaxEntries','timeoutMs','visionTaskTimeoutMs','ocrTimeoutMs','ocrEngine','visionTurnBudgetMs','freeCloudFirst','autoWrapProviders','allowRemoteSettings','proxy','proxyHosts','rewriteImages','routing','reverseRouting','textProvider','progressiveTools','stealth','wrapperRoute','chainRoute','extraVisionModels']),
87
87
  diagnostics:new Set([])
88
88
  };
89
89
  var invalidChainDraft=chainDraft!==undefined&&!validChain(chainRows());