Increase VectorEngine timeouts and add image UI
Add VectorEngine image generation config and raise request timeouts (env + scripts) from 180000 to 1000000ms. Introduce a reusable CreativeImageInputPanel component with tests and wire up mobile keyboard-focus helpers; update generation views and related tests (CustomWorldGenerationView, BarkBattle editor, Match3D, Puzzle flows). Improve API error handling / VectorEngine request guidance (packages/shared http.ts and docs), and apply multiple backend/frontend fixes for puzzle/match3d/prompt handling. Also include extensive docs and decision-log updates describing UI/UX decisions and verification steps.
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { motion } from 'motion/react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime';
|
||||
import type { CustomWorldStructuredAnchorEntry } from '../services/customWorldAgentGenerationProgress';
|
||||
@@ -24,6 +26,7 @@ interface CustomWorldGenerationViewProps {
|
||||
pausedBadgeLabel?: string;
|
||||
idleBadgeLabel?: string;
|
||||
structuredEmptyText?: string;
|
||||
hideBatchModule?: boolean;
|
||||
}
|
||||
|
||||
function formatDuration(ms: number) {
|
||||
@@ -86,6 +89,49 @@ function buildFallbackRenderKey(
|
||||
return normalizedValue ? normalizedValue : fallback;
|
||||
}
|
||||
|
||||
function useIsMobileGenerationLayout() {
|
||||
const [isMobile, setIsMobile] = useState(() => {
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
typeof window.matchMedia !== 'function'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return window.matchMedia('(max-width: 639px)').matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === 'undefined' ||
|
||||
typeof window.matchMedia !== 'function'
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia('(max-width: 639px)');
|
||||
const syncMobileLayout = () => {
|
||||
setIsMobile(mediaQuery.matches);
|
||||
};
|
||||
|
||||
syncMobileLayout();
|
||||
|
||||
if (typeof mediaQuery.addEventListener === 'function') {
|
||||
mediaQuery.addEventListener('change', syncMobileLayout);
|
||||
return () => {
|
||||
mediaQuery.removeEventListener('change', syncMobileLayout);
|
||||
};
|
||||
}
|
||||
|
||||
mediaQuery.addListener(syncMobileLayout);
|
||||
return () => {
|
||||
mediaQuery.removeListener(syncMobileLayout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
export function CustomWorldGenerationView({
|
||||
settingText,
|
||||
anchorEntries = [],
|
||||
@@ -107,7 +153,9 @@ export function CustomWorldGenerationView({
|
||||
pausedBadgeLabel = '生成已暂停',
|
||||
idleBadgeLabel = '等待操作',
|
||||
structuredEmptyText = '正在整理当前设定结构,请稍后。',
|
||||
hideBatchModule = false,
|
||||
}: CustomWorldGenerationViewProps) {
|
||||
const isMobileGenerationLayout = useIsMobileGenerationLayout();
|
||||
const progressValue = getProgressPercentage(progress);
|
||||
const steps = progress?.steps ?? [];
|
||||
const hasStructuredAnchors = anchorEntries.length > 0;
|
||||
@@ -116,6 +164,10 @@ export function CustomWorldGenerationView({
|
||||
const normalizedSettingDescription = settingDescription?.trim() ?? '';
|
||||
const hasSettingActionLabel = normalizedSettingActionLabel.length > 0;
|
||||
const hasSettingDescription = normalizedSettingDescription.length > 0;
|
||||
const shouldHideBatchModule =
|
||||
hideBatchModule ||
|
||||
progressTitle === '拼图草稿生成进度' ||
|
||||
progressTitle === '抓大鹅草稿生成进度';
|
||||
const estimatedWaitText =
|
||||
progress?.estimatedRemainingMs != null
|
||||
? `预计还需 ${formatDuration(progress.estimatedRemainingMs)}`
|
||||
@@ -179,28 +231,41 @@ export function CustomWorldGenerationView({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2 sm:grid-cols-3 xl:gap-3">
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-3">
|
||||
<div className="text-[11px] tracking-[0.16em] text-zinc-500">
|
||||
当前批次
|
||||
<div
|
||||
className={`custom-world-generation-stats mt-4 grid gap-2 xl:gap-3 ${
|
||||
shouldHideBatchModule
|
||||
? 'custom-world-generation-stats--two-column grid-cols-2'
|
||||
: 'sm:grid-cols-3'
|
||||
}`}
|
||||
style={
|
||||
shouldHideBatchModule
|
||||
? { gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{shouldHideBatchModule ? null : (
|
||||
<div className="platform-subpanel min-w-0 rounded-2xl px-4 py-3">
|
||||
<div className="text-[11px] tracking-[0.16em] text-zinc-500">
|
||||
当前批次
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-white">
|
||||
{progress?.batchLabel ?? '准备中'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-white">
|
||||
{progress?.batchLabel ?? '准备中'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-3">
|
||||
)}
|
||||
<div className="platform-subpanel min-w-0 rounded-2xl px-3 py-3 sm:px-4">
|
||||
<div className="text-[11px] tracking-[0.16em] text-zinc-500">
|
||||
预计等待
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-white">
|
||||
<div className="mt-1 break-keep text-xs font-semibold text-white sm:text-sm">
|
||||
{estimatedWaitText}
|
||||
</div>
|
||||
</div>
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-3">
|
||||
<div className="platform-subpanel min-w-0 rounded-2xl px-3 py-3 sm:px-4">
|
||||
<div className="text-[11px] tracking-[0.16em] text-zinc-500">
|
||||
计时
|
||||
</div>
|
||||
<div className="mt-1 text-sm font-semibold text-white">
|
||||
<div className="mt-1 break-keep text-xs font-semibold text-white sm:text-sm">
|
||||
{elapsedText}
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,7 +276,7 @@ export function CustomWorldGenerationView({
|
||||
const stepProgress = getStepProgressPercentage(step);
|
||||
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
key={buildFallbackRenderKey(
|
||||
step.id,
|
||||
`progress-step-${index}`,
|
||||
@@ -222,7 +287,23 @@ export function CustomWorldGenerationView({
|
||||
: step.status === 'active'
|
||||
? 'border-sky-300/22 bg-sky-500/10'
|
||||
: 'platform-subpanel'
|
||||
}`}
|
||||
} custom-world-generation-step`}
|
||||
initial={
|
||||
isMobileGenerationLayout
|
||||
? { opacity: 0, x: '-110vw' }
|
||||
: false
|
||||
}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.34,
|
||||
ease: 'easeOut',
|
||||
delay: isMobileGenerationLayout ? index * 0.09 : 0,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
'--generation-step-delay': `${index * 90}ms`,
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 text-sm font-semibold text-white">
|
||||
@@ -248,7 +329,7 @@ export function CustomWorldGenerationView({
|
||||
<div className="mt-2 text-xs leading-6 text-zinc-400">
|
||||
{step.detail}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user