diff --git a/ui/src/contexts/APIProvider.tsx b/ui/src/contexts/APIProvider.tsx index 9344359..f528777 100644 --- a/ui/src/contexts/APIProvider.tsx +++ b/ui/src/contexts/APIProvider.tsx @@ -44,16 +44,16 @@ interface APIEventEnvelope { const APIContext = createContext(undefined); type APIProviderProps = { children: ReactNode; + autoStartAPIEvents?: boolean; }; -export function APIProvider({ children }: APIProviderProps) { +export function APIProvider({ children, autoStartAPIEvents = true }: APIProviderProps) { const [proxyLogs, setProxyLogs] = useState(""); const [upstreamLogs, setUpstreamLogs] = useState(""); const [metrics, setMetrics] = useState([]); const apiEventSource = useRef(null); const [models, setModels] = useState([]); - const modelStatusEventSource = useRef(null); const appendLog = useCallback((newData: string, setter: React.Dispatch>) => { setter((prev) => { @@ -66,6 +66,7 @@ export function APIProvider({ children }: APIProviderProps) { if (!enabled) { apiEventSource.current?.close(); apiEventSource.current = null; + setMetrics([]); return; } @@ -101,7 +102,7 @@ export function APIProvider({ children }: APIProviderProps) { case "metrics": { const newMetric = JSON.parse(message.data) as Metrics; - setMetrics(prevMetrics => { + setMetrics((prevMetrics) => { return [newMetric, ...prevMetrics]; }); } @@ -125,10 +126,14 @@ export function APIProvider({ children }: APIProviderProps) { }, []); useEffect(() => { + if (autoStartAPIEvents) { + enableAPIEvents(true); + } + return () => { - modelStatusEventSource.current?.close(); + enableAPIEvents(false); }; - }, []); + }, [enableAPIEvents, autoStartAPIEvents]); const listModels = useCallback(async (): Promise => { try { diff --git a/ui/src/lib/Utils.ts b/ui/src/lib/Utils.ts deleted file mode 100644 index 6352efb..0000000 --- a/ui/src/lib/Utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -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]; -} diff --git a/ui/src/pages/Activity.tsx b/ui/src/pages/Activity.tsx index 0f4832e..890bbe9 100644 --- a/ui/src/pages/Activity.tsx +++ b/ui/src/pages/Activity.tsx @@ -1,17 +1,10 @@ -import { useState, useEffect } from 'react'; -import { useAPI } from '../contexts/APIProvider'; +import { useState, useEffect } from "react"; +import { useAPI } from "../contexts/APIProvider"; const ActivityPage = () => { - const { metrics, enableAPIEvents } = useAPI(); + const { metrics } = useAPI(); const [error, setError] = useState(null); - useEffect(() => { - enableAPIEvents(true); - return () => { - enableAPIEvents(false); - }; - }, []); - useEffect(() => { if (metrics.length > 0) { setError(null); @@ -23,11 +16,11 @@ const ActivityPage = () => { }; const formatSpeed = (speed: number) => { - return speed.toFixed(2) + ' t/s'; + return speed.toFixed(2) + " t/s"; }; const formatDuration = (ms: number) => { - return (ms / 1000).toFixed(2) + 's'; + return (ms / 1000).toFixed(2) + "s"; }; if (error) { @@ -54,47 +47,23 @@ const ActivityPage = () => { - - - - - - + + + + + + {metrics.map((metric, index) => ( - - - - - - + + + + + + ))} diff --git a/ui/src/pages/LogViewer.tsx b/ui/src/pages/LogViewer.tsx index fc31dba..8bfcd52 100644 --- a/ui/src/pages/LogViewer.tsx +++ b/ui/src/pages/LogViewer.tsx @@ -3,14 +3,7 @@ import { useAPI } from "../contexts/APIProvider"; import { usePersistentState } from "../hooks/usePersistentState"; const LogViewer = () => { - const { proxyLogs, upstreamLogs, enableAPIEvents } = useAPI(); - - useEffect(() => { - enableAPIEvents(true); - return () => { - enableAPIEvents(false); - }; - }, []); + const { proxyLogs, upstreamLogs } = useAPI(); return (
diff --git a/ui/src/pages/Models.tsx b/ui/src/pages/Models.tsx index 0c1ad7b..876a94c 100644 --- a/ui/src/pages/Models.tsx +++ b/ui/src/pages/Models.tsx @@ -1,11 +1,10 @@ import { useState, useEffect, useCallback, useMemo } from "react"; import { useAPI } from "../contexts/APIProvider"; import { LogPanel } from "./LogViewer"; -import { processEvalTimes } from "../lib/Utils"; import { usePersistentState } from "../hooks/usePersistentState"; export default function ModelsPage() { - const { models, unloadAllModels, loadModel, upstreamLogs, enableAPIEvents } = useAPI(); + const { models, unloadAllModels, loadModel, upstreamLogs, metrics } = useAPI(); const [isUnloading, setIsUnloading] = useState(false); const [showUnlisted, setShowUnlisted] = usePersistentState("showUnlisted", true); @@ -13,13 +12,6 @@ export default function ModelsPage() { return models.filter((model) => showUnlisted || !model.unlisted); }, [models, showUnlisted]); - useEffect(() => { - enableAPIEvents(true); - return () => { - enableAPIEvents(false); - }; - }, []); - const handleUnloadAllModels = useCallback(async () => { setIsUnloading(true); try { @@ -34,9 +26,12 @@ export default function ModelsPage() { } }, []); - const [totalLines, totalTokens, avgTokensPerSecond] = useMemo(() => { - return processEvalTimes(upstreamLogs); - }, [upstreamLogs]); + const [totalRequests, totalTokens, avgTokensPerSecond] = useMemo(() => { + const totalTokens = metrics.reduce((sum, m) => sum + m.input_tokens + m.output_tokens, 0); + const totalSeconds = metrics.reduce((sum, m) => sum + m.duration_ms / 1000, 0); + const avgTokensPerSecond = totalSeconds > 0 ? totalTokens / totalSeconds : 0; + return [metrics.length, totalTokens, avgTokensPerSecond.toFixed(2)]; + }, [metrics]); return (
@@ -96,14 +91,13 @@ export default function ModelsPage() { {/* Right Column */}
-
-

Log Stats

-

note: eval logs from llama-server

+
+

Chat Activity

- Timestamp - - Model - - Input Tokens - - Output Tokens - - Processing Speed - - Duration - TimestampModelInput TokensOutput TokensProcessing SpeedDuration
- {formatTimestamp(metric.timestamp)} - - {metric.model} - - {metric.input_tokens.toLocaleString()} - - {metric.output_tokens.toLocaleString()} - - {formatSpeed(metric.tokens_per_second)} - - {formatDuration(metric.duration_ms)} - {formatTimestamp(metric.timestamp)}{metric.model}{metric.input_tokens.toLocaleString()}{metric.output_tokens.toLocaleString()}{formatSpeed(metric.tokens_per_second)}{formatDuration(metric.duration_ms)}
- +
Requests{totalLines}{totalRequests}
Total Tokens Generated