claude-smart 0.2.31 → 0.2.32
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 +2 -2
- package/bin/claude-smart.js +172 -18
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/app/api/config/route.ts +11 -2
- package/plugin/dashboard/app/api/health/route.ts +45 -6
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
- package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
- package/plugin/dashboard/app/configure/env/page.tsx +36 -31
- package/plugin/dashboard/app/configure/layout.tsx +1 -1
- package/plugin/dashboard/app/configure/server/page.tsx +8 -14
- package/plugin/dashboard/app/dashboard/page.tsx +311 -115
- package/plugin/dashboard/app/globals.css +80 -66
- package/plugin/dashboard/app/layout.tsx +13 -10
- package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
- package/plugin/dashboard/app/preferences/page.tsx +154 -54
- package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
- package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
- package/plugin/dashboard/app/sessions/page.tsx +14 -10
- package/plugin/dashboard/app/skills/page.tsx +175 -56
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
- package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
- package/plugin/dashboard/components/common/empty-state.tsx +4 -2
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +5 -3
- package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
- package/plugin/dashboard/components/common/stat-card.tsx +9 -5
- package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
- package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
- package/plugin/dashboard/components/ui/input.tsx +1 -0
- package/plugin/dashboard/hooks/use-settings.tsx +30 -61
- package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
- package/plugin/dashboard/lib/config-file.ts +2 -0
- package/plugin/dashboard/lib/reflexio-client.ts +23 -48
- package/plugin/dashboard/lib/session-reader.ts +222 -6
- package/plugin/dashboard/lib/types.ts +20 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +61 -0
- package/plugin/scripts/backend-service.sh +13 -2
- package/plugin/scripts/cli.sh +1 -0
- package/plugin/scripts/codex-hook.js +100 -3
- package/plugin/scripts/dashboard-service.sh +95 -19
- package/plugin/scripts/hook_entry.sh +13 -7
- package/plugin/scripts/smart-install.sh +18 -13
- package/plugin/src/claude_smart/cli.py +86 -20
- package/plugin/src/claude_smart/context_format.py +244 -6
- package/plugin/src/claude_smart/context_inject.py +8 -1
- package/plugin/src/claude_smart/cs_cite.py +186 -34
- package/plugin/src/claude_smart/env_config.py +102 -0
- package/plugin/src/claude_smart/events/stop.py +32 -6
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +5 -0
- package/plugin/src/claude_smart/reflexio_adapter.py +102 -26
- package/plugin/uv.lock +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
4
|
import Link from "next/link";
|
|
5
5
|
import {
|
|
6
6
|
BookOpen,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Sparkles,
|
|
9
9
|
Activity,
|
|
10
10
|
ExternalLink,
|
|
11
|
+
Users,
|
|
11
12
|
} from "lucide-react";
|
|
12
13
|
import { LearningsBadge } from "@/components/common/learnings-badge";
|
|
13
14
|
import { PageHeader } from "@/components/common/page-header";
|
|
@@ -15,16 +16,35 @@ import { StatCard } from "@/components/common/stat-card";
|
|
|
15
16
|
import { EmptyState } from "@/components/common/empty-state";
|
|
16
17
|
import { Badge } from "@/components/ui/badge";
|
|
17
18
|
import { reflexio } from "@/lib/reflexio-client";
|
|
18
|
-
import {
|
|
19
|
-
import { formatRelative, truncateId } from "@/lib/format";
|
|
19
|
+
import { formatRelative, truncate, truncateId } from "@/lib/format";
|
|
20
20
|
import { agentPlaybookStatusLabel } from "@/lib/status";
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
AgentPlaybook,
|
|
23
|
+
PlaybookApplicationStat,
|
|
24
|
+
SessionSummary,
|
|
25
|
+
UserPlaybook,
|
|
26
|
+
UserProfile,
|
|
27
|
+
} from "@/lib/types";
|
|
28
|
+
|
|
29
|
+
type RecentLearningKind = "project-skill" | "shared-skill" | "preference";
|
|
30
|
+
|
|
31
|
+
interface RecentLearning {
|
|
32
|
+
id: string;
|
|
33
|
+
kind: RecentLearningKind;
|
|
34
|
+
href: string;
|
|
35
|
+
content: string;
|
|
36
|
+
createdAt: number;
|
|
37
|
+
statKey: string;
|
|
38
|
+
}
|
|
22
39
|
|
|
23
40
|
export default function DashboardPage() {
|
|
24
|
-
const { reflexioUrl } = useSettings();
|
|
25
41
|
const [sessions, setSessions] = useState<SessionSummary[] | null>(null);
|
|
26
42
|
const [projectSkills, setProjectSkills] = useState<UserPlaybook[] | null>(null);
|
|
27
43
|
const [sharedSkills, setSharedSkills] = useState<AgentPlaybook[] | null>(null);
|
|
44
|
+
const [preferences, setPreferences] = useState<UserProfile[] | null>(null);
|
|
45
|
+
const [topApplied, setTopApplied] = useState<PlaybookApplicationStat[] | null>(
|
|
46
|
+
null,
|
|
47
|
+
);
|
|
28
48
|
const [error, setError] = useState<string | null>(null);
|
|
29
49
|
|
|
30
50
|
useEffect(() => {
|
|
@@ -32,19 +52,32 @@ export default function DashboardPage() {
|
|
|
32
52
|
async function load() {
|
|
33
53
|
setError(null);
|
|
34
54
|
try {
|
|
35
|
-
const [sRes, projectRes, sharedRes] = await Promise.all([
|
|
55
|
+
const [sRes, projectRes, sharedRes, prefRes, statsRes] = await Promise.all([
|
|
36
56
|
fetch("/api/sessions", { cache: "no-store" }).then((r) => r.json()),
|
|
37
57
|
reflexio
|
|
38
|
-
.getUserPlaybooks({
|
|
58
|
+
.getUserPlaybooks({})
|
|
39
59
|
.catch(() => ({ user_playbooks: [] as UserPlaybook[] })),
|
|
40
60
|
reflexio
|
|
41
|
-
.getAgentPlaybooks({
|
|
61
|
+
.getAgentPlaybooks({})
|
|
42
62
|
.catch(() => ({ agent_playbooks: [] as AgentPlaybook[] })),
|
|
63
|
+
reflexio
|
|
64
|
+
.getAllProfiles({ limit: 100 })
|
|
65
|
+
.catch(() => ({ user_profiles: [] as UserProfile[] })),
|
|
66
|
+
fetch("/api/rules/applied?daysBack=30&limit=200", {
|
|
67
|
+
cache: "no-store",
|
|
68
|
+
})
|
|
69
|
+
.then((r) => r.json())
|
|
70
|
+
.catch(() => ({
|
|
71
|
+
success: false,
|
|
72
|
+
stats: [] as PlaybookApplicationStat[],
|
|
73
|
+
})),
|
|
43
74
|
]);
|
|
44
75
|
if (cancelled) return;
|
|
45
76
|
setSessions(sRes.sessions ?? []);
|
|
46
77
|
setProjectSkills(projectRes.user_playbooks ?? []);
|
|
47
78
|
setSharedSkills(sharedRes.agent_playbooks ?? []);
|
|
79
|
+
setPreferences(prefRes.user_profiles ?? []);
|
|
80
|
+
setTopApplied(statsRes.stats ?? []);
|
|
48
81
|
} catch (e) {
|
|
49
82
|
if (!cancelled)
|
|
50
83
|
setError(e instanceof Error ? e.message : "failed to load");
|
|
@@ -54,7 +87,7 @@ export default function DashboardPage() {
|
|
|
54
87
|
return () => {
|
|
55
88
|
cancelled = true;
|
|
56
89
|
};
|
|
57
|
-
}, [
|
|
90
|
+
}, []);
|
|
58
91
|
|
|
59
92
|
// CURRENT project-specific skills arrive as `status: null` (response_model_exclude_none
|
|
60
93
|
// strips the field). Anything else (e.g. "archived", "pending") is excluded.
|
|
@@ -62,6 +95,52 @@ export default function DashboardPage() {
|
|
|
62
95
|
const approvedSharedSkills = (sharedSkills ?? []).filter(
|
|
63
96
|
(p) => agentPlaybookStatusLabel(p) === "APPROVED",
|
|
64
97
|
);
|
|
98
|
+
const currentPreferences = (preferences ?? []).filter((p) => p.status == null);
|
|
99
|
+
const statsByRule = useMemo(() => {
|
|
100
|
+
const map = new Map<string, PlaybookApplicationStat>();
|
|
101
|
+
for (const s of topApplied ?? []) {
|
|
102
|
+
map.set(`${s.kind}:${s.source_kind ?? "unknown"}:${s.real_id}`, s);
|
|
103
|
+
}
|
|
104
|
+
return map;
|
|
105
|
+
}, [topApplied]);
|
|
106
|
+
const recentLearnings = useMemo(() => {
|
|
107
|
+
const items: RecentLearning[] = [
|
|
108
|
+
...currentProjectSkills.map((p) => ({
|
|
109
|
+
id: `project:${p.user_playbook_id}`,
|
|
110
|
+
kind: "project-skill" as const,
|
|
111
|
+
href: `/skills/project/${encodeURIComponent(p.user_playbook_id)}`,
|
|
112
|
+
content: p.content,
|
|
113
|
+
createdAt: p.created_at,
|
|
114
|
+
statKey: `playbook:user_playbook:${p.user_playbook_id}`,
|
|
115
|
+
})),
|
|
116
|
+
...approvedSharedSkills.map((p) => ({
|
|
117
|
+
id: `shared:${p.agent_playbook_id}`,
|
|
118
|
+
kind: "shared-skill" as const,
|
|
119
|
+
href: `/skills/shared/${encodeURIComponent(p.agent_playbook_id)}`,
|
|
120
|
+
content: p.content,
|
|
121
|
+
createdAt: p.created_at,
|
|
122
|
+
statKey: `playbook:agent_playbook:${p.agent_playbook_id}`,
|
|
123
|
+
})),
|
|
124
|
+
...currentPreferences.map((p) => ({
|
|
125
|
+
id: `preference:${p.profile_id}`,
|
|
126
|
+
kind: "preference" as const,
|
|
127
|
+
href: `/preferences/project/${encodeURIComponent(p.profile_id)}`,
|
|
128
|
+
content: p.content,
|
|
129
|
+
createdAt: p.last_modified_timestamp,
|
|
130
|
+
statKey: `profile:profile:${p.profile_id}`,
|
|
131
|
+
})),
|
|
132
|
+
];
|
|
133
|
+
const seen = new Set<string>();
|
|
134
|
+
return items
|
|
135
|
+
.sort((a, b) => b.createdAt - a.createdAt)
|
|
136
|
+
.filter((item) => {
|
|
137
|
+
const key = `${item.kind}:${item.content.trim().toLowerCase()}`;
|
|
138
|
+
if (seen.has(key)) return false;
|
|
139
|
+
seen.add(key);
|
|
140
|
+
return true;
|
|
141
|
+
})
|
|
142
|
+
.slice(0, 4);
|
|
143
|
+
}, [approvedSharedSkills, currentPreferences, currentProjectSkills]);
|
|
65
144
|
const learningInteractionTotal = (sessions ?? []).reduce(
|
|
66
145
|
(acc, s) => acc + s.learning_interaction_count,
|
|
67
146
|
0,
|
|
@@ -107,121 +186,238 @@ export default function DashboardPage() {
|
|
|
107
186
|
</div>
|
|
108
187
|
)}
|
|
109
188
|
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
href={`/sessions/${s.session_id}`}
|
|
126
|
-
className="flex items-center justify-between px-4 py-3 hover:bg-accent/40 transition-colors"
|
|
127
|
-
>
|
|
128
|
-
<div className="min-w-0 flex items-center gap-3">
|
|
129
|
-
<MessageSquare className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
130
|
-
<code className="font-mono text-xs truncate">
|
|
131
|
-
{truncateId(s.session_id, 10, 6)}
|
|
132
|
-
</code>
|
|
133
|
-
<LearningsBadge count={s.learning_interaction_count} />
|
|
134
|
-
</div>
|
|
135
|
-
<div className="flex items-center gap-4 text-xs text-muted-foreground shrink-0">
|
|
136
|
-
<span>{s.turn_count} turns</span>
|
|
137
|
-
<span>{formatRelative(s.last_activity)}</span>
|
|
138
|
-
</div>
|
|
139
|
-
</Link>
|
|
140
|
-
))}
|
|
189
|
+
<div className="grid min-w-0 gap-6 lg:grid-cols-2">
|
|
190
|
+
<section className="min-w-0">
|
|
191
|
+
<div className="flex items-center justify-between mb-3">
|
|
192
|
+
<div>
|
|
193
|
+
<h2 className="text-sm font-semibold">Recent sessions</h2>
|
|
194
|
+
<p className="text-xs text-muted-foreground">
|
|
195
|
+
Latest local buffers and cited learnings.
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
<Link
|
|
199
|
+
href="/sessions"
|
|
200
|
+
className="text-xs text-primary hover:text-foreground inline-flex items-center gap-1"
|
|
201
|
+
>
|
|
202
|
+
View all <ExternalLink className="h-3 w-3" />
|
|
203
|
+
</Link>
|
|
141
204
|
</div>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
{sessions && sessions.length > 0 ? (
|
|
206
|
+
<div className="rounded-lg border border-border divide-y divide-border bg-card/92 shadow-sm overflow-hidden">
|
|
207
|
+
{sessions.slice(0, 5).map((s) => (
|
|
208
|
+
<Link
|
|
209
|
+
key={s.session_id}
|
|
210
|
+
href={`/sessions/${s.session_id}`}
|
|
211
|
+
className="flex flex-col gap-2 px-4 py-3 transition-colors hover:bg-accent/45 sm:flex-row sm:items-center sm:justify-between"
|
|
212
|
+
>
|
|
213
|
+
<div className="min-w-0 flex w-full items-center gap-3">
|
|
214
|
+
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
|
|
215
|
+
<MessageSquare className="h-4 w-4" />
|
|
216
|
+
</span>
|
|
217
|
+
<code className="font-mono text-xs truncate">
|
|
218
|
+
{truncateId(s.session_id, 10, 6)}
|
|
219
|
+
</code>
|
|
220
|
+
<LearningsBadge count={s.learning_interaction_count} />
|
|
221
|
+
</div>
|
|
222
|
+
<div className="flex w-full items-center justify-end gap-4 text-xs text-muted-foreground sm:w-auto sm:shrink-0">
|
|
223
|
+
<span>{s.turn_count} turns</span>
|
|
224
|
+
<span>{formatRelative(s.last_activity)}</span>
|
|
225
|
+
</div>
|
|
226
|
+
</Link>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
) : (
|
|
230
|
+
<EmptyState
|
|
231
|
+
icon={MessageSquare}
|
|
232
|
+
title="No sessions yet"
|
|
233
|
+
description="Run Claude Code with claude-smart enabled — sessions will appear here."
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
</section>
|
|
237
|
+
|
|
238
|
+
<section className="min-w-0">
|
|
239
|
+
<div className="flex items-center justify-between mb-3">
|
|
240
|
+
<div>
|
|
241
|
+
<h2 className="text-sm font-semibold">Recent learnings</h2>
|
|
242
|
+
<p className="text-xs text-muted-foreground">
|
|
243
|
+
New skills and preferences extracted from recent patterns.
|
|
244
|
+
</p>
|
|
245
|
+
</div>
|
|
246
|
+
<div className="flex items-center gap-3">
|
|
164
247
|
<Link
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
className="block rounded-xl border border-border bg-card p-4 hover:bg-accent/40 transition-colors"
|
|
248
|
+
href="/skills"
|
|
249
|
+
className="text-xs text-primary hover:text-foreground inline-flex items-center gap-1"
|
|
168
250
|
>
|
|
169
|
-
<
|
|
170
|
-
<Badge variant="outline" className="font-mono text-[10px]">
|
|
171
|
-
{p.agent_version || "default"}
|
|
172
|
-
</Badge>
|
|
173
|
-
<Badge variant="secondary" className="text-[10px]">
|
|
174
|
-
project-specific
|
|
175
|
-
</Badge>
|
|
176
|
-
<span className="text-[11px] text-muted-foreground">
|
|
177
|
-
{formatRelative(p.created_at)}
|
|
178
|
-
</span>
|
|
179
|
-
</div>
|
|
180
|
-
<p className="text-sm line-clamp-3">{p.content}</p>
|
|
181
|
-
{p.trigger && (
|
|
182
|
-
<p className="text-xs text-muted-foreground mt-2 line-clamp-1">
|
|
183
|
-
<span className="font-medium">trigger:</span> {p.trigger}
|
|
184
|
-
</p>
|
|
185
|
-
)}
|
|
251
|
+
Skills <ExternalLink className="h-3 w-3" />
|
|
186
252
|
</Link>
|
|
187
|
-
))}
|
|
188
|
-
{approvedSharedSkills.slice(0, 2).map((p) => (
|
|
189
253
|
<Link
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
className="block rounded-xl border border-border bg-card p-4 hover:bg-accent/40 transition-colors"
|
|
254
|
+
href="/preferences"
|
|
255
|
+
className="text-xs text-primary hover:text-foreground inline-flex items-center gap-1"
|
|
193
256
|
>
|
|
194
|
-
<
|
|
195
|
-
<div className="flex items-center gap-2">
|
|
196
|
-
<Badge variant="outline" className="font-mono text-[10px]">
|
|
197
|
-
{p.agent_version || "default"}
|
|
198
|
-
</Badge>
|
|
199
|
-
<Badge variant="secondary" className="text-[10px]">
|
|
200
|
-
shared
|
|
201
|
-
</Badge>
|
|
202
|
-
</div>
|
|
203
|
-
<span className="text-[11px] text-muted-foreground">
|
|
204
|
-
{formatRelative(p.created_at)}
|
|
205
|
-
</span>
|
|
206
|
-
</div>
|
|
207
|
-
<p className="text-sm line-clamp-3">{p.content}</p>
|
|
208
|
-
{p.trigger && (
|
|
209
|
-
<p className="text-xs text-muted-foreground mt-2 line-clamp-1">
|
|
210
|
-
<span className="font-medium">trigger:</span> {p.trigger}
|
|
211
|
-
</p>
|
|
212
|
-
)}
|
|
257
|
+
Preferences <ExternalLink className="h-3 w-3" />
|
|
213
258
|
</Link>
|
|
214
|
-
|
|
259
|
+
</div>
|
|
215
260
|
</div>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
261
|
+
{recentLearnings.length > 0 ? (
|
|
262
|
+
<div className="rounded-lg border border-border divide-y divide-border bg-card/92 shadow-sm overflow-hidden">
|
|
263
|
+
{recentLearnings.map((item) => {
|
|
264
|
+
const stat = statsByRule.get(item.statKey);
|
|
265
|
+
return (
|
|
266
|
+
<Link
|
|
267
|
+
key={item.id}
|
|
268
|
+
href={item.href}
|
|
269
|
+
className="flex min-w-0 items-center justify-between gap-3 px-4 py-3 transition-colors hover:bg-accent/45"
|
|
270
|
+
>
|
|
271
|
+
<div className="min-w-0 flex flex-1 items-center gap-3">
|
|
272
|
+
{learningIcon(item.kind)}
|
|
273
|
+
<div className="min-w-0 flex-1">
|
|
274
|
+
<p className="text-sm truncate">{item.content}</p>
|
|
275
|
+
<div className="mt-1 flex flex-wrap items-center gap-2">
|
|
276
|
+
<Badge variant="outline" className="h-5 text-[10px]">
|
|
277
|
+
{learningKindLabel(item.kind)}
|
|
278
|
+
</Badge>
|
|
279
|
+
<span className="text-[11px] text-muted-foreground">
|
|
280
|
+
{learningScope(item.kind)}
|
|
281
|
+
</span>
|
|
282
|
+
<LearningApplicationStatBadge stat={stat} />
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground shrink-0">
|
|
287
|
+
<span>{formatRelative(item.createdAt)}</span>
|
|
288
|
+
</div>
|
|
289
|
+
</Link>
|
|
290
|
+
);
|
|
291
|
+
})}
|
|
292
|
+
</div>
|
|
293
|
+
) : (
|
|
294
|
+
<EmptyState
|
|
295
|
+
icon={BookOpen}
|
|
296
|
+
title="No learnings yet"
|
|
297
|
+
description="Keep using Claude with claude-smart enabled. Skills and preferences are extracted automatically when patterns emerge."
|
|
298
|
+
/>
|
|
299
|
+
)}
|
|
300
|
+
</section>
|
|
301
|
+
|
|
302
|
+
<section className="min-w-0 lg:col-span-2">
|
|
303
|
+
<div className="flex items-center justify-between mb-3">
|
|
304
|
+
<div className="flex items-center gap-2">
|
|
305
|
+
<h2 className="text-sm font-semibold">
|
|
306
|
+
Most used claude-smart learnings
|
|
307
|
+
</h2>
|
|
308
|
+
<span className="text-[11px] text-muted-foreground">
|
|
309
|
+
last 30 days
|
|
310
|
+
</span>
|
|
311
|
+
</div>
|
|
312
|
+
<Link
|
|
313
|
+
href="/sessions"
|
|
314
|
+
className="text-xs text-muted-foreground hover:text-foreground inline-flex items-center gap-1"
|
|
315
|
+
>
|
|
316
|
+
Review sessions <ExternalLink className="h-3 w-3" />
|
|
317
|
+
</Link>
|
|
318
|
+
</div>
|
|
319
|
+
{topApplied === null ? (
|
|
320
|
+
<div className="text-sm text-muted-foreground">Loading...</div>
|
|
321
|
+
) : topApplied.length > 0 ? (
|
|
322
|
+
<div className="rounded-lg border border-border divide-y divide-border bg-card/92 shadow-sm">
|
|
323
|
+
{topApplied.slice(0, 5).map((s) => {
|
|
324
|
+
const href = s.href ?? null;
|
|
325
|
+
const label = appliedRuleLabel(s);
|
|
326
|
+
const rowBody = (
|
|
327
|
+
<>
|
|
328
|
+
<div className="min-w-0 flex flex-1 items-center gap-3">
|
|
329
|
+
<Sparkles className="h-4 w-4 text-muted-foreground shrink-0" />
|
|
330
|
+
<div className="min-w-0 flex-1">
|
|
331
|
+
<p className="text-sm truncate">
|
|
332
|
+
{s.title || (
|
|
333
|
+
<span className="text-muted-foreground italic">
|
|
334
|
+
(rule removed)
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
</p>
|
|
338
|
+
<p className="text-[11px] text-muted-foreground">
|
|
339
|
+
{label} · {truncate(s.real_id, 12)}
|
|
340
|
+
</p>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground shrink-0">
|
|
344
|
+
<Badge variant="secondary" className="text-[10px]">
|
|
345
|
+
Used {s.applied_count}×
|
|
346
|
+
</Badge>
|
|
347
|
+
<span>{formatRelative(s.last_applied_at)}</span>
|
|
348
|
+
</div>
|
|
349
|
+
</>
|
|
350
|
+
);
|
|
351
|
+
return href ? (
|
|
352
|
+
<Link
|
|
353
|
+
key={`${s.kind}:${s.source_kind ?? "unknown"}:${s.real_id}`}
|
|
354
|
+
href={href}
|
|
355
|
+
className="flex min-w-0 items-center justify-between gap-3 px-4 py-3 hover:bg-accent/40 transition-colors"
|
|
356
|
+
>
|
|
357
|
+
{rowBody}
|
|
358
|
+
</Link>
|
|
359
|
+
) : (
|
|
360
|
+
<div
|
|
361
|
+
key={`${s.kind}:${s.source_kind ?? "unknown"}:${s.real_id}`}
|
|
362
|
+
className="flex min-w-0 items-center justify-between gap-3 px-4 py-3"
|
|
363
|
+
>
|
|
364
|
+
{rowBody}
|
|
365
|
+
</div>
|
|
366
|
+
);
|
|
367
|
+
})}
|
|
368
|
+
</div>
|
|
369
|
+
) : (
|
|
370
|
+
<EmptyState
|
|
371
|
+
icon={Sparkles}
|
|
372
|
+
title="No applied learnings yet"
|
|
373
|
+
description="When a claude-smart learning is cited in a local assistant response, it will appear here with usage and recency."
|
|
374
|
+
/>
|
|
375
|
+
)}
|
|
376
|
+
</section>
|
|
377
|
+
</div>
|
|
224
378
|
</div>
|
|
225
379
|
</div>
|
|
226
380
|
);
|
|
227
381
|
}
|
|
382
|
+
|
|
383
|
+
function appliedRuleLabel(stat: PlaybookApplicationStat): string {
|
|
384
|
+
if (stat.kind === "profile") return "preference";
|
|
385
|
+
return stat.source_kind === "agent_playbook" ? "shared skill" : "project skill";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function learningScope(kind: RecentLearningKind): string {
|
|
389
|
+
if (kind === "shared-skill") return "shared";
|
|
390
|
+
return "project";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function learningIcon(kind: RecentLearningKind) {
|
|
394
|
+
const Icon = kind === "preference" ? Users : BookOpen;
|
|
395
|
+
return <Icon className="h-4 w-4 text-muted-foreground shrink-0" />;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function learningKindLabel(kind: RecentLearningKind): string {
|
|
399
|
+
return kind === "preference" ? "preference" : "skill";
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function LearningApplicationStatBadge({
|
|
403
|
+
stat,
|
|
404
|
+
}: {
|
|
405
|
+
stat: PlaybookApplicationStat | undefined;
|
|
406
|
+
}) {
|
|
407
|
+
if (!stat || stat.applied_count === 0) {
|
|
408
|
+
return (
|
|
409
|
+
<Badge
|
|
410
|
+
variant="outline"
|
|
411
|
+
className="h-5 text-[10px] text-muted-foreground"
|
|
412
|
+
>
|
|
413
|
+
Never applied
|
|
414
|
+
</Badge>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const last = formatRelative(stat.last_applied_at);
|
|
418
|
+
return (
|
|
419
|
+
<Badge variant="secondary" className="h-5 text-[10px]">
|
|
420
|
+
Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
|
|
421
|
+
</Badge>
|
|
422
|
+
);
|
|
423
|
+
}
|