feat(*): 创作体验 - 支持资源技能引用并统一生成反馈与画布列表展示
This commit is contained in:
@@ -4,6 +4,7 @@ import { TaskDetailItem, taskStatusText } from "./canvas-project-feedback";
|
||||
import type { GenerationTask, TaskLog } from "@/services/api/task-center";
|
||||
import { CanvasNodeType, type CanvasNodeData } from "@/types/canvas";
|
||||
import { VideoPlayer } from "@/components/video-player";
|
||||
import { modelDisplayName, useEffectiveConfig } from "@/stores/use-config-store";
|
||||
|
||||
type CanvasProjectStatusDialogsProps = {
|
||||
theme: { node: { stroke: string; panel: string; muted: string; fill: string } };
|
||||
@@ -21,6 +22,7 @@ type CanvasProjectStatusDialogsProps = {
|
||||
};
|
||||
|
||||
export function CanvasProjectStatusDialogs({ theme, task, taskLogs, taskLoading, superResolveNode, previewNode, clearConfirmOpen, onCloseTask, onCloseSuperResolve, onClosePreview, onCancelClear, onConfirmClear }: CanvasProjectStatusDialogsProps) {
|
||||
const config = useEffectiveConfig();
|
||||
return (
|
||||
<>
|
||||
<Modal title="任务详情" open={Boolean(task)} footer={null} width={760} onCancel={onCloseTask}>
|
||||
@@ -29,7 +31,7 @@ export function CanvasProjectStatusDialogs({ theme, task, taskLogs, taskLoading,
|
||||
<div className="grid grid-cols-2 gap-3 rounded-lg border p-3" style={{ borderColor: theme.node.stroke, background: theme.node.panel }}>
|
||||
<TaskDetailItem label="当前阶段" value={task.stage || taskStatusText(task.status)} />
|
||||
<TaskDetailItem label="进度" value={`${task.progress ?? 0}%`} />
|
||||
<TaskDetailItem label="模型" value={task.model || "默认模型"} />
|
||||
<TaskDetailItem label="模型" value={task.model ? modelDisplayName(config, task.model) : "默认模型"} />
|
||||
<TaskDetailItem label="任务 ID" value={task.id} />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -154,11 +154,12 @@ export default function CanvasPage() {
|
||||
/>
|
||||
|
||||
<ListToolbar
|
||||
className="canvas-library-toolbar"
|
||||
active={Boolean(keyword || projectFilter !== "all" || sort !== "updated")}
|
||||
trailing={<span className="text-xs tabular-nums text-foreground/42">显示 {visibleProjects.length} / {filteredProjects.length}</span>}
|
||||
onReset={() => { setKeyword(""); setProjectFilter("all"); setSort("updated"); setPage(1); }}
|
||||
>
|
||||
<div className="min-w-[220px] flex-1 sm:max-w-[380px]">
|
||||
<div className="canvas-library-search min-w-[220px] flex-[1_1_420px]">
|
||||
<Input allowClear className="w-full" prefix={<Search className="size-4 text-foreground/40" />} value={keyword} placeholder="搜索画布" aria-label="搜索画布" onChange={(event) => { setKeyword(event.target.value); setPage(1); }} />
|
||||
</div>
|
||||
<Select aria-label="按所属项目筛选" className="w-[168px]" value={projectFilter} onChange={(value) => { setProjectFilter(value); setPage(1); }} options={[{ label: "全部项目", value: "all" }, { label: "自由画布", value: "independent" }, ...(projectQuery.data?.projects || []).map(({ project }) => ({ label: project.name, value: project.id }))]} />
|
||||
@@ -178,7 +179,7 @@ export default function CanvasPage() {
|
||||
{!hydrated ? (
|
||||
<WorkspaceLoadingState label="正在恢复画布" detail="读取本地缓存与账号同步状态" />
|
||||
) : visibleProjects.length ? (
|
||||
<CollectionGrid className="sm:grid-cols-[repeat(auto-fill,minmax(272px,320px))] sm:justify-start">
|
||||
<CollectionGrid className="canvas-library-grid">
|
||||
{visibleProjects.map((project) => (
|
||||
<CanvasProjectCard key={project.id} project={project} projectName={project.projectId ? projectNames.get(project.projectId) || "未同步项目" : undefined} />
|
||||
))}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buildGenerationConfig, isGenerationCanceled, supportsVideoReferenceAudi
|
||||
import { isGenerationTaskCapacityError } from "@/lib/canvas/canvas-generation-batch";
|
||||
import { buildPortraitTexturePrompt } from "@/lib/canvas/canvas-portrait-texture";
|
||||
import { expandSkillMentions } from "@/lib/canvas/canvas-skill-mentions";
|
||||
import { generationFailureMetadata } from "@/lib/generation-error";
|
||||
import { generationErrorMessage, generationFailureMetadata } from "@/lib/generation-error";
|
||||
import { navigateToSettings } from "@/lib/settings-navigation";
|
||||
import type { Skill } from "@/services/api/skills";
|
||||
import type { GenerationTask } from "@/services/api/task-center";
|
||||
@@ -132,7 +132,7 @@ export function useCanvasGenerationExecutor({
|
||||
mode === "video" && supportsVideoReferenceAudio(generationConfig),
|
||||
);
|
||||
} catch (error) {
|
||||
const errorDetails = error instanceof Error ? error.message : "生成任务准备失败";
|
||||
const errorDetails = generationErrorMessage(error);
|
||||
if (isPreparingEmptyImage) {
|
||||
setNodes((current) => current.map((node) => (node.id === nodeId ? { ...node, metadata: { ...node.metadata, status: controller.signal.aborted ? NODE_STATUS_IDLE : NODE_STATUS_ERROR, taskStage: undefined, taskProgress: undefined, taskCreatedAt: undefined, errorDetails: controller.signal.aborted ? undefined : errorDetails } } : node)));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { compositeEmotionImage, emotionGenerationSize } from "@/lib/canvas/canva
|
||||
import { DEFAULT_PORTRAIT_TEXTURE_SETTINGS } from "@/lib/canvas/canvas-portrait-texture";
|
||||
import { captureVideoLastFrame } from "@/lib/canvas/canvas-video-frame";
|
||||
import { mergeVideos, type MergeVideoProgress } from "@/lib/canvas/canvas-video-merge";
|
||||
import { generationErrorMessage } from "@/lib/generation-error";
|
||||
import { navigateToSettings } from "@/lib/settings-navigation";
|
||||
import { storeGeneratedVideo } from "@/services/api/video";
|
||||
import { getMediaBlob } from "@/services/file-storage";
|
||||
@@ -349,7 +350,7 @@ export function useCanvasMediaTools({
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...item.metadata, ...imageMetadata(uploaded), prompt, ...generationMetadata } } : item));
|
||||
} catch (error) {
|
||||
if (isGenerationCanceled(error)) return;
|
||||
const details = error instanceof Error ? error.message : "局部修改失败";
|
||||
const details = generationErrorMessage(error);
|
||||
message.error(details);
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails: details } } : item));
|
||||
} finally {
|
||||
@@ -402,7 +403,7 @@ export function useCanvasMediaTools({
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...item.metadata, ...imageMetadata(uploaded), prompt, ...generationMetadata } } : item));
|
||||
} catch (error) {
|
||||
if (isGenerationCanceled(error)) return;
|
||||
const details = error instanceof Error ? error.message : "生成失败";
|
||||
const details = generationErrorMessage(error);
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails: details } } : item));
|
||||
} finally {
|
||||
finishGenerationRequest(childId, controller);
|
||||
@@ -455,7 +456,7 @@ export function useCanvasMediaTools({
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, width: size.width, height: size.height, metadata: { ...item.metadata, ...imageMetadata(uploaded), prompt: payload.prompt, ...generationMetadata, emotionEdit } } : item));
|
||||
} catch (error) {
|
||||
if (isGenerationCanceled(error)) return;
|
||||
const details = error instanceof Error ? error.message : "表情生成失败";
|
||||
const details = generationErrorMessage(error);
|
||||
message.error(details);
|
||||
setNodes((current) => current.map((item) => item.id === childId ? { ...item, metadata: { ...item.metadata, status: NODE_STATUS_ERROR, errorDetails: details } } : item));
|
||||
} finally { finishGenerationRequest(childId, controller); setRunningNodeId(null); }
|
||||
|
||||
@@ -17,9 +17,10 @@ import {
|
||||
storyboardRowsFromTask,
|
||||
} from "@/lib/canvas/canvas-project-domain";
|
||||
import { buildNodeMentionReferences } from "@/lib/canvas/canvas-resource-references";
|
||||
import { generationErrorMessage } from "@/lib/generation-error";
|
||||
import { navigateToSettings } from "@/lib/settings-navigation";
|
||||
import { createGenerationTask, waitForGenerationTask } from "@/services/api/task-center";
|
||||
import { modelOptionName, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
|
||||
import { modelDisplayName, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
|
||||
import {
|
||||
CanvasNodeType,
|
||||
type CanvasConnection,
|
||||
@@ -60,14 +61,14 @@ export function useCanvasStoryboard({
|
||||
if (!count) return resolve(false);
|
||||
modal.confirm({
|
||||
title: `确认提交 ${count} 个${taskLabel}任务`,
|
||||
content: `任务数:${count};模型:${modelOptionName(model) || model}。当前没有可用价格数据,将提交 ${count} 个外部模型任务。`,
|
||||
content: `任务数:${count};模型:${modelDisplayName(effectiveConfig, model)}。当前没有可用价格数据,将提交 ${count} 个外部模型任务。`,
|
||||
okText: "确认生成",
|
||||
cancelText: "取消",
|
||||
centered: true,
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
});
|
||||
}), [modal]);
|
||||
}), [effectiveConfig, modal]);
|
||||
|
||||
const updateScriptRows = useCallback((nodeId: string, updater: (rows: StoryboardRow[]) => StoryboardRow[]) => {
|
||||
setNodes((current) => current.map((node) => node.id === nodeId ? {
|
||||
@@ -159,7 +160,7 @@ export function useCanvasStoryboard({
|
||||
message.success(`已生成 ${result.rows.length} 个镜头`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
const details = error instanceof Error ? error.message : "脚本生成失败";
|
||||
const details = generationErrorMessage(error);
|
||||
setNodes((current) => current.map((node) => node.id === nodeId ? { ...node, metadata: { ...node.metadata, status: NODE_STATUS_ERROR, errorDetails: details } } : node));
|
||||
message.error(details);
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user