agentlytics 0.0.3 → 0.0.4
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/package.json +1 -1
- package/server.js +14 -0
- package/ui/src/lib/api.js +5 -0
- package/ui/src/pages/Dashboard.jsx +65 -2
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const cache = require('./cache');
|
|
4
|
+
const { generateShareSvg } = require('./share-image');
|
|
4
5
|
|
|
5
6
|
const app = express();
|
|
6
7
|
app.use(express.json());
|
|
@@ -148,6 +149,19 @@ app.get('/api/schema', (req, res) => {
|
|
|
148
149
|
}
|
|
149
150
|
});
|
|
150
151
|
|
|
152
|
+
app.get('/api/share-image', (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const overview = cache.getCachedOverview();
|
|
155
|
+
const stats = cache.getCachedDashboardStats();
|
|
156
|
+
const svg = generateShareSvg(overview, stats);
|
|
157
|
+
res.setHeader('Content-Type', 'image/svg+xml');
|
|
158
|
+
res.send(svg);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error('Share image error:', err);
|
|
161
|
+
res.status(500).json({ error: err.message, stack: err.stack });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
151
165
|
app.get('/api/refetch', async (req, res) => {
|
|
152
166
|
res.writeHead(200, {
|
|
153
167
|
'Content-Type': 'text/event-stream',
|
package/ui/src/lib/api.js
CHANGED
|
@@ -83,6 +83,11 @@ export async function fetchSchema() {
|
|
|
83
83
|
return res.json();
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
export async function fetchShareImage() {
|
|
87
|
+
const res = await fetch(`${BASE}/api/share-image`);
|
|
88
|
+
return res.text();
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
export async function fetchToolCalls(name, opts = {}) {
|
|
87
92
|
const q = new URLSearchParams({ name });
|
|
88
93
|
if (opts.limit) q.set('limit', opts.limit);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
|
-
import { ArrowRight, X, Flame, Zap, MessageSquare, Wrench } from 'lucide-react'
|
|
3
|
+
import { ArrowRight, X, Flame, Zap, MessageSquare, Wrench, Share2 } from 'lucide-react'
|
|
4
4
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement, PointElement, LineElement, Filler } from 'chart.js'
|
|
5
5
|
import { Doughnut, Bar, Line } from 'react-chartjs-2'
|
|
6
6
|
import KpiCard from '../components/KpiCard'
|
|
7
7
|
import ActivityHeatmap from '../components/ActivityHeatmap'
|
|
8
|
-
import { fetchDailyActivity, fetchOverview as fetchOverviewApi, fetchDashboardStats } from '../lib/api'
|
|
8
|
+
import { fetchDailyActivity, fetchOverview as fetchOverviewApi, fetchDashboardStats, fetchShareImage } from '../lib/api'
|
|
9
9
|
import { editorColor, editorLabel, formatNumber } from '../lib/constants'
|
|
10
10
|
import { useTheme } from '../lib/theme'
|
|
11
11
|
|
|
@@ -30,6 +30,7 @@ export default function Dashboard({ overview }) {
|
|
|
30
30
|
const [stats, setStats] = useState(null)
|
|
31
31
|
const [selectedEditor, setSelectedEditor] = useState(null)
|
|
32
32
|
const { dark } = useTheme()
|
|
33
|
+
const [sharing, setSharing] = useState(false)
|
|
33
34
|
const txtColor = dark ? '#888' : '#555'
|
|
34
35
|
const txtDim = dark ? '#555' : '#999'
|
|
35
36
|
const gridColor = dark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)'
|
|
@@ -157,8 +158,70 @@ export default function Dashboard({ overview }) {
|
|
|
157
158
|
return s + v * midpoints[i]
|
|
158
159
|
}, 0) / tk.sessions).toFixed(1) : '—') : '—'
|
|
159
160
|
|
|
161
|
+
const handleShare = async () => {
|
|
162
|
+
setSharing(true)
|
|
163
|
+
try {
|
|
164
|
+
const svg = await fetchShareImage()
|
|
165
|
+
if (!svg || svg.startsWith('{')) throw new Error('Failed to fetch image')
|
|
166
|
+
|
|
167
|
+
// Try PNG conversion via canvas, fallback to SVG download
|
|
168
|
+
let downloaded = false
|
|
169
|
+
try {
|
|
170
|
+
const canvas = document.createElement('canvas')
|
|
171
|
+
canvas.width = 1600
|
|
172
|
+
canvas.height = 880
|
|
173
|
+
const ctx = canvas.getContext('2d')
|
|
174
|
+
const img = new Image()
|
|
175
|
+
const svgB64 = btoa(unescape(encodeURIComponent(svg)))
|
|
176
|
+
const dataUrl = `data:image/svg+xml;base64,${svgB64}`
|
|
177
|
+
await new Promise((resolve, reject) => {
|
|
178
|
+
img.onload = resolve
|
|
179
|
+
img.onerror = reject
|
|
180
|
+
img.src = dataUrl
|
|
181
|
+
})
|
|
182
|
+
ctx.drawImage(img, 0, 0, 1600, 880)
|
|
183
|
+
const pngUrl = canvas.toDataURL('image/png')
|
|
184
|
+
const a = document.createElement('a')
|
|
185
|
+
a.href = pngUrl
|
|
186
|
+
a.download = 'agentlytics.png'
|
|
187
|
+
a.click()
|
|
188
|
+
downloaded = true
|
|
189
|
+
} catch {
|
|
190
|
+
// Fallback: download SVG directly
|
|
191
|
+
const blob = new Blob([svg], { type: 'image/svg+xml' })
|
|
192
|
+
const a = document.createElement('a')
|
|
193
|
+
a.href = URL.createObjectURL(blob)
|
|
194
|
+
a.download = 'agentlytics.svg'
|
|
195
|
+
a.click()
|
|
196
|
+
URL.revokeObjectURL(a.href)
|
|
197
|
+
downloaded = true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (downloaded) {
|
|
201
|
+
const text = encodeURIComponent("Here's my agentic coding stats using github.com/f/agentlytics")
|
|
202
|
+
window.open(`https://x.com/intent/post?text=${text}`, '_blank')
|
|
203
|
+
}
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.error('Share failed:', e)
|
|
206
|
+
}
|
|
207
|
+
setSharing(false)
|
|
208
|
+
}
|
|
209
|
+
|
|
160
210
|
return (
|
|
161
211
|
<div className="fade-in space-y-3">
|
|
212
|
+
{/* Share button */}
|
|
213
|
+
<div className="flex justify-end">
|
|
214
|
+
<button
|
|
215
|
+
onClick={handleShare}
|
|
216
|
+
disabled={sharing}
|
|
217
|
+
className="flex items-center gap-1.5 px-3 py-1 text-[11px] rounded-md transition hover:opacity-80"
|
|
218
|
+
style={{ background: '#6366f1', color: '#fff', opacity: sharing ? 0.5 : 1 }}
|
|
219
|
+
>
|
|
220
|
+
<Share2 size={12} />
|
|
221
|
+
{sharing ? 'Generating...' : 'Share Stats'}
|
|
222
|
+
</button>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
162
225
|
{/* Editor breakdown - top */}
|
|
163
226
|
<div className="card p-3">
|
|
164
227
|
<SectionTitle>editors</SectionTitle>
|