/** * BrowserLLMLauncher.js * * Opens AI chat interfaces directly in the browser. * No API keys required - uses user's existing sessions. * * Supported: * - ChatGPT (chat.openai.com) * - Gemini (gemini.google.com) * - Claude (claude.ai) * - Perplexity (perplexity.ai) * - Poe (poe.com) * - Grok (x.ai/grok) * - DeepSeek (chat.deepseek.com) * - Local Ollama (localhost:11434) */ class BrowserLLMLauncher { constructor() { this.providers = { chatgpt: { name: 'ChatGPT', url: 'https://chat.openai.com', searchUrl: 'https://chat.openai.com/?q=', icon: '🤖', color: '#10a37f', promptMethod: 'clipboard' }, gemini: { name: 'Gemini', url: 'https://gemini.google.com', searchUrl: 'https://gemini.google.com/?query=', icon: '✨', color: '#4285f4', promptMethod: 'clipboard' }, claude: { name: 'Claude', url: 'https://claude.ai', searchUrl: 'https://claude.ai/?q=', icon: '🧠', color: '#d4a574', promptMethod: 'clipboard' }, perplexity: { name: 'Perplexity', url: 'https://perplexity.ai', searchUrl: 'https://perplexity.ai/?q=', icon: '🔍', color: '#20b2aa', promptMethod: 'clipboard' }, poe: { name: 'Poe', url: 'https://poe.com', searchUrl: 'https://poe.com/?q=', icon: '🦊', color: '#6b5ce7', promptMethod: 'clipboard' }, grok: { name: 'Grok', url: 'https://x.ai/grok', searchUrl: 'https://x.ai/grok?q=', icon: '🚀', color: '#000000', promptMethod: 'clipboard' }, deepseek: { name: 'DeepSeek', url: 'https://chat.deepseek.com', searchUrl: 'https://chat.deepseek.com/?q=', icon: '🔭', color: '#0066cc', promptMethod: 'clipboard' }, ollama: { name: 'Ollama (Local)', url: 'http://localhost:11434', searchUrl: 'http://localhost:11434/?q=', icon: '💻', color: '#9333ea', promptMethod: 'web' } }; this.activeProviders = ['chatgpt', 'gemini']; this.clipboardFallback = true; this.showNotification = true; } setActiveProviders(providers) { this.activeProviders = providers.filter(p => this.providers[p]); } getActiveProviders() { return this.activeProviders.map(key => ({ key, ...this.providers[key] })); } async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.error('Failed to copy to clipboard:', err); return false; } } async launchProvider(providerKey, prompt, options = {}) { const provider = this.providers[providerKey]; if (!provider) { console.error(`Unknown provider: ${providerKey}`); return { success: false, error: 'Unknown provider' }; } const finalPrompt = options.customPrompt || prompt; let url = provider.url; if (finalPrompt && provider.promptMethod === 'clipboard') { await this.copyToClipboard(finalPrompt); if (this.showNotification) { this.showBrowserNotification( provider.name, `Copied prompt! Paste in ${provider.name}`, provider.icon ); } } if (finalPrompt && provider.searchUrl) { const encodedPrompt = encodeURIComponent(finalPrompt.substring(0, 500)); url = provider.searchUrl + encodedPrompt; } const features = 'width=1200,height=800,scrollbars=yes,resizable=yes'; const newWindow = window.open(url, `_blank`, features); if (!newWindow) { return { success: false, error: 'Popup blocked. Please allow popups for this site.' }; } return { success: true, provider: providerKey, providerName: provider.name, url: url, prompt: finalPrompt, method: provider.promptMethod }; } async launchAll(prompt, options = {}) { const results = []; for (const providerKey of this.activeProviders) { const result = await this.launchProvider(providerKey, prompt, options); results.push(result); await new Promise(resolve => setTimeout(resolve, 500)); } return results; } async launchWithGesture(gestureType, prompt, context = {}) { const results = []; switch (gestureType) { case '2_finger_swipe_right': results.push(...await this.launchAll(prompt, { gesture: gestureType })); break; case '2_finger_swipe_left': const defaultProvider = this.activeProviders[0]; results.push(await this.launchProvider(defaultProvider, prompt, { gesture: gestureType })); break; case '1_finger_tap': results.push(...await this.launchAll(prompt, { gesture: gestureType, rlMode: true })); break; case 'pinch': const lastProvider = this.activeProviders[this.activeProviders.length - 1]; results.push(await this.launchProvider(lastProvider, prompt, { gesture: gestureType })); break; case 'swipe_up': results.push(...await this.launchAll(prompt, { gesture: gestureType })); break; case 'swipe_down': const focusedPrompt = `Focus on: ${context.topic || prompt}`; results.push(...await this.launchAll(focusedPrompt, { gesture: gestureType })); break; default: results.push(...await this.launchAll(prompt, { gesture: gestureType })); } return results; } showBrowserNotification(title, message, icon = '🤖') { if ('Notification' in window) { if (Notification.permission === 'granted') { new Notification(`${icon} ${title}`, { body: message, icon: '/favicon.ico' }); } else if (Notification.permission !== 'denied') { Notification.requestPermission().then(permission => { if (permission === 'granted') { new Notification(`${icon} ${title}`, { body: message, icon: '/favicon.ico' }); } }); } } this.showToast(message, 'info'); } showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `llm-toast llm-toast-${type}`; toast.innerHTML = ` ${message} `; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('show'); }, 10); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, 3000); } checkProviderStatus() { const status = {}; for (const [key, provider] of Object.entries(this.providers)) { status[key] = { available: true, url: provider.url, name: provider.name }; } return status; } getInstructions() { return ` To use browser-based LLM launching: 1. Make sure you're logged into the AI services you want to use 2. Allow popups for this site 3. Use hand gestures to trigger queries When a gesture is detected: - The prompt will be copied to your clipboard - The AI chat interface will open in a new tab - Just paste (Ctrl+V) to send the prompt! Supported services: ${Object.values(this.providers).map(p => `- ${p.name}: ${p.url}`).join('\n')} `; } } class GesturePromptBuilder { constructor() { this.templates = { learning_explain: (context) => `Explain ${context.topic || 'this concept'} in simple terms.`, doubt_resolution: (context) => { let prompt = `I need help understanding: ${context.doubt || context.topic || 'this concept'}\n\n`; prompt += `I've tried: ${context.attempted || 'reading the material'}\n`; prompt += `What specifically confuses me is: ${context.confusion || 'the underlying concept'}`; return prompt; }, summarize: (context) => `Summarize the key points about ${context.topic || 'this topic'} in a way that's easy to understand.`, practice: (context) => `Generate 5 practice questions about ${context.topic || 'this topic'} to test my understanding.`, compare: (context) => `Compare and contrast ${context.concept_a || 'concept A'} and ${context.concept_b || 'concept B'}.`, real_world: (context) => `Explain ${context.topic || 'this concept'} using real-world examples that a beginner would understand.`, step_by_step: (context) => `Break down ${context.topic || 'this concept'} into simple, easy-to-follow steps.`, common_mistakes: (context) => `What are the most common mistakes people make when learning about ${context.topic || 'this'}? How can I avoid them?` }; } build(gesture, context) { const template = this.templates[context.template] || this.templates.learning_explain; return template(context); } buildFromGestures(gestures, context) { const prompts = []; for (const gesture of gestures) { const type = this.getGesturePromptType(gesture); const prompt = this.build(type, context); prompts.push({ gesture, prompt }); } return prompts; } getGesturePromptType(gesture) { if (gesture.includes('swipe_right')) return 'learning_explain'; if (gesture.includes('swipe_left')) return 'doubt_resolution'; if (gesture.includes('swipe_up')) return 'summarize'; if (gesture.includes('swipe_down')) return 'step_by_step'; if (gesture.includes('pinch')) return 'real_world'; if (gesture.includes('tap') || gesture.includes('1_finger')) return 'practice'; return 'learning_explain'; } } window.BrowserLLMLauncher = BrowserLLMLauncher; window.GesturePromptBuilder = GesturePromptBuilder; export default BrowserLLMLauncher; export { BrowserLLMLauncher, GesturePromptBuilder };