| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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 = ` |
| <span>${message}</span> |
| <button onclick="this.parentElement.remove()">×</button> |
| `; |
| 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 }; |
|
|