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,246 @@
import { useMemo, useState } from 'react';
import type { Character, CustomWorldProfile } from '../../types';
import {
CustomWorldEntityCatalog,
type ResultTab,
} from '../CustomWorldEntityCatalog';
import RpgCreationEntityEditorModal from '../rpg-creation-editor/RpgCreationEntityEditorModal';
import RpgCreationAssetDebugPanel, {
shouldEnableRpgCreationAssetDebugPanel,
} from './RpgCreationAssetDebugPanel';
import RpgCreationResultActionBar from './RpgCreationResultActionBar';
import RpgCreationResultHeader from './RpgCreationResultHeader';
import { useRpgCreationResultActions } from './useRpgCreationResultActions';
import type { EntityGenerationKind } from './useRpgCreationResultActions';
export interface RpgCreationResultViewProps {
profile: CustomWorldProfile;
previewCharacters: Character[];
isGenerating: boolean;
progress: number;
progressLabel: string;
error: string | null;
onBack: () => void;
onEditSetting?: () => void;
onRegenerate?: () => void;
onContinueExpand?: () => void;
onEnterWorld?: () => void;
onOpenCoverEditor?: () => void;
onPublishWorld?: () => Promise<void> | void;
onTestWorld?: () => void;
onDeleteEntities?: (kind: 'story' | 'landmark', ids: string[]) => Promise<void> | void;
onGenerateEntity?:
| ((kind: EntityGenerationKind) => Promise<{ profile?: CustomWorldProfile | null } | void> | { profile?: CustomWorldProfile | null } | void)
| undefined;
onProfileChange: (profile: CustomWorldProfile) => void;
readOnly?: boolean;
backLabel?: string;
editActionLabel?: string;
regenerateActionLabel?: string;
enterWorldActionLabel?: string;
autoSaveState?: 'idle' | 'saving' | 'saved' | 'error';
compactAgentResultMode?: boolean;
publishReady?: boolean;
publishBlockers?: string[];
qualityFindings?: Array<{
id: string;
severity: 'info' | 'warning' | 'blocker';
code: string;
targetId?: string | null;
message: string;
}>;
previewSourceLabel?: string | null;
}
export function RpgCreationResultView({
profile,
previewCharacters,
isGenerating,
progress,
progressLabel,
error,
onBack,
onEditSetting,
onRegenerate: triggerRegenerate,
onContinueExpand,
onOpenCoverEditor,
onPublishWorld,
onTestWorld,
onDeleteEntities,
onEnterWorld,
onGenerateEntity,
onProfileChange,
readOnly = false,
backLabel = '返回',
editActionLabel = '修改设定',
regenerateActionLabel = '重新生成',
enterWorldActionLabel = '进入世界',
autoSaveState = 'idle',
compactAgentResultMode = false,
publishReady = true,
publishBlockers = [],
qualityFindings = [],
}: RpgCreationResultViewProps) {
const [activeTab, setActiveTab] = useState<ResultTab>('world');
const assetDebugEnabled = useMemo(
() => shouldEnableRpgCreationAssetDebugPanel(),
[],
);
const {
closeEditorTarget,
createLabel,
createTarget,
editorTarget,
handleDeleteLandmarks,
handleDeleteStoryNpcs,
handleGenerateEntity,
handleRegenerate,
localGenerationError,
pendingGeneratedEntity,
recentGeneratedIds,
setEditorTarget,
} = useRpgCreationResultActions({
activeTab,
agentEntityGenerator: onGenerateEntity
? async (kind) => {
return onGenerateEntity(kind);
}
: undefined,
isGenerating,
onProfileChange,
profile,
readOnly,
triggerRegenerate,
});
const deleteStoryNpcs = onDeleteEntities
? (ids: string[]) => {
void onDeleteEntities('story', ids);
}
: handleDeleteStoryNpcs;
const deleteLandmarks = onDeleteEntities
? (ids: string[]) => {
void onDeleteEntities('landmark', ids);
}
: handleDeleteLandmarks;
return (
<div className="platform-remap-surface flex h-full min-h-0 flex-col">
<RpgCreationResultHeader
autoSaveState={autoSaveState}
backLabel={backLabel}
isGenerating={isGenerating}
onBack={onBack}
/>
<div className="min-h-0 flex-1 overflow-hidden">
<CustomWorldEntityCatalog
profile={profile}
previewCharacters={previewCharacters}
activeTab={activeTab}
onActiveTabChange={setActiveTab}
onEditTarget={setEditorTarget}
onProfileChange={onProfileChange}
onDeleteStoryNpcs={deleteStoryNpcs}
onDeleteLandmarks={deleteLandmarks}
createActionLabel={
readOnly || (compactAgentResultMode && !onGenerateEntity)
? undefined
: createLabel
}
onCreateAction={
readOnly || (compactAgentResultMode && !onGenerateEntity) || !createTarget
? undefined
: () => {
if (activeTab === 'playable') {
void handleGenerateEntity('playable');
return;
}
if (activeTab === 'story') {
void handleGenerateEntity('story');
return;
}
if (activeTab === 'landmarks') {
void handleGenerateEntity('landmark');
return;
}
setEditorTarget(createTarget);
}
}
createActionDisabled={Boolean(
isGenerating || pendingGeneratedEntity,
)}
pendingGeneratedEntity={pendingGeneratedEntity}
recentGeneratedIds={recentGeneratedIds}
readOnly={readOnly}
/>
</div>
{isGenerating && (
<div className="platform-banner platform-banner--info mt-3 rounded-2xl px-4 py-4">
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-semibold text-[var(--platform-text-strong)]">
{progressLabel}
</div>
<div className="text-xs text-[var(--platform-text-base)]">
{Math.round(progress)}%
</div>
</div>
<div className="platform-progress-track mt-3 h-3 overflow-hidden rounded-full">
<div
className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_48%,#ffd2a6_100%)] transition-[width] duration-300"
style={{ width: `${Math.max(0, Math.min(100, progress))}%` }}
/>
</div>
</div>
)}
{error ? (
<div className="platform-banner platform-banner--danger mt-3 rounded-2xl text-sm leading-6">
{error}
</div>
) : null}
{!error &&
compactAgentResultMode &&
publishBlockers.length <= 0 &&
qualityFindings.some((entry) => entry.severity === 'warning') ? (
<div className="platform-banner platform-banner--info mt-3 rounded-2xl text-sm leading-6">
{qualityFindings.filter((entry) => entry.severity === 'warning').length} warning
</div>
) : null}
{!error && localGenerationError ? (
<div className="platform-banner platform-banner--danger mt-3 rounded-2xl text-sm leading-6">
{localGenerationError}
</div>
) : null}
{assetDebugEnabled ? <RpgCreationAssetDebugPanel profile={profile} /> : null}
<RpgCreationResultActionBar
editActionLabel={editActionLabel}
enterWorldActionLabel={enterWorldActionLabel}
isGenerating={isGenerating}
onContinueExpand={onContinueExpand}
onEditSetting={onEditSetting}
onEnterWorld={onEnterWorld}
onOpenCoverEditor={
onOpenCoverEditor ?? (() => setEditorTarget({ kind: 'cover' }))
}
onPublishWorld={onPublishWorld}
onTestWorld={onTestWorld}
onRegenerate={triggerRegenerate ? handleRegenerate : undefined}
profile={profile}
regenerateActionLabel={regenerateActionLabel}
publishReady={publishReady}
publishBlockers={publishBlockers}
/>
<RpgCreationEntityEditorModal
profile={profile}
target={editorTarget}
onClose={closeEditorTarget}
onProfileChange={onProfileChange}
/>
</div>
);
}