dsh-decision-tools 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +234 -2
- package/README.zh.md +235 -0
- package/THIRD_PARTY_NOTICES +16 -0
- package/assets/decision-usage/SKILL.md +71 -0
- package/cordis.patch.yml +49 -0
- package/docs/architecture-current.md +349 -0
- package/docs/threshold-calibration.md +112 -0
- package/docs/use-cases.md +182 -0
- package/lib/browser.js +1014 -0
- package/lib/client.js +352 -0
- package/lib/config-bus.js +133 -0
- package/lib/configs.js +58 -0
- package/lib/decision-entry.js +15 -0
- package/lib/deliver.js +44 -0
- package/lib/density.js +88 -0
- package/lib/escalate.js +328 -0
- package/lib/event-log.js +159 -0
- package/lib/index.js +75 -0
- package/lib/monitor-entry.js +15 -0
- package/lib/monitor-tool.js +648 -0
- package/lib/monitor.js +1232 -0
- package/lib/notify.js +209 -0
- package/lib/ollama.js +199 -0
- package/lib/parameters.js +183 -0
- package/lib/pipe.js +525 -0
- package/lib/plan.js +73 -0
- package/lib/render.js +193 -0
- package/lib/scan.js +151 -0
- package/lib/skill.js +88 -0
- package/lib/source.js +362 -0
- package/lib/tool.js +1331 -0
- package/lib/validate.js +934 -0
- package/lib/wire.js +76 -0
- package/package.json +85 -4
- package/scripts/link-peers.mjs +55 -0
- package/scripts/list-models.mjs +50 -0
- package/scripts/rebuild-node-modules.mjs +109 -0
package/lib/browser.js
ADDED
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-decision-tools — browser half.
|
|
3
|
+
*
|
|
4
|
+
* 给核心行、decision 行、monitor 行各注册一个 Plugins 页 Configure 表单:
|
|
5
|
+
* plugins.row.config,key = 包名#行 id。
|
|
6
|
+
*
|
|
7
|
+
* 表单走平台自己的 settings 服务:ctx.configForms.get(rowId) 读快照,Save 时
|
|
8
|
+
* mutate(ops, revision)。草稿是 staged 的:编辑不写盘,Save 才写;冲突保留草稿。
|
|
9
|
+
*
|
|
10
|
+
* 这个模块在 web boot 时被加载,任何一个 import/render 抛出都可能拖垮整次启动,
|
|
11
|
+
* 因此 React / primitives 都是可选 import,每个贡献都包在 safely() 里。
|
|
12
|
+
*/
|
|
13
|
+
window.__ModuleLoader__.load({
|
|
14
|
+
id: 'dsh-decision-tools',
|
|
15
|
+
factory: (require) => {
|
|
16
|
+
var module = { exports: {} };
|
|
17
|
+
var exports = module.exports;
|
|
18
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
19
|
+
|
|
20
|
+
function unavailable(reason) {
|
|
21
|
+
// React 缺失时无法渲染任何东西,只能不注册;primitives 缺失则用下面的 honest HTML 兜底。
|
|
22
|
+
module.exports = {
|
|
23
|
+
inject: [],
|
|
24
|
+
apply: function (ctx) {
|
|
25
|
+
try { ctx?.logger?.warn?.('dsh-decision-tools: client disabled (' + reason + ')'); } catch (ignored) { /* noop */ }
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
return module.exports;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var React;
|
|
32
|
+
try { React = require('react'); } catch (error) { React = undefined; }
|
|
33
|
+
if (React === undefined || React === null || typeof React.createElement !== 'function') {
|
|
34
|
+
return unavailable('react missing');
|
|
35
|
+
}
|
|
36
|
+
var h = React.createElement;
|
|
37
|
+
var loadedPrimitives;
|
|
38
|
+
try { loadedPrimitives = require('@deepseek-ai/dsh-client-ui-primitives'); } catch (error) { loadedPrimitives = undefined; }
|
|
39
|
+
var REQUIRED_PRIMITIVES = ['SettingsForm', 'SettingsValueField', 'Checkbox', 'Button', 'SegmentedControl'];
|
|
40
|
+
// 真实部署(profile 里没有 @deepseek-ai/dsh-client-ui-primitives)会走 HTML 兜底。
|
|
41
|
+
// 不能因为缺一个导出就整页不注册:那会让 Configure 入口直接消失;也不能把 undefined
|
|
42
|
+
// 交给 React(Element type is invalid)——所以逐个补齐,缺哪个补哪个。
|
|
43
|
+
var stubPrimitives = {
|
|
44
|
+
SettingsForm: function (props) { return h('div', { className: 'dshdt-form' }, props && props.children); },
|
|
45
|
+
SettingsValueField: function (props) {
|
|
46
|
+
return h('input', {
|
|
47
|
+
type: 'text',
|
|
48
|
+
value: props && props.text !== undefined ? props.text : '',
|
|
49
|
+
disabled: props && props.disabled,
|
|
50
|
+
onChange: function (event) { if (props && props.onEdit) props.onEdit(event.target.value); },
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
Checkbox: function (props) {
|
|
54
|
+
return h('input', {
|
|
55
|
+
type: 'checkbox',
|
|
56
|
+
checked: props && props.checked === true,
|
|
57
|
+
disabled: props && props.disabled,
|
|
58
|
+
onChange: function (event) { if (props && props.onChange) props.onChange(event.target.checked); },
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
Button: function (props) {
|
|
62
|
+
return h('button', {
|
|
63
|
+
type: props && props.type ? props.type : 'button',
|
|
64
|
+
disabled: props && props.disabled,
|
|
65
|
+
onClick: props && props.onClick,
|
|
66
|
+
}, props && props.children);
|
|
67
|
+
},
|
|
68
|
+
SegmentedControl: function (props) {
|
|
69
|
+
return h('div', { className: 'dshdt-segmented' }, (props && props.options ? props.options : []).map(function (option) {
|
|
70
|
+
return h('button', {
|
|
71
|
+
key: option.value,
|
|
72
|
+
type: 'button',
|
|
73
|
+
disabled: props && props.disabled,
|
|
74
|
+
onClick: function () { if (props && props.onChange) props.onChange(option.value); },
|
|
75
|
+
}, option.label);
|
|
76
|
+
}));
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
var primitives = Object.assign({}, loadedPrimitives || {});
|
|
80
|
+
var degradedPrimitives = REQUIRED_PRIMITIVES.filter(function (name) { return typeof primitives[name] !== 'function'; });
|
|
81
|
+
degradedPrimitives.forEach(function (name) { primitives[name] = stubPrimitives[name]; });
|
|
82
|
+
|
|
83
|
+
var NS = 'settings.decision-tools';
|
|
84
|
+
var PACKAGE_NAME = 'dsh-decision-tools';
|
|
85
|
+
var CORE_ROW_ID = 'decision-tools-plugin';
|
|
86
|
+
|
|
87
|
+
var zh = {
|
|
88
|
+
'summary.generic': '在这里配置模型与工具行为;保存后下一次调用生效。',
|
|
89
|
+
'defaultLabel': '默认',
|
|
90
|
+
'group.connection': '连接',
|
|
91
|
+
'group.runtime': '运行时',
|
|
92
|
+
'group.models': '模型',
|
|
93
|
+
'group.behavior': '判定行为',
|
|
94
|
+
'group.scan': '扫描',
|
|
95
|
+
'group.probe': '探测',
|
|
96
|
+
'modelsTitle': '可用模型',
|
|
97
|
+
'modelsHint': '先保存连接配置,再查询;结果可直接设为默认或加入顺序。',
|
|
98
|
+
'modelsFilter': '筛选模型…',
|
|
99
|
+
'modelsEmpty': '还没有查询结果。',
|
|
100
|
+
'modelsNoMatch': '没有匹配的模型。',
|
|
101
|
+
'currentDefault': '当前默认',
|
|
102
|
+
'inOrder': '已在顺序',
|
|
103
|
+
'modelsAddAll': '全部加入顺序',
|
|
104
|
+
'modelsColumnModel': '模型',
|
|
105
|
+
'modelsColumnContext': '上下文',
|
|
106
|
+
'modelsColumnSize': '大小',
|
|
107
|
+
'modelsAllFamilies': '全部家族',
|
|
108
|
+
'modelsAllCapabilities': '全部能力',
|
|
109
|
+
'modelsAnyContext': '任意上下文',
|
|
110
|
+
'capability.completion': '问答',
|
|
111
|
+
'capability.tools': '工具',
|
|
112
|
+
'capability.vision': '视觉',
|
|
113
|
+
'capability.embedding': '嵌入',
|
|
114
|
+
'modelsColumnRole': '状态',
|
|
115
|
+
'modelsColumnAction': '操作',
|
|
116
|
+
'refreshModels': '查询模型',
|
|
117
|
+
'modelsLoading': '查询中…',
|
|
118
|
+
'modelLoadFailed': '查询失败:',
|
|
119
|
+
'useModel': '设为默认',
|
|
120
|
+
'addToOrder': '加入顺序',
|
|
121
|
+
'coreNote': '核心行承载共享模型配置与这些 Configure 页;decision / monitor 两行可独立开关。核心行关闭后两个工具仍用内置默认值工作,但本页的配置入口会消失。',
|
|
122
|
+
'unavailable': '这一行当前没有加载,暂时无法配置。',
|
|
123
|
+
'readOnly': '当前部署以只读方式提供设置。',
|
|
124
|
+
'saveFailed': '保存没有被接受,草稿已保留,请修正后重试。',
|
|
125
|
+
'save': '保存',
|
|
126
|
+
'saving': '保存中…',
|
|
127
|
+
'overridden': '已覆盖',
|
|
128
|
+
'reset': '恢复默认',
|
|
129
|
+
'invalidNumber': '请输入数字,留空表示使用默认值。',
|
|
130
|
+
'invalidNumberFormat': '这个值会按数字保存,而不是按输入的文本保存。',
|
|
131
|
+
'option.quick': '快速',
|
|
132
|
+
'option.normal': '标准',
|
|
133
|
+
'option.deep': '深度',
|
|
134
|
+
'option.none': '不聚合',
|
|
135
|
+
'option.any': '任一命中',
|
|
136
|
+
'baseUrl.label': 'Ollama 地址',
|
|
137
|
+
'baseUrl.hint': '保留路径前缀;留空使用默认地址。',
|
|
138
|
+
'defaultModel.label': '默认 decision 模型',
|
|
139
|
+
'defaultModel.hint': '工具行未设模型覆盖时使用;例如 tev1:0.8b、tev1:latest、clef-flash:latest、nimble:latest;用 node scripts/list-models.mjs 查本机已装模型。',
|
|
140
|
+
'timeoutMs.label': '请求超时(毫秒)',
|
|
141
|
+
'timeoutMs.hint': '正整数;留空使用默认值。',
|
|
142
|
+
'keepAlive.label': '模型驻留时间',
|
|
143
|
+
'keepAlive.hint': '如 30m 或秒数;留空表示不下发 keep_alive。',
|
|
144
|
+
'apiKey.label': 'API Key',
|
|
145
|
+
'apiKey.hint': '可选;作为 Authorization: Bearer 发送给模型接口。',
|
|
146
|
+
'modelsPath.label': '模型列表路径',
|
|
147
|
+
'modelsPath.hint': 'Ollama 用 /api/tags,OpenAI 兼容接口可用 /v1/models。',
|
|
148
|
+
'systemOnePath.label': '判定接口路径',
|
|
149
|
+
'systemOnePath.hint': '相对 baseUrl;默认 v1/systemone。',
|
|
150
|
+
'modelOrder.label': '模型优先级',
|
|
151
|
+
'modelOrder.hint': '逗号分隔;可用 model@numCtx 指定上下文;未知模型按 8192 参与。',
|
|
152
|
+
'escalationEnabled.label': '允许容量溢出升档',
|
|
153
|
+
'escalationEnabled.hint': '关闭后只尝试选中的档,不再因上下文溢出换更大模型。',
|
|
154
|
+
'modelOverride.label': '模型覆盖(本行)',
|
|
155
|
+
'modelOverride.hint': '在这里为这一行选择模型覆盖。',
|
|
156
|
+
'defaultDepth.label': '默认深度',
|
|
157
|
+
'defaultDepth.hint': '调用方省略 depth 时使用。',
|
|
158
|
+
'bodyWarning.label': '体积告警',
|
|
159
|
+
'bodyWarning.hint': '关闭后不再返回体积/文本形状提示。',
|
|
160
|
+
'softBodyBytes.label': '体积告警阈值(字节)',
|
|
161
|
+
'softBodyBytes.hint': '0 表示每次都告警;留空使用默认值。',
|
|
162
|
+
'minConfidence.label': '置信度告警阈值',
|
|
163
|
+
'minConfidence.hint': '0 表示关闭;留空使用默认值。',
|
|
164
|
+
'maxChoiceOptions.label': 'choice 选项上限',
|
|
165
|
+
'maxChoiceOptions.hint': 'choice 选项数上限。',
|
|
166
|
+
'escalationMaxAttempts.label': '额外升档次数',
|
|
167
|
+
'escalationMaxAttempts.hint': '不含首次调用;deep 会再 +2;留空使用默认值 3。',
|
|
168
|
+
'scanFirstChunkBytes.label': '扫描首块字节',
|
|
169
|
+
'scanFirstChunkBytes.hint': 'deep 内部分块的首块预算。',
|
|
170
|
+
'scanMaxChunks.label': '扫描最大块数',
|
|
171
|
+
'scanMaxChunks.hint': 'deep 内部分块的块数上限。',
|
|
172
|
+
'scanMaxTotalBytes.label': '扫描总量上限(字节)',
|
|
173
|
+
'scanMaxTotalBytes.hint': 'deep 内部分块读取的总量上限。',
|
|
174
|
+
'scanDeadlineMs.label': '扫描墙钟预算(毫秒)',
|
|
175
|
+
'scanDeadlineMs.hint': 'deep 内部分块的总时间预算。',
|
|
176
|
+
'scanAggregate.label': '扫描聚合',
|
|
177
|
+
'scanAggregate.hint': '分块答案的合并策略。',
|
|
178
|
+
'minProbeIntervalMs.label': '探测间隔下限(毫秒)',
|
|
179
|
+
'minProbeIntervalMs.hint': '模型给的 intervalMs 不得低于此值。',
|
|
180
|
+
'notifyCoalesceWindowMs.label': '通知合并窗口(毫秒)',
|
|
181
|
+
'notifyCoalesceWindowMs.hint': '同类通知在该窗口内合并;0 表示不合并。',
|
|
182
|
+
'maxNotificationsPerMinute.label': '每分钟通知上限',
|
|
183
|
+
'maxNotificationsPerMinute.hint': '正整数;留空使用默认值。',
|
|
184
|
+
};
|
|
185
|
+
var en = {
|
|
186
|
+
'summary.generic': 'Configure the model and tool behaviour here; saved values apply to the next call.',
|
|
187
|
+
'defaultLabel': 'Default',
|
|
188
|
+
'group.connection': 'Connection',
|
|
189
|
+
'group.runtime': 'Runtime',
|
|
190
|
+
'group.models': 'Models',
|
|
191
|
+
'group.behavior': 'Behaviour',
|
|
192
|
+
'group.scan': 'Scanning',
|
|
193
|
+
'group.probe': 'Probing',
|
|
194
|
+
'modelsTitle': 'Available models',
|
|
195
|
+
'modelsHint': 'Save the connection settings, then query; results can be set as default or added to the order.',
|
|
196
|
+
'modelsFilter': 'Filter models…',
|
|
197
|
+
'modelsEmpty': 'No models loaded yet.',
|
|
198
|
+
'modelsNoMatch': 'No matching models.',
|
|
199
|
+
'currentDefault': 'Current default',
|
|
200
|
+
'inOrder': 'In order',
|
|
201
|
+
'modelsAddAll': 'Add all to order',
|
|
202
|
+
'modelsColumnModel': 'Model',
|
|
203
|
+
'modelsColumnContext': 'Context',
|
|
204
|
+
'modelsColumnSize': 'Size',
|
|
205
|
+
'modelsAllFamilies': 'All families',
|
|
206
|
+
'modelsAllCapabilities': 'All capabilities',
|
|
207
|
+
'modelsAnyContext': 'Any context',
|
|
208
|
+
'capability.completion': 'Chat',
|
|
209
|
+
'capability.tools': 'Tools',
|
|
210
|
+
'capability.vision': 'Vision',
|
|
211
|
+
'capability.embedding': 'Embed',
|
|
212
|
+
'modelsColumnRole': 'Status',
|
|
213
|
+
'modelsColumnAction': 'Actions',
|
|
214
|
+
'refreshModels': 'Refresh models',
|
|
215
|
+
'modelsLoading': 'Loading…',
|
|
216
|
+
'modelLoadFailed': 'Model discovery failed:',
|
|
217
|
+
'useModel': 'Use as default',
|
|
218
|
+
'addToOrder': 'Add to order',
|
|
219
|
+
'coreNote': 'The core row carries the shared model configuration and these Configure pages; the decision and monitor rows can be switched independently. With the core row off, both tools still run on built-in defaults, but these configuration pages disappear.',
|
|
220
|
+
'unavailable': 'This row is not loaded, so it cannot be configured right now.',
|
|
221
|
+
'readOnly': 'This deployment serves settings read-only.',
|
|
222
|
+
'saveFailed': 'The deployment did not accept these values; the drafts were kept for you to correct.',
|
|
223
|
+
'save': 'Save',
|
|
224
|
+
'saving': 'Saving…',
|
|
225
|
+
'overridden': 'Overridden',
|
|
226
|
+
'reset': 'Reset to default',
|
|
227
|
+
'invalidNumber': 'Enter a number, or leave it blank to use the default.',
|
|
228
|
+
'invalidNumberFormat': 'This value is saved as the number it names, not as the text you typed.',
|
|
229
|
+
'option.quick': 'Quick',
|
|
230
|
+
'option.normal': 'Normal',
|
|
231
|
+
'option.deep': 'Deep',
|
|
232
|
+
'option.none': 'None',
|
|
233
|
+
'option.any': 'Any',
|
|
234
|
+
'baseUrl.label': 'Ollama endpoint',
|
|
235
|
+
'baseUrl.hint': 'Path prefixes are preserved; blank uses the default address.',
|
|
236
|
+
'defaultModel.label': 'Default decision model',
|
|
237
|
+
'defaultModel.hint': 'Used when a tool row has no model override; e.g. tev1:0.8b, tev1:latest, clef-flash:latest, nimble:latest; run node scripts/list-models.mjs for installed models.',
|
|
238
|
+
'timeoutMs.label': 'Request timeout (ms)',
|
|
239
|
+
'timeoutMs.hint': 'Positive integer; blank uses the default.',
|
|
240
|
+
'keepAlive.label': 'Model keep-alive',
|
|
241
|
+
'keepAlive.hint': 'For example 30m or seconds; blank means keep_alive is not sent.',
|
|
242
|
+
'apiKey.label': 'API key',
|
|
243
|
+
'apiKey.hint': 'Optional; sent as Authorization: Bearer to the model endpoint.',
|
|
244
|
+
'modelsPath.label': 'Model list path',
|
|
245
|
+
'modelsPath.hint': 'Ollama uses /api/tags; OpenAI-compatible endpoints may use /v1/models.',
|
|
246
|
+
'systemOnePath.label': 'Judgement path',
|
|
247
|
+
'systemOnePath.hint': 'Relative to baseUrl; default v1/systemone.',
|
|
248
|
+
'modelOrder.label': 'Model priority',
|
|
249
|
+
'modelOrder.hint': 'Comma-separated; use model@numCtx for context; unknown models use 8192.',
|
|
250
|
+
'escalationEnabled.label': 'Allow overflow escalation',
|
|
251
|
+
'escalationEnabled.hint': 'When off, only the selected model is tried; context overflow does not switch to a larger one.',
|
|
252
|
+
'modelOverride.label': 'Model override (this row)',
|
|
253
|
+
'modelOverride.hint': 'Choose a per-row model override here.',
|
|
254
|
+
'defaultDepth.label': 'Default depth',
|
|
255
|
+
'defaultDepth.hint': 'Used when the caller omits depth.',
|
|
256
|
+
'bodyWarning.label': 'Body warnings',
|
|
257
|
+
'bodyWarning.hint': 'When off, size and text-shape hints are not returned.',
|
|
258
|
+
'softBodyBytes.label': 'Body warning threshold (bytes)',
|
|
259
|
+
'softBodyBytes.hint': '0 warns on every call; blank uses the default.',
|
|
260
|
+
'minConfidence.label': 'Confidence warning threshold',
|
|
261
|
+
'minConfidence.hint': '0 disables the warning; blank uses the default.',
|
|
262
|
+
'maxChoiceOptions.label': 'Maximum choice options',
|
|
263
|
+
'maxChoiceOptions.hint': 'Maximum number of choice options.',
|
|
264
|
+
'escalationMaxAttempts.label': 'Extra escalation attempts',
|
|
265
|
+
'escalationMaxAttempts.hint': 'Excludes the first call; deep adds 2 more; blank uses 3.',
|
|
266
|
+
'scanFirstChunkBytes.label': 'Scan first chunk (bytes)',
|
|
267
|
+
'scanFirstChunkBytes.hint': 'Initial budget for deep internal chunking.',
|
|
268
|
+
'scanMaxChunks.label': 'Maximum scan chunks',
|
|
269
|
+
'scanMaxChunks.hint': 'Chunk count limit for deep internal chunking.',
|
|
270
|
+
'scanMaxTotalBytes.label': 'Maximum scan bytes',
|
|
271
|
+
'scanMaxTotalBytes.hint': 'Total bytes read by deep internal chunking.',
|
|
272
|
+
'scanDeadlineMs.label': 'Scan wall-clock budget (ms)',
|
|
273
|
+
'scanDeadlineMs.hint': 'Total time budget for deep internal chunking.',
|
|
274
|
+
'scanAggregate.label': 'Scan aggregation',
|
|
275
|
+
'scanAggregate.hint': 'How chunk answers are combined.',
|
|
276
|
+
'minProbeIntervalMs.label': 'Minimum probe interval (ms)',
|
|
277
|
+
'minProbeIntervalMs.hint': 'The intervalMs supplied by the model cannot go below this.',
|
|
278
|
+
'notifyCoalesceWindowMs.label': 'Notice coalesce window (ms)',
|
|
279
|
+
'notifyCoalesceWindowMs.hint': 'Same-kind notices are merged in this window; 0 disables merging.',
|
|
280
|
+
'maxNotificationsPerMinute.label': 'Maximum notices per minute',
|
|
281
|
+
'maxNotificationsPerMinute.hint': 'Positive integer; blank uses the default.',
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
function field(path, kind, options) {
|
|
285
|
+
return { key: path.join('.'), path: path, kind: kind, options: options };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 表单里空值并不等于“没有行为”:工具层有大量运行期默认值。这里显式给出每个字段的
|
|
289
|
+
// 生效默认,既用于空值时的控件选中态,也用于字段下方的“默认:…”一行。
|
|
290
|
+
var FIELD_DEFAULTS = {
|
|
291
|
+
baseUrl: { value: 'http://localhost:11434', text: 'http://localhost:11434' },
|
|
292
|
+
defaultModel: { value: 'tev1:0.8b', text: 'tev1:0.8b' },
|
|
293
|
+
timeoutMs: { value: 120000, text: '120000 ms' },
|
|
294
|
+
keepAlive: { text: '未设(服务端默认)' },
|
|
295
|
+
apiKey: { text: '未设' },
|
|
296
|
+
modelsPath: { value: '/api/tags', text: '/api/tags' },
|
|
297
|
+
systemOnePath: { value: 'v1/systemone', text: 'v1/systemone' },
|
|
298
|
+
modelOrder: { text: '默认 ladder 顺序' },
|
|
299
|
+
escalationEnabled: { value: true, text: '开' },
|
|
300
|
+
modelOverride: { text: '跟随核心行 defaultModel' },
|
|
301
|
+
defaultDepth: { value: 'normal', text: 'normal' },
|
|
302
|
+
bodyWarning: { value: true, text: '开' },
|
|
303
|
+
softBodyBytes: { value: 4096, text: '4096 B' },
|
|
304
|
+
minConfidence: { value: 0.25, text: '0.25' },
|
|
305
|
+
maxChoiceOptions: { value: 255, text: '255' },
|
|
306
|
+
escalationMaxAttempts: { value: 3, text: '3' },
|
|
307
|
+
scanFirstChunkBytes: { value: 4096, text: '4096 B' },
|
|
308
|
+
scanMaxChunks: { value: 32, text: '32' },
|
|
309
|
+
scanMaxTotalBytes: { value: 4194304, text: '4194304(4 MiB)' },
|
|
310
|
+
scanDeadlineMs: { value: 180000, text: '180000 ms' },
|
|
311
|
+
scanAggregate: { value: 'none', text: 'none' },
|
|
312
|
+
minProbeIntervalMs: { value: 1000, text: '1000 ms' },
|
|
313
|
+
notifyCoalesceWindowMs: { value: 5000, text: '5000 ms' },
|
|
314
|
+
maxNotificationsPerMinute: { value: 6, text: '6' },
|
|
315
|
+
};
|
|
316
|
+
var FIELD_GROUP = {
|
|
317
|
+
baseUrl: 'connection', apiKey: 'connection', modelsPath: 'connection', systemOnePath: 'connection',
|
|
318
|
+
timeoutMs: 'runtime', keepAlive: 'runtime', escalationEnabled: 'runtime',
|
|
319
|
+
defaultModel: 'models', modelOrder: 'models', modelOverride: 'models', defaultDepth: 'models',
|
|
320
|
+
bodyWarning: 'behavior', softBodyBytes: 'behavior', minConfidence: 'behavior',
|
|
321
|
+
maxChoiceOptions: 'behavior', escalationMaxAttempts: 'behavior',
|
|
322
|
+
scanFirstChunkBytes: 'scan', scanMaxChunks: 'scan', scanMaxTotalBytes: 'scan',
|
|
323
|
+
scanDeadlineMs: 'scan', scanAggregate: 'scan',
|
|
324
|
+
minProbeIntervalMs: 'probe', notifyCoalesceWindowMs: 'probe', maxNotificationsPerMinute: 'probe',
|
|
325
|
+
};
|
|
326
|
+
function defaultOf(key) { return FIELD_DEFAULTS[key]; }
|
|
327
|
+
function defaultTextOf(key) { var entry = FIELD_DEFAULTS[key]; return entry === undefined ? undefined : entry.text; }
|
|
328
|
+
|
|
329
|
+
var ROWS = [
|
|
330
|
+
{
|
|
331
|
+
rowId: CORE_ROW_ID,
|
|
332
|
+
summary: 'summary.generic',
|
|
333
|
+
fields: [
|
|
334
|
+
field(['baseUrl'], 'text'),
|
|
335
|
+
field(['apiKey'], 'text'),
|
|
336
|
+
field(['modelsPath'], 'text'),
|
|
337
|
+
field(['systemOnePath'], 'text'),
|
|
338
|
+
field(['timeoutMs'], 'number'),
|
|
339
|
+
field(['keepAlive'], 'text'),
|
|
340
|
+
field(['escalationEnabled'], 'boolean'),
|
|
341
|
+
field(['defaultModel'], 'text'),
|
|
342
|
+
field(['modelOrder'], 'text'),
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
rowId: 'decision-tools-decision',
|
|
347
|
+
summary: 'summary.generic',
|
|
348
|
+
fields: [
|
|
349
|
+
field(['modelOverride'], 'text'),
|
|
350
|
+
field(['defaultDepth'], 'enum', ['quick', 'normal', 'deep']),
|
|
351
|
+
field(['bodyWarning'], 'boolean'),
|
|
352
|
+
field(['softBodyBytes'], 'number'),
|
|
353
|
+
field(['minConfidence'], 'number'),
|
|
354
|
+
field(['maxChoiceOptions'], 'number'),
|
|
355
|
+
field(['escalationMaxAttempts'], 'number'),
|
|
356
|
+
field(['scanFirstChunkBytes'], 'number'),
|
|
357
|
+
field(['scanMaxChunks'], 'number'),
|
|
358
|
+
field(['scanMaxTotalBytes'], 'number'),
|
|
359
|
+
field(['scanDeadlineMs'], 'number'),
|
|
360
|
+
field(['scanAggregate'], 'enum', ['none', 'any']),
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
rowId: 'decision-tools-monitor',
|
|
365
|
+
summary: 'summary.generic',
|
|
366
|
+
fields: [
|
|
367
|
+
field(['modelOverride'], 'text'),
|
|
368
|
+
field(['minProbeIntervalMs'], 'number'),
|
|
369
|
+
field(['notifyCoalesceWindowMs'], 'number'),
|
|
370
|
+
field(['maxNotificationsPerMinute'], 'number'),
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
function rowKey(rowId) { return PACKAGE_NAME + '#' + rowId; }
|
|
376
|
+
|
|
377
|
+
function at(layer, path) {
|
|
378
|
+
var node = layer;
|
|
379
|
+
for (var i = 0; i < path.length; i += 1) {
|
|
380
|
+
if (node === null || typeof node !== 'object') return undefined;
|
|
381
|
+
node = node[path[i]];
|
|
382
|
+
}
|
|
383
|
+
return node;
|
|
384
|
+
}
|
|
385
|
+
function has(layer, path) {
|
|
386
|
+
var node = layer;
|
|
387
|
+
for (var i = 0; i < path.length; i += 1) {
|
|
388
|
+
if (node === null || typeof node !== 'object' || !Object.hasOwn(node, path[i])) return false;
|
|
389
|
+
node = node[path[i]];
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
function coerce(kind, draft) {
|
|
394
|
+
if (kind === 'number') {
|
|
395
|
+
if (typeof draft === 'number') return Number.isFinite(draft) ? draft : undefined;
|
|
396
|
+
var text = String(draft).trim();
|
|
397
|
+
if (text.length === 0) return undefined;
|
|
398
|
+
var value = Number(text);
|
|
399
|
+
return Number.isFinite(value) ? value : undefined;
|
|
400
|
+
}
|
|
401
|
+
if (kind === 'boolean') return draft === true;
|
|
402
|
+
return String(draft).trim();
|
|
403
|
+
}
|
|
404
|
+
function sameValue(kind, current, next) {
|
|
405
|
+
if (kind === 'number') {
|
|
406
|
+
var left = typeof current === 'number' ? current : Number(current);
|
|
407
|
+
var right = Number(next);
|
|
408
|
+
return Number.isFinite(left) && Number.isFinite(right) ? left === right : Object.is(current, next);
|
|
409
|
+
}
|
|
410
|
+
if (kind === 'boolean') return (current === true) === (next === true);
|
|
411
|
+
return current === next;
|
|
412
|
+
}
|
|
413
|
+
function invalidReason(knob, text) {
|
|
414
|
+
var trimmed = String(text).trim();
|
|
415
|
+
if (trimmed.length === 0) return undefined;
|
|
416
|
+
if (knob.kind !== 'number') return undefined;
|
|
417
|
+
var number = Number(trimmed);
|
|
418
|
+
if (!Number.isFinite(number)) return 'number';
|
|
419
|
+
return String(number) === trimmed ? undefined : 'numberFormat';
|
|
420
|
+
}
|
|
421
|
+
function plannedOp(knob, entry, snapshot) {
|
|
422
|
+
if (entry === undefined) return undefined;
|
|
423
|
+
if (entry.op === 'unset') return has(snapshot.user, knob.path) ? { op: 'unset', path: knob.path } : undefined;
|
|
424
|
+
var value = coerce(knob.kind, entry.draft);
|
|
425
|
+
if (value === undefined) return undefined;
|
|
426
|
+
if (sameValue(knob.kind, at(snapshot.value, knob.path), value)) return undefined;
|
|
427
|
+
return { op: 'set', path: knob.path, value: value };
|
|
428
|
+
}
|
|
429
|
+
function planOps(fields, staged, snapshot) {
|
|
430
|
+
var ops = [];
|
|
431
|
+
for (var i = 0; i < fields.length; i += 1) {
|
|
432
|
+
var op = plannedOp(fields[i], staged.get(fields[i].key), snapshot);
|
|
433
|
+
if (op !== undefined) ops.push(op);
|
|
434
|
+
}
|
|
435
|
+
return ops;
|
|
436
|
+
}
|
|
437
|
+
function fallbackScope() {
|
|
438
|
+
// 核心行的设置命名空间在部分部署里不会被服务;页面仍要出现,卡片内部显示 unavailable。
|
|
439
|
+
var snapshot = { status: 'unavailable', writable: false, revision: 0, value: {}, base: {}, user: {} };
|
|
440
|
+
return {
|
|
441
|
+
getSnapshot: function () { return snapshot; },
|
|
442
|
+
subscribe: function () { return function () {}; },
|
|
443
|
+
mutate: function () { return Promise.resolve(false); },
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function storeOf(compute) {
|
|
448
|
+
var snapshot = compute();
|
|
449
|
+
var listeners = new Set();
|
|
450
|
+
return {
|
|
451
|
+
getSnapshot: function () { return snapshot; },
|
|
452
|
+
subscribe: function (listener) {
|
|
453
|
+
listeners.add(listener);
|
|
454
|
+
return function () { listeners.delete(listener); };
|
|
455
|
+
},
|
|
456
|
+
publish: function () {
|
|
457
|
+
snapshot = compute();
|
|
458
|
+
listeners.forEach(function (listener) { listener(); });
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
class RowConfigForm {
|
|
464
|
+
constructor(scope, row) {
|
|
465
|
+
this.scope = scope;
|
|
466
|
+
this.row = row;
|
|
467
|
+
this.staged = new Map();
|
|
468
|
+
this.baseline = undefined;
|
|
469
|
+
this.saving = false;
|
|
470
|
+
this.failed = false;
|
|
471
|
+
this.store = storeOf(this.project.bind(this));
|
|
472
|
+
this.unsubscribe = scope.subscribe(this.store.publish);
|
|
473
|
+
}
|
|
474
|
+
project() {
|
|
475
|
+
var self = this;
|
|
476
|
+
var snapshot = this.scope.getSnapshot();
|
|
477
|
+
var fields = this.row.fields.map(function (knob) {
|
|
478
|
+
var staged = self.staged.get(knob.key);
|
|
479
|
+
var stored = at(snapshot.value, knob.path);
|
|
480
|
+
var inherited = at(snapshot.base, knob.path);
|
|
481
|
+
var shown = staged === undefined ? stored : staged.op === 'unset' ? inherited : staged.draft;
|
|
482
|
+
var fallback = defaultOf(knob.key);
|
|
483
|
+
var effective = shown === undefined && fallback !== undefined ? fallback.value : shown;
|
|
484
|
+
var text = shown === undefined || shown === null ? '' : String(shown);
|
|
485
|
+
var op = plannedOp(knob, staged, snapshot);
|
|
486
|
+
var reason = invalidReason(knob, text);
|
|
487
|
+
return {
|
|
488
|
+
key: knob.key,
|
|
489
|
+
kind: knob.kind,
|
|
490
|
+
path: knob.path,
|
|
491
|
+
options: knob.options,
|
|
492
|
+
group: FIELD_GROUP[knob.key],
|
|
493
|
+
text: text,
|
|
494
|
+
value: effective,
|
|
495
|
+
checked: effective === true,
|
|
496
|
+
defaultText: defaultTextOf(knob.key),
|
|
497
|
+
overridden: op === undefined ? has(snapshot.user, knob.path) : op.op === 'set',
|
|
498
|
+
invalid: reason !== undefined,
|
|
499
|
+
invalidReason: reason,
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
var plan = planOps(this.row.fields, this.staged, snapshot);
|
|
503
|
+
return {
|
|
504
|
+
available: snapshot.status === 'ready',
|
|
505
|
+
writable: snapshot.writable === true,
|
|
506
|
+
dirty: plan.length > 0 || fields.some(function (entry) { return entry.invalid && entry.kind === 'number'; }),
|
|
507
|
+
invalid: false,
|
|
508
|
+
saving: this.saving,
|
|
509
|
+
failed: this.failed,
|
|
510
|
+
fields: fields,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
knobOf(key) {
|
|
514
|
+
for (var i = 0; i < this.row.fields.length; i += 1) if (this.row.fields[i].key === key) return this.row.fields[i];
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
stage(key, entry) {
|
|
518
|
+
if (this.baseline === undefined) this.baseline = this.scope.getSnapshot();
|
|
519
|
+
this.staged.set(key, entry);
|
|
520
|
+
this.failed = false;
|
|
521
|
+
this.store.publish();
|
|
522
|
+
}
|
|
523
|
+
edit(key, draft) {
|
|
524
|
+
var knob = this.knobOf(key);
|
|
525
|
+
if (knob === undefined) return;
|
|
526
|
+
var empty = (knob.kind === 'text' || knob.kind === 'number') && String(draft).trim().length === 0;
|
|
527
|
+
this.stage(key, empty ? { op: 'unset' } : { op: 'set', draft: draft });
|
|
528
|
+
}
|
|
529
|
+
resetField(key) {
|
|
530
|
+
if (this.knobOf(key) === undefined) return;
|
|
531
|
+
this.stage(key, { op: 'unset' });
|
|
532
|
+
}
|
|
533
|
+
discard() {
|
|
534
|
+
this.staged.clear();
|
|
535
|
+
this.baseline = undefined;
|
|
536
|
+
this.failed = false;
|
|
537
|
+
this.store.publish();
|
|
538
|
+
}
|
|
539
|
+
async save() {
|
|
540
|
+
var snapshot = this.scope.getSnapshot();
|
|
541
|
+
var ops = planOps(this.row.fields, this.staged, snapshot);
|
|
542
|
+
// 非法数字没有可写的 op;不能静默“保存成功”却不发请求。
|
|
543
|
+
var invalidNumber = this.project().fields.some(function (entry) { return entry.invalid && entry.kind === 'number'; });
|
|
544
|
+
if (invalidNumber) {
|
|
545
|
+
this.failed = true;
|
|
546
|
+
this.store.publish();
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (snapshot.writable !== true || this.saving || ops.length === 0) return;
|
|
550
|
+
this.saving = true;
|
|
551
|
+
this.failed = false;
|
|
552
|
+
this.store.publish();
|
|
553
|
+
var accepted = false;
|
|
554
|
+
try {
|
|
555
|
+
accepted = await this.scope.mutate(ops, this.baseline === undefined ? undefined : this.baseline.revision);
|
|
556
|
+
} catch (error) {
|
|
557
|
+
accepted = false;
|
|
558
|
+
}
|
|
559
|
+
this.saving = false;
|
|
560
|
+
if (accepted) {
|
|
561
|
+
this.staged.clear();
|
|
562
|
+
this.baseline = undefined;
|
|
563
|
+
}
|
|
564
|
+
this.failed = !accepted;
|
|
565
|
+
this.store.publish();
|
|
566
|
+
}
|
|
567
|
+
inject() {
|
|
568
|
+
var self = this;
|
|
569
|
+
return {
|
|
570
|
+
hooks: { rowForm: this.store },
|
|
571
|
+
rowId: this.row.rowId,
|
|
572
|
+
edit: function (key, draft) { self.edit(key, draft); },
|
|
573
|
+
resetField: function (key) { self.resetField(key); },
|
|
574
|
+
discard: function () { self.discard(); },
|
|
575
|
+
save: function () { return self.save(); },
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
dispose() { this.unsubscribe(); }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function textOf(props) {
|
|
582
|
+
return typeof props?.t === 'function' ? props.t : function (key) { return en[key] ?? key; };
|
|
583
|
+
}
|
|
584
|
+
function leaf(key) { return key.split('.').pop(); }
|
|
585
|
+
|
|
586
|
+
function fieldControl(knob, disabled, props, t) {
|
|
587
|
+
var label = t(leaf(knob.key) + '.label');
|
|
588
|
+
var hint = t(leaf(knob.key) + '.hint');
|
|
589
|
+
var defaultLine = knob.defaultText
|
|
590
|
+
? h('p', { className: 'dshdt-hint dshdt-default' }, t('defaultLabel') + ': ' + knob.defaultText)
|
|
591
|
+
: null;
|
|
592
|
+
var invalidLabel = knob.invalid
|
|
593
|
+
? t(knob.invalidReason === 'numberFormat' ? 'invalidNumberFormat' : 'invalidNumber')
|
|
594
|
+
: t('invalidNumber');
|
|
595
|
+
if (knob.kind === 'text' || knob.kind === 'number') {
|
|
596
|
+
return h('div', { key: knob.key, className: 'dshdt-field' },
|
|
597
|
+
h(primitives.SettingsValueField, {
|
|
598
|
+
id: knob.key,
|
|
599
|
+
label: label,
|
|
600
|
+
hint: hint,
|
|
601
|
+
text: knob.text,
|
|
602
|
+
invalid: knob.invalid,
|
|
603
|
+
invalidLabel: invalidLabel,
|
|
604
|
+
numeric: knob.kind === 'number',
|
|
605
|
+
disabled: disabled,
|
|
606
|
+
overridden: knob.overridden,
|
|
607
|
+
overriddenLabel: t('overridden'),
|
|
608
|
+
resetLabel: t('reset'),
|
|
609
|
+
onEdit: function (text) { props.edit(knob.key, text); },
|
|
610
|
+
onReset: function () { props.resetField(knob.key); },
|
|
611
|
+
}),
|
|
612
|
+
defaultLine,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
if (knob.kind === 'boolean') {
|
|
616
|
+
return h('div', { key: knob.key, className: 'dshdt-field' },
|
|
617
|
+
h(primitives.Checkbox, {
|
|
618
|
+
id: knob.key,
|
|
619
|
+
label: label,
|
|
620
|
+
checked: knob.checked,
|
|
621
|
+
disabled: disabled,
|
|
622
|
+
onChange: function (next) { props.edit(knob.key, next); },
|
|
623
|
+
}),
|
|
624
|
+
h('p', { className: 'dshdt-hint' }, hint),
|
|
625
|
+
defaultLine,
|
|
626
|
+
knob.overridden ? h(primitives.Button, { variant: 'ghost', size: 'sm', type: 'button', disabled: disabled, onClick: function () { props.resetField(knob.key); } }, t('reset')) : null,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
return h('div', { key: knob.key, className: 'dshdt-field' },
|
|
630
|
+
h('p', { className: 'dshdt-label' }, label),
|
|
631
|
+
h(primitives.SegmentedControl, {
|
|
632
|
+
id: knob.key,
|
|
633
|
+
value: String(knob.value ?? ''),
|
|
634
|
+
label: label,
|
|
635
|
+
disabled: disabled,
|
|
636
|
+
options: (knob.options || []).map(function (option) { return { value: option, label: t('option.' + option) }; }),
|
|
637
|
+
onChange: function (next) { props.edit(knob.key, next); },
|
|
638
|
+
}),
|
|
639
|
+
h('p', { className: 'dshdt-hint' }, hint),
|
|
640
|
+
defaultLine,
|
|
641
|
+
knob.overridden ? h(primitives.Button, { variant: 'ghost', size: 'sm', type: 'button', disabled: disabled, onClick: function () { props.resetField(knob.key); } }, t('reset')) : null,
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function ModelPicker(props) {
|
|
646
|
+
var useState = React.useState;
|
|
647
|
+
var result = useState({ loading: false, models: [], error: '' });
|
|
648
|
+
var view = result[0];
|
|
649
|
+
var setView = result[1];
|
|
650
|
+
var queryState = useState('');
|
|
651
|
+
var filter = queryState[0];
|
|
652
|
+
var setFilter = queryState[1];
|
|
653
|
+
var familyState = useState('');
|
|
654
|
+
var familyFilter = familyState[0];
|
|
655
|
+
var setFamilyFilter = familyState[1];
|
|
656
|
+
var capabilityState = useState('');
|
|
657
|
+
var capabilityFilter = capabilityState[0];
|
|
658
|
+
var setCapabilityFilter = capabilityState[1];
|
|
659
|
+
var contextState = useState('0');
|
|
660
|
+
var contextFilter = contextState[0];
|
|
661
|
+
var setContextFilter = contextState[1];
|
|
662
|
+
var state = props.state;
|
|
663
|
+
var field = function (key) { return state.fields.find(function (entry) { return entry.key === key; }); };
|
|
664
|
+
var currentModel = function () {
|
|
665
|
+
var entry = field('defaultModel');
|
|
666
|
+
return entry === undefined || entry.value === undefined ? '' : entry.value;
|
|
667
|
+
};
|
|
668
|
+
var orderNames = function () {
|
|
669
|
+
var entry = field('modelOrder');
|
|
670
|
+
return (entry === undefined ? '' : entry.text).split(/[,\s]+/).filter(function (item) { return item !== ''; });
|
|
671
|
+
};
|
|
672
|
+
var refresh = function () {
|
|
673
|
+
// 同源宿主端点:浏览器直连 Ollama 没有 CORS 头(本机实测 /api/tags 不带
|
|
674
|
+
// Access-Control-Allow-Origin),所以由核心行在 host 侧代查;查询前先保存
|
|
675
|
+
// baseUrl/modelsPath/apiKey,宿主只读已保存的共享配置。
|
|
676
|
+
setView({ loading: true, models: [], error: '' });
|
|
677
|
+
fetch('/decision-tools-models', { headers: { accept: 'application/json' } })
|
|
678
|
+
.then(function (response) {
|
|
679
|
+
return response.json().then(function (payload) {
|
|
680
|
+
if (!response.ok) throw new Error(payload && payload.error ? payload.error : 'HTTP ' + response.status);
|
|
681
|
+
return payload;
|
|
682
|
+
});
|
|
683
|
+
})
|
|
684
|
+
.then(function (payload) {
|
|
685
|
+
var rows = payload && Array.isArray(payload.models) ? payload.models : [];
|
|
686
|
+
var models = rows.map(function (row) {
|
|
687
|
+
return typeof row === 'string' ? { name: row } : row;
|
|
688
|
+
}).filter(function (row) {
|
|
689
|
+
return row && typeof row.name === 'string' && row.name !== '';
|
|
690
|
+
});
|
|
691
|
+
setView({ loading: false, models: models, error: '' });
|
|
692
|
+
})
|
|
693
|
+
.catch(function (error) {
|
|
694
|
+
setView({ loading: false, models: [], error: String(error && error.message ? error.message : error) });
|
|
695
|
+
});
|
|
696
|
+
};
|
|
697
|
+
if (typeof React.useEffect === 'function') {
|
|
698
|
+
React.useEffect(function () { refresh(); }, []);
|
|
699
|
+
}
|
|
700
|
+
var baseName = function (entry) { return entry.split('@')[0]; };
|
|
701
|
+
var orderPosition = function (name) {
|
|
702
|
+
var names = orderNames();
|
|
703
|
+
for (var i = 0; i < names.length; i += 1) if (baseName(names[i]) === name) return i + 1;
|
|
704
|
+
return 0;
|
|
705
|
+
};
|
|
706
|
+
var appendOrder = function (name) {
|
|
707
|
+
if (orderPosition(name) > 0) return;
|
|
708
|
+
var names = orderNames();
|
|
709
|
+
names.push(name);
|
|
710
|
+
props.edit('modelOrder', names.join(', '));
|
|
711
|
+
};
|
|
712
|
+
var formatBytes = function (bytes) {
|
|
713
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '—';
|
|
714
|
+
var units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
715
|
+
var value = bytes;
|
|
716
|
+
var index = 0;
|
|
717
|
+
while (value >= 1024 && index < units.length - 1) { value /= 1024; index += 1; }
|
|
718
|
+
return (value >= 10 || index === 0 ? Math.round(value) : value.toFixed(1)) + ' ' + units[index];
|
|
719
|
+
};
|
|
720
|
+
var formatContext = function (numCtx) {
|
|
721
|
+
if (!Number.isFinite(numCtx) || numCtx <= 0) return '—';
|
|
722
|
+
return numCtx >= 1024 ? Math.round(numCtx / 1024) + 'K' : String(numCtx);
|
|
723
|
+
};
|
|
724
|
+
var capabilityLabel = function (capability) {
|
|
725
|
+
var label = props.t('capability.' + capability);
|
|
726
|
+
return label === 'capability.' + capability ? capability : label;
|
|
727
|
+
};
|
|
728
|
+
var capabilitiesOf = function (model) { return Array.isArray(model.capabilities) ? model.capabilities : []; };
|
|
729
|
+
var families = [];
|
|
730
|
+
var capabilities = [];
|
|
731
|
+
view.models.forEach(function (model) {
|
|
732
|
+
if (typeof model.family === 'string' && model.family !== '' && families.indexOf(model.family) < 0) families.push(model.family);
|
|
733
|
+
capabilitiesOf(model).forEach(function (capability) {
|
|
734
|
+
if (typeof capability === 'string' && capabilities.indexOf(capability) < 0) capabilities.push(capability);
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
var filterText = filter.trim().toLowerCase();
|
|
738
|
+
var minContext = Number(contextFilter) || 0;
|
|
739
|
+
var visible = view.models.filter(function (model) {
|
|
740
|
+
var text = [model.name, model.family, model.parameters].filter(function (value) { return typeof value === 'string'; }).join(' ').toLowerCase();
|
|
741
|
+
if (filterText !== '' && text.indexOf(filterText) < 0) return false;
|
|
742
|
+
if (familyFilter !== '' && model.family !== familyFilter) return false;
|
|
743
|
+
if (capabilityFilter !== '' && capabilitiesOf(model).indexOf(capabilityFilter) < 0) return false;
|
|
744
|
+
if (minContext > 0 && !(Number.isFinite(model.contextLength) && model.contextLength >= minContext)) return false;
|
|
745
|
+
return true;
|
|
746
|
+
});
|
|
747
|
+
var appendAll = function () {
|
|
748
|
+
var names = orderNames();
|
|
749
|
+
visible.forEach(function (model) { if (orderPosition(model.name) === 0) names.push(model.name); });
|
|
750
|
+
props.edit('modelOrder', names.join(', '));
|
|
751
|
+
};
|
|
752
|
+
var items = visible.slice(0, 50).map(function (model) {
|
|
753
|
+
var name = model.name;
|
|
754
|
+
var isDefault = name === currentModel();
|
|
755
|
+
var position = orderPosition(name);
|
|
756
|
+
var meta = [model.family, model.parameters].filter(function (value) { return typeof value === 'string' && value !== ''; });
|
|
757
|
+
var caps = capabilitiesOf(model);
|
|
758
|
+
return h('div', { key: name, className: 'dshdt-model-item', title: [model.modifiedAt, caps.join(', ')].filter(Boolean).join(' · ') },
|
|
759
|
+
h('div', { className: 'dshdt-model-name' },
|
|
760
|
+
h('code', null, name),
|
|
761
|
+
h('div', { className: 'dshdt-model-meta' },
|
|
762
|
+
meta.length === 0 ? null : h('span', null, meta.join(' · ')),
|
|
763
|
+
caps.length === 0 ? null : h('span', null, caps.map(capabilityLabel).join(' / ')),
|
|
764
|
+
),
|
|
765
|
+
),
|
|
766
|
+
h('div', { className: 'dshdt-model-num' }, formatContext(model.contextLength)),
|
|
767
|
+
h('div', { className: 'dshdt-model-num' }, formatBytes(model.sizeBytes)),
|
|
768
|
+
h('div', { className: 'dshdt-model-role' },
|
|
769
|
+
isDefault ? h('span', { className: 'dshdt-badge' }, props.t('currentDefault')) : null,
|
|
770
|
+
position > 0 ? h('span', { className: 'dshdt-badge dshdt-badge-muted' }, '#' + position) : null,
|
|
771
|
+
),
|
|
772
|
+
h('div', { className: 'dshdt-model-actions' },
|
|
773
|
+
h(primitives.Button, { type: 'button', variant: 'ghost', size: 'sm', disabled: props.disabled || isDefault, onClick: function () { props.edit('defaultModel', name); } }, isDefault ? props.t('currentDefault') : props.t('useModel')),
|
|
774
|
+
h(primitives.Button, { type: 'button', variant: 'ghost', size: 'sm', disabled: props.disabled || position > 0, onClick: function () { appendOrder(name); } }, position > 0 ? '#' + position : props.t('addToOrder')),
|
|
775
|
+
),
|
|
776
|
+
);
|
|
777
|
+
});
|
|
778
|
+
var empty = null;
|
|
779
|
+
if (view.loading) empty = h('p', { className: 'dshdt-hint' }, props.t('modelsLoading'));
|
|
780
|
+
else if (view.error !== '') empty = h('p', { className: 'dshdt-hint' }, props.t('modelLoadFailed') + ' ' + view.error);
|
|
781
|
+
else if (view.models.length === 0) empty = h('p', { className: 'dshdt-hint' }, props.t('modelsEmpty'));
|
|
782
|
+
else if (visible.length === 0) empty = h('p', { className: 'dshdt-hint' }, props.t('modelsNoMatch'));
|
|
783
|
+
return h('div', { className: 'dshdt-model-picker' },
|
|
784
|
+
h('div', { className: 'dshdt-model-head' },
|
|
785
|
+
h('div', null,
|
|
786
|
+
h('p', { className: 'dshdt-label' }, props.t('modelsTitle')),
|
|
787
|
+
h('p', { className: 'dshdt-hint' }, props.t('modelsHint')),
|
|
788
|
+
),
|
|
789
|
+
h('div', { className: 'dshdt-model-tools' },
|
|
790
|
+
h('span', { className: 'dshdt-model-count' }, String(visible.length) + ' / ' + String(view.models.length)),
|
|
791
|
+
h(primitives.Button, { type: 'button', variant: 'ghost', size: 'sm', disabled: props.disabled || view.loading, onClick: refresh }, view.loading ? props.t('modelsLoading') : props.t('refreshModels')),
|
|
792
|
+
h(primitives.Button, { type: 'button', variant: 'ghost', size: 'sm', disabled: props.disabled || visible.length === 0, onClick: appendAll }, props.t('modelsAddAll')),
|
|
793
|
+
),
|
|
794
|
+
),
|
|
795
|
+
view.models.length === 0 ? null : h('div', { className: 'dshdt-model-filters' },
|
|
796
|
+
h('input', {
|
|
797
|
+
className: 'dshdt-model-filter',
|
|
798
|
+
value: filter,
|
|
799
|
+
placeholder: props.t('modelsFilter'),
|
|
800
|
+
onChange: function (event) { setFilter(event.target.value); },
|
|
801
|
+
}),
|
|
802
|
+
h('select', { className: 'dshdt-model-select', value: familyFilter, onChange: function (event) { setFamilyFilter(event.target.value); } },
|
|
803
|
+
h('option', { value: '' }, props.t('modelsAllFamilies')),
|
|
804
|
+
families.map(function (family) { return h('option', { key: family, value: family }, family); }),
|
|
805
|
+
),
|
|
806
|
+
h('select', { className: 'dshdt-model-select', value: capabilityFilter, onChange: function (event) { setCapabilityFilter(event.target.value); } },
|
|
807
|
+
h('option', { value: '' }, props.t('modelsAllCapabilities')),
|
|
808
|
+
capabilities.map(function (capability) { return h('option', { key: capability, value: capability }, capabilityLabel(capability)); }),
|
|
809
|
+
),
|
|
810
|
+
h('select', { className: 'dshdt-model-select', value: contextFilter, onChange: function (event) { setContextFilter(event.target.value); } },
|
|
811
|
+
h('option', { value: '0' }, props.t('modelsAnyContext')),
|
|
812
|
+
h('option', { value: '8192' }, '≥ 8K'),
|
|
813
|
+
h('option', { value: '32768' }, '≥ 32K'),
|
|
814
|
+
h('option', { value: '131072' }, '≥ 128K'),
|
|
815
|
+
),
|
|
816
|
+
),
|
|
817
|
+
empty,
|
|
818
|
+
visible.length === 0 ? null : h('div', { className: 'dshdt-model-table' },
|
|
819
|
+
h('div', { className: 'dshdt-model-table-head' },
|
|
820
|
+
h('span', null, props.t('modelsColumnModel')),
|
|
821
|
+
h('span', null, props.t('modelsColumnContext')),
|
|
822
|
+
h('span', null, props.t('modelsColumnSize')),
|
|
823
|
+
h('span', null, props.t('modelsColumnRole')),
|
|
824
|
+
h('span', null, props.t('modelsColumnAction')),
|
|
825
|
+
),
|
|
826
|
+
h('div', { className: 'dshdt-model-list' }, items),
|
|
827
|
+
),
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function groupedControls(fields, disabled, props, t) {
|
|
832
|
+
var out = [];
|
|
833
|
+
var group;
|
|
834
|
+
fields.forEach(function (knob) {
|
|
835
|
+
if (knob.group !== undefined && knob.group !== group) {
|
|
836
|
+
group = knob.group;
|
|
837
|
+
out.push(h('p', { key: 'section-' + group, className: 'dshdt-section' }, t('group.' + group)));
|
|
838
|
+
}
|
|
839
|
+
out.push(fieldControl(knob, disabled, props, t));
|
|
840
|
+
});
|
|
841
|
+
return out;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function RowConfigCard(props) {
|
|
845
|
+
var t = textOf(props);
|
|
846
|
+
if (typeof props?.useRowForm !== 'function') return h('p', { className: 'dshdt-hint' }, t('summary.generic'));
|
|
847
|
+
var state = props.useRowForm(function (snapshot) { return snapshot; });
|
|
848
|
+
if (props.view === 'summary') return t('summary.generic');
|
|
849
|
+
var disabled = state.writable !== true;
|
|
850
|
+
return h(primitives.SettingsForm, {
|
|
851
|
+
labels: {
|
|
852
|
+
unavailable: t('unavailable'),
|
|
853
|
+
readOnly: t('readOnly'),
|
|
854
|
+
saveFailed: t('saveFailed'),
|
|
855
|
+
save: t('save'),
|
|
856
|
+
saving: t('saving'),
|
|
857
|
+
},
|
|
858
|
+
state: state,
|
|
859
|
+
onSave: props.save,
|
|
860
|
+
onDiscard: props.discard,
|
|
861
|
+
}, groupedControls(state.fields, disabled, props, t).concat(
|
|
862
|
+
props.rowId === CORE_ROW_ID
|
|
863
|
+
? [h(ModelPicker, { key: 'model-picker', state: state, edit: props.edit, disabled: disabled, t: t })]
|
|
864
|
+
: [],
|
|
865
|
+
));
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function CoreNote(props) {
|
|
869
|
+
var t = textOf(props);
|
|
870
|
+
var subject = props?.subject;
|
|
871
|
+
if (subject === null || typeof subject !== 'object') return null;
|
|
872
|
+
if (subject.kind === 'bundle') {
|
|
873
|
+
if (subject.pkg?.name !== PACKAGE_NAME) return null;
|
|
874
|
+
} else if (subject.kind === 'row') {
|
|
875
|
+
if (subject.pkg?.name !== PACKAGE_NAME || subject.row?.rowId !== CORE_ROW_ID) return null;
|
|
876
|
+
} else {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
return h('section', { className: 'dshdt-note' }, h('p', { className: 'dshdt-hint' }, t('coreNote')));
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
var CSS = [
|
|
883
|
+
'.dshdt-field{margin:0 0 14px}',
|
|
884
|
+
'.dshdt-section{margin:20px 0 8px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;opacity:.55}',
|
|
885
|
+
'.dshdt-label{margin:0 0 6px;font-size:13px;font-weight:500}',
|
|
886
|
+
'.dshdt-hint{margin:6px 0 0;font-size:12px;opacity:.65;line-height:1.5}',
|
|
887
|
+
'.dshdt-note{margin:14px 0 0}',
|
|
888
|
+
'.dshdt-segmented{display:flex;gap:6px;flex-wrap:wrap}',
|
|
889
|
+
'.dshdt-model-picker{margin:16px 0 0;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l3)}',
|
|
890
|
+
'.dshdt-model-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}',
|
|
891
|
+
'.dshdt-model-filters{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0}',
|
|
892
|
+
'.dshdt-model-filter{flex:1 1 180px;box-sizing:border-box;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:6px;background:var(--dsw-alias-bg-base);color:inherit;font:inherit}',
|
|
893
|
+
'.dshdt-model-select{padding:6px 8px;border:1px solid var(--dsw-alias-border-l3);border-radius:6px;background:var(--dsw-alias-bg-base);color:inherit;font:inherit}',
|
|
894
|
+
'.dshdt-model-name{display:flex;align-items:center;gap:6px;min-width:0}',
|
|
895
|
+
'.dshdt-model-name code{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
896
|
+
'.dshdt-model-actions{display:flex;gap:4px;flex-shrink:0}',
|
|
897
|
+
'.dshdt-badge{flex:none;padding:1px 6px;border-radius:999px;background:var(--dsw-alias-bg-l2);font-size:10px;line-height:16px;opacity:.8}',
|
|
898
|
+
'.dshdt-badge-muted{opacity:.55}',
|
|
899
|
+
'.dshdt-model-list{display:flex;flex-direction:column;gap:4px;margin-top:8px;max-height:220px;overflow:auto}',
|
|
900
|
+
'.dshdt-model-tools{display:flex;align-items:center;gap:6px;flex-wrap:wrap;justify-content:flex-end}',
|
|
901
|
+
'.dshdt-model-count{font-size:11px;opacity:.6;font-variant-numeric:tabular-nums}',
|
|
902
|
+
'.dshdt-model-table{margin-top:4px}',
|
|
903
|
+
'.dshdt-model-table-head{display:grid;grid-template-columns:minmax(0,1.4fr) .5fr .5fr auto auto;gap:8px;padding:0 2px 6px;font-size:10px;letter-spacing:.06em;text-transform:uppercase;opacity:.45}',
|
|
904
|
+
'.dshdt-model-item{display:grid;grid-template-columns:minmax(0,1.4fr) .5fr .5fr auto auto;align-items:center;gap:8px;padding:6px 2px;border-top:1px solid var(--dsw-alias-border-l3)}',
|
|
905
|
+
'.dshdt-model-meta{display:flex;flex-direction:column;gap:1px;font-size:10px;opacity:.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
906
|
+
'.dshdt-model-num{font-variant-numeric:tabular-nums;font-size:12px;opacity:.8}',
|
|
907
|
+
'.dshdt-model-role{display:flex;align-items:center;justify-content:flex-end;gap:4px}',
|
|
908
|
+
].join('');
|
|
909
|
+
var STYLE_ID = PACKAGE_NAME + '/client.js';
|
|
910
|
+
|
|
911
|
+
function installStyles() {
|
|
912
|
+
if (typeof document === 'undefined' || document.head === undefined || document.head === null) return function () {};
|
|
913
|
+
if (document.querySelector('style[data-plugin="' + PACKAGE_NAME + '"]') !== null) return function () {};
|
|
914
|
+
var tag = document.createElement('style');
|
|
915
|
+
tag.dataset.plugin = PACKAGE_NAME;
|
|
916
|
+
tag.dataset.pluginCss = STYLE_ID;
|
|
917
|
+
tag.textContent = CSS;
|
|
918
|
+
document.head.appendChild(tag);
|
|
919
|
+
return function () { tag.remove(); };
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function apply(ctx) {
|
|
923
|
+
var safely = function (what, run) {
|
|
924
|
+
try { run(); } catch (error) {
|
|
925
|
+
try { ctx?.logger?.warn?.('dsh-decision-tools: ' + what + ' failed: ' + (error?.message ?? error)); } catch (ignored) { /* noop */ }
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
if (degradedPrimitives.length > 0) {
|
|
929
|
+
safely('primitives', function () {
|
|
930
|
+
ctx?.logger?.warn?.('dsh-decision-tools: using HTML fallback for missing primitives: ' + degradedPrimitives.join(', '));
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
safely('dictionaries', function () {
|
|
934
|
+
var register = function () { return ctx.locale.register(NS, { zh: zh, en: en }); };
|
|
935
|
+
if (typeof ctx.effect === 'function') ctx.effect(register, 'dsh-decision-tools: dictionaries');
|
|
936
|
+
else register();
|
|
937
|
+
});
|
|
938
|
+
safely('stylesheet', function () {
|
|
939
|
+
if (typeof ctx.effect === 'function') ctx.effect(installStyles, 'dsh-decision-tools: stylesheet');
|
|
940
|
+
else installStyles();
|
|
941
|
+
});
|
|
942
|
+
ROWS.filter(function (row) { return row.rowId !== CORE_ROW_ID; }).forEach(function (row) {
|
|
943
|
+
safely(row.rowId + ' page', function () {
|
|
944
|
+
var scope;
|
|
945
|
+
try { scope = ctx.configForms.get(row.rowId); } catch (error) { scope = undefined; }
|
|
946
|
+
var form = new RowConfigForm(scope === undefined || scope === null ? fallbackScope() : scope, row);
|
|
947
|
+
if (typeof ctx.effect === 'function') ctx.effect(function () { return function () { form.dispose(); }; }, 'dsh-decision-tools: ' + row.rowId + ' form');
|
|
948
|
+
// 没有 effect 能力时不要把 form 立刻 dispose:先注册、让它活到页面生命周期。
|
|
949
|
+
var register = function () {
|
|
950
|
+
return ctx.configForms.whileServed([row.rowId], function () {
|
|
951
|
+
return ctx.slots.inject('plugins.row.config', function () {
|
|
952
|
+
return ctx.slots.register({
|
|
953
|
+
name: 'plugins.row.config',
|
|
954
|
+
key: rowKey(row.rowId),
|
|
955
|
+
locale: NS,
|
|
956
|
+
inject: function () { return form.inject(); },
|
|
957
|
+
}, RowConfigCard);
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
if (typeof ctx.effect === 'function') ctx.effect(register, 'dsh-decision-tools: ' + row.rowId + ' page');
|
|
962
|
+
else register();
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
safely('core bundle config', function () {
|
|
966
|
+
var coreRow = ROWS.find(function (row) { return row.rowId === CORE_ROW_ID; });
|
|
967
|
+
var scope;
|
|
968
|
+
try { scope = ctx.configForms.get(CORE_ROW_ID); } catch (error) { scope = undefined; }
|
|
969
|
+
var form = new RowConfigForm(scope === undefined || scope === null ? fallbackScope() : scope, coreRow);
|
|
970
|
+
if (typeof ctx.effect === 'function') ctx.effect(function () { return function () { form.dispose(); }; }, 'dsh-decision-tools: core form');
|
|
971
|
+
// 核心行不像工具行那样由 row 槽渲染;多部署实测核心行设置命名空间不会被服务,
|
|
972
|
+
// 所以像首方 shell 卡片一样,无条件挂到 bundle 卡片上(两个 key 都挂,兼容两种实现)。
|
|
973
|
+
[CORE_ROW_ID, PACKAGE_NAME].forEach(function (key) {
|
|
974
|
+
var register = function () {
|
|
975
|
+
return ctx.slots.inject('plugins.bundle.config', function () {
|
|
976
|
+
return ctx.slots.register({
|
|
977
|
+
name: 'plugins.bundle.config',
|
|
978
|
+
key: key,
|
|
979
|
+
locale: NS,
|
|
980
|
+
inject: function () { return form.inject(); },
|
|
981
|
+
}, RowConfigCard);
|
|
982
|
+
});
|
|
983
|
+
};
|
|
984
|
+
if (typeof ctx.effect === 'function') ctx.effect(register, 'dsh-decision-tools: core bundle config ' + key);
|
|
985
|
+
else register();
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
safely('core note', function () {
|
|
989
|
+
var register = function () {
|
|
990
|
+
return ctx.slots.inject('plugins.detail.section', function () {
|
|
991
|
+
return ctx.slots.register({
|
|
992
|
+
name: 'plugins.detail.section',
|
|
993
|
+
id: PACKAGE_NAME + ':core',
|
|
994
|
+
order: 100,
|
|
995
|
+
locale: NS,
|
|
996
|
+
}, CoreNote);
|
|
997
|
+
});
|
|
998
|
+
};
|
|
999
|
+
if (typeof ctx.effect === 'function') ctx.effect(register, 'dsh-decision-tools: core note');
|
|
1000
|
+
else register();
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
exports.NS = NS;
|
|
1005
|
+
exports.apply = function (ctx) {
|
|
1006
|
+
try { apply(ctx); } catch (error) {
|
|
1007
|
+
try { ctx?.logger?.warn?.('dsh-decision-tools: client apply failed: ' + (error?.message ?? error)); } catch (ignored) { /* noop */ }
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
exports.inject = ['slots', 'locale', 'configForms'];
|
|
1011
|
+
exports.__internals = { NS: NS, PACKAGE_NAME: PACKAGE_NAME, CORE_ROW_ID: CORE_ROW_ID, ROWS: ROWS, FIELD_DEFAULTS: FIELD_DEFAULTS, rowKey: rowKey, RowConfigForm: RowConfigForm, planOps: planOps, zh: zh, en: en, installStyles: installStyles };
|
|
1012
|
+
return module.exports;
|
|
1013
|
+
},
|
|
1014
|
+
});
|