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

+147
View File
@@ -0,0 +1,147 @@
/* Single token source. Colours are the v3 porcelain/ink/seal set only:
ink, paper, white cards, pine #14544a, seal #be2a1e as accent.
No other coloured backgrounds. Spacing is an 8px grid. */
:root{
--ink:#1c2220; --ink-soft:#4a554f; --paper:#f5f3ee; --card:#ffffff;
--line:#e6e1d6; --line-strong:#d3ccbc;
--seal:#be2a1e; --seal-soft:#fdeeeb; --seal-dark:#9c2118;
--pine:#14544a; --pine-soft:#e2efea;
--amber:#92400e; --amber-soft:#fef3c7;
--success:#15803d; --danger:#b42318; --danger-soft:#fef3f2; --purple:#6941c6;
--radius:16px; --radius-sm:10px;
--shadow:0 1px 2px rgba(28,34,32,.05), 0 12px 32px rgba(28,34,32,.06);
--serif:"Songti SC","Noto Serif SC","SimSun",serif;
--sans:-apple-system,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
--mono:"IBM Plex Mono",Menlo,Consolas,monospace;
--ctl:40px;
}
*{box-sizing:border-box}
body{font-family:var(--sans);margin:0;color:var(--ink);background:var(--paper);line-height:1.6;font-size:15px}
a{color:var(--pine);text-underline-offset:3px}
:focus-visible{outline:3px solid var(--seal);outline-offset:2px}
@media (prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
.muted{color:#7a857e;font-size:13px;line-height:1.6}
.err{color:var(--danger)} .ok{color:var(--success)}
/* Top bar + fixed nav */
.site-header{position:sticky;top:0;z-index:30;background:#fff}
.topbar{display:flex;align-items:center;gap:16px;padding:16px 28px;background:#fff;border-bottom:1px solid var(--line)}
.seal-mark{flex:none;width:38px;height:38px;background:var(--seal);color:#fff;display:grid;place-items:center;font-family:var(--serif);font-size:20px;font-weight:700;border-radius:8px}
.topbar h1{font-family:var(--serif);font-size:25px;line-height:1.15;margin:0;font-weight:700;letter-spacing:1px}
.topbar .subtitle{font-size:13px;color:#7a857e;margin:1px 0 0;letter-spacing:2px}
.version{font-size:12px;color:#7a857e;border:1px solid var(--line);background:#fff;border-radius:999px;padding:6px 11px;white-space:nowrap}
.topnav{display:flex;gap:4px;flex-wrap:wrap;align-items:center;padding:8px 28px;background:#fff;border-bottom:1px solid var(--line);overflow-x:auto;scroll-margin-top:0}
.topnav a{color:var(--ink-soft);text-decoration:none;font-size:13.5px;font-weight:600;padding:6px 10px;border-radius:999px;white-space:nowrap}
.topnav a:hover{color:var(--ink);background:var(--pine-soft)}
.topnav a.active{color:var(--ink);font-weight:700}
.topnav .auto-refresh{margin-left:auto;color:#7a857e;font-size:12px;white-space:nowrap}
section[id],div[id]{scroll-margin-top:130px}
/* Layout: three columns, ChatGPT/Claude style.
Left 300px (nav + task list), centre elastic (content centred to
~800px), right 340px (detail/context). */
.shell{display:grid;grid-template-columns:300px minmax(0,1fr) 340px;gap:20px;align-items:start;padding:24px 28px 48px}
.rail{display:flex;flex-direction:column;gap:20px;min-width:0}
.center-col{min-width:0}
.center-inner{max-width:800px;margin:0 auto;width:100%;display:flex;flex-direction:column;gap:20px}
.detail-col{display:flex;flex-direction:column;gap:20px;min-width:0}
.left-nav{display:flex;flex-direction:column;gap:8px}
.left-nav button{text-align:left;border-radius:12px;background:#fff}
.left-nav button.active{background:var(--ink);color:#fff;border-color:var(--ink)}
.left-tasks input[type="text"]{width:100%}
.selected-task{border-color:var(--seal)!important;background:var(--seal-soft)!important}
.conversation{display:flex;flex-direction:column;gap:20px}
/* Cards: every module is one rounded independent white card */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:24px}
.card h2{font-family:var(--serif);font-size:18px;margin:0 0 12px;letter-spacing:.5px}
.card h3{font-family:var(--serif);font-size:15px;margin:24px 0 12px;padding-top:20px;border-top:1px solid var(--line)}
.card h3:first-of-type{border-top:0;padding-top:0}
.composer h2{font-size:22px}
.composer textarea#prompt,.composer textarea.prompt{ min-height:148px;font-size:16px;padding:16px 18px;border-radius:12px}
.composer textarea.coord{font-family:var(--mono);font-size:13px}
/* Unified controls: same-row controls are all 40px */
.bar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:12px 0}
.bar > button,.bar > label{display:inline-flex;align-items:center;height:var(--ctl);min-height:var(--ctl);padding:0 16px;box-sizing:border-box;white-space:nowrap}
.bar > input,.bar > select{height:var(--ctl);min-height:var(--ctl)}
input,select,textarea,button{font:inherit;font-size:14px;padding:10px 13px;border-radius:var(--radius-sm);border:1px solid var(--line-strong);background:#fff;color:var(--ink)}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--ink);box-shadow:0 0 0 3px rgba(28,34,32,.08)}
button{cursor:pointer;font-weight:600;border-radius:999px;padding:9px 16px}
button:hover{box-shadow:0 4px 14px rgba(28,34,32,.10)}
button:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}
button.primary{background:var(--ink);border-color:var(--ink);color:#fff}
button.primary:hover{background:#000}
button.seal{background:var(--seal);border-color:var(--seal);color:#fff}
button.seal:hover{background:var(--seal-dark)}
button.danger{color:var(--danger);border-color:#fecdca;background:var(--danger-soft)}
button.danger:hover{background:#fee4e2}
select{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:34px;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' fill='none' stroke='%23737373' stroke-width='1.5' stroke-linecap='round'/></svg>");background-repeat:no-repeat;background-position:right 12px center}
select::-ms-expand{display:none}
select:disabled,input:disabled{color:#7a857e;background:var(--paper);cursor:not-allowed}
input[type="checkbox"]{width:16px;height:16px;accent-color:var(--seal)}
input[type="file"]{height:var(--ctl);display:inline-flex;align-items:center;padding:0 12px;border:1px dashed var(--line-strong);border-radius:999px;background:#fff}
input[type="file"]::file-selector-button{height:28px;padding:0 12px;margin-right:10px;border:0;border-radius:999px;background:var(--paper);color:var(--ink);font:inherit;font-size:13px;cursor:pointer}
textarea{width:100%;min-height:84px;resize:vertical;line-height:1.65;padding:12px 14px;border-radius:12px}
.composer .bar label{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line-strong);border-radius:999px;padding:7px 13px;background:#fff;cursor:pointer;font-size:13px;font-weight:600}
.composer .bar label:has(input:checked){background:var(--ink);color:#fff;border-color:var(--ink)}
.rail .bar > input,.rail .bar > select{flex:1 1 120px;min-width:0}
.rail .bar > button,.rail .bar > label{flex:none}
.stacked{margin-bottom:12px}
.stacked + .bar{margin-top:0}
/* Data lists: one shared list style, no per-section inline styles */
.data-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
.data-list li{position:relative;margin:0;padding:10px 12px;background:var(--paper);border:1px solid var(--line);border-radius:12px;line-height:1.6;display:flex;flex-wrap:wrap;align-items:center;gap:8px;overflow-wrap:anywhere}
.data-list li .row-main{flex:1 1 200px;min-width:0}
.data-list li button{flex:none}
.data-list li.is-online,.data-list li.is-offline{padding-left:26px}
.data-list li.is-online::before,.data-list li.is-offline::before{content:"";position:absolute;left:12px;top:17px;width:8px;height:8px;border-radius:50%}
.data-list li.is-online::before{background:var(--pine)}
.data-list li.is-offline::before{background:var(--seal)}
.data-group{margin-bottom:12px}.data-group:last-child{margin-bottom:0}.data-group > b{display:block;margin-bottom:8px}
/* Status pills / dots */
.st-queued{color:#7a857e}.st-running{color:var(--amber);font-weight:700}.st-completed{color:var(--success)}.st-failed,.st-merge-failed{color:var(--danger)}.st-merged{color:var(--purple)}.st-interrupted{color:var(--amber)}
/* Task table */
.table-wrap{border:0;border-top:1px solid var(--line);overflow:auto;background:var(--card)}
table{border-collapse:collapse;width:100%;font-size:13px}
th,td{padding:12px 16px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line)}
th{background:#faf9f6;color:#7a857e;font-size:12px;font-weight:600;white-space:nowrap;letter-spacing:.3px}
tbody tr{cursor:pointer;border-left:3px solid transparent}
tbody tr:hover{background:#faf9f6}
tr.selected{background:var(--seal-soft);border-left-color:var(--seal)}
td.prompt-col{font-family:var(--serif);font-size:15px;line-height:1.55}
td.st-queued::before,td.st-running::before,td.st-completed::before,td.st-failed::before,td.st-merge-failed::before,td.st-merged::before,td.st-interrupted::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;background:currentColor;margin-right:7px;vertical-align:1px}
/* Dark result / trace block: the one dark element */
pre{background:var(--ink);color:#ece7db;border:1px solid var(--ink);border-radius:12px;padding:16px 18px;overflow:auto;max-height:420px;white-space:pre-wrap;word-break:break-word;font-size:13px;font-family:var(--mono);line-height:1.7}
.trace-box{background:var(--ink);color:#ece7db;border:1px solid var(--ink);border-left:4px solid var(--seal);border-radius:12px;padding:16px 18px;max-height:520px;overflow:auto;font-size:13px;line-height:1.9;font-family:var(--mono)}
.trace-box .muted{color:#a8b0aa}
/* Chat: one shared conversation style for trace timeline and continue.
Centred column, user right, assistant left, tool mono, status centred.
Colours are v3 tokens only; seal is only the follow-back dot. */
.chat-column{max-width:680px;margin:0 auto;width:100%}
.chat-list{position:relative;display:flex;flex-direction:column;gap:12px;max-height:520px;overflow:auto;padding:8px 4px}
.chat-row{display:flex}.chat-row.user{justify-content:flex-end}.chat-row.assistant,.chat-row.tool{justify-content:flex-start}
.chat-bubble{max-width:85%;padding:10px 14px;border-radius:16px;line-height:1.65;white-space:pre-wrap;word-break:break-word}
.chat-bubble.user{background:var(--pine);color:#fff;border-bottom-right-radius:4px}
.chat-bubble.assistant{background:#fff;border:1px solid var(--line);border-bottom-left-radius:4px}
.chat-bubble.tool{background:var(--paper);border:1px solid var(--line);font-family:var(--mono);font-size:13px;border-bottom-left-radius:4px}
.chat-label{font-size:12px;color:#7a857e;margin-bottom:4px}
.chat-bubble.user .chat-label{color:#cfe3dd}
.chat-status{text-align:center;font-size:12px;color:#7a857e}
.chat-follow{position:sticky;bottom:8px;align-self:center;background:var(--seal);border-color:var(--seal);color:#fff;z-index:2}
.chat-input-bar{position:sticky;bottom:0;display:flex;gap:8px;align-items:center;background:#fff;padding:12px 0;border-top:1px solid var(--line);z-index:2}
.chat-input{flex:1;height:var(--ctl);min-height:var(--ctl);resize:none}
.chat-send{height:var(--ctl);flex:none}
@media (max-width:1240px){
.shell{grid-template-columns:280px minmax(0,1fr)}
.detail-col{grid-column:1 / -1}
}
@media (max-width:900px){
.shell{grid-template-columns:1fr;padding:16px 14px 32px;gap:16px}
.topbar{padding:12px 16px}.topbar h1{font-size:19px}.topbar .subtitle{display:none}
.topnav{flex-wrap:nowrap;overflow-x:auto;padding:8px 16px}.topnav a{flex:none}.topnav .auto-refresh{margin-left:0;flex:none}
.card{padding:20px}
th:nth-child(2),td:nth-child(2),th:nth-child(3),td:nth-child(3),th:nth-child(7),td:nth-child(7){display:none}
}
@media (max-width:560px){
.version{display:none}.card{padding:16px}
th:nth-child(6),td:nth-child(6),th:nth-child(9),td:nth-child(9){display:none}
.bar > input[type="text"],.bar > input[type="password"],.bar > select{flex:1 1 100%}
th,td{padding:10px 12px}
}