- 抽屉和会话页渲染同一套 GoalComposer / AgentRunPanel;抽屉标题即最近会话切换, 头部只有新对话、在页面打开、关闭;去掉右下角悬浮按钮和最小化小窗 - 会话列表合一:对话与巡检混排带类型小标签,搜索 + 一个状态下拉,只列顶层会话 (GET /agent/runs?roots=true,新增可选参数) - 子会话卡片:由镜像的 subagent_* 事件折成,显示任务、状态、当前步骤、用时、调用次数、费用, 可展开看过程、可打开;顶部「N 个子会话运行中」跳到卡片;subagent_completed 加可选 durationMs - 回答去内部字段:汇报规则禁止照抄键值与字段表;展示层 stripInternalFields 把字段行 / 字段表移进技术信息 - 开场建议按页面给 2 条完整问法、点了直接发;指令只留 /先给方案,/重跑 改成「重新运行」按钮 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
// 智能体页面的导航状态(纯函数 + 模块级记忆,单测覆盖):
|
|
// - 会话链接、列表页链接的拼法(保留演示开关);
|
|
// - 会话列表的筛选与滚动位置(列表页 ↔ 会话页之间保留,刷新页面才清)。
|
|
// 列表不再分「对话 / 自动巡检」两栏:旧链接里的 ?tab= 直接忽略。
|
|
|
|
function withQuery(path: string, q: Record<string, string | null>): string {
|
|
const p = new URLSearchParams();
|
|
for (const [k, v] of Object.entries(q)) if (v) p.set(k, v);
|
|
const s = p.toString();
|
|
return s ? `${path}?${s}` : path;
|
|
}
|
|
|
|
/** 会话页链接 */
|
|
export function agentRunHref(id: string, mock = false): string {
|
|
return withQuery(`/console/agent/${id}`, { mock: mock ? "1" : null });
|
|
}
|
|
|
|
/** 列表页链接 */
|
|
export function agentListHref(mock = false): string {
|
|
return withQuery("/console/agent", { mock: mock ? "1" : null });
|
|
}
|
|
|
|
/** 会话列表的记忆:搜索、状态筛选、滚动位置 */
|
|
export type ListMemory = {
|
|
q: string;
|
|
view: "all" | "pending" | "active" | "failed";
|
|
scroll: number;
|
|
};
|
|
export const newListMemory = (): ListMemory => ({ q: "", view: "all", scroll: 0 });
|
|
export const pageListMemory: ListMemory = newListMemory();
|
|
|
|
/**
|
|
* 当前项要不要滚进可见区:完全看不见才滚,只滚到刚好露出(nearest),返回新的 scrollTop;不用滚时为 null。
|
|
* top / height 是当前项相对滚动内容顶部的位置。
|
|
*/
|
|
export function revealScrollTop(
|
|
item: { top: number; height: number },
|
|
viewport: { scrollTop: number; height: number },
|
|
): number | null {
|
|
const bottom = item.top + item.height;
|
|
const viewBottom = viewport.scrollTop + viewport.height;
|
|
if (item.top >= viewport.scrollTop && bottom <= viewBottom) return null;
|
|
if (item.top < viewport.scrollTop) return Math.max(0, item.top);
|
|
return Math.max(0, bottom - viewport.height);
|
|
}
|
|
|
|
// ── 输入草稿:按会话(和两处新对话输入框)分开记,切换会话、关抽屉不丢;发送成功后清掉。模块级,刷新页面才清 ──
|
|
|
|
const drafts = new Map<string, string>();
|
|
export const DRAFT_PAGE_GOAL = "goal:page";
|
|
export const DRAFT_DRAWER_GOAL = "goal:drawer";
|
|
export const runDraftKey = (runId: string) => `run:${runId}`;
|
|
|
|
export function readDraft(key: string | undefined): string {
|
|
return key ? (drafts.get(key) ?? "") : "";
|
|
}
|
|
|
|
export function writeDraft(key: string | undefined, text: string): void {
|
|
if (!key) return;
|
|
if (text.trim()) drafts.set(key, text);
|
|
else drafts.delete(key);
|
|
}
|