File size: 32,874 Bytes
f112559
c13e4ae
 
 
231ae6d
3d5188c
 
 
c13e4ae
91d0941
d9f7e10
 
 
 
3d5188c
 
231ae6d
ee4abc0
3d5188c
 
ee4abc0
d9f7e10
 
 
231ae6d
 
 
 
ee4abc0
 
 
 
 
d9f7e10
91d0941
3d5188c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9f7e10
 
 
 
 
 
3d5188c
d9f7e10
 
3d5188c
c13e4ae
3d5188c
 
 
 
 
 
 
 
 
 
 
 
c13e4ae
 
3d5188c
d9f7e10
3d5188c
 
c13e4ae
662ab87
 
 
 
 
 
 
 
 
 
3d5188c
 
 
c13e4ae
3d5188c
 
 
 
c13e4ae
f112559
3d5188c
662ab87
231ae6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee4abc0
 
 
 
 
 
 
662ab87
 
 
 
ee4abc0
662ab87
ee4abc0
 
 
662ab87
 
 
ee4abc0
 
 
 
662ab87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee4abc0
 
662ab87
 
 
ee4abc0
662ab87
ee4abc0
 
 
 
662ab87
ee4abc0
 
662ab87
 
ee4abc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662ab87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee4abc0
662ab87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee4abc0
 
662ab87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee4abc0
 
 
 
 
 
 
 
 
 
 
 
 
 
662ab87
 
 
 
ee4abc0
662ab87
ee4abc0
 
 
 
 
 
 
 
 
 
 
c13e4ae
 
 
 
 
 
 
 
3d5188c
c13e4ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d5188c
 
231ae6d
 
 
 
 
c13e4ae
3d5188c
c13e4ae
3d5188c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231ae6d
3d5188c
c13e4ae
 
3d5188c
 
 
231ae6d
3d5188c
c13e4ae
3d5188c
c13e4ae
 
 
 
3d5188c
 
c13e4ae
3d5188c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9f7e10
 
 
 
 
 
 
3d5188c
d9f7e10
 
 
 
 
 
 
 
 
 
 
f112559
d9f7e10
 
 
 
 
 
 
 
 
 
c13e4ae
d9f7e10
 
3d5188c
d9f7e10
3d5188c
d9f7e10
 
3d5188c
d9f7e10
 
 
3d5188c
d9f7e10
 
 
 
 
 
3d5188c
d9f7e10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c13e4ae
d9f7e10
c13e4ae
d9f7e10
 
 
 
 
3d5188c
d9f7e10
 
 
 
3d5188c
c13e4ae
d9f7e10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637

