agentlytics 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/relay-client.js CHANGED
@@ -1,9 +1,5 @@
1
1
  const chalk = require('chalk');
2
2
  const http = require('http');
3
- const https = require('https');
4
- const path = require('path');
5
- const os = require('os');
6
- const fs = require('fs');
7
3
  const readline = require('readline');
8
4
  const crypto = require('crypto');
9
5
 
package/ui/src/App.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useRef, useCallback } from 'react'
2
2
  import { Routes, Route, NavLink } from 'react-router-dom'
3
- import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Radio, Plug, Copy, Check, Settings as SettingsIcon } from 'lucide-react'
3
+ import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Plug, Copy, Check, Settings as SettingsIcon } from 'lucide-react'
4
4
  import { fetchOverview, refetchAgents, fetchMode, fetchRelayConfig, getAuthToken, setOnAuthFailure } from './lib/api'
5
5
  import { useTheme } from './lib/theme'
6
6
  import AnimatedLogo from './components/AnimatedLogo'
@@ -9,7 +9,6 @@ import Dashboard from './pages/Dashboard'
9
9
  import Sessions from './pages/Sessions'
10
10
  import DeepAnalysis from './pages/DeepAnalysis'
11
11
  import Compare from './pages/Compare'
12
- import ChatDetail from './pages/ChatDetail'
13
12
  import Projects from './pages/Projects'
14
13
  import ProjectDetail from './pages/ProjectDetail'
15
14
  import CostAnalysis from './pages/CostAnalysis'
@@ -4,15 +4,6 @@ import EditorDot from './EditorDot'
4
4
  import { editorLabel, formatNumber } from '../lib/constants'
5
5
  import { fetchRelayFeed } from '../lib/api'
6
6
 
