feat: add operational timeline workspace

This commit is contained in:
2026-08-19 16:42:41 +08:00
parent d2f2895c36
commit 45f1ebee7e
19 changed files with 1127 additions and 105 deletions
@@ -1,8 +1,6 @@
import { AgentCollapsedRail, AgentCommandPanel, AgentPersona } from "@/features/agent";
import { cn } from "@/shared/ui/cn";
import { useEffect, type ComponentProps } from "react";
import { getAgentPanelDefaultWidth, getAgentPanelMaxWidth } from "../layout/workbench-layout";
import { useAgentPanelResize } from "../hooks/use-agent-panel-resize";
import { getWorkbenchViewportLayout } from "../layout/workbench-layout";
type AgentPanelProps = Omit<ComponentProps<typeof AgentCommandPanel>, "collapsing" | "onCollapse">;
@@ -10,8 +8,6 @@ type WorkbenchAgentPanelsProps = {
panelProps: AgentPanelProps;
panelOpen: boolean;
panelCollapsing: boolean;
artifactActive: boolean;
conditionExpanded: boolean;
personaState: ComponentProps<typeof AgentPersona>["state"];
statusLabel: string;
viewportWidth: number;
@@ -24,8 +20,6 @@ export function WorkbenchAgentPanels({
panelProps,
panelOpen,
panelCollapsing,
artifactActive,
conditionExpanded,
personaState,
statusLabel,
viewportWidth,
@@ -33,9 +27,7 @@ export function WorkbenchAgentPanels({
onExpandPanel,
onWidthCommit
}: WorkbenchAgentPanelsProps) {
const resize = useAgentPanelResize(viewportWidth, conditionExpanded);
const defaultWidth = getAgentPanelDefaultWidth(viewportWidth, conditionExpanded);
const committedWidth = resize.committedWidth ?? defaultWidth;
const committedWidth = getWorkbenchViewportLayout(viewportWidth).agentWidth;
useEffect(() => {
onWidthCommit?.(committedWidth);
@@ -43,43 +35,16 @@ export function WorkbenchAgentPanels({
return (
<div
ref={resize.panelRef}
className={cn(
"pointer-events-none absolute z-50 hidden lg:block",
!artifactActive && panelOpen && "workbench-agent-dock",
artifactActive && panelOpen
? "bottom-4 left-3 top-20 max-w-[720px]"
: "bottom-0 left-0 top-14"
)}
style={{ width: panelOpen ? resize.width ?? defaultWidth : 72 }}
className="pointer-events-none absolute bottom-0 left-0 top-14 z-50 hidden lg:block"
style={{ width: panelOpen ? committedWidth : 72 }}
>
{panelOpen ? (
<>
<AgentCommandPanel
{...panelProps}
presentation={artifactActive ? "desktop-floating" : "desktop-dock"}
collapsing={panelCollapsing}
onCollapse={onCollapsePanel}
/>
<div
aria-label="调整 Agent 面板宽度"
aria-orientation="vertical"
aria-valuemax={getAgentPanelMaxWidth(viewportWidth, conditionExpanded)}
aria-valuemin={defaultWidth}
aria-valuenow={Math.round(resize.width ?? defaultWidth)}
className={cn(
"group pointer-events-auto absolute -right-3 top-4 bottom-4 z-10 flex w-6 cursor-ew-resize touch-none items-center justify-center outline-hidden",
resize.resizing && "[&>span]:bg-blue-500 [&>span]:opacity-100"
)}
role="separator"
tabIndex={0}
title="拖拽调整 Agent 面板宽度"
onKeyDown={resize.handleKeyDown}
onPointerDown={resize.handlePointerDown}
>
<span className="h-14 w-1 rounded-full bg-slate-500/60 opacity-50 shadow-xs transition-[height,background-color,opacity] group-hover:h-20 group-hover:bg-blue-500 group-hover:opacity-100 group-focus-visible:h-20 group-focus-visible:bg-blue-500 group-focus-visible:opacity-100" />
</div>
</>
<AgentCommandPanel
{...panelProps}
presentation="desktop-dock"
collapsing={panelCollapsing}
onCollapse={onCollapsePanel}
/>
) : (
<div className="h-full bg-[#e8eef4] px-1.5 pt-5 shadow-[1px_0_0_rgba(15,23,42,0.08)]">
<AgentCollapsedRail
@@ -28,6 +28,7 @@ export type WorkbenchTopBarProps = {
alerts: WorkbenchAlert[];
user: WorkbenchUser;
conditionFeedVisible: boolean;
timelineVisible: boolean;
taskTickerAvailable: boolean;
taskTickerVisible: boolean;
devPanelEnabled: boolean;
@@ -35,6 +36,7 @@ export type WorkbenchTopBarProps = {
onSelectScenario: (scenarioId: string) => void;
onSelectAlert: (alert: WorkbenchAlert) => void;
onToggleConditionFeed: () => void;
onToggleTimeline: () => void;
onToggleTaskTicker: () => void;
onToggleDevPanel: () => void;
onPreviewScenario: () => void;
@@ -59,6 +61,7 @@ export function WorkbenchTopBar({
alerts,
user,
conditionFeedVisible,
timelineVisible,
taskTickerAvailable,
taskTickerVisible,
devPanelEnabled,
@@ -66,6 +69,7 @@ export function WorkbenchTopBar({
onSelectScenario,
onSelectAlert,
onToggleConditionFeed,
onToggleTimeline,
onToggleTaskTicker,
onToggleDevPanel,
onPreviewScenario,
@@ -144,6 +148,12 @@ export function WorkbenchTopBar({
</div>
<div className="flex shrink-0 items-center gap-2">
<div className="lg:hidden">
<TimelineToggle compact visible={timelineVisible} onToggle={onToggleTimeline} />
</div>
<div className="hidden lg:block">
<TimelineToggle visible={timelineVisible} onToggle={onToggleTimeline} />
</div>
{onCreateTestAnalysis ? (
<div className="hidden lg:block">
<button
@@ -257,6 +267,33 @@ function ConditionFeedToggle({ visible, onToggle }: { visible: boolean; onToggle
);
}
function TimelineToggle({
visible,
compact = false,
onToggle
}: {
visible: boolean;
compact?: boolean;
onToggle: () => void;
}) {
return (
<button
type="button"
aria-label={visible ? "隐藏运行时间轴" : "显示运行时间轴"}
aria-pressed={visible}
data-selection-tone="soft"
title={visible ? "隐藏运行时间轴" : "显示运行时间轴"}
onClick={onToggle}
className={cn("group", compact ? "px-1.5" : "px-2", headerControlButtonClassName)}
>
<span className={headerControlIconClassName}>
<Clock3 size={14} aria-hidden="true" />
</span>
{!compact ? <span className={compactHeaderTextClassName}>时间轴</span> : null}
</button>
);
}
function HeaderReadout({
icon: Icon,
label,