modelbeast/web/src/Settings.jsx
MODELBEAST 76f065d3a6 A/B image-gen: local FLUX (mflux/MLX) + OpenRouter nano-banana operators
- flux_local: prompt->image fully on-device via mflux (schnell/dev, steps,
  quantize, seed, size). Both FLUX repos are HF-gated as of 2026-07 (schnell
  included) — clean GatedRepoError surfaces with a hint; needs owner HF token.
- openrouter_image: prompt->image via OpenRouter chat/completions with
  modalities [image,text]; parses data-URL images from the response; model
  picker for nano-banana / nano-banana-pro. Gated on OPENROUTER_API_KEY.
- settings: openrouter_key added to vault (masked/redacted/env-injected)
- scripts/install_mflux.sh; venv installed
- A/B flow: run both with the same prompt, judge in Compare mode

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 22:31:19 +10:00

65 lines
2.3 KiB
JavaScript

import { useEffect, useState } from "react";
import { getSettings, putSettings } from "./api";
const LABELS = {
fal_key: "fal.ai API key",
tripo_key: "Tripo API key",
meshy_key: "Meshy API key",
replicate_token: "Replicate token",
openrouter_key: "OpenRouter API key (nano-banana A/B tests)",
hf_token: "HuggingFace token (for SF3D / TRELLIS.2 weights)",
models_dir: "Models directory",
archive_host: "Archive host (rsync)",
archive_path: "Archive path",
};
export default function Settings({ onClose, onSaved }) {
const [data, setData] = useState(null);
const [edits, setEdits] = useState({});
const [saving, setSaving] = useState(false);
useEffect(() => { getSettings().then(setData); }, []);
if (!data) return null;
const secretKeys = new Set(data._secret_keys || []);
const keys = data._env_keys || [];
const save = async () => {
setSaving(true);
const fresh = await putSettings(edits);
setData(fresh); setEdits({}); setSaving(false);
onSaved?.(fresh);
};
return (
<div className="modal-backdrop" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2>Settings</h2>
<p className="dim">API keys unlock cloud operators. HuggingFace token lets local SF3D / TRELLIS.2 download their gated weights. Secrets are masked and never written to logs.</p>
{keys.map((k) => {
const isSecret = secretKeys.has(k);
const current = data[k] || "";
const placeholder = isSecret && current ? "•••••••• (set — leave blank to keep)" : "";
return (
<label key={k} className="setting">
<span>{LABELS[k] || k}</span>
<input
type={isSecret ? "password" : "text"}
placeholder={placeholder}
defaultValue={isSecret ? "" : current}
onChange={(e) => setEdits({ ...edits, [k]: e.target.value })}
/>
</label>
);
})}
<div className="modal-actions">
<button onClick={onClose}>Close</button>
<button className="go" onClick={save} disabled={saving || !Object.keys(edits).length}>
{saving ? "Saving…" : "Save"}
</button>
</div>
</div>
</div>
);
}