From 2cb54d6cad720d0e9dd817da01c6ace159f5115c Mon Sep 17 00:00:00 2001 From: Krikorios <99836218+Krikorios@users.noreply.github.com> Date: Sun, 25 May 2025 03:35:50 +0300 Subject: [PATCH] FINAL --- index.html | 1285 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 1042 insertions(+), 243 deletions(-) diff --git a/index.html b/index.html index bccf62a..990ee13 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - Ollama Vision Chat Pro + Ollama Vision Chat Pro - Enhanced +
+
FPS: --
+
Memory: --MB
+
@@ -756,8 +999,9 @@
Model: Loading...
-
- Response time: --ms +
+ Response: --ms + Images: 0
@@ -766,11 +1010,13 @@ +
+
@@ -782,12 +1028,12 @@
-
Settings
+
βš™οΈ Settings
-
Connection
+
πŸ”— Connection
@@ -798,7 +1044,9 @@ + +
@@ -806,13 +1054,20 @@
+
+ + +
+ 30s +
+
-
Camera
+
πŸ“· Camera
- +
10s
@@ -827,18 +1082,34 @@
+
+ + +
+
+ + +
-
AI Behavior
+
πŸ€– AI Behavior
- +
@@ -846,8 +1117,50 @@ +
+
+ + +
+
+ + +
+
+ +
+
⚑ Performance
+
+ + +
+
+ + +
+
+ + +
@@ -856,18 +1169,27 @@
-

Vision Chat Pro

-

AI that can see and understand your world

+

πŸŽ₯ Vision Chat Pro

+

AI that can see and understand your world in real-time

- - + + +
- πŸŽ‰ Welcome to Vision Chat Pro! I can see through your camera and help with anything you need. Try asking me about what I can see, or just have a conversation! + πŸŽ‰ Welcome to Enhanced Vision Chat Pro! I'm your AI assistant with advanced vision capabilities. I can see through your camera and help with: +

+ β€’ πŸ“Š Analyzing images and scenes + β€’ πŸ“– Reading text and documents + β€’ 🎨 Providing creative feedback + β€’ 🏠 Organizing and decorating spaces + β€’ πŸ”§ Technical troubleshooting +

