From 87f3576df2368542ec8d67fd29c0db0c5891eabb Mon Sep 17 00:00:00 2001 From: ddCat Date: Mon, 27 Jul 2026 11:14:16 +0800 Subject: [PATCH] =?UTF-8?q?fix(*):=20Web=20=E6=9E=84=E5=BB=BA=20-=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9F=AD=E5=89=A7=E9=A1=B9=E7=9B=AE=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E5=90=88=E5=B9=B6=E5=90=8E=E7=9A=84=20TypeScript=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/canvas/asset-picker-modal.tsx | 9 +++-- .../canvas/canvas-assistant-panel.tsx | 38 +++++++++---------- .../canvas/canvas-drawing-editor-modal.tsx | 2 +- .../canvas/canvas-node-hover-toolbar.tsx | 6 +-- .../canvas/canvas-node-prompt-panel.tsx | 4 +- web/src/lib/canvas/canvas-export.ts | 4 +- web/src/lib/canvas/canvas-node-asset.ts | 3 +- web/src/lib/canvas/canvas-project-domain.ts | 2 +- web/src/pages/assets/index.tsx | 28 +++++++------- web/src/pages/canvas/project.tsx | 1 - .../pages/canvas/use-canvas-node-editor.ts | 4 +- web/src/pages/projects/detail/assets.tsx | 2 +- web/src/pages/projects/detail/chapters.tsx | 6 +-- web/src/services/user-data-sync.ts | 8 +++- web/src/types/canvas-export.ts | 1 + 15 files changed, 63 insertions(+), 55 deletions(-) diff --git a/web/src/components/canvas/asset-picker-modal.tsx b/web/src/components/canvas/asset-picker-modal.tsx index 6dfb38569..65c0be578 100644 --- a/web/src/components/canvas/asset-picker-modal.tsx +++ b/web/src/components/canvas/asset-picker-modal.tsx @@ -6,6 +6,8 @@ import { WorkspaceState } from "@/components/layout/workspace-state"; import { cn } from "@/lib/utils"; import { useAssetStore, type Asset } from "@/stores/use-asset-store"; +type InsertableAsset = Extract; + export type InsertAssetPayload = | { kind: "text"; content: string; title: string; assetId?: string } | { kind: "image"; dataUrl: string; title: string; storageKey?: string; assetId?: string } @@ -70,7 +72,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" || a.kind === "video" || a.kind === "audio") + .filter((asset): asset is InsertableAsset => asset.kind === "text" || asset.kind === "image" || asset.kind === "video" || asset.kind === "audio") .filter((a) => kindFilter === "all" || a.kind === kindFilter) .filter((a) => !query || [a.title, ...(a.tags || [])].join(" ").toLowerCase().includes(query)); }, [assets, keyword, kindFilter]); @@ -82,15 +84,14 @@ function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => setPage((v) => Math.min(v, maxPage)); }, [filtered.length]); - const handleInsert = (asset: Asset) => { - if (asset.kind === "model") return; + const handleInsert = (asset: InsertableAsset) => { if (asset.kind === "text") { onInsert({ kind: "text", content: asset.data.content, title: asset.title, assetId: asset.id }); } else if (asset.kind === "audio") { onInsert({ kind: "audio", url: asset.data.url, storageKey: asset.data.storageKey, title: asset.title, durationMs: asset.data.durationMs, bytes: asset.data.bytes, mimeType: asset.data.mimeType, assetId: asset.id }); } else if (asset.kind === "video") { onInsert({ kind: "video", url: asset.data.url, storageKey: asset.data.storageKey, title: asset.title, width: asset.data.width, height: asset.data.height, durationMs: asset.data.durationMs, bytes: asset.data.bytes, mimeType: asset.data.mimeType, assetId: asset.id }); - } else { + } else if (asset.kind === "image") { onInsert({ kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title, assetId: asset.id }); } }; diff --git a/web/src/components/canvas/canvas-assistant-panel.tsx b/web/src/components/canvas/canvas-assistant-panel.tsx index d99bb4517..c4aae40ad 100644 --- a/web/src/components/canvas/canvas-assistant-panel.tsx +++ b/web/src/components/canvas/canvas-assistant-panel.tsx @@ -908,25 +908,25 @@ function AgentTextModelPicker({ config, value, onChange }: { config: AiConfig; v const options = useMemo(() => Array.from(new Set([value, ...selectableModelsByCapability(config, "text")].filter(Boolean))), [config, value]); const current = value || ""; return ( - - size="small" - variant="borderless" - value={current || undefined} - className="agent-text-model-select min-w-0 max-w-[240px]" - popupMatchSelectWidth={288} - options={options.map((model) => ({ value: model, label: `${modelDisplayName(config, model)} ${modelOptionName(model)} ${resolveModelChannel(config, model).name}` }))} - notFoundContent={暂无文本模型} - optionRender={(option) => { - const model = String(option.value); - return {modelDisplayName(config, model)}{modelOptionName(model)}{resolveModelChannel(config, model).name}; - }} - labelRender={() => {current ? modelDisplayName(config, current) : "选择文本模型"}{current ? {resolveModelChannel(config, current).name} : null}} - onChange={onChange} - onMouseDown={(event) => event.stopPropagation()} - onPointerDown={(event) => event.stopPropagation()} - aria-label="选择 Agent 文本模型" - title={current ? `${modelDisplayName(config, current)} · ${modelOptionName(current)} · ${resolveModelChannel(config, current).name}` : "选择文本模型"} - /> +
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}> + + size="small" + variant="borderless" + value={current || undefined} + className="agent-text-model-select w-full" + popupMatchSelectWidth={288} + options={options.map((model) => ({ value: model, label: `${modelDisplayName(config, model)} ${modelOptionName(model)} ${resolveModelChannel(config, model).name}` }))} + notFoundContent={暂无文本模型} + optionRender={(option) => { + const model = String(option.value); + return {modelDisplayName(config, model)}{modelOptionName(model)}{resolveModelChannel(config, model).name}; + }} + labelRender={() => {current ? modelDisplayName(config, current) : "选择文本模型"}{current ? {resolveModelChannel(config, current).name} : null}} + onChange={onChange} + aria-label="选择 Agent 文本模型" + title={current ? `${modelDisplayName(config, current)} · ${modelOptionName(current)} · ${resolveModelChannel(config, current).name}` : "选择文本模型"} + /> +
); } diff --git a/web/src/components/canvas/canvas-drawing-editor-modal.tsx b/web/src/components/canvas/canvas-drawing-editor-modal.tsx index 0cb75d548..e96ca8778 100644 --- a/web/src/components/canvas/canvas-drawing-editor-modal.tsx +++ b/web/src/components/canvas/canvas-drawing-editor-modal.tsx @@ -77,7 +77,7 @@ export function CanvasDrawingEditorModal({ open, projectId, node, onClose, onSav destroyOnHidden width="100vw" centered - styles={{ body: { padding: 0 }, content: { padding: 0, overflow: "hidden" } }} + styles={{ body: { padding: 0 }, container: { padding: 0, overflow: "hidden" } }} className="canvas-drawing-editor-modal" >
diff --git a/web/src/components/canvas/canvas-node-hover-toolbar.tsx b/web/src/components/canvas/canvas-node-hover-toolbar.tsx index e1ab84fa6..24522a8b9 100644 --- a/web/src/components/canvas/canvas-node-hover-toolbar.tsx +++ b/web/src/components/canvas/canvas-node-hover-toolbar.tsx @@ -11,7 +11,7 @@ import { CONTENT_MODERATION_ERROR_CODE, isContentModerationError } from "@/lib/g import { useCopyText } from "@/hooks/use-copy-text"; import { useThemeStore } from "@/stores/use-theme-store"; import { FloatingDock, type FloatingDockEntry } from "@/components/ui/aceternity/floating-dock"; -import { CanvasNodeType, type CanvasNodeData, type CanvasWorkspaceMode, type ViewportTransform } from "@/types/canvas"; +import { CanvasNodeType, type CanvasNodeData, type CanvasNodeMetadata, type CanvasWorkspaceMode, type ViewportTransform } from "@/types/canvas"; import { ImageToolSettingsModal } from "./canvas-image-toolbar-settings-modal"; import { IMAGE_QUICK_TOOLS_STORAGE_KEY, buildImageToolbarTools, defaultImageQuickToolIds, isImageQuickToolId, readImageQuickToolsConfig, type ImageQuickToolId } from "./canvas-image-toolbar-tools"; @@ -309,7 +309,7 @@ export function CanvasNodeHoverToolbar({ ); } -export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, readOnly = false, onUnauthorized }: { node: CanvasNodeData | null; open: boolean; onClose: () => void; onMetadataChange?: (nodeId: string, metadata: Partial) => void; readOnly?: boolean; onUnauthorized?: () => void }) { +export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, readOnly = false, onUnauthorized }: { node: CanvasNodeData | null; open: boolean; onClose: () => void; onMetadataChange?: (nodeId: string, metadata: Partial) => void; readOnly?: boolean; onUnauthorized?: () => void }) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; const [view, setView] = useState<"info" | "json">("info"); const [assetTags, setAssetTags] = useState([]); @@ -418,7 +418,7 @@ export function CanvasNodeInfoModal({ node, open, onClose, onMetadataChange, rea
{assetCategoryOptions.map((option) => { const active = assetCategory === option.value; - return ; + return ; })}
生成后会按此分类进入项目资产;角色、场景和画风工作流会自动预填。
diff --git a/web/src/components/canvas/canvas-node-prompt-panel.tsx b/web/src/components/canvas/canvas-node-prompt-panel.tsx index 11669c026..279ec726e 100644 --- a/web/src/components/canvas/canvas-node-prompt-panel.tsx +++ b/web/src/components/canvas/canvas-node-prompt-panel.tsx @@ -15,7 +15,7 @@ import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textare import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover"; import { CanvasVideoPromptTools } from "./canvas-video-prompt-tools"; import { CanvasPresetPicker, type CanvasPromptPreset } from "./canvas-preset-picker"; -import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData, type CanvasWorkspaceMode } from "@/types/canvas"; +import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData, type CanvasNodeMetadata, type CanvasWorkspaceMode } from "@/types/canvas"; import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; export type CanvasNodeGenerationMode = CanvasGenerationMode; @@ -24,7 +24,7 @@ type CanvasNodePromptPanelProps = { node: CanvasNodeData; isRunning: boolean; onPromptChange: (nodeId: string, prompt: string) => void; - onConfigChange: (nodeId: string, patch: Partial) => void; + onConfigChange: (nodeId: string, patch: Partial) => void; onGenerate: (nodeId: string, mode: CanvasNodeGenerationMode, prompt: string) => void; onStop: (nodeId: string) => void; mentionReferences?: CanvasResourceReference[]; diff --git a/web/src/lib/canvas/canvas-export.ts b/web/src/lib/canvas/canvas-export.ts index 427bc27dc..e7f7d5f18 100644 --- a/web/src/lib/canvas/canvas-export.ts +++ b/web/src/lib/canvas/canvas-export.ts @@ -22,7 +22,7 @@ export async function exportCanvasProjects(projects: CanvasProject[], fileName = zipFiles.push({ name: path, data: blob }); }), ); - const drawingDocuments = (await Promise.all(project.nodes.filter((node) => node.type === "drawing" && node.metadata?.drawingId).map(async (node) => { + const drawingDocuments = (await Promise.all(project.nodes.filter((node) => node.type === "drawing" && node.metadata?.drawingId).map(async (node): Promise => { const drawingId = node.metadata?.drawingId; if (!drawingId) return null; const [saved, preview, render] = await Promise.all([ @@ -43,7 +43,7 @@ export async function exportCanvasProjects(projects: CanvasProject[], fileName = ? { path: generationRenderPath, pageId: render.pageId, width: render.width, height: render.height, mimeType: render.mimeType, background: render.background } : undefined, } satisfies CanvasDrawingExport; - }))).filter((item): item is CanvasDrawingExport => Boolean(item)); + }))).filter((item): item is CanvasDrawingExport => item !== null); drawingDocuments.forEach((document) => zipFiles.push({ name: `projects/${project.id}/drawings/${safeFileName(document.drawingId)}.json`, data: JSON.stringify(document) })); return { project, files, drawingDocuments }; }), diff --git a/web/src/lib/canvas/canvas-node-asset.ts b/web/src/lib/canvas/canvas-node-asset.ts index 5912c5d6d..d9bb34d44 100644 --- a/web/src/lib/canvas/canvas-node-asset.ts +++ b/web/src/lib/canvas/canvas-node-asset.ts @@ -106,7 +106,8 @@ function sameAssetResource(asset: Asset, node: CanvasNodeData) { } function assetStorageKey(asset: Asset) { - return asset.kind === "text" ? undefined : asset.data.storageKey; + if (asset.kind === "text" || asset.kind === "entity") return undefined; + return asset.data.storageKey; } export function declaredCanvasNodeAssetCategory(node: CanvasNodeData): AssetCategory | undefined { diff --git a/web/src/lib/canvas/canvas-project-domain.ts b/web/src/lib/canvas/canvas-project-domain.ts index fe3bcaae6..8f5e9c82c 100644 --- a/web/src/lib/canvas/canvas-project-domain.ts +++ b/web/src/lib/canvas/canvas-project-domain.ts @@ -96,7 +96,7 @@ export function storyboardRowsFromTask(task: GenerationTask) { } -export function applyNodeConfigPatch(node: CanvasNodeData, patch: Partial) { +export function applyNodeConfigPatch(node: CanvasNodeData, patch: Partial) { const safePatch = patch || {}; const next = { ...node, metadata: { ...node.metadata, ...safePatch } }; const spec = node.type === CanvasNodeType.Video ? NODE_DEFAULT_SIZE[CanvasNodeType.Video] : NODE_DEFAULT_SIZE[CanvasNodeType.Image]; diff --git a/web/src/pages/assets/index.tsx b/web/src/pages/assets/index.tsx index 15a5ba047..16b3d98e3 100644 --- a/web/src/pages/assets/index.tsx +++ b/web/src/pages/assets/index.tsx @@ -15,6 +15,8 @@ import { useAssetStore, type Asset, type AssetCategory, type AssetKind, type Ima import { exportAssets, readAssetPackage } from "./asset-transfer"; import { deleteAssetWithRemoteSync } from "@/services/user-data-sync"; +type LibraryAsset = Exclude; + type AssetFormValues = { kind: AssetKind; category: AssetCategory; @@ -64,10 +66,10 @@ export default function AssetsPage() { const [categoryFilter, setCategoryFilter] = useState("all"); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); - const [editingAsset, setEditingAsset] = useState(null); + const [editingAsset, setEditingAsset] = useState(null); const [isAssetOpen, setIsAssetOpen] = useState(false); - const [previewAsset, setPreviewAsset] = useState(null); - const [deletingAsset, setDeletingAsset] = useState(null); + const [previewAsset, setPreviewAsset] = useState(null); + const [deletingAsset, setDeletingAsset] = useState(null); const [selectedIds, setSelectedIds] = useState([]); const [batchDeleteOpen, setBatchDeleteOpen] = useState(false); const [formKind, setFormKind] = useState("text"); @@ -76,7 +78,7 @@ export default function AssetsPage() { const title = Form.useWatch("title", form) || ""; const tags = Form.useWatch("tags", form) || []; const content = Form.useWatch("content", form) || ""; - const validAssets = useMemo(() => assets.filter((asset) => asset.kind === "text" || asset.kind === "image" || asset.kind === "video" || asset.kind === "audio" || asset.kind === "model"), [assets]); + const validAssets = useMemo(() => assets.filter((asset): asset is LibraryAsset => asset.kind !== "entity"), [assets]); const selectedAssets = useMemo(() => validAssets.filter((asset) => selectedIds.includes(asset.id)), [selectedIds, validAssets]); const kindCounts = useMemo(() => new Map(kindOptions.map((option) => [option.value, option.value === "all" ? validAssets.length : validAssets.filter((asset) => asset.kind === option.value).length])), [validAssets]); const categoryCounts = useMemo(() => new Map(categoryOptions.map((option) => [option.value, option.value === "all" ? validAssets.length : validAssets.filter((asset) => (asset.category || "other") === option.value).length])), [validAssets]); @@ -114,7 +116,7 @@ export default function AssetsPage() { setIsAssetOpen(true); }; - const openEdit = (asset: Asset) => { + const openEdit = (asset: LibraryAsset) => { setEditingAsset(asset); setFormKind(asset.kind); setImageDraft(asset.kind === "image" ? asset.data : null); @@ -183,12 +185,12 @@ export default function AssetsPage() { message.success("3D 模型已保存"); }; - const copyAssetText = async (asset: Asset) => { + const copyAssetText = async (asset: LibraryAsset) => { if (asset.kind !== "text") return; copyText(asset.data.content, "文本已复制"); }; - const downloadImage = (asset: Asset) => { + const downloadImage = (asset: LibraryAsset) => { if (asset.kind !== "image" && asset.kind !== "video" && asset.kind !== "audio" && asset.kind !== "model") return; const url = asset.kind === "image" ? asset.data.dataUrl : asset.data.url; const extension = asset.kind === "model" ? asset.data.fileName.split(".").pop() || "glb" : asset.data.mimeType.split("/")[1] || "png"; @@ -421,7 +423,7 @@ export default function AssetsPage() { ); } -function AssetCard({ asset, selected, onSelect, onOpen, onEdit, onCopy, onDownload, onDelete }: { asset: Asset; selected: boolean; onSelect: (selected: boolean) => void; onOpen: () => void; onEdit: () => void; onCopy: (asset: Asset) => void; onDownload: (asset: Asset) => void; onDelete: () => void }) { +function AssetCard({ asset, selected, onSelect, onOpen, onEdit, onCopy, onDownload, onDelete }: { asset: LibraryAsset; selected: boolean; onSelect: (selected: boolean) => void; onOpen: () => void; onEdit: () => void; onCopy: (asset: LibraryAsset) => void; onDownload: (asset: LibraryAsset) => void; onDelete: () => void }) { const cover = asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : ""); const summary = assetSummary(asset); return ( @@ -494,7 +496,7 @@ function AssetFilterGroup({ title, options, value, counts, onChange, className = ); } -function AssetDrawer({ asset, onClose, onCopy, onDownload }: { asset: Asset | null; onClose: () => void; onCopy: (asset: Asset) => void; onDownload: (asset: Asset) => void }) { +function AssetDrawer({ asset, onClose, onCopy, onDownload }: { asset: LibraryAsset | null; onClose: () => void; onCopy: (asset: LibraryAsset) => void; onDownload: (asset: LibraryAsset) => void }) { const cover = asset ? asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : "") : ""; return ( @@ -565,14 +567,14 @@ function AssetDrawer({ asset, onClose, onCopy, onDownload }: { asset: Asset | nu ); } -function assetSummary(asset: Asset) { +function assetSummary(asset: LibraryAsset) { if (asset.kind === "text") return asset.data.content; if (asset.kind === "audio") return `${formatAssetDuration(asset.data.durationMs)} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`; if (asset.kind === "model") return `${asset.data.fileName} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`; return `${asset.data.width}x${asset.data.height} · ${formatBytes(asset.data.bytes)} · ${asset.data.mimeType}`; } -function StorageTag({ asset }: { asset: Asset }) { +function StorageTag({ asset }: { asset: LibraryAsset }) { if (asset.kind !== "image" && asset.kind !== "video" && asset.kind !== "audio" && asset.kind !== "model") return null; const location = resourceStorageLocation(asset.data.storageKey); const color = location === "oss" ? "green" : location === "local" ? "gold" : "default"; @@ -583,7 +585,7 @@ function StorageTag({ asset }: { asset: Asset }) { ); } -function assetSearchText(asset: Asset) { +function assetSearchText(asset: LibraryAsset) { return [asset.title, asset.source || "", asset.note || "", assetCategoryLabel(asset.category), (asset.tags || []).join(" "), asset.kind === "text" ? asset.data.content : asset.data.mimeType].join(" ").toLowerCase(); } @@ -591,7 +593,7 @@ function assetCategoryLabel(category?: AssetCategory) { return categoryOptions.find((item) => item.value === (category || "other"))?.label || "其他"; } -function assetProjectLabel(asset: Asset) { +function assetProjectLabel(asset: LibraryAsset) { const projectName = asset.metadata?.projectName; if (typeof projectName === "string" && projectName.trim()) return projectName; return Array.isArray(asset.metadata?.projectIds) && asset.metadata.projectIds.length ? "已关联项目" : "未关联项目"; diff --git a/web/src/pages/canvas/project.tsx b/web/src/pages/canvas/project.tsx index b4c9c7277..b6843c469 100644 --- a/web/src/pages/canvas/project.tsx +++ b/web/src/pages/canvas/project.tsx @@ -423,7 +423,6 @@ function InfiniteCanvasPage() { setSelectedConnectionId, setContextMenu, setDialogNodeId, - setDrawingNodeId, setToolbarNodeId, }); diff --git a/web/src/pages/canvas/use-canvas-node-editor.ts b/web/src/pages/canvas/use-canvas-node-editor.ts index 54366d432..6cd12f76e 100644 --- a/web/src/pages/canvas/use-canvas-node-editor.ts +++ b/web/src/pages/canvas/use-canvas-node-editor.ts @@ -9,7 +9,7 @@ import { applyNodeConfigPatch } from "@/lib/canvas/canvas-project-domain"; import { audioExtension, imageExtension, resetGenerationTaskMetadata } from "@/lib/canvas/canvas-project-generation"; import { CONTENT_MODERATION_ERROR_CODE, isContentModerationError } from "@/lib/generation-error"; import { ensureCanvasNodeAsset } from "@/services/project-asset-sync"; -import { CanvasNodeType, type CanvasNodeData, type Position } from "@/types/canvas"; +import { CanvasNodeType, type CanvasNodeData, type CanvasNodeMetadata, type Position } from "@/types/canvas"; type UseCanvasNodeEditorOptions = { canvasId: string; @@ -136,7 +136,7 @@ export function useCanvasNodeEditor({ })); }, [setNodes]); - const handleConfigNodeChange = useCallback((nodeId: string, patch: Partial) => { + const handleConfigNodeChange = useCallback((nodeId: string, patch: Partial) => { setNodes((current) => current.map((node) => (node.id === nodeId ? applyNodeConfigPatch(node, patch) : node))); if (!patch.assetCategory) return; const node = nodesRef.current.find((item) => item.id === nodeId); diff --git a/web/src/pages/projects/detail/assets.tsx b/web/src/pages/projects/detail/assets.tsx index 590e6ed5a..18c137b63 100644 --- a/web/src/pages/projects/detail/assets.tsx +++ b/web/src/pages/projects/detail/assets.tsx @@ -179,7 +179,7 @@ function MediaAssetCard({ asset, personalAsset, onCategoryChange, onVersion, onR } function characterDefinition(values: CharacterForm) { - const definition = Object.fromEntries(characterFields.map(([key]) => [key, values[key]?.trim() || (key === "aliases" ? [] : "")])); + const definition: Record = Object.fromEntries(characterFields.map(([key]) => [key, values[key]?.trim() || (key === "aliases" ? [] : "")])); definition.aliases = values.aliases?.split(/[,,]/).map((item) => item.trim()).filter(Boolean) || []; return definition; } diff --git a/web/src/pages/projects/detail/chapters.tsx b/web/src/pages/projects/detail/chapters.tsx index 09819324b..c0f1b2b97 100644 --- a/web/src/pages/projects/detail/chapters.tsx +++ b/web/src/pages/projects/detail/chapters.tsx @@ -8,7 +8,7 @@ import Highlight from "@tiptap/extension-highlight"; import Placeholder from "@tiptap/extension-placeholder"; import TextAlign from "@tiptap/extension-text-align"; import { TextStyle } from "@tiptap/extension-text-style"; -import { EditorContent, useEditor } from "@tiptap/react"; +import { EditorContent, useEditor, type Editor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { AlignCenter, @@ -425,7 +425,7 @@ export default function ProjectChaptersView({ detail, refreshProject }: ProjectD ); } -function EditorToolbar({ editor }: { editor: ReturnType }) { +function EditorToolbar({ editor }: { editor: Editor | null }) { const setLink = () => { if (!editor) return; const current = String(editor.getAttributes("link").href || ""); @@ -472,7 +472,7 @@ function EditorToolbar({ editor }: { editor: ReturnType }) { ); } -function EditorTool({ editor, label, icon, active = false, onClick }: { editor: ReturnType; label: string; icon: ReactNode; active?: boolean; onClick: () => void }) { +function EditorTool({ editor, label, icon, active = false, onClick }: { editor: Editor | null; label: string; icon: ReactNode; active?: boolean; onClick: () => void }) { return ; } diff --git a/web/src/services/user-data-sync.ts b/web/src/services/user-data-sync.ts index acfde21da..abeffab7f 100644 --- a/web/src/services/user-data-sync.ts +++ b/web/src/services/user-data-sync.ts @@ -161,14 +161,18 @@ async function saveRemoteUserDataBatch() { } } -async function hydrateAssets(assets: Asset[]) { +async function hydrateAssets(assets: Asset[]): Promise { return Promise.all( assets.map(async (asset) => { if (asset.kind === "image" && asset.data.storageKey) { const dataUrl = await resolveImageUrl(asset.data.storageKey, asset.data.dataUrl); return { ...asset, coverUrl: shouldReplaceEphemeralUrl(asset.coverUrl) ? dataUrl : asset.coverUrl, data: { ...asset.data, dataUrl } }; } - if ((asset.kind === "video" || asset.kind === "audio") && asset.data.storageKey) { + if (asset.kind === "video" && asset.data.storageKey) { + const url = await resolveResourceOrMediaUrl(asset.data.storageKey, asset.data.url); + return { ...asset, coverUrl: shouldReplaceEphemeralUrl(asset.coverUrl) ? url : asset.coverUrl, data: { ...asset.data, url } }; + } + if (asset.kind === "audio" && asset.data.storageKey) { const url = await resolveResourceOrMediaUrl(asset.data.storageKey, asset.data.url); return { ...asset, coverUrl: shouldReplaceEphemeralUrl(asset.coverUrl) ? url : asset.coverUrl, data: { ...asset.data, url } }; } diff --git a/web/src/types/canvas-export.ts b/web/src/types/canvas-export.ts index 1a84aff15..1bc63638d 100644 --- a/web/src/types/canvas-export.ts +++ b/web/src/types/canvas-export.ts @@ -14,6 +14,7 @@ export type CanvasProjectExportItem = { }; export type CanvasDrawingExport = { + version: 1; drawingId: string; previewPath?: string; snapshot: unknown;