codebee 0.1.103 → 0.1.104
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 +4 -0
- package/LICENSE +21 -21
- package/README.md +2 -3
- package/app/core/acceptance.py +100 -100
- package/app/core/acp_client.py +457 -457
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backend_profiles.py +188 -188
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -102
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/cleanup.py +303 -303
- package/app/core/command_auth.py +302 -302
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +91 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -33
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/knowledge_pipeline.py +86 -86
- package/app/core/manager.py +2385 -2385
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +285 -181
- package/app/core/openapi.py +217 -217
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +55 -3
- package/app/core/planner.py +7 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +206 -206
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +659 -659
- package/app/core/publish/chaprows.py +138 -138
- package/app/core/publish/flows-fanqie-calibrated.json +442 -442
- package/app/core/publish/manager.py +1185 -1185
- package/app/core/publish/qimao.py +193 -193
- package/app/core/publish/volumes.py +108 -108
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +201 -201
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/sandbox_runtime.py +95 -95
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings.py +215 -215
- package/app/core/settings_schema.py +282 -282
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/step_runner.py +132 -132
- package/app/core/story_tracking.py +275 -275
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tool_outputs.py +107 -107
- package/app/core/tracing.py +207 -207
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -89
- package/app/core/wxdigest.py +865 -865
- package/app/core/zentao.py +2692 -2692
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/artdiff.js +74 -74
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/bin/tutti.js +147 -147
- package/package.json +1 -1
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
- package/app/output/workbench-clean-preview.jpg +0 -0
- package/app/output/workbench-reference-render.png +0 -0
- package/app/output/workbench-shell.png +0 -0
package/app/pick_dialog.py
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
|
|
3
|
-
|
|
4
|
-
Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
|
|
5
|
-
Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
|
|
6
|
-
入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
|
|
7
|
-
走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
|
|
8
|
-
对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
|
|
9
|
-
fallback=true 让前端回落网页目录弹框。"""
|
|
10
|
-
import json
|
|
11
|
-
import os
|
|
12
|
-
import subprocess
|
|
13
|
-
import sys
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
|
|
16
|
-
# 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
|
|
17
|
-
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
|
18
|
-
_SELF = str(Path(__file__).resolve())
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def ask_directory(initial="", title="选择文件夹"):
|
|
22
|
-
"""父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
|
|
23
|
-
|
|
24
|
-
用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
|
|
25
|
-
非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
|
|
26
|
-
if not Path(__file__).exists():
|
|
27
|
-
return "", "pick_dialog.py 缺失", True
|
|
28
|
-
req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
|
|
29
|
-
env = dict(os.environ, PYTHONIOENCODING="utf-8")
|
|
30
|
-
try:
|
|
31
|
-
if os.name == "nt":
|
|
32
|
-
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
33
|
-
capture_output=True, timeout=600, env=env,
|
|
34
|
-
creationflags=CREATE_NO_WINDOW)
|
|
35
|
-
else:
|
|
36
|
-
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
37
|
-
capture_output=True, timeout=600, env=env)
|
|
38
|
-
except Exception as e:
|
|
39
|
-
return "", str(e), True
|
|
40
|
-
if cp.returncode != 0:
|
|
41
|
-
err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
|
|
42
|
-
return "", (err[-1] if err else "native picker unavailable"), True
|
|
43
|
-
try:
|
|
44
|
-
data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
|
|
45
|
-
except Exception:
|
|
46
|
-
return "", "对话框输出无法解析", True
|
|
47
|
-
return str(data.get("path") or ""), "", False
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def ask_file(initial="", title="选择文件"):
|
|
51
|
-
"""父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
|
|
52
|
-
返回 (path, error, fallback),语义与 ask_directory 一致。"""
|
|
53
|
-
if not Path(__file__).exists():
|
|
54
|
-
return "", "pick_dialog.py 缺失", True
|
|
55
|
-
req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
|
|
56
|
-
env = dict(os.environ, PYTHONIOENCODING="utf-8")
|
|
57
|
-
try:
|
|
58
|
-
if os.name == "nt":
|
|
59
|
-
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
60
|
-
capture_output=True, timeout=600, env=env,
|
|
61
|
-
creationflags=CREATE_NO_WINDOW)
|
|
62
|
-
else:
|
|
63
|
-
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
64
|
-
capture_output=True, timeout=600, env=env)
|
|
65
|
-
except Exception as e:
|
|
66
|
-
return "", str(e), True
|
|
67
|
-
if cp.returncode != 0:
|
|
68
|
-
err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
|
|
69
|
-
return "", (err[-1] if err else "native picker unavailable"), True
|
|
70
|
-
try:
|
|
71
|
-
data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
|
|
72
|
-
except Exception:
|
|
73
|
-
return "", "对话框输出无法解析", True
|
|
74
|
-
return str(data.get("path") or ""), "", False
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def main():
|
|
78
|
-
req = {}
|
|
79
|
-
try:
|
|
80
|
-
req = json.loads(sys.stdin.read() or "{}")
|
|
81
|
-
except Exception:
|
|
82
|
-
pass
|
|
83
|
-
import tkinter as tk
|
|
84
|
-
from tkinter import filedialog
|
|
85
|
-
|
|
86
|
-
root = tk.Tk()
|
|
87
|
-
root.withdraw()
|
|
88
|
-
try:
|
|
89
|
-
root.attributes("-topmost", True) # 别被全屏浏览器盖住
|
|
90
|
-
except Exception:
|
|
91
|
-
pass
|
|
92
|
-
is_file = req.get("mode") == "file"
|
|
93
|
-
kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
|
|
94
|
-
init = req.get("initial") or ""
|
|
95
|
-
if init and os.path.isdir(init):
|
|
96
|
-
kw["initialdir"] = init
|
|
97
|
-
try:
|
|
98
|
-
if is_file:
|
|
99
|
-
kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
|
|
100
|
-
path = filedialog.askopenfilename(**kw) or ""
|
|
101
|
-
else:
|
|
102
|
-
path = filedialog.askdirectory(**kw) or ""
|
|
103
|
-
except Exception:
|
|
104
|
-
sys.exit(1)
|
|
105
|
-
sys.stdout.write(json.dumps({"path": path}))
|
|
106
|
-
root.destroy()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if __name__ == "__main__":
|
|
110
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
|
|
3
|
+
|
|
4
|
+
Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
|
|
5
|
+
Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
|
|
6
|
+
入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
|
|
7
|
+
走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
|
|
8
|
+
对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
|
|
9
|
+
fallback=true 让前端回落网页目录弹框。"""
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
|
|
17
|
+
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
|
18
|
+
_SELF = str(Path(__file__).resolve())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ask_directory(initial="", title="选择文件夹"):
|
|
22
|
+
"""父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
|
|
23
|
+
|
|
24
|
+
用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
|
|
25
|
+
非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
|
|
26
|
+
if not Path(__file__).exists():
|
|
27
|
+
return "", "pick_dialog.py 缺失", True
|
|
28
|
+
req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
|
|
29
|
+
env = dict(os.environ, PYTHONIOENCODING="utf-8")
|
|
30
|
+
try:
|
|
31
|
+
if os.name == "nt":
|
|
32
|
+
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
33
|
+
capture_output=True, timeout=600, env=env,
|
|
34
|
+
creationflags=CREATE_NO_WINDOW)
|
|
35
|
+
else:
|
|
36
|
+
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
37
|
+
capture_output=True, timeout=600, env=env)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return "", str(e), True
|
|
40
|
+
if cp.returncode != 0:
|
|
41
|
+
err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
|
|
42
|
+
return "", (err[-1] if err else "native picker unavailable"), True
|
|
43
|
+
try:
|
|
44
|
+
data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
|
|
45
|
+
except Exception:
|
|
46
|
+
return "", "对话框输出无法解析", True
|
|
47
|
+
return str(data.get("path") or ""), "", False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ask_file(initial="", title="选择文件"):
|
|
51
|
+
"""父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
|
|
52
|
+
返回 (path, error, fallback),语义与 ask_directory 一致。"""
|
|
53
|
+
if not Path(__file__).exists():
|
|
54
|
+
return "", "pick_dialog.py 缺失", True
|
|
55
|
+
req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
|
|
56
|
+
env = dict(os.environ, PYTHONIOENCODING="utf-8")
|
|
57
|
+
try:
|
|
58
|
+
if os.name == "nt":
|
|
59
|
+
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
60
|
+
capture_output=True, timeout=600, env=env,
|
|
61
|
+
creationflags=CREATE_NO_WINDOW)
|
|
62
|
+
else:
|
|
63
|
+
cp = subprocess.run([sys.executable, _SELF], input=req,
|
|
64
|
+
capture_output=True, timeout=600, env=env)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
return "", str(e), True
|
|
67
|
+
if cp.returncode != 0:
|
|
68
|
+
err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
|
|
69
|
+
return "", (err[-1] if err else "native picker unavailable"), True
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
|
|
72
|
+
except Exception:
|
|
73
|
+
return "", "对话框输出无法解析", True
|
|
74
|
+
return str(data.get("path") or ""), "", False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def main():
|
|
78
|
+
req = {}
|
|
79
|
+
try:
|
|
80
|
+
req = json.loads(sys.stdin.read() or "{}")
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
import tkinter as tk
|
|
84
|
+
from tkinter import filedialog
|
|
85
|
+
|
|
86
|
+
root = tk.Tk()
|
|
87
|
+
root.withdraw()
|
|
88
|
+
try:
|
|
89
|
+
root.attributes("-topmost", True) # 别被全屏浏览器盖住
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
is_file = req.get("mode") == "file"
|
|
93
|
+
kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
|
|
94
|
+
init = req.get("initial") or ""
|
|
95
|
+
if init and os.path.isdir(init):
|
|
96
|
+
kw["initialdir"] = init
|
|
97
|
+
try:
|
|
98
|
+
if is_file:
|
|
99
|
+
kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
|
|
100
|
+
path = filedialog.askopenfilename(**kw) or ""
|
|
101
|
+
else:
|
|
102
|
+
path = filedialog.askdirectory(**kw) or ""
|
|
103
|
+
except Exception:
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
sys.stdout.write(json.dumps({"path": path}))
|
|
106
|
+
root.destroy()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
package/app/ui/artdiff.js
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
/* 成品两版对比的纯 diff 计算(OpenCreator「版本化对比」借鉴)。
|
|
2
|
-
* 独立文件无 DOM 依赖:app.js 用 window.ArtifactDiff 渲染,tests 用
|
|
3
|
-
* module.exports 直测。算法:公共前后缀修剪 + 中段 LCS(上限保护,
|
|
4
|
-
* 超限诚实降级为整块替换计数,不装作对齐了)。 */
|
|
5
|
-
(function (root, factory) {
|
|
6
|
-
var api = factory();
|
|
7
|
-
if (typeof window !== "undefined") window.ArtifactDiff = api;
|
|
8
|
-
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
9
|
-
})(typeof self !== "undefined" ? self : this, function () {
|
|
10
|
-
"use strict";
|
|
11
|
-
|
|
12
|
-
var MID_CAP = 400; // 中段行数单边上限
|
|
13
|
-
var MID_PRODUCT_CAP = 160000; // LCS DP 格子上限(400×400)
|
|
14
|
-
|
|
15
|
-
/* LCS 回溯产出统一 diff 行:[{t:"="|"-"/"+", s:行文本}](只对中段)。 */
|
|
16
|
-
function lcsRows(A, B) {
|
|
17
|
-
var n = A.length, m = B.length;
|
|
18
|
-
var dp = [];
|
|
19
|
-
for (var k = 0; k <= n; k++) dp.push(new Uint32Array(m + 1));
|
|
20
|
-
for (var i = n - 1; i >= 0; i--)
|
|
21
|
-
for (var j = m - 1; j >= 0; j--)
|
|
22
|
-
dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1
|
|
23
|
-
: (dp[i + 1][j] >= dp[i][j + 1] ? dp[i + 1][j] : dp[i][j + 1]);
|
|
24
|
-
var rows = [];
|
|
25
|
-
i = 0; j = 0;
|
|
26
|
-
while (i < n && j < m) {
|
|
27
|
-
if (A[i] === B[j]) { rows.push({ t: "=", s: A[i] }); i++; j++; }
|
|
28
|
-
else if (dp[i + 1][j] >= dp[i][j + 1]) { rows.push({ t: "-", s: A[i] }); i++; }
|
|
29
|
-
else { rows.push({ t: "+", s: B[j] }); j++; }
|
|
30
|
-
}
|
|
31
|
-
while (i < n) { rows.push({ t: "-", s: A[i] }); i++; }
|
|
32
|
-
while (j < m) { rows.push({ t: "+", s: B[j] }); j++; }
|
|
33
|
-
return rows;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* 对外主口:旧版 a、新版 b → {
|
|
37
|
-
* prefix/suffix: 相同行数(渲染端折叠展示),
|
|
38
|
-
* identical: 布尔,两版一致,
|
|
39
|
-
* rows: 中段统一 diff 行(超限时为 null),
|
|
40
|
-
* oversized: {a, b} 超限时中段行数(诚实整块计数) } */
|
|
41
|
-
function diff(a, b) {
|
|
42
|
-
a = String(a == null ? "" : a);
|
|
43
|
-
b = String(b == null ? "" : b);
|
|
44
|
-
var A = a.split("\n"), B = b.split("\n");
|
|
45
|
-
var p = 0;
|
|
46
|
-
while (p < A.length && p < B.length && A[p] === B[p]) p++;
|
|
47
|
-
var s = 0;
|
|
48
|
-
while (s < A.length - p && s < B.length - p &&
|
|
49
|
-
A[A.length - 1 - s] === B[B.length - 1 - s]) s++;
|
|
50
|
-
var midA = A.slice(p, A.length - s), midB = B.slice(p, B.length - s);
|
|
51
|
-
var out = { prefix: p, suffix: s, identical: !midA.length && !midB.length,
|
|
52
|
-
rows: null, oversized: null };
|
|
53
|
-
if (out.identical) return out;
|
|
54
|
-
if (midA.length > MID_CAP || midB.length > MID_CAP ||
|
|
55
|
-
midA.length * midB.length > MID_PRODUCT_CAP) {
|
|
56
|
-
out.oversized = { a: midA.length, b: midB.length };
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
out.rows = lcsRows(midA, midB);
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/* rows → 摘要计数(+x/-y,渲染端徽章用)。 */
|
|
64
|
-
function counts(d) {
|
|
65
|
-
var add = 0, del = 0;
|
|
66
|
-
if (d.oversized) return { add: d.oversized.b, del: d.oversized.a };
|
|
67
|
-
(d.rows || []).forEach(function (r) {
|
|
68
|
-
if (r.t === "+") add++; else if (r.t === "-") del++;
|
|
69
|
-
});
|
|
70
|
-
return { add: add, del: del };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { diff: diff, counts: counts, lcsRows: lcsRows };
|
|
74
|
-
});
|
|
1
|
+
/* 成品两版对比的纯 diff 计算(OpenCreator「版本化对比」借鉴)。
|
|
2
|
+
* 独立文件无 DOM 依赖:app.js 用 window.ArtifactDiff 渲染,tests 用
|
|
3
|
+
* module.exports 直测。算法:公共前后缀修剪 + 中段 LCS(上限保护,
|
|
4
|
+
* 超限诚实降级为整块替换计数,不装作对齐了)。 */
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
var api = factory();
|
|
7
|
+
if (typeof window !== "undefined") window.ArtifactDiff = api;
|
|
8
|
+
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
|
9
|
+
})(typeof self !== "undefined" ? self : this, function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var MID_CAP = 400; // 中段行数单边上限
|
|
13
|
+
var MID_PRODUCT_CAP = 160000; // LCS DP 格子上限(400×400)
|
|
14
|
+
|
|
15
|
+
/* LCS 回溯产出统一 diff 行:[{t:"="|"-"/"+", s:行文本}](只对中段)。 */
|
|
16
|
+
function lcsRows(A, B) {
|
|
17
|
+
var n = A.length, m = B.length;
|
|
18
|
+
var dp = [];
|
|
19
|
+
for (var k = 0; k <= n; k++) dp.push(new Uint32Array(m + 1));
|
|
20
|
+
for (var i = n - 1; i >= 0; i--)
|
|
21
|
+
for (var j = m - 1; j >= 0; j--)
|
|
22
|
+
dp[i][j] = A[i] === B[j] ? dp[i + 1][j + 1] + 1
|
|
23
|
+
: (dp[i + 1][j] >= dp[i][j + 1] ? dp[i + 1][j] : dp[i][j + 1]);
|
|
24
|
+
var rows = [];
|
|
25
|
+
i = 0; j = 0;
|
|
26
|
+
while (i < n && j < m) {
|
|
27
|
+
if (A[i] === B[j]) { rows.push({ t: "=", s: A[i] }); i++; j++; }
|
|
28
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) { rows.push({ t: "-", s: A[i] }); i++; }
|
|
29
|
+
else { rows.push({ t: "+", s: B[j] }); j++; }
|
|
30
|
+
}
|
|
31
|
+
while (i < n) { rows.push({ t: "-", s: A[i] }); i++; }
|
|
32
|
+
while (j < m) { rows.push({ t: "+", s: B[j] }); j++; }
|
|
33
|
+
return rows;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* 对外主口:旧版 a、新版 b → {
|
|
37
|
+
* prefix/suffix: 相同行数(渲染端折叠展示),
|
|
38
|
+
* identical: 布尔,两版一致,
|
|
39
|
+
* rows: 中段统一 diff 行(超限时为 null),
|
|
40
|
+
* oversized: {a, b} 超限时中段行数(诚实整块计数) } */
|
|
41
|
+
function diff(a, b) {
|
|
42
|
+
a = String(a == null ? "" : a);
|
|
43
|
+
b = String(b == null ? "" : b);
|
|
44
|
+
var A = a.split("\n"), B = b.split("\n");
|
|
45
|
+
var p = 0;
|
|
46
|
+
while (p < A.length && p < B.length && A[p] === B[p]) p++;
|
|
47
|
+
var s = 0;
|
|
48
|
+
while (s < A.length - p && s < B.length - p &&
|
|
49
|
+
A[A.length - 1 - s] === B[B.length - 1 - s]) s++;
|
|
50
|
+
var midA = A.slice(p, A.length - s), midB = B.slice(p, B.length - s);
|
|
51
|
+
var out = { prefix: p, suffix: s, identical: !midA.length && !midB.length,
|
|
52
|
+
rows: null, oversized: null };
|
|
53
|
+
if (out.identical) return out;
|
|
54
|
+
if (midA.length > MID_CAP || midB.length > MID_CAP ||
|
|
55
|
+
midA.length * midB.length > MID_PRODUCT_CAP) {
|
|
56
|
+
out.oversized = { a: midA.length, b: midB.length };
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
out.rows = lcsRows(midA, midB);
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* rows → 摘要计数(+x/-y,渲染端徽章用)。 */
|
|
64
|
+
function counts(d) {
|
|
65
|
+
var add = 0, del = 0;
|
|
66
|
+
if (d.oversized) return { add: d.oversized.b, del: d.oversized.a };
|
|
67
|
+
(d.rows || []).forEach(function (r) {
|
|
68
|
+
if (r.t === "+") add++; else if (r.t === "-") del++;
|
|
69
|
+
});
|
|
70
|
+
return { add: add, del: del };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { diff: diff, counts: counts, lcsRows: lcsRows };
|
|
74
|
+
});
|
package/app/ui/icons/bee.svg
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
-
<!-- CodeBee 写实小蜂:琥珀黑条纹腹 + 绒毛胸背 + 半透明翅脉 + 扇翅动画。
|
|
3
|
-
经 <img> 引用(独立文档,渐变 id 互不串扰);翅动媒体查询随系统 reduce-motion 关停。 -->
|
|
4
|
-
<defs>
|
|
5
|
-
<linearGradient id="cbAbd" x1="0" y1="0" x2="0" y2="1">
|
|
6
|
-
<stop offset="0" stop-color="#ffcd66"/>
|
|
7
|
-
<stop offset=".55" stop-color="#f0a92c"/>
|
|
8
|
-
<stop offset="1" stop-color="#c67c12"/>
|
|
9
|
-
</linearGradient>
|
|
10
|
-
<radialGradient id="cbThx" cx=".38" cy=".3" r="1.05">
|
|
11
|
-
<stop offset="0" stop-color="#7a5a26"/>
|
|
12
|
-
<stop offset=".6" stop-color="#3d2c10"/>
|
|
13
|
-
<stop offset="1" stop-color="#1f1505"/>
|
|
14
|
-
</radialGradient>
|
|
15
|
-
<linearGradient id="cbWing" x1="0" y1="0" x2="1" y2="1">
|
|
16
|
-
<stop offset="0" stop-color="#ffffff" stop-opacity=".85"/>
|
|
17
|
-
<stop offset="1" stop-color="#cfe0f5" stop-opacity=".16"/>
|
|
18
|
-
</linearGradient>
|
|
19
|
-
<clipPath id="cbAbdClip"><ellipse cx="32" cy="44" rx="11.5" ry="13.5"/></clipPath>
|
|
20
|
-
</defs>
|
|
21
|
-
<style>
|
|
22
|
-
.wingL, .wingR { transform-box: view-box; }
|
|
23
|
-
.wingL { transform-origin: 27px 21px; animation: cbFlapL .16s ease-in-out infinite alternate; }
|
|
24
|
-
.wingR { transform-origin: 37px 21px; animation: cbFlapR .16s ease-in-out infinite alternate; }
|
|
25
|
-
@keyframes cbFlapL { from { transform: rotate(-16deg); } to { transform: rotate(9deg); } }
|
|
26
|
-
@keyframes cbFlapR { from { transform: rotate(16deg); } to { transform: rotate(-9deg); } }
|
|
27
|
-
@media (prefers-reduced-motion: reduce) { .wingL, .wingR { animation: none; } }
|
|
28
|
-
</style>
|
|
29
|
-
|
|
30
|
-
<!-- 触角 -->
|
|
31
|
-
<path d="M29.5 8C27.5 4.6 24.6 3 21.8 3.6" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
32
|
-
<path d="M34.5 8c2-3.4 4.9-5 7.7-4.4" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
33
|
-
<circle cx="21.6" cy="3.7" r="1.5" fill="#241a05"/>
|
|
34
|
-
<circle cx="42.4" cy="3.7" r="1.5" fill="#241a05"/>
|
|
35
|
-
|
|
36
|
-
<!-- 左翅(前后两片,扇翅绕胸侧基点) -->
|
|
37
|
-
<g class="wingL">
|
|
38
|
-
<ellipse cx="16.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(-34 26 20.5)"
|
|
39
|
-
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
40
|
-
<ellipse cx="20" cy="24.5" rx="8.2" ry="3.5" transform="rotate(-22 26.5 24.5)"
|
|
41
|
-
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
42
|
-
<g transform="rotate(-34 26 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
43
|
-
<path d="M26 20.5 8 15.5"/><path d="M26 21.5 7.5 20.5"/>
|
|
44
|
-
</g>
|
|
45
|
-
</g>
|
|
46
|
-
<!-- 右翅 -->
|
|
47
|
-
<g class="wingR">
|
|
48
|
-
<ellipse cx="47.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(34 38 20.5)"
|
|
49
|
-
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
50
|
-
<ellipse cx="44" cy="24.5" rx="8.2" ry="3.5" transform="rotate(22 37.5 24.5)"
|
|
51
|
-
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
52
|
-
<g transform="rotate(34 38 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
53
|
-
<path d="M38 20.5 56 15.5"/><path d="M38 21.5 56.5 20.5"/>
|
|
54
|
-
</g>
|
|
55
|
-
</g>
|
|
56
|
-
|
|
57
|
-
<!-- 腹部:琥珀底 + 黑条纹 + 高光 + 螫针 -->
|
|
58
|
-
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="url(#cbAbd)"/>
|
|
59
|
-
<g clip-path="url(#cbAbdClip)" fill="#241a05">
|
|
60
|
-
<path d="M20 40.5 Q32 36.5 44 40.5 L44 46.5 Q32 42.5 20 46.5 Z"/>
|
|
61
|
-
<path d="M20.5 49 Q32 45.5 43.5 49 L43.2 54.5 Q32 51.5 20.8 54.5 Z" opacity=".92"/>
|
|
62
|
-
</g>
|
|
63
|
-
<path d="M25.2 36q-2.6 4-2 9.5" stroke="#ffe9ad" stroke-opacity=".7" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
|
64
|
-
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="none" stroke="#8a5a10" stroke-opacity=".35" stroke-width=".8"/>
|
|
65
|
-
<path d="M32 58.5 29.8 54h4.4Z" fill="#241a05"/>
|
|
66
|
-
|
|
67
|
-
<!-- 胸背:绒毛渐变 + 虚线描边模拟绒毛缘 -->
|
|
68
|
-
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="url(#cbThx)"/>
|
|
69
|
-
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="none" stroke="#8a6a2e" stroke-opacity=".45"
|
|
70
|
-
stroke-width="1" stroke-dasharray="1.2 1.6"/>
|
|
71
|
-
|
|
72
|
-
<!-- 头:复眼 + 高光 -->
|
|
73
|
-
<circle cx="32" cy="12.8" r="6.3" fill="#241a05"/>
|
|
74
|
-
<ellipse cx="28" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
75
|
-
<ellipse cx="36" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
76
|
-
<circle cx="28.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
77
|
-
<circle cx="36.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
78
|
-
<path d="M28.5 8.6q3.5-1.6 7 0" stroke="#8a6a2e" stroke-opacity=".55" stroke-width=".8" fill="none"/>
|
|
79
|
-
</svg>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<!-- CodeBee 写实小蜂:琥珀黑条纹腹 + 绒毛胸背 + 半透明翅脉 + 扇翅动画。
|
|
3
|
+
经 <img> 引用(独立文档,渐变 id 互不串扰);翅动媒体查询随系统 reduce-motion 关停。 -->
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="cbAbd" x1="0" y1="0" x2="0" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#ffcd66"/>
|
|
7
|
+
<stop offset=".55" stop-color="#f0a92c"/>
|
|
8
|
+
<stop offset="1" stop-color="#c67c12"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<radialGradient id="cbThx" cx=".38" cy=".3" r="1.05">
|
|
11
|
+
<stop offset="0" stop-color="#7a5a26"/>
|
|
12
|
+
<stop offset=".6" stop-color="#3d2c10"/>
|
|
13
|
+
<stop offset="1" stop-color="#1f1505"/>
|
|
14
|
+
</radialGradient>
|
|
15
|
+
<linearGradient id="cbWing" x1="0" y1="0" x2="1" y2="1">
|
|
16
|
+
<stop offset="0" stop-color="#ffffff" stop-opacity=".85"/>
|
|
17
|
+
<stop offset="1" stop-color="#cfe0f5" stop-opacity=".16"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<clipPath id="cbAbdClip"><ellipse cx="32" cy="44" rx="11.5" ry="13.5"/></clipPath>
|
|
20
|
+
</defs>
|
|
21
|
+
<style>
|
|
22
|
+
.wingL, .wingR { transform-box: view-box; }
|
|
23
|
+
.wingL { transform-origin: 27px 21px; animation: cbFlapL .16s ease-in-out infinite alternate; }
|
|
24
|
+
.wingR { transform-origin: 37px 21px; animation: cbFlapR .16s ease-in-out infinite alternate; }
|
|
25
|
+
@keyframes cbFlapL { from { transform: rotate(-16deg); } to { transform: rotate(9deg); } }
|
|
26
|
+
@keyframes cbFlapR { from { transform: rotate(16deg); } to { transform: rotate(-9deg); } }
|
|
27
|
+
@media (prefers-reduced-motion: reduce) { .wingL, .wingR { animation: none; } }
|
|
28
|
+
</style>
|
|
29
|
+
|
|
30
|
+
<!-- 触角 -->
|
|
31
|
+
<path d="M29.5 8C27.5 4.6 24.6 3 21.8 3.6" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
32
|
+
<path d="M34.5 8c2-3.4 4.9-5 7.7-4.4" fill="none" stroke="#241a05" stroke-width="1.4" stroke-linecap="round"/>
|
|
33
|
+
<circle cx="21.6" cy="3.7" r="1.5" fill="#241a05"/>
|
|
34
|
+
<circle cx="42.4" cy="3.7" r="1.5" fill="#241a05"/>
|
|
35
|
+
|
|
36
|
+
<!-- 左翅(前后两片,扇翅绕胸侧基点) -->
|
|
37
|
+
<g class="wingL">
|
|
38
|
+
<ellipse cx="16.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(-34 26 20.5)"
|
|
39
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
40
|
+
<ellipse cx="20" cy="24.5" rx="8.2" ry="3.5" transform="rotate(-22 26.5 24.5)"
|
|
41
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
42
|
+
<g transform="rotate(-34 26 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
43
|
+
<path d="M26 20.5 8 15.5"/><path d="M26 21.5 7.5 20.5"/>
|
|
44
|
+
</g>
|
|
45
|
+
</g>
|
|
46
|
+
<!-- 右翅 -->
|
|
47
|
+
<g class="wingR">
|
|
48
|
+
<ellipse cx="47.5" cy="19.5" rx="11.5" ry="4.8" transform="rotate(34 38 20.5)"
|
|
49
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".65" stroke-width=".7"/>
|
|
50
|
+
<ellipse cx="44" cy="24.5" rx="8.2" ry="3.5" transform="rotate(22 37.5 24.5)"
|
|
51
|
+
fill="url(#cbWing)" stroke="#e8f1fb" stroke-opacity=".55" stroke-width=".6"/>
|
|
52
|
+
<g transform="rotate(34 38 20.5)" fill="none" stroke="#a9c1dd" stroke-opacity=".4" stroke-width=".5">
|
|
53
|
+
<path d="M38 20.5 56 15.5"/><path d="M38 21.5 56.5 20.5"/>
|
|
54
|
+
</g>
|
|
55
|
+
</g>
|
|
56
|
+
|
|
57
|
+
<!-- 腹部:琥珀底 + 黑条纹 + 高光 + 螫针 -->
|
|
58
|
+
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="url(#cbAbd)"/>
|
|
59
|
+
<g clip-path="url(#cbAbdClip)" fill="#241a05">
|
|
60
|
+
<path d="M20 40.5 Q32 36.5 44 40.5 L44 46.5 Q32 42.5 20 46.5 Z"/>
|
|
61
|
+
<path d="M20.5 49 Q32 45.5 43.5 49 L43.2 54.5 Q32 51.5 20.8 54.5 Z" opacity=".92"/>
|
|
62
|
+
</g>
|
|
63
|
+
<path d="M25.2 36q-2.6 4-2 9.5" stroke="#ffe9ad" stroke-opacity=".7" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
|
64
|
+
<ellipse cx="32" cy="44" rx="11.5" ry="13.5" fill="none" stroke="#8a5a10" stroke-opacity=".35" stroke-width=".8"/>
|
|
65
|
+
<path d="M32 58.5 29.8 54h4.4Z" fill="#241a05"/>
|
|
66
|
+
|
|
67
|
+
<!-- 胸背:绒毛渐变 + 虚线描边模拟绒毛缘 -->
|
|
68
|
+
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="url(#cbThx)"/>
|
|
69
|
+
<ellipse cx="32" cy="25.5" rx="9.2" ry="9.8" fill="none" stroke="#8a6a2e" stroke-opacity=".45"
|
|
70
|
+
stroke-width="1" stroke-dasharray="1.2 1.6"/>
|
|
71
|
+
|
|
72
|
+
<!-- 头:复眼 + 高光 -->
|
|
73
|
+
<circle cx="32" cy="12.8" r="6.3" fill="#241a05"/>
|
|
74
|
+
<ellipse cx="28" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
75
|
+
<ellipse cx="36" cy="12.6" rx="2.4" ry="3" fill="#0e0a03"/>
|
|
76
|
+
<circle cx="28.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
77
|
+
<circle cx="36.9" cy="11.3" r=".7" fill="#7d6538" opacity=".8"/>
|
|
78
|
+
<path d="M28.5 8.6q3.5-1.6 7 0" stroke="#8a6a2e" stroke-opacity=".55" stroke-width=".8" fill="none"/>
|
|
79
|
+
</svg>
|
package/app/ui/manifest.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "CodeBee · 多智能体编排台",
|
|
3
|
-
"short_name": "CodeBee",
|
|
4
|
-
"description": "All agents, one hive. 一句话目标,自动拆解、路由、执行、验证与跨厂商评审。",
|
|
5
|
-
"start_url": "/",
|
|
6
|
-
"scope": "/",
|
|
7
|
-
"display": "standalone",
|
|
8
|
-
"orientation": "portrait-primary",
|
|
9
|
-
"background_color": "#0f1115",
|
|
10
|
-
"theme_color": "#6d5df6",
|
|
11
|
-
"icons": [
|
|
12
|
-
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
|
13
|
-
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
|
14
|
-
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
|
15
|
-
]
|
|
16
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "CodeBee · 多智能体编排台",
|
|
3
|
+
"short_name": "CodeBee",
|
|
4
|
+
"description": "All agents, one hive. 一句话目标,自动拆解、路由、执行、验证与跨厂商评审。",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"orientation": "portrait-primary",
|
|
9
|
+
"background_color": "#0f1115",
|
|
10
|
+
"theme_color": "#6d5df6",
|
|
11
|
+
"icons": [
|
|
12
|
+
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
|
13
|
+
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
|
14
|
+
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
|
15
|
+
]
|
|
16
|
+
}
|