feat(engine): merge cloudflare-migration — paradigm D engine, BYOK proxy, story persistence (#95)

Squash-merge the cloudflare-migration branch (7 commits by Kai ki) into
staging with conflict resolution, feature integration, and bug fixes.

Engine:
- Paradigm D: single-stream Writer replacing dual-phase Plan/Beats
- Delete Architect agent; story bible generated via Writer <plan> tag
- Modular prompt architecture (segments/registry/builder)
- StreamRouter for tagged stream splitting (<plan>/<story>/<choices>)

Infrastructure:
- Cloudflare Workers deployment (wrangler.jsonc, OpenNext adapter)
- D1 database schema + Drizzle ORM (scaffolded, not yet active)
- R2 storage helpers (scaffolded, not yet active)
- Story persistence API routes + client-side persistence

BYOK (Bring Your Own Key):
- /api/llm/user-proxy with SSRF-protected LLM proxy (+ requireUser auth)
- CORS-aware fetch in ai-client: auto-detect CORS failure, fallback to
  server proxy transparently via OpenAI SDK custom fetch
- BYO config support added to classify-freeform and vision routes
- SettingsModal CORS privacy notice (keys never logged/stored)

SSE streaming:
- engineClient.ts: fetchSSE helper for progressive scene events
- startSession/requestScene accept optional emit callback
- Fix SSE error event field name (error → message) in scene/start routes

i18n integration:
- Wire buildLanguageDirective into paradigm D's prompt builder
- Update corsNotice i18n keys (zh-CN/en/ja) with CORS proxy privacy text
- Preserve Session.language + LanguageSwitcher from i18n commit

Co-authored-by: Kai ki <155355644+zbf1009@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zonghao Yuan
2026-06-18 18:05:38 +08:00
committed by GitHub
parent 05bd7e229c
commit 0e4c2ebef4
78 changed files with 7396 additions and 919 deletions
+14 -3
View File
@@ -7,6 +7,7 @@ import {
} from "@infiplot/tts-client";
import type {
Character,
CharacterIntent,
CharacterVoice,
EngineConfig,
Session,
@@ -55,6 +56,7 @@ async function runDesignLLM(
config: EngineConfig,
session: Session,
charName: string,
intent?: CharacterIntent,
): Promise<CharacterDesignOutput> {
const raw = await chat(
config.text,
@@ -62,12 +64,20 @@ async function runDesignLLM(
{ role: "system", content: buildCharacterDesignerSystem({ stepfun: stepfunEnabled(config) }) },
{
role: "user",
content: buildCharacterDesignerUserMessage(charName, session),
content: buildCharacterDesignerUserMessage(charName, session, intent),
},
],
{ temperature: 0.7, tag: "character-designer" },
);
return parseJsonLoose<CharacterDesignOutput>(raw);
// parseJsonLoose can throw on irreparable JSON; degrade to an empty card so
// designCharacterCard's fallbacks (name-inference voice, no portrait) kick in.
try {
return parseJsonLoose<CharacterDesignOutput>(raw);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.error(`[characterDesigner] design JSON parse failed for ${charName}: ${msg}`);
return {};
}
}
/** True when the server's TTS config points at StepFun (so the CharacterDesigner
@@ -155,9 +165,10 @@ export async function designCharacterCard(
config: EngineConfig,
session: Session,
charName: string,
intent?: CharacterIntent,
): Promise<CharacterCard> {
const tDesign = Date.now();
const design = await runDesignLLM(config, session, charName);
const design = await runDesignLLM(config, session, charName, intent);
tlog(`[charDesigner ${charName}] design LLM`, tDesign);
// Drop invalid catalog picks before they reach provision/synth. A hallucinated