diff --git a/web/src/components/canvas/canvas-agent-chat-ui.tsx b/web/src/components/canvas/canvas-agent-chat-ui.tsx index 9b6918e74..5ed0c77cf 100644 --- a/web/src/components/canvas/canvas-agent-chat-ui.tsx +++ b/web/src/components/canvas/canvas-agent-chat-ui.tsx @@ -3,7 +3,7 @@ import { Tooltip } from "@/components/ui/base/tooltip"; import { useEffect, useMemo, useRef, useState, type ClipboardEvent as ReactClipboardEvent, type KeyboardEvent as ReactKeyboardEvent, type ReactNode } from "react"; import { motion, useReducedMotion } from "motion/react"; -import { ArrowUp, AtSign, CheckCircle2, CircleAlert, ImagePlus, LoaderCircle, RotateCcw, Sparkles, UserRound, Wrench, X, XCircle } from "lucide-react"; +import { ArrowUp, AtSign, CheckCircle2, ChevronDown, CircleAlert, ImagePlus, LoaderCircle, RotateCcw, Sparkles, UserRound, Wrench, X, XCircle } from "lucide-react"; import { canvasThemes } from "@/lib/canvas-theme"; import type { CanvasAgentOperationImpact } from "@/lib/canvas/canvas-agent-ops"; @@ -100,10 +100,7 @@ export function AgentChatMessage({ if (item.role === "tool") { if (objectField(item.detail, "status") === "pending") return onRejectTool?.(item.id)} onApprove={() => onApproveTool?.(item.id)} />; return ( -
- - -
+ ); } return ( @@ -151,26 +148,26 @@ export function AgentPendingToolCard({ summary, detail, theme, onReject, onAppro return (
-
+
- +
-
+
确认工具调用 等待确认
-
+
{summary}
{impact?.operationCount ? (
-
+
0} theme={theme} /> @@ -198,11 +195,11 @@ export function AgentPendingToolCard({ summary, detail, theme, onReject, onAppro ) : null} {onReject || onApprove ? ( -
- -
@@ -242,27 +239,21 @@ function agentImpactFromDetail(detail: unknown) { export function AgentToolCard({ title, text, detail, theme }: { title: string; text: string; detail?: unknown; theme: (typeof canvasThemes)[keyof typeof canvasThemes] }) { const state = toolCardState(title, text, detail); return ( -
- -
- +
+ +
+ {state.icon}
-
+
{title} - + {state.label} - {detail ? ( - - 详情 - - ) : null} -
-
- {text} + {detail ?
+
{text}
diff --git a/web/src/components/canvas/canvas-assistant-panel.tsx b/web/src/components/canvas/canvas-assistant-panel.tsx index 11f2ebcbf..70613c4e9 100644 --- a/web/src/components/canvas/canvas-assistant-panel.tsx +++ b/web/src/components/canvas/canvas-assistant-panel.tsx @@ -1,9 +1,9 @@ import { useCanvasTheme } from "@/hooks/use-canvas-theme"; import { Button, Modal, Segmented, Select } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import copyToClipboard from "copy-to-clipboard"; -import { Copy, Cpu, FileText, Image as ImageIcon, Music, Sparkles, Video, Settings2, Trash2, X } from "lucide-react"; +import { AtSign, Copy, Cpu, FileText, Music, Sparkles, Video, Settings2, Trash2, X } from "lucide-react"; import { motion } from "motion/react"; @@ -26,19 +26,19 @@ import { consumeGenerationTaskAgent } from "@/services/project-asset-sync"; import { applyGenerationConsumerEffect, generationEffectApplied } from "@/services/generation-consumer-dedupe"; import { activeGenerationConsumerController } from "@/services/generation-consumer-lifecycle"; import { useAssetStore } from "@/stores/use-asset-store"; -import { useThemeStore } from "@/stores/use-theme-store"; import { useUserStore } from "@/stores/use-user-store"; import { imageReferenceLabel } from "@/lib/image-reference-prompt"; import { navigateToSettings } from "@/lib/settings-navigation"; import { cinematicAgentSessionOpsJson, createCinematicAgentSession, isAgentSessionPollingAbort, resumeCinematicAgentSession } from "@/lib/canvas/canvas-agent-session"; import { summarizeCanvasContext } from "@/lib/canvas/canvas-context-summary"; import { budgetCanvasAgentHistory } from "@/lib/canvas/canvas-agent-context-budget"; -import { buildOrderedCanvasResourceReferences, canvasResourceMentionToken } from "@/lib/canvas/canvas-resource-references"; -import { AgentChatComposer, AgentChatMessage, AgentWorkingMessage, type CanvasAgentChatMessage, type CanvasAgentMode } from "./canvas-agent-chat-ui"; +import { buildOrderedCanvasResourceReferences, canvasResourceMentionToken, type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; +import { AgentChatComposer, AgentChatMessage, AgentImagePreview, AgentWorkingMessage, type CanvasAgentChatMessage, type CanvasAgentMode } from "./canvas-agent-chat-ui"; import { VoiceRecordingButton } from "@/components/conversation/voice-recording-button"; import { ModelLogo } from "@/components/model-logo"; import { AgentChatEmptyState, AgentPanelChrome } from "./canvas-agent-panel-chrome"; import { CanvasLocalAgentPanel } from "./canvas-local-agent-panel"; +import { useResolvedCanvasResourceReferences } from "./use-resolved-canvas-resource-references"; import { NODE_DEFAULT_SIZE } from "@/constant/canvas"; import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantPendingBackendSession, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "@/types/canvas"; import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store"; @@ -413,7 +413,7 @@ export function CanvasAssistantPanel({ const [deleteChatIds, setDeleteChatIds] = useState([]); const [onlineLogs, setOnlineLogs] = useState([]); const [composerSkills, setComposerSkills] = useState([]); - const [removedReferenceIds, setRemovedReferenceIds] = useState>(new Set()); + const [attachedReferenceIds, setAttachedReferenceIds] = useState>(() => new Set()); const [localSessions, setLocalSessionsState] = useState(() => (sessions.length ? sessions : [createSession()])); const localSessionsRef = useRef(localSessions); const [localActiveSessionId, setLocalActiveSessionIdState] = useState(activeSessionId); @@ -486,10 +486,12 @@ export function CanvasAssistantPanel({ const agentBusy = isRunning || Object.values(creativeBusy).some(Boolean) || safeSessions.some((session) => session.pendingBackendSession?.status === "pending"); const activeCreativeId = messages.findLast((message) => Boolean(canvasCreativeDetail(message)))?.id; const latestAssistantMessage = messages.findLast((message) => message.role === "assistant"); - const currentCreativeState = latestAssistantMessage ? canvasCreativeDetail(latestAssistantMessage)?.state : undefined; - const selectedNodeKey = useMemo(() => Array.from(selectedNodeIds).sort().join(","), [selectedNodeIds]); - const allSelectedReferences = useMemo(() => buildAssistantReferences(nodes, selectedNodeIds), [nodes, selectedNodeIds]); - const selectedReferences = useMemo(() => allSelectedReferences.filter((item) => !removedReferenceIds.has(item.id)), [allSelectedReferences, removedReferenceIds]); + const currentCreativeState = activeCreativeId ? canvasCreativeDetail(messages.find((message) => message.id === activeCreativeId)!)?.state : latestAssistantMessage ? canvasCreativeDetail(latestAssistantMessage)?.state : undefined; + const selectedReferences = useMemo(() => buildAssistantReferences(nodes, attachedReferenceIds), [attachedReferenceIds, nodes]); + const composerImageReferences = useMemo(() => assistantReferencesToMentionReferences(selectedReferences), [selectedReferences]); + const resolvedComposerImageReferences = useResolvedCanvasResourceReferences(composerImageReferences); + const composerReferences = useMemo(() => [...buildSkillMentionReferences(composerSkills), ...resolvedComposerImageReferences], [composerSkills, resolvedComposerImageReferences]); + const resolvedPreviewById = useMemo(() => new Map(resolvedComposerImageReferences.map((item) => [item.id, item.previewUrl || ""])), [resolvedComposerImageReferences]); const contextSummary = useMemo(() => summarizeCanvasContext(nodes, selectedNodeIds), [nodes, selectedNodeIds]); const iconButtonStyle = { color: theme.node.muted }; @@ -500,8 +502,27 @@ export function CanvasAssistantPanel({ }, [agentBusy, agentMode, localActiveSessionId, messages, view]); useEffect(() => { - setRemovedReferenceIds(new Set()); - }, [selectedNodeKey]); + setAttachedReferenceIds((current) => { + const nodeById = new Map(nodes.map((node) => [node.id, node])); + const next = new Set(current); + let changed = false; + selectedNodeIds.forEach((id) => { + const node = nodeById.get(id); + if (!node || !nodeToReference(node) || next.has(id)) return; + next.add(id); + changed = true; + }); + return changed ? next : current; + }); + }, [nodes, selectedNodeIds]); + + useEffect(() => { + setAttachedReferenceIds((current) => { + const nodeIds = new Set(nodes.map((node) => node.id)); + const next = new Set(Array.from(current).filter((id) => nodeIds.has(id))); + return next.size === current.size ? current : next; + }); + }, [nodes]); const updateSession = (sessionId: string, updater: (session: CanvasAssistantSession) => CanvasAssistantSession) => { const next = localSessionsRef.current.map((session) => (session.id === sessionId ? updater(session) : session)); @@ -733,7 +754,7 @@ export function CanvasAssistantPanel({ const toolMessage: CanvasAssistantMessage = { id: toolMessageId, role: "tool", - title: "确认工具调用", + title: `确认${capabilityBatchTitle(result.toolCalls)}`, text: summarizeToolCalls(result.toolCalls), detail: { status: "pending", step: loop.step, toolCalls: result.toolCalls, impact: previewOnlineToolCalls(result.toolCalls, snapshotRef.current, effectiveConfig) }, }; @@ -784,7 +805,7 @@ export function CanvasAssistantPanel({ appendMessage(sessionId, { id: nanoid(), role: "tool", - title: "工具自动执行完成", + title: capabilityBatchTitle(result.toolCalls), text: toolResults.map((item) => toolResultText(item.result)).join("\n"), detail: { status: "completed", step, toolCalls: result.toolCalls, results: toolResults }, }); @@ -833,7 +854,7 @@ export function CanvasAssistantPanel({ appendMessage(sessionId, { id: toolMessageId, role: "tool", - title: "确认工具调用", + title: `确认${capabilityBatchTitle(next.toolCalls)}`, text: summarizeToolCalls(next.toolCalls), detail: { status: "pending", step: step + 1, toolCalls: next.toolCalls, impact: previewOnlineToolCalls(next.toolCalls, snapshotRef.current, effectiveConfig) }, }); @@ -1027,7 +1048,7 @@ export function CanvasAssistantPanel({ setIsRunning(true); const results = await executeOnlineToolCalls(session.id, toolCalls); addOnlineLog("工具执行结果", results); - upsertMessage(session.id, { id: messageId, role: "tool", title: "工具执行完成", text: results.map((item) => toolResultText(item.result)).join("\n"), detail: { ...detail, results, status: "completed" } }); + upsertMessage(session.id, { id: messageId, role: "tool", title: capabilityBatchTitle(toolCalls), text: results.map((item) => toolResultText(item.result)).join("\n"), detail: { ...detail, results, status: "completed" } }); pendingToolContextRef.current.delete(messageId); await continueOnlineToolLoopAfterResults(session.id, assistantId, previousMessages, toolCalls, results, pendingContext?.step || Number(detail.step) || 1); } catch (error) { @@ -1162,8 +1183,10 @@ export function CanvasAssistantPanel({
{messages.length ? ( <> - {messages.map((message) => ( -
+ {messages.map((message, index) => ( + + {agentBusy && index === messages.findLastIndex((item) => item.role === "user") + 1 ?
: null} +
{canvasCreativeDetail(message) && } {message.references?.length ? : null}
+
))} - {agentBusy ? : null} + {agentBusy && messages.findLastIndex((item) => item.role === "user") === messages.length - 1 ?
: null} ) : ( { - setRemovedReferenceIds((prev) => new Set(prev).add(item.id)); + setAttachedReferenceIds((prev) => { + const next = new Set(prev); + next.delete(item.id); + return next; + }); if (selectedNodeIds.has(item.id)) onSelectNodeIds(new Set(Array.from(selectedNodeIds).filter((nodeId) => nodeId !== item.id))); }} + onInsertMention={(label) => { + const token = `@${label} `; + setPrompt((prev) => (promptAlreadyHasMention(prev, label) ? prev : prev.trim() ? `${prev.replace(/\s+$/u, "")} ${token}` : token)); + }} /> ))}
) : null} - {currentCreativeState &&
+ {currentCreativeState &&
onSelectNodeIds(new Set([id]))} />
}
void }) { +function promptAlreadyHasMention(prompt: string, label: string) { + const token = `@${label}`; + let from = 0; + while (from <= prompt.length) { + const index = prompt.indexOf(token, from); + if (index < 0) return false; + const next = prompt[index + token.length]; + if (!next || /\s|[,.!?;:,。!?;:、)\]}】)]/.test(next)) return true; + from = index + 1; + } + return false; +} + +function AssistantReferenceChip({ item, label, previewUrl, onRemove, onInsertMention }: { item: CanvasAssistantReference; label?: string; previewUrl?: string; onRemove?: () => void; onInsertMention?: (label: string) => void }) { const theme = useCanvasTheme(); + const [previewOpen, setPreviewOpen] = useState(false); + const mentionReference = useMemo(() => assistantReferencesToMentionReferences([item]), [item]); + const resolvedReference = useResolvedCanvasResourceReferences(mentionReference)[0]; const title = item.title.trim() || "未命名节点"; - const kind = item.type === "image" ? "图片" : item.type === "video" ? "视频" : item.type === "audio" ? "音频" : item.type === "skill" ? "技能" : "文本"; - const Icon = item.type === "image" ? ImageIcon : item.type === "video" ? Video : item.type === "audio" ? Music : item.type === "skill" ? Sparkles : FileText; + const Icon = item.type === "video" ? Video : item.type === "audio" ? Music : item.type === "skill" ? Sparkles : FileText; + const imageUrl = previewUrl || resolvedReference?.previewUrl || item.dataUrl; + const hasImage = Boolean(imageUrl || item.storageKey); + const chipStyle = { background: theme.spatial.surface, color: theme.node.text }; + const actionStyle = { background: theme.toolbar.panel, borderColor: theme.node.stroke, color: theme.node.text }; + + if (hasImage) { + return ( + <> + + + {label && onInsertMention ? ( + + ) : label ? ( + + + {label} + + ) : null} + {onRemove ? ( + + ) : null} + + {previewOpen && imageUrl ? setPreviewOpen(false)} /> : null} + + ); + } + return ( - -
-
-
+ ); } +function assistantReferencesToMentionReferences(references: CanvasAssistantReference[]): CanvasResourceReference[] { + return references.flatMap((item, index): CanvasResourceReference[] => { + if (item.dataUrl || item.storageKey) { + const label = assistantImageReferenceLabel(references, index) || item.title; + return [{ id: item.id, nodeId: item.id, kind: "image", label, title: item.title, previewUrl: item.dataUrl, storageKey: item.storageKey, active: true }]; + } + if (item.text) { + const label = item.type === CanvasNodeType.Skill ? `技能${index + 1}` : `文本${index + 1}`; + return [{ id: item.id, nodeId: item.id, kind: item.type === CanvasNodeType.Skill ? "skill" : "text", label, title: item.title, text: item.text, active: true }]; + } + return []; + }); +} + function assistantImageReferenceLabel(references: CanvasAssistantReference[], index: number) { - if (!references[index]?.dataUrl) return undefined; - const imageIndex = references.slice(0, index + 1).filter((item) => item.dataUrl).length - 1; + if (!references[index]?.dataUrl && !references[index]?.storageKey) return undefined; + const imageIndex = references.slice(0, index + 1).filter((item) => item.dataUrl || item.storageKey).length - 1; return imageIndex >= 0 ? imageReferenceLabel(imageIndex) : undefined; } @@ -1769,6 +1890,13 @@ function summarizeToolCalls(calls: ResponseToolCall[]) { return calls.map((call) => toolCallLabel(call.function.name)).join(",") || "工具调用"; } +function capabilityBatchTitle(calls: ResponseToolCall[]) { + const names = calls.map((call) => call.function.name); + if (names.some((name) => name.startsWith("canvas_") && (name.includes("skill") || name.includes("plugin")))) return "技能与插件"; + if (names.some((name) => name.startsWith("canvas_"))) return "画布操作"; + return "工具调用"; +} + function previewOnlineToolCalls(calls: ResponseToolCall[], snapshot: CanvasAgentSnapshot, config: AiConfig): CanvasAgentOperationImpact { const ops: CanvasAgentOp[] = []; let deferredCinematicCount = 0; @@ -1842,11 +1970,22 @@ function toolCallLabel(name: string) { if (name === "canvas_select_nodes") return "选择节点"; if (name === "canvas_set_viewport") return "调整视口"; if (name === "canvas_run_generation") return "触发生成"; - return name; + return "执行系统能力"; } function toolResultText(result: OnlineToolResult) { - return result.message; + return friendlyToolMessage(result.message); +} + +function friendlyToolMessage(message: string) { + return message + .replace(/canvas_[a-z0-9_]+/gi, "画布能力") + .replace(/SKILL\.md/gi, "技能说明") + .replace(/nodeId/gi, "节点").replace(/storageKey/gi, "素材引用") + .replace(/executionEpoch|expectedStateHash|expectedRevision/gi, "当前画布状态") + .replace(/toolCalls?/gi, "操作") + .replace(/API|MCP|JSON|HTTP/gi, "系统服务") + .replace(/\s{2,}/g, " "); } function requireStringArray(value: unknown, field: string): string[] { @@ -1997,7 +2136,7 @@ function explainNoop(ops: CanvasAgentOp[], snapshot: CanvasAgentSnapshot) { } function nodeToReference(node: CanvasNodeData): CanvasAssistantReference | null { - if (node.type === CanvasNodeType.Image && node.metadata?.content) { + if (node.type === CanvasNodeType.Image && (node.metadata?.content || node.metadata?.storageKey)) { return { id: node.id, type: node.type, title: node.title, dataUrl: node.metadata.content, storageKey: node.metadata.storageKey }; } if (node.type === CanvasNodeType.Text && node.metadata?.content) { diff --git a/web/src/components/creation/creative-agent-cards.css b/web/src/components/creation/creative-agent-cards.css index 6fb6579f8..a93c7616b 100644 --- a/web/src/components/creation/creative-agent-cards.css +++ b/web/src/components/creation/creative-agent-cards.css @@ -1,12 +1,26 @@ -.creative-agent-card { container-type: inline-size; padding: var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--background); color: var(--foreground); display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; overflow-wrap: anywhere; } +.creative-agent-card { container-type: inline-size; padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--background); color: var(--foreground); display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; overflow-wrap: anywhere; } .creative-agent-card h3, .creative-agent-card p { margin: 0; } .creative-agent-card-content { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; } -.creative-agent-receipt { padding: var(--space-2) var(--space-3); color: var(--muted-foreground); font-size: var(--fs-caption); line-height: 1.7; overflow-wrap: anywhere; } +.creative-agent-receipt { padding: var(--space-1) var(--space-2); color: var(--muted-foreground); font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; } .creative-agent-receipt summary { cursor: pointer; } .creative-agent-receipt p { margin-block: var(--space-2); } -.creative-agent-next { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-2) var(--space-1); color: var(--muted-foreground); line-height: 1.7; } +.creative-agent-next { position: sticky; bottom: 0; z-index: 4; display: flex; flex-direction: column; gap: var(--space-1); margin: 0 -4px; padding: var(--space-1) var(--space-2) calc(var(--space-1) + env(safe-area-inset-bottom)); color: var(--muted-foreground); line-height: 1.5; font-size: var(--fs-caption); background: color-mix(in srgb, var(--background) 96%, transparent); border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); backdrop-filter: blur(10px); } +.creative-agent-primary-actions { position: sticky; bottom: 0; z-index: 3; margin-inline: calc(var(--space-3) * -1); padding: var(--space-2) var(--space-3) 0; background: color-mix(in srgb, var(--background) 96%, transparent); } .creative-agent-next p { margin: 0; } .creative-agent-next .creative-agent-actions:empty { display: none; } +.creative-agent-thinking-wrap { display: flex; align-items: center; min-height: 24px; padding: 0 2px; } +.creative-agent-thinking-wrap > [role="status"] { font-size: var(--fs-caption); gap: var(--space-1); } +.agent-tool-row { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); padding: 2px 4px; } +.agent-tool-message { margin: 0; } +.agent-tool-row:last-child { border-bottom: 0; } +.agent-tool-row summary { padding: 3px 0; } +.agent-tool-status { display: grid; width: 18px; height: 18px; place-items: center; flex-shrink: 0; } +.agent-tool-status svg { width: 15px; height: 15px; } +.agent-tool-label { flex-shrink: 0; font-size: var(--fs-caption); } +.agent-tool-row > .creative-agent-detail { margin: 4px 0 5px 26px; padding: 6px 8px; border-radius: var(--control-radius); background: color-mix(in srgb, var(--surface-card) 70%, transparent); font-size: var(--fs-caption); } +.agent-pending-tool .creative-agent-detail { margin-top: 6px; padding: 6px 8px; font-size: var(--fs-caption); } +.agent-pending-tool details { margin-top: 6px !important; padding-top: 0 !important; } +.agent-pending-tool .creative-agent-archive-row { gap: 6px; } .creative-agent-receipt summary:focus-visible { outline: 2px solid var(--control-focus-ring); outline-offset: 2px; } .creative-agent-archive-row { display: flex; align-items: center; gap: var(--space-1); min-width: 0; } .creative-agent-archive-row > button { flex-shrink: 0; } @@ -20,8 +34,8 @@ .creative-agent-step-dots { display: flex; gap: var(--space-1); flex-wrap: wrap; max-width: 45%; } .creative-agent-step-dots i { width: var(--space-4); height: var(--space-1); border-radius: var(--control-radius); background: var(--border); } .creative-agent-step-dots i[data-current="true"] { background: var(--foreground); } -.creative-agent-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-top: var(--space-1); } -.creative-agent-actions .ant-btn { max-width: 100%; height: auto; min-height: var(--space-8); white-space: normal; } +.creative-agent-actions { display: flex; flex-wrap: nowrap; gap: var(--space-2); padding-top: var(--space-1); } +.creative-agent-actions .ant-btn { max-width: 100%; height: 30px; min-height: 30px; padding-inline: 12px; white-space: nowrap; font-size: 13px; } .creative-agent-options { display: flex; flex-direction: column; gap: var(--space-2); } .creative-agent-option { display: flex; align-items: flex-start; gap: var(--space-2); padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--control-radius); cursor: pointer; } .creative-agent-option[data-selected="true"] { background: var(--control-selected-bg); border-color: var(--control-selected-border); color: var(--control-selected-fg); } @@ -47,6 +61,9 @@ .creative-agent-plan li p { color: var(--muted-foreground); margin: var(--space-1) 0 0; line-height: 1.6; } .creative-agent-plan li[data-status="completed"]::marker { color: var(--muted-foreground); } .creative-agent-plan-trigger { display: flex; width: 100%; gap: var(--space-2); white-space: normal; height: auto; min-height: var(--space-8); text-align: start; padding: var(--space-2) var(--space-3); background: var(--surface-card); border-color: var(--border); } +.creative-agent-plan-docked { position: relative; z-index: 3; width: 100%; margin: 0; padding: 0 12px; display: flex; justify-content: flex-end; } +.creative-agent-plan-docked .creative-agent-plan-trigger { width: auto; min-width: 0; max-width: 230px; min-height: 30px; padding: 5px 9px; border-radius: 999px; font-size: var(--fs-caption); box-shadow: 0 5px 16px var(--shadow-color, rgba(0,0,0,.12)); } +.creative-agent-plan-docked .creative-agent-plan-current { flex: 0 1 auto; } .creative-agent-plan-count { flex-shrink: 0; color: var(--muted-foreground); font-variant-numeric: tabular-nums; } .creative-agent-plan-current { flex: 1; min-width: 0; overflow-wrap: anywhere; } .creative-agent-plan-chevron { color: var(--muted-foreground); flex-shrink: 0; } @@ -61,6 +78,7 @@ .creative-agent-quote-total strong { text-align: end; font-size: var(--fs-heading-lg); font-variant-numeric: tabular-nums; } .creative-agent-card :focus-visible, .creative-agent-plan :focus-visible, .creative-agent-plan-trigger:focus-visible { outline: 2px solid var(--control-focus-ring); outline-offset: 2px; } @container (max-width: 280px) { + .creative-agent-actions { flex-wrap: wrap; } .creative-agent-deliverables { grid-template-columns: minmax(0, 1fr); } .creative-agent-quote-total { flex-wrap: wrap; } } diff --git a/web/src/components/creation/creative-agent-cards.tsx b/web/src/components/creation/creative-agent-cards.tsx index 4c43440e4..800ff1170 100644 --- a/web/src/components/creation/creative-agent-cards.tsx +++ b/web/src/components/creation/creative-agent-cards.tsx @@ -77,7 +77,7 @@ export function CreativeProposalCard({ proposal, onApprove, onModify, onRedirect {!archived && } {expanded &&
{proposal.markdown}
} {!archived && <>
确认后创建本阶段节点并准备参数;图片、视频等生成费用另行确认。
-
} +
} } ; } @@ -96,7 +96,7 @@ export function CreativePlanBar({ plan, onLocateNode }: { plan?: CreativePlan | return { setOpen(next); if (!next) trigger.current?.focus(); }} afterOpenChange={(next) => { if (next) panel.current?.focus(); }} placement="top" trigger="click" content={
{ if (event.key === "Escape") { event.stopPropagation(); close(); } }}>
制作计划
    {plan.steps.map((step) =>
  1. {step.title}{stepLabels[step.status]}
    {step.detail &&

    {step.detail}

    }{onLocateNode && step.nodeIds?.map((id, index) => )}
  2. )}
-
}>
; +
}>; } export function CreativeQuoteCard({ quote, onApprove, onRefresh, busy, disabled }: { quote: CreativeQuote; onApprove: () => void; onRefresh?: () => void; busy?: boolean; disabled?: boolean }) { @@ -108,6 +108,6 @@ export function CreativeQuoteCard({ quote, onApprove, onRefresh, busy, disabled {quote.expiresAt &&

有效期至 {quote.expiresAt}

}{quote.approvedQuantity !== undefined &&

已批准 {quote.approvedQuantity} 项

} {onRefresh && }
{quote.externalBilling &&

由外部渠道计费,平台无法封顶第三方账单。

} -
+
; }