dsh-paoding 0.3.10 → 0.4.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/README.md +5 -0
- package/README_en.md +5 -0
- package/bin/dsh-paoding.mjs +23 -0
- package/docs/architecture.md +4 -3
- package/docs/architecture_en.md +6 -4
- package/docs/configuration.md +59 -2
- package/docs/configuration_en.md +59 -2
- package/docs/installation.md +14 -2
- package/docs/installation_en.md +14 -2
- package/package.json +1 -1
- package/plugins/paoding-config-ui/api-core.mjs +107 -4
- package/plugins/paoding-config-ui/index.mjs +93 -28
- package/plugins/paoding-config-ui/lib/client.js +941 -237
- package/plugins/paoding-config-ui/package.json +1 -1
- package/tools/lib/cli.mjs +88 -10
- package/tools/lib/compose.mjs +48 -15
- package/tools/lib/doctor.mjs +245 -0
- package/tools/lib/glob.mjs +76 -0
- package/tools/lib/health.mjs +298 -0
|
@@ -83,6 +83,341 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
});
|
|
84
84
|
})();
|
|
85
85
|
|
|
86
|
+
// ── i18n(zh / en 双语)───────────────────────────────────────────────
|
|
87
|
+
//
|
|
88
|
+
// 机制:
|
|
89
|
+
// - 字典 PAODING_I18N_DICT 是「纯数据字面量」——对象里只有字符串值,无
|
|
90
|
+
// 函数、无外部变量引用,便于 tests/i18n.test.mjs 用 node:vm 直接执行本
|
|
91
|
+
// 片段提取字典,做 zh/en key 对账、占位符校验与「文案 key 必须在字典」
|
|
92
|
+
// 的静态扫描。key 按片段/功能分命名空间:dom.*(渲染助手)/ api.* /
|
|
93
|
+
// ver.*(版本更新卡)/ models.* / tpl.*(场景模板)/ ws.*(工作区)/
|
|
94
|
+
// panel.*(配置面板正文)/ role.*(角色卡与委派行标题)/ warn.*(体检
|
|
95
|
+
// 告警卡)/ nav.*(侧栏入口与页头壳)/ menu.*(页头「更多」菜单与升级)/
|
|
96
|
+
// common.*(跨片段复用的短词)。
|
|
97
|
+
// - t(key, params):按当前语言取文案,params 做 {name} 插值(split/join
|
|
98
|
+
// 而非 replace,避免替换串里的 $ 模式被二次解释)。缺 key 原样返回 key
|
|
99
|
+
// 本身——线上宁可露出 key 也不静默吞掉,测试侧保证不会发生。
|
|
100
|
+
// - 语言解析:localStorage "paoding-lang"("zh"/"en")显式优先 →
|
|
101
|
+
// navigator.language 以 zh 开头 → zh,否则 en(首次使用者多半是中文
|
|
102
|
+
// 文档进来的,缺省 en 但 zh 环境自动命中)。
|
|
103
|
+
// - 切语言:setLang(lang) 写 localStorage + 同步广播 onLangChange;
|
|
104
|
+
// 监听方各自刷新(86 PageShell 整页重渲染、82 委派行重建标题),面板
|
|
105
|
+
// 数据重拉复用既有 registerReload 通道,不另造机制。
|
|
106
|
+
// - 取舍:服务端下发的文案(API error message、体检 items 的 message、
|
|
107
|
+
// 升级 note/message)本轮保持原文展示(服务端仅中文),字典只补本地化
|
|
108
|
+
// 的标题/框架文案;warn.title.<code> 按 code 给行级标题,服务端 message
|
|
109
|
+
// 原文作正文。
|
|
110
|
+
|
|
111
|
+
var PAODING_LANG_KEY = "paoding-lang";
|
|
112
|
+
|
|
113
|
+
// 语言探测:localStorage 显式值优先;其余按 navigator.language 首缀猜 zh,
|
|
114
|
+
// 兜底 en。全程 try/catch——非浏览器/受限环境(vm 测试、老内核)不炸,逐层回落。
|
|
115
|
+
function paodingDetectLang() {
|
|
116
|
+
try {
|
|
117
|
+
var saved = window.localStorage.getItem(PAODING_LANG_KEY);
|
|
118
|
+
if (saved === "zh" || saved === "en") return saved;
|
|
119
|
+
} catch (err) { /* localStorage 不可用:继续探测 */ }
|
|
120
|
+
try {
|
|
121
|
+
if (typeof navigator !== "undefined" && navigator.language
|
|
122
|
+
&& String(navigator.language).toLowerCase().indexOf("zh") === 0) return "zh";
|
|
123
|
+
} catch (err) { /* navigator 不可用:落兜底 */ }
|
|
124
|
+
return "en";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var paodingLang = paodingDetectLang();
|
|
128
|
+
|
|
129
|
+
// 当前语言(组件渲染期读;setLang 后经广播触发重渲染再读到新值)。
|
|
130
|
+
function pdLang() { return paodingLang; }
|
|
131
|
+
|
|
132
|
+
// 取文案 + {name} 插值。缺 key 原样返回 key(显式暴露,防静默错文案)。
|
|
133
|
+
function t(key, params) {
|
|
134
|
+
var entry = PAODING_I18N_DICT[key];
|
|
135
|
+
var s = entry ? (entry[paodingLang] != null ? entry[paodingLang] : entry.zh) : key;
|
|
136
|
+
if (params) {
|
|
137
|
+
Object.keys(params).forEach(function (name) {
|
|
138
|
+
s = s.split("{" + name + "}").join(String(params[name]));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return s;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// key 是否在字典(动态拼 key 时防「缺 key 露 key 串」的守卫,如 warn.title.<code>)。
|
|
145
|
+
function tHas(key) {
|
|
146
|
+
return Object.prototype.hasOwnProperty.call(PAODING_I18N_DICT, key);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 语言变更广播:setLang 同步通知;返回反注册函数(组件卸载时退订)。
|
|
150
|
+
var paodingLangListeners = [];
|
|
151
|
+
function onLangChange(fn) {
|
|
152
|
+
paodingLangListeners.push(fn);
|
|
153
|
+
return function () {
|
|
154
|
+
var i = paodingLangListeners.indexOf(fn);
|
|
155
|
+
if (i !== -1) paodingLangListeners.splice(i, 1);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 切语言:只认 zh/en(其余一律归 zh,与探测兜底口径一致);写盘失败只降级
|
|
160
|
+
// 为「本次会话生效」(隐私模式等场景不炸)。
|
|
161
|
+
function setLang(lang) {
|
|
162
|
+
var next = lang === "en" ? "en" : "zh";
|
|
163
|
+
if (next === paodingLang) return;
|
|
164
|
+
paodingLang = next;
|
|
165
|
+
try { window.localStorage.setItem(PAODING_LANG_KEY, next); } catch (err) { /* 写不进就只换内存态 */ }
|
|
166
|
+
paodingLangListeners.slice().forEach(function (fn) {
|
|
167
|
+
try { fn(); } catch (err) { /* 单个监听失败不阻断其余刷新 */ }
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── 字典(纯数据字面量:无函数、无外部引用,测试按整个片段 vm 执行提取)──
|
|
172
|
+
var PAODING_I18N_DICT = {
|
|
173
|
+
// common.*:跨片段复用的短词
|
|
174
|
+
"common.confirm": { zh: "确认", en: "Confirm" },
|
|
175
|
+
"common.cancel": { zh: "取消", en: "Cancel" },
|
|
176
|
+
"common.close": { zh: "关闭", en: "Close" },
|
|
177
|
+
"common.closeToast": { zh: "关闭提示", en: "Dismiss notification" },
|
|
178
|
+
"common.confirmDelete": { zh: "确认删除", en: "Delete" },
|
|
179
|
+
"common.restoreDefault": { zh: "恢复默认", en: "Restore default" },
|
|
180
|
+
"common.listSep": { zh: ";", en: "; " },
|
|
181
|
+
"common.period": { zh: "。", en: "." },
|
|
182
|
+
"common.projectBadge": { zh: "项目", en: "Project" },
|
|
183
|
+
|
|
184
|
+
// dom.*:20 片段渲染助手的定稿文案
|
|
185
|
+
"dom.runtimeFallback": { zh: "运行时识别不可用,已回退文件扫描({err})", en: "Runtime detection unavailable — fell back to file scanning ({err})" },
|
|
186
|
+
"dom.bgContinuableHint": { zh: "可续会话跨轮保留子代理上下文:产出不满可用 send_message 就地续修;无自动清理,落盘与 token 成本随轮次累积,建议仅多轮打磨类角色开启。", en: "Continuable sessions keep the child conversation across turns: when a result falls short, the main agent can refine it in place via send_message. Nothing is cleaned up automatically — disk and token costs accumulate per turn, so enable it only for iterate-and-polish roles." },
|
|
187
|
+
"dom.bgPersistenceWarn": { zh: "未检测到会话持久化后端(sessionPersistence)。可续模式在委派时会直接报错 PERSISTENCE_UNAVAILABLE:需在 ~/.dsh/cordis.patch.yml 挂 @deepseek-ai/dsh-session-persistence-jsonl(host 层、机器级改动,详见 docs/orchestration.md)。", en: "No session persistence backend (sessionPersistence) detected. Delegating to a continuable role fails outright with PERSISTENCE_UNAVAILABLE: mount @deepseek-ai/dsh-session-persistence-jsonl in ~/.dsh/cordis.patch.yml (a host-level, machine-wide change — see docs/orchestration.md)." },
|
|
188
|
+
"dom.forkModelHint": { zh: "继承对话的子代理随主 agent 的当前模型启动,上方专用模型不会生效;应用时会忽略该项模型配置。", en: "A forked child starts with the main agent's current model — the dedicated model above has no effect and is ignored on apply." },
|
|
189
|
+
|
|
190
|
+
// api.*:30 片段请求助手的可读错误
|
|
191
|
+
"api.notJson": { zh: "HTTP {status}(响应不是 JSON)", en: "HTTP {status} (response was not JSON)" },
|
|
192
|
+
"api.timeout": { zh: "请求超时({s}s 无响应)", en: "Request timed out (no response for {s}s)" },
|
|
193
|
+
|
|
194
|
+
// ver.*:40 片段页头版本更新卡
|
|
195
|
+
"ver.checkFailed": { zh: "版本检测失败", en: "Version check failed" },
|
|
196
|
+
"ver.checking": { zh: "检测中…", en: "Checking…" },
|
|
197
|
+
"ver.retry": { zh: "重试", en: "Retry" },
|
|
198
|
+
"ver.newAvailable": { zh: "新版本 {v} 可用 · ", en: "New version {v} available · " },
|
|
199
|
+
"ver.viewUpdate": { zh: "查看更新", en: "View release" },
|
|
200
|
+
"ver.upgrade": { zh: "升级", en: "Upgrade" },
|
|
201
|
+
"ver.upgrading": { zh: "升级中…", en: "Upgrading…" },
|
|
202
|
+
"ver.downloadHint": { zh: "下载安装中,最长约 2 分钟", en: "Downloading and installing — may take up to 2 minutes" },
|
|
203
|
+
|
|
204
|
+
// models.*:50 片段模型选项与 70/72 的目录降级文案
|
|
205
|
+
"models.followRouting": { zh: "(跟随主 agent 路由)", en: "follows main agent routing" },
|
|
206
|
+
"models.current": { zh: "当前配置:{provider} · {model}", en: "Current config: {provider} · {model}" },
|
|
207
|
+
"models.currentNoModel": { zh: "当前配置:{provider} ·(无模型,后端忽略)", en: "Current config: {provider} · (no model — ignored by the backend)" },
|
|
208
|
+
"models.followMain": { zh: "跟随主 agent 当前模型", en: "Follow the main agent's current model" },
|
|
209
|
+
"models.option": { zh: "{model}({provider})", en: "{model} ({provider})" },
|
|
210
|
+
"models.listBadResponse": { zh: "模型目录响应格式异常", en: "Unexpected model catalog response format" },
|
|
211
|
+
|
|
212
|
+
// tpl.*:55 片段场景模板(档位名 + 场景说明;55 的 label/desc 字段存的就是这里的 key)
|
|
213
|
+
"tpl.lite.label": { zh: "轻量档", en: "Lite" },
|
|
214
|
+
"tpl.lite.desc": { zh: "只留实现角色(implement),主 agent 不额外带工具。适合纯写代码。", en: "Keeps only the implement role; the main agent carries no extra tools. For pure coding sessions." },
|
|
215
|
+
"tpl.research.label": { zh: "调研档", en: "Research" },
|
|
216
|
+
"tpl.research.desc": { zh: "只留检索角色(search_external + 深搜),去掉设计与实现。", en: "Keeps only the research roles (search_external + deep search); drops design and implement." },
|
|
217
|
+
"tpl.full.label": { zh: "全开档", en: "Full" },
|
|
218
|
+
"tpl.full.desc": { zh: "四个内置角色全留,主 agent 额外工具勾上检测到的全部库存。", en: "Keeps all four built-in roles; the main agent's extra tools cover the full detected inventory." },
|
|
219
|
+
|
|
220
|
+
// ws.*:60 片段工作区助手 + 72 的标签条 / 添加工作区弹窗
|
|
221
|
+
"ws.tag.project": { zh: "项目", en: "Project" },
|
|
222
|
+
"ws.tag.pending": { zh: "待应用", en: "Pending" },
|
|
223
|
+
"ws.tag.global": { zh: "全局", en: "Global" },
|
|
224
|
+
"ws.sessionCount": { zh: "{n} 会话", en: "{n} sessions" },
|
|
225
|
+
"ws.tip.sessions": { zh: " · {n} 会话", en: " · {n} sessions" },
|
|
226
|
+
"ws.tip.project": { zh: " · 项目配置(预设 {id})", en: " · project config (preset {id})" },
|
|
227
|
+
"ws.tip.pending": { zh: " · 已保存项目配置、预设未生成(点「保存并应用」生效)", en: " · project config saved, preset not generated yet (click Save & Apply to apply it)" },
|
|
228
|
+
"ws.tip.global": { zh: " · 跟随全局配置", en: " · follows the global config" },
|
|
229
|
+
"ws.globalTab": { zh: "全局默认", en: "Global default" },
|
|
230
|
+
"ws.globalTabTip": { zh: "全局默认:写入共享的 orchestrator 预设", en: "Global default: writes the shared orchestrator preset" },
|
|
231
|
+
"ws.more": { zh: "更多", en: "More" },
|
|
232
|
+
"ws.moreTip": { zh: "其余 {n} 个工作区", en: "{n} more workspaces" },
|
|
233
|
+
"ws.addTab": { zh: "添加工作区", en: "Add workspace" },
|
|
234
|
+
"ws.addTabTip": { zh: "注册新的工作区目录", en: "Register a new workspace directory" },
|
|
235
|
+
"ws.tablistLabel": { zh: "配置对象", en: "Config target" },
|
|
236
|
+
"ws.menuLabel": { zh: "更多工作区", en: "More workspaces" },
|
|
237
|
+
"ws.barHint": { zh: "「全局默认」写入共享的 orchestrator 预设;选中某个工作区后,预览 / 应用会生成该工作区专属预设(orchestrator-<目录名>),两者互不影响。", en: "\"Global default\" writes the shared orchestrator preset; with a workspace selected, preview/apply generates that workspace's own preset (orchestrator-<directory name>). The two never interfere." },
|
|
238
|
+
"ws.addTitle": { zh: "添加工作区", en: "Add workspace" },
|
|
239
|
+
"ws.pathLabel": { zh: "工作区目录绝对路径", en: "Absolute path of the workspace directory" },
|
|
240
|
+
"ws.pathPlaceholder": { zh: "输入工作区目录绝对路径,如 /Users/me/code/project-a", en: "Enter an absolute directory path, e.g. /Users/me/code/project-a" },
|
|
241
|
+
"ws.pathHint": { zh: "目录需已存在;已注册的目录重复提交幂等返回。", en: "The directory must already exist; re-submitting a registered path is an idempotent no-op." },
|
|
242
|
+
"ws.addConfirm": { zh: "确认添加", en: "Add" },
|
|
243
|
+
"ws.adding": { zh: "添加中…", en: "Adding…" },
|
|
244
|
+
"ws.addedToast": { zh: "已注册工作区:{path};配置后点「保存并应用」生成专属预设", en: "Workspace registered: {path}. Configure it, then hit Save & Apply to generate its preset" },
|
|
245
|
+
"ws.existsToast": { zh: "工作区已在列表里:{path};配置后点「保存并应用」生成专属预设", en: "Workspace already listed: {path}. Configure it, then hit Save & Apply to generate its preset" },
|
|
246
|
+
"ws.listBadResponse": { zh: "工作区列表响应格式异常", en: "Unexpected workspace list response format" },
|
|
247
|
+
"ws.listFailHint": { zh: "宿主工作区列表拉取失败,仅显示配置文件中的工作区;重启 DSH 后通常可恢复", en: "Failed to fetch the host workspace list — showing only workspaces from the config file; usually recovers after restarting DSH" },
|
|
248
|
+
"ws.listFailDetail": { zh: "({msg})", en: " ({msg})" },
|
|
249
|
+
"ws.notInList": { zh: "工作区 {path} 不在列表里,未切换 tab", en: "Workspace {path} is not in the list — tab not switched" },
|
|
250
|
+
"ws.skillsFail": { zh: "该工作区技能列表拉取失败,技能网格已回退用户级技能", en: "Failed to fetch this workspace's skill list — the skill grid fell back to user-level skills" },
|
|
251
|
+
|
|
252
|
+
// panel.*:70/72 配置面板正文(表单标签 / 占位符 / 提示 / toast / 按钮)
|
|
253
|
+
"panel.loading": { zh: "加载检测状态…", en: "Loading detection state…" },
|
|
254
|
+
"panel.scopeGlobal": { zh: "仅用户级技能", en: "User-level skills only" },
|
|
255
|
+
"panel.scopeWs": { zh: "用户级 + 该工作区项目级技能", en: "User-level + this workspace's project-level skills" },
|
|
256
|
+
"panel.applyBtn": { zh: "保存并应用", en: "Save & Apply" },
|
|
257
|
+
"panel.applying": { zh: "处理中…", en: "Working…" },
|
|
258
|
+
"panel.previewBtn": { zh: "预览生成", en: "Generate preview" },
|
|
259
|
+
"panel.previewing": { zh: "生成中…", en: "Generating…" },
|
|
260
|
+
"panel.rescanBtn": { zh: "重新检测", en: "Rescan" },
|
|
261
|
+
"panel.scanning": { zh: "检测中…", en: "Scanning…" },
|
|
262
|
+
"panel.rolesToolsHeading": { zh: "角色与工具", en: "Roles & tools" },
|
|
263
|
+
"panel.tplHeading": { zh: "场景模板", en: "Scenario templates" },
|
|
264
|
+
"panel.removedHeading": { zh: "已删除的内置角色", en: "Removed built-in roles" },
|
|
265
|
+
"panel.restoreHint": { zh: "恢复后卡片回到上方内置列表;工具面取内置默认(不沿用删前自定义配置)。", en: "Restored cards return to the built-in list above; the toolset is rebuilt from the built-in default (pre-deletion customizations are not kept)." },
|
|
266
|
+
"panel.roleAddBtn": { zh: "新建自定义 agent 角色", en: "New custom role" },
|
|
267
|
+
"panel.skillsHeading": { zh: "技能分配(软约束:写入对应 agent 的 persona 引导)", en: "Skill assignment (soft constraint: written into each agent's persona guidance)" },
|
|
268
|
+
"panel.skillsMain": { zh: "技能分配(read 按需:勾选 = persona 写入技能行,主 agent 用 read 加载正文)", en: "Skill assignment (read on demand: checking writes a skill line into the persona; the main agent loads the body with read)" },
|
|
269
|
+
"panel.skillsInline": { zh: "内联全文(硬生效,每轮固定 persona 开销,适合 caveman 等风格技能)", en: "Inline full text (hard effect, fixed persona cost every turn — for style skills like caveman)" },
|
|
270
|
+
"panel.baseToolsMain": { zh: "基础工具(取消勾选即从主 agent 移除——写入 main_agent_remove)", en: "Base tools (unchecking removes them from the main agent — written to main_agent_remove)" },
|
|
271
|
+
"panel.mcpToolsMain": { zh: "MCP 工具(可加入主 agent)", en: "MCP tools (can join the main agent)" },
|
|
272
|
+
"panel.pluginToolsMain": { zh: "插件工具(可加入主 agent)", en: "Plugin tools (can join the main agent)" },
|
|
273
|
+
"panel.baseTools": { zh: "基础工具", en: "Base tools" },
|
|
274
|
+
"panel.mcpTools": { zh: "MCP 工具", en: "MCP tools" },
|
|
275
|
+
"panel.pluginTools": { zh: "插件工具", en: "Plugin tools" },
|
|
276
|
+
"panel.noMcp": { zh: "未检测到 MCP 工具", en: "No MCP tools detected" },
|
|
277
|
+
"panel.noPlugin": { zh: "未检测到插件工具", en: "No plugin tools detected" },
|
|
278
|
+
"panel.noSkills": { zh: "未检测到技能目录", en: "No skill directories detected" },
|
|
279
|
+
"panel.none": { zh: "无", en: "None" },
|
|
280
|
+
"panel.tagReadOnDemand": { zh: "read按需", en: "read on demand" },
|
|
281
|
+
"panel.tagInline": { zh: "内联全文", en: "Inline" },
|
|
282
|
+
"panel.previewOk": { zh: "✅ 生成成功(未写盘)", en: "✅ Generated (nothing written to disk)" },
|
|
283
|
+
"panel.previewPreset": { zh: " · 预设 {id}", en: " · preset {id}" },
|
|
284
|
+
// 「编排模式 (Orchestrator)」是后端默认 preset 显示名的「值」,不是界面标签——
|
|
285
|
+
// 占位符展示的就是生效值本身,不本地化(英文文档也沿用中文名)。
|
|
286
|
+
"panel.mainNameLabel": { zh: "主 agent 显示名(仅 preset 显示名)", en: "Main agent display name (preset display name only)" },
|
|
287
|
+
"panel.mainNameHint": { zh: "仅替换预设显示名,persona 身份行(You are the …)不动;留空 = 默认「编排模式 (Orchestrator)」(工作区预设用派生名)。改动需重启 GUI / 新会话生效。", en: "Replaces only the preset display name — the persona identity line (You are the …) is untouched; leave empty for the default \"编排模式 (Orchestrator)\" (workspace presets use a derived name). Takes effect after a GUI restart or in new sessions." },
|
|
288
|
+
"panel.mainNameNewline": { zh: "主 agent 显示名(仅 preset 显示名)不能包含换行(\\r / \\n),未保存", en: "Main agent display name (preset display name only) cannot contain line breaks (\\r / \\n) — not saved" },
|
|
289
|
+
"panel.mainNameBlank": { zh: "主 agent 显示名(仅 preset 显示名)不能为纯空白(清空字段或点「恢复默认」= 默认显示名),未保存", en: "Main agent display name (preset display name only) cannot be whitespace-only (clear the field or click Restore default for the default name) — not saved" },
|
|
290
|
+
"panel.mainNameTooLong": { zh: "主 agent 显示名(仅 preset 显示名)过长:trim 后最多 60 字符,未保存", en: "Main agent display name (preset display name only) is too long: at most 60 characters after trim — not saved" },
|
|
291
|
+
"panel.personaToggle": { zh: "persona(可选,覆盖默认角色说明)", en: "Persona (optional, overrides the default role description)" },
|
|
292
|
+
"panel.personaPlaceholder": { zh: "输入该角色的 persona 说明…", en: "Enter this role's persona description…" },
|
|
293
|
+
"panel.personaHint": { zh: "技能引导会自动追加到 persona 末尾;自定义角色必填此说明。", en: "Skill guidance is appended to the end of the persona automatically; custom roles must fill this in." },
|
|
294
|
+
"panel.extraToggle": { zh: "人设追加(主 agent 提示词尾部,可编辑)", en: "Persona addendum (tail of the main agent prompt, editable)" },
|
|
295
|
+
"panel.extraPlaceholder": { zh: "留空并保存 = 不追加任何内容(默认 codegraph 规则将被移除)…", en: "Leave empty and save to append nothing (the default codegraph rules will be removed)…" },
|
|
296
|
+
"panel.extraActionHint": { zh: "保存并应用时按此追加到主 agent persona 末尾(顺序:SRC 本体 → 技能行 → 此处)。改动需重启 GUI / 新会话生效。", en: "Appended to the end of the main agent persona on Save & Apply (order: SRC body → skill lines → this). Takes effect after a GUI restart or in new sessions." },
|
|
297
|
+
"panel.extraClearHint": { zh: "清空保存 = 移除默认 codegraph 规则(配置键 main_agent_persona_extra 记为 '')。", en: "Save while empty to remove the default codegraph rules (config key main_agent_persona_extra is stored as \'\')." },
|
|
298
|
+
"panel.roleNameLabelCustom": { zh: "角色显示名(事件流委派行标题;不动 toolName / persona)", en: "Role display name (title of the delegation row in the event stream; does not touch toolName / persona)" },
|
|
299
|
+
"panel.roleNameLabelBuiltin": { zh: "角色显示名(persona 身份句主语 + 事件流委派行标题;不动工具名/调用名)", en: "Role display name (subject of the persona identity line + delegation row title; does not touch tool names / call names)" },
|
|
300
|
+
"panel.roleNameHintCustom": { zh: "留空 = 事件流显示 toolName({name});保存并应用后生效(仅 GUI 展示,不影响生成产物与调用名)。", en: "Leave empty to show the toolName ({name}) in the event stream; takes effect on Save & Apply (GUI display only — no effect on generated artifacts or call names)." },
|
|
301
|
+
"panel.roleNameHintBuiltin": { zh: "留空 = 默认身份(如 \"You are the {subject} agent.\");已为该角色自定义 persona 时仅作事件流标题。", en: "Leave empty for the default identity (e.g. \"You are the {subject} agent.\"); serves only as the event-stream title when this role has a custom persona." },
|
|
302
|
+
"panel.roleNameNewline": { zh: "角色 \"{name}\" 显示名不能包含换行(\\r / \\n),未保存", en: "Display name of role \"{name}\" cannot contain line breaks (\\r / \\n) — not saved" },
|
|
303
|
+
"panel.roleNameTooLong": { zh: "角色 \"{name}\" 显示名过长:trim 后最多 60 字符,未保存", en: "Display name of role \"{name}\" is too long: at most 60 characters after trim — not saved" },
|
|
304
|
+
"panel.roleModelLabel": { zh: "专用模型(可选;留空 = 跟随主 agent 当前模型)", en: "Dedicated model (optional; leave empty to follow the main agent's current model)" },
|
|
305
|
+
"panel.roleModelNewline": { zh: "角色 \"{name}\" {field}不能包含换行(\\r / \\n),未保存", en: "{field} of role \"{name}\" cannot contain line breaks (\\r / \\n) — not saved" },
|
|
306
|
+
"panel.roleModelTooLong": { zh: "角色 \"{name}\" {field}过长:trim 后最多 120 字符,未保存", en: "{field} of role \"{name}\" is too long: at most 120 characters after trim — not saved" },
|
|
307
|
+
"panel.modelField": { zh: "专用模型", en: "Dedicated model" },
|
|
308
|
+
"panel.providerIgnored": { zh: "角色 \"{name}\":provider 仅在同时配置模型时生效(后端将忽略),已保存", en: "Role \"{name}\": provider takes effect only when a model is also set (ignored by the backend) — saved" },
|
|
309
|
+
"panel.providerPlaceholder": { zh: "provider(留空=跟随主 agent)", en: "provider (empty = follow main agent)" },
|
|
310
|
+
"panel.modelFallbackHint": { zh: "模型目录不可用({err}),已回退手输。写入该角色委派块的 agentOptions;含冷恢复在内,子 agent 固定用该模型。provider 需与模型成对(如 deepseek-official)。", en: "Model catalog unavailable ({err}) — fell back to manual input. Written into the agentOptions of this role's delegation block; the child agent pins this model, cold restore included. provider must pair with a model (e.g. deepseek-official)." },
|
|
311
|
+
"panel.unknownError": { zh: "未知错误", en: "unknown error" },
|
|
312
|
+
"panel.modelSelectHint": { zh: "选项来自 DSH 已注册的模型路由;留空 = 跟随主 agent,主 agent 换模型时未固定的角色跟着变。", en: "Options come from the model routes registered in DSH; leave empty to follow the main agent — roles without a pinned model follow along when the main agent switches models." },
|
|
313
|
+
"panel.bgModeLabel": { zh: "会话模式", en: "Session mode" },
|
|
314
|
+
"panel.bgOneshot": { zh: "一次性(one-shot)", en: "One-shot" },
|
|
315
|
+
"panel.bgContinuable": { zh: "可续(continuable)", en: "Continuable" },
|
|
316
|
+
"panel.startModeLabel": { zh: "启动方式", en: "Start mode" },
|
|
317
|
+
"panel.smFresh": { zh: "全新(fresh)", en: "Fresh" },
|
|
318
|
+
"panel.smFork": { zh: "继承对话(fork)", en: "Inherit conversation (fork)" },
|
|
319
|
+
"panel.forkStartHint": { zh: "继承对话的子代理以主 agent 已完成的对话轮次为初始内容启动(一次性快照,之后两边各自推进);工具面仍以本角色配置为准。", en: "A forked child starts from the main agent's already-completed turns (a one-time snapshot; from then on both sides move on independently); the toolset still follows this role's own configuration." },
|
|
320
|
+
"panel.roleAddTitle": { zh: "新建自定义 agent 角色", en: "New custom role" },
|
|
321
|
+
"panel.roleAddToolNameLabel": { zh: "角色 toolName(主 agent 的委派调用名)", en: "Role toolName (the main agent's delegation call name)" },
|
|
322
|
+
"panel.roleAddNamePlaceholder": { zh: "输入英文小写角色名,如 reviewer", en: "Enter a lowercase role name, e.g. reviewer" },
|
|
323
|
+
"panel.dispNameLabel": { zh: "显示名(可选)", en: "Display name (optional)" },
|
|
324
|
+
"panel.dispNamePlaceholder": { zh: "如 代码评审,留空 = 用 toolName", en: "e.g. Code review — leave empty to use the toolName" },
|
|
325
|
+
"panel.roleAddHint": { zh: "角色名须为 2-32 位小写字母/数字/下划线/连字符,小写字母开头;内置名不可新建。显示名可选,不能换行,trim 后最多 60 字符,留空用 toolName 当标题。新角色工具面默认放开全部检测到的工具,可在卡片里再收窄。会话模式缺省一次性(one-shot),选「可续」后仍可在角色卡里改。启动方式缺省全新(fresh),选「继承对话」后仍可在角色卡里改。", en: "Role names are 2-32 chars of lowercase letters/digits/underscores/hyphens, starting with a letter; built-in names cannot be recreated. Display name is optional: no line breaks, at most 60 chars after trim; leave empty to use the toolName as the title. A new role's toolset opens up every detected tool by default — narrow it down in the role card later. Session mode defaults to one-shot and can still be changed in the role card after picking Continuable. Start mode defaults to fresh and can still be changed in the role card after picking Inherit conversation." },
|
|
326
|
+
"panel.roleAddConfirm": { zh: "确认新建", en: "Create" },
|
|
327
|
+
"panel.roleRenameTitle": { zh: "重命名自定义角色", en: "Rename custom role" },
|
|
328
|
+
"panel.roleRenameLabel": { zh: "新 toolName(主 agent 的委派调用名)", en: "New toolName (the main agent's delegation call name)" },
|
|
329
|
+
"panel.roleRenameHint": { zh: "2-32 位小写字母/数字/下划线/连字符,小写字母开头;不可改成内置名或已存在的名字。skills 里的引用自动迁移;改名后需重启 / 新会话生效。", en: "2-32 chars of lowercase letters/digits/underscores/hyphens, starting with a letter; cannot be a built-in or an existing name. References in skills are migrated automatically; takes effect after a restart / in new sessions." },
|
|
330
|
+
"panel.roleRenameConfirm": { zh: "确认重命名", en: "Rename" },
|
|
331
|
+
"panel.roleNameInvalid": { zh: "角色名须为 2-32 位小写字母/数字/下划线/连字符", en: "Role name must be 2-32 chars of lowercase letters/digits/underscores/hyphens" },
|
|
332
|
+
"panel.roleNameBuiltin": { zh: "内置角色不可新建(\"{name}\"如已删除,请在下方「已删除的内置角色」区恢复)", en: "\"{name}\" is a built-in role and cannot be recreated (if deleted, restore it from the Removed built-in roles section below)" },
|
|
333
|
+
"panel.roleExists": { zh: "角色 \"{name}\" 已存在", en: "Role \"{name}\" already exists" },
|
|
334
|
+
"panel.dispNameNewline": { zh: "显示名不能包含换行(\\r / \\n)", en: "Display name cannot contain line breaks (\\r / \\n)" },
|
|
335
|
+
"panel.dispNameTooLong": { zh: "显示名过长:trim 后最多 60 字符", en: "Display name is too long: at most 60 characters after trim" },
|
|
336
|
+
"panel.roleAddedToast": { zh: "已新建角色 \"{name}\",记得「保存并应用」", en: "Created role \"{name}\" — remember to Save & Apply" },
|
|
337
|
+
"panel.roleRenameBuiltin": { zh: "不可改成内置角色名 \"{name}\"", en: "Cannot rename to the built-in role name \"{name}\"" },
|
|
338
|
+
"panel.roleRenamedToast": { zh: "已重命名 \"{old}\" → \"{new}\",记得「保存并应用」", en: "Renamed \"{old}\" → \"{new}\" — remember to Save & Apply" },
|
|
339
|
+
"panel.delCustomRoleTitle": { zh: "删除自定义角色", en: "Delete custom role" },
|
|
340
|
+
"panel.delCustomRoleBody": { zh: "删除自定义角色 \"{name}\"?", en: "Delete custom role \"{name}\"?" },
|
|
341
|
+
"panel.delBuiltinRoleTitle": { zh: "删除内置角色", en: "Delete built-in role" },
|
|
342
|
+
"panel.delBuiltinRoleBody": { zh: "删除内置角色 \"{name}\"?\n该角色的委派工具、工具面、persona 与主 agent 的委派指引将一并移除;\n可在下方「已删除的内置角色」区一键恢复。", en: "Delete built-in role \"{name}\"?\nIts delegation tool, toolset, persona and the main agent's delegation guidance are removed with it;\nyou can restore it anytime from the Removed built-in roles section below." },
|
|
343
|
+
"panel.applyTitle": { zh: "保存并应用", en: "Save & Apply" },
|
|
344
|
+
"panel.applyBodyWs": { zh: "确认应用当前配置?将为该工作区生成专属预设(orchestrator-<slug>)并保存到 dsh-paoding.config.yml 的 workspaces 段,全局 orchestrator 预设不受影响。", en: "Apply the current configuration? This generates a dedicated preset for this workspace (orchestrator-<slug>) and saves it to the workspaces section of dsh-paoding.config.yml — the global orchestrator preset is untouched." },
|
|
345
|
+
"panel.applyBodyGlobal": { zh: "确认应用当前配置?将写入 preset 并覆盖 ~/.dsh/.agent-presets/orchestrator,且保存到 dsh-paoding.config.yml。", en: "Apply the current configuration? This writes the preset, overwriting ~/.dsh/.agent-presets/orchestrator, and saves it to dsh-paoding.config.yml." },
|
|
346
|
+
"panel.applyConfirm": { zh: "确认应用", en: "Apply" },
|
|
347
|
+
"panel.warnTail": { zh: "有 {n} 条检查提示,见下方。", en: "{n} check messages below." },
|
|
348
|
+
"panel.appliedWs": { zh: "已生成预设 {id}:{summary}新建会话时在预设选择器选它。", en: "Generated preset {id}: {summary} Pick it in the preset selector when starting a new session." },
|
|
349
|
+
"panel.appliedGlobal": { zh: "已安装:{summary}", en: "Installed: {summary}" },
|
|
350
|
+
"panel.staleReapply": { zh: " ⚠️ patch 较新,重新应用后生效。", en: " ⚠️ The patch is newer — apply again to take effect." },
|
|
351
|
+
"panel.staleRestart": { zh: " ⚠️ patch 较新,重启后生效。", en: " ⚠️ The patch is newer — restart to take effect." },
|
|
352
|
+
"panel.effectiveRestart": { zh: " 重启 DSH 或新建会话后生效。", en: " Takes effect after restarting DSH or in a new session." },
|
|
353
|
+
"panel.rescanned": { zh: "已重新检测:{n} 个 MCP / 插件工具 · {m} 个技能", en: "Rescan complete: {n} MCP/plugin tools · {m} skills" },
|
|
354
|
+
"panel.tplAppliedToast": { zh: "已切换到「{label}」,保存并应用后生效", en: "Switched to \"{label}\" — takes effect after Save & Apply" },
|
|
355
|
+
|
|
356
|
+
// role.*:角色卡通用词 + 82 片段委派行标题缺省映射(值 = 字典 key)
|
|
357
|
+
"role.mainAgent": { zh: "主 agent", en: "Main agent" },
|
|
358
|
+
"role.badgeFixed": { zh: "固定", en: "Fixed" },
|
|
359
|
+
"role.badgeCustom": { zh: "自定义", en: "Custom" },
|
|
360
|
+
"role.badgeBuiltin": { zh: "内置", en: "Built-in" },
|
|
361
|
+
"role.toolCount": { zh: "{n} 个工具", en: "{n} tools" },
|
|
362
|
+
"role.renameTitle": { zh: "重命名角色 \"{name}\"(toolName = 委派调用名)", en: "Rename role \"{name}\" (toolName = delegation call name)" },
|
|
363
|
+
"role.delCustomTitle": { zh: "删除角色 \"{name}\"", en: "Delete role \"{name}\"" },
|
|
364
|
+
"role.delBuiltinTitle": { zh: "删除内置角色 \"{name}\"(可在下方恢复)", en: "Delete built-in role \"{name}\" (restorable below)" },
|
|
365
|
+
"role.restoreTitle": { zh: "恢复内置角色 \"{name}\"(工具面重建为内置默认 allow)", en: "Restore built-in role \"{name}\" (toolset rebuilt from the built-in default allow)" },
|
|
366
|
+
"role.restorePill": { zh: "恢复 {name}", en: "Restore {name}" },
|
|
367
|
+
"role.title.search_external": { zh: "外部检索", en: "External research" },
|
|
368
|
+
"role.title.design": { zh: "设计", en: "Design" },
|
|
369
|
+
"role.title.implement": { zh: "实现", en: "Implement" },
|
|
370
|
+
"role.title.search_internal_deep": { zh: "深搜", en: "Deep search" },
|
|
371
|
+
|
|
372
|
+
// warn.*:70 片段体检告警卡。heading 是卡片标题;title.<code> 是按服务端
|
|
373
|
+
// code 给的行级本地化标题(进悬浮提示)。取舍:服务端 message 原文作正文
|
|
374
|
+
// 展示,本轮不译(服务端仅中文)——只把「这是什么类别的检查」本地化。
|
|
375
|
+
"warn.heading.apply": { zh: "应用检查提示", en: "Apply checks" },
|
|
376
|
+
"warn.heading.preview": { zh: "预览检查提示", en: "Preview checks" },
|
|
377
|
+
"warn.itemTitle": { zh: "检查项:{code}", en: "Check: {code}" },
|
|
378
|
+
"warn.title.unmounted_tool": { zh: "已配置但未挂载的工具", en: "Configured but unmounted tool" },
|
|
379
|
+
"warn.title.zero_tool_role": { zh: "零工具角色", en: "Role with no tools" },
|
|
380
|
+
"warn.title.unknown_model": { zh: "未知的专用模型", en: "Unknown dedicated model" },
|
|
381
|
+
"warn.title.preset_drift": { zh: "预设与配置漂移", en: "Preset drift" },
|
|
382
|
+
"warn.title.generation": { zh: "生成期告警", en: "Generation warning" },
|
|
383
|
+
"warn.title.generation_error": { zh: "生成预演失败", en: "Generation dry-run failed" },
|
|
384
|
+
|
|
385
|
+
// nav.*:80 侧栏入口 + 86 页头壳(庖丁为品牌名,英文文档沿用 Paoding Config 译名)
|
|
386
|
+
"nav.entryTitle": { zh: "庖丁配置", en: "Paoding Config" },
|
|
387
|
+
"nav.openToWs": { zh: "打开庖丁配置并切到该工作区", en: "Open Paoding Config for this workspace" },
|
|
388
|
+
"nav.closeAria": { zh: "关闭庖丁配置", en: "Close Paoding Config" },
|
|
389
|
+
"nav.closeTip": { zh: "关闭(Esc)", en: "Close (Esc)" },
|
|
390
|
+
|
|
391
|
+
// menu.*:86 页头「更多」菜单 / 导入导出 / 升级流程(服务端 r.message 原文
|
|
392
|
+
// 优先展示,本地化串只作缺省兜底——同 warn 的取舍)。
|
|
393
|
+
"menu.subtitle": { zh: "为主 agent 与子 agent 编排工具、技能与角色,预览并应用生成的 agent.cordis.yml。", en: "Orchestrate tools, skills and roles for the main agent and its sub-agents; preview and apply the generated agent.cordis.yml." },
|
|
394
|
+
"menu.moreBtn": { zh: "更多", en: "More" },
|
|
395
|
+
"menu.moreBtnTip": { zh: "配置文件导出 / 导入", en: "Export / import the config file" },
|
|
396
|
+
"menu.menuLabel": { zh: "配置文件操作", en: "Config file actions" },
|
|
397
|
+
"menu.exportItem": { zh: "导出配置", en: "Export config" },
|
|
398
|
+
"menu.exporting": { zh: "导出中…", en: "Exporting…" },
|
|
399
|
+
"menu.exported": { zh: "已导出配置文件 {name}", en: "Exported config file {name}" },
|
|
400
|
+
"menu.exportFail": { zh: "导出失败:{msg}", en: "Export failed: {msg}" },
|
|
401
|
+
"menu.importItem": { zh: "导入配置", en: "Import config" },
|
|
402
|
+
"menu.importing": { zh: "导入中…", en: "Importing…" },
|
|
403
|
+
"menu.importTitle": { zh: "导入配置", en: "Import config" },
|
|
404
|
+
"menu.importBody": { zh: "将用文件「{name}」整体覆盖当前配置文件:\n角色、工具、技能分配与 workspaces(工作区)段全部以文件内容为准,现有配置被替换,此操作不可撤销。", en: "The file \"{name}\" will overwrite the whole config file:\nroles, tools, skill assignment and the workspaces section are all replaced with the file's content. The existing configuration is replaced — this cannot be undone." },
|
|
405
|
+
"menu.importConfirm": { zh: "覆盖导入", en: "Overwrite & import" },
|
|
406
|
+
"menu.imported": { zh: "配置已导入并覆盖原文件,视图已按新配置刷新", en: "Config imported and the original file overwritten — the view has been refreshed" },
|
|
407
|
+
"menu.importFail": { zh: "导入失败:{msg}", en: "Import failed: {msg}" },
|
|
408
|
+
"menu.readFail": { zh: "文件读取失败", en: "Failed to read file" },
|
|
409
|
+
"menu.readFailPrefix": { zh: "读取文件失败:{msg}", en: "Failed to read the file: {msg}" },
|
|
410
|
+
"menu.wsNotRegistered": { zh: "宿主工作区「{title}」还没在庖丁注册,已打开全局默认", en: "Host workspace \"{title}\" is not registered in Paoding yet — opened the global default" },
|
|
411
|
+
"menu.listFail": { zh: "工作区列表拉取失败,已打开全局默认", en: "Failed to fetch the workspace list — opened the global default" },
|
|
412
|
+
"menu.upgradeTitle": { zh: "升级 dsh-paoding", en: "Upgrade dsh-paoding" },
|
|
413
|
+
"menu.upgradeConfirm": { zh: "升级", en: "Upgrade" },
|
|
414
|
+
"menu.upgradeDevBody": { zh: "当前为开发 link 直连形态:无法就地升级,请手动执行 dsh plugin add dsh-paoding@latest 切换到 registry 版本。仍要继续尝试吗?", en: "This is a dev link checkout: it cannot be upgraded in place. Run dsh plugin add dsh-paoding@latest manually to switch to the registry version. Continue anyway?" },
|
|
415
|
+
"menu.upgradeBody": { zh: "确定升级到 {v}?升级将通过插件通道原地换版本,完成后需重启 DSH 生效。", en: "Upgrade to {v}? The upgrade swaps the version in place through the plugin channel; restart DSH afterwards to apply it." },
|
|
416
|
+
"menu.upToDate": { zh: "已是最新版本", en: "Already up to date" },
|
|
417
|
+
"menu.upgraded": { zh: "✅ 已升级到 {v},重启 DSH 后新版本生效(preset 会自动按新版重生成)", en: "✅ Upgraded to {v} — restart DSH to apply the new version (the preset regenerates itself against it)" },
|
|
418
|
+
"menu.errTail": { zh: ":{tail}", en: ": {tail}" },
|
|
419
|
+
"menu.langTip": { zh: "切换界面语言", en: "Switch UI language" },
|
|
420
|
+
};
|
|
86
421
|
// ── 样式注入(幂等)─────────────────────────────────────────────────
|
|
87
422
|
//
|
|
88
423
|
// 样式正文在 lib/base.css,由 Node 半经 GET /api/paoding/client.css 下发
|
|
@@ -185,16 +520,12 @@ window.__ModuleLoader__.load({
|
|
|
185
520
|
return false;
|
|
186
521
|
}
|
|
187
522
|
|
|
188
|
-
// 「可续」选中时的行内成本提示(定稿文案)。
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"无自动清理,落盘与 token 成本随轮次累积,建议仅多轮打磨类角色开启。";
|
|
523
|
+
// 「可续」选中时的行内成本提示(定稿文案)。i18n:常量会在模块加载期固化
|
|
524
|
+
// 成单语字符串,切语言后就错了——包装成函数,渲染期才经 t() 取当前语言。
|
|
525
|
+
function roleBgContinuableHint() { return t("dom.bgContinuableHint"); }
|
|
192
526
|
|
|
193
527
|
// persistence 后端缺失时的警告(定稿文案;只警示不拦截,预览 / 应用照常)。
|
|
194
|
-
|
|
195
|
-
"未检测到会话持久化后端(sessionPersistence)。可续模式在委派时会直接报错 " +
|
|
196
|
-
"PERSISTENCE_UNAVAILABLE:需在 ~/.dsh/cordis.patch.yml 挂 " +
|
|
197
|
-
"@deepseek-ai/dsh-session-persistence-jsonl(host 层、机器级改动,详见 docs/orchestration.md)。";
|
|
528
|
+
function roleBgPersistenceWarn() { return t("dom.bgPersistenceWarn"); }
|
|
198
529
|
|
|
199
530
|
// ── 启动方式(roles.<toolName>.start_mode)─────────────────────────────
|
|
200
531
|
// 定稿键名 'fresh' | 'fork',缺省 fresh。与后端序列化形状一致:config 层
|
|
@@ -212,10 +543,8 @@ window.__ModuleLoader__.load({
|
|
|
212
543
|
}
|
|
213
544
|
|
|
214
545
|
// 「继承对话(fork)」选中且该角色配了专用模型时的行内提示(定稿文案):
|
|
215
|
-
// fork 子代理继承主 agent
|
|
216
|
-
|
|
217
|
-
"继承对话的子代理随主 agent 的当前模型启动,上方专用模型不会生效;" +
|
|
218
|
-
"应用时会忽略该项模型配置。";
|
|
546
|
+
// fork 子代理继承主 agent 的提供方与模型,专用模型不生效。同为渲染期取文案。
|
|
547
|
+
function roleForkModelHint() { return t("dom.forkModelHint"); }
|
|
219
548
|
|
|
220
549
|
|
|
221
550
|
// mcp__ 前缀判定(归「MCP 工具」组的依据,供角色工具网格分组与
|
|
@@ -247,7 +576,7 @@ window.__ModuleLoader__.load({
|
|
|
247
576
|
var err = String(d.runtimeError);
|
|
248
577
|
if (err.length > 200) err = err.slice(0, 200) + "…";
|
|
249
578
|
return h("div", { className: "omd-runtimeNote" },
|
|
250
|
-
"
|
|
579
|
+
t("dom.runtimeFallback", { err: err }));
|
|
251
580
|
}
|
|
252
581
|
|
|
253
582
|
// 版本号展示归一:tag 缺前缀 v 时补上(服务端原样透传 GitHub tag_name,
|
|
@@ -295,13 +624,17 @@ window.__ModuleLoader__.load({
|
|
|
295
624
|
if (!r.ok) {
|
|
296
625
|
var err = new Error((d && d.error) || ("HTTP " + r.status));
|
|
297
626
|
if (d && typeof d.output === "string" && d.output) err.output = d.output;
|
|
627
|
+
// 结构化体检结果(preview/apply 的 422 会把 warnings 一并带回,如
|
|
628
|
+
// 零工具角色的可读说明):message 装不下多条,挂 error.warnings,
|
|
629
|
+
// 调用方 catch 侧渲染成检查提示卡(见 ConfigPanel 的 warnState)。
|
|
630
|
+
if (d && Array.isArray(d.warnings)) err.warnings = d.warnings;
|
|
298
631
|
throw err;
|
|
299
632
|
}
|
|
300
|
-
if (!d) throw new Error("
|
|
633
|
+
if (!d) throw new Error(t("api.notJson", { status: r.status }));
|
|
301
634
|
return d;
|
|
302
635
|
});
|
|
303
636
|
}).catch(function (err) {
|
|
304
|
-
if (ctrl && err && err.name === "AbortError") throw new Error("
|
|
637
|
+
if (ctrl && err && err.name === "AbortError") throw new Error(t("api.timeout", { s: PAODING_API_TIMEOUT_MS / 1000 }));
|
|
305
638
|
throw err;
|
|
306
639
|
}).finally(function () {
|
|
307
640
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -362,14 +695,14 @@ window.__ModuleLoader__.load({
|
|
|
362
695
|
var retryBusy = !!(opts && opts.retryBusy);
|
|
363
696
|
return h("span", { className: "omd-versionRow omd-versionFail", title: v.error },
|
|
364
697
|
h("span", { className: "omd-versionIcon" }, "⚠"),
|
|
365
|
-
h("span", { className: "omd-versionText" }, "
|
|
698
|
+
h("span", { className: "omd-versionText" }, t("ver.checkFailed")),
|
|
366
699
|
typeof (opts && opts.onRetry) === "function"
|
|
367
700
|
? h("button", {
|
|
368
701
|
type: "button",
|
|
369
702
|
className: "omd-retryBtn",
|
|
370
703
|
disabled: retryBusy,
|
|
371
704
|
onClick: opts.onRetry,
|
|
372
|
-
}, retryBusy ? "
|
|
705
|
+
}, retryBusy ? t("ver.checking") : t("ver.retry"))
|
|
373
706
|
: null);
|
|
374
707
|
}
|
|
375
708
|
var busy = !!opts && !!opts.busy;
|
|
@@ -378,7 +711,7 @@ window.__ModuleLoader__.load({
|
|
|
378
711
|
return h("div", { className: "omd-versionRow omd-versionNew" },
|
|
379
712
|
h("span", { className: "omd-versionIcon" }, "🆙"),
|
|
380
713
|
h("span", { className: "omd-versionText" },
|
|
381
|
-
"
|
|
714
|
+
t("ver.newAvailable", { v: versionTagOf(v.latest) }),
|
|
382
715
|
// 只信 https://github.com/ 的 release 链接;其余取值一律不渲染 <a>。
|
|
383
716
|
v.releaseUrl && /^https:\/\/github\.com\//.test(v.releaseUrl)
|
|
384
717
|
? h("a", {
|
|
@@ -386,7 +719,7 @@ window.__ModuleLoader__.load({
|
|
|
386
719
|
href: v.releaseUrl,
|
|
387
720
|
target: "_blank",
|
|
388
721
|
rel: "noreferrer",
|
|
389
|
-
}, "
|
|
722
|
+
}, t("ver.viewUpdate"))
|
|
390
723
|
: null),
|
|
391
724
|
typeof (opts && opts.onUpgrade) === "function"
|
|
392
725
|
? h("button", {
|
|
@@ -394,9 +727,9 @@ window.__ModuleLoader__.load({
|
|
|
394
727
|
className: "omd-upgradeBtn",
|
|
395
728
|
disabled: busy,
|
|
396
729
|
onClick: opts.onUpgrade,
|
|
397
|
-
}, busy ? "
|
|
730
|
+
}, busy ? t("ver.upgrading") : t("ver.upgrade"))
|
|
398
731
|
: null,
|
|
399
|
-
busy ? h("span", { className: "omd-upgradeHint" }, "
|
|
732
|
+
busy ? h("span", { className: "omd-upgradeHint" }, t("ver.downloadHint")) : null,
|
|
400
733
|
error
|
|
401
734
|
? h("span", { className: "omd-versionNote omd-versionNoteErr" }, error)
|
|
402
735
|
: (note ? h("span", { className: "omd-versionNote" }, note) : null));
|
|
@@ -424,7 +757,7 @@ window.__ModuleLoader__.load({
|
|
|
424
757
|
h("button", {
|
|
425
758
|
type: "button",
|
|
426
759
|
className: "pd-toastClose",
|
|
427
|
-
"aria-label": "
|
|
760
|
+
"aria-label": t("common.closeToast"),
|
|
428
761
|
onClick: function () { toast.dismiss(it.id); },
|
|
429
762
|
}, h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×")));
|
|
430
763
|
}));
|
|
@@ -491,16 +824,73 @@ window.__ModuleLoader__.load({
|
|
|
491
824
|
}
|
|
492
825
|
}
|
|
493
826
|
var rtValue = encodeModelOption(provider || null, model || null);
|
|
494
|
-
// 文案照实:模型缺失(只配 provider
|
|
827
|
+
// 文案照实:模型缺失(只配 provider,后端会忽略)时按约定不带空格接全角括号。
|
|
828
|
+
// i18n:provider 缺省文案也随语言走,占位符拼进完整句子。
|
|
829
|
+
var providerLabel = provider || t("models.followRouting");
|
|
495
830
|
var label = model
|
|
496
|
-
? "
|
|
497
|
-
: "
|
|
831
|
+
? t("models.current", { provider: providerLabel, model: model })
|
|
832
|
+
: t("models.currentNoModel", { provider: providerLabel });
|
|
498
833
|
return {
|
|
499
834
|
value: rtValue,
|
|
500
835
|
extra: { value: rtValue, label: label },
|
|
501
836
|
};
|
|
502
837
|
}
|
|
503
838
|
|
|
839
|
+
// ── 场景模板(三档快捷预设)────────────────────────────────────────────────
|
|
840
|
+
// 纯数据 + 纯函数,与面板解耦(便于 i18n / 单测)。label / desc 存字典 key
|
|
841
|
+
// (tpl.*,见 04-i18n 字典),渲染侧经 t() 取当前语言文案——数据本身保持
|
|
842
|
+
// 无语文。模板只动两个键:
|
|
843
|
+
// - roles_remove:删哪些内置角色;
|
|
844
|
+
// - main_agent_extra:主 agent 在基础白名单之外额外带哪些工具。
|
|
845
|
+
// 工具面(roles.*.tools / main_agent_remove)、自定义角色、显示名、persona、
|
|
846
|
+
// 角色专用模型、会话/启动方式、skills 一概不碰——「换班底」的语义收窄到
|
|
847
|
+
// 「谁在场 + 主 agent 额外带什么」,切档永远不会悄悄改掉用户精调过的配置。
|
|
848
|
+
//
|
|
849
|
+
// 注意 search_internal_deep 是静态委派工具(不在配置层 ROLES 里,写进
|
|
850
|
+
// roles_remove 会被后端 warn 并忽略,见 docs/configuration.md §2.2),三档
|
|
851
|
+
// 都不写它;调研档「留深搜」正是靠它兜底,无需任何配置动作。
|
|
852
|
+
var PAODING_SCENARIO_TEMPLATES = [
|
|
853
|
+
{
|
|
854
|
+
id: "lite",
|
|
855
|
+
label: "tpl.lite.label",
|
|
856
|
+
desc: "tpl.lite.desc",
|
|
857
|
+
rolesRemove: ["search_external", "design"],
|
|
858
|
+
extraAll: false,
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
id: "research",
|
|
862
|
+
label: "tpl.research.label",
|
|
863
|
+
desc: "tpl.research.desc",
|
|
864
|
+
rolesRemove: ["design", "implement"],
|
|
865
|
+
extraAll: false,
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
id: "full",
|
|
869
|
+
label: "tpl.full.label",
|
|
870
|
+
desc: "tpl.full.desc",
|
|
871
|
+
rolesRemove: [],
|
|
872
|
+
extraAll: true,
|
|
873
|
+
},
|
|
874
|
+
];
|
|
875
|
+
|
|
876
|
+
// 应用模板:深拷贝 assign 后只改 roles_remove 与 main_agent_extra,返回新
|
|
877
|
+
// 对象(与面板其他 setter 的 JSON 克隆口径一致——新引用才会触发「assign 一变
|
|
878
|
+
// 预览作废」等既有失效逻辑)。roles_remove 去重保序,只收内置角色名(与后端
|
|
879
|
+
// parseRolesRemove 口径一致);全开档 main_agent_extra = 检测库存全量副本,
|
|
880
|
+
// 其余档位清空(档位 = 确定态而非「在现状上增减」,重复点击同档幂等)。
|
|
881
|
+
// 不删 roles 里被删角色的条目:键保留,卡片显隐由渲染层按 roles_remove 过滤
|
|
882
|
+
// ——这样「轻量/调研 → 全开」切回来时,此前的 persona / 模型 / 工具面原样
|
|
883
|
+
// 复原,正是「模板不碰这些字段」要保的效果。
|
|
884
|
+
function applyScenarioTemplate(assign, tpl, inventory) {
|
|
885
|
+
var next = JSON.parse(JSON.stringify(assign));
|
|
886
|
+
var remove = [];
|
|
887
|
+
(tpl.rolesRemove || []).forEach(function (n) {
|
|
888
|
+
if (remove.indexOf(n) === -1) remove.push(n);
|
|
889
|
+
});
|
|
890
|
+
next.roles_remove = remove;
|
|
891
|
+
next.main_agent_extra = tpl.extraAll ? (inventory || []).slice() : [];
|
|
892
|
+
return next;
|
|
893
|
+
}
|
|
504
894
|
// ── 工作区选择(配置对象)助手 ───────────────────────────────────────
|
|
505
895
|
|
|
506
896
|
// 剥掉两个非目标字段容器键后的全局配置副本:existing 里现在还带 workspaces /
|
|
@@ -563,9 +953,9 @@ window.__ModuleLoader__.load({
|
|
|
563
953
|
return "global";
|
|
564
954
|
}
|
|
565
955
|
|
|
566
|
-
// 三态 →
|
|
956
|
+
// 三态 → 标签文案(i18n:tab 小徽标与「更多」菜单共用,与 tooltip 口径一致)
|
|
567
957
|
function wsTagTextOf(kind) {
|
|
568
|
-
return kind === "project" ? "
|
|
958
|
+
return kind === "project" ? t("ws.tag.project") : kind === "pending" ? t("ws.tag.pending") : t("ws.tag.global");
|
|
569
959
|
}
|
|
570
960
|
|
|
571
961
|
// 角色卡「默认展开」判定单一来源(toggleRole / roleCard 共用):主 agent
|
|
@@ -580,12 +970,12 @@ window.__ModuleLoader__.load({
|
|
|
580
970
|
var m = meta && meta[row.path];
|
|
581
971
|
var kind = wsTagOf(existingWs, meta, row.path);
|
|
582
972
|
return row.path +
|
|
583
|
-
(row.sessionCount != null ? "
|
|
973
|
+
(row.sessionCount != null ? t("ws.tip.sessions", { n: row.sessionCount }) : "") +
|
|
584
974
|
(kind === "project"
|
|
585
|
-
? "
|
|
975
|
+
? t("ws.tip.project", { id: m && m.presetId })
|
|
586
976
|
: kind === "pending"
|
|
587
|
-
? "
|
|
588
|
-
: "
|
|
977
|
+
? t("ws.tip.pending")
|
|
978
|
+
: t("ws.tip.global"));
|
|
589
979
|
}
|
|
590
980
|
|
|
591
981
|
// 工作区 tab 的显示顺序:按会话数降序(缺失沉底、同分保持注册表顺序),
|
|
@@ -657,8 +1047,8 @@ window.__ModuleLoader__.load({
|
|
|
657
1047
|
req: {
|
|
658
1048
|
title: opts.title,
|
|
659
1049
|
body: opts.body,
|
|
660
|
-
confirmText: opts.confirmText || "
|
|
661
|
-
cancelText: opts.cancelText || "
|
|
1050
|
+
confirmText: opts.confirmText || t("common.confirm"),
|
|
1051
|
+
cancelText: opts.cancelText || t("common.cancel"),
|
|
662
1052
|
danger: !!opts.danger,
|
|
663
1053
|
onConfirm: opts.onConfirm,
|
|
664
1054
|
},
|
|
@@ -687,7 +1077,7 @@ window.__ModuleLoader__.load({
|
|
|
687
1077
|
"aria-label": req.title, "aria-describedby": "pd-confirmBody" },
|
|
688
1078
|
h("div", { className: "pd-modalHeader" },
|
|
689
1079
|
h("h2", { className: "pd-modalTitle" }, req.title),
|
|
690
|
-
h("button", { type: "button", className: "pd-modalClose", "aria-label": "
|
|
1080
|
+
h("button", { type: "button", className: "pd-modalClose", "aria-label": t("common.close"), onClick: close },
|
|
691
1081
|
h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
692
1082
|
h("div", { className: "pd-modalBody" },
|
|
693
1083
|
h("div", { className: "pd-modalText", id: "pd-confirmBody" }, req.body)),
|
|
@@ -696,7 +1086,7 @@ window.__ModuleLoader__.load({
|
|
|
696
1086
|
// 取消,与原生 confirm 的键盘习惯对齐;danger 反向落焦是让破坏性
|
|
697
1087
|
// 操作多一道显式点击的门槛。弹窗关闭即整体卸载,重开重新自动聚焦。
|
|
698
1088
|
h(P.Button, { variant: "outline", size: "md", autoFocus: !!req.danger, onClick: close },
|
|
699
|
-
req.cancelText || "
|
|
1089
|
+
req.cancelText || t("common.cancel")),
|
|
700
1090
|
h(P.Button, {
|
|
701
1091
|
variant: "primary",
|
|
702
1092
|
size: "md",
|
|
@@ -708,7 +1098,40 @@ window.__ModuleLoader__.load({
|
|
|
708
1098
|
close();
|
|
709
1099
|
if (typeof req.onConfirm === "function") req.onConfirm();
|
|
710
1100
|
},
|
|
711
|
-
}, req.confirmText || "
|
|
1101
|
+
}, req.confirmText || t("common.confirm")))));
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// ── 检查提示卡(结构化体检结果的渲染)──────────────────────────────────
|
|
1105
|
+
// warnState = { source: "preview" | "apply", items: [{level, code, message}] }
|
|
1106
|
+
// (items 即 tools/lib/health.mjs healthCheck 的 items 原样)。无 items 不渲染
|
|
1107
|
+
// (零占位);逐条一行,按 level 分色(error 红 / warn 琥珀 / info 中性,复用
|
|
1108
|
+
// statusRow 同款 omd-status* 类),code 收进悬浮提示(面向排障,不占版面)。
|
|
1109
|
+
// 行不放 role=alert:卡片随响应常驻展示而非突发警报,读屏按普通内容播报即可。
|
|
1110
|
+
function warningsCardOf(warnState) {
|
|
1111
|
+
if (!warnState || !Array.isArray(warnState.items) || warnState.items.length === 0) return null;
|
|
1112
|
+
var rows = warnState.items.map(function (w, i) {
|
|
1113
|
+
var level = w && (w.level === "error" || w.level === "warn") ? w.level : "info";
|
|
1114
|
+
var cls = level === "error" ? "omd-statusError" : level === "warn" ? "omd-statusWarn" : "omd-status";
|
|
1115
|
+
var icon = level === "error" ? "⛔" : level === "warn" ? "⚠️" : "ℹ️";
|
|
1116
|
+
// 悬浮提示:已知 code(tools/lib/health.mjs 的固定集)给本地化标题
|
|
1117
|
+
// (warn.title.<code>),未知 code 沿用「检查项:<code>」;服务端 message
|
|
1118
|
+
// 原文作正文(服务端体检文案仅中文,本轮不译——取舍见 04 字典注释)。
|
|
1119
|
+
var title = undefined;
|
|
1120
|
+
if (w && w.code) {
|
|
1121
|
+
title = tHas("warn.title." + w.code)
|
|
1122
|
+
? t("warn.title." + w.code) + "(" + w.code + ")"
|
|
1123
|
+
: t("warn.itemTitle", { code: w.code });
|
|
1124
|
+
}
|
|
1125
|
+
return h("div", { className: cls, key: i, title: title },
|
|
1126
|
+
h("span", { className: "omd-statusIcon" }, icon),
|
|
1127
|
+
h("span", { className: "omd-statusText" }, (w && w.message) || ""));
|
|
1128
|
+
});
|
|
1129
|
+
return h("div", { className: "omd-group" },
|
|
1130
|
+
h("div", { className: "omd-groupHeading" },
|
|
1131
|
+
h("span", { className: "omd-groupIcon" }, ic(P.IconWarningOutline16, 14)),
|
|
1132
|
+
h("span", null, warnState.source === "apply" ? t("warn.heading.apply") : t("warn.heading.preview")),
|
|
1133
|
+
h("span", { className: "omd-count" }, warnState.items.length)),
|
|
1134
|
+
h("div", { className: "omd-section" }, rows));
|
|
712
1135
|
}
|
|
713
1136
|
|
|
714
1137
|
// ── 主面板 ─────────────────────────────────────────────────────────────
|
|
@@ -720,6 +1143,10 @@ window.__ModuleLoader__.load({
|
|
|
720
1143
|
var _b = useState(false), busy = _b[0], setBusy = _b[1];
|
|
721
1144
|
var _op = useState(null), busyOp = _op[0], setBusyOp = _op[1];
|
|
722
1145
|
var _p = useState(null), preview = _p[0], setPreview = _p[1];
|
|
1146
|
+
// 检查提示卡状态(形状见 warningsCardOf):preview 源随 assign 变更作废
|
|
1147
|
+
//(与预览同一失效逻辑),apply 源描述的是「已落盘结果」——应用成功后
|
|
1148
|
+
// 服务端回拉 state 重建 assign 不算编辑,不作废,直到下一次预览/应用。
|
|
1149
|
+
var _wn = useState(null), warnState = _wn[0], setWarnState = _wn[1];
|
|
723
1150
|
var _o = useState({}), openMap = _o[0], setOpenMap = _o[1];
|
|
724
1151
|
var _m = useState({}), personaMap = _m[0], setPersonaMap = _m[1];
|
|
725
1152
|
var _x = useState(false), extraOpen = _x[0], setExtraOpen = _x[1];
|
|
@@ -734,6 +1161,10 @@ window.__ModuleLoader__.load({
|
|
|
734
1161
|
// 状态挂在页头 PageShell(版本号展示在标题旁),经 props 下发:
|
|
735
1162
|
// setVerInfo 供 loadVersion 回写页头(版本号与更新卡都在 PageShell)。
|
|
736
1163
|
var setVerInfo = props && props.setVerInfo;
|
|
1164
|
+
// 导入配置后的整体刷新入口:ConfigPanel 把 load 注册给页头(PageShell 的
|
|
1165
|
+
// 「更多 → 导入配置」成功后调用)。面板与页头分属两棵子树,props 只能
|
|
1166
|
+
// 单向传「注册函数」,刷新动作反过来经 ref 回调触达。
|
|
1167
|
+
var registerReload = props && props.registerReload;
|
|
737
1168
|
var _ws = useState(null), activeWs = _ws[0], setActiveWs = _ws[1]; // 当前配置对象:null = 全局默认;否则工作区绝对路径
|
|
738
1169
|
// 视角切换计数:switchWorkspace 每次自增,拼进全部未受控(defaultValue)
|
|
739
1170
|
// 编辑器的 key——否则切 tab 不重挂,旧工作区输入框里未提交的文本会在
|
|
@@ -882,6 +1313,19 @@ window.__ModuleLoader__.load({
|
|
|
882
1313
|
|
|
883
1314
|
useEffect(load, [load]);
|
|
884
1315
|
|
|
1316
|
+
// 把「整体刷新」注册给页头(「导入配置」成功后由 PageShell 调用):load
|
|
1317
|
+
// 重拉 state + 按新配置重建 assign;导入 = 整份文件被替换,两源检查提示
|
|
1318
|
+
// 一并作废(旧卡描述的已落盘结果已不在盘上,留着只会误导)。load 是
|
|
1319
|
+
// useCallback 稳定引用;卸载时反注册,防页头持有失效闭包。
|
|
1320
|
+
useEffect(function () {
|
|
1321
|
+
if (typeof registerReload !== "function") return;
|
|
1322
|
+
registerReload(function () {
|
|
1323
|
+
setWarnState(null);
|
|
1324
|
+
load();
|
|
1325
|
+
});
|
|
1326
|
+
return function () { registerReload(null); };
|
|
1327
|
+
}, [load, registerReload]);
|
|
1328
|
+
|
|
885
1329
|
// 工作区列表:面板加载时拉一次宿主注册表(与 state 并行)。失败降级为
|
|
886
1330
|
// 空列表——workspaceRowsOf 会用 state.existing.workspaces 的键兜底出 pill,
|
|
887
1331
|
// 绝不阻塞面板。降级不再无声:宿主侧故障(如插件实例已 disposed 的僵尸
|
|
@@ -890,7 +1334,7 @@ window.__ModuleLoader__.load({
|
|
|
890
1334
|
// 日志定位。请求错误 / 响应格式异常都汇入同一个 catch,提示口径一致。
|
|
891
1335
|
var loadWorkspaces = useCallback(function () {
|
|
892
1336
|
api("workspaces").then(function (d) {
|
|
893
|
-
if (!d || d.ok !== true || !Array.isArray(d.items)) throw new Error("
|
|
1337
|
+
if (!d || d.ok !== true || !Array.isArray(d.items)) throw new Error(t("ws.listBadResponse"));
|
|
894
1338
|
setWsList(d.items);
|
|
895
1339
|
setWsListTried(true);
|
|
896
1340
|
setWsListErr("");
|
|
@@ -898,8 +1342,8 @@ window.__ModuleLoader__.load({
|
|
|
898
1342
|
setWsList([]); // 静默降级:只用配置文件里已有的工作区键
|
|
899
1343
|
setWsListTried(true);
|
|
900
1344
|
var msg = err && err.message ? String(err.message) : "";
|
|
901
|
-
setWsListErr("
|
|
902
|
-
+ (msg ? "
|
|
1345
|
+
setWsListErr(t("ws.listFailHint")
|
|
1346
|
+
+ (msg ? t("ws.listFailDetail", { msg: msg.length > 80 ? msg.slice(0, 80) + "…" : msg }) : ""));
|
|
903
1347
|
});
|
|
904
1348
|
}, [api]);
|
|
905
1349
|
|
|
@@ -919,7 +1363,7 @@ window.__ModuleLoader__.load({
|
|
|
919
1363
|
if (rows[i].path.replace(/[\/\\]+$/, "") === target) { hit = rows[i].path; break; }
|
|
920
1364
|
}
|
|
921
1365
|
if (hit) switchWorkspace(hit);
|
|
922
|
-
else toast.warn("
|
|
1366
|
+
else toast.warn(t("ws.notInList", { path: target }));
|
|
923
1367
|
}, [data, wsListTried]);
|
|
924
1368
|
|
|
925
1369
|
// 「添加工作区」候选:输入框首次聚焦时拉一次服务端扫描
|
|
@@ -951,7 +1395,7 @@ window.__ModuleLoader__.load({
|
|
|
951
1395
|
if (cached !== undefined) {
|
|
952
1396
|
setWsSkills(cached ? cached.skills : null);
|
|
953
1397
|
setWsProjSkills(cached ? new Set(cached.projectSkills) : null);
|
|
954
|
-
setWsSkillsErr(cached ? "" : "
|
|
1398
|
+
setWsSkillsErr(cached ? "" : t("ws.skillsFail"));
|
|
955
1399
|
return;
|
|
956
1400
|
}
|
|
957
1401
|
api("workspaces?skills=1&path=" + encodeURIComponent(wsPath)).then(function (d) {
|
|
@@ -967,7 +1411,7 @@ window.__ModuleLoader__.load({
|
|
|
967
1411
|
if (activeWsRef.current !== wsPath) return;
|
|
968
1412
|
setWsSkills(null);
|
|
969
1413
|
setWsProjSkills(null);
|
|
970
|
-
setWsSkillsErr("
|
|
1414
|
+
setWsSkillsErr(t("ws.skillsFail"));
|
|
971
1415
|
});
|
|
972
1416
|
}
|
|
973
1417
|
|
|
@@ -1003,7 +1447,7 @@ window.__ModuleLoader__.load({
|
|
|
1003
1447
|
return api("workspaces").then(function (d) {
|
|
1004
1448
|
if (d && d.ok === true && Array.isArray(d.items)) setWsList(d.items);
|
|
1005
1449
|
switchWorkspace(wsPath);
|
|
1006
|
-
toast.ok((r.created === false ? "
|
|
1450
|
+
toast.ok(t(r.created === false ? "ws.existsToast" : "ws.addedToast", { path: wsPath }));
|
|
1007
1451
|
});
|
|
1008
1452
|
}).catch(function (err) {
|
|
1009
1453
|
setWsAddErr(err.message);
|
|
@@ -1133,7 +1577,7 @@ window.__ModuleLoader__.load({
|
|
|
1133
1577
|
var loadModels = useCallback(function () {
|
|
1134
1578
|
api("models").then(function (d) {
|
|
1135
1579
|
var groups = d && Array.isArray(d.groups) ? d.groups : null;
|
|
1136
|
-
if (!d || d.ok !== true || !groups) throw new Error("
|
|
1580
|
+
if (!d || d.ok !== true || !groups) throw new Error(t("models.listBadResponse"));
|
|
1137
1581
|
setModelGroups(groups);
|
|
1138
1582
|
setModelGroupsOk(true);
|
|
1139
1583
|
setModelGroupsErr("");
|
|
@@ -1263,23 +1707,23 @@ window.__ModuleLoader__.load({
|
|
|
1263
1707
|
// 空名静默吞掉:确认按钮在空值时本来就 disabled,这里只兜输入框直接
|
|
1264
1708
|
// 按 Enter 的路径(与旧 prompt 实现的 if (!name) return 同语义)。
|
|
1265
1709
|
if (name === "") return;
|
|
1266
|
-
if (!/^[a-z][a-z0-9_-]{1,31}$/.test(name)) { setRoleAddErr("
|
|
1710
|
+
if (!/^[a-z][a-z0-9_-]{1,31}$/.test(name)) { setRoleAddErr(t("panel.roleNameInvalid")); return; }
|
|
1267
1711
|
// 内置名不可经「新建」再造:已删除(不在 roles、在 roles_remove)的
|
|
1268
1712
|
// 内置名在此拦截并引导走恢复区,避免同一内置名同时出现在卡片流与
|
|
1269
1713
|
// 「已删除的内置角色」恢复区;仍正常存在的内置名由下方「已存在」命中。
|
|
1270
|
-
if (DEFAULT_ROLES.indexOf(name) !== -1) { setRoleAddErr(
|
|
1714
|
+
if (DEFAULT_ROLES.indexOf(name) !== -1) { setRoleAddErr(t("panel.roleNameBuiltin", { name: name })); return; }
|
|
1271
1715
|
// 已存在检查从 setAssign updater 挪到外层:报错要走弹窗内错误行,
|
|
1272
1716
|
// setState 副作用不能塞进 updater;闭包 assign 与点击时刻一致,双击
|
|
1273
1717
|
// 极端场景下重复创建也只是同形状覆盖,幂等无害。
|
|
1274
|
-
if (assign.roles[name]) { setRoleAddErr(
|
|
1718
|
+
if (assign.roles[name]) { setRoleAddErr(t("panel.roleExists", { name: name })); return; }
|
|
1275
1719
|
// 显示名校验与 roleTitleBlock 的 commitRoleName 同源(不含 \r \n、
|
|
1276
1720
|
// trim 后 ≤60、空 = null):新建时就把显示名写进出厂形状,省得建完
|
|
1277
1721
|
// 再去卡片里补一枪。报错走弹窗内错误行(不 toast)——此刻弹窗还开着,
|
|
1278
1722
|
// 错误行就贴在输入框下面,比 toast 更贴输入现场。
|
|
1279
1723
|
var dispRaw = String(roleAddDispName == null ? "" : roleAddDispName);
|
|
1280
|
-
if (/[\r\n]/.test(dispRaw)) { setRoleAddErr("
|
|
1724
|
+
if (/[\r\n]/.test(dispRaw)) { setRoleAddErr(t("panel.dispNameNewline")); return; }
|
|
1281
1725
|
var dispTrim = dispRaw.trim();
|
|
1282
|
-
if (dispTrim.length > 60) { setRoleAddErr("
|
|
1726
|
+
if (dispTrim.length > 60) { setRoleAddErr(t("panel.dispNameTooLong")); return; }
|
|
1283
1727
|
var disp = dispTrim === "" ? null : dispTrim; // 空 / 纯空白 = null:事件流标题回落 toolName
|
|
1284
1728
|
setAssign(function (prev) {
|
|
1285
1729
|
var next = JSON.parse(JSON.stringify(prev));
|
|
@@ -1308,7 +1752,7 @@ window.__ModuleLoader__.load({
|
|
|
1308
1752
|
setRoleAddBgMode("one-shot"); // 成功提交即复位出厂默认(取消重开才保留残值)
|
|
1309
1753
|
setRoleAddStartMode("fresh"); // 成功提交即复位出厂默认(取消重开才保留残值)
|
|
1310
1754
|
setRoleAddErr("");
|
|
1311
|
-
toast.ok(
|
|
1755
|
+
toast.ok(t("panel.roleAddedToast", { name: name }));
|
|
1312
1756
|
}
|
|
1313
1757
|
|
|
1314
1758
|
// 自定义角色改名(toolName = 委派调用名):名称是身份键,改它要联动
|
|
@@ -1332,12 +1776,12 @@ window.__ModuleLoader__.load({
|
|
|
1332
1776
|
var v = String(roleRenameVal || "").trim();
|
|
1333
1777
|
// 未改动:原 prompt 下按 Enter 即关窗(只是没改名),这里对齐——直接收起。
|
|
1334
1778
|
if (v === name) { closeRoleRenameModal(); return; }
|
|
1335
|
-
if (!/^[a-z][a-z0-9_-]{1,31}$/.test(v)) { setRoleRenameErr("
|
|
1336
|
-
if (DEFAULT_ROLES.indexOf(v) !== -1) { setRoleRenameErr(
|
|
1779
|
+
if (!/^[a-z][a-z0-9_-]{1,31}$/.test(v)) { setRoleRenameErr(t("panel.roleNameInvalid")); return; }
|
|
1780
|
+
if (DEFAULT_ROLES.indexOf(v) !== -1) { setRoleRenameErr(t("panel.roleRenameBuiltin", { name: v })); return; }
|
|
1337
1781
|
// 已存在检查从 setAssign updater 挪到外层(与 submitRoleAdd 同款):
|
|
1338
1782
|
// 报错要走弹窗内错误行,setState 副作用不能塞进 updater;闭包 assign
|
|
1339
1783
|
// 与点击时刻一致。
|
|
1340
|
-
if (assign.roles[v]) { setRoleRenameErr(
|
|
1784
|
+
if (assign.roles[v]) { setRoleRenameErr(t("panel.roleExists", { name: v })); return; }
|
|
1341
1785
|
setAssign(function (prev) {
|
|
1342
1786
|
var next = JSON.parse(JSON.stringify(prev));
|
|
1343
1787
|
var roles = {};
|
|
@@ -1362,16 +1806,16 @@ window.__ModuleLoader__.load({
|
|
|
1362
1806
|
closeRoleRenameModal();
|
|
1363
1807
|
setRoleRenameVal("");
|
|
1364
1808
|
setRoleRenameErr("");
|
|
1365
|
-
toast.ok(
|
|
1809
|
+
toast.ok(t("panel.roleRenamedToast", { old: name, new: v }));
|
|
1366
1810
|
}
|
|
1367
1811
|
|
|
1368
1812
|
function removeRole(name) {
|
|
1369
1813
|
// 原原生 confirm 改通用确认弹窗:删除属破坏性操作,danger 红底确认钮。
|
|
1370
1814
|
confirmCtrl.ask({
|
|
1371
|
-
title: "
|
|
1372
|
-
body: "
|
|
1815
|
+
title: t("panel.delCustomRoleTitle"),
|
|
1816
|
+
body: t("panel.delCustomRoleBody", { name: name }),
|
|
1373
1817
|
danger: true,
|
|
1374
|
-
confirmText: "
|
|
1818
|
+
confirmText: t("common.confirmDelete"),
|
|
1375
1819
|
onConfirm: function () {
|
|
1376
1820
|
setAssign(function (prev) {
|
|
1377
1821
|
var next = JSON.parse(JSON.stringify(prev));
|
|
@@ -1392,10 +1836,10 @@ window.__ModuleLoader__.load({
|
|
|
1392
1836
|
// 原原生 confirm 改通用确认弹窗;文案含恢复引导(多行由
|
|
1393
1837
|
// pd-modalText 的 pre-line 保留换行),danger 红底确认钮。
|
|
1394
1838
|
confirmCtrl.ask({
|
|
1395
|
-
title: "
|
|
1396
|
-
body: "
|
|
1839
|
+
title: t("panel.delBuiltinRoleTitle"),
|
|
1840
|
+
body: t("panel.delBuiltinRoleBody", { name: name }),
|
|
1397
1841
|
danger: true,
|
|
1398
|
-
confirmText: "
|
|
1842
|
+
confirmText: t("common.confirmDelete"),
|
|
1399
1843
|
onConfirm: function () {
|
|
1400
1844
|
setAssign(function (prev) {
|
|
1401
1845
|
var next = JSON.parse(JSON.stringify(prev));
|
|
@@ -1427,18 +1871,38 @@ window.__ModuleLoader__.load({
|
|
|
1427
1871
|
});
|
|
1428
1872
|
}
|
|
1429
1873
|
|
|
1874
|
+
// 场景模板一键切换:只改 roles_remove 与 main_agent_extra(见
|
|
1875
|
+
// applyScenarioTemplate),点的是本地 assign、不自动保存——预览随 assign
|
|
1876
|
+
// 变更由既有失效逻辑自动作废,toast 明示「保存并应用后生效」。
|
|
1877
|
+
function applyTemplateClick(tpl) {
|
|
1878
|
+
if (!assign) return;
|
|
1879
|
+
setAssign(function (prev) {
|
|
1880
|
+
return applyScenarioTemplate(prev, tpl, (data && data.inventory) || []);
|
|
1881
|
+
});
|
|
1882
|
+
toast.ok(t("panel.tplAppliedToast", { label: t(tpl.label) }));
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1430
1885
|
function doPreview() {
|
|
1431
1886
|
if (!assign) return;
|
|
1432
1887
|
var reqAssign = assign;
|
|
1433
1888
|
setBusy(true);
|
|
1434
1889
|
setBusyOp("preview");
|
|
1435
1890
|
setPreview(null);
|
|
1891
|
+
setWarnState(null); // 旧一轮的提示条目随新预览请求一并清掉(失败也不留陈账)
|
|
1436
1892
|
// workspace 透传当前配置对象(null = 全局默认),服务端据此选生成落点。
|
|
1437
1893
|
api("preview", { assignments: reqAssign, workspace: activeWsRef.current }).then(function (r) {
|
|
1438
1894
|
if (assignRef.current !== reqAssign) return; // 生成期间配置已改:结果过期,不挂旧预览
|
|
1439
1895
|
setPreview(r);
|
|
1896
|
+
setWarnState({ source: "preview", items: Array.isArray(r.warnings) ? r.warnings : [] });
|
|
1440
1897
|
setScrollReq("preview"); // 预览成功后自动滚动定位到预览生成区
|
|
1441
|
-
}).catch(function (err) {
|
|
1898
|
+
}).catch(function (err) {
|
|
1899
|
+
toast.err(err.message);
|
|
1900
|
+
// 预览失败(如零工具角色 422)通常伴随体检说明:卡片给出每条原因,
|
|
1901
|
+
// 比 toast 一行错误更可行动。
|
|
1902
|
+
if (Array.isArray(err.warnings) && err.warnings.length) {
|
|
1903
|
+
setWarnState({ source: "preview", items: err.warnings });
|
|
1904
|
+
}
|
|
1905
|
+
}).finally(function () {
|
|
1442
1906
|
setBusy(false);
|
|
1443
1907
|
setBusyOp(null);
|
|
1444
1908
|
});
|
|
@@ -1447,9 +1911,14 @@ window.__ModuleLoader__.load({
|
|
|
1447
1911
|
// 预览时效:assign 一变即作废已挂出的预览——预览文本对应生成时刻的目标
|
|
1448
1912
|
// 配置,继续挂着「✅ 生成成功」只会误导(switchWorkspace 自带
|
|
1449
1913
|
// setPreview(null) 只覆盖切视角,这里兜住其余一切编辑路径;生成中的
|
|
1450
|
-
// 请求由 doPreview 里的 assignRef
|
|
1914
|
+
// 请求由 doPreview 里的 assignRef 快照比对作废)。检查提示卡里 preview
|
|
1915
|
+
// 源的条目描述的是同一次生成的产物,随预览一并作废;apply 源的描述的是
|
|
1916
|
+
// 已落盘结果,不受编辑影响,保留到下一次预览 / 应用。
|
|
1451
1917
|
useEffect(function () {
|
|
1452
1918
|
setPreview(null);
|
|
1919
|
+
setWarnState(function (prev) {
|
|
1920
|
+
return prev && prev.source === "preview" ? null : prev;
|
|
1921
|
+
});
|
|
1453
1922
|
}, [assign]);
|
|
1454
1923
|
|
|
1455
1924
|
function doApply() {
|
|
@@ -1458,14 +1927,14 @@ window.__ModuleLoader__.load({
|
|
|
1458
1927
|
// 确认文案按配置对象分流(语义原样保留):全局覆盖共享的 orchestrator
|
|
1459
1928
|
// 预设(现状文案);工作区落独立预设、只并进 workspaces 段,全局预设
|
|
1460
1929
|
// 不受影响。写预设非破坏性操作,danger 不置位(主色确认钮)。
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1930
|
+
// 确认文案按配置对象分流(语义原样保留):全局覆盖共享的 orchestrator
|
|
1931
|
+
// 预设(现状文案);工作区落独立预设、只并进 workspaces 段,全局预设
|
|
1932
|
+
// 不受影响。写预设非破坏性操作,danger 不置位(主色确认钮)。
|
|
1464
1933
|
confirmCtrl.ask({
|
|
1465
|
-
title: "
|
|
1466
|
-
body:
|
|
1934
|
+
title: t("panel.applyTitle"),
|
|
1935
|
+
body: wsPath ? t("panel.applyBodyWs") : t("panel.applyBodyGlobal"),
|
|
1467
1936
|
danger: false,
|
|
1468
|
-
confirmText: "
|
|
1937
|
+
confirmText: t("panel.applyConfirm"),
|
|
1469
1938
|
onConfirm: function () {
|
|
1470
1939
|
setBusy(true);
|
|
1471
1940
|
setBusyOp("apply");
|
|
@@ -1473,24 +1942,46 @@ window.__ModuleLoader__.load({
|
|
|
1473
1942
|
var notes = (r.roleResults || []).map(function (x) {
|
|
1474
1943
|
return x.role + ": " + x.intent.length + " → " + x.kept.length;
|
|
1475
1944
|
});
|
|
1476
|
-
var summary = notes.join("
|
|
1945
|
+
var summary = notes.join(t("common.listSep")) + t("common.period");
|
|
1946
|
+
// 体检结果挂卡展示;warn/error 在场时成功 toast 降为 warn 档点一句
|
|
1947
|
+
// 「见下方」(成功 toast 的既有信息——preset id / 生效说明——不丢,
|
|
1948
|
+
// 并进 warn 文案),全 info / 无条目保持原成功 toast。
|
|
1949
|
+
var warnItems = Array.isArray(r.warnings) ? r.warnings : [];
|
|
1950
|
+
setWarnState({ source: "apply", items: warnItems });
|
|
1951
|
+
var badCount = warnItems.filter(function (w) {
|
|
1952
|
+
return w && (w.level === "error" || w.level === "warn");
|
|
1953
|
+
}).length;
|
|
1954
|
+
var warnTail = t("panel.warnTail", { n: warnItems.length });
|
|
1477
1955
|
if (wsPath) {
|
|
1478
1956
|
// 工作区应用:点明生成的 preset id,提示去预设选择器选用(工作区
|
|
1479
|
-
//
|
|
1480
|
-
|
|
1957
|
+
// 预设不会自动成为默认,须在新建会话时显式选择)。体检 bad 时
|
|
1958
|
+
// summary 槽位换 warnTail(沿用原口径:角色明细让位给检查提示)。
|
|
1959
|
+
var wsMsg = t("panel.appliedWs", { id: r.presetId || "orchestrator-<slug>", summary: badCount > 0 ? warnTail : summary })
|
|
1960
|
+
+ (r.staleNote ? t("panel.staleReapply") : "");
|
|
1961
|
+
if (badCount > 0) toast.warn(wsMsg); else toast.ok(wsMsg);
|
|
1481
1962
|
// 服务端 apply 后已重刷状态缓存:静默拉一次,让 workspaceMeta /
|
|
1482
1963
|
// existing.workspaces 跟上(全局路径保持旧交互,不额外拉取)。
|
|
1964
|
+
// 回拉重建 assign 不清 apply 源提示卡(见 warnState 注释)。
|
|
1483
1965
|
api("state").then(function (d) {
|
|
1484
1966
|
setData(d);
|
|
1485
1967
|
setAssign(buildTargetAssignments(d, activeWsRef.current));
|
|
1486
1968
|
}).catch(function () { /* 静默:落盘已成功,仅展示层暂旧 */ });
|
|
1487
1969
|
} else {
|
|
1488
|
-
|
|
1970
|
+
var gMsg = t("panel.appliedGlobal", { summary: badCount > 0 ? warnTail : summary })
|
|
1971
|
+
+ (r.staleNote ? t("panel.staleRestart") : t("panel.effectiveRestart"));
|
|
1972
|
+
if (badCount > 0) toast.warn(gMsg); else toast.ok(gMsg);
|
|
1489
1973
|
}
|
|
1490
1974
|
// 显示名 / 角色名单可能变了:事件流委派行键控注册跟着刷新(行组件
|
|
1491
1975
|
// 标题读 delegateMeta,刷新即可见,无需重开会话)。
|
|
1492
1976
|
refreshDelegateRows();
|
|
1493
|
-
}).catch(function (err) {
|
|
1977
|
+
}).catch(function (err) {
|
|
1978
|
+
toast.err(err.message);
|
|
1979
|
+
// 失败(如零工具角色 422)也把体检说明挂卡:错误原因逐条可读,
|
|
1980
|
+
// 修起来有据可依。落盘未发生,卡描述的是「这次为什么没成」。
|
|
1981
|
+
if (Array.isArray(err.warnings) && err.warnings.length) {
|
|
1982
|
+
setWarnState({ source: "apply", items: err.warnings });
|
|
1983
|
+
}
|
|
1984
|
+
}).finally(function () {
|
|
1494
1985
|
setBusy(false);
|
|
1495
1986
|
setBusyOp(null);
|
|
1496
1987
|
});
|
|
@@ -1506,7 +1997,7 @@ window.__ModuleLoader__.load({
|
|
|
1506
1997
|
setData(d);
|
|
1507
1998
|
setAssign(buildTargetAssignments(d, activeWsRef.current));
|
|
1508
1999
|
// 回调参数 d 即重扫后的新 state,直接报数字,补回原徽标条的信息量
|
|
1509
|
-
toast.ok("
|
|
2000
|
+
toast.ok(t("panel.rescanned", { n: (d.inventory || []).length, m: (d.skills || []).length }));
|
|
1510
2001
|
}).catch(function (err) { toast.err(err.message); }).finally(function () {
|
|
1511
2002
|
setBusy(false);
|
|
1512
2003
|
setBusyOp(null);
|
|
@@ -1554,9 +2045,9 @@ window.__ModuleLoader__.load({
|
|
|
1554
2045
|
role: "tab",
|
|
1555
2046
|
"aria-selected": activeWs === null ? "true" : "false",
|
|
1556
2047
|
className: "omd-tab" + (activeWs === null ? " omd-tabActive" : ""),
|
|
1557
|
-
title: "
|
|
2048
|
+
title: t("ws.globalTabTip"),
|
|
1558
2049
|
onClick: function () { switchWorkspace(null); },
|
|
1559
|
-
}, h("span", { className: "omd-tabText" }, "
|
|
2050
|
+
}, h("span", { className: "omd-tabText" }, t("ws.globalTab")))];
|
|
1560
2051
|
tabs = tabs.concat(visible.map(function (row) {
|
|
1561
2052
|
var kind = wsTagOf(existingWs, meta, row.path);
|
|
1562
2053
|
var tip = wsTabTipOf(row, meta, existingWs);
|
|
@@ -1572,7 +2063,7 @@ window.__ModuleLoader__.load({
|
|
|
1572
2063
|
h("span", { className: "omd-tabText" }, wsLabelOf(row)),
|
|
1573
2064
|
h("span", { className: "omd-tabTag omd-tabTag" + kind.charAt(0).toUpperCase() + kind.slice(1) },
|
|
1574
2065
|
wsTagTextOf(kind)),
|
|
1575
|
-
row.sessionCount != null ? h("span", { className: "omd-tabCount" },
|
|
2066
|
+
row.sessionCount != null ? h("span", { className: "omd-tabCount" }, t("ws.sessionCount", { n: row.sessionCount })) : null);
|
|
1576
2067
|
}));
|
|
1577
2068
|
// 「更多」入口:仅在确有收进来的工作区时出现。正常路径激活项按显示
|
|
1578
2069
|
// 顺序排最前、必然可见,这里仍兜底判一次——首帧 wsFitCount 还是兜底
|
|
@@ -1587,12 +2078,12 @@ window.__ModuleLoader__.load({
|
|
|
1587
2078
|
key: "__more__",
|
|
1588
2079
|
type: "button",
|
|
1589
2080
|
className: "omd-tab omd-tabMore" + (hiddenActive ? " omd-tabMoreActive omd-tabActive" : ""),
|
|
1590
|
-
title: "
|
|
2081
|
+
title: t("ws.moreTip", { n: hidden.length }),
|
|
1591
2082
|
"aria-haspopup": "listbox",
|
|
1592
2083
|
"aria-expanded": wsMenuOpen ? "true" : "false",
|
|
1593
2084
|
onClick: function () { setWsMenuOpen(!wsMenuOpen); },
|
|
1594
2085
|
},
|
|
1595
|
-
h("span", { className: "omd-tabText" }, "
|
|
2086
|
+
h("span", { className: "omd-tabText" }, t("ws.more")),
|
|
1596
2087
|
h("span", { className: "omd-tabMoreCount" }, String(hidden.length))));
|
|
1597
2088
|
}
|
|
1598
2089
|
// 末尾「+ 添加工作区」:与 tab 同尺寸的弱化按钮(ghost),点击打开输入弹窗。
|
|
@@ -1600,12 +2091,12 @@ window.__ModuleLoader__.load({
|
|
|
1600
2091
|
key: "__add__",
|
|
1601
2092
|
type: "button",
|
|
1602
2093
|
className: "omd-tab omd-tabAdd",
|
|
1603
|
-
title: "
|
|
2094
|
+
title: t("ws.addTabTip"),
|
|
1604
2095
|
disabled: busy,
|
|
1605
2096
|
onClick: function () { setWsAddOpen(true); setWsAddErr(""); },
|
|
1606
2097
|
},
|
|
1607
2098
|
h("span", { className: "omd-groupIcon" }, ic(P.IconPlusOutline16, 14)),
|
|
1608
|
-
h("span", { className: "omd-tabText" }, "
|
|
2099
|
+
h("span", { className: "omd-tabText" }, t("ws.addTab"))));
|
|
1609
2100
|
// 测量层单件:与真实 tab 同款 .omd-tab 类,自然宽度因此一致;不进
|
|
1610
2101
|
// a11y 树、不可聚焦,只负责被装填算法 offsetWidth 量。
|
|
1611
2102
|
function measureTabOf(key, label) {
|
|
@@ -1617,12 +2108,12 @@ window.__ModuleLoader__.load({
|
|
|
1617
2108
|
// 下拉恰好贴着标签行的下边框展开。
|
|
1618
2109
|
return h("div", { className: "omd-wsTabs" },
|
|
1619
2110
|
h("div", { className: "omd-tabsRow" },
|
|
1620
|
-
h("div", { className: "omd-tabs", role: "tablist", "aria-label": "
|
|
2111
|
+
h("div", { className: "omd-tabs", role: "tablist", "aria-label": t("ws.tablistLabel"), ref: tabsRowRef }, tabs),
|
|
1621
2112
|
// 宽度测量层:常驻渲染(不随 wsFitCount 变),挪出视口且不可见,只把
|
|
1622
2113
|
// 各 tab 的自然宽度喂给装填算法。「更多」原型计数固定写 99 按两位数
|
|
1623
2114
|
// 算宽,与装填里的 SAFETY 余量搭配。
|
|
1624
2115
|
h("div", { className: "omd-tabsMeasure", ref: tabsMeasureRef, "aria-hidden": "true" },
|
|
1625
|
-
measureTabOf("__global__", "
|
|
2116
|
+
measureTabOf("__global__", t("ws.globalTab")),
|
|
1626
2117
|
order.map(function (row) {
|
|
1627
2118
|
var m = meta[row.path];
|
|
1628
2119
|
var mk = wsTagOf(existingWs, meta, row.path);
|
|
@@ -1630,19 +2121,19 @@ window.__ModuleLoader__.load({
|
|
|
1630
2121
|
h("span", { className: "omd-tabText" }, wsLabelOf(row)),
|
|
1631
2122
|
h("span", { className: "omd-tabTag omd-tabTag" + mk.charAt(0).toUpperCase() + mk.slice(1) },
|
|
1632
2123
|
wsTagTextOf(mk)),
|
|
1633
|
-
row.sessionCount != null ? h("span", { className: "omd-tabCount" },
|
|
2124
|
+
row.sessionCount != null ? h("span", { className: "omd-tabCount" }, t("ws.sessionCount", { n: row.sessionCount })) : null);
|
|
1634
2125
|
}),
|
|
1635
2126
|
h("button", { type: "button", "data-ws-key": "__more__", className: "omd-tab omd-tabMore", tabIndex: -1 },
|
|
1636
|
-
h("span", { className: "omd-tabText" }, "
|
|
2127
|
+
h("span", { className: "omd-tabText" }, t("ws.more")),
|
|
1637
2128
|
h("span", { className: "omd-tabMoreCount" }, "99")),
|
|
1638
2129
|
h("button", { type: "button", "data-ws-key": "__add__", className: "omd-tab omd-tabAdd", tabIndex: -1 },
|
|
1639
2130
|
h("span", { className: "omd-groupIcon" }, ic(P.IconPlusOutline16, 14)),
|
|
1640
|
-
h("span", { className: "omd-tabText" }, "
|
|
2131
|
+
h("span", { className: "omd-tabText" }, t("ws.addTab")))),
|
|
1641
2132
|
// 「更多」下拉:菜单项信息结构与 tab 同款(小圆点 / 名称 / 会话数,
|
|
1642
2133
|
// title 拼法也一致),点击直接切过去。role=listbox/option 表达
|
|
1643
2134
|
// 「从清单里选一个配置对象」的语义,aria-selected 标当前项。
|
|
1644
2135
|
wsMenuOpen && hidden.length > 0
|
|
1645
|
-
? h("div", { className: "omd-tabMenu", role: "listbox", "aria-label": "
|
|
2136
|
+
? h("div", { className: "omd-tabMenu", role: "listbox", "aria-label": t("ws.menuLabel") },
|
|
1646
2137
|
hidden.map(function (row) {
|
|
1647
2138
|
var kind = wsTagOf(existingWs, meta, row.path);
|
|
1648
2139
|
var tip = wsTabTipOf(row, meta, existingWs);
|
|
@@ -1658,11 +2149,11 @@ window.__ModuleLoader__.load({
|
|
|
1658
2149
|
h("span", { className: "omd-tabText" }, wsLabelOf(row)),
|
|
1659
2150
|
h("span", { className: "omd-tabTag omd-tabTag" + kind.charAt(0).toUpperCase() + kind.slice(1) },
|
|
1660
2151
|
wsTagTextOf(kind)),
|
|
1661
|
-
row.sessionCount != null ? h("span", { className: "omd-tabCount" },
|
|
2152
|
+
row.sessionCount != null ? h("span", { className: "omd-tabCount" }, t("ws.sessionCount", { n: row.sessionCount })) : null);
|
|
1662
2153
|
}))
|
|
1663
2154
|
: null),
|
|
1664
2155
|
h("div", { className: "omd-hint" },
|
|
1665
|
-
"
|
|
2156
|
+
t("ws.barHint")));
|
|
1666
2157
|
}
|
|
1667
2158
|
|
|
1668
2159
|
// 「添加工作区」弹窗:旧版是 tab 条下方的内联展开行,改为模态框输入。
|
|
@@ -1677,19 +2168,19 @@ window.__ModuleLoader__.load({
|
|
|
1677
2168
|
return h("div", { className: "pd-modalMask",
|
|
1678
2169
|
// 只点遮罩空白处才收(mousedown 真正落在遮罩上),在卡片内按下拖选不误关
|
|
1679
2170
|
onMouseDown: function (e) { if (e.target === e.currentTarget) closeWsModal(); } },
|
|
1680
|
-
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
2171
|
+
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": t("ws.addTitle") },
|
|
1681
2172
|
h("div", { className: "pd-modalHeader" },
|
|
1682
|
-
h("h2", { className: "pd-modalTitle" }, "
|
|
1683
|
-
h("button", { type: "button", className: "pd-modalClose", "aria-label": "
|
|
2173
|
+
h("h2", { className: "pd-modalTitle" }, t("ws.addTitle")),
|
|
2174
|
+
h("button", { type: "button", className: "pd-modalClose", "aria-label": t("common.close"), onClick: closeWsModal },
|
|
1684
2175
|
h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
1685
2176
|
h("div", { className: "pd-modalBody" },
|
|
1686
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2177
|
+
h("label", { className: "pd-modalLabel" }, t("ws.pathLabel")),
|
|
1687
2178
|
h("div", { className: "omd-wsSuggestWrap" },
|
|
1688
2179
|
h("input", {
|
|
1689
2180
|
type: "text",
|
|
1690
2181
|
className: "omd-nameInput omd-wsAddInput",
|
|
1691
2182
|
value: wsAddPath,
|
|
1692
|
-
placeholder: "
|
|
2183
|
+
placeholder: t("ws.pathPlaceholder"),
|
|
1693
2184
|
spellCheck: false,
|
|
1694
2185
|
autoFocus: true,
|
|
1695
2186
|
onFocus: function () { loadWsCands(); setWsSuggestOpen(true); },
|
|
@@ -1726,15 +2217,15 @@ window.__ModuleLoader__.load({
|
|
|
1726
2217
|
}))
|
|
1727
2218
|
: null),
|
|
1728
2219
|
wsAddErr ? statusRow("omd-statusError", ic(P.IconWarningOutline16, 16), wsAddErr) : null,
|
|
1729
|
-
h("div", { className: "omd-hint" }, "
|
|
2220
|
+
h("div", { className: "omd-hint" }, t("ws.pathHint"))),
|
|
1730
2221
|
h("div", { className: "pd-modalFooter" },
|
|
1731
|
-
h(P.Button, { variant: "outline", size: "md", disabled: busy, onClick: closeWsModal }, "
|
|
2222
|
+
h(P.Button, { variant: "outline", size: "md", disabled: busy, onClick: closeWsModal }, t("common.cancel")),
|
|
1732
2223
|
h(P.Button, {
|
|
1733
2224
|
variant: "primary",
|
|
1734
2225
|
size: "md",
|
|
1735
2226
|
disabled: busy || String(wsAddPath).trim() === "",
|
|
1736
2227
|
onClick: submitWorkspace,
|
|
1737
|
-
}, busyOp === "wsadd" ? "
|
|
2228
|
+
}, busyOp === "wsadd" ? t("ws.adding") : t("ws.addConfirm")))));
|
|
1738
2229
|
}
|
|
1739
2230
|
|
|
1740
2231
|
// 「新建自定义 agent 角色」弹窗:结构 / 关闭路径与「添加工作区」弹窗
|
|
@@ -1748,18 +2239,18 @@ window.__ModuleLoader__.load({
|
|
|
1748
2239
|
return h("div", { className: "pd-modalMask",
|
|
1749
2240
|
// 只点遮罩空白处才收(mousedown 真正落在遮罩上),在卡片内按下拖选不误关
|
|
1750
2241
|
onMouseDown: function (e) { if (e.target === e.currentTarget) closeRoleAddModal(); } },
|
|
1751
|
-
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
2242
|
+
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": t("panel.roleAddTitle") },
|
|
1752
2243
|
h("div", { className: "pd-modalHeader" },
|
|
1753
|
-
h("h2", { className: "pd-modalTitle" }, "
|
|
1754
|
-
h("button", { type: "button", className: "pd-modalClose", "aria-label": "
|
|
2244
|
+
h("h2", { className: "pd-modalTitle" }, t("panel.roleAddTitle")),
|
|
2245
|
+
h("button", { type: "button", className: "pd-modalClose", "aria-label": t("common.close"), onClick: closeRoleAddModal },
|
|
1755
2246
|
h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
1756
2247
|
h("div", { className: "pd-modalBody" },
|
|
1757
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2248
|
+
h("label", { className: "pd-modalLabel" }, t("panel.roleAddToolNameLabel")),
|
|
1758
2249
|
h("input", {
|
|
1759
2250
|
type: "text",
|
|
1760
2251
|
className: "omd-nameInput",
|
|
1761
2252
|
value: roleAddName,
|
|
1762
|
-
placeholder: "
|
|
2253
|
+
placeholder: t("panel.roleAddNamePlaceholder"),
|
|
1763
2254
|
spellCheck: false,
|
|
1764
2255
|
autoFocus: true,
|
|
1765
2256
|
onChange: function (e) { setRoleAddName(e.target.value); },
|
|
@@ -1774,14 +2265,14 @@ window.__ModuleLoader__.load({
|
|
|
1774
2265
|
}
|
|
1775
2266
|
},
|
|
1776
2267
|
}),
|
|
1777
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2268
|
+
h("label", { className: "pd-modalLabel" }, t("panel.dispNameLabel")),
|
|
1778
2269
|
h("input", {
|
|
1779
2270
|
ref: roleAddDispRef,
|
|
1780
2271
|
type: "text",
|
|
1781
2272
|
className: "omd-nameInput",
|
|
1782
2273
|
value: roleAddDispName,
|
|
1783
2274
|
// 占位语义与 roleTitleBlock 的 subject=toolName 一致:留空 = 事件流标题回落 toolName
|
|
1784
|
-
placeholder: "
|
|
2275
|
+
placeholder: t("panel.dispNamePlaceholder"),
|
|
1785
2276
|
spellCheck: false,
|
|
1786
2277
|
onChange: function (e) { setRoleAddDispName(e.target.value); },
|
|
1787
2278
|
// 第二段的 Enter 才是提交:第一段已把焦点送到这里,用户在此按
|
|
@@ -1792,7 +2283,7 @@ window.__ModuleLoader__.load({
|
|
|
1792
2283
|
}),
|
|
1793
2284
|
// 「会话模式」单选(与角色卡 roleBgModeBlock 同款语义):缺省一次性,
|
|
1794
2285
|
// 选「可续」才写 background_mode: 'continuable';提示 / 警告行内联。
|
|
1795
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2286
|
+
h("label", { className: "pd-modalLabel" }, t("panel.bgModeLabel")),
|
|
1796
2287
|
h("div", { className: "omd-nameRow" },
|
|
1797
2288
|
h("label", { className: "omd-toolItem" },
|
|
1798
2289
|
h("input", {
|
|
@@ -1802,7 +2293,7 @@ window.__ModuleLoader__.load({
|
|
|
1802
2293
|
checked: roleAddBgMode !== "continuable",
|
|
1803
2294
|
onChange: function () { setRoleAddBgMode("one-shot"); },
|
|
1804
2295
|
}),
|
|
1805
|
-
h("span", { className: "omd-toolName" }, "
|
|
2296
|
+
h("span", { className: "omd-toolName" }, t("panel.bgOneshot"))),
|
|
1806
2297
|
h("label", { className: "omd-toolItem" },
|
|
1807
2298
|
h("input", {
|
|
1808
2299
|
type: "radio",
|
|
@@ -1811,16 +2302,16 @@ window.__ModuleLoader__.load({
|
|
|
1811
2302
|
checked: roleAddBgMode === "continuable",
|
|
1812
2303
|
onChange: function () { setRoleAddBgMode("continuable"); },
|
|
1813
2304
|
}),
|
|
1814
|
-
h("span", { className: "omd-toolName" }, "
|
|
2305
|
+
h("span", { className: "omd-toolName" }, t("panel.bgContinuable")))),
|
|
1815
2306
|
roleAddBgMode === "continuable"
|
|
1816
|
-
? h("div", { className: "omd-hint" },
|
|
2307
|
+
? h("div", { className: "omd-hint" }, roleBgContinuableHint())
|
|
1817
2308
|
: null,
|
|
1818
2309
|
roleAddBgMode === "continuable" && data && data.persistenceAvailable === false
|
|
1819
|
-
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16),
|
|
2310
|
+
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16), roleBgPersistenceWarn())
|
|
1820
2311
|
: null,
|
|
1821
2312
|
// 「启动方式」单选(与角色卡 roleStartModeBlock 同款语义):缺省全新,
|
|
1822
2313
|
// 选「继承对话」才写 start_mode: 'fork'(委派块块级 provider 行)。
|
|
1823
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2314
|
+
h("label", { className: "pd-modalLabel" }, t("panel.startModeLabel")),
|
|
1824
2315
|
h("div", { className: "omd-nameRow" },
|
|
1825
2316
|
h("label", { className: "omd-toolItem" },
|
|
1826
2317
|
h("input", {
|
|
@@ -1830,7 +2321,7 @@ window.__ModuleLoader__.load({
|
|
|
1830
2321
|
checked: roleAddStartMode !== "fork",
|
|
1831
2322
|
onChange: function () { setRoleAddStartMode("fresh"); },
|
|
1832
2323
|
}),
|
|
1833
|
-
h("span", { className: "omd-toolName" }, "
|
|
2324
|
+
h("span", { className: "omd-toolName" }, t("panel.smFresh"))),
|
|
1834
2325
|
h("label", { className: "omd-toolItem" },
|
|
1835
2326
|
h("input", {
|
|
1836
2327
|
type: "radio",
|
|
@@ -1839,22 +2330,22 @@ window.__ModuleLoader__.load({
|
|
|
1839
2330
|
checked: roleAddStartMode === "fork",
|
|
1840
2331
|
onChange: function () { setRoleAddStartMode("fork"); },
|
|
1841
2332
|
}),
|
|
1842
|
-
h("span", { className: "omd-toolName" }, "
|
|
2333
|
+
h("span", { className: "omd-toolName" }, t("panel.smFork")))),
|
|
1843
2334
|
roleAddStartMode === "fork"
|
|
1844
2335
|
? h("div", { className: "omd-hint" },
|
|
1845
|
-
"
|
|
2336
|
+
t("panel.forkStartHint"))
|
|
1846
2337
|
: null,
|
|
1847
2338
|
roleAddErr ? statusRow("omd-statusError", ic(P.IconWarningOutline16, 16), roleAddErr) : null,
|
|
1848
2339
|
h("div", { className: "omd-hint" },
|
|
1849
|
-
"
|
|
2340
|
+
t("panel.roleAddHint"))),
|
|
1850
2341
|
h("div", { className: "pd-modalFooter" },
|
|
1851
|
-
h(P.Button, { variant: "outline", size: "md", onClick: closeRoleAddModal }, "
|
|
2342
|
+
h(P.Button, { variant: "outline", size: "md", onClick: closeRoleAddModal }, t("common.cancel")),
|
|
1852
2343
|
h(P.Button, {
|
|
1853
2344
|
variant: "primary",
|
|
1854
2345
|
size: "md",
|
|
1855
2346
|
disabled: String(roleAddName).trim() === "",
|
|
1856
2347
|
onClick: submitRoleAdd,
|
|
1857
|
-
}, "
|
|
2348
|
+
}, t("panel.roleAddConfirm")))));
|
|
1858
2349
|
}
|
|
1859
2350
|
|
|
1860
2351
|
// 「重命名自定义角色」弹窗:结构 / 关闭路径与 roleAddModalOf 一致——
|
|
@@ -1867,13 +2358,13 @@ window.__ModuleLoader__.load({
|
|
|
1867
2358
|
return h("div", { className: "pd-modalMask",
|
|
1868
2359
|
// 只点遮罩空白处才收(mousedown 真正落在遮罩上),在卡片内按下拖选不误关
|
|
1869
2360
|
onMouseDown: function (e) { if (e.target === e.currentTarget) closeRoleRenameModal(); } },
|
|
1870
|
-
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
2361
|
+
h("div", { className: "pd-modalCard", role: "dialog", "aria-modal": "true", "aria-label": t("panel.roleRenameTitle") },
|
|
1871
2362
|
h("div", { className: "pd-modalHeader" },
|
|
1872
|
-
h("h2", { className: "pd-modalTitle" }, "
|
|
1873
|
-
h("button", { type: "button", className: "pd-modalClose", "aria-label": "
|
|
2363
|
+
h("h2", { className: "pd-modalTitle" }, t("panel.roleRenameTitle")),
|
|
2364
|
+
h("button", { type: "button", className: "pd-modalClose", "aria-label": t("common.close"), onClick: closeRoleRenameModal },
|
|
1874
2365
|
h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
1875
2366
|
h("div", { className: "pd-modalBody" },
|
|
1876
|
-
h("label", { className: "pd-modalLabel" }, "
|
|
2367
|
+
h("label", { className: "pd-modalLabel" }, t("panel.roleRenameLabel")),
|
|
1877
2368
|
h("input", {
|
|
1878
2369
|
type: "text",
|
|
1879
2370
|
className: "omd-nameInput",
|
|
@@ -1891,9 +2382,9 @@ window.__ModuleLoader__.load({
|
|
|
1891
2382
|
}),
|
|
1892
2383
|
roleRenameErr ? statusRow("omd-statusError", ic(P.IconWarningOutline16, 16), roleRenameErr) : null,
|
|
1893
2384
|
h("div", { className: "omd-hint" },
|
|
1894
|
-
"
|
|
2385
|
+
t("panel.roleRenameHint"))),
|
|
1895
2386
|
h("div", { className: "pd-modalFooter" },
|
|
1896
|
-
h(P.Button, { variant: "outline", size: "md", onClick: closeRoleRenameModal }, "
|
|
2387
|
+
h(P.Button, { variant: "outline", size: "md", onClick: closeRoleRenameModal }, t("common.cancel")),
|
|
1897
2388
|
h(P.Button, {
|
|
1898
2389
|
variant: "primary",
|
|
1899
2390
|
size: "md",
|
|
@@ -1901,7 +2392,7 @@ window.__ModuleLoader__.load({
|
|
|
1901
2392
|
// 让按钮状态与「确实改了名」对齐。
|
|
1902
2393
|
disabled: String(roleRenameVal).trim() === "" || String(roleRenameVal).trim() === roleRenameFor,
|
|
1903
2394
|
onClick: submitRoleRename,
|
|
1904
|
-
}, "
|
|
2395
|
+
}, t("panel.roleRenameConfirm")))));
|
|
1905
2396
|
}
|
|
1906
2397
|
|
|
1907
2398
|
if (!data || !assign) {
|
|
@@ -1912,7 +2403,7 @@ window.__ModuleLoader__.load({
|
|
|
1912
2403
|
? statusRow("omd-statusError", ic(P.IconWarningOutline16, 16), error)
|
|
1913
2404
|
: h("div", { className: "omd-loading" },
|
|
1914
2405
|
h("span", { className: "omd-spin" }, ic(P.IconLoadingOutline16, 16)),
|
|
1915
|
-
h("span", null, "
|
|
2406
|
+
h("span", null, t("panel.loading"))));
|
|
1916
2407
|
}
|
|
1917
2408
|
|
|
1918
2409
|
// 技能网格数据源(主 agent 软引导/内联 + 下方角色 skills 分组共用):
|
|
@@ -1926,7 +2417,7 @@ window.__ModuleLoader__.load({
|
|
|
1926
2417
|
// 全局 = 仅用户级技能;工作区 = 用户级 + 该工作区项目级技能。
|
|
1927
2418
|
function skillScopeNote() {
|
|
1928
2419
|
return h("span", { className: "omd-skillScope" },
|
|
1929
|
-
activeWs !== null ? "
|
|
2420
|
+
activeWs !== null ? t("panel.scopeWs") : t("panel.scopeGlobal"));
|
|
1930
2421
|
}
|
|
1931
2422
|
|
|
1932
2423
|
// 角色工具候选(三组网格共用数据源):所有角色(内置 + 自定义)= SRC
|
|
@@ -1951,14 +2442,14 @@ window.__ModuleLoader__.load({
|
|
|
1951
2442
|
onClick: function () { togglePersona(name); },
|
|
1952
2443
|
},
|
|
1953
2444
|
h("span", { className: open ? "omd-chevron omd-chevronOpen" : "omd-chevron" }, ic(P.IconChevronDownOutline14, 14)),
|
|
1954
|
-
h("span", null, "
|
|
2445
|
+
h("span", null, t("panel.personaToggle")),
|
|
1955
2446
|
h("span", { className: "omd-personaEdit" }, ic(P.IconEditOutline16, 14))),
|
|
1956
2447
|
open ? h("div", { className: "omd-personaBody" },
|
|
1957
2448
|
h("textarea", {
|
|
1958
2449
|
key: "omd-persona-" + name + "-" + switchRev, // 切视角重挂:defaultValue 显示新目标的 persona
|
|
1959
2450
|
className: "omd-textarea",
|
|
1960
2451
|
defaultValue: assign.roles[name].persona || "",
|
|
1961
|
-
placeholder: "
|
|
2452
|
+
placeholder: t("panel.personaPlaceholder"),
|
|
1962
2453
|
spellCheck: false,
|
|
1963
2454
|
disabled: busy,
|
|
1964
2455
|
onBlur: function (e) {
|
|
@@ -1969,7 +2460,7 @@ window.__ModuleLoader__.load({
|
|
|
1969
2460
|
});
|
|
1970
2461
|
},
|
|
1971
2462
|
}),
|
|
1972
|
-
h("div", { className: "omd-hint" }, "
|
|
2463
|
+
h("div", { className: "omd-hint" }, t("panel.personaHint")))
|
|
1973
2464
|
: null);
|
|
1974
2465
|
}
|
|
1975
2466
|
|
|
@@ -1987,14 +2478,14 @@ window.__ModuleLoader__.load({
|
|
|
1987
2478
|
onClick: function () { setExtraOpen(!extraOpen); },
|
|
1988
2479
|
},
|
|
1989
2480
|
h("span", { className: extraOpen ? "omd-chevron omd-chevronOpen" : "omd-chevron" }, ic(P.IconChevronDownOutline14, 14)),
|
|
1990
|
-
h("span", null, "
|
|
2481
|
+
h("span", null, t("panel.extraToggle")),
|
|
1991
2482
|
h("span", { className: "omd-personaEdit" }, ic(P.IconEditOutline16, 14))),
|
|
1992
2483
|
extraOpen ? h("div", { className: "omd-personaBody" },
|
|
1993
2484
|
h("textarea", {
|
|
1994
2485
|
key: "omd-extra-" + extraRev + "-" + switchRev,
|
|
1995
2486
|
className: "omd-textarea omd-extraArea",
|
|
1996
2487
|
defaultValue: val,
|
|
1997
|
-
placeholder: "
|
|
2488
|
+
placeholder: t("panel.extraPlaceholder"),
|
|
1998
2489
|
spellCheck: false,
|
|
1999
2490
|
disabled: busy,
|
|
2000
2491
|
onBlur: function (e) { setMainPersonaExtra(e.target.value); },
|
|
@@ -2005,10 +2496,10 @@ window.__ModuleLoader__.load({
|
|
|
2005
2496
|
className: "omd-extraRestore",
|
|
2006
2497
|
disabled: busy,
|
|
2007
2498
|
onClick: restoreMainPersonaExtra,
|
|
2008
|
-
}, "
|
|
2499
|
+
}, t("common.restoreDefault")),
|
|
2009
2500
|
h("span", { className: "omd-extraHint" },
|
|
2010
|
-
"
|
|
2011
|
-
h("div", { className: "omd-hint" }, "
|
|
2501
|
+
t("panel.extraActionHint"))),
|
|
2502
|
+
h("div", { className: "omd-hint" }, t("panel.extraClearHint")))
|
|
2012
2503
|
: null);
|
|
2013
2504
|
}
|
|
2014
2505
|
|
|
@@ -2025,18 +2516,18 @@ window.__ModuleLoader__.load({
|
|
|
2025
2516
|
function commitDisplayName(raw) {
|
|
2026
2517
|
var s = String(raw == null ? "" : raw);
|
|
2027
2518
|
if (/[\r\n]/.test(s)) {
|
|
2028
|
-
toast.warn("
|
|
2519
|
+
toast.warn(t("panel.mainNameNewline"));
|
|
2029
2520
|
setDisplayNameRev(displayNameRev + 1); // 重挂 input,回到上次生效值
|
|
2030
2521
|
return;
|
|
2031
2522
|
}
|
|
2032
2523
|
var v = s.trim();
|
|
2033
2524
|
if (s !== "" && v === "") {
|
|
2034
|
-
toast.warn("
|
|
2525
|
+
toast.warn(t("panel.mainNameBlank"));
|
|
2035
2526
|
setDisplayNameRev(displayNameRev + 1);
|
|
2036
2527
|
return;
|
|
2037
2528
|
}
|
|
2038
2529
|
if (v.length > 60) {
|
|
2039
|
-
toast.warn("
|
|
2530
|
+
toast.warn(t("panel.mainNameTooLong"));
|
|
2040
2531
|
setDisplayNameRev(displayNameRev + 1);
|
|
2041
2532
|
return;
|
|
2042
2533
|
}
|
|
@@ -2056,7 +2547,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2547
|
setDisplayNameRev(displayNameRev + 1);
|
|
2057
2548
|
}
|
|
2058
2549
|
return h("div", { className: "omd-mainName" },
|
|
2059
|
-
h("div", { className: "omd-glabel" }, "
|
|
2550
|
+
h("div", { className: "omd-glabel" }, t("panel.mainNameLabel")),
|
|
2060
2551
|
h("div", { className: "omd-nameRow" },
|
|
2061
2552
|
h("input", {
|
|
2062
2553
|
key: "omd-displayName-" + displayNameRev + "-" + switchRev,
|
|
@@ -2068,9 +2559,9 @@ window.__ModuleLoader__.load({
|
|
|
2068
2559
|
disabled: busy,
|
|
2069
2560
|
onBlur: function (e) { commitDisplayName(e.target.value); },
|
|
2070
2561
|
}),
|
|
2071
|
-
h("button", { type: "button", className: "omd-extraRestore", disabled: busy, onClick: restoreMainDisplayName }, "
|
|
2562
|
+
h("button", { type: "button", className: "omd-extraRestore", disabled: busy, onClick: restoreMainDisplayName }, t("common.restoreDefault"))),
|
|
2072
2563
|
h("div", { className: "omd-hint" },
|
|
2073
|
-
|
|
2564
|
+
t("panel.mainNameHint")));
|
|
2074
2565
|
}
|
|
2075
2566
|
|
|
2076
2567
|
// 内置角色「显示名」input 重挂(key 技巧):写回校验失败 / 清空回默认时
|
|
@@ -2108,13 +2599,13 @@ window.__ModuleLoader__.load({
|
|
|
2108
2599
|
function commitRoleName(raw) {
|
|
2109
2600
|
var s = String(raw == null ? "" : raw);
|
|
2110
2601
|
if (/[\r\n]/.test(s)) {
|
|
2111
|
-
toast.warn(
|
|
2602
|
+
toast.warn(t("panel.roleNameNewline", { name: name }));
|
|
2112
2603
|
bumpRoleNameRev(name); // 重挂 input,回到上次生效值
|
|
2113
2604
|
return;
|
|
2114
2605
|
}
|
|
2115
2606
|
var v = s.trim();
|
|
2116
2607
|
if (v.length > 60) {
|
|
2117
|
-
toast.warn(
|
|
2608
|
+
toast.warn(t("panel.roleNameTooLong", { name: name }));
|
|
2118
2609
|
bumpRoleNameRev(name);
|
|
2119
2610
|
return;
|
|
2120
2611
|
}
|
|
@@ -2127,7 +2618,7 @@ window.__ModuleLoader__.load({
|
|
|
2127
2618
|
}
|
|
2128
2619
|
return h("div", { className: "omd-mainName" },
|
|
2129
2620
|
h("div", { className: "omd-glabel" },
|
|
2130
|
-
isCustom ? "
|
|
2621
|
+
isCustom ? t("panel.roleNameLabelCustom") : t("panel.roleNameLabelBuiltin")),
|
|
2131
2622
|
h("div", { className: "omd-nameRow" },
|
|
2132
2623
|
h("input", {
|
|
2133
2624
|
key: "omd-roleName-" + name + "-" + ((roleNameRev && roleNameRev[name]) || 0) + "-" + switchRev,
|
|
@@ -2141,8 +2632,8 @@ window.__ModuleLoader__.load({
|
|
|
2141
2632
|
})),
|
|
2142
2633
|
h("div", { className: "omd-hint" },
|
|
2143
2634
|
isCustom
|
|
2144
|
-
?
|
|
2145
|
-
:
|
|
2635
|
+
? t("panel.roleNameHintCustom", { name: name })
|
|
2636
|
+
: t("panel.roleNameHintBuiltin", { subject: subject })));
|
|
2146
2637
|
}
|
|
2147
2638
|
|
|
2148
2639
|
// 角色「专用模型」行(内置与自定义角色通用同一组件,签名不变):
|
|
@@ -2161,17 +2652,17 @@ window.__ModuleLoader__.load({
|
|
|
2161
2652
|
// ── 降级模式:目录不可用 → 现状双 input ───────────────────────────
|
|
2162
2653
|
if (!modelGroupsOk) {
|
|
2163
2654
|
var rev = (roleModelRev && roleModelRev[name]) || 0;
|
|
2164
|
-
function labelOf(key) { return key === "provider" ? "provider" : "
|
|
2655
|
+
function labelOf(key) { return key === "provider" ? "provider" : t("panel.modelField"); }
|
|
2165
2656
|
function commitRoleModel(key, raw) {
|
|
2166
2657
|
var s = String(raw == null ? "" : raw);
|
|
2167
2658
|
if (/[\r\n]/.test(s)) {
|
|
2168
|
-
toast.warn(
|
|
2659
|
+
toast.warn(t("panel.roleModelNewline", { name: name, field: labelOf(key) }));
|
|
2169
2660
|
bumpRoleModelRev(name); // 重挂 input,回到上次生效值
|
|
2170
2661
|
return;
|
|
2171
2662
|
}
|
|
2172
2663
|
var v = s.trim();
|
|
2173
2664
|
if (v.length > 120) {
|
|
2174
|
-
toast.warn(
|
|
2665
|
+
toast.warn(t("panel.roleModelTooLong", { name: name, field: labelOf(key) }));
|
|
2175
2666
|
bumpRoleModelRev(name);
|
|
2176
2667
|
return;
|
|
2177
2668
|
}
|
|
@@ -2187,12 +2678,12 @@ window.__ModuleLoader__.load({
|
|
|
2187
2678
|
});
|
|
2188
2679
|
// provider 非空而 model 为空 → 允许保存(后端原样落盘),但明确提示会被忽略
|
|
2189
2680
|
if (provider && !model) {
|
|
2190
|
-
toast.warn(
|
|
2681
|
+
toast.warn(t("panel.providerIgnored", { name: name }));
|
|
2191
2682
|
}
|
|
2192
2683
|
if (v === "") bumpRoleModelRev(name); // 清空 = 跟随主 agent:重挂显示占位
|
|
2193
2684
|
}
|
|
2194
2685
|
return h("div", { className: "omd-mainName" },
|
|
2195
|
-
h("div", { className: "omd-glabel" }, "
|
|
2686
|
+
h("div", { className: "omd-glabel" }, t("panel.roleModelLabel")),
|
|
2196
2687
|
h("div", { className: "omd-nameRow" },
|
|
2197
2688
|
h("input", {
|
|
2198
2689
|
key: "omd-roleModel-" + name + "-" + rev + "-" + switchRev,
|
|
@@ -2209,22 +2700,22 @@ window.__ModuleLoader__.load({
|
|
|
2209
2700
|
type: "text",
|
|
2210
2701
|
className: "omd-nameInput",
|
|
2211
2702
|
defaultValue: role.provider || "",
|
|
2212
|
-
placeholder: "
|
|
2703
|
+
placeholder: t("panel.providerPlaceholder"),
|
|
2213
2704
|
spellCheck: false,
|
|
2214
2705
|
disabled: busy,
|
|
2215
2706
|
onBlur: function (e) { commitRoleModel("provider", e.target.value); },
|
|
2216
2707
|
})),
|
|
2217
2708
|
h("div", { className: "omd-hint" },
|
|
2218
|
-
"
|
|
2709
|
+
t("panel.modelFallbackHint", { err: modelGroupsErr || t("panel.unknownError") })));
|
|
2219
2710
|
}
|
|
2220
2711
|
|
|
2221
2712
|
// ── 下拉模式:选项来自 /api/paoding/models ────────────────────────
|
|
2222
2713
|
var options = modelOptionsOf(modelGroups);
|
|
2223
2714
|
var sel = roleModelSelectionOf(options, role.provider || null, role.model || null);
|
|
2224
|
-
var children = [h("option", { key: "follow", value: "" }, "
|
|
2715
|
+
var children = [h("option", { key: "follow", value: "" }, t("models.followMain"))];
|
|
2225
2716
|
options.forEach(function (opt) {
|
|
2226
2717
|
children.push(h("option", { key: opt.value, value: opt.value },
|
|
2227
|
-
opt.modelName
|
|
2718
|
+
t("models.option", { model: opt.modelName, provider: opt.providerName })));
|
|
2228
2719
|
});
|
|
2229
2720
|
if (sel.extra) children.push(h("option", { key: "current", value: sel.extra.value }, sel.extra.label));
|
|
2230
2721
|
function commitRoleModelOption(value) {
|
|
@@ -2248,7 +2739,7 @@ window.__ModuleLoader__.load({
|
|
|
2248
2739
|
});
|
|
2249
2740
|
}
|
|
2250
2741
|
return h("div", { className: "omd-mainName" },
|
|
2251
|
-
h("div", { className: "omd-glabel" }, "
|
|
2742
|
+
h("div", { className: "omd-glabel" }, t("panel.roleModelLabel")),
|
|
2252
2743
|
h("div", { className: "omd-nameRow" },
|
|
2253
2744
|
h("select", {
|
|
2254
2745
|
key: "omd-roleModel-" + name, // 每角色独立实例;模式 / 选项集切换可整体重挂
|
|
@@ -2258,7 +2749,7 @@ window.__ModuleLoader__.load({
|
|
|
2258
2749
|
onChange: function (e) { commitRoleModelOption(e.target.value); },
|
|
2259
2750
|
}, children)),
|
|
2260
2751
|
h("div", { className: "omd-hint" },
|
|
2261
|
-
"
|
|
2752
|
+
t("panel.modelSelectHint")));
|
|
2262
2753
|
}
|
|
2263
2754
|
|
|
2264
2755
|
// 角色「会话模式」行(内置与自定义角色通用,同 roleModelBlock 的定位):
|
|
@@ -2290,13 +2781,13 @@ window.__ModuleLoader__.load({
|
|
|
2290
2781
|
h("span", { className: "omd-toolName" }, label));
|
|
2291
2782
|
}
|
|
2292
2783
|
return h("div", { className: "omd-mainName" },
|
|
2293
|
-
h("div", { className: "omd-glabel" }, "
|
|
2784
|
+
h("div", { className: "omd-glabel" }, t("panel.bgModeLabel")),
|
|
2294
2785
|
h("div", { className: "omd-nameRow" },
|
|
2295
|
-
bgOption("one-shot", "
|
|
2296
|
-
bgOption("continuable", "
|
|
2297
|
-
continuable ? h("div", { className: "omd-hint" },
|
|
2786
|
+
bgOption("one-shot", t("panel.bgOneshot"), !continuable, false),
|
|
2787
|
+
bgOption("continuable", t("panel.bgContinuable"), continuable, true)),
|
|
2788
|
+
continuable ? h("div", { className: "omd-hint" }, roleBgContinuableHint()) : null,
|
|
2298
2789
|
continuable && data.persistenceAvailable === false
|
|
2299
|
-
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16),
|
|
2790
|
+
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16), roleBgPersistenceWarn())
|
|
2300
2791
|
: null);
|
|
2301
2792
|
}
|
|
2302
2793
|
|
|
@@ -2331,22 +2822,22 @@ window.__ModuleLoader__.load({
|
|
|
2331
2822
|
h("span", { className: "omd-toolName" }, label));
|
|
2332
2823
|
}
|
|
2333
2824
|
return h("div", { className: "omd-mainName" },
|
|
2334
|
-
h("div", { className: "omd-glabel" }, "
|
|
2825
|
+
h("div", { className: "omd-glabel" }, t("panel.startModeLabel")),
|
|
2335
2826
|
h("div", { className: "omd-nameRow" },
|
|
2336
|
-
smOption("fresh", "
|
|
2337
|
-
smOption("fork", "
|
|
2827
|
+
smOption("fresh", t("panel.smFresh"), !fork, false),
|
|
2828
|
+
smOption("fork", t("panel.smFork"), fork, true)),
|
|
2338
2829
|
fork
|
|
2339
2830
|
? h("div", { className: "omd-hint" },
|
|
2340
|
-
"
|
|
2831
|
+
t("panel.forkStartHint"))
|
|
2341
2832
|
: null,
|
|
2342
2833
|
fork && hasModel
|
|
2343
|
-
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16),
|
|
2834
|
+
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16), roleForkModelHint())
|
|
2344
2835
|
: null);
|
|
2345
2836
|
}
|
|
2346
2837
|
|
|
2347
2838
|
function roleCard(name) {
|
|
2348
2839
|
var isMain = name === "__main__";
|
|
2349
|
-
var display = isMain ? "
|
|
2840
|
+
var display = isMain ? t("role.mainAgent") : name;
|
|
2350
2841
|
var isCustom = !isMain && DEFAULT_ROLES.indexOf(name) === -1;
|
|
2351
2842
|
var open = name in openMap ? openMap[name] : defaultOpenOf(name);
|
|
2352
2843
|
|
|
@@ -2362,16 +2853,18 @@ window.__ModuleLoader__.load({
|
|
|
2362
2853
|
},
|
|
2363
2854
|
h("span", { className: open ? "omd-chevron omd-chevronOpen" : "omd-chevron" }, ic(P.IconChevronDownOutline14, 14)),
|
|
2364
2855
|
h("span", { className: "omd-roleName" }, display),
|
|
2365
|
-
h("span", { className: "omd-roleBadge" }, isMain ? "
|
|
2856
|
+
h("span", { className: "omd-roleBadge" }, isMain ? t("role.badgeFixed") : (isCustom ? t("role.badgeCustom") : t("role.badgeBuiltin"))),
|
|
2366
2857
|
h("span", { className: "omd-roleCount" },
|
|
2367
|
-
(
|
|
2858
|
+
t("role.toolCount", { n: isMain
|
|
2859
|
+
? ((data.restrictBase || []).length + a.main_agent_extra.length - (a.main_agent_remove || []).length)
|
|
2860
|
+
: a.roles[name].tools.length }))),
|
|
2368
2861
|
// 自定义角色改名按钮:toolName 是身份键(委派调用名 / skills 引用),
|
|
2369
2862
|
// 与内置角色的「显示名」不是一回事——那个只改 persona 主语。
|
|
2370
2863
|
isCustom
|
|
2371
2864
|
? h("button", {
|
|
2372
2865
|
type: "button",
|
|
2373
2866
|
className: "omd-roleEdit",
|
|
2374
|
-
title:
|
|
2867
|
+
title: t("role.renameTitle", { name: name }),
|
|
2375
2868
|
onClick: function (e) {
|
|
2376
2869
|
e.stopPropagation();
|
|
2377
2870
|
renameRole(name);
|
|
@@ -2385,7 +2878,7 @@ window.__ModuleLoader__.load({
|
|
|
2385
2878
|
? h("button", {
|
|
2386
2879
|
type: "button",
|
|
2387
2880
|
className: "omd-roleDel",
|
|
2388
|
-
title: isCustom ?
|
|
2881
|
+
title: isCustom ? t("role.delCustomTitle", { name: name }) : t("role.delBuiltinTitle", { name: name }),
|
|
2389
2882
|
onClick: function (e) {
|
|
2390
2883
|
e.stopPropagation();
|
|
2391
2884
|
if (isCustom) removeRole(name);
|
|
@@ -2397,7 +2890,7 @@ window.__ModuleLoader__.load({
|
|
|
2397
2890
|
var body = [];
|
|
2398
2891
|
if (isMain) {
|
|
2399
2892
|
body.push(mainNameBlock());
|
|
2400
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2893
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.baseToolsMain")));
|
|
2401
2894
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2402
2895
|
(data.restrictBase || []).map(function (t) {
|
|
2403
2896
|
return toolItem(t, "base", (a.main_agent_remove || []).indexOf(t) === -1, busy,
|
|
@@ -2409,37 +2902,37 @@ window.__ModuleLoader__.load({
|
|
|
2409
2902
|
var mainInv = data.inventory || [];
|
|
2410
2903
|
var mainMcpTools = mainInv.filter(function (t) { return hostTool(t); });
|
|
2411
2904
|
var mainPluginTools = mainInv.filter(function (t) { return !hostTool(t); });
|
|
2412
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2905
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.mcpToolsMain")));
|
|
2413
2906
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2414
2907
|
mainMcpTools.length
|
|
2415
2908
|
? mainMcpTools.map(function (t) {
|
|
2416
2909
|
return toolItem(t, "mcp", a.main_agent_extra.indexOf(t) !== -1, busy,
|
|
2417
2910
|
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
2418
2911
|
})
|
|
2419
|
-
: h("div", { className: "omd-empty" }, "
|
|
2420
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2912
|
+
: h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
2913
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.pluginToolsMain")));
|
|
2421
2914
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2422
2915
|
mainPluginTools.length
|
|
2423
2916
|
? mainPluginTools.map(function (t) {
|
|
2424
2917
|
return toolItem(t, "plugin", a.main_agent_extra.indexOf(t) !== -1, busy,
|
|
2425
2918
|
function (e) { setMainExtra(t, e.target.checked); }, t);
|
|
2426
2919
|
})
|
|
2427
|
-
: h("div", { className: "omd-empty" }, "
|
|
2428
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2920
|
+
: h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
2921
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.skillsMain")));
|
|
2429
2922
|
body.push(gridSkills.length
|
|
2430
2923
|
? h("div", null,
|
|
2431
2924
|
h("div", { className: "omd-toolGrid" }, gridSkills.map(function (s) {
|
|
2432
|
-
return toolItem(s, "
|
|
2925
|
+
return toolItem(s, t("panel.tagReadOnDemand"), a.main_agent_skills.indexOf(s) !== -1, busy,
|
|
2433
2926
|
function (e) { setMainSkill(s, e.target.checked); }, s + "s",
|
|
2434
|
-
gridProj && gridProj.has(s) ? "
|
|
2927
|
+
gridProj && gridProj.has(s) ? t("common.projectBadge") : null);
|
|
2435
2928
|
})),
|
|
2436
|
-
h("div", { className: "omd-glabel" }, "
|
|
2929
|
+
h("div", { className: "omd-glabel" }, t("panel.skillsInline")),
|
|
2437
2930
|
h("div", { className: "omd-toolGrid" }, gridSkills.map(function (s) {
|
|
2438
|
-
return toolItem(s, "
|
|
2931
|
+
return toolItem(s, t("panel.tagInline"), a.main_agent_skills_inline.indexOf(s) !== -1, busy,
|
|
2439
2932
|
function (e) { setMainSkillInline(s, e.target.checked); }, s + "i",
|
|
2440
|
-
gridProj && gridProj.has(s) ? "
|
|
2933
|
+
gridProj && gridProj.has(s) ? t("common.projectBadge") : null);
|
|
2441
2934
|
})))
|
|
2442
|
-
: h("div", { className: "omd-empty" }, "
|
|
2935
|
+
: h("div", { className: "omd-empty" }, t("panel.noSkills")));
|
|
2443
2936
|
body.push(mainPersonaExtraBlock());
|
|
2444
2937
|
} else {
|
|
2445
2938
|
// 模型行位置:内置角色卡在「显示名」之后、工具面区之前;自定义角色卡
|
|
@@ -2466,30 +2959,30 @@ window.__ModuleLoader__.load({
|
|
|
2466
2959
|
var pluginTools = candidates.filter(function (t) { return !hostTool(t) && inv.indexOf(t) !== -1; });
|
|
2467
2960
|
var baseTools = candidates.filter(function (t) { return !hostTool(t) && inv.indexOf(t) === -1; });
|
|
2468
2961
|
var checked = a.roles[name].tools;
|
|
2469
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2962
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.baseTools")));
|
|
2470
2963
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2471
2964
|
baseTools.length
|
|
2472
2965
|
? baseTools.map(function (t) {
|
|
2473
2966
|
return toolItem(t, "base", checked.indexOf(t) !== -1, busy,
|
|
2474
2967
|
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
2475
2968
|
})
|
|
2476
|
-
: h("div", { className: "omd-empty" }, "
|
|
2477
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2969
|
+
: h("div", { className: "omd-empty" }, t("panel.none"))));
|
|
2970
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.mcpTools")));
|
|
2478
2971
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2479
2972
|
mcpTools.length
|
|
2480
2973
|
? mcpTools.map(function (t) {
|
|
2481
2974
|
return toolItem(t, "mcp", checked.indexOf(t) !== -1, busy,
|
|
2482
2975
|
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
2483
2976
|
})
|
|
2484
|
-
: h("div", { className: "omd-empty" }, "
|
|
2485
|
-
body.push(h("div", { className: "omd-glabel" }, "
|
|
2977
|
+
: h("div", { className: "omd-empty" }, t("panel.noMcp"))));
|
|
2978
|
+
body.push(h("div", { className: "omd-glabel" }, t("panel.pluginTools")));
|
|
2486
2979
|
body.push(h("div", { className: "omd-toolGrid" },
|
|
2487
2980
|
pluginTools.length
|
|
2488
2981
|
? pluginTools.map(function (t) {
|
|
2489
2982
|
return toolItem(t, "plugin", checked.indexOf(t) !== -1, busy,
|
|
2490
2983
|
function (e) { setRoleTools(name, t, e.target.checked); }, t);
|
|
2491
2984
|
})
|
|
2492
|
-
: h("div", { className: "omd-empty" }, "
|
|
2985
|
+
: h("div", { className: "omd-empty" }, t("panel.noPlugin"))));
|
|
2493
2986
|
body.push(personaBlock(name));
|
|
2494
2987
|
}
|
|
2495
2988
|
|
|
@@ -2506,7 +2999,7 @@ window.__ModuleLoader__.load({
|
|
|
2506
2999
|
var picked = assign.skills[s] || [];
|
|
2507
3000
|
return h("div", { className: "omd-skillRow", key: s },
|
|
2508
3001
|
h("span", { className: "omd-skillName", title: s }, s),
|
|
2509
|
-
gridProj && gridProj.has(s) ? h("span", { className: "omd-skillProj" }, "
|
|
3002
|
+
gridProj && gridProj.has(s) ? h("span", { className: "omd-skillProj" }, t("common.projectBadge")) : null,
|
|
2510
3003
|
h("div", { className: "omd-skillRoles" }, roleNames.map(function (r) {
|
|
2511
3004
|
return h("label", { className: "omd-skillRole", key: r },
|
|
2512
3005
|
h("input", {
|
|
@@ -2519,10 +3012,16 @@ window.__ModuleLoader__.load({
|
|
|
2519
3012
|
h("span", null, r));
|
|
2520
3013
|
})));
|
|
2521
3014
|
});
|
|
2522
|
-
return rows.length ? rows : h("div", { className: "omd-empty" }, "
|
|
3015
|
+
return rows.length ? rows : h("div", { className: "omd-empty" }, t("panel.noSkills"));
|
|
2523
3016
|
}
|
|
2524
3017
|
|
|
2525
3018
|
var customRoles = Object.keys(assign.roles).filter(function (n) { return DEFAULT_ROLES.indexOf(n) === -1; });
|
|
3019
|
+
// 内置角色可见名单:条目键存在且未被 roles_remove 删除(场景模板切到
|
|
3020
|
+
// 轻量/调研档后条目键仍保留——为的是切回全开档时 persona / 模型 / 工具面
|
|
3021
|
+
// 原样复原——卡片显隐一律按 roles_remove 判定;标题计数同口径)。
|
|
3022
|
+
var visibleBuiltinRoles = DEFAULT_ROLES.filter(function (n) {
|
|
3023
|
+
return !!assign.roles[n] && (assign.roles_remove || []).indexOf(n) === -1;
|
|
3024
|
+
});
|
|
2526
3025
|
|
|
2527
3026
|
// persistence 警告横幅:host 层会话持久化后端缺失(state.persistenceAvailable
|
|
2528
3027
|
// === false;字段缺失的老版服务端视为可用,不误报)且当前视角的任意角色
|
|
@@ -2537,7 +3036,7 @@ window.__ModuleLoader__.load({
|
|
|
2537
3036
|
icon: busyOp === "apply"
|
|
2538
3037
|
? h("span", { className: "omd-spin" }, ic(P.IconLoadingOutline16, 16))
|
|
2539
3038
|
: ic(P.IconCheckOutline16, 16),
|
|
2540
|
-
}, busyOp === "apply" ? "
|
|
3039
|
+
}, busyOp === "apply" ? t("panel.applying") : t("panel.applyBtn"));
|
|
2541
3040
|
|
|
2542
3041
|
var previewBtn = h(P.Button, {
|
|
2543
3042
|
variant: "outline",
|
|
@@ -2547,7 +3046,7 @@ window.__ModuleLoader__.load({
|
|
|
2547
3046
|
icon: busyOp === "preview"
|
|
2548
3047
|
? h("span", { className: "omd-spin" }, ic(P.IconLoadingOutline16, 16))
|
|
2549
3048
|
: ic(P.IconCordisPluginOutline14, 14),
|
|
2550
|
-
}, busyOp === "preview" ? "
|
|
3049
|
+
}, busyOp === "preview" ? t("panel.previewing") : t("panel.previewBtn"));
|
|
2551
3050
|
|
|
2552
3051
|
var rescanBtn = h(P.Button, {
|
|
2553
3052
|
variant: "ghost",
|
|
@@ -2557,7 +3056,7 @@ window.__ModuleLoader__.load({
|
|
|
2557
3056
|
icon: busyOp === "rescan"
|
|
2558
3057
|
? h("span", { className: "omd-spin" }, ic(P.IconLoadingOutline16, 16))
|
|
2559
3058
|
: ic(P.IconRefreshOutline16, 16),
|
|
2560
|
-
}, busyOp === "rescan" ? "
|
|
3059
|
+
}, busyOp === "rescan" ? t("panel.scanning") : t("panel.rescanBtn"));
|
|
2561
3060
|
|
|
2562
3061
|
return h("div", { className: "pd-page", ref: rootRef },
|
|
2563
3062
|
// 运行时识别降级:持续状态(不是事件),内联一行小字贴标签行上方,
|
|
@@ -2567,7 +3066,7 @@ window.__ModuleLoader__.load({
|
|
|
2567
3066
|
// persistence 警告横幅:仅当前视角确有可续角色且 host 层持久化后端
|
|
2568
3067
|
// 缺失时出现(判定见上方 persistenceWarn),其余场合零占位
|
|
2569
3068
|
persistenceWarn
|
|
2570
|
-
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16),
|
|
3069
|
+
? statusRow("omd-statusWarn", ic(P.IconWarningOutline16, 16), roleBgPersistenceWarn())
|
|
2571
3070
|
: null,
|
|
2572
3071
|
|
|
2573
3072
|
// 配置对象(全局默认 / 各工作区,标签页)
|
|
@@ -2597,37 +3096,51 @@ window.__ModuleLoader__.load({
|
|
|
2597
3096
|
h("div", { className: "omd-group" },
|
|
2598
3097
|
h("div", { className: "omd-groupHeading" },
|
|
2599
3098
|
h("span", { className: "omd-groupIcon" }, ic(P.IconUserOutline16, 14)),
|
|
2600
|
-
h("span", null, "
|
|
2601
|
-
h("span", { className: "omd-count" },
|
|
3099
|
+
h("span", null, t("panel.rolesToolsHeading")),
|
|
3100
|
+
h("span", { className: "omd-count" }, visibleBuiltinRoles.length + customRoles.length)),
|
|
3101
|
+
// 场景模板行:三档快捷预设(只动 roles_remove + main_agent_extra),
|
|
3102
|
+
// 贴工具配置区上方;说明进 title 悬浮,不占版面。仅改本地 assign,
|
|
3103
|
+
// 保存仍走「保存并应用」。
|
|
3104
|
+
h("div", { className: "omd-chips" },
|
|
3105
|
+
h("span", { className: "omd-restoreTitle" }, t("panel.tplHeading")),
|
|
3106
|
+
PAODING_SCENARIO_TEMPLATES.map(function (tpl) {
|
|
3107
|
+
return h("button", {
|
|
3108
|
+
type: "button",
|
|
3109
|
+
key: tpl.id,
|
|
3110
|
+
className: "omd-restorePill",
|
|
3111
|
+
title: t(tpl.desc),
|
|
3112
|
+
disabled: busy,
|
|
3113
|
+
onClick: function () { applyTemplateClick(tpl); },
|
|
3114
|
+
}, t(tpl.label));
|
|
3115
|
+
})),
|
|
2602
3116
|
roleCard("__main__"),
|
|
2603
|
-
|
|
2604
|
-
DEFAULT_ROLES.filter(function (n) { return !!assign.roles[n]; }).map(function (n) { return roleCard(n); }),
|
|
3117
|
+
visibleBuiltinRoles.map(function (n) { return roleCard(n); }),
|
|
2605
3118
|
customRoles.map(function (n) { return roleCard(n); }),
|
|
2606
3119
|
// 已删除的内置角色恢复区(仅删除过内置角色时出现)
|
|
2607
3120
|
(assign.roles_remove || []).length
|
|
2608
3121
|
? h("div", { className: "omd-restoreRow" },
|
|
2609
|
-
h("div", { className: "omd-restoreTitle" }, "
|
|
3122
|
+
h("div", { className: "omd-restoreTitle" }, t("panel.removedHeading")),
|
|
2610
3123
|
h("div", { className: "omd-chips" }, (assign.roles_remove || []).map(function (n) {
|
|
2611
3124
|
return h("button", {
|
|
2612
3125
|
type: "button",
|
|
2613
3126
|
key: n,
|
|
2614
3127
|
className: "omd-restorePill",
|
|
2615
|
-
title:
|
|
3128
|
+
title: t("role.restoreTitle", { name: n }),
|
|
2616
3129
|
onClick: function () { restoreBuiltinRole(n); },
|
|
2617
|
-
}, "
|
|
3130
|
+
}, t("role.restorePill", { name: n }));
|
|
2618
3131
|
})),
|
|
2619
3132
|
h("div", { className: "omd-hint" },
|
|
2620
|
-
"
|
|
3133
|
+
t("panel.restoreHint")))
|
|
2621
3134
|
: null,
|
|
2622
3135
|
h("button", { type: "button", className: "omd-addRole", onClick: openRoleAddModal },
|
|
2623
3136
|
h("span", { className: "omd-addRoleIcon" }, ic(P.IconPlusOutline16, 16)),
|
|
2624
|
-
h("span", null, "
|
|
3137
|
+
h("span", null, t("panel.roleAddBtn")))),
|
|
2625
3138
|
|
|
2626
3139
|
// 技能分配
|
|
2627
3140
|
h("div", { className: "omd-group" },
|
|
2628
3141
|
h("div", { className: "omd-groupHeading" },
|
|
2629
3142
|
h("span", { className: "omd-groupIcon" }, ic(P.IconSkillOutline16, 14)),
|
|
2630
|
-
h("span", null, "
|
|
3143
|
+
h("span", null, t("panel.skillsHeading")),
|
|
2631
3144
|
skillScopeNote(),
|
|
2632
3145
|
h("span", { className: "omd-count" }, gridSkills.length)),
|
|
2633
3146
|
// 工作区技能拉取失败的降级提示(一句话):此刻网格数据源已回落用户级。
|
|
@@ -2636,14 +3149,19 @@ window.__ModuleLoader__.load({
|
|
|
2636
3149
|
: null,
|
|
2637
3150
|
skillsBlock()),
|
|
2638
3151
|
|
|
3152
|
+
// 检查提示卡(预览 / 应用的结构化体检结果;无条目零占位):
|
|
3153
|
+
// preview 源贴着预览区上方,assign 一变即随预览作废;apply 源描述已
|
|
3154
|
+
// 落盘结果,保留到下一次预览 / 应用(失效口径见 warnState 注释)。
|
|
3155
|
+
warningsCardOf(warnState),
|
|
3156
|
+
|
|
2639
3157
|
// 预览生成(预览成功后才出现)
|
|
2640
3158
|
preview ? h("div", { className: "omd-group", ref: previewRef },
|
|
2641
3159
|
h("div", { className: "omd-groupHeading" },
|
|
2642
3160
|
h("span", { className: "omd-groupIcon" }, ic(P.IconDataOutline16, 14)),
|
|
2643
|
-
h("span", null, "
|
|
3161
|
+
h("span", null, t("panel.previewBtn"))),
|
|
2644
3162
|
h("div", { className: "omd-previewSummary" },
|
|
2645
|
-
"
|
|
2646
|
-
(preview.presetId ? "
|
|
3163
|
+
t("panel.previewOk") +
|
|
3164
|
+
(preview.presetId ? t("panel.previewPreset", { id: preview.presetId }) : "") +
|
|
2647
3165
|
(preview.roleResults || []).map(function (r) {
|
|
2648
3166
|
return " · " + r.role + ": " + r.intent.length + " → " + r.kept.length;
|
|
2649
3167
|
}).join("")),
|
|
@@ -2726,21 +3244,27 @@ window.__ModuleLoader__.load({
|
|
|
2726
3244
|
return function FooterEntry(props) {
|
|
2727
3245
|
var wide = !!(props && props.wide);
|
|
2728
3246
|
var _st = useState(controller.getSnapshot().open), open = _st[0], setOpenState = _st[1];
|
|
3247
|
+
var _lt = useState(0), langTick = _lt[0], setLangTick = _lt[1];
|
|
2729
3248
|
useEffect(function () {
|
|
2730
3249
|
return controller.subscribe(function () { setOpenState(controller.getSnapshot().open); });
|
|
2731
3250
|
}, []);
|
|
3251
|
+
// 语言切换时整体重渲染入口(label / aria / title 全走 t() 取新文案);
|
|
3252
|
+
// tick 只为触发重渲染,无业务含义。
|
|
3253
|
+
useEffect(function () {
|
|
3254
|
+
return onLangChange(function () { setLangTick(function (n) { return n + 1; }); });
|
|
3255
|
+
}, []);
|
|
2732
3256
|
return h("button", {
|
|
2733
3257
|
type: "button",
|
|
2734
3258
|
className: "pd-footerBtn" + (wide ? "" : " pd-footerRail"),
|
|
2735
3259
|
"data-active": open ? "true" : undefined,
|
|
2736
|
-
"aria-label": "
|
|
3260
|
+
"aria-label": t("nav.entryTitle"),
|
|
2737
3261
|
"aria-haspopup": "dialog",
|
|
2738
3262
|
"aria-expanded": open ? "true" : "false",
|
|
2739
|
-
title: wide ? undefined : "
|
|
3263
|
+
title: wide ? undefined : t("nav.entryTitle"),
|
|
2740
3264
|
onClick: function () { controller.toggle(); },
|
|
2741
3265
|
},
|
|
2742
3266
|
h(P.IconSkillOutline16, { size: wide ? 16 : 18 }),
|
|
2743
|
-
wide ? h("span", { className: "pd-footerLabel" }, "
|
|
3267
|
+
wide ? h("span", { className: "pd-footerLabel" }, t("nav.entryTitle")) : null);
|
|
2744
3268
|
};
|
|
2745
3269
|
}
|
|
2746
3270
|
|
|
@@ -2751,7 +3275,7 @@ window.__ModuleLoader__.load({
|
|
|
2751
3275
|
}
|
|
2752
3276
|
var Entry = makeFooterEntry(controller);
|
|
2753
3277
|
var dispose = ctx.slots.inject("sidebar.footer.action", function () {
|
|
2754
|
-
return ctx.slots.register({ name: "sidebar.footer.action", id: "paoding-config", order: 10, label: "
|
|
3278
|
+
return ctx.slots.register({ name: "sidebar.footer.action", id: "paoding-config", order: 10, label: t("nav.entryTitle") }, Entry);
|
|
2755
3279
|
});
|
|
2756
3280
|
return typeof dispose === "function" ? dispose : function () {};
|
|
2757
3281
|
}
|
|
@@ -2767,11 +3291,13 @@ window.__ModuleLoader__.load({
|
|
|
2767
3291
|
// 边界:只管工具调用行;子 agent 会话树 / 面包屑标题来自服务端会话摘要,
|
|
2768
3292
|
// 不在客户端控制面。标题取全局配置的显示名(行渲染时不知道会话属于哪个
|
|
2769
3293
|
// 工作区,工作区级显示名够不着)。
|
|
3294
|
+
// 标题回落表:值是 i18n 字典 key(role.title.*),取文案须过 t()——
|
|
3295
|
+
// 语言切换后 refreshDelegateRows 会重算 titles(见模块尾的订阅)。
|
|
2770
3296
|
var DELEGATE_TITLE_FALLBACK = {
|
|
2771
|
-
search_external: "
|
|
2772
|
-
design: "
|
|
2773
|
-
implement: "
|
|
2774
|
-
search_internal_deep: "
|
|
3297
|
+
search_external: "role.title.search_external",
|
|
3298
|
+
design: "role.title.design",
|
|
3299
|
+
implement: "role.title.implement",
|
|
3300
|
+
search_internal_deep: "role.title.search_internal_deep",
|
|
2775
3301
|
};
|
|
2776
3302
|
// 静态委派工具:不在 roles 键里(无条目、不可删不可改名),恒注册。
|
|
2777
3303
|
var DELEGATE_STATIC_TOOLS = ["search_internal_deep"];
|
|
@@ -2782,12 +3308,16 @@ window.__ModuleLoader__.load({
|
|
|
2782
3308
|
var removed = Array.isArray(cfg.roles_remove) ? cfg.roles_remove : [];
|
|
2783
3309
|
var titles = {};
|
|
2784
3310
|
var tools = DELEGATE_STATIC_TOOLS.slice();
|
|
2785
|
-
DELEGATE_STATIC_TOOLS.forEach(function (n) {
|
|
3311
|
+
DELEGATE_STATIC_TOOLS.forEach(function (n) {
|
|
3312
|
+
var tk = DELEGATE_TITLE_FALLBACK[n];
|
|
3313
|
+
titles[n] = tk ? t(tk) : n;
|
|
3314
|
+
});
|
|
2786
3315
|
Object.keys(roles).forEach(function (n) {
|
|
2787
3316
|
if (removed.indexOf(n) !== -1) return; // 已删内置角色:委派工具不存在,键命中不了,不注册
|
|
2788
3317
|
if (tools.indexOf(n) === -1) tools.push(n);
|
|
2789
3318
|
var nm = roles[n] && typeof roles[n].name === "string" ? roles[n].name.trim() : "";
|
|
2790
|
-
|
|
3319
|
+
var tk = DELEGATE_TITLE_FALLBACK[n];
|
|
3320
|
+
titles[n] = nm !== "" ? nm : (tk ? t(tk) : n);
|
|
2791
3321
|
});
|
|
2792
3322
|
return { titles: titles, tools: tools };
|
|
2793
3323
|
}
|
|
@@ -2846,7 +3376,7 @@ window.__ModuleLoader__.load({
|
|
|
2846
3376
|
var status = delegateStatusOf(block);
|
|
2847
3377
|
var stateCls = status === "running" ? "pd-delegateRunning" : (status === "error" ? "pd-delegateFailed" : (status === "stopped" ? "pd-delegateStopped" : "pd-delegateDone"));
|
|
2848
3378
|
var stateGlyph = status === "running" ? "◌" : (status === "error" ? "✕" : (status === "stopped" ? "⊘" : "✓"));
|
|
2849
|
-
var title = delegateMeta.titles[toolName] || DELEGATE_TITLE_FALLBACK[toolName] || toolName;
|
|
3379
|
+
var title = delegateMeta.titles[toolName] || (tHas(DELEGATE_TITLE_FALLBACK[toolName]) ? t(DELEGATE_TITLE_FALLBACK[toolName]) : "") || toolName;
|
|
2850
3380
|
return h("div", { className: "pd-delegateRow" + (status === "error" ? " pd-delegateErr" : "") },
|
|
2851
3381
|
h("button", {
|
|
2852
3382
|
type: "button",
|
|
@@ -2906,6 +3436,10 @@ window.__ModuleLoader__.load({
|
|
|
2906
3436
|
if (typeof document === "undefined") return function () {};
|
|
2907
3437
|
delegateRowsCtx = ctx;
|
|
2908
3438
|
refreshDelegateRows();
|
|
3439
|
+
// 语言切换:titles 里的 t() 值已过期,整表重算;比对串含 titles 值,
|
|
3440
|
+
// 变了就重注册(无需理会 controller 是否已挂载——未挂载时 ctx.slots
|
|
3441
|
+
// 注入照常,行组件只在事件流渲染时才取值)。
|
|
3442
|
+
onLangChange(function () { refreshDelegateRows(); });
|
|
2909
3443
|
var offReset = null;
|
|
2910
3444
|
if (ctx && typeof ctx.on === "function") {
|
|
2911
3445
|
try {
|
|
@@ -2993,14 +3527,15 @@ window.__ModuleLoader__.load({
|
|
|
2993
3527
|
var inner = mine.querySelectorAll ? mine.querySelectorAll("[id]") : [];
|
|
2994
3528
|
for (var i = 0; i < inner.length; i++) inner[i].removeAttribute("id");
|
|
2995
3529
|
mine.setAttribute("data-paoding-ws-menu", "1");
|
|
2996
|
-
mine.setAttribute("aria-label", "
|
|
2997
|
-
mine.setAttribute("title", "
|
|
3530
|
+
mine.setAttribute("aria-label", t("nav.entryTitle"));
|
|
3531
|
+
mine.setAttribute("title", t("nav.openToWs"));
|
|
2998
3532
|
var walker = document.createTreeWalker(mine, NodeFilter.SHOW_TEXT, null);
|
|
2999
3533
|
var node;
|
|
3000
3534
|
while ((node = walker.nextNode())) {
|
|
3001
|
-
var
|
|
3002
|
-
|
|
3003
|
-
|
|
3535
|
+
var txt = node.nodeValue;
|
|
3536
|
+
// 注意局部名用 txt:全局 t() 是 i18n 取词函数,不能被文本值遮蔽
|
|
3537
|
+
if (txt.indexOf("重命名") !== -1 || txt.toLowerCase().indexOf("rename") !== -1) {
|
|
3538
|
+
node.nodeValue = txt.replace(/重命名|Rename/gi, t("nav.entryTitle"));
|
|
3004
3539
|
}
|
|
3005
3540
|
}
|
|
3006
3541
|
mine.addEventListener("click", function (e) {
|
|
@@ -3044,11 +3579,11 @@ window.__ModuleLoader__.load({
|
|
|
3044
3579
|
if (hit) controller.open(hit);
|
|
3045
3580
|
else {
|
|
3046
3581
|
controller.open();
|
|
3047
|
-
toast.warn("
|
|
3582
|
+
toast.warn(t("menu.wsNotRegistered", { title: title }));
|
|
3048
3583
|
}
|
|
3049
3584
|
}).catch(function () {
|
|
3050
3585
|
controller.open();
|
|
3051
|
-
toast.warn("
|
|
3586
|
+
toast.warn(t("menu.listFail"));
|
|
3052
3587
|
});
|
|
3053
3588
|
}, 80);
|
|
3054
3589
|
}
|
|
@@ -3115,21 +3650,23 @@ window.__ModuleLoader__.load({
|
|
|
3115
3650
|
if (!verInfo || !verInfo.latest) return;
|
|
3116
3651
|
var isDev = verInfo.layout === "dev";
|
|
3117
3652
|
var msg = isDev
|
|
3118
|
-
? "
|
|
3119
|
-
: "
|
|
3653
|
+
? t("menu.upgradeDevBody")
|
|
3654
|
+
: t("menu.upgradeBody", { v: versionTagOf(verInfo.latest) });
|
|
3120
3655
|
confirmCtrl.ask({
|
|
3121
|
-
title: "
|
|
3656
|
+
title: t("menu.upgradeTitle"),
|
|
3122
3657
|
body: msg,
|
|
3123
3658
|
danger: false,
|
|
3124
|
-
confirmText: "
|
|
3659
|
+
confirmText: t("menu.upgradeConfirm"),
|
|
3125
3660
|
onConfirm: function () {
|
|
3126
3661
|
setUpgBusy(true);
|
|
3127
3662
|
setUpgNote("");
|
|
3128
3663
|
setUpgErr("");
|
|
3129
3664
|
paodingApi("upgrade", {}).then(function (r) {
|
|
3130
|
-
if (r && r.upToDate) { setUpgNote("
|
|
3665
|
+
if (r && r.upToDate) { setUpgNote(t("menu.upToDate")); return; }
|
|
3131
3666
|
var done = versionTagOf((r && r.version) || verInfo.latest);
|
|
3132
|
-
|
|
3667
|
+
// 服务端返回了 message 就原文透传(服务端消息不做客户端翻译),
|
|
3668
|
+
// 没有才落本地化兜底文案。
|
|
3669
|
+
setUpgNote((r && r.message) || t("menu.upgraded", { v: done }));
|
|
3133
3670
|
// 重拉版本信息:服务端有 1 小时缓存且本进程挂的还是旧代码,重启前
|
|
3134
3671
|
// 更新卡可能仍在(重启后自然消失),以此处的生效提示为准。
|
|
3135
3672
|
paodingApi("version").then(function (d) {
|
|
@@ -3138,7 +3675,7 @@ window.__ModuleLoader__.load({
|
|
|
3138
3675
|
}).catch(function (err) {
|
|
3139
3676
|
// 失败要醒目:截服务端透传的子进程输出尾部 ~300 字符并入 err,方便
|
|
3140
3677
|
// 定位 pnpm 缓存权限(EPERM)之类环境问题,不用翻服务端日志。
|
|
3141
|
-
var tail = typeof err.output === "string" && err.output ? "
|
|
3678
|
+
var tail = typeof err.output === "string" && err.output ? t("menu.errTail", { tail: err.output.slice(-300) }) : "";
|
|
3142
3679
|
setUpgErr(err.message + tail);
|
|
3143
3680
|
}).finally(function () {
|
|
3144
3681
|
setUpgBusy(false);
|
|
@@ -3190,6 +3727,122 @@ window.__ModuleLoader__.load({
|
|
|
3190
3727
|
// 版本检测重试进行中(页头失败行的「重试」按钮禁用态):与升级三态分开,
|
|
3191
3728
|
// 检测失败重试不该锁升级(升级预检在服务端另做一次,不依赖这份 verInfo)。
|
|
3192
3729
|
var _vb = useState(false), verRetryBusy = _vb[0], setVerRetryBusy = _vb[1];
|
|
3730
|
+
// 语言切换 tick:页头文案(标题 / 副标题 / 菜单 / 语言项)走 t(),切换后
|
|
3731
|
+
// 需整体重渲染一次;ConfigPanel 内部另有 registerReload 整页重拉。
|
|
3732
|
+
var _lg = useState(0), langTick = _lg[0], setLangTick = _lg[1];
|
|
3733
|
+
useEffect(function () {
|
|
3734
|
+
return onLangChange(function () { setLangTick(function (n) { return n + 1; }); });
|
|
3735
|
+
}, []);
|
|
3736
|
+
|
|
3737
|
+
// ── 页头「更多」菜单:配置导出 / 导入 ────────────────────────────────
|
|
3738
|
+
// 低频的整文件级操作收进菜单,页头保持一行动静。导出 / 导入写的是整份
|
|
3739
|
+
// 配置文件(非面板视图的局部编辑),与面板 busy 互不相干,独立 busy 态。
|
|
3740
|
+
var _mo = useState(false), moreOpen = _mo[0], setMoreOpen = _mo[1];
|
|
3741
|
+
var _eb = useState(false), expBusy = _eb[0], setExpBusy = _eb[1];
|
|
3742
|
+
var _ib = useState(false), impBusy = _ib[0], setImpBusy = _ib[1];
|
|
3743
|
+
var importFileRef = useRef(null); // 隐藏 file input(菜单项点击 → 程序化触发选文件)
|
|
3744
|
+
var reloadRef = useRef(null); // ConfigPanel 注册进来的 load(导入成功后整体刷新)
|
|
3745
|
+
|
|
3746
|
+
// 导出:GET config/export 拿配置文件原文(服务端不存在时回基础模板),
|
|
3747
|
+
// Blob + a[download] 落盘,文件名以服务端给的为准(= 实际配置文件名)。
|
|
3748
|
+
function doExportConfig() {
|
|
3749
|
+
setMoreOpen(false);
|
|
3750
|
+
setExpBusy(true);
|
|
3751
|
+
paodingApi("config/export").then(function (r) {
|
|
3752
|
+
var name = r && typeof r.filename === "string" && r.filename ? r.filename : "dsh-paoding.config.yml";
|
|
3753
|
+
var blob = new Blob([r && typeof r.content === "string" ? r.content : ""], { type: "text/yaml;charset=utf-8" });
|
|
3754
|
+
var url = URL.createObjectURL(blob);
|
|
3755
|
+
var a = document.createElement("a");
|
|
3756
|
+
a.href = url;
|
|
3757
|
+
a.download = name;
|
|
3758
|
+
document.body.appendChild(a);
|
|
3759
|
+
a.click();
|
|
3760
|
+
a.remove();
|
|
3761
|
+
// revoke 留一拍:点击派发的下载任务未必当场读完 blob URL
|
|
3762
|
+
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
|
|
3763
|
+
toast.ok(t("menu.exported", { name: name }));
|
|
3764
|
+
}).catch(function (err) {
|
|
3765
|
+
toast.err(t("menu.exportFail", { msg: err.message }));
|
|
3766
|
+
}).finally(function () {
|
|
3767
|
+
setExpBusy(false);
|
|
3768
|
+
});
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
// 导入:选文件 → 读文本 → 确认弹窗(整体覆盖,danger 红钮)→ POST。
|
|
3772
|
+
// file input 复用同一个小众路径:每次先清 value,选同一个文件也能再触发。
|
|
3773
|
+
function pickImportFile() {
|
|
3774
|
+
setMoreOpen(false);
|
|
3775
|
+
if (!importFileRef.current) return;
|
|
3776
|
+
importFileRef.current.value = "";
|
|
3777
|
+
importFileRef.current.click();
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
function onImportFilePicked(e) {
|
|
3781
|
+
var file = e.target.files && e.target.files[0];
|
|
3782
|
+
e.target.value = "";
|
|
3783
|
+
if (!file) return;
|
|
3784
|
+
// File.text() 现代环境都有;老环境回落 FileReader,读不出只 toast 不打扰面板
|
|
3785
|
+
var reading = typeof file.text === "function"
|
|
3786
|
+
? file.text()
|
|
3787
|
+
: new Promise(function (resolve, reject) {
|
|
3788
|
+
var reader = new FileReader();
|
|
3789
|
+
reader.onload = function () { resolve(String(reader.result)); };
|
|
3790
|
+
reader.onerror = function () { reject(new Error(t("menu.readFail"))); };
|
|
3791
|
+
reader.readAsText(file);
|
|
3792
|
+
});
|
|
3793
|
+
reading.then(function (text) {
|
|
3794
|
+
confirmCtrl.ask({
|
|
3795
|
+
title: t("menu.importTitle"),
|
|
3796
|
+
// 多行文案由 pd-modalText 的 pre-line 保留换行;工作区段会被整份
|
|
3797
|
+
// 替换这点必须说清(面板视图只显示一个配置对象,导入却是全文件)。
|
|
3798
|
+
body: t("menu.importBody", { name: file.name }),
|
|
3799
|
+
danger: true,
|
|
3800
|
+
confirmText: t("menu.importConfirm"),
|
|
3801
|
+
onConfirm: function () {
|
|
3802
|
+
setImpBusy(true);
|
|
3803
|
+
paodingApi("config/import", { content: text }).then(function () {
|
|
3804
|
+
toast.ok(t("menu.imported"));
|
|
3805
|
+
// 服务端 saveConfig 已改 mtime → state 缓存自动失效;这里触发
|
|
3806
|
+
// 面板重拉 state 并按新配置重建 assign(预览 / 提示卡一并作废)。
|
|
3807
|
+
if (typeof reloadRef.current === "function") reloadRef.current();
|
|
3808
|
+
}).catch(function (err) {
|
|
3809
|
+
toast.err(t("menu.importFail", { msg: err.message }));
|
|
3810
|
+
}).finally(function () {
|
|
3811
|
+
setImpBusy(false);
|
|
3812
|
+
});
|
|
3813
|
+
},
|
|
3814
|
+
});
|
|
3815
|
+
}).catch(function (err) {
|
|
3816
|
+
toast.err(t("menu.readFailPrefix", { msg: err.message }));
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
// 菜单开着时 Esc 只收菜单(捕获级先于 PageShell 的关页监听,与
|
|
3821
|
+
// ConfigPanel 弹窗的 Esc 分层同款);点菜单外空白处收菜单。
|
|
3822
|
+
useEffect(function () {
|
|
3823
|
+
if (!moreOpen) return;
|
|
3824
|
+
var onKey = function (e) {
|
|
3825
|
+
if (e && e.key === "Escape") {
|
|
3826
|
+
e.stopPropagation();
|
|
3827
|
+
setMoreOpen(false);
|
|
3828
|
+
}
|
|
3829
|
+
};
|
|
3830
|
+
var onClick = function (e) {
|
|
3831
|
+
var t = e && e.target;
|
|
3832
|
+
if (t && typeof t.closest === "function" &&
|
|
3833
|
+
(t.closest("[data-pd-more-menu]") || t.closest("[data-pd-more-btn]"))) return;
|
|
3834
|
+
setMoreOpen(false);
|
|
3835
|
+
};
|
|
3836
|
+
document.addEventListener("keydown", onKey, true);
|
|
3837
|
+
document.addEventListener("mousedown", onClick, true);
|
|
3838
|
+
return function () {
|
|
3839
|
+
document.removeEventListener("keydown", onKey, true);
|
|
3840
|
+
document.removeEventListener("mousedown", onClick, true);
|
|
3841
|
+
};
|
|
3842
|
+
}, [moreOpen]);
|
|
3843
|
+
|
|
3844
|
+
// 稳定的注册函数(ConfigPanel effect 依赖它):导入成功后的整体刷新入口
|
|
3845
|
+
var registerReload = useCallback(function (fn) { reloadRef.current = fn; }, []);
|
|
3193
3846
|
|
|
3194
3847
|
// 一键升级逻辑在共享的 runUpgradeFlow(页头更新卡统一走这一份)。
|
|
3195
3848
|
function doUpgrade() {
|
|
@@ -3220,21 +3873,72 @@ window.__ModuleLoader__.load({
|
|
|
3220
3873
|
// 标题行:标题 + 版本号 + (有新版时的)更新卡并排;窄屏放不下时
|
|
3221
3874
|
// 换行收纳,副标题独占下一行
|
|
3222
3875
|
h("div", { className: "pd-titleRow" },
|
|
3223
|
-
h("h1", { className: "pd-pageTitle" }, "
|
|
3876
|
+
h("h1", { className: "pd-pageTitle" }, t("nav.entryTitle"),
|
|
3224
3877
|
verInfo && typeof verInfo.current === "string" && verInfo.current
|
|
3225
3878
|
? h("span", { className: "pd-versionTag" }, versionTagOf(verInfo.current))
|
|
3226
3879
|
: null),
|
|
3227
3880
|
versionRowOf(verInfo, { busy: upgBusy, onUpgrade: doUpgrade, note: upgNote, error: upgErr, onRetry: doRetryVersion, retryBusy: verRetryBusy })),
|
|
3228
3881
|
h("p", { className: "pd-pageSubtitle" },
|
|
3229
|
-
"
|
|
3882
|
+
t("menu.subtitle"))),
|
|
3883
|
+
// 页头「更多」菜单:整份配置文件的导出 / 导入(低频操作收进来,页头
|
|
3884
|
+
// 不添第二排按钮)。定位锚点用相对定位的薄包裹层,下拉复用工作区
|
|
3885
|
+
// 「更多」的 omd-tabMenu / omd-tabMenuItem 既有样式;靠右对齐(inline
|
|
3886
|
+
// style 压掉类里的 left:2px),避免从页头右缘向右溢出。
|
|
3887
|
+
h("div", { style: { position: "relative", flex: "none" } },
|
|
3888
|
+
h("button", {
|
|
3889
|
+
type: "button",
|
|
3890
|
+
className: "omd-restorePill",
|
|
3891
|
+
"data-pd-more-btn": "1",
|
|
3892
|
+
"aria-haspopup": "menu",
|
|
3893
|
+
"aria-expanded": moreOpen ? "true" : "false",
|
|
3894
|
+
title: t("menu.moreBtnTip"),
|
|
3895
|
+
onClick: function () { setMoreOpen(!moreOpen); },
|
|
3896
|
+
}, t("menu.moreBtn")),
|
|
3897
|
+
moreOpen ? h("div", {
|
|
3898
|
+
className: "omd-tabMenu",
|
|
3899
|
+
role: "menu",
|
|
3900
|
+
"aria-label": t("menu.menuLabel"),
|
|
3901
|
+
"data-pd-more-menu": "1",
|
|
3902
|
+
style: { left: "auto", right: "0", top: "calc(100% + 6px)", minWidth: "180px" },
|
|
3903
|
+
},
|
|
3904
|
+
h("button", {
|
|
3905
|
+
type: "button", role: "menuitem", className: "omd-tabMenuItem",
|
|
3906
|
+
disabled: expBusy || impBusy,
|
|
3907
|
+
onClick: doExportConfig,
|
|
3908
|
+
}, h("span", { className: "omd-tabText" }, expBusy ? t("menu.exporting") : t("menu.exportItem"))),
|
|
3909
|
+
h("button", {
|
|
3910
|
+
type: "button", role: "menuitem", className: "omd-tabMenuItem",
|
|
3911
|
+
disabled: expBusy || impBusy,
|
|
3912
|
+
onClick: pickImportFile,
|
|
3913
|
+
}, h("span", { className: "omd-tabText" }, impBusy ? t("menu.importing") : t("menu.importItem"))),
|
|
3914
|
+
// 语言切换项:label 刻意不进字典——按当前语言显示「另一端」的叫法,
|
|
3915
|
+
// 中文界面显示 English,英文界面显示中文(语言名用母语写才认得出)。
|
|
3916
|
+
h("button", {
|
|
3917
|
+
type: "button", role: "menuitem", className: "omd-tabMenuItem",
|
|
3918
|
+
title: t("menu.langTip"),
|
|
3919
|
+
onClick: function () {
|
|
3920
|
+
setMoreOpen(false);
|
|
3921
|
+
setLang(pdLang() === "zh" ? "en" : "zh");
|
|
3922
|
+
if (typeof reloadRef.current === "function") reloadRef.current();
|
|
3923
|
+
},
|
|
3924
|
+
}, h("span", { className: "omd-tabText" }, pdLang() === "zh" ? "English" : "中文")))
|
|
3925
|
+
: null,
|
|
3926
|
+
// 隐藏选文件 input:导入入口的程序化触发器(样式零占位)
|
|
3927
|
+
h("input", {
|
|
3928
|
+
ref: importFileRef,
|
|
3929
|
+
type: "file",
|
|
3930
|
+
accept: ".yml,.yaml,text/yaml,text/plain",
|
|
3931
|
+
style: { display: "none" },
|
|
3932
|
+
onChange: onImportFilePicked,
|
|
3933
|
+
})),
|
|
3230
3934
|
h("button", {
|
|
3231
3935
|
type: "button",
|
|
3232
3936
|
className: "pd-closeBtn",
|
|
3233
|
-
"aria-label": "
|
|
3234
|
-
title: "
|
|
3937
|
+
"aria-label": t("nav.closeAria"),
|
|
3938
|
+
title: t("nav.closeTip"),
|
|
3235
3939
|
onClick: function () { if (typeof onClose === "function") onClose(); },
|
|
3236
3940
|
}, h("span", { className: "pd-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
3237
|
-
h(ConfigPanel, { setVerInfo: setVerInfo }),
|
|
3941
|
+
h(ConfigPanel, { setVerInfo: setVerInfo, registerReload: registerReload }),
|
|
3238
3942
|
// 反馈 toast 宿主:订阅模块级 toast 控制器,悬浮于操作坞上方
|
|
3239
3943
|
h(ToastHost),
|
|
3240
3944
|
// 通用确认弹窗层:订阅 confirmCtrl(发起方见各 confirmCtrl.ask 调用点),
|