/** * WidgeTDC Browser Extension - Content Script * Captures page content and enables AI assistance */ class WidgeTDCAssistant { constructor() { this.apiUrl = 'http://localhost:3001/api'; this.sidebar = null; // Try to load from storage try { chrome.storage.sync.get({ widgetdc_api_url: 'http://localhost:3001' }, (items) => { this.apiUrl = `${items.widgetdc_api_url}/api`; }); } catch (e) { // Fallback if storage access fails } this.init(); } async init() { // Listen for messages from background script chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { this.handleMessage(message, sendResponse); return true; // Keep channel open for async response }); // Add context menu handler document.addEventListener('mouseup', this.handleTextSelection.bind(this)); } /** * Handle messages from background script */ async handleMessage(message, sendResponse) { switch (message.action) { case 'captureContent': const content = this.capturePageContent(); await this.sendToBackend('/memory/ingest', content); sendResponse({ success: true, content }); break; case 'searchSimilar': const results = await this.searchSimilar(message.query); sendResponse({ success: true, results }); break; case 'toggleSidebar': this.toggleSidebar(); sendResponse({ success: true }); break; case 'askQuestion': const answer = await this.askQuestion(message.question); sendResponse({ success: true, answer }); break; default: sendResponse({ success: false, error: 'Unknown action' }); } } /** * Capture page content */ capturePageContent() { // Extract main content (remove scripts, styles, etc.) const clone = document.cloneNode(true); const scripts = clone.querySelectorAll('script, style, noscript'); scripts.forEach(el => el.remove()); // Get text content const text = clone.body.innerText.trim(); // Extract metadata const metadata = { author: this.getMetaContent('author'), publishedDate: this.getMetaContent('article:published_time'), description: this.getMetaContent('description'), }; return { url: window.location.href, title: document.title, text: text.substring(0, 10000), // Limit to 10k chars metadata, }; } /** * Get meta tag content */ getMetaContent(name) { const meta = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`); return meta ? meta.getAttribute('content') : undefined; } /** * Handle text selection */ handleTextSelection(event) { const selection = window.getSelection(); const selectedText = selection ? selection.toString().trim() : ''; if (selectedText && selectedText.length > 10) { // Show floating action button this.showFloatingButton(event.clientX, event.clientY, selectedText); } } /** * Show floating action button */ showFloatingButton(x, y, text) { // Remove existing button const existing = document.getElementById('widgetdc-floating-btn'); if (existing) existing.remove(); // Create button const button = document.createElement('div'); button.id = 'widgetdc-floating-btn'; button.className = 'widgetdc-floating-button'; button.innerHTML = ` `; button.style.position = 'fixed'; button.style.left = `${x}px`; button.style.top = `${y + 10}px`; button.style.zIndex = '10000'; // Add event listeners button.querySelectorAll('.widgetdc-btn').forEach(btn => { btn.addEventListener('click', async (e) => { const action = e.target.getAttribute('data-action'); await this.handleAction(action, text); button.remove(); }); }); document.body.appendChild(button); // Remove on click outside setTimeout(() => { document.addEventListener('click', () => button.remove(), { once: true }); }, 100); } /** * Handle floating button action */ async handleAction(action, text) { switch (action) { case 'save': await this.saveText(text); this.showNotification('Text saved to WidgeTDC'); break; case 'search': const results = await this.searchSimilar(text); this.showSidebarWithResults(results); break; case 'ask': const answer = await this.askQuestion(text); this.showSidebarWithAnswer(answer); break; } } /** * Save text to backend */ async saveText(text) { await this.sendToBackend('/memory/ingest', { content: text, source: 'browser_selection', url: window.location.href, timestamp: new Date().toISOString(), }); } /** * Search for similar content */ async searchSimilar(query) { const response = await this.sendToBackend('/search', { query, limit: 5 }); return response.results || []; } /** * Ask AI a question */ async askQuestion(question) { const response = await this.sendToBackend('/query', { question }); return response.answer || 'No answer available'; } /** * Send data to backend */ async sendToBackend(endpoint, data) { try { const response = await fetch(`${this.apiUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { console.error('WidgeTDC API error:', error); return { error: String(error) }; } } /** * Toggle sidebar */ toggleSidebar() { if (this.sidebar) { this.sidebar.remove(); this.sidebar = null; } else { this.createSidebar(); } } /** * Create sidebar */ createSidebar() { const sidebar = document.createElement('div'); sidebar.id = 'widgetdc-sidebar'; sidebar.className = 'widgetdc-sidebar'; sidebar.innerHTML = `
`; sidebar.querySelector('.widgetdc-close')?.addEventListener('click', () => { this.toggleSidebar(); }); document.body.appendChild(sidebar); this.sidebar = sidebar; } /** * Show sidebar with search results */ showSidebarWithResults(results) { this.createSidebar(); const content = this.sidebar?.querySelector('.widgetdc-sidebar-content'); if (content) { content.innerHTML = `