import { FormEvent, useMemo, useRef, useState } from "react"; import { apiFetch } from "../api"; import { DownloadMediaButton } from "../components/DownloadMediaButton"; import { ElapsedTimer } from "../components/ElapsedTimer"; import { ImageSlot } from "../components/ImageSlot"; import { useGenerationOptions } from "../context/GenerationOptionsContext"; const FLASH25_ID = "gemini-2.5-flash-image"; const FLASH_ID = "gemini-3.1-flash-image-preview"; const PRO_ID = "gemini-3-pro-image-preview"; /** Order: older flash → Nano Banana 2 (default) → long-thinking → Pro */ type ImageGenVariant = "nb25-minimal" | "flash-minimal" | "flash-high" | "pro-high"; export function ImageGen() { const opts = useGenerationOptions(); const nano25Label = opts.image.models.find((m) => m.value === FLASH25_ID)?.label ?? "Nano Banana"; const flashLabel = opts.image.models.find((m) => m.value === FLASH_ID)?.label ?? "Nano Banana 2"; const proLabel = opts.image.models.find((m) => m.value === PRO_ID)?.label ?? "Nano Banana Pro"; const [prompt, setPrompt] = useState(""); const [variant, setVariant] = useState("flash-minimal"); const [aspect, setAspect] = useState(opts.image.aspect_ratios[0] ?? "1:1"); const [res, setRes] = useState(opts.image.resolutions[0] ?? "1K"); const [f0, setF0] = useState(null); const [f1, setF1] = useState(null); const [f2, setF2] = useState(null); const [busy, setBusy] = useState(false); const [err, setErr] = useState(null); const [out, setOut] = useState(null); const formRef = useRef(null); const variantOptions = useMemo( () => [ { id: "nb25-minimal" as const, label: nano25Label }, { id: "flash-minimal" as const, label: flashLabel }, { id: "flash-high" as const, label: `${flashLabel}(长思考)` }, { id: "pro-high" as const, label: `${proLabel}(长思考)` }, ], [nano25Label, flashLabel, proLabel], ); async function onSubmit(e: FormEvent) { e.preventDefault(); setErr(null); setOut(null); if (!prompt.trim()) { setErr("请填写提示词。"); return; } let model: string; let thinking: "minimal" | "high"; switch (variant) { case "nb25-minimal": model = FLASH25_ID; thinking = "minimal"; break; case "flash-minimal": model = FLASH_ID; thinking = "minimal"; break; case "flash-high": model = FLASH_ID; thinking = "high"; break; case "pro-high": model = PRO_ID; thinking = "high"; break; } const fd = new FormData(); fd.append("prompt", prompt.trim()); fd.append("model", model); fd.append("aspect_ratio", aspect); fd.append("resolution", res); fd.append("thinking_level", thinking); if (f0) fd.append("image_0", f0); if (f1) fd.append("image_1", f1); if (f2) fd.append("image_2", f2); setBusy(true); try { const r = await apiFetch("/api/generate/image", { method: "POST", body: fd, }); const j = await r.json().catch(() => ({})); if (!r.ok) { throw new Error((j as { detail?: string }).detail || r.statusText); } const b64 = (j as { image_base64: string; mime_type: string }).image_base64; const mime = (j as { mime_type: string }).mime_type; setOut(`data:${mime};base64,${b64}`); } catch (e: unknown) { setErr(e instanceof Error ? e.message : "请求失败"); } finally { setBusy(false); } } return (

图片编辑或生成

填写提示词;参考图最多三张。再选比例与分辨率。

{nano25Label} 为稳定版;{flashLabel} 与 {proLabel} 为实验性,接口或效果可能变更。

{nano25Label} 为上一代快速;{flashLabel} 为默认推荐;{flashLabel}(长思考)与 {proLabel}(长思考)为长思考模式,更慢、更细;{proLabel} 为 Pro 路线。