60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
type State = "unknown" | "connected" | "offline";
|
|
|
|
// ponytail: a title-bar pill that probes the LLM endpoint on mount and on
|
|
// click. Shows ● connected / ○ offline so the DM knows their backend is up
|
|
// before generating. Replaces the inert "My Campaign" placeholder.
|
|
export function ConnectionPill() {
|
|
const [state, setState] = useState<State>("unknown");
|
|
const [models, setModels] = useState(0);
|
|
|
|
async function test() {
|
|
setState("unknown");
|
|
try {
|
|
const result = await invoke<{ ok: boolean; models: string[]; error: string }>("test_connection");
|
|
if (result.ok) {
|
|
setState("connected");
|
|
setModels(result.models.length);
|
|
} else {
|
|
setState("offline");
|
|
}
|
|
} catch {
|
|
setState("offline");
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
void test();
|
|
}, []);
|
|
|
|
const color =
|
|
state === "connected"
|
|
? "var(--color-success)"
|
|
: state === "offline"
|
|
? "var(--color-danger)"
|
|
: "var(--color-text-dim)";
|
|
const label =
|
|
state === "connected"
|
|
? `LLM · ${models} models`
|
|
: state === "offline"
|
|
? "LLM offline"
|
|
: "LLM…";
|
|
|
|
return (
|
|
<button
|
|
onClick={test}
|
|
data-tauri-no-drag
|
|
className="ml-2 flex items-center gap-1.5 text-[var(--color-text-dim)] text-xs hover:text-[var(--color-text-secondary)] transition-colors cursor-pointer"
|
|
title="Click to re-test LLM connection"
|
|
aria-label={`LLM connection: ${state}`}
|
|
>
|
|
<span
|
|
className="w-1.5 h-1.5 rounded-full"
|
|
style={{ backgroundColor: color }}
|
|
/>
|
|
<span className="hidden sm:inline">{label}</span>
|
|
</button>
|
|
);
|
|
} |