81 lines
3.1 KiB
TypeScript
81 lines
3.1 KiB
TypeScript
import { Box, Zap, ArrowLeftRight, Settings2 } from "lucide-react";
|
|
import { getRuntimeStateLabel, getRuntimeStatusTone } from "../../runtime/runtime-utils";
|
|
import type { RuntimeSnapshot } from "../../runtime/types";
|
|
|
|
export type Section = "model" | "runtime" | "connection" | "advanced";
|
|
|
|
const NAV: { id: Section; label: string; icon: typeof Box }[] = [
|
|
{ id: "model", label: "Model", icon: Box },
|
|
{ id: "runtime", label: "Runtime", icon: Zap },
|
|
{ id: "connection", label: "Connection", icon: ArrowLeftRight },
|
|
{ id: "advanced", label: "Advanced", icon: Settings2 },
|
|
];
|
|
|
|
function statusColor(snapshot: RuntimeSnapshot): string {
|
|
const tone = getRuntimeStatusTone(snapshot);
|
|
if (tone === "success") return "bg-green-500 shadow-[0_0_6px_rgba(48,209,88,0.55)]";
|
|
if (tone === "warning") return "bg-yellow-500 animate-pulse";
|
|
if (tone === "danger") return "bg-red-500";
|
|
return "bg-neutral-500";
|
|
}
|
|
|
|
interface Props {
|
|
section: Section;
|
|
setSection: (section: Section) => void;
|
|
snapshot: RuntimeSnapshot;
|
|
loading: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function SettingsSidebar({ section, setSection, snapshot, loading, onClose }: Props) {
|
|
const runtimeStatusLabel = loading ? "Loading..." : getRuntimeStateLabel(snapshot);
|
|
|
|
return (
|
|
<nav className="w-[220px] min-w-[220px] bg-neutral-900 border-r border-neutral-800 flex flex-col h-screen overflow-hidden">
|
|
<div className="p-6 pb-4 border-b border-neutral-800 shrink-0">
|
|
<div className="text-[11px] font-semibold text-neutral-500 tracking-widest uppercase mb-1">ForgeLM</div>
|
|
<h2 className="text-lg font-semibold text-neutral-100 tracking-tight">Settings</h2>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto p-2">
|
|
{NAV.map((item) => {
|
|
const Icon = item.icon;
|
|
const isActive = section === item.id;
|
|
return (
|
|
<button
|
|
key={item.id}
|
|
type="button"
|
|
onClick={() => setSection(item.id)}
|
|
className={`flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium rounded-md w-full transition-colors text-left ${
|
|
isActive
|
|
? "bg-neutral-800 text-neutral-100 border-l-2 border-blue-500"
|
|
: "text-neutral-400 hover:bg-neutral-800 hover:text-neutral-100 border-l-2 border-transparent"
|
|
}`}
|
|
>
|
|
<Icon size={16} strokeWidth={1.5} />
|
|
<span className="flex-1">{item.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 px-4 py-2.5 border-t border-neutral-800 shrink-0">
|
|
<span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${statusColor(snapshot)}`} />
|
|
<span className="text-xs text-neutral-500">
|
|
{runtimeStatusLabel}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="p-3 px-4 border-t border-neutral-800 shrink-0">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="text-[13px] text-neutral-500 hover:text-neutral-100 transition-colors"
|
|
>
|
|
← Back to Chat
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|