feat(web,engine): custom style — image upload, AI-extract prompt, painter ref
自定义画风入口里加上传按钮:客户端把图缩到 512px webp(base64),传到新 路由 /api/parse-style-image,vision LLM 解析成英文 style prompt 回填 textarea; 图本身随 sessionStorage → /api/start → Session.styleReferenceImage 透传, painter.collectReferenceImages 把它置于 slot 0,整局每一幕都作为 reference 图锚定画风(brush / color / mood),比 priorScene 优先级更高。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+25
-3
@@ -500,7 +500,11 @@ function PlayInner() {
|
||||
const presetId = params.get("preset");
|
||||
const isCustom = params.get("custom") === "1";
|
||||
|
||||
let livePayload: { worldSetting: string; styleGuide: string } | null = null;
|
||||
let livePayload: {
|
||||
worldSetting: string;
|
||||
styleGuide: string;
|
||||
styleReferenceImage?: string;
|
||||
} | null = null;
|
||||
if (!cardName) {
|
||||
if (presetId) {
|
||||
const p = PRESETS.find((x) => x.id === presetId);
|
||||
@@ -513,8 +517,13 @@ function PlayInner() {
|
||||
worldSetting: string;
|
||||
styleGuide: string;
|
||||
audioEnabled?: boolean;
|
||||
styleReferenceImage?: string;
|
||||
};
|
||||
livePayload = {
|
||||
worldSetting: parsed.worldSetting,
|
||||
styleGuide: parsed.styleGuide,
|
||||
styleReferenceImage: parsed.styleReferenceImage || undefined,
|
||||
};
|
||||
livePayload = { worldSetting: parsed.worldSetting, styleGuide: parsed.styleGuide };
|
||||
// audioEnabled 已在 useState 初始化时反向投射到 muted;这里无需再额外存。
|
||||
} catch {
|
||||
livePayload = null;
|
||||
@@ -531,6 +540,11 @@ function PlayInner() {
|
||||
type PrebakedFirstAct = StartResponse & {
|
||||
worldSetting: string;
|
||||
styleGuide: string;
|
||||
// Live /api/start path tags this on after the response (prebaked card
|
||||
// JSONs never have one — they were rendered at build time without any
|
||||
// user-uploaded reference). Carried into Session so /api/scene's painter
|
||||
// anchors the same style image on every subsequent scene.
|
||||
styleReferenceImage?: string;
|
||||
cardName?: string;
|
||||
cardTitle?: string;
|
||||
cardGender?: string;
|
||||
@@ -554,7 +568,14 @@ function PlayInner() {
|
||||
}
|
||||
const data = (await r.json()) as StartResponse;
|
||||
// Live /api/start doesn't echo ws/sg back — splice in what we sent.
|
||||
return { ...data, worldSetting: livePayload!.worldSetting, styleGuide: livePayload!.styleGuide };
|
||||
// styleReferenceImage is similarly not in StartResponse; tag it on so
|
||||
// the session we build below carries it for every /api/scene call.
|
||||
return {
|
||||
...data,
|
||||
worldSetting: livePayload!.worldSetting,
|
||||
styleGuide: livePayload!.styleGuide,
|
||||
styleReferenceImage: livePayload!.styleReferenceImage,
|
||||
};
|
||||
});
|
||||
|
||||
fetchStart
|
||||
@@ -577,6 +598,7 @@ function PlayInner() {
|
||||
],
|
||||
characters: data.characters,
|
||||
storyState: data.storyState,
|
||||
styleReferenceImage: data.styleReferenceImage,
|
||||
};
|
||||
visitedBeatsRef.current = [data.scene.entryBeatId];
|
||||
setSession(initial);
|
||||
|
||||
Reference in New Issue
Block a user