feat(canvas): add send to Agent shortcuts

This commit is contained in:
yepwait
2026-09-17 08:35:43 +08:00
parent 9868f5e8b8
commit 91c77b8222
9 changed files with 47 additions and 7 deletions

View File

@@ -27,11 +27,11 @@ import { CanvasCloudAgentSettings, agentPermissionLabel, agentPermissionMenuItem
import { useAgentPanelLayout } from "./use-agent-panel-layout";
import "./canvas-cloud-agent.css";
type CloudAgentPanelProps = { canvasId: string; domainProjectId?: string; nodeCount: number; references: CanvasResourceReference[]; open: boolean; onOpen: () => void; onCollapse: () => void; onFocusNode?: (nodeId: string) => void };
type CloudAgentPanelProps = { canvasId: string; domainProjectId?: string; nodeCount: number; references: CanvasResourceReference[]; open: boolean; prefillPrompt?: string; onOpen: () => void; onCollapse: () => void; onFocusNode?: (nodeId: string) => void };
type ApprovalState = { approvalId: string; detail: Record<string, unknown>; reason: string };
type AgentPanelView = "chat" | "history" | "settings";
export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, references, open, onOpen, onCollapse, onFocusNode }: CloudAgentPanelProps) {
export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, references, open, prefillPrompt, onOpen, onCollapse, onFocusNode }: CloudAgentPanelProps) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
const config = useEffectiveConfig();
const updateConfig = useConfigStore((state) => state.updateConfig);
@@ -42,6 +42,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
const [connectionEpoch, setConnectionEpoch] = useState(0);
const [messages, setMessages] = useState<CloudAgentChatMessage[]>([]);
const [prompt, setPrompt] = useState("");
const lastPrefillPromptRef = useRef("");
const [reasoningMode, setReasoningMode] = useState<AgentReasoningMode>("off");
const [profileView, setProfileView] = useState<AgentProfileView | null>(null);
const [profileLoading, setProfileLoading] = useState(false);
@@ -103,6 +104,13 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
const statusLabel = status === "waiting_approval" ? "等待审批" : status === "running" || status === "queued" ? "运行中" : status === "completed" ? "已完成" : status === "failed" ? "异常" : status === "cancelled" ? "已停止" : status === "rejected" ? "已拒绝" : "待命";
const statusColor = status === "failed" ? "#e66b6b" : status === "rejected" || status === "cancelled" ? theme.node.muted : status === "waiting_approval" ? "#d6a24a" : status === "running" || status === "queued" ? "#69c29b" : theme.node.muted;
useEffect(() => {
const value = prefillPrompt?.trim();
if (!value || value === lastPrefillPromptRef.current) return;
lastPrefillPromptRef.current = value;
setPrompt(value);
}, [prefillPrompt]);
useEffect(() => {
if (!open || view !== "chat") setSkillsOpen(false);
}, [open, view]);

View File

