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.
- package/CLAUDE.md +41 -0
- package/INSTALLATION.md +367 -0
- package/README.md +303 -0
- package/WORKLOG.md +162 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +185 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/dashboard/auth.d.mts +16 -0
- package/dist/dashboard/auth.d.ts +16 -0
- package/dist/dashboard/auth.js +123 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/auth.mjs +87 -0
- package/dist/dashboard/auth.mjs.map +1 -0
- package/dist/dashboard/routes/analytics-api-route.d.mts +6 -0
- package/dist/dashboard/routes/analytics-api-route.d.ts +6 -0
- package/dist/dashboard/routes/analytics-api-route.js +180 -0
- package/dist/dashboard/routes/analytics-api-route.js.map +1 -0
- package/dist/dashboard/routes/analytics-api-route.mjs +145 -0
- package/dist/dashboard/routes/analytics-api-route.mjs.map +1 -0
- package/dist/dashboard/routes/api-key-route.d.mts +8 -0
- package/dist/dashboard/routes/api-key-route.d.ts +8 -0
- package/dist/dashboard/routes/api-key-route.js +173 -0
- package/dist/dashboard/routes/api-key-route.js.map +1 -0
- package/dist/dashboard/routes/api-key-route.mjs +137 -0
- package/dist/dashboard/routes/api-key-route.mjs.map +1 -0
- package/dist/dashboard/routes/citation-route.d.mts +14 -0
- package/dist/dashboard/routes/citation-route.d.ts +14 -0
- package/dist/dashboard/routes/citation-route.js +202 -0
- package/dist/dashboard/routes/citation-route.js.map +1 -0
- package/dist/dashboard/routes/citation-route.mjs +166 -0
- package/dist/dashboard/routes/citation-route.mjs.map +1 -0
- package/dist/dashboard/routes/llms-txt-route.d.mts +6 -0
- package/dist/dashboard/routes/llms-txt-route.d.ts +6 -0
- package/dist/dashboard/routes/llms-txt-route.js +119 -0
- package/dist/dashboard/routes/llms-txt-route.js.map +1 -0
- package/dist/dashboard/routes/llms-txt-route.mjs +84 -0
- package/dist/dashboard/routes/llms-txt-route.mjs.map +1 -0
- package/dist/dashboard/routes/login-route.d.mts +6 -0
- package/dist/dashboard/routes/login-route.d.ts +6 -0
- package/dist/dashboard/routes/login-route.js +313 -0
- package/dist/dashboard/routes/login-route.js.map +1 -0
- package/dist/dashboard/routes/login-route.mjs +284 -0
- package/dist/dashboard/routes/login-route.mjs.map +1 -0
- package/dist/dashboard/routes/markdown-route.d.mts +15 -0
- package/dist/dashboard/routes/markdown-route.d.ts +15 -0
- package/dist/dashboard/routes/markdown-route.js +239 -0
- package/dist/dashboard/routes/markdown-route.js.map +1 -0
- package/dist/dashboard/routes/markdown-route.mjs +204 -0
- package/dist/dashboard/routes/markdown-route.mjs.map +1 -0
- package/dist/dashboard/routes/okf-route.d.mts +13 -0
- package/dist/dashboard/routes/okf-route.d.ts +13 -0
- package/dist/dashboard/routes/okf-route.js +184 -0
- package/dist/dashboard/routes/okf-route.js.map +1 -0
- package/dist/dashboard/routes/okf-route.mjs +149 -0
- package/dist/dashboard/routes/okf-route.mjs.map +1 -0
- package/dist/dashboard/routes/sitemap-ai-route.d.mts +6 -0
- package/dist/dashboard/routes/sitemap-ai-route.d.ts +6 -0
- package/dist/dashboard/routes/sitemap-ai-route.js +134 -0
- package/dist/dashboard/routes/sitemap-ai-route.js.map +1 -0
- package/dist/dashboard/routes/sitemap-ai-route.mjs +99 -0
- package/dist/dashboard/routes/sitemap-ai-route.mjs.map +1 -0
- package/dist/dashboard/ui/components/Sidebar.d.mts +5 -0
- package/dist/dashboard/ui/components/Sidebar.d.ts +5 -0
- package/dist/dashboard/ui/components/Sidebar.js +102 -0
- package/dist/dashboard/ui/components/Sidebar.js.map +1 -0
- package/dist/dashboard/ui/components/Sidebar.mjs +68 -0
- package/dist/dashboard/ui/components/Sidebar.mjs.map +1 -0
- package/dist/dashboard/ui/globals.css +175 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.js +269 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.js.map +1 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs +232 -0
- package/dist/dashboard/ui/pages/BotAnalyticsPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/BotManagementPage.d.mts +13 -0
- package/dist/dashboard/ui/pages/BotManagementPage.d.ts +13 -0
- package/dist/dashboard/ui/pages/BotManagementPage.js +177 -0
- package/dist/dashboard/ui/pages/BotManagementPage.js.map +1 -0
- package/dist/dashboard/ui/pages/BotManagementPage.mjs +153 -0
- package/dist/dashboard/ui/pages/BotManagementPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.js +203 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.js.map +1 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.mjs +168 -0
- package/dist/dashboard/ui/pages/LlmTrafficPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/SettingsPage.d.mts +8 -0
- package/dist/dashboard/ui/pages/SettingsPage.d.ts +8 -0
- package/dist/dashboard/ui/pages/SettingsPage.js +181 -0
- package/dist/dashboard/ui/pages/SettingsPage.js.map +1 -0
- package/dist/dashboard/ui/pages/SettingsPage.mjs +157 -0
- package/dist/dashboard/ui/pages/SettingsPage.mjs.map +1 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.d.mts +5 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.d.ts +5 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.js +183 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.js.map +1 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.mjs +148 -0
- package/dist/dashboard/ui/pages/SystemHealthPage.mjs.map +1 -0
- package/dist/index.d.mts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +372 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +346 -0
- package/dist/index.mjs.map +1 -0
- 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,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
|