feat(canvas): 前端重构与渠道兼容 - 重构工作区、任务中心等多页面,修复智能体对话与节点生成
Co-authored-by: sikongyue <3155157671@qq.com> Original-PR: https://github.com/ddcat-ai/open-ai-canvas/pull/217
This commit is contained in:
@@ -750,7 +750,7 @@ export function CanvasAssistantPanel({ nodes, selectedNodeIds, snapshot, project
|
||||
{agentBusy ? <AgentWorkingMessage theme={theme} /> : null}
|
||||
</>
|
||||
) : (
|
||||
<AgentChatEmptyState theme={theme} nodeCount={contextSummary.nodeCount} onSelect={setPrompt} />
|
||||
<AgentChatEmptyState theme={theme} nodeCount={contextSummary.nodeCount} onSelect={(text) => { setPrompt(text); void sendMessage(text, messages); }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -172,8 +172,8 @@ export const CanvasLocalAgentPanel = memo(function CanvasLocalAgentPanel({ snaps
|
||||
return () => clearTimeout(timer);
|
||||
}, [connected, endpoint, snapshot, token]);
|
||||
|
||||
const sendPrompt = async () => {
|
||||
const text = prompt.trim();
|
||||
const sendPrompt = async (overrideText?: string) => {
|
||||
const text = (overrideText ?? prompt).trim();
|
||||
const files = attachments;
|
||||
const requestPrompt = promptWithAttachments(text, files);
|
||||
if (!connected || !requestPrompt || sending || waiting) return;
|
||||
@@ -545,7 +545,7 @@ export const CanvasLocalAgentPanel = memo(function CanvasLocalAgentPanel({ snaps
|
||||
) : (
|
||||
<>
|
||||
<div ref={listRef} className="thin-scrollbar min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
|
||||
{!messages.length && !pendingTool && !waiting ? <AgentChatEmptyState theme={theme} nodeCount={snapshot.nodes.length} onSelect={(prompt) => setAgentState({ prompt })} /> : null}
|
||||
{!messages.length && !pendingTool && !waiting ? <AgentChatEmptyState theme={theme} nodeCount={snapshot.nodes.length} onSelect={(text) => { setAgentState({ prompt: text }); void sendPrompt(text); }} /> : null}
|
||||
{messages.map((item) => (
|
||||
<AgentChatMessage key={item.id} item={agentMessageToChatMessage(item)} theme={theme} user={user} />
|
||||
))}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { ArrowUp, AtSign, Boxes, ChevronDown, FileText, ImageIcon, ImagePlus, Ma
|
||||
import { Button, Image as AntImage, Modal, Tooltip } from "antd";
|
||||
|
||||
import { ModelPicker } from "@/components/model-picker";
|
||||
import { configuredModelMatchesCapability, defaultConfig, modelOptionName, resolveModelChannel, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store";
|
||||
import { defaultConfig, modelOptionName, resolveModelChannel, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store";
|
||||
import { resolveCanvasGenerationModel } from "@/lib/canvas/canvas-project-generation";
|
||||
import { CreditSymbol, requestCreditCost } from "@/constant/credits";
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { normalizeVideoDuration, normalizeVideoResolution } from "@/lib/video-generation-options";
|
||||
@@ -475,8 +476,7 @@ function defaultMode(type: CanvasNodeData["type"]): CanvasNodeGenerationMode {
|
||||
function buildNodeConfig(globalConfig: AiConfig, node: CanvasNodeData, mode: CanvasNodeGenerationMode): AiConfig {
|
||||
const defaultModel = mode === "image" ? globalConfig.imageModel : mode === "video" ? globalConfig.videoModel : mode === "audio" ? globalConfig.audioModel : globalConfig.textModel;
|
||||
const fallbackModel = mode === "image" ? defaultConfig.imageModel : mode === "video" ? defaultConfig.videoModel : mode === "audio" ? defaultConfig.audioModel : defaultConfig.textModel;
|
||||
const storedModel = node.metadata?.model;
|
||||
const model = storedModel && configuredModelMatchesCapability(globalConfig, storedModel, mode) ? storedModel : defaultModel && configuredModelMatchesCapability(globalConfig, defaultModel, mode) ? defaultModel : fallbackModel;
|
||||
const model = resolveCanvasGenerationModel(globalConfig, node.metadata?.model, mode) || resolveCanvasGenerationModel(globalConfig, defaultModel, mode) || fallbackModel;
|
||||
return {
|
||||
...globalConfig,
|
||||
model,
|
||||
|
||||
@@ -51,7 +51,7 @@ export function CanvasProjectAssetModal({ open, detail, initialCategory = "all",
|
||||
<div className="flex h-[min(620px,calc(100vh-80px))] min-h-[440px] flex-col overflow-hidden">
|
||||
<header className="flex h-12 shrink-0 items-center justify-between border-b border-border py-0 pl-4 pr-12"><h2 className="text-sm font-semibold">引用项目角色与资产</h2><span className="text-[var(--fs-label)] text-foreground/42">已选 {selectedIds.size} 项</span></header>
|
||||
<div className="grid min-h-0 flex-1 grid-rows-[auto_minmax(0,1fr)] sm:grid-cols-[150px_minmax(0,1fr)] sm:grid-rows-1">
|
||||
<nav className="thin-scrollbar flex min-w-0 gap-1 overflow-x-auto border-b border-border p-2 sm:block sm:overflow-y-auto sm:border-b-0 sm:border-r" aria-label="项目资产分类">{categories.map((item) => <button key={item} type="button" onClick={() => setCategory(item)} className={`flex h-11 min-w-[104px] shrink-0 items-center justify-between rounded-md px-2 text-xs sm:w-full sm:min-w-0 ${category === item ? "bg-foreground/[.08] font-medium" : "text-foreground/55 hover:bg-foreground/[.04]"}`}><span>{categoryLabels[item] || "其他"}</span><span className="min-w-5 rounded bg-foreground/[.05] px-1 text-center text-[var(--fs-tiny)] tabular-nums">{item === "all" ? items.length : items.filter((asset) => asset.category === item).length}</span></button>)}</nav>
|
||||
<nav className="thin-scrollbar flex min-w-0 gap-1 overflow-x-auto border-b border-border p-2 sm:block sm:overflow-y-auto sm:border-b-0 sm:border-r" aria-label="项目资产分类">{categories.map((item) => <button key={item} type="button" onClick={() => setCategory(item)} className={`flex h-11 min-w-[104px] shrink-0 items-center justify-between rounded-md px-2 text-xs sm:w-full sm:min-w-0 ${category === item ? "bg-surface-active font-medium" : "text-foreground/55 hover:bg-surface-hover"}`}><span>{categoryLabels[item] || "其他"}</span><span className="min-w-5 rounded bg-foreground/[.05] px-1 text-center text-[var(--fs-tiny)] tabular-nums">{item === "all" ? items.length : items.filter((asset) => asset.category === item).length}</span></button>)}</nav>
|
||||
<div className="thin-scrollbar min-h-0 overflow-y-auto p-3">{visible.length ? <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4">{visible.map((item) => <ProjectAssetCard key={item.id} item={item} selected={selectedIds.has(item.id)} onToggle={() => toggle(item.id)} />)}</div> : <WorkspaceState icon="assets" compact className="h-full" title="此分类没有可引用资产" description={detail ? "先在项目角色与资产中完成角色确认或素材关联。" : "当前为自由画布,下方展示本地素材库;也可先上传素材后再引用。"} />}</div>
|
||||
</div>
|
||||
<footer className="flex h-12 shrink-0 items-center justify-between border-t border-border px-3"><span className="text-[var(--fs-tiny)] text-foreground/42">角色引用会在生成时解析当前角色版本</span><div className="flex gap-2"><Button size="small" onClick={onClose}>取消</Button><Button size="small" type="primary" disabled={!selectedIds.size} loading={inserting} onClick={() => void insert()}>引入 {selectedIds.size || ""} 项</Button></div></footer>
|
||||
|
||||
@@ -103,10 +103,10 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
if (collapsed) {
|
||||
return (
|
||||
<aside className="relative z-[var(--z-panel)] hidden w-11 shrink-0 flex-col items-center border-r border-border bg-background/94 py-2 backdrop-blur-xl lg:flex">
|
||||
<button type="button" className="grid size-7 place-items-center rounded-md text-foreground/55 hover:bg-foreground/[.06]" title="展开项目侧栏" aria-label="展开项目侧栏" onClick={() => setCollapsed(false)}>
|
||||
<button type="button" className="grid size-7 place-items-center rounded-md text-foreground/55 hover:bg-surface-hover" title="展开项目侧栏" aria-label="展开项目侧栏" onClick={() => setCollapsed(false)}>
|
||||
<ChevronRight className="size-4" />
|
||||
</button>
|
||||
<Link to={`/projects/${projectId}/canvases`} className="mt-2 grid size-7 place-items-center rounded-md text-foreground/55 hover:bg-foreground/[.06]" title="返回项目画布列表">
|
||||
<Link to={`/projects/${projectId}/canvases`} className="mt-2 grid size-7 place-items-center rounded-md text-foreground/55 hover:bg-surface-hover" title="返回项目画布列表">
|
||||
<FolderKanban className="size-4" />
|
||||
</Link>
|
||||
</aside>
|
||||
@@ -120,7 +120,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
<FolderKanban className="size-3.5 shrink-0" />
|
||||
<span className="truncate">{projectDetail?.project.name || "项目空间"}</span>
|
||||
</Link>
|
||||
<button type="button" className="grid size-7 shrink-0 place-items-center rounded-md text-foreground/45 hover:bg-foreground/[.06]" title="收起项目侧栏" aria-label="收起项目侧栏" onClick={() => setCollapsed(true)}>
|
||||
<button type="button" className="grid size-7 shrink-0 place-items-center rounded-md text-foreground/45 hover:bg-surface-hover" title="收起项目侧栏" aria-label="收起项目侧栏" onClick={() => setCollapsed(true)}>
|
||||
<ChevronLeft className="size-4" />
|
||||
</button>
|
||||
</header>
|
||||
@@ -131,7 +131,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
<Palette className="size-3.5" />
|
||||
项目画风
|
||||
</span>
|
||||
<Link to={`/projects/${projectId}/settings`} className="grid size-6 place-items-center rounded text-foreground/35 hover:bg-foreground/[.06] hover:text-foreground" title="编辑项目画风" aria-label="编辑项目画风">
|
||||
<Link to={`/projects/${projectId}/settings`} className="grid size-6 place-items-center rounded text-foreground/35 hover:bg-surface-hover hover:text-foreground" title="编辑项目画风" aria-label="编辑项目画风">
|
||||
<Settings2 className="size-3.5" />
|
||||
</Link>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
type="button"
|
||||
disabled={!style}
|
||||
onClick={onLocateStyle}
|
||||
className="group flex h-11 w-full items-center gap-2 rounded-md px-1.5 text-left hover:bg-foreground/[.045] disabled:cursor-default disabled:hover:bg-transparent"
|
||||
className="group flex h-11 w-full items-center gap-2 rounded-md px-1.5 text-left hover:bg-surface-hover disabled:cursor-default disabled:hover:bg-transparent"
|
||||
title={style ? "定位画布中的项目画风节点" : "项目尚未设置画风"}
|
||||
>
|
||||
{style ? (
|
||||
@@ -164,7 +164,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
剧情章节 <span className="tabular-nums text-foreground/32">{orderedUnits.length.toLocaleString("zh-CN")}</span>
|
||||
</span>
|
||||
{selectedId ? (
|
||||
<button type="button" onClick={revealSelectedChapter} className="grid size-6 place-items-center rounded text-foreground/35 hover:bg-foreground/[.06] hover:text-foreground" title="回到当前章节" aria-label="回到当前章节">
|
||||
<button type="button" onClick={revealSelectedChapter} className="grid size-6 place-items-center rounded text-foreground/35 hover:bg-surface-hover hover:text-foreground" title="回到当前章节" aria-label="回到当前章节">
|
||||
<Crosshair className="size-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
@@ -183,7 +183,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
aria-label="搜索项目章节"
|
||||
/>
|
||||
{query ? (
|
||||
<button type="button" onClick={() => setQuery("")} className="grid size-5 shrink-0 place-items-center rounded text-foreground/32 hover:bg-foreground/[.06] hover:text-foreground" aria-label="清空章节搜索">
|
||||
<button type="button" onClick={() => setQuery("")} className="grid size-5 shrink-0 place-items-center rounded text-foreground/32 hover:bg-surface-hover hover:text-foreground" aria-label="清空章节搜索">
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
) : null}
|
||||
@@ -208,7 +208,7 @@ export function CanvasProjectSidebar({ projectId, detail, onAddChapter, onLocate
|
||||
event.dataTransfer.setData(CANVAS_PROJECT_CHAPTER_DND_TYPE, JSON.stringify(payload));
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
}}
|
||||
className={`group flex h-9 cursor-grab items-center gap-1 rounded-md px-1 text-xs active:cursor-grabbing ${active ? "bg-foreground/[.08]" : "hover:bg-foreground/[.045]"}`}
|
||||
className={`group flex h-9 cursor-grab items-center gap-1 rounded-md px-1 text-xs active:cursor-grabbing ${active ? "bg-surface-active" : "hover:bg-surface-hover"}`}
|
||||
>
|
||||
<GripVertical className="size-3.5 shrink-0 text-foreground/22" />
|
||||
<button
|
||||
@@ -295,7 +295,7 @@ function ChapterPreview({
|
||||
<div className="text-[var(--fs-tiny)] tabular-nums text-foreground/38">第 {chapterNumber.toLocaleString("zh-CN")} 章</div>
|
||||
<h2 className="mt-0.5 truncate text-sm font-semibold">{unit.title}</h2>
|
||||
</div>
|
||||
<button type="button" onClick={onClose} className="grid size-6 shrink-0 place-items-center rounded text-foreground/38 hover:bg-foreground/[.06] hover:text-foreground" aria-label="关闭章节预览">
|
||||
<button type="button" onClick={onClose} className="grid size-6 shrink-0 place-items-center rounded text-foreground/38 hover:bg-surface-hover hover:text-foreground" aria-label="关闭章节预览">
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
</header>
|
||||
@@ -312,7 +312,7 @@ function ChapterPreview({
|
||||
>
|
||||
{adding ? <LoaderCircle className="size-3.5 animate-spin" /> : <Plus className="size-3.5" />}添加到画布
|
||||
</button>
|
||||
<Link to={`/projects/${projectId}/chapters/${unit.id}`} className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-foreground/62 hover:bg-foreground/[.06] hover:text-foreground">
|
||||
<Link to={`/projects/${projectId}/chapters/${unit.id}`} className="inline-flex h-8 items-center gap-1.5 rounded-md px-2.5 text-xs text-foreground/62 hover:bg-surface-hover hover:text-foreground">
|
||||
打开编辑器
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
</Link>
|
||||
|
||||
@@ -115,6 +115,7 @@ export function CanvasScriptNodeContent({ node, batch, pipeline, scale, mentionR
|
||||
? `${node.metadata.taskStage || "正在创建任务"}${typeof node.metadata.taskProgress === "number" ? ` · ${node.metadata.taskProgress}%` : ""}`
|
||||
: node.metadata?.status === "error" ? generationErrorMessage(node.metadata.errorDetails) : "";
|
||||
const [batchDetailsOpen, setBatchDetailsOpen] = useState(false);
|
||||
const [moreMenuOpen, setMoreMenuOpen] = useState(false);
|
||||
const pipelineDisabled = !rows.length || node.metadata?.status === "loading" || hasActiveBatchItems;
|
||||
const missingImages = Math.max(0, pipeline.images.total - pipeline.images.created);
|
||||
const missingVideos = Math.max(0, pipeline.videos.total - pipeline.videos.created);
|
||||
@@ -164,9 +165,9 @@ export function CanvasScriptNodeContent({ node, batch, pipeline, scale, mentionR
|
||||
<span className="min-w-0 flex-1 truncate text-sm font-semibold" title={node.title || "分镜脚本"}>{node.title || "分镜脚本"}</span>
|
||||
{batchSummary ? <span className="min-w-0 max-w-[42%] truncate text-[var(--fs-label)] font-medium" title={batchSummary} style={{ color: batch?.status === "partial_failed" ? theme.accent.danger : theme.node.muted }}>{batchSummary}</span> : taskFeedback ? <span className="min-w-0 max-w-[38%] truncate text-[var(--fs-label)] font-medium" title={taskFeedback} style={{ color: node.metadata?.status === "error" ? theme.accent.danger : theme.node.muted }}>{taskFeedback}</span> : null}
|
||||
<span className="text-[var(--fs-caption)] font-semibold tabular-nums" style={{ color: theme.node.muted }}>{rows.length} 镜 · {totalDuration}s</span>
|
||||
<Tooltip title="全屏编辑"><button type="button" className="grid size-7 place-items-center rounded outline-none transition hover:bg-black/5 focus-visible:ring-2 dark:hover:bg-white/10" style={{ "--tw-ring-color": theme.node.muted } as CSSProperties} onMouseDown={(event) => event.stopPropagation()} onClick={(event) => { event.stopPropagation(); onOpen(); }} aria-label="全屏编辑"><Expand className="size-3.5" /></button></Tooltip>
|
||||
<Dropdown menu={{ items: moreMenuItems }} trigger={["click"]} placement="bottomRight">
|
||||
<button type="button" className="grid size-7 place-items-center rounded outline-none transition hover:bg-black/5 focus-visible:ring-2 dark:hover:bg-white/10" style={{ "--tw-ring-color": theme.node.muted } as CSSProperties} onMouseDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()} aria-label="更多操作"><MoreHorizontal className="size-3.5" /></button>
|
||||
<Tooltip title="全屏编辑"><button type="button" className="grid size-7 place-items-center rounded outline-none transition hover:bg-black/5 focus-visible:ring-2 dark:hover:bg-white/10" style={{ "--tw-ring-color": theme.node.muted } as CSSProperties} onMouseDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} onClick={(event) => { event.stopPropagation(); onOpen(); }} aria-label="全屏编辑"><Expand className="size-3.5" /></button></Tooltip>
|
||||
<Dropdown open={moreMenuOpen} onOpenChange={setMoreMenuOpen} menu={{ items: moreMenuItems, onClick: () => setMoreMenuOpen(false) }} trigger={["click"]} placement="bottomRight">
|
||||
<button type="button" className="grid size-7 place-items-center rounded outline-none transition hover:bg-black/5 focus-visible:ring-2 dark:hover:bg-white/10" style={{ "--tw-ring-color": theme.node.muted } as CSSProperties} onMouseDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} onClick={(event) => { event.stopPropagation(); setMoreMenuOpen(true); }} aria-label="更多操作"><MoreHorizontal className="size-3.5" /></button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
{batch ? <Modal title="批次详情" open={batchDetailsOpen} onCancel={() => setBatchDetailsOpen(false)} footer={null} width={560} centered destroyOnHidden>
|
||||
|
||||
Reference in New Issue
Block a user