Fix DashScope env loading for scene image generation
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user