// Configuration - Now using local AI simulation (no API required)
const USE_LOCAL_AI = true;
const LOCAL_AI_DELAY = 800; // ms delay to simulate AI thinking
const LOCAL_IMAGE_MODEL = true; // Enable local image generation
// State
let isGenerating = false;
let abortController = null;
let localAiInterval = null;
document.addEventListener('DOMContentLoaded', function() {
    const chatForm = document.getElementById('chatForm');
    const messageInput = document.getElementById('messageInput');
    const chatMessages = document.getElementById('chatMessages');
    const charCount = document.getElementById('charCount');
    const stopButton = document.getElementById('stopButton');
    const sendButton = document.getElementById('sendButton');
    const generateImageBtn = document.getElementById('generateImageBtn');
    const generateImageButton = document.getElementById('generateImageButton');
    const aiStatus = document.getElementById('aiStatus');
    const apiStatus = document.getElementById('apiStatus');
// Character counter
    messageInput.addEventListener('input', function() {
        charCount.textContent = `${this.value.length}/1000`;
    });
    // Image generation button
    if (generateImageBtn) {
        generateImageBtn.addEventListener('click', generateSceneImage);
    }
    // New image generation from last message
    if (generateImageButton) {
        generateImageButton.addEventListener('click', generateImageFromLastMessage);
    }
// Send message
    chatForm.addEventListener('submit', async function(e) {
        e.preventDefault();
        if (isGenerating) {
            stopGeneration();
            return;
        }
        await sendMessage();
    });
    // Stop generation
    stopButton.addEventListener('click', stopGeneration);
    // Allow Shift+Enter for new line, Enter to send
    messageInput.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            if (isGenerating) {
                stopGeneration();
            } else {
                chatForm.dispatchEvent(new Event('submit'));
            }
        }
    });
    // Send message function
    async function sendMessage() {
        const text = messageInput.value.trim();
        if (!text) return;
        // Add user message
        addMessage('user', text);
        messageInput.value = '';
        charCount.textContent = '0/1000';

        // Show typing indicator
        showTyping();
        setGenerating(true);
        updateAPIStatus(true, 'Generating locally...');

        try {
            const characterName = document.getElementById('characterName').textContent;
            const characterRole = document.querySelector('.bg-surface.rounded-xl.p-5 span').textContent;
            const responseLength = document.getElementById('responseLength').value;
            
            // Build system prompt
            const systemPrompt = buildSystemPrompt(characterName, characterRole, responseLength);
            
            // Get conversation history
            const messages = getConversationHistory(systemPrompt);
            
            // Use local AI simulation (no API calls)
            const aiResponse = await generateLocalAIResponse(characterName, messages, responseLength);
            
            removeTyping();
            addMessage('ai', aiResponse);
            setGenerating(false);
            updateAPIStatus(true, 'Response ready');
            
            // Check if AI response suggests image generation
            if (aiResponse.includes('generate an image') || aiResponse.includes('Would you like me to generate')) {
                // Auto-suggest image generation after a delay
                setTimeout(() => {
                    if (confirm(`${characterName} suggested generating an image. Would you like to create one based on your last message?`)) {
                        generateImageFromLastMessage();
                    }
                }, 1500);
            }
        } catch (error) {
            removeTyping();
            console.error('AI Error:', error);
            addMessage('system', `Local AI error: ${error.message}. Using fallback response.`);
            // Fallback to local response
            const fallbackResponse = getFallbackResponse();
            addMessage('ai', fallbackResponse);
            setGenerating(false);
            updateAPIStatus(true, 'Using fallback');
        }
    }
