Fix prop confirmation bug, add Wan 2.7 model, refine multiple UI details, improve prop generation quality and aspect ratio, remove text overlays from Asset Center created images, and optimize prop filtering logic

This commit is contained in:
saturn
2026-04-03 22:36:41 +08:00
parent 854b932e67
commit 78b93331b4
136 changed files with 3393 additions and 875 deletions

View File

@@ -3,15 +3,22 @@
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { AppIcon } from '@/components/ui/icons'
import { shouldShowError } from '@/lib/error-utils'
import TaskStatusInline from '@/components/task/TaskStatusInline'
import { resolveTaskPresentationState } from '@/lib/task/presentation'
import { useAssetActions } from '@/lib/query/hooks'
import {
useAiModifyProjectPropDescription,
useAiModifyPropDescription,
useAssetActions,
} from '@/lib/query/hooks'
import { AiModifyDescriptionField } from './AiModifyDescriptionField'
export interface PropEditModalProps {
mode: 'asset-hub' | 'project'
propId: string
propName: string
summary: string
description: string
variantId?: string
projectId?: string
onClose: () => void
@@ -23,6 +30,7 @@ export function PropEditModal({
propId,
propName,
summary,
description,
variantId,
projectId,
onClose,
@@ -36,7 +44,18 @@ export function PropEditModal({
})
const [editingName, setEditingName] = useState(propName)
const [editingSummary, setEditingSummary] = useState(summary)
const [editingDescription, setEditingDescription] = useState(description)
const [aiModifyInstruction, setAiModifyInstruction] = useState('')
const [isAiModifying, setIsAiModifying] = useState(false)
const [isSaving, setIsSaving] = useState(false)
const aiModifyingState = isAiModifying
? resolveTaskPresentationState({
phase: 'processing',
intent: 'modify',
resource: 'image',
hasOutput: true,
})
: null
const savingState = isSaving
? resolveTaskPresentationState({
phase: 'processing',
@@ -45,6 +64,13 @@ export function PropEditModal({
hasOutput: false,
})
: null
const aiModifyAssetHub = useAiModifyPropDescription()
const aiModifyProject = useAiModifyProjectPropDescription(projectId ?? '')
const getErrorMessage = (error: unknown, fallback: string) => {
if (error instanceof Error && error.message) return error.message
return fallback
}
const persist = async () => {
await actions.update(propId, {
@@ -53,14 +79,49 @@ export function PropEditModal({
})
if (variantId) {
await actions.updateVariant(propId, variantId, {
description: editingSummary.trim(),
description: editingDescription.trim(),
})
}
onRefresh?.()
}
const handleAiModify = async () => {
if (!aiModifyInstruction.trim()) return false
try {
setIsAiModifying(true)
const data = mode === 'asset-hub'
? await aiModifyAssetHub.mutateAsync({
propId,
variantId,
currentDescription: editingDescription,
modifyInstruction: aiModifyInstruction,
})
: await aiModifyProject.mutateAsync({
propId,
variantId,
currentDescription: editingDescription,
modifyInstruction: aiModifyInstruction,
})
if (data?.modifiedDescription) {
setEditingDescription(data.modifiedDescription)
setAiModifyInstruction('')
return true
}
return false
} catch (error: unknown) {
if (shouldShowError(error)) {
alert(`${t('modal.modifyFailed')}: ${getErrorMessage(error, t('errors.failed'))}`)
}
return false
} finally {
setIsAiModifying(false)
}
}
const handleSaveOnly = async () => {
if (!editingName.trim() || !editingSummary.trim()) return
if (!editingName.trim() || !editingSummary.trim() || !editingDescription.trim()) return
try {
setIsSaving(true)
await persist()
@@ -71,7 +132,7 @@ export function PropEditModal({
}
const handleSaveAndGenerate = async () => {
if (!editingName.trim() || !editingSummary.trim()) return
if (!editingName.trim() || !editingSummary.trim() || !editingDescription.trim()) return
try {
setIsSaving(true)
await persist()
@@ -118,10 +179,25 @@ export function PropEditModal({
<textarea
value={editingSummary}
onChange={(event) => setEditingSummary(event.target.value)}
className="glass-textarea-base w-full h-48 px-3 py-2 resize-none"
className="glass-textarea-base h-28 w-full px-3 py-2 resize-none"
placeholder={t('prop.summaryPlaceholder')}
/>
</div>
<AiModifyDescriptionField
label={t('prop.description')}
description={editingDescription}
onDescriptionChange={setEditingDescription}
descriptionPlaceholder={t('prop.descriptionPlaceholder')}
aiInstruction={aiModifyInstruction}
onAiInstructionChange={setAiModifyInstruction}
aiInstructionPlaceholder={t('modal.modifyPlaceholderProp')}
onAiModify={handleAiModify}
isAiModifying={isAiModifying}
aiModifyingState={aiModifyingState}
actionLabel={t('modal.modifyDescription')}
cancelLabel={t('common.cancel')}
/>
</div>
<div className="flex gap-3 justify-end p-4 border-t border-[var(--glass-stroke-base)] bg-[var(--glass-bg-surface-strong)] rounded-b-lg flex-shrink-0">
@@ -134,7 +210,7 @@ export function PropEditModal({
</button>
<button
onClick={() => void handleSaveOnly()}
disabled={isSaving || !editingName.trim() || !editingSummary.trim()}
disabled={isSaving || !editingName.trim() || !editingSummary.trim() || !editingDescription.trim()}
className="glass-btn-base glass-btn-tone-info px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isSaving ? (
@@ -145,7 +221,7 @@ export function PropEditModal({
</button>
<button
onClick={() => void handleSaveAndGenerate()}
disabled={isSaving || !editingName.trim() || !editingSummary.trim()}
disabled={isSaving || !editingName.trim() || !editingSummary.trim() || !editingDescription.trim()}
className="glass-btn-base glass-btn-primary px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
>
{t('modal.saveAndGenerate')}