feat(web): gender-differentiated 4:5 covers + per-card styleGuide prebake
- Regenerate 60 covers (30 male + 30 female) via FLUX with story-specific prompts, replacing the prior gender-shared set - Crop covers to 4:5 (960×1200) via sharp attention cover; matches new homepage card aspectRatio - Persist all 60 prompts to public/home/prompts.json so the prebake step can reuse the cover's exact visual anchor (per-card styleGuide) and the first-act scene visually carries over from the poster the player clicked - Restore /play?card= prebaked instant-play path on homepage card click - Add OpenAI-compatible image route in ai-client for non-Runware endpoints - Hide Next.js dev indicators globally; tweak F-key fullscreen label Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@@ -66,3 +66,16 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide Next.js dev compiler indicators and static overlays completely */
|
||||
next-route-announcer,
|
||||
nextjs-portal,
|
||||
#nextjs-dev-indicator,
|
||||
.nextjs-static-indicator,
|
||||
div[data-nextjs-toast-wrapper],
|
||||
div[data-nextjs-toast] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
opacity: 0 !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
@@ -962,7 +962,7 @@ function PlayInner() {
|
||||
title="全屏 (F)"
|
||||
>
|
||||
<i className="fa-solid fa-expand text-[10px]" />
|
||||
F · 全 · 屏
|
||||
F · 键 · 全 · 屏
|
||||
</button>
|
||||
}
|
||||
aboveCanvasLeft={
|
||||
|
||||
@@ -71,6 +71,49 @@ export async function generateImage(
|
||||
): Promise<GenerateImageResult> {
|
||||
const url = config.baseUrl.replace(/\/$/, "");
|
||||
|
||||
// 1. OpenAI-compatible route (GPTGod, DALL-E, etc.)
|
||||
const isOpenAi = !url.includes("runware.ai") || config.model === "image-2-vip";
|
||||
if (isOpenAi) {
|
||||
const endpoint = url.endsWith("/images/generations") ? url : `${url}/images/generations`;
|
||||
console.log(`[ai-client] Calling OpenAI-compatible image generations at: ${endpoint} with model: ${config.model}`);
|
||||
|
||||
const res = await fetchWithRetry(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
prompt: prompt,
|
||||
n: 1,
|
||||
size: "1792x1024", // Use horizontal size (16:9)
|
||||
}),
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
let json: any;
|
||||
try {
|
||||
json = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error(`OpenAI Image API error ${res.status}: ${text.slice(0, 500)}`);
|
||||
}
|
||||
|
||||
if (json.error) {
|
||||
throw new Error(`OpenAI Image API error: ${json.error.message || JSON.stringify(json.error)}`);
|
||||
}
|
||||
|
||||
const data = json.data?.[0];
|
||||
const imageUrl = data?.url;
|
||||
if (!imageUrl) {
|
||||
throw new Error(`No image URL in OpenAI response: ${text.slice(0, 300)}`);
|
||||
}
|
||||
// Generate a mock UUID since OpenAI compatible endpoint doesn't have UUIDs
|
||||
const imageUuid = crypto.randomUUID();
|
||||
return { imageUrl, imageUuid };
|
||||
}
|
||||
|
||||
// 2. Runware task-array route
|
||||
const task: Record<string, unknown> = {
|
||||
taskType: "imageInference",
|
||||
taskUUID: crypto.randomUUID(),
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 296 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 75 KiB |