init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,502 @@
import type {
CustomWorldAgentOperationRecord,
CustomWorldAgentSessionSnapshot,
EightAnchorContent,
} from '../../packages/shared/src/contracts/customWorldAgent';
import type {
CustomWorldGenerationProgress,
CustomWorldGenerationStep,
} from '../../packages/shared/src/contracts/runtime';
import {
buildCustomWorldCreatorIntentFoundationText,
normalizeCustomWorldCreatorIntent,
} from './customWorldCreatorIntent';
export type CustomWorldStructuredAnchorEntry = {
id: string;
label: string;
value: string;
};
function normalizeAnchorText(value: string | null | undefined) {
return typeof value === 'string' ? value.trim() : '';
}
function buildEightAnchorFoundationText(anchorContent: EightAnchorContent) {
return [
['世界承诺', anchorContent.worldPromise],
['玩家幻想', anchorContent.playerFantasy],
['主题边界', anchorContent.themeBoundary],
['玩家切入口', anchorContent.playerEntryPoint],
['核心冲突', anchorContent.coreConflict],
['关键关系', anchorContent.keyRelationships],
['暗线与揭示', anchorContent.hiddenLines],
['标志元素', anchorContent.iconicElements],
]
.map(([label, value]) => {
const text = normalizeAnchorText(value);
return text ? `${label}${text}` : '';
})
.filter((line) => line)
.join('\n');
}
export function buildAgentDraftFoundationAnchorEntries(
session: CustomWorldAgentSessionSnapshot | null | undefined,
): CustomWorldStructuredAnchorEntry[] {
if (!session) {
return [];
}
const anchorContent = session.anchorContent;
return [
{
id: 'world-promise',
label: '世界承诺',
value: normalizeAnchorText(anchorContent.worldPromise),
},
{
id: 'player-fantasy',
label: '玩家幻想',
value: normalizeAnchorText(anchorContent.playerFantasy),
},
{
id: 'theme-boundary',
label: '主题边界',
value: normalizeAnchorText(anchorContent.themeBoundary),
},
{
id: 'player-entry-point',
label: '玩家切入口',
value: normalizeAnchorText(anchorContent.playerEntryPoint),
},
{
id: 'core-conflict',
label: '核心冲突',
value: normalizeAnchorText(anchorContent.coreConflict),
},
{
id: 'key-relationships',
label: '关键关系',
value: normalizeAnchorText(anchorContent.keyRelationships),
},
{
id: 'hidden-lines',
label: '暗线与揭示',
value: normalizeAnchorText(anchorContent.hiddenLines),
},
{
id: 'iconic-elements',
label: '标志元素',
value: normalizeAnchorText(anchorContent.iconicElements),
},
].filter((entry) => entry.value.trim());
}
type AgentDraftFoundationStepDefinition = {
id: string;
label: string;
detail: string;
matchers: string[];
minProgress: number;
expectedDurationMs: number;
};
type AgentDraftFoundationFailedStep = {
id: string;
label: string;
detail: string;
};
// 这里按真实服务端 phaseLabel 归并步骤,避免把草稿生成硬折成 4 个失真的阶段。
const AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS = [
{
id: 'queue',
label: '接收生成请求',
detail: '正在校验当前锚点并准备底稿编译链路。',
matchers: ['已接收请求'],
minProgress: 0,
expectedDurationMs: 3_000,
},
{
id: 'framework',
label: '整理世界骨架',
detail: '正在生成第一版世界框架、主题与核心冲突。',
matchers: ['整理世界骨架', '生成世界底稿'],
minProgress: 12,
expectedDurationMs: 25_000,
},
{
id: 'playable-outline',
label: '生成可扮演角色',
detail: '正在补出玩家视角角色的首轮名单与定位。',
matchers: ['生成可扮演角色'],
minProgress: 16,
expectedDurationMs: 18_000,
},
{
id: 'story-outline',
label: '生成场景角色',
detail: '正在整理关键 NPC、势力接口人与关系入口。',
matchers: ['生成场景角色'],
minProgress: 30,
expectedDurationMs: 45_000,
},
{
id: 'landmark-seed',
label: '生成关键场景',
detail: '正在补出关键场景、幕 NPC 与地点连接。',
matchers: ['生成关键场景'],
minProgress: 44,
expectedDurationMs: 36_000,
},
{
id: 'playable-detail',
label: '补全可扮演角色细节',
detail: '正在补全可扮演角色的叙事基础与档案细节。',
matchers: ['补全可扮演角色'],
minProgress: 66,
expectedDurationMs: 32_000,
},
{
id: 'story-detail',
label: '补全场景角色细节',
detail: '正在补全场景角色的叙事基础与档案细节。',
matchers: ['补全场景角色'],
minProgress: 84,
expectedDurationMs: 65_000,
},
{
id: 'finalize',
label: '编译世界底稿',
detail: '正在把分批生成结果汇总成第一版可浏览的世界底稿。',
matchers: ['编译世界底稿'],
minProgress: 97,
expectedDurationMs: 6_000,
},
{
id: 'role-visuals',
label: '生成角色主形象',
detail: '正在为关键角色补主形象预览资源。',
matchers: ['生成角色主形象'],
minProgress: 97,
expectedDurationMs: 85_000,
},
{
id: 'act-backgrounds',
label: '生成幕背景图',
detail: '正在为场景章节的每一幕补背景图预览资源。',
matchers: ['生成幕背景图'],
minProgress: 98,
expectedDurationMs: 85_000,
},
{
id: 'cards',
label: '编译草稿卡',
detail: '正在整理世界卡、角色卡、地点卡与详情结构。',
matchers: ['编译草稿卡'],
minProgress: 99,
expectedDurationMs: 15_000,
},
{
id: 'workspace',
label: '准备结果页',
detail: '正在写回草稿数据,并打开可继续完善的结果页。',
matchers: ['世界底稿已生成'],
minProgress: 100,
expectedDurationMs: 4_000,
},
] as const satisfies ReadonlyArray<AgentDraftFoundationStepDefinition>;
const AGENT_DRAFT_FOUNDATION_FAILED_STEP = {
id: 'failed',
label: '生成失败',
detail: '这一轮世界草稿没有编译完成,可以返回工作区补充设定后重试。',
} as const satisfies AgentDraftFoundationFailedStep;
function clampProgress(progress: number | null | undefined) {
if (typeof progress !== 'number' || Number.isNaN(progress)) {
return 0;
}
return Math.max(0, Math.min(100, Math.round(progress)));
}
function resolveAgentDraftFoundationStepIndexByProgress(progress: number) {
let matchedIndex = 0;
for (
let index = 0;
index < AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS.length - 1;
index += 1
) {
const step = AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[index];
if (step && progress >= step.minProgress) {
matchedIndex = index;
}
}
return matchedIndex;
}
function resolveFailedProgress(
operation: CustomWorldAgentOperationRecord,
activeStepIndex: number,
) {
const progress = clampProgress(operation.progress);
if (operation.status !== 'failed') {
return progress;
}
if (progress < 100) {
return progress;
}
const activeStep =
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[activeStepIndex] ??
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[0];
return Math.max(0, Math.min(99, activeStep.minProgress));
}
function parseOperationUpdatedAtMs(
operation: CustomWorldAgentOperationRecord,
) {
const rawUpdatedAt = operation.updatedAt?.trim();
if (!rawUpdatedAt) {
return null;
}
const parsedMs = Date.parse(rawUpdatedAt);
return Number.isFinite(parsedMs) ? parsedMs : null;
}
function parseOperationStartedAtMs(
operation: CustomWorldAgentOperationRecord,
) {
const rawStartedAt = operation.startedAt?.trim();
if (!rawStartedAt) {
return null;
}
const parsedMs = Date.parse(rawStartedAt);
return Number.isFinite(parsedMs) ? parsedMs : null;
}
function resolveAgentDraftFoundationStepIndex(
operation: CustomWorldAgentOperationRecord,
) {
const progress = clampProgress(operation.progress);
const phaseLabel = operation.phaseLabel.trim();
if (operation.status === 'completed' || phaseLabel.includes('世界底稿已生成')) {
return AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS.length - 1;
}
for (
let index = AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS.length - 2;
index >= 0;
index -= 1
) {
const step = AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[index];
if (step?.matchers.some((matcher) => phaseLabel.includes(matcher))) {
return index;
}
}
return resolveAgentDraftFoundationStepIndexByProgress(progress);
}
function resolveAgentDraftFoundationFailedStep(
operation: CustomWorldAgentOperationRecord,
) {
if (operation.status !== 'failed') {
return null;
}
const phaseLabel = operation.phaseLabel.trim();
const phaseDetail = operation.phaseDetail.trim();
const error = operation.error?.trim() ?? '';
return {
id: AGENT_DRAFT_FOUNDATION_FAILED_STEP.id,
label:
phaseLabel ||
error ||
AGENT_DRAFT_FOUNDATION_FAILED_STEP.label,
detail:
phaseDetail ||
error ||
AGENT_DRAFT_FOUNDATION_FAILED_STEP.detail,
} satisfies AgentDraftFoundationFailedStep;
}
function buildAgentDraftFoundationSteps(
operation: CustomWorldAgentOperationRecord,
activeStepIndex: number,
) {
return AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS.map((step, index) => {
const isCompleted =
operation.status === 'completed' ||
(operation.status === 'failed'
? index < activeStepIndex
: index < activeStepIndex);
const isActive =
operation.status !== 'failed' && !isCompleted && index === activeStepIndex;
return {
id: step.id,
label: step.label,
detail: step.detail,
completed: isCompleted ? 1 : 0,
total: 1,
status: isCompleted ? 'completed' : isActive ? 'active' : 'pending',
} satisfies CustomWorldGenerationStep;
});
}
function resolveEstimatedRemainingMs(
progress: number,
startedAtMs: number | null,
nowMs: number,
status: CustomWorldAgentOperationRecord['status'],
activeStepIndex: number,
operationUpdatedAtMs: number | null,
) {
if (status === 'completed') {
return 0;
}
if (status === 'failed' || progress >= 100) {
return null;
}
const activeStep =
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[activeStepIndex] ??
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[0];
const nextStep =
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[activeStepIndex + 1] ??
activeStep;
const phaseProgressRange = Math.max(
1,
nextStep.minProgress - activeStep.minProgress,
);
const phaseProgressRatio = Math.max(
0,
Math.min(0.95, (progress - activeStep.minProgress) / phaseProgressRange),
);
const phaseStartedAtMs = operationUpdatedAtMs ?? startedAtMs;
const currentPhaseElapsedMs = phaseStartedAtMs
? Math.max(0, nowMs - phaseStartedAtMs)
: 0;
const currentPhaseRemainingMs = Math.max(
0,
Math.round(
activeStep.expectedDurationMs * (1 - phaseProgressRatio) -
currentPhaseElapsedMs,
),
);
const followingStepsRemainingMs = AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS.slice(
activeStepIndex + 1,
).reduce((sum, step) => sum + step.expectedDurationMs, 0);
return currentPhaseRemainingMs + followingStepsRemainingMs;
}
export function isDraftFoundationOperation(
operation: CustomWorldAgentOperationRecord | null | undefined,
): operation is CustomWorldAgentOperationRecord {
return Boolean(operation && operation.type === 'draft_foundation');
}
export function isDraftFoundationOperationRunning(
operation: CustomWorldAgentOperationRecord | null | undefined,
) {
return (
isDraftFoundationOperation(operation) &&
(operation.status === 'queued' || operation.status === 'running')
);
}
export function buildAgentDraftFoundationGenerationProgress(
operation: CustomWorldAgentOperationRecord | null | undefined,
fallbackStartedAtMs: number | null,
nowMs = Date.now(),
): CustomWorldGenerationProgress | null {
if (!isDraftFoundationOperation(operation)) {
return null;
}
const activeStepIndex = resolveAgentDraftFoundationStepIndex(operation);
const overallProgress = resolveFailedProgress(operation, activeStepIndex);
// 中文注释:总耗时必须绑定服务端 operation 创建时间,避免刷新或前端重挂载后重新计时。
const startedAtMs = parseOperationStartedAtMs(operation) ?? fallbackStartedAtMs;
const elapsedMs = startedAtMs ? Math.max(0, nowMs - startedAtMs) : 0;
const estimatedRemainingMs = resolveEstimatedRemainingMs(
overallProgress,
startedAtMs,
nowMs,
operation.status,
activeStepIndex,
parseOperationUpdatedAtMs(operation),
);
const failedStep = resolveAgentDraftFoundationFailedStep(operation);
const activeStep =
failedStep ??
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[activeStepIndex] ??
AGENT_DRAFT_FOUNDATION_STEP_DEFINITIONS[0];
return {
phaseId: activeStep.id,
phaseLabel: operation.phaseLabel || activeStep.label,
phaseDetail: operation.phaseDetail || activeStep.detail,
batchLabel: activeStep.label,
overallProgress,
completedWeight: overallProgress,
totalWeight: 100,
elapsedMs,
estimatedRemainingMs,
activeStepIndex,
steps: buildAgentDraftFoundationSteps(operation, activeStepIndex),
};
}
export function buildAgentDraftFoundationSettingText(
session: CustomWorldAgentSessionSnapshot | null | undefined,
) {
if (!session) {
return '';
}
const creatorIntent = normalizeCustomWorldCreatorIntent(
session.creatorIntent,
'freeform',
);
if (creatorIntent) {
const foundationText =
buildCustomWorldCreatorIntentFoundationText(creatorIntent).trim();
if (foundationText) {
return foundationText;
}
if (creatorIntent.rawSettingText.trim()) {
return creatorIntent.rawSettingText.trim();
}
}
const latestUserMessage = [...session.messages]
.reverse()
.find((message) => message.role === 'user' && message.text.trim());
const anchorSettingText = buildEightAnchorFoundationText(session.anchorContent);
return (
anchorSettingText ||
latestUserMessage?.text.trim() ||
'正在整理当前共创设定。'
);
}