From aee2bae4cec0fa79eb6f99dab1f7778ce1e2ed5e Mon Sep 17 00:00:00 2001 From: Mars Date: Sun, 22 Feb 2026 02:46:52 -0500 Subject: [PATCH] feat(web): ui/ux cleanup --- interface/src/api/client.ts | 2 + interface/src/components/Markdown.tsx | 10 +- interface/src/hooks/useChannelLiveState.ts | 11 +- interface/src/routes/AgentDetail.tsx | 121 ++++++----- interface/src/routes/AgentSkills.tsx | 221 +++++++++++-------- interface/src/routes/ChannelDetail.tsx | 76 ++++--- src/api/server.rs | 8 +- src/api/skills.rs | 233 ++++++++++++++++++++- src/conversation/history.rs | 2 +- src/tools/browser.rs | 5 +- 10 files changed, 511 insertions(+), 178 deletions(-) diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts index 837861d77..69d298aed 100644 --- a/interface/src/api/client.ts +++ b/interface/src/api/client.ts @@ -779,12 +779,14 @@ export interface RegistrySkill { skillId: string; name: string; installs: number; + description?: string; id?: string; } export interface RegistryBrowseResponse { skills: RegistrySkill[]; has_more: boolean; + total?: number; } export interface RegistrySearchResponse { diff --git a/interface/src/components/Markdown.tsx b/interface/src/components/Markdown.tsx index 81bbf057e..b903aa96b 100644 --- a/interface/src/components/Markdown.tsx +++ b/interface/src/components/Markdown.tsx @@ -1,9 +1,15 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; -export function Markdown({ children }: { children: string }) { +export function Markdown({ + children, + className, +}: { + children: string; + className?: string; +}) { return ( -
+
{ const existing = current[channelId]; if (!existing) return current; + const existingKeys = new Set(existing.timeline.map(itemKey)); + const olderItems = data.items.filter((item) => !existingKeys.has(itemKey(item))); + const hasMore = olderItems.length === 0 ? false : data.has_more; return { ...current, [channelId]: { ...existing, - timeline: [...data.items, ...existing.timeline], - hasMore: data.has_more, + timeline: [...olderItems, ...existing.timeline], + hasMore, loadingMore: false, }, }; diff --git a/interface/src/routes/AgentDetail.tsx b/interface/src/routes/AgentDetail.tsx index 4ed6f6cda..97528a299 100644 --- a/interface/src/routes/AgentDetail.tsx +++ b/interface/src/routes/AgentDetail.tsx @@ -144,35 +144,51 @@ export function AgentDetail({ agentId, liveStates }: AgentDetailProps) { {/* Memory Donut */}
-

Memory Types

- {overviewData.memory_total} +

Memory

+ + Edit +
{/* Model Routing */} {configData && ( -
+

Model Routing

Edit
- +
+ +
)} {/* Quick Stats */} -
-
+
+

Configuration

+ + Edit +
-
+
@@ -470,8 +486,8 @@ function ActivityHeatmap({ data }: { data: { day: number; hour: number; count: n const maxCount = Math.max(...data.map((d) => d.count), 1); const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const hours = Array.from({ length: 24 }, (_value, index) => index); - // Create a 7x24 grid const getCell = (day: number, hour: number) => { const cell = data.find((d) => d.day === day && d.hour === hour); return cell?.count ?? 0; @@ -483,34 +499,30 @@ function ActivityHeatmap({ data }: { data: { day: number; hour: number; count: n }; return ( -
-
- {/* Hour labels */} -
-
{/* Day label spacer */} - {Array.from({ length: 24 }, (_, h) => ( -
- {h % 6 === 0 ? h : ""} +
+
+
+
+ {hours.map((hour) => ( +
+ {hour % 6 === 0 ? hour : ""}
))}
- {/* Heatmap grid */} {days.map((dayLabel, day) => ( -
-
{dayLabel}
-
- {Array.from({ length: 24 }, (_, hour) => { - const count = getCell(day, hour); - return ( -
- ); - })} -
+
+
{dayLabel}
+ {hours.map((hour) => { + const count = getCell(day, hour); + return ( +
+ ); + })}
))}
@@ -524,14 +536,15 @@ function MemoryDonut({ counts }: { counts: Record }) { value: counts[type] ?? 0, color: MEMORY_TYPE_COLORS[idx % MEMORY_TYPE_COLORS.length], })).filter((d) => d.value > 0); + const total = data.reduce((sum, item) => sum + item.value, 0); if (data.length === 0) { return
No memories
; } return ( -
-
+
+
}) { border: `1px solid ${CHART_COLORS.tooltip.border}`, borderRadius: "6px", fontSize: "12px", + padding: "4px 6px", }} - itemStyle={{ color: CHART_COLORS.tooltip.text }} + wrapperStyle={{ zIndex: 20 }} + labelStyle={{ display: "none" }} + itemStyle={{ color: CHART_COLORS.tooltip.text, margin: 0, lineHeight: 1.2 }} /> +
+ {total} + total +
{data.map((item) => ( @@ -586,7 +606,7 @@ function ModelRoutingList({ config }: { config: { routing: { channel: string; br ]; return ( -
+
{models.map(({ label, model, color }) => (
{label} @@ -628,29 +648,32 @@ function IdentitySection({ if (!hasContent) return null; const files = [ - { label: "SOUL.md", content: identity.soul }, - { label: "IDENTITY.md", content: identity.identity }, - { label: "USER.md", content: identity.user }, + { label: "SOUL.md", tab: "soul", content: identity.soul }, + { label: "IDENTITY.md", tab: "identity", content: identity.identity }, + { label: "USER.md", tab: "user", content: identity.user }, ].filter((f) => f.content && f.content.trim().length > 0 && !f.content.startsWith("