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,153 @@
1
+ "use client";
2
+
3
+ // src/dashboard/ui/pages/BotManagementPage.tsx
4
+ import { useState } from "react";
5
+
6
+ // src/dashboard/ui/components/Card.tsx
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ function Card({ title, action, children }) {
9
+ return /* @__PURE__ */ jsxs("div", { className: "ta-card ta-section", children: [
10
+ /* @__PURE__ */ jsxs("div", { className: "ta-card-header", children: [
11
+ /* @__PURE__ */ jsx("h2", { children: title }),
12
+ action
13
+ ] }),
14
+ /* @__PURE__ */ jsx("div", { className: "ta-card-body", children })
15
+ ] });
16
+ }
17
+
18
+ // src/detection/known-patterns.ts
19
+ var KNOWN_BOTS = [
20
+ // AI Crawlers
21
+ { name: "ClaudeBot", category: "ai_crawler", patterns: [/claudebot/i, /claude-web/i] },
22
+ { name: "GPTBot", category: "ai_crawler", patterns: [/gptbot/i] },
23
+ { name: "ChatGPT-User", category: "ai_crawler", patterns: [/chatgpt-user/i] },
24
+ { name: "PerplexityBot", category: "ai_crawler", patterns: [/perplexitybot/i] },
25
+ { name: "Googlebot-AI", category: "ai_crawler", patterns: [/google-extended/i, /googleother/i] },
26
+ { name: "FacebookBot", category: "ai_crawler", patterns: [/facebookbot/i] },
27
+ { name: "Applebot-Extended", category: "ai_crawler", patterns: [/applebot-extended/i] },
28
+ { name: "YouBot", category: "ai_crawler", patterns: [/youbot/i] },
29
+ { name: "CCBot", category: "ai_crawler", patterns: [/ccbot/i] },
30
+ { name: "CohereCrawler", category: "ai_crawler", patterns: [/cohere-ai/i] },
31
+ { name: "AI2Bot", category: "ai_crawler", patterns: [/ai2bot/i] },
32
+ { name: "Bytespider", category: "ai_crawler", patterns: [/bytespider/i] },
33
+ { name: "Diffbot", category: "ai_crawler", patterns: [/diffbot/i] },
34
+ // Search Engines
35
+ { name: "Googlebot", category: "search_engine", patterns: [/googlebot/i] },
36
+ { name: "Bingbot", category: "search_engine", patterns: [/bingbot/i, /msnbot/i] },
37
+ { name: "DuckDuckBot", category: "search_engine", patterns: [/duckduckbot/i] },
38
+ { name: "Baiduspider", category: "search_engine", patterns: [/baiduspider/i] },
39
+ { name: "YandexBot", category: "search_engine", patterns: [/yandexbot/i] },
40
+ { name: "Sogou", category: "search_engine", patterns: [/sogou/i] },
41
+ { name: "Exabot", category: "search_engine", patterns: [/exabot/i] },
42
+ { name: "ia_archiver", category: "search_engine", patterns: [/ia_archiver/i] }
43
+ ];
44
+
45
+ // src/dashboard/ui/pages/BotManagementPage.tsx
46
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
47
+ function BotManagementPage({ config }) {
48
+ const [allowlist, setAllowlist] = useState(config.allowlist);
49
+ const [blocklist, setBlocklist] = useState(config.blocklist);
50
+ const [trackUnknown, setTrackUnknown] = useState(config.track_unknown);
51
+ const [newAllow, setNewAllow] = useState("");
52
+ const [newBlock, setNewBlock] = useState("");
53
+ const [saving, setSaving] = useState(false);
54
+ const [saved, setSaved] = useState(false);
55
+ async function save() {
56
+ setSaving(true);
57
+ await fetch("/api/third-audience/bots-config", {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({ allowlist, blocklist, track_unknown: trackUnknown })
61
+ });
62
+ setSaving(false);
63
+ setSaved(true);
64
+ setTimeout(() => setSaved(false), 2e3);
65
+ }
66
+ const AI_CRAWLERS = KNOWN_BOTS.filter((b) => b.category === "ai_crawler");
67
+ const SEARCH_ENGINES = KNOWN_BOTS.filter((b) => b.category === "search_engine");
68
+ return /* @__PURE__ */ jsxs2("div", { children: [
69
+ /* @__PURE__ */ jsx2("h1", { className: "ta-page-title", children: "Bot Management" }),
70
+ /* @__PURE__ */ jsx2("p", { className: "ta-page-subtitle", children: "Configure which bots to track or block" }),
71
+ /* @__PURE__ */ jsx2(Card, { title: "Known AI Crawlers", children: /* @__PURE__ */ jsxs2("table", { className: "ta-table", children: [
72
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
73
+ /* @__PURE__ */ jsx2("th", { children: "Bot" }),
74
+ /* @__PURE__ */ jsx2("th", { children: "User-Agent Pattern" }),
75
+ /* @__PURE__ */ jsx2("th", { children: "Category" })
76
+ ] }) }),
77
+ /* @__PURE__ */ jsxs2("tbody", { children: [
78
+ AI_CRAWLERS.map((b) => /* @__PURE__ */ jsxs2("tr", { children: [
79
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("strong", { children: b.name }) }),
80
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("code", { children: b.patterns[0].source }) }),
81
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("span", { className: "ta-badge ta-badge--blue", children: "AI Crawler" }) })
82
+ ] }, b.name)),
83
+ SEARCH_ENGINES.map((b) => /* @__PURE__ */ jsxs2("tr", { children: [
84
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("strong", { children: b.name }) }),
85
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("code", { children: b.patterns[0].source }) }),
86
+ /* @__PURE__ */ jsx2("td", { children: /* @__PURE__ */ jsx2("span", { className: "ta-badge ta-badge--gray", children: "Search Engine" }) })
87
+ ] }, b.name))
88
+ ] })
89
+ ] }) }),
90
+ /* @__PURE__ */ jsx2(Card, { title: "Allowlist", action: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
91
+ /* @__PURE__ */ jsx2(
92
+ "input",
93
+ {
94
+ value: newAllow,
95
+ onChange: (e) => setNewAllow(e.target.value),
96
+ placeholder: "Bot name or UA pattern",
97
+ style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--ta-gray-200)", fontSize: 13, width: 200 }
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx2("button", { className: "ta-btn ta-btn--primary", onClick: () => {
101
+ if (newAllow) {
102
+ setAllowlist((l) => [...l, newAllow]);
103
+ setNewAllow("");
104
+ }
105
+ }, children: "Add" })
106
+ ] }), children: allowlist.length === 0 ? /* @__PURE__ */ jsx2("p", { style: { color: "var(--ta-gray-500)", fontSize: 13 }, children: "No overrides. All detected bots are tracked." }) : /* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", display: "flex", flexWrap: "wrap", gap: 8 }, children: allowlist.map((name) => /* @__PURE__ */ jsxs2("li", { style: { display: "flex", alignItems: "center", gap: 6, background: "rgba(52,199,89,0.1)", borderRadius: 6, padding: "4px 10px", fontSize: 13 }, children: [
107
+ /* @__PURE__ */ jsx2("span", { className: "ta-dot ta-dot--green" }),
108
+ name,
109
+ /* @__PURE__ */ jsx2("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" })
110
+ ] }, name)) }) }),
111
+ /* @__PURE__ */ jsx2(Card, { title: "Blocklist", action: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
112
+ /* @__PURE__ */ jsx2(
113
+ "input",
114
+ {
115
+ value: newBlock,
116
+ onChange: (e) => setNewBlock(e.target.value),
117
+ placeholder: "Bot name or UA pattern",
118
+ style: { padding: "6px 10px", borderRadius: 6, border: "1px solid var(--ta-gray-200)", fontSize: 13, width: 200 }
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx2("button", { className: "ta-btn ta-btn--danger", onClick: () => {
122
+ if (newBlock) {
123
+ setBlocklist((l) => [...l, newBlock]);
124
+ setNewBlock("");
125
+ }
126
+ }, children: "Block" })
127
+ ] }), children: blocklist.length === 0 ? /* @__PURE__ */ jsx2("p", { style: { color: "var(--ta-gray-500)", fontSize: 13 }, children: "No blocked bots." }) : /* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", display: "flex", flexWrap: "wrap", gap: 8 }, children: blocklist.map((name) => /* @__PURE__ */ jsxs2("li", { style: { display: "flex", alignItems: "center", gap: 6, background: "rgba(255,59,48,0.1)", borderRadius: 6, padding: "4px 10px", fontSize: 13 }, children: [
128
+ /* @__PURE__ */ jsx2("span", { className: "ta-dot ta-dot--red" }),
129
+ name,
130
+ /* @__PURE__ */ jsx2("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" })
131
+ ] }, name)) }) }),
132
+ /* @__PURE__ */ jsx2(Card, { title: "Unknown Bots", children: /* @__PURE__ */ jsxs2("label", { style: { display: "flex", alignItems: "center", gap: 10, cursor: "pointer" }, children: [
133
+ /* @__PURE__ */ jsx2(
134
+ "input",
135
+ {
136
+ type: "checkbox",
137
+ checked: trackUnknown,
138
+ onChange: (e) => setTrackUnknown(e.target.checked),
139
+ style: { width: 16, height: 16 }
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsx2("span", { children: "Track unknown bots (bots detected by heuristics, not in the known list)" })
143
+ ] }) }),
144
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
145
+ /* @__PURE__ */ jsx2("button", { className: "ta-btn ta-btn--primary", onClick: save, disabled: saving, children: saving ? "Saving\u2026" : "Save Configuration" }),
146
+ saved && /* @__PURE__ */ jsx2("span", { style: { color: "var(--ta-green)", fontSize: 13 }, children: "\u2713 Saved" })
147
+ ] })
148
+ ] });
149
+ }
150
+ export {
151
+ BotManagementPage
152
+ };
153
+ //# sourceMappingURL=BotManagementPage.mjs.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":["jsx","jsxs"]}
@@ -0,0 +1,5 @@
1
+ import * as react from 'react';
2
+
3
+ declare function LlmTrafficPage(): Promise<react.JSX.Element>;
4
+
5
+ export { LlmTrafficPage };
@@ -0,0 +1,5 @@
1
+ import * as react from 'react';
2
+
3
+ declare function LlmTrafficPage(): Promise<react.JSX.Element>;
4
+
5
+ export { LlmTrafficPage };
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/dashboard/ui/pages/LlmTrafficPage.tsx
31
+ var LlmTrafficPage_exports = {};
32
+ __export(LlmTrafficPage_exports, {
33
+ LlmTrafficPage: () => LlmTrafficPage
34
+ });
35
+ module.exports = __toCommonJS(LlmTrafficPage_exports);
36
+ var import_fs = __toESM(require("fs"));
37
+ var import_path = __toESM(require("path"));
38
+
39
+ // src/dashboard/ui/components/Card.tsx
40
+ var import_jsx_runtime = require("react/jsx-runtime");
41
+ function Card({ title, action, children }) {
42
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ta-card ta-section", children: [
43
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ta-card-header", children: [
44
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: title }),
45
+ action
46
+ ] }),
47
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ta-card-body", children })
48
+ ] });
49
+ }
50
+
51
+ // src/dashboard/ui/components/HeroCard.tsx
52
+ var import_jsx_runtime2 = require("react/jsx-runtime");
53
+ function HeroCard({ label, value, meta, color = "blue", icon }) {
54
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `ta-hero-card ta-hero-card--${color}`, children: [
55
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ta-hero-icon", children: icon }),
56
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
57
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ta-hero-label", children: label }),
58
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ta-hero-value", children: value }),
59
+ meta && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ta-hero-meta", children: meta })
60
+ ] })
61
+ ] });
62
+ }
63
+
64
+ // src/dashboard/ui/pages/LlmTrafficPage.tsx
65
+ var import_jsx_runtime3 = require("react/jsx-runtime");
66
+ var PLATFORM_COLORS = {
67
+ ChatGPT: "green",
68
+ Perplexity: "blue",
69
+ Claude: "orange",
70
+ Gemini: "teal",
71
+ Copilot: "blue",
72
+ default: "gray"
73
+ };
74
+ function loadCitations(days = 30) {
75
+ const dataDir = process.env.TA_DATA_DIR ?? "data";
76
+ const filePath = import_path.default.join(process.cwd(), dataDir, "ta-citations.jsonl");
77
+ if (!import_fs.default.existsSync(filePath)) return [];
78
+ const cutoff = /* @__PURE__ */ new Date();
79
+ cutoff.setDate(cutoff.getDate() - days);
80
+ const cutoffStr = cutoff.toISOString();
81
+ return import_fs.default.readFileSync(filePath, "utf-8").split("\n").filter(Boolean).map((l) => {
82
+ try {
83
+ return JSON.parse(l);
84
+ } catch {
85
+ return null;
86
+ }
87
+ }).filter((r) => r !== null && r.timestamp >= cutoffStr);
88
+ }
89
+ function groupBy(arr, key) {
90
+ const map = /* @__PURE__ */ new Map();
91
+ for (const item of arr) {
92
+ const k = key(item);
93
+ map.set(k, (map.get(k) ?? 0) + 1);
94
+ }
95
+ return [...map.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
96
+ }
97
+ async function LlmTrafficPage() {
98
+ const records = loadCitations(30);
99
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
100
+ const todayCount = records.filter((r) => r.timestamp.startsWith(today)).length;
101
+ const byPlatform = groupBy(records, (r) => r.platform);
102
+ const byPage = groupBy(records, (r) => r.url);
103
+ const byQuery = groupBy(records.filter((r) => r.query), (r) => r.query).slice(0, 10);
104
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
105
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "ta-page-title", children: "LLM Traffic" }),
106
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "ta-page-subtitle", children: "Citation clicks from AI platforms (last 30 days)" }),
107
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ta-hero-grid", children: [
108
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
109
+ HeroCard,
110
+ {
111
+ label: "Total Citations",
112
+ value: records.length.toLocaleString(),
113
+ meta: `${todayCount} today`,
114
+ color: "blue",
115
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })
116
+ }
117
+ ),
118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
119
+ HeroCard,
120
+ {
121
+ label: "AI Platforms",
122
+ value: byPlatform.length,
123
+ meta: "distinct sources",
124
+ color: "green",
125
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
126
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })
129
+ ] })
130
+ }
131
+ ),
132
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
133
+ HeroCard,
134
+ {
135
+ label: "Pages Cited",
136
+ value: byPage.length,
137
+ meta: "unique URLs cited",
138
+ color: "orange",
139
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
140
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "14 2 14 8 20 8" })
142
+ ] })
143
+ }
144
+ ),
145
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
146
+ HeroCard,
147
+ {
148
+ label: "Top Platform",
149
+ value: byPlatform[0]?.name ?? "\u2014",
150
+ meta: byPlatform[0] ? `${byPlatform[0].count} citations` : "no data yet",
151
+ color: "teal",
152
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) })
153
+ }
154
+ )
155
+ ] }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ta-grid-2", children: [
157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Card, { title: "By Platform", children: byPlatform.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ta-empty", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "No citation data yet. Make sure citation-tracker.js is included in your layout." }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "ta-table", children: [
158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Platform" }),
160
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Citations" }),
161
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Share" })
162
+ ] }) }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: byPlatform.map((p) => {
164
+ const pct = records.length > 0 ? (p.count / records.length * 100).toFixed(1) : "0";
165
+ const color = PLATFORM_COLORS[p.name] ?? PLATFORM_COLORS.default;
166
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `ta-badge ta-badge--${color}`, children: p.name }) }),
168
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: p.count }) }),
169
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("td", { style: { fontSize: 12, color: "var(--ta-gray-600)" }, children: [
170
+ pct,
171
+ "%"
172
+ ] })
173
+ ] }, p.name);
174
+ }) })
175
+ ] }) }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Card, { title: "Top Queries", children: byQuery.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ta-empty", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "No query data yet. Queries are captured when AI platforms include a search term in the referrer URL." }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "ta-table", children: [
177
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
178
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Query" }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Citations" })
180
+ ] }) }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: byQuery.map((q) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
182
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { style: { fontFamily: "var(--ta-font-mono)", fontSize: 12 }, children: q.name }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: q.count }) })
184
+ ] }, q.name)) })
185
+ ] }) })
186
+ ] }),
187
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Card, { title: "Top Cited Pages", children: byPage.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ta-empty", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: "No pages cited yet." }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "ta-table", children: [
188
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
189
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Page" }),
190
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { children: "Citations" })
191
+ ] }) }),
192
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: byPage.slice(0, 20).map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
193
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("a", { href: p.name, target: "_blank", rel: "noreferrer", style: { fontFamily: "var(--ta-font-mono)", fontSize: 12 }, children: p.name }) }),
194
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: p.count }) })
195
+ ] }, p.name)) })
196
+ ] }) })
197
+ ] });
198
+ }
199
+ // Annotate the CommonJS export names for ESM import in node:
200
+ 0 && (module.exports = {
201
+ LlmTrafficPage
202
+ });
203
+ //# sourceMappingURL=LlmTrafficPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/dashboard/ui/pages/LlmTrafficPage.tsx","../../../../src/dashboard/ui/components/Card.tsx","../../../../src/dashboard/ui/components/HeroCard.tsx"],"sourcesContent":["import fs from 'fs'\nimport path from 'path'\nimport { Card } from '../components/Card.js'\nimport { HeroCard } from '../components/HeroCard.js'\nimport type { CitationRecord } from '../../../citations/citation-tracker.js'\n\nconst PLATFORM_COLORS: Record<string, string> = {\n ChatGPT: 'green', Perplexity: 'blue', Claude: 'orange',\n Gemini: 'teal', Copilot: 'blue', default: 'gray',\n}\n\nfunction loadCitations(days = 30): CitationRecord[] {\n const dataDir = process.env.TA_DATA_DIR ?? 'data'\n const filePath = path.join(process.cwd(), dataDir, 'ta-citations.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').filter(Boolean)\n .map(l => { try { return JSON.parse(l) as CitationRecord } catch { return null } })\n .filter((r): r is CitationRecord => r !== null && r.timestamp >= cutoffStr)\n}\n\nfunction groupBy<T>(arr: T[], key: (item: T) => string): Array<{ name: string; count: number }> {\n const map = new Map<string, number>()\n for (const item of arr) {\n const k = key(item)\n map.set(k, (map.get(k) ?? 0) + 1)\n }\n return [...map.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, count]) => ({ name, count }))\n}\n\nexport async function LlmTrafficPage() {\n const records = loadCitations(30)\n\n const today = new Date().toISOString().slice(0, 10)\n const todayCount = records.filter(r => r.timestamp.startsWith(today)).length\n\n const byPlatform = groupBy(records, r => r.platform)\n const byPage = groupBy(records, r => r.url)\n const byQuery = groupBy(records.filter(r => r.query), r => r.query!).slice(0, 10)\n\n return (\n <div>\n <h1 className=\"ta-page-title\">LLM Traffic</h1>\n <p className=\"ta-page-subtitle\">Citation clicks from AI platforms (last 30 days)</p>\n\n <div className=\"ta-hero-grid\">\n <HeroCard\n label=\"Total Citations\"\n value={records.length.toLocaleString()}\n meta={`${todayCount} today`}\n color=\"blue\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>}\n />\n <HeroCard\n label=\"AI Platforms\"\n value={byPlatform.length}\n meta=\"distinct sources\"\n color=\"green\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"/><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"/></svg>}\n />\n <HeroCard\n label=\"Pages Cited\"\n value={byPage.length}\n meta=\"unique URLs cited\"\n color=\"orange\"\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=\"Top Platform\"\n value={byPlatform[0]?.name ?? '—'}\n meta={byPlatform[0] ? `${byPlatform[0].count} citations` : 'no data yet'}\n color=\"teal\"\n icon={<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"/></svg>}\n />\n </div>\n\n <div className=\"ta-grid-2\">\n <Card title=\"By Platform\">\n {byPlatform.length === 0 ? (\n <div className=\"ta-empty\"><p>No citation data yet. Make sure citation-tracker.js is included in your layout.</p></div>\n ) : (\n <table className=\"ta-table\">\n <thead><tr><th>Platform</th><th>Citations</th><th>Share</th></tr></thead>\n <tbody>\n {byPlatform.map(p => {\n const pct = records.length > 0 ? ((p.count / records.length) * 100).toFixed(1) : '0'\n const color = PLATFORM_COLORS[p.name] ?? PLATFORM_COLORS.default\n return (\n <tr key={p.name}>\n <td><span className={`ta-badge ta-badge--${color}`}>{p.name}</span></td>\n <td><strong>{p.count}</strong></td>\n <td style={{ fontSize: 12, color: 'var(--ta-gray-600)' }}>{pct}%</td>\n </tr>\n )\n })}\n </tbody>\n </table>\n )}\n </Card>\n\n <Card title=\"Top Queries\">\n {byQuery.length === 0 ? (\n <div className=\"ta-empty\"><p>No query data yet. Queries are captured when AI platforms include a search term in the referrer URL.</p></div>\n ) : (\n <table className=\"ta-table\">\n <thead><tr><th>Query</th><th>Citations</th></tr></thead>\n <tbody>\n {byQuery.map(q => (\n <tr key={q.name}>\n <td style={{ fontFamily: 'var(--ta-font-mono)', fontSize: 12 }}>{q.name}</td>\n <td><strong>{q.count}</strong></td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </Card>\n </div>\n\n <Card title=\"Top Cited Pages\">\n {byPage.length === 0 ? (\n <div className=\"ta-empty\"><p>No pages cited yet.</p></div>\n ) : (\n <table className=\"ta-table\">\n <thead><tr><th>Page</th><th>Citations</th></tr></thead>\n <tbody>\n {byPage.slice(0, 20).map(p => (\n <tr key={p.name}>\n <td>\n <a href={p.name} target=\"_blank\" rel=\"noreferrer\" style={{ fontFamily: 'var(--ta-font-mono)', fontSize: 12 }}>\n {p.name}\n </a>\n </td>\n <td><strong>{p.count}</strong></td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </Card>\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","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"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","path","fs"]}
@@ -0,0 +1,168 @@
1
+ // src/dashboard/ui/pages/LlmTrafficPage.tsx
2
+ import fs from "fs";
3
+ import path from "path";
4
+
5
+ // src/dashboard/ui/components/Card.tsx
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function Card({ title, action, children }) {
8
+ return /* @__PURE__ */ jsxs("div", { className: "ta-card ta-section", children: [
9
+ /* @__PURE__ */ jsxs("div", { className: "ta-card-header", children: [
10
+ /* @__PURE__ */ jsx("h2", { children: title }),
11
+ action
12
+ ] }),
13
+ /* @__PURE__ */ jsx("div", { className: "ta-card-body", children })
14
+ ] });
15
+ }
16
+
17
+ // src/dashboard/ui/components/HeroCard.tsx
18
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
19
+ function HeroCard({ label, value, meta, color = "blue", icon }) {
20
+ return /* @__PURE__ */ jsxs2("div", { className: `ta-hero-card ta-hero-card--${color}`, children: [
21
+ /* @__PURE__ */ jsx2("div", { className: "ta-hero-icon", children: icon }),
22
+ /* @__PURE__ */ jsxs2("div", { children: [
23
+ /* @__PURE__ */ jsx2("div", { className: "ta-hero-label", children: label }),
24
+ /* @__PURE__ */ jsx2("div", { className: "ta-hero-value", children: value }),
25
+ meta && /* @__PURE__ */ jsx2("div", { className: "ta-hero-meta", children: meta })
26
+ ] })
27
+ ] });
28
+ }
29
+
30
+ // src/dashboard/ui/pages/LlmTrafficPage.tsx
31
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
32
+ var PLATFORM_COLORS = {
33
+ ChatGPT: "green",
34
+ Perplexity: "blue",
35
+ Claude: "orange",
36
+ Gemini: "teal",
37
+ Copilot: "blue",
38
+ default: "gray"
39
+ };
40
+ function loadCitations(days = 30) {
41
+ const dataDir = process.env.TA_DATA_DIR ?? "data";
42
+ const filePath = path.join(process.cwd(), dataDir, "ta-citations.jsonl");
43
+ if (!fs.existsSync(filePath)) return [];
44
+ const cutoff = /* @__PURE__ */ new Date();
45
+ cutoff.setDate(cutoff.getDate() - days);
46
+ const cutoffStr = cutoff.toISOString();
47
+ return fs.readFileSync(filePath, "utf-8").split("\n").filter(Boolean).map((l) => {
48
+ try {
49
+ return JSON.parse(l);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }).filter((r) => r !== null && r.timestamp >= cutoffStr);
54
+ }
55
+ function groupBy(arr, key) {
56
+ const map = /* @__PURE__ */ new Map();
57
+ for (const item of arr) {
58
+ const k = key(item);
59
+ map.set(k, (map.get(k) ?? 0) + 1);
60
+ }
61
+ return [...map.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
62
+ }
63
+ async function LlmTrafficPage() {
64
+ const records = loadCitations(30);
65
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
66
+ const todayCount = records.filter((r) => r.timestamp.startsWith(today)).length;
67
+ const byPlatform = groupBy(records, (r) => r.platform);
68
+ const byPage = groupBy(records, (r) => r.url);
69
+ const byQuery = groupBy(records.filter((r) => r.query), (r) => r.query).slice(0, 10);
70
+ return /* @__PURE__ */ jsxs3("div", { children: [
71
+ /* @__PURE__ */ jsx3("h1", { className: "ta-page-title", children: "LLM Traffic" }),
72
+ /* @__PURE__ */ jsx3("p", { className: "ta-page-subtitle", children: "Citation clicks from AI platforms (last 30 days)" }),
73
+ /* @__PURE__ */ jsxs3("div", { className: "ta-hero-grid", children: [
74
+ /* @__PURE__ */ jsx3(
75
+ HeroCard,
76
+ {
77
+ label: "Total Citations",
78
+ value: records.length.toLocaleString(),
79
+ meta: `${todayCount} today`,
80
+ color: "blue",
81
+ icon: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx3(
85
+ HeroCard,
86
+ {
87
+ label: "AI Platforms",
88
+ value: byPlatform.length,
89
+ meta: "distinct sources",
90
+ color: "green",
91
+ icon: /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
92
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "10" }),
93
+ /* @__PURE__ */ jsx3("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
94
+ /* @__PURE__ */ jsx3("path", { d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })
95
+ ] })
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsx3(
99
+ HeroCard,
100
+ {
101
+ label: "Pages Cited",
102
+ value: byPage.length,
103
+ meta: "unique URLs cited",
104
+ color: "orange",
105
+ icon: /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
106
+ /* @__PURE__ */ jsx3("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
107
+ /* @__PURE__ */ jsx3("polyline", { points: "14 2 14 8 20 8" })
108
+ ] })
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx3(
112
+ HeroCard,
113
+ {
114
+ label: "Top Platform",
115
+ value: byPlatform[0]?.name ?? "\u2014",
116
+ meta: byPlatform[0] ? `${byPlatform[0].count} citations` : "no data yet",
117
+ color: "teal",
118
+ icon: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx3("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }) })
119
+ }
120
+ )
121
+ ] }),
122
+ /* @__PURE__ */ jsxs3("div", { className: "ta-grid-2", children: [
123
+ /* @__PURE__ */ jsx3(Card, { title: "By Platform", children: byPlatform.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "ta-empty", children: /* @__PURE__ */ jsx3("p", { children: "No citation data yet. Make sure citation-tracker.js is included in your layout." }) }) : /* @__PURE__ */ jsxs3("table", { className: "ta-table", children: [
124
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
125
+ /* @__PURE__ */ jsx3("th", { children: "Platform" }),
126
+ /* @__PURE__ */ jsx3("th", { children: "Citations" }),
127
+ /* @__PURE__ */ jsx3("th", { children: "Share" })
128
+ ] }) }),
129
+ /* @__PURE__ */ jsx3("tbody", { children: byPlatform.map((p) => {
130
+ const pct = records.length > 0 ? (p.count / records.length * 100).toFixed(1) : "0";
131
+ const color = PLATFORM_COLORS[p.name] ?? PLATFORM_COLORS.default;
132
+ return /* @__PURE__ */ jsxs3("tr", { children: [
133
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsx3("span", { className: `ta-badge ta-badge--${color}`, children: p.name }) }),
134
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsx3("strong", { children: p.count }) }),
135
+ /* @__PURE__ */ jsxs3("td", { style: { fontSize: 12, color: "var(--ta-gray-600)" }, children: [
136
+ pct,
137
+ "%"
138
+ ] })
139
+ ] }, p.name);
140
+ }) })
141
+ ] }) }),
142
+ /* @__PURE__ */ jsx3(Card, { title: "Top Queries", children: byQuery.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "ta-empty", children: /* @__PURE__ */ jsx3("p", { children: "No query data yet. Queries are captured when AI platforms include a search term in the referrer URL." }) }) : /* @__PURE__ */ jsxs3("table", { className: "ta-table", children: [
143
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
144
+ /* @__PURE__ */ jsx3("th", { children: "Query" }),
145
+ /* @__PURE__ */ jsx3("th", { children: "Citations" })
146
+ ] }) }),
147
+ /* @__PURE__ */ jsx3("tbody", { children: byQuery.map((q) => /* @__PURE__ */ jsxs3("tr", { children: [
148
+ /* @__PURE__ */ jsx3("td", { style: { fontFamily: "var(--ta-font-mono)", fontSize: 12 }, children: q.name }),
149
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsx3("strong", { children: q.count }) })
150
+ ] }, q.name)) })
151
+ ] }) })
152
+ ] }),
153
+ /* @__PURE__ */ jsx3(Card, { title: "Top Cited Pages", children: byPage.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "ta-empty", children: /* @__PURE__ */ jsx3("p", { children: "No pages cited yet." }) }) : /* @__PURE__ */ jsxs3("table", { className: "ta-table", children: [
154
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
155
+ /* @__PURE__ */ jsx3("th", { children: "Page" }),
156
+ /* @__PURE__ */ jsx3("th", { children: "Citations" })
157
+ ] }) }),
158
+ /* @__PURE__ */ jsx3("tbody", { children: byPage.slice(0, 20).map((p) => /* @__PURE__ */ jsxs3("tr", { children: [
159
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsx3("a", { href: p.name, target: "_blank", rel: "noreferrer", style: { fontFamily: "var(--ta-font-mono)", fontSize: 12 }, children: p.name }) }),
160
+ /* @__PURE__ */ jsx3("td", { children: /* @__PURE__ */ jsx3("strong", { children: p.count }) })
161
+ ] }, p.name)) })
162
+ ] }) })
163
+ ] });
164
+ }
165
+ export {
166
+ LlmTrafficPage
167
+ };
168
+ //# sourceMappingURL=LlmTrafficPage.mjs.map