import { Trans, useLingui } from "@lingui/react/macro"; import type { MediaSettingsUpdate, MediaSettingsView } from "@rakazo/contracts"; import { Button, Field, FieldLabel, Input, Textarea } from "@rakazo/ui-web"; import { useEffect, useId, useState } from "react"; import { rpc } from "../lib/rpc"; import { SuccessPop } from "./ai/primitives"; type KeyDraft = { value: string; remove: boolean }; const emptyKey: KeyDraft = { value: "", remove: false }; /** Omitted keeps the saved key, empty removes it, anything else replaces it. */ function keyUpdate(draft: KeyDraft): string | undefined { if (draft.value.trim()) return draft.value.trim(); return draft.remove ? "" : undefined; } function ApiKeyField({ saved, draft, disabled, onChange, }: { saved: boolean; draft: KeyDraft; disabled: boolean; onChange: (draft: KeyDraft) => void; }) { const { t } = useLingui(); const id = useId(); const showSaved = saved && !draft.remove; return (
API key {showSaved && !draft.value ? ( ) : null}
onChange({ ...draft, value: event.target.value })} />
); } /** The deployment owner's media servers: ComfyUI for generation, a transcription server. */ export function MediaSettings() { const { t } = useLingui(); const ids = useId(); const [view, setView] = useState(null); const [comfyUrl, setComfyUrl] = useState(""); const [comfyKey, setComfyKey] = useState(emptyKey); const [imageModel, setImageModel] = useState(""); const [imageWorkflow, setImageWorkflow] = useState(""); const [videoWorkflow, setVideoWorkflow] = useState(""); const [transcriptionUrl, setTranscriptionUrl] = useState(""); const [transcriptionModel, setTranscriptionModel] = useState(""); const [transcriptionKey, setTranscriptionKey] = useState(emptyKey); const [busy, setBusy] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); function apply(next: MediaSettingsView) { setView(next); setComfyUrl(next.comfyui?.baseUrl ?? ""); setImageModel(next.comfyui?.imageModel ?? ""); setImageWorkflow(next.comfyui?.imageWorkflow ?? ""); setVideoWorkflow(next.comfyui?.videoWorkflow ?? ""); setTranscriptionUrl(next.transcription?.baseUrl ?? ""); setTranscriptionModel(next.transcription?.model ?? ""); setComfyKey(emptyKey); setTranscriptionKey(emptyKey); } useEffect(() => { let cancelled = false; rpc.media.settings .get() .then((next) => { if (!cancelled) apply(next); }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : t`Couldn't load`); }); return () => { cancelled = true; }; }, [t]); async function save() { if (busy) return; setBusy(true); setSaved(false); setError(null); const update: MediaSettingsUpdate = { comfyui: comfyUrl.trim() ? { baseUrl: comfyUrl.trim(), apiKey: keyUpdate(comfyKey), imageModel, imageWorkflow, videoWorkflow, } : null, transcription: transcriptionUrl.trim() ? { baseUrl: transcriptionUrl.trim(), model: transcriptionModel, apiKey: keyUpdate(transcriptionKey), } : null, }; try { apply(await rpc.media.settings.update(update)); setSaved(true); } catch (err) { setError(err instanceof Error ? err.message : t`Couldn't save`); } finally { setBusy(false); } } const disabled = busy || view === null; const advancedSaved = Boolean( view?.comfyui?.imageModel || view?.comfyui?.imageWorkflow || view?.comfyui?.videoWorkflow, ); const workflowClass = "max-h-48 overflow-auto font-mono text-xs"; return (

ComfyUI

Server URL setComfyUrl(event.target.value)} />
Advanced Image model setImageModel(event.target.value)} /> Image workflow