codebee 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +24 -2
- package/app/core/builtin_agent.py +38 -2
- package/app/core/jobs.py +11 -0
- package/app/core/modelhub.py +17 -5
- package/app/core/pipeline.py +58 -12
- package/app/core/planner.py +34 -10
- package/app/core/repeat_guard.py +3 -3
- package/app/core/runner.py +64 -14
- package/app/core/selfupdate.py +14 -5
- package/app/core/store.py +61 -13
- package/app/core/task_compile.py +27 -5
- package/app/core/usage.py +55 -14
- package/app/core/wxdigest.py +140 -0
- package/app/main.py +22 -1
- package/app/pet.py +1564 -1535
- package/app/ui/app.js +249 -58
- package/app/ui/i18n.js +36 -0
- package/app/ui/index.html +1172 -1165
- package/app/ui/style.css +44 -0
- package/package.json +1 -1
package/app/ui/app.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/* CodeBee 前端(无依赖;状态走 SSE 实时推送,断线自动降级轮询) */
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
+
// 桌宠通过窗口标题里的端口识别并复用当前 CodeBee 实例。
|
|
5
|
+
function codebeeDocumentTitle(lang) {
|
|
6
|
+
const brand = lang === "en" ? "CodeBee · Multi-agent Orchestrator" : "CodeBee · 多智能体编排台";
|
|
7
|
+
const port = location.port || (location.protocol === "https:" ? "443" : "80");
|
|
8
|
+
return brand + " [" + port + "]";
|
|
9
|
+
}
|
|
10
|
+
document.title = codebeeDocumentTitle((localStorage.getItem("orch.lang") || "zh").toLowerCase());
|
|
11
|
+
|
|
4
12
|
const $ = (id) => document.getElementById(id);
|
|
5
13
|
const S = { state: null, catalog: null, catSig: "", providers: null, bindings: null, modelsSig: "", bindSig: "", tab: "tasks", detailRunId: null, pollTimer: null, showArchived: false, /* 会话内开关:每次加载默认隐藏已归档、图标不选中(不持久化,见 btn-side-arch) */ selProvs: {}, selModels: {}, selRuns: {}, bindSel: {}, catalogChecking: false, updateCheckAt: 0, control: null, sseLive: false, es: null, flows: null, orch: null, skills: null, orchSig: "", settings: null, sessionAgents: new Set(), atts: [], gitInfo: null, gitWb: null, gitWbKey: "", gitWbAt: 0, gitWbBusy: false, creatingTask: false, inspKey: null, inspData: null, inspSig: "", inspAt: 0, inspTab: "git", inspAutoSig: "", rdTab: null, rdTabSig: "", rdTabPin: false, _rdCtx: {}, lastPrefs: null };
|
|
6
14
|
|
|
@@ -155,6 +163,8 @@ function onTypeChange() {
|
|
|
155
163
|
// 直连:验证命令与评审参数都不适用,两块一起收起(目标+附件即全部输入)
|
|
156
164
|
if (codeOnly) codeOnly.classList.toggle("hidden", isReview || isDirect);
|
|
157
165
|
if (reviewOnly) reviewOnly.classList.toggle("hidden", !isReview);
|
|
166
|
+
if ($("f-direct-model")) $("f-direct-model").classList.toggle("hidden", !isDirect);
|
|
167
|
+
if (isDirect) renderDirectModelPicker();
|
|
158
168
|
const bibleCreate = $("bible-create-field");
|
|
159
169
|
const isSerialReview = isReview && !!(flow && flow.serial);
|
|
160
170
|
if (bibleCreate) bibleCreate.classList.toggle("hidden", !isSerialReview);
|
|
@@ -196,6 +206,46 @@ function onTypeChange() {
|
|
|
196
206
|
refreshEstimate();
|
|
197
207
|
}
|
|
198
208
|
|
|
209
|
+
function directProviders() {
|
|
210
|
+
return (S.providers || []).filter((p) => p.enabled !== false && p.api_key);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderDirectModelPicker() {
|
|
214
|
+
const ps = $("f-direct-provider"), ms = $("f-direct-model-name");
|
|
215
|
+
if (!ps || !ms) return;
|
|
216
|
+
const prev = ps.value;
|
|
217
|
+
const provs = directProviders();
|
|
218
|
+
ps.innerHTML = '<option value="">' + t("自动推荐") + '</option>' + provs.map((p) =>
|
|
219
|
+
'<option value="' + esc(p.id) + '">' + esc(p.name || p.id) + "</option>").join("");
|
|
220
|
+
if (prev && provs.some((p) => p.id === prev)) ps.value = prev;
|
|
221
|
+
const p = provs.find((x) => x.id === ps.value);
|
|
222
|
+
const oldModel = ms.value;
|
|
223
|
+
const names = p ? (p.models || []).filter((m) => m.enabled !== false && !m.hidden)
|
|
224
|
+
.map((m) => m.name).filter(Boolean) : [];
|
|
225
|
+
ms.innerHTML = '<option value="">' + t("随厂商推荐") + '</option>' + names.map((name) =>
|
|
226
|
+
'<option value="' + esc(name) + '">' + esc(name) + "</option>").join("");
|
|
227
|
+
if (oldModel && names.includes(oldModel)) ms.value = oldModel;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function recommendTaskType(goal) {
|
|
231
|
+
const s = String(goal || "").toLowerCase();
|
|
232
|
+
const rules = [
|
|
233
|
+
["code", /(代码|bug|报错|修复|重构|接口|数据库|前端|后端|python|javascript|typescript|java|go\b|git)/i],
|
|
234
|
+
["translation", /(翻译|译成|translate|translation|中译英|英译中)/i],
|
|
235
|
+
["email", /(邮件|email|e-mail|回信|邀约信|商务函)/i],
|
|
236
|
+
["weekly_report", /(周报|月报|述职|工作汇报|工作总结)/i],
|
|
237
|
+
["video_script", /(短视频|口播|分镜|抖音|视频脚本)/i],
|
|
238
|
+
["speech", /(演讲稿|发言稿|致辞|演讲)/i],
|
|
239
|
+
["tech_proposal", /(技术方案|架构方案|选型方案|实施方案)/i],
|
|
240
|
+
["research", /(调研报告|竞品调研|市场调研|深度研究)/i],
|
|
241
|
+
["serial_novel", /(连载|续写.*章|网文|长篇小说)/i],
|
|
242
|
+
["novel", /(小说|故事|短篇|章节)/i],
|
|
243
|
+
["article", /(公众号|自媒体|文章|博客|小红书)/i],
|
|
244
|
+
];
|
|
245
|
+
const hit = rules.find((x) => x[1].test(s));
|
|
246
|
+
return hit && flowById(hit[0]) ? hit[0] : "";
|
|
247
|
+
}
|
|
248
|
+
|
|
199
249
|
/* 快捷类型 chips(Composer 框下方):内置流程一键选定,选中态跟 #f-type 走 */
|
|
200
250
|
function renderQuickChips() {
|
|
201
251
|
const box = $("cmp-quick");
|
|
@@ -227,8 +277,8 @@ function cmpGreeting() {
|
|
|
227
277
|
: "晚上好呀,有什么想让我帮忙的吗");
|
|
228
278
|
}
|
|
229
279
|
|
|
230
|
-
/*
|
|
231
|
-
*
|
|
280
|
+
/* 开跑前成本与耗时预估(借鉴 omnigent 的 pre-run estimate):优先读同类台账,
|
|
281
|
+
* 无历史时显示流程基线;快速切类型用序号丢弃过期响应。 */
|
|
232
282
|
let _estSeq = 0;
|
|
233
283
|
async function refreshEstimate() {
|
|
234
284
|
const el = $("cmp-estimate");
|
|
@@ -236,13 +286,26 @@ async function refreshEstimate() {
|
|
|
236
286
|
if (!el) return;
|
|
237
287
|
const seq = ++_estSeq;
|
|
238
288
|
try {
|
|
239
|
-
const
|
|
289
|
+
const mode = ($("f-mode") || {}).value || "auto";
|
|
290
|
+
const thinking = ($("f-thinking") || {}).value || "standard";
|
|
291
|
+
const rounds = ($("f-rounds") || {}).value || "";
|
|
292
|
+
const query = new URLSearchParams({ type: flowId, days: "90", mode, thinking });
|
|
293
|
+
if (rounds) query.set("rounds", rounds);
|
|
294
|
+
const d = await api("/api/usage/estimate?" + query.toString());
|
|
240
295
|
if (seq !== _estSeq) return;
|
|
241
|
-
if (!d || !(d.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
296
|
+
if (!d || !(d.estimated_duration_s > 0)) {
|
|
297
|
+
el.classList.add("hidden"); el.textContent = ""; return;
|
|
298
|
+
}
|
|
299
|
+
const duration = chatDurTxt(d.estimated_duration_s);
|
|
300
|
+
const p90 = chatDurTxt(d.p90_duration_s || d.estimated_duration_s);
|
|
301
|
+
const source = d.samples > 0
|
|
302
|
+
? t("近{0}天 · {1}次同类", d.days || 90, d.samples)
|
|
303
|
+
: t("暂无同类历史,按流程基线估算");
|
|
304
|
+
const tokenPart = d.samples > 0 && d.median_tokens != null
|
|
305
|
+
? " · ≈" + fmtTok(d.median_tokens) + " tokens" : "";
|
|
306
|
+
const cost = (d.median_cost_usd || 0) > 0 ? " · ≈$" + Number(d.median_cost_usd).toFixed(2) : "";
|
|
307
|
+
el.textContent = t("预计完成约 {0},保守不超过 {1}", duration, p90) +
|
|
308
|
+
tokenPart + cost + "(" + source + ")";
|
|
246
309
|
el.classList.remove("hidden");
|
|
247
310
|
} catch (e) {
|
|
248
311
|
if (seq === _estSeq) { el.classList.add("hidden"); el.textContent = ""; }
|
|
@@ -919,6 +982,9 @@ async function poll() {
|
|
|
919
982
|
S.providers = models.providers; S.bindings = models.bindings;
|
|
920
983
|
S.modelCatalog = models.catalog || [];
|
|
921
984
|
S.sourceNames = models.source_names || S.sourceNames || {};
|
|
985
|
+
if ($("f-direct-model") && !$('f-direct-model').classList.contains("hidden")) {
|
|
986
|
+
renderDirectModelPicker();
|
|
987
|
+
}
|
|
922
988
|
// 供应商集合/启停/密钥变了才重拉编排者:否则「生效中」状态点会在停用厂商后失真
|
|
923
989
|
const provSig = JSON.stringify((S.providers || []).map((p) => [p.id, p.enabled, !!p.api_key]));
|
|
924
990
|
if (provSig !== S.provSig) { S.provSig = provSig; loadOrchestrator(); }
|
|
@@ -2145,6 +2211,7 @@ async function createTask() {
|
|
|
2145
2211
|
goal: $("f-goal").value.trim(),
|
|
2146
2212
|
context: $("f-context").value.trim(),
|
|
2147
2213
|
workdir: $("f-workdir").value.trim(),
|
|
2214
|
+
thinking: $("f-thinking").value,
|
|
2148
2215
|
};
|
|
2149
2216
|
if (!payload.goal) {
|
|
2150
2217
|
msg.className = "msg err";
|
|
@@ -2153,6 +2220,22 @@ async function createTask() {
|
|
|
2153
2220
|
resetSubmit();
|
|
2154
2221
|
return;
|
|
2155
2222
|
}
|
|
2223
|
+
if (payload.type === "direct" && !S.typeSuggestionDone) {
|
|
2224
|
+
const suggested = recommendTaskType(payload.goal);
|
|
2225
|
+
if (suggested) {
|
|
2226
|
+
const flow = flowById(suggested) || {};
|
|
2227
|
+
resetSubmit();
|
|
2228
|
+
const yes = await uiConfirm(
|
|
2229
|
+
t("这个需求更适合「{0}」流程,能使用对应的规划与质量门禁。是否切换后执行?")
|
|
2230
|
+
.replace("{0}", t(flow.name || suggested)),
|
|
2231
|
+
{ title: t("推荐任务类型"), ok: t("切换类型") });
|
|
2232
|
+
S.typeSuggestionDone = true;
|
|
2233
|
+
if (yes) pickType(suggested);
|
|
2234
|
+
$("btn-create").click();
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
S.typeSuggestionDone = false;
|
|
2156
2239
|
// 需求拷问闸(借鉴 grill-me):goal 很短且没写背景时先问 1-3 个澄清问题;
|
|
2157
2240
|
// 采访失败/无问题照常创建——是增强不是闸门
|
|
2158
2241
|
if (payload.goal.length < 12 && !payload.context && !S.clarifyDone) {
|
|
@@ -2198,6 +2281,8 @@ async function createTask() {
|
|
|
2198
2281
|
payload.verify_command = $("f-verify").value.trim();
|
|
2199
2282
|
} else if (_eng === "direct") {
|
|
2200
2283
|
// 直连:目标+附件即全部输入,不带验证/评审参数
|
|
2284
|
+
payload.direct_provider_id = $("f-direct-provider").value;
|
|
2285
|
+
payload.direct_model = $("f-direct-model-name").value;
|
|
2201
2286
|
} else {
|
|
2202
2287
|
payload.manuscript = $("f-manuscript").value.trim() || "manuscript.md";
|
|
2203
2288
|
payload.rounds = parseInt($("f-rounds").value, 10) || 2;
|
|
@@ -2863,11 +2948,19 @@ function newFromTask(id) {
|
|
|
2863
2948
|
$("f-workdir").value = tk.workdir || "";
|
|
2864
2949
|
S.atts = []; renderAttachChips(); // 附件清单不继承:同目录引用已随 context 保留
|
|
2865
2950
|
queueGitProbe(); // 工作目录变了,重新探测代码版本
|
|
2866
|
-
$("f-mode").value =
|
|
2951
|
+
$("f-mode").value = ["fast", "expert", "manual"].includes(tk.mode) ? tk.mode : "auto";
|
|
2952
|
+
$("f-thinking").value = ["low", "standard", "high"].includes(tk.thinking)
|
|
2953
|
+
? tk.thinking : "auto";
|
|
2954
|
+
$("f-manual-only").classList.toggle("hidden", $("f-mode").value !== "manual");
|
|
2867
2955
|
renderImplSelects();
|
|
2868
2956
|
if (tk.implementer) $("f-impl").value = tk.implementer;
|
|
2869
2957
|
if (flow && flow.engine === "code") {
|
|
2870
2958
|
$("f-verify").value = tk.verify_command || "";
|
|
2959
|
+
} else if (flow && flow.engine === "direct") {
|
|
2960
|
+
renderDirectModelPicker();
|
|
2961
|
+
$("f-direct-provider").value = tk.direct_provider_id || "";
|
|
2962
|
+
renderDirectModelPicker();
|
|
2963
|
+
$("f-direct-model-name").value = tk.direct_model || "";
|
|
2871
2964
|
} else {
|
|
2872
2965
|
$("f-manuscript").value = tk.manuscript || "manuscript.md";
|
|
2873
2966
|
$("f-rounds").value = tk.rounds || 2;
|
|
@@ -3957,7 +4050,8 @@ async function renderRunDetail() {
|
|
|
3957
4050
|
'<span class="stat">' + t("创建 ") + '<b>' + esc(run.created_at) + "</b></span>" +
|
|
3958
4051
|
'<span class="stat">' + t("成本 ") + '<b>$' + Number(run.cost_usd || 0).toFixed(3) + "</b></span>" +
|
|
3959
4052
|
'<span class="stat">tokens <b>' + (run.tokens || 0) + "</b></span>" +
|
|
3960
|
-
(run.mode ? '<span class="stat">' + t("模式 ") + '<b>' + (
|
|
4053
|
+
(run.mode ? '<span class="stat">' + t("模式 ") + '<b>' + ({auto: t("自动"), fast: t("快速"), expert: t("专家"), manual: t("手动")}[run.mode] || esc(run.mode)) + "</b></span>" : "") +
|
|
4054
|
+
(runEtaText(run) ? '<span class="stat eta">' + esc(runEtaText(run)) + "</span>" : "") +
|
|
3961
4055
|
(run.error ? '<span class="stat err">' + errTag(run.error) + esc(run.error.slice(0, 200)) + "</span>" : "") +
|
|
3962
4056
|
'<span class="stat tasksum hidden" id="rd-meta-task"></span>';
|
|
3963
4057
|
if (S.detailSide) fillMetaTask(S.detailSide.stats || {}); // 缓存命中:轮询重画不闪丢累计组
|
|
@@ -6438,6 +6532,29 @@ function runDurationSeconds(run) {
|
|
|
6438
6532
|
return Number.isFinite(a) && Number.isFinite(b) && b >= a ? (b - a) / 1000 : null;
|
|
6439
6533
|
}
|
|
6440
6534
|
|
|
6535
|
+
function runEtaText(run) {
|
|
6536
|
+
if (!run || !Number.isFinite(Number(run.estimated_duration_s))) return "";
|
|
6537
|
+
const estimate = Math.max(1, Number(run.estimated_duration_s));
|
|
6538
|
+
const p90 = Math.max(estimate, Number(run.estimated_p90_s) || estimate);
|
|
6539
|
+
const active = run.status === "running" || run.status === "queued";
|
|
6540
|
+
if (!active) return t("预计完成约 {0}", chatDurTxt(Math.round(estimate)));
|
|
6541
|
+
if (run.status === "queued" || !run.started_at) {
|
|
6542
|
+
return t("预计完成约 {0},保守不超过 {1}",
|
|
6543
|
+
chatDurTxt(Math.round(estimate)), chatDurTxt(Math.round(p90)));
|
|
6544
|
+
}
|
|
6545
|
+
const started = Date.parse(String(run.started_at).replace(" ", "T"));
|
|
6546
|
+
if (!Number.isFinite(started)) return "";
|
|
6547
|
+
const elapsed = Math.max(0, (Date.now() - started) / 1000);
|
|
6548
|
+
if (elapsed < estimate) {
|
|
6549
|
+
return t("预计剩余约 {0}", chatDurTxt(Math.max(1, Math.round(estimate - elapsed))));
|
|
6550
|
+
}
|
|
6551
|
+
if (elapsed < p90) {
|
|
6552
|
+
return t("已超过常规预估,仍在保守区间内(约剩 {0})",
|
|
6553
|
+
chatDurTxt(Math.max(1, Math.round(p90 - elapsed))));
|
|
6554
|
+
}
|
|
6555
|
+
return t("已超过保守预估,正在继续执行,可查看步骤日志");
|
|
6556
|
+
}
|
|
6557
|
+
|
|
6441
6558
|
function runOutcomeSummary(run) {
|
|
6442
6559
|
if (!run) return "";
|
|
6443
6560
|
const last = (run.steps || []).slice().reverse().find((s) => s.output || s.summary);
|
|
@@ -6504,6 +6621,7 @@ function renderDetailOverview(run, runs, task) {
|
|
|
6504
6621
|
const lastText = last ? String(last.summary || last.output || last.note || "").replace(/\s+/g, " ").trim().slice(0, 240) : "";
|
|
6505
6622
|
const executor = run.executor || run.implementer || (last && (last.agent_label || last.agent)) || "";
|
|
6506
6623
|
const duration = runDurationSeconds(run);
|
|
6624
|
+
const etaText = runEtaText(run);
|
|
6507
6625
|
const goal = (task && task.goal) || run.goal || run.title || "";
|
|
6508
6626
|
const actions = [];
|
|
6509
6627
|
if (total) actions.push('<button type="button" class="ghost" data-overview-tab="steps">' + esc(t("查看步骤")) + "</button>");
|
|
@@ -6524,6 +6642,7 @@ function renderDetailOverview(run, runs, task) {
|
|
|
6524
6642
|
'<span><b>' + settled + "/" + total + '</b> ' + esc(t("步骤已完成")) + '</span>' +
|
|
6525
6643
|
(executor ? '<span>' + esc(t("执行者")) + ' <b>' + esc(executor) + '</b></span>' : "") +
|
|
6526
6644
|
(duration != null ? '<span>' + esc(t("耗时")) + ' <b>' + esc(chatDurTxt(duration < 1 ? Number(duration.toFixed(1)) : Math.round(duration))) + '</b></span>' : "") +
|
|
6645
|
+
(etaText ? '<span class="rd-eta">' + esc(etaText) + '</span>' : "") +
|
|
6527
6646
|
'</div>' +
|
|
6528
6647
|
(lastText ? '<div class="rd-overview-last"><b>' + esc(t("最近一步")) + '</b><span>' + esc(lastText) + '</span></div>' : "") +
|
|
6529
6648
|
(actions.length ? '<div class="rd-overview-actions">' + actions.join("") + '</div>' : "");
|
|
@@ -9782,7 +9901,7 @@ async function suStartupCheck() {
|
|
|
9782
9901
|
}
|
|
9783
9902
|
|
|
9784
9903
|
/* ---------------------------------------------------------- 页签 & 初始化 */
|
|
9785
|
-
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "本机智能体", models: "模型接入", bindings: "模型调度(可选)", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", data: "数据与备份", appearance: "皮肤", about: "关于与更新" };
|
|
9904
|
+
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", __wxdigest: "群摘要", usage: "用量统计", agents: "本机智能体", models: "模型接入", bindings: "模型调度(可选)", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", data: "数据与备份", appearance: "皮肤", about: "关于与更新" };
|
|
9786
9905
|
const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 设置导航里的子页(__phone 是弹框,不算)
|
|
9787
9906
|
|
|
9788
9907
|
function tabTitle(name) {
|
|
@@ -9938,7 +10057,7 @@ function setLangBtn(lang) {
|
|
|
9938
10057
|
S.catSig = ""; S.modelsSig = ""; S.bindSig = ""; S.orchSig = ""; S.sideSig = ""; S.taskSig = ""; S.mgmt = {};
|
|
9939
10058
|
syncLangMode();
|
|
9940
10059
|
// 浏览器标签标题
|
|
9941
|
-
document.title = (lang
|
|
10060
|
+
document.title = codebeeDocumentTitle(lang);
|
|
9942
10061
|
applyI18n();
|
|
9943
10062
|
render();
|
|
9944
10063
|
cmpGreeting(); // 问候语跟随语言重算
|
|
@@ -10621,11 +10740,6 @@ function collapseDrawerIfMobile() {
|
|
|
10621
10740
|
function switchTab(name) {
|
|
10622
10741
|
if (name === "__phone") { openPhoneConnect(); return; } // 手机连接是弹框,不切页
|
|
10623
10742
|
if (name === "__guide") { welcomeOpen(); return; } // 帮助中心是弹层,不切页(设置导航「软件」组)
|
|
10624
|
-
if (name === "__wxdigest") { // 群聊汇报沿用全局蜜蜂面板
|
|
10625
|
-
beeRefresh().finally(() => beeToggle(true));
|
|
10626
|
-
collapseDrawerIfMobile();
|
|
10627
|
-
return;
|
|
10628
|
-
}
|
|
10629
10743
|
// 导航收进「设置」:进设置后左栏整体换成设置导航,内容铺满
|
|
10630
10744
|
S.tab = name;
|
|
10631
10745
|
if (SET_TABS.has(name)) localStorage.setItem("orch.setTab", name);
|
|
@@ -10644,6 +10758,7 @@ function switchTab(name) {
|
|
|
10644
10758
|
if (name === "automation") { loadAutomation(); startAutoPoll(); } // 进自动化页:拉取 + 页面可见时每 8s 轮询
|
|
10645
10759
|
else stopAutoPoll(); // 离开自动化页(或切到别的子页)即停表
|
|
10646
10760
|
if (name === "zentao") loadZentao(); // 进禅道页:拉配置与修复记录回填表单
|
|
10761
|
+
if (name === "__wxdigest") { beeRefresh(); beeMarkSeen(); } // 进群摘要页:拉取视图并清未读
|
|
10647
10762
|
if (name === "market") loadMarket(); // 进插件市场页拉取目录
|
|
10648
10763
|
if (name === "data") loadDataPage(); // 进数据与备份页:清理配置/状态/可清理预估
|
|
10649
10764
|
if (name === "usage") { syncUsageRange(); loadUsage(); } // 进用量页:对齐范围选中态并拉取
|
|
@@ -11111,7 +11226,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
11111
11226
|
});
|
|
11112
11227
|
$("f-mode").addEventListener("change", () => {
|
|
11113
11228
|
$("f-manual-only").classList.toggle("hidden", $("f-mode").value !== "manual");
|
|
11229
|
+
refreshEstimate();
|
|
11114
11230
|
});
|
|
11231
|
+
$("f-thinking").addEventListener("change", refreshEstimate);
|
|
11232
|
+
$("f-rounds").addEventListener("change", refreshEstimate);
|
|
11233
|
+
$("f-direct-provider").addEventListener("change", renderDirectModelPicker);
|
|
11115
11234
|
$("f-type").dispatchEvent(new Event("change"));
|
|
11116
11235
|
cmpGreeting(); // 问候语按时段刷新(切语言/回任务页也会重算)
|
|
11117
11236
|
$("btn-reload-catalog").addEventListener("click", async () => {
|
|
@@ -11337,46 +11456,20 @@ function bindCmdK() {
|
|
|
11337
11456
|
});
|
|
11338
11457
|
}
|
|
11339
11458
|
|
|
11340
|
-
/* ------------------------------------------------
|
|
11341
|
-
*
|
|
11342
|
-
*
|
|
11343
|
-
* 60s 轮询 /api/wxdigest
|
|
11344
|
-
const Bee = { view: null, lastUnseen: 0, bubT: 0, selGroups: null };
|
|
11459
|
+
/* ------------------------------------------------ 群摘要页(core/wxdigest.py)
|
|
11460
|
+
* 设置 → 群摘要:配置监控文件夹/微信直连 + 各群摘要列表(替代原右下角悬浮
|
|
11461
|
+
* 蜜蜂坞——用户拍板去掉悬浮组件,配置与总结统一进页面)。桌面蜜蜂 pet.py
|
|
11462
|
+
* 右键「群摘要」直达本页。进页/60s 轮询 /api/wxdigest;进页 POST seen 清未读。 */
|
|
11463
|
+
const Bee = { view: null, lastUnseen: 0, bubT: 0, selGroups: null, pyDirty: false };
|
|
11345
11464
|
|
|
11346
11465
|
function beeShow(el, on) { if (el) el.classList.toggle("hidden", !on); }
|
|
11347
|
-
function beePanelOpen() { const p = $("bee-panel"); return !!p && !p.classList.contains("hidden"); }
|
|
11348
11466
|
|
|
11349
11467
|
async function beeRefresh() {
|
|
11350
11468
|
let v;
|
|
11351
11469
|
try { v = await api("/api/wxdigest"); } catch (e) { return; } // 静默:不打扰主流程
|
|
11352
11470
|
Bee.view = v;
|
|
11353
|
-
beeShow($("bee-dock"), true);
|
|
11354
|
-
beeShow($("bee-badge"), v.unseen > 0);
|
|
11355
|
-
if (v.unseen > 0) $("bee-badge").textContent = v.unseen > 99 ? "99+" : String(v.unseen);
|
|
11356
|
-
if (v.unseen > Bee.lastUnseen && !beePanelOpen()) { // 新摘要到:气泡 6s
|
|
11357
|
-
const b = $("bee-bubble");
|
|
11358
|
-
const names = [...new Set((v.digests || []).slice(0, v.unseen).map((d) => d.group))];
|
|
11359
|
-
b.textContent = t("嗡!新群摘要好了:") + (names.join(t("、")) || "…");
|
|
11360
|
-
beeShow(b, true);
|
|
11361
|
-
clearTimeout(Bee.bubT);
|
|
11362
|
-
Bee.bubT = setTimeout(() => beeShow(b, false), 6000);
|
|
11363
|
-
}
|
|
11364
11471
|
Bee.lastUnseen = v.unseen;
|
|
11365
|
-
|
|
11366
|
-
}
|
|
11367
|
-
|
|
11368
|
-
function beeToggle(force) {
|
|
11369
|
-
const p = $("bee-panel");
|
|
11370
|
-
if (!p) return;
|
|
11371
|
-
const open = force !== undefined ? force : p.classList.contains("hidden");
|
|
11372
|
-
beeShow(p, open);
|
|
11373
|
-
if (open) {
|
|
11374
|
-
const v = Bee.view || {};
|
|
11375
|
-
const hasData = (v.digests || []).length > 0;
|
|
11376
|
-
beeShow($("bee-cfg"), !hasData); // 没数据时首开直落配置(可发现性)
|
|
11377
|
-
beeRenderPanel();
|
|
11378
|
-
beeMarkSeen();
|
|
11379
|
-
}
|
|
11472
|
+
beeRenderPanel();
|
|
11380
11473
|
}
|
|
11381
11474
|
|
|
11382
11475
|
async function beeMarkSeen() {
|
|
@@ -11384,7 +11477,6 @@ async function beeMarkSeen() {
|
|
|
11384
11477
|
const r = await api("/api/wxdigest/seen", { method: "POST", body: "{}" });
|
|
11385
11478
|
Bee.view = r.view;
|
|
11386
11479
|
Bee.lastUnseen = 0;
|
|
11387
|
-
beeShow($("bee-badge"), false);
|
|
11388
11480
|
beeRenderPanel();
|
|
11389
11481
|
} catch (e) { /* ignore */ }
|
|
11390
11482
|
}
|
|
@@ -11396,7 +11488,10 @@ function beeRenderPanel() {
|
|
|
11396
11488
|
$("bee-enabled").checked = !!cfg.enabled;
|
|
11397
11489
|
$("bee-reader").checked = !!cfg.reader_enabled;
|
|
11398
11490
|
beeShow($("bee-reader-box"), !!cfg.reader_enabled);
|
|
11399
|
-
|
|
11491
|
+
// 用户手动改过路径(选候选/输入)后轮询不再回填覆盖——否则 60s 一冲,
|
|
11492
|
+
// 选择根本存不进去(「选不中 Python」根因之一)
|
|
11493
|
+
if (!Bee.pyDirty && document.activeElement !== $("bee-reader-py"))
|
|
11494
|
+
$("bee-reader-py").value = cfg.reader_python || "";
|
|
11400
11495
|
if (!Bee.selGroups) Bee.selGroups = (cfg.groups || []).slice();
|
|
11401
11496
|
const n = Bee.selGroups.length;
|
|
11402
11497
|
$("bee-groups-btn").textContent = t("选择要监听的群({0})", n);
|
|
@@ -11509,6 +11604,7 @@ async function beeSave() {
|
|
|
11509
11604
|
}) });
|
|
11510
11605
|
if (Bee.view) Bee.view.config = r.config;
|
|
11511
11606
|
Bee.selGroups = null; // 下次渲染用保存后的值重建勾选态
|
|
11607
|
+
Bee.pyDirty = false; // 保存成功:服务端值与所见一致,恢复轮询回填
|
|
11512
11608
|
toast(t("群摘要设置已保存"));
|
|
11513
11609
|
beeRefresh();
|
|
11514
11610
|
} catch (e) { toast(t("保存失败:") + (e.message || e), true); }
|
|
@@ -11530,13 +11626,7 @@ async function beeScan() {
|
|
|
11530
11626
|
|
|
11531
11627
|
(function () {
|
|
11532
11628
|
const boot = () => {
|
|
11533
|
-
if (!$("bee-
|
|
11534
|
-
$("bee-fab").addEventListener("click", () => beeToggle());
|
|
11535
|
-
$("bee-x").addEventListener("click", () => beeToggle(false));
|
|
11536
|
-
$("bee-cfg-btn").addEventListener("click", () => {
|
|
11537
|
-
const c = $("bee-cfg");
|
|
11538
|
-
beeShow(c, c.classList.contains("hidden"));
|
|
11539
|
-
});
|
|
11629
|
+
if (!$("bee-save")) return;
|
|
11540
11630
|
$("bee-save").addEventListener("click", beeSave);
|
|
11541
11631
|
$("bee-scan-btn").addEventListener("click", beeScan);
|
|
11542
11632
|
$("bee-dir-pick").addEventListener("click", () => window.pickFolder("bee-dir", true));
|
|
@@ -11552,8 +11642,19 @@ async function beeScan() {
|
|
|
11552
11642
|
});
|
|
11553
11643
|
$("bee-groups-search").addEventListener("input", beeRenderGroups);
|
|
11554
11644
|
$("bee-groups-refresh").addEventListener("click", beeRefreshGroups);
|
|
11645
|
+
// 64 位 Python 自动扫描 + 一键安装 wechatauto-replica(借鉴用户诉求:
|
|
11646
|
+
// 别手填解释器路径、别自己开 conda 装依赖)
|
|
11647
|
+
$("bee-py-scan").addEventListener("click", beePyScan);
|
|
11648
|
+
$("bee-py-install").addEventListener("click", beePyInstall);
|
|
11649
|
+
// 用户手动改过路径(选候选/输入)后置脏:轮询不再回填覆盖
|
|
11650
|
+
$("bee-reader-py").addEventListener("input", () => { Bee.pyDirty = true; });
|
|
11555
11651
|
beeRefresh();
|
|
11556
11652
|
setInterval(beeRefresh, 60000);
|
|
11653
|
+
// 桌宠右键「群摘要」直达:URL 带 #goto=__wxdigest 时切到群摘要子页
|
|
11654
|
+
if (/goto=__wxdigest/.test(location.hash)) {
|
|
11655
|
+
setTimeout(() => switchTab("__wxdigest"), 800);
|
|
11656
|
+
history.replaceState(null, "", location.pathname + location.search);
|
|
11657
|
+
}
|
|
11557
11658
|
};
|
|
11558
11659
|
// 错开首屏:令牌门/状态首帧先走,群摘要轮询晚 2s 再起
|
|
11559
11660
|
if (document.readyState === "loading") {
|
|
@@ -11562,3 +11663,93 @@ async function beeScan() {
|
|
|
11562
11663
|
setTimeout(boot, 2000);
|
|
11563
11664
|
}
|
|
11564
11665
|
})();
|
|
11666
|
+
|
|
11667
|
+
/* ---- 64 位 Python 扫描/一键安装(蜜蜂坞微信直连配置)---- */
|
|
11668
|
+
let beePyTimer = null;
|
|
11669
|
+
async function beePyScan() {
|
|
11670
|
+
const btn = $("bee-py-scan"), box = $("bee-py-cands");
|
|
11671
|
+
if (!btn) return;
|
|
11672
|
+
btn.disabled = true;
|
|
11673
|
+
try { await api("/api/wxdigest/pythons/scan", { method: "POST", body: "{}" }); }
|
|
11674
|
+
catch (e) { toast(t("扫描失败:") + (e.message || e), true); btn.disabled = false; return; }
|
|
11675
|
+
box.classList.remove("hidden");
|
|
11676
|
+
box.innerHTML = '<span class="hint">' + esc(t("正在扫描本机 Python…")) + "</span>";
|
|
11677
|
+
beePyTimer = setInterval(async () => {
|
|
11678
|
+
try {
|
|
11679
|
+
const st = await api("/api/wxdigest/pythons");
|
|
11680
|
+
if (st.scanning) return;
|
|
11681
|
+
clearInterval(beePyTimer); beePyTimer = null;
|
|
11682
|
+
btn.disabled = false;
|
|
11683
|
+
beePyRender(st.results || []);
|
|
11684
|
+
} catch (e) { /* 轮询失败下一拍再试 */ }
|
|
11685
|
+
}, 1500);
|
|
11686
|
+
}
|
|
11687
|
+
function beePyRender(list) {
|
|
11688
|
+
const box = $("bee-py-cands"), st = $("bee-py-status"), ib = $("bee-py-install");
|
|
11689
|
+
if (!box) return;
|
|
11690
|
+
if (!list.length) { box.innerHTML = '<span class="hint">' + esc(t("没有找到可用的 Python,请手动填写路径")) + "</span>"; return; }
|
|
11691
|
+
const cur = ($("bee-reader-py").value || "").trim();
|
|
11692
|
+
box.innerHTML = list.map((p, idx) => {
|
|
11693
|
+
const tag = p.has_replica ? t("已装 wechatauto-replica") : t("未装依赖");
|
|
11694
|
+
const cls = "bee-py-item" + (p.path === cur ? " on" : "");
|
|
11695
|
+
return '<button type="button" class="' + cls + '" data-i="' + idx + '">' +
|
|
11696
|
+
'<span class="bee-py-path">' + esc(p.path) + "</span>" +
|
|
11697
|
+
'<span class="bee-py-meta">' + esc("Py" + p.version + " · " + p.bits + "bit · " + tag) + "</span></button>";
|
|
11698
|
+
}).join("");
|
|
11699
|
+
box.querySelectorAll(".bee-py-item").forEach((b) => {
|
|
11700
|
+
b.addEventListener("click", () => {
|
|
11701
|
+
const p = list[Number(b.dataset.i)];
|
|
11702
|
+
$("bee-reader-py").value = p.path;
|
|
11703
|
+
box.querySelectorAll(".bee-py-item").forEach((x) => x.classList.remove("on"));
|
|
11704
|
+
b.classList.add("on");
|
|
11705
|
+
beePyInstallVisibility(p);
|
|
11706
|
+
});
|
|
11707
|
+
});
|
|
11708
|
+
// 默认选中:当前输入框命中的;否则第一个已装依赖的 64 位
|
|
11709
|
+
const hit = list.find((p) => p.path === cur) ||
|
|
11710
|
+
list.find((p) => p.bits >= 64 && p.has_replica) ||
|
|
11711
|
+
list.find((p) => p.bits >= 64);
|
|
11712
|
+
if (hit) {
|
|
11713
|
+
$("bee-reader-py").value = hit.path;
|
|
11714
|
+
beePyInstallVisibility(hit);
|
|
11715
|
+
const btn = box.querySelector('.bee-py-item[data-i="' + list.indexOf(hit) + '"]');
|
|
11716
|
+
if (btn) btn.classList.add("on");
|
|
11717
|
+
}
|
|
11718
|
+
}
|
|
11719
|
+
function beePyInstallVisibility(p) {
|
|
11720
|
+
const ib = $("bee-py-install"), st = $("bee-py-status");
|
|
11721
|
+
if (!ib) return;
|
|
11722
|
+
const need = p && p.bits >= 64 && !p.has_replica;
|
|
11723
|
+
ib.classList.toggle("hidden", !need);
|
|
11724
|
+
if (st && !need) st.textContent = "";
|
|
11725
|
+
}
|
|
11726
|
+
async function beePyInstall() {
|
|
11727
|
+
const py = ($("bee-reader-py").value || "").trim();
|
|
11728
|
+
if (!py) { toast(t("请先选择或填写 Python 路径"), true); return; }
|
|
11729
|
+
const ib = $("bee-py-install"), st = $("bee-py-status");
|
|
11730
|
+
ib.disabled = true;
|
|
11731
|
+
if (st) st.textContent = t("正在安装 wechatauto-replica…(约 1-3 分钟)");
|
|
11732
|
+
try { await api("/api/wxdigest/replica/install", { method: "POST", body: JSON.stringify({ python: py }) }); }
|
|
11733
|
+
catch (e) {
|
|
11734
|
+
toast(t("安装失败:") + (e.message || e), true);
|
|
11735
|
+
ib.disabled = false;
|
|
11736
|
+
return;
|
|
11737
|
+
}
|
|
11738
|
+
const timer = setInterval(async () => {
|
|
11739
|
+
try {
|
|
11740
|
+
const s = await api("/api/wxdigest/replica/status");
|
|
11741
|
+
if (!s.running) {
|
|
11742
|
+
clearInterval(timer); timer = null;
|
|
11743
|
+
ib.disabled = false;
|
|
11744
|
+
if (s.ok) {
|
|
11745
|
+
if (st) st.textContent = t("安装完成,依赖已就绪");
|
|
11746
|
+
toast(t("wechatauto-replica 安装完成"));
|
|
11747
|
+
} else {
|
|
11748
|
+
if (st) st.textContent = (s.log || []).slice(-3).join(" ").slice(0, 200);
|
|
11749
|
+
toast(t("安装失败,详见状态行"), true);
|
|
11750
|
+
}
|
|
11751
|
+
beePyScan(); // 重扫刷新 has_replica 标记
|
|
11752
|
+
}
|
|
11753
|
+
} catch (e) { /* 轮询失败下一拍再试 */ }
|
|
11754
|
+
}, 3000);
|
|
11755
|
+
}
|
package/app/ui/i18n.js
CHANGED
|
@@ -28,6 +28,24 @@
|
|
|
28
28
|
// —— 表单字段/标签 ——
|
|
29
29
|
|
|
30
30
|
// —— 智能/手动 模式 ——
|
|
31
|
+
"编排模式": "Orchestration mode",
|
|
32
|
+
"自动(推荐):按任务类型与风险动态决定步骤": "Auto (recommended): adapt steps to task type and risk",
|
|
33
|
+
"快速:少步骤优先,先做确定性验收": "Fast: fewer steps with deterministic checks first",
|
|
34
|
+
"专家:完整规划、多评审、失败修复与换将": "Expert: full planning, reviews, repairs, and fallback agents",
|
|
35
|
+
"手动:自行指定实现者与评审组": "Manual: choose implementer and reviewers",
|
|
36
|
+
"思考程度": "Reasoning effort",
|
|
37
|
+
"自动:随任务难度调整": "Auto: adapt to task difficulty",
|
|
38
|
+
"快速:少推理、优先响应": "Fast: less reasoning, faster response",
|
|
39
|
+
"标准:平衡质量与速度": "Standard: balance quality and speed",
|
|
40
|
+
"深度:更多推理与检查": "Deep: more reasoning and checks",
|
|
41
|
+
"对话模型(可选)": "Chat model (optional)",
|
|
42
|
+
"对话厂商": "Chat provider",
|
|
43
|
+
"对话模型": "Chat model",
|
|
44
|
+
"随厂商推荐": "Provider default",
|
|
45
|
+
"只对本次对话生效,不会修改全局绑定;指定渠道不可用时会明确报错。": "Applies only to this chat and does not change global bindings. An unavailable selected route returns an explicit error.",
|
|
46
|
+
"推荐任务类型": "Recommended task type",
|
|
47
|
+
"切换类型": "Switch type",
|
|
48
|
+
"这个需求更适合「{0}」流程,能使用对应的规划与质量门禁。是否切换后执行?": "This request fits the {0} workflow, which provides matching planning and quality gates. Switch before running?",
|
|
31
49
|
|
|
32
50
|
// —— 引擎/角色 ——
|
|
33
51
|
|
|
@@ -748,6 +766,12 @@
|
|
|
748
766
|
"常用类型": "Common types",
|
|
749
767
|
"预估:≈{0}{1}": "Est: ≈{0}{1}",
|
|
750
768
|
"近{0}天 · {1}次同类": "last {0}d · {1} similar runs",
|
|
769
|
+
"暂无同类历史,按流程基线估算": "No similar history; estimated from the workflow baseline",
|
|
770
|
+
"预计完成约 {0}": "Estimated completion: about {0}",
|
|
771
|
+
"预计完成约 {0},保守不超过 {1}": "Estimated completion: about {0}; conservative bound {1}",
|
|
772
|
+
"预计剩余约 {0}": "About {0} remaining",
|
|
773
|
+
"已超过常规预估,仍在保守区间内(约剩 {0})": "Past the usual estimate but within the conservative bound (about {0} remaining)",
|
|
774
|
+
"已超过保守预估,正在继续执行,可查看步骤日志": "Past the conservative estimate; still running. Check step logs for progress",
|
|
751
775
|
"候选并发数(单稿赛马,1=关)": "Best-of-N candidates (1=off)",
|
|
752
776
|
"同时起草多份候选稿,终审择优并吸收落选稿优点;每多一路多一份起草开销": "Draft several candidates in parallel; a final selector picks the best and harvests the losers' strengths. Each extra candidate costs one more draft",
|
|
753
777
|
"建 PR": "Create PR",
|
|
@@ -1894,6 +1918,18 @@
|
|
|
1894
1918
|
"反馈摘要生成失败:": "Failed to build issue summary: ",
|
|
1895
1919
|
"端口占用": "Port Usage",
|
|
1896
1920
|
"扫描本机端口": "Scan local ports",
|
|
1921
|
+
"扫描": "Scan",
|
|
1922
|
+
"一键安装 wechatauto-replica": "Install wechatauto-replica",
|
|
1923
|
+
"已装 wechatauto-replica": "replica installed",
|
|
1924
|
+
"未装依赖": "dependency missing",
|
|
1925
|
+
"正在扫描本机 Python…": "Scanning local Pythons…",
|
|
1926
|
+
"没有找到可用的 Python,请手动填写路径": "No usable Python found — please fill in the path manually",
|
|
1927
|
+
"请先选择或填写 Python 路径": "Pick or type a Python path first",
|
|
1928
|
+
"正在安装 wechatauto-replica…(约 1-3 分钟)": "Installing wechatauto-replica… (1-3 minutes)",
|
|
1929
|
+
"安装完成,依赖已就绪": "Installed — dependency ready",
|
|
1930
|
+
"安装失败,详见状态行": "Install failed — see status line",
|
|
1931
|
+
"扫描失败:": "Scan failed: ",
|
|
1932
|
+
"安装失败:": "Install failed: ",
|
|
1897
1933
|
"谁占着端口、属于哪个项目一目了然;「仅本机」的端口外网不可达。关闭只发温和信号,系统进程与本服务自身拒绝关闭。": "See at a glance which process holds each port and which project it belongs to; \"local only\" ports are unreachable from outside. Close sends a gentle termination signal only — system processes and this service itself are refused.",
|
|
1898
1934
|
"端口": "Port",
|
|
1899
1935
|
"进程": "Process",
|