// Generate scene image using local model
    async function generateSceneImage() {
        if (!LOCAL_IMAGE_MODEL) {
            addMessage('system', 'Local image model is not enabled.');
            return;
        }

        // Create and show image generator component
        const generator = document.createElement('image-generator');
        
        // Create a container for the generator in the chat
        const container = document.createElement('div');
        container.className = 'message user animate-message-slide mb-6';
        container.innerHTML = `
            <div class="flex gap-4 justify-end">
                <div class="text-right">
                    <div class="font-medium text-accent mb-2">You</div>
                    <div class="bg-primary/20 rounded-2xl rounded-tr-none p-4">
                        <p><i>Generating an image of the current scene using local AI model...</i></p>
                        <div id="imageGenContainer" style="margin-top: 1rem;"></div>
                    </div>
                    <div class="text-xs text-gray-500 mt-2">${new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
                </div>
                <img src="https://static.photos/people/48x48/10" class="w-12 h-12 rounded-full flex-shrink-0">
            </div>
        `;
        
        chatMessages.appendChild(container);
        const genContainer = container.querySelector('#imageGenContainer');
        genContainer.appendChild(generator);
        
        // Scroll to show
        chatMessages.scrollTop = chatMessages.scrollHeight;
        
        // Trigger generation after a short delay
        setTimeout(() => {
            generator.shadowRoot.querySelector('.generate-btn').click();
        }, 500);
    }
    // Generate image from the most recent message in chat
    async function generateImageFromLastMessage() {
        if (!LOCAL_IMAGE_MODEL) {
            addMessage('system', 'Local image model is not enabled.');
            return;
        }

        // Get the most recent USER message content
        const messages = chatMessages.querySelectorAll('.message.user');
        let lastUserMessage = '';
        let lastUserMessageElement = null;
        
        // Look for the last user message (skip system messages)
        for (let i = messages.length - 1; i >= 0; i--) {
            const msg = messages[i];
            const p = msg.querySelector('p');
            if (p && !p.textContent.includes('Generating an image') && !p.textContent.includes('*Generating image for')) {
                lastUserMessage = p.textContent.trim();
                lastUserMessageElement = msg;
                break;
            }
        }

        if (!lastUserMessage) {
            // If no user message, fallback to last AI message
            const aiMessages = chatMessages.querySelectorAll('.message.ai');
            for (let i = aiMessages.length - 1; i >= 0; i--) {
                const msg = aiMessages[i];
                const p = msg.querySelector('p');
                if (p && !p.textContent.includes('Here\'s an image inspired by')) {
                    lastUserMessage = p.textContent.trim();
                    lastUserMessageElement = msg;
                    break;
                }
            }
        }

        if (!lastUserMessage) {
            lastUserMessage = "A mysterious scene from the conversation";
        }

        // Use the exact last message as prompt
        const prompt = lastUserMessage.substring(0, 200); // Limit length
        
        // Add a message indicating image generation is starting
        const thinkingMsg = addMessage('user', `*Generating image based on your message: "${prompt.substring(0, 60)}..."*`);
        
        // Show typing indicator
        showTyping();
        setGenerating(true);
        updateAPIStatus(true, 'Generating image with AI model...');

        try {
            // Use built-in AI image generator (simulated for now)
            const imageUrl = await generateImageWithLocalAIModel(prompt);
            
            removeTyping();
            setGenerating(false);
            
            // Remove the thinking message
            if (thinkingMsg && thinkingMsg.parentNode) {
                thinkingMsg.parentNode.remove();
            }
            
            // Display the generated image in chat
            displayGeneratedImage(imageUrl, prompt);
            updateAPIStatus(true, 'Image generated');
        } catch (error) {
            removeTyping();
            setGenerating(false);
            addMessage('system', `Image generation failed: ${error.message}. Using fallback.`);
            // Fallback to static image based on prompt
            const fallbackImage = getFallbackImage(prompt);
            if (thinkingMsg && thinkingMsg.parentNode) {
                thinkingMsg.parentNode.remove();
            }
            displayGeneratedImage(fallbackImage, prompt);
            updateAPIStatus(true, 'Used fallback image');
        }
    }

    // Generate image using built-in AI model simulation
    async function generateImageWithLocalAIModel(prompt) {
        // Simulate a local AI image model running in the browser
        // In a real implementation, this would use TensorFlow.js or ONNX Runtime
        // with a model like Stable Diffusion Lite
        
        // For demonstration, we'll use a more sophisticated simulation
        // that generates images based on the prompt content
        
        // Analyze prompt for themes
        const themes = analyzePromptForThemes(prompt);
        const category = themes.category;
        const style = themes.style;
        const seed = Math.floor(Math.random() * 1000) + 1;
        
        // Generate a deterministic image URL based on prompt
        const promptHash = hashString(prompt);
        const imageSeed = (promptHash + seed) % 1000;
        
        // Use static.photos with parameters that match the prompt
        const width = 400;
        const height = 300;
        
        return `https://static.photos/${category}/${width}x${height}/${imageSeed}`;
    }

    // Analyze prompt to determine image category and style
    function analyzePromptForThemes(prompt) {
        const lowerPrompt = prompt.toLowerCase();
        let category = 'abstract';
        let style = 'default';
        
        // Determine category based on keywords
        if (lowerPrompt.includes('dragon') || lowerPrompt.includes('wizard') || 
            lowerPrompt.includes('castle') || lowerPrompt.includes('fantasy')) {
            category = 'fantasy';
        } else if (lowerPrompt.includes('cyber') || lowerPrompt.includes('robot') || 
                   lowerPrompt.includes('future') || lowerPrompt.includes('tech')) {
            category = 'technology';
        } else if (lowerPrompt.includes('space') || lowerPrompt.includes('alien') || 
                   lowerPrompt.includes('star') || lowerPrompt.includes('planet')) {
            category = 'aerial';
        } else if (lowerPrompt.includes('forest') || lowerPrompt.includes('nature') || 
                   lowerPrompt.includes('tree') || lowerPrompt.includes('mountain')) {
            category = 'nature';
        } else if (lowerPrompt.includes('city') || lowerPrompt.includes('building') || 
                   lowerPrompt.includes('urban') || lowerPrompt.includes('street')) {
            category = 'cityscape';
        } else if (lowerPrompt.includes('person') || lowerPrompt.includes('people') || 
                   lowerPrompt.includes('man') || lowerPrompt.includes('woman')) {
            category = 'people';
        } else if (lowerPrompt.includes('viking') || lowerPrompt.includes('warrior') || 
                   lowerPrompt.includes('battle') || lowerPrompt.includes('ancient')) {
            category = 'vintage';
        }
        
        // Determine style based on keywords
        if (lowerPrompt.includes('dark') || lowerPrompt.includes('night') || 
            lowerPrompt.includes('shadow') || lowerPrompt.includes('black')) {
            style = 'dark';
        } else if (lowerPrompt.includes('bright') || lowerPrompt.includes('light') || 
                   lowerPrompt.includes('sun') || lowerPrompt.includes('white')) {
            style = 'white';
        } else if (lowerPrompt.includes('blue') || lowerPrompt.includes('ocean') || 
                   lowerPrompt.includes('sky') || lowerPrompt.includes('water')) {
            style = 'blue';
        } else if (lowerPrompt.includes('green') || lowerPrompt.includes('forest') || 
                   lowerPrompt.includes('nature') || lowerPrompt.includes('plant')) {
            style = 'green';
        }
        
        return { category, style };
    }

    // Simple string hash function
    function hashString(str) {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return Math.abs(hash);
    }

    // Fallback image generation
    function getFallbackImage(prompt) {
        const hash = hashString(prompt);
        const categories = ['abstract', 'nature', 'technology', 'people', 'fantasy', 'cityscape'];
        const category = categories[hash % categories.length];
        const seed = (hash % 999) + 1;
        return `https://static.photos/${category}/400x300/${seed}`;
    }
    // Display generated image in chat
    function displayGeneratedImage(imageUrl, prompt) {
        const messageDiv = document.createElement('div');
        messageDiv.className = 'message ai animate-message-slide mb-6';
        
        const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
        const avatar = document.querySelector('.bg-surface.rounded-xl.p-5 img')?.src || 'https://static.photos/people/48x48/5';
        const characterName = document.getElementById('characterName').textContent;
        
        messageDiv.innerHTML = `
            <div class="flex gap-4">
                <img src="${avatar}" class="w-12 h-12 rounded-full flex-shrink-0">
                <div>
                    <div class="font-medium text-primary mb-1">${characterName}</div>
                    <div class="bg-surface-light rounded-2xl rounded-tl-none p-4 hovered-element">
                        <p class="hovered-element">Here's an image generated from your message using our built-in AI image model:</p>
                        <div class="image-gen-preview mt-3 hovered-element">
                            <img src="${imageUrl}" alt="Generated image" class="rounded-lg hovered-element" style="max-width: 100%; height: auto;">
                        </div>
                        <p class="text-sm text-gray-400 mt-2 hovered-element">Prompt: "${prompt.substring(0, 100)}..."</p>
                    </div>
                    <div class="text-xs text-gray-500 mt-2">${time}</div>
                </div>
            </div>
        `;
        
        chatMessages.appendChild(messageDiv);
        chatMessages.scrollTop = chatMessages.scrollHeight;
        feather.replace();
    }
