Process timeline: a run of consecutive thinking and tool calls is one
block that folds to a single line ("思考 4 次 · 工具 6 次"; while running
it shows the current action) and opens into a compact timeline, one row
per thought or call. Visible text, a note or the next prompt closes the
block; history and the live stream follow the same rule. show_thinking
now also applies while streaming instead of forcing thoughts open.
Layout: the avatar gutter and the flex-wrap that dropped it under long
answers are gone, so answer, process blocks, notes and composer share one
left edge. The text column and composer are both 720px (--content); the
rail and run panel grow with clamp() on wide screens. A rebuild after
each turn no longer replays every entrance animation, and the turn time
survives it. Message actions are icon buttons and only appear under text.
Images: a prompt's images sit above the bubble, right-aligned (one keeps
its shape, several are 152px rounded tiles) and open in a lightbox (Esc,
arrow keys). Images uploaded from the page show as thumbnails over the
composer and appear on the prompt as soon as it is sent.
Also: ?theme= is no longer overridden by app.js, the user bubble is
visible in the light theme, and the narrow top bar no longer wraps.
251 lines
13 KiB
HTML
251 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<meta name="color-scheme" content="dark light">
|
|
<title>CST-Agent</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body>
|
|
<!-- one icon set, drawn as a sprite so every glyph shares the same 24px grid,
|
|
stroke weight and round caps: mixed emoji/unicode never looks like a family -->
|
|
<svg id="atlas" aria-hidden="true" hidden>
|
|
<symbol id="i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol>
|
|
<symbol id="i-panel-left" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="3"/><path d="M9.5 5v14"/></symbol>
|
|
<symbol id="i-panel-right" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="3"/><path d="M14.5 5v14"/></symbol>
|
|
<symbol id="i-chevron" viewBox="0 0 24 24"><path d="M6 9.5l6 5 6-5"/></symbol>
|
|
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
|
|
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M20 12a8 8 0 1 1-2.6-5.9M20 4.5V9h-4.5"/></symbol>
|
|
<symbol id="i-settings" viewBox="0 0 24 24"><path d="M4 6.5h5M15 6.5h5M4 12h7M17 12h3M4 17.5h5M15 17.5h5"/><circle cx="11.5" cy="6.5" r="2"/><circle cx="15" cy="12" r="2"/><circle cx="11.5" cy="17.5" r="2"/></symbol>
|
|
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M20 14.6A8.6 8.6 0 0 1 9.4 4 8.6 8.6 0 1 0 20 14.6z"/></symbol>
|
|
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.5v2.2M12 19.3v2.2M2.5 12h2.2M19.3 12h2.2M5.2 5.2l1.6 1.6M17.2 17.2l1.6 1.6M18.8 5.2l-1.6 1.6M6.8 17.2l-1.6 1.6"/></symbol>
|
|
<symbol id="i-more" viewBox="0 0 24 24"><circle cx="5.5" cy="12" r="1.6" class="f"/><circle cx="12" cy="12" r="1.6" class="f"/><circle cx="18.5" cy="12" r="1.6" class="f"/></symbol>
|
|
<symbol id="i-send" viewBox="0 0 24 24"><path d="M12 19.5V5M5.8 11.2 12 5l6.2 6.2"/></symbol>
|
|
<symbol id="i-stop" viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" rx="2.5" class="f"/></symbol>
|
|
<symbol id="i-image" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="14" rx="3"/><path d="M4 15.5 8.6 11l4 4 2.8-2.8L20.5 16"/><circle cx="9" cy="9.4" r="1.5"/></symbol>
|
|
<symbol id="i-clipboard" viewBox="0 0 24 24"><rect x="8.5" y="3.5" width="7" height="4" rx="1.6"/><path d="M15.5 5.5h3v15h-13v-15h3M9 12h6M9 15.5h4"/></symbol>
|
|
<symbol id="i-list" viewBox="0 0 24 24"><path d="M4 6.8h2M4 12h2M4 17.2h2M9 6.8h11M9 12h11M9 17.2h11"/></symbol>
|
|
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="8.5" y="8.5" width="11" height="11" rx="2.6"/><path d="M15.5 8.5V7a2.5 2.5 0 0 0-2.5-2.5H6.5A2.5 2.5 0 0 0 4 7v6.5A2.5 2.5 0 0 0 6.5 16h1.5"/></symbol>
|
|
<symbol id="i-compress" viewBox="0 0 24 24"><path d="M8 3.5V9H3M16 20.5V15h5M3 9h4M16 15h5"/></symbol>
|
|
<symbol id="i-undo" viewBox="0 0 24 24"><path d="M9.5 5.5 4 11l5.5 5.5"/><path d="M4 11h10.2a5.2 5.2 0 0 1 0 10.4H8.5"/></symbol>
|
|
<symbol id="i-close" viewBox="0 0 24 24"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></symbol>
|
|
<symbol id="i-check" viewBox="0 0 24 24"><path d="M5 12.6l4.6 4.6L19 7.6"/></symbol>
|
|
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 3.5 5 6.2v5.4c0 4.2 2.9 7.5 7 8.9 4.1-1.4 7-4.7 7-8.9V6.2z"/></symbol>
|
|
<symbol id="i-bolt" viewBox="0 0 24 24"><path d="M13.4 3 5.5 13.6H11l-.9 7.4L18.5 10H13z"/></symbol>
|
|
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M12 4 2.8 20h18.4z"/><path d="M12 10v4.4M12 17.2v.2"/></symbol>
|
|
<symbol id="i-cpu" viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="10" rx="2.6"/><path d="M10 3v4M14 3v4M10 17v4M14 17v4M3 10h4M3 14h4M17 10h4M17 14h4"/></symbol>
|
|
<symbol id="i-lv1" viewBox="0 0 24 24"><path d="M5 18.5V13h3.4v5.5z" class="f"/></symbol>
|
|
<symbol id="i-lv2" viewBox="0 0 24 24"><path d="M5 18.5V13h3.4v5.5zM10.3 18.5V9.6h3.4v8.9z" class="f"/></symbol>
|
|
<symbol id="i-lv3" viewBox="0 0 24 24"><path d="M5 18.5V13h3.4v5.5zM10.3 18.5V9.6h3.4v8.9zM15.6 18.5V6.4h3.4v12.1z" class="f"/></symbol>
|
|
<symbol id="i-lv4" viewBox="0 0 24 24"><path d="M4 18.5V13h2.6v5.5zM8.7 18.5V10h2.6v8.5zM13.4 18.5V6.8h2.6v11.7zM18.1 18.5V4h2.6v14.5z" class="f"/></symbol>
|
|
<symbol id="i-file" viewBox="0 0 24 24"><path d="M13.5 3.5H7A2.5 2.5 0 0 0 4.5 6v12A2.5 2.5 0 0 0 7 20.5h10A2.5 2.5 0 0 0 19.5 18V9.5z"/><path d="M13.5 3.5v6h6"/></symbol>
|
|
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.2V12l3.2 2"/></symbol>
|
|
<symbol id="i-edit" viewBox="0 0 24 24"><path d="M14.8 5.2l4 4L9 19H5v-4z"/><path d="M12.6 7.4l4 4"/></symbol>
|
|
<symbol id="i-retry" viewBox="0 0 24 24"><path d="M4.5 12a7.5 7.5 0 0 1 13-5.1M19.5 12a7.5 7.5 0 0 1-13 5.1"/><path d="M17.8 3.6v3.6h-3.6M6.2 20.4v-3.6h3.6"/></symbol>
|
|
<symbol id="i-left" viewBox="0 0 24 24"><path d="M14.5 6l-6 6 6 6"/></symbol>
|
|
<symbol id="i-right" viewBox="0 0 24 24"><path d="M9.5 6l6 6-6 6"/></symbol>
|
|
<symbol id="i-folder" viewBox="0 0 24 24"><path d="M3.5 6.5A1.5 1.5 0 0 1 5 5h4l2 2.5h8A1.5 1.5 0 0 1 20.5 9v9A1.5 1.5 0 0 1 19 19.5H5A1.5 1.5 0 0 1 3.5 18z"/></symbol>
|
|
</svg>
|
|
|
|
<div id="app">
|
|
|
|
<!-- rail: identity, one primary action, sessions, identity again.
|
|
Everything else is a popover anchored where it belongs. -->
|
|
<aside id="side">
|
|
<div class="side-inner">
|
|
<div class="side-head">
|
|
<div class="brand">
|
|
<span class="brand-name">CST-Agent</span>
|
|
</div>
|
|
<button id="btn-collapse" class="icon" title="收起侧栏 (Ctrl+B)">
|
|
<svg class="i"><use href="#i-panel-left"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<button id="btn-new" class="new-task">
|
|
<svg class="i"><use href="#i-plus"/></svg><span>新建任务</span>
|
|
</button>
|
|
|
|
<div class="side-sec">
|
|
<span>最近</span>
|
|
<button id="btn-refresh-sessions" class="icon tiny" title="刷新">
|
|
<svg class="i"><use href="#i-refresh"/></svg>
|
|
</button>
|
|
</div>
|
|
<input id="sess-search" class="sess-search" type="search" placeholder="搜索会话" autocomplete="off">
|
|
<div id="sessions-list" class="sessions"></div>
|
|
|
|
<div class="side-foot">
|
|
<div class="who">
|
|
<span class="avatar">C</span>
|
|
<span class="who-text">
|
|
<b id="foot-ws">workspace</b>
|
|
<i id="foot-session">-</i>
|
|
</span>
|
|
</div>
|
|
<button id="btn-settings" class="icon" title="设置">
|
|
<svg class="i"><use href="#i-settings"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- main column -->
|
|
<main id="main">
|
|
<header id="topbar">
|
|
<button id="btn-side" class="icon" title="展开侧栏 (Ctrl+B)">
|
|
<svg class="i"><use href="#i-panel-left"/></svg>
|
|
</button>
|
|
<button id="btn-drawer" class="icon only-narrow" title="侧栏">
|
|
<svg class="i"><use href="#i-menu"/></svg>
|
|
</button>
|
|
<button id="chip-model" class="chip model" title="模型与推理强度">
|
|
<svg class="i"><use href="#i-cpu"/></svg><span class="lbl">-</span>
|
|
<svg class="i chev"><use href="#i-chevron"/></svg>
|
|
</button>
|
|
<span class="grow"></span>
|
|
<div class="seg" id="seg-mode" title="计划模式只读,不改文件">
|
|
<button data-mode="build">构建</button>
|
|
<button data-mode="plan">计划</button>
|
|
</div>
|
|
<button id="chip-approval" class="chip quiet" title="审批权限">
|
|
<svg class="i"><use href="#i-shield"/></svg><span class="lbl">auto</span>
|
|
<svg class="i chev"><use href="#i-chevron"/></svg>
|
|
</button>
|
|
<button id="btn-theme" class="icon" title="明暗切换">
|
|
<svg class="i only-dark"><use href="#i-moon"/></svg>
|
|
<svg class="i only-light"><use href="#i-sun"/></svg>
|
|
</button>
|
|
<button id="btn-panel" class="icon" title="运行详情 (Ctrl+J)">
|
|
<svg class="i"><use href="#i-panel-right"/></svg>
|
|
</button>
|
|
<button id="btn-menu" class="icon" title="更多">
|
|
<svg class="i"><use href="#i-more"/></svg>
|
|
</button>
|
|
</header>
|
|
|
|
<div id="scroll">
|
|
<section id="empty">
|
|
<p class="ready">准备好开始构建了吗</p>
|
|
</section>
|
|
<div id="log"></div>
|
|
</div>
|
|
|
|
<button id="to-bottom" class="round" hidden title="回到最新">
|
|
<svg class="i"><use href="#i-chevron"/></svg>
|
|
</button>
|
|
|
|
<footer id="composer">
|
|
<div id="pending" class="pending" hidden></div>
|
|
<div id="edit-bar" class="edit-bar" hidden>
|
|
<span>编辑上一条消息:发送后替换它并重新回答(文件改动保留,需要时先撤销)</span>
|
|
<span class="grow"></span>
|
|
<button id="btn-edit-cancel" class="link">取消</button>
|
|
</div>
|
|
|
|
<!-- permission gate: inline, the turn really is waiting on it -->
|
|
<div id="gate" class="gate" hidden>
|
|
<div class="gate-head">
|
|
<span class="gate-title" id="gate-title">需要确认</span>
|
|
<span class="grow"></span>
|
|
<span class="gate-timer" id="gate-timer"></span>
|
|
</div>
|
|
<pre id="gate-detail"></pre>
|
|
<div class="gate-keys" id="gate-keys"></div>
|
|
<div class="gate-row" id="gate-buttons"></div>
|
|
</div>
|
|
|
|
<div id="slash" class="slash" role="listbox" hidden></div>
|
|
<div class="box">
|
|
<textarea id="input" rows="1" placeholder="给 CST-Agent 派个任务…(/ 打开命令,F1 看快捷键)"></textarea>
|
|
<div class="box-row">
|
|
<button id="btn-plus" class="icon att" title="添加图片或计划模式">
|
|
<svg class="i"><use href="#i-plus"/></svg>
|
|
</button>
|
|
<button id="btn-undo" class="chip quiet" title="撤销上一次文件写入">
|
|
<svg class="i"><use href="#i-undo"/></svg><span>撤销</span>
|
|
</button>
|
|
<span class="grow"></span>
|
|
<button id="cost" class="cost" title="用量详情"></button>
|
|
<button id="btn-stop" class="icon stop" hidden title="停止">
|
|
<svg class="i"><use href="#i-stop"/></svg>
|
|
</button>
|
|
<button id="btn-send" class="round send" title="发送 (Enter)">
|
|
<svg class="i"><use href="#i-send"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="hint-line">
|
|
<span id="status">就绪</span>
|
|
<span class="grow"></span>
|
|
<span id="usage" class="quiet"></span>
|
|
</div>
|
|
<input type="file" id="file" accept="image/*" multiple hidden>
|
|
</footer>
|
|
</main>
|
|
|
|
<!-- right panel: the run, not the chat -->
|
|
<aside id="panel">
|
|
<div class="panel-inner">
|
|
<div class="panel-head">
|
|
<span>运行详情</span>
|
|
<span class="grow"></span>
|
|
<button id="btn-panel-close" class="icon tiny" title="关闭">
|
|
<svg class="i"><use href="#i-close"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<section class="card" id="card-todo">
|
|
<h4>任务清单</h4>
|
|
<div id="todo-body" class="muted">模型还没有给出任务清单。</div>
|
|
</section>
|
|
|
|
<section class="card" id="card-files">
|
|
<h4>文件改动 <span class="badge" id="files-count">0</span>
|
|
<span class="grow"></span><button id="btn-diff" class="link" title="git status + git diff">查看 diff</button></h4>
|
|
<div id="files-body" class="muted">本轮还没有改动文件。</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h4>上下文</h4>
|
|
<div class="kv"><span>占用</span><b id="kv-ctx">0</b></div>
|
|
<div class="kv"><span>缓存命中</span><b id="kv-cache">-</b></div>
|
|
</section>
|
|
</div>
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
<div id="backdrop"></div>
|
|
<div id="pop" class="pop" hidden></div>
|
|
<div id="modal" hidden><div id="modal-box"></div></div>
|
|
<div id="lightbox" hidden>
|
|
<img id="lb-img" alt="">
|
|
<button id="lb-close" class="lb-btn" title="关闭 (Esc)"><svg class="i"><use href="#i-close"/></svg></button>
|
|
<button id="lb-prev" class="lb-btn" title="上一张 (←)"><svg class="i"><use href="#i-left"/></svg></button>
|
|
<button id="lb-next" class="lb-btn" title="下一张 (→)"><svg class="i"><use href="#i-right"/></svg></button>
|
|
<span id="lb-count"></span>
|
|
</div>
|
|
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
|
<script>
|
|
/* Layout is decided here, before app.js runs and before the first paint: the
|
|
* rail must not flash open and then fold, and a broken script further down can
|
|
* no longer take the layout down with it. */
|
|
(() => {
|
|
const q = new URLSearchParams(location.search);
|
|
const pick = (key) => {
|
|
const v = q.get(key);
|
|
if (v === '0' || v === '1') return v === '0';
|
|
return localStorage.getItem('cst.' + key) === '0';
|
|
};
|
|
if (pick('side')) document.body.classList.add('no-side');
|
|
if (pick('panel')) document.body.classList.add('no-panel');
|
|
const t = q.get('theme') || localStorage.getItem('cst.theme');
|
|
if (t) document.documentElement.dataset.theme = t;
|
|
})();
|
|
</script>
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|