216 lines
6.1 KiB
TypeScript
216 lines
6.1 KiB
TypeScript
import { useState } from 'react';
|
|
|
|
import type {
|
|
ExecuteMatch3DActionRequest,
|
|
Match3DAgentSessionSnapshot,
|
|
Match3DAnchorItemResponse,
|
|
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;
|