add LLM configuration and backend management components with automated setup scripts

This commit is contained in:
techjarves committed 2026-06-21 17:00:09 +05:30
1 parent 822e9c9481
commit 20b81926b6
10 files changed
+575 -234

No files matched your search

+62 -1
View File
@@ -5,7 +5,7 @@ import {
Monitor, HardDrive, MemoryStick, Thermometer, Hash, Layers,
ChevronRight, Box, Wand2, Lightbulb, RotateCcw, Check, Palette
} from "lucide-react";
import { stopServer, formatBytes, getLlmStatus, startLlm, stopLlm } from "../services/api";
import { stopServer, formatBytes, getLlmBackends, getLlmStats, getLlmStatus, benchmarkLlm, startLlm, stopLlm } from "../services/api";
import { THEMES } from "../themes";
const ASPECT_RATIOS = [
@@ -187,6 +187,9 @@ function Settings({
setTheme,
}) {
const [llmStatus, setLlmStatus] = useState({ ready: false, settings: {} });
const [llmBackends, setLlmBackends] = useState({ available: [], candidates: [] });
const [llmStats, setLlmStats] = useState({ benchmarks: [] });
const [benchmarkBusy, setBenchmarkBusy] = useState(false);
useEffect(() => {
let cancelled = false;
@@ -195,6 +198,13 @@ function Settings({
const status = await getLlmStatus();
if (!cancelled) setLlmStatus(status);
} catch (_) {}
try {
const [backends, stats] = await Promise.all([getLlmBackends(), getLlmStats()]);
if (!cancelled) {
setLlmBackends(backends);
setLlmStats(stats);
}
} catch (_) {}
};
fetchLlmStatus();
const interval = setInterval(fetchLlmStatus, 3000);
@@ -247,6 +257,37 @@ function Settings({
performanceProfile: settings?.performanceProfile,
});
const handleBenchmarkTextBackend = async () => {
const model = llmStatus.settings?.model;
if (!model) {
showAlert({ title: "No Text Model Loaded", message: "Load a GGUF text model before benchmarking.", danger: true });
return;
}
setBenchmarkBusy(true);
try {
const result = await benchmarkLlm(model, {
contextSize: Math.min(2048, Number(textSettings?.contextSize) || 2048),
gpuLayers: textSettings?.gpuLayers ?? -1,
includeCpu: true,
});
const winner = result.winner;
showAlert({
title: winner ? "Benchmark Complete" : "Benchmark Finished",
message: winner
? `Fastest backend: ${winner.backendMode} at ${Number(winner.predicted_per_second || 0).toFixed(1)} tokens/sec.`
: "Benchmark finished, but no backend returned usable timing data.",
});
const [status, backends, stats] = await Promise.all([getLlmStatus(), getLlmBackends(true), getLlmStats()]);
setLlmStatus(status);
setLlmBackends(backends);
setLlmStats(stats);
} catch (err) {
showAlert({ title: "Benchmark Failed", message: err.message || String(err), danger: true });
} finally {
setBenchmarkBusy(false);
}
};
const handleThinkingToggle = async (enabled) => {
const nextSettings = { ...textSettings, enableThinking: enabled };
@@ -924,6 +965,26 @@ function Settings({
GPU Layers: {llmStatus.settings?.gpuLayers === -1 ? "All" : llmStatus.settings?.gpuLayers}
</div>
)}
<div style={{ marginTop: "10px", fontSize: "0.78rem", lineHeight: "1.5" }}>
Available: {(llmBackends.available || []).map((backend) => backend.key.toUpperCase()).join(", ") || "None"}<br />
Fastest saved: {(() => {
const model = llmStatus.settings?.model;
const winner = (llmStats.benchmarks || []).find((item) => item.ok && (!model || item.model === model));
return winner
? `${winner.backendMode} (${Number(winner.predicted_per_second || 0).toFixed(1)} tok/s)`
: "Not benchmarked";
})()}
</div>
<button
className="m3-button secondary"
type="button"
onClick={handleBenchmarkTextBackend}
disabled={benchmarkBusy || !llmStatus.ready}
style={{ marginTop: "12px", width: "100%", justifyContent: "center" }}
>
<Gauge size={15} />
{benchmarkBusy ? "Benchmarking..." : "Benchmark Text Backends"}
</button>
</div>
</div>
</div>
+27
View File
@@ -316,6 +316,32 @@ export async function getLlmStatus() {
}
}
export async function getLlmBackends(refresh = false) {
const res = await fetch(`/api/llm/backends${refresh ? "?refresh=1" : ""}`);
return await readJsonResponse(res, "The local server returned invalid text backend data.");
}
export async function getLlmStats() {
const res = await fetch("/api/llm/stats");
return await readJsonResponse(res, "The local server returned invalid text runtime stats.");
}
export async function benchmarkLlm(model, options = {}) {
const res = await fetch("/api/llm/benchmark", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model,
backends: options.backends,
includeCpu: options.includeCpu,
prompt: options.prompt,
contextSize: options.contextSize,
gpuLayers: options.gpuLayers,
}),
});
return await readJsonResponse(res, "The local server returned invalid benchmark data.");
}
export async function listLlmModels() {
const res = await fetch("/api/llm/models");
const data = await readJsonResponse(res, "The local server returned invalid text model data.");
@@ -358,6 +384,7 @@ export async function startLlm(model, options = {}) {
batchSize: options.batchSize,
ubatchSize: options.ubatchSize,
performanceProfile: options.performanceProfile,
preferredBackend: options.preferredBackend,
}),
});
return await readJsonResponse(res, "The local server returned an invalid text backend response.");