third-audience-mdx 1.0.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.
Files changed (108) hide show
  1. package/CLAUDE.md +41 -0
  2. package/INSTALLATION.md +367 -0
  3. package/README.md +303 -0
  4. package/WORKLOG.md +162 -0
  5. package/dist/cli/index.d.mts +1 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/index.js +208 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/index.mjs +185 -0
  10. package/dist/cli/index.mjs.map +1 -0
  11. package/dist/dashboard/auth.d.mts +16 -0
  12. package/dist/dashboard/auth.d.ts +16 -0
  13. package/dist/dashboard/auth.js +123 -0
  14. package/dist/dashboard/auth.js.map +1 -0
  15. package/dist/dashboard/auth.mjs +87 -0
  16. package/dist/dashboard/auth.mjs.map +1 -0
  17. package/dist/dashboard/routes/analytics-api-route.d.mts +6 -0
  18. package/dist/dashboard/routes/analytics-api-route.d.ts +6 -0
  19. package/dist/dashboard/routes/analytics-api-route.js +180 -0
  20. package/dist/dashboard/routes/analytics-api-route.js.map +1 -0
  21. package/dist/dashboard/routes/analytics-api-route.mjs +145 -0
  22. package/dist/dashboard/routes/analytics-api-route.mjs.map +1 -0
  23. package/dist/dashboard/routes/api-key-route.d.mts +8 -0
  24. package/dist/dashboard/routes/api-key-route.d.ts +8 -0
  25. package/dist/dashboard/routes/api-key-route.js +173 -0
  26. package/dist/dashboard/routes/api-key-route.js.map +1 -0
  27. package/dist/dashboard/routes/api-key-route.mjs +137 -0
  28. package/dist/dashboard/routes/api-key-route.mjs.map +1 -0
  29. package/dist/dashboard/routes/citation-route.d.mts +14 -0
  30. package/dist/dashboard/routes/citation-route.d.ts +14 -0
  31. package/dist/dashboard/routes/citation-route.js +202 -0
  32. package/dist/dashboard/routes/citation-route.js.map +1 -0
  33. package/dist/dashboard/routes/citation-route.mjs +166 -0
  34. package/dist/dashboard/routes/citation-route.mjs.map +1 -0
  35. package/dist/dashboard/routes/llms-txt-route.d.mts +6 -0
  36. package/dist/dashboard/routes/llms-txt-route.d.ts +6 -0
  37. package/dist/dashboard/routes/llms-txt-route.js +119 -0
  38. package/dist/dashboard/routes/llms-txt-route.js.map +1 -0
  39. package/dist/dashboard/routes/llms-txt-route.mjs +84 -0
  40. package/dist/dashboard/routes/llms-txt-route.mjs.map +1 -0
  41. package/dist/dashboard/routes/login-route.d.mts +6 -0
  42. package/dist/dashboard/routes/login-route.d.ts +6 -0
  43. package/dist/dashboard/routes/login-route.js +313 -0
  44. package/dist/dashboard/routes/login-route.js.map +1 -0
  45. package/dist/dashboard/routes/login-route.mjs +284 -0
  46. package/dist/dashboard/routes/login-route.mjs.map +1 -0
  47. package/dist/dashboard/routes/markdown-route.d.mts +15 -0
  48. package/dist/dashboard/routes/markdown-route.d.ts +15 -0
  49. package/dist/dashboard/routes/markdown-route.js +239 -0
  50. package/dist/dashboard/routes/markdown-route.js.map +1 -0
  51. package/dist/dashboard/routes/markdown-route.mjs +204 -0
  52. package/dist/dashboard/routes/markdown-route.mjs.map +1 -0
  53. package/dist/dashboard/routes/okf-route.d.mts +13 -0
  54. package/dist/dashboard/routes/okf-route.d.ts +13 -0
  55. package/dist/dashboard/routes/okf-route.js +184 -0
  56. package/dist/dashboard/routes/okf-route.js.map +1 -0
  57. package/dist/dashboard/routes/okf-route.mjs +149 -0
  58. package/dist/dashboard/routes/okf-route.mjs.map +1 -0
  59. package/dist/dashboard/routes/sitemap-ai-route.d.mts +6 -0
  60. package/dist/dashboard/routes/sitemap-ai-route.d.ts +6 -0
  61. package/dist/dashboard/routes/sitemap-ai-route.js +134 -0
  62. package/dist/dashboard/routes/sitemap-ai-route.js.map +1 -0
  63. package/dist/dashboard/routes/sitemap-ai-route.mjs +99 -0
  64. package/dist/dashboard/routes/sitemap-ai-route.mjs.map +1 -0
  65. package/dist/dashboard/ui/components/Sidebar.d.mts +5 -0
  66. package/dist/dashboard/ui/components/Sidebar.d.ts +5 -0
  67. package/dist/dashboard/ui/components/Sidebar.js +102 -0
  68. package/dist/dashboard/ui/components/Sidebar.js.map +1 -0
  69. package/dist/dashboard/ui/components/Sidebar.mjs +68 -0
  70. package/dist/dashboard/ui/components/Sidebar.mjs.map +1 -0
  71. package/dist/dashboard/ui/globals.css +175 -0
  72. package/dist/dashboard/ui/pages/BotAnalyticsPage.d.mts +5 -0
  73. package/dist/dashboard/ui/pages/BotAnalyticsPage.d.ts +5 -0
  74. package/dist/dashboard/ui/pages/BotAnalyticsPage.js +269 -0
  75. package/dist/dashboard/ui/pages/BotAnalyticsPage.js.map +1 -0
  76. package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs +232 -0
  77. package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs.map +1 -0
  78. package/dist/dashboard/ui/pages/BotManagementPage.d.mts +13 -0
  79. package/dist/dashboard/ui/pages/BotManagementPage.d.ts +13 -0
  80. package/dist/dashboard/ui/pages/BotManagementPage.js +177 -0
  81. package/dist/dashboard/ui/pages/BotManagementPage.js.map +1 -0
  82. package/dist/dashboard/ui/pages/BotManagementPage.mjs +153 -0
  83. package/dist/dashboard/ui/pages/BotManagementPage.mjs.map +1 -0
  84. package/dist/dashboard/ui/pages/LlmTrafficPage.d.mts +5 -0
  85. package/dist/dashboard/ui/pages/LlmTrafficPage.d.ts +5 -0
  86. package/dist/dashboard/ui/pages/LlmTrafficPage.js +203 -0
  87. package/dist/dashboard/ui/pages/LlmTrafficPage.js.map +1 -0
  88. package/dist/dashboard/ui/pages/LlmTrafficPage.mjs +168 -0
  89. package/dist/dashboard/ui/pages/LlmTrafficPage.mjs.map +1 -0
  90. package/dist/dashboard/ui/pages/SettingsPage.d.mts +8 -0
  91. package/dist/dashboard/ui/pages/SettingsPage.d.ts +8 -0
  92. package/dist/dashboard/ui/pages/SettingsPage.js +181 -0
  93. package/dist/dashboard/ui/pages/SettingsPage.js.map +1 -0
  94. package/dist/dashboard/ui/pages/SettingsPage.mjs +157 -0
  95. package/dist/dashboard/ui/pages/SettingsPage.mjs.map +1 -0
  96. package/dist/dashboard/ui/pages/SystemHealthPage.d.mts +5 -0
  97. package/dist/dashboard/ui/pages/SystemHealthPage.d.ts +5 -0
  98. package/dist/dashboard/ui/pages/SystemHealthPage.js +183 -0
  99. package/dist/dashboard/ui/pages/SystemHealthPage.js.map +1 -0
  100. package/dist/dashboard/ui/pages/SystemHealthPage.mjs +148 -0
  101. package/dist/dashboard/ui/pages/SystemHealthPage.mjs.map +1 -0
  102. package/dist/index.d.mts +84 -0
  103. package/dist/index.d.ts +84 -0
  104. package/dist/index.js +372 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/index.mjs +346 -0
  107. package/dist/index.mjs.map +1 -0
  108. package/package.json +125 -0
