This commit is contained in:
2026-04-22 20:14:15 +08:00
parent 0773a0d0ca
commit 0e9c286a57
205 changed files with 25790 additions and 1623 deletions

View File

@@ -4,11 +4,17 @@ import type {
CustomWorldAgentSessionSnapshot,
SendCustomWorldAgentMessageRequest,
} from '../../../packages/shared/src/contracts/customWorldAgent';
import { CustomWorldAgentComposer } from './CustomWorldAgentComposer';
import { CustomWorldAgentHeader } from './CustomWorldAgentHeader';
import { CustomWorldAgentOperationBanner } from './CustomWorldAgentOperationBanner';
import { CustomWorldAgentThread } from './CustomWorldAgentThread';
import { EightAnchorProgressBar } from './EightAnchorProgressBar';
import {
createCreationAgentClientMessageId,
isCreationAgentOperationBusy,
} from '../../services/creation-agent';
import {
type CreationAgentAnchorView,
type CreationAgentOperationView,
type CreationAgentSessionView,
type CreationAgentTheme,
CreationAgentWorkspace,
} from '../creation-agent';
type CustomWorldAgentWorkspaceProps = {
session: CustomWorldAgentSessionSnapshot | null;
@@ -20,15 +26,132 @@ type CustomWorldAgentWorkspaceProps = {
onExecuteAction: (payload: CustomWorldAgentActionRequest) => void;
};
function createClientMessageId() {
if (
typeof crypto !== 'undefined' &&
typeof crypto.randomUUID === 'function'
) {
return crypto.randomUUID();
const CUSTOM_WORLD_AGENT_THEME: CreationAgentTheme = {
accentTextClass: 'text-emerald-100/86',
accentBgClass: 'bg-emerald-300',
accentButtonClass: 'bg-emerald-200 shadow-emerald-950/20',
userBubbleClass:
'border border-[var(--platform-cool-border)] bg-[var(--platform-cool-bg)] text-[var(--platform-text-strong)]',
heroClass:
'border border-emerald-100/18 bg-[radial-gradient(circle_at_top_left,rgba(52,211,153,0.2),transparent_32%),linear-gradient(135deg,rgba(6,78,59,0.95),rgba(24,33,39,0.96))]',
anchorGridClass: 'grid gap-2 sm:grid-cols-2 xl:grid-cols-4',
};
function stringifyAnchorValue(value: unknown): string {
if (!value) {
return '';
}
return `client-message-${Date.now()}`;
if (typeof value === 'string') {
return value;
}
if (Array.isArray(value)) {
return value
.map((item): string => stringifyAnchorValue(item))
.filter((item): item is string => Boolean(item))
.join(' / ');
}
if (typeof value !== 'object') {
return String(value);
}
return Object.values(value as Record<string, unknown>)
.map((item): string => stringifyAnchorValue(item))
.filter((item): item is string => Boolean(item))
.join(' / ');
}
function buildCustomWorldAnchor(
key: string,
label: string,
value: unknown,
): CreationAgentAnchorView {
const text = stringifyAnchorValue(value);
return {
key,
label,
value: text,
status: text ? 'confirmed' : 'missing',
};
}
function mapCustomWorldSession(
session: CustomWorldAgentSessionSnapshot,
): CreationAgentSessionView {
return {
sessionId: session.sessionId,
title: '世界共创',
assistantSummary:
session.lastAssistantReply ||
'先说一个你最想让玩家记住的世界方向,我会帮你收束成可生成草稿的锚点。',
currentTurn: session.currentTurn,
progressPercent: session.progressPercent,
anchors: [
buildCustomWorldAnchor(
'worldPromise',
'世界承诺',
session.anchorContent.worldPromise,
),
buildCustomWorldAnchor(
'playerFantasy',
'玩家幻想',
session.anchorContent.playerFantasy,
),
buildCustomWorldAnchor(
'themeBoundary',
'主题边界',
session.anchorContent.themeBoundary,
),
buildCustomWorldAnchor(
'playerEntryPoint',
'开局切入',
session.anchorContent.playerEntryPoint,
),
buildCustomWorldAnchor(
'coreConflict',
'核心冲突',
session.anchorContent.coreConflict,
),
buildCustomWorldAnchor(
'keyRelationships',
'关键关系',
session.anchorContent.keyRelationships,
),
buildCustomWorldAnchor(
'hiddenLines',
'暗线',
session.anchorContent.hiddenLines,
),
buildCustomWorldAnchor(
'iconicElements',
'标志元素',
session.anchorContent.iconicElements,
),
],
messages: session.messages,
recommendedReplies: session.recommendedReplies,
};
}
function mapCustomWorldOperation(
operation: CustomWorldAgentOperationRecord | null,
): CreationAgentOperationView | null {
if (!operation || operation.type === 'process_message') {
return null;
}
return {
operationId: operation.operationId,
type: operation.type,
status: operation.status,
phaseLabel: operation.phaseLabel,
phaseDetail: operation.phaseDetail,
progress: operation.progress,
error: operation.error,
};
}
export function CustomWorldAgentWorkspace({
@@ -40,22 +163,12 @@ export function CustomWorldAgentWorkspace({
onSubmitMessage,
onExecuteAction,
}: CustomWorldAgentWorkspaceProps) {
if (!session) {
return (
<div className="platform-remap-surface platform-subpanel mx-auto flex h-full w-full max-w-4xl items-center justify-center rounded-[1.75rem] px-6 py-8 text-center text-sm text-[var(--platform-text-soft)]">
</div>
);
}
const isBusy =
activeOperation?.status === 'queued' ||
activeOperation?.status === 'running' ||
isStreamingReply;
isCreationAgentOperationBusy(activeOperation) || isStreamingReply;
const submitMessage = (text: string, quickFillRequested = false) => {
onSubmitMessage({
clientMessageId: createClientMessageId(),
clientMessageId: createCreationAgentClientMessageId('custom-world'),
text,
quickFillRequested,
focusCardId: null,
@@ -64,48 +177,44 @@ export function CustomWorldAgentWorkspace({
};
return (
<div className="mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col gap-3 overflow-hidden px-1 sm:px-0">
<CustomWorldAgentHeader onBack={onBack} />
<EightAnchorProgressBar
currentTurn={session.currentTurn}
progressPercent={session.progressPercent}
disabled={isBusy}
onSummaryClick={() => {
submitMessage('请总结一下当前已经成形的世界设定。');
}}
onQuickFill={() => {
<CreationAgentWorkspace
session={session ? mapCustomWorldSession(session) : null}
theme={CUSTOM_WORLD_AGENT_THEME}
loadingText="正在恢复"
composerPlaceholder="输入消息"
primaryActionLabel="生成游戏设定草稿"
activeOperation={mapCustomWorldOperation(activeOperation)}
streamingReplyText={streamingReplyText}
isStreamingReply={isStreamingReply}
isBusy={isBusy}
quickActions={[
{
key: 'summarize',
label: '总结当前设定',
},
{
key: 'quickFill',
label: '补全剩余设定',
minTurn: 2,
},
]}
onBack={onBack}
onSubmitText={(text) => {
submitMessage(text);
}}
onPrimaryAction={() => {
onExecuteAction({
action: 'draft_foundation',
});
}}
onQuickAction={(action) => {
if (action.key === 'quickFill') {
submitMessage('请补全剩余设定。', true);
}}
onGenerateDraft={() => {
onExecuteAction({
action: 'draft_foundation',
});
}}
/>
return;
}
{activeOperation?.type !== 'process_message' ? (
<CustomWorldAgentOperationBanner operation={activeOperation} />
) : null}
<div className="min-h-0 flex-1 overflow-hidden">
<div className="h-full min-h-[18rem] lg:min-h-0">
<CustomWorldAgentThread
messages={session.messages}
recommendedReplies={session.recommendedReplies}
onRecommendedReply={(text) => {
submitMessage(text);
}}
streamingReplyText={streamingReplyText}
isStreamingReply={isStreamingReply}
/>
</div>
</div>
<CustomWorldAgentComposer
disabled={isBusy}
onSubmit={onSubmitMessage}
/>
</div>
submitMessage('请总结一下当前已经成形的世界设定。');
}}
/>
);
}