feat(engine): auto-select art style via parallel LLM call
When user picks "自动", the client sends styleGuide="auto" to the server. The orchestrator then runs a lightweight style-selector LLM call in parallel with the Architect — both only depend on worldSetting, so there is zero added latency. The selector picks the best-matching preset from STYLE_MAP based on genre, mood, and setting. Also moves STYLE_MAP from page.tsx to lib/options.ts so it can be shared between client and server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { chat } from "@infiplot/ai-client";
|
||||
import type { ProviderConfig } from "@infiplot/types";
|
||||
import { STYLE_MAP } from "@/lib/options";
|
||||
|
||||
const STYLE_NAMES = Object.keys(STYLE_MAP);
|
||||
|
||||
const SYSTEM = `You are an art director for a visual novel. Given the story premise, pick the single best-matching art style from the list below. Consider the genre, mood, setting, and target audience.
|
||||
|
||||
Available styles:
|
||||
${STYLE_NAMES.map((s) => `- ${s}`).join("\n")}
|
||||
|
||||
Reply with ONLY the style name, nothing else. If uncertain, default to 吉卜力.`;
|
||||
|
||||
export async function selectStyle(
|
||||
textConfig: ProviderConfig,
|
||||
worldSetting: string,
|
||||
): Promise<string> {
|
||||
const result = await chat(
|
||||
textConfig,
|
||||
[
|
||||
{ role: "system", content: SYSTEM },
|
||||
{ role: "user", content: worldSetting },
|
||||
],
|
||||
{ temperature: 0, tag: "styleSelector" },
|
||||
);
|
||||
|
||||
const picked = result.trim();
|
||||
if (STYLE_MAP[picked]) {
|
||||
return STYLE_MAP[picked];
|
||||
}
|
||||
const fuzzy = STYLE_NAMES.find((s) => picked.includes(s) || s.includes(picked));
|
||||
if (fuzzy) {
|
||||
return STYLE_MAP[fuzzy]!;
|
||||
}
|
||||
console.warn(`[styleSelector] unrecognized style "${picked}", falling back to 吉卜力`);
|
||||
return STYLE_MAP["吉卜力"]!;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
} from "@infiplot/types";
|
||||
import { coerceOrientation } from "@infiplot/types";
|
||||
import { runArchitect } from "./agents/architect";
|
||||
import { selectStyle } from "./agents/styleSelector";
|
||||
import { directInsertBeat, directScene } from "./director";
|
||||
import { synthesizeBeat } from "./voice";
|
||||
import { interpret } from "./vision";
|
||||
@@ -52,16 +53,25 @@ export async function startSession(
|
||||
orientation: coerceOrientation(req.orientation),
|
||||
};
|
||||
|
||||
// Stage 0 — Architect: expand the terse world/style prompt into a story
|
||||
// bible BEFORE the first scene. Serial by necessity (the opening Writer
|
||||
// reads session.storyState), but it gives the whole story a spine from beat
|
||||
// one — the latency is offset by the director's portrait/voice overlap win.
|
||||
// Stage 0 — Architect (+ optional auto style selection, in parallel).
|
||||
// Both only depend on worldSetting, so they run concurrently.
|
||||
console.log(
|
||||
`[start] worldSetting (${session.worldSetting.length} chars):\n${session.worldSetting}`,
|
||||
);
|
||||
const isAutoStyle = session.styleGuide === "auto";
|
||||
const tArchitect = Date.now();
|
||||
session.storyState = await runArchitect(config.text, session);
|
||||
tlog("[start] Architect", tArchitect);
|
||||
const [architectResult, autoStyleGuide] = await Promise.all([
|
||||
runArchitect(config.text, session),
|
||||
isAutoStyle
|
||||
? selectStyle(config.text, session.worldSetting)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
session.storyState = architectResult;
|
||||
if (isAutoStyle && autoStyleGuide) {
|
||||
session.styleGuide = autoStyleGuide;
|
||||
console.log(`[start] auto-selected style: ${autoStyleGuide.slice(0, 60)}…`);
|
||||
}
|
||||
tlog("[start] Architect" + (isAutoStyle ? " + StyleSelector" : ""), tArchitect);
|
||||
console.log(
|
||||
`[start] storyBible: logline="${session.storyState.logline}" | genreTags="${session.storyState.genreTags}" | synopsis="${session.storyState.synopsis}"`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user