1
This commit is contained in:
@@ -83,21 +83,18 @@ test('puzzle workspace submits the work form instead of agent chat', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('作品名称'), {
|
||||
target: { value: '暖灯猫街' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('作品描述'), {
|
||||
target: { value: '一套雨夜猫街主题拼图。' },
|
||||
});
|
||||
expect(screen.queryByLabelText('作品名称')).toBeNull();
|
||||
expect(screen.queryByLabelText('作品描述')).toBeNull();
|
||||
expect(screen.getByText('创建拼图')).toBeTruthy();
|
||||
expect(screen.queryByText('try')).toBeNull();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /生成草稿/u }));
|
||||
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||||
seedText: '暖灯猫街',
|
||||
workTitle: '暖灯猫街',
|
||||
workDescription: '一套雨夜猫街主题拼图。',
|
||||
seedText: '一只猫在雨夜灯牌下回头。',
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: null,
|
||||
imageModel: 'gpt-image-2',
|
||||
@@ -107,6 +104,35 @@ test('puzzle workspace submits the work form instead of agent chat', () => {
|
||||
expect(screen.queryByText('旧会话消息不再渲染为聊天入口。')).toBeNull();
|
||||
});
|
||||
|
||||
test('puzzle workspace applies a creation template prompt', () => {
|
||||
const onCreateFromForm = vi.fn();
|
||||
|
||||
render(
|
||||
<PuzzleAgentWorkspace
|
||||
session={null}
|
||||
onBack={() => {}}
|
||||
onSubmitMessage={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
onCreateFromForm={onCreateFromForm}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '宠物可爱拼图模板' }));
|
||||
|
||||
expect((screen.getByLabelText('画面描述') as HTMLTextAreaElement).value).toBe(
|
||||
'一只可爱的橘猫趴在阳光窗台上,旁边有绿植、毛线球和小毯子,猫的表情清楚,画面温柔干净,适合萌宠拼图分享。',
|
||||
);
|
||||
expect(screen.getAllByText('宠物可爱拼图').length).toBeGreaterThan(1);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /生成草稿/u }));
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pictureDescription:
|
||||
'一只可爱的橘猫趴在阳光窗台上,旁边有绿植、毛线球和小毯子,猫的表情清楚,画面温柔干净,适合萌宠拼图分享。',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('puzzle workspace falls back to compile action for restored sessions', () => {
|
||||
const onExecuteAction = vi.fn();
|
||||
const onCreateFromForm = vi.fn();
|
||||
@@ -126,10 +152,8 @@ test('puzzle workspace falls back to compile action for restored sessions', () =
|
||||
expect(onCreateFromForm).not.toHaveBeenCalled();
|
||||
expect(onExecuteAction).toHaveBeenCalledWith({
|
||||
action: 'compile_puzzle_draft',
|
||||
promptText: '潮雾中的灯塔与断桥',
|
||||
workTitle: '雾港遗迹拼图',
|
||||
workDescription: '雾港遗迹拼图',
|
||||
pictureDescription: '潮雾中的灯塔与断桥',
|
||||
promptText: '潮雾中的灯塔与断桥',
|
||||
referenceImageSrc: null,
|
||||
imageModel: 'gpt-image-2',
|
||||
candidateCount: 1,
|
||||
@@ -149,12 +173,6 @@ test('puzzle workspace switches the image model from the description box', () =>
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('作品名称'), {
|
||||
target: { value: '暖灯猫街' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('作品描述'), {
|
||||
target: { value: '一套雨夜猫街主题拼图。' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
@@ -175,8 +193,7 @@ test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||||
const onAutoSaveForm = vi.fn();
|
||||
const formDraftSession: PuzzleAgentSessionSnapshot = {
|
||||
...baseSession,
|
||||
seedText:
|
||||
'作品名称:旧街拼图\n作品描述:旧街雨夜的拼图草稿。\n画面描述:旧街灯牌下的猫。',
|
||||
seedText: '画面描述:旧街灯牌下的猫。',
|
||||
draft: {
|
||||
workTitle: '旧街拼图',
|
||||
workDescription: '旧街雨夜的拼图草稿。',
|
||||
@@ -204,8 +221,6 @@ test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||||
},
|
||||
],
|
||||
formDraft: {
|
||||
workTitle: '旧街拼图',
|
||||
workDescription: '旧街雨夜的拼图草稿。',
|
||||
pictureDescription: '旧街灯牌下的猫。',
|
||||
},
|
||||
},
|
||||
@@ -221,12 +236,6 @@ test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect((screen.getByLabelText('作品名称') as HTMLInputElement).value).toBe(
|
||||
'旧街拼图',
|
||||
);
|
||||
expect((screen.getByLabelText('作品描述') as HTMLTextAreaElement).value).toBe(
|
||||
'旧街雨夜的拼图草稿。',
|
||||
);
|
||||
expect((screen.getByLabelText('画面描述') as HTMLTextAreaElement).value).toBe(
|
||||
'旧街灯牌下的猫。',
|
||||
);
|
||||
@@ -240,9 +249,7 @@ test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||||
});
|
||||
|
||||
expect(onAutoSaveForm).toHaveBeenCalledWith({
|
||||
seedText: '旧街拼图',
|
||||
workTitle: '旧街拼图',
|
||||
workDescription: '旧街雨夜的拼图草稿。',
|
||||
seedText: '旧街灯牌下的猫和发光雨伞。',
|
||||
pictureDescription: '旧街灯牌下的猫和发光雨伞。',
|
||||
referenceImageSrc: null,
|
||||
imageModel: 'gpt-image-2',
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
SendPuzzleAgentMessageRequest,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
|
||||
import { PUZZLE_CREATION_TEMPLATES } from './puzzleCreationTemplates';
|
||||
import {
|
||||
normalizePuzzleImageModel,
|
||||
PUZZLE_IMAGE_MODEL_GPT_IMAGE_2,
|
||||
@@ -28,8 +29,6 @@ type PuzzleAgentWorkspaceProps = {
|
||||
};
|
||||
|
||||
type PuzzleFormState = {
|
||||
workTitle: string;
|
||||
workDescription: string;
|
||||
pictureDescription: string;
|
||||
referenceImageSrc: string;
|
||||
referenceImageLabel: string;
|
||||
@@ -37,8 +36,6 @@ type PuzzleFormState = {
|
||||
};
|
||||
|
||||
const EMPTY_FORM_STATE: PuzzleFormState = {
|
||||
workTitle: '',
|
||||
workDescription: '',
|
||||
pictureDescription: '',
|
||||
referenceImageSrc: '',
|
||||
referenceImageLabel: '',
|
||||
@@ -52,8 +49,6 @@ function resolveInitialFormState(
|
||||
const formDraft = session?.draft?.formDraft;
|
||||
if (formDraft) {
|
||||
return {
|
||||
workTitle: formDraft.workTitle ?? '',
|
||||
workDescription: formDraft.workDescription ?? '',
|
||||
pictureDescription: formDraft.pictureDescription ?? '',
|
||||
referenceImageSrc: initialFormPayload?.referenceImageSrc ?? '',
|
||||
referenceImageLabel: initialFormPayload?.referenceImageSrc
|
||||
@@ -65,10 +60,10 @@ function resolveInitialFormState(
|
||||
|
||||
if (initialFormPayload) {
|
||||
return {
|
||||
workTitle:
|
||||
initialFormPayload.workTitle ?? initialFormPayload.seedText ?? '',
|
||||
workDescription: initialFormPayload.workDescription ?? '',
|
||||
pictureDescription: initialFormPayload.pictureDescription ?? '',
|
||||
pictureDescription:
|
||||
initialFormPayload.pictureDescription ??
|
||||
initialFormPayload.seedText ??
|
||||
'',
|
||||
referenceImageSrc: initialFormPayload.referenceImageSrc ?? '',
|
||||
referenceImageLabel: initialFormPayload.referenceImageSrc
|
||||
? '已选择参考图'
|
||||
@@ -82,19 +77,12 @@ function resolveInitialFormState(
|
||||
}
|
||||
|
||||
return {
|
||||
workTitle:
|
||||
session.draft?.workTitle ||
|
||||
session.draft?.levelName ||
|
||||
session.seedText ||
|
||||
session.anchorPack.themePromise.value ||
|
||||
session.messages.find((message) => message.role === 'user')?.text ||
|
||||
'',
|
||||
workDescription:
|
||||
session.draft?.workDescription ||
|
||||
session.anchorPack.themePromise.value ||
|
||||
'',
|
||||
pictureDescription:
|
||||
session.draft?.summary || session.anchorPack.visualSubject.value || '',
|
||||
session.draft?.formDraft?.pictureDescription ||
|
||||
session.draft?.levels?.[0]?.pictureDescription ||
|
||||
session.anchorPack.visualSubject.value ||
|
||||
session.seedText ||
|
||||
'',
|
||||
referenceImageSrc: '',
|
||||
referenceImageLabel: '',
|
||||
imageModel: PUZZLE_IMAGE_MODEL_GPT_IMAGE_2,
|
||||
@@ -121,6 +109,9 @@ export function PuzzleAgentWorkspace({
|
||||
const [referenceImageError, setReferenceImageError] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState(
|
||||
PUZZLE_CREATION_TEMPLATES[0]?.id ?? '',
|
||||
);
|
||||
const previousSessionIdRef = useRef<string | null>(
|
||||
session?.sessionId ?? null,
|
||||
);
|
||||
@@ -148,18 +139,13 @@ export function PuzzleAgentWorkspace({
|
||||
appliedInitialFormKeyRef.current = nextInitialFormKey;
|
||||
setFormState(resolveInitialFormState(session, initialFormPayload));
|
||||
setReferenceImageError(null);
|
||||
}, [initialFormPayload, session?.sessionId]);
|
||||
}, [initialFormPayload, session]);
|
||||
|
||||
const workTitle = formState.workTitle.trim();
|
||||
const workDescription = formState.workDescription.trim();
|
||||
const pictureDescription = formState.pictureDescription.trim();
|
||||
const canSubmit =
|
||||
Boolean(workTitle && workDescription && pictureDescription) && !isBusy;
|
||||
const canSubmit = Boolean(pictureDescription) && !isBusy;
|
||||
const autosavePayload = useMemo(
|
||||
() => ({
|
||||
seedText: workTitle,
|
||||
workTitle,
|
||||
workDescription,
|
||||
seedText: pictureDescription,
|
||||
pictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
imageModel: formState.imageModel,
|
||||
@@ -168,13 +154,9 @@ export function PuzzleAgentWorkspace({
|
||||
formState.referenceImageSrc,
|
||||
formState.imageModel,
|
||||
pictureDescription,
|
||||
workDescription,
|
||||
workTitle,
|
||||
],
|
||||
);
|
||||
const autosaveSignature = JSON.stringify([
|
||||
autosavePayload.workTitle,
|
||||
autosavePayload.workDescription,
|
||||
autosavePayload.pictureDescription,
|
||||
autosavePayload.imageModel,
|
||||
]);
|
||||
@@ -189,7 +171,7 @@ export function PuzzleAgentWorkspace({
|
||||
|
||||
autosaveSessionIdRef.current = currentSessionId;
|
||||
lastAutosaveSignatureRef.current = autosaveSignature;
|
||||
}, [autosaveSignature, session?.sessionId]);
|
||||
}, [autosaveSignature, session]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -214,7 +196,7 @@ export function PuzzleAgentWorkspace({
|
||||
onAutoSaveForm,
|
||||
session?.draft?.formDraft,
|
||||
session?.stage,
|
||||
session?.sessionId,
|
||||
session,
|
||||
]);
|
||||
|
||||
const handleReferenceImageChange = async (
|
||||
@@ -243,15 +225,28 @@ export function PuzzleAgentWorkspace({
|
||||
}
|
||||
};
|
||||
|
||||
const applyTemplatePrompt = (templateId: string) => {
|
||||
const template = PUZZLE_CREATION_TEMPLATES.find(
|
||||
(item) => item.id === templateId,
|
||||
);
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedTemplateId(template.id);
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
pictureDescription: template.prompt,
|
||||
}));
|
||||
};
|
||||
|
||||
const submitForm = () => {
|
||||
if (!canSubmit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
seedText: workTitle,
|
||||
workTitle,
|
||||
workDescription,
|
||||
seedText: pictureDescription,
|
||||
pictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
imageModel: formState.imageModel,
|
||||
@@ -265,8 +260,6 @@ export function PuzzleAgentWorkspace({
|
||||
onExecuteAction({
|
||||
action: 'compile_puzzle_draft',
|
||||
promptText: pictureDescription,
|
||||
workTitle,
|
||||
workDescription,
|
||||
pictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
imageModel: formState.imageModel,
|
||||
@@ -275,7 +268,7 @@ export function PuzzleAgentWorkspace({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="platform-remap-surface mx-auto flex h-full min-h-0 w-full max-w-4xl flex-col">
|
||||
<div className="platform-remap-surface mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col">
|
||||
<div className="mb-4 flex items-center justify-between gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -291,61 +284,107 @@ export function PuzzleAgentWorkspace({
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
|
||||
<section className="platform-subpanel rounded-[1.5rem] p-4 sm:p-5">
|
||||
<div className="space-y-5">
|
||||
<label className="block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
作品名称
|
||||
<div className="mb-5">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h1 className="m-0 text-5xl font-black leading-none tracking-normal text-[var(--platform-text-strong)] sm:text-7xl">
|
||||
创建拼图
|
||||
</h1>
|
||||
<span className="rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-[11px] font-black text-emerald-700">
|
||||
BETA
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="platform-subpanel overflow-hidden rounded-[1.5rem] p-4 sm:p-5">
|
||||
<div className="rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/70 p-3 sm:p-4">
|
||||
<div className="mb-3 flex min-h-6 items-center justify-between gap-3">
|
||||
<span className="text-xs font-black text-[var(--platform-text-soft)]">
|
||||
Template
|
||||
</span>
|
||||
<span className="max-w-[11rem] truncate text-xs font-black text-[var(--platform-text-strong)]">
|
||||
{PUZZLE_CREATION_TEMPLATES.find(
|
||||
(item) => item.id === selectedTemplateId,
|
||||
)?.title ?? PUZZLE_CREATION_TEMPLATES[0]?.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex gap-3 overflow-x-auto pb-2"
|
||||
aria-label="拼图创作模板"
|
||||
>
|
||||
{PUZZLE_CREATION_TEMPLATES.map((template) => {
|
||||
const selected = template.id === selectedTemplateId;
|
||||
return (
|
||||
<button
|
||||
key={template.id}
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => applyTemplatePrompt(template.id)}
|
||||
className={`min-h-[10.2rem] w-[7.45rem] shrink-0 rounded-[1rem] border p-2 text-left transition ${
|
||||
selected
|
||||
? 'border-emerald-300 bg-emerald-50/86 shadow-[0_0_0_1px_rgba(16,185,129,0.18)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/82 hover:bg-white'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
aria-pressed={selected}
|
||||
aria-label={`${template.title}模板`}
|
||||
>
|
||||
<span className="block aspect-square overflow-hidden rounded-[0.8rem] bg-[var(--platform-subpanel-fill)]">
|
||||
<img
|
||||
src={template.imageSrc}
|
||||
alt=""
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</span>
|
||||
<span className="mt-2 block min-h-8 overflow-hidden text-ellipsis text-xs font-black leading-4 text-[var(--platform-text-strong)]">
|
||||
{template.title}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="mt-2 inline-flex max-w-full rounded-full bg-emerald-100 px-2 py-1 text-[10px] font-black text-emerald-700">
|
||||
已选择
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-4">
|
||||
<label
|
||||
className={`inline-flex min-h-10 cursor-pointer items-center gap-2 rounded-full border border-[var(--platform-subpanel-border)] bg-white/92 px-4 text-sm font-black text-[var(--platform-text-strong)] shadow-sm transition hover:bg-white ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
title={formState.referenceImageSrc ? '更换参考图' : '添加参考图'}
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
<span>
|
||||
{formState.referenceImageSrc ? '更换参考图' : '上传参考图'}
|
||||
</span>
|
||||
<input
|
||||
value={formState.workTitle}
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
disabled={isBusy}
|
||||
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-4 py-3 text-base font-semibold text-[var(--platform-text-strong)] outline-none"
|
||||
aria-label="作品名称"
|
||||
onChange={(event) => {
|
||||
void handleReferenceImageChange(event);
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
作品描述
|
||||
</span>
|
||||
<textarea
|
||||
value={formState.workDescription}
|
||||
disabled={isBusy}
|
||||
rows={4}
|
||||
onChange={(event) =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
workDescription: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="mt-2 w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-sm leading-6 text-[var(--platform-text-strong)] outline-none"
|
||||
aria-label="作品描述"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
|
||||
画面描述
|
||||
</span>
|
||||
<div className="relative mt-2">
|
||||
<span className="sr-only">画面描述</span>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={formState.pictureDescription}
|
||||
disabled={isBusy}
|
||||
rows={10}
|
||||
placeholder="一只猫在雨夜灯牌下回头,霓虹反光清晰,街角有花店和小伞,适合切成拼图。"
|
||||
onChange={(event) =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
pictureDescription: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 pb-16 text-sm leading-6 text-[var(--platform-text-strong)] outline-none"
|
||||
className="min-h-[18rem] w-full resize-none rounded-[1.35rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-4 pb-16 text-base leading-7 text-[var(--platform-text-strong)] outline-none placeholder:text-zinc-400 sm:min-h-[20rem]"
|
||||
aria-label="画面描述"
|
||||
/>
|
||||
<PuzzleImageModelPicker
|
||||
@@ -358,26 +397,6 @@ export function PuzzleAgentWorkspace({
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className={`absolute bottom-3 right-3 inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-full border border-amber-300/70 bg-white/96 text-amber-700 shadow-sm transition hover:bg-amber-50 ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
title={
|
||||
formState.referenceImageSrc ? '更换参考图' : '添加参考图'
|
||||
}
|
||||
>
|
||||
<ImagePlus className="h-4 w-4" />
|
||||
<span className="sr-only">
|
||||
{formState.referenceImageSrc ? '更换参考图' : '添加参考图'}
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
disabled={isBusy}
|
||||
onChange={(event) => {
|
||||
void handleReferenceImageChange(event);
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
||||
94
src/components/puzzle-agent/puzzleCreationTemplates.ts
Normal file
94
src/components/puzzle-agent/puzzleCreationTemplates.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
export type PuzzleCreationTemplate = {
|
||||
id: string;
|
||||
title: string;
|
||||
imageSrc: string;
|
||||
prompt: string;
|
||||
};
|
||||
|
||||
// 中文注释:模板只服务入口快速填词,正式作品信息仍在结果页补全。
|
||||
export const PUZZLE_CREATION_TEMPLATES: PuzzleCreationTemplate[] = [
|
||||
{
|
||||
id: 'couple-memory',
|
||||
title: '情侣合照拼图',
|
||||
imageSrc: '/puzzle-creation-templates/couple-memory.webp',
|
||||
prompt:
|
||||
'温暖自然光下的一对情侣纪念合照,城市咖啡馆窗边,桌面有花束和两杯热饮,人物神情自然,画面主体清晰,前中后景层次明确。',
|
||||
},
|
||||
{
|
||||
id: 'family-keepsake',
|
||||
title: '家庭纪念拼图',
|
||||
imageSrc: '/puzzle-creation-templates/family-keepsake.webp',
|
||||
prompt:
|
||||
'三代家人在客厅沙发前的家庭纪念合照,柔和午后阳光,孩子抱着生日蛋糕,长辈微笑,画面温暖完整,细节丰富但不杂乱。',
|
||||
},
|
||||
{
|
||||
id: 'friends-party',
|
||||
title: '朋友聚会拼图',
|
||||
imageSrc: '/puzzle-creation-templates/friends-party.webp',
|
||||
prompt:
|
||||
'朋友们在露台夜晚聚会,彩灯、桌上零食和举杯瞬间,人物分布有层次,中央焦点清楚,氛围轻松热闹。',
|
||||
},
|
||||
{
|
||||
id: 'festival-card',
|
||||
title: '节日贺卡拼图',
|
||||
imageSrc: '/puzzle-creation-templates/festival-card.webp',
|
||||
prompt:
|
||||
'节日餐桌与礼物布置,暖色灯光、彩带、蜡烛和窗外烟花,画面像无字贺卡,主体集中,边角细节可辨。',
|
||||
},
|
||||
{
|
||||
id: 'knowledge-summary',
|
||||
title: '知识总结拼图',
|
||||
imageSrc: '/puzzle-creation-templates/knowledge-summary.webp',
|
||||
prompt:
|
||||
'一张无文字的知识学习主题插画,书桌上有打开的笔记本、便签、咖啡、台灯和思维导图式图形元素,构图整洁,重点明确。',
|
||||
},
|
||||
{
|
||||
id: 'product-detail',
|
||||
title: '商品细节拼图',
|
||||
imageSrc: '/puzzle-creation-templates/product-detail.webp',
|
||||
prompt:
|
||||
'精致商品静物展示,一只高质感香水瓶放在丝绸与花瓣之间,玻璃反光清晰,包装和材质细节丰富,背景干净。',
|
||||
},
|
||||
{
|
||||
id: 'healing-landscape',
|
||||
title: '治愈风景拼图',
|
||||
imageSrc: '/puzzle-creation-templates/healing-landscape.webp',
|
||||
prompt:
|
||||
'治愈风景插画,清晨湖边、薄雾、远山、木栈道和一盏小灯,色彩柔和。',
|
||||
},
|
||||
{
|
||||
id: 'cute-pet',
|
||||
title: '宠物可爱拼图',
|
||||
imageSrc: '/puzzle-creation-templates/cute-pet.webp',
|
||||
prompt:
|
||||
'一只可爱的橘猫趴在阳光窗台上,旁边有绿植、毛线球和小毯子,猫的表情清楚,画面温柔干净。',
|
||||
},
|
||||
{
|
||||
id: 'hot-topic-poster',
|
||||
title: '热点海报拼图',
|
||||
imageSrc: '/puzzle-creation-templates/hot-topic-poster.webp',
|
||||
prompt:
|
||||
'电影感热点海报风插画,雨夜街头、霓虹反光、奔跑的人影和远处光束,强烈视觉焦点,画面无文字。',
|
||||
},
|
||||
{
|
||||
id: 'event-invitation',
|
||||
title: '活动邀请拼图',
|
||||
imageSrc: '/puzzle-creation-templates/event-invitation.webp',
|
||||
prompt:
|
||||
'活动邀请主题插画,展厅入口、花艺装置、签到台和柔和灯带,人群剪影自然分布,画面高级干净,无文字。',
|
||||
},
|
||||
{
|
||||
id: 'daily-challenge',
|
||||
title: '每日挑战拼图',
|
||||
imageSrc: '/puzzle-creation-templates/daily-challenge.webp',
|
||||
prompt:
|
||||
'每日挑战主题插画,清爽桌面上摆放相机、明信片、计时器和小奖章,色彩明亮,构图有趣,细节可拆解。',
|
||||
},
|
||||
{
|
||||
id: 'children-learning',
|
||||
title: '儿童认知拼图',
|
||||
imageSrc: '/puzzle-creation-templates/children-learning.webp',
|
||||
prompt:
|
||||
'儿童认知学习插画,木质桌面上有积木、彩色形状、动物玩偶和小书本,色彩明快,元素边界清晰,无文字。',
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user