// Local AI response generation (simulated)
    async function generateLocalAIResponse(characterName, messages, length) {
        return new Promise((resolve) => {
            // Simulate AI thinking time
            setTimeout(() => {
                const lastUserMessage = messages[messages.length - 1]?.content || '';
                const response = generateCharacterResponse(characterName, lastUserMessage, length);
                resolve(response);
            }, LOCAL_AI_DELAY);
        });
    }
    // Character-specific response generation
    function generateCharacterResponse(characterName, userMessage, length) {
        const characterResponses = {
            'Astrid': [
                `*adjusts her robe thoughtfully* Your words, "${userMessage}", resonate with the ancient prophecies. The stars whisper of similar patterns in the Crystal Archives.`,
                `Ah, a curious mind indeed! ${userMessage} reminds me of the time when the Moonstone Amulet revealed its secrets to the chosen one.`,
                `*eyes twinkle with arcane energy* In Eldoria, such questions are pondered by the wisest sages. Let me share what the scrolls reveal about this matter.`,
                `The mystical winds carry echoes of your query. ${userMessage}... yes, I recall an enchantment that dealt with similar concepts in the Whispering Woods.`,
                `*gestures with a glowing staff* By the old gods, your inquiry touches upon forbidden lore. But for you, traveler, I shall reveal what I know.`
            ],
            'Kael': [
                `*takes a drag from his virtual cigarette* "${userMessage}"... that's a loaded question in Neo‑Tokyo. The data points to several possibilities, none of them pretty.`,
                `Hmm. ${userMessage}. Let me check my neural implant's database. Yeah, there's a case file from '48 that matches this pattern.`,
                `*checks his wrist‑holo* You're asking about ${userMessage}? That's corporate‑level intel. But for the right price... I might have some leads.`,
                `In this city, every byte has a price. Your query about "${userMessage}" is no exception. Let me dig through the encrypted channels.`,
                `*cyber‑eye flickers* ${userMessage}... that triggers a security alert. But I know a backdoor into the mainframe that might give us answers.`
            ],
            'Lyra': [
                `*checks star chart* Captain's log: our guest asks, "${userMessage}". This aligns with our recent discovery in the Andromeda sector.`,
                `Fascinating! ${userMessage} is precisely what we encountered near the quantum nebula. The alien flora there exhibited similar properties.`,
                `*adjusts comms headset* On the Aether, we've documented phenomena related to "${userMessage}". Let me pull up the holographic records.`,
                `Your curiosity about ${userMessage} reminds me of the Silicate Entities we met on Kepler‑186f. Their communication patterns were remarkably similar.`,
                `*gestures to the viewport* See that pulsar? It's emitting signals that correlate with your query about "${userMessage}". Coincidence? I think not.`
            ],
            'Ragnar': [
                `*grins, sharpening his axe* By Odin's beard! "${userMessage}" is a question worthy of a true warrior! Let me tell you a tale from the frozen north.`,
                `HA! ${userMessage} reminds me of the time I faced the Ice Giant Jörmund! His roars shook the very mountains with similar intent!`,
                `*drinks from a horn* Your words, "${userMessage}", echo in the great halls of Valhalla! The All‑Father himself would approve of such curiosity!`,
                `A warrior's mind is as sharp as his blade! ${userMessage}... let me consult the rune stones for their ancient wisdom on this matter.`,
                `*slams fist on table* ${userMessage}! A bold query! The skalds will sing of this day when wisdom was sought with such courage!`
            ],
            'Elara': [
                `*a leaf drifts into her hand* The forest whispers of your question: "${userMessage}". The ancient trees have dreamed of similar concepts.`,
                `Gentle one, ${userMessage}... let me consult the spirit of the river. Its flowing waters carry memories of such mysteries.`,
                `*birds gather nearby* Your curiosity about "${userMessage}" is known to the woodland creatures. The fox has seen similar patterns in the moonlit glades.`,
                `The moss on the standing stones tells stories related to ${userMessage}. Let me translate their silent language for you.`,
                `*breathes in the forest air* ${userMessage}... yes, the mycelium network beneath us pulses with knowledge of this. The mushrooms will guide us.`
            ],
            'Victor': [
                `*tinkers with a brass device* "${userMessage}" you say? That's precisely what my latest invention, the Aether‑Oscillograph, was designed to measure!`,
                `Fascinating! ${userMessage} aligns perfectly with the theoretical principles I outlined in my monograph on quantum‑steam dynamics!`,
                `*adjusts his goggles* Your query about "${userMessage}" reminds me of the incident with the Phase‑Shift Engine last Tuesday! Nearly vaporized my laboratory!`,
                `Ah, ${userMessage}! That's elementary, my dear friend! Let me demonstrate with this pocket‑sized Tesla coil and some copper wiring...`,
                `*consults a blueprint* "${userMessage}"... yes, yes! I have schematics for a device that could potentially address that very conundrum!`
            ]
        };

        // Get character-specific responses or generic ones
        const responses = characterResponses[characterName] || [
            `*considers thoughtfully* "${userMessage}"... that's an interesting perspective. Let me reflect on this.`,
            `Ah, your question about ${userMessage} touches upon deep matters. Allow me to share my thoughts.`,
            `*nods slowly* ${userMessage}. Yes, I have experience with similar situations. Here's what I've learned.`,
            `Fascinating inquiry! "${userMessage}" reminds me of something I encountered before. Let me elaborate.`,
            `*pauses for a moment* Your words, "${userMessage}", resonate with me. I believe I can offer some insight.`
        ];

        // Adjust response length
        let response = responses[Math.floor(Math.random() * responses.length)];
        
        if (length === 'short') {
            // Keep it brief
            response = response.split('.')[0] + '.';
        } else if (length === 'detailed') {
            // Make it more detailed
            const details = [
                ' The implications of this are far‑reaching, affecting multiple dimensions of our current situation.',
                ' I recall an ancient text that elaborates further on this very subject, suggesting deeper connections.',
                ' This aligns with the broader patterns we have observed throughout our journey together.',
                ' There are nuances here that warrant careful consideration, as they may reveal hidden truths.',
                ' Let me expand upon this with additional context from my own experiences and observations.'
            ];
            response += details[Math.floor(Math.random() * details.length)];
        }

        // Sometimes include image generation suggestion
        if (Math.random() > 0.7 && LOCAL_IMAGE_MODEL) {
            response += ' *Would you like me to generate an image of this scene?*';
        }

        return response;
    }
