feat: Vercel Hobby deploy readiness — image URLs, jsonrepair, DeepSeek

- Move vercel.json to apps/web/ with correct route paths; cap scene route
  maxDuration 120→60s for Hobby. Root vercel.json removed. Vercel project's
  Root Directory must be set to apps/web (Deploy button URL passes this).
- Switch image transport from base64-in-JSON to Runware-hosted URLs:
  generateImage now uses outputType=URL and returns {imageUrl, imageUuid};
  StartResponse/SceneResponse carry imageUrl; VisionRequest carries
  prevImageUrl (server re-fetches the bytes for click annotation). This
  eliminates the 4.5MB serverless body-size risk.
- Painter and director prefer URL over UUID for referenceImages — the UUID
  returned by Runware imageInference isn't always recognized in the refs
  pipeline (surfaces as `failedToTransferImage`).
- Client preloads scene images via `new Image().decode()` before committing
  to React state, so URL transitions render instantly; prefetched scenes
  also warm the HTTP cache.
- jsonParser uses the jsonrepair package (replaces hand-rolled repair) and
  adds a targeted preRepair regex for the missing-key-close-quote pattern
  that jsonrepair couldn't disambiguate. Full raw model output dumped on
  failure for diagnostic visibility.
- Default text provider switched to DeepSeek v4-flash via direct API
  (significantly more stable JSON than MiMo v2.5-pro). VISION/TTS stay on
  MiMo (DeepSeek has no multimodal / TTS offerings).
- next.config: drop dead experimental.serverActions.bodySizeLimit (no
  server actions used).
- README: real Deploy button URL (zonghaoyuan/yume + root-directory=apps/web
  + TTS/MOCK_IMAGE in env list); refreshed env vars table with optional
  TTS section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-01 16:04:13 +08:00
parent a426b82275
commit addbede929
21 changed files with 392 additions and 325 deletions
+7 -7
View File
@@ -159,7 +159,7 @@ function ChoiceButton({
// ── Main component ─────────────────────────────────────────────────────
export function PlayCanvas({
imageBase64,
imageUrl,
audioBase64,
audioMime,
muted,
@@ -171,7 +171,7 @@ export function PlayCanvas({
onSelectChoice,
fullViewport = false,
}: {
imageBase64: string | null;
imageUrl: string | null;
audioBase64: string | null;
audioMime: string | null;
muted: boolean;
@@ -271,7 +271,7 @@ export function PlayCanvas({
});
}
const interactive = phase === "ready" && !!imageBase64;
const interactive = phase === "ready" && !!imageUrl;
const dimmed = phase === "transitioning";
const sizeStyle = fullViewport
@@ -306,16 +306,16 @@ export function PlayCanvas({
/>
)}
{imageBase64 ? (
{imageUrl ? (
<div
className="relative inline-block"
style={{ boxShadow: fullViewport ? "none" : SHADOW }}
>
{/* Background image */}
{/* Background image — Runware CDN URL or data URI (mock mode) */}
<img
key={imageBase64.slice(-48)}
key={imageUrl.slice(-48)}
ref={imgRef}
src={`data:image/png;base64,${imageBase64}`}
src={imageUrl}
alt="Generated scene"
onClick={handleImageClick}
onLoad={(e) => {