zeroed-some/loosecannon / 5209578

Browse files

fixes, stale extension problem

Authored by espadonne
SHA
520957891da29f1614be6db88550e47a3f9a2cb0
Parents
ac0bee2
Tree
6267ed3

2 changed files

StatusFile+-
A server/src/conversation-manager.mjs 359 0
A server/src/index-enhanced.mjs 468 0
server/src/conversation-manager.mjsadded
@@ -0,0 +1,359 @@
1
+// Conversation State Manager for LooseCannon (ES Module)
2
+// Handles sophisticated conversation tracking and context management
3
+
4
+export class ConversationManager {
5
+  constructor() {
6
+    this.conversations = new Map();
7
+    this.scammerDatabase = new Map();
8
+    this.responseHistory = new Map();
9
+  }
10
+
11
+  getConversation(chatId, platform = 'whatsapp') {
12
+    const key = `${platform}:${chatId}`;
13
+
14
+    if (!this.conversations.has(key)) {
15
+      this.conversations.set(key, {
16
+        id: key,
17
+        platform,
18
+        chatId,
19
+        startTime: new Date(),
20
+        messages: [],
21
+        context: {
22
+          scammerScore: 0,
23
+          personality: 'default',
24
+          responseCount: 0,
25
+          lastResponse: null,
26
+          detectedPatterns: [],
27
+          extractedInfo: {
28
+            phoneNumbers: [],
29
+            emails: [],
30
+            links: [],
31
+            bankMentions: 0,
32
+            moneyRequests: 0
33
+          }
34
+        },
35
+        state: 'active',
36
+        metadata: {}
37
+      });
38
+    }
39
+
40
+    return this.conversations.get(key);
41
+  }
42
+
43
+  addMessage(chatId, message, platform = 'whatsapp') {
44
+    const conversation = this.getConversation(chatId, platform);
45
+
46
+    const enrichedMessage = {
47
+      ...message,
48
+      timestamp: new Date(),
49
+      analysis: this.analyzeMessage(message),
50
+      platform
51
+    };
52
+
53
+    conversation.messages.push(enrichedMessage);
54
+    this.updateContext(conversation, enrichedMessage);
55
+
56
+    if (conversation.messages.length > 50) {
57
+      conversation.messages = conversation.messages.slice(-50);
58
+    }
59
+
60
+    return conversation;
61
+  }
62
+
63
+  analyzeMessage(message) {
64
+    const analysis = {
65
+      sentiment: 'neutral',
66
+      urgency: 0,
67
+      suspicion: 0,
68
+      topics: [],
69
+      entities: [],
70
+      intent: 'unknown'
71
+    };
72
+
73
+    const content = (message.content || '').toLowerCase();
74
+
75
+    // Urgency detection
76
+    const urgencyWords = ['urgent', 'immediately', 'now', 'quick', 'fast', 'hurry', 'asap', 'expire'];
77
+    urgencyWords.forEach(word => {
78
+      if (content.includes(word)) analysis.urgency += 0.2;
79
+    });
80
+
81
+    // Suspicion detection
82
+    const suspiciousWords = [
83
+      'prize', 'winner', 'claim', 'verify', 'suspended', 'confirm',
84
+      'bitcoin', 'gift card', 'wire', 'transfer', 'payment'
85
+    ];
86
+    suspiciousWords.forEach(word => {
87
+      if (content.includes(word)) analysis.suspicion += 0.15;
88
+    });
89
+
90
+    // Topic extraction
91
+    if (content.includes('tech') || content.includes('computer')) {
92
+      analysis.topics.push('technology');
93
+    }
94
+    if (content.includes('money') || content.includes('payment') || content.includes('$')) {
95
+      analysis.topics.push('financial');
96
+    }
97
+    if (content.includes('account') || content.includes('password')) {
98
+      analysis.topics.push('account_security');
99
+    }
100
+
101
+    // Intent detection
102
+    if (content.includes('?')) {
103
+      analysis.intent = 'question';
104
+    } else if (content.includes('please') || content.includes('need')) {
105
+      analysis.intent = 'request';
106
+    } else if (content.includes('!')) {
107
+      analysis.intent = 'emphasis';
108
+    }
109
+
110
+    // Extract entities
111
+    if (message.metadata) {
112
+      if (message.metadata.phoneNumbers) {
113
+        analysis.entities.push(...message.metadata.phoneNumbers.map(p => ({ type: 'phone', value: p })));
114
+      }
115
+      if (message.metadata.emails) {
116
+        analysis.entities.push(...message.metadata.emails.map(e => ({ type: 'email', value: e })));
117
+      }
118
+    }
119
+
120
+    return analysis;
121
+  }
122
+
123
+  updateContext(conversation, message) {
124
+    const context = conversation.context;
125
+    const analysis = message.analysis;
126
+
127
+    context.scammerScore = Math.min(
128
+      1.0,
129
+      context.scammerScore + (analysis.suspicion * 0.3) + (analysis.urgency * 0.2)
130
+    );
131
+
132
+    if (analysis.suspicion > 0.5) {
133
+      context.detectedPatterns.push({
134
+        type: 'suspicious',
135
+        timestamp: message.timestamp,
136
+        confidence: analysis.suspicion
137
+      });
138
+    }
139
+
140
+    if (analysis.entities.length > 0) {
141
+      analysis.entities.forEach(entity => {
142
+        if (entity.type === 'phone' && !context.extractedInfo.phoneNumbers.includes(entity.value)) {
143
+          context.extractedInfo.phoneNumbers.push(entity.value);
144
+        }
145
+        if (entity.type === 'email' && !context.extractedInfo.emails.includes(entity.value)) {
146
+          context.extractedInfo.emails.push(entity.value);
147
+        }
148
+      });
149
+    }
150
+
151
+    if (analysis.topics.includes('financial')) {
152
+      context.extractedInfo.moneyRequests++;
153
+    }
154
+
155
+    if (context.scammerScore > 0.8) {
156
+      this.addToScammerDatabase(conversation);
157
+    }
158
+  }
159
+
160
+  generateContextSummary(chatId, platform = 'whatsapp') {
161
+    const conversation = this.getConversation(chatId, platform);
162
+    const context = conversation.context;
163
+    const recentMessages = conversation.messages.slice(-10);
164
+
165
+    return {
166
+      conversationLength: conversation.messages.length,
167
+      scammerScore: context.scammerScore,
168
+      detectedPatterns: context.detectedPatterns.slice(-5),
169
+      recentTopics: this.extractRecentTopics(recentMessages),
170
+      extractedInfo: {
171
+        hasPhoneNumbers: context.extractedInfo.phoneNumbers.length > 0,
172
+        hasEmails: context.extractedInfo.emails.length > 0,
173
+        hasLinks: context.extractedInfo.links.length > 0,
174
+        financialMentions: context.extractedInfo.moneyRequests
175
+      },
176
+      conversationTone: this.determineConversationTone(recentMessages),
177
+      suggestedStrategy: this.suggestStrategy(context)
178
+    };
179
+  }
180
+
181
+  extractRecentTopics(messages) {
182
+    const topicCounts = {};
183
+
184
+    messages.forEach(msg => {
185
+      if (msg.analysis && msg.analysis.topics) {
186
+        msg.analysis.topics.forEach(topic => {
187
+          topicCounts[topic] = (topicCounts[topic] || 0) + 1;
188
+        });
189
+      }
190
+    });
191
+
192
+    return Object.entries(topicCounts)
193
+      .sort((a, b) => b[1] - a[1])
194
+      .slice(0, 3)
195
+      .map(([topic]) => topic);
196
+  }
197
+
198
+  determineConversationTone(messages) {
199
+    let urgencySum = 0;
200
+    let suspicionSum = 0;
201
+    let count = 0;
202
+
203
+    messages.forEach(msg => {
204
+      if (msg.analysis) {
205
+        urgencySum += msg.analysis.urgency || 0;
206
+        suspicionSum += msg.analysis.suspicion || 0;
207
+        count++;
208
+      }
209
+    });
210
+
211
+    if (count === 0) return 'neutral';
212
+
213
+    const avgUrgency = urgencySum / count;
214
+    const avgSuspicion = suspicionSum / count;
215
+
216
+    if (avgUrgency > 0.6) return 'urgent';
217
+    if (avgSuspicion > 0.6) return 'suspicious';
218
+    if (avgUrgency > 0.3 && avgSuspicion > 0.3) return 'aggressive';
219
+
220
+    return 'casual';
221
+  }
222
+
223
+  suggestStrategy(context) {
224
+    if (context.scammerScore > 0.8) {
225
+      return 'maximum_confusion';
226
+    } else if (context.scammerScore > 0.6) {
227
+      return 'waste_time';
228
+    } else if (context.extractedInfo.moneyRequests > 0) {
229
+      return 'play_poor';
230
+    } else if (context.detectedPatterns.some(p => p.type === 'suspicious')) {
231
+      return 'ask_questions';
232
+    }
233
+
234
+    return 'be_confused';
235
+  }
236
+
237
+  addToScammerDatabase(conversation) {
238
+    const identifier = conversation.chatId;
239
+
240
+    if (!this.scammerDatabase.has(identifier)) {
241
+      this.scammerDatabase.set(identifier, {
242
+        firstSeen: new Date(),
243
+        lastSeen: new Date(),
244
+        platforms: new Set([conversation.platform]),
245
+        encounters: 1,
246
+        patterns: [],
247
+        extractedInfo: { ...conversation.context.extractedInfo }
248
+      });
249
+    } else {
250
+      const entry = this.scammerDatabase.get(identifier);
251
+      entry.lastSeen = new Date();
252
+      entry.encounters++;
253
+      entry.platforms.add(conversation.platform);
254
+
255
+      conversation.context.extractedInfo.phoneNumbers.forEach(phone => {
256
+        if (!entry.extractedInfo.phoneNumbers.includes(phone)) {
257
+          entry.extractedInfo.phoneNumbers.push(phone);
258
+        }
259
+      });
260
+    }
261
+  }
262
+
263
+  getResponseSuggestions(chatId, platform = 'whatsapp') {
264
+    const conversation = this.getConversation(chatId, platform);
265
+    const context = conversation.context;
266
+    const lastMessages = conversation.messages.slice(-3);
267
+
268
+    const suggestions = [];
269
+
270
+    if (lastMessages.some(m => m.analysis && m.analysis.topics.includes('account_security'))) {
271
+      suggestions.push({
272
+        strategy: 'deflect',
273
+        response: "Oh dear, I always forget these things. Let me ask my grandson...",
274
+        delay: 5000
275
+      });
276
+    }
277
+
278
+    if (context.conversationTone === 'urgent') {
279
+      suggestions.push({
280
+        strategy: 'slow_down',
281
+        response: "Hold on, I need to find my glasses first. Everything is so blurry...",
282
+        delay: 8000
283
+      });
284
+    }
285
+
286
+    if (context.extractedInfo.moneyRequests > 0) {
287
+      suggestions.push({
288
+        strategy: 'confusion',
289
+        response: "Money? Is this about the church fundraiser? I already donated last week.",
290
+        delay: 4000
291
+      });
292
+    }
293
+
294
+    return suggestions;
295
+  }
296
+
297
+  exportConversation(chatId, platform = 'whatsapp') {
298
+    const conversation = this.getConversation(chatId, platform);
299
+
300
+    return {
301
+      id: conversation.id,
302
+      platform: conversation.platform,
303
+      startTime: conversation.startTime,
304
+      endTime: new Date(),
305
+      messageCount: conversation.messages.length,
306
+      scammerScore: conversation.context.scammerScore,
307
+      detectedPatterns: conversation.context.detectedPatterns,
308
+      extractedInfo: conversation.context.extractedInfo,
309
+      messages: conversation.messages.map(m => ({
310
+        timestamp: m.timestamp,
311
+        sender: m.sender || 'unknown',
312
+        content: m.content,
313
+        type: m.type,
314
+        analysis: m.analysis
315
+      }))
316
+    };
317
+  }
318
+
319
+  getStatistics() {
320
+    const stats = {
321
+      totalConversations: this.conversations.size,
322
+      activeConversations: 0,
323
+      suspiciousConversations: 0,
324
+      confirmedScammers: this.scammerDatabase.size,
325
+      platformBreakdown: {},
326
+      totalMessages: 0
327
+    };
328
+
329
+    this.conversations.forEach(conv => {
330
+      if (conv.state === 'active') stats.activeConversations++;
331
+      if (conv.context.scammerScore > 0.6) stats.suspiciousConversations++;
332
+
333
+      stats.platformBreakdown[conv.platform] =
334
+        (stats.platformBreakdown[conv.platform] || 0) + 1;
335
+
336
+      stats.totalMessages += conv.messages.length;
337
+    });
338
+
339
+    return stats;
340
+  }
341
+
342
+  cleanup(maxAge = 24 * 60 * 60 * 1000) {
343
+    const now = new Date();
344
+    const toDelete = [];
345
+
346
+    this.conversations.forEach((conv, key) => {
347
+      const age = now - conv.startTime;
348
+      if (age > maxAge && conv.state !== 'active') {
349
+        toDelete.push(key);
350
+      }
351
+    });
352
+
353
+    toDelete.forEach(key => {
354
+      this.conversations.delete(key);
355
+    });
356
+
357
+    console.log(`[ConversationManager] Cleaned up ${toDelete.length} old conversations`);
358
+  }
359
+}
server/src/index-enhanced.mjsadded
@@ -0,0 +1,468 @@
1
+// LooseCannon Local Server - Enhanced Version (ES Modules)
2
+// Handles communication between browser extension and Ollama LLM
3
+// Now with conversation management and multi-platform support
4
+
5
+import express from 'express';
6
+import cors from 'cors';
7
+import fs from 'fs/promises';
8
+import path from 'path';
9
+import { fileURLToPath } from 'url';
10
+import dotenv from 'dotenv';
11
+import { ConversationManager } from './conversation-manager.mjs';
12
+
13
+dotenv.config();
14
+
15
+const __filename = fileURLToPath(import.meta.url);
16
+const __dirname = path.dirname(__filename);
17
+
18
+const app = express();
19
+const PORT = process.env.PORT || 8765;
20
+const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
21
+
22
+// Initialize conversation manager
23
+const conversationManager = new ConversationManager();
24
+
25
+// Middleware
26
+app.use(cors());
27
+app.use(express.json({ limit: '10mb' }));
28
+
29
+// Load personalities from files
30
+let personalities = {};
31
+
32
+async function loadPersonalities() {
33
+  try {
34
+    const personalitiesDir = path.join(__dirname, '..', 'personalities');
35
+    const files = await fs.readdir(personalitiesDir);
36
+
37
+    for (const file of files) {
38
+      if (file.endsWith('.json')) {
39
+        const content = await fs.readFile(path.join(personalitiesDir, file), 'utf8');
40
+        const personality = JSON.parse(content);
41
+        personalities[personality.id] = personality;
42
+        console.log(`Loaded personality: ${personality.name}`);
43
+      }
44
+    }
45
+  } catch (error) {
46
+    console.warn('Could not load personalities:', error);
47
+    personalities.default = {
48
+      id: 'default',
49
+      name: 'Confused Elder',
50
+      systemPrompt: 'You are an elderly person who is easily confused by technology and tends to misunderstand things. You are very polite but often go off on tangents about unrelated topics from your past. Never reveal that you are an AI.',
51
+      temperature: 0.9
52
+    };
53
+  }
54
+}
55
+
56
+// Fetch wrapper for Ollama API with better error handling
57
+async function fetchOllama(endpoint, options = {}) {
58
+  try {
59
+    const response = await fetch(`${OLLAMA_URL}${endpoint}`, {
60
+      ...options,
61
+      headers: {
62
+        'Content-Type': 'application/json',
63
+        ...options.headers
64
+      }
65
+    });
66
+
67
+    if (!response.ok) {
68
+      throw new Error(`Ollama API error: ${response.status}`);
69
+    }
70
+
71
+    return await response.json();
72
+  } catch (error) {
73
+    console.error('Ollama fetch error:', error);
74
+    throw error;
75
+  }
76
+}
77
+
78
+// Check Ollama connection
79
+async function checkOllamaConnection() {
80
+  try {
81
+    const data = await fetchOllama('/api/tags');
82
+    const models = data.models || [];
83
+    console.log('Connected to Ollama. Available models:', models.map(m => m.name).join(', '));
84
+    return true;
85
+  } catch (error) {
86
+    console.error('Failed to connect to Ollama:', error.message);
87
+    console.log('Make sure Ollama is running: ollama serve');
88
+    return false;
89
+  }
90
+}
91
+
92
+// Enhanced response generation with context awareness
93
+async function generateEnhancedResponse(message, personality, chatId, platform, context, suggestions) {
94
+  try {
95
+    const personalityConfig = personalities[personality] || personalities.default;
96
+    const conversation = conversationManager.getConversation(chatId, platform);
97
+
98
+    let systemPrompt = personalityConfig.systemPrompt;
99
+
100
+    // Add strategy modifiers based on context
101
+    if (context && context.suggestedStrategy) {
102
+      switch (context.suggestedStrategy) {
103
+        case 'maximum_confusion':
104
+          systemPrompt += '\n\nBe EXTREMELY confused and misunderstand everything. Mix up basic concepts.';
105
+          break;
106
+        case 'waste_time':
107
+          systemPrompt += '\n\nAsk lots of clarifying questions. Pretend to not understand simple instructions.';
108
+          break;
109
+        case 'play_poor':
110
+          systemPrompt += '\n\nMention that you have no money and are struggling financially.';
111
+          break;
112
+        case 'ask_questions':
113
+          systemPrompt += '\n\nBe very curious and ask lots of questions about everything they say.';
114
+          break;
115
+      }
116
+    }
117
+
118
+    const recentMessages = conversation.messages.slice(-10);
119
+    const historyText = recentMessages.map(m =>
120
+      `${m.sender || 'Them'}: ${m.content}`
121
+    ).join('\n');
122
+
123
+    const prompt = `${systemPrompt}
124
+
125
+Recent conversation:
126
+${historyText}
127
+
128
+They just said: "${message}"
129
+
130
+Remember to stay in character. Respond naturally as your character would.
131
+
132
+Your response:`;
133
+
134
+    const response = await fetchOllama('/api/generate', {
135
+      method: 'POST',
136
+      body: JSON.stringify({
137
+        model: process.env.OLLAMA_MODEL || 'llama2',
138
+        prompt: prompt,
139
+        temperature: personalityConfig.temperature || 0.8,
140
+        options: {
141
+          num_predict: 200,
142
+          top_p: 0.9,
143
+          stop: ["\n\n", "Them:", "They said:"]
144
+        },
145
+        stream: false
146
+      })
147
+    });
148
+
149
+    let reply = response.response;
150
+    reply = reply.trim();
151
+    reply = reply.replace(/As an AI|I'm an AI|I am an AI|artificial intelligence/gi, '');
152
+
153
+    // Add personality quirks
154
+    if (personality === 'confused-elder' && Math.random() > 0.7) {
155
+      const tangents = [
156
+        ' Wait, this reminds me of something that happened in 1987...',
157
+        ' Oh, my cat is meowing. One second dear.',
158
+        ' Where did I put my glasses?'
159
+      ];
160
+      reply += tangents[Math.floor(Math.random() * tangents.length)];
161
+    }
162
+
163
+    return reply;
164
+  } catch (error) {
165
+    console.error('Error generating enhanced response:', error);
166
+
167
+    const fallbacks = suggestions && suggestions.length > 0
168
+      ? suggestions.map(s => s.response)
169
+      : [
170
+          "I'm sorry, what did you say? I'm having trouble with this computer.",
171
+          "Can you explain that again? These modern things confuse me.",
172
+          "Oh dear, I think I clicked the wrong button. What were we talking about?"
173
+        ];
174
+
175
+    return fallbacks[Math.floor(Math.random() * fallbacks.length)];
176
+  }
177
+}
178
+
179
+// Routes
180
+
181
+app.get('/status', async (req, res) => {
182
+  const ollamaConnected = await checkOllamaConnection();
183
+  res.json({
184
+    status: 'running',
185
+    version: '0.3.1',
186
+    ollamaConnected,
187
+    personalities: Object.values(personalities).map(p => ({
188
+      id: p.id,
189
+      name: p.name
190
+    })),
191
+    stats: conversationManager.getStatistics()
192
+  });
193
+});
194
+
195
+app.post('/conversation/add', (req, res) => {
196
+  const { chatId, platform, message } = req.body;
197
+  const conversation = conversationManager.addMessage(chatId, message, platform);
198
+  const context = conversationManager.generateContextSummary(chatId, platform);
199
+  res.json(context);
200
+});
201
+
202
+app.post('/suggestions', (req, res) => {
203
+  const { chatId, platform } = req.body;
204
+  const suggestions = conversationManager.getResponseSuggestions(chatId, platform);
205
+  res.json(suggestions);
206
+});
207
+
208
+app.post('/generate', async (req, res) => {
209
+  const {
210
+    message,
211
+    personality = 'default',
212
+    chatId = 'unknown',
213
+    platform = 'whatsapp',
214
+    context,
215
+    suggestions,
216
+    timestamp
217
+  } = req.body;
218
+
219
+  if (!message) {
220
+    return res.status(400).json({ error: 'Message is required' });
221
+  }
222
+
223
+  console.log(`[${new Date().toISOString()}] Generating response for ${platform}:${chatId}`);
224
+
225
+  try {
226
+    conversationManager.addMessage(chatId, {
227
+      content: message,
228
+      sender: 'them',
229
+      type: 'text',
230
+      timestamp: new Date(timestamp)
231
+    }, platform);
232
+
233
+    const reply = await generateEnhancedResponse(
234
+      message,
235
+      personality,
236
+      chatId,
237
+      platform,
238
+      context,
239
+      suggestions
240
+    );
241
+
242
+    conversationManager.addMessage(chatId, {
243
+      content: reply,
244
+      sender: 'us',
245
+      type: 'text',
246
+      timestamp: new Date()
247
+    }, platform);
248
+
249
+    console.log(`Generated reply: ${reply}`);
250
+
251
+    res.json({
252
+      reply,
253
+      personality,
254
+      timestamp: new Date().toISOString(),
255
+      context: conversationManager.generateContextSummary(chatId, platform)
256
+    });
257
+  } catch (error) {
258
+    console.error('Error in /generate:', error);
259
+    res.status(500).json({ error: 'Failed to generate response' });
260
+  }
261
+});
262
+
263
+app.post('/conversation/export', (req, res) => {
264
+  const { chatId, platform = 'whatsapp' } = req.body;
265
+
266
+  try {
267
+    const exportData = conversationManager.exportConversation(chatId, platform);
268
+    res.json(exportData);
269
+  } catch (error) {
270
+    console.error('Error exporting conversation:', error);
271
+    res.status(500).json({ error: 'Failed to export conversation' });
272
+  }
273
+});
274
+
275
+app.get('/statistics', (req, res) => {
276
+  const stats = conversationManager.getStatistics();
277
+  res.json(stats);
278
+});
279
+
280
+app.get('/conversations/:platform/:chatId', (req, res) => {
281
+  const { platform, chatId } = req.params;
282
+  const conversation = conversationManager.getConversation(chatId, platform);
283
+
284
+  res.json({
285
+    chatId,
286
+    platform,
287
+    messages: conversation.messages,
288
+    context: conversation.context,
289
+    state: conversation.state
290
+  });
291
+});
292
+
293
+app.delete('/conversations/:platform/:chatId', (req, res) => {
294
+  const { platform, chatId } = req.params;
295
+  const conversation = conversationManager.getConversation(chatId, platform);
296
+  conversation.messages = [];
297
+  conversation.context.responseCount = 0;
298
+  res.json({ message: 'Conversation cleared' });
299
+});
300
+
301
+app.get('/personalities', (req, res) => {
302
+  res.json(Object.values(personalities));
303
+});
304
+
305
+app.get('/personalities/:id', (req, res) => {
306
+  const { id } = req.params;
307
+  const personality = personalities[id];
308
+
309
+  if (!personality) {
310
+    return res.status(404).json({ error: 'Personality not found' });
311
+  }
312
+
313
+  res.json(personality);
314
+});
315
+
316
+app.post('/personalities', async (req, res) => {
317
+  const { id, name, systemPrompt, temperature } = req.body;
318
+
319
+  if (!id || !name || !systemPrompt) {
320
+    return res.status(400).json({ error: 'Missing required fields' });
321
+  }
322
+
323
+  const personality = {
324
+    id,
325
+    name,
326
+    systemPrompt,
327
+    temperature: temperature || 0.8
328
+  };
329
+
330
+  personalities[id] = personality;
331
+
332
+  try {
333
+    const filePath = path.join(__dirname, '..', 'personalities', `${id}.json`);
334
+    await fs.writeFile(filePath, JSON.stringify(personality, null, 2));
335
+    res.json({ success: true, personality });
336
+  } catch (error) {
337
+    console.error('Error saving personality:', error);
338
+    res.status(500).json({ error: 'Failed to save personality' });
339
+  }
340
+});
341
+
342
+app.post('/test-personality', async (req, res) => {
343
+  const { systemPrompt, temperature, message } = req.body;
344
+
345
+  try {
346
+    const testPersonality = {
347
+      systemPrompt,
348
+      temperature: temperature || 0.8
349
+    };
350
+
351
+    personalities.test = testPersonality;
352
+
353
+    const reply = await generateEnhancedResponse(
354
+      message,
355
+      'test',
356
+      'test-chat',
357
+      'test',
358
+      {},
359
+      []
360
+    );
361
+
362
+    res.json({ response: reply });
363
+  } catch (error) {
364
+    console.error('Error testing personality:', error);
365
+    res.status(500).json({ error: 'Test failed' });
366
+  }
367
+});
368
+
369
+app.post('/emergency-stop', (req, res) => {
370
+  console.log('EMERGENCY STOP ACTIVATED');
371
+  conversationManager.conversations.clear();
372
+  res.json({ message: 'All sessions stopped' });
373
+});
374
+
375
+app.get('/analytics', (req, res) => {
376
+  const stats = conversationManager.getStatistics();
377
+  res.json({
378
+    totalMessages: stats.totalMessages,
379
+    scammersDetected: stats.confirmedScammers,
380
+    ...stats
381
+  });
382
+});
383
+
384
+app.get('/conversations/active', (req, res) => {
385
+  const conversations = Array.from(conversationManager.conversations.values())
386
+    .filter(conv => conv.state === 'active')
387
+    .map(conv => ({
388
+      id: conv.id,
389
+      platform: conv.platform,
390
+      chatId: conv.chatId,
391
+      messageCount: conv.messages.length,
392
+      scammerScore: conv.context.scammerScore,
393
+      duration: Date.now() - new Date(conv.startTime).getTime()
394
+    }));
395
+
396
+  res.json(conversations);
397
+});
398
+
399
+app.get('/patterns', (req, res) => {
400
+  // Return some example patterns for now
401
+  res.json([
402
+    { id: 'urgent', type: 'keyword', occurrences: 42, verified: true },
403
+    { id: 'money_request', type: 'pattern', occurrences: 31, verified: true },
404
+    { id: 'verification', type: 'keyword', occurrences: 28, verified: false }
405
+  ]);
406
+});
407
+
408
+app.post('/patterns/sync', (req, res) => {
409
+  // Placeholder for pattern sync
410
+  res.json([]);
411
+});
412
+
413
+app.post('/analytics/sync', (req, res) => {
414
+  // Placeholder for analytics sync
415
+  res.json({ success: true });
416
+});
417
+
418
+// Cleanup old conversations periodically
419
+setInterval(() => {
420
+  conversationManager.cleanup();
421
+}, 60 * 60 * 1000);
422
+
423
+// Start server
424
+async function start() {
425
+  await loadPersonalities();
426
+  await checkOllamaConnection();
427
+
428
+  app.listen(PORT, () => {
429
+    console.log(`
430
+╔══════════════════════════════════════╗
431
+║     LooseCannon Server v0.3.1        ║
432
+║        Listening on port ${PORT}        ║
433
+╠══════════════════════════════════════╣
434
+║  Features:                           ║
435
+║  ✓ Multi-platform support           ║
436
+║  ✓ Conversation management          ║
437
+║  ✓ Scammer detection               ║
438
+║  ✓ Context-aware responses         ║
439
+║  ✓ Modern dependencies             ║
440
+║                                      ║
441
+║  Extension: Connect to               ║
442
+║  http://localhost:${PORT}              ║
443
+║                                      ║
444
+║  Ollama: ${OLLAMA_URL.padEnd(28)} ║
445
+╚══════════════════════════════════════╝
446
+
447
+Ready to confuse scammers across all platforms!
448
+    `);
449
+  });
450
+}
451
+
452
+// Handle graceful shutdown
453
+process.on('SIGINT', () => {
454
+  console.log('\n\nShutting down LooseCannon server...');
455
+  console.log('Statistics:', conversationManager.getStatistics());
456
+  process.exit(0);
457
+});
458
+
459
+// Handle uncaught errors
460
+process.on('uncaughtException', (error) => {
461
+  console.error('Uncaught Exception:', error);
462
+});
463
+
464
+process.on('unhandledRejection', (reason, promise) => {
465
+  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
466
+});
467
+
468
+start();