Spaces:
Runtime error
Runtime error
| import { | |
| createContext, | |
| useContext, | |
| useEffect, | |
| useState, | |
| type ReactNode, | |
| } from "react"; | |
| import { apiFetch } from "../api"; | |
| export type LabeledModel = { value: string; label: string }; | |
| export type ThinkingLevelOption = { value: string; label: string }; | |
| /** Video model entry; `supports_reference_images` / `supports_4k` / `supports_end_frame` default to true if omitted. */ | |
| export type VideoModelOption = { | |
| value: string; | |
| label: string; | |
| supports_reference_images?: boolean; | |
| supports_4k?: boolean; | |
| /** Start/end mode: if false, only start frame (e.g. Veo 3 family). */ | |
| supports_end_frame?: boolean; | |
| }; | |
| export type GenerationOptions = { | |
| image: { | |
| models: LabeledModel[]; | |
| aspect_ratios: string[]; | |
| resolutions: string[]; | |
| /** @deprecated No longer used by the UI; image model + thinking are combined in ImageGen. */ | |
| thinking_levels?: ThinkingLevelOption[]; | |
| }; | |
| video: { | |
| models: VideoModelOption[]; | |
| aspect_ratios: string[]; | |
| resolutions: string[]; | |
| durations_seconds: number[]; | |
| }; | |
| video_frames: { | |
| models: VideoModelOption[]; | |
| aspect_ratios: string[]; | |
| resolutions: string[]; | |
| durations_seconds: number[]; | |
| }; | |
| }; | |
| const GenerationOptionsContext = createContext<GenerationOptions | null>(null); | |
| export function GenerationOptionsProvider({ children }: { children: ReactNode }) { | |
| const [data, setData] = useState<GenerationOptions | null>(null); | |
| const [err, setErr] = useState<string | null>(null); | |
| useEffect(() => { | |
| let cancelled = false; | |
| apiFetch("/api/config/generation-options") | |
| .then(async (r) => { | |
| if (r.status === 401) { | |
| window.location.assign("/login"); | |
| return null; | |
| } | |
| const j = await r.json().catch(() => ({})); | |
| if (!r.ok) { | |
| const detail = (j as { detail?: unknown }).detail; | |
| const msg = | |
| typeof detail === "string" | |
| ? detail | |
| : Array.isArray(detail) | |
| ? JSON.stringify(detail) | |
| : r.statusText; | |
| throw new Error(msg); | |
| } | |
| return j as GenerationOptions; | |
| }) | |
| .then((opts) => { | |
| if (cancelled || opts === null) return; | |
| setData(opts); | |
| }) | |
| .catch((e: unknown) => { | |
| if (!cancelled) | |
| setErr(e instanceof Error ? e.message : "Failed to load options"); | |
| }); | |
| return () => { | |
| cancelled = true; | |
| }; | |
| }, []); | |
| if (err) { | |
| return ( | |
| <div className="p-8 text-sm text-red-800"> | |
| 页面暂时打不开,请稍后再试。 | |
| {err ? ( | |
| <span className="block mt-2 text-xs text-mist">({err})</span> | |
| ) : null} | |
| </div> | |
| ); | |
| } | |
| if (!data) { | |
| return ( | |
| <div className="p-8 text-sm text-mist">加载中…</div> | |
| ); | |
| } | |
| return ( | |
| <GenerationOptionsContext.Provider value={data}> | |
| {children} | |
| </GenerationOptionsContext.Provider> | |
| ); | |
| } | |
| const FAST_MODEL_IDS = new Set([ | |
| "gemini-3.1-flash-image-preview", | |
| "veo-3.0-fast-generate-001", | |
| "veo-3.1-fast-generate-preview", | |
| ]); | |
| /** Prefer Flash/Fast id, else label 快速, else first or `fallback`. */ | |
| export function defaultFastModelValue( | |
| models: { value: string; label?: string }[], | |
| fallback: string, | |
| ): string { | |
| const byId = models.find((m) => FAST_MODEL_IDS.has(m.value)); | |
| if (byId) return byId.value; | |
| const byLabel = models.find((m) => m.label?.includes("快速")); | |
| return byLabel?.value ?? models[0]?.value ?? fallback; | |
| } | |
| export function modelSupportsReferenceImages( | |
| models: VideoModelOption[], | |
| value: string, | |
| ): boolean { | |
| const m = models.find((x) => x.value === value); | |
| return m?.supports_reference_images !== false; | |
| } | |
| export function modelSupports4k(models: VideoModelOption[], value: string): boolean { | |
| const m = models.find((x) => x.value === value); | |
| return m?.supports_4k !== false; | |
| } | |
| export function modelSupportsEndFrame(models: VideoModelOption[], value: string): boolean { | |
| const m = models.find((x) => x.value === value); | |
| return m?.supports_end_frame !== false; | |
| } | |
| export function useGenerationOptions(): GenerationOptions { | |
| const ctx = useContext(GenerationOptionsContext); | |
| if (!ctx) { | |
| throw new Error("useGenerationOptions must be used inside GenerationOptionsProvider"); | |
| } | |
| return ctx; | |
| } | |