dsh-subagent-profile 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -8
- package/README.zh.md +20 -8
- package/index.mjs +177 -1386
- package/lib/client.js +871 -695
- package/lib/core/catalog-cache.mjs +235 -0
- package/lib/core/catalog.mjs +83 -0
- package/lib/core/cost-guard.mjs +122 -0
- package/lib/core/delegation.mjs +81 -0
- package/lib/core/dispatch-schema.mjs +71 -0
- package/lib/core/dispatch-tool.mjs +394 -0
- package/lib/core/http-routes.mjs +249 -0
- package/lib/core/intersection.mjs +27 -0
- package/lib/core/presets-sync.mjs +136 -0
- package/lib/core/profile-provider.mjs +242 -0
- package/lib/core/profiles-store.mjs +226 -0
- package/lib/{pure.mjs → core/pure.mjs} +146 -110
- package/lib/{shims.mjs → core/shims.mjs} +95 -9
- package/lib/core/whitelist.mjs +22 -0
- package/package.json +10 -5
package/lib/client.js
CHANGED
|
@@ -14,722 +14,898 @@
|
|
|
14
14
|
* bordered card per row with a layered name / description / meta layout, and a
|
|
15
15
|
* dashed "+ 新增" button that expands an inline form card instead of a
|
|
16
16
|
* permanently-visible input row. Dropdown options (preset / provider / model /
|
|
17
|
-
* reasoning effort) are served by the host `/options`
|
|
18
|
-
*
|
|
17
|
+
* reasoning effort) are served by the host `/options/summary` (models + presets,
|
|
18
|
+
* on settings open), `/options/efforts` (per-model, lazy on model select) and
|
|
19
|
+
* `/options/tools` (tool directory, lazy when the restriction panel opens)
|
|
20
|
+
* routes from the live presets roster and llm directory.
|
|
21
|
+
*
|
|
22
|
+
* 结构:纯数据(CSS/ZH 文案表)与纯函数(api/解析/文案映射等)驻留模块作用域
|
|
23
|
+
* (load 调用之前的顶层声明);依赖 React 的组件经参数注入的 maker 函数装配
|
|
24
|
+
* (makeChip / makeDispatchToolview / makeProfilesSection),factory 只做组装。
|
|
19
25
|
*/
|
|
20
|
-
window.__ModuleLoader__.load({
|
|
21
|
-
id: 'dsh-subagent-profile',
|
|
22
|
-
factory: (require) => {
|
|
23
|
-
const React = require('react')
|
|
24
|
-
const { useEffect, useState, useCallback } = React
|
|
25
|
-
const el = React.createElement
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
// ── 纯数据:样式(tokens 对齐 shipped「模型」设置节)────────────────────────
|
|
28
|
+
|
|
29
|
+
const CSS = [
|
|
30
|
+
'.sap-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}',
|
|
31
|
+
'.sap-title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}',
|
|
32
|
+
'.sap-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}',
|
|
33
|
+
'.sap-notice{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}',
|
|
34
|
+
'.sap-savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}',
|
|
35
|
+
'.sap-rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}',
|
|
36
|
+
'.sap-rowCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:10px;padding:12px 14px;display:flex}',
|
|
37
|
+
'.sap-rowHead{align-items:center;gap:10px;display:flex}',
|
|
38
|
+
'.sap-rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}',
|
|
39
|
+
'.sap-rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}',
|
|
40
|
+
'.sap-rowId{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;font-family:monospace}',
|
|
41
|
+
'.sap-rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}',
|
|
42
|
+
'.sap-rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}',
|
|
43
|
+
'.sap-desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}',
|
|
44
|
+
'.sap-warn{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}',
|
|
45
|
+
'.sap-versionWarn{background:var(--dsw-alias-state-warn-tertiary);border:1px solid var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);border-radius:8px;flex-direction:column;gap:4px;padding:10px 12px;font-size:12px;line-height:18px;margin:0;display:flex}',
|
|
46
|
+
'.sap-chips{flex-wrap:wrap;gap:6px;display:flex}',
|
|
47
|
+
'.sap-chip{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:6px;flex:none;align-items:center;gap:5px;padding:2px 8px;font-size:12px;line-height:18px;display:inline-flex}',
|
|
48
|
+
'.sap-chipLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}',
|
|
49
|
+
'.sap-chipValue{color:var(--dsw-alias-label-primary)}',
|
|
50
|
+
'.sap-chipValueDim{color:var(--dsw-alias-label-tertiary)}',
|
|
51
|
+
'.sap-chipWarn{background:var(--dsw-alias-state-warn-tertiary);border-color:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);font-weight:500}',
|
|
52
|
+
'.sap-chipSuccess{color:var(--dsw-alias-state-success-primary);font-weight:500}',
|
|
53
|
+
'.sap-chipDisabled{color:var(--dsw-alias-state-warn-label);font-weight:500}',
|
|
54
|
+
'.sap-metaValueDefault{color:var(--dsw-alias-label-tertiary)}',
|
|
55
|
+
'.sap-addBlock{margin-top:4px}',
|
|
56
|
+
'.sap-addActions{flex-wrap:wrap;gap:10px;display:flex}',
|
|
57
|
+
'.sap-addButton{box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;flex:1 1 0;gap:6px;min-width:180px;height:44px;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;background:0 0;cursor:pointer;justify-content:center;align-items:center;display:inline-flex}',
|
|
58
|
+
'.sap-addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
59
|
+
'.sap-addCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}',
|
|
60
|
+
'.sap-editorHeader{align-items:baseline;gap:8px;display:flex}',
|
|
61
|
+
'.sap-editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}',
|
|
62
|
+
'.sap-field{flex-direction:column;gap:6px;display:flex}',
|
|
63
|
+
'.sap-fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}',
|
|
64
|
+
'.sap-input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}',
|
|
65
|
+
'.sap-input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}',
|
|
66
|
+
'.sap-input::placeholder{color:var(--dsw-alias-label-dimmed)}',
|
|
67
|
+
'.sap-select{appearance:auto}',
|
|
68
|
+
'.sap-customized{border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}',
|
|
69
|
+
'.sap-customizedSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}',
|
|
70
|
+
'.sap-customizedSummary::-webkit-details-marker{display:none}',
|
|
71
|
+
'.sap-customizedSummary:before{content:"";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}',
|
|
72
|
+
'.sap-customized[open]>.sap-customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}',
|
|
73
|
+
'.sap-customizedSummary:hover{color:var(--dsw-alias-label-primary)}',
|
|
74
|
+
'.sap-customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}',
|
|
75
|
+
'.sap-editorActions{justify-content:flex-end;gap:8px;display:flex}',
|
|
76
|
+
'.sap-primaryButton,.sap-secondaryButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}',
|
|
77
|
+
'.sap-primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}',
|
|
78
|
+
'.sap-primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}',
|
|
79
|
+
'.sap-secondaryButton{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0}',
|
|
80
|
+
'.sap-secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
81
|
+
'.sap-dangerButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;justify-content:center;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}',
|
|
82
|
+
'.sap-dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}',
|
|
83
|
+
'.sap-primaryButton:disabled,.sap-secondaryButton:disabled,.sap-dangerButton:disabled,.sap-addButton:disabled{opacity:.4;cursor:default}',
|
|
84
|
+
'.sap-titleRow{display:flex;align-items:center;justify-content:space-between;gap:12px}',
|
|
85
|
+
'.sap-switch{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary);user-select:none}',
|
|
86
|
+
'.sap-switch input{appearance:none;width:36px;height:20px;border-radius:10px;background:var(--dsw-alias-border-l2);position:relative;cursor:pointer;margin:0;transition:background .2s;flex:none}',
|
|
87
|
+
'.sap-switch input::before{content:"";position:absolute;width:16px;height:16px;border-radius:50%;background:#fff;top:2px;left:2px;transition:left .2s;box-shadow:0 1px 2px rgba(0,0,0,.2)}',
|
|
88
|
+
'.sap-switch input:checked{background:var(--dsw-alias-brand-primary)}',
|
|
89
|
+
'.sap-switch input:checked::before{left:18px}',
|
|
90
|
+
'.sap-toolview{flex-direction:column;align-items:flex-start;gap:6px;padding:4px 8px;display:flex}',
|
|
91
|
+
'.sap-toolText{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word}',
|
|
92
|
+
'.sap-toolModes{flex-wrap:wrap;gap:4px 16px;display:flex}',
|
|
93
|
+
'.sap-toolGroups{flex-direction:column;gap:8px;display:flex}',
|
|
94
|
+
'.sap-toolLayerCard{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px}',
|
|
95
|
+
'.sap-toolLayerBody{flex-direction:column;gap:6px;padding-top:6px;display:flex}',
|
|
96
|
+
'.sap-toolGroup{flex-direction:column;gap:4px;display:flex}',
|
|
97
|
+
'.sap-toolGroupTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}',
|
|
98
|
+
'.sap-toolActions{flex-wrap:wrap;gap:6px;display:flex}',
|
|
99
|
+
'.sap-toolList{flex-wrap:wrap;gap:2px 16px;display:flex}',
|
|
100
|
+
'.sap-toolItem{align-items:center;gap:6px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;line-height:20px;user-select:none;display:inline-flex}',
|
|
101
|
+
'.sap-toolItem input{margin:0;accent-color:var(--dsw-alias-brand-primary)}',
|
|
102
|
+
].join('\n')
|
|
103
|
+
|
|
104
|
+
// ── 纯数据:dispatch 卡片面向用户固定文案 ──────────────────────────────────
|
|
105
|
+
|
|
106
|
+
// 面向用户的固定文案。注意:此表仅集中 dispatch 卡片文案;设置页文案仍为
|
|
107
|
+
// 散落硬编码,属待迁移债(迁移时一次性并入此表)。
|
|
108
|
+
const ZH = {
|
|
109
|
+
inlineProfile: '内联',
|
|
110
|
+
inherit: '继承父',
|
|
111
|
+
effortDefault: '默认(继承父)',
|
|
112
|
+
chipProfile: '方案',
|
|
113
|
+
chipPreset: '预设',
|
|
114
|
+
chipProvider: '提供方',
|
|
115
|
+
chipModel: '模型',
|
|
116
|
+
chipEffort: '推理强度',
|
|
117
|
+
chipTier: '档位',
|
|
118
|
+
chipEnabled: '已启用',
|
|
119
|
+
chipDisabled: '已禁用',
|
|
120
|
+
ignoredMark: '已忽略',
|
|
121
|
+
toolLabel: '派发子 Agent',
|
|
122
|
+
cont: 'continuable 子 Agent',
|
|
123
|
+
canContinue: '可续跑',
|
|
124
|
+
toolBackground: '后台任务',
|
|
125
|
+
toolForeground: '前台',
|
|
126
|
+
toolRunning: '运行中',
|
|
127
|
+
tierZh: { cheap: '省 token(cheap)', balanced: '均衡(balanced)', premium: '高成本(premium)' },
|
|
128
|
+
stopReasonZh: { completed: '完成', 'max-tokens': '超出预算', aborted: '已中止', refusal: '已拒绝', error: '出错' },
|
|
129
|
+
persistFail: '已保存但未持久化',
|
|
130
|
+
persistFailHint: '(磁盘写入未成功,重启 dsh web 后该改动可能丢失)'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── 纯数据:设置页常量 ─────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
const EMPTY_FORM = { id: '', description: '', preset: '', provider: '', model: '', reasoningEffort: '', toolMode: 'none', toolList: [] }
|
|
136
|
+
const EFFORT_ZH = { off: '关闭', high: '高', max: '最大' }
|
|
137
|
+
const TOOL_MODES = [
|
|
138
|
+
['none', '不限制(继承父的全部工具,自动剔除 run_code)'],
|
|
139
|
+
['allow', '白名单(只允许勾选的工具)'],
|
|
140
|
+
['deny', '黑名单(排除勾选的工具)'],
|
|
141
|
+
]
|
|
142
|
+
const CORE_ORDER = ['文件', '终端', '网络', '任务', '子 Agent', '工作流', '交互', '图片', 'Cordis', '计划']
|
|
143
|
+
const LAYER_TITLES = { core: '核心预设', plugin: '第三方插件', custom: '自建预设' }
|
|
144
|
+
const smallBtn = { height: '24px', padding: '0 8px', fontSize: '12px', borderRadius: '12px' }
|
|
145
|
+
|
|
146
|
+
// ── 纯函数:api / 样式注入 / 文案映射 ──────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
// loopback HTTP,host 侧服务 /subagent-profiles/*。
|
|
149
|
+
async function api(path, payload) {
|
|
150
|
+
const init = payload === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
155
|
+
body: JSON.stringify(payload),
|
|
46
156
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
'.sap-switch input:checked{background:var(--dsw-alias-brand-primary)}',
|
|
112
|
-
'.sap-switch input:checked::before{left:18px}',
|
|
113
|
-
'.sap-toolview{flex-direction:column;align-items:flex-start;gap:6px;padding:4px 8px;display:flex}',
|
|
114
|
-
'.sap-toolText{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word}',
|
|
115
|
-
'.sap-toolModes{flex-wrap:wrap;gap:4px 16px;display:flex}',
|
|
116
|
-
'.sap-toolGroups{flex-direction:column;gap:8px;display:flex}',
|
|
117
|
-
'.sap-toolLayerCard{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px}',
|
|
118
|
-
'.sap-toolLayerBody{flex-direction:column;gap:6px;padding-top:6px;display:flex}',
|
|
119
|
-
'.sap-toolGroup{flex-direction:column;gap:4px;display:flex}',
|
|
120
|
-
'.sap-toolGroupTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px;border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px}',
|
|
121
|
-
'.sap-toolActions{flex-wrap:wrap;gap:6px;display:flex}',
|
|
122
|
-
'.sap-toolList{flex-wrap:wrap;gap:2px 16px;display:flex}',
|
|
123
|
-
'.sap-toolItem{align-items:center;gap:6px;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;line-height:20px;user-select:none;display:inline-flex}',
|
|
124
|
-
'.sap-toolItem input{margin:0;accent-color:var(--dsw-alias-brand-primary)}',
|
|
125
|
-
].join('\n')
|
|
126
|
-
|
|
127
|
-
function ensureStyles() {
|
|
128
|
-
const tagId = 'dsh-subagent-profile/ProfilesSection.css'
|
|
129
|
-
if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
|
|
130
|
-
const style = document.createElement('style')
|
|
131
|
-
style.dataset.plugin = 'dsh-subagent-profile'
|
|
132
|
-
style.dataset.pluginCss = tagId
|
|
133
|
-
style.textContent = CSS
|
|
134
|
-
document.head.appendChild(style)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ── dispatch tool-call card ─────────────────────────────────────────────
|
|
138
|
-
|
|
139
|
-
// 面向用户的固定文案(G1)。注意:此表仅集中 dispatch 卡片文案;设置页文案
|
|
140
|
-
// 仍为 v0.1.0 散落硬编码,属 G1 待迁移债(V2.0 中期一次性迁入)。
|
|
141
|
-
const ZH = {
|
|
142
|
-
inlineProfile: '内联',
|
|
143
|
-
inherit: '继承父',
|
|
144
|
-
effortDefault: '默认(继承父)',
|
|
145
|
-
chipProfile: '方案',
|
|
146
|
-
chipPreset: '预设',
|
|
147
|
-
chipProvider: '提供方',
|
|
148
|
-
chipModel: '模型',
|
|
149
|
-
chipEffort: '推理强度',
|
|
150
|
-
chipTier: '档位',
|
|
151
|
-
chipEnabled: '已启用',
|
|
152
|
-
chipDisabled: '已禁用',
|
|
153
|
-
ignoredMark: '已忽略',
|
|
154
|
-
toolLabel: '派发子 Agent',
|
|
155
|
-
cont: 'continuable 子 Agent',
|
|
156
|
-
canContinue: '可续跑',
|
|
157
|
-
toolBackground: '后台任务',
|
|
158
|
-
toolForeground: '前台',
|
|
159
|
-
toolRunning: '运行中',
|
|
160
|
-
tierZh: { cheap: '省 token(cheap)', balanced: '均衡(balanced)', premium: '高成本(premium)' },
|
|
161
|
-
persistFail: '已保存但未持久化',
|
|
162
|
-
persistFailHint: '(磁盘写入未成功,重启 dsh web 后该改动可能丢失)'
|
|
157
|
+
const res = await fetch(`/subagent-profiles${path}`, init)
|
|
158
|
+
let data
|
|
159
|
+
try {
|
|
160
|
+
data = await res.json()
|
|
161
|
+
} catch {
|
|
162
|
+
throw new Error(`宿主返回了非 JSON 响应(HTTP ${res.status})`)
|
|
163
|
+
}
|
|
164
|
+
if (!data.ok) throw new Error(data.error || '请求失败')
|
|
165
|
+
return data
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ensureStyles() {
|
|
169
|
+
const tagId = 'dsh-subagent-profile/ProfilesSection.css'
|
|
170
|
+
if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
|
|
171
|
+
const style = document.createElement('style')
|
|
172
|
+
style.dataset.plugin = 'dsh-subagent-profile'
|
|
173
|
+
style.dataset.pluginCss = tagId
|
|
174
|
+
style.textContent = CSS
|
|
175
|
+
document.head.appendChild(style)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const effortZh = (id) => {
|
|
179
|
+
if (id === undefined || id === null || id === '') return ZH.inherit
|
|
180
|
+
if (id === '(default)') return ZH.effortDefault
|
|
181
|
+
if (id === '(parent)' || id === 'undefined') return ZH.inherit
|
|
182
|
+
return EFFORT_ZH[id] ? `${EFFORT_ZH[id]}(${id})` : id
|
|
183
|
+
}
|
|
184
|
+
const presetZh = (v) => (v === 'inherit' ? ZH.inherit : (typeof v === 'string' && v !== '' ? v : ZH.inherit))
|
|
185
|
+
const profileZh = (v) => (v === undefined || v === '' || v === '(inline)' ? ZH.inlineProfile : v)
|
|
186
|
+
const rawOrInherit = (v) => (v === '(parent)' ? ZH.inherit : (typeof v === 'string' && v !== '' ? v : ZH.inherit))
|
|
187
|
+
const effortLabel = (effort) => {
|
|
188
|
+
const id = effort && effort.id
|
|
189
|
+
return EFFORT_ZH[id] ? `${EFFORT_ZH[id]}(${id})` : (effort && effort.name) || id
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Distinct providers, derived from the model list.
|
|
193
|
+
function deriveProviders(models) {
|
|
194
|
+
const providers = []
|
|
195
|
+
const seenProvider = new Set()
|
|
196
|
+
for (const model of models) {
|
|
197
|
+
if (seenProvider.has(model.provider)) continue
|
|
198
|
+
seenProvider.add(model.provider)
|
|
199
|
+
providers.push({ id: model.provider, name: model.providerName || model.provider })
|
|
200
|
+
}
|
|
201
|
+
return providers
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── 纯函数:dispatch 块解析 ────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
// 请求值:argsRaw(运行中 / 已结算两种 block 形态都有)。
|
|
207
|
+
function readDispatchRequest(block) {
|
|
208
|
+
const out = { profile: '', preset: '', provider: '', model: '', reasoningEffort: '', continuable: false, runInBackground: false }
|
|
209
|
+
if (!block || typeof block !== 'object') return out
|
|
210
|
+
const done = 'kind' in block
|
|
211
|
+
const argsRaw = (done ? (block.call && block.call.argsRaw) : block.argsRaw) ?? ''
|
|
212
|
+
if (typeof argsRaw !== 'string' || argsRaw === '') return out
|
|
213
|
+
try {
|
|
214
|
+
const parsed = JSON.parse(argsRaw)
|
|
215
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
216
|
+
for (const key of ['profile', 'preset', 'provider', 'model', 'reasoningEffort']) {
|
|
217
|
+
if (typeof parsed[key] === 'string' && parsed[key] !== '') out[key] = parsed[key]
|
|
218
|
+
}
|
|
219
|
+
out.continuable = parsed.continuable === true
|
|
220
|
+
out.runInBackground = parsed.run_in_background === true
|
|
163
221
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
222
|
+
} catch {
|
|
223
|
+
// unparsable argsRaw:保持默认(继承/内联口径)。
|
|
224
|
+
}
|
|
225
|
+
return out
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 结果 meta(生效值):宿主 render 行("[dispatch] …")是持久回放的唯一
|
|
229
|
+
// 生效值来源——结构化 value 只存在于执行局部(dsh-tools 约定),且宿主未
|
|
230
|
+
// 定义 output.presentationMeta(block.meta 为空)。解析成功返回
|
|
231
|
+
// {kind, id, fields, ignored};任何一步不符预期返回 null,调用方回退请求值。
|
|
232
|
+
const DISPATCH_RESULT_KEYS = ['profile', 'preset', 'provider', 'model', 'reasoningEffort', 'tokenTier', 'childTotalTokens', 'elapsedMs', 'stopReason']
|
|
233
|
+
function parseDispatchText(text) {
|
|
234
|
+
if (typeof text !== 'string') return null
|
|
235
|
+
const firstLine = text.split('\n')[0] ?? ''
|
|
236
|
+
if (!firstLine.startsWith('[dispatch] ')) return null
|
|
237
|
+
let rest = firstLine.slice('[dispatch] '.length)
|
|
238
|
+
let kind = 'foreground'
|
|
239
|
+
let id = ''
|
|
240
|
+
if (rest.startsWith('background job ')) {
|
|
241
|
+
kind = 'background'
|
|
242
|
+
rest = rest.slice('background job '.length)
|
|
243
|
+
} else if (rest.startsWith('started subagent ')) {
|
|
244
|
+
kind = 'continuable'
|
|
245
|
+
rest = rest.slice('started subagent '.length)
|
|
246
|
+
}
|
|
247
|
+
if (kind !== 'foreground') {
|
|
248
|
+
const sep = rest.indexOf(' · ')
|
|
249
|
+
id = sep >= 0 ? rest.slice(0, sep) : rest
|
|
250
|
+
rest = sep >= 0 ? rest.slice(sep + 3) : ''
|
|
251
|
+
}
|
|
252
|
+
const fields = {}
|
|
253
|
+
let ignored = []
|
|
254
|
+
let known = 0
|
|
255
|
+
for (const seg of rest.split(' · ')) {
|
|
256
|
+
const eq = seg.indexOf('=')
|
|
257
|
+
if (eq <= 0) continue
|
|
258
|
+
const key = seg.slice(0, eq)
|
|
259
|
+
if (!DISPATCH_RESULT_KEYS.includes(key)) continue
|
|
260
|
+
let value = seg.slice(eq + 1)
|
|
261
|
+
const m = /^(.+)\(ignored:\s*([^)]*)\)$/.exec(value)
|
|
262
|
+
if (m !== null) {
|
|
263
|
+
value = m[1]
|
|
264
|
+
ignored = m[2].split(',').map((s) => s.trim()).filter((s) => s !== '')
|
|
170
265
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
266
|
+
if (value === 'undefined') value = '' // 旧版把该字段渲染为字面 undefined,此处归一为空
|
|
267
|
+
fields[key] = value
|
|
268
|
+
known += 1
|
|
269
|
+
}
|
|
270
|
+
if (known <= 0) return null
|
|
271
|
+
return { kind, id, fields, ignored }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function readDispatchResult(block) {
|
|
275
|
+
if (!block || typeof block !== 'object' || !('kind' in block)) return null
|
|
276
|
+
const content = block.content
|
|
277
|
+
if (!Array.isArray(content)) return null
|
|
278
|
+
for (const item of content) {
|
|
279
|
+
if (item && typeof item === 'object' && typeof item.text === 'string') {
|
|
280
|
+
const parsed = parseDispatchText(item.text)
|
|
281
|
+
if (parsed !== null) return parsed
|
|
186
282
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
283
|
+
}
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// childTotalTokens 文本后缀:宿主 render 行里的 `childTotalTokens=<number>` 经
|
|
288
|
+
// parseDispatchText 解析为字符串,此处归一为「≈N tokens」。仅前台/后台 completed
|
|
289
|
+
// 结算携带;无值返回空串(不显示)。渲染位置紧邻后续的耗时显示。
|
|
290
|
+
function tokensSuffix(result, block) {
|
|
291
|
+
let raw = null
|
|
292
|
+
if (result && result.fields && result.fields.childTotalTokens !== undefined && result.fields.childTotalTokens !== '') {
|
|
293
|
+
raw = result.fields.childTotalTokens
|
|
294
|
+
} else if (block && block.meta && typeof block.meta === 'object' && block.meta.childTotalTokens !== undefined) {
|
|
295
|
+
raw = block.meta.childTotalTokens
|
|
296
|
+
}
|
|
297
|
+
const n = typeof raw === 'number' ? raw : (typeof raw === 'string' && raw !== '' ? Number(raw) : NaN)
|
|
298
|
+
return Number.isFinite(n) ? ` · ≈${n} tokens` : ''
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 耗时 + 结束原因后缀:宿主 render 行里的 `elapsedMs=<n>` / `stopReason=<enum>`
|
|
302
|
+
// 经 parseDispatchText 解析为字符串,此处归一为「耗时=<n>ms · stopReason=<中文映射>」。
|
|
303
|
+
// 仅前台 completed 结算携带;后台结算经 job 结果呈现(不进 dispatch 卡片),
|
|
304
|
+
// continuable 不结算、不携带。任一字段缺失即省略该段。
|
|
305
|
+
function observabilitySuffix(result, block) {
|
|
306
|
+
let elapsedRaw = null
|
|
307
|
+
let stopRaw = null
|
|
308
|
+
if (result && result.fields) {
|
|
309
|
+
if (result.fields.elapsedMs !== undefined && result.fields.elapsedMs !== '') elapsedRaw = result.fields.elapsedMs
|
|
310
|
+
if (result.fields.stopReason !== undefined && result.fields.stopReason !== '') stopRaw = result.fields.stopReason
|
|
311
|
+
} else if (block && block.meta && typeof block.meta === 'object') {
|
|
312
|
+
// 运行中(无结果)或旧版结果:回退调用块 meta(向前兼容,与 tokenTier 同口径)。
|
|
313
|
+
if (block.meta.elapsedMs !== undefined) elapsedRaw = block.meta.elapsedMs
|
|
314
|
+
if (block.meta.stopReason !== undefined) stopRaw = block.meta.stopReason
|
|
315
|
+
}
|
|
316
|
+
const ms = typeof elapsedRaw === 'number' ? elapsedRaw : (typeof elapsedRaw === 'string' && elapsedRaw !== '' ? Number(elapsedRaw) : NaN)
|
|
317
|
+
const stopZh = typeof stopRaw === 'string' ? (ZH.stopReasonZh[stopRaw] || stopRaw) : ''
|
|
318
|
+
if (!Number.isFinite(ms) && stopZh === '') return ''
|
|
319
|
+
let out = ''
|
|
320
|
+
if (Number.isFinite(ms)) out += ` · 耗时=${ms}ms`
|
|
321
|
+
if (stopZh !== '') out += ` · stopReason=${stopZh}`
|
|
322
|
+
return out
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── dispatch 工具卡(React 依赖经参数注入)─────────────────────────────────
|
|
326
|
+
|
|
327
|
+
// 生效值:结果解析成功用结果(优先),字段缺失回退请求值。
|
|
328
|
+
function resolveDispatchState(request, result, settled) {
|
|
329
|
+
const eff = {
|
|
330
|
+
profile: result ? (result.fields.profile || request.profile) : request.profile,
|
|
331
|
+
preset: result ? (result.fields.preset || request.preset) : request.preset,
|
|
332
|
+
provider: result ? (result.fields.provider || request.provider) : request.provider,
|
|
333
|
+
model: result ? (result.fields.model || request.model) : request.model,
|
|
334
|
+
reasoningEffort: result ? (result.fields.reasoningEffort || request.reasoningEffort) : request.reasoningEffort
|
|
335
|
+
}
|
|
336
|
+
// ignored 列表:已结算读结果;运行中(无结果)按已知降级规则预判——
|
|
337
|
+
// continuable 恒忽略 preset 换用与 reasoningEffort(与结果同口径)。
|
|
338
|
+
const ignored = result ? result.ignored.slice() : []
|
|
339
|
+
if (!settled && request.continuable) {
|
|
340
|
+
if (request.preset !== '' && request.preset !== 'inherit' && !ignored.includes('preset')) ignored.push('preset')
|
|
341
|
+
if (request.reasoningEffort !== '' && !ignored.includes('reasoningEffort')) ignored.push('reasoningEffort')
|
|
342
|
+
}
|
|
343
|
+
// 请求≠生效:被忽略字段的生效值 = 确定性降级值。
|
|
344
|
+
if (ignored.includes('preset')) eff.preset = 'inherit'
|
|
345
|
+
if (ignored.includes('reasoningEffort')) eff.reasoningEffort = '(default)'
|
|
346
|
+
|
|
347
|
+
let kindLabel
|
|
348
|
+
const kindPrefix = request.continuable ? ZH.cont : (request.runInBackground ? ZH.toolBackground : ZH.toolForeground)
|
|
349
|
+
// 运行/结算标签判据 = settled(`'kind' in block`):已结算块即使 render 为
|
|
350
|
+
// 旧格式不可解析(result===null)也绝不落入「运行中」分支。
|
|
351
|
+
if (settled) {
|
|
352
|
+
if (result) {
|
|
353
|
+
if (result.kind === 'background') kindLabel = `${ZH.toolBackground} #${result.id}`
|
|
354
|
+
else if (result.kind === 'continuable') kindLabel = `${ZH.cont} #${result.id}(${ZH.canContinue})`
|
|
355
|
+
else kindLabel = ZH.toolForeground
|
|
356
|
+
} else {
|
|
357
|
+
// 已结算但结果不可解析:kind 由请求值推导,不带「运行中」。
|
|
358
|
+
kindLabel = kindPrefix
|
|
208
359
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
360
|
+
} else {
|
|
361
|
+
kindLabel = `${kindPrefix}(${ZH.toolRunning})`
|
|
362
|
+
}
|
|
363
|
+
return { eff, ignored, kindLabel }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function buildDispatchChips(el, chip, eff, result, block, ignored, request) {
|
|
367
|
+
const chips = []
|
|
368
|
+
chips.push(chip('profile', ZH.chipProfile, profileZh(eff.profile)))
|
|
369
|
+
// preset 继承值('inherit' 或未请求即空串,均渲染为「继承父」)与
|
|
370
|
+
// provider·model / effort 一样弱显。
|
|
371
|
+
chips.push(chip('preset', ZH.chipPreset, presetZh(eff.preset), { dim: eff.preset === 'inherit' || eff.preset === '' }))
|
|
372
|
+
const p = rawOrInherit(eff.provider)
|
|
373
|
+
const m = rawOrInherit(eff.model)
|
|
374
|
+
chips.push(chip('providerModel', `${ZH.chipProvider}·${ZH.chipModel}`, p === ZH.inherit && m === ZH.inherit ? ZH.inherit : `${p} / ${m}`, { dim: p === ZH.inherit && m === ZH.inherit }))
|
|
375
|
+
chips.push(chip('effort', ZH.chipEffort, effortZh(eff.reasoningEffort), { dim: eff.reasoningEffort === '' || eff.reasoningEffort === '(default)' }))
|
|
376
|
+
// tokenTier:宿主 render 行已产出;result.fields 缺失时回退 block.meta(向前兼容旧结果)。
|
|
377
|
+
let tier = ''
|
|
378
|
+
if (result && result.fields && typeof result.fields.tokenTier === 'string' && result.fields.tokenTier !== '') {
|
|
379
|
+
tier = result.fields.tokenTier
|
|
380
|
+
} else if (block && block.meta && typeof block.meta === 'object' && typeof block.meta.tokenTier === 'string' && block.meta.tokenTier !== '') {
|
|
381
|
+
tier = block.meta.tokenTier
|
|
382
|
+
}
|
|
383
|
+
if (tier !== '') chips.push(chip('tier', ZH.chipTier, ZH.tierZh[tier] || tier))
|
|
384
|
+
// ignored 项:警示色、置于 chip 行末尾(非静默)。
|
|
385
|
+
for (const key of ignored) {
|
|
386
|
+
const reqVal = request[key] || ''
|
|
387
|
+
chips.push(el('span', {
|
|
388
|
+
key: `ignored-${key}`,
|
|
389
|
+
className: 'sap-chip sap-chipWarn',
|
|
390
|
+
title: reqVal !== '' ? `请求值 "${reqVal}" 已忽略` : '请求值已忽略'
|
|
391
|
+
}, `⬆${key}(${ZH.ignoredMark})`))
|
|
392
|
+
}
|
|
393
|
+
return chips
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** 统一 chip 元素:label + value,dim=继承/默认值弱显。 */
|
|
397
|
+
function makeChip(el) {
|
|
398
|
+
const chip = (key, label, value, opts) => {
|
|
399
|
+
const valueCls = opts && opts.dim ? 'sap-chipValueDim' : 'sap-chipValue'
|
|
400
|
+
return el('span', {
|
|
401
|
+
key,
|
|
402
|
+
className: 'sap-chip',
|
|
403
|
+
...(opts && typeof opts.title === 'string' && opts.title !== '' ? { title: opts.title } : {})
|
|
404
|
+
},
|
|
405
|
+
el('span', { className: 'sap-chipLabel' }, label),
|
|
406
|
+
el('span', { className: valueCls }, value)
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
return chip
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function makeDispatchToolview(el, chip) {
|
|
413
|
+
function DispatchToolview(props) {
|
|
414
|
+
const block = props && props.block
|
|
415
|
+
const request = readDispatchRequest(block)
|
|
416
|
+
const result = readDispatchResult(block)
|
|
417
|
+
const settled = !!(block && typeof block === 'object' && 'kind' in block)
|
|
418
|
+
const { eff, ignored, kindLabel } = resolveDispatchState(request, result, settled)
|
|
419
|
+
const chips = buildDispatchChips(el, chip, eff, result, block, ignored, request)
|
|
420
|
+
const p = rawOrInherit(eff.provider)
|
|
421
|
+
const m = rawOrInherit(eff.model)
|
|
422
|
+
const text = `${ZH.toolLabel}:${kindLabel} · 方案=${profileZh(eff.profile)} · 预设=${presetZh(eff.preset)} · 提供方=${p} · 模型=${m} · 推理强度=${effortZh(eff.reasoningEffort)}${tokensSuffix(result, block)}${observabilitySuffix(result, block)}`
|
|
423
|
+
return el('div', { className: 'sap-toolview' },
|
|
424
|
+
el('div', { className: 'sap-chips' }, chips),
|
|
425
|
+
el('div', { className: 'sap-toolText' }, text)
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
return DispatchToolview
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── 设置页:事件处理工厂(state 统一经 s 注入)─────────────────────────────
|
|
432
|
+
|
|
433
|
+
// 写路径契约:HTTP 200 但 persisted:false 时 host 附 persistWarning
|
|
434
|
+
// 「已保存但未持久化」——这里改为 amber 警示(非绿色成功态)。
|
|
435
|
+
function makeApplyWrite(s) {
|
|
436
|
+
const applyWrite = (data, successNotice) => {
|
|
437
|
+
if (data && data.persisted === false) {
|
|
438
|
+
s.setSavedNotice('')
|
|
439
|
+
s.setPersistWarning(typeof data.persistWarning === 'string' && data.persistWarning !== '' ? data.persistWarning : ZH.persistFail)
|
|
440
|
+
return
|
|
254
441
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
442
|
+
s.setPersistWarning('')
|
|
443
|
+
if (typeof successNotice === 'string' && successNotice !== '') s.setSavedNotice(successNotice)
|
|
444
|
+
else s.setSavedNotice('')
|
|
445
|
+
}
|
|
446
|
+
return applyWrite
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function makeFormActions(s, loadEfforts, loadTools) {
|
|
450
|
+
const setField = (key) => (event) => {
|
|
451
|
+
const value = event && event.target ? event.target.value : ''
|
|
452
|
+
s.setForm((prev) => {
|
|
453
|
+
const next = { ...prev, [key]: value }
|
|
454
|
+
if (key === 'model') next.reasoningEffort = '' // 换模型时重置推理强度
|
|
455
|
+
return next
|
|
456
|
+
})
|
|
457
|
+
if (key === 'model' && value !== '') {
|
|
458
|
+
loadEfforts(value) // 选中模型 → 懒拉该模型 efforts
|
|
267
459
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const m = rawOrInherit(eff.model)
|
|
317
|
-
chips.push(chip('providerModel', `${ZH.chipProvider}·${ZH.chipModel}`, p === ZH.inherit && m === ZH.inherit ? ZH.inherit : `${p} / ${m}`, { dim: p === ZH.inherit && m === ZH.inherit }))
|
|
318
|
-
chips.push(chip('effort', ZH.chipEffort, effortZh(eff.reasoningEffort), { dim: eff.reasoningEffort === '' || eff.reasoningEffort === '(default)' }))
|
|
319
|
-
// tokenTier:宿主当前结果里未产出(§8.3 属中期切片);字段出现即渲染(向前兼容)。
|
|
320
|
-
let tier = ''
|
|
321
|
-
if (result && result.fields && typeof result.fields.tokenTier === 'string' && result.fields.tokenTier !== '') {
|
|
322
|
-
tier = result.fields.tokenTier
|
|
323
|
-
} else if (block && block.meta && typeof block.meta === 'object' && typeof block.meta.tokenTier === 'string' && block.meta.tokenTier !== '') {
|
|
324
|
-
tier = block.meta.tokenTier
|
|
325
|
-
}
|
|
326
|
-
if (tier !== '') chips.push(chip('tier', ZH.chipTier, ZH.tierZh[tier] || tier))
|
|
327
|
-
// ignored 项:警示色、置于 chip 行末尾(BP-03 根治:非静默)。
|
|
328
|
-
for (const key of ignored) {
|
|
329
|
-
const reqVal = request[key] || ''
|
|
330
|
-
chips.push(el('span', {
|
|
331
|
-
key: `ignored-${key}`,
|
|
332
|
-
className: 'sap-chip sap-chipWarn',
|
|
333
|
-
title: reqVal !== '' ? `请求值 "${reqVal}" 已忽略` : '请求值已忽略'
|
|
334
|
-
}, `⬆${key}(${ZH.ignoredMark})`))
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const text = `${ZH.toolLabel}:${kindLabel} · 方案=${profileZh(eff.profile)} · 预设=${presetZh(eff.preset)} · 提供方=${p} · 模型=${m} · 推理强度=${effortZh(eff.reasoningEffort)}`
|
|
338
|
-
return el('div', { className: 'sap-toolview' },
|
|
339
|
-
el('div', { className: 'sap-chips' }, chips),
|
|
340
|
-
el('div', { className: 'sap-toolText' }, text)
|
|
341
|
-
)
|
|
460
|
+
}
|
|
461
|
+
const setToolMode = (mode) => () => {
|
|
462
|
+
if (mode !== 'none') loadTools() // 工具限制面板启用 → 拉完整工具目录
|
|
463
|
+
s.setForm((prev) => ({ ...prev, toolMode: mode }))
|
|
464
|
+
}
|
|
465
|
+
const toggleTool = (name) => () => {
|
|
466
|
+
s.setForm((prev) => {
|
|
467
|
+
const cur = Array.isArray(prev.toolList) ? prev.toolList : []
|
|
468
|
+
const next = cur.includes(name) ? cur.filter((n) => n !== name) : [...cur, name]
|
|
469
|
+
return { ...prev, toolList: next }
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
const selectLayer = (layer) => () => s.setForm((prev) => {
|
|
473
|
+
const layerNames = s.tools.filter((t) => t.layer === layer).map((t) => t.name)
|
|
474
|
+
return { ...prev, toolList: [...new Set([...(Array.isArray(prev.toolList) ? prev.toolList : []), ...layerNames])] }
|
|
475
|
+
})
|
|
476
|
+
const clearLayer = (layer) => () => s.setForm((prev) => {
|
|
477
|
+
const layerSet = new Set(s.tools.filter((t) => t.layer === layer).map((t) => t.name))
|
|
478
|
+
return { ...prev, toolList: (Array.isArray(prev.toolList) ? prev.toolList : []).filter((n) => !layerSet.has(n)) }
|
|
479
|
+
})
|
|
480
|
+
const invertLayer = (layer) => () => s.setForm((prev) => {
|
|
481
|
+
const layerNames = s.tools.filter((t) => t.layer === layer).map((t) => t.name)
|
|
482
|
+
const cur = Array.isArray(prev.toolList) ? prev.toolList : []
|
|
483
|
+
const toAdd = layerNames.filter((n) => !cur.includes(n))
|
|
484
|
+
const toRemove = new Set(layerNames.filter((n) => cur.includes(n)))
|
|
485
|
+
return { ...prev, toolList: [...cur.filter((n) => !toRemove.has(n)), ...toAdd] }
|
|
486
|
+
})
|
|
487
|
+
return { setField, setToolMode, toggleTool, selectLayer, clearLayer, invertLayer }
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function makeEditActions(s, loadEfforts, loadTools) {
|
|
491
|
+
const openAdd = () => {
|
|
492
|
+
s.setError('')
|
|
493
|
+
s.setSavedNotice('')
|
|
494
|
+
s.setPersistWarning('')
|
|
495
|
+
s.setForm(EMPTY_FORM)
|
|
496
|
+
s.setEditingId(null)
|
|
497
|
+
s.setAdding(true)
|
|
498
|
+
}
|
|
499
|
+
const openEdit = (profile) => () => {
|
|
500
|
+
s.setError(''); s.setSavedNotice(''); s.setPersistWarning('')
|
|
501
|
+
const tf = profile.toolFilter && typeof profile.toolFilter === 'object' ? profile.toolFilter : {}
|
|
502
|
+
const allowArr = Array.isArray(tf.allow) ? tf.allow.slice() : []
|
|
503
|
+
const denyArr = Array.isArray(tf.deny) ? tf.deny.slice() : []
|
|
504
|
+
const toolMode = allowArr.length > 0 ? 'allow' : (denyArr.length > 0 ? 'deny' : 'none')
|
|
505
|
+
if (toolMode !== 'none') loadTools() // 编辑含 toolFilter 的 profile → 预拉工具目录
|
|
506
|
+
if (typeof profile.model === 'string' && profile.model !== '') {
|
|
507
|
+
loadEfforts(profile.model) // 编辑含 model 的 profile → 预拉该模型 efforts(下拉不空白)
|
|
342
508
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
509
|
+
s.setForm({
|
|
510
|
+
id: profile.id,
|
|
511
|
+
description: typeof profile.description === 'string' ? profile.description : '',
|
|
512
|
+
preset: typeof profile.preset === 'string' ? profile.preset : '',
|
|
513
|
+
provider: typeof profile.provider === 'string' ? profile.provider : '',
|
|
514
|
+
model: typeof profile.model === 'string' ? profile.model : '',
|
|
515
|
+
reasoningEffort: typeof profile.reasoningEffort === 'string' ? profile.reasoningEffort : '',
|
|
516
|
+
toolMode,
|
|
517
|
+
toolList: toolMode === 'allow' ? allowArr : denyArr
|
|
518
|
+
})
|
|
519
|
+
s.setEditingId(profile.id)
|
|
520
|
+
s.setAdding(true)
|
|
521
|
+
}
|
|
522
|
+
const closeAdd = () => {
|
|
523
|
+
s.setAdding(false)
|
|
524
|
+
s.setEditingId(null)
|
|
525
|
+
s.setError('')
|
|
526
|
+
s.setPersistWarning('')
|
|
527
|
+
s.setForm(EMPTY_FORM)
|
|
528
|
+
}
|
|
529
|
+
return { openAdd, openEdit, closeAdd }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function makeSubmitActions(api, s, refresh, applyWrite) {
|
|
533
|
+
const submit = () => {
|
|
534
|
+
const payload = { id: typeof s.form.id === 'string' ? s.form.id.trim() : '' }
|
|
535
|
+
// 字符串字段总是发送(含空串):后端 merge 语义里空串 = 清除该字段,
|
|
536
|
+
// 否则把字段清回“继承父/默认”会被静默忽略(保留旧值)。
|
|
537
|
+
const add = (key, value) => { if (typeof value === 'string') payload[key] = value.trim() }
|
|
538
|
+
add('description', s.form.description)
|
|
539
|
+
add('preset', s.form.preset)
|
|
540
|
+
add('provider', s.form.provider)
|
|
541
|
+
add('model', s.form.model)
|
|
542
|
+
add('reasoningEffort', s.form.reasoningEffort)
|
|
543
|
+
const list = Array.isArray(s.form.toolList) ? s.form.toolList : []
|
|
544
|
+
if (s.form.toolMode === 'allow' && list.length > 0) payload.toolFilter = { allow: list }
|
|
545
|
+
else if (s.form.toolMode === 'deny' && list.length > 0) payload.toolFilter = { deny: list }
|
|
546
|
+
else payload.toolFilter = {} // 不限制:空对象 = 清除(后端会 delete)
|
|
547
|
+
if (typeof payload.id !== 'string' || payload.id === '') { s.setError('profile id 不能为空'); return }
|
|
548
|
+
// 编辑:保留原 name 和 enabled 状态
|
|
549
|
+
const existing = s.profiles.find((p) => p.id === s.form.id)
|
|
550
|
+
if (existing) {
|
|
551
|
+
if (typeof existing.name === 'string' && existing.name !== '') payload.name = existing.name
|
|
552
|
+
if (existing.enabled === false) payload.enabled = false
|
|
351
553
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const [enabled, setEnabled] = useState(true)
|
|
357
|
-
const [adding, setAdding] = useState(false)
|
|
358
|
-
const [editingId, setEditingId] = useState(null)
|
|
359
|
-
const [error, setError] = useState('')
|
|
360
|
-
const [savedNotice, setSavedNotice] = useState('')
|
|
361
|
-
const [persistWarning, setPersistWarning] = useState('')
|
|
362
|
-
const [form, setForm] = useState(EMPTY_FORM)
|
|
363
|
-
const [tools, setTools] = useState([])
|
|
364
|
-
|
|
365
|
-
const refresh = useCallback(() => {
|
|
366
|
-
api('/list')
|
|
367
|
-
.then((data) => setProfiles(Array.isArray(data.profiles) ? data.profiles : []))
|
|
368
|
-
.catch((err) => setError(String((err && err.message) || err)))
|
|
369
|
-
}, [])
|
|
370
|
-
|
|
371
|
-
const loadOptions = useCallback(() => {
|
|
372
|
-
api('/options')
|
|
373
|
-
.then((data) => {
|
|
374
|
-
setEnabled(data.enabled !== false)
|
|
375
|
-
setOptions({
|
|
376
|
-
models: Array.isArray(data.models) ? data.models : [],
|
|
377
|
-
efforts: data.efforts && typeof data.efforts === 'object' ? data.efforts : {},
|
|
378
|
-
presets: Array.isArray(data.presets) ? data.presets : []
|
|
379
|
-
})
|
|
380
|
-
setTools(Array.isArray(data.tools) ? data.tools : [])
|
|
381
|
-
})
|
|
382
|
-
.catch(() => {})
|
|
383
|
-
}, [])
|
|
384
|
-
|
|
385
|
-
useEffect(() => {
|
|
554
|
+
api('/add', payload)
|
|
555
|
+
.then((data) => {
|
|
556
|
+
s.setForm(EMPTY_FORM); s.setAdding(false); s.setEditingId(null); s.setError('')
|
|
557
|
+
applyWrite(data, `已保存 ${payload.id}`)
|
|
386
558
|
refresh()
|
|
387
|
-
loadOptions()
|
|
388
|
-
}, [refresh, loadOptions])
|
|
389
|
-
|
|
390
|
-
const toggleEnabled = () => {
|
|
391
|
-
const next = !enabled
|
|
392
|
-
setEnabled(next)
|
|
393
|
-
setError('')
|
|
394
|
-
api('/set-enabled', { enabled: next })
|
|
395
|
-
.catch((err) => {
|
|
396
|
-
setEnabled(!next)
|
|
397
|
-
setError(String((err && err.message) || err))
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const setField = (key) => (event) => {
|
|
402
|
-
const value = event && event.target ? event.target.value : ''
|
|
403
|
-
setForm((prev) => {
|
|
404
|
-
const next = { ...prev, [key]: value }
|
|
405
|
-
if (key === 'model') next.reasoningEffort = '' // 换模型时重置推理强度
|
|
406
|
-
return next
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const setToolMode = (mode) => () => setForm((prev) => ({ ...prev, toolMode: mode }))
|
|
411
|
-
const toggleTool = (name) => () => {
|
|
412
|
-
setForm((prev) => {
|
|
413
|
-
const cur = Array.isArray(prev.toolList) ? prev.toolList : []
|
|
414
|
-
const next = cur.includes(name) ? cur.filter((n) => n !== name) : [...cur, name]
|
|
415
|
-
return { ...prev, toolList: next }
|
|
416
|
-
})
|
|
417
|
-
}
|
|
418
|
-
const selectLayer = (layer) => () => setForm((prev) => {
|
|
419
|
-
const layerNames = tools.filter((t) => t.layer === layer).map((t) => t.name)
|
|
420
|
-
return { ...prev, toolList: [...new Set([...(Array.isArray(prev.toolList) ? prev.toolList : []), ...layerNames])] }
|
|
421
559
|
})
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
560
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
561
|
+
}
|
|
562
|
+
return { submit }
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function makeRowActions(api, s, refresh, applyWrite) {
|
|
566
|
+
const toggleEnabled = () => {
|
|
567
|
+
const next = !s.enabled
|
|
568
|
+
s.setEnabled(next)
|
|
569
|
+
s.setError('')
|
|
570
|
+
api('/set-enabled', { enabled: next })
|
|
571
|
+
.catch((err) => {
|
|
572
|
+
s.setEnabled(!next)
|
|
573
|
+
s.setError(String((err && err.message) || err))
|
|
425
574
|
})
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
575
|
+
}
|
|
576
|
+
const setProfileEnabled = (profile, next) => () => {
|
|
577
|
+
s.setError('')
|
|
578
|
+
api('/set-profile-enabled', { id: profile.id, enabled: next })
|
|
579
|
+
.then((data) => { applyWrite(data, ''); refresh() })
|
|
580
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
581
|
+
}
|
|
582
|
+
const resetAll = () => {
|
|
583
|
+
if (!window.confirm('将所有内置方案重置为默认配置?已删除的内置也会恢复。')) return
|
|
584
|
+
s.setError('')
|
|
585
|
+
api('/reset-all')
|
|
586
|
+
.then((data) => { applyWrite(data, '已重置所有内置方案'); refresh() })
|
|
587
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
588
|
+
}
|
|
589
|
+
const remove = (profile) => () => {
|
|
590
|
+
if (!window.confirm(`删除方案「${profile.name || profile.id}」?${profile.builtin === true ? '内置方案删除后可在上方「重置内置方案」恢复' : ''}`)) return
|
|
591
|
+
api('/remove', { id: profile.id })
|
|
592
|
+
.then((data) => {
|
|
593
|
+
s.setError('')
|
|
594
|
+
applyWrite(data, `已删除 ${profile.id}`)
|
|
595
|
+
refresh()
|
|
432
596
|
})
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const openAdd = () => {
|
|
448
|
-
setError('')
|
|
449
|
-
setSavedNotice('')
|
|
450
|
-
setPersistWarning('')
|
|
451
|
-
setForm(EMPTY_FORM)
|
|
452
|
-
setEditingId(null)
|
|
453
|
-
setAdding(true)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const openEdit = (profile) => () => {
|
|
457
|
-
setError(''); setSavedNotice(''); setPersistWarning('')
|
|
458
|
-
const tf = profile.toolFilter && typeof profile.toolFilter === 'object' ? profile.toolFilter : {}
|
|
459
|
-
const allowArr = Array.isArray(tf.allow) ? tf.allow.slice() : []
|
|
460
|
-
const denyArr = Array.isArray(tf.deny) ? tf.deny.slice() : []
|
|
461
|
-
const toolMode = allowArr.length > 0 ? 'allow' : (denyArr.length > 0 ? 'deny' : 'none')
|
|
462
|
-
setForm({
|
|
463
|
-
id: profile.id,
|
|
464
|
-
description: typeof profile.description === 'string' ? profile.description : '',
|
|
465
|
-
preset: typeof profile.preset === 'string' ? profile.preset : '',
|
|
466
|
-
provider: typeof profile.provider === 'string' ? profile.provider : '',
|
|
467
|
-
model: typeof profile.model === 'string' ? profile.model : '',
|
|
468
|
-
reasoningEffort: typeof profile.reasoningEffort === 'string' ? profile.reasoningEffort : '',
|
|
469
|
-
toolMode,
|
|
470
|
-
toolList: toolMode === 'allow' ? allowArr : denyArr
|
|
471
|
-
})
|
|
472
|
-
setEditingId(profile.id)
|
|
473
|
-
setAdding(true)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const closeAdd = () => {
|
|
477
|
-
setAdding(false)
|
|
478
|
-
setEditingId(null)
|
|
479
|
-
setError('')
|
|
480
|
-
setPersistWarning('')
|
|
481
|
-
setForm(EMPTY_FORM)
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const submit = () => {
|
|
485
|
-
const payload = { id: typeof form.id === 'string' ? form.id.trim() : '' }
|
|
486
|
-
// 字符串字段总是发送(含空串):后端 merge 语义里空串 = 清除该字段,
|
|
487
|
-
// 否则把字段清回“继承父/默认”会被静默忽略(保留旧值)。
|
|
488
|
-
const add = (key, value) => { if (typeof value === 'string') payload[key] = value.trim() }
|
|
489
|
-
add('description', form.description)
|
|
490
|
-
add('preset', form.preset)
|
|
491
|
-
add('provider', form.provider)
|
|
492
|
-
add('model', form.model)
|
|
493
|
-
add('reasoningEffort', form.reasoningEffort)
|
|
494
|
-
const list = Array.isArray(form.toolList) ? form.toolList : []
|
|
495
|
-
if (form.toolMode === 'allow' && list.length > 0) payload.toolFilter = { allow: list }
|
|
496
|
-
else if (form.toolMode === 'deny' && list.length > 0) payload.toolFilter = { deny: list }
|
|
497
|
-
else payload.toolFilter = {} // 不限制:空对象 = 清除(后端会 delete)
|
|
498
|
-
if (typeof payload.id !== 'string' || payload.id === '') { setError('profile id 不能为空'); return }
|
|
499
|
-
// 编辑:保留原 name 和 enabled 状态
|
|
500
|
-
const existing = profiles.find((p) => p.id === form.id)
|
|
501
|
-
if (existing) {
|
|
502
|
-
if (typeof existing.name === 'string' && existing.name !== '') payload.name = existing.name
|
|
503
|
-
if (existing.enabled === false) payload.enabled = false
|
|
504
|
-
}
|
|
505
|
-
api('/add', payload)
|
|
506
|
-
.then((data) => {
|
|
507
|
-
setForm(EMPTY_FORM); setAdding(false); setEditingId(null); setError('')
|
|
508
|
-
applyWrite(data, `已保存 ${payload.id}`)
|
|
509
|
-
refresh()
|
|
510
|
-
})
|
|
511
|
-
.catch((err) => setError(String((err && err.message) || err)))
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const setProfileEnabled = (profile, next) => () => {
|
|
515
|
-
setError('')
|
|
516
|
-
api('/set-profile-enabled', { id: profile.id, enabled: next })
|
|
517
|
-
.then((data) => { applyWrite(data, ''); refresh() })
|
|
518
|
-
.catch((err) => setError(String((err && err.message) || err)))
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const resetAll = () => {
|
|
522
|
-
if (!window.confirm('将所有内置方案重置为默认配置?已删除的内置也会恢复。')) return
|
|
523
|
-
setError('')
|
|
524
|
-
api('/reset-all')
|
|
525
|
-
.then((data) => { applyWrite(data, '已重置所有内置方案'); refresh() })
|
|
526
|
-
.catch((err) => setError(String((err && err.message) || err)))
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const remove = (profile) => () => {
|
|
530
|
-
if (!window.confirm(`删除方案「${profile.name || profile.id}」?${profile.builtin === true ? '内置方案删除后可在上方「重置内置方案」恢复' : ''}`)) return
|
|
531
|
-
api('/remove', { id: profile.id })
|
|
532
|
-
.then((data) => {
|
|
533
|
-
setError('')
|
|
534
|
-
applyWrite(data, `已删除 ${profile.id}`)
|
|
535
|
-
refresh()
|
|
536
|
-
})
|
|
537
|
-
.catch((err) => setError(String((err && err.message) || err)))
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Distinct providers, derived from the model list.
|
|
541
|
-
const providers = []
|
|
542
|
-
const seenProvider = new Set()
|
|
543
|
-
for (const model of options.models) {
|
|
544
|
-
if (seenProvider.has(model.provider)) continue
|
|
545
|
-
seenProvider.add(model.provider)
|
|
546
|
-
providers.push({ id: model.provider, name: model.providerName || model.provider })
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const effortsForModel = form.model ? (options.efforts[form.model] ?? []) : []
|
|
550
|
-
|
|
551
|
-
const rows = profiles.map((profile) => {
|
|
552
|
-
const builtin = profile.builtin === true
|
|
553
|
-
const isDisabled = profile.enabled === false
|
|
554
|
-
const hasName = typeof profile.name === 'string' && profile.name !== '' && profile.name !== String(profile.id)
|
|
555
|
-
const hasPreset = typeof profile.preset === 'string' && profile.preset !== ''
|
|
556
|
-
const hasModel = typeof profile.model === 'string' && profile.model !== ''
|
|
557
|
-
const hasEffort = typeof profile.reasoningEffort === 'string' && profile.reasoningEffort !== ''
|
|
558
|
-
const chipItems = [
|
|
559
|
-
chip('preset', ZH.chipPreset, presetZh(profile.preset), { dim: !hasPreset }),
|
|
560
|
-
chip('model', ZH.chipModel, rawOrInherit(profile.model), { dim: !hasModel }),
|
|
561
|
-
chip('effort', ZH.chipEffort, effortZh(profile.reasoningEffort), { dim: !hasEffort }),
|
|
562
|
-
isDisabled
|
|
563
|
-
? el('span', { key: 'state', className: 'sap-chip sap-chipDisabled' }, ZH.chipDisabled)
|
|
564
|
-
: el('span', { key: 'state', className: 'sap-chip sap-chipSuccess' }, ZH.chipEnabled)
|
|
565
|
-
]
|
|
566
|
-
return el('li', { key: String(profile.id), className: 'sap-rowCard' },
|
|
567
|
-
el('div', { className: 'sap-rowHead' },
|
|
568
|
-
el('span', { className: 'sap-rowIdentity' },
|
|
569
|
-
el('span', { className: 'sap-rowName' }, hasName ? profile.name : String(profile.id)),
|
|
570
|
-
hasName ? el('span', { className: 'sap-rowId' }, String(profile.id)) : null,
|
|
571
|
-
builtin ? el('span', { className: 'sap-rowTag' }, '内置') : null
|
|
572
|
-
),
|
|
573
|
-
el('span', { className: 'sap-rowActions' },
|
|
574
|
-
el('label', { className: 'sap-switch', title: '启用/禁用' },
|
|
575
|
-
el('input', { type: 'checkbox', checked: !isDisabled, onChange: setProfileEnabled(profile, isDisabled) }),
|
|
576
|
-
el('span', null, isDisabled ? '已禁用' : '已启用')
|
|
577
|
-
),
|
|
578
|
-
el('button', { className: 'sap-secondaryButton', style: { height: '28px', padding: '0 10px', fontSize: '12px', borderRadius: '14px' }, onClick: openEdit(profile) }, '编辑'),
|
|
579
|
-
el('button', { className: 'sap-dangerButton', onClick: remove(profile) }, '删除')
|
|
580
|
-
)
|
|
581
|
-
),
|
|
582
|
-
el('div', { className: 'sap-chips' }, chipItems),
|
|
583
|
-
typeof profile.description === 'string' && profile.description !== ''
|
|
584
|
-
? el('p', { className: 'sap-desc' }, profile.description)
|
|
585
|
-
: null
|
|
586
|
-
)
|
|
597
|
+
.catch((err) => s.setError(String((err && err.message) || err)))
|
|
598
|
+
}
|
|
599
|
+
return { toggleEnabled, setProfileEnabled, resetAll, remove }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// 选中模型 → 懒拉 /options/efforts(按模型 id 存入 options.efforts)。
|
|
603
|
+
function makeEffortsLoader(api, setOptions) {
|
|
604
|
+
return (model) => {
|
|
605
|
+
const q = `?model=${encodeURIComponent(model)}`
|
|
606
|
+
api(`/options/efforts${q}`)
|
|
607
|
+
.then((data) => {
|
|
608
|
+
const list = Array.isArray(data.efforts) ? data.efforts : []
|
|
609
|
+
setOptions((prev) => ({ ...prev, efforts: { ...prev.efforts, [model]: list } }))
|
|
587
610
|
})
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
611
|
+
.catch(() => {})
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// 工具限制面板启用 → 拉 /options/tools(完整工具目录)。
|
|
616
|
+
function makeToolsLoader(api, setTools) {
|
|
617
|
+
return () => {
|
|
618
|
+
api('/options/tools')
|
|
619
|
+
.then((data) => setTools(Array.isArray(data.tools) ? data.tools : []))
|
|
620
|
+
.catch(() => {})
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// 版本探测 → 拉 /options/versions;warnings 非空时设置页顶部常驻 amber 提示条。
|
|
625
|
+
function makeVersionsLoader(api, setVersionWarnings) {
|
|
626
|
+
return () => {
|
|
627
|
+
api('/options/versions')
|
|
628
|
+
.then((data) => setVersionWarnings(Array.isArray(data.warnings) ? data.warnings : []))
|
|
629
|
+
.catch(() => {})
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools) {
|
|
634
|
+
return {
|
|
635
|
+
...makeFormActions(s, loadEfforts, loadTools),
|
|
636
|
+
...makeEditActions(s, loadEfforts, loadTools),
|
|
637
|
+
...makeSubmitActions(api, s, refresh, applyWrite),
|
|
638
|
+
...makeRowActions(api, s, refresh, applyWrite),
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ── 设置页:渲染 helpers ────────────────────────────────────────────────────
|
|
643
|
+
|
|
644
|
+
function buildSelectField(el, s, setField, label, key, optionsList, renderOption) {
|
|
645
|
+
return el('div', { className: 'sap-field' },
|
|
646
|
+
el('span', { className: 'sap-fieldLabel' }, label),
|
|
647
|
+
el('select', { className: 'sap-input sap-select', value: s.form[key], onChange: setField(key) },
|
|
648
|
+
el('option', { value: '' }, '继承父(不指定)'),
|
|
649
|
+
optionsList.map(renderOption)
|
|
650
|
+
)
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function renderTool(el, s, actions, t) {
|
|
655
|
+
const checked = Array.isArray(s.form.toolList) && s.form.toolList.includes(t.name)
|
|
656
|
+
return el('label', { key: t.name, className: 'sap-toolItem' },
|
|
657
|
+
el('input', { type: 'checkbox', checked, onChange: actions.toggleTool(t.name) }),
|
|
658
|
+
el('span', null, t.zh ? `${t.zh} ${t.name}` : t.name)
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function buildLayerGroups(tools, layer) {
|
|
663
|
+
const layerTools = tools.filter((t) => t.layer === layer)
|
|
664
|
+
const groups = new Map()
|
|
665
|
+
for (const t of layerTools) {
|
|
666
|
+
const g = typeof t.group === 'string' && t.group !== '' ? t.group : '其他'
|
|
667
|
+
if (!groups.has(g)) groups.set(g, [])
|
|
668
|
+
groups.get(g).push(t)
|
|
669
|
+
}
|
|
670
|
+
const keys = layer === 'core'
|
|
671
|
+
? [...CORE_ORDER.filter((c) => groups.has(c)), ...new Set([...groups.keys()].filter((k) => !CORE_ORDER.includes(k)))]
|
|
672
|
+
: [...groups.keys()]
|
|
673
|
+
return keys.map((k) => [k, groups.get(k)])
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function buildToolModePicker(el, s, actions) {
|
|
677
|
+
return el('div', { className: 'sap-toolModes' },
|
|
678
|
+
TOOL_MODES.map(([mode, label]) => el('label', { key: mode, className: 'sap-toolItem' },
|
|
679
|
+
el('input', { type: 'radio', name: 'sap-toolMode', checked: s.form.toolMode === mode, onChange: actions.setToolMode(mode) }),
|
|
680
|
+
el('span', null, label)
|
|
681
|
+
))
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function buildToolLayerCard(el, s, actions, layer, groups) {
|
|
686
|
+
const layerTools = s.tools.filter((t) => t.layer === layer)
|
|
687
|
+
const count = layerTools.length
|
|
688
|
+
const selectedCount = layerTools.filter((t) => Array.isArray(s.form.toolList) && s.form.toolList.includes(t.name)).length
|
|
689
|
+
const summaryText = selectedCount > 0
|
|
690
|
+
? `${LAYER_TITLES[layer]}(${count})· 已选 ${selectedCount}`
|
|
691
|
+
: `${LAYER_TITLES[layer]}(${count})`
|
|
692
|
+
return el('details', { key: layer, className: 'sap-toolLayerCard' },
|
|
693
|
+
el('summary', { className: 'sap-customizedSummary' }, summaryText),
|
|
694
|
+
el('div', { className: 'sap-toolLayerBody' },
|
|
695
|
+
el('div', { className: 'sap-toolActions', style: { marginTop: '2px' } },
|
|
696
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.selectLayer(layer) }, '全选'),
|
|
697
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.clearLayer(layer) }, '清空'),
|
|
698
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: smallBtn, onClick: actions.invertLayer(layer) }, '反选')
|
|
699
|
+
),
|
|
700
|
+
groups.map(([g, list]) => el('div', { key: g, className: 'sap-toolGroup' },
|
|
701
|
+
el('div', { className: 'sap-toolGroupTitle' }, `${g}(${list.length})`),
|
|
702
|
+
el('div', { className: 'sap-toolList' }, list.map((t) => renderTool(el, s, actions, t)))
|
|
703
|
+
))
|
|
704
|
+
)
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function buildToolLayers(el, s, actions) {
|
|
709
|
+
return el('div', { className: 'sap-toolGroups' },
|
|
710
|
+
['core', 'plugin', 'custom']
|
|
711
|
+
.filter((layer) => s.tools.some((t) => t.layer === layer))
|
|
712
|
+
.map((layer) => buildToolLayerCard(el, s, actions, layer, buildLayerGroups(s.tools, layer)))
|
|
713
|
+
)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function buildToolSection(el, s, actions) {
|
|
717
|
+
return el('div', { className: 'sap-field' },
|
|
718
|
+
el('span', { className: 'sap-fieldLabel' }, '工具限制'),
|
|
719
|
+
buildToolModePicker(el, s, actions),
|
|
720
|
+
s.form.toolMode === 'none'
|
|
721
|
+
? null
|
|
722
|
+
: (s.tools.length === 0
|
|
723
|
+
? el('span', { className: 'sap-metaValueDefault' }, '(无可用工具列表)')
|
|
724
|
+
: buildToolLayers(el, s, actions))
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function buildProfileRows(el, chip, s, actions) {
|
|
729
|
+
return s.profiles.map((profile) => {
|
|
730
|
+
const builtin = profile.builtin === true
|
|
731
|
+
const isDisabled = profile.enabled === false
|
|
732
|
+
const hasName = typeof profile.name === 'string' && profile.name !== '' && profile.name !== String(profile.id)
|
|
733
|
+
const hasPreset = typeof profile.preset === 'string' && profile.preset !== ''
|
|
734
|
+
const hasModel = typeof profile.model === 'string' && profile.model !== ''
|
|
735
|
+
const hasEffort = typeof profile.reasoningEffort === 'string' && profile.reasoningEffort !== ''
|
|
736
|
+
const chipItems = [
|
|
737
|
+
chip('preset', ZH.chipPreset, presetZh(profile.preset), { dim: !hasPreset }),
|
|
738
|
+
chip('model', ZH.chipModel, rawOrInherit(profile.model), { dim: !hasModel }),
|
|
739
|
+
chip('effort', ZH.chipEffort, effortZh(profile.reasoningEffort), { dim: !hasEffort }),
|
|
740
|
+
isDisabled
|
|
741
|
+
? el('span', { key: 'state', className: 'sap-chip sap-chipDisabled' }, ZH.chipDisabled)
|
|
742
|
+
: el('span', { key: 'state', className: 'sap-chip sap-chipSuccess' }, ZH.chipEnabled)
|
|
743
|
+
]
|
|
744
|
+
return el('li', { key: String(profile.id), className: 'sap-rowCard' },
|
|
745
|
+
el('div', { className: 'sap-rowHead' },
|
|
746
|
+
el('span', { className: 'sap-rowIdentity' },
|
|
747
|
+
el('span', { className: 'sap-rowName' }, hasName ? profile.name : String(profile.id)),
|
|
748
|
+
hasName ? el('span', { className: 'sap-rowId' }, String(profile.id)) : null,
|
|
749
|
+
builtin ? el('span', { className: 'sap-rowTag' }, '内置') : null
|
|
634
750
|
),
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
751
|
+
el('span', { className: 'sap-rowActions' },
|
|
752
|
+
el('label', { className: 'sap-switch', title: '启用/禁用' },
|
|
753
|
+
el('input', { type: 'checkbox', checked: !isDisabled, onChange: actions.setProfileEnabled(profile, isDisabled) }),
|
|
754
|
+
el('span', null, isDisabled ? '已禁用' : '已启用')
|
|
755
|
+
),
|
|
756
|
+
el('button', { className: 'sap-secondaryButton', style: { height: '28px', padding: '0 10px', fontSize: '12px', borderRadius: '14px' }, onClick: actions.openEdit(profile) }, '编辑'),
|
|
757
|
+
el('button', { className: 'sap-dangerButton', onClick: actions.remove(profile) }, '删除')
|
|
758
|
+
)
|
|
759
|
+
),
|
|
760
|
+
el('div', { className: 'sap-chips' }, chipItems),
|
|
761
|
+
typeof profile.description === 'string' && profile.description !== ''
|
|
762
|
+
? el('p', { className: 'sap-desc' }, profile.description)
|
|
763
|
+
: null
|
|
764
|
+
)
|
|
765
|
+
})
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function buildAddCard(el, s, actions, toolSection) {
|
|
769
|
+
const providers = deriveProviders(s.options.models)
|
|
770
|
+
const effortsForModel = s.form.model ? (s.options.efforts[s.form.model] ?? []) : []
|
|
771
|
+
const renderEffortOption = (effort) => el('option', { key: effort.id, value: effort.id }, effortLabel(effort))
|
|
772
|
+
return el('div', { className: 'sap-addCard' },
|
|
773
|
+
el('div', { className: 'sap-editorHeader' },
|
|
774
|
+
el('span', { className: 'sap-editorTitle' }, s.editingId !== null ? '编辑 profile' : '新增 profile')
|
|
775
|
+
),
|
|
776
|
+
el('div', { className: 'sap-field' },
|
|
777
|
+
el('span', { className: 'sap-fieldLabel' }, 'profile id(唯一标识,必填)'),
|
|
778
|
+
el('input', { className: 'sap-input', value: s.form.id, placeholder: '小写字母/数字/连字符,如 my-focus', disabled: s.editingId !== null, onChange: actions.setField('id') })
|
|
779
|
+
),
|
|
780
|
+
el('div', { className: 'sap-field' },
|
|
781
|
+
el('span', { className: 'sap-fieldLabel' }, '描述(一句话定位)'),
|
|
782
|
+
el('input', { className: 'sap-input', value: s.form.description, placeholder: '说明这个 profile 适合什么场景,帮助模型选择', onChange: actions.setField('description') })
|
|
783
|
+
),
|
|
784
|
+
buildSelectField(el, s, actions.setField, '模型(子 agent 使用的模型)', 'model', s.options.models,
|
|
785
|
+
(m) => el('option', { key: m.id, value: m.id }, m.name && m.name !== m.id ? `${m.name}(${m.providerName || m.provider})` : m.id)),
|
|
786
|
+
s.options.models.length === 0 ? el('p', { className: 'sap-metaValueDefault', style: { margin: '0' } }, '未检测到模型目录(headless 部署)——派发成本护栏将按「兼容模式」开关决定放行或拒绝') : null,
|
|
787
|
+
el('details', { className: 'sap-customized' },
|
|
788
|
+
el('summary', { className: 'sap-customizedSummary' }, '高级选项'),
|
|
789
|
+
el('div', { className: 'sap-customizedBody' },
|
|
790
|
+
el('p', { className: 'sap-metaValueDefault', style: { margin: '0' } }, 'continuable 模式忽略 preset 换用与推理强度(子 Agent 继承父预设),派发结果以「ignored」标注'),
|
|
791
|
+
buildSelectField(el, s, actions.setField, '目标预设(切换子 agent 使用的 Agent 预设)', 'preset', s.options.presets,
|
|
792
|
+
(p) => el('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? `${p.name}(${p.id})` : p.id)),
|
|
793
|
+
s.options.presets.length === 0 ? el('p', { className: 'sap-metaValueDefault', style: { margin: '0' } }, '未检测到 system-trust 预设名册(rosterless)——目标预设下拉不可用') : null,
|
|
794
|
+
buildSelectField(el, s, actions.setField, '提供方(模型服务商)', 'provider', providers,
|
|
795
|
+
(p) => el('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? `${p.name}(${p.id})` : p.id)),
|
|
796
|
+
s.form.model === ''
|
|
797
|
+
? el('div', { className: 'sap-field' },
|
|
798
|
+
el('span', { className: 'sap-fieldLabel' }, '推理强度(思考深度)'),
|
|
799
|
+
el('select', { className: 'sap-input sap-select', value: '', disabled: true },
|
|
800
|
+
el('option', { value: '' }, '先选择模型')
|
|
801
|
+
)
|
|
666
802
|
)
|
|
803
|
+
: buildSelectField(el, s, actions.setField, '推理强度(思考深度)', 'reasoningEffort', effortsForModel, renderEffortOption),
|
|
804
|
+
toolSection
|
|
667
805
|
)
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
savedNotice !== '' ? el('p', { className: 'sap-savedNotice', role: 'status' }, savedNotice) : null,
|
|
684
|
-
persistWarning !== '' ? el('p', { className: 'sap-warn', role: 'alert' }, `${persistWarning}${ZH.persistFailHint}`) : null,
|
|
685
|
-
el('ul', { className: 'sap-rows' }, rows),
|
|
686
|
-
el('div', { className: 'sap-addBlock' },
|
|
687
|
-
adding
|
|
688
|
-
? el('div', { className: 'sap-addCard' },
|
|
689
|
-
el('div', { className: 'sap-editorHeader' },
|
|
690
|
-
el('span', { className: 'sap-editorTitle' }, editingId !== null ? '编辑 profile' : '新增 profile')
|
|
691
|
-
),
|
|
692
|
-
el('div', { className: 'sap-field' },
|
|
693
|
-
el('span', { className: 'sap-fieldLabel' }, 'profile id(唯一标识,必填)'),
|
|
694
|
-
el('input', { className: 'sap-input', value: form.id, placeholder: '小写字母/数字/连字符,如 my-focus', disabled: editingId !== null, onChange: setField('id') })
|
|
695
|
-
),
|
|
696
|
-
el('div', { className: 'sap-field' },
|
|
697
|
-
el('span', { className: 'sap-fieldLabel' }, '描述(一句话定位)'),
|
|
698
|
-
el('input', { className: 'sap-input', value: form.description, placeholder: '说明这个 profile 适合什么场景,帮助模型选择', onChange: setField('description') })
|
|
699
|
-
),
|
|
700
|
-
selectField('模型(子 agent 使用的模型)', 'model', options.models,
|
|
701
|
-
(m) => el('option', { key: m.id, value: m.id }, m.name && m.name !== m.id ? `${m.name}(${m.providerName || m.provider})` : m.id)),
|
|
702
|
-
el('details', { className: 'sap-customized' },
|
|
703
|
-
el('summary', { className: 'sap-customizedSummary' }, '高级选项'),
|
|
704
|
-
el('div', { className: 'sap-customizedBody' },
|
|
705
|
-
selectField('目标预设(切换子 agent 使用的 Agent 预设)', 'preset', options.presets,
|
|
706
|
-
(p) => el('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? `${p.name}(${p.id})` : p.id)),
|
|
707
|
-
selectField('提供方(模型服务商)', 'provider', providers,
|
|
708
|
-
(p) => el('option', { key: p.id, value: p.id }, p.name && p.name !== p.id ? `${p.name}(${p.id})` : p.id)),
|
|
709
|
-
form.model === ''
|
|
710
|
-
? el('div', { className: 'sap-field' },
|
|
711
|
-
el('span', { className: 'sap-fieldLabel' }, '推理强度(思考深度)'),
|
|
712
|
-
el('select', { className: 'sap-input sap-select', value: '', disabled: true },
|
|
713
|
-
el('option', { value: '' }, '先选择模型')
|
|
714
|
-
)
|
|
715
|
-
)
|
|
716
|
-
: selectField('推理强度(思考深度)', 'reasoningEffort', effortsForModel, renderEffortOption),
|
|
717
|
-
toolSection
|
|
718
|
-
)
|
|
719
|
-
),
|
|
720
|
-
el('div', { className: 'sap-editorActions' },
|
|
721
|
-
el('button', { className: 'sap-secondaryButton', onClick: closeAdd }, '取消'),
|
|
722
|
-
el('button', { className: 'sap-primaryButton', onClick: submit }, '保存')
|
|
723
|
-
)
|
|
724
|
-
)
|
|
725
|
-
: el('div', { className: 'sap-addActions' },
|
|
726
|
-
el('button', { className: 'sap-addButton', onClick: openAdd }, '+ 新增 profile')
|
|
727
|
-
)
|
|
728
|
-
)
|
|
806
|
+
),
|
|
807
|
+
el('div', { className: 'sap-editorActions' },
|
|
808
|
+
el('button', { className: 'sap-secondaryButton', onClick: actions.closeAdd }, '取消'),
|
|
809
|
+
el('button', { className: 'sap-primaryButton', onClick: actions.submit }, '保存')
|
|
810
|
+
)
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function buildSectionReturn(el, s, actions, rows, addCard) {
|
|
815
|
+
return el('div', { className: 'sap-section' },
|
|
816
|
+
el('div', { className: 'sap-titleRow' },
|
|
817
|
+
el('h2', { className: 'sap-title' }, '子 Agent 方案'),
|
|
818
|
+
el('label', { className: 'sap-switch' },
|
|
819
|
+
el('input', { type: 'checkbox', checked: s.enabled, onChange: actions.toggleEnabled }),
|
|
820
|
+
el('span', null, s.enabled ? '已启用' : '已禁用')
|
|
729
821
|
)
|
|
730
|
-
|
|
822
|
+
),
|
|
823
|
+
s.versionWarnings.length > 0
|
|
824
|
+
? el('div', { className: 'sap-versionWarn' }, s.versionWarnings.map((warning, index) => el('span', { key: index }, warning)))
|
|
825
|
+
: null,
|
|
826
|
+
el('p', { className: 'sap-intro' }, '在此管理「派发子 Agent」工具可用的 profile。内置方案可编辑、可删除、可点下方按钮批量重置回默认;每个方案可单独启用/禁用。'),
|
|
827
|
+
el('div', { className: 'sap-addActions', style: { margin: '4px 0' } },
|
|
828
|
+
el('button', { type: 'button', className: 'sap-secondaryButton', style: { height: '28px', padding: '0 12px', fontSize: '12px', borderRadius: '14px' }, onClick: actions.resetAll }, '重置所有内置方案')
|
|
829
|
+
),
|
|
830
|
+
!s.enabled ? el('p', { className: 'sap-notice' }, '插件已禁用:「派发子 Agent」工具已从模型工具列表中移除,打开开关即可恢复。') : null,
|
|
831
|
+
s.error !== '' ? el('p', { className: 'sap-notice' }, s.error) : null,
|
|
832
|
+
s.savedNotice !== '' ? el('p', { className: 'sap-savedNotice', role: 'status' }, s.savedNotice) : null,
|
|
833
|
+
s.persistWarning !== '' ? el('p', { className: 'sap-warn', role: 'alert' }, `${s.persistWarning}${ZH.persistFailHint}`) : null,
|
|
834
|
+
el('ul', { className: 'sap-rows' }, rows),
|
|
835
|
+
el('div', { className: 'sap-addBlock' },
|
|
836
|
+
s.adding ? addCard : el('div', { className: 'sap-addActions' },
|
|
837
|
+
el('button', { className: 'sap-addButton', onClick: actions.openAdd }, '+ 新增 profile')
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function makeProfilesSection(el, api, chip, useState, useEffect, useCallback) {
|
|
844
|
+
function ProfilesSection() {
|
|
845
|
+
const [profiles, setProfiles] = useState([])
|
|
846
|
+
const [options, setOptions] = useState({ models: [], efforts: {}, presets: [] })
|
|
847
|
+
const [enabled, setEnabled] = useState(true)
|
|
848
|
+
const [adding, setAdding] = useState(false)
|
|
849
|
+
const [editingId, setEditingId] = useState(null)
|
|
850
|
+
const [error, setError] = useState('')
|
|
851
|
+
const [savedNotice, setSavedNotice] = useState('')
|
|
852
|
+
const [persistWarning, setPersistWarning] = useState('')
|
|
853
|
+
const [form, setForm] = useState(EMPTY_FORM)
|
|
854
|
+
const [tools, setTools] = useState([])
|
|
855
|
+
const [versionWarnings, setVersionWarnings] = useState([])
|
|
856
|
+
const s = { profiles, options, enabled, adding, editingId, error, savedNotice, persistWarning, form, tools, versionWarnings,
|
|
857
|
+
setProfiles, setOptions, setEnabled, setAdding, setEditingId, setError, setSavedNotice, setPersistWarning, setForm, setTools, setVersionWarnings }
|
|
858
|
+
const refresh = useCallback(() => {
|
|
859
|
+
api('/list')
|
|
860
|
+
.then((data) => setProfiles(Array.isArray(data.profiles) ? data.profiles : []))
|
|
861
|
+
.catch((err) => setError(String((err && err.message) || err)))
|
|
862
|
+
}, [])
|
|
863
|
+
const loadOptions = useCallback(() => {
|
|
864
|
+
api('/options/summary')
|
|
865
|
+
.then((data) => {
|
|
866
|
+
setEnabled(data.enabled !== false)
|
|
867
|
+
setOptions({
|
|
868
|
+
models: Array.isArray(data.models) ? data.models : [],
|
|
869
|
+
efforts: {},
|
|
870
|
+
presets: Array.isArray(data.presets) ? data.presets : []
|
|
871
|
+
})
|
|
872
|
+
})
|
|
873
|
+
.catch(() => {})
|
|
874
|
+
}, [])
|
|
875
|
+
const loadEfforts = makeEffortsLoader(api, setOptions)
|
|
876
|
+
const loadTools = makeToolsLoader(api, setTools)
|
|
877
|
+
// 版本探测只挂载时拉一次:useCallback([]) 稳定引用,防 effect 依赖恒变
|
|
878
|
+
// 导致设置页打开期间无限重取(与 loadOptions 同款稳定化)。
|
|
879
|
+
const loadVersions = useCallback(() => makeVersionsLoader(api, setVersionWarnings)(), [])
|
|
880
|
+
useEffect(() => {
|
|
881
|
+
refresh()
|
|
882
|
+
loadOptions()
|
|
883
|
+
loadVersions()
|
|
884
|
+
}, [refresh, loadOptions, loadVersions])
|
|
885
|
+
const applyWrite = makeApplyWrite(s)
|
|
886
|
+
const actions = makeProfilesActions(api, s, refresh, applyWrite, loadEfforts, loadTools)
|
|
887
|
+
const rows = buildProfileRows(el, chip, s, actions)
|
|
888
|
+
const toolSection = buildToolSection(el, s, actions)
|
|
889
|
+
return buildSectionReturn(el, s, actions, rows, buildAddCard(el, s, actions, toolSection))
|
|
890
|
+
}
|
|
891
|
+
return ProfilesSection
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// ── plugin 装配 ─────────────────────────────────────────────────────────────
|
|
895
|
+
|
|
896
|
+
window.__ModuleLoader__.load({
|
|
897
|
+
id: 'dsh-subagent-profile',
|
|
898
|
+
factory: (require) => {
|
|
899
|
+
const React = require('react')
|
|
900
|
+
const { useEffect, useState, useCallback } = React
|
|
901
|
+
const el = React.createElement
|
|
902
|
+
|
|
903
|
+
/** Required cordis services: the slot registry only. */
|
|
904
|
+
const inject = ['slots']
|
|
731
905
|
|
|
732
|
-
|
|
906
|
+
const chip = makeChip(el)
|
|
907
|
+
const DispatchToolview = makeDispatchToolview(el, chip)
|
|
908
|
+
const ProfilesSection = makeProfilesSection(el, api, chip, useState, useEffect, useCallback)
|
|
733
909
|
|
|
734
910
|
function apply(ctx) {
|
|
735
911
|
ensureStyles()
|