feat(*): 画布创作 - 支持文本多模态生成并优化媒体节点、连接线与视频预览

Fixes #59
This commit is contained in:
ddCat
2026-07-31 19:44:02 +08:00
parent 5db39ccad4
commit 8de9b42c00
29 changed files with 231 additions and 111 deletions

View File

@@ -3,6 +3,7 @@ import { Input, Modal, Pagination, Tag } from "antd";
import { Search } from "lucide-react";
import { WorkspaceState } from "@/components/layout/workspace-state";
import { AssetMediaPreview } from "@/components/asset-media-preview";
import { cn } from "@/lib/utils";
import { useAssetStore, type Asset } from "@/stores/use-asset-store";
@@ -40,18 +41,15 @@ const kindOptions = [
{ label: "音频", value: "audio" },
];
function PickerCard({ title, kind, cover, onClick }: { title: string; kind: string; cover: string; onClick: () => void }) {
function PickerCard({ asset, onClick }: { asset: InsertableAsset; onClick: () => void }) {
const { title, kind } = asset;
return (
<button
type="button"
className="group relative cursor-pointer overflow-hidden rounded-lg border border-stone-200 bg-white text-left transition hover:border-stone-400 hover:shadow-md dark:border-stone-700 dark:bg-stone-900 dark:hover:border-stone-500"
onClick={onClick}
>
{cover ? (
<img src={cover} alt={title} loading="lazy" decoding="async" className="aspect-[4/3] w-full object-cover" />
) : (
<div className="flex aspect-[4/3] items-center justify-center bg-stone-100 p-3 text-center text-xs leading-5 text-stone-500 dark:bg-stone-800 dark:text-stone-400">{title}</div>
)}
<AssetMediaPreview asset={asset} alt={title} className="aspect-[4/3] w-full bg-black object-cover" fallback={<div className="flex aspect-[4/3] items-center justify-center bg-stone-100 p-3 text-center text-xs leading-5 text-stone-500 dark:bg-stone-800 dark:text-stone-400">{title}</div>} />
<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>
@@ -131,7 +129,7 @@ function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) =>
{visible.length ? (
<div className="grid grid-cols-4 gap-3">
{visible.map((asset) => (
<PickerCard key={asset.id} title={asset.title} kind={asset.kind} cover={asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : "")} onClick={() => handleInsert(asset)} />
<PickerCard key={asset.id} asset={asset} onClick={() => handleInsert(asset)} />
))}
</div>
) : (