minor UI fixes

This commit is contained in:
itsamejms
2026-07-13 10:39:51 +01:00
parent a24f3615e0
commit a6171a8158
31 changed files with 1528 additions and 508 deletions
+136 -7
View File
@@ -19,6 +19,8 @@ export function SettingsPanel() {
const [saved, setSaved] = useState(false);
const [showKey, setShowKey] = useState(false);
const [error, setError] = useState("");
const [testing, setTesting] = useState(false);
const [conn, setConn] = useState<{ ok: boolean; models: string[]; error: string } | null>(null);
const { addToast } = useToast();
// ponytail: auto-load on mount so users don't see a gate before the form.
@@ -47,6 +49,59 @@ export function SettingsPanel() {
setLoading(false);
}
// ponytail: probe the configured endpoint for a model list. Ollama uses
// /api/tags, OpenAI-compatible uses /v1/models. The Rust side branches.
async function testConnection() {
if (!config) return;
setTesting(true);
setConn(null);
try {
// Save first so the backend uses the latest URL/key.
await invoke("set_llm_config", { config });
const result = await invoke<{ ok: boolean; models: string[]; error: string }>("test_connection");
setConn(result);
if (result.ok) addToast(`Connected — ${result.models.length} models`, "success");
else addToast(`Connection failed: ${result.error}`, "error");
} catch (e) {
setConn({ ok: false, models: [], error: String(e) });
addToast(`Connection failed: ${e}`, "error");
}
setTesting(false);
}
// ponytail: provider presets fill in the API URL pattern + default model.
const PRESETS: { label: string; url: string; model: string; key?: boolean }[] = [
{ label: "Ollama", url: "http://localhost:11434", model: "llama3.2" },
{ label: "LM Studio", url: "http://localhost:1234/v1", model: "local-model" },
{ label: "OpenAI", url: "https://api.openai.com", model: "gpt-4o-mini", key: true },
{ label: "Custom", url: "", model: "" },
];
function applyPreset(p: { url: string; model: string }) {
if (!config) return;
setConfig({ ...config, api_url: p.url, model: p.model });
setConn(null);
}
// ponytail: reset to the backend's default config. The Rust Default impl
// is the single source of truth; we just set an empty config and let the
// backend re-default on save — but we can't send a partial, so mirror the
// known defaults here and toast.
const DEFAULTS: LlmConfig = {
api_url: "http://localhost:11434",
api_key: "",
model: "llama3.2",
temperature: 0.7,
max_tokens: 512,
top_p: 0.9,
image_model: "x/flux2-klein:4b",
embed_model: "nomic-embed-text",
};
function resetDefaults() {
setConfig({ ...DEFAULTS });
setConn(null);
addToast("Reset to defaults — click Save to apply", "info");
}
if (!config) {
return (
<div className="flex flex-col items-center justify-center h-full gap-3">
@@ -60,6 +115,28 @@ export function SettingsPanel() {
return (
<div className="flex flex-col gap-4 text-sm">
<h3 className="font-heading text-[var(--color-gold-bright)] text-xs font-semibold uppercase tracking-wider border-b border-[var(--color-border-subtle)] pb-1 mb-1">LLM Connection</h3>
{/* Provider presets */}
<div className="flex flex-col gap-1">
<label className="text-[var(--color-text-secondary)] text-xs font-medium">Provider</label>
<div className="flex flex-wrap gap-1.5">
{PRESETS.map((p) => (
<button
key={p.label}
type="button"
onClick={() => applyPreset(p)}
className={`rounded-lg px-2.5 py-1 text-xs border transition-colors cursor-pointer ${
config.api_url === p.url
? "bg-[var(--color-gold-glow)] border-[var(--color-gold-bright)] text-[var(--color-gold-bright)]"
: "bg-[var(--color-bg-surface)] border-[var(--color-border-glass)] text-[var(--color-text-dim)] hover:text-[var(--color-text-secondary)]"
}`}
>
{p.label}
</button>
))}
</div>
</div>
<div className="flex flex-col gap-1">
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
API URL
@@ -104,9 +181,51 @@ export function SettingsPanel() {
value={config.model}
onChange={(e) => setConfig({ ...config, model: e.target.value })}
placeholder="llama3.2"
list="model-list"
/>
<datalist id="model-list">
{conn?.models.map((m) => (
<option key={m} value={m} />
))}
</datalist>
</div>
{/* Connection test */}
<div className="flex items-center gap-2">
<button
type="button"
onClick={testConnection}
disabled={testing}
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)] hover:border-[var(--color-gold-bright)] hover:text-[var(--color-gold-bright)] transition-colors cursor-pointer disabled:opacity-50"
>
{testing ? "Testing…" : "Test connection"}
</button>
{conn && (
<span className={`text-xs ${conn.ok ? "text-[var(--color-success)]" : "text-[var(--color-danger)]"}`}>
{conn.ok ? `✓ ${conn.models.length} models` : `✕ ${conn.error}`}
</span>
)}
</div>
{conn?.ok && conn.models.length > 0 && (
<div className="flex flex-wrap gap-1 max-h-24 overflow-y-auto">
{conn.models.map((m) => (
<button
key={m}
type="button"
onClick={() => setConfig({ ...config, model: m })}
className={`rounded-full px-2 py-0.5 text-[10px] border cursor-pointer transition-colors ${
config.model === m
? "bg-[var(--color-gold-glow)] border-[var(--color-gold-bright)] text-[var(--color-gold-bright)]"
: "bg-[var(--color-bg-deep)] border-[var(--color-border-subtle)] text-[var(--color-text-dim)] hover:text-[var(--color-text-secondary)]"
}`}
>
{m}
</button>
))}
</div>
)}
<h3 className="font-heading text-[var(--color-gold-bright)] text-xs font-semibold uppercase tracking-wider border-b border-[var(--color-border-subtle)] pb-1 mb-1">Image Model</h3>
<div className="flex flex-col gap-1">
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
Image model
@@ -119,6 +238,7 @@ export function SettingsPanel() {
/>
</div>
<h3 className="font-heading text-[var(--color-gold-bright)] text-xs font-semibold uppercase tracking-wider border-b border-[var(--color-border-subtle)] pb-1 mb-1">Embedding Model (Lore RAG)</h3>
<div className="flex flex-col gap-1">
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
Embedding model (Lore RAG)
@@ -131,6 +251,7 @@ export function SettingsPanel() {
/>
</div>
<h3 className="font-heading text-[var(--color-gold-bright)] text-xs font-semibold uppercase tracking-wider border-b border-[var(--color-border-subtle)] pb-1 mb-1">Text Model</h3>
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-1">
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
@@ -166,13 +287,21 @@ export function SettingsPanel() {
</div>
</div>
<button
onClick={saveConfig}
disabled={loading}
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer disabled:opacity-50"
>
{saved ? "✓ Saved" : loading ? "Saving…" : "Save Settings"}
</button>
<div className="flex gap-2">
<button
onClick={saveConfig}
disabled={loading}
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer disabled:opacity-50"
>
{saved ? "✓ Saved" : loading ? "Saving…" : "Save Settings"}
</button>
<button
onClick={resetDefaults}
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-danger)] hover:border-[var(--color-danger)] transition-colors cursor-pointer"
>
Reset to defaults
</button>
</div>
</div>
);
}