@@ -1,6 +1,6 @@
import { AnimatePresence } from "motion/react";
import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
import { ArrowLeft, Check, ChevronRight, Clipboard, CloudUpload, Copy, FolderOpen, FolderPlus, Image as ImageIcon, Layers3, LayoutTemplate, Link2, Maximize2, PanelTop, Pencil, Plus, Redo2, Tags, Trash2, Undo2, Upload, UserRound } from "lucide-react";
import { ArrowLeft, AtSign, Check, ChevronRight, Clipboard, CloudUpload, Copy, FolderOpen, FolderPlus, Image as ImageIcon, Layers3, LayoutTemplate, Link2, Maximize2, PanelTop, Pencil, Plus, Redo2, Tags, Trash2, Undo2, Upload, UserRound } from "lucide-react";
import { CanvasCreateMenu, type CanvasCreateCommand } from "@/components/canvas/canvas-create-menu";
import { ASSET_CATEGORY_OPTIONS } from "@/lib/asset-category";
@@ -55,6 +55,7 @@ type CanvasNodeContextMenuProps = {
onSpreadSelection?: () => void;
onCopySelection?: () => void;
onDeleteSelection?: () => void;
onSendToAgent?: () => void;
};
export function CanvasNodeContextMenu({
@@ -94,6 +95,7 @@ export function CanvasNodeContextMenu({
onSpreadSelection,
onCopySelection,
onDeleteSelection,
onSendToAgent,
}: CanvasNodeContextMenuProps) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
const [addOpen, setAddOpen] = useState(false);
@@ -200,6 +202,7 @@ export function CanvasNodeContextMenu({
<MenuButton icon={<LayoutTemplate />} label="自适应整理画布" detail="保持相对布局并加大边距" disabled={!onSpreadSelection} onClick={() => onSpreadSelection && runAction(onSpreadSelection)} />
<MenuDivider />
<MenuButton icon={<Copy />} label={`复制 ${selectedCount} 个节点`} shortcut="⌘C" disabled={!onCopySelection} onClick={() => onCopySelection && runAction(onCopySelection)} />
<MenuButton icon={<AtSign />} label="发送到 Agent" disabled={!onSendToAgent} onClick={() => onSendToAgent && runAction(onSendToAgent)} />
<MenuButton icon={<Trash2 />} label={`删除 ${selectedCount} 个节点`} danger disabled={!onDeleteSelection} onClick={() => onDeleteSelection && runAction(onDeleteSelection)} />
</>
) : menu.type === "node" ? (
@@ -211,6 +214,7 @@ export function CanvasNodeContextMenu({
<MenuButton icon={<UserRound />} label="查看角色详情" onClick={() => runAction(onEditText)} />
<MenuDivider />
<MenuSection label="节点" />
<MenuButton icon={<AtSign />} label="发送到 Agent" disabled={!onSendToAgent} onClick={() => onSendToAgent && runAction(onSendToAgent)} />
<MenuButton icon={<Copy />} label="复制角色引用" shortcut="⌘C" onClick={() => runAction(onCopyNode)} />
<MenuButton icon={<Layers3 />} label="创建引用副本" shortcut="⌘D" onClick={() => runAction(onDuplicate)} />
<MenuButton icon={<Trash2 />} label="删除节点" danger onClick={() => runAction(onDelete)} />
@@ -224,6 +228,7 @@ export function CanvasNodeContextMenu({
{isImage ? <MenuButton icon={<CloudUpload />} label="上传到方舟素材库" onClick={() => runAction(onUploadToArkPrivateAsset)} /> : null}
<MenuDivider />
<MenuSection label="节点" />
<MenuButton icon={<AtSign />} label="发送到 Agent" disabled={!onSendToAgent} onClick={() => onSendToAgent && runAction(onSendToAgent)} />
<MenuButton icon={<Copy />} label="复制节点" shortcut="⌘C" onClick={() => runAction(onCopyNode)} />
{isImage ? <MenuButton icon={<Clipboard />} label="复制图片" disabled={!hasNodeContent} onClick={() => runAction(onCopyContent)} /> : null}
<MenuButton icon={<Link2 />} label={isImage ? "复制图片地址" : "复制视频地址"} disabled={!canCopyMediaUrl} onClick={() => runAction(onCopyMediaUrl)} />
@@ -241,6 +246,7 @@ export function CanvasNodeContextMenu({
{isText ? <MenuButton icon={<ImageIcon />} label="用文本生图" disabled={!canGenerateFromText} onClick={() => runAction(onGenerateImage)} /> : null}
<MenuDivider />
<MenuSection label="副本与内容" />
<MenuButton icon={<AtSign />} label="发送到 Agent" disabled={!onSendToAgent} onClick={() => onSendToAgent && runAction(onSendToAgent)} />
<MenuButton icon={<Copy />} label={isFrame ? `复制${isFolder ? "文件夹" : "背板"}及内容` : "复制节点"} shortcut="⌘C" onClick={() => runAction(onCopyNode)} />
{isText ? <MenuButton icon={<Clipboard />} label="复制文本" disabled={!hasNodeContent} onClick={() => runAction(onCopyContent)} /> : null}
<MenuButton icon={<Copy />} label={isFrame ? `创建${isFolder ? "文件夹" : "背板"}副本` : "创建参数变体"} shortcut="⌘D" onClick={() => runAction(onDuplicate)} />