+ Just start chatting and I'll help you with whatever you need!
@@ -877,32 +1199,35 @@
+ AI is thinking...
- - - - - + + + + + +
+
@@ -922,16 +1247,20 @@ this.statusText = document.getElementById('statusText'); this.typingIndicator = document.getElementById('typingIndicator'); - // Settings + // Enhanced Settings this.ollamaUrl = 'http://localhost:11434'; this.modelName = 'llava'; - this.systemPrompt = 'You are a helpful AI assistant with vision capabilities.'; + this.systemPrompt = 'You are a helpful AI assistant with advanced vision capabilities.'; this.contextMemory = 10; this.imageQuality = 0.8; + this.timeout = 30000; + this.responseStyle = 'casual'; + this.cameraSource = 'user'; - // State + // Enhanced State this.isConnected = false; this.captureIntervalId = null; + this.performanceIntervalId = null; this.lastCaptureTime = 0; this.currentImageData = null; this.messageHistory = []; @@ -939,45 +1268,91 @@ this.isDarkTheme = false; this.isRecording = false; this.lastResponseTime = 0; + this.imageCount = 0; + this.responseCache = new Map(); + this.isVoiceMode = false; + this.recognition = null; + this.synthesis = null; + this.performanceStats = { + fps: 0, + memory: 0, + lastFrameTime: 0, + frameCount: 0 + }; this.init(); } async init() { + console.log('πŸš€ Initializing Enhanced Vision Chat Pro...'); await this.setupCamera(); this.setupEventListeners(); this.loadSettings(); + this.initializeVoice(); this.checkOllamaConnection(); this.startAutoCapture(); this.initializeTheme(); + this.showNotification('βœ… Enhanced Vision Chat Pro initialized successfully!', 'success'); + console.log('βœ… Initialization complete!'); } async setupCamera() { try { - const stream = await navigator.mediaDevices.getUserMedia({ + const constraints = { video: { width: { ideal: 1920 }, height: { ideal: 1080 }, - facingMode: 'user' + facingMode: this.cameraSource, + frameRate: { ideal: 30 } } - }); + }; + const stream = await navigator.mediaDevices.getUserMedia(constraints); this.video.srcObject = stream; this.video.addEventListener('loadedmetadata', () => { this.canvas.width = this.video.videoWidth; this.canvas.height = this.video.videoHeight; + console.log(`πŸ“Ή Camera initialized: ${this.video.videoWidth}x${this.video.videoHeight}`); }); - this.addMessage('system', 'πŸ“Ή Camera connected successfully!'); + this.addMessage('success', 'πŸ“Ή Camera connected successfully with enhanced features!'); } catch (error) { - console.error('Camera setup failed:', error); + console.error('❌ Camera setup failed:', error); this.addMessage('error', '❌ Failed to access camera. Please check permissions and try again.'); } } + initializeVoice() { + // Speech Recognition + if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + this.recognition = new SpeechRecognition(); + this.recognition.continuous = false; + this.recognition.interimResults = false; + this.recognition.lang = 'en-US'; + + this.recognition.onresult = (event) => { + const transcript = event.results[0][0].transcript; + this.messageInput.value = transcript; + this.messageInput.focus(); + this.showNotification('🎀 Voice input captured!'); + }; + + this.recognition.onerror = (event) => { + console.error('Speech recognition error:', event.error); + this.showNotification('❌ Voice recognition failed', 'error'); + }; + } + + // Speech Synthesis + if ('speechSynthesis' in window) { + this.synthesis = window.speechSynthesis; + } + } + setupEventListeners() { - // Send message + // Enhanced send message this.sendBtn.addEventListener('click', () => this.sendMessage()); this.messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -986,27 +1361,30 @@ } }); - // Auto-resize textarea + // Enhanced auto-resize textarea this.messageInput.addEventListener('input', () => { this.messageInput.style.height = 'auto'; - this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px'; + this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 140) + 'px'; }); - // Quick actions + // Enhanced quick actions document.querySelectorAll('.quick-action').forEach(btn => { btn.addEventListener('click', () => { this.messageInput.value = btn.dataset.prompt; this.messageInput.focus(); this.messageInput.style.height = 'auto'; this.messageInput.style.height = this.messageInput.scrollHeight + 'px'; + this.showNotification('πŸ“ Quick prompt loaded!'); }); }); - // Control buttons + // Enhanced control buttons document.getElementById('captureBtn').addEventListener('click', () => { this.captureFrame(); this.showImagePreview(); - this.addMessage('system', 'πŸ“Έ Photo captured!'); + this.addMessage('system', 'πŸ“Έ High-quality photo captured!'); + this.imageCount++; + this.updateImageCount(); }); document.getElementById('pauseBtn').addEventListener('click', () => { @@ -1033,12 +1411,53 @@ this.toggleFullscreen(); }); - // Theme toggle + document.getElementById('perfBtn').addEventListener('click', () => { + this.togglePerformanceMonitor(); + }); + + document.getElementById('voiceBtn').addEventListener('click', () => { + this.toggleVoiceMode(); + }); + + // Enhanced theme toggle document.getElementById('themeToggle').addEventListener('click', () => { this.toggleTheme(); }); - // Settings handlers + // Voice input button + document.getElementById('micBtn').addEventListener('click', () => { + this.startVoiceInput(); + }); + + // Enhanced settings handlers + this.setupSettingsListeners(); + + // Enhanced header buttons + document.getElementById('voiceToggle').addEventListener('click', () => { + this.toggleVoiceMode(); + }); + + document.getElementById('modeBtn').addEventListener('click', () => { + this.cycleChatMode(); + }); + + document.getElementById('shareBtn').addEventListener('click', () => { + this.shareChat(); + }); + + // Emoji button + document.getElementById('emojiBtn').addEventListener('click', () => { + this.openEmojiPicker(); + }); + + // Attach button + document.getElementById('attachBtn').addEventListener('click', () => { + this.attachImage(); + }); + } + + setupSettingsListeners() { + // Connection settings document.getElementById('ollamaUrl').addEventListener('change', (e) => { this.ollamaUrl = e.target.value; this.saveSettings(); @@ -1062,6 +1481,13 @@ this.updateModelInfo(); }); + document.getElementById('timeout').addEventListener('input', (e) => { + this.timeout = parseInt(e.target.value) * 1000; + document.getElementById('timeoutValue').textContent = e.target.value; + this.saveSettings(); + }); + + // Camera settings document.getElementById('autoCapture').addEventListener('change', (e) => { if (e.target.checked) { this.startAutoCapture(); @@ -1085,6 +1511,13 @@ this.saveSettings(); }); + document.getElementById('cameraSource').addEventListener('change', (e) => { + this.cameraSource = e.target.value; + this.saveSettings(); + this.setupCamera(); // Restart camera with new source + }); + + // AI settings document.getElementById('systemPrompt').addEventListener('change', (e) => { this.systemPrompt = e.target.value; this.saveSettings(); @@ -1095,20 +1528,38 @@ this.saveSettings(); }); - // Header buttons - document.getElementById('voiceBtn').addEventListener('click', () => { - this.toggleVoiceMode(); + document.getElementById('responseStyle').addEventListener('change', (e) => { + this.responseStyle = e.target.value; + this.saveSettings(); }); - document.getElementById('modeBtn').addEventListener('click', () => { - this.toggleChatMode(); + // Performance settings + document.getElementById('enablePerformance').addEventListener('change', (e) => { + if (e.target.checked) { + this.startPerformanceMonitoring(); + } else { + this.stopPerformanceMonitoring(); + } + this.saveSettings(); + }); + + document.getElementById('cacheResponses').addEventListener('change', (e) => { + if (!e.target.checked) { + this.responseCache.clear(); + } + this.saveSettings(); }); } async checkOllamaConnection() { try { this.setConnectionStatus('connecting'); - const response = await fetch(`${this.ollamaUrl}/api/tags`); + const controller = new AbortController(); + setTimeout(() => controller.abort(), this.timeout); + + const response = await fetch(`${this.ollamaUrl}/api/tags`, { + signal: controller.signal + }); if (response.ok) { const data = await response.json(); @@ -1120,19 +1571,25 @@ model.name.includes(this.modelName) ); - if (!hasCurrentModel) { + if (!hasCurrentModel && this.availableModels.length > 0) { this.addMessage('system', `⚠️ Model "${this.modelName}" not found. Available models: ${this.availableModels.map(m => m.name).join(', ')}`); this.setConnectionStatus('warning'); } this.updateModelInfo(); + this.showNotification('βœ… Connected to Ollama successfully!'); } else { throw new Error('Connection failed'); } } catch (error) { - console.error('Ollama connection failed:', error); + console.error('❌ Ollama connection failed:', error); this.setConnectionStatus('disconnected'); - this.addMessage('error', `❌ Failed to connect to Ollama at ${this.ollamaUrl}. Make sure Ollama is running.`); + if (error.name === 'AbortError') { + this.addMessage('error', `❌ Connection timeout after ${this.timeout/1000}s. Please check if Ollama is running.`); + } else { + this.addMessage('error', `❌ Failed to connect to Ollama at ${this.ollamaUrl}. Make sure Ollama is running.`); + } + this.showNotification('❌ Failed to connect to Ollama', 'error'); } } @@ -1142,7 +1599,7 @@ switch (status) { case 'connected': this.statusDot.classList.add('connected'); - this.statusText.textContent = 'Connected'; + this.statusText.textContent = 'Connected & Ready'; this.isConnected = true; break; case 'connecting': @@ -1164,12 +1621,16 @@ document.getElementById('currentModel').textContent = this.modelName; } + updateImageCount() { + document.getElementById('imageCount').textContent = this.imageCount; + } + updateAvailableModels() { const select = document.getElementById('modelName'); const customOption = select.querySelector('option[value="custom"]'); // Clear existing model options (except predefined ones) - const predefinedValues = ['llava', 'llava:7b', 'llava:13b', 'bakllava', 'custom']; + const predefinedValues = ['llava', 'llava:7b', 'llava:13b', 'llava:34b', 'bakllava', 'moondream', 'custom']; Array.from(select.options).forEach(option => { if (!predefinedValues.includes(option.value)) { option.remove(); @@ -1181,12 +1642,20 @@ if (!predefinedValues.includes(model.name)) { const option = document.createElement('option'); option.value = model.name; - option.textContent = model.name; + option.textContent = `${model.name} (${this.formatBytes(model.size)})`; select.insertBefore(option, customOption); } }); } + formatBytes(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + captureFrame() { if (!this.video.videoWidth || !this.video.videoHeight) return null; @@ -1207,7 +1676,7 @@ setTimeout(() => { preview.classList.remove('show'); - }, 3000); + }, 4000); } } @@ -1247,9 +1716,60 @@ if (autoCapture.checked) { this.startAutoCapture(); this.addMessage('system', '▢️ Auto-capture resumed'); + this.showNotification('▢️ Auto-capture resumed'); } else { this.stopAutoCapture(); this.addMessage('system', '⏸️ Auto-capture paused'); + this.showNotification('⏸️ Auto-capture paused'); + } + + this.saveSettings(); + } + + startPerformanceMonitoring() { + this.performanceIntervalId = setInterval(() => { + this.updatePerformanceStats(); + }, 1000); + document.getElementById('performanceMonitor').classList.add('show'); + } + + stopPerformanceMonitoring() { + if (this.performanceIntervalId) { + clearInterval(this.performanceIntervalId); + this.performanceIntervalId = null; + } + document.getElementById('performanceMonitor').classList.remove('show'); + } + + updatePerformanceStats() { + // FPS calculation + const currentTime = performance.now(); + if (this.performanceStats.lastFrameTime > 0) { + const deltaTime = currentTime - this.performanceStats.lastFrameTime; + this.performanceStats.fps = Math.round(1000 / deltaTime); + } + this.performanceStats.lastFrameTime = currentTime; + + // Memory usage + if (performance.memory) { + this.performanceStats.memory = Math.round(performance.memory.usedJSHeapSize / 1024 / 1024); + } + + // Update UI + document.getElementById('fpsCounter').textContent = this.performanceStats.fps; + document.getElementById('memoryUsage').textContent = this.performanceStats.memory; + } + + togglePerformanceMonitor() { + const isEnabled = document.getElementById('enablePerformance').checked; + document.getElementById('enablePerformance').checked = !isEnabled; + + if (!isEnabled) { + this.startPerformanceMonitoring(); + this.showNotification('πŸ“Š Performance monitor enabled'); + } else { + this.stopPerformanceMonitoring(); + this.showNotification('πŸ“Š Performance monitor disabled'); } this.saveSettings(); @@ -1261,12 +1781,25 @@ if (!this.isConnected) { this.addMessage('error', '❌ Not connected to Ollama. Please check the connection.'); + this.showNotification('❌ Connection required', 'error'); return; } // Capture current frame if we don't have recent data if (!this.currentImageData || (Date.now() - this.lastCaptureTime > 30000)) { this.captureFrame(); + this.imageCount++; + this.updateImageCount(); + } + + // Check cache for similar messages + const cacheKey = `${message}_${this.currentImageData?.substring(0, 100)}`; + if (document.getElementById('cacheResponses').checked && this.responseCache.has(cacheKey)) { + const cachedResponse = this.responseCache.get(cacheKey); + this.addMessage('user', message); + this.addMessage('assistant', cachedResponse + ' (cached)'); + this.showNotification('⚑ Response served from cache'); + return; } this.addMessage('user', message); @@ -1284,9 +1817,22 @@ this.addMessage('assistant', response); this.updateMessageHistory(message, response); + + // Cache the response + if (document.getElementById('cacheResponses').checked) { + this.responseCache.set(cacheKey, response); + } + + // Voice synthesis if enabled + if (this.isVoiceMode && this.synthesis) { + this.speakResponse(response); + } + + this.showNotification('βœ… Response received'); } catch (error) { - console.error('Error sending message:', error); + console.error('❌ Error sending message:', error); this.addMessage('error', '❌ Error: ' + error.message); + this.showNotification('❌ ' + error.message, 'error'); } finally { this.setSendingState(false); this.hideTypingIndicator(); @@ -1299,8 +1845,9 @@ prompt: this.buildContextualPrompt(message), stream: false, options: { - temperature: 0.7, + temperature: this.getTemperatureForStyle(), top_p: 0.9, + num_ctx: 4096, } }; @@ -1308,12 +1855,16 @@ payload.images = [imageData]; } + const controller = new AbortController(); + setTimeout(() => controller.abort(), this.timeout); + const response = await fetch(`${this.ollamaUrl}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(payload) + body: JSON.stringify(payload), + signal: controller.signal }); if (!response.ok) { @@ -1324,8 +1875,36 @@ return data.response || 'No response received'; } + getTemperatureForStyle() { + switch (this.responseStyle) { + case 'professional': return 0.3; + case 'technical': return 0.2; + case 'creative': return 0.9; + case 'casual': + default: return 0.7; + } + } + buildContextualPrompt(currentMessage) { - let prompt = this.systemPrompt + '\n\n'; + let prompt = this.systemPrompt; + + // Add style-specific instructions + switch (this.responseStyle) { + case 'professional': + prompt += ' Respond in a professional, formal manner.'; + break; + case 'technical': + prompt += ' Provide detailed technical explanations and analysis.'; + break; + case 'creative': + prompt += ' Be creative, imaginative, and engaging in your responses.'; + break; + case 'casual': + prompt += ' Be casual, friendly, and conversational.'; + break; + } + + prompt += '\n\n'; // Add recent conversation history const recentHistory = this.messageHistory.slice(-this.contextMemory); @@ -1344,11 +1923,12 @@ this.messageHistory.push({ user: userMessage, assistant: assistantResponse, - timestamp: Date.now() + timestamp: Date.now(), + imageUsed: !!this.currentImageData }); // Keep only recent messages based on context memory setting - if (this.messageHistory.length > this.contextMemory) { + if (this.messageHistory.length > this.contextMemory * 2) { this.messageHistory = this.messageHistory.slice(-this.contextMemory); } } @@ -1358,10 +1938,22 @@ messageDiv.className = `message ${type}`; const contentDiv = document.createElement('div'); - contentDiv.textContent = content; + contentDiv.innerHTML = this.formatMessageContent(content); messageDiv.appendChild(contentDiv); - if (type !== 'system' && type !== 'error') { + // Add message actions for assistant messages + if (type === 'assistant') { + const actionsDiv = document.createElement('div'); + actionsDiv.className = 'message-actions'; + actionsDiv.innerHTML = ` + + + + `; + messageDiv.appendChild(actionsDiv); + } + + if (type !== 'system' && type !== 'error' && type !== 'success') { const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = new Date().toLocaleTimeString(); @@ -1372,6 +1964,16 @@ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight; } + formatMessageContent(content) { + // Enhanced formatting for better readability + return content + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`(.*?)`/g, '$1') + .replace(/\n/g, '
') + .replace(/(\bhttps?:\/\/[^\s]+)/g, '$1'); + } + showTypingIndicator() { this.typingIndicator.classList.add('show'); this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight; @@ -1388,10 +1990,10 @@ if (sending) { btnText.textContent = 'Sending...'; - btnIcon.textContent = '⏳'; + btnIcon.innerHTML = '
'; } else { btnText.textContent = 'Send'; - btnIcon.textContent = 'πŸ“€'; + btnIcon.textContent = 'πŸš€'; } this.messageInput.disabled = sending; @@ -1411,10 +2013,12 @@ if (confirm('Are you sure you want to clear the chat history?')) { this.messagesContainer.innerHTML = `
- πŸ”„ Chat cleared! I can still see through your camera and help with anything you need. + πŸ”„ Chat cleared! Enhanced Vision Chat Pro is ready to help with your visual tasks and conversations.
`; this.messageHistory = []; + this.responseCache.clear(); + this.showNotification('πŸ—‘οΈ Chat cleared successfully'); } } @@ -1422,11 +2026,22 @@ const messages = Array.from(this.messagesContainer.children); const chatData = { timestamp: new Date().toISOString(), + settings: { + model: this.modelName, + ollamaUrl: this.ollamaUrl, + responseStyle: this.responseStyle + }, messages: messages.map(msg => ({ type: msg.className.replace('message ', ''), - content: msg.textContent, - time: msg.querySelector('.message-time')?.textContent || '' - })) + content: msg.querySelector('div').textContent, + time: msg.querySelector('.message-time')?.textContent || '', + html: msg.querySelector('div').innerHTML + })), + statistics: { + totalMessages: this.messageHistory.length, + imagesProcessed: this.imageCount, + averageResponseTime: this.lastResponseTime + } }; const blob = new Blob([JSON.stringify(chatData, null, 2)], { @@ -1436,20 +2051,41 @@ const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = `vision-chat-${new Date().toISOString().slice(0, 10)}.json`; + a.download = `enhanced-vision-chat-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); - this.addMessage('system', 'πŸ’Ύ Chat exported successfully!'); + this.addMessage('success', 'πŸ’Ύ Enhanced chat export completed successfully!'); + this.showNotification('πŸ’Ύ Chat exported with full metadata'); + } + + shareChat() { + if (navigator.share) { + const lastMessage = this.messageHistory[this.messageHistory.length - 1]; + navigator.share({ + title: 'Enhanced Vision Chat Pro Conversation', + text: lastMessage ? `AI: ${lastMessage.assistant}` : 'Check out this AI vision chat!', + url: window.location.href + }); + } else { + // Fallback to clipboard + const chatText = this.messageHistory.slice(-3).map(h => + `Human: ${h.user}\nAI: ${h.assistant}` + ).join('\n\n'); + navigator.clipboard.writeText(chatText); + this.showNotification('πŸ“‹ Recent conversation copied to clipboard'); + } } toggleFullscreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); + this.showNotification('β›Ά Entered fullscreen mode'); } else { document.exitFullscreen(); + this.showNotification('β›Ά Exited fullscreen mode'); } } @@ -1458,23 +2094,129 @@ document.documentElement.setAttribute('data-theme', this.isDarkTheme ? 'dark' : 'light'); document.getElementById('themeToggle').textContent = this.isDarkTheme ? 'β˜€οΈ' : 'πŸŒ™'; this.saveSettings(); + this.showNotification(`🎨 Switched to ${this.isDarkTheme ? 'dark' : 'light'} theme`); } initializeTheme() { - const savedTheme = localStorage.getItem('visionChatTheme'); + const savedTheme = localStorage.getItem('enhancedVisionChatTheme'); if (savedTheme === 'dark') { this.toggleTheme(); } } toggleVoiceMode() { - // Placeholder for voice functionality - this.addMessage('system', '🎀 Voice mode coming soon!'); + this.isVoiceMode = !this.isVoiceMode; + const btn = document.getElementById('voiceToggle'); + + if (this.isVoiceMode) { + btn.innerHTML = 'πŸ”Š Voice ON'; + btn.style.background = 'var(--success-color)'; + btn.style.color = 'white'; + this.showNotification('🎀 Voice mode enabled'); + } else { + btn.innerHTML = '🎀 Voice'; + btn.style.background = ''; + btn.style.color = ''; + this.showNotification('πŸ”‡ Voice mode disabled'); + } + + this.saveSettings(); } - toggleChatMode() { - // Placeholder for different chat modes - this.addMessage('system', 'πŸ’¬ Chat mode options coming soon!'); + startVoiceInput() { + if (!this.recognition) { + this.showNotification('❌ Voice recognition not supported', 'error'); + return; + } + + const btn = document.getElementById('micBtn'); + btn.style.color = 'var(--error-color)'; + btn.innerHTML = 'πŸŽ™οΈ'; + + this.recognition.start(); + this.showNotification('🎀 Listening... Speak now!'); + + setTimeout(() => { + btn.style.color = ''; + btn.innerHTML = '🎀'; + }, 5000); + } + + speakResponse(text) { + if (!this.synthesis) return; + + this.synthesis.cancel(); // Stop any ongoing speech + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.9; + utterance.pitch = 1; + utterance.volume = 0.8; + + this.synthesis.speak(utterance); + } + + cycleChatMode() { + const modes = ['Enhanced', 'Focus', 'Creative', 'Technical']; + const currentMode = document.getElementById('modeBtn').textContent.split(' ')[1]; + const currentIndex = modes.indexOf(currentMode); + const nextIndex = (currentIndex + 1) % modes.length; + const nextMode = modes[nextIndex]; + + document.getElementById('modeBtn').innerHTML = `πŸ’¬ ${nextMode}`; + this.responseStyle = nextMode.toLowerCase(); + this.saveSettings(); + this.showNotification(`🎯 Switched to ${nextMode} mode`); + } + + openEmojiPicker() { + const emojis = ['😊', 'πŸ‘', '❀️', 'πŸ˜„', 'πŸ€”', 'πŸ’‘', 'πŸŽ‰', 'πŸ”₯', '⭐', 'πŸ‘']; + const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)]; + this.messageInput.value += randomEmoji; + this.messageInput.focus(); + this.showNotification(`${randomEmoji} Emoji added!`); + } + + attachImage() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = async (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + this.currentImageData = e.target.result.split(',')[1]; + this.showImagePreview(); + this.showNotification('πŸ“Ž Image attached successfully'); + }; + reader.readAsDataURL(file); + } + }; + input.click(); + } + + regenerateResponse(messageElement) { + // Find the user message before this assistant message + const userMessage = messageElement.previousElementSibling; + if (userMessage && userMessage.classList.contains('user')) { + const userText = userMessage.querySelector('div').textContent; + this.messageInput.value = userText; + this.sendMessage(); + this.showNotification('πŸ”„ Regenerating response...'); + } + } + + showNotification(message, type = 'success') { + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.opacity = '0'; + notification.style.transform = 'translateX(100%)'; + setTimeout(() => notification.remove(), 300); + }, 3000); } saveSettings() { @@ -1484,16 +2226,22 @@ systemPrompt: this.systemPrompt, contextMemory: this.contextMemory, imageQuality: this.imageQuality, + timeout: this.timeout, + responseStyle: this.responseStyle, + cameraSource: this.cameraSource, autoCapture: document.getElementById('autoCapture').checked, captureInterval: document.getElementById('captureInterval').value, - theme: this.isDarkTheme ? 'dark' : 'light' + enablePerformance: document.getElementById('enablePerformance')?.checked || false, + cacheResponses: document.getElementById('cacheResponses')?.checked !== false, + theme: this.isDarkTheme ? 'dark' : 'light', + voiceMode: this.isVoiceMode }; - localStorage.setItem('visionChatSettings', JSON.stringify(settings)); + localStorage.setItem('enhancedVisionChatSettings', JSON.stringify(settings)); } loadSettings() { - const saved = localStorage.getItem('visionChatSettings'); + const saved = localStorage.getItem('enhancedVisionChatSettings'); if (saved) { const settings = JSON.parse(saved); @@ -1502,6 +2250,10 @@ this.systemPrompt = settings.systemPrompt || this.systemPrompt; this.contextMemory = settings.contextMemory || this.contextMemory; this.imageQuality = settings.imageQuality || this.imageQuality; + this.timeout = settings.timeout || this.timeout; + this.responseStyle = settings.responseStyle || this.responseStyle; + this.cameraSource = settings.cameraSource || this.cameraSource; + this.isVoiceMode = settings.voiceMode || false; // Update UI elements document.getElementById('ollamaUrl').value = this.ollamaUrl; @@ -1509,16 +2261,63 @@ document.getElementById('systemPrompt').value = this.systemPrompt; document.getElementById('contextMemory').value = this.contextMemory; document.getElementById('imageQuality').value = this.imageQuality; + document.getElementById('timeout').value = this.timeout / 1000; + document.getElementById('timeoutValue').textContent = this.timeout / 1000; + document.getElementById('responseStyle').value = this.responseStyle; + document.getElementById('cameraSource').value = this.cameraSource; document.getElementById('autoCapture').checked = settings.autoCapture !== false; document.getElementById('captureInterval').value = settings.captureInterval || 10; document.getElementById('intervalValue').textContent = settings.captureInterval || 10; + + if (settings.enablePerformance) { + document.getElementById('enablePerformance').checked = true; + this.startPerformanceMonitoring(); + } + + if (settings.cacheResponses !== undefined) { + document.getElementById('cacheResponses').checked = settings.cacheResponses; + } + + if (this.isVoiceMode) { + this.toggleVoiceMode(); + } } } } + // Global reference for message actions + let app; + // Initialize the enhanced app document.addEventListener('DOMContentLoaded', () => { - new EnhancedVisionChatApp(); + app = new EnhancedVisionChatApp(); + + // Add some helpful keyboard shortcuts + document.addEventListener('keydown', (e) => { + if (e.ctrlKey || e.metaKey) { + switch (e.key) { + case 'k': + e.preventDefault(); + app.clearChat(); + break; + case 'e': + e.preventDefault(); + app.exportChat(); + break; + case 'Enter': + e.preventDefault(); + app.captureFrame(); + app.showImagePreview(); + break; + } + } + + if (e.key === 'Escape') { + app.hideSettings(); + } + }); + + console.log('πŸŽ‰ Enhanced Vision Chat Pro loaded successfully!'); });