diff --git a/front/src/pages/aiStudio/assets/assetAdapters.ts b/front/src/pages/aiStudio/assets/assetAdapters.ts index 19de7cf..c61eda7 100644 --- a/front/src/pages/aiStudio/assets/assetAdapters.ts +++ b/front/src/pages/aiStudio/assets/assetAdapters.ts @@ -45,10 +45,21 @@ export const assetAdapters = { updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('actor', id, imageId, normalizeUpdateImagePayload(payload)) }, - createGenerationTask: async (id: string, imageId: number) => { + renderPrompt: async (id: string, imageId: number) => { + const res = await StudioImageTasksService.renderActorImagePromptApiV1StudioImageTasksActorsActorIdRenderPromptPost({ + actorId: id, + requestBody: { image_id: imageId, model_id: null } as any, + }) + const data = res.data + return { + prompt: (data?.prompt ?? '') as string, + images: (data?.images ?? []) as string[], + } + }, + createGenerationTask: async (id: string, imageId: number, payload: { prompt: string; images: string[] }) => { const res = await StudioImageTasksService.createActorImageGenerationTaskApiV1StudioImageTasksActorsActorIdImageTasksPost({ actorId: id, - requestBody: { image_id: imageId, model_id: null }, + requestBody: { image_id: imageId, model_id: null, prompt: payload.prompt, images: payload.images } as any, }) return res.data?.task_id ?? null }, @@ -76,11 +87,23 @@ export const assetAdapters = { updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('scene', id, imageId, normalizeUpdateImagePayload(payload)) }, - createGenerationTask: async (id: string, imageId: number) => { + renderPrompt: async (id: string, imageId: number) => { + const res = await StudioImageTasksService.renderAssetImagePromptApiV1StudioImageTasksAssetsAssetTypeAssetIdRenderPromptPost({ + assetType: 'scene', + assetId: id, + requestBody: { image_id: imageId, model_id: null } as any, + }) + const data = res.data + return { + prompt: (data?.prompt ?? '') as string, + images: (data?.images ?? []) as string[], + } + }, + createGenerationTask: async (id: string, imageId: number, payload: { prompt: string; images: string[] }) => { const res = await StudioImageTasksService.createAssetImageGenerationTaskApiV1StudioImageTasksAssetsAssetTypeAssetIdImageTasksPost({ assetType: 'scene', assetId: id, - requestBody: { image_id: imageId }, + requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) return res.data?.task_id ?? null }, @@ -108,11 +131,23 @@ export const assetAdapters = { updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('prop', id, imageId, normalizeUpdateImagePayload(payload)) }, - createGenerationTask: async (id: string, imageId: number) => { + renderPrompt: async (id: string, imageId: number) => { + const res = await StudioImageTasksService.renderAssetImagePromptApiV1StudioImageTasksAssetsAssetTypeAssetIdRenderPromptPost({ + assetType: 'prop', + assetId: id, + requestBody: { image_id: imageId, model_id: null } as any, + }) + const data = res.data + return { + prompt: (data?.prompt ?? '') as string, + images: (data?.images ?? []) as string[], + } + }, + createGenerationTask: async (id: string, imageId: number, payload: { prompt: string; images: string[] }) => { const res = await StudioImageTasksService.createAssetImageGenerationTaskApiV1StudioImageTasksAssetsAssetTypeAssetIdImageTasksPost({ assetType: 'prop', assetId: id, - requestBody: { image_id: imageId }, + requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) return res.data?.task_id ?? null }, @@ -140,11 +175,23 @@ export const assetAdapters = { updateImage: async (id: string, imageId: number, payload) => { await StudioEntitiesApi.updateImage('costume', id, imageId, normalizeUpdateImagePayload(payload)) }, - createGenerationTask: async (id: string, imageId: number) => { + renderPrompt: async (id: string, imageId: number) => { + const res = await StudioImageTasksService.renderAssetImagePromptApiV1StudioImageTasksAssetsAssetTypeAssetIdRenderPromptPost({ + assetType: 'costume', + assetId: id, + requestBody: { image_id: imageId, model_id: null } as any, + }) + const data = res.data + return { + prompt: (data?.prompt ?? '') as string, + images: (data?.images ?? []) as string[], + } + }, + createGenerationTask: async (id: string, imageId: number, payload: { prompt: string; images: string[] }) => { const res = await StudioImageTasksService.createAssetImageGenerationTaskApiV1StudioImageTasksAssetsAssetTypeAssetIdImageTasksPost({ assetType: 'costume', assetId: id, - requestBody: { image_id: imageId }, + requestBody: { image_id: imageId, prompt: payload.prompt, images: payload.images } as any, }) return res.data?.task_id ?? null }, diff --git a/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx b/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx index fd06266..cdd311b 100644 --- a/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx +++ b/front/src/pages/aiStudio/assets/components/AssetEditPageBase.tsx @@ -5,6 +5,7 @@ import { Col, Collapse, Empty, + Image, Input, InputNumber, Modal, @@ -80,7 +81,8 @@ export type AssetEditPageBaseProps Promise createImageSlot: (assetId: string, angle: AssetViewAngle) => Promise updateImage: (assetId: string, imageId: number, payload: { file_id: string; width?: number | null; height?: number | null; format?: string | null }) => Promise - createGenerationTask: (assetId: string, imageId: number) => Promise + renderPrompt: (assetId: string, imageId: number) => Promise<{ prompt: string; images: string[] }> + createGenerationTask: (assetId: string, imageId: number, payload: { prompt: string; images: string[] }) => Promise onNavigate: (to: string, replace?: boolean) => void } @@ -126,6 +128,7 @@ export function AssetEditPageBase) { @@ -141,6 +144,12 @@ export function AssetEditPageBase>({}) + const [promptPreviewOpen, setPromptPreviewOpen] = useState(false) + const [promptPreviewLoading, setPromptPreviewLoading] = useState(false) + const [promptPreviewImage, setPromptPreviewImage] = useState(null) + const [promptPreviewDraft, setPromptPreviewDraft] = useState('') + const [promptPreviewRefFileIds, setPromptPreviewRefFileIds] = useState([]) + const [historyOpen, setHistoryOpen] = useState(false) const [historyLoading, setHistoryLoading] = useState(false) const [historyCandidates, setHistoryCandidates] = useState[]>([]) @@ -255,12 +264,37 @@ export function AssetEditPageBase { + const openPromptPreview = async (image: TImage) => { if (!assetId) return - setGeneratingByImageId((prev) => ({ ...prev, [image.id]: true })) try { - const taskId = await createGenerationTask(assetId, image.id) + setPromptPreviewOpen(true) + setPromptPreviewLoading(true) + setPromptPreviewImage(image) + const res = await renderPrompt(assetId, image.id) + setPromptPreviewDraft(res.prompt ?? '') + setPromptPreviewRefFileIds(Array.isArray(res.images) ? res.images.filter(Boolean) : []) + } catch { + message.error('获取提示词失败') + } finally { + setPromptPreviewLoading(false) + } + } + + const confirmGenerateWithPrompt = async () => { + if (!assetId || !promptPreviewImage) return + const prompt = (promptPreviewDraft || '').trim() + if (!prompt) { + message.warning('请输入提示词') + return + } + + setGeneratingByImageId((prev) => ({ ...prev, [promptPreviewImage.id]: true })) + try { + const taskId = await createGenerationTask(assetId, promptPreviewImage.id, { + prompt, + images: promptPreviewRefFileIds, + }) if (!taskId) { message.error('生成任务创建失败:缺少任务 ID') return @@ -278,6 +312,8 @@ export function AssetEditPageBase ({ ...prev, [image.id]: false })) + setGeneratingByImageId((prev) => ({ ...prev, [promptPreviewImage.id]: false })) } } @@ -459,7 +495,7 @@ export function AssetEditPageBase slot.image && void handleGenerate(slot.image)} + onClick={() => slot.image && void openPromptPreview(slot.image)} > 生成 @@ -528,6 +564,59 @@ export function AssetEditPageBase )} + + { + setPromptPreviewOpen(false) + setPromptPreviewImage(null) + }} + okText="生成" + cancelText="取消" + confirmLoading={Boolean(promptPreviewImage && generatingByImageId[promptPreviewImage.id])} + onOk={() => void confirmGenerateWithPrompt()} + destroyOnClose + width={900} + > + {promptPreviewLoading ? ( +
+ +
+ ) : ( +
+
+
关联图片(参考图)
+ {promptPreviewRefFileIds.length === 0 ? ( +
暂无关联图片
+ ) : ( +
+ + {promptPreviewRefFileIds.map((fid) => ( + + ))} + +
+ )} +
+
+
提示词(可编辑)
+ setPromptPreviewDraft(e.target.value)} + placeholder="请输入提示词…" + /> +
+
+ )} +
) } diff --git a/front/src/pages/aiStudio/project/ProjectWorkbench/RoleDetailPage.tsx b/front/src/pages/aiStudio/project/ProjectWorkbench/RoleDetailPage.tsx index e383f4f..e0a79b2 100644 --- a/front/src/pages/aiStudio/project/ProjectWorkbench/RoleDetailPage.tsx +++ b/front/src/pages/aiStudio/project/ProjectWorkbench/RoleDetailPage.tsx @@ -51,11 +51,22 @@ export default function RoleDetailPage() { updateImage={async (id, imageId, payload) => { await StudioEntitiesApi.updateImage('character', id, imageId, normalizeUpdateImagePayload(payload)) }} - createGenerationTask={async (id, imageId) => { - const res = await StudioImageTasksService.createCharacterImageGenerationTaskApiV1StudioImageTasksCharactersCharacterIdImageTasksPost({ + renderPrompt={async (id, imageId) => { + const res = await StudioImageTasksService.renderCharacterImagePromptApiV1StudioImageTasksCharactersCharacterIdRenderPromptPost({ characterId: id, requestBody: { image_id: imageId, model_id: null }, }) + const data = res.data + return { + prompt: (data?.prompt ?? '') as string, + images: (data?.images ?? []) as string[], + } + }} + createGenerationTask={async (id, imageId, payload) => { + const res = await StudioImageTasksService.createCharacterImageGenerationTaskApiV1StudioImageTasksCharactersCharacterIdImageTasksPost({ + characterId: id, + requestBody: { image_id: imageId, model_id: null, prompt: payload.prompt, images: payload.images } as any, + }) return res.data?.task_id ?? null }} onNavigate={(to, replace) => navigate(to, replace ? { replace: true } : undefined)}