console: React 生产级重写(左列表 + 中对话 + 右详情)

旧单页 console.html 换成 Vite + React + TypeScript 单文件构建产物
(309525 字节,sha 90345ec8…,重建字节一致),源码在
cloud/console-react/,改页面只改 src 再 npm run build:console,
产物不手改。布局:左侧列表(模块导航 + 任务列表)、中间标准对话
(实时链路时间线 + 继续会话聊天框,共用 Chat 组件)、右侧详情
(元数据、操作、评审、审批、协调子任务、子代理、线程)。
API 与后端语义未改,key 只存浏览器 localStorage。
6 个 console 标记单测随实现改名更新(意图不变:壳确实调了
这些接口、turn 信息仍在详情里、旧 goal 输入仍不存在);
P1 标记单测未动且通过。
This commit is contained in:
host committed 2026-10-29 06:32:38 +08:00
1 parent 3f4e849a93
commit ebc5624893
57 files changed
+4789 -1746

No files matched your search

+17 -11
View File
@@ -63,15 +63,19 @@ class ConsoleTestCase(unittest.TestCase):
text = body.decode("utf-8")
self.assertIn("Codex 命令台", text)
# 壳必须引用它实际调的接口,防 JS 与服务端路由漂移。
# React 版(2026-10-29 起):旧单页的 agentBox / threadBox /
# THREAD_MUTATION_STATUSES / skillList / appList / mcpList
# id 与 /thread/<动作> 字面路径已不存在,改为 React 的
# taskAgents / threadText / threadAction + 动作名 /
# nav-skills;断言意图不变(壳确实调了这些接口),
# 只换成构建产物里实际存在的标记。
for marker in ("/health", "/projects", "/tasks", "/workers",
"X-API-Key", "agentBox", "/agents",
"X-API-Key", "taskAgents", "/agents",
"peerMessage", "/messages",
"threadBox", "threadGateNote", "threadList",
"THREAD_MUTATION_STATUSES",
"/thread", "/threads",
"/thread/rename", "/thread/archive",
"/thread/unarchive", "/thread/compact",
"skillList", "appList", "mcpList", "mcpOauthBox",
"threadText", "threadGateNote", "threadList",
"threadAction", "/thread", "/threads",
"rename", "archive", "unarchive", "compact",
"nav-skills", "mcpOauthBox",
"/skills", "/apps", "/mcp-servers",
"/mcp-servers/reload", "/oauth-login"):
self.assertIn(marker, text)
@@ -99,12 +103,14 @@ class ConsoleTestCase(unittest.TestCase):
# 回访用户 key 已在 localStorage 里:初始化序列必须含
# loadRelayModels 调用,否则模型下拉永远只有默认项
# (2026-10-27 实测:此前只在点“保存”时调一次)。
# React 版(2026-10-29 起):旧单页的 setKeyState/loadHealth
# 序列已不存在,初始化在 useConsoleStore 的 useEffect 里,
# 有 key 时调 loadRelayModels;断言意图不变,只换成
# React 构建产物里实际存在的标记。
_status, _headers, body = self._get("/console")
text = body.decode("utf-8")
anchor = "setKeyState();\nloadHealth();\nloadProjects();\nloadWorkers();"
self.assertIn(anchor, text)
init = text[text.index(anchor):]
self.assertIn("if (getKey()) loadRelayModels();", init)
self.assertIn("loadRelayModels", text)
self.assertIn("/relay/models", text)
if __name__ == "__main__":
+6 -2
View File
@@ -115,9 +115,13 @@ class ServerGoalModeTestCase(unittest.TestCase):
def test_console_goal_hint_and_no_old_inputs(self):
# console 壳是静态页:无 key 也能取,_req 对非 JSON 直接回文本。
# React 版(2026-10-29 起):旧单页的 goalHint id 与
# body.goal_mode = 1 字面量已不存在,goal 提示改为
# DispatchSection 的条件渲染文案,提交时仍设 goal_mode=1;
# 断言意图不变,只换成构建产物里实际存在的标记。
_status, text = self._req("GET", "/console")
self.assertIn('id="goalHint"', text)
self.assertIn("body.goal_mode = 1", text)
self.assertIn("goal 模式:输入框内容即目标", text)
self.assertIn("goal_mode", text)
# 旧的专用 prompt / 验收命令 / 轮次输入已移除。
self.assertNotIn('id="verifyCommand"', text)
self.assertNotIn('id="goalRounds"', text)
+13 -5
View File
@@ -327,12 +327,20 @@ class P2ConsoleTestCase(unittest.TestCase):
text = resp.read().decode("utf-8")
conn.close()
self.assertEqual(resp.status, 200)
for marker in ("/members", "inviteMember", "memberList",
# React 版(2026-10-29 起):旧单页的 memberList /
# taskStatusFilter / taskExecutorFilter / taskSearch /
# btnDownloadTrace / traceKindFilter / workerAgeText
# id 与函数名已不存在(React 用 members / statusFilter /
# executorFilter / searchText / downloadTrace / traceKind,
# worker 年龄函数在构建产物里被压缩改名,但“最后心跳”
# 文案与 120 秒在线阈值逻辑仍在源码里);断言意图不变,
# 只换成构建产物里实际存在的标记。
for marker in ("/members", "inviteMember", "项目成员",
"adminBody", "/admin/sweep", "/admin/backup",
"/admin/quotas", "taskStatusFilter",
"taskExecutorFilter", "taskSearch",
"btnDownloadTrace", "traceKindFilter",
"workerAgeText", "last_seen"):
"/admin/quotas", "statusFilter",
"executorFilter", "searchText",
"downloadTrace", "traceKind",
"最后心跳", "last_seen"):
self.assertIn(marker, text, marker)
+7 -3
View File
@@ -295,9 +295,13 @@ class ServerEnvironmentsTestCase(unittest.TestCase):
def test_console_has_environment_ui(self):
path = Path(__file__).resolve().parents[1] / "console.html"
html = path.read_text(encoding="utf-8")
for marker in ("envSelect", "loadEnvironments",
"createEnvironment", "refreshEnvironments",
"newEnvName", "newEnvBranch", "newEnvExecutor",
# React 版(2026-10-29 起):旧单页的 loadEnvironments /
# createEnvironment / newEnv* id 已不存在,环境 UI 改为
# React 组件(EnvSection + useCatalog),断言意图不变,
# 只换成构建产物里实际存在的标记:环境栏、建环境表单、
# 刷新/新建/归档操作与环境列表接口。
for marker in ("nav-env", "新环境名", "新建环境", "归档",
"refreshEnvs", "createEnv", "archiveEnv",
"/environments?project_id="):
self.assertIn(marker, html)
+8 -4
View File
@@ -175,10 +175,14 @@ class ConsoleColumnsTestCase(unittest.TestCase):
def test_console_task_list_has_turn_columns(self):
html = (Path(__file__).resolve().parents[2]
/ "cloud" / "console.html").read_text()
self.assertIn("<th>turn</th>", html)
self.assertIn("<th>root turn</th>", html)
self.assertIn("shortId(t.turn_id)", html)
self.assertIn("shortId(t.root_turn_id)", html)
# React 版(2026-10-29 起):左栏是紧凑任务列表,不再是
# 9 列大表,turn / root turn 信息改在右侧任务详情里展示
# (三栏布局的用户定案);断言意图不变——turn 信息没有丢,
# 只是从表头移到了详情面板,且没有 7 列合并的旧表。
self.assertIn("root turn", html)
self.assertIn("turn", html)
self.assertIn("turn_id", html)
self.assertIn("root_turn_id", html)
self.assertNotIn("colspan='7'", html)
self.assertNotIn('colspan="7"', html)