improve log display and add a small stats table in ui (#178)
This commit is contained in:
@@ -10,10 +10,10 @@ function App() {
|
|||||||
<Router basename="/ui/">
|
<Router basename="/ui/">
|
||||||
<APIProvider>
|
<APIProvider>
|
||||||
<div>
|
<div>
|
||||||
<nav className="bg-surface border-b border-border p-4">
|
<nav className="bg-surface border-b border-border p-2 h-[75px]">
|
||||||
<div className="flex items-center justify-between mx-auto px-4">
|
<div className="flex items-center justify-between mx-auto px-4 h-full">
|
||||||
<h1>llama-swap</h1>
|
<h1 className="flex items-center p-0">llama-swap</h1>
|
||||||
<div className="flex space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<NavLink to="/" className={({ isActive }) => (isActive ? "navlink active" : "navlink")}>
|
<NavLink to="/" className={({ isActive }) => (isActive ? "navlink active" : "navlink")}>
|
||||||
Logs
|
Logs
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|||||||
18
ui/src/lib/Utils.ts
Normal file
18
ui/src/lib/Utils.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function processEvalTimes(text: string) {
|
||||||
|
const lines = text.match(/^ *eval time.*$/gm) || [];
|
||||||
|
|
||||||
|
let totalTokens = 0;
|
||||||
|
let totalTime = 0;
|
||||||
|
|
||||||
|
lines.forEach((line) => {
|
||||||
|
const tokensMatch = line.match(/\/\s*(\d+)\s*tokens/);
|
||||||
|
const timeMatch = line.match(/=\s*(\d+\.\d+)\s*ms/);
|
||||||
|
|
||||||
|
if (tokensMatch) totalTokens += parseFloat(tokensMatch[1]);
|
||||||
|
if (timeMatch) totalTime += parseFloat(timeMatch[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const avgTokensPerSecond = totalTime > 0 ? totalTokens / (totalTime / 1000) : 0;
|
||||||
|
|
||||||
|
return [lines.length, totalTokens, Math.round(avgTokensPerSecond * 100) / 100];
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ const LogViewer = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5" style={{ height: "calc(100vh - 125px)" }}>
|
||||||
<LogPanel id="proxy" title="Proxy Logs" logData={proxyLogs} />
|
<LogPanel id="proxy" title="Proxy Logs" logData={proxyLogs} />
|
||||||
<LogPanel id="upstream" title="Upstream Logs" logData={upstreamLogs} />
|
<LogPanel id="upstream" title="Upstream Logs" logData={upstreamLogs} />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,11 +30,8 @@ interface LogPanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
||||||
|
const [isCollapsed, setIsCollapsed] = usePersistentState(`logPanel-${id}-isCollapsed`, false);
|
||||||
const [filterRegex, setFilterRegex] = useState("");
|
const [filterRegex, setFilterRegex] = useState("");
|
||||||
const [panelState, setPanelState] = usePersistentState<"hide" | "small" | "max">(
|
|
||||||
`logPanel-${id}-panelState`,
|
|
||||||
"small"
|
|
||||||
);
|
|
||||||
const [fontSize, setFontSize] = usePersistentState<"xxs" | "xs" | "small" | "normal">(
|
const [fontSize, setFontSize] = usePersistentState<"xxs" | "xs" | "small" | "normal">(
|
||||||
`logPanel-${id}-fontSize`,
|
`logPanel-${id}-fontSize`,
|
||||||
"normal"
|
"normal"
|
||||||
@@ -60,14 +57,6 @@ export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const togglePanelState = useCallback(() => {
|
|
||||||
setPanelState((prev) => {
|
|
||||||
if (prev === "small") return "max";
|
|
||||||
if (prev === "hide") return "small";
|
|
||||||
return "hide";
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fontSizeClass = useMemo(() => {
|
const fontSizeClass = useMemo(() => {
|
||||||
switch (fontSize) {
|
switch (fontSize) {
|
||||||
case "xxs":
|
case "xxs":
|
||||||
@@ -101,20 +90,21 @@ export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
|||||||
}, [filteredLogs]);
|
}, [filteredLogs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`bg-surface border border-border rounded-lg overflow-hidden flex flex-col ${className || ""}`}>
|
<div
|
||||||
|
className={`bg-surface border border-border rounded-lg overflow-hidden flex flex-col ${
|
||||||
|
!isCollapsed && "h-full"
|
||||||
|
} ${className || ""}`}
|
||||||
|
>
|
||||||
<div className="p-4 border-b border-border bg-secondary">
|
<div className="p-4 border-b border-border bg-secondary">
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||||
{/* Title - Always full width on mobile, normal on desktop */}
|
{/* Title - Always full width on mobile, normal on desktop */}
|
||||||
<div className="w-full md:w-auto" onClick={togglePanelState}>
|
<div className="w-full md:w-auto" onClick={() => setIsCollapsed(!isCollapsed)}>
|
||||||
<h3 className="m-0 text-lg">{title}</h3>
|
<h3 className="m-0 text-lg">{title}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 w-full md:w-auto">
|
<div className="flex flex-col sm:flex-row gap-4 w-full md:w-auto">
|
||||||
{/* Sizing Buttons - Stacks vertically on mobile */}
|
{/* Sizing Buttons - Stacks vertically on mobile */}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<button className="btn" onClick={togglePanelState}>
|
|
||||||
size: {panelState}
|
|
||||||
</button>
|
|
||||||
<button className="btn" onClick={toggleFontSize}>
|
<button className="btn" onClick={toggleFontSize}>
|
||||||
font: {fontSize}
|
font: {fontSize}
|
||||||
</button>
|
</button>
|
||||||
@@ -140,14 +130,11 @@ export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{panelState !== "hide" && (
|
{!isCollapsed && (
|
||||||
<div className="flex-1 bg-background font-mono text-sm leading-[1.4] p-3">
|
<div className="flex-1 bg-background font-mono text-sm p-3 overflow-hidden">
|
||||||
<pre
|
<pre
|
||||||
ref={preTagRef}
|
ref={preTagRef}
|
||||||
className={`flex-1 p-4 overflow-y-auto whitespace-pre min-h-0 ${textWrapClass} ${fontSizeClass}`}
|
className={`h-full p-4 overflow-y-auto whitespace-pre min-h-0 ${textWrapClass} ${fontSizeClass}`}
|
||||||
style={{
|
|
||||||
maxHeight: panelState === "max" ? "1500px" : "500px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{filteredLogs}
|
{filteredLogs}
|
||||||
</pre>
|
</pre>
|
||||||
@@ -156,5 +143,4 @@ export const LogPanel = ({ id, title, logData, className }: LogPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LogViewer;
|
export default LogViewer;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { useAPI } from "../contexts/APIProvider";
|
import { useAPI } from "../contexts/APIProvider";
|
||||||
import { LogPanel } from "./LogViewer";
|
import { LogPanel } from "./LogViewer";
|
||||||
|
import { processEvalTimes } from "../lib/Utils";
|
||||||
|
|
||||||
export default function ModelsPage() {
|
export default function ModelsPage() {
|
||||||
const { models, enableModelUpdates, unloadAllModels, loadModel, upstreamLogs, enableUpstreamLogs } = useAPI();
|
const { models, enableModelUpdates, unloadAllModels, loadModel, upstreamLogs, enableUpstreamLogs } = useAPI();
|
||||||
@@ -29,8 +30,12 @@ export default function ModelsPage() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const [totalLines, totalTokens, avgTokensPerSecond] = useMemo(() => {
|
||||||
|
return processEvalTimes(upstreamLogs);
|
||||||
|
}, [upstreamLogs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen">
|
<div>
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<div className="w-full md:w-1/2 flex items-top">
|
<div className="w-full md:w-1/2 flex items-top">
|
||||||
@@ -56,7 +61,13 @@ export default function ModelsPage() {
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<button className="btn btn--sm" disabled={model.state !== "stopped"} onClick={() => loadModel(model.id)}>Load</button>
|
<button
|
||||||
|
className="btn btn--sm"
|
||||||
|
disabled={model.state !== "stopped"}
|
||||||
|
onClick={() => loadModel(model.id)}
|
||||||
|
>
|
||||||
|
Load
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-2">
|
<td className="p-2">
|
||||||
<span className={`status status--${model.state}`}>{model.state}</span>
|
<span className={`status status--${model.state}`}>{model.state}</span>
|
||||||
@@ -69,8 +80,29 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Column */}
|
{/* Right Column */}
|
||||||
<div className="w-full md:w-1/2 flex items-top">
|
<div className="w-full md:w-1/2 flex flex-col" style={{ height: "calc(100vh - 125px)" }}>
|
||||||
<LogPanel id="modelsupstream" title="Upstream Logs" logData={upstreamLogs} className="h-full" />
|
<div className="card mb-4 min-h-[250px]">
|
||||||
|
<h2>Log Stats</h2>
|
||||||
|
<p className="italic my-2">note: eval logs from llama-server</p>
|
||||||
|
<table className="w-full border border-gray-200">
|
||||||
|
<tbody>
|
||||||
|
<tr className="border-b border-gray-200">
|
||||||
|
<td className="py-2 px-4 font-medium border-r border-gray-200">Requests</td>
|
||||||
|
<td className="py-2 px-4 text-right">{totalLines}</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-b border-gray-200">
|
||||||
|
<td className="py-2 px-4 font-medium border-r border-gray-200">Total Tokens Generated</td>
|
||||||
|
<td className="py-2 px-4 text-right">{totalTokens}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-2 px-4 font-medium border-r border-gray-200">Average Tokens/Second</td>
|
||||||
|
<td className="py-2 px-4 text-right">{avgTokensPerSecond}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LogPanel id="modelsupstream" title="Upstream Logs" logData={upstreamLogs} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user