fix(agent): 抽屉与整页之间的切换
- 在智能体页面按 Ctrl+J / 点智能体入口聚焦页面输入框,不再在抽屉里叠一份同一会话 - 会话页统计行右侧加「在侧边打开」:回到进智能体页面之前的页面,会话在抽屉里接着看 - 窄屏顶栏按钮与快捷键、悬浮按钮共用同一套切换 - 抽屉关闭钮放进标题栏按钮行(原来绝对定位,比其它按钮低 5px) - 关闭抽屉后焦点回到打开前的元素(快捷键打开没有触发元素,原来落到 body) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
93a5341688
commit
a87c2c19c4
6 files changed
+150
-25
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
import { Bot, ExternalLink, History, Maximize2, Minus, SquarePen, X } from "lucide-react";
|
import { Bot, ExternalLink, History, Maximize2, Minus, SquarePen, X } from "lucide-react";
|
||||||
import { useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Link, useLocation } from "react-router";
|
import { Link, useLocation } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { agentMockEnabled, isRunActive, useAgentRun, useCreateAgentRun } from "~/api/agent.ts";
|
import { agentMockEnabled, isRunActive, useAgentRun, useCreateAgentRun } from "~/api/agent.ts";
|
||||||
@@ -10,7 +10,7 @@ import { useSite } from "~/api/sites.ts";
|
|||||||
import { StreamingCursor } from "~/components/agentui/streaming-response.tsx";
|
import { StreamingCursor } from "~/components/agentui/streaming-response.tsx";
|
||||||
import { Button } from "~/components/ui/button.tsx";
|
import { Button } from "~/components/ui/button.tsx";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover.tsx";
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover.tsx";
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx";
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx";
|
||||||
import { errorMessage } from "~/lib/api.ts";
|
import { errorMessage } from "~/lib/api.ts";
|
||||||
import { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.tsx";
|
import { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.tsx";
|
||||||
import { DRAFT_DRAWER_GOAL, writeDraft } from "./agent-nav.ts";
|
import { DRAFT_DRAWER_GOAL, writeDraft } from "./agent-nav.ts";
|
||||||
@@ -20,15 +20,16 @@ import {
|
|||||||
type AgentContext,
|
type AgentContext,
|
||||||
agentShortcutLabel,
|
agentShortcutLabel,
|
||||||
clearAgentContext,
|
clearAgentContext,
|
||||||
contextFromPath,
|
|
||||||
contextPrefix,
|
contextPrefix,
|
||||||
minimizeAgentDrawer,
|
minimizeAgentDrawer,
|
||||||
newAgentConversation,
|
newAgentConversation,
|
||||||
openAgentDrawer,
|
noteAgentReturnPath,
|
||||||
restoreAgentDrawer,
|
restoreAgentDrawer,
|
||||||
setAgentDrawerOpen,
|
setAgentDrawerOpen,
|
||||||
shortGoalTitle,
|
shortGoalTitle,
|
||||||
showAgentRunInDrawer,
|
showAgentRunInDrawer,
|
||||||
|
takeReturnFocus,
|
||||||
|
toggleAgentDrawer,
|
||||||
useAgentDrawer,
|
useAgentDrawer,
|
||||||
} from "./agent-store.ts";
|
} from "./agent-store.ts";
|
||||||
import { AgentRunOpenContext } from "./agent-subagent-tree.tsx";
|
import { AgentRunOpenContext } from "./agent-subagent-tree.tsx";
|
||||||
@@ -99,8 +100,10 @@ function HistoryPopover({ activeId }: { activeId: string | null }) {
|
|||||||
export function AgentDrawer() {
|
export function AgentDrawer() {
|
||||||
const { open, minimized, context, runId } = useAgentDrawer();
|
const { open, minimized, context, runId } = useAgentDrawer();
|
||||||
const create = useCreateAgentRun();
|
const create = useCreateAgentRun();
|
||||||
const { pathname } = useLocation();
|
const { pathname, search } = useLocation();
|
||||||
const content = useRef<HTMLDivElement>(null);
|
const content = useRef<HTMLDivElement>(null);
|
||||||
|
// 记下最近的非智能体页面:会话页「在侧边打开」回到这里
|
||||||
|
useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]);
|
||||||
const name = useContextName(context);
|
const name = useContextName(context);
|
||||||
const mock = agentMockEnabled();
|
const mock = agentMockEnabled();
|
||||||
// 目标前缀带名称和 ID:名称给人看,ID 给模型用
|
// 目标前缀带名称和 ID:名称给人看,ID 给模型用
|
||||||
@@ -112,6 +115,7 @@ export function AgentDrawer() {
|
|||||||
<SheetContent
|
<SheetContent
|
||||||
ref={content}
|
ref={content}
|
||||||
side="right"
|
side="right"
|
||||||
|
showCloseButton={false}
|
||||||
className="w-full gap-0 p-0 sm:max-w-xl"
|
className="w-full gap-0 p-0 sm:max-w-xl"
|
||||||
// 打开即可输入:焦点直接给输入框,没有输入框时退回抽屉本身
|
// 打开即可输入:焦点直接给输入框,没有输入框时退回抽屉本身
|
||||||
onOpenAutoFocus={(e) => {
|
onOpenAutoFocus={(e) => {
|
||||||
@@ -121,11 +125,18 @@ export function AgentDrawer() {
|
|||||||
ta.focus();
|
ta.focus();
|
||||||
ta.setSelectionRange(ta.value.length, ta.value.length);
|
ta.setSelectionRange(ta.value.length, ta.value.length);
|
||||||
}}
|
}}
|
||||||
|
// 关闭后焦点回到打开前的位置(快捷键打开没有触发元素,缺省会落到 body)
|
||||||
|
onCloseAutoFocus={(e) => {
|
||||||
|
const el = takeReturnFocus();
|
||||||
|
if (!el) return;
|
||||||
|
e.preventDefault();
|
||||||
|
el.focus();
|
||||||
|
}}
|
||||||
onInteractOutside={(e) => {
|
onInteractOutside={(e) => {
|
||||||
if ((e.target as Element | null)?.closest?.(`[${AGENT_LAUNCHER_ATTR}]`)) e.preventDefault();
|
if ((e.target as Element | null)?.closest?.(`[${AGENT_LAUNCHER_ATTR}]`)) e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SheetHeader className="flex-row items-center gap-1 border-b border-[var(--shell-border)] py-2.5 pr-12 pl-5">
|
<SheetHeader className="flex-row items-center gap-1 border-b border-[var(--shell-border)] py-2.5 pr-3 pl-5">
|
||||||
<SheetTitle className="mr-auto flex items-center gap-2 text-lead">
|
<SheetTitle className="mr-auto flex items-center gap-2 text-lead">
|
||||||
<Bot className="h-4 w-4" />
|
<Bot className="h-4 w-4" />
|
||||||
智能体
|
智能体
|
||||||
@@ -157,6 +168,12 @@ export function AgentDrawer() {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
{/* 关闭钮放进标题栏这一行,与其它按钮对齐(Sheet 自带的是绝对定位,偏下 5px) */}
|
||||||
|
<SheetClose asChild>
|
||||||
|
<Button variant="ghost" size="icon-sm" aria-label="关闭" title="关闭(Esc)">
|
||||||
|
<X />
|
||||||
|
</Button>
|
||||||
|
</SheetClose>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<div className="flex min-h-0 flex-1 flex-col px-4 py-3">
|
<div className="flex min-h-0 flex-1 flex-col px-4 py-3">
|
||||||
{runId ? (
|
{runId ? (
|
||||||
@@ -258,9 +275,8 @@ function AgentMiniDock({ runId }: { runId: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 顶栏按钮:有最小化的运行时恢复它;否则按当前路由带上下文打开(页面内按钮用 openAgentDrawer 传更完整的上下文) */
|
/** 顶栏按钮(窄屏):与快捷键、悬浮按钮同一套切换(页面内按钮用 openAgentDrawer 传更完整的上下文) */
|
||||||
export function AgentDrawerTrigger({ className }: { className?: string }) {
|
export function AgentDrawerTrigger({ className }: { className?: string }) {
|
||||||
const { minimized, runId } = useAgentDrawer();
|
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -269,7 +285,8 @@ export function AgentDrawerTrigger({ className }: { className?: string }) {
|
|||||||
aria-label="打开智能体"
|
aria-label="打开智能体"
|
||||||
title={`智能体(${agentShortcutLabel()})`}
|
title={`智能体(${agentShortcutLabel()})`}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={() => (minimized && runId ? restoreAgentDrawer() : openAgentDrawer(contextFromPath(pathname)))}
|
{...{ [AGENT_LAUNCHER_ATTR]: "" }}
|
||||||
|
onClick={() => toggleAgentDrawer(pathname)}
|
||||||
>
|
>
|
||||||
<Bot />
|
<Bot />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
writeDraft,
|
writeDraft,
|
||||||
} from "./agent-nav.ts";
|
} from "./agent-nav.ts";
|
||||||
import { CompactPager } from "./agent-session-list.tsx";
|
import { CompactPager } from "./agent-session-list.tsx";
|
||||||
import { isAgentPage, outletKey } from "./agent-store.ts";
|
import { agentReturnPath, isAgentPage, noteAgentReturnPath, outletKey, toggleAction } from "./agent-store.ts";
|
||||||
import { cachedRunEvents, cacheRunEvents } from "./use-agent-stream.ts";
|
import { cachedRunEvents, cacheRunEvents } from "./use-agent-stream.ts";
|
||||||
|
|
||||||
// 智能体页面切换的状态逻辑:整页重挂载 key、分栏与 URL、草稿、滚动策略、快捷键在智能体页面的行为。
|
// 智能体页面切换的状态逻辑:整页重挂载 key、分栏与 URL、草稿、滚动策略、快捷键在智能体页面的行为。
|
||||||
@@ -150,3 +150,29 @@ describe("切回看过的会话:事件缓存", () => {
|
|||||||
expect(cachedRunEvents("live:n29")).toHaveLength(1);
|
expect(cachedRunEvents("live:n29")).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Ctrl+J / 智能体入口在不同页面", () => {
|
||||||
|
it("抽屉开着:收起", () => {
|
||||||
|
expect(toggleAction({ open: true, runId: "r" }, "/console/library")).toBe("close");
|
||||||
|
expect(toggleAction({ open: true, runId: null }, "/console/agent/r")).toBe("close");
|
||||||
|
});
|
||||||
|
it("已在智能体页面:聚焦页面输入框,不在抽屉里再开一份同一会话", () => {
|
||||||
|
expect(toggleAction({ open: false, runId: "r" }, "/console/agent/r")).toBe("focus-page");
|
||||||
|
expect(toggleAction({ open: false, runId: null }, "/console/agent")).toBe("focus-page");
|
||||||
|
});
|
||||||
|
it("其它页面:上次的会话还在就接着看,否则开新对话", () => {
|
||||||
|
expect(toggleAction({ open: false, runId: "r" }, "/console/library")).toBe("restore");
|
||||||
|
expect(toggleAction({ open: false, runId: null }, "/console/library")).toBe("open");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("会话页「在侧边打开」回到之前的页面", () => {
|
||||||
|
it("只记非智能体页面,带查询串", () => {
|
||||||
|
noteAgentReturnPath("/console/library", "?filter=unidentified");
|
||||||
|
noteAgentReturnPath("/console/agent");
|
||||||
|
noteAgentReturnPath("/console/agent/r1", "?tab=auto");
|
||||||
|
expect(agentReturnPath()).toBe("/console/library?filter=unidentified");
|
||||||
|
noteAgentReturnPath("/console/sites");
|
||||||
|
expect(agentReturnPath()).toBe("/console/sites");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { type AgentRunStatus, EventType } from "@umt/contracts";
|
import { type AgentRunStatus, EventType } from "@umt/contracts";
|
||||||
import { ChevronDown, FlaskConical, ListChecks, RefreshCw, WifiOff } from "lucide-react";
|
import { ChevronDown, FlaskConical, ListChecks, RefreshCw, WifiOff } from "lucide-react";
|
||||||
import { useId, useRef, useState } from "react";
|
import { type ReactNode, useId, useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
@@ -289,10 +289,13 @@ export function AgentRunPanel({
|
|||||||
compact = false,
|
compact = false,
|
||||||
className,
|
className,
|
||||||
onCreated,
|
onCreated,
|
||||||
|
actions,
|
||||||
}: {
|
}: {
|
||||||
runId: string;
|
runId: string;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** 统计行右侧的操作(会话页的「在侧边打开」) */
|
||||||
|
actions?: ReactNode;
|
||||||
/** /重跑 之后:缺省跳到新会话页;抽屉里改成在抽屉内打开 */
|
/** /重跑 之后:缺省跳到新会话页;抽屉里改成在抽屉内打开 */
|
||||||
onCreated?: (runId: string) => void;
|
onCreated?: (runId: string) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -392,7 +395,14 @@ export function AgentRunPanel({
|
|||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<RunStatsLine snapshot={run.data} live={live} className={column} />
|
{actions ? (
|
||||||
|
<div className={cn(column, "-my-1 flex min-w-0 items-center gap-2")}>
|
||||||
|
<RunStatsLine snapshot={run.data} live={live} className="min-w-0 flex-1" />
|
||||||
|
<div className="flex shrink-0 items-center gap-1">{actions}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<RunStatsLine snapshot={run.data} live={live} className={column} />
|
||||||
|
)}
|
||||||
<RunOverviewBar
|
<RunOverviewBar
|
||||||
snapshot={run.data}
|
snapshot={run.data}
|
||||||
status={shownStatus}
|
status={shownStatus}
|
||||||
|
|||||||
@@ -30,20 +30,50 @@ type State = {
|
|||||||
// AGUI-3:抽屉去掉「当前 / 历史」Tab。头部「历史」弹层点一条就在抽屉里打开,「新对话」回到输入框。
|
// AGUI-3:抽屉去掉「当前 / 历史」Tab。头部「历史」弹层点一条就在抽屉里打开,「新对话」回到输入框。
|
||||||
// 旧版本记在 localStorage 的 Tab(umt-agent-drawer-tab)不再读取。
|
// 旧版本记在 localStorage 的 Tab(umt-agent-drawer-tab)不再读取。
|
||||||
|
|
||||||
|
/** 当前在不在智能体页面(列表页或会话页) */
|
||||||
|
export const isAgentPage = (pathname: string) => /^\/console\/agent(\/|$)/.test(pathname);
|
||||||
|
|
||||||
let state: State = { open: false, minimized: false, context: null, runId: null };
|
let state: State = { open: false, minimized: false, context: null, runId: null };
|
||||||
const listeners = new Set<() => void>();
|
const listeners = new Set<() => void>();
|
||||||
const emit = () => {
|
const emit = () => {
|
||||||
for (const l of listeners) l();
|
for (const l of listeners) l();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开前的焦点:抽屉多从快捷键打开,没有 Radix 触发元素,关闭时焦点会落到 body。
|
||||||
|
* 打开时记下,关闭时还回去(元素还在页面上才还)。
|
||||||
|
*/
|
||||||
|
let returnFocus: HTMLElement | null = null;
|
||||||
|
function rememberFocus() {
|
||||||
|
if (state.open || typeof document === "undefined") return;
|
||||||
|
const el = document.activeElement;
|
||||||
|
returnFocus = el instanceof HTMLElement && el !== document.body ? el : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取出并清掉打开抽屉前的焦点元素(抽屉 onCloseAutoFocus 用) */
|
||||||
|
export function takeReturnFocus(): HTMLElement | null {
|
||||||
|
const el = returnFocus;
|
||||||
|
returnFocus = null;
|
||||||
|
return el?.isConnected ? el : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 最近一个不是智能体页面的地址:会话页「在侧边打开」回到这里 */
|
||||||
|
let returnPath: string | null = null;
|
||||||
|
export function noteAgentReturnPath(pathname: string, search = "") {
|
||||||
|
if (!isAgentPage(pathname)) returnPath = `${pathname}${search}`;
|
||||||
|
}
|
||||||
|
export const agentReturnPath = () => returnPath;
|
||||||
|
|
||||||
/** 打开抽屉开始新对话(页面内按钮带着对象打开) */
|
/** 打开抽屉开始新对话(页面内按钮带着对象打开) */
|
||||||
export function openAgentDrawer(context: AgentContext | null = null) {
|
export function openAgentDrawer(context: AgentContext | null = null) {
|
||||||
|
rememberFocus();
|
||||||
state = { ...state, open: true, minimized: false, context, runId: null };
|
state = { ...state, open: true, minimized: false, context, runId: null };
|
||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在抽屉里打开一条会话(历史弹层、/重跑、子会话链接) */
|
/** 在抽屉里打开一条会话(历史弹层、/重跑、子会话链接、会话页「在侧边打开」) */
|
||||||
export function showAgentRunInDrawer(runId: string) {
|
export function showAgentRunInDrawer(runId: string) {
|
||||||
|
rememberFocus();
|
||||||
state = { ...state, open: true, minimized: false, runId };
|
state = { ...state, open: true, minimized: false, runId };
|
||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
@@ -73,6 +103,7 @@ export function minimizeAgentDrawer() {
|
|||||||
|
|
||||||
/** 从小窗恢复抽屉 */
|
/** 从小窗恢复抽屉 */
|
||||||
export function restoreAgentDrawer() {
|
export function restoreAgentDrawer() {
|
||||||
|
rememberFocus();
|
||||||
state = { ...state, open: true, minimized: false };
|
state = { ...state, open: true, minimized: false };
|
||||||
emit();
|
emit();
|
||||||
}
|
}
|
||||||
@@ -90,13 +121,33 @@ export function isAgentShortcut(
|
|||||||
return (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "j";
|
return (e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === "j";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ToggleAction = "close" | "focus-page" | "restore" | "open";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 快捷键 / 悬浮按钮 / 侧栏主入口共用的切换:
|
* 切换要做什么(纯函数,单测覆盖):打开中 → 收起;已在智能体页面 → 聚焦页面的输入框(页面就是智能体,
|
||||||
* 打开中 → 收起;有最小化的运行 → 恢复;否则按当前路由带上下文打开(上次看的会话还在就接着看)。
|
* 不再在抽屉里叠一份同一会话);上次看的会话(含最小化的)还在 → 接着看;否则按当前路由带上下文开新对话。
|
||||||
*/
|
*/
|
||||||
|
export function toggleAction(s: Pick<State, "open" | "runId">, pathname: string): ToggleAction {
|
||||||
|
if (s.open) return "close";
|
||||||
|
if (isAgentPage(pathname)) return "focus-page";
|
||||||
|
return s.runId ? "restore" : "open";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 智能体页面的输入框(会话页「发消息」、列表页「目标」) */
|
||||||
|
function focusAgentPageInput(): boolean {
|
||||||
|
if (typeof document === "undefined") return false;
|
||||||
|
const ta = document.querySelector<HTMLTextAreaElement>("[data-agent-page] textarea:not([disabled])");
|
||||||
|
if (!ta) return false;
|
||||||
|
ta.focus();
|
||||||
|
ta.setSelectionRange(ta.value.length, ta.value.length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 快捷键 / 悬浮按钮 / 侧栏主入口 / 顶栏按钮共用的切换 */
|
||||||
export function toggleAgentDrawer(pathname: string) {
|
export function toggleAgentDrawer(pathname: string) {
|
||||||
if (state.open) setAgentDrawerOpen(false);
|
const action = toggleAction(state, pathname);
|
||||||
else if (state.minimized && state.runId) restoreAgentDrawer();
|
if (action === "close") setAgentDrawerOpen(false);
|
||||||
|
else if (action === "focus-page" && focusAgentPageInput()) return;
|
||||||
else if (state.runId) restoreAgentDrawer();
|
else if (state.runId) restoreAgentDrawer();
|
||||||
else openAgentDrawer(contextFromPath(pathname));
|
else openAgentDrawer(contextFromPath(pathname));
|
||||||
}
|
}
|
||||||
@@ -123,9 +174,6 @@ export function contextFromPath(pathname: string): AgentContext | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当前在不在智能体页面(列表页或会话页) */
|
|
||||||
export const isAgentPage = (pathname: string) => /^\/console\/agent(\/|$)/.test(pathname);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制台主内容区的重挂载 key:智能体列表页与各会话页共用一个 key,
|
* 控制台主内容区的重挂载 key:智能体列表页与各会话页共用一个 key,
|
||||||
* 切换会话不重挂载整页、不重播入场动画,左栏的分栏 / 滚动 / 展开状态保留。
|
* 切换会话不重挂载整页、不重播入场动画,左栏的分栏 / 滚动 / 展开状态保留。
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft, PanelRightOpen } from "lucide-react";
|
||||||
import { Link, useParams } from "react-router";
|
import { Link, useNavigate, useParams } from "react-router";
|
||||||
import { agentMockEnabled } from "~/api/agent.ts";
|
import { agentMockEnabled } from "~/api/agent.ts";
|
||||||
import {
|
import {
|
||||||
agentListHref,
|
agentListHref,
|
||||||
@@ -10,19 +10,21 @@ import {
|
|||||||
} from "~/components/agent/agent-nav.ts";
|
} from "~/components/agent/agent-nav.ts";
|
||||||
import { AgentRunPanel } from "~/components/agent/agent-run-panel.tsx";
|
import { AgentRunPanel } from "~/components/agent/agent-run-panel.tsx";
|
||||||
import { AgentSessionList } from "~/components/agent/agent-session-list.tsx";
|
import { AgentSessionList } from "~/components/agent/agent-session-list.tsx";
|
||||||
|
import { agentReturnPath, showAgentRunInDrawer } from "~/components/agent/agent-store.ts";
|
||||||
import { Button } from "~/components/ui/button.tsx";
|
import { Button } from "~/components/ui/button.tsx";
|
||||||
|
|
||||||
// 会话页(AGUI-3):左栏会话列表(xl 以上)· 会话列占满其余宽度(顶部概况条、底部输入区)。
|
// 会话页(AGUI-3):左栏会话列表(xl 以上)· 会话列占满其余宽度(顶部概况条、底部输入区)。
|
||||||
// 不再有页头目标描述和右侧栏;步骤、子会话、预算与模型在概况条里。
|
// 不再有页头目标描述和右侧栏;步骤、子会话、预算与模型在概况条里。
|
||||||
export default function AgentRunPage() {
|
export default function AgentRunPage() {
|
||||||
const { runId = "" } = useParams();
|
const { runId = "" } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const mock = agentMockEnabled();
|
const mock = agentMockEnabled();
|
||||||
// 左栏分栏跟随当前会话(巡检会话及其子会话在「自动巡检」),也可以手动切换(记在 URL)
|
// 左栏分栏跟随当前会话(巡检会话及其子会话在「自动巡检」),也可以手动切换(记在 URL)
|
||||||
const [group, setGroup] = usePageListGroup(useRunGroup(runId));
|
const [group, setGroup] = usePageListGroup(useRunGroup(runId));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="sr-only">智能体会话</h1>
|
<h1 className="sr-only">智能体会话</h1>
|
||||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,17rem)_minmax(0,1fr)]">
|
<div data-agent-page className="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,17rem)_minmax(0,1fr)]">
|
||||||
<aside className="pg-slab hidden rounded-xl p-3 xl:block xl:h-[calc(100dvh-7rem)] xl:overflow-hidden">
|
<aside className="pg-slab hidden rounded-xl p-3 xl:block xl:h-[calc(100dvh-7rem)] xl:overflow-hidden">
|
||||||
<AgentSessionList
|
<AgentSessionList
|
||||||
activeId={runId}
|
activeId={runId}
|
||||||
@@ -40,7 +42,26 @@ export default function AgentRunPage() {
|
|||||||
全部会话
|
全部会话
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<AgentRunPanel key={runId} runId={runId} className="min-h-0 flex-1" />
|
<AgentRunPanel
|
||||||
|
key={runId}
|
||||||
|
runId={runId}
|
||||||
|
className="min-h-0 flex-1"
|
||||||
|
actions={
|
||||||
|
// 整页 → 抽屉:回到进智能体页面之前的页面,会话在抽屉里接着看
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
aria-label="在侧边打开"
|
||||||
|
title="在侧边打开(回到之前的页面)"
|
||||||
|
onClick={() => {
|
||||||
|
showAgentRunInDrawer(runId);
|
||||||
|
navigate(agentReturnPath() ?? "/console");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PanelRightOpen />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ export default function AgentPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)] lg:items-start">
|
<div
|
||||||
|
data-agent-page
|
||||||
|
className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)] lg:items-start"
|
||||||
|
>
|
||||||
<div className="pg-slab order-2 rounded-xl p-3 lg:order-1 lg:h-[calc(100dvh-12rem)] lg:overflow-hidden">
|
<div className="pg-slab order-2 rounded-xl p-3 lg:order-1 lg:h-[calc(100dvh-12rem)] lg:overflow-hidden">
|
||||||
<AgentSessionList
|
<AgentSessionList
|
||||||
hrefFor={(id) => agentRunHref(id, mock)}
|
hrefFor={(id) => agentRunHref(id, mock)}
|
||||||
|
|||||||
Reference in new issue
Block a user