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:
94
src/services/puzzle-works/puzzleHistoryAsset.ts
Normal file
94
src/services/puzzle-works/puzzleHistoryAsset.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
const PUZZLE_HISTORY_ASSET_FALLBACK_NAME = '历史拼图素材';
|
||||
|
||||
function safeDecodePathSegment(value: string) {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function parsePuzzleHistoryTimestamp(value: string) {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const directDate = new Date(trimmed);
|
||||
if (!Number.isNaN(directDate.getTime())) {
|
||||
return directDate;
|
||||
}
|
||||
|
||||
const numericMatch = /^(-?\d+)(?:\.(\d{1,9}))?Z?$/u.exec(trimmed);
|
||||
if (!numericMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wholeSeconds = Number.parseInt(numericMatch[1] ?? '', 10);
|
||||
if (!Number.isFinite(wholeSeconds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fractionalMillis = Number.parseInt(
|
||||
(numericMatch[2] ?? '').padEnd(3, '0').slice(0, 3) || '0',
|
||||
10,
|
||||
);
|
||||
const normalizedMillis = Number.isFinite(fractionalMillis)
|
||||
? fractionalMillis
|
||||
: 0;
|
||||
const useMilliseconds =
|
||||
Math.abs(wholeSeconds) >= 100_000_000_000 ||
|
||||
(numericMatch[1] ?? '').length > 10;
|
||||
const timestampMs = useMilliseconds
|
||||
? wholeSeconds + (wholeSeconds < 0 ? -normalizedMillis : normalizedMillis)
|
||||
: wholeSeconds * 1000 +
|
||||
(wholeSeconds < 0 ? -normalizedMillis : normalizedMillis);
|
||||
|
||||
return new Date(timestampMs);
|
||||
}
|
||||
|
||||
export function getPuzzleHistoryAssetDisplayName(
|
||||
imageSrc: string | null | undefined,
|
||||
) {
|
||||
const trimmed = imageSrc?.trim() ?? '';
|
||||
if (!trimmed) {
|
||||
return PUZZLE_HISTORY_ASSET_FALLBACK_NAME;
|
||||
}
|
||||
|
||||
const pathOnly = trimmed.split(/[?#]/u)[0]?.trim() ?? '';
|
||||
if (!pathOnly) {
|
||||
return PUZZLE_HISTORY_ASSET_FALLBACK_NAME;
|
||||
}
|
||||
|
||||
const fileName = pathOnly.replace(/^\/+/u, '').split('/').filter(Boolean).pop();
|
||||
const displayName = safeDecodePathSegment(fileName ?? '').trim();
|
||||
return displayName || PUZZLE_HISTORY_ASSET_FALLBACK_NAME;
|
||||
}
|
||||
|
||||
export function formatPuzzleHistoryAssetCreatedAt(value: string) {
|
||||
const parsedDate = parsePuzzleHistoryTimestamp(value);
|
||||
if (!parsedDate) {
|
||||
return '未知时间';
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
}).format(parsedDate);
|
||||
}
|
||||
|
||||
export function getPuzzleHistoryAssetReferenceLabel(
|
||||
imageSrc: string | null | undefined,
|
||||
) {
|
||||
const displayName = getPuzzleHistoryAssetDisplayName(imageSrc);
|
||||
if (displayName === PUZZLE_HISTORY_ASSET_FALLBACK_NAME) {
|
||||
return '历史素材';
|
||||
}
|
||||
|
||||
return `历史素材 · ${displayName}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user