agentfit 0.1.0 → 0.1.1
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 +30 -34
- package/app/(dashboard)/daily/page.tsx +1 -1
- package/app/(dashboard)/flow/page.tsx +17 -0
- package/app/(dashboard)/layout.tsx +2 -0
- package/app/(dashboard)/page.tsx +24 -5
- package/app/(dashboard)/reports/[id]/page.tsx +72 -0
- package/app/(dashboard)/reports/page.tsx +132 -0
- package/app/(dashboard)/sessions/[id]/page.tsx +167 -0
- package/app/(dashboard)/settings/page.tsx +180 -0
- package/app/api/backup/route.ts +215 -0
- package/app/api/check/route.ts +11 -1
- package/app/api/command-insights/route.ts +13 -0
- package/app/api/images-analysis/route.ts +3 -4
- package/app/api/reports/[id]/route.ts +23 -0
- package/app/api/reports/route.ts +50 -0
- package/app/api/reset/route.ts +21 -0
- package/app/api/session/route.ts +40 -0
- package/app/api/usage/route.ts +25 -1
- package/app/layout.tsx +1 -1
- package/bin/agentfit.mjs +2 -2
- package/components/agent-coach.tsx +256 -129
- package/components/app-sidebar.tsx +258 -8
- package/components/backup-section.tsx +236 -0
- package/components/daily-chart.tsx +404 -83
- package/components/dashboard-shell.tsx +9 -24
- package/components/data-provider.tsx +66 -2
- package/components/fitness-score.tsx +95 -54
- package/components/overview-cards.tsx +148 -41
- package/components/report-view.tsx +307 -0
- package/components/screenshots-analysis.tsx +51 -46
- package/components/session-chatlog.tsx +124 -0
- package/components/session-timeline.tsx +184 -0
- package/components/session-workflow.tsx +183 -0
- package/components/sessions-table.tsx +9 -1
- package/components/tool-flow-graph.tsx +144 -0
- package/components/ui/carousel.tsx +242 -0
- package/components/ui/sidebar.tsx +1 -1
- package/components/ui/sonner.tsx +51 -0
- package/generated/prisma/browser.ts +5 -0
- package/generated/prisma/client.ts +5 -0
- package/generated/prisma/internal/class.ts +14 -4
- package/generated/prisma/internal/prismaNamespace.ts +96 -2
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +20 -1
- package/generated/prisma/models/Report.ts +1219 -0
- package/generated/prisma/models/Session.ts +187 -1
- package/generated/prisma/models.ts +1 -0
- package/lib/coach.ts +530 -211
- package/lib/command-insights.ts +231 -0
- package/lib/db.ts +1 -1
- package/lib/parse-codex.ts +5 -0
- package/lib/parse-logs.ts +65 -0
- package/lib/queries-codex.ts +22 -0
- package/lib/queries.ts +42 -0
- package/lib/report.ts +156 -0
- package/lib/session-detail.ts +382 -0
- package/lib/sync.ts +77 -0
- package/lib/tool-flow.ts +71 -0
- package/next.config.mjs +6 -1
- package/package.json +16 -2
- package/plugins/cost-heatmap/component.tsx +72 -50
- package/prisma/schema.prisma +17 -0
- package/.claude/settings.local.json +0 -26
- package/CONTRIBUTING.md +0 -209
- package/prisma/migrations/20260328152517_init/migration.sql +0 -41
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +0 -18
- package/prisma/migrations/migration_lock.toml +0 -3
- package/prisma.config.ts +0 -14
- package/setup.sh +0 -73
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useMemo } from 'react'
|
|
3
|
+
import { useMemo, useEffect, useState } from 'react'
|
|
4
4
|
import type { UsageData } from '@/lib/parse-logs'
|
|
5
|
-
import { generateCoachInsights, type CoachInsight, type InsightCategory, type InsightSeverity } from '@/lib/coach'
|
|
5
|
+
import { generateCoachInsights, type CoachInsight, type InsightCategory, type InsightSeverity, type CraftDimension, type CraftScores } from '@/lib/coach'
|
|
6
|
+
import type { CommandInsight } from '@/lib/command-insights'
|
|
6
7
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
7
8
|
import { formatCost, formatDuration, formatNumber } from '@/lib/format'
|
|
8
9
|
import {
|
|
@@ -18,8 +19,11 @@ import {
|
|
|
18
19
|
Calendar,
|
|
19
20
|
Search,
|
|
20
21
|
Clock,
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
Brain,
|
|
23
|
+
Radar,
|
|
24
|
+
Bot,
|
|
25
|
+
Activity,
|
|
26
|
+
Gauge,
|
|
23
27
|
} from 'lucide-react'
|
|
24
28
|
|
|
25
29
|
const CATEGORY_ICONS: Record<InsightCategory, typeof Trophy> = {
|
|
@@ -33,48 +37,90 @@ const CATEGORY_ICONS: Record<InsightCategory, typeof Trophy> = {
|
|
|
33
37
|
streak: Flame,
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
const CATEGORY_LABELS: Record<InsightCategory, string> = {
|
|
37
|
-
cost: 'Cost',
|
|
38
|
-
efficiency: 'Efficiency',
|
|
39
|
-
tools: 'Tools',
|
|
40
|
-
context: 'Context',
|
|
41
|
-
model: 'Model',
|
|
42
|
-
habits: 'Habits',
|
|
43
|
-
discovery: 'Discovery',
|
|
44
|
-
streak: 'Streak',
|
|
45
|
-
}
|
|
46
|
-
|
|
47
40
|
const SEVERITY_STYLES: Record<InsightSeverity, { border: string; icon: typeof Trophy; iconClass: string }> = {
|
|
48
41
|
achievement: { border: 'border-l-chart-2', icon: Trophy, iconClass: 'text-chart-2' },
|
|
49
42
|
warning: { border: 'border-l-chart-5', icon: AlertTriangle, iconClass: 'text-chart-5' },
|
|
50
43
|
tip: { border: 'border-l-chart-1', icon: Lightbulb, iconClass: 'text-chart-1' },
|
|
51
44
|
}
|
|
52
45
|
|
|
46
|
+
const CRAFT_DIMENSIONS: {
|
|
47
|
+
key: CraftDimension
|
|
48
|
+
letter: string
|
|
49
|
+
label: string
|
|
50
|
+
color: string
|
|
51
|
+
icon: typeof Brain
|
|
52
|
+
description: string
|
|
53
|
+
metrics: string[]
|
|
54
|
+
}[] = [
|
|
55
|
+
{
|
|
56
|
+
key: 'context',
|
|
57
|
+
letter: 'C',
|
|
58
|
+
label: 'Context',
|
|
59
|
+
color: 'var(--chart-1)',
|
|
60
|
+
icon: Brain,
|
|
61
|
+
description: 'How well you engineer the context available to the AI — not just window size, but the holistic curation of tokens: system prompts (CLAUDE.md), just-in-time retrieval, structured notes, sub-agent isolation, and cache efficiency.',
|
|
62
|
+
metrics: ['Cache reuse (20%)', 'Overflow avoidance (20%)', 'Just-in-time retrieval (20%)', 'Session length (10%)', 'Note-taking (10%)', 'Output density (10%)', 'Sub-agent isolation (10%)'],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: 'reach',
|
|
66
|
+
letter: 'R',
|
|
67
|
+
label: 'Reach',
|
|
68
|
+
color: 'var(--chart-2)',
|
|
69
|
+
icon: Radar,
|
|
70
|
+
description: 'How broadly you leverage available capabilities. Using diverse tools, subagents, and custom skills means you\'re getting more out of the AI assistant.',
|
|
71
|
+
metrics: ['Tool diversity (35%)', 'Subagent parallelization (35%)', 'Skill/command adoption (30%)'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: 'autonomy',
|
|
75
|
+
letter: 'A',
|
|
76
|
+
label: 'Autonomy',
|
|
77
|
+
color: 'var(--chart-3)',
|
|
78
|
+
icon: Bot,
|
|
79
|
+
description: 'How independently the agent works for you. High autonomy means clear prompts, fewer interruptions, the agent reading before editing, and trusting it with permissions.',
|
|
80
|
+
metrics: ['Assistant/user message ratio (25%)', 'Low interruption rate (25%)', 'Read-before-edit ratio (25%)', 'Permission trust level (25%)'],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'flow',
|
|
84
|
+
letter: 'F',
|
|
85
|
+
label: 'Flow',
|
|
86
|
+
color: 'var(--chart-4)',
|
|
87
|
+
icon: Activity,
|
|
88
|
+
description: 'How consistently you maintain a coding rhythm. Regular usage builds mastery faster than sporadic intense sessions.',
|
|
89
|
+
metrics: ['Current streak length (35%)', 'Daily consistency (35%)', 'Active days coverage (30%)'],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: 'throughput',
|
|
93
|
+
letter: 'T',
|
|
94
|
+
label: 'Throughput',
|
|
95
|
+
color: 'var(--chart-6)',
|
|
96
|
+
icon: Gauge,
|
|
97
|
+
description: 'How much output you get for your investment. Efficient sessions produce more output per dollar with fewer errors, and parallel sessions multiply your throughput.',
|
|
98
|
+
metrics: ['Cost efficiency (25%)', 'Output volume (25%)', 'Parallel sessions (25%)', 'Low error rate (25%)'],
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
|
|
53
102
|
function FitnessRing({ score, label }: { score: number; label: string }) {
|
|
54
103
|
const radius = 70
|
|
55
104
|
const circumference = 2 * Math.PI * radius
|
|
56
105
|
const offset = circumference - (score / 100) * circumference
|
|
57
106
|
|
|
107
|
+
const getColor = (s: number) => {
|
|
108
|
+
if (s >= 85) return 'var(--chart-2)'
|
|
109
|
+
if (s >= 70) return 'var(--chart-1)'
|
|
110
|
+
if (s >= 55) return 'var(--chart-3)'
|
|
111
|
+
return 'var(--chart-5)'
|
|
112
|
+
}
|
|
113
|
+
|
|
58
114
|
return (
|
|
59
115
|
<div className="flex flex-col items-center gap-2">
|
|
60
116
|
<div className="relative">
|
|
61
117
|
<svg width="180" height="180" viewBox="0 0 180 180">
|
|
118
|
+
<circle cx="90" cy="90" r={radius} fill="none" stroke="var(--muted)" strokeWidth="10" />
|
|
62
119
|
<circle
|
|
63
120
|
cx="90" cy="90" r={radius}
|
|
64
|
-
fill="none"
|
|
65
|
-
|
|
66
|
-
strokeWidth="10"
|
|
67
|
-
/>
|
|
68
|
-
<circle
|
|
69
|
-
cx="90" cy="90" r={radius}
|
|
70
|
-
fill="none"
|
|
71
|
-
stroke="var(--chart-2)"
|
|
72
|
-
strokeWidth="10"
|
|
73
|
-
strokeLinecap="round"
|
|
74
|
-
strokeDasharray={circumference}
|
|
75
|
-
strokeDashoffset={offset}
|
|
121
|
+
fill="none" stroke={getColor(score)} strokeWidth="10"
|
|
122
|
+
strokeLinecap="round" strokeDasharray={circumference} strokeDashoffset={offset}
|
|
76
123
|
transform="rotate(-90 90 90)"
|
|
77
|
-
className="transition-all duration-1000"
|
|
78
124
|
/>
|
|
79
125
|
</svg>
|
|
80
126
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
@@ -86,9 +132,19 @@ function FitnessRing({ score, label }: { score: number; label: string }) {
|
|
|
86
132
|
)
|
|
87
133
|
}
|
|
88
134
|
|
|
135
|
+
function DimensionBar({ value, color }: { value: number; color: string }) {
|
|
136
|
+
return (
|
|
137
|
+
<div className="flex-1 h-3 rounded-full bg-muted overflow-hidden">
|
|
138
|
+
<div
|
|
139
|
+
className="h-full rounded-full transition-all duration-500"
|
|
140
|
+
style={{ width: `${value}%`, backgroundColor: color }}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
89
146
|
function InsightCard({ insight }: { insight: CoachInsight }) {
|
|
90
147
|
const style = SEVERITY_STYLES[insight.severity]
|
|
91
|
-
const CategoryIcon = CATEGORY_ICONS[insight.category]
|
|
92
148
|
const SeverityIcon = style.icon
|
|
93
149
|
|
|
94
150
|
return (
|
|
@@ -98,17 +154,11 @@ function InsightCard({ insight }: { insight: CoachInsight }) {
|
|
|
98
154
|
<div className="flex-1 space-y-1.5">
|
|
99
155
|
<div className="flex items-center justify-between gap-2">
|
|
100
156
|
<h4 className="text-sm font-semibold">{insight.title}</h4>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{insight.metric}
|
|
105
|
-
</span>
|
|
106
|
-
)}
|
|
107
|
-
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
108
|
-
<CategoryIcon className="h-3 w-3" />
|
|
109
|
-
{CATEGORY_LABELS[insight.category]}
|
|
157
|
+
{insight.metric && (
|
|
158
|
+
<span className="rounded-md bg-muted px-2 py-0.5 text-xs font-mono font-medium shrink-0">
|
|
159
|
+
{insight.metric}
|
|
110
160
|
</span>
|
|
111
|
-
|
|
161
|
+
)}
|
|
112
162
|
</div>
|
|
113
163
|
<p className="text-sm text-muted-foreground">{insight.description}</p>
|
|
114
164
|
{insight.recommendation && (
|
|
@@ -125,124 +175,201 @@ function InsightCard({ insight }: { insight: CoachInsight }) {
|
|
|
125
175
|
|
|
126
176
|
export function AgentCoach({ data }: { data: UsageData }) {
|
|
127
177
|
const coach = useMemo(() => generateCoachInsights(data), [data])
|
|
178
|
+
const [cmdInsights, setCmdInsights] = useState<CommandInsight[]>([])
|
|
128
179
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
fetch('/api/command-insights')
|
|
182
|
+
.then(r => r.json())
|
|
183
|
+
.then(setCmdInsights)
|
|
184
|
+
.catch(() => {})
|
|
185
|
+
}, [])
|
|
132
186
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<Card className="lg:row-span-2">
|
|
138
|
-
<CardHeader>
|
|
139
|
-
<CardTitle>Agent Fitness Score</CardTitle>
|
|
140
|
-
<CardDescription>Overall health of your human-AI collaboration</CardDescription>
|
|
141
|
-
</CardHeader>
|
|
142
|
-
<CardContent className="flex justify-center pb-6">
|
|
143
|
-
<FitnessRing score={coach.score} label={coach.scoreLabel} />
|
|
144
|
-
</CardContent>
|
|
145
|
-
</Card>
|
|
187
|
+
const allInsights: CoachInsight[] = [
|
|
188
|
+
...coach.insights,
|
|
189
|
+
...cmdInsights.map(i => ({ ...i, category: 'discovery' as InsightCategory, craft: 'reach' as CraftDimension })),
|
|
190
|
+
]
|
|
146
191
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
|
151
|
-
</CardHeader>
|
|
152
|
-
<CardContent>
|
|
153
|
-
<div className="text-2xl font-bold">{formatCost(coach.stats.avgCostPerSession)}</div>
|
|
154
|
-
</CardContent>
|
|
155
|
-
</Card>
|
|
192
|
+
// Group insights by CRAFT dimension
|
|
193
|
+
const insightsByDimension = (dim: CraftDimension) =>
|
|
194
|
+
allInsights.filter(i => i.craft === dim)
|
|
156
195
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
</CardHeader>
|
|
162
|
-
<CardContent>
|
|
163
|
-
<div className="text-2xl font-bold">{formatDuration(coach.stats.avgDurationMinutes)}</div>
|
|
164
|
-
</CardContent>
|
|
165
|
-
</Card>
|
|
196
|
+
// Ungrouped insights (no craft tag)
|
|
197
|
+
const ungroupedWarnings = allInsights.filter(i => !i.craft && i.severity === 'warning')
|
|
198
|
+
const ungroupedTips = allInsights.filter(i => !i.craft && i.severity === 'tip')
|
|
199
|
+
const ungroupedAchievements = allInsights.filter(i => !i.craft && i.severity === 'achievement')
|
|
166
200
|
|
|
201
|
+
return (
|
|
202
|
+
<div className="space-y-6">
|
|
203
|
+
{/* Hero: CRAFT Score + Framework Overview */}
|
|
204
|
+
<div className="grid gap-4 lg:grid-cols-2">
|
|
167
205
|
<Card>
|
|
168
|
-
<CardHeader className="
|
|
169
|
-
<CardTitle
|
|
170
|
-
<
|
|
206
|
+
<CardHeader className="pb-2">
|
|
207
|
+
<CardTitle>CRAFT Score</CardTitle>
|
|
208
|
+
<CardDescription>
|
|
209
|
+
Your overall AI coding proficiency, measured across 5 dimensions
|
|
210
|
+
</CardDescription>
|
|
171
211
|
</CardHeader>
|
|
172
|
-
<CardContent>
|
|
173
|
-
<
|
|
212
|
+
<CardContent className="flex items-center gap-6">
|
|
213
|
+
<FitnessRing score={coach.score} label={coach.scoreLabel} />
|
|
214
|
+
<div className="flex-1 space-y-3">
|
|
215
|
+
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
216
|
+
<span className="flex items-center gap-1">
|
|
217
|
+
<Flame className="h-3.5 w-3.5" />
|
|
218
|
+
{coach.stats.currentStreak > 0 ? `${coach.stats.currentStreak}d streak` : 'No streak'}
|
|
219
|
+
{coach.stats.longestStreak > 0 && (
|
|
220
|
+
<span className="text-xs"> (best: {coach.stats.longestStreak}d)</span>
|
|
221
|
+
)}
|
|
222
|
+
</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div className="space-y-2">
|
|
225
|
+
{CRAFT_DIMENSIONS.map(({ key, letter, label, color }) => (
|
|
226
|
+
<div key={key} className="flex items-center gap-2">
|
|
227
|
+
<span className="w-5 text-xs font-bold" style={{ color }}>{letter}</span>
|
|
228
|
+
<span className="w-20 text-xs text-muted-foreground">{label}</span>
|
|
229
|
+
<DimensionBar value={coach.craft[key]} color={color} />
|
|
230
|
+
<span className="w-7 text-xs font-semibold text-right">{coach.craft[key]}</span>
|
|
231
|
+
</div>
|
|
232
|
+
))}
|
|
233
|
+
</div>
|
|
234
|
+
<div className="text-[10px] text-muted-foreground">
|
|
235
|
+
Weights: A 25% · C/R/T 20% each · F 15%
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
174
238
|
</CardContent>
|
|
175
239
|
</Card>
|
|
176
240
|
|
|
241
|
+
{/* What is CRAFT */}
|
|
177
242
|
<Card>
|
|
178
|
-
<CardHeader className="
|
|
179
|
-
<CardTitle
|
|
180
|
-
<
|
|
243
|
+
<CardHeader className="pb-2">
|
|
244
|
+
<CardTitle>What is CRAFT?</CardTitle>
|
|
245
|
+
<CardDescription>
|
|
246
|
+
A framework for measuring Human-AI coding proficiency
|
|
247
|
+
</CardDescription>
|
|
181
248
|
</CardHeader>
|
|
182
|
-
<CardContent>
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
249
|
+
<CardContent className="text-sm space-y-3">
|
|
250
|
+
<p className="text-muted-foreground">
|
|
251
|
+
Every CRAFT metric is derived directly from your local conversation logs.
|
|
252
|
+
No external integrations or surveys needed.
|
|
253
|
+
</p>
|
|
254
|
+
<div className="grid grid-cols-5 gap-2 text-center">
|
|
255
|
+
{CRAFT_DIMENSIONS.map(({ letter, label, color, icon: Icon }) => (
|
|
256
|
+
<div key={letter} className="space-y-1">
|
|
257
|
+
<div className="flex justify-center">
|
|
258
|
+
<Icon className="h-5 w-5" style={{ color }} />
|
|
259
|
+
</div>
|
|
260
|
+
<div className="text-xs font-bold" style={{ color }}>{letter}</div>
|
|
261
|
+
<div className="text-[10px] text-muted-foreground">{label}</div>
|
|
262
|
+
</div>
|
|
263
|
+
))}
|
|
188
264
|
</div>
|
|
265
|
+
<p className="text-xs text-muted-foreground">
|
|
266
|
+
Each dimension is scored 0-100 based on your actual usage patterns. The overall score is a
|
|
267
|
+
weighted average prioritizing behavioral quality (Autonomy) over volume (Throughput).
|
|
268
|
+
</p>
|
|
189
269
|
</CardContent>
|
|
190
270
|
</Card>
|
|
191
271
|
</div>
|
|
192
272
|
|
|
193
|
-
{/*
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<
|
|
273
|
+
{/* Per-dimension breakdown with insights */}
|
|
274
|
+
{CRAFT_DIMENSIONS.map(({ key, letter, label, color, icon: Icon, description, metrics }) => {
|
|
275
|
+
const dimInsights = insightsByDimension(key)
|
|
276
|
+
const dimScore = coach.craft[key]
|
|
277
|
+
return (
|
|
278
|
+
<Card key={key}>
|
|
279
|
+
<CardHeader>
|
|
280
|
+
<div className="flex items-center justify-between">
|
|
281
|
+
<div className="flex items-center gap-3">
|
|
282
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg" style={{ backgroundColor: `color-mix(in srgb, ${color} 15%, transparent)` }}>
|
|
283
|
+
<Icon className="h-5 w-5" style={{ color }} />
|
|
284
|
+
</div>
|
|
285
|
+
<div>
|
|
286
|
+
<CardTitle className="flex items-center gap-2">
|
|
287
|
+
<span className="text-lg font-bold" style={{ color }}>{letter}</span>
|
|
288
|
+
{label}
|
|
289
|
+
</CardTitle>
|
|
290
|
+
<CardDescription>{description}</CardDescription>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
<div className="text-right">
|
|
294
|
+
<div className="text-2xl font-bold" style={{ color }}>{dimScore}</div>
|
|
295
|
+
<div className="text-xs text-muted-foreground">/ 100</div>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</CardHeader>
|
|
299
|
+
<CardContent className="space-y-4">
|
|
300
|
+
{/* How this score is calculated */}
|
|
301
|
+
<div className="rounded-md bg-muted/50 p-3">
|
|
302
|
+
<div className="text-xs font-medium mb-2">How this score is calculated:</div>
|
|
303
|
+
<div className="grid gap-1">
|
|
304
|
+
{metrics.map((m) => (
|
|
305
|
+
<div key={m} className="text-xs text-muted-foreground flex items-center gap-2">
|
|
306
|
+
<div className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: color }} />
|
|
307
|
+
{m}
|
|
308
|
+
</div>
|
|
309
|
+
))}
|
|
310
|
+
</div>
|
|
202
311
|
</div>
|
|
203
|
-
</div>
|
|
204
|
-
</CardHeader>
|
|
205
|
-
<CardContent className="space-y-3">
|
|
206
|
-
{achievements.map(i => <InsightCard key={i.id} insight={i} />)}
|
|
207
|
-
</CardContent>
|
|
208
|
-
</Card>
|
|
209
|
-
)}
|
|
210
312
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<div className="flex items-center gap-2">
|
|
216
|
-
<AlertTriangle className="h-5 w-5 text-chart-5" />
|
|
217
|
-
<div>
|
|
218
|
-
<CardTitle>Areas to Improve</CardTitle>
|
|
219
|
-
<CardDescription>Issues that may be costing you time or money</CardDescription>
|
|
313
|
+
{/* Score bar */}
|
|
314
|
+
<div className="flex items-center gap-3">
|
|
315
|
+
<DimensionBar value={dimScore} color={color} />
|
|
316
|
+
<span className="text-sm font-semibold w-10 text-right">{dimScore}/100</span>
|
|
220
317
|
</div>
|
|
221
|
-
</div>
|
|
222
|
-
</CardHeader>
|
|
223
|
-
<CardContent className="space-y-3">
|
|
224
|
-
{warnings.map(i => <InsightCard key={i.id} insight={i} />)}
|
|
225
|
-
</CardContent>
|
|
226
|
-
</Card>
|
|
227
|
-
)}
|
|
228
318
|
|
|
229
|
-
|
|
230
|
-
|
|
319
|
+
{/* Insights for this dimension */}
|
|
320
|
+
{dimInsights.length > 0 ? (
|
|
321
|
+
<div className="space-y-3">
|
|
322
|
+
{dimInsights.map(i => <InsightCard key={i.id} insight={i} />)}
|
|
323
|
+
</div>
|
|
324
|
+
) : (
|
|
325
|
+
<p className="text-sm text-muted-foreground italic">No specific insights for this dimension yet.</p>
|
|
326
|
+
)}
|
|
327
|
+
</CardContent>
|
|
328
|
+
</Card>
|
|
329
|
+
)
|
|
330
|
+
})}
|
|
331
|
+
|
|
332
|
+
{/* Ungrouped insights (if any) */}
|
|
333
|
+
{(ungroupedWarnings.length > 0 || ungroupedTips.length > 0 || ungroupedAchievements.length > 0) && (
|
|
231
334
|
<Card>
|
|
232
335
|
<CardHeader>
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
<div>
|
|
236
|
-
<CardTitle>Tips & Suggestions</CardTitle>
|
|
237
|
-
<CardDescription>Ways to get more out of your coding agent</CardDescription>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
336
|
+
<CardTitle>Other Insights</CardTitle>
|
|
337
|
+
<CardDescription>General recommendations not tied to a specific CRAFT dimension</CardDescription>
|
|
240
338
|
</CardHeader>
|
|
241
339
|
<CardContent className="space-y-3">
|
|
242
|
-
{
|
|
340
|
+
{[...ungroupedWarnings, ...ungroupedTips, ...ungroupedAchievements].map(i => (
|
|
341
|
+
<InsightCard key={i.id} insight={i} />
|
|
342
|
+
))}
|
|
243
343
|
</CardContent>
|
|
244
344
|
</Card>
|
|
245
345
|
)}
|
|
346
|
+
|
|
347
|
+
{/* Session Averages */}
|
|
348
|
+
<Card>
|
|
349
|
+
<CardHeader>
|
|
350
|
+
<CardTitle className="text-sm">Session Averages</CardTitle>
|
|
351
|
+
</CardHeader>
|
|
352
|
+
<CardContent>
|
|
353
|
+
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4 text-sm">
|
|
354
|
+
<div>
|
|
355
|
+
<div className="text-muted-foreground">Cost / Session</div>
|
|
356
|
+
<div className="font-semibold">{formatCost(coach.stats.avgCostPerSession)}</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div>
|
|
359
|
+
<div className="text-muted-foreground">Duration</div>
|
|
360
|
+
<div className="font-semibold">{formatDuration(coach.stats.avgDurationMinutes)}</div>
|
|
361
|
+
</div>
|
|
362
|
+
<div>
|
|
363
|
+
<div className="text-muted-foreground">Messages</div>
|
|
364
|
+
<div className="font-semibold">{formatNumber(Math.round(coach.stats.avgMessagesPerSession))}</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div>
|
|
367
|
+
<div className="text-muted-foreground">Peak Hour</div>
|
|
368
|
+
<div className="font-semibold">{coach.stats.peakHour}:00</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</CardContent>
|
|
372
|
+
</Card>
|
|
246
373
|
</div>
|
|
247
374
|
)
|
|
248
375
|
}
|