547 lines
21 KiB
TypeScript
547 lines
21 KiB
TypeScript
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<Match3DFormState>(() =>
|
||
resolveInitialFormState(session, initialFormPayload),
|
||
);
|
||
const [isCustomStylePanelOpen, setIsCustomStylePanelOpen] = useState(false);
|
||
const [draftCustomStylePrompt, setDraftCustomStylePrompt] = useState('');
|
||
const appliedInitialFormKeyRef = useRef<string | null>(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<CreateMatch3DSessionRequest>(
|
||
() => ({
|
||
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 (
|
||
<div className="platform-remap-surface mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col overflow-hidden">
|
||
{showBackButton ? (
|
||
<div className="mb-3 flex shrink-0 items-center justify-between gap-3 sm:mb-4">
|
||
<button
|
||
type="button"
|
||
onClick={onBack}
|
||
disabled={isBusy}
|
||
className={`platform-button platform-button--ghost min-h-0 self-start px-3 py-1.5 text-[11px] ${isBusy ? 'opacity-45' : ''}`}
|
||
>
|
||
返回
|
||
</button>
|
||
</div>
|
||
) : null}
|
||
|
||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden pr-0">
|
||
{title ? (
|
||
<div className="mb-3 shrink-0 sm:mb-5">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<h1 className="m-0 text-3xl font-black leading-none tracking-normal text-[var(--platform-text-strong)] sm:text-7xl">
|
||
{title}
|
||
</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>
|
||
) : null}
|
||
|
||
<section className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||
<div
|
||
className={`grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-3 lg:grid-cols-[minmax(0,1.1fr)_minmax(16rem,0.9fr)] lg:grid-rows-1 ${isBusy ? 'opacity-55' : ''}`}
|
||
>
|
||
<label className="block min-h-0">
|
||
<span className="mb-2 block text-sm font-black text-[var(--platform-text-strong)]">
|
||
想做一个什么题材的抓大鹅?
|
||
</span>
|
||
<textarea
|
||
value={formState.themeText}
|
||
disabled={isBusy}
|
||
rows={5}
|
||
placeholder=""
|
||
onChange={(event) =>
|
||
setFormState((current) => ({
|
||
...current,
|
||
themeText: event.target.value,
|
||
}))
|
||
}
|
||
className="h-full min-h-[7.75rem] w-full resize-none rounded-[1.15rem] 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 sm:min-h-[9rem] lg:min-h-[14rem]"
|
||
aria-label="想做一个什么题材的抓大鹅?"
|
||
/>
|
||
</label>
|
||
|
||
<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)]">
|
||
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="2D素材风格"
|
||
>
|
||
{MATCH3D_ASSET_STYLE_OPTIONS.map((option) => {
|
||
const selected = formState.assetStyleId === option.id;
|
||
const isCustom = option.id === 'custom';
|
||
return (
|
||
<button
|
||
key={option.id}
|
||
type="button"
|
||
disabled={isBusy}
|
||
onClick={() => {
|
||
if (isCustom) {
|
||
openCustomStylePanel();
|
||
return;
|
||
}
|
||
setFormState((current) => ({
|
||
...current,
|
||
assetStyleId: option.id,
|
||
}));
|
||
}}
|
||
className={`group relative h-[4.9rem] w-[5.85rem] shrink-0 snap-start overflow-hidden rounded-[0.95rem] border p-0 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 sm:h-[5.45rem] sm:w-[6.4rem] ${
|
||
selected
|
||
? 'border-rose-300 bg-white shadow-[0_8px_18px_rgba(190,18,60,0.10)] ring-2 ring-rose-100'
|
||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:border-rose-200 hover:bg-white/95'
|
||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||
aria-pressed={selected}
|
||
aria-label={option.label}
|
||
>
|
||
{option.imageSrc ? (
|
||
<img
|
||
src={option.imageSrc}
|
||
alt=""
|
||
className="absolute inset-0 h-full w-full object-cover transition duration-200 group-hover:scale-[1.03]"
|
||
loading="lazy"
|
||
/>
|
||
) : (
|
||
<span className="absolute inset-0 bg-[radial-gradient(circle_at_32%_24%,rgba(255,255,255,0.98),transparent_30%),linear-gradient(135deg,rgba(255,247,250,0.98),rgba(255,236,241,0.92))]" />
|
||
)}
|
||
<span className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.02)_0%,rgba(255,255,255,0.18)_44%,rgba(255,255,255,0.82)_100%)]" />
|
||
{selected ? (
|
||
<span className="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-rose-400 shadow-[0_0_0_3px_rgba(255,255,255,0.84)]" />
|
||
) : null}
|
||
{isCustom ? (
|
||
<span className="absolute inset-0 flex items-center justify-center text-rose-500">
|
||
<span className="grid h-8 w-8 place-items-center rounded-full bg-white/82 shadow-[0_6px_18px_rgba(190,18,60,0.12)]">
|
||
<Plus className="h-5 w-5" />
|
||
</span>
|
||
</span>
|
||
) : null}
|
||
<span
|
||
className={`absolute inset-x-2 bottom-1.5 truncate rounded-full px-1.5 py-0.5 text-center text-[11px] font-black shadow-[0_3px_10px_rgba(15,23,42,0.10)] ${
|
||
selected
|
||
? 'bg-rose-50/95 text-rose-700'
|
||
: 'bg-white/88 text-[var(--platform-text-strong)]'
|
||
}`}
|
||
>
|
||
{option.label}
|
||
</span>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="shrink-0 rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/44 p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.7)]">
|
||
<div className="mb-1.5 text-sm font-black text-[var(--platform-text-strong)]">
|
||
难度
|
||
</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;
|
||
return (
|
||
<button
|
||
key={option.id}
|
||
type="button"
|
||
disabled={isBusy}
|
||
onClick={() =>
|
||
setFormState((current) => ({
|
||
...current,
|
||
difficultyOptionId: option.id,
|
||
}))
|
||
}
|
||
className={`min-h-10 rounded-[0.85rem] border px-2 text-sm font-black transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 sm:min-h-11 ${
|
||
selected
|
||
? 'border-[#ff7890] bg-[linear-gradient(180deg,#ff7890_0%,#ff4f6a_100%)] text-white shadow-[0_8px_18px_rgba(244,63,94,0.16)]'
|
||
: 'border-[var(--platform-subpanel-border)] bg-white/76 text-[var(--platform-text-strong)] hover:border-rose-200 hover:bg-white'
|
||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||
aria-pressed={selected}
|
||
>
|
||
{option.label}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-2 shrink-0 space-y-3">
|
||
{error ? (
|
||
<div className="platform-banner platform-banner--danger rounded-2xl text-sm leading-6">
|
||
{error}
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div className="mt-2 flex shrink-0 justify-center pb-[max(0.25rem,env(safe-area-inset-bottom))] sm:mt-3">
|
||
<button
|
||
type="button"
|
||
disabled={!canSubmit}
|
||
onClick={submitForm}
|
||
className={`platform-button platform-button--primary min-h-10 px-4 py-2 text-sm sm:min-h-11 sm:px-5 ${!canSubmit ? 'cursor-not-allowed opacity-55' : ''}`}
|
||
>
|
||
<span className="inline-flex flex-wrap items-center justify-center gap-1.5 sm:gap-2">
|
||
{isBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
||
{session ? (
|
||
<Sparkles className="h-4 w-4" />
|
||
) : (
|
||
<WandSparkles className="h-4 w-4" />
|
||
)}
|
||
<span>生成抓大鹅草稿</span>
|
||
<span className="rounded-full bg-white/24 px-2 py-0.5 text-[11px] font-bold">
|
||
消耗10泥点
|
||
</span>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
{isCustomStylePanelOpen ? (
|
||
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
|
||
<div
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="match3d-custom-style-title"
|
||
className="platform-modal-shell platform-remap-surface w-full max-w-sm rounded-[1.35rem] p-5 shadow-[0_24px_70px_rgba(15,23,42,0.22)]"
|
||
>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div
|
||
id="match3d-custom-style-title"
|
||
className="text-base font-black text-[var(--platform-text-strong)]"
|
||
>
|
||
自定义风格
|
||
</div>
|
||
<button
|
||
type="button"
|
||
aria-label="关闭自定义风格"
|
||
onClick={() => setIsCustomStylePanelOpen(false)}
|
||
className="platform-profile-icon-button flex h-8 w-8 items-center justify-center rounded-full"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</button>
|
||
</div>
|
||
<textarea
|
||
value={draftCustomStylePrompt}
|
||
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="自定义2D素材风格描述"
|
||
/>
|
||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => setIsCustomStylePanelOpen(false)}
|
||
className="platform-button platform-button--secondary justify-center"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="button"
|
||
disabled={!draftCustomStylePrompt.trim()}
|
||
onClick={applyCustomStylePrompt}
|
||
className={`platform-button platform-button--primary justify-center ${!draftCustomStylePrompt.trim() ? 'cursor-not-allowed opacity-55' : ''}`}
|
||
>
|
||
应用
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Match3DAgentWorkspace;
|