7
- function timeAgo(ts) {
8
- if (!ts) return ''
9
- const diff = Date.now() - ts
10
- if (diff < 60000) return 'just now'
11
- if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`
12
- if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`
13
- return `${Math.floor(diff / 86400000)}d ago`
14
- }
15
-
16
7
  function timeLabel(ts) {
17
8
  if (!ts) return ''
18
9
  const d = new Date(ts)
@@ -22,7 +13,6 @@ function timeLabel(ts) {
22
13
  export default function LiveFeed({ onSessionClick }) {
23
14
  const [items, setItems] = useState([])
24
15
  const scrollRef = useRef(null)
25
- const prevCountRef = useRef(0)
26
16
 
27
17
  useEffect(() => {
28
18
  const load = () => {
@@ -146,22 +146,3 @@ export default function MessageContent({ content, toolCallDetails }) {
146
146
  })
147
147
  }
148
148
 
149
- /**
150
- * Renders a single message bubble with role icon, model tag, and content.
151
- */
152
- export function MessageBubble({ msg, toolCallDetails }) {
153
- const cfg = ROLE_CONFIG[msg.role] || ROLE_CONFIG.system
154
- const Icon = cfg.icon
155
- return (
156
- <div className="rounded-r-lg px-4 py-3" style={{ borderLeft: `2px solid ${cfg.borderColor}`, background: cfg.bg }}>
157
- <div className="flex items-center gap-2 text-xs mb-2" style={{ color: 'var(--c-text2)' }}>
158
- <Icon size={13} />
159
- <span className="font-medium">{cfg.label}</span>
160
- {msg.model && <span className="font-mono" style={{ color: 'var(--c-accent)', opacity: 0.6 }}>· {msg.model}</span>}
161
- </div>
162
- <div className="text-sm" style={{ color: 'var(--c-text)' }}>
163
- <MessageContent content={msg.content} toolCallDetails={toolCallDetails} />
164
- </div>
165
- </div>
166
- )
167
- }
package/ui/src/lib/api.js CHANGED
@@ -210,11 +210,6 @@ export async function fetchRelayTeamStats() {
210
210
  return res.json();
211
211
  }
212
212
 
213
- export async function fetchRelayUsers() {
214
- const res = await authFetch(`${BASE}/relay/users`);
215
- return res.json();
216
- }
217
-
218
213
  export async function fetchRelayUserActivity(username, opts = {}) {
219
214
  const q = new URLSearchParams();
220
215
  if (opts.folder) q.set('folder', opts.folder);
@@ -1,9 +1,9 @@
1
1
  import { useState, useEffect, useRef, useMemo } from 'react'
2
2
  import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement } from 'chart.js'
3
3
  import { Doughnut, Bar } from 'react-chartjs-2'
4
- import { Loader2, X, ArrowRight, Zap, MessageSquare, Wrench, Cpu, TrendingUp, BarChart3 } from 'lucide-react'
4
+ import { Loader2, X, Zap, MessageSquare, Wrench, Cpu, TrendingUp } from 'lucide-react'
5
5
  import { fetchDeepAnalytics, fetchToolCalls, fetchCosts } from '../lib/api'
6
- import { editorLabel, editorColor, formatNumber, formatCost, formatDateTime, dateRangeToApiParams } from '../lib/constants'
6
+ import { editorLabel, editorColor, formatNumber, formatCost, dateRangeToApiParams } from '../lib/constants'
7
7
  import { useTheme } from '../lib/theme'
8
8
  import KpiCard from '../components/KpiCard'
9
9
  import EditorIcon from '../components/EditorIcon'
@@ -80,7 +80,6 @@ export default function ProjectDetail() {
80
80
  if (!project) return <div className="text-sm py-12 text-center" style={{ color: 'var(--c-text3)' }}>project not found</div>
81
81
 
82
82
  const editorEntries = Object.entries(project.editors).sort((a, b) => b[1] - a[1])
83
- const maxEditorCount = editorEntries.length > 0 ? editorEntries[0][1] : 1
84
83
  const allEnabled = !enabledEditors || enabledEditors.size === editorEntries.length
85
84
 
86
85
  // Derive stats from editor-filtered chats
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect } from 'react'
2
2
  import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement } from 'chart.js'
3
3
  import { Doughnut, Bar } from 'react-chartjs-2'
4
- import { Search, MessageSquare, Wrench, Cpu, FolderOpen, Calendar, ArrowRight } from 'lucide-react'
4
+ import { Search, MessageSquare, Wrench, Cpu, FolderOpen, Calendar } from 'lucide-react'
5
5
  import { useNavigate } from 'react-router-dom'
6
6
  import { fetchProjects, fetchCosts } from '../lib/api'
7
7
  import { editorColor, editorLabel, formatNumber, formatCost, formatDate, dateRangeToApiParams } from '../lib/constants'
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useMemo } from 'react'
2
2
  import { useNavigate } from 'react-router-dom'
3
- import { Users, Cpu, ArrowRight, Search, Merge, MessageSquare, Zap, FolderOpen, ChevronDown, ChevronRight, User } from 'lucide-react'
3
+ import { Users, Search, Merge, MessageSquare, FolderOpen, ChevronDown, ChevronRight } from 'lucide-react'
4
4
  import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement } from 'chart.js'
5
5
  import { Doughnut, Bar } from 'react-chartjs-2'
6
6
  import KpiCard from '../components/KpiCard'
@@ -36,7 +36,7 @@ function ProportionBar({ segments, height = 6 }) {
36
36
  }
37
37
 
38
38
  // Left sidebar: team members grouped by project (folder-tree view)
39
- function TeamSidebar({ userList, userColorMap, onUserClick, selectedUser }) {
39
+ function TeamSidebar({ userList, userColorMap, selectedUser }) {
40
40
  const [collapsed, setCollapsed] = useState(new Set())
41
41
  const navigate = useNavigate()
42
42
 
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useCallback, useMemo } from 'react'
2
2
  import { useParams, useNavigate } from 'react-router-dom'
3
- import { ArrowLeft, MessageSquare, FolderOpen, ChevronDown, ChevronRight, Zap, Clock, Hash } from 'lucide-react'
3
+ import { ArrowLeft, MessageSquare, FolderOpen, ChevronDown, ChevronRight, Hash } from 'lucide-react'
4
4
  import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement } from 'chart.js'
5
5
  import { Doughnut, Bar } from 'react-chartjs-2'
6
6
  import KpiCard from '../components/KpiCard'
@@ -37,7 +37,6 @@ function ProportionBar({ segments, height = 6 }) {
37
37
  // Left sidebar: sessions grouped by project (folder-tree)
38
38
  function SessionSidebar({ sessions, projects, selectedChat, onSelectChat }) {
39
39
  const [collapsed, setCollapsed] = useState(new Set())
40
- const [filter, setFilter] = useState(null) // null = all, or project path
41
40
 
42
41
  const toggle = (key) => {
43
42
  setCollapsed(prev => {
@@ -142,7 +141,6 @@ export default function RelayUserDetail() {
142
141
  const legendColor = dark ? '#888' : '#555'
143
142
  const gridColor = dark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)'
144
143
  const txtDim = dark ? '#555' : '#999'
145
- const txtColor = dark ? '#888' : '#555'
146
144
 
147
145
  useEffect(() => {
148
146
  if (username) {
@@ -1,22 +0,0 @@
1
- import EditorDot from './EditorDot'
2
- import { editorColor, editorLabel } from '../lib/constants'
3
-
4
- export default function EditorBreakdown({ editors, total }) {
5
- return (
6
- <div className="space-y-1.5">
7
- {editors.map(([src, count]) => {
8
- const pct = total > 0 ? (count / total * 100) : 0
9
- return (
10
- <div key={src} className="flex items-center gap-2">
11
- <EditorDot source={src} size={8} />
12
- <span className="text-[11px] w-24" style={{ color: 'var(--c-text)' }}>{editorLabel(src)}</span>
13
- <div className="flex-1 h-2 relative" style={{ background: 'var(--c-card)' }}>
14
- <div className="h-full" style={{ width: `${pct}%`, background: editorColor(src), opacity: 0.7 }} />
15
- </div>
16
- <span className="text-[11px] w-10 text-right" style={{ color: 'var(--c-text2)' }}>{count}</span>
17
- </div>
18
- )
19
- })}
20
- </div>
21
- )
22
- }
@@ -1,23 +0,0 @@
1
- import { Cpu } from 'lucide-react'
2
-
3
- export default function ModelBreakdown({ models }) {
4
- const max = models[0]?.[1] || 1
5
- return (
6
- <div className="space-y-1.5">
7
- {models.map(([name, count]) => {
8
- const pct = (count / max * 100)
9
- return (
10
- <div key={name} className="flex items-center gap-2">
11
- <Cpu size={10} style={{ color: '#818cf8' }} />
12
- <span className="text-[11px] truncate w-40" style={{ color: 'var(--c-text)' }}>{name}</span>
13
- <div className="flex-1 h-2 relative" style={{ background: 'var(--c-card)' }}>
14
- <div className="h-full" style={{ width: `${pct}%`, background: '#6366f1', opacity: 0.5 }} />
15
- </div>
16
- <span className="text-[11px] w-10 text-right" style={{ color: 'var(--c-text2)' }}>{count}</span>
17
- </div>
18
- )
19
- })}
20
- {models.length === 0 && <div className="text-[11px]" style={{ color: 'var(--c-text3)' }}>No model data</div>}
21
- </div>
22
- )
23
- }
@@ -1,107 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { useParams, useNavigate } from 'react-router-dom'
3
- import { ArrowLeft, Download } from 'lucide-react'
4
- import { fetchChat, fetchCosts, BASE } from '../lib/api'
5
- import { editorColor, editorLabel, formatDateTime, formatNumber, formatCost } from '../lib/constants'
6
- import KpiCard from '../components/KpiCard'
7
- import { MessageBubble } from '../components/MessageRenderer'
8
-
9
- export default function ChatDetail() {
10
- const { id } = useParams()
11
- const navigate = useNavigate()
12
- const [chat, setChat] = useState(null)
13
- const [costs, setCosts] = useState(null)
14
- const [loading, setLoading] = useState(true)
15
-
16
- useEffect(() => {
17
- setLoading(true)
18
- fetchChat(id).then(data => {
19
- setChat(data)
20
- fetchCosts({ chatId: data.id }).then(setCosts)
21
- setLoading(false)
22
- })
23
- }, [id])
24
-
25
- if (loading) return <div className="text-sm py-12 text-center" style={{ color: 'var(--c-text2)' }}>Loading conversation...</div>
26
- if (!chat) return <div className="text-sm py-12 text-center" style={{ color: 'var(--c-text2)' }}>Chat not found.</div>
27
-
28
- const s = chat.stats
29
-
30
- return (
31
- <div className="fade-in max-w-4xl mx-auto">
32
- <button
33
- onClick={() => navigate(-1)}
34
- className="flex items-center gap-2 text-sm mb-4 transition"
35
- style={{ color: 'var(--c-text2)' }}
36
- >
37
- <ArrowLeft size={16} /> Back
38
- </button>
39
-
40
- {/* Header */}
41
- <div className="card p-5 mb-4">
42
- <div className="flex items-start justify-between">
43
- <div className="flex-1 min-w-0">
44
- <h2 className="text-xl font-semibold mb-1" style={{ color: 'var(--c-white)' }}>{chat.name || '(untitled)'}</h2>
45
- <div className="flex items-center gap-3 text-xs" style={{ color: 'var(--c-text2)' }}>
46
- <span className="inline-flex items-center gap-1.5">
47
- <span className="w-2 h-2 rounded-full" style={{ background: editorColor(chat.source) }} />
48
- {editorLabel(chat.source)}
49
- </span>
50
- {chat.mode && <span>· {chat.mode}</span>}
51
- {chat.folder && <span className="font-mono">· {chat.folder}</span>}
52
- </div>
53
- <div className="text-xs mt-1" style={{ color: 'var(--c-text3)' }}>
54
- {formatDateTime(chat.createdAt)}
55
- {chat.lastUpdatedAt && chat.lastUpdatedAt !== chat.createdAt && ` — ${formatDateTime(chat.lastUpdatedAt)}`}
56
- <span className="ml-3 font-mono" style={{ color: 'var(--c-text3)' }}>{chat.id}</span>
57
- </div>
58
- </div>
59
- <a
60
- href={`${BASE}/api/chats/${chat.id}/markdown`}
61
- download
62
- className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition shrink-0"
63
- style={{ background: 'var(--c-bg3)', color: 'var(--c-text)', border: '1px solid var(--c-border)' }}
64
- onMouseEnter={e => e.currentTarget.style.background = 'var(--c-bg2)'}
65
- onMouseLeave={e => e.currentTarget.style.background = 'var(--c-bg3)'}
66
- >
67
- <Download size={13} /> Export .md
68
- </a>
69
- </div>
70
- </div>
71
-
72
- {/* Stats */}
73
- {s && (
74
- <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-3 mb-4">
75
- <KpiCard label="Messages" value={s.totalMessages} />
76
- <KpiCard label="User" value={s.userMessages} />
77
- <KpiCard label="Assistant" value={s.assistantMessages} />
78
- <KpiCard label="Tool Calls" value={s.toolCalls.length} />
79
- {s.totalInputTokens > 0 && <KpiCard label="Input Tokens" value={formatNumber(s.totalInputTokens)} />}
80
- {s.totalOutputTokens > 0 && <KpiCard label="Output Tokens" value={formatNumber(s.totalOutputTokens)} />}
81
- {costs && costs.totalCost > 0 && <KpiCard label="Est. Cost" value={formatCost(costs.totalCost)} />}
82
- </div>
83
- )}
84
-
85
- {/* Model badges */}
86
- {s && s.models.length > 0 && (
87
- <div className="flex flex-wrap gap-2 mb-4">
88
- {[...new Set(s.models)].map(m => (
89
- <span key={m} className="text-xs bg-accent/10 text-accent-light px-2.5 py-1 rounded-full font-mono">{m}</span>
90
- ))}
91
- </div>
92
- )}
93
-
94
- {/* Messages */}
95
- <div className="space-y-3">
96
- {chat.messages.length === 0 && (
97
- <div className="text-center py-12 text-sm" style={{ color: 'var(--c-text2)' }}>
98
- {chat.encrypted ? '🔒 This conversation is encrypted.' : 'No messages found.'}
99
- </div>
100
- )}
101
- {chat.messages.map((msg, i) => (
102
- <MessageBubble key={i} msg={msg} toolCallDetails={chat.toolCallDetails} />
103
- ))}
104
- </div>
105
- </div>
106
- )
107
- }
@@ -1,32 +0,0 @@
1
- import { useCallback } from 'react'
2
- import { useParams, useSearchParams, useNavigate } from 'react-router-dom'
3
- import { fetchRelaySession } from '../lib/api'
4
- import ChatSidebar from '../components/ChatSidebar'
5
-
6
- export default function RelaySessionDetail() {
7
- const { chatId } = useParams()
8
- const [searchParams] = useSearchParams()
9
- const username = searchParams.get('username')
10
- const navigate = useNavigate()
11
-
12
- const fetchFn = useCallback(
13
- (id) => fetchRelaySession(id, username),
14
- [username]
15
- )
16
-
17
- const extraHeader = username ? (
18
- <span className="text-[11px] font-medium px-1.5 py-0.5 shrink-0" style={{ background: 'rgba(99,102,241,0.15)', color: '#818cf8' }}>
19
- {username}
20
- </span>
21
- ) : null
22
-
23
- return (
24
- <ChatSidebar
25
- chatId={chatId}
26
- onClose={() => navigate(-1)}
27
- fetchFn={fetchFn}
28
- username={username}
29
- extraHeader={extraHeader}
30
- />
31
- )
32
- }