working through the plan + UI/ UX
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useToast } from "./Toast";
|
||||
|
||||
interface LlmConfig {
|
||||
api_url: string;
|
||||
@@ -8,21 +9,29 @@ interface LlmConfig {
|
||||
temperature: number;
|
||||
max_tokens: number;
|
||||
top_p: number;
|
||||
image_model: string;
|
||||
embed_model: string;
|
||||
}
|
||||
|
||||
export function SettingsPanel() {
|
||||
const [config, setConfig] = useState<LlmConfig | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const { addToast } = useToast();
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const c = await invoke<LlmConfig>("get_llm_config");
|
||||
setConfig(c);
|
||||
} catch (e) {
|
||||
console.error("Failed to load config:", e);
|
||||
}
|
||||
}
|
||||
// ponytail: auto-load on mount so users don't see a gate before the form.
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const c = await invoke<LlmConfig>("get_llm_config");
|
||||
setConfig(c);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
async function saveConfig() {
|
||||
if (!config) return;
|
||||
@@ -31,24 +40,20 @@ export function SettingsPanel() {
|
||||
await invoke("set_llm_config", { config });
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 2000);
|
||||
addToast("Settings saved", "success");
|
||||
} catch (e) {
|
||||
console.error("Failed to save config:", e);
|
||||
addToast(`Save failed: ${e}`, "error");
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4">
|
||||
<p className="text-[var(--color-text-secondary)] text-sm">
|
||||
Configure your LLM connection
|
||||
</p>
|
||||
<button
|
||||
onClick={loadConfig}
|
||||
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"
|
||||
>
|
||||
Load Settings
|
||||
</button>
|
||||
<div className="flex flex-col items-center justify-center h-full gap-3">
|
||||
<p className="text-[var(--color-text-secondary)] text-sm">Loading settings…</p>
|
||||
{error && (
|
||||
<p className="text-[var(--color-danger)] text-xs">Failed to load: {error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -71,13 +76,23 @@ export function SettingsPanel() {
|
||||
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
|
||||
API Key (optional)
|
||||
</label>
|
||||
<input
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-2 text-[var(--color-text-primary)] placeholder-[var(--color-text-dim)] focus:outline-none focus:border-[var(--color-gold-bright)]"
|
||||
type="password"
|
||||
value={config.api_key}
|
||||
onChange={(e) => setConfig({ ...config, api_key: e.target.value })}
|
||||
placeholder="sk-... (leave blank for local)"
|
||||
/>
|
||||
<div className="flex gap-1.5">
|
||||
<input
|
||||
className="flex-1 rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-2 text-[var(--color-text-primary)] placeholder-[var(--color-text-dim)] focus:outline-none focus:border-[var(--color-gold-bright)]"
|
||||
type={showKey ? "text" : "password"}
|
||||
value={config.api_key}
|
||||
onChange={(e) => setConfig({ ...config, api_key: e.target.value })}
|
||||
placeholder="sk-... (leave blank for local)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowKey((s) => !s)}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-2 text-[var(--color-text-dim)] hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors text-xs cursor-pointer"
|
||||
title={showKey ? "Hide" : "Show"}
|
||||
>
|
||||
{showKey ? "Hide" : "Show"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -92,6 +107,30 @@ export function SettingsPanel() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
|
||||
Image model
|
||||
</label>
|
||||
<input
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-2 text-[var(--color-text-primary)] placeholder-[var(--color-text-dim)] focus:outline-none focus:border-[var(--color-gold-bright)]"
|
||||
value={config.image_model}
|
||||
onChange={(e) => setConfig({ ...config, image_model: e.target.value })}
|
||||
placeholder="x/flux2-klein:4b"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[var(--color-text-secondary)] text-xs font-medium">
|
||||
Embedding model (Lore RAG)
|
||||
</label>
|
||||
<input
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-2 text-[var(--color-text-primary)] placeholder-[var(--color-text-dim)] focus:outline-none focus:border-[var(--color-gold-bright)]"
|
||||
value={config.embed_model}
|
||||
onChange={(e) => setConfig({ ...config, embed_model: e.target.value })}
|
||||
placeholder="nomic-embed-text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user