feat(*): 分镜与资产 - 完善五类资产、候选去重、搜索、中文镜头编号与分类整理
This commit is contained in:
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
||||
|
||||
import { AssetLibraryPickerModal, type AssetLibraryPickerItem } from "@/components/assets/asset-library-picker-modal";
|
||||
import { useExternalAssetSources } from "@/hooks/use-external-asset-sources";
|
||||
import { ASSET_CATEGORY_LABELS, normalizeAssetCategory } from "@/lib/asset-category";
|
||||
import type { ExternalAssetPickerReference } from "@/lib/plugins/plugin-types";
|
||||
import { useAssetStore, type Asset } from "@/stores/use-asset-store";
|
||||
|
||||
@@ -21,7 +22,7 @@ type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const categoryLabels: Record<string, string> = { all: "全部素材", character: "角色", environment: "场景", wardrobe: "服饰", prop: "道具", weapon: "武器", style: "画风", other: "其他" };
|
||||
const categoryLabels: Record<string, string> = { all: "全部素材", ...ASSET_CATEGORY_LABELS };
|
||||
|
||||
export function AssetPickerModal({ open, multiple = true, onInsert, onClose }: Props) {
|
||||
const assets = useAssetStore((state) => state.assets);
|
||||
@@ -31,7 +32,7 @@ export function AssetPickerModal({ open, multiple = true, onInsert, onClose }: P
|
||||
...insertableAssets.map((asset) => ({
|
||||
id: asset.id,
|
||||
title: asset.title,
|
||||
category: asset.category || "other",
|
||||
category: normalizeAssetCategory(asset.category),
|
||||
kindLabel: asset.kind === "image" ? "图片" : asset.kind === "video" ? "视频" : asset.kind === "audio" ? "音频" : "文本",
|
||||
asset,
|
||||
searchText: (asset.tags || []).join(" "),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useEffect, useState, type CSSProperties, type ReactNode } from "react";
|
||||
import { ArrowLeft, Check, ChevronRight, Clipboard, CloudUpload, Copy, FolderOpen, FolderPlus, Image as ImageIcon, Layers3, Link2, Maximize2, PanelTop, Pencil, Plus, Redo2, Tags, Trash2, Undo2, Upload, UserRound } from "lucide-react";
|
||||
|
||||
import { CanvasCreateMenu, type CanvasCreateCommand } from "@/components/canvas/canvas-create-menu";
|
||||
import { ASSET_CATEGORY_OPTIONS } from "@/lib/asset-category";
|
||||
import { aceternityMotion } from "@/lib/aceternity-motion";
|
||||
import { SpotlightSurface } from "@/components/ui/aceternity/spotlight-surface";
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
@@ -15,15 +16,7 @@ import { CanvasNodeType, type CanvasNodeData, type CanvasNodeTypeId, type Canvas
|
||||
|
||||
type CanvasAssetCategory = NonNullable<NonNullable<CanvasNodeData["metadata"]>["assetCategory"]>;
|
||||
|
||||
const assetCategoryOptions: Array<{ value: CanvasAssetCategory; label: string }> = [
|
||||
{ value: "character", label: "角色" },
|
||||
{ value: "environment", label: "场景" },
|
||||
{ value: "wardrobe", label: "服饰" },
|
||||
{ value: "prop", label: "道具" },
|
||||
{ value: "weapon", label: "武器" },
|
||||
{ value: "style", label: "画风" },
|
||||
{ value: "other", label: "其他" },
|
||||
];
|
||||
const assetCategoryOptions: Array<{ value: CanvasAssetCategory; label: string }> = ASSET_CATEGORY_OPTIONS;
|
||||
|
||||
type CanvasNodeContextMenuProps = {
|
||||
menu: ContextMenuState;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { MenuProps } from "antd";
|
||||
import { ChevronDown, Ellipsis, Lock, Plus, Unlock } from "lucide-react";
|
||||
|
||||
import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style";
|
||||
import { ASSET_CATEGORY_OPTIONS } from "@/lib/asset-category";
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { resolveToolbarTools, type ToolContext, type ToolbarHandlers } from "@/lib/canvas/tool-registry";
|
||||
import { subscribeCanvasViewportPreview } from "@/lib/canvas/canvas-live-viewport";
|
||||
@@ -58,15 +59,7 @@ type CanvasNodeToolbarProps = {
|
||||
|
||||
type CanvasAssetCategory = NonNullable<NonNullable<CanvasNodeData["metadata"]>["assetCategory"]>;
|
||||
|
||||
const assetCategoryOptions: Array<{ value: CanvasAssetCategory; label: string }> = [
|
||||
{ value: "character", label: "角色" },
|
||||
{ value: "environment", label: "场景" },
|
||||
{ value: "wardrobe", label: "服饰" },
|
||||
{ value: "prop", label: "道具" },
|
||||
{ value: "weapon", label: "武器" },
|
||||
{ value: "style", label: "画风" },
|
||||
{ value: "other", label: "其他" },
|
||||
];
|
||||
const assetCategoryOptions: Array<{ value: CanvasAssetCategory; label: string }> = ASSET_CATEGORY_OPTIONS;
|
||||
|
||||
type ToolbarTool = {
|
||||
id: string;
|
||||
|
||||
@@ -4,12 +4,13 @@ import { AssetLibraryPickerModal, type AssetLibraryPickerItem } from "@/componen
|
||||
import { useExternalAssetSources } from "@/hooks/use-external-asset-sources";
|
||||
import { externalAssetToInsertPayload, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
|
||||
import { compileCharacterReferencePrompt } from "@/lib/canvas/canvas-character-reference";
|
||||
import { ASSET_CATEGORY_LABELS, normalizeAssetCategory } from "@/lib/asset-category";
|
||||
import { resourceFileUrl, resourceIdFromStorageKey } from "@/services/api/resources";
|
||||
import type { ProjectAsset, ProjectDetail } from "@/services/api/projects";
|
||||
import { getRemoteAsset } from "@/services/api/user-data";
|
||||
import { useAssetStore, type Asset } from "@/stores/use-asset-store";
|
||||
|
||||
const categoryLabels: Record<string, string> = { all: "全部资产", character: "角色", environment: "场景", wardrobe: "服饰", prop: "道具", weapon: "武器", style: "画风", other: "其他" };
|
||||
const categoryLabels: Record<string, string> = { all: "全部资产", ...ASSET_CATEGORY_LABELS };
|
||||
type ProjectPickerItem = { id: string; category: string; folderId?: string; project?: ProjectAsset; character?: ProjectAsset; media?: Asset };
|
||||
|
||||
export function CanvasProjectAssetModal({ open, detail, initialCategory = "all", initialFolderId = "all", onClose, onInsert, onInsertFolder }: { open: boolean; detail?: ProjectDetail; initialCategory?: string; initialFolderId?: string; onClose: () => void; onInsert: (payloads: InsertAssetPayload[]) => Promise<void> | void; onInsertFolder?: (folderId: string) => Promise<void> | void }) {
|
||||
@@ -20,13 +21,13 @@ export function CanvasProjectAssetModal({ open, detail, initialCategory = "all",
|
||||
const projectItems = (detail?.assets || []).flatMap((asset): ProjectPickerItem[] => {
|
||||
if (asset.category === "character" && asset.character) return [{ id: asset.id, category: "character", folderId: asset.folderId, project: asset, character: asset }];
|
||||
const media = mediaById.get(asset.id);
|
||||
return asset.mediaType === "model" || asset.mediaType === "entity" ? [] : [{ id: asset.id, category: asset.category || media?.category || "other", folderId: asset.folderId, project: asset, media }];
|
||||
return asset.mediaType === "model" || asset.mediaType === "entity" ? [] : [{ id: asset.id, category: normalizeAssetCategory(asset.category || media?.category), folderId: asset.folderId, project: asset, media }];
|
||||
});
|
||||
if (detail) return projectItems;
|
||||
// 自由画布未关联项目时回退到个人素材库。
|
||||
return mediaAssets
|
||||
.filter((asset) => asset.kind !== "model" && asset.kind !== "entity")
|
||||
.map((media): ProjectPickerItem => ({ id: media.id, category: media.category || "other", media }));
|
||||
.map((media): ProjectPickerItem => ({ id: media.id, category: normalizeAssetCategory(media.category), media }));
|
||||
}, [detail?.assets, mediaAssets]);
|
||||
const localPickerItems = useMemo<AssetLibraryPickerItem[]>(() => items.map((item) => {
|
||||
const character = item.character;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createPortal } from "react-dom";
|
||||
import { ArrowLeft, ChevronRight, FileText, Folder, Image as ImageIcon, Music2, Pencil, Search, UserRound, Video, Workflow } from "lucide-react";
|
||||
|
||||
import { canvasThemes } from "@/lib/canvas-theme";
|
||||
import { ASSET_CATEGORY_LABELS } from "@/lib/asset-category";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { buildAssetMentionReferences, canvasResourceMentionToken, type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
|
||||
import { useAssetStore, type AssetCategory } from "@/stores/use-asset-store";
|
||||
@@ -504,16 +505,6 @@ function createInlinePreview(reference: CanvasResourceReference) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const ASSET_CATEGORY_LABELS: Record<AssetCategory, string> = {
|
||||
character: "角色",
|
||||
environment: "场景",
|
||||
wardrobe: "服饰",
|
||||
prop: "道具",
|
||||
weapon: "武器",
|
||||
style: "画风",
|
||||
other: "其他",
|
||||
};
|
||||
|
||||
function MentionMenu({ anchor, connectedReferences, assetReferences, filteredReferences, query, cursorOffset, activeReferenceId, preferredWidth, onQueryChange, onClose, onSelect }: {
|
||||
anchor: HTMLElement;
|
||||
connectedReferences: CanvasResourceReference[];
|
||||
|
||||
Reference in New Issue
Block a user