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:
DESKTOP-I1T6TF3\Q
2026-06-03 19:15:19 +08:00
parent 298ecd4ec0
commit 347ab297d5
10 changed files with 396 additions and 15 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
export { chat } from "./chat";
export { generateImage } from "./image";
export type { GenerateImageOptions, GenerateImageResult } from "./image";
export { interpretClick } from "./vision";
export { interpretClick, analyzeImageDataUrl } from "./vision";
export type { ChatMessage } from "./chat";
+26 -6
View File
@@ -5,26 +5,46 @@ export async function interpretClick(
config: ProviderConfig,
imageBase64: string,
prompt: string,
): Promise<string> {
// Wrap the raw base64 in a PNG data URL — the Canvas annotator on the
// client encodes as PNG. analyzeImageDataUrl handles the actual request.
return analyzeImageDataUrl(
config,
`data:image/png;base64,${imageBase64}`,
prompt,
{ responseFormat: "json_object" },
);
}
/**
* General single-image vision call. Accepts a complete data URL (preserves
* the source mime type, e.g. webp/jpeg) and lets the caller opt out of
* `response_format: json_object` for free-form text responses.
*/
export async function analyzeImageDataUrl(
config: ProviderConfig,
imageDataUrl: string,
prompt: string,
opts: { responseFormat?: "json_object" | "text" } = {},
): Promise<string> {
const url = `${config.baseUrl.replace(/\/$/, "")}/chat/completions`;
const body = {
const body: Record<string, unknown> = {
model: config.model,
messages: [
{
role: "user",
content: [
{ type: "text", text: prompt },
{
type: "image_url",
image_url: { url: `data:image/png;base64,${imageBase64}` },
},
{ type: "image_url", image_url: { url: imageDataUrl } },
],
},
],
temperature: 0.2,
response_format: { type: "json_object" },
};
if (opts.responseFormat === "json_object") {
body.response_format = { type: "json_object" };
}
const timeoutCtrl = new AbortController();
const timeoutId = setTimeout(() => timeoutCtrl.abort(), 60_000);