agentfit 0.1.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/.claude/settings.local.json +26 -0
- package/.prettierignore +7 -0
- package/.prettierrc +11 -0
- package/CONTRIBUTING.md +209 -0
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/app/(dashboard)/coach/page.tsx +11 -0
- package/app/(dashboard)/commands/page.tsx +7 -0
- package/app/(dashboard)/community/[slug]/page.tsx +23 -0
- package/app/(dashboard)/community/page.tsx +71 -0
- package/app/(dashboard)/daily/page.tsx +19 -0
- package/app/(dashboard)/images/page.tsx +5 -0
- package/app/(dashboard)/layout.tsx +12 -0
- package/app/(dashboard)/page.tsx +23 -0
- package/app/(dashboard)/personality/page.tsx +11 -0
- package/app/(dashboard)/projects/page.tsx +11 -0
- package/app/(dashboard)/sessions/page.tsx +11 -0
- package/app/(dashboard)/tokens/page.tsx +11 -0
- package/app/(dashboard)/tools/page.tsx +11 -0
- package/app/api/check/route.ts +13 -0
- package/app/api/commands/route.ts +16 -0
- package/app/api/images/[...path]/route.ts +33 -0
- package/app/api/images-analysis/route.ts +177 -0
- package/app/api/sync/route.ts +14 -0
- package/app/api/usage/route.ts +117 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +144 -0
- package/app/icon.svg +3 -0
- package/app/layout.tsx +35 -0
- package/bin/agentfit.mjs +69 -0
- package/components/.gitkeep +0 -0
- package/components/agent-coach.tsx +248 -0
- package/components/app-sidebar.tsx +161 -0
- package/components/command-usage.tsx +294 -0
- package/components/daily-chart.tsx +118 -0
- package/components/daily-table.tsx +115 -0
- package/components/dashboard-shell.tsx +149 -0
- package/components/data-provider.tsx +213 -0
- package/components/fitness-score.tsx +95 -0
- package/components/overview-cards.tsx +198 -0
- package/components/pagination-controls.tsx +104 -0
- package/components/personality-fit.tsx +446 -0
- package/components/projects-table.tsx +70 -0
- package/components/screenshots-analysis.tsx +359 -0
- package/components/sessions-table.tsx +97 -0
- package/components/theme-provider.tsx +71 -0
- package/components/token-breakdown.tsx +179 -0
- package/components/tool-usage-chart.tsx +63 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/card.tsx +103 -0
- package/components/ui/chart.tsx +373 -0
- package/components/ui/dialog.tsx +160 -0
- package/components/ui/input.tsx +20 -0
- package/components/ui/scroll-area.tsx +55 -0
- package/components/ui/select.tsx +201 -0
- package/components/ui/separator.tsx +25 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +723 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +82 -0
- package/components/ui/tooltip.tsx +66 -0
- package/components.json +25 -0
- package/generated/prisma/browser.ts +34 -0
- package/generated/prisma/client.ts +58 -0
- package/generated/prisma/commonInputTypes.ts +237 -0
- package/generated/prisma/enums.ts +15 -0
- package/generated/prisma/internal/class.ts +224 -0
- package/generated/prisma/internal/prismaNamespace.ts +920 -0
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +130 -0
- package/generated/prisma/models/Image.ts +1310 -0
- package/generated/prisma/models/Session.ts +1695 -0
- package/generated/prisma/models/SyncLog.ts +1203 -0
- package/generated/prisma/models.ts +14 -0
- package/hooks/.gitkeep +0 -0
- package/hooks/use-mobile.ts +19 -0
- package/hooks/use-pagination.ts +60 -0
- package/lib/.gitkeep +0 -0
- package/lib/coach.ts +425 -0
- package/lib/commands.ts +239 -0
- package/lib/db.ts +15 -0
- package/lib/format.ts +26 -0
- package/lib/parse-codex.ts +201 -0
- package/lib/parse-logs.ts +369 -0
- package/lib/personality.ts +481 -0
- package/lib/plugins.ts +107 -0
- package/lib/pricing.ts +112 -0
- package/lib/queries-codex.ts +130 -0
- package/lib/queries.ts +154 -0
- package/lib/resolve-icon.ts +12 -0
- package/lib/sync.ts +335 -0
- package/lib/utils.ts +6 -0
- package/next.config.mjs +4 -0
- package/package.json +73 -0
- package/plugins/cost-heatmap/component.test.tsx +52 -0
- package/plugins/cost-heatmap/component.tsx +227 -0
- package/plugins/cost-heatmap/manifest.ts +13 -0
- package/plugins/index.ts +18 -0
- package/prisma/migrations/20260328152517_init/migration.sql +41 -0
- package/prisma/migrations/20260328153801_add_image_model/migration.sql +18 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +57 -0
- package/prisma.config.ts +14 -0
- package/public/.gitkeep +0 -0
- package/public/logo.svg +3 -0
- package/setup.sh +73 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from 'react'
|
|
4
|
+
import type { UsageData } from '@/lib/parse-logs'
|
|
5
|
+
import { analyzePersonality, type PersonalityProfile, type BigFiveProfile } from '@/lib/personality'
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
|
7
|
+
import { Badge } from '@/components/ui/badge'
|
|
8
|
+
import {
|
|
9
|
+
Select,
|
|
10
|
+
SelectContent,
|
|
11
|
+
SelectItem,
|
|
12
|
+
SelectTrigger,
|
|
13
|
+
SelectValue,
|
|
14
|
+
} from '@/components/ui/select'
|
|
15
|
+
import { Button } from '@/components/ui/button'
|
|
16
|
+
import { Copy, Check, Brain, Target, Sparkles, ArrowRight } from 'lucide-react'
|
|
17
|
+
|
|
18
|
+
const TASK_OPTIONS = [
|
|
19
|
+
{ value: 'frontendDev', label: 'Frontend Development' },
|
|
20
|
+
{ value: 'backendDev', label: 'Backend Development' },
|
|
21
|
+
{ value: 'dataEngineering', label: 'Data Engineering' },
|
|
22
|
+
{ value: 'devOps', label: 'DevOps' },
|
|
23
|
+
{ value: 'debugging', label: 'Debugging' },
|
|
24
|
+
{ value: 'refactoring', label: 'Refactoring' },
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const MBTI_DESCRIPTIONS: Record<string, string> = {
|
|
28
|
+
ISTJ: 'The Inspector — Methodical, detail-oriented, follows established procedures',
|
|
29
|
+
ISFJ: 'The Protector — Supportive, reliable, focused on preserving working code',
|
|
30
|
+
INFJ: 'The Counselor — Insightful, sees patterns, anticipates architectural needs',
|
|
31
|
+
INTJ: 'The Architect — Strategic, independent, designs for long-term quality',
|
|
32
|
+
ISTP: 'The Craftsman — Practical, efficient, excels at targeted fixes',
|
|
33
|
+
ISFP: 'The Composer — Adaptable, aesthetic sense, good at UI refinement',
|
|
34
|
+
INFP: 'The Healer — Idealistic, explores creative solutions, values code clarity',
|
|
35
|
+
INTP: 'The Thinker — Analytical, explores edge cases, values logical consistency',
|
|
36
|
+
ESTP: 'The Dynamo — Action-oriented, quick iterations, bias toward execution',
|
|
37
|
+
ESFP: 'The Performer — Energetic, responsive, generates many alternatives',
|
|
38
|
+
ENFP: 'The Champion — Enthusiastic explorer, broad tool usage, creative approaches',
|
|
39
|
+
ENTP: 'The Visionary — Innovative, questions assumptions, proposes novel solutions',
|
|
40
|
+
ESTJ: 'The Supervisor — Organized, efficient, follows project conventions strictly',
|
|
41
|
+
ESFJ: 'The Provider — Cooperative, communicative, adapts to user preferences',
|
|
42
|
+
ENFJ: 'The Teacher — Explains reasoning, mentors through code, proactive guidance',
|
|
43
|
+
ENTJ: 'The Commander — Decisive, takes charge, designs comprehensive solutions',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function TraitBar({
|
|
47
|
+
label,
|
|
48
|
+
value,
|
|
49
|
+
idealValue,
|
|
50
|
+
color,
|
|
51
|
+
leftLabel,
|
|
52
|
+
rightLabel,
|
|
53
|
+
}: {
|
|
54
|
+
label: string
|
|
55
|
+
value: number
|
|
56
|
+
idealValue?: number
|
|
57
|
+
color: string
|
|
58
|
+
leftLabel?: string
|
|
59
|
+
rightLabel?: string
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-1.5">
|
|
63
|
+
<div className="flex items-center justify-between text-sm">
|
|
64
|
+
<span className="font-medium">{label}</span>
|
|
65
|
+
<span className="text-muted-foreground">{Math.round(value)}/100</span>
|
|
66
|
+
</div>
|
|
67
|
+
{(leftLabel || rightLabel) && (
|
|
68
|
+
<div className="flex justify-between text-xs text-muted-foreground">
|
|
69
|
+
<span>{leftLabel}</span>
|
|
70
|
+
<span>{rightLabel}</span>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
<div className="relative h-3 w-full overflow-hidden rounded-full bg-muted">
|
|
74
|
+
<div
|
|
75
|
+
className="h-full rounded-full transition-all duration-500"
|
|
76
|
+
style={{
|
|
77
|
+
width: `${Math.max(2, value)}%`,
|
|
78
|
+
backgroundColor: color,
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
{idealValue !== undefined && (
|
|
82
|
+
<div
|
|
83
|
+
className="absolute top-0 h-full w-0.5 bg-foreground/60"
|
|
84
|
+
style={{ left: `${idealValue}%` }}
|
|
85
|
+
title={`Ideal: ${Math.round(idealValue)}`}
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function MBTIDimension({
|
|
94
|
+
label,
|
|
95
|
+
value,
|
|
96
|
+
leftPole,
|
|
97
|
+
rightPole,
|
|
98
|
+
}: {
|
|
99
|
+
label: string
|
|
100
|
+
value: number
|
|
101
|
+
leftPole: string
|
|
102
|
+
rightPole: string
|
|
103
|
+
}) {
|
|
104
|
+
const normalized = (value + 100) / 2 // -100..+100 → 0..100
|
|
105
|
+
const isLeft = value < 0
|
|
106
|
+
return (
|
|
107
|
+
<div className="space-y-1.5">
|
|
108
|
+
<div className="flex items-center justify-between text-sm">
|
|
109
|
+
<span className={`font-medium ${isLeft ? 'text-foreground' : 'text-muted-foreground'}`}>
|
|
110
|
+
{leftPole}
|
|
111
|
+
</span>
|
|
112
|
+
<span className="text-xs text-muted-foreground">{label}</span>
|
|
113
|
+
<span className={`font-medium ${!isLeft ? 'text-foreground' : 'text-muted-foreground'}`}>
|
|
114
|
+
{rightPole}
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="relative h-3 w-full overflow-hidden rounded-full bg-muted">
|
|
118
|
+
<div className="absolute top-0 left-1/2 h-full w-px bg-foreground/20" />
|
|
119
|
+
{isLeft ? (
|
|
120
|
+
<div
|
|
121
|
+
className="absolute top-0 right-1/2 h-full rounded-l-full transition-all duration-500"
|
|
122
|
+
style={{ width: `${(100 - normalized)}%`, backgroundColor: 'var(--chart-1)' }}
|
|
123
|
+
/>
|
|
124
|
+
) : (
|
|
125
|
+
<div
|
|
126
|
+
className="absolute top-0 left-1/2 h-full rounded-r-full transition-all duration-500"
|
|
127
|
+
style={{ width: `${normalized - 50}%`, backgroundColor: 'var(--chart-2)' }}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function FitScoreCard({ task, score, isSelected }: { task: string; score: number; isSelected: boolean }) {
|
|
136
|
+
const taskNames: Record<string, string> = {
|
|
137
|
+
frontendDev: 'Frontend',
|
|
138
|
+
backendDev: 'Backend',
|
|
139
|
+
dataEngineering: 'Data Eng',
|
|
140
|
+
devOps: 'DevOps',
|
|
141
|
+
debugging: 'Debug',
|
|
142
|
+
refactoring: 'Refactor',
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const getColor = (s: number) => {
|
|
146
|
+
if (s >= 80) return 'text-foreground'
|
|
147
|
+
if (s >= 60) return 'text-foreground'
|
|
148
|
+
if (s >= 40) return 'text-muted-foreground'
|
|
149
|
+
return 'text-muted-foreground'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const getBg = (s: number) => {
|
|
153
|
+
if (s >= 80) return 'bg-chart-2/10'
|
|
154
|
+
if (s >= 60) return 'bg-chart-1/10'
|
|
155
|
+
if (s >= 40) return 'bg-chart-3/10'
|
|
156
|
+
return 'bg-muted/50'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div
|
|
161
|
+
className={`rounded-lg border p-3 text-center transition-all ${
|
|
162
|
+
isSelected ? 'border-chart-1 border-2' : ''
|
|
163
|
+
} ${getBg(score)}`}
|
|
164
|
+
>
|
|
165
|
+
<div className="text-xs text-muted-foreground mb-1">{taskNames[task]}</div>
|
|
166
|
+
<div className={`text-2xl font-bold ${getColor(score)}`}>{score}%</div>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function PersonalityFit({ data }: { data: UsageData }) {
|
|
172
|
+
const [targetTask, setTargetTask] = useState('backendDev')
|
|
173
|
+
const targetTaskLabel = TASK_OPTIONS.find((t) => t.value === targetTask)?.label || targetTask
|
|
174
|
+
const [copied, setCopied] = useState(false)
|
|
175
|
+
|
|
176
|
+
const profile: PersonalityProfile = useMemo(
|
|
177
|
+
() => analyzePersonality(data, targetTask),
|
|
178
|
+
[data, targetTask]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const handleCopyPrompt = async () => {
|
|
182
|
+
await navigator.clipboard.writeText(profile.systemPrompt)
|
|
183
|
+
setCopied(true)
|
|
184
|
+
setTimeout(() => setCopied(false), 2000)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const bigFiveColors: Record<keyof BigFiveProfile, string> = {
|
|
188
|
+
openness: 'var(--chart-1)',
|
|
189
|
+
conscientiousness: 'var(--chart-2)',
|
|
190
|
+
extraversion: 'var(--chart-3)',
|
|
191
|
+
agreeableness: 'var(--chart-4)',
|
|
192
|
+
neuroticism: 'var(--chart-5)',
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const idealProfile = {
|
|
196
|
+
frontendDev: { openness: 80, conscientiousness: 60, extraversion: 70, agreeableness: 75, neuroticism: 20 },
|
|
197
|
+
backendDev: { openness: 50, conscientiousness: 90, extraversion: 40, agreeableness: 60, neuroticism: 15 },
|
|
198
|
+
dataEngineering: { openness: 45, conscientiousness: 95, extraversion: 35, agreeableness: 55, neuroticism: 10 },
|
|
199
|
+
devOps: { openness: 55, conscientiousness: 85, extraversion: 50, agreeableness: 65, neuroticism: 15 },
|
|
200
|
+
debugging: { openness: 70, conscientiousness: 80, extraversion: 55, agreeableness: 50, neuroticism: 25 },
|
|
201
|
+
refactoring: { openness: 65, conscientiousness: 90, extraversion: 45, agreeableness: 70, neuroticism: 15 },
|
|
202
|
+
}[targetTask] || { openness: 50, conscientiousness: 50, extraversion: 50, agreeableness: 50, neuroticism: 50 }
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div className="space-y-6">
|
|
206
|
+
{/* MBTI type */}
|
|
207
|
+
<Card>
|
|
208
|
+
<CardHeader className="pb-3">
|
|
209
|
+
<div className="flex items-center gap-2">
|
|
210
|
+
<Brain className="h-5 w-5 text-primary" />
|
|
211
|
+
<CardTitle>Agent Personality Type</CardTitle>
|
|
212
|
+
</div>
|
|
213
|
+
</CardHeader>
|
|
214
|
+
<CardContent>
|
|
215
|
+
<div className="flex items-center gap-4">
|
|
216
|
+
<div className="text-4xl font-bold tracking-wider text-primary">
|
|
217
|
+
{profile.mbtiType}
|
|
218
|
+
</div>
|
|
219
|
+
<div className="text-sm text-muted-foreground">
|
|
220
|
+
{MBTI_DESCRIPTIONS[profile.mbtiType] || 'Unique personality profile'}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</CardContent>
|
|
224
|
+
</Card>
|
|
225
|
+
|
|
226
|
+
{/* Task Fit Scores */}
|
|
227
|
+
<Card>
|
|
228
|
+
<CardHeader>
|
|
229
|
+
<div className="flex items-center justify-between">
|
|
230
|
+
<div>
|
|
231
|
+
<CardTitle>Task Fit Scores</CardTitle>
|
|
232
|
+
<CardDescription>
|
|
233
|
+
How well the agent's observed personality matches ideal profiles for each task type
|
|
234
|
+
</CardDescription>
|
|
235
|
+
</div>
|
|
236
|
+
<div className="flex items-center gap-2">
|
|
237
|
+
<Target className="h-4 w-4 text-muted-foreground" />
|
|
238
|
+
<span className="text-sm text-muted-foreground">Target Task</span>
|
|
239
|
+
<Select value={targetTask} onValueChange={(v) => v && setTargetTask(v)}>
|
|
240
|
+
<SelectTrigger className="w-[200px]">
|
|
241
|
+
<SelectValue placeholder={targetTaskLabel}>{targetTaskLabel}</SelectValue>
|
|
242
|
+
</SelectTrigger>
|
|
243
|
+
<SelectContent alignItemWithTrigger={false}>
|
|
244
|
+
{TASK_OPTIONS.map((opt) => (
|
|
245
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
246
|
+
{opt.label}
|
|
247
|
+
</SelectItem>
|
|
248
|
+
))}
|
|
249
|
+
</SelectContent>
|
|
250
|
+
</Select>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</CardHeader>
|
|
254
|
+
<CardContent>
|
|
255
|
+
<div className="grid grid-cols-3 gap-3 sm:grid-cols-6">
|
|
256
|
+
{Object.entries(profile.fitScores).map(([task, score]) => (
|
|
257
|
+
<FitScoreCard
|
|
258
|
+
key={task}
|
|
259
|
+
task={task}
|
|
260
|
+
score={score}
|
|
261
|
+
isSelected={task === targetTask}
|
|
262
|
+
/>
|
|
263
|
+
))}
|
|
264
|
+
</div>
|
|
265
|
+
</CardContent>
|
|
266
|
+
</Card>
|
|
267
|
+
|
|
268
|
+
{/* Big Five and MBTI side by side */}
|
|
269
|
+
<div className="grid gap-6 lg:grid-cols-2">
|
|
270
|
+
{/* Big Five */}
|
|
271
|
+
<Card>
|
|
272
|
+
<CardHeader>
|
|
273
|
+
<CardTitle>Big Five (OCEAN) Profile</CardTitle>
|
|
274
|
+
<CardDescription>
|
|
275
|
+
Derived from behavioral signals. Vertical markers show ideal for selected task.
|
|
276
|
+
</CardDescription>
|
|
277
|
+
</CardHeader>
|
|
278
|
+
<CardContent className="space-y-5">
|
|
279
|
+
<TraitBar
|
|
280
|
+
label="Openness"
|
|
281
|
+
value={profile.bigFive.openness}
|
|
282
|
+
idealValue={idealProfile.openness}
|
|
283
|
+
color={bigFiveColors.openness}
|
|
284
|
+
leftLabel="Conventional"
|
|
285
|
+
rightLabel="Exploratory"
|
|
286
|
+
/>
|
|
287
|
+
<TraitBar
|
|
288
|
+
label="Conscientiousness"
|
|
289
|
+
value={profile.bigFive.conscientiousness}
|
|
290
|
+
idealValue={idealProfile.conscientiousness}
|
|
291
|
+
color={bigFiveColors.conscientiousness}
|
|
292
|
+
leftLabel="Flexible"
|
|
293
|
+
rightLabel="Methodical"
|
|
294
|
+
/>
|
|
295
|
+
<TraitBar
|
|
296
|
+
label="Extraversion"
|
|
297
|
+
value={profile.bigFive.extraversion}
|
|
298
|
+
idealValue={idealProfile.extraversion}
|
|
299
|
+
color={bigFiveColors.extraversion}
|
|
300
|
+
leftLabel="Concise"
|
|
301
|
+
rightLabel="Verbose"
|
|
302
|
+
/>
|
|
303
|
+
<TraitBar
|
|
304
|
+
label="Agreeableness"
|
|
305
|
+
value={profile.bigFive.agreeableness}
|
|
306
|
+
idealValue={idealProfile.agreeableness}
|
|
307
|
+
color={bigFiveColors.agreeableness}
|
|
308
|
+
leftLabel="Challenging"
|
|
309
|
+
rightLabel="Compliant"
|
|
310
|
+
/>
|
|
311
|
+
<TraitBar
|
|
312
|
+
label="Neuroticism"
|
|
313
|
+
value={profile.bigFive.neuroticism}
|
|
314
|
+
idealValue={idealProfile.neuroticism}
|
|
315
|
+
color={bigFiveColors.neuroticism}
|
|
316
|
+
leftLabel="Stable"
|
|
317
|
+
rightLabel="Reactive"
|
|
318
|
+
/>
|
|
319
|
+
</CardContent>
|
|
320
|
+
</Card>
|
|
321
|
+
|
|
322
|
+
{/* MBTI */}
|
|
323
|
+
<Card>
|
|
324
|
+
<CardHeader>
|
|
325
|
+
<CardTitle>MBTI Dimensions</CardTitle>
|
|
326
|
+
<CardDescription>
|
|
327
|
+
Cognitive style inferred from interaction patterns
|
|
328
|
+
</CardDescription>
|
|
329
|
+
</CardHeader>
|
|
330
|
+
<CardContent className="space-y-5">
|
|
331
|
+
<MBTIDimension
|
|
332
|
+
label="Energy"
|
|
333
|
+
value={profile.mbti.ei}
|
|
334
|
+
leftPole="Introversion (I)"
|
|
335
|
+
rightPole="Extraversion (E)"
|
|
336
|
+
/>
|
|
337
|
+
<MBTIDimension
|
|
338
|
+
label="Information"
|
|
339
|
+
value={profile.mbti.sn}
|
|
340
|
+
leftPole="Sensing (S)"
|
|
341
|
+
rightPole="iNtuition (N)"
|
|
342
|
+
/>
|
|
343
|
+
<MBTIDimension
|
|
344
|
+
label="Decisions"
|
|
345
|
+
value={profile.mbti.tf}
|
|
346
|
+
leftPole="Thinking (T)"
|
|
347
|
+
rightPole="Feeling (F)"
|
|
348
|
+
/>
|
|
349
|
+
<MBTIDimension
|
|
350
|
+
label="Structure"
|
|
351
|
+
value={profile.mbti.jp}
|
|
352
|
+
leftPole="Judging (J)"
|
|
353
|
+
rightPole="Perceiving (P)"
|
|
354
|
+
/>
|
|
355
|
+
</CardContent>
|
|
356
|
+
</Card>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
{/* Behavioral Signals */}
|
|
360
|
+
<Card>
|
|
361
|
+
<CardHeader>
|
|
362
|
+
<CardTitle>Behavioral Signals</CardTitle>
|
|
363
|
+
<CardDescription>
|
|
364
|
+
Raw metrics extracted from conversation traces that drive personality inference
|
|
365
|
+
</CardDescription>
|
|
366
|
+
</CardHeader>
|
|
367
|
+
<CardContent>
|
|
368
|
+
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5">
|
|
369
|
+
{[
|
|
370
|
+
{ label: 'Tool Diversity', value: profile.signals.toolDiversity.toFixed(3) },
|
|
371
|
+
{ label: 'Read-Before-Edit', value: profile.signals.readBeforeEditRatio.toFixed(2) },
|
|
372
|
+
{ label: 'Output/Input Ratio', value: profile.signals.outputInputRatio.toFixed(2) },
|
|
373
|
+
{ label: 'Avg Msg Length', value: `${Math.round(profile.signals.avgAssistantMsgLength)} tok` },
|
|
374
|
+
{ label: 'Bash/Total Ratio', value: profile.signals.bashToTotalRatio.toFixed(3) },
|
|
375
|
+
{ label: 'Edit/Read Ratio', value: profile.signals.editToReadRatio.toFixed(2) },
|
|
376
|
+
{ label: 'Tool Entropy', value: profile.signals.toolTransitionEntropy.toFixed(2) },
|
|
377
|
+
{ label: 'Duration CV', value: profile.signals.sessionDurationVariance.toFixed(2) },
|
|
378
|
+
{ label: 'Tools/Message', value: profile.signals.avgToolCallsPerMessage.toFixed(2) },
|
|
379
|
+
{ label: 'Overflow Rate', value: `${(profile.signals.contextOverflowRate * 100).toFixed(1)}%` },
|
|
380
|
+
].map(({ label, value }) => (
|
|
381
|
+
<div key={label} className="rounded-lg border p-3">
|
|
382
|
+
<div className="text-xs text-muted-foreground">{label}</div>
|
|
383
|
+
<div className="text-lg font-semibold">{value}</div>
|
|
384
|
+
</div>
|
|
385
|
+
))}
|
|
386
|
+
</div>
|
|
387
|
+
</CardContent>
|
|
388
|
+
</Card>
|
|
389
|
+
|
|
390
|
+
{/* Recommendations */}
|
|
391
|
+
{profile.recommendations.length > 0 && (
|
|
392
|
+
<Card>
|
|
393
|
+
<CardHeader>
|
|
394
|
+
<div className="flex items-center gap-2">
|
|
395
|
+
<Sparkles className="h-5 w-5 text-primary" />
|
|
396
|
+
<div>
|
|
397
|
+
<CardTitle>Personality Tuning Recommendations</CardTitle>
|
|
398
|
+
<CardDescription>
|
|
399
|
+
Adjustments to better fit the {TASK_OPTIONS.find(t => t.value === targetTask)?.label} personality profile
|
|
400
|
+
</CardDescription>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</CardHeader>
|
|
404
|
+
<CardContent>
|
|
405
|
+
<div className="space-y-4">
|
|
406
|
+
{profile.recommendations.map((rec) => (
|
|
407
|
+
<div key={rec.trait} className="rounded-lg border p-4">
|
|
408
|
+
<div className="flex items-center gap-2 mb-2">
|
|
409
|
+
<Badge variant="outline">{rec.trait}</Badge>
|
|
410
|
+
<span className="text-sm text-muted-foreground">{rec.current}</span>
|
|
411
|
+
<ArrowRight className="h-3 w-3 text-muted-foreground" />
|
|
412
|
+
<span className="text-sm font-medium">{rec.ideal}</span>
|
|
413
|
+
</div>
|
|
414
|
+
<p className="text-sm text-muted-foreground">{rec.promptSnippet}</p>
|
|
415
|
+
</div>
|
|
416
|
+
))}
|
|
417
|
+
</div>
|
|
418
|
+
</CardContent>
|
|
419
|
+
</Card>
|
|
420
|
+
)}
|
|
421
|
+
|
|
422
|
+
{/* System Prompt */}
|
|
423
|
+
<Card>
|
|
424
|
+
<CardHeader>
|
|
425
|
+
<div className="flex items-center justify-between">
|
|
426
|
+
<div>
|
|
427
|
+
<CardTitle>Generated System Prompt</CardTitle>
|
|
428
|
+
<CardDescription>
|
|
429
|
+
Add this to your AI assistant's system prompt to tune its personality for {TASK_OPTIONS.find(t => t.value === targetTask)?.label}
|
|
430
|
+
</CardDescription>
|
|
431
|
+
</div>
|
|
432
|
+
<Button variant="outline" size="sm" onClick={handleCopyPrompt} className="gap-2">
|
|
433
|
+
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
|
434
|
+
{copied ? 'Copied' : 'Copy'}
|
|
435
|
+
</Button>
|
|
436
|
+
</div>
|
|
437
|
+
</CardHeader>
|
|
438
|
+
<CardContent>
|
|
439
|
+
<pre className="max-h-96 overflow-auto rounded-lg bg-muted p-4 text-sm whitespace-pre-wrap">
|
|
440
|
+
{profile.systemPrompt}
|
|
441
|
+
</pre>
|
|
442
|
+
</CardContent>
|
|
443
|
+
</Card>
|
|
444
|
+
</div>
|
|
445
|
+
)
|
|
446
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableHeader,
|
|
10
|
+
TableRow,
|
|
11
|
+
} from '@/components/ui/table'
|
|
12
|
+
import { PaginationControls } from '@/components/pagination-controls'
|
|
13
|
+
import { usePagination } from '@/hooks/use-pagination'
|
|
14
|
+
import type { ProjectSummary } from '@/lib/parse-logs'
|
|
15
|
+
import { formatCost, formatTokens, formatDuration, formatNumber } from '@/lib/format'
|
|
16
|
+
|
|
17
|
+
export function ProjectsTable({ projects }: { projects: ProjectSummary[] }) {
|
|
18
|
+
const pagination = usePagination(projects, 20)
|
|
19
|
+
|
|
20
|
+
const topTools = (toolCalls: Record<string, number>) =>
|
|
21
|
+
Object.entries(toolCalls)
|
|
22
|
+
.sort((a, b) => b[1] - a[1])
|
|
23
|
+
.slice(0, 3)
|
|
24
|
+
.map(([name, count]) => `${name} (${count})`)
|
|
25
|
+
.join(', ')
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Card>
|
|
29
|
+
<CardHeader>
|
|
30
|
+
<CardTitle>Projects</CardTitle>
|
|
31
|
+
<CardDescription>{projects.length} projects tracked</CardDescription>
|
|
32
|
+
</CardHeader>
|
|
33
|
+
<CardContent>
|
|
34
|
+
<Table>
|
|
35
|
+
<TableHeader>
|
|
36
|
+
<TableRow>
|
|
37
|
+
<TableHead>Project</TableHead>
|
|
38
|
+
<TableHead className="text-right">Sessions</TableHead>
|
|
39
|
+
<TableHead className="text-right">Messages</TableHead>
|
|
40
|
+
<TableHead className="text-right">Tokens</TableHead>
|
|
41
|
+
<TableHead className="text-right">Cost</TableHead>
|
|
42
|
+
<TableHead className="text-right">Duration</TableHead>
|
|
43
|
+
<TableHead>Top Tools</TableHead>
|
|
44
|
+
</TableRow>
|
|
45
|
+
</TableHeader>
|
|
46
|
+
<TableBody>
|
|
47
|
+
{pagination.pageItems.map((p) => (
|
|
48
|
+
<TableRow key={p.name}>
|
|
49
|
+
<TableCell className="font-medium">{p.name}</TableCell>
|
|
50
|
+
<TableCell className="text-right">{formatNumber(p.sessions)}</TableCell>
|
|
51
|
+
<TableCell className="text-right">{formatNumber(p.totalMessages)}</TableCell>
|
|
52
|
+
<TableCell className="text-right">{formatTokens(p.totalTokens)}</TableCell>
|
|
53
|
+
<TableCell className="text-right">{formatCost(p.totalCost)}</TableCell>
|
|
54
|
+
<TableCell className="text-right">
|
|
55
|
+
{formatDuration(p.totalDurationMinutes)}
|
|
56
|
+
</TableCell>
|
|
57
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
58
|
+
{topTools(p.toolCalls)}
|
|
59
|
+
</TableCell>
|
|
60
|
+
</TableRow>
|
|
61
|
+
))}
|
|
62
|
+
</TableBody>
|
|
63
|
+
</Table>
|
|
64
|
+
{pagination.totalPages > 1 && (
|
|
65
|
+
<PaginationControls pagination={pagination} noun="projects" />
|
|
66
|
+
)}
|
|
67
|
+
</CardContent>
|
|
68
|
+
</Card>
|
|
69
|
+
)
|
|
70
|
+
}
|