feat: 完善敲木鱼结果页元信息补录
This commit is contained in:
@@ -3,7 +3,9 @@ import {
|
||||
Loader2,
|
||||
Mic,
|
||||
Pause,
|
||||
Plus,
|
||||
Send,
|
||||
X,
|
||||
Upload,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
@@ -32,44 +34,24 @@ type WoodenFishWorkspaceProps = {
|
||||
};
|
||||
|
||||
type WoodenFishWorkspaceFormState = {
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
themeTags: string;
|
||||
hitObjectPrompt: string;
|
||||
hitObjectReferenceImageSrc: string;
|
||||
hitSoundAsset: WoodenFishAudioAsset | null;
|
||||
floatingWords: string[];
|
||||
};
|
||||
|
||||
const DEFAULT_FLOATING_WORDS = [
|
||||
'幸运',
|
||||
'健康',
|
||||
'财富',
|
||||
'姻缘',
|
||||
'幸福',
|
||||
'事业',
|
||||
'成功',
|
||||
'功德',
|
||||
];
|
||||
const DEFAULT_WORK_TITLE = '今日敲木鱼';
|
||||
const DEFAULT_THEME_TAGS = ['敲木鱼', '解压'];
|
||||
const DEFAULT_FLOATING_WORDS = ['幸运'];
|
||||
const MAX_FLOATING_WORD_COUNT = 8;
|
||||
|
||||
const DEFAULT_FORM_STATE: WoodenFishWorkspaceFormState = {
|
||||
workTitle: '今日敲木鱼',
|
||||
workDescription: '',
|
||||
themeTags: '敲木鱼 解压',
|
||||
hitObjectPrompt: WOODEN_FISH_DEFAULT_HIT_OBJECT_PROMPT,
|
||||
hitObjectPrompt: '',
|
||||
hitObjectReferenceImageSrc: '',
|
||||
hitSoundAsset: null,
|
||||
floatingWords: DEFAULT_FLOATING_WORDS,
|
||||
};
|
||||
|
||||
function splitTags(value: string) {
|
||||
return value
|
||||
.split(/[,,、\s]+/u)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.slice(0, 6);
|
||||
}
|
||||
|
||||
function normalizeFloatingWords(words: string[]) {
|
||||
const seen = new Set<string>();
|
||||
const normalized: string[] = [];
|
||||
@@ -84,7 +66,7 @@ function normalizeFloatingWords(words: string[]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return normalized.length > 0 ? normalized : DEFAULT_FLOATING_WORDS;
|
||||
return normalized.length > 0 ? normalized : [...DEFAULT_FLOATING_WORDS];
|
||||
}
|
||||
|
||||
function readAudioFileAsAsset(file: File, source: 'uploaded' | 'recorded') {
|
||||
@@ -278,11 +260,42 @@ export function WoodenFishWorkspace({
|
||||
() => normalizeFloatingWords(formState.floatingWords),
|
||||
[formState.floatingWords],
|
||||
);
|
||||
const canSubmit = Boolean(
|
||||
formState.workTitle.trim() &&
|
||||
formState.hitObjectPrompt.trim() &&
|
||||
normalizedFloatingWords.length > 0,
|
||||
);
|
||||
const canSubmit = normalizedFloatingWords.length > 0;
|
||||
|
||||
const updateFloatingWord = (index: number, value: string) => {
|
||||
setFormState((current) => {
|
||||
const nextWords = [...current.floatingWords];
|
||||
nextWords[index] = value;
|
||||
return {
|
||||
...current,
|
||||
floatingWords: nextWords.slice(0, MAX_FLOATING_WORD_COUNT),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const addFloatingWord = () => {
|
||||
setFormState((current) => {
|
||||
if (current.floatingWords.length >= MAX_FLOATING_WORD_COUNT) {
|
||||
return current;
|
||||
}
|
||||
return {
|
||||
...current,
|
||||
floatingWords: [...current.floatingWords, ''],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const removeFloatingWord = (index: number) => {
|
||||
if (index <= 0) {
|
||||
return;
|
||||
}
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
floatingWords: current.floatingWords.filter(
|
||||
(_word, currentIndex) => currentIndex !== index,
|
||||
),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!canSubmit || isSubmitting || isBusy) {
|
||||
@@ -296,10 +309,11 @@ export function WoodenFishWorkspace({
|
||||
try {
|
||||
const payload: WoodenFishWorkspaceCreateRequest = {
|
||||
templateId: 'wooden-fish',
|
||||
workTitle: formState.workTitle.trim(),
|
||||
workDescription: formState.workDescription.trim(),
|
||||
themeTags: splitTags(formState.themeTags),
|
||||
hitObjectPrompt: formState.hitObjectPrompt.trim(),
|
||||
workTitle: DEFAULT_WORK_TITLE,
|
||||
workDescription: '',
|
||||
themeTags: DEFAULT_THEME_TAGS,
|
||||
hitObjectPrompt:
|
||||
formState.hitObjectPrompt.trim() || WOODEN_FISH_DEFAULT_HIT_OBJECT_PROMPT,
|
||||
hitObjectReferenceImageSrc:
|
||||
formState.hitObjectReferenceImageSrc.trim() || null,
|
||||
hitSoundPrompt: null,
|
||||
@@ -345,6 +359,7 @@ export function WoodenFishWorkspace({
|
||||
promptRows={4}
|
||||
aiRedraw={aiRedraw}
|
||||
promptReferenceImages={[]}
|
||||
showSubmitButton={false}
|
||||
submitLabel="生成"
|
||||
submitDisabled={!canSubmit || isSubmitting || isBusy}
|
||||
labels={{
|
||||
@@ -395,55 +410,6 @@ export function WoodenFishWorkspace({
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-col gap-3 overflow-y-auto pr-0 lg:pr-1">
|
||||
<section className="platform-subpanel rounded-[1.25rem] p-4">
|
||||
<label className="block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
作品标题
|
||||
</span>
|
||||
<input
|
||||
value={formState.workTitle}
|
||||
onChange={(event) =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
workTitle: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="mt-2 w-full rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 py-3 text-sm font-semibold text-[var(--platform-text-strong)] outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label className="mt-3 block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
作品简介
|
||||
</span>
|
||||
<textarea
|
||||
value={formState.workDescription}
|
||||
onChange={(event) =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
workDescription: event.target.value,
|
||||
}))
|
||||
}
|
||||
rows={2}
|
||||
className="mt-2 w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 py-3 text-sm leading-6 text-[var(--platform-text-strong)] outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label className="mt-3 block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
主题标签
|
||||
</span>
|
||||
<input
|
||||
value={formState.themeTags}
|
||||
onChange={(event) =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
themeTags: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="mt-2 w-full rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 py-3 text-sm font-semibold text-[var(--platform-text-strong)] outline-none"
|
||||
/>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<WoodenFishAudioInputPanel
|
||||
disabled={isBusy || isSubmitting}
|
||||
asset={formState.hitSoundAsset}
|
||||
@@ -460,24 +426,45 @@ export function WoodenFishWorkspace({
|
||||
<div className="mb-3 text-sm font-black text-[var(--platform-text-strong)]">
|
||||
功德有什么
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
{formState.floatingWords.map((word, index) => (
|
||||
<input
|
||||
key={index}
|
||||
value={word}
|
||||
maxLength={16}
|
||||
disabled={isBusy || isSubmitting}
|
||||
onChange={(event) => {
|
||||
const nextWords = [...formState.floatingWords];
|
||||
nextWords[index] = event.target.value;
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
floatingWords: nextWords.slice(0, 8),
|
||||
}));
|
||||
}}
|
||||
className="w-full rounded-[0.9rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 py-2.5 text-sm font-semibold text-[var(--platform-text-strong)] outline-none"
|
||||
/>
|
||||
<div key={index} className="relative">
|
||||
<input
|
||||
value={word}
|
||||
maxLength={16}
|
||||
disabled={isBusy || isSubmitting}
|
||||
aria-label={`功德词条 ${index + 1}`}
|
||||
onChange={(event) =>
|
||||
updateFloatingWord(index, event.target.value)
|
||||
}
|
||||
className="h-12 w-full rounded-[0.95rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-3 pr-10 text-sm font-semibold text-[var(--platform-text-strong)] outline-none"
|
||||
/>
|
||||
{index > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy || isSubmitting}
|
||||
onClick={() => removeFloatingWord(index)}
|
||||
className="absolute right-2 top-1/2 inline-flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full bg-white/92 text-[var(--platform-text-soft)] shadow-sm transition hover:text-[var(--platform-accent)] disabled:opacity-45"
|
||||
aria-label={`删除功德词条 ${index + 1}`}
|
||||
title="删除词条"
|
||||
>
|
||||
<X className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
{formState.floatingWords.length < MAX_FLOATING_WORD_COUNT ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy || isSubmitting}
|
||||
onClick={addFloatingWord}
|
||||
className="grid h-12 place-items-center rounded-[0.95rem] border border-dashed border-[var(--platform-subpanel-border)] bg-white/55 text-[var(--platform-text-soft)] transition hover:border-[var(--platform-accent)] hover:bg-white/78 hover:text-[var(--platform-accent)] disabled:opacity-45"
|
||||
aria-label="新增功德词条"
|
||||
title="新增词条"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user