feat(web): prominent agent launcher (FAB, Ctrl/Cmd+J, sidebar entry, quick goals)
- Bottom-right FAB (56px, prism ring) with active-run count, pulse and latest status summary; turns into "collapse" beside the open drawer; hidden on the Agent page and while the mini dock shows; lifts above bottom floating bars - Ctrl/Cmd+J toggles the drawer, Esc closes; input is focused on open - Emphasized Agent entry at the top of the sidebar (expanded and collapsed) with active-run count; header button kept only below xl - Drawer first screen offers 3 page-aware quick goals that fill the input - Respects reduced motion; AgentShortcutsHelp exported for the settings page Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
16ab2a6c84
commit
39a001c099
7 files changed
+433
-8
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { Bot, ExternalLink, Maximize2, Minus, Plus, X } from "lucide-react";
|
||||
import { useRef } from "react";
|
||||
import { Link, useLocation } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { isRunActive, useAgentRun, useCreateAgentRun } from "~/api/agent.ts";
|
||||
@@ -10,6 +11,7 @@ import { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.t
|
||||
import { AgentRunPanel, GoalComposer } from "./agent-run-panel.tsx";
|
||||
import {
|
||||
type AgentContext,
|
||||
agentShortcutLabel,
|
||||
contextFromPath,
|
||||
contextPrefix,
|
||||
minimizeAgentDrawer,
|
||||
@@ -20,8 +22,12 @@ import {
|
||||
splitContextGoal,
|
||||
useAgentDrawer,
|
||||
} from "./agent-store.ts";
|
||||
import { quickGoalsFor } from "./quick-goals.ts";
|
||||
import { useAgentStream } from "./use-agent-stream.ts";
|
||||
|
||||
/** 悬浮按钮等"抽屉外但属于抽屉"的控件打这个属性:点它不算点外面(否则先被关掉又被重新打开) */
|
||||
export const AGENT_LAUNCHER_ATTR = "data-agent-launcher";
|
||||
|
||||
const CONTEXT_KEYS: [keyof AgentContext, string][] = [
|
||||
["entityId", "作品"],
|
||||
["releaseId", "资源"],
|
||||
@@ -59,11 +65,28 @@ export function AgentDrawer() {
|
||||
const { open, minimized, context, runId } = useAgentDrawer();
|
||||
const create = useCreateAgentRun();
|
||||
const prefix = contextPrefix(context);
|
||||
const { pathname } = useLocation();
|
||||
const content = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sheet open={open} onOpenChange={setAgentDrawerOpen}>
|
||||
<SheetContent side="right" className="w-full gap-0 p-0 sm:max-w-xl">
|
||||
<SheetContent
|
||||
ref={content}
|
||||
side="right"
|
||||
className="w-full gap-0 p-0 sm:max-w-xl"
|
||||
// 打开即可输入:焦点直接给输入框(新目标或运行中的追加消息),没有输入框时退回抽屉本身
|
||||
onOpenAutoFocus={(e) => {
|
||||
const ta = content.current?.querySelector<HTMLTextAreaElement>("textarea:not([disabled])");
|
||||
if (!ta) return;
|
||||
e.preventDefault();
|
||||
ta.focus();
|
||||
ta.setSelectionRange(ta.value.length, ta.value.length);
|
||||
}}
|
||||
onInteractOutside={(e) => {
|
||||
if ((e.target as Element | null)?.closest?.(`[${AGENT_LAUNCHER_ATTR}]`)) e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<SheetHeader className="border-b border-[var(--shell-border)] px-5 py-4 pr-24">
|
||||
<SheetTitle className="flex items-center gap-2 text-lead">
|
||||
<Bot className="h-4 w-4" />
|
||||
@@ -71,6 +94,9 @@ export function AgentDrawer() {
|
||||
</SheetTitle>
|
||||
<SheetDescription className="break-words">
|
||||
{context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内边执行边汇报。"}
|
||||
<span className="ml-1 hidden text-muted-foreground/75 sm:inline">
|
||||
({agentShortcutLabel()} 开关,Esc 关闭)
|
||||
</span>
|
||||
</SheetDescription>
|
||||
{context ? <ContextChips context={context} /> : null}
|
||||
</SheetHeader>
|
||||
@@ -93,6 +119,7 @@ export function AgentDrawer() {
|
||||
<GoalComposer
|
||||
key={`${context?.entityId ?? ""}${context?.releaseId ?? ""}${context?.goal ?? ""}`}
|
||||
compact
|
||||
suggestions={context?.goal ? undefined : quickGoalsFor(pathname)}
|
||||
prefix={prefix || undefined}
|
||||
defaultGoal={context?.goal ?? ""}
|
||||
pending={create.isPending}
|
||||
@@ -200,7 +227,7 @@ function AgentMiniDock({ runId }: { runId: string }) {
|
||||
}
|
||||
|
||||
/** 顶栏按钮:有最小化的运行时恢复它;否则按当前路由带上下文打开(页面内按钮用 openAgentDrawer 传更完整的上下文) */
|
||||
export function AgentDrawerTrigger() {
|
||||
export function AgentDrawerTrigger({ className }: { className?: string }) {
|
||||
const { minimized, runId } = useAgentDrawer();
|
||||
const { pathname } = useLocation();
|
||||
return (
|
||||
@@ -208,7 +235,8 @@ export function AgentDrawerTrigger() {
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label="打开 Agent"
|
||||
title="Agent"
|
||||
title={`Agent(${agentShortcutLabel()})`}
|
||||
className={className}
|
||||
onClick={() => (minimized && runId ? restoreAgentDrawer() : openAgentDrawer(contextFromPath(pathname)))}
|
||||
>
|
||||
<Bot />
|
||||
|
||||
Reference in new issue
Block a user