Fix DashScope env loading for scene image generation

This commit is contained in:
2026-04-06 15:01:15 +08:00
parent fcd8d727b0
commit d678929064
23 changed files with 4943 additions and 138 deletions

View File

@@ -1,13 +1,19 @@
import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets';
import {
buildCustomWorldPlayableCharacters,
PRESET_CHARACTERS,
} from '../../data/characterPresets';
import {
readSavedCustomWorldProfiles,
upsertSavedCustomWorldProfile,
} from '../../data/customWorldLibrary';
import { getScenePreset } from '../../data/scenePresets';
import { generateCustomWorldProfile } from '../../services/ai';
import {
type CustomWorldGenerationProgress,
generateCustomWorldProfile,
} from '../../services/ai';
import {
type CustomWorldProfile,
type GameState,
@@ -19,12 +25,17 @@ import {
UI_CHROME,
WORLD_SELECT_ICONS,
} from '../../uiAssets';
import { CustomWorldGenerationView } from '../CustomWorldGenerationView';
import { CustomWorldResultView } from '../CustomWorldResultView';
import { DeveloperTeamModal } from '../DeveloperTeamModal';
import { PixelIcon } from '../PixelIcon';
import { CustomWorldCreatorModal } from '../SelectionCustomizationModals';
export type SelectionStage = 'start' | 'world' | 'custom-world-result';
export type SelectionStage =
| 'start'
| 'world'
| 'custom-world-generating'
| 'custom-world-result';
type WorldOnlineCounts = Partial<Record<WorldType, number>>;
@@ -66,6 +77,8 @@ const WORLD_OPTIONS = [
},
] as const;
const GENERATION_PREVIEW_CHARACTERS = PRESET_CHARACTERS.slice(0, 3);
function generateWorldOnlineCounts(): WorldOnlineCounts {
const roll = (base: number) =>
Math.max(100, Math.min(200, base + Math.floor(Math.random() * 19) - 9));
@@ -75,22 +88,6 @@ function generateWorldOnlineCounts(): WorldOnlineCounts {
};
}
function getCustomWorldGenerationLabel(progress: number) {
if (progress >= 96) return '正在完成世界归档...';
if (progress >= 78) return '正在关联地标和关键物品...';
if (progress >= 52) return '正在生成核心角色...';
if (progress >= 28) return '正在生成可玩角色...';
return '正在解析世界设置...';
}
function getCustomWorldProgressLabel(progress: number) {
if (progress >= 96) return '正在完成世界归档...';
if (progress >= 78) return '正在组合场景和视觉效果...';
if (progress >= 52) return '正在生成核心角色...';
if (progress >= 28) return '正在生成可玩角色...';
return '正在解析世界设置...';
}
export function PreGameSelectionFlow({
selectionStage,
setSelectionStage,
@@ -113,7 +110,9 @@ export function PreGameSelectionFlow({
const [customWorldDraft, setCustomWorldDraft] = useState('');
const [customWorldError, setCustomWorldError] = useState<string | null>(null);
const [isGeneratingCustomWorld, setIsGeneratingCustomWorld] = useState(false);
const [customWorldProgress, setCustomWorldProgress] = useState(0);
const [customWorldProgress, setCustomWorldProgress] =
useState<CustomWorldGenerationProgress | null>(null);
const customWorldAbortControllerRef = useRef<AbortController | null>(null);
const previewCustomWorldCharacters = useMemo(
() =>
@@ -186,13 +185,51 @@ export function PreGameSelectionFlow({
}
}, [generatedCustomWorldProfile, selectionStage, setSelectionStage]);
useEffect(
() => () => {
customWorldAbortControllerRef.current?.abort();
},
[],
);
const leaveCustomWorldResult = () => {
setGeneratedCustomWorldProfile(null);
setCustomWorldError(null);
setCustomWorldProgress(0);
setCustomWorldProgress(null);
setSelectionStage('world');
};
const leaveCustomWorldGeneration = () => {
if (isGeneratingCustomWorld) {
return;
}
setCustomWorldError(null);
setCustomWorldProgress(null);
setSelectionStage('world');
};
const openCustomWorldCreator = () => {
if (isGeneratingCustomWorld) {
return;
}
setCustomWorldError(null);
setCustomWorldProgress(null);
setShowCustomWorldModal(true);
};
const editCustomWorldSetting = () => {
if (isGeneratingCustomWorld) {
return;
}
setCustomWorldError(null);
setCustomWorldProgress(null);
setSelectionStage('world');
setShowCustomWorldModal(true);
};
const saveGeneratedCustomWorld = () => {
if (!generatedCustomWorldProfile) {
return;
@@ -212,51 +249,73 @@ export function PreGameSelectionFlow({
handleWorldSelect(WorldType.CUSTOM, generatedCustomWorldProfile);
setGeneratedCustomWorldProfile(null);
setCustomWorldError(null);
setCustomWorldProgress(0);
setCustomWorldProgress(null);
setSelectionStage('world');
};
const createCustomWorld = async () => {
if (isGeneratingCustomWorld) {
return;
}
const settingText = customWorldDraft.trim();
if (!settingText) {
setCustomWorldError('请先输入世界设置。');
return;
}
const abortController = new AbortController();
customWorldAbortControllerRef.current?.abort();
customWorldAbortControllerRef.current = abortController;
setCustomWorldError(null);
setGeneratedCustomWorldProfile(null);
setCustomWorldProgress(null);
setShowCustomWorldModal(false);
setSelectionStage('custom-world-generating');
setIsGeneratingCustomWorld(true);
setCustomWorldProgress(8);
const progressTimer = window.setInterval(() => {
setCustomWorldProgress((current) => {
if (current >= 92) return current;
return Math.min(
92,
current + Math.max(3, Math.round((96 - current) / 5)),
);
});
}, 260);
try {
const profile = await generateCustomWorldProfile(settingText);
window.clearInterval(progressTimer);
setCustomWorldProgress(100);
await new Promise((resolve) => window.setTimeout(resolve, 180));
const profile = await generateCustomWorldProfile(settingText, {
signal: abortController.signal,
onProgress: setCustomWorldProgress,
});
if (abortController.signal.aborted) {
return;
}
setGeneratedCustomWorldProfile(profile);
setShowCustomWorldModal(false);
setCustomWorldError(null);
setSelectionStage('custom-world-result');
} catch (error) {
window.clearInterval(progressTimer);
setCustomWorldProgress(0);
if (abortController.signal.aborted) {
setCustomWorldError('世界生成已中断。你可以返回修改设定,或重新开始。');
return;
}
setCustomWorldError(
error instanceof Error ? error.message : '生成自定义世界失败。',
);
} finally {
if (customWorldAbortControllerRef.current === abortController) {
customWorldAbortControllerRef.current = null;
}
setIsGeneratingCustomWorld(false);
}
};
const interruptCustomWorldGeneration = () => {
if (!isGeneratingCustomWorld || !customWorldAbortControllerRef.current) {
return;
}
const confirmed = window.confirm(
'确认中断当前世界生成吗?本轮未完成的内容不会保留。',
);
if (!confirmed) {
return;
}
customWorldAbortControllerRef.current.abort(new Error('世界生成已中断。'));
};
return (
<>
<AnimatePresence mode="wait">
@@ -296,7 +355,7 @@ export function PreGameSelectionFlow({
setGeneratedCustomWorldProfile(null);
setCustomWorldDraft('');
setCustomWorldError(null);
setCustomWorldProgress(0);
setCustomWorldProgress(null);
setShowCustomWorldModal(false);
setSelectionStage('world');
}}
@@ -495,12 +554,7 @@ export function PreGameSelectionFlow({
<button
type="button"
onClick={() => {
setGeneratedCustomWorldProfile(null);
setCustomWorldError(null);
setCustomWorldProgress(0);
setShowCustomWorldModal(true);
}}
onClick={openCustomWorldCreator}
className="pixel-nine-slice pixel-pressable order-first relative flex min-h-[12.5rem] flex-col items-start justify-between overflow-hidden text-left"
style={getNineSliceStyle(UI_CHROME.panel, {
paddingX: 18,
@@ -533,6 +587,31 @@ export function PreGameSelectionFlow({
</motion.div>
)}
{!gameState.worldType &&
selectionStage === 'custom-world-generating' && (
<motion.div
key="custom-world-generating"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 flex-col"
>
<CustomWorldGenerationView
settingText={customWorldDraft.trim()}
actionPreviewCharacters={GENERATION_PREVIEW_CHARACTERS}
progress={customWorldProgress}
isGenerating={isGeneratingCustomWorld}
error={customWorldError}
onBack={leaveCustomWorldGeneration}
onEditSetting={editCustomWorldSetting}
onRetry={() => {
void createCustomWorld();
}}
onInterrupt={interruptCustomWorldGeneration}
/>
</motion.div>
)}
{!gameState.worldType &&
selectionStage === 'custom-world-result' &&
generatedCustomWorldProfile && (
@@ -547,16 +626,12 @@ export function PreGameSelectionFlow({
profile={generatedCustomWorldProfile}
previewCharacters={previewCustomWorldCharacters}
isGenerating={isGeneratingCustomWorld}
progress={customWorldProgress}
progressLabel={getCustomWorldProgressLabel(customWorldProgress)}
progress={customWorldProgress?.overallProgress ?? 0}
progressLabel={customWorldProgress?.phaseLabel ?? ''}
error={customWorldError}
onProfileChange={setGeneratedCustomWorldProfile}
onBack={leaveCustomWorldResult}
onEditSetting={() => {
setCustomWorldError(null);
setCustomWorldProgress(0);
setShowCustomWorldModal(true);
}}
onEditSetting={editCustomWorldSetting}
onRegenerate={() => {
void createCustomWorld();
}}
@@ -581,8 +656,8 @@ export function PreGameSelectionFlow({
void createCustomWorld();
}}
isGenerating={isGeneratingCustomWorld}
progress={customWorldProgress}
progressLabel={getCustomWorldGenerationLabel(customWorldProgress)}
progress={customWorldProgress?.overallProgress ?? 0}
progressLabel={customWorldProgress?.phaseLabel ?? '正在准备生成'}
error={customWorldError}
/>