chore(release): v0.3.41
智能体抽屉读 ?open= 上下文、回复链接用抽屉地址;抽屉打开不亮焦点框;语句统计重复 key。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
78a4d562f0
commit
1ade49fe8e
22 files changed
+62
-33
No files matched your search
@@ -40,6 +40,7 @@ import { Markdown } from "~/components/agentui/markdown.tsx";
|
||||
import { MessageMarker, MessageTyping } from "~/components/agentui/message.tsx";
|
||||
import { StreamingResponse } from "~/components/agentui/streaming-response.tsx";
|
||||
import { consolePath } from "~/components/console/nav.ts";
|
||||
import { recordPath } from "~/components/console/record-sheet.tsx";
|
||||
import { StatusBadge } from "~/components/console/status-badge.tsx";
|
||||
import { TechInfo } from "~/components/console/tech-info.tsx";
|
||||
import { formatUsd } from "~/components/schedules/labels.ts";
|
||||
@@ -673,14 +674,14 @@ export function resultFacts(report: GoalReport | null): ResultFact[] {
|
||||
const only = r.downloads.length === 1 ? r.downloads[0] : undefined;
|
||||
facts.push({
|
||||
text: `提交了 ${downloads} 个下载`,
|
||||
to: only ? `/console/downloads/${only.downloadTaskId}` : r.downloads.length ? "/console/downloads" : undefined,
|
||||
to: only ? recordPath("downloads", only.downloadTaskId) : r.downloads.length ? "/console/downloads" : undefined,
|
||||
});
|
||||
}
|
||||
if (done.length) {
|
||||
const only = done.length === 1 ? done[0] : undefined;
|
||||
facts.push({
|
||||
text: `完成 ${done.length} 次整理`,
|
||||
to: only ? `/console/organize/${only.recordId}` : "/console/organize",
|
||||
to: only ? recordPath("organize", only.recordId) : "/console/organize",
|
||||
});
|
||||
}
|
||||
if (r.imported && r.imported !== done.length) facts.push({ text: `已整理 ${r.imported} 个` });
|
||||
|
||||
@@ -109,7 +109,7 @@ export function AgentDrawer() {
|
||||
const content = useRef<HTMLDivElement>(null);
|
||||
// 记下最近的非智能体页面:会话页「在侧边打开」回到这里
|
||||
useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]);
|
||||
useEffect(() => syncAgentContextToPath(pathname), [pathname]);
|
||||
useEffect(() => syncAgentContextToPath(pathname, search), [pathname, search]);
|
||||
const name = useContextName(context);
|
||||
const mock = agentMockEnabled();
|
||||
// 目标前缀带名称和 ID:名称给人看,ID 给模型用
|
||||
@@ -210,7 +210,7 @@ export function AgentDrawer() {
|
||||
|
||||
/** 顶栏胶囊里的智能体按钮(没有侧栏的窄屏):与快捷键、侧栏入口同一套切换(页面内按钮用 openAgentDrawer 传更完整的上下文) */
|
||||
export function AgentDrawerTrigger({ className }: { className?: string }) {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname, search } = useLocation();
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -219,7 +219,7 @@ export function AgentDrawerTrigger({ className }: { className?: string }) {
|
||||
title={`智能体(${agentShortcutLabel()})`}
|
||||
className={className}
|
||||
{...{ [AGENT_LAUNCHER_ATTR]: "" }}
|
||||
onClick={() => toggleAgentDrawer(pathname)}
|
||||
onClick={() => toggleAgentDrawer(pathname, search)}
|
||||
>
|
||||
<Bot />
|
||||
</Button>
|
||||
|
||||
@@ -80,6 +80,11 @@ describe("侧栏上下文", () => {
|
||||
expect(ctx?.taskId).toBe("5b4e76e0-f27f-4e7a-8e08-71486387df1d");
|
||||
expect(contextFromPath("/console/sites/hdhome")?.siteId).toBe("hdhome");
|
||||
expect(contextFromPath("/console/agent")).toBeNull();
|
||||
// 列表页的记录抽屉(?open=)与日志页的任务抽屉(?run=)
|
||||
expect(contextFromPath("/console/sites", "?open=hdhome&tab=rss")?.siteId).toBe("hdhome");
|
||||
expect(contextFromPath("/console/organize", "?open=r1")?.recordId).toBe("r1");
|
||||
expect(contextFromPath("/console/logs", "?type=task&run=t9")?.taskId).toBe("t9");
|
||||
expect(contextFromPath("/console/sites", "?tab=rss")).toBeNull();
|
||||
const goal = `${contextPrefix(ctx)}排查这个任务`;
|
||||
expect(splitContextGoal(goal)).toEqual({ context: expect.stringContaining("taskId="), text: "排查这个任务" });
|
||||
});
|
||||
|
||||
@@ -662,7 +662,7 @@ describe("实际结果行(FIX-9):只列系统核实的事实", () => {
|
||||
createElement(ResultLine, { run: RUN, report: done, live: withText("提交了 2 个下载,整理了 1 个。") }),
|
||||
);
|
||||
expect(text(html).replace(/\s*、\s*/g, "、")).toContain("提交了 2 个下载、完成 1 次整理、1 个进行中 · 41 秒");
|
||||
expect(html).toContain(`href="/console/organize/${ID(3)}"`);
|
||||
expect(html).toContain(`href="/console/organize?open=${ID(3)}"`);
|
||||
expect(html).toContain('href="/console/downloads"');
|
||||
expect(html).not.toContain("与汇报不一致");
|
||||
});
|
||||
|
||||
@@ -87,9 +87,9 @@ export function newAgentConversation() {
|
||||
* 抽屉开着时路由变了(浏览器前进后退、会话里的链接):还在新对话、上下文是按路由推断的,就换成新页面的对象;
|
||||
* 页面按钮带进来的上下文、已经在看的会话不动。
|
||||
*/
|
||||
export function syncAgentContextToPath(pathname: string) {
|
||||
export function syncAgentContextToPath(pathname: string, search = "") {
|
||||
if (!state.open || state.runId || (state.context && !state.context.fromPath)) return;
|
||||
const next = contextFromPath(pathname);
|
||||
const next = contextFromPath(pathname, search);
|
||||
if (JSON.stringify(next) === JSON.stringify(state.context)) return;
|
||||
state = { ...state, context: next };
|
||||
emit();
|
||||
@@ -144,17 +144,24 @@ function focusAgentPageInput(): boolean {
|
||||
}
|
||||
|
||||
/** 快捷键 / 侧栏主入口 / 顶栏胶囊里的按钮共用的切换 */
|
||||
export function toggleAgentDrawer(pathname: string) {
|
||||
export function toggleAgentDrawer(pathname: string, search = "") {
|
||||
const action = toggleAction(state, pathname);
|
||||
if (action === "close") setAgentDrawerOpen(false);
|
||||
// 智能体页面本身就是会话:聚焦页面的输入框,找不到输入框才开抽屉
|
||||
else if (action === "focus-page" && focusAgentPageInput()) return;
|
||||
else if (state.runId) setAgentDrawerOpen(true);
|
||||
else openAgentDrawer(contextFromPath(pathname));
|
||||
else openAgentDrawer(contextFromPath(pathname, search));
|
||||
}
|
||||
|
||||
/** 按当前路由推断页面上下文(顶栏按钮用;页面内按钮直接传更完整的上下文) */
|
||||
export function contextFromPath(pathname: string): AgentContext | null {
|
||||
export function contextFromPath(pathname: string, search = ""): AgentContext | null {
|
||||
// 列表页上开着的记录抽屉(?open=,console-ia §9)等同于旧的详情页;日志页的任务抽屉是 ?run=
|
||||
const q = new URLSearchParams(search);
|
||||
const open = q.get("open");
|
||||
const listPage = pathname.match(/^\/console\/(sites|downloads|organize)\/?$/);
|
||||
if (listPage && open) return contextFromPath(`/console/${listPage[1]}/${open}`);
|
||||
const run = q.get("run");
|
||||
if (/^\/console\/logs\/?$/.test(pathname) && run) return contextFromPath(`/console/engine/${run}`);
|
||||
const m = pathname.match(/^\/console\/(library|sites|tasks|downloads|organize|engine)\/([^/?#]+)/);
|
||||
if (!m) return null;
|
||||
const [, kind, id] = m as unknown as [string, string, string];
|
||||
|
||||
@@ -30,16 +30,16 @@ export function useActiveAgentRuns() {
|
||||
|
||||
/** 全局快捷键:Ctrl/⌘+J 切换抽屉(Esc 关闭由抽屉自身处理) */
|
||||
function useAgentHotkey() {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname, search } = useLocation();
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.repeat || !isAgentShortcut(e)) return;
|
||||
e.preventDefault();
|
||||
toggleAgentDrawer(pathname);
|
||||
toggleAgentDrawer(pathname, search);
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [pathname]);
|
||||
}, [pathname, search]);
|
||||
}
|
||||
|
||||
/** 全局快捷键 Ctrl/⌘+J(没有界面,挂在 console layout 里一次) */
|
||||
@@ -74,12 +74,12 @@ function CountBadge({ count, className }: { count: number; className?: string })
|
||||
|
||||
/** 侧栏最上方的 Agent 主入口:强调样式(棱镜环),显示进行中会话数 */
|
||||
export function AgentNavEntry({ collapsed = false, onNavigate }: { collapsed?: boolean; onNavigate?: () => void }) {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname, search } = useLocation();
|
||||
const { count, summary } = useActiveAgentRuns();
|
||||
const shortcut = agentShortcutLabel();
|
||||
const activate = () => {
|
||||
onNavigate?.();
|
||||
toggleAgentDrawer(pathname);
|
||||
toggleAgentDrawer(pathname, search);
|
||||
};
|
||||
const title = `智能体(${shortcut})${summary ? `\n${summary}` : ""}`;
|
||||
|
||||
|
||||
@@ -77,6 +77,11 @@ function RootSheet({
|
||||
<SheetContent
|
||||
side="right"
|
||||
className={cn("w-full gap-0 p-0", WIDTH[width], className)}
|
||||
// 打开时焦点落在抽屉本身而不是第一个按钮 / 标签,避免一打开就亮焦点框;Tab 仍从抽屉内开始
|
||||
onOpenAutoFocus={(e) => {
|
||||
e.preventDefault();
|
||||
(e.currentTarget as HTMLElement | null)?.focus({ preventScroll: true });
|
||||
}}
|
||||
onEscapeKeyDown={(e) => {
|
||||
// 有子视图时 Esc 先回到上一层
|
||||
const top = stack.current.at(-1);
|
||||
|
||||
@@ -207,10 +207,12 @@ const fmtMs = (ms: number) =>
|
||||
ms >= 1000 ? `${(ms / 1000).toFixed(1)} 秒` : `${ms < 10 ? ms.toFixed(2) : Math.round(ms)} 毫秒`;
|
||||
|
||||
function StatementTable({ rows }: { rows: StatementRow[] }) {
|
||||
// 规范化后的语句文本可能重复(不同角色 / 库各一行),用序号做 key
|
||||
const keyed = rows.map((r, i) => ({ ...r, rowIndex: i }));
|
||||
return (
|
||||
<DataTable<StatementRow>
|
||||
rows={rows}
|
||||
rowKey={(r) => r.query}
|
||||
<DataTable<StatementRow & { rowIndex: number }>
|
||||
rows={keyed}
|
||||
rowKey={(r) => String(r.rowIndex)}
|
||||
empty={{ title: "还没有统计" }}
|
||||
columns={[
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@umt/web",
|
||||
"version": "0.3.40",
|
||||
"version": "0.3.41",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Reference in new issue
Block a user