import React, { useEffect, useState } from 'react'; import { Save, CheckCircle, AlertTriangle } from '../../../icons'; import api from '../../../utils/api'; import { useToast } from '../../Toasts/useToast'; import '../../DeckyFleet.css'; import '../../PersonaGeneration.css'; import './LLMTab.css'; interface LLMPayload { provider: string; base_url: string | null; model: string; timeout: number; api_key_set: boolean; } interface PutBody { provider?: string; base_url?: string | null; model?: string; timeout?: number; api_key?: string; } const DEFAULTS: LLMPayload = { provider: 'ollama', base_url: null, model: 'llama3.1', timeout: 60, api_key_set: false, }; interface Props { isAdmin: boolean; } export const LLMTab: React.FC = ({ isAdmin }) => { const { push } = useToast(); const [cfg, setCfg] = useState(DEFAULTS); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [apiKeyInput, setApiKeyInput] = useState(''); const [clearApiKey, setClearApiKey] = useState(false); useEffect(() => { api.get('/realism/llm') .then((r) => setCfg(r.data)) .catch(() => setError('Failed to load LLM config.')) .finally(() => setLoading(false)); }, []); const handleSave = async () => { setSaving(true); setError(null); const body: PutBody = { provider: cfg.provider, base_url: cfg.base_url || null, model: cfg.model, timeout: cfg.timeout, }; if (clearApiKey) body.api_key = ''; else if (apiKeyInput.trim()) body.api_key = apiKeyInput.trim(); try { const r = await api.put('/realism/llm', body); setCfg(r.data); setApiKeyInput(''); setClearApiKey(false); push({ text: 'LLM CONFIG SAVED', tone: 'matrix', icon: 'terminal' }); } catch (err: any) { const detail = err?.response?.data?.detail; const status = err?.response?.status; if (status === 403) setError('Admin role required to save.'); else if (status === 400 && detail) setError(`Validation failed: ${detail}`); else setError('Save failed.'); } finally { setSaving(false); } }; if (loading) { return
Loading…
; } return (
Scope: configures the LLM backend used for{' '} email bodies, drafts, and notes generated by the realism subsystem. Changes are persisted and hot-reloaded immediately; the orchestrator worker picks them up within one refresh tick (~5 min).
{error && (
{error}
)}
setCfg({ ...cfg, model: e.target.value })} />
setCfg({ ...cfg, base_url: e.target.value || null })} /> Blank = use local ollama run subprocess. Set to the daemon URL to target a remote host.
{ const v = parseFloat(e.target.value); if (v > 0) setCfg({ ...cfg, timeout: v }); }} />
{cfg.api_key_set && !clearApiKey ? ( <>
KEY SET — enter a new value to rotate
{isAdmin && (
)} ) : ( <> setApiKeyInput(e.target.value)} /> {clearApiKey && isAdmin && (
)} )}
{isAdmin && (
)}
); };