codebee 0.1.16 → 0.1.18
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 +18 -2
- package/README.md +3 -3
- package/app/core/jobs.py +23 -10
- package/app/core/knowledge.py +371 -0
- package/app/core/pipeline.py +91 -7
- package/app/core/store.py +13 -1
- package/app/main.py +13 -0
- package/app/ui/app.js +159 -15
- package/app/ui/i18n.js +196 -2
- package/app/ui/index.html +29 -8
- package/app/ui/style.css +4 -0
- package/package.json +1 -1
package/app/core/pipeline.py
CHANGED
|
@@ -19,7 +19,7 @@ import re
|
|
|
19
19
|
import threading
|
|
20
20
|
import time
|
|
21
21
|
|
|
22
|
-
from . import aiflavor, catalog, history, jobs, manager, modelhub, mocks, paihang, planner, registry, router, runner, skills, store, usage
|
|
22
|
+
from . import aiflavor, catalog, history, jobs, knowledge, manager, modelhub, mocks, paihang, planner, registry, router, runner, skills, store, usage
|
|
23
23
|
from . import builtin_agent
|
|
24
24
|
from . import diagnostics
|
|
25
25
|
from . import paths as paths_mod
|
|
@@ -82,6 +82,9 @@ def _agents():
|
|
|
82
82
|
# 本轮运行的智能体池:execute_run 入口快照,_run_step 死链补位时扫描。
|
|
83
83
|
# 直接调 _run_step 的场景(单测/内部工具)池为空 → 补位不触发,闸门语义不变。
|
|
84
84
|
_CURRENT_AGENTS: list = []
|
|
85
|
+
# 本次 run 的任务宪章注入块(execute_run 起跑时从 .codebee/constitution.md 读入;
|
|
86
|
+
# 步骤函数在各提示词组装点引用,空串=未配置零噪音)
|
|
87
|
+
_RUN_CONSTITUTION = ""
|
|
85
88
|
|
|
86
89
|
|
|
87
90
|
def _dead_binding_substitute(dead_id, resume=None):
|
|
@@ -660,6 +663,11 @@ CODE_REVIEW_PROMPT = """你是代码评审员(不要修改任何文件)。
|
|
|
660
663
|
"summary": "一句话结论"
|
|
661
664
|
}
|
|
662
665
|
|
|
666
|
+
## 评审要求(findings 锚定证据)
|
|
667
|
+
每个 issue 的 detail 必须给出「文件名:行号」(从 diff 的 hunk 头 @@ -a,b +c,d @@ 与上下文推算),
|
|
668
|
+
并引用该处一行关键代码作为依据——没有证据定位的问题不要报,宁可少报不报猜测。
|
|
669
|
+
(借鉴 pr-af:findings grounded in code evidence 是评审可信度的根。)
|
|
670
|
+
|
|
663
671
|
## 任务目标
|
|
664
672
|
__GOAL__
|
|
665
673
|
|
|
@@ -792,7 +800,7 @@ def _code_bestof(run, task, impl, difficulty, ev):
|
|
|
792
800
|
agt_b = modelhub.bind_agent(impl, difficulty)
|
|
793
801
|
res = None
|
|
794
802
|
for i, sub in enumerate(subtasks):
|
|
795
|
-
prompt = (CODE_IMPL_PROMPT
|
|
803
|
+
prompt = (_RUN_CONSTITUTION + CODE_IMPL_PROMPT
|
|
796
804
|
.replace("__GOAL__", task["goal"])
|
|
797
805
|
.replace("__SUBTASK__", sub["detail"] if sub["detail"] else sub["title"])
|
|
798
806
|
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
@@ -941,7 +949,7 @@ def _run_code(run, task, agents, ev, stats, mode):
|
|
|
941
949
|
(s.get("title") or "")[:40] for s in subtasks[:i]) or "(无)"
|
|
942
950
|
prog = ("\n\n## 计划进度(第 %d/%d 项)\n已完成:%s。当前接着做下面这一项,"
|
|
943
951
|
"不要重做已完成的。" % (i + 1, len(subtasks), done_titles))
|
|
944
|
-
prompt = (CODE_IMPL_PROMPT
|
|
952
|
+
prompt = (_RUN_CONSTITUTION + CODE_IMPL_PROMPT
|
|
945
953
|
.replace("__GOAL__", task["goal"])
|
|
946
954
|
.replace("__SUBTASK__",
|
|
947
955
|
sub["detail"] if sub["detail"] else sub["title"])
|
|
@@ -1396,10 +1404,25 @@ __GOAL__
|
|
|
1396
1404
|
## 背景与上下文
|
|
1397
1405
|
__CONTEXT__
|
|
1398
1406
|
|
|
1407
|
+
## 评审维度(写前自查)
|
|
1408
|
+
__RUBRIC__
|
|
1409
|
+
(按这些维度组织内容重点——评审会按它们打分)
|
|
1410
|
+
|
|
1399
1411
|
## 要求
|
|
1400
1412
|
- 只修改 `__FILE__` 这一个文件;保持 Markdown 结构。
|
|
1401
1413
|
- 完成后用 3 句话说明本轮写了什么。"""
|
|
1402
1414
|
|
|
1415
|
+
# 调研报告类稿件的追加要求(借鉴 gpt-researcher 迭代深研):有网络/读文件工具时
|
|
1416
|
+
# 多源交叉验证,单源结论降权——调研的可信度来自证据链而非文采
|
|
1417
|
+
RESEARCH_APPENDIX = """
|
|
1418
|
+
|
|
1419
|
+
## 调研要求(证据链)
|
|
1420
|
+
- 有联网/检索工具就先搜集资料再写:同一关键结论至少两个独立来源交叉验证,
|
|
1421
|
+
单源信息要标注「仅单一来源」。
|
|
1422
|
+
- 引用来源在文中用行内链接或脚注标明(域名即可,不编造 URL)。
|
|
1423
|
+
- 区分「事实」与「观点」:数据/时间/版本号给来源,预测/评价标明是分析。
|
|
1424
|
+
- 结构建议:结论先行 → 分层论证 → 风险与局限(说明哪些结论证据不足)。"""
|
|
1425
|
+
|
|
1403
1426
|
NOVEL_REVISE_PROMPT = """你是一名专业作者。请根据下方汇总评审意见修订稿件文件:`__FILE__`(直接写入该文件)。文件必须以 UTF-8 编码保存(PowerShell 写文件显式加 -Encoding UTF8,禁止依赖默认编码)。
|
|
1404
1427
|
|
|
1405
1428
|
## 原始写作任务
|
|
@@ -1465,7 +1488,10 @@ def _story_bible(workdir):
|
|
|
1465
1488
|
if not txt:
|
|
1466
1489
|
return ""
|
|
1467
1490
|
return ("## 故事圣经(story-bible.md:人物/世界观/伏笔台账,本书一切写作与评审以此为准,"
|
|
1468
|
-
"与其冲突处以圣经为准)\n\n"
|
|
1491
|
+
"与其冲突处以圣经为准)\n\n"
|
|
1492
|
+
"**连续性锁**(借鉴 drama-skills):人物外貌/性格口头禅/物品/能力等需要跨章一致"
|
|
1493
|
+
"的设定,写作时原样沿用圣经中的表述(可整句贴入正文),不要同义改写——每章用词"
|
|
1494
|
+
"一致读者才不会出戏。\n\n" + txt)
|
|
1469
1495
|
|
|
1470
1496
|
|
|
1471
1497
|
def _plot_modules(workdir):
|
|
@@ -1733,6 +1759,10 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1733
1759
|
mods = _plot_modules(workdir)
|
|
1734
1760
|
if mods:
|
|
1735
1761
|
bible = (bible + "\n\n" + mods) if bible else mods
|
|
1762
|
+
# 任务宪章(spec-kit constitution):作者定的质量原则拼在注入块最前——
|
|
1763
|
+
# 优先级最高的约束放最前面,写作者先读原则再读设定
|
|
1764
|
+
if _RUN_CONSTITUTION:
|
|
1765
|
+
bible = _RUN_CONSTITUTION + (bible or "")
|
|
1736
1766
|
|
|
1737
1767
|
|
|
1738
1768
|
def crit_prompt_for(text, note=""):
|
|
@@ -1747,6 +1777,9 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1747
1777
|
tpl = tpl.replace("## 待评审稿件", "%s\n\n## 待评审稿件" % sk, 1)
|
|
1748
1778
|
if bible:
|
|
1749
1779
|
tpl = tpl.replace("## 待评审稿件", "%s\n\n## 待评审稿件" % bible, 1)
|
|
1780
|
+
kb = knowledge.block_for(task)
|
|
1781
|
+
if kb:
|
|
1782
|
+
tpl = tpl.replace("## 待评审稿件", "%s\n\n## 待评审稿件" % kb, 1)
|
|
1750
1783
|
return tpl.replace("__DIMKEYS__", dimkey).replace(
|
|
1751
1784
|
"__MANUSCRIPT__", text or "(稿件为空!)")
|
|
1752
1785
|
|
|
@@ -1944,6 +1977,9 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
1944
1977
|
sk_block, _ = skills.block_for(task, stable_order=True)
|
|
1945
1978
|
if bible:
|
|
1946
1979
|
sk_block = (sk_block + "\n\n" + bible) if sk_block else bible
|
|
1980
|
+
kb_block = knowledge.block_for(task)
|
|
1981
|
+
if kb_block:
|
|
1982
|
+
sk_block = (sk_block + "\n\n" + kb_block) if sk_block else kb_block
|
|
1947
1983
|
scope = ("本章 = 大纲第 %d 章" % i) if start == 1 else (
|
|
1948
1984
|
"本批为第 %d–%d 章,下列按全书章号列出各章要点" % (start, end))
|
|
1949
1985
|
|
|
@@ -2397,10 +2433,25 @@ def _run_serial_review(run, task, agents, ev, stats, mode, critics, impl, route,
|
|
|
2397
2433
|
fixed.append(i)
|
|
2398
2434
|
store.update_run(run_id, chapter_scores=chapter_scores)
|
|
2399
2435
|
_check_cancel(ev)
|
|
2436
|
+
if not fixed:
|
|
2437
|
+
# 最弱章重改全部失败(供应商拥堵/流断等)→ 章稿没有任何变化,
|
|
2438
|
+
# 继续跑全书重评只会白烧评审链,组长步骤一挂「工作中」就是几十
|
|
2439
|
+
# 分钟(2026-09-19 实案:polish-c18/c11 双败后仍进全书重评,
|
|
2440
|
+
# 单个评审 35 分钟,用户侧只见打磨 2/3 久卡不动)。直接收尾。
|
|
2441
|
+
store.finish_step(run_id, pstep["n"], "failed",
|
|
2442
|
+
summary="重改未成功(%s 全部失败),本轮打磨中止;"
|
|
2443
|
+
"已落盘章稿不受影响"
|
|
2444
|
+
% "、".join("第 %d 章" % c["chapter"] for c in weak))
|
|
2445
|
+
break
|
|
2400
2446
|
# 重评全书一致性(同一评审闭包;本轮全挂则保留上一轮结论——评审链挂了
|
|
2401
2447
|
# 不代表书变差,不能拿「无法评审」覆盖真实分数)
|
|
2402
|
-
|
|
2403
|
-
|
|
2448
|
+
try:
|
|
2449
|
+
full_text = "\n\n".join(_read_chapter(workdir, i2) for i2 in range(1, end + 1))
|
|
2450
|
+
gscored2, gmeans_acc2 = run_global_round(critics)
|
|
2451
|
+
except BaseException:
|
|
2452
|
+
store.finish_step(run_id, pstep["n"], "failed",
|
|
2453
|
+
summary="打磨后全书重评异常中止,已落盘章稿不受影响")
|
|
2454
|
+
raise
|
|
2404
2455
|
if gscored2:
|
|
2405
2456
|
global_means = {d: round(sum(xs) / len(xs), 1) for d, xs in gmeans_acc2.items()}
|
|
2406
2457
|
global_pass = _all_ge(global_means, threshold)
|
|
@@ -2659,13 +2710,19 @@ def _run_content_review(run, task, agents, ev, stats, mode):
|
|
|
2659
2710
|
except Exception:
|
|
2660
2711
|
pass
|
|
2661
2712
|
else:
|
|
2713
|
+
is_research = task.get("type") == "research"
|
|
2714
|
+
|
|
2662
2715
|
def _draft_prompt_for(vfile):
|
|
2663
2716
|
p = (_tpl(task, "draft_prompt", NOVEL_DRAFT_PROMPT).replace("__FILE__", vfile)
|
|
2664
2717
|
.replace("__GOAL__", task["goal"])
|
|
2665
|
-
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
2718
|
+
.replace("__CONTEXT__", task.get("context") or "(无)")
|
|
2719
|
+
.replace("__RUBRIC__", "、".join(dims) if dims else "(按流程默认维度)"))
|
|
2666
2720
|
if outline:
|
|
2667
2721
|
p += "\n\n## 编排者大纲(按要点组织稿件)\n" + \
|
|
2668
2722
|
"\n".join("- " + i for i in outline["items"])
|
|
2723
|
+
if is_research:
|
|
2724
|
+
# 调研报告追加证据链要求(gpt-researcher 借鉴)
|
|
2725
|
+
p += RESEARCH_APPENDIX
|
|
2669
2726
|
return p
|
|
2670
2727
|
|
|
2671
2728
|
best_of = max(1, min(3, int(task.get("best_of") or 1)))
|
|
@@ -2871,6 +2928,23 @@ def _run_serial_qa(run, task, agents, ev):
|
|
|
2871
2928
|
error="答疑失败(执行/评审链不可用)——" + ";".join(errors[-3:]))
|
|
2872
2929
|
|
|
2873
2930
|
|
|
2931
|
+
def _read_constitution(workdir):
|
|
2932
|
+
"""任务宪章(借鉴 spec-kit constitution):工作目录 .codebee/constitution.md
|
|
2933
|
+
(作者手工维护的质量原则——代码规范/文风/测试要求)。存在且非空时返回注入块,
|
|
2934
|
+
每次 run 的所有智能体提示词都会带上;否则返回 ""(零配置零噪音)。"""
|
|
2935
|
+
p = os.path.abspath(os.path.join(str(workdir or ""), ".codebee", "constitution.md"))
|
|
2936
|
+
if not _inside(workdir, p) or not os.path.isfile(p):
|
|
2937
|
+
return ""
|
|
2938
|
+
try:
|
|
2939
|
+
txt = _read_text_any_enc(p)[:6000].strip()
|
|
2940
|
+
except OSError:
|
|
2941
|
+
return ""
|
|
2942
|
+
if not txt:
|
|
2943
|
+
return ""
|
|
2944
|
+
return ("## 项目宪章(constitution.md:本项目一切产出的质量原则,优先级最高,"
|
|
2945
|
+
"与其他要求冲突时以宪章为准)\n\n" + txt + "\n\n")
|
|
2946
|
+
|
|
2947
|
+
|
|
2874
2948
|
def _write_task_spec(task, workdir):
|
|
2875
2949
|
"""任务规格落盘 .codebee/spec.md(借鉴 agent-orchestrator 的 .spec/PROMPT.md 与
|
|
2876
2950
|
planning-with-files 的文件化计划):任务定义随工作目录留存、随任务分支版本化,
|
|
@@ -3028,6 +3102,11 @@ def execute_run(run_id):
|
|
|
3028
3102
|
# 任务规格文件化(借鉴 planning-with-files/agent-orchestrator):任何任务都在
|
|
3029
3103
|
# 工作目录留一份 .codebee/spec.md——原始意图可见、随任务分支版本化
|
|
3030
3104
|
_write_task_spec(task, task["workdir"])
|
|
3105
|
+
# 任务宪章(借鉴 spec-kit constitution):工作目录 .codebee/constitution.md
|
|
3106
|
+
# 是作者定下的质量原则(代码规范/文风/测试要求),每次 run 注入所有智能体
|
|
3107
|
+
# 提示词——代码/文章/翻译全类型通用,一次定义持续生效。缺省零噪音。
|
|
3108
|
+
global _RUN_CONSTITUTION
|
|
3109
|
+
_RUN_CONSTITUTION = _read_constitution(task["workdir"])
|
|
3031
3110
|
agents = _agents()
|
|
3032
3111
|
global _CURRENT_AGENTS
|
|
3033
3112
|
_CURRENT_AGENTS = agents
|
|
@@ -3117,3 +3196,8 @@ def execute_run(run_id):
|
|
|
3117
3196
|
skills.learn_async(run_id)
|
|
3118
3197
|
except Exception:
|
|
3119
3198
|
pass
|
|
3199
|
+
# 知识库闭环:从产出材料提炼可复用知识条目(草稿态,人工转正后参与注入)
|
|
3200
|
+
try:
|
|
3201
|
+
knowledge.learn_async(run_id)
|
|
3202
|
+
except Exception:
|
|
3203
|
+
pass
|
package/app/core/store.py
CHANGED
|
@@ -621,11 +621,23 @@ def update_run(run_id, expected_status=None, **fields):
|
|
|
621
621
|
if expected_status is not None and run.get("status") != expected_status:
|
|
622
622
|
return None
|
|
623
623
|
run.update(fields)
|
|
624
|
+
# 终态收尸:run 已结束却还挂 queued/running 的步骤统一落 cancelled,
|
|
625
|
+
# 语义与 recover_orphaned_runs 的启动清扫对齐(那套清跨进程遗留,
|
|
626
|
+
# 这套在落终态瞬间即时收口——打磨组长被取消/异常打断时 UI 不闪
|
|
627
|
+
# 假「工作中」)。run 终态后不可能还有步骤在真跑。
|
|
628
|
+
st = fields.get("status")
|
|
629
|
+
if st in ("done", "failed", "cancelled"):
|
|
630
|
+
end_s = time.strftime("%H:%M:%S")
|
|
631
|
+
for s in run["steps"]:
|
|
632
|
+
if s.get("status") in ("queued", "running"):
|
|
633
|
+
s["status"] = "cancelled"
|
|
634
|
+
s["ended_at"] = s.get("ended_at") or end_s
|
|
635
|
+
if not s.get("summary"):
|
|
636
|
+
s["summary"] = "步骤未正常收尾(终态自动恢复)"
|
|
624
637
|
_save_json(paths.RUNS_DIR / run_id / "run.json", run)
|
|
625
638
|
# 状态回填:起跑与结束都同步任务状态。只回填终态的话,run 在跑、
|
|
626
639
|
# 任务永远显示「排队中」(2026-09-18 实案:「# 重写·续」run 已 running
|
|
627
640
|
# 写了一小时,任务卡 queued,用户以为卡死连点重试)。
|
|
628
|
-
st = fields.get("status")
|
|
629
641
|
if st in ("running", "done", "failed", "cancelled"):
|
|
630
642
|
tid = run.get("task_id")
|
|
631
643
|
task = _TASKS.get(tid) if tid else None
|
package/app/main.py
CHANGED
|
@@ -233,6 +233,9 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
233
233
|
if path == "/api/skills":
|
|
234
234
|
from core import skills
|
|
235
235
|
return self._json(200, skills.view())
|
|
236
|
+
if path == "/api/knowledge":
|
|
237
|
+
from core import knowledge
|
|
238
|
+
return self._json(200, knowledge.view())
|
|
236
239
|
if path == "/api/settings":
|
|
237
240
|
return self._json(200, dict(settings.load(), **jobs.workers_info()))
|
|
238
241
|
if path == "/api/settings-v2":
|
|
@@ -835,6 +838,16 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
835
838
|
from core import skills
|
|
836
839
|
n = skills.learn_from_run((self._body().get("run_id") or ""))
|
|
837
840
|
return self._json(200, {"ok": True, "learned": n})
|
|
841
|
+
if path == "/api/knowledge/op":
|
|
842
|
+
from core import knowledge
|
|
843
|
+
body = self._body()
|
|
844
|
+
err = knowledge.entry_op(body.get("id") or "", body.get("op") or "",
|
|
845
|
+
fields=body.get("fields"))
|
|
846
|
+
return self._json(400, {"error": err}) if err else self._json(200, {"ok": True})
|
|
847
|
+
if path == "/api/knowledge/learn":
|
|
848
|
+
from core import knowledge
|
|
849
|
+
n = knowledge.learn_from_run((self._body().get("run_id") or ""))
|
|
850
|
+
return self._json(200, {"ok": True, "learned": n})
|
|
838
851
|
m = re.match(r"^/api/flows/([^/]+)/reset$", path)
|
|
839
852
|
if m:
|
|
840
853
|
err = flows.reset_flow(m.group(1))
|
package/app/ui/app.js
CHANGED
|
@@ -1679,7 +1679,7 @@ async function openImportDialog() {
|
|
|
1679
1679
|
'<input type="checkbox" value="' + esc(s.id) + '"' + (ok ? " checked" : " disabled") +
|
|
1680
1680
|
(ok ? ' onchange="updateImportSelHint()"' : "") + ">" +
|
|
1681
1681
|
'<div class="src-main">' +
|
|
1682
|
-
'<div class="src-name">' + esc(s.name) + status + "</div>" +
|
|
1682
|
+
'<div class="src-name">' + esc(t(s.name)) + status + "</div>" +
|
|
1683
1683
|
'<div class="src-desc">' + esc(t(s.desc)) + "</div>" +
|
|
1684
1684
|
'<div class="src-path">' + esc((s.paths || []).join(" · ")) + "</div>" +
|
|
1685
1685
|
(s.note ? '<div class="src-note">' + esc(t.apply(null, [s.note].concat(s.note_args || []))) + "</div>" : "") +
|
|
@@ -3846,6 +3846,7 @@ function renderBookMetaPanel(task) {
|
|
|
3846
3846
|
let html = '<div class="bm-head"><svg class="ico" aria-hidden="true"><use href="#i-idcard"/></svg>' +
|
|
3847
3847
|
'<span class="sec-title">' + t("作品信息") + "</span>" +
|
|
3848
3848
|
'<span class="hint">' + esc(t("按发布平台生成建书表单资料,逐字段复制过去")) + "</span>" +
|
|
3849
|
+
'<button class="hhelp" data-help-topic="publish" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' +
|
|
3849
3850
|
'<span class="flex1"></span>';
|
|
3850
3851
|
if (taskBusy) html += '<span class="bm-warn">' + esc(t("任务运行中,生成将在本轮结束后可用")) + "</span>";
|
|
3851
3852
|
html += "</div>";
|
|
@@ -4284,6 +4285,7 @@ function _renderGitSnapshot(run, task) {
|
|
|
4284
4285
|
let html =
|
|
4285
4286
|
'<div class="git-head"><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg>' +
|
|
4286
4287
|
'<span class="sec-title">' + t("代码版本隔离") + '</span>' +
|
|
4288
|
+
'<button class="hhelp" data-help-topic="code" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' +
|
|
4287
4289
|
'<code class="git-branch" title="' + esc(t("任务分支:产物提交在此,原分支未受影响")) + '">' + esc(g.branch) + "</code>";
|
|
4288
4290
|
const addT = ch.add_total, delT = ch.del_total;
|
|
4289
4291
|
if (addT != null && delT != null) {
|
|
@@ -4379,7 +4381,8 @@ function renderGitWbMain(d, task) {
|
|
|
4379
4381
|
sel += "</select>";
|
|
4380
4382
|
let html =
|
|
4381
4383
|
'<div class="git-head"><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg>' +
|
|
4382
|
-
'<span class="sec-title">' + t("Git 工作台") + "</span>" +
|
|
4384
|
+
'<span class="sec-title">' + t("Git 工作台") + "</span>" +
|
|
4385
|
+
'<button class="hhelp" data-help-topic="code" title="' + esc(t("这是什么?点开看帮助")) + '" aria-label="' + esc(t("帮助")) + '"><svg class="ico" aria-hidden="true"><use href="#i-help"></use></svg></button>' + sel +
|
|
4383
4386
|
'<code class="git-branch" title="HEAD">' + esc(d.head || "") + "</code>" +
|
|
4384
4387
|
(d.upstream ? '<span class="gm" title="' + esc(d.upstream) + '">↑' + (d.ahead || 0) + " ↓" + (d.behind || 0) + "</span>" : "") +
|
|
4385
4388
|
'<span class="flex1"></span>' +
|
|
@@ -7519,6 +7522,126 @@ async function skillLessonOp(id, op) {
|
|
|
7519
7522
|
loadSkills();
|
|
7520
7523
|
}
|
|
7521
7524
|
|
|
7525
|
+
/* ---------------------------------------------------------- 知识库 */
|
|
7526
|
+
/* 运行产出自动提炼(草稿→人工转正)+ 手动新建的个人知识库;
|
|
7527
|
+
* 进页拉一次,操作后重拉,搜索/状态/标签过滤全在客户端。 */
|
|
7528
|
+
let KB = { data: null, q: "", tag: "", status: "" };
|
|
7529
|
+
|
|
7530
|
+
async function loadKnowledge() {
|
|
7531
|
+
try { KB.data = await api("/api/knowledge"); } catch (e) { KB.data = null; }
|
|
7532
|
+
renderKnowledge();
|
|
7533
|
+
}
|
|
7534
|
+
|
|
7535
|
+
function renderKnowledge() {
|
|
7536
|
+
const box = $("kb-cards");
|
|
7537
|
+
if (!box || !KB.data) return;
|
|
7538
|
+
const entries = KB.data.entries || [];
|
|
7539
|
+
// 状态 chips:全部 / 草稿 / 已转正(转正=人工确认过、参与注入)
|
|
7540
|
+
const stBox = $("kb-status-chips");
|
|
7541
|
+
if (stBox) {
|
|
7542
|
+
const cnt = (s) => s ? entries.filter((x) => (x.status || "draft") === s).length : entries.length;
|
|
7543
|
+
stBox.innerHTML = [["", "全部"], ["draft", "草稿"], ["approved", "已转正"]].map((p) =>
|
|
7544
|
+
'<button class="cat-chip' + (KB.status === p[0] ? " active" : "") + '" onclick="kbSetFilter(\'status\',\'' + p[0] + '\')">' +
|
|
7545
|
+
esc(t(p[1])) + "<b>" + cnt(p[0]) + "</b></button>").join("");
|
|
7546
|
+
}
|
|
7547
|
+
const tags = KB.data.tags || [];
|
|
7548
|
+
const tagBox = $("kb-tag-chips");
|
|
7549
|
+
if (tagBox) {
|
|
7550
|
+
tagBox.innerHTML = tags.length
|
|
7551
|
+
? '<button class="cat-chip' + (!KB.tag ? " active" : "") + '" onclick="kbSetFilter(\'tag\',\'\')">' + esc(t("全部标签")) + "</button>" +
|
|
7552
|
+
tags.map((tg) => '<button class="cat-chip' + (KB.tag === tg ? " active" : "") +
|
|
7553
|
+
'" onclick="kbSetFilter(\'tag\',\'' + esc(tg) + '\')">' + esc(tg) + "<b>" + (KB.data.tag_counts[tg] || 0) + "</b></button>").join("")
|
|
7554
|
+
: "";
|
|
7555
|
+
}
|
|
7556
|
+
let shown = entries;
|
|
7557
|
+
if (KB.status) shown = shown.filter((x) => (x.status || "draft") === KB.status);
|
|
7558
|
+
if (KB.tag) shown = shown.filter((x) => (x.tags || []).includes(KB.tag));
|
|
7559
|
+
if (KB.q) shown = shown.filter((x) =>
|
|
7560
|
+
(x.title || "").toLowerCase().includes(KB.q) || (x.body || "").toLowerCase().includes(KB.q) ||
|
|
7561
|
+
(x.tags || []).some((tg) => tg.toLowerCase().includes(KB.q)));
|
|
7562
|
+
const cnt = $("kb-count");
|
|
7563
|
+
if (cnt) cnt.textContent = shown.length ? shown.length + t(" / 共 ") + entries.length + t(" 条") : t("(暂无)");
|
|
7564
|
+
box.innerHTML = shown.map(kbCard).join("") ||
|
|
7565
|
+
'<div class="empty">' + t("还没有知识条目——完成一次产出型任务后自动提炼(草稿态待转正),也可以点右上角手动新建。") + "</div>";
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
function kbCard(x) {
|
|
7569
|
+
const draft = (x.status || "draft") !== "approved";
|
|
7570
|
+
return '<div class="card' + (x.enabled === false ? " off" : "") + '" data-kb-id="' + esc(x.id) + '"><div class="head">' +
|
|
7571
|
+
'<span class="name">' + esc(x.title) + "</span>" +
|
|
7572
|
+
(draft ? '<span class="tag warn">' + esc(t("草稿")) + "</span>" : '<span class="tag ok">' + esc(t("已转正")) + "</span>") +
|
|
7573
|
+
(x.stale ? '<span class="tag" title="' + esc(t("事实较旧,注入时会标注可能过期")) + '">' + esc(t("可能过期")) + "</span>" : "") +
|
|
7574
|
+
'<span class="tag">' + esc(x.scope === "*" ? t("通用") : x.scope) + "</span>" +
|
|
7575
|
+
(x.tags || []).map((tg) => '<span class="tag" onclick="kbSetFilter(\'tag\',\'' + esc(tg) + '\')" title="' + esc(t("只看该标签")) + '">' + esc(tg) + "</span>").join("") +
|
|
7576
|
+
(x.seen > 1 ? '<span class="tag">' + t("出现 ") + x.seen + t(" 次") + "</span>" : "") +
|
|
7577
|
+
(x.hits ? '<span class="tag">' + t("已注入 ") + x.hits + t(" 次") + "</span>" : "") +
|
|
7578
|
+
(x.enabled === false ? '<span class="tag">' + t("已停用") + "</span>" : "") + "</div>" +
|
|
7579
|
+
'<div class="note">' + esc(x.body) + "</div>" +
|
|
7580
|
+
'<div class="facts">' + (x.as_of ? t("事实截至 ") + esc(x.as_of) : "") +
|
|
7581
|
+
(x.source ? (x.as_of ? " · " : "") + '<a href="#" onclick="openRun(\'' + esc(x.source) + '\');return false">' + t("来源运行") + "</a>" : "") + "</div>" +
|
|
7582
|
+
'<div class="ops">' +
|
|
7583
|
+
(draft ? '<button class="ghost small" onclick="kbOp(\'' + esc(x.id) + '\', \'approve\')">' + t("转正") + "</button>" : "") +
|
|
7584
|
+
'<button class="ghost small" onclick="kbEditOpen(\'' + esc(x.id) + '\')">' + t("编辑") + "</button>" +
|
|
7585
|
+
'<button class="ghost small" onclick="kbOp(\'' + esc(x.id) + '\', \'' + (x.enabled === false ? "enable" : "disable") + '\')">' + (x.enabled === false ? t("启用") : t("停用")) + "</button>" +
|
|
7586
|
+
'<button class="danger small" onclick="kbOp(\'' + esc(x.id) + '\', \'delete\')">' + t("删除") + "</button>" +
|
|
7587
|
+
"</div></div>";
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
function kbSetFilter(k, v) {
|
|
7591
|
+
KB[k] = v || "";
|
|
7592
|
+
renderKnowledge(); // 客户端过滤,无需重拉
|
|
7593
|
+
}
|
|
7594
|
+
|
|
7595
|
+
function kbSearch(v) {
|
|
7596
|
+
KB.q = String(v || "").trim().toLowerCase();
|
|
7597
|
+
renderKnowledge();
|
|
7598
|
+
}
|
|
7599
|
+
|
|
7600
|
+
async function kbOp(id, op) {
|
|
7601
|
+
if (op === "delete" && !await uiConfirm(t("删除这条知识?转正条目不再注入,删除后不可恢复。"), { ok: t("删除"), danger: true })) return;
|
|
7602
|
+
try { await api("/api/knowledge/op", { method: "POST", body: JSON.stringify({ id, op }) }); }
|
|
7603
|
+
catch (e) { toast(e.message, true); return; }
|
|
7604
|
+
loadKnowledge();
|
|
7605
|
+
}
|
|
7606
|
+
|
|
7607
|
+
function kbFormOpen(x) { // x 为空 = 新建(保存即转正),否则编辑(不改状态)
|
|
7608
|
+
const isNew = !x;
|
|
7609
|
+
openModal(isNew ? t("新建知识条目") : t("编辑知识条目"),
|
|
7610
|
+
'<div class="form">' +
|
|
7611
|
+
'<div class="field"><label>' + t("标题") + ' <span class="req">*</span></label><input id="kb-f-title" value="' + esc(x ? x.title || "" : "") + '"></div>' +
|
|
7612
|
+
'<div class="field"><label>' + t("正文") + ' <span class="req">*</span></label><textarea id="kb-f-body" rows="5">' + esc(x ? x.body || "" : "") + "</textarea></div>" +
|
|
7613
|
+
'<div class="grid-2">' +
|
|
7614
|
+
'<div class="field"><label>' + t("标签(逗号分隔)") + '</label><input id="kb-f-tags" value="' + esc((x ? x.tags || [] : []).join(", ")) + '" data-i18n-ph="例:番茄, 签约" placeholder="例:番茄, 签约"></div>' +
|
|
7615
|
+
'<div class="field"><label>' + t("事实截至") + '</label><input id="kb-f-asof" value="' + esc(x ? x.as_of || "" : "") + '" placeholder="YYYY-MM-DD"></div>' +
|
|
7616
|
+
"</div>" +
|
|
7617
|
+
'<div class="field"><label>' + t("适用范围(任务类型,* 为通用)") + '</label><input id="kb-f-scope" value="' + esc(x ? x.scope || "*" : "*") + '" placeholder="novel / serial_novel / code / *"></div>' +
|
|
7618
|
+
"</div>",
|
|
7619
|
+
'<button class="ghost" onclick="closeModal()">' + t("取消") + "</button>" +
|
|
7620
|
+
'<button onclick="kbFormSave(' + (isNew ? "" : "'" + esc(x.id) + "'") + ')">' + t("保存") + "</button>");
|
|
7621
|
+
}
|
|
7622
|
+
|
|
7623
|
+
async function kbFormSave(id) {
|
|
7624
|
+
const fields = {
|
|
7625
|
+
title: $("kb-f-title").value.trim(),
|
|
7626
|
+
body: $("kb-f-body").value.trim(),
|
|
7627
|
+
tags: $("kb-f-tags").value.split(/[,,]/).map((s) => s.trim()).filter(Boolean),
|
|
7628
|
+
scope: $("kb-f-scope").value.trim() || "*",
|
|
7629
|
+
as_of: $("kb-f-asof").value.trim(),
|
|
7630
|
+
};
|
|
7631
|
+
if (!fields.title || !fields.body) { toast(t("标题与正文不能为空"), true); return; }
|
|
7632
|
+
try {
|
|
7633
|
+
await api("/api/knowledge/op", { method: "POST", body: JSON.stringify(id
|
|
7634
|
+
? { id, op: "edit", fields }
|
|
7635
|
+
: { op: "create", fields }) });
|
|
7636
|
+
} catch (e) { toast(e.message, true); return; }
|
|
7637
|
+
closeModal();
|
|
7638
|
+
loadKnowledge();
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7641
|
+
function kbEditOpen(id) {
|
|
7642
|
+
kbFormOpen((KB.data && KB.data.entries || []).find((x) => x.id === id));
|
|
7643
|
+
}
|
|
7644
|
+
|
|
7522
7645
|
/* ---------------------------------------------------------- 自动化(定时任务) */
|
|
7523
7646
|
/* 进页拉一次 + 每次操作后重拉 + 页面可见时每 8s 轮询(离页停表)。
|
|
7524
7647
|
* 后端 /api/automation → {tasks, templates};任务/模板操作走子路径。 */
|
|
@@ -8191,7 +8314,7 @@ function mkrCardHtml(p) {
|
|
|
8191
8314
|
const meta = [p.author, p.version ? "v" + p.version : ""].filter(Boolean).join(" · ");
|
|
8192
8315
|
return '<div class="card mk-card' + (p.compat === "blocked" ? " blocked" : "") + '"><div class="head">' +
|
|
8193
8316
|
'<span class="name">' + esc(t(p.title || p.name)) + "</span>" +
|
|
8194
|
-
'<span class="tag mk-src">' + esc(p.source_name || "") + "</span>" +
|
|
8317
|
+
'<span class="tag mk-src">' + esc(t(p.source_name || "")) + "</span>" +
|
|
8195
8318
|
(p.category ? '<span class="tag">' + esc(p.category) + "</span>" : "") +
|
|
8196
8319
|
"</div>" +
|
|
8197
8320
|
'<div class="note">' + esc(t(p.desc || "")) + "</div>" +
|
|
@@ -8214,7 +8337,7 @@ function renderMarketRemote() {
|
|
|
8214
8337
|
if (srcSel && !srcSel.options.length) {
|
|
8215
8338
|
srcSel.innerHTML = '<option value="">' + t("全部来源") + "</option>" +
|
|
8216
8339
|
(data.sources || []).map((s) =>
|
|
8217
|
-
'<option value="' + esc(s.id) + '">' + esc(s.name) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8340
|
+
'<option value="' + esc(s.id) + '">' + esc(t(s.name)) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8218
8341
|
}
|
|
8219
8342
|
const times = (data.sources || []).map((s) => s.fetched_at).filter(Boolean);
|
|
8220
8343
|
const meta = $("mkr-meta");
|
|
@@ -8269,7 +8392,7 @@ async function mkrRefresh() {
|
|
|
8269
8392
|
const keep = srcSel.value;
|
|
8270
8393
|
srcSel.innerHTML = '<option value="">' + t("全部来源") + "</option>" +
|
|
8271
8394
|
(r.sources || []).map((s) =>
|
|
8272
|
-
'<option value="' + esc(s.id) + '">' + esc(s.name) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8395
|
+
'<option value="' + esc(s.id) + '">' + esc(t(s.name)) + t("(") + s.count + t(")") + "</option>").join("");
|
|
8273
8396
|
srcSel.value = keep;
|
|
8274
8397
|
}
|
|
8275
8398
|
mkrLoadPage(true);
|
|
@@ -8389,7 +8512,7 @@ async function loadSettingsV2() {
|
|
|
8389
8512
|
inp = '<input id="' + iid + '" type="text" value="' + esc(String(cur)) + '">';
|
|
8390
8513
|
}
|
|
8391
8514
|
html += '<div class="set-v2-row">' + inp +
|
|
8392
|
-
'<span class="set-v2-lb" title="' + esc(f.description || f.path) + '">' + esc(f.description || f.path) + "</span></div>";
|
|
8515
|
+
'<span class="set-v2-lb" title="' + esc(t(f.description || f.path)) + '">' + esc(t(f.description || f.path)) + "</span></div>";
|
|
8393
8516
|
}
|
|
8394
8517
|
html += '<div class="input-row" style="margin-top:6px"><button class="ghost small" onclick="saveSettingsV2(\'' + esc(ns) + '\')">' + t("保存") + "</button>" +
|
|
8395
8518
|
'<span class="set-v2-msg msg"></span></div></div>';
|
|
@@ -8577,6 +8700,7 @@ var HELP_CHAPTERS = [
|
|
|
8577
8700
|
{ id: "features", icon: "i-bee", label: "功能一览" },
|
|
8578
8701
|
{ id: "serial", icon: "i-book-open", label: "连载创作流程" },
|
|
8579
8702
|
{ id: "publish", icon: "i-clapper", label: "发布上架" },
|
|
8703
|
+
{ id: "code", icon: "i-git-branch", label: "代码任务与版本" },
|
|
8580
8704
|
{ id: "auto", icon: "i-calendar-days", label: "自动化与技能市场" },
|
|
8581
8705
|
{ id: "faq", icon: "i-search", label: "常见问题" },
|
|
8582
8706
|
];
|
|
@@ -8620,6 +8744,7 @@ function helpChapterBody(id) {
|
|
|
8620
8744
|
'<li><svg class="ico" aria-hidden="true"><use href="#i-clapper"></use></svg><div><b>' + t("发布上架") + '</b><span>' + t("一键发布到番茄、七猫,含封面与作品资料") + '</span></div></li>' +
|
|
8621
8745
|
'<li><svg class="ico" aria-hidden="true"><use href="#i-git-branch"></use></svg><div><b>' + t("版本工作台") + '</b><span>' + t("任务分支隔离,改动可审可合") + '</span></div></li>' +
|
|
8622
8746
|
'<li><svg class="ico" aria-hidden="true"><use href="#i-calendar-days"></use></svg><div><b>' + t("自动化") + '</b><span>' + t("定时任务,无人值守") + '</span></div></li>' +
|
|
8747
|
+
'<li><svg class="ico" aria-hidden="true"><use href="#i-library"></use></svg><div><b>' + t("经验库") + '</b><span>' + t("内置规范包 + 自动沉淀教训,越用越顺手") + '</span></div></li>' +
|
|
8623
8748
|
'<li><svg class="ico" aria-hidden="true"><use href="#i-blocks"></use></svg><div><b>' + t("插件市场") + '</b><span>' + t("600+ 技能,一键安装") + '</span></div></li>' +
|
|
8624
8749
|
'</ul>';
|
|
8625
8750
|
}
|
|
@@ -8643,13 +8768,25 @@ function helpChapterBody(id) {
|
|
|
8643
8768
|
'<h3 class="help-h3">' + t("发布失败") + '</h3>' +
|
|
8644
8769
|
'<p class="help-p">' + t("平台改版或登录态过期会导致失败,失败信息会写明原因。重新登录后再点重试即可;反复失败可以到 GitHub 提 Issue 附上失败截图。") + '</p>';
|
|
8645
8770
|
}
|
|
8771
|
+
if (id === "code") {
|
|
8772
|
+
return '<h3 class="help-h3">' + t("代码任务怎么跑") + '</h3>' +
|
|
8773
|
+
'<p class="help-p">' + t("新建任务选开发类类型,目标写清楚要做什么(可以附需求文档、截图,附件会进入上下文)。蜂群先出计划再实现:实现 → 客观验证 → 跨厂商评审 → 修复,单路实现失败还有多候选赛马——各候选在隔离的 worktree 里各写各的,择优采纳。跑的过程中可以在详情页递话(文字/截图),下一轮生效。") + '</p>' +
|
|
8774
|
+
'<h3 class="help-h3">' + t("任务分支隔离:你的工作区不受影响") + '</h3>' +
|
|
8775
|
+
'<p class="help-p">' + t("启用代码版本隔离的任务会检出独立的任务分支(codebee/任务ID),所有改动只落在那条分支上,你当前的分支和未提交的改动完全不动——开跑前工作区有脏改动会先帮你收起(stash),跑完自动还原。") + '</p>' +
|
|
8776
|
+
'<h3 class="help-h3">' + t("版本裁决:合并还是丢弃") + '</h3>' +
|
|
8777
|
+
'<p class="help-p">' + t("任务跑完后,「版本」页签会亮起「待裁决」等你拍板:先看变更清单,双击文件能看 diff;满意点「合并」,任务分支合回基线分支;不满意点「丢弃」,分支和工作现场自动清场。中途停下的任务也会留好提交,可以继续跑完再裁决。合并与丢弃绝不静默替你决定。") + '</p>' +
|
|
8778
|
+
'<h3 class="help-h3">' + t("哪里看细节") + '</h3>' +
|
|
8779
|
+
'<p class="help-p">' + t("「蜂巢」页签看每个智能体实时在干什么(点格子看实时日志);「步骤」页签看任务拆解、每步实际用的模型、耗时与失败原因,评审分数与打磨记录也在这里。") + '</p>';
|
|
8780
|
+
}
|
|
8646
8781
|
if (id === "auto") {
|
|
8647
8782
|
return '<h3 class="help-h3">' + t("定时任务") + '</h3>' +
|
|
8648
8783
|
'<p class="help-p">' + t("设置 → 自动化:把任意任务类型挂上时间表(每天/每周固定时刻),到点自动按真实运行链开跑,结果照常落盘。配合群机器人(钉钉/飞书/企微自动适配)可以把完成状态推到群里。") + '</p>' +
|
|
8784
|
+
'<h3 class="help-h3">' + t("禅道 Bug 自动修复") + '</h3>' +
|
|
8785
|
+
'<p class="help-p">' + t("设置 → 禅道:配好地址与产品档案后,定时扫描激活 Bug,按模块路由 / AI 排查定责(前端/后端/双端/非我方);我方端的 Bug 自动创建代码修复任务,修完自动合并、resolve 并回写报告到群里;测试指错人的也会按排查结论改派。合并失败不 resolve 不转派,留人工兜底。") + '</p>' +
|
|
8649
8786
|
'<h3 class="help-h3">' + t("技能市场") + '</h3>' +
|
|
8650
8787
|
'<p class="help-p">' + t("设置 → 插件市场:600+ 技能一键安装,来源包括内置库和 ZCode、Anthropic 等多个外部目录。装上的技能会注入智能体能力,按任务类型生效。") + '</p>' +
|
|
8651
8788
|
'<h3 class="help-h3">' + t("用量台账") + '</h3>' +
|
|
8652
|
-
'<p class="help-p">' + t("设置 →
|
|
8789
|
+
'<p class="help-p">' + t("设置 → 用量统计:每次调用的模型、token 与费用按多个维度聚合展示,历史运行会在启动时自动回填——每个任务花了多少,一目了然。") + '</p>';
|
|
8653
8790
|
}
|
|
8654
8791
|
if (id === "faq") {
|
|
8655
8792
|
return '<div class="help-qa"><b>' + t("点「获取模型列表」报 404?") + '</b><p>' + t("部分网关不提供模型列表接口,属正常现象;只要能正常对话就不用管,模型名手工填即可。") + '</p></div>' +
|
|
@@ -8690,6 +8827,12 @@ function setHelpChapter(id) {
|
|
|
8690
8827
|
renderHelp();
|
|
8691
8828
|
}
|
|
8692
8829
|
function helpGo(id) { setHelpChapter(id); }
|
|
8830
|
+
function paintHelpChrome() {
|
|
8831
|
+
const badge = $("welcome-badge");
|
|
8832
|
+
if (badge) badge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
8833
|
+
const title = $("welcome-title");
|
|
8834
|
+
if (title) title.textContent = t(helpFirst ? "欢迎使用 CodeBee" : "帮助中心");
|
|
8835
|
+
}
|
|
8693
8836
|
function welcomeOpen(opts) {
|
|
8694
8837
|
const w = $("welcome");
|
|
8695
8838
|
if (!w) return;
|
|
@@ -8697,8 +8840,7 @@ function welcomeOpen(opts) {
|
|
|
8697
8840
|
helpFirst = !!(opts && opts.firstRun);
|
|
8698
8841
|
if (helpFirst) helpCur = "quickstart";
|
|
8699
8842
|
else if (opts && opts.topic && HELP_CHAPTERS.some((c) => c.id === opts.topic)) helpCur = opts.topic;
|
|
8700
|
-
|
|
8701
|
-
if (badge) badge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
8843
|
+
paintHelpChrome();
|
|
8702
8844
|
renderHelp();
|
|
8703
8845
|
w.classList.remove("hidden");
|
|
8704
8846
|
document.body.classList.add("welcome-open");
|
|
@@ -8906,7 +9048,7 @@ async function suStartupCheck() {
|
|
|
8906
9048
|
}
|
|
8907
9049
|
|
|
8908
9050
|
/* ---------------------------------------------------------- 页签 & 初始化 */
|
|
8909
|
-
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "智能体管理", models: "模型接入", bindings: "CLI 绑定", skills: "经验库", market: "插件市场", orch: "编排设置", appearance: "皮肤", about: "关于与更新" };
|
|
9051
|
+
const TAB_TITLES = { tasks: "任务", runs: "运行记录", automation: "自动化", zentao: "禅道 Bug 自动修复", usage: "用量统计", agents: "智能体管理", models: "模型接入", bindings: "CLI 绑定", skills: "经验库", knowledge: "知识库", market: "插件市场", orch: "编排设置", appearance: "皮肤", about: "关于与更新" };
|
|
8910
9052
|
const SET_TABS = new Set(Object.keys(TAB_TITLES)); // 设置导航里的子页(__phone 是弹框,不算)
|
|
8911
9053
|
|
|
8912
9054
|
function tabTitle(name) {
|
|
@@ -9070,10 +9212,9 @@ function setLangBtn(lang) {
|
|
|
9070
9212
|
paintArchToggle();
|
|
9071
9213
|
syncSideExpandBtn();
|
|
9072
9214
|
renderCodePreviews(); // 预览徽章是动态文案:语言切换时若停在皮肤页要跟着换
|
|
9073
|
-
//
|
|
9215
|
+
// 帮助中心若开着:徽章/标题/目录/正文跟着换语言重画
|
|
9074
9216
|
if (!$("welcome").classList.contains("hidden")) {
|
|
9075
|
-
|
|
9076
|
-
if (wbadge) wbadge.textContent = t(helpFirst ? "首次启动" : "帮助中心");
|
|
9217
|
+
paintHelpChrome();
|
|
9077
9218
|
renderHelp();
|
|
9078
9219
|
}
|
|
9079
9220
|
// 重画还在缓存里的页面标题
|
|
@@ -9554,7 +9695,7 @@ function collapseDrawerIfMobile() {
|
|
|
9554
9695
|
|
|
9555
9696
|
function switchTab(name) {
|
|
9556
9697
|
if (name === "__phone") { openPhoneConnect(); return; } // 手机连接是弹框,不切页
|
|
9557
|
-
if (name === "__guide") { welcomeOpen(); return; } //
|
|
9698
|
+
if (name === "__guide") { welcomeOpen(); return; } // 帮助中心是弹层,不切页(设置导航「软件」组)
|
|
9558
9699
|
// 导航收进「设置」:进设置后左栏整体换成设置导航,内容铺满
|
|
9559
9700
|
S.tab = name;
|
|
9560
9701
|
if (SET_TABS.has(name)) localStorage.setItem("orch.setTab", name);
|
|
@@ -9569,6 +9710,7 @@ function switchTab(name) {
|
|
|
9569
9710
|
if (name === "agents") autoCheckUpdates(); // 进目录页自动查各 CLI 新版本
|
|
9570
9711
|
if (name === "orch") { loadOrchestrator(); loadSettings(); } // 进编排设置页拉取配置
|
|
9571
9712
|
if (name === "skills") loadSkills(); // 进经验库页拉取沉淀
|
|
9713
|
+
if (name === "knowledge") loadKnowledge(); // 进知识库页拉取条目
|
|
9572
9714
|
if (name === "automation") { loadAutomation(); startAutoPoll(); } // 进自动化页:拉取 + 页面可见时每 8s 轮询
|
|
9573
9715
|
else stopAutoPoll(); // 离开自动化页(或切到别的子页)即停表
|
|
9574
9716
|
if (name === "zentao") loadZentao(); // 进禅道页:拉配置与修复记录回填表单
|
|
@@ -10041,6 +10183,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
10041
10183
|
&& e.target.tagName !== "TEXTAREA") { _askClose(true); return; }
|
|
10042
10184
|
// 文件内容弹窗(240) 夹在 ask(250) 与 modal(200) 之间,Esc 同样逐层关
|
|
10043
10185
|
if (e.key === "Escape" && filePopIsOpen()) { window.filePopClose(); return; }
|
|
10186
|
+
if (e.key === "F1") { e.preventDefault(); welcomeOpen(); return; } // F1 = 帮助中心(浏览器帮助被拦下)
|
|
10044
10187
|
if (e.key === "Escape" && !$("welcome").classList.contains("hidden")) { welcomeClose(); return; }
|
|
10045
10188
|
if (e.key === "Escape" && !$("modal").classList.contains("hidden")) closeModal();
|
|
10046
10189
|
// 日志抽屉:Esc 收起(最底层,放在弹窗之后)
|
|
@@ -10136,10 +10279,11 @@ function cmdkOps() {
|
|
|
10136
10279
|
{ icon: "i-calendar-days", label: t("自动化"), run: () => switchTab("automation") },
|
|
10137
10280
|
{ icon: "i-blocks", label: t("插件市场"), run: () => switchTab("market") },
|
|
10138
10281
|
{ icon: "i-book", label: t("经验库"), run: () => switchTab("skills") },
|
|
10282
|
+
{ icon: "i-sigma", label: t("知识库"), run: () => switchTab("knowledge") },
|
|
10139
10283
|
{ icon: "i-cpu", label: t("智能体管理"), run: () => switchTab("agents") },
|
|
10140
10284
|
{ icon: "i-chart", label: t("用量统计"), run: () => switchTab("usage") },
|
|
10141
10285
|
{ icon: "i-gear", label: t("设置"), run: () => enterSettings() },
|
|
10142
|
-
{ icon: "i-bee", label: t("
|
|
10286
|
+
{ icon: "i-bee", label: t("帮助中心"), run: () => welcomeOpen() },
|
|
10143
10287
|
{ icon: "i-phone", label: t("手机连接"), run: () => switchTab("__phone") },
|
|
10144
10288
|
];
|
|
10145
10289
|
}
|