Skip to content

Commit 8b47ba1

Browse files
committed
feat(stats): add autocomplete entry and time-axis groupBy in UI
1 parent 41162cb commit 8b47ba1

4 files changed

Lines changed: 47 additions & 8 deletions

File tree

src/services/command/__tests__/built-in-commands.spec.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ describe("Built-in Commands", () => {
55
it("should return all built-in commands", async () => {
66
const commands = await getBuiltInCommands()
77

8-
expect(commands).toHaveLength(1)
9-
expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init"]))
8+
expect(commands).toHaveLength(2)
9+
expect(commands.map((cmd) => cmd.name)).toEqual(expect.arrayContaining(["init", "stats"]))
1010

1111
// Verify all commands have required properties
1212
commands.forEach((command) => {
@@ -63,10 +63,10 @@ describe("Built-in Commands", () => {
6363
it("should return all built-in command names", async () => {
6464
const names = await getBuiltInCommandNames()
6565

66-
expect(names).toHaveLength(1)
67-
expect(names).toEqual(expect.arrayContaining(["init"]))
66+
expect(names).toHaveLength(2)
67+
expect(names).toEqual(expect.arrayContaining(["init", "stats"]))
6868
// Order doesn't matter since it's based on filesystem order
69-
expect(names.sort()).toEqual(["init"])
69+
expect(names.sort()).toEqual(["init", "stats"])
7070
})
7171

7272
it("should return array of strings", async () => {
@@ -99,5 +99,21 @@ describe("Built-in Commands", () => {
9999
expect(content).toContain("rules-ask")
100100
expect(content).toContain("rules-architect")
101101
})
102+
103+
it("stats command should expose the /stats token for ChatView interception", async () => {
104+
const command = await getBuiltInCommand("stats")
105+
106+
expect(command).toBeDefined()
107+
expect(command!.name).toBe("stats")
108+
expect(command!.source).toBe("built-in")
109+
expect(command!.filePath).toBe("<built-in:stats>")
110+
// Content must be the literal "/stats" so that ChatView's
111+
// exact-match interception (text === "/stats") fires when the
112+
// user picks this autocomplete entry.
113+
expect(command!.content).toBe("/stats")
114+
expect(command!.description).toBe(
115+
"Open the local usage statistics panel (token usage by model, provider, mode, and time range)",
116+
)
117+
})
102118
})
103119
})

src/services/command/built-in-commands.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ interface BuiltInCommandDefinition {
88
}
99

1010
const BUILT_IN_COMMANDS: Record<string, BuiltInCommandDefinition> = {
11+
stats: {
12+
name: "stats",
13+
description: "Open the local usage statistics panel (token usage by model, provider, mode, and time range)",
14+
// Content is the literal "/stats" token so that ChatView's exact-match
15+
// interception (text === "/stats") fires when the user picks this
16+
// autocomplete entry. A non-empty content is required by the Command
17+
// type and also prevents the trimmed input from becoming empty, which
18+
// would bypass the interception. No prompt body is sent to the LLM.
19+
content: "/stats",
20+
},
1121
init: {
1222
name: "init",
1323
description: "Analyze codebase and create concise AGENTS.md files for AI assistants",

webview-ui/src/components/stats/StatsView.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import UsageHeatmap from "./UsageHeatmap"
2525
// ── Types ───────────────────────────────────────────────────────────────────
2626

2727
type StatsPreset = "today" | "7d" | "30d" | "all"
28-
type GroupByOption = "model" | "provider" | "mode" | "status"
28+
type GroupByOption = "model" | "provider" | "mode" | "status" | "day" | "week" | "month"
2929

3030
interface StatsViewProps {
3131
onDone: () => void
@@ -413,8 +413,18 @@ const StatsView = memo(({ onDone }: StatsViewProps) => {
413413
<h4 className="text-sm font-medium text-vscode-foreground m-0">
414414
{t("stats:breakdown.title")}
415415
</h4>
416-
<div className="flex gap-1">
417-
{(["model", "provider", "mode", "status"] as GroupByOption[]).map((g) => (
416+
<div className="flex flex-wrap gap-1">
417+
{(
418+
[
419+
"model",
420+
"provider",
421+
"mode",
422+
"status",
423+
"day",
424+
"week",
425+
"month",
426+
] as GroupByOption[]
427+
).map((g) => (
418428
<Button
419429
key={g}
420430
variant={groupBy === g ? "primary" : "ghost"}

webview-ui/src/i18n/locales/en/stats.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
"provider": "Provider",
2222
"mode": "Mode",
2323
"status": "Status",
24+
"day": "Day",
25+
"week": "Week",
26+
"month": "Month",
2427
"events": "Events",
2528
"completed": "Completed",
2629
"failed": "Failed",

0 commit comments

Comments
 (0)