215
src/components/match3d-creation/Match3DAgentWorkspace.tsx
Normal file
215
src/components/match3d-creation/Match3DAgentWorkspace.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import type {
|
||||
ExecuteMatch3DActionRequest,
|
||||
Match3DAnchorItemResponse,
|
||||
Match3DAgentSessionSnapshot,
|
||||
SendMatch3DMessageRequest,
|
||||
} from '../../../packages/shared/src/contracts/match3dAgent';
|
||||
import {
|
||||
buildCreationAgentChatMessage,
|
||||
createCreationAgentChatQuickActions,
|
||||
createCreationAgentClientMessageId,
|
||||
resolveCreationAgentQuickActionMessage,
|
||||
} from '../../services/creation-agent';
|
||||
import {
|
||||
type CreationAgentAnchorView,
|
||||
type CreationAgentSessionView,
|
||||
type CreationAgentTheme,
|
||||
CreationAgentWorkspace,
|
||||
} from '../creation-agent';
|
||||
|
||||
type Match3DAgentWorkspaceProps = {
|
||||
session: Match3DAgentSessionSnapshot | null;
|
||||
streamingReplyText?: string;
|
||||
isStreamingReply?: boolean;
|
||||
isBusy?: boolean;
|
||||
error?: string | null;
|
||||
onBack: () => void;
|
||||
onSubmitMessage: (payload: SendMatch3DMessageRequest) => void;
|
||||
onExecuteAction: (payload: ExecuteMatch3DActionRequest) => void;
|
||||
};
|
||||
|
||||
type Match3DReferenceImageState = {
|
||||
src: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const MATCH3D_AGENT_THEME: CreationAgentTheme = {
|
||||
accentTextClass: 'text-lime-100/86',
|
||||
accentBgClass: 'bg-lime-200',
|
||||
accentButtonClass: 'bg-lime-200 shadow-emerald-950/20',
|
||||
userBubbleClass: 'bg-emerald-600 text-white',
|
||||
heroClass:
|
||||
'border border-lime-100/18 bg-[radial-gradient(circle_at_top_left,rgba(190,242,100,0.24),transparent_34%),radial-gradient(circle_at_bottom_right,rgba(251,146,60,0.2),transparent_32%),linear-gradient(135deg,rgba(20,83,45,0.96),rgba(39,39,42,0.96))]',
|
||||
anchorGridClass: 'grid gap-2 sm:grid-cols-3',
|
||||
};
|
||||
|
||||
const MATCH3D_QUICK_ACTIONS = [
|
||||
...createCreationAgentChatQuickActions(),
|
||||
{
|
||||
key: 'match3d-auto-config',
|
||||
label: '自动配置',
|
||||
},
|
||||
];
|
||||
|
||||
function readMatch3DReferenceImageAsDataUrl(file: File) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
reject(new Error('请选择图片文件。'));
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onerror = () => reject(new Error('参考图读取失败,请重试。'));
|
||||
reader.onload = () => resolve(String(reader.result || ''));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
function mapMatch3DAnchor(
|
||||
anchor: Match3DAnchorItemResponse,
|
||||
): CreationAgentAnchorView {
|
||||
return {
|
||||
key: anchor.key,
|
||||
label: anchor.label,
|
||||
value: anchor.value,
|
||||
status: anchor.status,
|
||||
};
|
||||
}
|
||||
|
||||
function mapMatch3DSession(
|
||||
session: Match3DAgentSessionSnapshot,
|
||||
): CreationAgentSessionView {
|
||||
// 中文注释:抓大鹅 F1 只展示聊天与配置锚点,草稿结果交给后续结果页承接。
|
||||
const chatMessages = session.messages.filter(
|
||||
(message) =>
|
||||
message.kind === 'chat' ||
|
||||
message.kind === 'summary' ||
|
||||
message.kind === 'warning',
|
||||
);
|
||||
|
||||
return {
|
||||
sessionId: session.sessionId,
|
||||
title: null,
|
||||
assistantSummary: null,
|
||||
currentTurn: session.currentTurn,
|
||||
progressPercent: session.progressPercent,
|
||||
anchors: [
|
||||
session.anchorPack.theme,
|
||||
session.anchorPack.clearCount,
|
||||
session.anchorPack.difficulty,
|
||||
].map(mapMatch3DAnchor),
|
||||
messages: chatMessages,
|
||||
recommendedReplies: [],
|
||||
};
|
||||
}
|
||||
|
||||
function buildMatch3DChatPayload({
|
||||
text,
|
||||
quickFillRequested = false,
|
||||
referenceImageSrc,
|
||||
}: {
|
||||
text: string;
|
||||
quickFillRequested?: boolean;
|
||||
referenceImageSrc?: string | null;
|
||||
}) {
|
||||
return buildCreationAgentChatMessage<{
|
||||
referenceImageSrc?: string | null;
|
||||
}>({
|
||||
clientMessageId: createCreationAgentClientMessageId('match3d'),
|
||||
text,
|
||||
quickFillRequested,
|
||||
extraPayload: {
|
||||
referenceImageSrc: referenceImageSrc || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function Match3DAgentWorkspace({
|
||||
session,
|
||||
streamingReplyText = '',
|
||||
isStreamingReply = false,
|
||||
isBusy = false,
|
||||
error = null,
|
||||
onBack,
|
||||
onSubmitMessage,
|
||||
onExecuteAction,
|
||||
}: Match3DAgentWorkspaceProps) {
|
||||
const [referenceImage, setReferenceImage] =
|
||||
useState<Match3DReferenceImageState | null>(null);
|
||||
const [referenceImageError, setReferenceImageError] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
return (
|
||||
<CreationAgentWorkspace
|
||||
session={session ? mapMatch3DSession(session) : null}
|
||||
theme={MATCH3D_AGENT_THEME}
|
||||
loadingText="正在准备抓大鹅共创工作区..."
|
||||
composerPlaceholder="题材、消除次数、难度..."
|
||||
primaryActionLabel="生成结果页"
|
||||
streamingReplyText={streamingReplyText}
|
||||
isStreamingReply={isStreamingReply}
|
||||
isBusy={isBusy}
|
||||
error={error}
|
||||
quickActions={MATCH3D_QUICK_ACTIONS}
|
||||
referenceImagePreviewSrc={referenceImage?.src ?? null}
|
||||
referenceImageLabel={referenceImage?.label ?? null}
|
||||
referenceImageError={referenceImageError}
|
||||
onBack={onBack}
|
||||
onSubmitText={(text) => {
|
||||
onSubmitMessage(
|
||||
buildMatch3DChatPayload({
|
||||
text,
|
||||
referenceImageSrc: referenceImage?.src ?? null,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
onPrimaryAction={() => {
|
||||
onExecuteAction({ action: 'match3d_compile_draft' });
|
||||
}}
|
||||
onQuickAction={(action) => {
|
||||
const quickActionMessage =
|
||||
action.key === 'match3d-auto-config'
|
||||
? {
|
||||
text: '自动配置',
|
||||
quickFillRequested: true,
|
||||
}
|
||||
: resolveCreationAgentQuickActionMessage(
|
||||
action.key,
|
||||
'请总结一下当前抓大鹅设定。',
|
||||
);
|
||||
|
||||
onSubmitMessage(
|
||||
buildMatch3DChatPayload({
|
||||
...quickActionMessage,
|
||||
referenceImageSrc: referenceImage?.src ?? null,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
onReferenceImageChange={async (file) => {
|
||||
try {
|
||||
const dataUrl = await readMatch3DReferenceImageAsDataUrl(file);
|
||||
setReferenceImage({
|
||||
src: dataUrl,
|
||||
label: file.name.trim() || '本地参考图',
|
||||
});
|
||||
setReferenceImageError(null);
|
||||
} catch (caughtError) {
|
||||
setReferenceImageError(
|
||||
caughtError instanceof Error
|
||||
? caughtError.message
|
||||
: '参考图读取失败,请重试。',
|
||||
);
|
||||
}
|
||||
}}
|
||||
onClearReferenceImage={() => {
|
||||
setReferenceImage(null);
|
||||
setReferenceImageError(null);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Match3DAgentWorkspace;
|
||||
Reference in New Issue
Block a user