import { useMutation, useQueryClient } from '@tanstack/react-query' import { queryKeys } from '../keys' import { apiFetch } from '@/lib/api-fetch' import { clearTaskTargetOverlay, upsertTaskTargetOverlay, } from '../task-target-overlay' import { invalidateQueryTemplates, requestJsonWithError, requestTaskResponseWithError, } from './mutation-shared' import { resolveTaskResponse } from '@/lib/task/client' export function useModifyProjectCharacterImage(projectId: string) { const queryClient = useQueryClient() const invalidateProjectAssetAndProjectData = () => invalidateQueryTemplates(queryClient, [ queryKeys.projectAssets.all(projectId), queryKeys.projectData(projectId), ]) return useMutation({ mutationFn: async (params: { characterId: string appearanceId: string imageIndex: number modifyPrompt: string extraImageUrls?: string[] }) => { const response = await requestTaskResponseWithError(`/api/assets/${params.characterId}/modify-render`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'project', kind: 'character', projectId, ...params, }), }, 'Failed to modify image') return await resolveTaskResponse(response) }, onMutate: ({ appearanceId }) => { upsertTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, intent: 'modify', }) }, onError: (_error, { appearanceId }) => { clearTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, }) }, onSettled: invalidateProjectAssetAndProjectData, }) } /** * 修改项目场景图片 */ export function useRegenerateCharacterGroup(projectId: string) { const queryClient = useQueryClient() const invalidateProjectAssets = () => invalidateQueryTemplates(queryClient, [queryKeys.projectAssets.all(projectId)]) return useMutation({ mutationFn: async ({ characterId, appearanceId, count, }: { characterId: string appearanceId: string count?: number }) => { return await requestJsonWithError(`/api/assets/${characterId}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'project', kind: 'character', projectId, appearanceId, count, }) }, 'Failed to regenerate group') }, onMutate: ({ appearanceId }) => { upsertTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, intent: 'regenerate', }) }, onError: (_error, { appearanceId }) => { clearTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, }) }, onSettled: invalidateProjectAssets, }) } /** * 重新生成单张角色图片 */ export function useRegenerateSingleCharacterImage(projectId: string) { const queryClient = useQueryClient() const invalidateProjectAssets = () => invalidateQueryTemplates(queryClient, [queryKeys.projectAssets.all(projectId)]) return useMutation({ mutationFn: async ({ characterId, appearanceId, imageIndex, }: { characterId: string appearanceId: string imageIndex: number }) => { return await requestJsonWithError(`/api/assets/${characterId}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'project', kind: 'character', projectId, appearanceId, imageIndex, }) }, 'Failed to regenerate image') }, onMutate: ({ appearanceId }) => { upsertTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, intent: 'regenerate', }) }, onError: (_error, { appearanceId }) => { clearTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: appearanceId, }) }, onSettled: invalidateProjectAssets, }) } /** * 重新生成场景组图片 */ export function useUpdateProjectAppearanceDescription(projectId: string) { const queryClient = useQueryClient() const invalidateProjectAssets = () => invalidateQueryTemplates(queryClient, [queryKeys.projectAssets.all(projectId)]) return useMutation({ mutationFn: async ({ characterId, appearanceId, description, descriptionIndex, }: { characterId: string appearanceId: string description: string descriptionIndex?: number }) => { return await requestJsonWithError(`/api/assets/${characterId}/variants/${appearanceId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'project', kind: 'character', projectId, description, descriptionIndex: typeof descriptionIndex === 'number' ? descriptionIndex : 0, }), }, 'Failed to update appearance description') }, onSuccess: invalidateProjectAssets, }) } export function useBatchGenerateCharacterImages(projectId: string) { const queryClient = useQueryClient() return useMutation({ mutationFn: async (items: Array<{ characterId: string; appearanceId: string }>) => { const results = await Promise.allSettled( items.map(item => apiFetch(`/api/assets/${item.characterId}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scope: 'project', kind: 'character', projectId, appearanceId: item.appearanceId }) }) ) ) return results }, onMutate: (items) => { for (const item of items) { upsertTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: item.appearanceId, intent: 'generate', }) } }, onError: (_error, items) => { for (const item of items) { clearTaskTargetOverlay(queryClient, { projectId, targetType: 'CharacterAppearance', targetId: item.appearanceId, }) } }, onSettled: () => { invalidateQueryTemplates(queryClient, [queryKeys.projectAssets.all(projectId)]) } }) }