agentlytics 0.1.19 → 0.2.0

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/ui/src/App.jsx CHANGED
@@ -1,9 +1,10 @@
1
1
  import { useState, useEffect, useRef, useCallback } from 'react'
2
- import { Routes, Route, NavLink } from 'react-router-dom'
3
- import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, CreditCard, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Plug, Copy, Check, Settings as SettingsIcon } from 'lucide-react'
2
+ import { Routes, Route, NavLink, useLocation } from 'react-router-dom'
3
+ import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, CreditCard, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Plug, Copy, Check, Settings as SettingsIcon, Package, ChevronDown } 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'
7
+ import AnimatedLoader from './components/AnimatedLoader'
7
8
  import LoginScreen from './components/LoginScreen'
8
9
  import Dashboard from './pages/Dashboard'
9
10
  import Sessions from './pages/Sessions'
@@ -13,11 +14,58 @@ import Projects from './pages/Projects'
13
14
  import ProjectDetail from './pages/ProjectDetail'
14
15
  import CostAnalysis from './pages/CostAnalysis'
15
16
  import SqlViewer from './pages/SqlViewer'
17
+ import Artifacts from './pages/Artifacts'
16
18
  import Settings from './pages/Settings'
17
19
  import Subscriptions from './pages/Subscriptions'
18
20
  import RelayDashboard from './pages/RelayDashboard'
19
21
  import RelayUserDetail from './pages/RelayUserDetail'
20
22
 
