'use client' import { logError as _ulogError } from '@/lib/logging/core' import { useState } from 'react' import { useTranslations } from 'next-intl' import { ART_STYLES } from '@/lib/constants' import { useAiDesignLocation, useCreateAssetHubLocation } from '@/lib/query/hooks' import { useImageGenerationCount } from '@/lib/image-generation/use-image-generation-count' import TaskStatusInline from '@/components/task/TaskStatusInline' import { resolveTaskPresentationState } from '@/lib/task/presentation' import { AppIcon } from '@/components/ui/icons' import type { LocationAvailableSlot } from '@/lib/location-available-slots' interface AddLocationModalProps { folderId: string | null onClose: () => void onSuccess: () => void } // 内联 SVG 图标 const XMarkIcon = ({ className }: { className?: string }) => ( ) const SparklesIcon = ({ className }: { className?: string }) => ( ) export function AddLocationModal({ folderId, onClose, onSuccess }: AddLocationModalProps) { const t = useTranslations('assetHub') // 表单字段 const [name, setName] = useState('') const [summary, setSummary] = useState('') const [aiInstruction, setAiInstruction] = useState('') const [artStyle, setArtStyle] = useState('american-comic') const [availableSlots, setAvailableSlots] = useState([]) const aiDesignMutation = useAiDesignLocation() const createLocationMutation = useCreateAssetHubLocation() const { count: locationGenerationCount } = useImageGenerationCount('location') const isSubmitting = createLocationMutation.isPending const isAiDesigning = aiDesignMutation.isPending const aiDesigningState = isAiDesigning ? resolveTaskPresentationState({ phase: 'processing', intent: 'generate', resource: 'image', hasOutput: false, }) : null const submittingState = isSubmitting ? resolveTaskPresentationState({ phase: 'processing', intent: 'generate', resource: 'image', hasOutput: false, }) : null // AI 设计描述 const handleAiDesign = async () => { if (!aiInstruction.trim()) return try { const data = await aiDesignMutation.mutateAsync(aiInstruction.trim()) setSummary(data.prompt || '') setAvailableSlots(Array.isArray(data.availableSlots) ? data.availableSlots : []) setAiInstruction('') } catch (error) { _ulogError('AI设计失败:', error) } } // 提交 const handleSubmit = async () => { if (!name.trim() || !summary.trim()) return try { await createLocationMutation.mutateAsync({ name: name.trim(), summary: summary.trim(), folderId, artStyle, count: locationGenerationCount, availableSlots, }) onSuccess() } catch (error) { _ulogError('创建场景失败:', error) } } return ( {/* 标题 */} {t('modal.newLocation')} {/* AI 设计区域 */} {t('modal.aiDesign')} setAiInstruction(e.target.value)} placeholder={t('modal.aiDesignLocationPlaceholder')} className="glass-input-base flex-1 px-3 py-2 text-sm" disabled={isAiDesigning} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleAiDesign() } }} /> {isAiDesigning ? ( ) : ( <> {t('modal.generate')} > )} {t('modal.aiDesignLocationTip')} {/* 场景名称 */} {t('modal.locationNameLabel')} setName(e.target.value)} placeholder={t('modal.locationNamePlaceholder')} className="glass-input-base w-full px-3 py-2 text-sm" /> {/* 风格选择 */} 画面风格 {ART_STYLES.map((style) => ( setArtStyle(style.value)} className={`glass-btn-base px-3 py-2 rounded-lg text-sm border flex items-center justify-start transition-all ${artStyle === style.value ? 'glass-btn-tone-info border-[var(--glass-stroke-focus)]' : 'glass-btn-soft border-[var(--glass-stroke-base)] text-[var(--glass-text-secondary)] hover:border-[var(--glass-stroke-strong)]' }`} > {style.label} ))} {/* 场景描述 */} {t('modal.locationSummaryLabel')} setSummary(e.target.value)} placeholder={t('modal.locationSummaryPlaceholder')} className="glass-textarea-base w-full h-40 px-3 py-2 text-sm resize-none" /> {/* 按钮区 */} {t('common.cancel')} {isSubmitting ? ( ) : ( {t('modal.addLocation')} )} ) }
{t('modal.aiDesignLocationTip')}