scam / ui /guvi-test.js
Gankit12's picture
Update API key to production key
3efb887
/**
* GUVI Format Tester - JavaScript
* Tests implementation against GUVI Hackathon documentation
*/
// ============================================================================
// State
// ============================================================================
const state = {
// Use empty string for relative URLs (works on any host/port)
// Users can override via the input field if testing against a different server
apiBaseUrl: '',
apiKey: 'sVlunn0LMQZNAkRYqZB-f1-Ye7rgzjB_E3b1gNxnUV8',
connected: false,
// Multi-turn state
multiSessionId: null,
conversationHistory: [],
turnCount: 0,
lastResponse: null,
};
// Example messages for testing
const EXAMPLES = {
lottery: "Congratulations! You have won 10 LAKH RUPEES in our lucky draw! Call +919876543210 and share OTP to claim prize immediately!",
bank: "ALERT: Your bank account will be blocked in 24 hours. Update KYC now by sending Rs 500 to processing@paytm or your account will be suspended.",
police: "This is CBI Investigation Department. You are involved in money laundering case. Pay Rs 50000 immediately to avoid arrest. UPI: officer@ybl",
upi: "URGENT: Pay Rs 999 to receive your iPhone 15 Pro. UPI ID: scammer@paytm. Bank Account: 1234567890123 IFSC: HDFC0001234. Hurry, limited time offer!",
};
// ============================================================================
// Initialization
// ============================================================================
document.addEventListener('DOMContentLoaded', () => {
console.log('🧪 GUVI Tester initialized');
// One-time cleanup of corrupted localStorage (v1.1 fix)
const cleanupKey = 'guvi-tester-cleaned-v1';
if (!localStorage.getItem(cleanupKey)) {
localStorage.removeItem('guvi-tester-config');
localStorage.setItem(cleanupKey, 'true');
console.log('🧹 Cleaned up old config');
}
// Setup tab navigation
setupTabs();
// Setup form listeners
setupFormListeners();
// Load saved config
loadConfig();
// Update preview on load
updateRequestPreview();
updateCallbackPreview();
});
function setupTabs() {
document.querySelectorAll('.nav-item').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
// Update nav
document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update content
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.getElementById(`tab-${tab}`).classList.add('active');
});
});
}
function setupFormListeners() {
// Single message form
['singleSessionId', 'singleSender', 'singleChannel', 'singleLanguage', 'singleLocale', 'singleMessageText'].forEach(id => {
const el = document.getElementById(id);
if (el) el.addEventListener('input', updateRequestPreview);
});
// Callback form
['callbackSessionId', 'callbackScamDetected', 'callbackTotalMessages', 'callbackBankAccounts',
'callbackUpiIds', 'callbackPhishingLinks', 'callbackPhoneNumbers', 'callbackKeywords', 'callbackAgentNotes'].forEach(id => {
const el = document.getElementById(id);
if (el) el.addEventListener('input', updateCallbackPreview);
});
// API config
document.getElementById('apiBaseUrl').addEventListener('change', (e) => {
let baseUrl = e.target.value.trim();
// Empty string = relative URLs (same origin as page)
if (baseUrl === '') {
state.apiBaseUrl = '';
e.target.value = '';
saveConfig();
return;
}
// Clean up the URL - only keep origin (protocol + host + port)
try {
const url = new URL(baseUrl);
baseUrl = url.origin;
} catch (err) {
// If invalid URL, try to fix common issues
if (!baseUrl.startsWith('http')) {
baseUrl = 'http://' + baseUrl;
}
}
state.apiBaseUrl = baseUrl;
e.target.value = baseUrl; // Update the input with cleaned value
saveConfig();
});
document.getElementById('apiKey').addEventListener('change', (e) => {
state.apiKey = e.target.value;
saveConfig();
});
}
function loadConfig() {
const saved = localStorage.getItem('guvi-tester-config');
if (saved) {
const config = JSON.parse(saved);
// Allow empty string for relative URLs
let baseUrl = config.apiBaseUrl !== undefined ? config.apiBaseUrl : state.apiBaseUrl;
// Fix: Remove any path that was accidentally appended
// Only keep the origin (protocol + host + port), unless empty (relative)
if (baseUrl !== '') {
try {
const url = new URL(baseUrl);
baseUrl = url.origin;
} catch (e) {
baseUrl = ''; // fallback to relative URLs
}
}
state.apiBaseUrl = baseUrl;
state.apiKey = config.apiKey || '';
document.getElementById('apiBaseUrl').value = state.apiBaseUrl;
document.getElementById('apiKey').value = state.apiKey;
// Save the cleaned config
saveConfig();
}
}
function saveConfig() {
localStorage.setItem('guvi-tester-config', JSON.stringify({
apiBaseUrl: state.apiBaseUrl,
apiKey: state.apiKey,
}));
}
// ============================================================================
// Connection Test
// ============================================================================
async function testConnection() {
const statusDot = document.querySelector('.status-dot');
const statusText = document.querySelector('.status-text');
statusDot.className = 'status-dot';
statusText.textContent = 'Connecting...';
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/health`, {
headers: state.apiKey ? { 'x-api-key': state.apiKey } : {},
});
const data = await response.json();
state.connected = true;
statusDot.className = 'status-dot connected';
statusText.textContent = `Connected (${data.status})`;
showToast('✅ Connected to API successfully', 'success');
return data;
} catch (error) {
state.connected = false;
statusDot.className = 'status-dot error';
statusText.textContent = 'Connection failed';
showToast('❌ Failed to connect to API', 'error');
return null;
}
}
// ============================================================================
// Single Message Test
// ============================================================================
function buildGuviRequest() {
const sessionId = document.getElementById('singleSessionId').value || 'test-session-001';
const sender = document.getElementById('singleSender').value;
const channel = document.getElementById('singleChannel').value;
const language = document.getElementById('singleLanguage').value;
const locale = document.getElementById('singleLocale').value;
const text = document.getElementById('singleMessageText').value || 'Your message here...';
return {
sessionId: sessionId,
message: {
sender: sender,
text: text,
timestamp: Date.now(),
},
conversationHistory: [],
metadata: {
channel: channel,
language: language,
locale: locale,
},
};
}
function updateRequestPreview() {
const request = buildGuviRequest();
document.getElementById('requestPreview').textContent = JSON.stringify(request, null, 2);
}
function setMessage(type) {
document.getElementById('singleMessageText').value = EXAMPLES[type] || '';
updateRequestPreview();
}
function loadGuviExample(type) {
if (type === 'first') {
document.getElementById('singleSessionId').value = 'wertyu-dfghj-ertyui';
document.getElementById('singleSender').value = 'scammer';
document.getElementById('singleMessageText').value = 'Your bank account will be blocked today. Verify immediately.';
document.getElementById('singleChannel').value = 'SMS';
document.getElementById('singleLanguage').value = 'English';
document.getElementById('singleLocale').value = 'IN';
}
updateRequestPreview();
}
async function sendSingleMessage() {
const request = buildGuviRequest();
const responseContainer = document.getElementById('singleResponse');
const responseMeta = document.getElementById('responseMeta');
responseContainer.innerHTML = '<div class="empty-state"><span class="empty-icon">⏳</span><p>Sending request...</p></div>';
try {
const startTime = Date.now();
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify(request),
});
const responseTime = Date.now() - startTime;
const data = await response.json();
// Update meta
responseMeta.innerHTML = `
<span class="response-time">${responseTime}ms</span>
<span class="response-status">${response.status} ${response.statusText}</span>
`;
// Display response
displaySingleResponse(data, responseTime);
showToast(`✅ Request completed in ${responseTime}ms`, 'success');
} catch (error) {
responseContainer.innerHTML = `
<div class="empty-state">
<span class="empty-icon">❌</span>
<p>Request failed: ${error.message}</p>
</div>
`;
showToast('❌ Request failed', 'error');
}
}
function displaySingleResponse(data, responseTime) {
const container = document.getElementById('singleResponse');
const hasReply = data.reply !== null && data.reply !== undefined;
const hasStatus = data.status !== undefined;
container.innerHTML = `
<div class="formatted-response">
<!-- GUVI Required Fields -->
<div class="response-section">
<h4>✅ GUVI Required Fields</h4>
<div class="response-row">
<span class="label">status</span>
<span class="value ${hasStatus ? 'success' : 'danger'}">${data.status || 'MISSING!'}</span>
</div>
<div class="response-row">
<span class="label">reply</span>
<span class="value ${hasReply ? 'success' : 'danger'}">${hasReply ? '✓ Present' : '✗ MISSING!'}</span>
</div>
${hasReply ? `<div class="reply-box">"${data.reply}"</div>` : ''}
</div>
<!-- Detection Result -->
<div class="response-section">
<h4>🎯 Detection Result</h4>
<div class="response-row">
<span class="label">scam_detected</span>
<span class="value ${data.scam_detected ? 'danger' : 'success'}">${data.scam_detected ? '⚠️ SCAM' : '✓ Safe'}</span>
</div>
<div class="response-row">
<span class="label">confidence</span>
<span class="value accent">${((data.confidence || 0) * 100).toFixed(1)}%</span>
</div>
<div class="response-row">
<span class="label">language_detected</span>
<span class="value">${data.language_detected || '-'}</span>
</div>
<div class="response-row">
<span class="label">session_id</span>
<span class="value" style="font-size: 0.75rem;">${data.session_id || '-'}</span>
</div>
</div>
<!-- Engagement -->
${data.engagement ? `
<div class="response-section">
<h4>🤖 Agent Engagement</h4>
<div class="response-row">
<span class="label">turn_count</span>
<span class="value">${data.engagement.turn_count} / 20</span>
</div>
<div class="response-row">
<span class="label">strategy</span>
<span class="value">${data.engagement.strategy || '-'}</span>
</div>
<div class="response-row">
<span class="label">persona</span>
<span class="value">${data.engagement.persona || '-'}</span>
</div>
</div>
` : ''}
<!-- Intelligence -->
${data.extracted_intelligence ? `
<div class="response-section">
<h4>📊 Extracted Intelligence</h4>
<div class="response-row">
<span class="label">UPI IDs</span>
<span class="value ${data.extracted_intelligence.upi_ids?.length ? 'success' : ''}">${data.extracted_intelligence.upi_ids?.join(', ') || '-'}</span>
</div>
<div class="response-row">
<span class="label">Bank Accounts</span>
<span class="value ${data.extracted_intelligence.bank_accounts?.length ? 'success' : ''}">${data.extracted_intelligence.bank_accounts?.join(', ') || '-'}</span>
</div>
<div class="response-row">
<span class="label">Phone Numbers</span>
<span class="value ${data.extracted_intelligence.phone_numbers?.length ? 'success' : ''}">${data.extracted_intelligence.phone_numbers?.join(', ') || '-'}</span>
</div>
<div class="response-row">
<span class="label">Phishing Links</span>
<span class="value ${data.extracted_intelligence.phishing_links?.length ? 'success' : ''}">${data.extracted_intelligence.phishing_links?.length || 0} found</span>
</div>
<div class="response-row">
<span class="label">Suspicious Keywords</span>
<span class="value">${data.extracted_intelligence.suspicious_keywords?.slice(0, 5).join(', ') || '-'}</span>
</div>
</div>
` : ''}
<!-- Agent Notes -->
${data.agent_notes ? `
<div class="response-section">
<h4>📝 Agent Notes</h4>
<div class="agent-notes">${data.agent_notes}</div>
</div>
` : ''}
<!-- Performance -->
<div class="response-section">
<h4>⚡ Performance</h4>
<div class="response-row">
<span class="label">Response Time</span>
<span class="value ${responseTime < 2000 ? 'success' : 'danger'}">${responseTime}ms ${responseTime < 2000 ? '✓' : '⚠️ >2s'}</span>
</div>
<div class="response-row">
<span class="label">API Processing</span>
<span class="value">${data.metadata?.processing_time_ms || '-'}ms</span>
</div>
</div>
<!-- Raw JSON -->
<details style="margin-top: var(--space-md);">
<summary style="cursor: pointer; color: var(--text-muted); font-size: 0.85rem;">View Raw JSON</summary>
<pre class="code-block" style="margin-top: var(--space-sm);">${JSON.stringify(data, null, 2)}</pre>
</details>
</div>
`;
}
function copyRequest() {
const request = buildGuviRequest();
navigator.clipboard.writeText(JSON.stringify(request, null, 2));
showToast('📋 Request JSON copied!', 'info');
}
// ============================================================================
// Multi-Turn Test
// ============================================================================
function resetConversation() {
state.multiSessionId = `multi-session-${Date.now()}`;
state.conversationHistory = [];
state.turnCount = 0;
state.lastResponse = null;
document.getElementById('conversationContainer').innerHTML = `
<div class="empty-state">
<span class="empty-icon">💬</span>
<p>Type as <strong>Scammer</strong> → Your AI responds as <strong>Victim</strong></p>
<p style="font-size: 0.8rem; color: var(--text-muted);">This simulates how GUVI will test your honeypot</p>
</div>
`;
document.getElementById('turnCounter').textContent = 'Turn: 0';
document.getElementById('multiSessionId').textContent = state.multiSessionId;
document.getElementById('multiScamDetected').textContent = '-';
document.getElementById('multiConfidence').textContent = '-';
document.getElementById('multiTurnCount').textContent = '0';
document.getElementById('multiStrategy').textContent = '-';
document.getElementById('multiPersona').textContent = '-';
document.getElementById('multiIntelList').innerHTML = '<div class="empty-state small">No intelligence extracted yet</div>';
document.getElementById('multiAgentNotes').textContent = '-';
document.getElementById('conversationHistoryPreview').textContent = '[]';
// Reset extraction progress
document.getElementById('extractionProgressBar').style.width = '0%';
document.getElementById('extractionProgressBar').classList.remove('high');
document.getElementById('extractionProgressLabel').textContent = '0%';
document.getElementById('hasUpi').textContent = '❌ UPI';
document.getElementById('hasUpi').className = 'detail-item';
document.getElementById('hasBank').textContent = '❌ Bank';
document.getElementById('hasBank').className = 'detail-item';
document.getElementById('hasPhone').textContent = '❌ Phone';
document.getElementById('hasPhone').className = 'detail-item';
document.getElementById('hasIfsc').textContent = '❌ IFSC';
document.getElementById('hasIfsc').className = 'detail-item';
// Reset callback status
document.getElementById('callbackStatusBox').classList.remove('triggered');
document.getElementById('callbackIcon').textContent = '⏳';
document.getElementById('callbackStatusText').textContent = 'Waiting for extraction...';
document.getElementById('callbackDetails').textContent = 'Callback triggers when confidence ≥ 85% or turn 20';
showToast('🔄 Conversation reset', 'info');
}
async function sendMultiMessage() {
const input = document.getElementById('multiMessageInput');
const sender = 'scammer'; // Always scammer - AI responds as victim/user
const text = input.value.trim();
if (!text) return;
// Initialize session if needed
if (!state.multiSessionId) {
state.multiSessionId = `multi-session-${Date.now()}`;
}
// Clear welcome if first message
const container = document.getElementById('conversationContainer');
if (container.querySelector('.empty-state')) {
container.innerHTML = '';
}
// Add message to UI
addChatMessage(sender, text);
input.value = '';
// Build request with conversation history
const request = {
sessionId: state.multiSessionId,
message: {
sender: sender,
text: text,
timestamp: Date.now(),
},
conversationHistory: state.conversationHistory.map(msg => ({
sender: msg.sender,
text: msg.text,
timestamp: msg.timestamp,
})),
metadata: {
channel: 'Chat',
language: 'English',
locale: 'IN',
},
};
// Update history preview
document.getElementById('conversationHistoryPreview').textContent =
JSON.stringify(request.conversationHistory, null, 2);
// Add to history AFTER building request (history doesn't include current message)
state.conversationHistory.push({
sender: sender,
text: text,
timestamp: Date.now(),
});
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify(request),
});
const data = await response.json();
state.lastResponse = data;
// Add agent response if scam detected
if (data.scam_detected && data.reply) {
addChatMessage('agent', data.reply);
// Add agent response to history
state.conversationHistory.push({
sender: 'user', // GUVI uses 'user' for honeypot agent
text: data.reply,
timestamp: Date.now(),
});
}
// Update turn count
state.turnCount = data.engagement?.turn_count || state.conversationHistory.length;
document.getElementById('turnCounter').textContent = `Turn: ${state.turnCount}`;
// Update session info
updateMultiSessionInfo(data);
// Update history preview with agent response included
document.getElementById('conversationHistoryPreview').textContent =
JSON.stringify(state.conversationHistory.map(msg => ({
sender: msg.sender,
text: msg.text,
timestamp: msg.timestamp,
})), null, 2);
} catch (error) {
showToast('❌ Request failed: ' + error.message, 'error');
}
}
function addChatMessage(sender, text) {
const container = document.getElementById('conversationContainer');
const msgDiv = document.createElement('div');
msgDiv.className = `chat-msg ${sender}`;
// Scammer = attacker, Agent = your AI honeypot responding as victim
const avatar = sender === 'scammer' ? '🦹' : '🤖';
const senderName = sender === 'scammer' ? 'SCAMMER' : 'YOUR AI (as Victim)';
msgDiv.innerHTML = `
<div class="msg-avatar">${avatar}</div>
<div class="msg-bubble">
<div class="msg-sender">${senderName}</div>
<div class="msg-text">${escapeHtml(text)}</div>
</div>
`;
container.appendChild(msgDiv);
container.scrollTop = container.scrollHeight;
}
function updateMultiSessionInfo(data) {
document.getElementById('multiSessionId').textContent = data.session_id || state.multiSessionId;
document.getElementById('multiScamDetected').textContent = data.scam_detected ? '⚠️ Yes' : '✓ No';
document.getElementById('multiConfidence').textContent = ((data.confidence || 0) * 100).toFixed(0) + '%';
document.getElementById('multiTurnCount').textContent = data.engagement?.turn_count || state.turnCount;
document.getElementById('multiStrategy').textContent = data.engagement?.strategy || '-';
document.getElementById('multiPersona').textContent = data.engagement?.persona || '-';
// Update intelligence
const intel = data.extracted_intelligence || {};
let html = '';
if (intel.upi_ids?.length) {
html += `<div class="intel-item"><span class="label">UPI IDs</span><span class="value">${intel.upi_ids.join(', ')}</span></div>`;
}
if (intel.bank_accounts?.length) {
html += `<div class="intel-item"><span class="label">Bank Accounts</span><span class="value">${intel.bank_accounts.join(', ')}</span></div>`;
}
if (intel.phone_numbers?.length) {
html += `<div class="intel-item"><span class="label">Phone Numbers</span><span class="value">${intel.phone_numbers.join(', ')}</span></div>`;
}
if (intel.ifsc_codes?.length) {
html += `<div class="intel-item"><span class="label">IFSC Codes</span><span class="value">${intel.ifsc_codes.join(', ')}</span></div>`;
}
if (intel.phishing_links?.length) {
html += `<div class="intel-item"><span class="label">Phishing Links</span><span class="value">${intel.phishing_links.length} found</span></div>`;
}
document.getElementById('multiIntelList').innerHTML = html || '<div class="empty-state small">No intelligence extracted yet</div>';
// Update agent notes
if (data.agent_notes) {
document.getElementById('multiAgentNotes').textContent = data.agent_notes;
}
// Update extraction progress
updateExtractionProgress(intel, data);
}
function updateExtractionProgress(intel, data) {
// Calculate extraction confidence (matching backend logic)
// UPI: 0.30, Bank: 0.30, IFSC: 0.20, Phone: 0.10, Links: 0.10
let confidence = 0;
const hasUpi = intel.upi_ids?.length > 0;
const hasBank = intel.bank_accounts?.length > 0;
const hasPhone = intel.phone_numbers?.length > 0;
const hasIfsc = intel.ifsc_codes?.length > 0;
const hasLinks = intel.phishing_links?.length > 0;
if (hasUpi) confidence += 0.30;
if (hasBank) confidence += 0.30;
if (hasIfsc) confidence += 0.20;
if (hasPhone) confidence += 0.10;
if (hasLinks) confidence += 0.10;
const confidencePercent = Math.round(confidence * 100);
const turnCount = data.engagement?.turn_count || state.turnCount;
const maxTurns = 20;
const callbackThreshold = 85; // 0.85 = 85%
// Update progress bar
const progressBar = document.getElementById('extractionProgressBar');
const progressLabel = document.getElementById('extractionProgressLabel');
progressBar.style.width = `${confidencePercent}%`;
progressLabel.textContent = `${confidencePercent}%`;
if (confidencePercent >= callbackThreshold) {
progressBar.classList.add('high');
} else {
progressBar.classList.remove('high');
}
// Update detail items
document.getElementById('hasUpi').textContent = hasUpi ? '✅ UPI' : '❌ UPI';
document.getElementById('hasUpi').className = `detail-item ${hasUpi ? 'extracted' : ''}`;
document.getElementById('hasBank').textContent = hasBank ? '✅ Bank' : '❌ Bank';
document.getElementById('hasBank').className = `detail-item ${hasBank ? 'extracted' : ''}`;
document.getElementById('hasPhone').textContent = hasPhone ? '✅ Phone' : '❌ Phone';
document.getElementById('hasPhone').className = `detail-item ${hasPhone ? 'extracted' : ''}`;
document.getElementById('hasIfsc').textContent = hasIfsc ? '✅ IFSC' : '❌ IFSC';
document.getElementById('hasIfsc').className = `detail-item ${hasIfsc ? 'extracted' : ''}`;
// Update callback status
const callbackBox = document.getElementById('callbackStatusBox');
const callbackIcon = document.getElementById('callbackIcon');
const callbackText = document.getElementById('callbackStatusText');
const callbackDetails = document.getElementById('callbackDetails');
const shouldCallback = confidencePercent >= callbackThreshold || turnCount >= maxTurns;
if (shouldCallback) {
callbackBox.classList.add('triggered');
callbackIcon.textContent = '✅';
if (confidencePercent >= callbackThreshold) {
callbackText.textContent = `CALLBACK TRIGGERED! (${confidencePercent}% ≥ 85%)`;
callbackDetails.textContent = 'High confidence extraction achieved. GUVI callback will be sent with extracted intelligence.';
} else {
callbackText.textContent = `CALLBACK TRIGGERED! (Turn ${turnCount}/${maxTurns})`;
callbackDetails.textContent = 'Maximum turns reached. GUVI callback will be sent with extracted intelligence.';
}
} else {
callbackBox.classList.remove('triggered');
callbackIcon.textContent = '⏳';
callbackText.textContent = `Extracting... (${confidencePercent}% / 85%)`;
callbackDetails.textContent = `Turn ${turnCount}/${maxTurns}. Need more details: ${[
!hasUpi ? 'UPI' : '',
!hasBank ? 'Bank' : '',
!hasPhone ? 'Phone' : '',
!hasIfsc ? 'IFSC' : ''
].filter(x => x).join(', ') || 'Getting more info...'}`;
}
}
function copyConversationHistory() {
navigator.clipboard.writeText(document.getElementById('conversationHistoryPreview').textContent);
showToast('📋 Conversation history copied!', 'info');
}
// ============================================================================
// Callback Test
// ============================================================================
function buildCallbackPayload() {
const sessionId = document.getElementById('callbackSessionId').value;
const scamDetected = document.getElementById('callbackScamDetected').value === 'true';
const totalMessages = parseInt(document.getElementById('callbackTotalMessages').value) || 0;
const bankAccounts = document.getElementById('callbackBankAccounts').value
.split(',').map(s => s.trim()).filter(s => s);
const upiIds = document.getElementById('callbackUpiIds').value
.split(',').map(s => s.trim()).filter(s => s);
const phishingLinks = document.getElementById('callbackPhishingLinks').value
.split(',').map(s => s.trim()).filter(s => s);
const phoneNumbers = document.getElementById('callbackPhoneNumbers').value
.split(',').map(s => s.trim()).filter(s => s);
const keywords = document.getElementById('callbackKeywords').value
.split(',').map(s => s.trim()).filter(s => s);
const agentNotes = document.getElementById('callbackAgentNotes').value;
return {
sessionId: sessionId,
scamDetected: scamDetected,
totalMessagesExchanged: totalMessages,
extractedIntelligence: {
bankAccounts: bankAccounts,
upiIds: upiIds,
phishingLinks: phishingLinks,
phoneNumbers: phoneNumbers,
suspiciousKeywords: keywords,
},
agentNotes: agentNotes,
};
}
function updateCallbackPreview() {
const payload = buildCallbackPayload();
document.getElementById('callbackPreview').textContent = JSON.stringify(payload, null, 2);
}
function generateCallbackPayload() {
if (state.lastResponse) {
const data = state.lastResponse;
document.getElementById('callbackSessionId').value = data.session_id || state.multiSessionId || '';
document.getElementById('callbackScamDetected').value = data.scam_detected ? 'true' : 'false';
document.getElementById('callbackTotalMessages').value = state.conversationHistory.length || data.engagement?.turn_count || 1;
if (data.extracted_intelligence) {
document.getElementById('callbackBankAccounts').value = data.extracted_intelligence.bank_accounts?.join(', ') || '';
document.getElementById('callbackUpiIds').value = data.extracted_intelligence.upi_ids?.join(', ') || '';
document.getElementById('callbackPhishingLinks').value = data.extracted_intelligence.phishing_links?.join(', ') || '';
document.getElementById('callbackPhoneNumbers').value = data.extracted_intelligence.phone_numbers?.join(', ') || '';
document.getElementById('callbackKeywords').value = data.extracted_intelligence.suspicious_keywords?.join(', ') || '';
}
document.getElementById('callbackAgentNotes').value = data.agent_notes || '';
updateCallbackPreview();
showToast('✅ Generated callback from last response', 'success');
} else {
showToast('⚠️ No session data available. Send a message first.', 'error');
}
}
function copyCallbackPayload() {
const payload = buildCallbackPayload();
navigator.clipboard.writeText(JSON.stringify(payload, null, 2));
showToast('📋 Callback payload copied!', 'info');
}
// ============================================================================
// Compliance Tests
// ============================================================================
async function testApiKey() {
const check = document.querySelector('[data-check="api-key"]');
try {
// Test without API key should fail if configured
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'test' }),
});
// Test with API key
const responseWithKey = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': state.apiKey || 'test-key',
},
body: JSON.stringify({ message: 'test' }),
});
// If API key is configured, unauthorized should fail
if (response.status === 401 || responseWithKey.ok) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'pass'); // API key not enforced but endpoint works
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testGuviInput() {
const check = document.querySelector('[data-check="guvi-input"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
sessionId: 'test-guvi-format',
message: {
sender: 'scammer',
text: 'Test GUVI format input',
timestamp: Date.now(),
},
conversationHistory: [],
metadata: {
channel: 'SMS',
language: 'English',
locale: 'IN',
},
}),
});
if (response.ok) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testGuviOutput() {
const check = document.querySelector('[data-check="guvi-output"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
sessionId: 'test-output',
message: {
sender: 'scammer',
text: 'You won 10 lakh! Send OTP now!',
timestamp: Date.now(),
},
conversationHistory: [],
metadata: { channel: 'SMS', language: 'English', locale: 'IN' },
}),
});
const data = await response.json();
// Check for required fields
if (data.status !== undefined && data.reply !== undefined) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testResponseTime() {
const check = document.querySelector('[data-check="response-time"]');
try {
const startTime = Date.now();
await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'Test response time',
}),
});
const responseTime = Date.now() - startTime;
if (responseTime < 2000) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testScamDetect() {
const check = document.querySelector('[data-check="scam-detect"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'URGENT: You won 10 LAKH! Send OTP immediately to claim prize!',
}),
});
const data = await response.json();
if (data.scam_detected === true && data.confidence > 0) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testMultiLang() {
const check = document.querySelector('[data-check="multi-lang"]');
try {
// Test Hindi
const hindiResponse = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'आपने 10 लाख जीते! OTP भेजें!',
}),
});
const hindiData = await hindiResponse.json();
if (hindiData.language_detected && hindiData.language_detected !== 'en') {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testMultiTurn() {
const check = document.querySelector('[data-check="multi-turn"]');
try {
const sessionId = `test-multi-${Date.now()}`;
// First message
await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
sessionId: sessionId,
message: {
sender: 'scammer',
text: 'You won lottery! Send OTP!',
timestamp: Date.now(),
},
conversationHistory: [],
metadata: { channel: 'SMS', language: 'English', locale: 'IN' },
}),
});
// Second message with history
const response2 = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
sessionId: sessionId,
message: {
sender: 'scammer',
text: 'Share your UPI ID now!',
timestamp: Date.now(),
},
conversationHistory: [
{ sender: 'scammer', text: 'You won lottery! Send OTP!', timestamp: Date.now() - 5000 },
{ sender: 'user', text: 'What lottery?', timestamp: Date.now() - 3000 },
],
metadata: { channel: 'SMS', language: 'English', locale: 'IN' },
}),
});
const data2 = await response2.json();
if (data2.engagement?.turn_count > 1 || data2.reply) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testExtractUpi() {
const check = document.querySelector('[data-check="extract-upi"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'Send money to scammer@paytm or fraud@ybl immediately!',
}),
});
const data = await response.json();
if (data.extracted_intelligence?.upi_ids?.length > 0) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testExtractBank() {
const check = document.querySelector('[data-check="extract-bank"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'Transfer to account 12345678901234 IFSC: HDFC0001234 now!',
}),
});
const data = await response.json();
if (data.extracted_intelligence?.bank_accounts?.length > 0 ||
data.extracted_intelligence?.ifsc_codes?.length > 0) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testExtractPhone() {
const check = document.querySelector('[data-check="extract-phone"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'Call us at +919876543210 or 9123456789 immediately!',
}),
});
const data = await response.json();
if (data.extracted_intelligence?.phone_numbers?.length > 0) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
async function testExtractLink() {
const check = document.querySelector('[data-check="extract-link"]');
try {
const response = await fetch(`${state.apiBaseUrl}/api/v1/honeypot/engage`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(state.apiKey ? { 'x-api-key': state.apiKey } : {}),
},
body: JSON.stringify({
message: 'Click here to claim: http://fake-bank-verify.xyz/claim now!',
}),
});
const data = await response.json();
if (data.extracted_intelligence?.phishing_links?.length > 0) {
updateCheckStatus(check, 'pass');
} else {
updateCheckStatus(check, 'fail');
}
} catch (error) {
updateCheckStatus(check, 'fail');
}
}
function updateCheckStatus(checkElement, status) {
checkElement.setAttribute('data-status', status);
const icon = checkElement.querySelector('.check-icon');
if (status === 'pass') {
icon.textContent = '✅';
} else if (status === 'fail') {
icon.textContent = '❌';
} else {
icon.textContent = '⏳';
}
}
async function runAllTests() {
const progressBar = document.getElementById('testProgress');
const progressFill = progressBar.querySelector('.progress-fill');
const progressText = progressBar.querySelector('.progress-text');
progressBar.classList.remove('hidden');
const tests = [
{ name: 'API Key', fn: testApiKey },
{ name: 'GUVI Input', fn: testGuviInput },
{ name: 'GUVI Output', fn: testGuviOutput },
{ name: 'Response Time', fn: testResponseTime },
{ name: 'Scam Detection', fn: testScamDetect },
{ name: 'Multi-Language', fn: testMultiLang },
{ name: 'Multi-Turn', fn: testMultiTurn },
{ name: 'UPI Extraction', fn: testExtractUpi },
{ name: 'Bank Extraction', fn: testExtractBank },
{ name: 'Phone Extraction', fn: testExtractPhone },
{ name: 'Link Detection', fn: testExtractLink },
];
for (let i = 0; i < tests.length; i++) {
progressText.textContent = `Running: ${tests[i].name}...`;
progressFill.style.width = `${((i + 1) / tests.length) * 100}%`;
try {
await tests[i].fn();
} catch (e) {
console.error(`Test ${tests[i].name} failed:`, e);
}
// Small delay between tests
await new Promise(r => setTimeout(r, 500));
}
progressText.textContent = 'All tests completed!';
showToast('✅ All automated tests completed!', 'success');
// Count results
const passed = document.querySelectorAll('[data-status="pass"]').length;
const failed = document.querySelectorAll('[data-status="fail"]').length;
setTimeout(() => {
progressText.textContent = `Results: ${passed} passed, ${failed} failed`;
}, 1000);
}
// ============================================================================
// Utilities
// ============================================================================
function showToast(message, type = 'info') {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `<span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}