chore(release): 版本发布 - publish v1.0.12

This commit is contained in:
ddCat
2026-07-28 21:55:42 +08:00
parent 1380849d03
commit 2a16eb5d46
19 changed files with 99 additions and 111 deletions

View File

@@ -31,7 +31,7 @@ const ONLINE_AGENT_PROMPT =
const JSON_RECORD_SCHEMA = { type: "object", additionalProperties: true };
const POSITION_SCHEMA = { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"], additionalProperties: false };
const VIEWPORT_SCHEMA = { type: "object", properties: { x: { type: "number" }, y: { type: "number" }, k: { type: "number" } }, required: ["x", "y", "k"], additionalProperties: false };
const NODE_TYPE_SCHEMA = { type: "string", enum: ["image", "text", "skill", "config", "video", "audio"] };
const NODE_TYPE_SCHEMA = { type: "string", enum: ["image", "text", "skill", "video", "audio"] };
const GENERATION_MODE_SCHEMA = { type: "string", enum: ["text", "image", "video", "audio"] };
const GENERATION_OPTION_PROPERTIES = {
model: { type: "string" },
@@ -94,13 +94,12 @@ const ONLINE_AGENT_TOOLS: ResponseFunctionTool[] = [
toolDefinition("canvas_get_selection", "读取当前网页画布选中的节点。", {}),
toolDefinition("canvas_export_snapshot", "导出当前画布快照,用于理解布局。", {}),
toolDefinition("canvas_apply_ops", "批量操作当前网页画布。ops 支持 add_node、update_node、delete_node、delete_connections、connect_nodes、set_viewport、select_nodes、run_generation。", { ops: { type: "array", items: CANVAS_OP_SCHEMA } }, ["ops"], false),
toolDefinition("canvas_create_node", "创建任意类型节点:text、image、config、video、audio。适合创建占位图、媒体占位、配置节点或自定义 metadata 节点。", { nodeType: NODE_TYPE_SCHEMA, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" }, metadata: JSON_RECORD_SCHEMA }, ["nodeType"]),
toolDefinition("canvas_create_node", "创建任意类型节点:text、image、video、audio。适合创建文本、媒体占位或自定义 metadata 节点。", { nodeType: NODE_TYPE_SCHEMA, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" }, metadata: JSON_RECORD_SCHEMA }, ["nodeType"]),
toolDefinition("canvas_create_text_node", "在当前画布创建单个文本节点。", { text: { type: "string" }, x: { type: "number" }, y: { type: "number" }, title: { type: "string" }, width: { type: "number" }, height: { type: "number" } }),
toolDefinition("canvas_create_text_nodes", "批量创建文本节点,适合生成标题、段落、脚本、说明等内容块。", { items: { type: "array", minItems: 1, items: { type: "object", properties: { text: { type: "string" }, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" } }, required: ["text"], additionalProperties: false } }, x: { type: "number" }, y: { type: "number" }, gap: { type: "number" }, direction: { type: "string", enum: ["row", "column"] } }, ["items"]),
toolDefinition("canvas_create_cinematic_session", "把自然语言创作指令提交给后端影视 Agent 会话,后端拆解为剧本、场景、分镜、镜头和成片节点,并返回可写回画布的操作。", { prompt: { type: "string" } }, ["prompt"]),
toolDefinition("canvas_create_config_node", "创建生成配置节点,可指定 text/image/video/audio 模式和生成参数,可选择立即触发生成。", { prompt: { type: "string" }, mode: GENERATION_MODE_SCHEMA, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" }, autoRun: { type: "boolean" }, ...GENERATION_OPTION_PROPERTIES }),
toolDefinition("canvas_create_image_prompt_flow", "创建提示词文本节点和图片生成配置节点,并自动连线,可选择立即触发生图。", { prompt: { type: "string" }, x: { type: "number" }, y: { type: "number" }, autoRun: { type: "boolean" }, ...GENERATION_OPTION_PROPERTIES }, ["prompt"]),
generationToolDefinition("canvas_create_generation_flow", "创建通用生成流程:提示词文本节点、生成配置节点、参考节点连线,可用于文案、生图、视频或音频。"),
toolDefinition("canvas_create_image_prompt_flow", "创建提示词文本节点和图片目标节点并自动连线,可选择立即触发生图。", { prompt: { type: "string" }, x: { type: "number" }, y: { type: "number" }, autoRun: { type: "boolean" }, ...GENERATION_OPTION_PROPERTIES }, ["prompt"]),
generationToolDefinition("canvas_create_generation_flow", "创建通用生成流程:提示词文本节点、对应类型的生成目标节点和参考节点连线,可用于文案、生图、视频或音频。"),
generationToolDefinition("canvas_generate_text", "创建通用文本生成流程并立即触发生成。", "text"),
generationToolDefinition("canvas_generate_image", "创建通用图片生成流程并立即触发生成。", "image"),
generationToolDefinition("canvas_generate_video", "创建通用视频生成流程并立即触发生成。", "video"),
@@ -1177,7 +1176,6 @@ function onlineToolToOps(name: string, input: Record<string, unknown>, snapshot:
const nodeType = requireNodeType(input.nodeType);
const x = numberOr(input.x, nextCanvasX(snapshot));
const y = numberOr(input.y, 0);
if (nodeType === CanvasNodeType.Config) return [configNodeOp(stringOptional(input.id) || `config-${nanoid()}`, { ...recordOptional(input.metadata), ...input }, x, y, config)];
return [{ type: "add_node", nodeType, title: stringOptional(input.title), position: { x, y }, width: numberOptional(input.width), height: numberOptional(input.height), metadata: recordOptional(input.metadata) as CanvasNodeData["metadata"] }];
}
if (name === "canvas_create_text_node") return [textNodeOp(input, numberOr(input.x, nextCanvasX(snapshot)), numberOr(input.y, 0))];
@@ -1190,11 +1188,6 @@ function onlineToolToOps(name: string, input: Record<string, unknown>, snapshot:
return items.map((item, index) => textNodeOp({ ...item, text: requireString(item.text, "text") }, numberOr(item.x, direction === "row" ? x + index * (NODE_DEFAULT_SIZE[CanvasNodeType.Text].width + gap) : x), numberOr(item.y, direction === "row" ? y : y + index * (NODE_DEFAULT_SIZE[CanvasNodeType.Text].height + gap))));
}
if (name === "canvas_create_image_prompt_flow") return generationFlowOps({ ...input, mode: "image" }, snapshot, config);
if (name === "canvas_create_config_node") {
const configId = `config-${nanoid()}`;
const mode = generationMode(input.mode);
return [configNodeOp(configId, input, numberOr(input.x, nextCanvasX(snapshot)), numberOr(input.y, 0), config), ...(input.autoRun ? [runGenerationOp(configId, mode, stringOptional(input.prompt))] : [])];
}
if (name === "canvas_create_generation_flow") return generationFlowOps(input, snapshot, config);
if (name === "canvas_generate_text") return generationFlowOps({ ...input, mode: "text", autoRun: true }, snapshot, config);
if (name === "canvas_generate_image") return generationFlowOps({ ...input, mode: "image", autoRun: true }, snapshot, config);
@@ -1224,16 +1217,16 @@ function generationFlowOps(input: Record<string, unknown>, snapshot: CanvasAgent
const x = numberOr(input.x, nextCanvasX(snapshot));
const y = numberOr(input.y, 0);
const textId = `text-${nanoid()}`;
const configId = `config-${nanoid()}`;
const targetId = `${mode}-${nanoid()}`;
const referenceNodeIds = Array.isArray(input.referenceNodeIds) ? input.referenceNodeIds.filter((id): id is string => typeof id === "string") : [];
const tokens = [`@[node:${textId}]`, ...referenceNodeIds.map((id) => `@[node:${id}]`)];
return [
textNodeOp({ id: textId, text: prompt, title: stringOptional(input.title) || "提示词" }, x, y),
configNodeOp(configId, { ...input, prompt: tokens.join("\n") }, x + NODE_DEFAULT_SIZE[CanvasNodeType.Text].width + 80, y, config),
{ type: "connect_nodes", fromNodeId: textId, toNodeId: configId },
...referenceNodeIds.map((fromNodeId) => ({ type: "connect_nodes" as const, fromNodeId, toNodeId: configId })),
{ type: "select_nodes", ids: [configId] },
...(input.autoRun ? [runGenerationOp(configId, mode, tokens.join("\n"))] : []),
generationTargetNodeOp(targetId, { ...input, prompt: tokens.join("\n") }, x + NODE_DEFAULT_SIZE[CanvasNodeType.Text].width + 80, y, config),
{ type: "connect_nodes", fromNodeId: textId, toNodeId: targetId },
...referenceNodeIds.map((fromNodeId) => ({ type: "connect_nodes" as const, fromNodeId, toNodeId: targetId })),
{ type: "select_nodes", ids: [targetId] },
...(input.autoRun ? [runGenerationOp(targetId, mode, tokens.join("\n"))] : []),
];
}
@@ -1241,18 +1234,21 @@ function textNodeOp(input: Record<string, unknown>, x: number, y: number): Canva
return { type: "add_node", id: stringOptional(input.id), nodeType: CanvasNodeType.Text, title: stringOptional(input.title), position: { x, y }, width: numberOptional(input.width), height: numberOptional(input.height), metadata: { content: stringOptional(input.text), status: "success", fontSize: 14 } };
}
function configNodeOp(id: string, input: Record<string, unknown>, x: number, y: number, config: AiConfig): CanvasAgentOp {
function generationTargetNodeOp(id: string, input: Record<string, unknown>, x: number, y: number, config: AiConfig): CanvasAgentOp {
const mode = generationMode(input.mode);
const prompt = stringOptional(input.prompt);
const nodeType = generationNodeType(mode);
return {
type: "add_node",
id,
nodeType: CanvasNodeType.Config,
nodeType,
title: stringOptional(input.title) || generationTitle(mode),
position: { x, y },
width: numberOptional(input.width),
height: numberOptional(input.height),
metadata: cleanRecord({
content: "",
fontSize: nodeType === CanvasNodeType.Text ? 14 : undefined,
generationMode: mode,
composerContent: prompt,
prompt,
@@ -1274,6 +1270,13 @@ function configNodeOp(id: string, input: Record<string, unknown>, x: number, y:
};
}
function generationNodeType(mode: "text" | "image" | "video" | "audio") {
if (mode === "text") return CanvasNodeType.Text;
if (mode === "video") return CanvasNodeType.Video;
if (mode === "audio") return CanvasNodeType.Audio;
return CanvasNodeType.Image;
}
function runGenerationOp(nodeId: string, mode: "text" | "image" | "video" | "audio", prompt?: string): CanvasAgentOp {
return { type: "run_generation", nodeId, mode, prompt };
}
@@ -1333,7 +1336,6 @@ function toolCallLabel(name: string) {
if (name === "canvas_create_node") return "创建节点";
if (name === "canvas_create_text_node") return "创建文本";
if (name === "canvas_create_text_nodes") return "批量创建文本";
if (name === "canvas_create_config_node") return "创建生成配置";
if (name === "canvas_create_image_prompt_flow") return "创建生图流程";
if (name === "canvas_create_generation_flow") return "创建生成流程";
if (name === "canvas_generate_text") return "生成文本";

View File

@@ -1,6 +1,6 @@
import { AnimatePresence, useReducedMotion } from "motion/react";
import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
import { ArrowLeft, Check, ChevronRight, Clapperboard, Clipboard, Copy, FolderOpen, FolderPlus, Image as ImageIcon, Layers3, Link2, Maximize2, Music2, PanelTop, Pencil, Plus, Redo2, Settings2, Tags, Trash2, Type, Undo2, Upload, UserRound, Video } from "lucide-react";
import { ArrowLeft, Check, ChevronRight, Clapperboard, Clipboard, Copy, FolderOpen, FolderPlus, Image as ImageIcon, Layers3, Link2, Maximize2, Music2, PanelTop, Pencil, Plus, Redo2, Tags, Trash2, Type, Undo2, Upload, UserRound, Video } from "lucide-react";
import { aceternityMotion } from "@/lib/aceternity-motion";
import { SpotlightSurface } from "@/components/ui/aceternity/spotlight-surface";
@@ -259,7 +259,6 @@ function AddNodeContextMenu({ parentPosition, workspaceMode, isProjectLinked, re
...(!simpleMode ? [
{ id: "director", label: "导演台", icon: <Layers3 />, badge: "3D", onClick: onOpenDirector },
{ id: "audio", label: "音频", icon: <Music2 />, onClick: () => onAddNode(CanvasNodeType.Audio) },
{ id: "config", label: "生成配置", icon: <Settings2 />, onClick: () => onAddNode(CanvasNodeType.Config) },
] : []),
];
const resourceCommands: CanvasCreateCommand[] = [

View File

@@ -875,7 +875,6 @@ function toolName(name: string) {
if (name === "canvas_create_node") return "创建节点";
if (name === "canvas_create_text_node") return "创建文本";
if (name === "canvas_create_text_nodes") return "批量创建文本";
if (name === "canvas_create_config_node") return "创建生成配置";
if (name === "canvas_create_image_prompt_flow") return "创建生图流程";
if (name === "canvas_create_generation_flow") return "创建生成流程";
if (name === "canvas_generate_text") return "生成文本";

View File

@@ -459,6 +459,7 @@ export const CanvasNode = React.memo(function CanvasNode({
function NodeContent(props: NodeContentRendererProps) {
const hasCustomContent = props.node.type === CanvasNodeType.Config
|| props.node.type === CanvasNodeType.Script
|| Boolean(props.node.metadata?.directorSceneId)
|| (props.node.metadata?.workflowKind === "character" && Boolean(props.node.metadata.characterAssetId))
|| (props.node.metadata?.workflowKind === "story_input" && !props.isEditingContent)
|| (props.node.metadata?.workflowKind === "styleboard" && !props.node.metadata.content);
@@ -716,7 +717,7 @@ function SkillContent({ node, theme }: NodeContentRendererProps) {
<span key={tag} className="rounded-md border px-1.5 py-0.5 text-[10px]" style={{ borderColor: theme.node.stroke, color: theme.node.muted }}>
{tag}
</span>
)) : <span className="text-[11px]" style={{ color: theme.node.muted }}>连接到生成配置节点后生效</span>}
)) : <span className="text-[11px]" style={{ color: theme.node.muted }}>连接到图片、视频、音频或文本节点后生效</span>}
</div>
</div>
);

View File

@@ -1,7 +1,7 @@
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useRef, useState, type MouseEvent as ReactMouseEvent, type ReactNode } from "react";
import { Segmented, Switch } from "antd";
import { CircleDot, Clapperboard, Eraser, FolderOpen, Grid2x2, Hand, Image as ImageIcon, Info, Layers3, Moon, Music2, Palette, PanelTop, Pencil, Plus, Redo2, Square, Sun, Trash2, Type, Undo2, UploadCloud, UserRound, Video, WandSparkles, X } from "lucide-react";
import { CircleDot, Clapperboard, Eraser, FolderOpen, Grid2x2, Hand, Image as ImageIcon, Info, Layers3, Moon, Music2, Palette, PanelTop, Pencil, Plus, Redo2, Square, Sun, Trash2, Type, Undo2, UploadCloud, UserRound, Video, X } from "lucide-react";
import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler";
import { FloatingDock, type FloatingDockEntry } from "@/components/ui/aceternity/floating-dock";
@@ -29,7 +29,6 @@ export function CanvasToolbar({
onAddScript,
onAddFrame,
onAddDrawing,
onAddConfig,
onOpenDirector,
onUndo,
onRedo,
@@ -57,7 +56,6 @@ export function CanvasToolbar({
onAddScript: () => void;
onAddFrame: () => void;
onAddDrawing: () => void;
onAddConfig: () => void;
onOpenDirector: () => void;
onUndo: () => void;
onRedo: () => void;
@@ -127,7 +125,6 @@ export function CanvasToolbar({
onAddImage={() => runAddAction(onAddImage)}
onAddVideo={() => runAddAction(onAddVideo)}
onAddAudio={() => runAddAction(onAddAudio)}
onAddConfig={() => runAddAction(onAddConfig)}
onOpenDirector={() => runAddAction(onOpenDirector)}
onUpload={() => runAddAction(onUpload)}
onOpenAssets={() => runAddAction(onOpenMyAssets)}
@@ -171,7 +168,7 @@ export function CanvasToolbar({
);
}
function AddNodeMenu({ x, theme, workspaceMode, isProjectLinked, onAddText, onChooseStyle, onAddScript, onAddFrame, onAddDrawing, onAddImage, onAddVideo, onAddAudio, onAddConfig, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: {
function AddNodeMenu({ x, theme, workspaceMode, isProjectLinked, onAddText, onChooseStyle, onAddScript, onAddFrame, onAddDrawing, onAddImage, onAddVideo, onAddAudio, onOpenDirector, onUpload, onOpenAssets, onOpenProjectCharacters }: {
x: number;
theme: CanvasTheme;
workspaceMode: CanvasWorkspaceMode;
@@ -184,7 +181,6 @@ function AddNodeMenu({ x, theme, workspaceMode, isProjectLinked, onAddText, onCh
onAddImage: () => void;
onAddVideo: () => void;
onAddAudio: () => void;
onAddConfig: () => void;
onOpenDirector: () => void;
onUpload: () => void;
onOpenAssets: () => void;
@@ -204,7 +200,6 @@ function AddNodeMenu({ x, theme, workspaceMode, isProjectLinked, onAddText, onCh
...(!simpleMode ? [
{ id: "director", label: "导演台", icon: <Layers3 />, badge: "3D", onClick: onOpenDirector },
{ id: "audio", label: "音频", icon: <Music2 />, onClick: onAddAudio },
{ id: "config", label: "生成配置", icon: <WandSparkles />, onClick: onAddConfig },
] : []),
];
const resourceCommands: CanvasCreateCommand[] = [

View File

@@ -1,6 +1,6 @@
import { motion, useReducedMotion } from "motion/react";
import { useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from "react";
import { ChevronRight, Clapperboard, Image as ImageIcon, List, Music2, Pencil, Settings2, Video, WandSparkles, X } from "lucide-react";
import { ChevronRight, Clapperboard, Image as ImageIcon, List, Music2, Pencil, Video, WandSparkles, X } from "lucide-react";
import { SpotlightSurface } from "@/components/ui/aceternity/spotlight-surface";
import { canvasThemes } from "@/lib/canvas-theme";
@@ -123,7 +123,7 @@ export function CanvasNodePanelOverlay({ node, viewport, containerRef, panelWidt
);
}
export function CanvasConnectionCreateMenu({ pending, viewport, viewportSize, containerRef, canCreateDrawing, onCreate, onClose }: { pending: PendingConnectionCreate; viewport: ViewportTransform; viewportSize: { width: number; height: number }; containerRef: RefObject<HTMLDivElement | null>; canCreateDrawing: boolean; onCreate: (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Config | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing) => void; onClose: () => void }) {
export function CanvasConnectionCreateMenu({ pending, viewport, viewportSize, containerRef, canCreateDrawing, onCreate, onClose }: { pending: PendingConnectionCreate; viewport: ViewportTransform; viewportSize: { width: number; height: number }; containerRef: RefObject<HTMLDivElement | null>; canCreateDrawing: boolean; onCreate: (type: CanvasNodeType.Image | CanvasNodeType.Text | CanvasNodeType.Script | CanvasNodeType.Video | CanvasNodeType.Audio | CanvasNodeType.Drawing) => void; onClose: () => void }) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
const reducedMotion = useReducedMotion();
const menuRef = useRef<HTMLDivElement>(null);
@@ -175,7 +175,6 @@ export function CanvasConnectionCreateMenu({ pending, viewport, viewportSize, co
{canCreateDrawing ? <ConnectionCreateOption motionEnabled={!reducedMotion} icon={<Pencil className="size-4" />} title="绘图" onClick={() => onCreate(CanvasNodeType.Drawing)} /> : null}
<ConnectionCreateOption motionEnabled={!reducedMotion} icon={<Video className="size-4" />} title="视频生成" onClick={() => onCreate(CanvasNodeType.Video)} />
<ConnectionCreateOption motionEnabled={!reducedMotion} icon={<Music2 className="size-4" />} title="音频参考" onClick={() => onCreate(CanvasNodeType.Audio)} />
<ConnectionCreateOption motionEnabled={!reducedMotion} icon={<Settings2 className="size-4" />} title="配置节点" onClick={() => onCreate(CanvasNodeType.Config)} />
</div>
</SpotlightSurface>
);