@goldensheepai/toknxr-cli 0.2.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/.env +21 -0
- package/.env.example +21 -0
- package/README.md +238 -0
- package/interactions.log +8 -0
- package/lib/ai-analytics.js +296 -0
- package/lib/auth.js +73 -0
- package/lib/cli.js +382 -0
- package/lib/code-analysis.js +304 -0
- package/lib/code-review.js +319 -0
- package/lib/config.js +7 -0
- package/lib/dashboard.js +363 -0
- package/lib/hallucination-detector.js +272 -0
- package/lib/policy.js +49 -0
- package/lib/pricing.js +20 -0
- package/lib/proxy.js +359 -0
- package/lib/sync.js +95 -0
- package/package.json +38 -0
- package/src/ai-analytics.ts +418 -0
- package/src/auth.ts +80 -0
- package/src/cli.ts +447 -0
- package/src/code-analysis.ts +365 -0
- package/src/config.ts +10 -0
- package/src/dashboard.tsx +391 -0
- package/src/hallucination-detector.ts +368 -0
- package/src/policy.ts +55 -0
- package/src/pricing.ts +21 -0
- package/src/proxy.ts +438 -0
- package/src/sync.ts +129 -0
- package/start.sh +56 -0
- package/test-analysis.mjs +77 -0
- package/test-coding.mjs +27 -0
- package/test-generate-sample-data.js +118 -0
- package/test-proxy.mjs +25 -0
- package/toknxr.config.json +63 -0
- package/toknxr.policy.json +18 -0
- package/tsconfig.json +19 -0
package/lib/dashboard.js
ADDED
@@ -0,0 +1,363 @@
|
|
1
|
+
// Simple vanilla JavaScript dashboard for browser compatibility
|
2
|
+
// This creates a pure HTML/CSS/JS dashboard that works without React
|
3
|
+
function createDashboard() {
|
4
|
+
const COLORS = {
|
5
|
+
primary: '#6B5BED',
|
6
|
+
secondary: '#9B5BED',
|
7
|
+
accent: '#ED5B9B',
|
8
|
+
success: '#10B981',
|
9
|
+
warning: '#F59E0B',
|
10
|
+
error: '#EF4444',
|
11
|
+
background: '#0F172A',
|
12
|
+
surface: '#1E293B',
|
13
|
+
text: '#F8FAFC',
|
14
|
+
textMuted: '#94A3B8'
|
15
|
+
};
|
16
|
+
let stats = null;
|
17
|
+
let lastUpdated = new Date();
|
18
|
+
// Create stat card HTML
|
19
|
+
function createStatCard(title, value, icon, color = COLORS.primary) {
|
20
|
+
return `
|
21
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${color}20; box-shadow: 0 4px 6px -1px ${color}10;">
|
22
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
23
|
+
<div>
|
24
|
+
<p style="color: ${COLORS.textMuted}; font-size: 14px; margin: 0 0 8px 0;">${title}</p>
|
25
|
+
<p style="font-size: 32px; font-weight: bold; margin: 0; color: ${COLORS.text};">${value}</p>
|
26
|
+
</div>
|
27
|
+
<div style="font-size: 24px; color: ${color}; opacity: 0.8;">${icon}</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
`;
|
31
|
+
}
|
32
|
+
// Create provider card HTML
|
33
|
+
function createProviderChart(name, data) {
|
34
|
+
return `
|
35
|
+
<div style="background: ${COLORS.surface}; border-radius: 8px; padding: 16px; border: 1px solid ${COLORS.primary}20;">
|
36
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
37
|
+
<h3 style="margin: 0; color: ${COLORS.text}; font-size: 16px;">${name}</h3>
|
38
|
+
<div style="font-size: 12px; color: ${COLORS.textMuted};">${(data.cost || 0).toFixed(2)}</div>
|
39
|
+
</div>
|
40
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 12px;">
|
41
|
+
<div style="color: ${COLORS.textMuted};">Interactions: <span style="color: ${COLORS.text};">${data.interactions || 0}</span></div>
|
42
|
+
${data.avgQuality ? `<div style="color: ${COLORS.textMuted};">Quality: <span style="color: ${COLORS.accent};">${data.avgQuality}/100</span></div>` : ''}
|
43
|
+
${data.avgEffectiveness ? `<div style="color: ${COLORS.textMuted};">Effectiveness: <span style="color: ${COLORS.secondary};">${data.avgEffectiveness}/100</span></div>` : ''}
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
`;
|
47
|
+
}
|
48
|
+
// Create recent activity HTML with prompt previews
|
49
|
+
function createRecentActivity(interactions) {
|
50
|
+
const recentHTML = interactions.slice(-10).map((interaction, i) => {
|
51
|
+
const qualityColor = !interaction.qualityScore ? COLORS.textMuted :
|
52
|
+
interaction.qualityScore >= 90 ? COLORS.success :
|
53
|
+
interaction.qualityScore >= 70 ? COLORS.warning : COLORS.error;
|
54
|
+
const effectivenessColor = !interaction.effectivenessScore ? COLORS.textMuted :
|
55
|
+
interaction.effectivenessScore >= 90 ? COLORS.success :
|
56
|
+
interaction.effectivenessScore >= 70 ? COLORS.warning : COLORS.error;
|
57
|
+
const promptPreview = interaction.userPrompt ?
|
58
|
+
(interaction.userPrompt.length > 60 ?
|
59
|
+
interaction.userPrompt.substring(0, 60) + '...' :
|
60
|
+
interaction.userPrompt) : 'No prompt captured';
|
61
|
+
return `
|
62
|
+
<div style="padding: 16px 0; border-bottom: ${i < interactions.length - 1 ? `1px solid ${COLORS.primary}10` : 'none'}; cursor: pointer; transition: background-color 0.2s;"
|
63
|
+
onmouseover="this.style.background='${COLORS.primary}08'"
|
64
|
+
onmouseout="this.style.background='transparent'"
|
65
|
+
onclick="showPromptDetails('${interaction.provider}', '${interaction.model}', \`${(interaction.userPrompt || 'N/A').replace(/'/g, "\\'")}\`, \`${(interaction.aiResponse || 'N/A').substring(0, 300).replace(/'/g, "\\'")}\`, ${interaction.qualityScore || 0}, ${interaction.effectivenessScore || 0}, '${interaction.taskType || 'unknown'}', ${interaction.cost || 0})">
|
66
|
+
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;">
|
67
|
+
<div style="flex: 1;">
|
68
|
+
<div style="color: ${COLORS.text}; font-size: 14px; font-weight: 500; margin-bottom: 4px;">
|
69
|
+
${interaction.provider} • ${interaction.model}
|
70
|
+
</div>
|
71
|
+
<div style="color: ${COLORS.textMuted}; font-size: 12px; margin-bottom: 6px;">
|
72
|
+
${new Date(interaction.timestamp).toLocaleTimeString()}
|
73
|
+
${interaction.taskType ? ` • <span style="color: ${COLORS.accent};">${interaction.taskType}</span>` : ''}
|
74
|
+
</div>
|
75
|
+
<div style="color: ${COLORS.textMuted}; font-size: 11px; font-style: italic; line-height: 1.3; margin-bottom: 8px;">
|
76
|
+
💬 ${promptPreview}
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
<div style="text-align: right; margin-left: 16px;">
|
80
|
+
<div style="color: ${COLORS.success}; font-size: 14px; font-weight: 500; margin-bottom: 4px;">
|
81
|
+
${interaction.cost.toFixed(4)}
|
82
|
+
</div>
|
83
|
+
<div style="display: flex; gap: 8px; font-size: 12px;">
|
84
|
+
${interaction.qualityScore ? `<span style="color: ${qualityColor};">Q:${interaction.qualityScore}</span>` : ''}
|
85
|
+
${interaction.effectivenessScore ? `<span style="color: ${effectivenessColor};">E:${interaction.effectivenessScore}</span>` : ''}
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
`;
|
91
|
+
}).join('');
|
92
|
+
return `
|
93
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${COLORS.primary}20;">
|
94
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
95
|
+
<h2 style="margin: 0; color: ${COLORS.text};">Recent Activity</h2>
|
96
|
+
<div style="display: flex; gap: 8px;">
|
97
|
+
<input type="text" id="prompt-search" placeholder="Search prompts..." style="background: ${COLORS.background}; border: 1px solid ${COLORS.primary}20; color: ${COLORS.text}; padding: 4px 8px; border-radius: 4px; font-size: 12px;" onkeyup="filterPrompts()">
|
98
|
+
<select id="provider-filter" style="background: ${COLORS.background}; border: 1px solid ${COLORS.primary}20; color: ${COLORS.text}; padding: 4px 8px; border-radius: 4px; font-size: 12px;" onchange="filterPrompts()">
|
99
|
+
<option value="">All Providers</option>
|
100
|
+
<option value="Gemini-Pro">Gemini-Pro</option>
|
101
|
+
<option value="OpenAI-GPT4">OpenAI-GPT4</option>
|
102
|
+
<option value="Anthropic-Claude">Anthropic-Claude</option>
|
103
|
+
<option value="Ollama-Llama3">Ollama-Llama3</option>
|
104
|
+
</select>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
<div id="activity-list" style="max-height: 400px; overflow-y: auto;">
|
108
|
+
${recentHTML}
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
`;
|
112
|
+
}
|
113
|
+
// Fetch and update stats
|
114
|
+
async function fetchStats() {
|
115
|
+
try {
|
116
|
+
const response = await fetch('/api/stats');
|
117
|
+
if (!response.ok)
|
118
|
+
throw new Error('Failed to fetch stats');
|
119
|
+
const data = await response.json();
|
120
|
+
// Transform the data for display
|
121
|
+
const totalCost = data.totals.total || 0;
|
122
|
+
const totalInteractions = data.totals.requestCount || 0;
|
123
|
+
const avgCostPerTask = totalInteractions ? (totalCost / totalInteractions) : 0;
|
124
|
+
// Update stats cards
|
125
|
+
document.getElementById('total-cost').innerHTML = `${totalCost.toFixed(4)}`;
|
126
|
+
document.getElementById('total-interactions').innerHTML = totalInteractions.toString();
|
127
|
+
document.getElementById('avg-cost').innerHTML = `${avgCostPerTask.toFixed(4)}`;
|
128
|
+
document.getElementById('waste-rate').innerHTML = '0.0%';
|
129
|
+
// Update provider cards
|
130
|
+
const providerContainer = document.getElementById('provider-container');
|
131
|
+
if (providerContainer && data.totals.byProvider) {
|
132
|
+
const providerHTML = Object.entries(data.totals.byProvider).map(([name, providerData]) => createProviderChart(name, {
|
133
|
+
cost: providerData.costUSD || 0,
|
134
|
+
interactions: providerData.requestCount || 0,
|
135
|
+
avgQuality: providerData.avgQualityScore,
|
136
|
+
avgEffectiveness: providerData.avgEffectivenessScore
|
137
|
+
})).join('');
|
138
|
+
providerContainer.innerHTML = providerHTML;
|
139
|
+
}
|
140
|
+
// Update recent activity
|
141
|
+
const activityContainer = document.getElementById('recent-activity');
|
142
|
+
if (activityContainer) {
|
143
|
+
activityContainer.innerHTML = createRecentActivity(data.recentInteractions || []);
|
144
|
+
}
|
145
|
+
// Update timestamp
|
146
|
+
lastUpdated = new Date();
|
147
|
+
document.getElementById('last-updated').textContent = lastUpdated.toLocaleTimeString();
|
148
|
+
}
|
149
|
+
catch (error) {
|
150
|
+
console.error('Dashboard error:', error);
|
151
|
+
document.getElementById('error-message').textContent = error.message;
|
152
|
+
document.getElementById('error-container').style.display = 'block';
|
153
|
+
}
|
154
|
+
}
|
155
|
+
// Create the complete dashboard HTML
|
156
|
+
const dashboardHTML = `
|
157
|
+
<div style="background: ${COLORS.background}; min-height: 100vh; color: ${COLORS.text}; font-family: system-ui, -apple-system, sans-serif;">
|
158
|
+
<!-- Header -->
|
159
|
+
<header style="background: ${COLORS.surface}; border-bottom: 1px solid ${COLORS.primary}20; padding: 16px 24px;">
|
160
|
+
<div style="display: flex; justify-content: space-between; align-items: center; max-width: 1200px; margin: 0 auto;">
|
161
|
+
<div>
|
162
|
+
<h1 style="margin: 0; font-size: 24px; color: ${COLORS.text};">🚀 TokNXR Dashboard</h1>
|
163
|
+
<p style="color: ${COLORS.textMuted}; margin: 4px 0 0 0; font-size: 14px;">
|
164
|
+
Real-time AI Effectiveness & Code Quality Analytics
|
165
|
+
</p>
|
166
|
+
</div>
|
167
|
+
<div style="text-align: right;">
|
168
|
+
<div id="last-updated" style="color: ${COLORS.textMuted}; font-size: 12px;">
|
169
|
+
Last updated: ${lastUpdated.toLocaleTimeString()}
|
170
|
+
</div>
|
171
|
+
<button
|
172
|
+
onclick="fetchStats()"
|
173
|
+
style="background: ${COLORS.primary}; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-top: 4px;"
|
174
|
+
>
|
175
|
+
Refresh
|
176
|
+
</button>
|
177
|
+
</div>
|
178
|
+
</div>
|
179
|
+
</header>
|
180
|
+
|
181
|
+
<!-- Error Message -->
|
182
|
+
<div id="error-container" style="display: none; background: ${COLORS.error}; color: white; padding: 12px 16px; margin: 16px; border-radius: 8px;">
|
183
|
+
Error: <span id="error-message"></span>
|
184
|
+
</div>
|
185
|
+
|
186
|
+
<!-- Main Content -->
|
187
|
+
<main style="padding: 24px; max-width: 1200px; margin: 0 auto;">
|
188
|
+
<!-- Stats Overview -->
|
189
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 24px; margin-bottom: 32px;">
|
190
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${COLORS.success}20;">
|
191
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
192
|
+
<div>
|
193
|
+
<p style="color: ${COLORS.textMuted}; font-size: 14px; margin: 0 0 8px 0;">Total Cost</p>
|
194
|
+
<p id="total-cost" style="font-size: 32px; font-weight: bold; margin: 0; color: ${COLORS.text};">$0.0000</p>
|
195
|
+
</div>
|
196
|
+
<div style="font-size: 24px; color: ${COLORS.success}; opacity: 0.8;">💰</div>
|
197
|
+
</div>
|
198
|
+
</div>
|
199
|
+
|
200
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${COLORS.primary}20;">
|
201
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
202
|
+
<div>
|
203
|
+
<p style="color: ${COLORS.textMuted}; font-size: 14px; margin: 0 0 8px 0;">Total Interactions</p>
|
204
|
+
<p id="total-interactions" style="font-size: 32px; font-weight: bold; margin: 0; color: ${COLORS.text};">0</p>
|
205
|
+
</div>
|
206
|
+
<div style="font-size: 24px; color: ${COLORS.primary}; opacity: 0.8;">📊</div>
|
207
|
+
</div>
|
208
|
+
</div>
|
209
|
+
|
210
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${COLORS.secondary}20;">
|
211
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
212
|
+
<div>
|
213
|
+
<p style="color: ${COLORS.textMuted}; font-size: 14px; margin: 0 0 8px 0;">Avg Cost per Task</p>
|
214
|
+
<p id="avg-cost" style="font-size: 32px; font-weight: bold; margin: 0; color: ${COLORS.text};">$0.0000</p>
|
215
|
+
</div>
|
216
|
+
<div style="font-size: 24px; color: ${COLORS.secondary}; opacity: 0.8;">🎯</div>
|
217
|
+
</div>
|
218
|
+
</div>
|
219
|
+
|
220
|
+
<div style="background: ${COLORS.surface}; border-radius: 12px; padding: 24px; border: 1px solid ${COLORS.warning}20;">
|
221
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
222
|
+
<div>
|
223
|
+
<p style="color: ${COLORS.textMuted}; font-size: 14px; margin: 0 0 8px 0;">Waste Rate</p>
|
224
|
+
<p id="waste-rate" style="font-size: 32px; font-weight: bold; margin: 0; color: ${COLORS.text};">0.0%</p>
|
225
|
+
</div>
|
226
|
+
<div style="font-size: 24px; color: ${COLORS.warning}; opacity: 0.8;">⚠️</div>
|
227
|
+
</div>
|
228
|
+
</div>
|
229
|
+
</div>
|
230
|
+
|
231
|
+
<!-- Provider Breakdown -->
|
232
|
+
<div id="provider-section" style="margin-bottom: 32px;">
|
233
|
+
<h2 style="color: ${COLORS.text}; margin: 0 0 16px 0;">Provider Breakdown</h2>
|
234
|
+
<div id="provider-container" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px;">
|
235
|
+
<!-- Provider cards will be inserted here -->
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
|
239
|
+
<!-- Recent Activity -->
|
240
|
+
<div id="recent-activity">
|
241
|
+
<!-- Recent activity will be inserted here -->
|
242
|
+
</div>
|
243
|
+
</main>
|
244
|
+
|
245
|
+
<!-- Footer -->
|
246
|
+
<footer style="background: ${COLORS.surface}; border-top: 1px solid ${COLORS.primary}20; padding: 16px 24px; text-align: center; color: ${COLORS.textMuted}; font-size: 12px;">
|
247
|
+
<div style="max-width: 1200px; margin: 0 auto;">
|
248
|
+
TokNXR Dashboard • Real-time AI Analytics • ${new Date().getFullYear()}
|
249
|
+
<br>
|
250
|
+
<span style="color: #FFD700; font-size: 11px; margin-top: 4px; display: block;">
|
251
|
+
🐑 Powered by Golden Sheep AI
|
252
|
+
</span>
|
253
|
+
</div>
|
254
|
+
</footer>
|
255
|
+
</div>
|
256
|
+
`;
|
257
|
+
// Set the dashboard content
|
258
|
+
const container = document.getElementById('dashboard-root');
|
259
|
+
if (container) {
|
260
|
+
container.innerHTML = dashboardHTML;
|
261
|
+
// Start fetching stats
|
262
|
+
fetchStats();
|
263
|
+
// Auto-refresh every 5 seconds
|
264
|
+
setInterval(fetchStats, 5000);
|
265
|
+
}
|
266
|
+
}
|
267
|
+
// Modal for showing prompt details
|
268
|
+
function showPromptDetails(provider, model, userPrompt, aiResponse, qualityScore, effectivenessScore, taskType, cost) {
|
269
|
+
const modal = document.createElement('div');
|
270
|
+
modal.id = 'prompt-modal';
|
271
|
+
modal.style.cssText = `
|
272
|
+
position: fixed;
|
273
|
+
top: 0;
|
274
|
+
left: 0;
|
275
|
+
width: 100%;
|
276
|
+
height: 100%;
|
277
|
+
background: rgba(0, 0, 0, 0.8);
|
278
|
+
display: flex;
|
279
|
+
align-items: center;
|
280
|
+
justify-content: center;
|
281
|
+
z-index: 1000;
|
282
|
+
padding: 20px;
|
283
|
+
box-sizing: border-box;
|
284
|
+
`;
|
285
|
+
const modalContent = document.createElement('div');
|
286
|
+
modalContent.style.cssText = `
|
287
|
+
background: #1E293B;
|
288
|
+
border-radius: 12px;
|
289
|
+
padding: 24px;
|
290
|
+
max-width: 800px;
|
291
|
+
max-height: 80vh;
|
292
|
+
overflow-y: auto;
|
293
|
+
color: #F8FAFC;
|
294
|
+
border: 1px solid #6B5BED20;
|
295
|
+
`;
|
296
|
+
modalContent.innerHTML = `
|
297
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
298
|
+
<h3 style="margin: 0; color: #F8FAFC;">Interaction Details</h3>
|
299
|
+
<button onclick="closePromptModal()" style="background: #6B5BED; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer;">✕ Close</button>
|
300
|
+
</div>
|
301
|
+
|
302
|
+
<div style="margin-bottom: 16px;">
|
303
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 20px;">
|
304
|
+
<div>
|
305
|
+
<strong>Provider:</strong> ${provider}<br>
|
306
|
+
<strong>Model:</strong> ${model}<br>
|
307
|
+
<strong>Task Type:</strong> ${taskType}<br>
|
308
|
+
<strong>Cost:</strong> $${cost.toFixed(4)}
|
309
|
+
</div>
|
310
|
+
<div>
|
311
|
+
<strong>Quality Score:</strong> ${qualityScore}/100<br>
|
312
|
+
<strong>Effectiveness:</strong> ${effectivenessScore}/100<br>
|
313
|
+
<strong>Status:</strong> ${qualityScore >= 90 ? '🟢 Excellent' : qualityScore >= 70 ? '🟡 Good' : '🔴 Needs Improvement'}
|
314
|
+
</div>
|
315
|
+
</div>
|
316
|
+
|
317
|
+
<div style="margin-bottom: 16px;">
|
318
|
+
<h4 style="margin: 0 0 8px 0; color: #F8FAFC;">📝 User Prompt:</h4>
|
319
|
+
<div style="background: #0F172A; padding: 12px; border-radius: 6px; border: 1px solid #6B5BED20; font-family: monospace; white-space: pre-wrap;">${userPrompt}</div>
|
320
|
+
</div>
|
321
|
+
|
322
|
+
<div>
|
323
|
+
<h4 style="margin: 0 0 8px 0; color: #F8FAFC;">🤖 AI Response:</h4>
|
324
|
+
<div style="background: #0F172A; padding: 12px; border-radius: 6px; border: 1px solid #6B5BED20; font-family: monospace; white-space: pre-wrap; max-height: 200px; overflow-y: auto;">${aiResponse}${aiResponse.length > 300 ? '...' : ''}</div>
|
325
|
+
</div>
|
326
|
+
</div>
|
327
|
+
`;
|
328
|
+
modal.appendChild(modalContent);
|
329
|
+
document.body.appendChild(modal);
|
330
|
+
// Close modal on outside click
|
331
|
+
modal.addEventListener('click', function (e) {
|
332
|
+
if (e.target === modal) {
|
333
|
+
closePromptModal();
|
334
|
+
}
|
335
|
+
});
|
336
|
+
}
|
337
|
+
function closePromptModal() {
|
338
|
+
const modal = document.getElementById('prompt-modal');
|
339
|
+
if (modal) {
|
340
|
+
modal.remove();
|
341
|
+
}
|
342
|
+
}
|
343
|
+
// Filter prompts based on search and provider filter
|
344
|
+
function filterPrompts() {
|
345
|
+
const searchTermValue = document.getElementById('prompt-search')?.value.toLowerCase() || '';
|
346
|
+
const providerFilterValue = document.getElementById('provider-filter')?.value || '';
|
347
|
+
const activityItems = document.querySelectorAll('#activity-list > div');
|
348
|
+
activityItems.forEach(item => {
|
349
|
+
const text = item.textContent?.toLowerCase() || '';
|
350
|
+
const provider = item.textContent?.split(' • ')[0] || '';
|
351
|
+
const shouldShow = text.includes(searchTermValue) &&
|
352
|
+
(providerFilterValue === '' || text.includes(providerFilterValue.toLowerCase()));
|
353
|
+
item.style.display = shouldShow ? 'block' : 'none';
|
354
|
+
});
|
355
|
+
}
|
356
|
+
// Initialize dashboard when DOM is loaded
|
357
|
+
if (document.readyState === 'loading') {
|
358
|
+
document.addEventListener('DOMContentLoaded', createDashboard);
|
359
|
+
}
|
360
|
+
else {
|
361
|
+
createDashboard();
|
362
|
+
}
|
363
|
+
export {};
|
@@ -0,0 +1,272 @@
|
|
1
|
+
/**
|
2
|
+
* Main hallucination detection engine
|
3
|
+
*/
|
4
|
+
export class HallucinationDetector {
|
5
|
+
constructor() {
|
6
|
+
this.technicalTerms = new Set([
|
7
|
+
'api', 'endpoint', 'function', 'method', 'class', 'interface', 'module',
|
8
|
+
'library', 'framework', 'database', 'server', 'client', 'request', 'response',
|
9
|
+
'parameter', 'argument', 'variable', 'constant', 'algorithm', 'data structure'
|
10
|
+
]);
|
11
|
+
this.commonLibraries = new Set([
|
12
|
+
'react', 'express', 'axios', 'lodash', 'jquery', 'bootstrap', 'tailwind',
|
13
|
+
'tensorflow', 'pytorch', 'pandas', 'numpy', 'requests', 'flask', 'django'
|
14
|
+
]);
|
15
|
+
}
|
16
|
+
/**
|
17
|
+
* Analyze response for potential hallucinations
|
18
|
+
*/
|
19
|
+
detectHallucination(userPrompt, aiResponse, context) {
|
20
|
+
const issues = [];
|
21
|
+
const evidence = [];
|
22
|
+
const categories = [];
|
23
|
+
// 1. Check for overconfidence indicators
|
24
|
+
const overconfidenceEvidence = this.detectOverconfidence(aiResponse);
|
25
|
+
if (overconfidenceEvidence) {
|
26
|
+
evidence.push(overconfidenceEvidence);
|
27
|
+
issues.push('Response shows signs of overconfidence without sufficient evidence');
|
28
|
+
}
|
29
|
+
// 2. Check for factual contradictions
|
30
|
+
const contradictions = this.detectContradictions(aiResponse, context);
|
31
|
+
evidence.push(...contradictions);
|
32
|
+
if (contradictions.length > 0) {
|
33
|
+
issues.push('Internal contradictions detected in response');
|
34
|
+
}
|
35
|
+
// 3. Check for technical hallucinations (made-up APIs, libraries, etc.)
|
36
|
+
const technicalHallucinations = this.detectTechnicalHallucinations(aiResponse);
|
37
|
+
evidence.push(...technicalHallucinations);
|
38
|
+
if (technicalHallucinations.length > 0) {
|
39
|
+
issues.push('Potential technical hallucinations detected');
|
40
|
+
}
|
41
|
+
// 4. Check for context drift
|
42
|
+
const contextDrift = this.detectContextDrift(userPrompt, aiResponse, context);
|
43
|
+
if (contextDrift) {
|
44
|
+
evidence.push(contextDrift);
|
45
|
+
issues.push('Response may have drifted from original context');
|
46
|
+
}
|
47
|
+
// 5. Check for citation/reference issues
|
48
|
+
const citationIssues = this.detectCitationIssues(aiResponse);
|
49
|
+
evidence.push(...citationIssues);
|
50
|
+
if (citationIssues.length > 0) {
|
51
|
+
issues.push('Questionable citations or references detected');
|
52
|
+
}
|
53
|
+
// Calculate overall confidence and categorize
|
54
|
+
const overallConfidence = this.calculateOverallConfidence(evidence, categories);
|
55
|
+
const severity = this.determineSeverity(overallConfidence);
|
56
|
+
// Determine if this is likely a hallucination
|
57
|
+
const isLikelyHallucination = overallConfidence > 60 || issues.length >= 2;
|
58
|
+
return {
|
59
|
+
isLikelyHallucination,
|
60
|
+
confidence: overallConfidence,
|
61
|
+
severity,
|
62
|
+
categories,
|
63
|
+
issues,
|
64
|
+
evidence
|
65
|
+
};
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Detect overconfidence indicators
|
69
|
+
*/
|
70
|
+
detectOverconfidence(response) {
|
71
|
+
const overconfidencePatterns = [
|
72
|
+
/definitely\s+(correct|right|accurate)/gi,
|
73
|
+
/absolutely\s+(certain|sure|positive)/gi,
|
74
|
+
/without\s+(a\s+)?doubt/gi,
|
75
|
+
/everyone\s+knows/gi,
|
76
|
+
/obviously/gi,
|
77
|
+
/clearly/gi
|
78
|
+
];
|
79
|
+
const confidence = overconfidencePatterns.reduce((score, pattern) => {
|
80
|
+
const matches = response.match(pattern);
|
81
|
+
return score + (matches ? matches.length * 15 : 0);
|
82
|
+
}, 0);
|
83
|
+
if (confidence > 30) {
|
84
|
+
return {
|
85
|
+
type: 'overconfidence',
|
86
|
+
description: `Response shows ${confidence}% overconfidence indicators`,
|
87
|
+
severity: Math.min(confidence / 10, 10)
|
88
|
+
};
|
89
|
+
}
|
90
|
+
return null;
|
91
|
+
}
|
92
|
+
/**
|
93
|
+
* Detect internal contradictions
|
94
|
+
*/
|
95
|
+
detectContradictions(response, _context) {
|
96
|
+
const evidence = [];
|
97
|
+
// Look for contradictory statements
|
98
|
+
const contradictions = [
|
99
|
+
{ pattern: /(yes|correct|true).*?(no|incorrect|false)/gi, description: 'Direct yes/no contradiction' },
|
100
|
+
{ pattern: /(always).*?(never)/gi, description: 'Always/never contradiction' },
|
101
|
+
{ pattern: /(all|every).*?(none|no)/gi, description: 'All/none contradiction' },
|
102
|
+
{ pattern: /(\d+).*?(\d+)/g, description: 'Numerical contradictions' }
|
103
|
+
];
|
104
|
+
contradictions.forEach(({ pattern, description }) => {
|
105
|
+
const matches = response.match(pattern);
|
106
|
+
if (matches) {
|
107
|
+
evidence.push({
|
108
|
+
type: 'contradiction',
|
109
|
+
description: `${description} detected`,
|
110
|
+
severity: 8,
|
111
|
+
context: matches[0]
|
112
|
+
});
|
113
|
+
}
|
114
|
+
});
|
115
|
+
return evidence;
|
116
|
+
}
|
117
|
+
/**
|
118
|
+
* Detect technical hallucinations (made-up APIs, libraries, etc.)
|
119
|
+
*/
|
120
|
+
detectTechnicalHallucinations(response) {
|
121
|
+
const evidence = [];
|
122
|
+
// Extract technical terms and check if they're likely made up
|
123
|
+
const technicalTerms = response.match(/\b[A-Z][a-zA-Z]*[A-Z]\w*\b/g) || [];
|
124
|
+
const suspiciousTerms = technicalTerms.filter(term => {
|
125
|
+
// Check if it looks like a class name or API but isn't common
|
126
|
+
return term.length > 6 &&
|
127
|
+
!this.technicalTerms.has(term.toLowerCase()) &&
|
128
|
+
/[A-Z]/.test(term) && // Has uppercase letters (likely class/API name)
|
129
|
+
!this.commonLibraries.has(term.toLowerCase());
|
130
|
+
});
|
131
|
+
if (suspiciousTerms.length > 0) {
|
132
|
+
evidence.push({
|
133
|
+
type: 'fabrication',
|
134
|
+
description: `Suspicious technical terms detected: ${suspiciousTerms.join(', ')}`,
|
135
|
+
severity: 7
|
136
|
+
});
|
137
|
+
}
|
138
|
+
// Check for made-up method names
|
139
|
+
const methodPatterns = [
|
140
|
+
/\.([a-z][a-zA-Z]*[A-Z]\w*)\(/g, // camelCase methods
|
141
|
+
/\b([a-z]+_[a-z_]*)\(/g // snake_case functions
|
142
|
+
];
|
143
|
+
methodPatterns.forEach(pattern => {
|
144
|
+
const matches = Array.from(response.matchAll(pattern));
|
145
|
+
const suspiciousMethods = matches.filter(match => {
|
146
|
+
const methodName = match[1];
|
147
|
+
return methodName.length > 10 &&
|
148
|
+
!this.technicalTerms.has(methodName.toLowerCase()) &&
|
149
|
+
/[A-Z]/.test(methodName); // Likely made up
|
150
|
+
});
|
151
|
+
if (suspiciousMethods.length > 0) {
|
152
|
+
evidence.push({
|
153
|
+
type: 'fabrication',
|
154
|
+
description: `Potentially fabricated method names: ${suspiciousMethods.map(m => m[1]).join(', ')}`,
|
155
|
+
severity: 6
|
156
|
+
});
|
157
|
+
}
|
158
|
+
});
|
159
|
+
return evidence;
|
160
|
+
}
|
161
|
+
/**
|
162
|
+
* Detect context drift from conversation history
|
163
|
+
*/
|
164
|
+
detectContextDrift(userPrompt, response, context) {
|
165
|
+
if (!context || context.length === 0)
|
166
|
+
return null;
|
167
|
+
// Check if response addresses the current prompt or drifts to previous context
|
168
|
+
const promptKeywords = this.extractKeywords(userPrompt);
|
169
|
+
const responseKeywords = this.extractKeywords(response);
|
170
|
+
const contextOverlap = promptKeywords.filter(keyword => responseKeywords.some(respKeyword => respKeyword.includes(keyword) || keyword.includes(respKeyword))).length;
|
171
|
+
const driftScore = Math.max(0, (promptKeywords.length - contextOverlap) / promptKeywords.length * 100);
|
172
|
+
if (driftScore > 60) {
|
173
|
+
return {
|
174
|
+
type: 'context_drift',
|
175
|
+
description: `High context drift detected (${driftScore.toFixed(1)}% deviation from prompt)`,
|
176
|
+
severity: Math.min(driftScore / 10, 10)
|
177
|
+
};
|
178
|
+
}
|
179
|
+
return null;
|
180
|
+
}
|
181
|
+
/**
|
182
|
+
* Detect citation and reference issues
|
183
|
+
*/
|
184
|
+
detectCitationIssues(response) {
|
185
|
+
const evidence = [];
|
186
|
+
// Look for citations that might be fabricated
|
187
|
+
const citationPatterns = [
|
188
|
+
/according\s+to\s+([^,\.]+)/gi,
|
189
|
+
/as\s+stated\s+(in|by)\s+([^,\.]+)/gi,
|
190
|
+
/\[([^\]]+)\]/g, // Reference brackets
|
191
|
+
/source[s]?:\s*([^,\.]+)/gi
|
192
|
+
];
|
193
|
+
citationPatterns.forEach(pattern => {
|
194
|
+
const matches = Array.from(response.matchAll(pattern));
|
195
|
+
matches.forEach(match => {
|
196
|
+
const citation = match[1] || match[0];
|
197
|
+
if (citation && citation.length > 50) { // Unusually long citation
|
198
|
+
evidence.push({
|
199
|
+
type: 'invalid_reference',
|
200
|
+
description: `Suspiciously long or complex citation: ${citation.substring(0, 50)}...`,
|
201
|
+
severity: 5
|
202
|
+
});
|
203
|
+
}
|
204
|
+
});
|
205
|
+
});
|
206
|
+
return evidence;
|
207
|
+
}
|
208
|
+
/**
|
209
|
+
* Calculate overall hallucination confidence
|
210
|
+
*/
|
211
|
+
calculateOverallConfidence(evidence, _categories) {
|
212
|
+
if (evidence.length === 0)
|
213
|
+
return 0;
|
214
|
+
// Weight different types of evidence
|
215
|
+
const weights = {
|
216
|
+
contradiction: 1.0,
|
217
|
+
overconfidence: 0.8,
|
218
|
+
fabrication: 0.9,
|
219
|
+
context_drift: 0.7,
|
220
|
+
invalid_reference: 0.6
|
221
|
+
};
|
222
|
+
const totalWeightedScore = evidence.reduce((sum, ev) => {
|
223
|
+
return sum + (ev.severity * (weights[ev.type] || 0.5));
|
224
|
+
}, 0);
|
225
|
+
const avgScore = totalWeightedScore / evidence.length;
|
226
|
+
// Cap at 100 and apply some randomness to simulate uncertainty
|
227
|
+
return Math.min(100, Math.max(0, avgScore * 10 + Math.random() * 10 - 5));
|
228
|
+
}
|
229
|
+
/**
|
230
|
+
* Determine severity level
|
231
|
+
*/
|
232
|
+
determineSeverity(confidence) {
|
233
|
+
if (confidence >= 80)
|
234
|
+
return 'critical';
|
235
|
+
if (confidence >= 60)
|
236
|
+
return 'high';
|
237
|
+
if (confidence >= 40)
|
238
|
+
return 'medium';
|
239
|
+
return 'low';
|
240
|
+
}
|
241
|
+
/**
|
242
|
+
* Extract meaningful keywords from text
|
243
|
+
*/
|
244
|
+
extractKeywords(text) {
|
245
|
+
return text
|
246
|
+
.toLowerCase()
|
247
|
+
.split(/\s+/)
|
248
|
+
.filter(word => word.length > 4)
|
249
|
+
.filter(word => !['that', 'with', 'from', 'this', 'will', 'should', 'would', 'could'].includes(word))
|
250
|
+
.slice(0, 10); // Limit to top 10 keywords
|
251
|
+
}
|
252
|
+
/**
|
253
|
+
* Calculate business impact of hallucinations
|
254
|
+
*/
|
255
|
+
calculateBusinessImpact(hallucinationRate, totalInteractions, avgCostPerInteraction, avgDevTimePerFix = 0.5 // hours
|
256
|
+
) {
|
257
|
+
const devTimeWasted = (hallucinationRate / 100) * totalInteractions * avgDevTimePerFix;
|
258
|
+
const qualityDegradationScore = Math.min(100, hallucinationRate * 1.5);
|
259
|
+
const roiImpact = hallucinationRate * 0.8; // 0.8% ROI reduction per 1% hallucination rate
|
260
|
+
const costOfHallucinations = (hallucinationRate / 100) * totalInteractions * avgCostPerInteraction * 2; // 2x multiplier for debugging cost
|
261
|
+
return {
|
262
|
+
estimatedDevTimeWasted: Math.round(devTimeWasted * 10) / 10,
|
263
|
+
qualityDegradationScore: Math.round(qualityDegradationScore),
|
264
|
+
roiImpact: Math.round(roiImpact * 10) / 10,
|
265
|
+
costOfHallucinations: Math.round(costOfHallucinations * 100) / 100
|
266
|
+
};
|
267
|
+
}
|
268
|
+
}
|
269
|
+
/**
|
270
|
+
* Global hallucination detector instance
|
271
|
+
*/
|
272
|
+
export const hallucinationDetector = new HallucinationDetector();
|