1
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Loader2, Plus, Sparkles, WandSparkles, X } from 'lucide-react';
|
||||
import { Loader2, Music2, Plus, Sparkles, WandSparkles, X } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type {
|
||||
@@ -26,13 +26,15 @@ type Match3DFormState = {
|
||||
difficultyOptionId: Match3DDifficultyOptionId;
|
||||
assetStyleId: Match3DAssetStyleOptionId;
|
||||
customAssetStylePrompt: string;
|
||||
generateClickSound: boolean;
|
||||
};
|
||||
|
||||
const EMPTY_FORM_STATE: Match3DFormState = {
|
||||
themeText: '',
|
||||
difficultyOptionId: 'standard',
|
||||
assetStyleId: 'clay-toy',
|
||||
assetStyleId: 'flat-icon',
|
||||
customAssetStylePrompt: '',
|
||||
generateClickSound: false,
|
||||
};
|
||||
|
||||
// 中文注释:入口页只暴露难度选项,消除次数和难度数值由选项稳定派生给后端。
|
||||
@@ -40,7 +42,7 @@ 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: 20, difficulty: 8 },
|
||||
{ id: 'hardcore', label: '硬核', clearCount: 21, difficulty: 8 },
|
||||
] as const;
|
||||
|
||||
type Match3DDifficultyOptionId =
|
||||
@@ -48,40 +50,46 @@ type Match3DDifficultyOptionId =
|
||||
|
||||
const MATCH3D_ASSET_STYLE_OPTIONS = [
|
||||
{
|
||||
id: 'clay-toy',
|
||||
label: '黏土手作',
|
||||
imageSrc: '/match3d-style-references/clay-toy.png',
|
||||
prompt: '圆润、哑光、带轻微手捏痕迹的黏土手作 3D 素材风格。',
|
||||
id: 'flat-icon',
|
||||
label: '扁平图标',
|
||||
imageSrc: '/match3d-style-references/flat-icon.png',
|
||||
prompt:
|
||||
'干净扁平的 2D 游戏道具图标风格,正面视角,色块清楚,边缘硬朗,高可读性,适合移动端休闲游戏素材。',
|
||||
},
|
||||
{
|
||||
id: 'low-poly',
|
||||
label: '低多边形',
|
||||
imageSrc: '/match3d-style-references/low-poly.png',
|
||||
prompt: '块面清晰、轮廓简洁、颜色分区明确的低多边形 3D 素材风格。',
|
||||
id: 'cel-cartoon',
|
||||
label: '赛璐璐卡通',
|
||||
imageSrc: '/match3d-style-references/cel-cartoon.png',
|
||||
prompt:
|
||||
'明亮赛璐璐卡通 2D 游戏道具风格,清晰线稿,硬边阴影,饱和配色,轮廓醒目。',
|
||||
},
|
||||
{
|
||||
id: 'toy-plastic',
|
||||
label: '玩具塑料',
|
||||
imageSrc: '/match3d-style-references/toy-plastic.png',
|
||||
prompt: '亮面、光滑、有柔和高光的玩具塑料 3D 素材风格。',
|
||||
id: 'pixel-retro',
|
||||
label: '像素复古',
|
||||
imageSrc: '/match3d-style-references/pixel-retro.png',
|
||||
prompt:
|
||||
'复古像素 2D 游戏道具素材风格,有限色板,清晰像素边缘,主体轮廓稳定,不使用真实 3D 渲染。',
|
||||
},
|
||||
{
|
||||
id: 'wood-carved',
|
||||
label: '木质雕刻',
|
||||
imageSrc: '/match3d-style-references/wood-carved.png',
|
||||
prompt: '保留木纹和手工雕刻感的温润木质 3D 素材风格。',
|
||||
id: 'watercolor',
|
||||
label: '手绘水彩',
|
||||
imageSrc: '/match3d-style-references/watercolor.png',
|
||||
prompt:
|
||||
'手绘水彩 2D 道具素材风格,柔和纸张纹理,透明叠色,边缘轻微晕染,主体仍保持清楚可读。',
|
||||
},
|
||||
{
|
||||
id: 'voxel-block',
|
||||
label: '体素积木',
|
||||
imageSrc: '/match3d-style-references/voxel-block.png',
|
||||
prompt: '由小方块构成、边缘清晰、带游戏感的体素积木 3D 素材风格。',
|
||||
id: 'sticker-outline',
|
||||
label: '贴纸描边',
|
||||
imageSrc: '/match3d-style-references/sticker-outline.png',
|
||||
prompt:
|
||||
'贴纸描边 2D 游戏道具素材风格,粗白边与深色外轮廓,柔和投影,色彩活泼,适合休闲消除游戏。',
|
||||
},
|
||||
{
|
||||
id: 'metal-mecha',
|
||||
label: '金属机甲',
|
||||
imageSrc: '/match3d-style-references/metal-mecha.png',
|
||||
prompt: '带金属拉丝、柔和高光和轻科幻感的金属机甲 3D 素材风格。',
|
||||
id: 'painterly-icon',
|
||||
label: '厚涂图标',
|
||||
imageSrc: '/match3d-style-references/painterly-icon.png',
|
||||
prompt:
|
||||
'厚涂 2D 游戏道具图标风格,笔触细腻,体积光影明确,中心构图,保持图标级清晰剪影。',
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
@@ -149,7 +157,7 @@ function resolveAssetStyleOptionId(
|
||||
return matchedOption.id;
|
||||
}
|
||||
|
||||
return assetStylePrompt?.trim() ? 'custom' : 'clay-toy';
|
||||
return assetStylePrompt?.trim() ? 'custom' : 'flat-icon';
|
||||
}
|
||||
|
||||
function resolveInitialFormState(
|
||||
@@ -164,21 +172,13 @@ function resolveInitialFormState(
|
||||
initialFormPayload?.seedText?.trim() ||
|
||||
'';
|
||||
const clearCount =
|
||||
initialFormPayload?.clearCount ??
|
||||
config?.clearCount ??
|
||||
null;
|
||||
initialFormPayload?.clearCount ?? config?.clearCount ?? null;
|
||||
const difficulty =
|
||||
initialFormPayload?.difficulty ??
|
||||
config?.difficulty ??
|
||||
null;
|
||||
initialFormPayload?.difficulty ?? config?.difficulty ?? null;
|
||||
const assetStyleId =
|
||||
initialFormPayload?.assetStyleId ??
|
||||
config?.assetStyleId ??
|
||||
null;
|
||||
initialFormPayload?.assetStyleId ?? config?.assetStyleId ?? null;
|
||||
const assetStylePrompt =
|
||||
initialFormPayload?.assetStylePrompt ??
|
||||
config?.assetStylePrompt ??
|
||||
'';
|
||||
initialFormPayload?.assetStylePrompt ?? config?.assetStylePrompt ?? '';
|
||||
|
||||
return {
|
||||
...EMPTY_FORM_STATE,
|
||||
@@ -186,6 +186,10 @@ function resolveInitialFormState(
|
||||
difficultyOptionId: resolveDifficultyOptionId(difficulty, clearCount),
|
||||
assetStyleId: resolveAssetStyleOptionId(assetStyleId, assetStylePrompt),
|
||||
customAssetStylePrompt: assetStylePrompt,
|
||||
generateClickSound:
|
||||
initialFormPayload?.generateClickSound ??
|
||||
config?.generateClickSound ??
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -255,11 +259,13 @@ export function Match3DAgentWorkspace({
|
||||
assetStyleId: formState.assetStyleId,
|
||||
assetStyleLabel,
|
||||
assetStylePrompt,
|
||||
generateClickSound: formState.generateClickSound,
|
||||
}),
|
||||
[
|
||||
assetStyleLabel,
|
||||
assetStylePrompt,
|
||||
formState.assetStyleId,
|
||||
formState.generateClickSound,
|
||||
selectedDifficultyOption,
|
||||
themeText,
|
||||
],
|
||||
@@ -290,7 +296,10 @@ export function Match3DAgentWorkspace({
|
||||
}
|
||||
|
||||
if (session) {
|
||||
onExecuteAction({ action: 'match3d_compile_draft' });
|
||||
onExecuteAction({
|
||||
action: 'match3d_compile_draft',
|
||||
generateClickSound: formState.generateClickSound,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -350,11 +359,11 @@ export function Match3DAgentWorkspace({
|
||||
<div className="flex min-h-0 flex-col gap-2 overflow-hidden">
|
||||
<div className="min-h-0 rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/52 p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.78)]">
|
||||
<div className="mb-1.5 text-sm font-black text-[var(--platform-text-strong)]">
|
||||
3D素材风格
|
||||
2D素材风格
|
||||
</div>
|
||||
<div
|
||||
className="flex snap-x gap-2.5 overflow-x-auto overscroll-x-contain pb-0.5 scrollbar-hide touch-pan-x [-webkit-overflow-scrolling:touch]"
|
||||
aria-label="3D素材风格"
|
||||
aria-label="2D素材风格"
|
||||
>
|
||||
{MATCH3D_ASSET_STYLE_OPTIONS.map((option) => {
|
||||
const selected = formState.assetStyleId === option.id;
|
||||
@@ -424,8 +433,7 @@ export function Match3DAgentWorkspace({
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-1.5 sm:gap-2 lg:grid-cols-2">
|
||||
{MATCH3D_DIFFICULTY_OPTIONS.map((option) => {
|
||||
const selected =
|
||||
formState.difficultyOptionId === option.id;
|
||||
const selected = formState.difficultyOptionId === option.id;
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
@@ -450,6 +458,43 @@ export function Match3DAgentWorkspace({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() =>
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
generateClickSound: !current.generateClickSound,
|
||||
}))
|
||||
}
|
||||
className={`flex min-h-12 shrink-0 items-center justify-between gap-3 rounded-[1.05rem] border px-3 py-2.5 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 ${
|
||||
formState.generateClickSound
|
||||
? 'border-rose-200 bg-rose-50/80 text-rose-700 shadow-[0_8px_18px_rgba(244,63,94,0.10)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/58 text-[var(--platform-text-strong)] hover:border-rose-200 hover:bg-white/86'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
aria-pressed={formState.generateClickSound}
|
||||
aria-label="生成音效"
|
||||
>
|
||||
<span className="inline-flex min-w-0 items-center gap-2">
|
||||
<Music2 className="h-4 w-4 shrink-0" />
|
||||
<span className="truncate text-sm font-black">生成音效</span>
|
||||
</span>
|
||||
<span
|
||||
className={`relative h-6 w-11 shrink-0 rounded-full transition ${
|
||||
formState.generateClickSound
|
||||
? 'bg-rose-400'
|
||||
: 'bg-slate-200'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span
|
||||
className={`absolute top-1 h-4 w-4 rounded-full bg-white shadow-sm transition ${
|
||||
formState.generateClickSound ? 'left-6' : 'left-1'
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -511,10 +556,12 @@ export function Match3DAgentWorkspace({
|
||||
</div>
|
||||
<textarea
|
||||
value={draftCustomStylePrompt}
|
||||
onChange={(event) => setDraftCustomStylePrompt(event.target.value)}
|
||||
onChange={(event) =>
|
||||
setDraftCustomStylePrompt(event.target.value)
|
||||
}
|
||||
rows={4}
|
||||
className="mt-4 h-[7.5rem] w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
aria-label="自定义3D素材风格描述"
|
||||
aria-label="自定义2D素材风格描述"
|
||||
/>
|
||||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user