// Build system prompt
    function buildSystemPrompt(name, role, length) {
        const lengthMap = {
            short: 'Keep responses brief, 1-2 sentences.',
            medium: 'Respond with 2-4 sentences, descriptive but concise.',
            detailed: 'Respond with detailed, immersive paragraphs (4-6 sentences).'
        };
        
        return `You are ${name}, a ${role}. You are in an immersive roleplay conversation. Stay in character at all times. ${lengthMap[length]} Use expressive language, show emotions, and advance the story. Never break character. If the user asks out-of-character questions, gently steer back to the roleplay.`;
    }

    // Get conversation history
    function getConversationHistory(systemPrompt) {
        const messages = [{ role: 'system', content: systemPrompt }];
        const messageElements = chatMessages.querySelectorAll('.message');
        
        messageElements.forEach(el => {
            const isUser = el.classList.contains('user');
            const content = el.querySelector('p')?.textContent || '';
            if (content && !content.includes('has joined the chat') && !content.includes('Chat cleared')) {
                messages.push({
                    role: isUser ? 'user' : 'assistant',
                    content: content
                });
            }
        });
        
        return messages;
    }
    // Helper functions
    function getMaxTokens() {
        const length = document.getElementById('responseLength').value;
        switch(length) {
            case 'short': return 150;
            case 'detailed': return 400;
            default: return 250;
        }
    }

    function getTemperature() {
        const slider = document.querySelector('input[type="range"]');
        return slider.value / 10;
    }

    function getFallbackResponse() {
        const character = document.getElementById('characterName').textContent;
        const fallbacks = {
            'Astrid': ['*adjusts her robe thoughtfully* The stars align in curious patterns tonight. Your query resonates with an old prophecy I once deciphered in the Crystal Library.'],
            'Kael': '*takes a drag from his virtual cigarette* The data doesn\'t lie, but it doesn\'t tell the whole truth either. In this city, every byte has a price.',
            'Lyra': '*checks star chart* Captain\'s log: we\'re approaching an uncharted nebula. Your question reminds me of the time we first encountered the Silicate Entities.',
            'Ragnar': '*grins, sharpening his axe* By Odin\'s beard! That\'s a tale worth telling over mead. Listen closely, for the winds carry whispers of glory.',
            'Elara': '*a leaf drifts into her hand* The forest speaks of your curiosity. Let me share what the ancient trees have shown me in their dreams.',
            'Victor': '*tinkers with a brass device* Fascinating! That aligns perfectly with my latest invention. Allow me to demonstrate the theoretical principles.'
        };
        
        const generic = [
            "I ponder your words carefully. There's more to this than meets the eye.",
            "Ah, an intriguing proposition! Let me weave that into our ongoing narrative.",
            "*considers thoughtfully* Your perspective adds a new layer to this situation.",
            "The universe holds many mysteries, and your question touches upon one of them.",
            "I must consult my knowledge on this matter. Meanwhile, tell me more of your thoughts."
        ];
        
        return fallbacks[character] 
            ? fallbacks[character][Math.floor(Math.random() * fallbacks[character].length)]
            : generic[Math.floor(Math.random() * generic.length)];
    }

    // UI Functions
    function setGenerating(generating) {
        isGenerating = generating;
        if (generating) {
            stopButton.classList.remove('hidden');
            sendButton.classList.add('hidden');
            if (generateImageBtn) generateImageBtn.disabled = true;
            messageInput.disabled = true;
            aiStatus.textContent = `${document.getElementById('characterName').textContent} is thinking...`;
            updateAPIStatus(true, 'Generating locally');
        } else {
            stopButton.classList.add('hidden');
            sendButton.classList.remove('hidden');
            if (generateImageBtn) generateImageBtn.disabled = false;
            messageInput.disabled = false;
            aiStatus.textContent = `AI is in character. Using local intelligence for immersive roleplay.`;
            abortController = null;
            if (localAiInterval) {
                clearInterval(localAiInterval);
                localAiInterval = null;
            }
        }
    }
