From 61e512ffe5f5b0f2b13eef2102d80f101865822b Mon Sep 17 00:00:00 2001 From: Hermes Date: Thu, 9 Apr 2026 21:38:33 -0500 Subject: [PATCH] Auto-refresh active sessions, collapsible skill categories, platform toolsets in config, search highlighting --- app/config/page.tsx | 20 +++++++++++++ app/history/[id]/page.tsx | 12 +++++++- app/history/page.tsx | 12 +++++++- app/skills/page.tsx | 59 +++++++++++++++++++++++++++------------ 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/app/config/page.tsx b/app/config/page.tsx index 5b00204..c11ab27 100644 --- a/app/config/page.tsx +++ b/app/config/page.tsx @@ -13,6 +13,7 @@ export default function ConfigPage() { const memory = cfg.memory as Record | undefined; const display = cfg.display as Record | undefined; const tts = cfg.tts as Record | undefined; + const platformToolsets = cfg.platform_toolsets as Record | undefined; return (
@@ -58,6 +59,25 @@ export default function ConfigPage() { ["Skin", String(display?.skin || "default")], ...(tts ? [["TTS", String(tts.provider || "—")] as [string, string]] : []), ]} /> + {platformToolsets && Object.keys(platformToolsets).length > 0 && ( +
+
Platform Toolsets
+
+ {Object.entries(platformToolsets).map(([platform, tools]) => ( +
+
{platform}
+
+ {(tools as string[]).map((t) => ( + + {t} + + ))} +
+
+ ))} +
+
+ )}
)} diff --git a/app/history/[id]/page.tsx b/app/history/[id]/page.tsx index 0024d91..c736358 100644 --- a/app/history/[id]/page.tsx +++ b/app/history/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { use, useState, useRef } from "react"; +import { use, useState, useRef, useEffect } from "react"; import { ArrowLeft, Send, Loader2, Eye, RefreshCw, Trash2 } from "lucide-react"; import Link from "next/link"; import { useSession, useMessages } from "@/lib/hooks"; @@ -37,6 +37,16 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st const session = sessionData?.session; const messages = messagesData?.items || []; + // Auto-refresh active sessions every 5s + useEffect(() => { + if (!session || session.ended_at) return; + const interval = setInterval(() => { + qc.invalidateQueries({ queryKey: ["messages", id, msgLimit] }); + qc.invalidateQueries({ queryKey: ["session", id] }); + }, 5000); + return () => clearInterval(interval); + }, [session, id, msgLimit, qc]); + // Filter based on verbosity let visibleMessages = messages.filter((m) => { if (m.role === "user") return true; diff --git a/app/history/page.tsx b/app/history/page.tsx index e592a1a..0085705 100644 --- a/app/history/page.tsx +++ b/app/history/page.tsx @@ -14,6 +14,12 @@ const PLATFORMS = [ { id: "web", label: "Web" }, ] as const; +function highlightMatch(text: string, query: string): string { + if (!query) return text; + const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return text.replace(new RegExp("(" + escaped + ")", "gi"), '$1'); +} + export default function HistoryPage() { const [filter, setFilter] = useState(null); const [query, setQuery] = useState(""); @@ -68,7 +74,11 @@ export default function HistoryPage() {
{r.session_title || r.session_id.slice(0, 8)}
-

{r.content}

+

))} diff --git a/app/skills/page.tsx b/app/skills/page.tsx index 3135b89..0f67505 100644 --- a/app/skills/page.tsx +++ b/app/skills/page.tsx @@ -2,13 +2,14 @@ import { useState } from "react"; import Link from "next/link"; -import { ChevronRight, Search, X } from "lucide-react"; +import { ChevronRight, ChevronDown, Search, X } from "lucide-react"; import { useSkills } from "@/lib/hooks"; import type { SkillSummary } from "@/lib/types"; export default function SkillsPage() { const { data, isLoading } = useSkills(); const [search, setSearch] = useState(""); + const [collapsed, setCollapsed] = useState>(new Set()); const skills = data?.skills || []; const categories = [...new Set(skills.map((s) => s.category || "uncategorized"))].sort(); @@ -21,6 +22,14 @@ export default function SkillsPage() { .map((cat) => ({ category: cat, skills: filtered.filter((s) => (s.category || "uncategorized") === cat) })) .filter((g) => g.skills.length > 0); + const toggle = (cat: string) => { + setCollapsed((prev) => { + const next = new Set(prev); + next.has(cat) ? next.delete(cat) : next.add(cat); + return next; + }); + }; + return (

@@ -48,25 +57,39 @@ export default function SkillsPage() { {isLoading ? (
{[...Array(5)].map((_, i) =>
)}
) : ( -
- {grouped.map(({ category, skills }) => ( -
-
{category}
- {skills.map((s) => ( - -
-
-
{s.name}
-
{s.description}
+
+ {grouped.map(({ category, skills }) => { + const isCollapsed = collapsed.has(category) && !search; + return ( +
+ + {!isCollapsed && skills.map((s) => ( + +
+
+
{s.name}
+
{s.description}
+
+
- -
- - ))} -
- ))} + + ))} +
+ ); + })} {grouped.length === 0 && ( -

No skills match "{search}"

+

No skills match “{search}”

)}
)}