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/README.md +59 -32
- package/cache.js +76 -14
- package/editors/base.js +0 -116
- package/editors/codex.js +0 -11
- package/editors/index.js +1 -9
- package/editors/opencode.js +1 -1
- package/editors/windsurf.js +3 -3
- package/editors/zed.js +3 -3
- package/index.js +3 -1
- package/package.json +3 -3
- package/pricing.js +88 -0
- package/pricing.json +822 -0
- package/relay-client.js +0 -4
- package/ui/src/App.jsx +1 -2
- package/ui/src/components/LiveFeed.jsx +0 -10
- package/ui/src/components/MessageRenderer.jsx +0 -19
- package/ui/src/lib/api.js +0 -5
- package/ui/src/pages/DeepAnalysis.jsx +2 -2
- package/ui/src/pages/ProjectDetail.jsx +0 -1
- package/ui/src/pages/Projects.jsx +1 -1
- package/ui/src/pages/RelayDashboard.jsx +2 -2
- package/ui/src/pages/RelayUserDetail.jsx +1 -3
- package/ui/src/components/EditorBreakdown.jsx +0 -22
- package/ui/src/components/ModelBreakdown.jsx +0 -23
- package/ui/src/pages/ChatDetail.jsx +0 -107
- package/ui/src/pages/RelaySessionDetail.jsx +0 -32
package/relay-client.js
CHANGED
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
}
|