המלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠
-
@יוסי-רחמים הוא על API של ג'מיני אז הוא אמור לעבוד. אא"כ, @פרוזי יש לך הגדרות קהילה/מסלול שחוסם את ג'מיני.
-
@פרוזי לבינתיים לא
-
@פרוזי לבינתיים לא
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
לבינתיים לא
אז בכל זאת...
יש מידע מדוע הוא אינו עובד לי? -
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
לבינתיים לא
אז בכל זאת...
יש מידע מדוע הוא אינו עובד לי? -
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
לבינתיים לא
אז בכל זאת...
יש מידע מדוע הוא אינו עובד לי?@פרוזי ובדף תוספים אם מופיע שגיאה
-
@פרוזי ובדף תוספים אם מופיע שגיאה
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@פרוזי ובדף תוספים אם מופיע שגיאה
AI Tooltip Error: TypeError: Failed to fetch
הקשר
https://mitmachim.top/topic/83852/המלצה-שדרגו-את-הדפדפן-שלכם-עם-ai-suite-pro-חבילת-ה-ai-האולטימטיבית-לגלישה?_=1748123175980
התחקות אחר ערימה
content.js:334 (handleTooltipAIAction)// === Global Variables & Constants === const API_URL = 'https://php-render-test.onrender.com/main-ai.php'; const CHAT_SIDEBAR_ID = 'ai-chat-sidebar-custom'; const FAB_ID = 'ai-fab-button'; const FAB_MENU_ID = 'ai-fab-menu'; const THEME_STORAGE_KEY = 'aiSuiteProTheme'; const ONBOARDING_SHOWN_KEY_PREFIX_CS = 'aiSuiteProOnboardingShown_v'; // --- Tooltip State & Variables --- let currentTooltip = null; let tooltipAiHelperWrapper = null; let tooltipActionListVisible = false; // --- Chat State & Variables --- let aiChatSidebarInstance = null; // --- FAB State & Variables --- let fabMenuVisible = false; let fabButtonElement = null; let fabMenuElement = null; // --- Theme State --- let currentTheme = 'light'; // Default console.log("AI Suite Pro: content.js loaded. Version 1.1.1 (Auto Save Chat, UI Fixes)"); // === Helper Functions for Custom Actions === function searchSelectedOnGoogle(selectionObject) { const query = selectionObject ? selectionObject.toString().trim() : ""; if (query) { window.open(`https://www.google.com/search?q=${encodeURIComponent(query)}`, '_blank'); } else { alert("אנא סמן טקסט לחיפוש בגוגל."); } } async function shareCurrentPage() { const pageTitle = document.title; const pageUrl = window.location.href; if (navigator.share) { try { await navigator.share({ title: pageTitle, text: `בדוק את הדף הזה: ${pageTitle}`, url: pageUrl }); } catch (error) { console.error('Error sharing page:', error); copyLinkToClipboard(pageUrl, pageTitle, "שגיאה בשיתוף, הקישור הועתק במקום:"); } } else { copyLinkToClipboard(pageUrl, pageTitle, "שיתוף Web API לא נתמך, הקישור הועתק:"); } } function copyLinkToClipboard(url, title = "", prefixMessage = "") { navigator.clipboard.writeText(url) .then(() => { const message = `${prefixMessage ? prefixMessage + " " : ""}הקישור לדף "${title || url}" הועתק ללוח!`; showTooltipPopup(message, "", 2500); // Auto-dismiss after 2.5s }) .catch(err => { console.error('Failed to copy link: ', err); alert('שגיאה בהעתקת הקישור.'); }); } // === CONFIGURATIONS === const QUICK_ACTION_IDS = [ 'summarize_selected', 'translate_selected_custom_dialog', 'explain_contextual_selected', 'google_search_selected', 'share_page', 'open_ai_chat' ]; const allActionsConfig = { summarize_selected: { id: 'summarize_selected', label: '📝', title: 'סכם מסומן', shortLabel: 'סכם', command: 'סכם את הטקסט המסומן הבא בצורה תמציתית: ', type: 'selected_text' }, translate_selected_custom_dialog: { id: 'translate_selected_custom_dialog', label: '🌐', title: 'תרגם מסומן...', shortLabel: 'תרגם', type: 'prompt_language_selected_custom_dialog', baseCommand: 'תרגם את הטקסט המסומן הבא ל{LANG}: ' }, explain_contextual_selected: { id: 'explain_contextual_selected', label: '💡', title: 'הסבר מסומן בהקשר', shortLabel: 'הסבר', type: 'explain_with_context' }, google_search_selected: { id: 'google_search_selected', label: '🔍', title: 'חפש מסומן בגוגל', shortLabel: 'חפש', type: 'custom_action', actionFn: () => searchSelectedOnGoogle(window.getSelection()) }, share_page: { id: 'share_page', label: '🔗', title: 'שתף דף זה', shortLabel: 'שתף', type: 'custom_action', actionFn: shareCurrentPage }, open_ai_chat: { id: 'open_ai_chat', label: '💬', title: 'פתח צ\'אט AI', shortLabel: 'צ\'אט', type: 'custom_action', actionFn: launchAIChatSidebar }, page_analysis: { categoryLabel: 'ניתוח כל הדף', actions: [ { id: 'summarize_page', label: '📄', title: 'סכם את כל הדף', command: 'סכם את תוכן הדף הבא: ', type: 'full_page_text' }, { id: 'keywords_page', label: '🔑', title: 'מילות מפתח מהדף', command: 'חלץ מילות מפתח עיקריות מתוכן הדף הבא: ', type: 'full_page_text' }, { id: 'translate_page_custom_dialog', label: '🌍', title: 'תרגם את כל הדף...', type: 'prompt_language_page_custom_dialog', baseCommand: 'תרגם את כל תוכן הדף הבא ל{LANG}: ' }, { id: 'fact_check_page', label: '✔️', title: 'אמת את העובדות שבדף הזה', command: 'בדוק את העובדות המוצגות בטקסט הבא וחפש אישורים או סתירות ממקורות אמינים: ', type: 'full_page_text' }, { id: 'teach_me_page_info', label: '🧑🏫', title: 'למד אותי את המידע שבדף', command: 'הסבר לי את המושגים והמידע העיקריים המוצגים בטקסט הבא בצורה פשוטה וברורה, כאילו אתה מלמד אותי את הנושא: ', type: 'full_page_text' }, { id: 'page_to_qa', label: '❓️', title: 'הפוך תוכן דף לשאלות ותשובות', command: 'הפוך את תוכן הדף הבא לפורמט של שאלות ותשובות המסכמות את המידע העיקרי: ', type: 'full_page_text' } ]}, selected_text_extended: { categoryLabel: 'פעולות נוספות על מסומן', actions: [ { id: 'improve_selected', label: '✍️', title: 'שפר ניסוח (מסומן)', command: 'תקן ושפר ניסוח של הטקסט המסומן הבא: ', type: 'selected_text' }, { id: 'ask_on_selected_custom_dialog', label: '❓', title: 'שאל על המסומן...', type: 'prompt_question_custom_dialog', baseCommand: 'בהתייחס לטקסט המסומן הבא: "{TEXT}". ענה על השאלה: "{QUESTION}"' }, ]} }; // === THEME MANAGEMENT === function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); currentTheme = theme; const themeToggleBtn = aiChatSidebarInstance?.getThemeToggleButton(); if (themeToggleBtn) { themeToggleBtn.innerHTML = theme === 'dark' ? '☀️' : '🌙'; themeToggleBtn.title = theme === 'dark' ? 'מצב בהיר' : 'מצב כהה'; } } function toggleTheme() { const newTheme = currentTheme === 'light' ? 'dark' : 'light'; applyTheme(newTheme); chrome.storage.local.set({ [THEME_STORAGE_KEY]: newTheme }); } function loadThemePreference() { chrome.storage.local.get(THEME_STORAGE_KEY, (result) => { applyTheme(result[THEME_STORAGE_KEY] || 'light'); }); } // === ONBOARDING === function checkForOnboarding() { chrome.storage.local.get(['showAiSuiteProOnboarding'], (result) => { if (result.showAiSuiteProOnboarding) { const onboardingVersion = result.showAiSuiteProOnboarding; const shownKey = ONBOARDING_SHOWN_KEY_PREFIX_CS + onboardingVersion; chrome.storage.local.get([shownKey], (shownResult) => { if (!shownResult[shownKey]) { showOnboardingFlow(onboardingVersion); chrome.storage.local.set({ [shownKey]: true }); // This content script instance handled it. // Background script won't clear this, this specific instance clears its trigger chrome.storage.local.remove('showAiSuiteProOnboarding', () => { if (chrome.runtime.lastError) { console.error("Error removing onboarding flag:", chrome.runtime.lastError.message); } }); } }); } }); } function showOnboardingFlow(version) { const existingOverlay = document.querySelector('.ai-custom-prompt-overlay.onboarding'); if (existingOverlay) existingOverlay.remove(); const overlay = document.createElement('div'); overlay.className = 'ai-custom-prompt-overlay visible onboarding'; overlay.style.zIndex = "2147483648"; // Ensure it's on top const dialog = document.createElement('div'); dialog.className = 'ai-custom-prompt-dialog'; dialog.style.maxWidth = "550px"; dialog.innerHTML = ` <div class="prompt-title" style="font-size: 22px;">🎉 ברוכים הבאים ל-AI Suite Pro ${version}!</div> <div class="prompt-message" style="text-align: right; line-height: 1.7;"> <p>גלה את היכולות החדשות והמשופרות:</p> <ul> <li><strong>מצב כהה:</strong> הפעל מצב כהה לנוחות צפייה דרך כפתור ה-🌙 בצ'אט.</li> <li><strong>שמירת היסטוריית צ'אטים:</strong> שמור, טען ומחק שיחות צ'אט באמצעות הכפתורים 💾 (שמירה) ו-📜 (היסטוריה) בצ'אט. הצ'אטים נשמרים אוטומטית!</li> <li><strong>סרגל כלים מהיר:</strong> סמן טקסט בכל אתר כדי לגשת לפעולות AI מהירות.</li> <li><strong>צ'אט AI מתקדם:</strong> פתח את הצ'אט מלחצן התוסף או מהתפריטים השונים.</li> <li><strong>כפתור פעולות צף (FAB):</strong> גש לפעולות AI על כל הדף דרך הכפתור 🧠 בפינה הימנית תחתונה.</li> </ul> <p>אנו מקווים שתהנה מהשימוש בתוסף!</p> </div> <div class="prompt-actions" style="justify-content: center;"> <button class="prompt-button primary onboard-dismiss-btn">הבנתי, נתחיל!</button> </div> `; overlay.appendChild(dialog); document.body.appendChild(overlay); const dismissBtn = dialog.querySelector('.onboard-dismiss-btn'); const closeAndRemove = () => { overlay.classList.remove('visible'); setTimeout(() => overlay.remove(), 300); }; dismissBtn.onclick = closeAndRemove; overlay.onclick = (e) => { if (e.target === overlay) closeAndRemove(); }; dialog.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeAndRemove(); }); dismissBtn.focus(); } // === UI HELPER FUNCTIONS === function getPageContentForAI() { let text = ""; if (document.body) text = (document.querySelector('main article') || document.querySelector('article') || document.querySelector('main') || document.body).innerText || ""; return text.substring(0, 10000); } function ensureTooltipAiHelperWrapper() { if (!tooltipAiHelperWrapper) { tooltipAiHelperWrapper = document.createElement('div'); tooltipAiHelperWrapper.id = 'ai-text-helper-wrapper'; document.body.appendChild(tooltipAiHelperWrapper); } } function showTooltipProcessingUI(actionText = "מעבד...") { ensureTooltipAiHelperWrapper(); tooltipAiHelperWrapper.innerHTML = ''; const banner = document.createElement("div"); banner.className = "processing-banner"; banner.innerText = actionText; tooltipAiHelperWrapper.appendChild(banner); const progressContainer = document.createElement("div"); progressContainer.className = "progress-container"; const progressBar = document.createElement("div"); progressBar.className = "progress-bar"; progressContainer.appendChild(progressBar); tooltipAiHelperWrapper.appendChild(progressContainer); let startTime = Date.now(); let intervalId = setInterval(() => { let progress = Math.min(100, (Date.now() - startTime) / 80); progressBar.style.width = progress + "%"; }, 100); return () => { clearInterval(intervalId); if(tooltipAiHelperWrapper) tooltipAiHelperWrapper.innerHTML = '';}; } function showTooltipPopup(responseText, originalText = "", autoDismissDelay = 0) { ensureTooltipAiHelperWrapper(); tooltipAiHelperWrapper.innerHTML = ''; const popupOverlay = document.createElement("div"); popupOverlay.className = "popup-overlay visible"; const popup = document.createElement("div"); popup.className = "popup"; popup.tabIndex = -1; const popupContent = document.createElement("div"); popupContent.className = "popup-content"; popupContent.innerHTML = ''; const lines = responseText.split('\n'); lines.forEach((line, index) => { if (index > 0) popupContent.appendChild(document.createElement('br')); popupContent.appendChild(document.createTextNode(line)); }); popup.appendChild(popupContent); const actionsBar = document.createElement("div"); actionsBar.className = "popup-actions"; const closeButton = document.createElement("button"); closeButton.className = "close"; closeButton.innerHTML = "✖"; closeButton.title = "סגור"; closeButton.onclick = () => { if (tooltipAiHelperWrapper) tooltipAiHelperWrapper.innerHTML = ''; }; actionsBar.appendChild(closeButton); popup.appendChild(actionsBar); popupOverlay.appendChild(popup); tooltipAiHelperWrapper.appendChild(popupOverlay); popup.focus(); popupOverlay.addEventListener("keydown", (e) => { if (e.key === "Escape") closeButton.click(); }); popupOverlay.addEventListener('click', (event) => { if (event.target === popupOverlay) closeButton.click(); }); if (autoDismissDelay > 0) { setTimeout(() => { if (popupOverlay.classList.contains('visible') && popupOverlay.parentNode) { closeButton.click(); } }, autoDismissDelay); } } function getSentenceAroundSelection(selection) { if (!selection || selection.rangeCount === 0) return null; const range = selection.getRangeAt(0); const selectedText = selection.toString(); if (!selectedText.trim()) return null; let node = range.commonAncestorContainer; if (node.nodeType !== Node.ELEMENT_NODE) node = node.parentNode; if (!node || typeof node.innerText !== 'string') return null; const fullText = node.innerText; const selectionStartIndex = fullText.indexOf(selectedText); if (selectionStartIndex === -1) return selectedText; let sentenceStart = selectionStartIndex; while (sentenceStart > 0 && !['.', '!', '?', '\n'].includes(fullText[sentenceStart - 1])) { sentenceStart--; } while (sentenceStart < selectionStartIndex && /\s/.test(fullText[sentenceStart])) { sentenceStart++; } let sentenceEnd = selectionStartIndex + selectedText.length; while (sentenceEnd < fullText.length && !['.', '!', '?', '\n'].includes(fullText[sentenceEnd])) { sentenceEnd++; } if (sentenceEnd < fullText.length && ['.', '!', '?'].includes(fullText[sentenceEnd])) { sentenceEnd++; } let sentence = fullText.substring(sentenceStart, sentenceEnd).trim(); if (!sentence.includes(selectedText)) { const searchWindow = 200; const windowStart = Math.max(0, selectionStartIndex - searchWindow); const windowEnd = Math.min(fullText.length, selectionStartIndex + selectedText.length + searchWindow); const textWindow = fullText.substring(windowStart, windowEnd); const localSelectionStart = selectedText ? textWindow.indexOf(selectedText) : -1; if(localSelectionStart !== -1) { let localSentenceStart = localSelectionStart; while(localSentenceStart > 0 && !['.','!','?','\n'].includes(textWindow[localSentenceStart-1])) localSentenceStart--; while(localSentenceStart < localSelectionStart && /\s/.test(textWindow[localSentenceStart])) localSentenceStart++; let localSentenceEnd = localSelectionStart + selectedText.length; while(localSentenceEnd < textWindow.length && !['.','!','?','\n'].includes(textWindow[localSentenceEnd])) localSentenceEnd++; if(localSentenceEnd < textWindow.length && ['.','!','?'].includes(textWindow[localSentenceEnd])) localSentenceEnd++; sentence = textWindow.substring(localSentenceStart, localSentenceEnd).trim(); } else { sentence = selectedText; } } return sentence; } function showCustomPrompt({ title = "קלט נדרש", message, inputPlaceholder = "", inputType = "text", defaultValue = "" }) { return new Promise((resolve) => { const existingOverlay = document.querySelector('.ai-custom-prompt-overlay:not(.onboarding)'); if (existingOverlay) existingOverlay.remove(); const overlay = document.createElement('div'); overlay.className = 'ai-custom-prompt-overlay'; const dialog = document.createElement('div'); dialog.className = 'ai-custom-prompt-dialog'; dialog.innerHTML = ` <div class="prompt-header"> ${title ? `<div class="prompt-title">${title}</div>` : ''} </div> ${message ? `<div class="prompt-message">${message}</div>` : ''} <input type="${inputType}" class="prompt-input-field" placeholder="${inputPlaceholder}" value="${defaultValue}"> <div class="prompt-actions"> <button class="prompt-button secondary cancel-btn">ביטול</button> <button class="prompt-button primary confirm-btn">אישור</button> </div> `; overlay.appendChild(dialog); document.body.appendChild(overlay); const inputField = dialog.querySelector('.prompt-input-field'); const confirmBtn = dialog.querySelector('.confirm-btn'); const cancelBtn = dialog.querySelector('.cancel-btn'); requestAnimationFrame(() => { overlay.classList.add('visible'); inputField.focus(); inputField.select(); }); const closeDialog = (value) => { overlay.classList.remove('visible'); setTimeout(() => { overlay.remove(); resolve(value); }, 200); }; confirmBtn.onclick = () => closeDialog(inputField.value); cancelBtn.onclick = () => closeDialog(null); overlay.onclick = (e) => { if (e.target === overlay) closeDialog(null); }; inputField.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); closeDialog(inputField.value); } else if (e.key === 'Escape') { e.preventDefault(); closeDialog(null);}}; }); } // === TOOLTIP/ACTION BUTTON CREATION & HANDLING === async function createTooltipActionButton(actionId, actionConfig, selectionObject, type = 'quick') { const button = document.createElement('button'); button.className = 'tooltip-button'; button.title = actionConfig.title || ''; const iconSpan = document.createElement('span'); iconSpan.className = 'tooltip-icon'; iconSpan.textContent = actionConfig.label || ''; button.appendChild(iconSpan); if (type === 'quick' && actionConfig.shortLabel) { const textSpan = document.createElement('span'); textSpan.textContent = actionConfig.shortLabel || ''; button.appendChild(textSpan); } else if (type === 'list' && actionConfig.title) { const textSpan = document.createElement('span'); textSpan.textContent = actionConfig.title || ''; button.appendChild(textSpan); } button.addEventListener('click', async (e) => { e.stopPropagation(); removeTooltip(); let fullPrompt = ""; let contentForAI = ""; let actionTitleForDisplay = actionConfig.title; const selectedText = selectionObject ? selectionObject.toString().trim() : ""; switch (actionConfig.type) { case 'selected_text': if (!selectedText) { alert("אנא סמן טקסט."); return; } contentForAI = selectedText; fullPrompt = actionConfig.command + contentForAI; break; case 'explain_with_context': if (!selectedText) { alert("אנא סמן טקסט להסבר."); return; } const sentence = getSentenceAroundSelection(selectionObject); contentForAI = sentence || selectedText; fullPrompt = `הסבר את החלק המסומן (בתוך גרשיים כפולות) בהקשר של המשפט המלא: "${selectedText}".\nהמשפט המלא הוא: ${contentForAI}`; actionTitleForDisplay = "הסבר בהקשר"; break; case 'full_page_text': contentForAI = getPageContentForAI(); if (!contentForAI) { alert("אין תוכן בדף."); return; } fullPrompt = actionConfig.command + contentForAI; break; case 'prompt_language_selected_custom_dialog': if (!selectedText) { alert("אנא סמן טקסט."); return; } contentForAI = selectedText; const targetLangSel = await showCustomPrompt({ title: "תרגום טקסט מסומן", message: `לאיזו שפה לתרגם את הטקסט: "${selectedText.substring(0,50)}..."?`, inputPlaceholder: "לדוגמה: אנגלית, ספרדית" }); if (targetLangSel && targetLangSel.trim() !== "") { fullPrompt = actionConfig.baseCommand.replace('{LANG}', targetLangSel) + contentForAI; actionTitleForDisplay = `תרגום ל${targetLangSel}`; } else return; break; case 'prompt_language_page_custom_dialog': contentForAI = getPageContentForAI(); if (!contentForAI) { alert("אין תוכן בדף."); return; } const targetLangPage = await showCustomPrompt({ title: "תרגום כל הדף", message: "לאיזו שפה לתרגם את כל תוכן הדף הנוכחי?", inputPlaceholder: "לדוגמה: אנגלית, צרפתית" }); if (targetLangPage && targetLangPage.trim() !== "") { fullPrompt = actionConfig.baseCommand.replace('{LANG}', targetLangPage) + contentForAI; actionTitleForDisplay = `תרגום דף ל${targetLangPage}`; } else return; break; case 'prompt_question_custom_dialog': if (!selectedText) { alert("אנא סמן טקסט."); return; } contentForAI = selectedText; const userQ = await showCustomPrompt({ title: "שאל על הטקסט המסומן", message: `הקלד את שאלתך על הטקסט: "${selectedText.substring(0,100)}..."`, inputPlaceholder: "שאלתך כאן..." }); if (userQ && userQ.trim() !== "") { fullPrompt = actionConfig.baseCommand.replace('{TEXT}', selectedText).replace('{QUESTION}', userQ); } else return; break; case 'custom_action': if (actionConfig.actionFn) { actionConfig.actionFn(); } else { console.warn("Custom action with no actionFn:", actionConfig.id); } return; default: if (actionConfig.command) { fullPrompt = actionConfig.command + (selectedText || ""); } else { console.warn("Tooltip action default case with no command:", actionConfig); return; } break; } if (fullPrompt) await handleTooltipAIAction(fullPrompt, contentForAI, actionTitleForDisplay); }); return button; } function removeTooltip() { const tooltipToRemove = currentTooltip; tooltipActionListVisible = false; currentTooltip = null; if (tooltipToRemove) { tooltipToRemove.classList.remove('visible'); setTimeout(() => { if (tooltipToRemove && tooltipToRemove.parentNode) tooltipToRemove.remove(); }, 200); } } async function createTooltip(x, y, selectionObject) { removeTooltip(); const newTooltipElement = document.createElement('div'); newTooltipElement.className = 'ai-helper-tooltip'; const quickActionsBar = document.createElement('div'); quickActionsBar.className = 'tooltip-quick-actions'; const buttonPromises = QUICK_ACTION_IDS.map(id => { const cfg = allActionsConfig[id]; if (cfg) return createTooltipActionButton(id, cfg, selectionObject, 'quick'); return Promise.resolve(null); }); try { const buttonElements = await Promise.all(buttonPromises); buttonElements.forEach(buttonElement => { if (buttonElement && buttonElement instanceof Node) quickActionsBar.appendChild(buttonElement); else if (buttonElement) { console.error("createTooltip: Received non-Node element for quick action:", buttonElement); quickActionsBar.appendChild(buttonElement); } }); } catch (error) { console.error("createTooltip: Error awaiting quick action buttons:", error); } newTooltipElement.appendChild(quickActionsBar); const moreToggle = document.createElement('button'); moreToggle.className = 'tooltip-more-actions-toggle'; moreToggle.innerHTML = 'עוד <span class="tooltip-icon">▾</span>'; moreToggle.addEventListener('click', async (e) => { e.stopPropagation(); await toggleTooltipActionList(newTooltipElement, selectionObject); }); newTooltipElement.appendChild(moreToggle); try { document.body.appendChild(newTooltipElement); } catch (error) { console.error("createTooltip: Error appending tooltip to body:", error); return; } const rect = newTooltipElement.getBoundingClientRect(); let left = x - rect.width / 2; let top = y - rect.height - 10; if (left < 0) left = 5; if (left + rect.width > window.innerWidth) left = window.innerWidth - rect.width - 5; if (top < 0) top = y + 15; newTooltipElement.style.left = `${left + window.scrollX}px`; newTooltipElement.style.top = `${top + window.scrollY}px`; currentTooltip = newTooltipElement; tooltipActionListVisible = false; setTimeout(() => { if (currentTooltip === newTooltipElement && newTooltipElement.parentNode) newTooltipElement.classList.add('visible'); else if (newTooltipElement.parentNode) newTooltipElement.remove(); }, 10); } async function toggleTooltipActionList(tooltipElement, selectionObject) { if (!tooltipElement) { console.warn("toggleTooltipActionList: tooltipElement is null"); return; } let actionList = tooltipElement.querySelector('.tooltip-action-list'); if (!actionList) { actionList = document.createElement('div'); actionList.className = 'tooltip-action-list'; const allButtonPromisesWithDetails = []; Object.keys(allActionsConfig).forEach(categoryKey => { const categoryConfig = allActionsConfig[categoryKey]; if (categoryConfig.categoryLabel && categoryConfig.actions && Array.isArray(categoryConfig.actions)) { categoryConfig.actions.forEach(actionCfg => { if (actionCfg && actionCfg.id && !QUICK_ACTION_IDS.includes(actionCfg.id)) { const buttonPromise = createTooltipActionButton(actionCfg.id, actionCfg, selectionObject, 'list') .then(buttonNode => ({ button: buttonNode, categoryKey: categoryKey, originalConfig: actionCfg })) .catch(error => { console.error(`Error creating button for ${actionCfg.id} in category ${categoryKey}:`, error); const errorPlaceholder = document.createElement('span'); errorPlaceholder.textContent = `Error: ${actionCfg.id}`; errorPlaceholder.style.color = 'red'; return { button: errorPlaceholder, categoryKey: categoryKey, originalConfig: actionCfg }; }); allButtonPromisesWithDetails.push(buttonPromise); } }); } }); try { const resolvedButtonsWithDetails = await Promise.all(allButtonPromisesWithDetails); const buttonsByCategory = {}; resolvedButtonsWithDetails.forEach(item => { if (!buttonsByCategory[item.categoryKey]) buttonsByCategory[item.categoryKey] = []; buttonsByCategory[item.categoryKey].push(item.button); }); Object.keys(allActionsConfig).forEach(categoryKey => { const categoryConfig = allActionsConfig[categoryKey]; if (categoryConfig.categoryLabel && buttonsByCategory[categoryKey] && buttonsByCategory[categoryKey].length > 0) { const categoryTitleElement = document.createElement('div'); categoryTitleElement.className = 'tooltip-category-title'; categoryTitleElement.textContent = categoryConfig.categoryLabel; actionList.appendChild(categoryTitleElement); buttonsByCategory[categoryKey].forEach(buttonNode => { if (buttonNode && buttonNode instanceof Node) actionList.appendChild(buttonNode); }); } }); } catch (error) { console.error("toggleTooltipActionList: Error awaiting list action buttons:", error); } tooltipElement.appendChild(actionList); } tooltipActionListVisible = !tooltipActionListVisible; actionList.style.display = tooltipActionListVisible ? 'block' : 'none'; } async function handleTooltipAIAction(fullPrompt, contextText = "", actionName = "פעולה") { const cleanupProcessingUI = showTooltipProcessingUI(actionName); try { const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: fullPrompt }) }); cleanupProcessingUI(); if (!response.ok) { const errTxt = await response.text(); let displayError = `שגיאת שרת (${response.status})`; try { const errJson = JSON.parse(errTxt); if (errJson && errJson.error) { displayError += `: ${errJson.error}`; } else { displayError += `: ${errTxt.substring(0,100)}`; } } catch (e) { displayError += `: ${errTxt.substring(0,100)}`; } showTooltipPopup(displayError, contextText); return; } const data = await response.json(); if (data && data.text) showTooltipPopup(data.text, contextText); else showTooltipPopup("לא התקבלה תשובה תקינה מהשרת.", contextText); } catch (error) { cleanupProcessingUI(); console.error("AI Tooltip Error:", error); showTooltipPopup(`שגיאת תקשורת: ${error.message}`, contextText); } } // === FAB (Floating Action Button) CODE === async function createFabMenuItem(actionId, actionConfig) { const button = document.createElement('button'); button.className = 'fab-menu-button'; button.title = actionConfig.title || ''; const iconSpan = document.createElement('span'); iconSpan.className = 'fab-menu-icon'; iconSpan.textContent = actionConfig.label || ''; button.appendChild(iconSpan); const textSpan = document.createElement('span'); textSpan.className = 'fab-menu-text'; textSpan.textContent = actionConfig.title || ''; button.appendChild(textSpan); button.addEventListener('click', async (e) => { e.stopPropagation(); hideFabMenu(); let fullPrompt = ""; let contentForAI = ""; let actionTitleForDisplay = actionConfig.title; switch (actionConfig.type) { case 'full_page_text': contentForAI = getPageContentForAI(); if (!contentForAI) { alert("אין תוכן בדף."); return; } fullPrompt = actionConfig.command + contentForAI; break; case 'prompt_language_page_custom_dialog': contentForAI = getPageContentForAI(); if (!contentForAI) { alert("אין תוכן בדף."); return; } const targetLangPage = await showCustomPrompt({ title: "תרגום כל הדף", message: "לאיזו שפה לתרגם את כל תוכן הדף הנוכחי?", inputPlaceholder: "לדוגמה: אנגלית, צרפתית" }); if (targetLangPage && targetLangPage.trim() !== "") { fullPrompt = actionConfig.baseCommand.replace('{LANG}', targetLangPage) + contentForAI; actionTitleForDisplay = `תרגום דף ל${targetLangPage}`; } else return; break; case 'custom_action': if (actionConfig.actionFn) actionConfig.actionFn(); else console.warn("FAB custom action with no actionFn:", actionConfig.id); return; default: console.warn("FAB action with unhandled type or no command:", actionConfig); return; } if (fullPrompt) await handleTooltipAIAction(fullPrompt, contentForAI, actionTitleForDisplay); }); return button; } async function populateFabMenu() { if (!fabMenuElement) return; fabMenuElement.innerHTML = ''; const pageActionPromises = []; if (allActionsConfig.open_ai_chat) { pageActionPromises.push(createFabMenuItem('open_ai_chat_fab', { ...allActionsConfig.open_ai_chat, title: "פתח צ'אט AI" })); } Object.values(allActionsConfig).forEach(categoryOrAction => { if (categoryOrAction.categoryLabel && categoryOrAction.actions) { categoryOrAction.actions.forEach(actionCfg => { if (actionCfg.type === 'full_page_text' || actionCfg.type === 'prompt_language_page_custom_dialog' || actionCfg.id === 'share_page') { pageActionPromises.push(createFabMenuItem(actionCfg.id, actionCfg)); } }); } else if (categoryOrAction.id && (categoryOrAction.type === 'full_page_text' || categoryOrAction.type === 'prompt_language_page_custom_dialog' || categoryOrAction.id === 'share_page')) { if (categoryOrAction.id !== 'open_ai_chat') pageActionPromises.push(createFabMenuItem(categoryOrAction.id, categoryOrAction)); } }); const buttons = await Promise.all(pageActionPromises); buttons.forEach(button => { if (button) fabMenuElement.appendChild(button); }); } function showFabMenu() { if (!fabMenuElement || !fabButtonElement) return; populateFabMenu(); const fabRect = fabButtonElement.getBoundingClientRect(); const menuHeightEstimate = fabMenuElement.childElementCount * 40; let bottomPosition = window.innerHeight - fabRect.top + 10; let rightPosition = window.innerWidth - fabRect.right; if ((fabRect.top - menuHeightEstimate - 10) < 0 && (fabRect.bottom + menuHeightEstimate + 10) < window.innerHeight) { bottomPosition = window.innerHeight - fabRect.bottom - menuHeightEstimate - 10; } else if ((fabRect.top - menuHeightEstimate - 10) < 0) { bottomPosition = 10; } fabMenuElement.style.bottom = `${bottomPosition}px`; fabMenuElement.style.right = `${rightPosition}px`; fabMenuElement.style.left = 'auto'; fabMenuElement.classList.add('visible'); fabMenuVisible = true; } function hideFabMenu() { if (!fabMenuElement) return; fabMenuElement.classList.remove('visible'); fabMenuVisible = false; } function toggleFabMenu() { if (fabMenuVisible) hideFabMenu(); else showFabMenu(); } function createFab() { if (document.getElementById(FAB_ID)) return; fabButtonElement = document.createElement('button'); fabButtonElement.id = FAB_ID; fabButtonElement.title = "פעולות AI על הדף"; fabButtonElement.innerHTML = `🧠`; fabButtonElement.addEventListener('click', (e) => { e.stopPropagation(); toggleFabMenu(); }); document.body.appendChild(fabButtonElement); fabMenuElement = document.createElement('div'); fabMenuElement.id = FAB_MENU_ID; document.body.appendChild(fabMenuElement); } // === INITIALIZATION FUNCTION === function initializeExtension() { loadThemePreference(); checkForOnboarding(); createFab(); } if (document.readyState === 'complete' || document.readyState === 'interactive') { initializeExtension(); } else { document.addEventListener('DOMContentLoaded', initializeExtension); } // === EVENT LISTENERS === document.addEventListener('mouseup', (event) => { if (fabMenuVisible && fabMenuElement && !fabMenuElement.contains(event.target) && event.target !== fabButtonElement && !fabButtonElement.contains(event.target)) hideFabMenu(); if (event.target.closest('#ai-text-helper-wrapper') || event.target.closest('.ai-helper-tooltip') || event.target.closest(`#${CHAT_SIDEBAR_ID}`) || event.target.closest('.ai-custom-prompt-overlay') || event.target.matches('input, textarea, [contenteditable="true"]') || event.target.closest(`#${FAB_ID}`) || event.target.closest(`#${FAB_MENU_ID}`)) return; const selection = window.getSelection(); const selectedText = selection ? selection.toString().trim() : ""; if (selectedText.length > 1 && selectedText.length < 10000) { if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); if (rect && (rect.width > 0 || rect.height > 0)) createTooltip(rect.left + rect.width / 2, rect.top, selection); else if (!tooltipActionListVisible) removeTooltip(); } else if (!tooltipActionListVisible) removeTooltip(); } else if (!tooltipActionListVisible) removeTooltip(); }); document.addEventListener('mousedown', (event) => { if (currentTooltip && !currentTooltip.contains(event.target) && !event.target.closest(`#${CHAT_SIDEBAR_ID}`) && !event.target.closest('.ai-custom-prompt-overlay') && !event.target.closest('#ai-text-helper-wrapper')) removeTooltip(); }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { const customPromptOverlay = document.querySelector('.ai-custom-prompt-overlay.visible:not(.onboarding)'); // Prioritize non-onboarding prompts if (customPromptOverlay) { const cancelBtn = customPromptOverlay.querySelector('.cancel-btn'); if (cancelBtn) cancelBtn.click(); return; } const onboardingOverlay = document.querySelector('.ai-custom-prompt-overlay.visible.onboarding'); if (onboardingOverlay) { const dismissBtn = onboardingOverlay.querySelector('.onboard-dismiss-btn'); if(dismissBtn) dismissBtn.click(); return; } if (aiChatSidebarInstance && aiChatSidebarInstance.isHistoryPanelOpen()) { aiChatSidebarInstance.closeHistoryPanel(); } else if (fabMenuVisible) { hideFabMenu(); } else if (currentTooltip) { removeTooltip(); } else if (tooltipAiHelperWrapper && tooltipAiHelperWrapper.innerHTML !== '') { tooltipAiHelperWrapper.innerHTML = ''; } else if (aiChatSidebarInstance && aiChatSidebarInstance.isOpen()) { aiChatSidebarInstance.close(); } } }); // === AI CHAT SIDEBAR === function launchAIChatSidebar() { if (aiChatSidebarInstance && aiChatSidebarInstance.isOpen()) { aiChatSidebarInstance.close(); return; } if (aiChatSidebarInstance) { aiChatSidebarInstance.open(); return; } aiChatSidebarInstance = (() => { let sidebarElement = null; let chatTitleEl, clearChatBtnEl, closeChatBtnEl, themeToggleBtnEl, renameChatBtnEl, viewHistoryBtnEl; let messagesAreaEl, inputFieldEl, sendBtnEl; let historyPanelEl, historyListEl; let chatMessages = []; let isLoading = false; let _isOpen = false; const MAX_HISTORY_MESSAGES_TO_SEND = 10; const CHAT_HISTORY_STORAGE_KEY = 'aiChatHistory'; let currentChatSessionId = null; let autoSaveTimer = null; const AUTO_SAVE_DEBOUNCE = 3000; // 3 seconds function generateChatId() { return `chat_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; } function formatChatTitle(date) { return `צ'אט ${date.toLocaleDateString('he-IL', {day:'2-digit',month:'2-digit',year:'numeric'})} ${date.toLocaleTimeString('he-IL', {hour:'2-digit', minute:'2-digit'})}`; } function updateChatTitleDisplay(title) { if (chatTitleEl) { const displayTitle = title || formatChatTitle(new Date()); chatTitleEl.textContent = displayTitle.length > 30 ? displayTitle.substring(0, 27) + "..." : displayTitle; } } function createSidebarDOM() { sidebarElement = document.createElement('aside'); sidebarElement.id = CHAT_SIDEBAR_ID; sidebarElement.innerHTML = ` <header class="chat-header"> <span class="chat-title" id="ai-chat-title">AI Chat</span> <div class="header-controls"> <button id="ai-chat-rename-btn" class="chat-control-btn" title="שמור בשם אחר / עדכן שם">✏️</button> <button id="ai-chat-history-btn" class="chat-control-btn" title="היסטוריית צ'אטים">📜</button> <button id="ai-chat-theme-toggle-btn" class="chat-control-btn" title="מצב כהה">🌙</button> <button id="ai-chat-clear-btn" class="chat-control-btn" title="צ'אט חדש">➕</button> <button id="ai-chat-close-btn" class="chat-control-btn" title="סגור (Esc)">✖</button> </div> </header> <main class="chat-main-content"> <div class="chat-messages-area" id="ai-chat-messages-area"></div> </main> <div class="chat-input-container"> <textarea class="chat-input-field" id="ai-chat-input-field" placeholder="הקלד הודעה..." rows="1"></textarea> <button class="chat-send-button" id="ai-chat-send-btn" title="שלח"> <svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path></svg> </button> </div> <div id="ai-chat-history-panel" class="chat-history-panel"> <div class="history-panel-header"> <h3>היסטוריית צ'אטים</h3> <button id="ai-chat-history-close-btn" class="chat-control-btn">×</button> </div> <div id="ai-chat-history-list" class="history-panel-list"> </div> </div> `; document.body.appendChild(sidebarElement); chatTitleEl = sidebarElement.querySelector('#ai-chat-title'); renameChatBtnEl = sidebarElement.querySelector('#ai-chat-rename-btn'); viewHistoryBtnEl = sidebarElement.querySelector('#ai-chat-history-btn'); themeToggleBtnEl = sidebarElement.querySelector('#ai-chat-theme-toggle-btn'); clearChatBtnEl = sidebarElement.querySelector('#ai-chat-clear-btn'); closeChatBtnEl = sidebarElement.querySelector('#ai-chat-close-btn'); messagesAreaEl = sidebarElement.querySelector('#ai-chat-messages-area'); inputFieldEl = sidebarElement.querySelector('#ai-chat-input-field'); sendBtnEl = sidebarElement.querySelector('#ai-chat-send-btn'); historyPanelEl = sidebarElement.querySelector('#ai-chat-history-panel'); historyListEl = sidebarElement.querySelector('#ai-chat-history-list'); const historyCloseBtn = sidebarElement.querySelector('#ai-chat-history-close-btn'); if(historyCloseBtn) historyCloseBtn.onclick = closeHistoryPanel; attachEventListeners(); if (themeToggleBtnEl) { themeToggleBtnEl.innerHTML = currentTheme === 'dark' ? '☀️' : '🌙'; themeToggleBtnEl.title = currentTheme === 'dark' ? 'מצב בהיר' : 'מצב כהה'; } } function attachEventListeners() { closeChatBtnEl.onclick = close; themeToggleBtnEl.onclick = () => { toggleTheme(); }; if (renameChatBtnEl) renameChatBtnEl.onclick = promptAndSaveChatName; if (viewHistoryBtnEl) viewHistoryBtnEl.onclick = openHistoryPanel; clearChatBtnEl.onclick = () => { startNewChat(); if(inputFieldEl) inputFieldEl.focus(); }; sendBtnEl.onclick = handleSendMessage; inputFieldEl.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }); inputFieldEl.addEventListener('input', autoResizeInput); } function autoResizeInput() { inputFieldEl.style.height = 'auto'; inputFieldEl.style.height = (Math.min(inputFieldEl.scrollHeight, 120)) + 'px'; } function setLoadingState(loading) { isLoading = loading; sendBtnEl.disabled = loading; inputFieldEl.disabled = loading; let loadingDiv = messagesAreaEl.querySelector('.chat-message.loading'); if (loading) { if (!loadingDiv) { loadingDiv = document.createElement('div'); loadingDiv.className = 'chat-message loading'; const dots = document.createElement('div'); dots.className = 'loading-dots'; for (let i = 0; i < 3; i++) dots.appendChild(document.createElement('span')); loadingDiv.appendChild(dots); messagesAreaEl.appendChild(loadingDiv); } messagesAreaEl.scrollTop = messagesAreaEl.scrollHeight; } else if (loadingDiv) loadingDiv.remove(); } function addMessageToHistory(role, content) { chatMessages.push({ role, content }); triggerAutoSave(); } function addMessageToUI(text, type, addToHistoryArr = true) { const msgDiv = document.createElement('div'); msgDiv.className = `chat-message ${type}`; msgDiv.innerHTML = ''; const lines = text.split('\n'); lines.forEach((line, index) => { if (index > 0) msgDiv.appendChild(document.createElement('br')); const parts = line.split(/\*\*(.*?)\*\*/g); parts.forEach((part, partIndex) => { if (partIndex % 2 === 1) { const strong = document.createElement('strong'); strong.textContent = part; msgDiv.appendChild(strong); } else { msgDiv.appendChild(document.createTextNode(part)); } }); }); if (messagesAreaEl) { const loadingDiv = messagesAreaEl.querySelector('.chat-message.loading'); if (loadingDiv) messagesAreaEl.insertBefore(msgDiv, loadingDiv); else messagesAreaEl.appendChild(msgDiv); messagesAreaEl.scrollTop = messagesAreaEl.scrollHeight; } else { console.error("addMessageToUI (IIFE): messagesAreaEl is not defined!"); } if (addToHistoryArr && (type === 'user' || type === 'assistant')) { addMessageToHistory(type, text); } } async function getChatSession(sessionId) { try { const result = await chrome.storage.local.get(CHAT_HISTORY_STORAGE_KEY); const history = result[CHAT_HISTORY_STORAGE_KEY] || []; return history.find(session => session.id === sessionId); } catch (e) { console.error("Error fetching chat session:", e); return null;} } async function autoSaveCurrentChat(isClosing = false) { if (chatMessages.length === 0) return; const sessionIdToSave = currentChatSessionId || generateChatId(); let currentTitle = formatChatTitle(new Date()); if(currentChatSessionId){ const existingSession = await getChatSession(currentChatSessionId); if(existingSession) currentTitle = existingSession.title; } // Keep existing title on auto-save unless it's a new chat const chatSession = { id: sessionIdToSave, title: currentTitle, messages: [...chatMessages], createdAt: (await getChatSession(sessionIdToSave))?.createdAt || Date.now(), updatedAt: Date.now() }; try { const result = await chrome.storage.local.get(CHAT_HISTORY_STORAGE_KEY); const history = result[CHAT_HISTORY_STORAGE_KEY] || []; const existingIndex = history.findIndex(session => session.id === sessionIdToSave); if (existingIndex > -1) { history[existingIndex] = chatSession; } else { history.unshift(chatSession); } await chrome.storage.local.set({ [CHAT_HISTORY_STORAGE_KEY]: history }); currentChatSessionId = sessionIdToSave; // Ensure it's set for subsequent auto-saves updateChatTitleDisplay(chatSession.title); if (!isClosing) { console.log(`Chat "${chatSession.title}" auto-saved.`); } if (historyPanelEl && historyPanelEl.classList.contains('visible')) { openHistoryPanel(); } // Refresh history panel if open } catch (error) { console.error("Error auto-saving chat:", error); } } function triggerAutoSave() { clearTimeout(autoSaveTimer); autoSaveTimer = setTimeout(() => autoSaveCurrentChat(), AUTO_SAVE_DEBOUNCE); } async function promptAndSaveChatName() { if (!currentChatSessionId || chatMessages.length === 0) { showTooltipPopup("אין צ'אט פעיל לשמירה או שינוי שם.", "", 2000); return; } let existingSession = await getChatSession(currentChatSessionId); let defaultTitle = existingSession ? existingSession.title : formatChatTitle(new Date()); const promptedTitle = await showCustomPrompt({ title: "שנה שם צ'אט", message: `הזן כותרת חדשה לצ'אט "${defaultTitle}":`, defaultValue: defaultTitle, inputPlaceholder: "כותרת הצ'אט" }); if (promptedTitle === null) { console.log("Chat rename cancelled."); return; } const finalTitle = promptedTitle.trim() === "" ? defaultTitle : promptedTitle.trim(); existingSession.title = finalTitle; existingSession.updatedAt = Date.now(); try { const result = await chrome.storage.local.get(CHAT_HISTORY_STORAGE_KEY); const history = result[CHAT_HISTORY_STORAGE_KEY] || []; const existingIndex = history.findIndex(session => session.id === currentChatSessionId); if (existingIndex > -1) { history[existingIndex] = existingSession; } else { history.unshift(existingSession); } // Should not happen if currentChatSessionId is valid await chrome.storage.local.set({ [CHAT_HISTORY_STORAGE_KEY]: history }); updateChatTitleDisplay(finalTitle); showTooltipPopup(`כותרת הצ'אט עודכנה ל-"${finalTitle}"!`, "", 2000); if (historyPanelEl.classList.contains('visible')) openHistoryPanel(); } catch (error) { console.error("Error renaming chat:", error); showTooltipPopup("שגיאה בשינוי שם הצ'אט.", "", 2000); } } async function openHistoryPanel() { try { const result = await chrome.storage.local.get(CHAT_HISTORY_STORAGE_KEY); const history = result[CHAT_HISTORY_STORAGE_KEY] || []; history.sort((a, b) => (b.updatedAt || b.createdAt) - (a.updatedAt || a.createdAt)); historyListEl.innerHTML = ''; if (history.length === 0) { historyListEl.innerHTML = '<p class="empty-history-message">אין צ\'אטים שמורים.</p>'; } else { history.forEach(session => { const itemEl = document.createElement('div'); itemEl.className = 'history-item'; itemEl.innerHTML = ` <div class="history-item-info"> <span class="history-item-title">${session.title}</span> <span class="history-item-date">${new Date(session.updatedAt || session.createdAt).toLocaleString('he-IL', {day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit'})}</span> </div> <div class="history-item-actions"> <button class="history-load-btn" title="טען צ'אט">✔️</button> <button class="history-delete-btn" title="מחק צ'אט">🗑️</button> </div> `; itemEl.querySelector('.history-load-btn').onclick = () => loadChatSession(session.id); itemEl.querySelector('.history-delete-btn').onclick = () => deleteChatSession(session.id); historyListEl.appendChild(itemEl); }); } historyPanelEl.classList.add('visible'); } catch (error) { console.error("Error loading history:", error); historyListEl.innerHTML = '<p class="empty-history-message">שגיאה בטעינת היסטוריה.</p>'; } } function closeHistoryPanel() { if (historyPanelEl) historyPanelEl.classList.remove('visible'); } async function loadChatSession(sessionId) { try { const sessionToLoad = await getChatSession(sessionId); if (sessionToLoad) { messagesAreaEl.innerHTML = ''; chatMessages = [...sessionToLoad.messages]; currentChatSessionId = sessionToLoad.id; chatMessages.forEach(msg => addMessageToUI(msg.content, msg.role, false)); updateChatTitleDisplay(sessionToLoad.title); clearTimeout(autoSaveTimer); // Clear any pending auto-save from previous chat closeHistoryPanel(); inputFieldEl.focus(); } else { showTooltipPopup("הצ'אט לא נמצא.", "", 2000); } } catch (error) { console.error("Error loading chat session:", error); showTooltipPopup("שגיאה בטעינת הצ'אט.", "", 2000); } } async function deleteChatSession(sessionId) { if (!confirm("האם אתה בטוח שברצונך למחוק צ'אט זה?")) return; try { const result = await chrome.storage.local.get(CHAT_HISTORY_STORAGE_KEY); let history = result[CHAT_HISTORY_STORAGE_KEY] || []; history = history.filter(s => s.id !== sessionId); await chrome.storage.local.set({ [CHAT_HISTORY_STORAGE_KEY]: history }); if (currentChatSessionId === sessionId) { startNewChat(); } openHistoryPanel(); showTooltipPopup("הצ'אט נמחק.", "", 1500); } catch (error) { console.error("Error deleting chat session:", error); showTooltipPopup("שגיאה במחיקת הצ'אט.", "", 2000); } } function startNewChat() { messagesAreaEl.innerHTML = ''; chatMessages = []; currentChatSessionId = generateChatId(); updateChatTitleDisplay(formatChatTitle(new Date())); addMessageToUI("הצ'אט נוקה. איך אפשר לעזור היום?", "assistant", false); clearTimeout(autoSaveTimer); } async function sendQueryToAPI() { const currentUserInput = inputFieldEl.value.trim(); if (currentUserInput) { addMessageToUI(currentUserInput, 'user'); inputFieldEl.value = ''; autoResizeInput(); } else if (chatMessages.length === 0) { addMessageToUI("שלום! איך אוכל לעזור לך היום?", "assistant", false); return; } setLoadingState(true); const messagesForPrompt = chatMessages.slice(-MAX_HISTORY_MESSAGES_TO_SEND); let promptString = ""; messagesForPrompt.forEach(msg => { const prefix = msg.role === 'user' ? 'המשתמש' : 'AI'; promptString += `${prefix}: ${msg.content}\n`; }); const apiPayload = { text: promptString.trim() }; try { const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(apiPayload) }); if (!response.ok) { const errorText = await response.text(); let displayError = `שגיאת שרת (${response.status})`; try { const errJson = JSON.parse(errorText); if (errJson && errJson.error) { displayError += `: ${errJson.error}`; } else { displayError += `: ${errorText.substring(0,100)}`; } } catch (e) { displayError += `: ${errorText.substring(0,100)}`; } addMessageToUI(displayError, 'server-error', false); throw new Error(displayError); } const data = await response.json(); const reply = data.text || "לא התקבלה תשובה ברורה מהשרת."; addMessageToUI(reply, 'assistant'); triggerAutoSave(); // Auto-save after AI reply } catch (e) { console.error("AI Chat API Error:", e.message); if (!messagesAreaEl.querySelector('.chat-message.server-error')) { addMessageToUI(`שגיאה בתקשורת: ${e.message}`, 'server-error', false); } } finally { setLoadingState(false); if(inputFieldEl) inputFieldEl.focus(); } } function handleSendMessage() { sendQueryToAPI(); } async function open() { if (!sidebarElement) createSidebarDOM(); requestAnimationFrame(() => { if(sidebarElement) sidebarElement.classList.add('open'); }); _isOpen = true; if(inputFieldEl) inputFieldEl.focus(); if (!currentChatSessionId) { // Only start new if no current session (e.g., first open or after explicit new chat) currentChatSessionId = generateChatId(); updateChatTitleDisplay(formatChatTitle(new Date())); if (chatMessages.length === 0) { addMessageToUI("שלום! איך אוכל לעזור לך היום?", "assistant", false); } } else { // If there's a session ID, try to load its title const session = await getChatSession(currentChatSessionId); if (session) { updateChatTitleDisplay(session.title); } else { // Stale ID, start fresh currentChatSessionId = generateChatId(); updateChatTitleDisplay(formatChatTitle(new Date())); if (chatMessages.length === 0) addMessageToUI("שלום! איך אוכל לעזור לך היום?", "assistant", false); } } } async function close() { clearTimeout(autoSaveTimer); // Clear pending auto-save if (chatMessages.length > 0 && currentChatSessionId) { await autoSaveCurrentChat(true); } // Perform final save on close if (sidebarElement) sidebarElement.classList.remove('open'); _isOpen = false; if (historyPanelEl && historyPanelEl.classList.contains('visible')) closeHistoryPanel(); } return { open, close, isOpen: () => _isOpen, getThemeToggleButton: () => themeToggleBtnEl, isHistoryPanelOpen: () => historyPanelEl && historyPanelEl.classList.contains('visible'), closeHistoryPanel }; })(); aiChatSidebarInstance.open(); } // === END OF CHAT SIDEBAR CODE === chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === "TOGGLE_AI_CHAT_SIDEBAR") { if (!aiChatSidebarInstance) { launchAIChatSidebar(); sendResponse({status: "Sidebar launched and opened"}); } else if (aiChatSidebarInstance.isOpen()) { aiChatSidebarInstance.close(); sendResponse({status: "Sidebar closed"}); } else { aiChatSidebarInstance.open(); sendResponse({status: "Sidebar opened"}); } return true; } }); console.log("AI Suite Pro (Tooltip, Chat, Custom Dialog, Dark Mode, History, Onboarding, AutoSave) content script initialized. Version 1.1.1");
ויש שם עוד שגיאה אבל נראה לי זה אותו רעיון רק כל פעולה שאני רוצה לבצע הוא יוצר עוד שגיאה
-
מה הופך את AI Suite Pro למיוחד?
טולטיפ הקסם:
סיכום בזק: סמנו טקסט ארוך וקבלו תמצית בהירה.
תרגום מיידי: תרגמו קטעים לכל שפה שתבחרו.
הסבר בהקשר: הבינו מונחים מסובכים במשפט המקורי.
חיפוש חכם: חפשו את הטקסט המסומן בגוגל בלחיצה.
שיתוף קל: שתפו את הדף הנוכחי בקלות.
ניתוח דף מלא:
סיכום עמוד שלם: הבינו את עיקרי הדברים במאמרים ארוכים.
️ אימות עובדות: קבלו הערכה על אמינות המידע.
🧑למידה מודרכת: בקשו מה-AI "ללמד" אתכם את תוכן הדף.
שאלות ותשובות: הפכו כל טקסט למבנה Q&A מועיל.
צ'אט AI מתקדם:
סרגל צ'אט צידי, זמין תמיד.
🧠 זוכר את מהלך השיחה להמשכיות טבעית.
שאלו כל דבר, קבלו תשובות מבוססות הקשר.
כפתור פעולה צף (FAB):
גישה נוחה לפעולות על כל הדף, בלי צורך בסימון טקסט.
AI Suite Pro – כי הידע והיעילות שלכם חשובים!
התקינו היום ותתחילו לגלוש חכם יותר, מהר יותר ועם הבנה עמוקה יותר.
AI.zipהתוסף עודכן
נוסף היסטוריה לצאטים
נוספה חלונית צדדית לצאט
כפתור שאל על דף זההתקנת תוסף Chrome מקובץ ZIP:
-
חלצו את קובץ ה-ZIP:
- מצאו את קובץ ה-
.zip
של התוסף. - לחצו עליו קליק ימני ובחרו "חלץ הכל..." (Extract All...).
- שמרו את התיקייה שנוצרה במקום שקל למצוא.
- מצאו את קובץ ה-
-
בכרום, פתחו את דף התוספים:
- בשורת הכתובת, הקלידו: chrome://extensions
- לחצו Enter.
-
הפעילו "מצב מפתח":
- בדף התוספים, מצאו את המתג "מצב מפתח" (Developer mode) והפעילו אותו (בדרך כלל בפינה העליונה).
-
טענו את התיקייה שחולצה:
- לחצו על הכפתור "טען תוסף שלא נארז" (Load unpacked).
- בחלון שנפתח, נווטו אל התיקייה שחילצתם בשלב 1 (זו שמכילה את קובץ ה-manifest.json).
- בחרו את התיקייה ולחצו "בחר תיקייה" (Select Folder).
זהו! התוסף מותקן ומוכן לשימוש.
@יוסי-רחמים זה חינמי
-
-
@יוסי-רחמים זה חינמי
@אלוף-תימן כן
-
@יוסי-רחמים נראה שזה כן קשור לנטפרי לפי מה שאני מבין...
יש מישהו בנטפרי שזה עובד לו?
אם תשנה את זה ל API אישי לכאו' זה אמור לעזור...@פרוזי לא יודע אם זה יעזור API אישי(מה יהיה ההבדל מהAPI האישי שלי לשל כל אחד אחר?)
אבל לא יודע... אשמח למידע ממישהו שמבין -
@יוסי-רחמים
אני מעלה לך גרסה חדשה, לא גמורה, אבל לא יודע כמה אוכל להמשיך כעת...@י.-פל. כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@יוסי-רחמים
אני מעלה לך גרסה חדשה, לא גמורה, אבל לא יודע כמה אוכל להמשיך כעת...אתה מתכנן לקבל?
כתבתי ברדמי את השינויים שלדעתי נצרכים, מי רוצה להמשיך?
כרגע הכל כאן:
https://github.com/Y-PLONI/AI-Suite-Pro
עד ש @יוסי-רחמים יקבל את הPR. -
@י.-פל. כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@יוסי-רחמים
אני מעלה לך גרסה חדשה, לא גמורה, אבל לא יודע כמה אוכל להמשיך כעת...אתה מתכנן לקבל?
כתבתי ברדמי את השינויים שלדעתי נצרכים, מי רוצה להמשיך?
כרגע הכל כאן:
https://github.com/Y-PLONI/AI-Suite-Pro
עד ש @יוסי-רחמים יקבל את הPR.@י.-פל. קיבלתי🫡
(האמת שכבר עשיתי כמה שינויים בתוסף אז אני יצטרך בהמשך לשלב את מה שעשית) -
@י.-פל. קיבלתי🫡
(האמת שכבר עשיתי כמה שינויים בתוסף אז אני יצטרך בהמשך לשלב את מה שעשית)@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@י.-פל. קיבלתי🫡
(האמת שכבר עשיתי כמה שינויים בתוסף אז אני יצטרך בהמשך לשלב את מה שעשית)אז למה קיבלת?
איפה אנחנו אוחזים, תכל'ס?
צריך פשוט להחליט מי עושה מה, וככה נוכל להתקדם מהר יותר!
[אתה פשוט יכול לפתוח בעיה, ואז להקצות אלי/אליך/מישהו אחר שיתנדב]. -
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@י.-פל. קיבלתי🫡
(האמת שכבר עשיתי כמה שינויים בתוסף אז אני יצטרך בהמשך לשלב את מה שעשית)אז למה קיבלת?
איפה אנחנו אוחזים, תכל'ס?
צריך פשוט להחליט מי עושה מה, וככה נוכל להתקדם מהר יותר!
[אתה פשוט יכול לפתוח בעיה, ואז להקצות אלי/אליך/מישהו אחר שיתנדב].@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
(ביקשו חלונית צדדית אז עשיתי)
-
@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
(ביקשו חלונית צדדית אז עשיתי)
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
- אתה הבעלים.
- אם הוא לא שולח ישירות לג'מיני - הוא לא שווה למשתמשי נטפרי.
-
@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
(ביקשו חלונית צדדית אז עשיתי)
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
(ביקשו חלונית צדדית אז עשיתי)
1
י
יוסי רחמים
@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
(ביקשו חלונית צדדית אז עשיתי)זה אמור להיות פונקציה צדדית, לא?
-
@יוסי-רחמים כתב בהמלצה | 🧠 שדרגו את הדפדפן שלכם עם AI Suite Pro – חבילת ה-AI האולטימטיבית לגלישה! 🧠:
@י.-פל. הזיפ של התוסף כאן למעלה.לעדכן אותו בגיטהב?(זה יהרוס את מה שעשית)
- אתה הבעלים.
- אם הוא לא שולח ישירות לג'מיני - הוא לא שווה למשתמשי נטפרי.
@י.-פל. API שאני שם מראש של גמיני זה לא טוב?מה העניין בAPI אישי?
והפכתי את זה לחלונית צד -
@י.-פל. API שאני שם מראש של גמיני זה לא טוב?מה העניין בAPI אישי?
והפכתי את זה לחלונית צד@יוסי-רחמים הסיבה שהוא לא פועל כעת בנטפרי היא לכאו' מפני שכתובת השרת בה נמצא מפתח הAPI - חסומה בנטפרי.
כתובת השרת היא https://php-render-test.onrender.com/main-ai.php, אבל שם אני לא מגיע לשום מקום, ואילו כשאני חוזר אחד אחורה ל https://php-render-test.onrender.com/ אני מגיע לצ'אט AI. למה זה ומה זה?
ואגב, אם שמת לב א"א להשתמש אתו בג'מיני עצמו. עשיתי אימות עובדות עם התוסף בשיחה הזו https://g.co/gemini/share/d56116801f85 שהובאה כאן https://mitmachim.top/post/963192
והוא כותב ליכלומר, התשובה שלו בתוך דף של ג'מיני היא כשל ג'מיני עצמו, כאילו הוא גם חלק מהשיחה ולא כמישהו חיצוני שמסכם/מנתח את הדף. סתם נקודה למחשבה.