function stopGeneration() {
        if (abortController) {
            abortController.abort();
        }
        removeTyping();
        setGenerating(false);
        addMessage('system', 'Response generation stopped.');
        updateAPIStatus(false, 'Stopped by user');
    }

    function updateAPIStatus(connected, message) {
        if (connected) {
            apiStatus.textContent = message;
            apiStatus.className = 'px-2 py-1 rounded-full bg-green-900/30 text-green-400 text-xs';
        } else {
            apiStatus.textContent = message;
            apiStatus.className = 'px-2 py-1 rounded-full bg-yellow-900/30 text-yellow-400 text-xs';
        }
    }
    // Export functions to window
    window.addMessage = function(type, content) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${type} animate-message-slide mb-6`;
        
        const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
        const avatar = type === 'user' 
            ? 'https://static.photos/people/48x48/10' 
            : document.querySelector('.bg-surface.rounded-xl.p-5 img')?.src || 'https://static.photos/people/48x48/5';
        
        const name = type === 'user' ? 'You' : document.getElementById('characterName').textContent;
        const nameColor = type === 'user' ? 'text-accent' : 'text-primary';
        
        messageDiv.innerHTML = `
            <div class="flex gap-4 ${type === 'user' ? 'justify-end' : ''}">
                ${type !== 'user' ? `<img src="${avatar}" class="w-12 h-12 rounded-full flex-shrink-0">` : ''}
                <div class="${type === 'user' ? 'text-right' : ''}">
                    <div class="font-medium ${nameColor} mb-1">${name}</div>
                    <div class="${type === 'user' ? 'bg-primary/20 rounded-2xl rounded-tr-none' : 'bg-surface-light rounded-2xl rounded-tl-none'} p-4">
                        <p>${content}</p>
                    </div>
                    <div class="text-xs text-gray-500 mt-2">${time}</div>
                </div>
                ${type === 'user' ? `<img src="${avatar}" class="w-12 h-12 rounded-full flex-shrink-0">` : ''}
            </div>
        `;
        
        chatMessages.appendChild(messageDiv);
        chatMessages.scrollTop = chatMessages.scrollHeight;
        feather.replace();
    };
function showTyping() {
        const typingDiv = document.createElement('div');
        typingDiv.id = 'typingIndicator';
        typingDiv.className = 'typing-indicator mb-6 flex gap-4';
        typingDiv.innerHTML = `
            <img src="${document.querySelector('.bg-surface.rounded-xl.p-5 img')?.src || 'https://static.photos/people/48x48/5'}" class="w-12 h-12 rounded-full flex-shrink-0">
            <div>
                <div class="font-medium text-primary mb-1">${document.getElementById('characterName').textContent}</div>
                <div class="bg-surface-light rounded-2xl rounded-tl-none p-4 flex gap-1 items-center">
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                    <span class="ml-2 text-sm text-gray-400">Thinking...</span>
                </div>
            </div>
        `;
        chatMessages.appendChild(typingDiv);
        chatMessages.scrollTop = chatMessages.scrollHeight;
    }

    function removeTyping() {
        const typing = document.getElementById('typingIndicator');
        if (typing) typing.remove();
    }
    // Pre‑fill example
    window.switchCharacter = function(name, role, avatar) {
        document.getElementById('characterName').textContent = name;
        const profile = document.querySelector('.bg-surface.rounded-xl.p-5');
        const img = profile.querySelector('img');
        const h2 = profile.querySelector('h2');
        const span = profile.querySelector('span');
        
        img.src = avatar;
        h2.textContent = name;
        span.textContent = role;
        
        addMessage('system', `${name} has joined the chat. Role: ${role}`);
        aiStatus.textContent = `AI is in character as ${name} (${role}). Using local intelligence for immersive roleplay.`;
    };
window.startNewScenario = function() {
        if (confirm('Start a new scenario? Current chat will be cleared.')) {
            chatMessages.innerHTML = '';
            addMessage('system', 'New scenario started. Setting the stage...');
        }
    };

    window.clearChat = function() {
        chatMessages.innerHTML = '';
        addMessage('system', 'Chat cleared. Ready for new adventures!');
    };
    // Initialize API status
    updateAPIStatus(true, 'Local AI Ready');
});