codebee 0.1.103 → 0.1.105
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 +12 -0
- package/LICENSE +21 -21
- package/README.md +5 -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/checkpoints.py +48 -5
- 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 +2386 -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 +246 -217
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +81 -11
- 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 +252 -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/registry.py +35 -0
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/review_requests.py +119 -0
- package/app/core/runner.py +5 -1
- 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/main.py +43 -0
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +21 -2
- package/app/ui/artdiff.js +74 -74
- package/app/ui/hive3d.js +54 -31
- package/app/ui/i18n.js +32 -1
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +1 -0
- 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/app.js
CHANGED
|
@@ -434,7 +434,7 @@ function flowIconHtml(f) {
|
|
|
434
434
|
function flowDesc(f) {
|
|
435
435
|
if (f.id === "rank_scan") return t("抓四平台榜 → AI 选题洞察·附证据分级");
|
|
436
436
|
if (f.engine === "direct") return t("单智能体直达(快)");
|
|
437
|
-
if (f.engine === "code") return t("实现 → 验证
|
|
437
|
+
if (f.engine === "code") return t("实现 → 验证 →(复杂任务进)评审");
|
|
438
438
|
// 连载与单稿件同引擎,描述必须区分:连载强调逐章与断点续跑
|
|
439
439
|
if (f.serial) return t("大纲 → 逐章起草评审 → 合并(可续跑)");
|
|
440
440
|
return t("起草 → 多维评审 → 门禁");
|
|
@@ -4796,6 +4796,20 @@ window.loadProjectMemory = async function (taskId) {
|
|
|
4796
4796
|
} catch (error) { box.innerHTML = '<span class="hint">' + esc(t("加载项目记忆失败:")) + esc(error.message) + "</span>"; }
|
|
4797
4797
|
};
|
|
4798
4798
|
|
|
4799
|
+
window.previewProjectMemory = async function (taskId) {
|
|
4800
|
+
taskId = taskId || S.detailTaskKey;
|
|
4801
|
+
if (!taskId) return;
|
|
4802
|
+
const box = $("rd-project-memory-body");
|
|
4803
|
+
if (!box) return;
|
|
4804
|
+
try {
|
|
4805
|
+
const data = await api("/api/tasks/" + encodeURIComponent(taskId) + "/memory/preview?cap=4000");
|
|
4806
|
+
const entries = (data.entries || []).map((entry) =>
|
|
4807
|
+
'<span class="chip">' + esc(entry.title || t("项目记忆")) + ' · v' + esc(String(entry.version || 1)) + '</span>').join(" ");
|
|
4808
|
+
const text = data.text ? '<pre>' + esc(data.text) + '</pre>' : '<span class="hint">' + esc(t("暂无可注入记忆")) + '</span>';
|
|
4809
|
+
box.insertAdjacentHTML("afterbegin", '<div class="rd-memory-preview"><strong>' + esc(t("本次注入预览")) + '</strong><span class="hint">' + esc(t("已使用 {0}/{1} 字符", data.budget?.used || 0, data.budget?.cap || 0)) + '</span><div>' + entries + '</div>' + text + '</div>');
|
|
4810
|
+
} catch (error) { toast(t("项目记忆预览失败:") + error.message, true); }
|
|
4811
|
+
};
|
|
4812
|
+
|
|
4799
4813
|
window.decideProjectMemory = async function (taskId, id, action, version) {
|
|
4800
4814
|
try {
|
|
4801
4815
|
await api("/api/tasks/" + encodeURIComponent(taskId) + "/memory", {
|
|
@@ -4841,7 +4855,7 @@ window.loadRunCheckpoints = async function (requestedRunId) {
|
|
|
4841
4855
|
const checkpoint = data.checkpoint || {};
|
|
4842
4856
|
const steps = Array.isArray(checkpoint.steps) ? checkpoint.steps : [];
|
|
4843
4857
|
const stepSummary = steps.length ? '<div class="rd-checkpoint-steps"><strong>' + esc(t("步骤回放预览")) + '</strong>' +
|
|
4844
|
-
steps.map((step) => '<div class="rd-checkpoint-step"><span>#' + esc(String(step.step ?? "")) + ' · ' + esc(step.role || t("未命名步骤")) + '</span><span class="chip">' + esc(step.status || step.event || "unknown") + (step.attempt ? ' · ' + esc(t("第"
|
|
4858
|
+
steps.map((step) => '<div class="rd-checkpoint-step"><span>#' + esc(String(step.step ?? "")) + ' · ' + esc(step.role || t("未命名步骤")) + '</span><span class="chip">' + esc(step.status || step.event || "unknown") + (step.attempt ? ' · ' + esc(t("第{0}次", step.attempt)) : "") + '</span></div>').join("") +
|
|
4845
4859
|
(checkpoint.truncated ? '<p class="hint">' + esc(t("仅显示最近 200 个步骤,共 ") + (checkpoint.total_steps || steps.length)) + '</p>' : '') +
|
|
4846
4860
|
(checkpoint.replayable ? '<p class="hint">' + esc(t("所有步骤均已完成,可人工复核后继续")) + '</p>' : '<p class="hint">' + esc(t("无法安全自动回放:") + (checkpoint.blocked_reason || t("需要人工核对"))) + '</p>') + '</div>' : '';
|
|
4847
4861
|
body.innerHTML = (data.files && data.files.warning ? '<p class="hint">' + esc(data.files.warning) + "</p>" : "") + stepSummary +
|
|
@@ -11144,6 +11158,10 @@ function card(c) {
|
|
|
11144
11158
|
? '<label class="toggle"><input type="checkbox" ' + (c.orch_enabled ? "checked" : "") +
|
|
11145
11159
|
' onchange="toggleOrch(\'' + esc(c.id) + '\', this.checked)">' + t(" 参与编排") + '</label>'
|
|
11146
11160
|
: '<span class="tag">' + t("仅管理") + '</span>';
|
|
11161
|
+
const capability = c.capabilities || {};
|
|
11162
|
+
const capTags = [capability.transport, capability.resume ? t("支持续会话") : "",
|
|
11163
|
+
capability.mcp ? "MCP" : "", capability.sandbox].filter(Boolean)
|
|
11164
|
+
.map((x) => '<span class="tag">' + esc(t(x)) + '</span>').join("");
|
|
11147
11165
|
const dis = mgmtBusy(c.id) ? " disabled" : "";
|
|
11148
11166
|
const hasModels = modelGroups().length > 0;
|
|
11149
11167
|
const modelBox = c.config_writable
|
|
@@ -11172,6 +11190,7 @@ function card(c) {
|
|
|
11172
11190
|
(c.installed && c.uninstall_cmd ? '<button class="danger small"' + dis + ' onclick="mgmt(\'' + esc(c.id) + '\', \'uninstall\')">' + t("卸载") + '</button>' : "") +
|
|
11173
11191
|
"</div>" +
|
|
11174
11192
|
'<div class="note">' + esc(t(c.note || "")) + "</div>" +
|
|
11193
|
+
(capTags ? '<div class="facts capability-tags">' + capTags + '</div>' : "") +
|
|
11175
11194
|
'<div class="facts">' + t("版本 ") + '<b>' + esc(c.version || "-") + "</b>" + t(" 模型 ") + "<b>" + esc(fmtModel(c.model) || "-") + "</b>" +
|
|
11176
11195
|
((c.detail || c.config_path) ? "<br>" + esc(c.detail || c.config_path) : "") + "</div>" +
|
|
11177
11196
|
mgmtPanel(c.id) +
|
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/hive3d.js
CHANGED
|
@@ -91,17 +91,21 @@ window.Hive3D = (function () {
|
|
|
91
91
|
function organicGeometry(gl){
|
|
92
92
|
// A polished, tapered leaf/wing silhouette with a gently domed center and two-sided normals.
|
|
93
93
|
let outline=[
|
|
94
|
-
[-.5,0
|
|
95
|
-
[.38,.13,.
|
|
96
|
-
[-.12,-.255,.
|
|
94
|
+
[-.5,0,.006],[-.34,.17,.028],[-.12,.255,.064],[.14,.225,.078],
|
|
95
|
+
[.38,.13,.038],[.5,0,.006],[.38,-.13,.038],[.14,-.225,.078],
|
|
96
|
+
[-.12,-.255,.064],[-.34,-.17,.028]
|
|
97
97
|
];
|
|
98
98
|
const area=outline.reduce((sum,p,i)=>{const q=outline[(i+1)%outline.length];return sum+p[0]*q[1]-q[0]*p[1];},0);
|
|
99
99
|
if(area<0)outline=outline.reverse();
|
|
100
100
|
const positions=[],normals=[],indices=[];
|
|
101
101
|
const emitSide=(sign)=>{
|
|
102
102
|
const centerIndex=positions.length/3;
|
|
103
|
-
positions.push(0,0,sign*.
|
|
104
|
-
for(const p of outline){
|
|
103
|
+
positions.push(0,0,sign*.105);normals.push(0,0,sign);
|
|
104
|
+
for(const p of outline){
|
|
105
|
+
positions.push(p[0],p[1],p[2]*sign);
|
|
106
|
+
const n=norm([-.20*p[0],-.28*p[1],sign]);
|
|
107
|
+
normals.push(...n);
|
|
108
|
+
}
|
|
105
109
|
for(let i=0;i<outline.length;i++){
|
|
106
110
|
const a=centerIndex+1+i,b=centerIndex+1+(i+1)%outline.length;
|
|
107
111
|
if(sign>0)indices.push(centerIndex,a,b);else indices.push(centerIndex,b,a);
|
|
@@ -188,7 +192,7 @@ window.Hive3D = (function () {
|
|
|
188
192
|
const vs="attribute vec3 aPosition; attribute vec3 aNormal; attribute vec4 aColor; uniform mat4 uViewProj; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; void main(){gl_Position=uViewProj*vec4(aPosition,1.0);vColor=aColor;vWorld=aPosition;vNormal=normalize(aNormal);vec3 n=normalize(aNormal);float sky=clamp(n.y*0.5+0.5,0.0,1.0);float side=abs(n.x)*0.035;float heightShade=mix(0.94,1.0,smoothstep(-0.2,3.6,aPosition.y));vShade=(0.88+0.08*sky+side)*heightShade;}";
|
|
189
193
|
const fs="precision mediump float; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; uniform vec3 uEyePosition; void main(){vec3 n=normalize(vNormal);vec3 v=normalize(uEyePosition-vWorld);vec3 key=normalize(vec3(-0.48,0.86,0.42));vec3 fill=normalize(vec3(0.62,0.32,-0.72));vec3 warm=normalize(vec3(0.34,0.46,0.82));float ndl=max(dot(n,key),0.0);float fillN=max(dot(n,fill),0.0);float warmN=max(dot(n,warm),0.0);float hemi=clamp(n.y*0.5+0.5,0.0,1.0);vec3 base=vColor.rgb;float wallBlue=step(0.38,base.b)*step(0.35,base.g)*step(base.r*1.65,base.g)*step(1.7,vWorld.y);float wallGradient=0.86+0.14*smoothstep(1.7,4.5,vWorld.y)+0.055*exp(-pow((vWorld.x+2.1)*0.24,2.0));base=mix(base,base*wallGradient,wallBlue);vec3 h=normalize(key+v);vec3 hf=normalize(fill+v);float chroma=max(max(base.r,base.g),base.b)-min(min(base.r,base.g),base.b);float gloss=mix(0.10,0.34,smoothstep(0.10,0.82,chroma));float spec=pow(max(dot(n,h),0.0),mix(24.0,58.0,gloss))*gloss*0.42;float specFill=pow(max(dot(n,hf),0.0),36.0)*0.075;float fresnel=pow(1.0-max(dot(n,v),0.0),3.0);vec3 ambient=base*(0.55+0.13*hemi);vec3 direct=base*(0.34*ndl+0.16*fillN+0.08*warmN);vec3 highlight=vec3(1.0,0.91,0.78)*spec+vec3(0.52,0.84,1.0)*specFill+vec3(0.18,0.48,0.68)*fresnel*0.075;float cyan=max(0.0,min(base.g,base.b)-base.r)*0.65;vec3 emissive=base*cyan;vec3 lit=ambient+direct+highlight+emissive;lit*=vShade;gl_FragColor=vec4(min(lit,vec3(1.0)),vColor.a);}";
|
|
190
194
|
const program=gl.createProgram();gl.attachShader(program,shader(gl,gl.VERTEX_SHADER,vs));gl.attachShader(program,shader(gl,gl.FRAGMENT_SHADER,fs));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));this.program=program;gl.useProgram(program);
|
|
191
|
-
this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.grout=hexGroutGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.
|
|
195
|
+
this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.grout=hexGroutGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.08,.18);this.bandGoldBottom=sphereBandGeometry(gl,-.97,-.61);this.bandSeparator=sphereBandGeometry(gl,-.51,-.38);this.bandGoldTop.smooth=true;this.bandGoldBottom.smooth=true;this.bandSeparator.smooth=true;gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);gl.clearColor(0,0,0,0);
|
|
192
196
|
};
|
|
193
197
|
Scene.prototype.listen=function(el,type,fn,opts){el.addEventListener(type,fn,opts);this.listeners.push(()=>el.removeEventListener(type,fn,opts));};
|
|
194
198
|
Scene.prototype.bindSurface=function(){
|
|
@@ -204,10 +208,10 @@ window.Hive3D = (function () {
|
|
|
204
208
|
Scene.prototype.addLink=function(a,b,radius,color){const dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2],length=Math.hypot(dx,dy,dz)||.001,rotX=Math.asin(clamp(dz/length,-1,1)),rotZ=Math.atan2(-dx,dy);this.add(this.cylinder,(a[0]+b[0])/2,(a[1]+b[1])/2,(a[2]+b[2])/2,radius,length,radius,color,0,rotZ,rotX);};
|
|
205
209
|
Scene.prototype.buildWorld=function(){
|
|
206
210
|
const b=this.box,rb=this.roundBox,s=this.sphere;this.objects=[];
|
|
207
|
-
// Bright, calm blue-white studio with a continuous floor and a single clean feature wall.
|
|
211
|
+
// Bright, calm blue-white studio with a continuous floor and a single clean feature wall. The free camera is framed wide enough to keep all 14 stations visible at desktop aspect ratios.
|
|
208
212
|
this.add(b,0,-.18,5.5,23,.28,34,[.97,.985,1,1]);
|
|
209
213
|
this.add(b,0,2.70,-6.45,19,3.65,.24,[.10,.52,.89,1]);
|
|
210
|
-
this.add(b,0,
|
|
214
|
+
this.add(b,0,4.58,-6.30,19,.075,.12,[.985,.99,1,1]);
|
|
211
215
|
// Wall seams, lower trim, and glass side windows.
|
|
212
216
|
this.add(b,0,.90,-6.28,18.7,.12,.06,[.08,.42,.78,1]);
|
|
213
217
|
this.add(b,0,.82,-6.285,18.7,.035,.035,[.25,.67,.94,1]);
|
|
@@ -265,6 +269,10 @@ window.Hive3D = (function () {
|
|
|
265
269
|
this.add(this.cylinder,x,.78,z-.43,.072,.40,.072,[.32,.39,.45,1]);
|
|
266
270
|
this.add(b,x,.49,z-.27,.54,.045,.34,[.16,.20,.24,1]);
|
|
267
271
|
this.add(b,x,screenY-screenHeight*.42,z-.382,screenWidth*.84,.018,.014,[.10,.83,.94,1]);
|
|
272
|
+
// A tiny centered webcam and a restrained power indicator give the monitor a finished hardware read.
|
|
273
|
+
this.add(rb,x,screenY+screenHeight*.5+.064,z-.386,.070,.026,.018,[.025,.035,.045,1]);
|
|
274
|
+
this.add(s,x,screenY+screenHeight*.5+.064,z-.374,.012,.012,.008,[.14,.82,.98,1]);
|
|
275
|
+
this.add(s,x+screenWidth*.39,screenY-screenHeight*.5+.025,z-.375,.010,.010,.008,[.28,.94,.62,1]);
|
|
268
276
|
this.add(rb,x-.18,.635,z+.25,.60,.035,.20,[.11,.14,.17,1]);
|
|
269
277
|
this.add(s,x+.40,.648,z+.22,.060,.025,.075,[.055,.065,.075,1]);
|
|
270
278
|
this.add(b,x+.40,.674,z+.20,.009,.005,.024,[.30,.66,.78,1]);
|
|
@@ -279,13 +287,13 @@ window.Hive3D = (function () {
|
|
|
279
287
|
for(let k=0;k<3;k++)this.add(b,blueBinderStart+k*.095,.79,z+.34,.075,.35,.18,[[.08,.34,.80,1],[.10,.51,.91,1],[.06,.25,.65,1]][k]);
|
|
280
288
|
this.add(this.cylinder,x,.08,z+1.10,.075,.28,.075,[.12,.15,.18,1]);
|
|
281
289
|
this.add(rb,x,.30,z+1.10,.94,.12,.68,[.11,.14,.17,1]);
|
|
282
|
-
this.add(rb,x,.50,z+.82
|
|
283
|
-
this.add(rb,x,.50,z+.91,.
|
|
290
|
+
this.add(rb,x,.50,z+.82,.88,.60,.15,[.12,.17,.22,1]);
|
|
291
|
+
this.add(rb,x,.50,z+.91,.66,.43,.024,[.20,.25,.29,1]);
|
|
284
292
|
this.add(rb,x,.42,z+1.10,.48,.12,.12,[.18,.22,.25,1]);
|
|
285
293
|
// Contrasting graphite upholstery separates the chair from the bee chassis.
|
|
286
|
-
this.add(rb,x,.50,z+.90,.
|
|
287
|
-
for(const ribY of [.34,.48,.62])this.add(rb,x,ribY,z+.93,.
|
|
288
|
-
for(const side of [-1,1])this.add(rb,x+side*.
|
|
294
|
+
this.add(rb,x,.50,z+.90,.67,.43,.050,[.16,.20,.24,1]);
|
|
295
|
+
for(const ribY of [.34,.48,.62])this.add(rb,x,ribY,z+.93,.48,.026,.020,[.34,.39,.43,1]);
|
|
296
|
+
for(const side of [-1,1])this.add(rb,x+side*.43,.50,z+.93,.030,.48,.025,[.23,.30,.36,1]);
|
|
289
297
|
for(const side of [-1,1]){
|
|
290
298
|
this.add(rb,x+side*.39,.63,z+.96,.10,.075,.38,[.14,.18,.21,1]);
|
|
291
299
|
this.add(b,x+side*.39,.48,z+.97,.035,.28,.045,[.10,.13,.16,1]);
|
|
@@ -293,14 +301,23 @@ window.Hive3D = (function () {
|
|
|
293
301
|
for(let k=0;k<5;k++){const a=k*Math.PI*2/5;const wx=x+Math.cos(a)*.48,wz=z+1.10+Math.sin(a)*.40;this.add(b,x+Math.cos(a)*.38,.035,z+1.10+Math.sin(a)*.32,.30,.06,.075,[.08,.10,.12,1],a);this.add(s,wx,.005,wz,.085,.065,.075,[.035,.045,.055,1]);}
|
|
294
302
|
const by=1.25,bz=z+.98;
|
|
295
303
|
// Bee assistant: distinct head, dark chassis, striped abdomen, headset and translucent wings.
|
|
296
|
-
this.add(s,x,by-.
|
|
304
|
+
this.add(s,x,by-.48,bz+.005,.345,.37,.255,[.075,.09,.11,1]);
|
|
297
305
|
// Slightly enlarge the band ellipsoid to avoid depth-buffer z-fighting with the shell.
|
|
298
|
-
const shellX=.
|
|
306
|
+
const shellX=.350,shellY=.375,shellZ=.260,shellCenterY=by-.48,shellCenterZ=bz+.005;
|
|
299
307
|
this.add(this.bandGoldTop,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.66,.035,1]);
|
|
300
308
|
this.add(this.bandGoldBottom,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[.99,.56,.018,1]);
|
|
301
309
|
this.add(this.bandSeparator,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[.045,.055,.065,1]);
|
|
302
310
|
this.add(s,x,by,bz,.405,.37,.345,[1,.70,.045,1]);
|
|
303
|
-
//
|
|
311
|
+
// A forward-facing graphite visor makes the assistant read as a character, not a plain yellow sphere.
|
|
312
|
+
// The workstation monitor is on -Z, so the faceplate and optical details sit on that side of the head.
|
|
313
|
+
this.add(rb,x,by+.012,bz-.166,.258,.142,.026,[.035,.055,.070,1]);
|
|
314
|
+
this.add(rb,x,by-.006,bz-.183,.214,.092,.012,[.055,.085,.105,1]);
|
|
315
|
+
for(const side of [-1,1]){
|
|
316
|
+
this.add(s,x+side*.066,by+.022,bz-.194,.045,.046,.020,[.20,.91,.98,1]);
|
|
317
|
+
this.add(s,x+side*.066,by+.029,bz-.205,.017,.018,.008,[.92,.99,1,1]);
|
|
318
|
+
}
|
|
319
|
+
this.add(rb,x,by-.043,bz-.197,.070,.010,.008,[.98,.61,.035,1]);
|
|
320
|
+
// The rear silhouette carries the headset details.
|
|
304
321
|
for(const side of [-1,1]){
|
|
305
322
|
this.add(s,x+side*.382,by+.015,bz+.035,.078,.118,.094,[.045,.055,.065,1]);
|
|
306
323
|
this.add(s,x+side*.407,by+.015,bz+.052,.030,.052,.048,[.99,.68,.045,1]);
|
|
@@ -317,26 +334,32 @@ window.Hive3D = (function () {
|
|
|
317
334
|
}
|
|
318
335
|
// Shoulders, articulated elbows and forearms form continuous 3D links down to the keyboard.
|
|
319
336
|
for(const side of [-1,1]){
|
|
320
|
-
const shoulder=[x+side*.
|
|
321
|
-
this.addLink(shoulder,elbow,.
|
|
322
|
-
this.addLink(elbow,wrist,.
|
|
337
|
+
const shoulder=[x+side*.40,.99,z+.86],elbow=[x+side*.49,.83,z+.63],wrist=[x+(side<0?-.34:.35),.675,z+.31],hand=[x+(side<0?-.30:.34),.655,z+.22];
|
|
338
|
+
this.addLink(shoulder,elbow,.042,[.12,.15,.18,1]);
|
|
339
|
+
this.addLink(elbow,wrist,.036,[.18,.22,.25,1]);
|
|
323
340
|
this.addLink(wrist,hand,.030,[.96,.63,.035,1]);
|
|
324
|
-
this.add(s,shoulder[0],shoulder[1],shoulder[2],.
|
|
325
|
-
this.add(s,elbow[0],elbow[1],elbow[2],.
|
|
326
|
-
this.add(s,wrist[0],wrist[1],wrist[2],.052,.052,.052,[.
|
|
341
|
+
this.add(s,shoulder[0],shoulder[1],shoulder[2],.080,.080,.085,[.14,.17,.20,1]);
|
|
342
|
+
this.add(s,elbow[0],elbow[1],elbow[2],.068,.068,.068,[.26,.30,.33,1]);
|
|
343
|
+
this.add(s,wrist[0],wrist[1],wrist[2],.052,.052,.052,[.21,.25,.28,1]);
|
|
327
344
|
this.add(rb,hand[0],hand[1],hand[2],.085,.045,.070,[.96,.63,.035,1]);
|
|
328
|
-
this.add(s,
|
|
329
|
-
for(let finger=0;finger<3;finger++)this.add(b,
|
|
345
|
+
this.add(s,hand[0],hand[1],hand[2],.070,.035,.055,[.98,.68,.045,1]);
|
|
346
|
+
for(let finger=0;finger<3;finger++)this.add(b,hand[0]+(finger-1)*.026,.638,hand[2]-.06,.012,.010,.046,[.22,.29,.35,1]);
|
|
330
347
|
}
|
|
331
348
|
// Small cyan service badge on the back of the chassis.
|
|
332
349
|
this.add(rb,x,by-.585,bz+.205,.13,.068,.018,[.025,.10,.14,1]);
|
|
333
350
|
this.add(rb,x,by-.585,bz+.218,.082,.016,.010,[.16,.88,.98,1]);
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
this.add(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
351
|
+
// Compact camera-facing wing panels meet the shoulder and read as distinct blue petals.
|
|
352
|
+
this.add(this.organic,x-.72,by+.39,bz+.32,.64,.62,.045,[.26,.64,.86,.46],0,-.45);
|
|
353
|
+
this.add(this.organic,x+.72,by+.39,bz+.32,.64,.62,.045,[.26,.64,.86,.46],0,.45);
|
|
354
|
+
// Fine cyan veins follow the smaller wing shape without spanning the workstation monitors.
|
|
355
|
+
for(const side of [-1,1]){
|
|
356
|
+
const root=[x+side*.38,by+.29,bz+.325];
|
|
357
|
+
const mid=[x+side*.68,by+.40,bz+.325];
|
|
358
|
+
const tip=[x+side*.98,by+.49,bz+.325];
|
|
359
|
+
this.addLink(root,mid,.0048,[.64,.88,1,.88]);
|
|
360
|
+
this.addLink(mid,tip,.0036,[.64,.88,1,.78]);
|
|
361
|
+
this.addLink([x+side*.64,by+.36,bz+.33],[x+side*.82,by+.46,bz+.33],.0028,[.82,.95,1,.66]);
|
|
362
|
+
}
|
|
340
363
|
|
|
341
364
|
}
|
|
342
365
|
// Planters and stylized leaves soften the room edges.
|
|
@@ -408,7 +431,7 @@ window.Hive3D = (function () {
|
|
|
408
431
|
if(!this.gl||!this.active||this.displayMode!=="live3d")return;const gl=this.gl;const w=Math.max(1,this.cssW),h=Math.max(1,this.cssH);const dpr=Math.min(window.devicePixelRatio||1,1.6);
|
|
409
432
|
const bw=Math.floor(w*dpr),bh=Math.floor(h*dpr);if(this.canvas.width!==bw||this.canvas.height!==bh){this.canvas.width=bw;this.canvas.height=bh;}
|
|
410
433
|
gl.viewport(0,0,bw,bh);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);gl.useProgram(this.program);
|
|
411
|
-
const radius=
|
|
434
|
+
const radius=29.0/this.zoom;const eye=[Math.sin(this.yaw)*radius,3.1+Math.sin(this.pitch)*radius*.52,Math.cos(this.yaw)*radius-1.3];const view=mat4.lookAt(eye,[0,.62,0],[0,1,0]);const proj=mat4.perspective(.46,w/h,.1,80);const vp=mat4.multiply(mat4.multiply(proj,view),mat4.translate(this.panX,this.panY,0));this.viewProj=vp;
|
|
412
435
|
gl.uniformMatrix4fv(this.uViewProj,false,vp);gl.uniform3f(this.uEyePosition,eye[0],eye[1],eye[2]);
|
|
413
436
|
this.drawBatch(this.batchBuffers&&this.batchBuffers.opaque);
|
|
414
437
|
const translucent=this.batchBuffers&&this.batchBuffers.transparent;
|