feat(web): add model settings button, modal, and client-side style image parsing
Signed-off-by: baizhi958216 <1475289190@qq.com>
This commit is contained in:
+34
-10
@@ -12,6 +12,10 @@ import {
|
|||||||
} from "@/lib/options";
|
} from "@/lib/options";
|
||||||
import { readStoredTtsConfig } from "@/lib/clientTtsConfig";
|
import { readStoredTtsConfig } from "@/lib/clientTtsConfig";
|
||||||
import { SettingsModal, readStoredPlayerName, readStoredVisionClick } from "@/components/SettingsModal";
|
import { SettingsModal, readStoredPlayerName, readStoredVisionClick } from "@/components/SettingsModal";
|
||||||
|
import { ModelSettingsModal } from "@/components/ModelSettingsModal";
|
||||||
|
import { analyzeImageDataUrl } from "@infiplot/ai-client";
|
||||||
|
import { readStoredModelConfig, resolveEngineConfig } from "@/lib/clientModelConfig";
|
||||||
|
import { STYLE_EXTRACTION_PROMPT } from "@/lib/styleExtraction";
|
||||||
import { STORY_SHARE_STORAGE_KEY, parseStoryShareDoc } from "@/lib/storyShare";
|
import { STORY_SHARE_STORAGE_KEY, parseStoryShareDoc } from "@/lib/storyShare";
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
@@ -976,17 +980,21 @@ function StyleModal({
|
|||||||
setParsing(true);
|
setParsing(true);
|
||||||
try {
|
try {
|
||||||
const resized = await resizeImageToDataUrl(file);
|
const resized = await resizeImageToDataUrl(file);
|
||||||
const res = await fetch("/api/parse-style-image", {
|
const modelCfg = readStoredModelConfig();
|
||||||
method: "POST",
|
if (!modelCfg) {
|
||||||
headers: { "Content-Type": "application/json" },
|
throw new Error("请先点击首页右上角的「模型设置」配置视觉模型参数");
|
||||||
body: JSON.stringify({ imageDataUrl: resized }),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const j = (await res.json().catch(() => ({}))) as { error?: string };
|
|
||||||
throw new Error(j.error ?? `${res.status}`);
|
|
||||||
}
|
}
|
||||||
const data = (await res.json()) as { stylePrompt: string };
|
const config = resolveEngineConfig(modelCfg, null);
|
||||||
setDraft(data.stylePrompt);
|
const raw = await analyzeImageDataUrl(config.vision, resized, STYLE_EXTRACTION_PROMPT);
|
||||||
|
let parsed: { stylePrompt?: string };
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
parsed = { stylePrompt: raw };
|
||||||
|
}
|
||||||
|
const stylePrompt = (parsed.stylePrompt ?? "").trim();
|
||||||
|
if (!stylePrompt) throw new Error("视觉模型返回了空的风格描述");
|
||||||
|
setDraft(stylePrompt);
|
||||||
setCustomStyleRefImage(resized);
|
setCustomStyleRefImage(resized);
|
||||||
track("style_image_upload", { ok: true });
|
track("style_image_upload", { ok: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1258,6 +1266,7 @@ export default function HomePage() {
|
|||||||
|
|
||||||
// 统一设置弹窗(名字 + 识图 + TTS Key):可选增强,数据只存浏览器。
|
// 统一设置弹窗(名字 + 识图 + TTS Key):可选增强,数据只存浏览器。
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
const [modelSettingsOpen, setModelSettingsOpen] = useState(false);
|
||||||
const [ttsConfigured, setTtsConfigured] = useState(false);
|
const [ttsConfigured, setTtsConfigured] = useState(false);
|
||||||
const [playerName, setPlayerName] = useState("");
|
const [playerName, setPlayerName] = useState("");
|
||||||
const [visionClickEnabled, setVisionClickEnabled] = useState(true);
|
const [visionClickEnabled, setVisionClickEnabled] = useState(true);
|
||||||
@@ -1475,6 +1484,15 @@ export default function HomePage() {
|
|||||||
Infi<em className="italic font-light text-ember-500">Plot</em>
|
Infi<em className="italic font-light text-ember-500">Plot</em>
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-5">
|
<div className="flex items-center gap-5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setModelSettingsOpen(true)}
|
||||||
|
aria-label="模型设置"
|
||||||
|
title="模型设置"
|
||||||
|
className="text-base text-clay-500 hover:text-ember-500 transition-colors"
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-sliders" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSettingsOpen(true)}
|
onClick={() => setSettingsOpen(true)}
|
||||||
@@ -1789,6 +1807,12 @@ export default function HomePage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{modelSettingsOpen && (
|
||||||
|
<ModelSettingsModal
|
||||||
|
onClose={() => setModelSettingsOpen(false)}
|
||||||
|
onSaved={() => setModelSettingsOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user