feat(canvas): 添加视频节点支持功能

- 在后端添加 AI 视频相关接口处理函数
- 扩展前端画布组件支持视频节点类型
- 实现视频上传、生成和播放功能
- 更新画布数据结构文档以包含视频节点
- 添加视频节点的操作工具栏和配置面板
- 集成视频存储和检索服务
- 优化视频节点的渲染和交互体验
This commit is contained in:
HouYunFei
2026-05-23 16:53:21 +08:00
parent 0cc27b5a4c
commit 44c5825cb0
23 changed files with 306 additions and 57 deletions
@@ -12,7 +12,7 @@ import { fetchAssetLibrary, type AssetLibraryItem } from "@/services/api/assets"
export type AssetPickerTab = "my-assets" | "library";
export type InsertAssetPayload = { kind: "text"; content: string; title: string } | { kind: "image"; dataUrl: string; title: string; storageKey?: string };
export type InsertAssetPayload = { kind: "text"; content: string; title: string } | { kind: "image"; dataUrl: string; title: string; storageKey?: string } | { kind: "video"; url: string; title: string; storageKey?: string };
type Props = {
open: boolean;
@@ -48,6 +48,7 @@ const kindOptions = [
{ label: "全部", value: "all" },
{ label: "文本", value: "text" },
{ label: "图片", value: "image" },
{ label: "视频", value: "video" },
];
function LibraryTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => void }) {
@@ -157,7 +158,7 @@ function PickerCard({ title, kind, cover, loading, onClick }: { title: string; k
<div className="p-2.5">
<div className="flex items-center justify-between gap-2">
<span className="line-clamp-1 text-xs font-medium text-stone-800 dark:text-stone-200">{title}</span>
<Tag className="m-0 shrink-0 text-[10px]">{kind === "image" ? "图片" : "文本"}</Tag>
<Tag className="m-0 shrink-0 text-[10px]">{kind === "image" ? "图片" : kind === "video" ? "视频" : "文本"}</Tag>
</div>
</div>
{loading && (
@@ -190,7 +191,7 @@ function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) =>
const filtered = useMemo(() => {
const query = keyword.trim().toLowerCase();
return assets
.filter((a) => a.kind === "text" || a.kind === "image")
.filter((a) => a.kind === "text" || a.kind === "image" || a.kind === "video")
.filter((a) => kindFilter === "all" || a.kind === kindFilter)
.filter((a) => !query || [a.title, ...(a.tags || [])].join(" ").toLowerCase().includes(query));
}, [assets, keyword, kindFilter]);
@@ -206,7 +207,7 @@ function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) =>
if (asset.kind === "text") {
onInsert({ kind: "text", content: asset.data.content, title: asset.title });
} else {
onInsert({ kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title });
onInsert(asset.kind === "video" ? { kind: "video", url: asset.data.url, storageKey: asset.data.storageKey, title: asset.title } : { kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title });
}
};