23
+ function NavDropdown({ icon: Icon, label, items }) {
24
+ const [open, setOpen] = useState(false)
25
+ const location = useLocation()
26
+ const isActive = items.some(i => i.to === location.pathname)
27
+ const timeout = useRef(null)
28
+
29
+ const enter = () => { clearTimeout(timeout.current); setOpen(true) }
30
+ const leave = () => { timeout.current = setTimeout(() => setOpen(false), 150) }
31
+
32
+ return (
33
+ <div className="relative" onMouseEnter={enter} onMouseLeave={leave}>
34
+ <button
35
+ className={`flex items-center gap-1.5 px-2.5 py-1 text-[12px] rounded transition ${
36
+ isActive ? 'bg-[var(--c-card)] text-[var(--c-white)]' : 'text-[var(--c-text2)] hover:text-[var(--c-white)]'
37
+ }`}
38
+ >
39
+ <Icon size={12} />
40
+ {label}
41
+ <ChevronDown size={10} style={{ opacity: 0.5 }} />
42
+ </button>
43
+ {open && (
44
+ <div
45
+ className="absolute top-full left-0 mt-1 py-1 rounded shadow-lg min-w-[160px] z-[100]"
46
+ style={{ background: 'var(--c-bg)', border: '1px solid var(--c-border)' }}
47
+ >
48
+ {items.map(({ to, icon: SubIcon, label: subLabel }) => (
49
+ <NavLink
50
+ key={to}
51
+ to={to}
52
+ onClick={() => setOpen(false)}
53
+ className={({ isActive: a }) =>
54
+ `flex items-center gap-2 px-3 py-1.5 text-[12px] transition ${
55
+ a ? 'bg-[var(--c-bg3)] text-[var(--c-white)]' : 'text-[var(--c-text2)] hover:text-[var(--c-white)] hover:bg-[var(--c-bg3)]'
56
+ }`
57
+ }
58
+ >
59
+ <SubIcon size={12} />
60
+ {subLabel}
61
+ </NavLink>
62
+ ))}
63
+ </div>
64
+ )}
65
+ </div>
66
+ )
67
+ }
68
+
21
69
  export default function App() {
22
70
  const [overview, setOverview] = useState(null)
23
71
  const [refetchState, setRefetchState] = useState(null) // null | { scanned, total }
@@ -82,16 +130,24 @@ export default function App() {
82
130
  const isRelay = mode === 'relay'
83
131
  const showLogin = isRelay && needsAuth && !authed
84
132
 
133
+ const location = useLocation()
134
+ const isFullWidth = location.pathname === '/artifacts'
135
+
85
136
  const nav = isRelay ? [
86
137
  { to: '/', icon: Users, label: 'Team' },
87
138
  ] : [
88
139
  { to: '/', icon: Activity, label: 'Dashboard' },
89
- { to: '/projects', icon: FolderOpen, label: 'Projects' },
90
140
  { to: '/sessions', icon: MessageSquare, label: 'Sessions' },
91
- { to: '/costs', icon: DollarSign, label: 'Costs' },
92
- { to: '/analysis', icon: BarChart3, label: 'Analysis' },
93
- { to: '/compare', icon: GitCompare, label: 'Compare' },
94
- { to: '/subscriptions', icon: CreditCard, label: 'Subscriptions' },
141
+ { to: '/projects', icon: FolderOpen, label: 'Projects' },
142
+ { icon: DollarSign, label: 'Costs', children: [
143
+ { to: '/costs', icon: DollarSign, label: 'Cost Analysis' },
144
+ { to: '/subscriptions', icon: CreditCard, label: 'Subscriptions' },
145
+ ]},
146
+ { icon: BarChart3, label: 'Insights', children: [
147
+ { to: '/analysis', icon: BarChart3, label: 'Deep Analysis' },
148
+ { to: '/compare', icon: GitCompare, label: 'Compare' },
149
+ ]},
150
+ { to: '/artifacts', icon: Package, label: 'Artifacts' },
95
151
  { to: '/sql', icon: Database, label: 'SQL' },
96
152
  ]
97
153
 
@@ -107,19 +163,21 @@ export default function App() {
107
163
  Agentlytics{isRelay && <span className="ml-1.5 text-[10px] font-medium px-1.5 py-0.5" style={{ background: 'rgba(99,102,241,0.15)', color: '#818cf8' }}>relay</span>}
108
164
  </span>
109
165
  <nav className="flex gap-0.5 ml-2">
110
- {nav.map(({ to, icon: Icon, label }) => (
166
+ {nav.map((item) => item.children ? (
167
+ <NavDropdown key={item.label} icon={item.icon} label={item.label} items={item.children} />
168
+ ) : (
111
169
  <NavLink
112
- key={to}
113
- to={to}
114
- end={to === '/'}
170
+ key={item.to}
171
+ to={item.to}
172
+ end={item.to === '/'}
115
173
  className={({ isActive }) =>
116
174
  `flex items-center gap-1.5 px-2.5 py-1 text-[12px] rounded transition ${
117
175
  isActive ? 'bg-[var(--c-card)] text-[var(--c-white)]' : 'text-[var(--c-text2)] hover:text-[var(--c-white)]'
118
176
  }`
119
177
  }
120
178
  >
121
- <Icon size={12} />
122
- {label}
179
+ <item.icon size={12} />
180
+ {item.label}
123
181
  </NavLink>
124
182
  ))}
125
183
  </nav>
@@ -196,9 +254,9 @@ export default function App() {
196
254
  </div>
197
255
  )}
198
256
 
199
- <main className={isRelay ? 'px-0' : 'p-4 max-w-[1400px] mx-auto'}>
257
+ <main className={isRelay ? 'px-0' : isFullWidth ? 'p-0 overflow-hidden' : 'p-4 max-w-[1400px] mx-auto'}>
200
258
  {mode === null ? (
201
- <div className="text-sm py-12 text-center" style={{ color: 'var(--c-text2)' }}>loading...</div>
259
+ <AnimatedLoader label="Loading..." />
202
260
  ) : isRelay ? (
203
261
  <Routes>
204
262
  <Route path="/" element={<RelayDashboard />} />
@@ -216,13 +274,14 @@ export default function App() {
216
274
  <Route path="/analysis" element={<DeepAnalysis overview={overview} />} />
217
275
  <Route path="/compare" element={<Compare overview={overview} />} />
218
276
  <Route path="/subscriptions" element={<Subscriptions />} />
277
+ <Route path="/artifacts" element={<Artifacts />} />
219
278
  <Route path="/sql" element={<SqlViewer />} />
220
279
  <Route path="/settings" element={<Settings />} />
221
280
  </Routes>
222
281
  )}
223
282
  </main>
224
283
 
225
- <footer className="border-t mt-8 px-4 py-3 flex items-center justify-between text-[11px]" style={{ borderColor: 'var(--c-border)', color: 'var(--c-text3)' }}>
284
+ <footer className={`border-t mt-8 px-4 py-3 flex items-center justify-between text-[11px]${isFullWidth ? ' hidden' : ''}`} style={{ borderColor: 'var(--c-border)', color: 'var(--c-text3)' }}>
226
285
  <div className="flex items-center gap-3">
227
286
  <a href="https://github.com/f/agentlytics" target="_blank" rel="noopener noreferrer" className="flex items-center gap-1 hover:text-[var(--c-text)] transition">
228
287
  <Github size={11} />
@@ -68,13 +68,14 @@ function Tip({ missing }) {
68
68
  }
69
69
 
70
70
  export default function AiAuditCard({ folder }) {
71
+ console.log('AiAuditCard rendering, folder:', folder)
71
72
  const [audit, setAudit] = useState(null)
72
73
  const [loading, setLoading] = useState(false)
73
74
  const [error, setError] = useState(null)
74
75
  const [expanded, setExpanded] = useState(new Set())
75
- const ran = useRef(false)
76
76
 
77
77
  const runAudit = async () => {
78
+ if (!folder) return
78
79
  setLoading(true)
79
80
  setError(null)
80
81
  try {
@@ -88,10 +89,8 @@ export default function AiAuditCard({ folder }) {
88
89
  }
89
90
 
90
91
  useEffect(() => {
91
- if (folder && !ran.current) {
92
- ran.current = true
93
- runAudit()
94
- }
92
+ console.log('AiAuditCard useEffect triggered, folder:', folder)
93
+ runAudit()
95
94
  }, [folder])
96
95
 
97
96
  if (loading) {
@@ -0,0 +1,14 @@
1
+ import AnimatedLogo from './AnimatedLogo'
2
+
3
+ export default function AnimatedLoader({ label = 'Loading...' }) {
4
+ return (
5
+ <div className="flex flex-col items-center justify-center py-16 gap-3">
6
+ <div style={{ opacity: 0.7 }}>
7
+ <AnimatedLogo size={32} />
8
+ </div>
9
+ {label && (
10
+ <span className="text-[12px]" style={{ color: 'var(--c-text3)' }}>{label}</span>
11
+ )}
12
+ </div>
13
+ )
14
+ }
package/ui/src/lib/api.js CHANGED
@@ -214,6 +214,19 @@ export async function fetchUsage() {
214
214
  return res.json();
215
215
  }
216
216
 
217
+ // ── Artifacts API ──
218
+
219
+ export async function fetchArtifacts() {
220
+ const res = await fetch(`${BASE}/api/artifacts`);
221
+ return res.json();
222
+ }
223
+
224
+ export async function fetchArtifactContent(filePath) {
225
+ const q = new URLSearchParams({ path: filePath });
226
+ const res = await fetch(`${BASE}/api/artifact-content?${q}`);
227
+ return res.json();
228
+ }
229
+
217
230
  // ── Relay API ──
218
231
 
219
232
  export async function fetchMode() {