Skip to content

Commit 229bfd2

Browse files
devakoneclaude
andcommitted
feat: track LLM usage events
- Track llm_key_added/removed with provider property - Track llm_opt_in_enabled/disabled for narrative opt-in toggle - Add new events to AnalyticsEvents constants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1b0c994 commit 229bfd2

3 files changed

Lines changed: 29 additions & 50 deletions

File tree

.github/workflows/release-pr.yml

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,12 @@ jobs:
2222
GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
2323
DEFAULT_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2424
PR_TITLE: "Release: develop → main"
25-
PR_TEMPLATE: .github/PULL_REQUEST_TEMPLATE.md
25+
GITHUB_REPOSITORY: ${{ github.repository }}
2626
run: |
2727
set -euo pipefail
2828
2929
export GH_TOKEN="${GH_TOKEN:-$DEFAULT_GH_TOKEN}"
3030
31-
if [ ! -f "$PR_TEMPLATE" ]; then
32-
echo "Pull request template not found at $PR_TEMPLATE"
33-
exit 1
34-
fi
35-
3631
git fetch origin main --prune
3732
3833
COMMIT_COUNT="$(git rev-list --count origin/main..HEAD)"
@@ -46,11 +41,10 @@ jobs:
4641
4742
python3 - <<'PY'
4843
import os
49-
import re
5044
import subprocess
5145
52-
template_path = os.environ["PR_TEMPLATE"]
5346
body_file_path = os.environ["BODY_FILE"]
47+
repository = os.environ["GITHUB_REPOSITORY"]
5448
5549
# Get commits that are in develop but not in main
5650
log = subprocess.check_output(
@@ -68,51 +62,29 @@ jobs:
6862
if items:
6963
bullets = [f"- {line}" for line in items]
7064
if extra:
71-
bullets.append(f"- and {extra} more commits")
65+
bullets.append(f"- ...and {extra} more commits")
7266
else:
7367
bullets = ["- No new changes since last release"]
7468
75-
with open(template_path, "r", encoding="utf-8") as f:
76-
content = f.read()
77-
lines = content.splitlines()
78-
79-
# Find the Changes section and replace the placeholder
80-
if "## Changes" in lines:
81-
header_index = lines.index("## Changes")
82-
83-
# Find the next section header
84-
end_index = len(lines)
85-
for i in range(header_index + 1, len(lines)):
86-
if lines[i].startswith("## "):
87-
end_index = i
88-
break
89-
90-
# Extract section content
91-
section = lines[header_index + 1 : end_index]
92-
93-
# Find and replace the placeholder bullet point
94-
replaced = False
95-
for idx, line in enumerate(section):
96-
# Match empty bullet point (with optional whitespace)
97-
if re.fullmatch(r"-\s*", line.strip()) or line.strip() == "-":
98-
section = section[:idx] + bullets + section[idx + 1 :]
99-
replaced = True
100-
break
101-
102-
if not replaced:
103-
# If no placeholder found, insert bullets after empty lines/comments
104-
insert_idx = 0
105-
for idx, line in enumerate(section):
106-
if line.strip() and not line.strip().startswith("<!--"):
107-
insert_idx = idx
108-
break
109-
insert_idx = idx + 1
110-
section = section[:insert_idx] + bullets + section[insert_idx:]
111-
112-
final_lines = lines[: header_index + 1] + section + lines[end_index:]
113-
else:
114-
# If no Changes section exists, append one
115-
final_lines = lines + ["", "## Changes", ""] + bullets
69+
summary_lines = [
70+
"This PR syncs `develop` into `main` for the next production release.",
71+
f"It includes **{len(log)}** commit(s) ahead of `main`.",
72+
]
73+
74+
compare_url = f"https://github.com/{repository}/compare/main...develop"
75+
final_lines = [
76+
"## Release Notes",
77+
"",
78+
*summary_lines,
79+
"",
80+
"## Changes",
81+
"",
82+
*bullets,
83+
"",
84+
"## Compare",
85+
"",
86+
f"- Full diff: {compare_url}",
87+
]
11688
11789
with open(body_file_path, "w", encoding="utf-8") as f:
11890
f.write("\n".join(final_lines).rstrip() + "\n")

apps/web/src/app/settings/llm-keys/LLMKeysClient.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState, useEffect } from "react";
44
import { wrappedTheme } from "@/lib/theme";
5+
import { trackEvent, AnalyticsEvents } from "@/lib/analytics";
56

67
interface LLMKey {
78
id: string;
@@ -84,6 +85,7 @@ export default function LLMKeysClient() {
8485
});
8586
if (res.ok) {
8687
setLlmOptIn(newValue);
88+
trackEvent(newValue ? AnalyticsEvents.LLM_OPT_IN_ENABLED : AnalyticsEvents.LLM_OPT_IN_DISABLED);
8789
}
8890
} catch {
8991
// Revert on error
@@ -137,6 +139,7 @@ export default function LLMKeysClient() {
137139

138140
// Success - refresh keys and reset form
139141
await fetchKeys();
142+
trackEvent(AnalyticsEvents.LLM_KEY_ADDED, { provider: addProvider });
140143
setShowAddForm(false);
141144
setAddApiKey("");
142145
setAddLabel("");
@@ -150,6 +153,7 @@ export default function LLMKeysClient() {
150153
async function handleDelete(id: string) {
151154
if (!confirm("Remove this API key? You can add it again later.")) return;
152155

156+
const keyToDelete = keys.find((k) => k.id === id);
153157
setDeletingId(id);
154158
try {
155159
const res = await fetch(`/api/settings/llm-keys/${id}`, {
@@ -158,6 +162,7 @@ export default function LLMKeysClient() {
158162
if (!res.ok) {
159163
throw new Error("Failed to delete key");
160164
}
165+
trackEvent(AnalyticsEvents.LLM_KEY_REMOVED, { provider: keyToDelete?.provider ?? "unknown" });
161166
setKeys((prev) => prev.filter((k) => k.id !== id));
162167
} catch (err) {
163168
alert(err instanceof Error ? err.message : "Failed to delete");

apps/web/src/lib/analytics.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export const AnalyticsEvents = {
5555
PUBLIC_PROFILE_DISABLED: "public_profile_disabled",
5656
LLM_KEY_ADDED: "llm_key_added",
5757
LLM_KEY_REMOVED: "llm_key_removed",
58+
LLM_OPT_IN_ENABLED: "llm_opt_in_enabled",
59+
LLM_OPT_IN_DISABLED: "llm_opt_in_disabled",
5860
} as const;
5961

6062
export type AnalyticsEvent = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents];

0 commit comments

Comments
 (0)