Welcome to Goosey

Enter your email to get started

Enter the 6-digit code sent to

← Use a different email

What can I help with?

Chat, code, research, create — pick a mode or try a prompt

📊 Prediction Markets

Loading markets...
📄
Ready
Settings
Inference
Auto-selects the best model for each task
Groq API Keys
Add multiple keys to multiply your daily limit. Get keys →
Connection
Connected Apps (Composio)
Get a free key →
Memory (0)
Goosey learns from conversations. Delete memories you want forgotten.
Experimental
Use on-device AI when offline (beta)
Browse and install community skills

📋 Tasks

🧩 Plugin Marketplace

📎
Drop file to attach
`; doc.open(); doc.write(html); doc.close(); // Auto-resize iframe setTimeout(() => { try { const height = Math.min(600, Math.max(200, doc.body.scrollHeight + 32)); iframe.style.height = height + 'px'; } catch (e) {} }, 500); } function filterConversations(query) { const q = query.toLowerCase(); const items = document.querySelectorAll('.conv-item'); items.forEach(item => { const title = item.querySelector('.conv-item-title')?.textContent?.toLowerCase() || ''; item.style.display = (!q || title.includes(q)) ? '' : 'none'; }); } // ════════════════════════════════════════════════════════════ // FEATURE: 👍/👎 Feedback // ════════════════════════════════════════════════════════════ function feedbackMsg(idx, type, btn) { const msg = chatHistory[idx]; if (!msg) return; msg._feedback = msg._feedback === type ? '' : type; // Toggle saveConversation(); // Update UI const fb = btn.closest('.msg-feedback'); fb.querySelectorAll('button').forEach(b => { b.classList.remove('active','up','down'); }); if (msg._feedback) { btn.classList.add('active', msg._feedback); } } // ════════════════════════════════════════════════════════════ // FEATURE: Suggested Follow-Up Chips // ════════════════════════════════════════════════════════════ function generateFollowUpChips(assistantText, userText, msgDiv) { // Generate 3 contextual follow-up suggestions based on the conversation const chips = []; const text = (userText + ' ' + assistantText).toLowerCase(); // Smart chip generation based on content patterns if (/code|function|component|build|implement/i.test(text)) { chips.push('Can you add error handling?', 'Write tests for this', 'How would I deploy this?'); } else if (/explain|what is|how does|why/i.test(text)) { chips.push('Can you give me an example?', 'How does this compare to alternatives?', 'What are the limitations?'); } else if (/plan|strategy|idea|brainstorm/i.test(text)) { chips.push('What should I prioritize first?', 'What are the risks?', 'Create an action plan'); } else if (/write|article|post|content|caption/i.test(text)) { chips.push('Can you make it shorter?', 'Write a different angle', 'Optimize for SEO'); } else if (/debug|error|fix|broken|bug/i.test(text)) { chips.push('Are there related issues?', 'How do I prevent this?', 'Show me the full fixed code'); } else { chips.push('Tell me more', 'Can you elaborate?', 'What should I do next?'); } if (chips.length) { const chipDiv = document.createElement('div'); chipDiv.className = 'follow-up-chips'; chipDiv.innerHTML = chips.map(c => `` ).join(''); msgDiv.appendChild(chipDiv); } } // ════════════════════════════════════════════════════════════ // FEATURE: Conversation Pinning // ════════════════════════════════════════════════════════════ function togglePinConversation(id, event) { if (event) event.stopPropagation(); const conv = state.conversations.find(c => c.id === id); if (conv) { conv.pinned = !conv.pinned; saveState(); renderConversations(); } } // ════════════════════════════════════════════════════════════ // FEATURE: Export Conversation as Markdown // ════════════════════════════════════════════════════════════ function exportConversation() { if (!chatHistory.length) return; const conv = state.conversations.find(c => c.id === state.activeConvId); const title = conv?.title || 'conversation'; let md = `# ${title}\n\nExported from Goosey · ${new Date().toLocaleString()}\n\n---\n\n`; chatHistory.forEach(msg => { const role = msg.role === 'user' ? '**You**' : '**Goosey** 🪿'; const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ''; md += `### ${role} ${time ? `(${time})` : ''}\n\n${msg.content}\n\n---\n\n`; }); const blob = new Blob([md], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}.md`; a.click(); URL.revokeObjectURL(url); } // ════════════════════════════════════════════════════════════ // FEATURE: Drag-and-Drop File Upload // ════════════════════════════════════════════════════════════ ;(function initDragDrop() { let dragCounter = 0; document.addEventListener('dragenter', (e) => { e.preventDefault(); dragCounter++; document.getElementById('drop-overlay').classList.add('visible'); }); document.addEventListener('dragleave', (e) => { e.preventDefault(); dragCounter--; if (dragCounter <= 0) { dragCounter = 0; document.getElementById('drop-overlay').classList.remove('visible'); } }); document.addEventListener('dragover', (e) => { e.preventDefault(); }); document.addEventListener('drop', (e) => { e.preventDefault(); dragCounter = 0; document.getElementById('drop-overlay').classList.remove('visible'); const file = e.dataTransfer?.files?.[0]; if (file && file.size > 0) { handleFileSelect({ target: { files: [file] } }); } }); })(); // ════════════════════════════════════════════════════════════ // FEATURE: Keyboard Shortcuts // ════════════════════════════════════════════════════════════ document.addEventListener('keydown', (e) => { const mod = e.metaKey || e.ctrlKey; if (mod && e.key === 'n') { e.preventDefault(); newConversation(); } if (mod && e.key === 'e') { e.preventDefault(); exportConversation(); } if (mod && e.key === '/') { e.preventDefault(); document.getElementById('msg-input').focus(); } if (e.key === 'Escape') { closeSettings(); closeTaskPanel(); closePluginPanel(); document.getElementById('drop-overlay').classList.remove('visible'); } }); // ════════════════════════════════════════════════════════════ // FEATURE: Timestamp Formatting // ════════════════════════════════════════════════════════════ function formatTimeAgo(ts) { if (!ts) return ''; const diff = Date.now() - ts; if (diff < 60000) return 'just now'; if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago'; if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago'; return new Date(ts).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } // ════════════════════════════════════════════════════════════ // FEATURE 4: Syntax Highlighting // ════════════════════════════════════════════════════════════ function applySyntaxHighlighting(container) { if (typeof hljs === 'undefined') return; try { container.querySelectorAll('pre code').forEach(block => { if (!block.dataset.highlighted) { hljs.highlightElement(block); block.dataset.highlighted = 'true'; } }); } catch {} } // ════════════════════════════════════════════════════════════ // FEATURE 5: Memory Viewer / Editor // ════════════════════════════════════════════════════════════ function renderMemoryViewer() { const viewer = document.getElementById('memory-viewer'); const countEl = document.getElementById('settings-memory-count'); if (!viewer) return; // Ensure memories is always a proper array let memories = state.memories || []; if (!Array.isArray(memories)) { try { memories = Object.values(memories); } catch { memories = []; } } state.memories = memories; // Normalize countEl.textContent = memories.length; if (!memories.length) { viewer.innerHTML = '
No memories yet. Goosey learns as you chat.
'; return; } // Show last 50 memories (most recent first) const recent = memories.slice(-50).reverse(); viewer.innerHTML = recent.map((m, i) => { const idx = memories.length - 1 - i; const content = typeof m === 'string' ? m : (m?.content || JSON.stringify(m)); const source = m?.source || 'auto'; const confidence = m?.confidence ? ` · ${Math.round(m.confidence * 100)}%` : ''; return `
${esc(String(content).slice(0, 120))} ${source}${confidence}
`; }).join(''); } function deleteMemory(idx) { if (idx >= 0 && idx < state.memories.length) { state.memories.splice(idx, 1); ss('goosey_memories', state.memories); document.getElementById('header-memory-count').textContent = state.memories.length; renderMemoryViewer(); } } function clearAllMemories() { if (!confirm('Delete all memories? Goosey will forget everything it learned about you.')) return; state.memories = []; ss('goosey_memories', []); document.getElementById('header-memory-count').textContent = '0'; renderMemoryViewer(); } // ════════════════════════════════════════════════════════════ // CONTEXT WINDOWING — Compress old messages to save tokens // ════════════════════════════════════════════════════════════ const MAX_CONTEXT_MESSAGES = 12; // Keep last 12 messages verbatim const MAX_MSG_CHARS = 5000; // Allow longer individual messages function buildContextWindow(history) { const mapped = history.map(m => ({ role: m.role, content: (m._llmContent || m.content || '').slice(0, MAX_MSG_CHARS), })); if (mapped.length <= MAX_CONTEXT_MESSAGES) return mapped; // Compress older messages into a summary const older = mapped.slice(0, -MAX_CONTEXT_MESSAGES); const recent = mapped.slice(-MAX_CONTEXT_MESSAGES); // Build compressed context from older messages const compressed = older.map(m => { const prefix = m.role === 'user' ? 'U' : 'A'; // Take first 80 chars of each old message return `${prefix}: ${m.content.slice(0, 80).replace(/\n/g, ' ')}`; }).join('\n'); // Prepend as a system-style context note return [ { role: 'user', content: `[Earlier conversation summary:\n${compressed}]\n\n${recent[0].content}` }, ...recent.slice(1), ]; } // ════════════════════════════════════════════════════════════ // BOOT // ════════════════════════════════════════════════════════════ window.addEventListener('DOMContentLoaded', () => { setMode(state.mode); checkAuth(); }); // Service worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').catch(() => {}); }