diff --git a/ui/litellm-dashboard/src/app/sessions/[sid]/view.tsx b/ui/litellm-dashboard/src/app/sessions/[sid]/view.tsx index b517755934e..09b34813edd 100644 --- a/ui/litellm-dashboard/src/app/sessions/[sid]/view.tsx +++ b/ui/litellm-dashboard/src/app/sessions/[sid]/view.tsx @@ -10,6 +10,24 @@ import React, { import { useParams, useRouter } from "next/navigation"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import { + PanelLeft, + Search, + SquarePen, + Workflow, + Home, + Bug, + Circle, + ChevronDown, + ChevronRight, + CheckCircle2, + Folder, + MoreHorizontal, + PanelRight, + ArrowUp, + Square, + Image as ImageIcon, +} from "lucide-react"; type MessageStatus = "in_progress" | "completed" | "failed"; type MessageRole = "user" | "assistant"; @@ -65,10 +83,14 @@ interface ListResponse { has_more: boolean; } +interface AgentRow { + id: string; + name: string; +} + const DEFAULT_PROXY = "http://localhost:4000"; const DEFAULT_KEY = "sk-1234"; const POLL_INTERVAL_MS = 2000; -const COLUMN_MAX_WIDTH = 720; const getProxyBase = (): string => (typeof window !== "undefined" && @@ -91,8 +113,7 @@ const formatRelative = (iso: string | null | undefined): string => { if (!iso) return ""; try { const d = new Date(iso).getTime(); - const now = Date.now(); - const diff = Math.max(0, now - d); + const diff = Math.max(0, Date.now() - d); const sec = Math.floor(diff / 1000); if (sec < 60) return `${sec}s`; const min = Math.floor(sec / 60); @@ -103,29 +124,12 @@ const formatRelative = (iso: string | null | undefined): string => { if (day < 7) return `${day}d`; const wk = Math.floor(day / 7); if (wk < 5) return `${wk}w`; - const mo = Math.floor(day / 30); - if (mo < 12) return `${mo}mo`; - return `${Math.floor(day / 365)}y`; + return `${Math.floor(day / 30)}mo`; } catch { return ""; } }; -const statusColor = (status: string): string => { - switch ((status || "").toLowerCase()) { - case "error": - case "failed": - return "#dc2626"; - case "provisioning": - case "pending": - case "queued": - return "#d97706"; - default: - // ready, terminated, anything else — subtle gray - return "#bcbcc0"; - } -}; - const groupSessionsByAge = ( sessions: SessionRow[], ): Array<{ label: string; items: SessionRow[] }> => { @@ -134,20 +138,20 @@ const groupSessionsByAge = ( const buckets: Record = { Today: [], "This Week": [], - "Last Week": [], + "This Month": [], Older: [], }; for (const s of sessions) { const age = now - new Date(s.created_at).getTime(); if (age < day) buckets.Today.push(s); else if (age < 7 * day) buckets["This Week"].push(s); - else if (age < 14 * day) buckets["Last Week"].push(s); + else if (age < 30 * day) buckets["This Month"].push(s); else buckets.Older.push(s); } return [ { label: "Today", items: buckets.Today }, { label: "This Week", items: buckets["This Week"] }, - { label: "Last Week", items: buckets["Last Week"] }, + { label: "This Month", items: buckets["This Month"] }, { label: "Older", items: buckets.Older }, ].filter((g) => g.items.length > 0); }; @@ -171,38 +175,6 @@ export default function SessionThreadView() { const messagesEndRef = useRef(null); - // Fetch sessions + agents (for the left rail) - useEffect(() => { - let cancelled = false; - (async () => { - const proxy = getProxyBase(); - const headers = buildHeaders(); - try { - const [sRes, aRes] = await Promise.all([ - fetch(`${proxy}/v2/sessions?limit=100`, { headers }), - fetch(`${proxy}/v2/agents?limit=100`, { headers }), - ]); - if (cancelled) return; - if (sRes.ok) { - const data: ListResponse = await sRes.json(); - setSessionsList(data.data || []); - } - if (aRes.ok) { - const data: ListResponse<{ id: string; name: string }> = - await aRes.json(); - const map: Record = {}; - for (const a of data.data || []) map[a.id] = a.name; - setAgentNameById(map); - } - } catch { - // silent — rail is non-critical - } - })(); - return () => { - cancelled = true; - }; - }, [sessionId]); // refresh rail when nav changes - const hasInProgress = useMemo( () => messages.some((m) => m.status === "in_progress"), [messages], @@ -216,6 +188,13 @@ export default function SessionThreadView() { return session?.default_model || ""; }, [messages, session]); + const currentAgentName = useMemo(() => { + if (session?.agent_name) return session.agent_name; + if (session) return agentNameById[session.agent_id] || session.agent_id; + return ""; + }, [session, agentNameById]); + + // Load this session + messages const loadSession = useCallback(async () => { if (!sessionId) return; setLoading(true); @@ -249,6 +228,37 @@ export default function SessionThreadView() { loadSession(); }, [loadSession]); + // Load all sessions + agents for the rail + useEffect(() => { + let cancelled = false; + (async () => { + const proxy = getProxyBase(); + const headers = buildHeaders(); + try { + const [sRes, aRes] = await Promise.all([ + fetch(`${proxy}/v2/sessions?limit=100`, { headers }), + fetch(`${proxy}/v2/agents?limit=100`, { headers }), + ]); + if (cancelled) return; + if (sRes.ok) { + const data: ListResponse = await sRes.json(); + setSessionsList(data.data || []); + } + if (aRes.ok) { + const data: ListResponse = await aRes.json(); + const map: Record = {}; + for (const a of data.data || []) map[a.id] = a.name; + setAgentNameById(map); + } + } catch { + // silent + } + })(); + return () => { + cancelled = true; + }; + }, [sessionId]); + // Poll while any message is in_progress useEffect(() => { if (!sessionId || !hasInProgress) return; @@ -329,16 +339,11 @@ export default function SessionThreadView() { const handleAbort = useCallback(async () => { if (!sessionId || aborting) return; setAborting(true); - setError(null); try { - const res = await fetch( - `${getProxyBase()}/v2/sessions/${sessionId}/abort`, - { method: "POST", headers: buildHeaders() }, - ); - if (!res.ok) { - const errText = await res.text().catch(() => ""); - throw new Error(`${res.status} ${errText || res.statusText}`); - } + await fetch(`${getProxyBase()}/v2/sessions/${sessionId}/abort`, { + method: "POST", + headers: buildHeaders(), + }); const refreshed = await fetch( `${getProxyBase()}/v2/sessions/${sessionId}/messages`, { headers: buildHeaders() }, @@ -347,8 +352,8 @@ export default function SessionThreadView() { const m: ListResponse = await refreshed.json(); setMessages(m.data || []); } - } catch (e) { - setError(e instanceof Error ? e.message : String(e)); + } catch { + // silent } finally { setAborting(false); } @@ -365,341 +370,341 @@ export default function SessionThreadView() { ); return ( -
- + router.push(`/sessions/${id}`)} /> - -
-
- - {session?.agent_name || - (session ? agentNameById[session.agent_id] : "") || - "Session"} - - {session?.status && ( - - {session.status !== "ready" && ( - - )} - {session.status} - - )} -
-
- -
-
- {loading && messages.length === 0 && ( -
- Loading… -
- )} - {!loading && messages.length === 0 && ( -
- No messages. Send one below. -
- )} - {messages.map((m) => ( - - ))} -
-
-
- -
-
-
-