@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.
@@ -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();