import { Loader2, Plus, Sparkles, WandSparkles, X } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import type { CreateMatch3DSessionRequest, ExecuteMatch3DActionRequest, Match3DAgentSessionSnapshot, SendMatch3DMessageRequest, } from '../../../packages/shared/src/contracts/match3dAgent'; type Match3DAgentWorkspaceProps = { session: Match3DAgentSessionSnapshot | null; isBusy?: boolean; error?: string | null; onBack: () => void; onSubmitMessage?: (payload: SendMatch3DMessageRequest) => void; onExecuteAction: (payload: ExecuteMatch3DActionRequest) => void; onCreateFromForm?: (payload: CreateMatch3DSessionRequest) => void; initialFormPayload?: CreateMatch3DSessionRequest | null; showBackButton?: boolean; title?: string | null; }; type Match3DFormState = { themeText: string; difficultyOptionId: Match3DDifficultyOptionId; assetStyleId: Match3DAssetStyleOptionId; customAssetStylePrompt: string; }; const EMPTY_FORM_STATE: Match3DFormState = { themeText: '', difficultyOptionId: 'standard', assetStyleId: 'flat-icon', customAssetStylePrompt: '', }; // 中文注释:入口页只暴露难度选项,消除次数和难度数值由选项稳定派生给后端。 const MATCH3D_DIFFICULTY_OPTIONS = [ { id: 'easy', label: '轻松', clearCount: 8, difficulty: 2 }, { id: 'standard', label: '标准', clearCount: 12, difficulty: 4 }, { id: 'advanced', label: '进阶', clearCount: 16, difficulty: 6 }, { id: 'hardcore', label: '硬核', clearCount: 21, difficulty: 8 }, ] as const; type Match3DDifficultyOptionId = (typeof MATCH3D_DIFFICULTY_OPTIONS)[number]['id']; const MATCH3D_ASSET_STYLE_OPTIONS = [ { id: 'flat-icon', label: '扁平图标', imageSrc: '/match3d-style-references/flat-icon.png', prompt: '干净扁平的2D游戏道具图标风格,正面视角,色块清楚,边缘硬朗。', }, { id: 'cel-cartoon', label: '赛璐璐卡通', imageSrc: '/match3d-style-references/cel-cartoon.png', prompt: '明亮赛璐璐卡通2D游戏道具风格,清晰线稿,硬边阴影,饱和配色,轮廓醒目。', }, { id: 'pixel-retro', label: '像素', imageSrc: '/match3d-style-references/pixel-retro.png', prompt: '像素2D游戏道具sprite风格', }, { id: 'watercolor', label: '手绘水彩', imageSrc: '/match3d-style-references/watercolor.png', prompt: '手绘水彩2D道具素材风格', }, { id: 'sticker-outline', label: '贴纸描边', imageSrc: '/match3d-style-references/sticker-outline.png', prompt: '贴纸描边2D游戏道具素材风格,粗白边与深色外轮廓', }, { id: 'painterly-icon', label: '厚涂图标', imageSrc: '/match3d-style-references/painterly-icon.png', prompt: '厚涂2D游戏道具图标风格,笔触细腻,体积光影明确,中心构图,保持图标级清晰剪影。', }, { id: 'custom', label: '自定义', imageSrc: null, prompt: '', }, ] as const; type Match3DAssetStyleOptionId = (typeof MATCH3D_ASSET_STYLE_OPTIONS)[number]['id']; function normalizeDifficulty(value: number) { return Math.max(1, Math.min(10, Math.round(value))); } function resolveDifficultyOptionId( difficulty: number | null | undefined, clearCount: number | null | undefined, ): Match3DDifficultyOptionId { const clearCountMatchedOption = MATCH3D_DIFFICULTY_OPTIONS.find( (option) => option.clearCount === clearCount, ); if (clearCountMatchedOption) { return clearCountMatchedOption.id; } if (typeof difficulty !== 'number' || !Number.isFinite(difficulty)) { return 'standard'; } const normalizedDifficulty = normalizeDifficulty(Number(difficulty)); return MATCH3D_DIFFICULTY_OPTIONS.reduce( (nearestOption, option) => Math.abs(option.difficulty - normalizedDifficulty) < Math.abs(nearestOption.difficulty - normalizedDifficulty) ? option : nearestOption, MATCH3D_DIFFICULTY_OPTIONS[1], ).id; } function getDifficultyOption(optionId: Match3DDifficultyOptionId) { return ( MATCH3D_DIFFICULTY_OPTIONS.find((option) => option.id === optionId) ?? MATCH3D_DIFFICULTY_OPTIONS[1] ); } function getAssetStyleOption(optionId: Match3DAssetStyleOptionId) { return ( MATCH3D_ASSET_STYLE_OPTIONS.find((option) => option.id === optionId) ?? MATCH3D_ASSET_STYLE_OPTIONS[0] ); } function resolveAssetStyleOptionId( assetStyleId: string | null | undefined, assetStylePrompt: string | null | undefined, ): Match3DAssetStyleOptionId { const matchedOption = MATCH3D_ASSET_STYLE_OPTIONS.find( (option) => option.id === assetStyleId, ); if (matchedOption) { return matchedOption.id; } return assetStylePrompt?.trim() ? 'custom' : 'flat-icon'; } function resolveInitialFormState( session: Match3DAgentSessionSnapshot | null, initialFormPayload: CreateMatch3DSessionRequest | null = null, ): Match3DFormState { const config = session?.config; const themeText = initialFormPayload?.themeText?.trim() || config?.themeText?.trim() || session?.anchorPack.theme.value?.trim() || initialFormPayload?.seedText?.trim() || ''; const clearCount = initialFormPayload?.clearCount ?? config?.clearCount ?? null; const difficulty = initialFormPayload?.difficulty ?? config?.difficulty ?? null; const assetStyleId = initialFormPayload?.assetStyleId ?? config?.assetStyleId ?? null; const assetStylePrompt = initialFormPayload?.assetStylePrompt ?? config?.assetStylePrompt ?? ''; return { ...EMPTY_FORM_STATE, themeText, difficultyOptionId: resolveDifficultyOptionId(difficulty, clearCount), assetStyleId: resolveAssetStyleOptionId(assetStyleId, assetStylePrompt), customAssetStylePrompt: assetStylePrompt, }; } /** * 抓大鹅创作入口已从固定 Agent 追问改成表单式。 * 组件名保留为 Match3DAgentWorkspace,兼容现有路由、草稿恢复和父层分流。 */ export function Match3DAgentWorkspace({ session, isBusy = false, error = null, onBack, onExecuteAction, onCreateFromForm, initialFormPayload = null, showBackButton = true, title = '想做个什么玩法?', }: Match3DAgentWorkspaceProps) { const [formState, setFormState] = useState(() => resolveInitialFormState(session, initialFormPayload), ); const [isCustomStylePanelOpen, setIsCustomStylePanelOpen] = useState(false); const [draftCustomStylePrompt, setDraftCustomStylePrompt] = useState(''); const appliedInitialFormKeyRef = useRef(null); useEffect(() => { const nextInitialFormKey = session?.sessionId ?? JSON.stringify(initialFormPayload ?? null); if (appliedInitialFormKeyRef.current === nextInitialFormKey) { return; } appliedInitialFormKeyRef.current = nextInitialFormKey; setFormState(resolveInitialFormState(session, initialFormPayload)); setIsCustomStylePanelOpen(false); setDraftCustomStylePrompt(''); }, [initialFormPayload, session]); const themeText = formState.themeText.trim(); const selectedDifficultyOption = getDifficultyOption( formState.difficultyOptionId, ); const selectedAssetStyleOption = getAssetStyleOption(formState.assetStyleId); const assetStylePrompt = formState.assetStyleId === 'custom' ? formState.customAssetStylePrompt.trim() : selectedAssetStyleOption.prompt; const assetStyleLabel = formState.assetStyleId === 'custom' ? '自定义风格' : selectedAssetStyleOption.label; const canSubmit = Boolean( themeText && !isBusy && (formState.assetStyleId !== 'custom' || formState.customAssetStylePrompt.trim()), ); const formPayload = useMemo( () => ({ seedText: themeText ? `${themeText}题材,消除${selectedDifficultyOption.clearCount}次,难度${selectedDifficultyOption.difficulty}` : themeText, themeText, referenceImageSrc: null, clearCount: selectedDifficultyOption.clearCount, difficulty: selectedDifficultyOption.difficulty, assetStyleId: formState.assetStyleId, assetStyleLabel, assetStylePrompt, generateClickSound: false, }), [ assetStyleLabel, assetStylePrompt, formState.assetStyleId, selectedDifficultyOption, themeText, ], ); const openCustomStylePanel = () => { setDraftCustomStylePrompt(formState.customAssetStylePrompt); setIsCustomStylePanelOpen(true); }; const applyCustomStylePrompt = () => { setFormState((current) => ({ ...current, assetStyleId: 'custom', customAssetStylePrompt: draftCustomStylePrompt.trim(), })); setIsCustomStylePanelOpen(false); }; const submitForm = () => { if (!canSubmit) { return; } if (onCreateFromForm) { onCreateFromForm(formPayload); return; } if (session) { onExecuteAction({ action: 'match3d_compile_draft', generateClickSound: false, }); } }; return (
{showBackButton ? (
) : null}
{title ? (

{title}

BETA
) : null}