@@ -0,0 +1,232 @@
1
+ // src/analytics/performance-stats.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var PerformanceStats = class {
5
+ constructor(dataDir = process.env.TA_DATA_DIR ?? "data") {
6
+ this.dataDir = dataDir;
7
+ }
8
+ compute(days = 30) {
9
+ const records = this.loadRecords(days);
10
+ const totalVisits = records.length;
11
+ const uniqueBots = [...new Set(records.map((r) => r.bot_name).filter(Boolean))];
12
+ const withResponseMs = records.filter((r) => r.response_ms !== null);
13
+ const avgResponseMs = withResponseMs.length > 0 ? withResponseMs.reduce((s, r) => s + r.response_ms, 0) / withResponseMs.length : null;
14
+ const cacheHitRate = records.length > 0 ? records.filter((r) => r.cache_hit).length / records.length : null;
15
+ const pageCounts = /* @__PURE__ */ new Map();
16
+ const botCounts = /* @__PURE__ */ new Map();
17
+ const dayCounts = /* @__PURE__ */ new Map();
18
+ for (const r of records) {
19
+ pageCounts.set(r.url, (pageCounts.get(r.url) ?? 0) + 1);
20
+ const name = r.bot_name ?? "unknown";
21
+ botCounts.set(name, (botCounts.get(name) ?? 0) + 1);
22
+ const day = r.timestamp.slice(0, 10);
23
+ dayCounts.set(day, (dayCounts.get(day) ?? 0) + 1);
24
+ }
25
+ const topPages = [...pageCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([url, visits]) => ({ url, visits }));
26
+ const topBots = [...botCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, visits]) => ({ name, visits }));
27
+ const visitsByDay = [...dayCounts.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([date, visits]) => ({ date, visits }));
28
+ return { totalVisits, uniqueBots, avgResponseMs, cacheHitRate, topPages, topBots, visitsByDay };
29
+ }
30
+ loadRecords(days) {
31
+ const filePath = path.join(this.dataDir, "ta-visits.jsonl");
32
+ if (!fs.existsSync(filePath)) return [];
33
+ const cutoff = /* @__PURE__ */ new Date();
34
+ cutoff.setDate(cutoff.getDate() - days);
35
+ const cutoffStr = cutoff.toISOString();
36
+ return fs.readFileSync(filePath, "utf-8").split("\n").filter(Boolean).map((line) => {
37
+ try {
38
+ return JSON.parse(line);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }).filter((r) => r !== null && r.timestamp >= cutoffStr);
43
+ }
44
+ };
45
+
46
+ // src/dashboard/ui/components/HeroCard.tsx
47
+ import { jsx, jsxs } from "react/jsx-runtime";
48
+ function HeroCard({ label, value, meta, color = "blue", icon }) {
49
+ return /* @__PURE__ */ jsxs("div", { className: `ta-hero-card ta-hero-card--${color}`, children: [
50
+ /* @__PURE__ */ jsx("div", { className: "ta-hero-icon", children: icon }),
51
+ /* @__PURE__ */ jsxs("div", { children: [
52
+ /* @__PURE__ */ jsx("div", { className: "ta-hero-label", children: label }),
53
+ /* @__PURE__ */ jsx("div", { className: "ta-hero-value", children: value }),
54
+ meta && /* @__PURE__ */ jsx("div", { className: "ta-hero-meta", children: meta })
55
+ ] })
56
+ ] });
57
+ }
58
+
59
+ // src/dashboard/ui/components/Card.tsx
60
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
61
+ function Card({ title, action, children }) {
62
+ return /* @__PURE__ */ jsxs2("div", { className: "ta-card ta-section", children: [
63
+ /* @__PURE__ */ jsxs2("div", { className: "ta-card-header", children: [
64
+ /* @__PURE__ */ jsx2("h2", { children: title }),
65
+ action
66
+ ] }),
67
+ /* @__PURE__ */ jsx2("div", { className: "ta-card-body", children })
68
+ ] });
69
+ }
70
+
71
+ // src/dashboard/ui/components/VisitsChart.tsx
72
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
73
+ function VisitsChart({ data, height = 160 }) {
74
+ if (!data.length) {
75
+ return /* @__PURE__ */ jsx3("div", { className: "ta-empty", children: /* @__PURE__ */ jsx3("p", { children: "No visit data yet." }) });
76
+ }
77
+ const max = Math.max(...data.map((d) => d.visits), 1);
78
+ const barWidth = Math.max(4, Math.floor(560 / data.length) - 2);
79
+ const showLabel = data.length <= 14;
80
+ return /* @__PURE__ */ jsx3("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsx3(
81
+ "svg",
82
+ {
83
+ width: "100%",
84
+ viewBox: `0 0 ${Math.max(data.length * (barWidth + 2), 560)} ${height + 40}`,
85
+ style: { display: "block", minWidth: 320 },
86
+ children: data.map((d, i) => {
87
+ const barH = Math.max(2, Math.round(d.visits / max * height));
88
+ const x = i * (barWidth + 2);
89
+ const y = height - barH;
90
+ return /* @__PURE__ */ jsxs3("g", { children: [
91
+ /* @__PURE__ */ jsx3(
92
+ "rect",
93
+ {
94
+ x,
95
+ y,
96
+ width: barWidth,
97
+ height: barH,
98
+ rx: 3,
99
+ fill: "var(--ta-blue)",
100
+ opacity: 0.85,
101
+ children: /* @__PURE__ */ jsx3("title", { children: `${d.date}: ${d.visits} visits` })
102
+ }
103
+ ),
104
+ showLabel && /* @__PURE__ */ jsxs3(
105
+ "text",
106
+ {
107
+ x: x + barWidth / 2,
108
+ y: height + 16,
109
+ textAnchor: "middle",
110
+ fontSize: 9,
111
+ fill: "var(--ta-gray-500)",
112
+ children: [
113
+ d.date.slice(5),
114
+ " "
115
+ ]
116
+ }
117
+ )
118
+ ] }, d.date);
119
+ })
120
+ }
121
+ ) });
122
+ }
123
+
124
+ // src/dashboard/ui/pages/BotAnalyticsPage.tsx
125
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
126
+ var BOT_COLORS = {
127
+ ClaudeBot: "orange",
128
+ GPTBot: "green",
129
+ PerplexityBot: "blue",
130
+ "Googlebot-AI": "teal",
131
+ default: "gray"
132
+ };
133
+ async function BotAnalyticsPage() {
134
+ const stats = new PerformanceStats();
135
+ const summary = stats.compute(30);
136
+ const cacheRate = summary.cacheHitRate !== null ? `${(summary.cacheHitRate * 100).toFixed(0)}%` : "\u2014";
137
+ const avgMs = summary.avgResponseMs !== null ? `${Math.round(summary.avgResponseMs)}ms` : "\u2014";
138
+ return /* @__PURE__ */ jsxs4("div", { children: [
139
+ /* @__PURE__ */ jsx4("h1", { className: "ta-page-title", children: "Bot Analytics" }),
140
+ /* @__PURE__ */ jsx4("p", { className: "ta-page-subtitle", children: "AI crawler visits over the last 30 days" }),
141
+ /* @__PURE__ */ jsxs4("div", { className: "ta-hero-grid", children: [
142
+ /* @__PURE__ */ jsx4(
143
+ HeroCard,
144
+ {
145
+ label: "Total Bot Visits",
146
+ value: summary.totalVisits.toLocaleString(),
147
+ meta: `${summary.uniqueBots.length} unique bots`,
148
+ color: "blue",
149
+ icon: /* @__PURE__ */ jsx4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx4("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" }) })
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsx4(
153
+ HeroCard,
154
+ {
155
+ label: "Unique Pages Crawled",
156
+ value: summary.topPages.length.toLocaleString(),
157
+ meta: "distinct URLs visited",
158
+ color: "teal",
159
+ icon: /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
160
+ /* @__PURE__ */ jsx4("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
161
+ /* @__PURE__ */ jsx4("polyline", { points: "14 2 14 8 20 8" })
162
+ ] })
163
+ }
164
+ ),
165
+ /* @__PURE__ */ jsx4(
166
+ HeroCard,
167
+ {
168
+ label: "Cache Hit Rate",
169
+ value: cacheRate,
170
+ meta: "higher = faster bot response",
171
+ color: "green",
172
+ icon: /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
173
+ /* @__PURE__ */ jsx4("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
174
+ /* @__PURE__ */ jsx4("polyline", { points: "22 4 12 14.01 9 11.01" })
175
+ ] })
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx4(
179
+ HeroCard,
180
+ {
181
+ label: "Avg Response",
182
+ value: avgMs,
183
+ meta: "for markdown requests",
184
+ color: "orange",
185
+ icon: /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
186
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "10" }),
187
+ /* @__PURE__ */ jsx4("polyline", { points: "12 6 12 12 16 14" })
188
+ ] })
189
+ }
190
+ )
191
+ ] }),
192
+ /* @__PURE__ */ jsx4(Card, { title: "Daily Visits (30 days)", children: /* @__PURE__ */ jsx4(VisitsChart, { data: summary.visitsByDay }) }),
193
+ /* @__PURE__ */ jsxs4("div", { className: "ta-grid-2", children: [
194
+ /* @__PURE__ */ jsx4(Card, { title: "Top Bots", children: summary.topBots.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "ta-empty", children: /* @__PURE__ */ jsx4("p", { children: "No bot visits recorded yet." }) }) : /* @__PURE__ */ jsxs4("table", { className: "ta-table", children: [
195
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs4("tr", { children: [
196
+ /* @__PURE__ */ jsx4("th", { children: "Bot" }),
197
+ /* @__PURE__ */ jsx4("th", { children: "Visits" }),
198
+ /* @__PURE__ */ jsx4("th", { children: "Share" })
199
+ ] }) }),
200
+ /* @__PURE__ */ jsx4("tbody", { children: summary.topBots.map((b) => {
201
+ const pct = summary.totalVisits > 0 ? (b.visits / summary.totalVisits * 100).toFixed(1) : "0";
202
+ const color = BOT_COLORS[b.name] ?? BOT_COLORS.default;
203
+ return /* @__PURE__ */ jsxs4("tr", { children: [
204
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4("span", { className: `ta-badge ta-badge--${color}`, children: b.name }) }),
205
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4("strong", { children: b.visits.toLocaleString() }) }),
206
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
207
+ /* @__PURE__ */ jsx4("div", { style: { flex: 1, height: 6, background: "var(--ta-gray-100)", borderRadius: 3 }, children: /* @__PURE__ */ jsx4("div", { style: { width: `${pct}%`, height: "100%", background: "var(--ta-blue)", borderRadius: 3 } }) }),
208
+ /* @__PURE__ */ jsxs4("span", { style: { fontSize: 12, color: "var(--ta-gray-600)", width: 36 }, children: [
209
+ pct,
210
+ "%"
211
+ ] })
212
+ ] }) })
213
+ ] }, b.name);
214
+ }) })
215
+ ] }) }),
216
+ /* @__PURE__ */ jsx4(Card, { title: "Top Pages Crawled", children: summary.topPages.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "ta-empty", children: /* @__PURE__ */ jsx4("p", { children: "No pages crawled yet." }) }) : /* @__PURE__ */ jsxs4("table", { className: "ta-table", children: [
217
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs4("tr", { children: [
218
+ /* @__PURE__ */ jsx4("th", { children: "Page" }),
219
+ /* @__PURE__ */ jsx4("th", { children: "Visits" })
220
+ ] }) }),
221
+ /* @__PURE__ */ jsx4("tbody", { children: summary.topPages.map((p) => /* @__PURE__ */ jsxs4("tr", { children: [
222
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4("a", { href: p.url, target: "_blank", rel: "noreferrer", style: { fontFamily: "var(--ta-font-mono)", fontSize: 12 }, children: p.url }) }),
223
+ /* @__PURE__ */ jsx4("td", { children: /* @__PURE__ */ jsx4("strong", { children: p.visits.toLocaleString() }) })
224
+ ] }, p.url)) })
225
+ ] }) })
226
+ ] })
227
+ ] });
228
+ }
229
+ export {
230
+ BotAnalyticsPage
231
+ };
232
+ //# sourceMappingURL=BotAnalyticsPage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/analytics/performance-stats.ts","../../../../src/dashboard/ui/components/HeroCard.tsx","../../../../src/dashboard/ui/components/Card.tsx","../../../../src/dashboard/ui/components/VisitsChart.tsx","../../../../src/dashboard/ui/pages/BotAnalyticsPage.tsx"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport type { VisitRecord } from './visit-tracker.js'\n\nexport interface PerformanceSummary {\n totalVisits: number\n uniqueBots: string[]\n avgResponseMs: number | null\n cacheHitRate: number | null\n topPages: Array<{ url: string; visits: number }>\n topBots: Array<{ name: string; visits: number }>\n visitsByDay: Array<{ date: string; visits: number }>\n}\n\nexport class PerformanceStats {\n private dataDir: string\n\n constructor(dataDir = process.env.TA_DATA_DIR ?? 'data') {\n this.dataDir = dataDir\n }\n\n compute(days = 30): PerformanceSummary {\n const records = this.loadRecords(days)\n\n const totalVisits = records.length\n const uniqueBots = [...new Set(records.map(r => r.bot_name).filter(Boolean))] as string[]\n\n const withResponseMs = records.filter(r => r.response_ms !== null)\n const avgResponseMs = withResponseMs.length > 0\n ? withResponseMs.reduce((s, r) => s + r.response_ms!, 0) / withResponseMs.length\n : null\n\n const cacheHitRate = records.length > 0\n ? records.filter(r => r.cache_hit).length / records.length\n : null\n\n const pageCounts = new Map<string, number>()\n const botCounts = new Map<string, number>()\n const dayCounts = new Map<string, number>()\n\n for (const r of records) {\n pageCounts.set(r.url, (pageCounts.get(r.url) ?? 0) + 1)\n const name = r.bot_name ?? 'unknown'\n botCounts.set(name, (botCounts.get(name) ?? 0) + 1)\n const day = r.timestamp.slice(0, 10)\n dayCounts.set(day, (dayCounts.get(day) ?? 0) + 1)\n }\n\n const topPages = [...pageCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([url, visits]) => ({ url, visits }))\n\n const topBots = [...botCounts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([name, visits]) => ({ name, visits }))\n\n const visitsByDay = [...dayCounts.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([date, visits]) => ({ date, visits }))\n\n return { totalVisits, uniqueBots, avgResponseMs, cacheHitRate, topPages, topBots, visitsByDay }\n }\n\n private loadRecords(days: number): VisitRecord[] {\n const filePath = path.join(this.dataDir, 'ta-visits.jsonl')\n if (!fs.existsSync(filePath)) return []\n\n const cutoff = new Date()\n cutoff.setDate(cutoff.getDate() - days)\n const cutoffStr = cutoff.toISOString()\n\n return fs.readFileSync(filePath, 'utf-8')\n .split('\\n')\n .filter(Boolean)\n .map(line => { try { return JSON.parse(line) as VisitRecord } catch { return null } })\n .filter((r): r is VisitRecord => r !== null && r.timestamp >= cutoffStr)\n }\n}\n","import type { ReactNode } from 'react'\n\ninterface HeroCardProps {\n label: string\n value: string | number\n meta?: string\n color?: 'blue' | 'green' | 'orange' | 'teal'\n icon: ReactNode\n}\n\nexport function HeroCard({ label, value, meta, color = 'blue', icon }: HeroCardProps) {\n return (\n <div className={`ta-hero-card ta-hero-card--${color}`}>\n <div className=\"ta-hero-icon\">{icon}</div>\n <div>\n <div className=\"ta-hero-label\">{label}</div>\n <div className=\"ta-hero-value\">{value}</div>\n {meta && <div className=\"ta-hero-meta\">{meta}</div>}\n </div>\n </div>\n )\n}\n","import type { ReactNode } from 'react'\n\ninterface CardProps {\n title: string\n action?: ReactNode\n children: ReactNode\n}\n\nexport function Card({ title, action, children }: CardProps) {\n return (\n <div className=\"ta-card ta-section\">\n <div className=\"ta-card-header\">\n <h2>{title}</h2>\n {action}\n </div>\n <div className=\"ta-card-body\">{children}</div>\n </div>\n )\n}\n","'use client'\n\ninterface DayData { date: string; visits: number }\n\ninterface VisitsChartProps {\n data: DayData[]\n height?: number\n}\n\nexport function VisitsChart({ data, height = 160 }: VisitsChartProps) {\n if (!data.length) {\n return <div className=\"ta-empty\"><p>No visit data yet.</p></div>\n }\n\n const max = Math.max(...data.map(d => d.visits), 1)\n const barWidth = Math.max(4, Math.floor(560 / data.length) - 2)\n const showLabel = data.length <= 14\n\n return (\n <div style={{ overflowX: 'auto' }}>\n <svg\n width=\"100%\"\n viewBox={`0 0 ${Math.max(data.length * (barWidth + 2), 560)} ${height + 40}`}\n style={{ display: 'block', minWidth: 320 }}\n >\n {data.map((d, i) => {\n const barH = Math.max(2, Math.round((d.visits / max) * height))\n const x = i * (barWidth + 2)\n const y = height - barH\n return (\n <g key={d.date}>\n <rect\n x={x} y={y}\n width={barWidth} height={barH}\n rx={3}\n fill=\"var(--ta-blue)\"\n opacity={0.85}\n >\n <title>{`${d.date}: ${d.visits} visits`}</title>\n </rect>\n {showLabel && (\n <text\n x={x + barWidth / 2} y={height + 16}\n textAnchor=\"middle\"\n fontSize={9}\n fill=\"var(--ta-gray-500)\"\n >\n {d.date.slice(5)} {/* MM-DD */}\n </text>\n )}\n </g>\n )\n })}\n </svg>\n </div>\n )\n}\n","import { PerformanceStats } from '../../../analytics/performance-stats.js'\nimport { HeroCard } from '../components/HeroCard.js'\nimport { Card } from '../components/Card.js'\nimport { VisitsChart } from '../components/VisitsChart.js'\n\nconst BOT_COLORS: Record<string, string> = {\n ClaudeBot: 'orange', GPTBot: 'green', PerplexityBot: 'blue',\n 'Googlebot-AI': 'teal', default: 'gray',\n}\n\nexport async function BotAnalyticsPage() {\n const stats = new PerformanceStats()\n const summary = stats.compute(30)\n\n const cacheRate = summary.cacheHitRate !== null\n ? `${(summary.cacheHitRate * 100).toFixed(0)}%` : '—'\n const avgMs = summary.avgResponseMs !== null\n ? `${Math.round(summary.avgResponseMs)}ms` : '—'\n\n return (\n <div>\n <h1 className=\"ta-page-title\">Bot Analytics</h1>\n <p className=\"ta-page-subtitle\">AI crawler visits over the last 30 days</p>\n\n {/* Hero metrics */}\n <div className=\"ta-hero-grid\">\n <HeroCard\n label=\"Total Bot Visits\"\n value={summary.totalVisits.toLocaleString()}\n meta={`${summary.uniqueBots.length} unique bots`}\n color=\"blue\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\" /></svg>}\n />\n <HeroCard\n label=\"Unique Pages Crawled\"\n value={summary.topPages.length.toLocaleString()}\n meta=\"distinct URLs visited\"\n color=\"teal\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/></svg>}\n />\n <HeroCard\n label=\"Cache Hit Rate\"\n value={cacheRate}\n meta=\"higher = faster bot response\"\n color=\"green\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/></svg>}\n />\n <HeroCard\n label=\"Avg Response\"\n value={avgMs}\n meta=\"for markdown requests\"\n color=\"orange\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>}\n />\n </div>\n\n {/* Visits chart */}\n <Card title=\"Daily Visits (30 days)\">\n <VisitsChart data={summary.visitsByDay} />\n </Card>\n\n <div className=\"ta-grid-2\">\n {/* Top bots */}\n <Card title=\"Top Bots\">\n {summary.topBots.length === 0 ? (\n <div className=\"ta-empty\"><p>No bot visits recorded yet.</p></div>\n ) : (\n <table className=\"ta-table\">\n <thead>\n <tr><th>Bot</th><th>Visits</th><th>Share</th></tr>\n </thead>\n <tbody>\n {summary.topBots.map(b => {\n const pct = summary.totalVisits > 0\n ? ((b.visits / summary.totalVisits) * 100).toFixed(1)\n : '0'\n const color = BOT_COLORS[b.name] ?? BOT_COLORS.default\n return (\n <tr key={b.name}>\n <td>\n <span className={`ta-badge ta-badge--${color}`}>{b.name}</span>\n </td>\n <td><strong>{b.visits.toLocaleString()}</strong></td>\n <td>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <div style={{ flex: 1, height: 6, background: 'var(--ta-gray-100)', borderRadius: 3 }}>\n <div style={{ width: `${pct}%`, height: '100%', background: 'var(--ta-blue)', borderRadius: 3 }} />\n </div>\n <span style={{ fontSize: 12, color: 'var(--ta-gray-600)', width: 36 }}>{pct}%</span>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n )}\n </Card>\n\n {/* Top pages */}\n <Card title=\"Top Pages Crawled\">\n {summary.topPages.length === 0 ? (\n <div className=\"ta-empty\"><p>No pages crawled yet.</p></div>\n ) : (\n <table className=\"ta-table\">\n <thead>\n <tr><th>Page</th><th>Visits</th></tr>\n </thead>\n <tbody>\n {summary.topPages.map(p => (\n <tr key={p.url}>\n <td>\n <a href={p.url} target=\"_blank\" rel=\"noreferrer\" style={{ fontFamily: 'var(--ta-font-mono)', fontSize: 12 }}>\n {p.url}\n </a>\n </td>\n <td><strong>{p.visits.toLocaleString()}</strong></td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </Card>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
@@ -0,0 +1,13 @@
1
+ import * as react from 'react';
2
+
3
+ interface BotConfig {
4
+ allowlist: string[];
5
+ blocklist: string[];
6
+ track_unknown: boolean;
7
+ }
8
+ interface BotManagementPageProps {
9
+ config: BotConfig;
10
+ }
11
+ declare function BotManagementPage({ config }: BotManagementPageProps): react.JSX.Element;
12
+
13
+ export { BotManagementPage };
@@ -0,0 +1,13 @@
1
+ import * as react from 'react';
2
+
3
+ interface BotConfig {
4
+ allowlist: string[];
5
+ blocklist: string[];
6
+ track_unknown: boolean;
7
+ }
8
+ interface BotManagementPageProps {
9
+ config: BotConfig;
10
+ }
11
+ declare function BotManagementPage({ config }: BotManagementPageProps): react.JSX.Element;
12
+
13
+ export { BotManagementPage };
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/dashboard/ui/pages/BotManagementPage.tsx
22
+ var BotManagementPage_exports = {};
23
+ __export(BotManagementPage_exports, {
24
+ BotManagementPage: () => BotManagementPage
25
+ });
26
+ module.exports = __toCommonJS(BotManagementPage_exports);
27
+ var import_react = require("react");
28
+
29
+ // src/dashboard/ui/components/Card.tsx
30
+ var import_jsx_runtime = require("react/jsx-runtime");
31
+ function Card({ title, action, children }) {
32
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ta-card ta-section", children: [
33
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ta-card-header", children: [
34
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
35
+ action
36
+ ] }),
37
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ta-card-body", children })
38
+ ] });
39
+ }
40
+
41
+ // src/detection/known-patterns.ts
42
+ var KNOWN_BOTS = [
43
+ // AI Crawlers
44
+ { name: "ClaudeBot", category: "ai_crawler", patterns: [/claudebot/i, /claude-web/i] },
45
+ { name: "GPTBot", category: "ai_crawler", patterns: [/gptbot/i] },
46
+ { name: "ChatGPT-User", category: "ai_crawler", patterns: [/chatgpt-user/i] },
47
+ { name: "PerplexityBot", category: "ai_crawler", patterns: [/perplexitybot/i] },
48
+ { name: "Googlebot-AI", category: "ai_crawler", patterns: [/google-extended/i, /googleother/i] },
49
+ { name: "FacebookBot", category: "ai_crawler", patterns: [/facebookbot/i] },
50
+ { name: "Applebot-Extended", category: "ai_crawler", patterns: [/applebot-extended/i] },
51
+ { name: "YouBot", category: "ai_crawler", patterns: [/youbot/i] },
52
+ { name: "CCBot", category: "ai_crawler", patterns: [/ccbot/i] },
53
+ { name: "CohereCrawler", category: "ai_crawler", patterns: [/cohere-ai/i] },
54
+ { name: "AI2Bot", category: "ai_crawler", patterns: [/ai2bot/i] },
55
+ { name: "Bytespider", category: "ai_crawler", patterns: [/bytespider/i] },
56
+ { name: "Diffbot", category: "ai_crawler", patterns: [/diffbot/i] },
57
+ // Search Engines
58
+ { name: "Googlebot", category: "search_engine", patterns: [/googlebot/i] },
59
+ { name: "Bingbot", category: "search_engine", patterns: [/bingbot/i, /msnbot/i] },
60
+ { name: "DuckDuckBot", category: "search_engine", patterns: [/duckduckbot/i] },
61
+ { name: "Baiduspider", category: "search_engine", patterns: [/baiduspider/i] },
62
+ { name: "YandexBot", category: "search_engine", patterns: [/yandexbot/i] },
63
+ { name: "Sogou", category: "search_engine", patterns: [/sogou/i] },
64
+ { name: "Exabot", category: "search_engine", patterns: [/exabot/i] },
65
+ { name: "ia_archiver", category: "search_engine", patterns: [/ia_archiver/i] }
66
+ ];
67
+
68
+ // src/dashboard/ui/pages/BotManagementPage.tsx
69
+ var import_jsx_runtime2 = require("react/jsx-runtime");
70
+ function BotManagementPage({ config }) {
71
+ const [allowlist, setAllowlist] = (0, import_react.useState)(config.allowlist);
72
+ const [blocklist, setBlocklist] = (0, import_react.useState)(config.blocklist);
73
+ const [trackUnknown, setTrackUnknown] = (0, import_react.useState)(config.track_unknown);
74
+ const [newAllow, setNewAllow] = (0, import_react.useState)("");
75
+ const [newBlock, setNewBlock] = (0, import_react.useState)("");
76
+ const [saving, setSaving] = (0, import_react.useState)(false);
77
+ const [saved, setSaved] = (0, import_react.useState)(false);
78
+ async function save() {
79
+ setSaving(true);
80
+ await fetch("/api/third-audience/bots-config", {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ allowlist, blocklist, track_unknown: trackUnknown })
84
+ });
85
+ setSaving(false);
86
+ setSaved(true);
87
+ setTimeout(() => setSaved(false), 2e3);
88
+ }
89
+ const AI_CRAWLERS = KNOWN_BOTS.filter((b) => b.category === "ai_crawler");
90
+ const SEARCH_ENGINES = KNOWN_BOTS.filter((b) => b.category === "search_engine");
91
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
92
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: "ta-page-title", children: "Bot Management" }),
93
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "ta-page-subtitle", children: "Configure which bots to track or block" }),
94
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { title: "Known AI Crawlers", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "ta-table", children: [
95
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
96
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: "Bot" }),
97
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: "User-Agent Pattern" }),
98
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: "Category" })
99
+ ] }) }),
100
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tbody", { children: [
101
+ AI_CRAWLERS.map((b) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
102
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: b.name }) }),
103
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: b.patterns[0].source }) }),
104
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ta-badge ta-badge--blue", children: "AI Crawler" }) })
105
+ ] }, b.name)),
106
+ SEARCH_ENGINES.map((b) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
107
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: b.name }) }),
108
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: b.patterns[0].source }) }),
109
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ta-badge ta-badge--gray", children: "Search Engine" }) })
110
+ ] }, b.name))
111
+ ] })
112
+ ] }) }),
113
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { title: "Allowlist", action: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
114
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
115
+ "input",
116
+ {
117
+ value: newAllow,
118
+ onChange: (e) => setNewAllow(e.target.value),
119
+ placeholder: "Bot name or UA pattern",
120
+ style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--ta-gray-200)", fontSize: 13, width: 200 }
121
+ }
122
+ ),
123
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "ta-btn ta-btn--primary", onClick: () => {
124
+ if (newAllow) {
125
+ setAllowlist((l) => [...l, newAllow]);
126
+ setNewAllow("");
127
+ }
128
+ }, children: "Add" })
129
+ ] }), children: allowlist.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "var(--ta-gray-500)", fontSize: 13 }, children: "No overrides. All detected bots are tracked." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { listStyle: "none", display: "flex", flexWrap: "wrap", gap: 8 }, children: allowlist.map((name) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { style: { display: "flex", alignItems: "center", gap: 6, background: "rgba(52,199,89,0.1)", borderRadius: 6, padding: "4px 10px", fontSize: 13 }, children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ta-dot ta-dot--green" }),
131
+ name,
132
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setAllowlist((l) => l.filter((x) => x !== name)), style: { background: "none", border: "none", cursor: "pointer", color: "var(--ta-gray-500)", marginLeft: 4 }, children: "\xD7" })
133
+ ] }, name)) }) }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { title: "Blocklist", action: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
135
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
136
+ "input",
137
+ {
138
+ value: newBlock,
139
+ onChange: (e) => setNewBlock(e.target.value),
140
+ placeholder: "Bot name or UA pattern",
141
+ style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--ta-gray-200)", fontSize: 13, width: 200 }
142
+ }
143
+ ),
144
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "ta-btn ta-btn--danger", onClick: () => {
145
+ if (newBlock) {
146
+ setBlocklist((l) => [...l, newBlock]);
147
+ setNewBlock("");
148
+ }
149
+ }, children: "Block" })
150
+ ] }), children: blocklist.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "var(--ta-gray-500)", fontSize: 13 }, children: "No blocked bots." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { listStyle: "none", display: "flex", flexWrap: "wrap", gap: 8 }, children: blocklist.map((name) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { style: { display: "flex", alignItems: "center", gap: 6, background: "rgba(255,59,48,0.1)", borderRadius: 6, padding: "4px 10px", fontSize: 13 }, children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ta-dot ta-dot--red" }),
152
+ name,
153
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { onClick: () => setBlocklist((l) => l.filter((x) => x !== name)), style: { background: "none", border: "none", cursor: "pointer", color: "var(--ta-gray-500)", marginLeft: 4 }, children: "\xD7" })
154
+ ] }, name)) }) }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Card, { title: "Unknown Bots", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { display: "flex", alignItems: "center", gap: 10, cursor: "pointer" }, children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
157
+ "input",
158
+ {
159
+ type: "checkbox",
160
+ checked: trackUnknown,
161
+ onChange: (e) => setTrackUnknown(e.target.checked),
162
+ style: { width: 16, height: 16 }
163
+ }
164
+ ),
165
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Track unknown bots (bots detected by heuristics, not in the known list)" })
166
+ ] }) }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
168
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "ta-btn ta-btn--primary", onClick: save, disabled: saving, children: saving ? "Saving\u2026" : "Save Configuration" }),
169
+ saved && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "var(--ta-green)", fontSize: 13 }, children: "\u2713 Saved" })
170
+ ] })
171
+ ] });
172
+ }
173
+ // Annotate the CommonJS export names for ESM import in node:
174
+ 0 && (module.exports = {
175
+ BotManagementPage
176
+ });
177
+ //# sourceMappingURL=BotManagementPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/dashboard/ui/pages/BotManagementPage.tsx","../../../../src/dashboard/ui/components/Card.tsx","../../../../src/detection/known-patterns.ts"],"sourcesContent":["'use client'\nimport { useState } from 'react'\nimport { Card } from '../components/Card.js'\nimport { KNOWN_BOTS } from '../../../detection/known-patterns.js'\n\ninterface BotConfig {\n allowlist: string[]\n blocklist: string[]\n track_unknown: boolean\n}\n\ninterface BotManagementPageProps {\n config: BotConfig\n}\n\nexport function BotManagementPage({ config }: BotManagementPageProps) {\n const [allowlist, setAllowlist] = useState(config.allowlist)\n const [blocklist, setBlocklist] = useState(config.blocklist)\n const [trackUnknown, setTrackUnknown] = useState(config.track_unknown)\n const [newAllow, setNewAllow] = useState('')\n const [newBlock, setNewBlock] = useState('')\n const [saving, setSaving] = useState(false)\n const [saved, setSaved] = useState(false)\n\n async function save() {\n setSaving(true)\n await fetch('/api/third-audience/bots-config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ allowlist, blocklist, track_unknown: trackUnknown }),\n })\n setSaving(false)\n setSaved(true)\n setTimeout(() => setSaved(false), 2000)\n }\n\n const AI_CRAWLERS = KNOWN_BOTS.filter(b => b.category === 'ai_crawler')\n const SEARCH_ENGINES = KNOWN_BOTS.filter(b => b.category === 'search_engine')\n\n return (\n <div>\n <h1 className=\"ta-page-title\">Bot Management</h1>\n <p className=\"ta-page-subtitle\">Configure which bots to track or block</p>\n\n {/* Known bots reference */}\n <Card title=\"Known AI Crawlers\">\n <table className=\"ta-table\">\n <thead><tr><th>Bot</th><th>User-Agent Pattern</th><th>Category</th></tr></thead>\n <tbody>\n {AI_CRAWLERS.map(b => (\n <tr key={b.name}>\n <td><strong>{b.name}</strong></td>\n <td><code>{b.patterns[0].source}</code></td>\n <td><span className=\"ta-badge ta-badge--blue\">AI Crawler</span></td>\n </tr>\n ))}\n {SEARCH_ENGINES.map(b => (\n <tr key={b.name}>\n <td><strong>{b.name}</strong></td>\n <td><code>{b.patterns[0].source}</code></td>\n <td><span className=\"ta-badge ta-badge--gray\">Search Engine</span></td>\n </tr>\n ))}\n </tbody>\n </table>\n </Card>\n\n {/* Allowlist */}\n <Card title=\"Allowlist\" action={\n <div style={{ display: 'flex', gap: 8 }}>\n <input\n value={newAllow}\n onChange={e => setNewAllow(e.target.value)}\n placeholder=\"Bot name or UA pattern\"\n style={{ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--ta-gray-200)', fontSize: 13, width: 200 }}\n />\n <button className=\"ta-btn ta-btn--primary\" onClick={() => { if (newAllow) { setAllowlist(l => [...l, newAllow]); setNewAllow('') } }}>\n Add\n </button>\n </div>\n }>\n {allowlist.length === 0 ? (\n <p style={{ color: 'var(--ta-gray-500)', fontSize: 13 }}>No overrides. All detected bots are tracked.</p>\n ) : (\n <ul style={{ listStyle: 'none', display: 'flex', flexWrap: 'wrap', gap: 8 }}>\n {allowlist.map(name => (\n <li key={name} style={{ display: 'flex', alignItems: 'center', gap: 6, background: 'rgba(52,199,89,0.1)', borderRadius: 6, padding: '4px 10px', fontSize: 13 }}>\n <span className=\"ta-dot ta-dot--green\" />\n {name}\n <button onClick={() => setAllowlist(l => l.filter(x => x !== name))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ta-gray-500)', marginLeft: 4 }}>×</button>\n </li>\n ))}\n </ul>\n )}\n </Card>\n\n {/* Blocklist */}\n <Card title=\"Blocklist\" action={\n <div style={{ display: 'flex', gap: 8 }}>\n <input\n value={newBlock}\n onChange={e => setNewBlock(e.target.value)}\n placeholder=\"Bot name or UA pattern\"\n style={{ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--ta-gray-200)', fontSize: 13, width: 200 }}\n />\n <button className=\"ta-btn ta-btn--danger\" onClick={() => { if (newBlock) { setBlocklist(l => [...l, newBlock]); setNewBlock('') } }}>\n Block\n </button>\n </div>\n }>\n {blocklist.length === 0 ? (\n <p style={{ color: 'var(--ta-gray-500)', fontSize: 13 }}>No blocked bots.</p>\n ) : (\n <ul style={{ listStyle: 'none', display: 'flex', flexWrap: 'wrap', gap: 8 }}>\n {blocklist.map(name => (\n <li key={name} style={{ display: 'flex', alignItems: 'center', gap: 6, background: 'rgba(255,59,48,0.1)', borderRadius: 6, padding: '4px 10px', fontSize: 13 }}>\n <span className=\"ta-dot ta-dot--red\" />\n {name}\n <button onClick={() => setBlocklist(l => l.filter(x => x !== name))} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ta-gray-500)', marginLeft: 4 }}>×</button>\n </li>\n ))}\n </ul>\n )}\n </Card>\n\n {/* Track unknown */}\n <Card title=\"Unknown Bots\">\n <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={trackUnknown}\n onChange={e => setTrackUnknown(e.target.checked)}\n style={{ width: 16, height: 16 }}\n />\n <span>Track unknown bots (bots detected by heuristics, not in the known list)</span>\n </label>\n </Card>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>\n <button className=\"ta-btn ta-btn--primary\" onClick={save} disabled={saving}>\n {saving ? 'Saving…' : 'Save Configuration'}\n </button>\n {saved && <span style={{ color: 'var(--ta-green)', fontSize: 13 }}>✓ Saved</span>}\n </div>\n </div>\n )\n}\n","import type { ReactNode } from 'react'\n\ninterface CardProps {\n title: string\n action?: ReactNode\n children: ReactNode\n}\n\nexport function Card({ title, action, children }: CardProps) {\n return (\n <div className=\"ta-card ta-section\">\n <div className=\"ta-card-header\">\n <h2>{title}</h2>\n {action}\n </div>\n <div className=\"ta-card-body\">{children}</div>\n </div>\n )\n}\n","/** Known AI crawler and search engine user-agent patterns. */\nexport interface KnownBot {\n name: string\n category: 'ai_crawler' | 'search_engine'\n patterns: RegExp[]\n}\n\nexport const KNOWN_BOTS: KnownBot[] = [\n // AI Crawlers\n { name: 'ClaudeBot', category: 'ai_crawler', patterns: [/claudebot/i, /claude-web/i] },\n { name: 'GPTBot', category: 'ai_crawler', patterns: [/gptbot/i] },\n { name: 'ChatGPT-User', category: 'ai_crawler', patterns: [/chatgpt-user/i] },\n { name: 'PerplexityBot', category: 'ai_crawler', patterns: [/perplexitybot/i] },\n { name: 'Googlebot-AI', category: 'ai_crawler', patterns: [/google-extended/i, /googleother/i] },\n { name: 'FacebookBot', category: 'ai_crawler', patterns: [/facebookbot/i] },\n { name: 'Applebot-Extended',category: 'ai_crawler', patterns: [/applebot-extended/i] },\n { name: 'YouBot', category: 'ai_crawler', patterns: [/youbot/i] },\n { name: 'CCBot', category: 'ai_crawler', patterns: [/ccbot/i] },\n { name: 'CohereCrawler', category: 'ai_crawler', patterns: [/cohere-ai/i] },\n { name: 'AI2Bot', category: 'ai_crawler', patterns: [/ai2bot/i] },\n { name: 'Bytespider', category: 'ai_crawler', patterns: [/bytespider/i] },\n { name: 'Diffbot', category: 'ai_crawler', patterns: [/diffbot/i] },\n\n // Search Engines\n { name: 'Googlebot', category: 'search_engine', patterns: [/googlebot/i] },\n { name: 'Bingbot', category: 'search_engine', patterns: [/bingbot/i, /msnbot/i] },\n { name: 'DuckDuckBot', category: 'search_engine', patterns: [/duckduckbot/i] },\n { name: 'Baiduspider', category: 'search_engine', patterns: [/baiduspider/i] },\n { name: 'YandexBot', category: 'search_engine', patterns: [/yandexbot/i] },\n { name: 'Sogou', category: 'search_engine', patterns: [/sogou/i] },\n { name: 'Exabot', category: 'search_engine', patterns: [/exabot/i] },\n { name: 'ia_archiver', category: 'search_engine', patterns: [/ia_archiver/i] },\n]\n"],"mappings":"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","names":["import_jsx_runtime"]}