图片画布接入 3D 资源:新增 model3d 类别、3D 预览与 3D 生成入口
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 16s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 16s
Project CI / Native shell tests (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Failing after 7s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 16s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 16s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 16s
Project CI / Native shell tests (pull_request) Failing after 16s
Project CI / Frontend tests (pull_request) Failing after 7s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
- 后端资源投影按 asset_kind 分叉,新增 project_editor_client_media_src 与 EDITOR_MODEL3D_ASSET_KIND:model3d 只投影预览图,模型本体只留 objectKey,预览缺失才回落历史口径 - tripo3d 存储新增按字节魔数识别模型 content_type 与扩展名映射,不采信 provider 声明与下载响应头 - editor_project_storage 把 model3d 加入 EDITOR_CANVAS_ASSET_KINDS 白名单 - 前端 CanvasAssetKind 与 image-canvas-core 类型新增 model3d:画布卡片渲染预览图或 3D 占位,类型角标新增「3D模型」,覆盖资格只在它与空类别之间成立 - 新增 model3d-preview 子目录(Model3dViewerModal / Model3dViewerModel),Tailwind 内联、不新增 CSS,失败原因分超限 / 格式不可识别 / 无 WebGL2 三类展示 - 选中工具条新增只读 preview-model3d 能力位,仅 objectKey 为空时置灰;打开预览同时选中图层并暂停画布快捷键与舞台交互 - 查看器包源对象 format 改为可选,新增「声明 Content-Type → 字节魔数 → 地址扩展名」三级格式判定并导出支持格式清单 - 导出工作流新增 3d_models/ 目录,模型下载按真实响应头 MIME 定扩展名、对象键扩展名兜底 - 新增 3D 生成入口:底部工具栏浮动子选项与文生 / 图生 3D 面板,落 projectResource + canvasCompletion,复用占位框与任务侧栏 - 3D 定价只读实时定价查询,段缺失即入口不可提交;幂等键由前端铸造,用户重试必须换键 - 新增 ADR 0003 / 0004 与实施计划、里程碑文档;更新 Tripo 技术方案、后端架构、CONTEXT 术语、decision-log 与 pitfalls - .gitignore 忽略仓库根目录手工下载的 3D 样例模型
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type { ExternalGenerationJobStatusRecord } from '../../../../packages/shared/src/contracts/externalGeneration';
|
||||
import type {
|
||||
EditorCanvasGenerationCompletionInput,
|
||||
EditorProjectSnapshot,
|
||||
} from '../../../services/image-editor/editorProjectClient';
|
||||
import { submitModel3dGenerationRequest } from '../../../services/image-editor/editorProjectClient';
|
||||
import type {
|
||||
CanvasGenerationDialogState,
|
||||
GenerateDialogState,
|
||||
} from '../ImageCanvasEditorTypes';
|
||||
import { readEditorModel3dPricingConfig } from '../ImageCanvasGenerationModel';
|
||||
import { isModel3dGenerationMode } from './Model3dGenerationFormModel';
|
||||
import {
|
||||
buildModel3dSubmissionPlan,
|
||||
model3dQueueStateFromResponse,
|
||||
resolveModel3dGenerationErrorMessage,
|
||||
resolveModel3dResultTitle,
|
||||
} from './Model3dGenerationSubmission';
|
||||
|
||||
type ApplyQueuedGeneration = (
|
||||
response: { queueState?: ExternalGenerationJobStatusRecord | null },
|
||||
projectId: string | null | undefined,
|
||||
applyProjectSnapshot: ((project: EditorProjectSnapshot) => void) | undefined,
|
||||
onQueuedGenerationTask?: () => void,
|
||||
onWalletBalanceMayHaveChanged?: () => void,
|
||||
onGenerationWarning?: (message: string) => void,
|
||||
completionDialogId?: string | null,
|
||||
) => Promise<boolean>;
|
||||
|
||||
type UpdateCanvasGenerationDialogById = (
|
||||
dialogId: string,
|
||||
updater: (
|
||||
dialog: CanvasGenerationDialogState,
|
||||
) => CanvasGenerationDialogState | null,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* 3D 生成的提交编排:只负责校验、入队、轮询与收口,请求构造在
|
||||
* `Model3dGenerationSubmission`,画布与任务侧栏复用既有链路。
|
||||
*/
|
||||
export function useModel3dGenerationTask({
|
||||
projectId,
|
||||
updateCanvasGenerationDialogById,
|
||||
applyProjectSnapshot,
|
||||
getGeneratingDialogPlaceholder,
|
||||
applyQueuedGeneration,
|
||||
onQueuedGenerationTask,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
onGenerationWarning,
|
||||
}: {
|
||||
projectId?: string | null;
|
||||
updateCanvasGenerationDialogById: UpdateCanvasGenerationDialogById;
|
||||
applyProjectSnapshot?: (project: EditorProjectSnapshot) => void;
|
||||
getGeneratingDialogPlaceholder: (
|
||||
dialog: GenerateDialogState,
|
||||
) => EditorCanvasGenerationCompletionInput['placeholder'] | null | undefined;
|
||||
applyQueuedGeneration: ApplyQueuedGeneration;
|
||||
onQueuedGenerationTask?: () => void;
|
||||
onWalletBalanceMayHaveChanged?: () => void;
|
||||
onGenerationWarning?: (message: string) => void;
|
||||
}) {
|
||||
const submitModel3dGeneration = useCallback(
|
||||
async (dialog: GenerateDialogState) => {
|
||||
const dialogId = dialog.id;
|
||||
if (!dialogId || !isModel3dGenerationMode(dialog.mode)) {
|
||||
return;
|
||||
}
|
||||
const placeholder = getGeneratingDialogPlaceholder(dialog);
|
||||
const canvasCompletion =
|
||||
projectId && placeholder
|
||||
? {
|
||||
dialogId,
|
||||
title: resolveModel3dResultTitle(dialog),
|
||||
placeholder,
|
||||
}
|
||||
: null;
|
||||
const planResult = buildModel3dSubmissionPlan({
|
||||
dialog,
|
||||
projectId,
|
||||
canvasCompletion,
|
||||
pricing: readEditorModel3dPricingConfig(),
|
||||
});
|
||||
if (!planResult.ok) {
|
||||
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
|
||||
...currentDialog,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: planResult.message,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
|
||||
...currentDialog,
|
||||
status: 'generating',
|
||||
composerOpen: false,
|
||||
errorMessage: undefined,
|
||||
}));
|
||||
try {
|
||||
const response = await submitModel3dGenerationRequest({
|
||||
endpoint: planResult.plan.endpoint,
|
||||
body: planResult.plan.body,
|
||||
idempotencyKey: planResult.plan.requestKey,
|
||||
});
|
||||
const queueState = model3dQueueStateFromResponse(response);
|
||||
if (
|
||||
queueState &&
|
||||
(await applyQueuedGeneration(
|
||||
{ queueState },
|
||||
projectId,
|
||||
applyProjectSnapshot,
|
||||
onQueuedGenerationTask,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
onGenerationWarning,
|
||||
dialogId,
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
|
||||
...currentDialog,
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
errorMessage: undefined,
|
||||
}));
|
||||
} catch (error) {
|
||||
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
|
||||
...currentDialog,
|
||||
status: 'failed',
|
||||
composerOpen: true,
|
||||
errorMessage: resolveModel3dGenerationErrorMessage(error),
|
||||
}));
|
||||
}
|
||||
},
|
||||
[
|
||||
applyProjectSnapshot,
|
||||
applyQueuedGeneration,
|
||||
getGeneratingDialogPlaceholder,
|
||||
onGenerationWarning,
|
||||
onQueuedGenerationTask,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
projectId,
|
||||
updateCanvasGenerationDialogById,
|
||||
],
|
||||
);
|
||||
|
||||
return { submitModel3dGeneration };
|
||||
}
|
||||
Reference in New Issue
Block a user