המלצה | 🤖 עוזר AI חדש לפורום - עונה תוך שניות, מכיר את מתמחים טופ מבפנים!
-
רבותיי אני ממש מתנצל ולא נעים לי אבל לצערי אני דוחה את זה שוב והגרסה החדשה תצא בעז"ה ביום שישי
-
י יודע ומייבין התייחס לנושא זה
-
רבותיי אני ממש מתנצל ולא נעים לי אבל לצערי אני דוחה את זה שוב והגרסה החדשה תצא בעז"ה ביום שישי
-
@מייבין-במקצת למה לא? הבטחתי כבר לפני כמה שבועות ולא קיימתי
-
@מייבין-במקצת למה לא? הבטחתי כבר לפני כמה שבועות ולא קיימתי
אתה חייב למישהו משהו? יכול להיות שלא נעים לך כלפי עצמך שאתה לא עומד בהבטחות שלך - אבל מאחרים?
שיגידו תודה יפה על הפיתוח שלך יבקשו בקשות לשדרוג - לא הספקת - שיחכו יפה - אתה לא חייב מראש לפתח את התוסף הזה... -
שלום וברכה,
כיון שזה היה חשוב לי, ישבתי על הקוד שלו שעות, וב"ה הצלחתי לעשות לו שטיפת מוח, כולל כמה וכמה שיפורים {כגון הסטוריית בקשות, וקישורים לחיצים וכן אפשרות לגרור את החלון ממקום למקום, וכן הוספתי אפשרות להוספת קבצים או תמונות לדיוק השאלה או הבקשה}.
הקוד המתוקן בספויילר:
// ==UserScript== // @name מתמחים טופ + בינה טופ - עוזר AI מושלם עם תמיכה בקבצים, תמונות והדבקה ישירה // @namespace https://mitmachim.top/ // @version 23.0.0 // @description עוזר AI צף עם תמיכה בצירוף והדבקת קבצים ותמונות, חיפוש משתמשים ואשכולות, היסטוריית שיחות וגרירת חלון // @author you // @match https://mitmachim.top/* // @match https://bina.top/* // @icon https://mitmachim.top/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect generativelanguage.googleapis.com // @connect mitmachim.top // @connect bina.top // @run-at document-end // ==/UserScript== (function () { 'use strict'; function getSiteOrigin() { const host = location.hostname.toLowerCase(); if (host === 'bina.top' || host.endsWith('.bina.top')) { return 'https://bina.top'; } return 'https://mitmachim.top'; } const SITE_ORIGIN = getSiteOrigin(); const SITE_HOST = new URL(SITE_ORIGIN).hostname; const K = { apiKey: 'mt_ai_gemini_key', context: 'mt_ai_context', persona: 'mt_ai_persona', sessions: 'mt_ai_sessions_list', currentSessionId: 'mt_ai_current_session_id' }; const get = (k, d = '') => GM_getValue(k, d); const set = (k, v) => GM_setValue(k, v); function request(details) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...details, onload: resolve, onerror: () => reject(new Error('שגיאת רשת')), ontimeout: () => reject(new Error('זמן קצוב עבר')) }); }); } async function fetchText(url) { const res = await request({ method: 'GET', url, timeout: 25000 }); if (res.status < 200 || res.status >= 300) throw new Error('שגיאה בטעינת נתונים'); return res.responseText; } function htmlToText(root) { if (!root) return ''; const clone = root.cloneNode(true); clone.querySelectorAll('script,style').forEach(x => x.remove()); clone.querySelectorAll('a[href]').forEach(a => { try { const href = new URL(a.getAttribute('href'), SITE_ORIGIN).href; const label = (a.textContent || '').trim(); const replacement = label && label !== href ? `${label} (${href})` : href; a.replaceWith(document.createTextNode(' ' + replacement + ' ')); } catch {} }); clone.querySelectorAll('br').forEach(br => br.replaceWith(document.createTextNode('\n'))); clone.querySelectorAll('p,div,li,blockquote,tr,h1,h2,h3,h4,h5,h6').forEach(b => { b.appendChild(document.createTextNode('\n')); }); return (clone.textContent || '') .replace(/[ \t]+/g, ' ') .replace(/ *\n */g, '\n') .replace(/\n{3,}/g, '\n\n') .trim(); } function extractSearchHits(data) { const arr = data?.posts || data?.matches || data?.results || []; if (!Array.isArray(arr)) return []; return arr.map(p => { const topic = p.topic || p; const title = topic?.title || p.title || '(ללא כותרת)'; const slug = topic?.slug || p.slug; const tid = topic?.tid || p.tid; let url = ''; if (slug) url = `${SITE_ORIGIN}/topic/${slug}`; else if (tid) url = `${SITE_ORIGIN}/topic/${tid}`; return { title, url }; }).filter(h => h.url); } async function legacySearchScrape(query) { try { const url = `${SITE_ORIGIN}/search?term=${encodeURIComponent(query)}`; const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer').forEach(x => x.remove()); const results = []; doc.querySelectorAll('a[href*="/topic/"]').forEach(a => { if (results.length >= 6) return; const href = a.getAttribute('href'); const text = (a.textContent || '').trim(); if (href && text && text.length > 3) { try { const absolute = new URL(href, url).href; if (!results.some(r => r.url === absolute)) { results.push({ title: text, url: absolute }); } } catch {} } }); return results; } catch { return []; } } async function searchUserProfile(query) { query = String(query || '').trim(); if (!query) return ''; try { const apiUrl = `${SITE_ORIGIN}/api/users?query=${encodeURIComponent(query)}&page=1`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); const users = data?.users || []; if (!users.length) return ''; let block = `משתמשים שנמצאו בפורום עבור "${query}":\n\n`; users.slice(0, 5).forEach(u => { const username = u.username || 'לא ידוע'; const fullname = u.fullname ? ` (${u.fullname})` : ''; const reputation = u.reputation !== undefined ? u.reputation : 'לא ידוע'; const postCount = u.postcount !== undefined ? u.postcount : 'לא ידוע'; const profileUrl = `${SITE_ORIGIN}/user/${u.userslug || username}`; block += `- משתמש: ${username}${fullname}\n קישור לפרופיל: ${profileUrl}\n מוניטין: ${reputation} | הודעות: ${postCount}\n\n`; }); return block; } catch { return ''; } } async function quickForumSearch(query) { query = String(query || '').trim(); if (!query) return ''; let userBlock = ''; if (query.includes('משתמש') || query.includes('פרופיל') || query.includes('מוניטין')) { const cleanName = query.replace(/משתמש|פרופיל|מוניטין|של/g, '').trim(); if (cleanName) { userBlock = await searchUserProfile(cleanName); } } let hits = []; try { const apiUrl = `${SITE_ORIGIN}/api/search?term=${encodeURIComponent(query)}`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); hits = extractSearchHits(data); } catch { hits = []; } if (!hits.length) { hits = await legacySearchScrape(query); } let block = userBlock ? userBlock + '\n' : ''; if (hits.length > 0) { const seen = new Set(); const uniqueHits = hits.filter(h => { if (seen.has(h.url)) return false; seen.add(h.url); return true; }); const topResults = uniqueHits.slice(0, 3); const enriched = await Promise.all(topResults.map(async r => { const snippet = await fetchTopicSnippet(r.url); return { ...r, snippet }; })); block += `תוצאות חיפוש אשכולות בפורום ${SITE_HOST} עבור "${query}":\n\n`; enriched.forEach((r, i) => { block += `--- נושא ${i + 1}: ${r.title} ---\nקישור מלא: ${r.url}\n`; block += r.snippet ? `תוכן הנושא:\n${r.snippet}\n\n` : '(לא ניתן היה לשלוף את תוכן הנושא)\n\n'; }); } return block; } async function fetchTopicSnippet(url, maxChars = 1400) { try { const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer,aside,form').forEach(x => x.remove()); const postEls = Array.from(doc.querySelectorAll('[component="post"]')); let text = ''; if (postEls.length) { const parts = postEls.slice(0, 8).map((post, i) => { const contentEl = post.querySelector('[component="post/content"]') || post; const authorEl = post.querySelector('[component="user/username"]') || post.querySelector('a[href*="/user/"]'); const author = authorEl ? authorEl.textContent.trim() : ''; const postText = htmlToText(contentEl); return postText ? `[פוסט ${i + 1}${author ? ' מאת ' + author : ''}]\n${postText}` : ''; }).filter(Boolean); text = parts.join('\n\n'); } if (!text) { const container = doc.querySelector('[component="topic"]') || doc.querySelector('main') || doc.body; text = htmlToText(container); } if (text.length > maxChars) text = text.slice(0, maxChars) + '\n...[הטקסט קוצץ]'; return text; } catch { return ''; } } function getPageContext() { try { const main = document.querySelector('[component="topic"]') || document.querySelector('main') || document.body; let text = htmlToText(main); if (text.length > 1500) { text = text.slice(0, 1500) + '\n...[הטקסט קוצץ]'; } return `אתר: ${SITE_HOST}\nכותרת: ${document.title}\nתוכן העמוד:\n${text}`; } catch { return ''; } } const MODEL_CANDIDATES = ['gemini-3.6-flash', 'gemini-3.5-flash-lite', 'gemini-2.5-flash-lite']; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function callGeminiWithModel(model, key, body) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`; const res = await request({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(body), timeout: 30000 }); let data; try { data = JSON.parse(res.responseText); } catch { throw new Error('תשובת השרת אינה תקינה.'); } if (res.status < 200 || res.status >= 300) { const err = new Error(data?.error?.message || `שגיאה (${res.status})`); err.status = res.status; throw err; } const parts = data?.candidates?.[0]?.content?.parts || []; const answer = parts.filter(p => !p.thought).map(p => p.text || '').join(''); const finishReason = data?.candidates?.[0]?.finishReason; if (finishReason === 'MAX_TOKENS' && answer.trim()) { return answer + '\n\n⚠️ (התשובה נקטעה עקב מגבלת אורך)'; } return answer || '(לא התקבלה תשובה)'; } async function callGemini(history, searchResults) { const key = get(K.apiKey, '').trim(); if (!key) throw new Error('לא הוגדר מפתח API בהגדרות.'); let systemText = `אתה עוזר AI לפורום ${SITE_HOST}. ענה בעברית ברורה, מדויקת ולעניין. באפשרותך לחפש ולהציג גם פרופילים, מוניטין ונתוני משתמשים בפורום במידת הצורך.`; const persona = get(K.persona, ''); if (persona) systemText += '\n\n' + persona; if (get(K.context, true)) { const context = getPageContext(); if (context) systemText += '\n\n' + context; } if (searchResults) { systemText += '\n\nמידע שנמצא בחיפוש בפורום ובמשתמשים:\n' + searchResults; } const contents = history.map(m => { const parts = []; if (m.fileData) { parts.push({ inlineData: { mimeType: m.fileData.mimeType, data: m.fileData.data } }); } parts.push({ text: m.text }); return { role: m.role === 'model' ? 'model' : 'user', parts }; }); const body = { contents, generationConfig: { maxOutputTokens: 2500 } }; if (systemText) { body.systemInstruction = { parts: [{ text: systemText }] }; } let lastError; for (const model of MODEL_CANDIDATES) { for (let attempt = 0; attempt < 2; attempt++) { try { return await callGeminiWithModel(model, key, body); } catch (e) { lastError = e; const retryable = e.status === 503 || e.status === 429; if (retryable && attempt === 0) { await sleep(1200); continue; } break; } } } throw lastError || new Error('כל המודלים נכשלו.'); } // --- UI Elements & Styling --- const style = document.createElement('style'); style.textContent = ` #bina-ai-button { position:fixed; bottom:22px; left:22px; width:58px; height:58px; border-radius:50%; border:0; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-size:27px; cursor:pointer; z-index:999999; box-shadow:0 5px 20px rgba(0,0,0,.3); } #bina-ai-panel { position:fixed; left:22px; bottom:92px; width:400px; max-width:92vw; height:560px; max-height:80vh; background:white; border-radius:15px; overflow:hidden; display:none; flex-direction:column; z-index:999999; box-shadow:0 10px 40px rgba(0,0,0,.3); direction:rtl; font-family:Arial,sans-serif; border:1px solid #ddd; } #bina-ai-panel.open { display:flex; } #bina-ai-panel.fullscreen { inset:0; width:100vw; height:100vh; max-width:none; max-height:none; border-radius:0; } #bina-ai-header { padding:11px; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-weight:bold; display:flex; justify-content:space-between; align-items:center; cursor:move; user-select:none; } #bina-ai-header button { border:0; background:transparent; color:white; font-size:15px; cursor:pointer; margin-right:4px; } #bina-ai-new-btn { background: #ffffff !important; color: #4f8cff !important; padding: 4px 10px; border-radius: 8px; font-size: 12px !important; font-weight: bold; margin-left: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.15); transition: all 0.2s; cursor: pointer; } #bina-ai-new-btn:hover { background: #f0f4ff !important; transform: translateY(-1px); } #bina-ai-info { padding:6px 10px; font-size:11px; background:#fafafa; border-bottom:1px solid #eee; color:#666; display:flex; justify-content:space-between; align-items:center; } #bina-ai-messages { flex:1; overflow-y:auto; padding:10px; background:#f5f6fa; display:flex; flex-direction:column; gap:8px; } .bina-ai-msg { padding:9px 11px; border-radius:10px; max-width:88%; font-size:13px; line-height:1.5; white-space:pre-wrap; word-break:break-word; } .bina-ai-user { align-self:flex-end; background:#4f8cff; color:white; } .bina-ai-model { align-self:flex-start; background:white; border:1px solid #ddd; color:#222; } .bina-ai-error { align-self:center; color:#a00000; background:#ffe3e3; border:1px solid #f0b0b0; } .bina-ai-step { align-self:center; color:#444; background:#eef2ff; font-size:11px; } .bina-ai-msg a { color:#2a5fd6; text-decoration:underline; word-break:break-all; } .bina-ai-user a { color:#eaf1ff; } #bina-ai-inputbar { display:flex; gap:6px; padding:8px; border-top:1px solid #ddd; background: #fff; align-items: center; } #bina-ai-input { flex:1; resize:none; height:40px; border:1px solid #ddd; border-radius:8px; padding:8px; font-family:inherit; } #bina-ai-send { border:0; border-radius:8px; padding:0 14px; background:#4f8cff; color:white; font-weight:bold; cursor:pointer; height:40px; } #bina-ai-file-btn { border:0; border-radius:8px; width:40px; height:40px; background:#f0f2f5; color:#555; font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center; } #bina-ai-file-btn:hover { background:#e4e6eb; } #bina-file-indicator { font-size: 11px; color: #4f8cff; padding: 2px 10px; background: #eef2ff; display: none; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; } #bina-ai-settings { position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,.45); z-index:1000000; } #bina-ai-settings.open { display:flex; } #bina-ai-settings-box { width:460px; max-width:92vw; max-height:88vh; overflow:auto; background:white; border-radius:15px; padding:18px; direction:rtl; font-family:Arial,sans-serif; } .bina-provider { border:1px solid #ddd; border-radius:10px; padding:10px; margin-bottom:9px; } .bina-provider-title { font-weight:bold; margin-bottom:8px; } .bina-provider input[type=password] { width:100%; box-sizing:border-box; padding:6px; margin-top:5px; border:1px solid #ddd; border-radius:6px; } .bina-field { margin-top:10px; } .bina-field label { display:block; font-weight:bold; font-size:12px; margin-bottom:5px; } .bina-field textarea { width:100%; box-sizing:border-box; padding:8px; border:1px solid #ddd; border-radius:8px; } .bina-buttons { display:flex; justify-content:flex-end; gap:8px; margin-top:15px; } .bina-buttons button { padding:8px 16px; border:0; border-radius:8px; cursor:pointer; } #bina-ai-save { background:#4f8cff; color:white; } .session-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee; font-size: 13px; } .session-item:hover { background: #f8f9fa; } .session-title { cursor: pointer; color: #2a5fd6; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: 10px; font-weight: 500; } .session-title:hover { text-decoration: underline; } .session-actions button { background: #ff4d4d; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 11px; margin-right: 4px; } .session-actions button.load-btn { background: #4f8cff; } `; document.head.appendChild(style); const button = document.createElement('button'); button.id = 'bina-ai-button'; button.textContent = '🤖'; button.title = `עוזר AI — ${SITE_HOST}`; document.body.appendChild(button); const panel = document.createElement('div'); panel.id = 'bina-ai-panel'; panel.innerHTML = ` <div id="bina-ai-header"> <div style="display:flex; align-items:center;"> <span>🤖 עוזר AI</span> <button id="bina-ai-new-btn" title="התחל שיחה חדשה">💬 שיחה חדשה</button> </div> <div> <button id="bina-ai-settings-btn" title="הגדרות והיסטוריית שיחות">⚙️</button> <button id="bina-ai-fullscreen" title="מסך מלא">⛶</button> <button id="bina-ai-close" title="סגור">✕</button> </div> </div> <div id="bina-ai-info"> <div>🌐 מחובר ל-${SITE_HOST}</div> <div> <label><input type="checkbox" id="bina-ai-context"> הקשר עמוד</label> </div> </div> <div id="bina-file-indicator"> <span id="bina-file-name">📎 קובץ צורף</span> <button id="bina-remove-file" style="background:none; border:none; color:#a00000; cursor:pointer; font-weight:bold;">הסר ✕</button> </div> <div id="bina-ai-messages"></div> <div id="bina-ai-inputbar"> <input type="file" id="bina-ai-file-input" style="display:none;" /> <button id="bina-ai-file-btn" type="button" title="צירוף קובץ או תמונה">📎</button> <textarea id="bina-ai-input" placeholder="שאל שאלה, חפש אשכול, בדוק משתמש או הדבק תמונה/קובץ (Ctrl+V)..."></textarea> <button id="bina-ai-send" type="button">שלח</button> </div> `; document.body.appendChild(panel); // --- Dragging Logic --- const header = document.querySelector('#bina-ai-header'); let isDragging = false, startX, startY, initialX, initialY; header.addEventListener('mousedown', e => { if (e.target.tagName === 'BUTTON') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; panel.style.bottom = 'auto'; panel.style.left = initialX + 'px'; panel.style.top = initialY + 'px'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; panel.style.left = (initialX + dx) + 'px'; panel.style.top = (initialY + dy) + 'px'; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } // --- File Attachment & Paste Logic --- let attachedFile = null; const fileInput = document.querySelector('#bina-ai-file-input'); const fileBtn = document.querySelector('#bina-ai-file-btn'); const fileIndicator = document.querySelector('#bina-file-indicator'); const fileNameSpan = document.querySelector('#bina-file-name'); const removeFileBtn = document.querySelector('#bina-remove-file'); const input = document.querySelector('#bina-ai-input'); fileBtn.onclick = () => fileInput.click(); function processFile(file) { if (!file) return; const reader = new FileReader(); reader.onload = function (uploadEvent) { const base64Data = uploadEvent.target.result.split(',')[1]; attachedFile = { mimeType: file.type || 'application/octet-stream', data: base64Data, name: file.name || 'קובץ מודבק' }; fileNameSpan.textContent = `📎 ${attachedFile.name} צורף בהצלחה`; fileIndicator.style.display = 'flex'; }; reader.readAsDataURL(file); } fileInput.onchange = (e) => { processFile(e.target.files[0]); }; // תמיכה בהדבקת קובץ/תמונה ישירות לתיבת הטקסט (Ctrl + V) input.addEventListener('paste', (e) => { const items = (e.clipboardData || e.originalEvent.clipboardData).items; for (let index in items) { const item = items[index]; if (item.kind === 'file') { const blob = item.getAsFile(); if (blob) { e.preventDefault(); processFile(blob); break; } } } }); removeFileBtn.onclick = () => { attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; }; // --- Sessions Management Logic --- let sessions = []; try { sessions = JSON.parse(get(K.sessions, '[]')); if (!Array.isArray(sessions)) sessions = []; } catch (e) { sessions = []; } let currentSessionId = get(K.currentSessionId, ''); let history = []; function saveSessionsToStorage() { set(K.sessions, JSON.stringify(sessions)); set(K.currentSessionId, currentSessionId); } function startNewSession() { const newId = 'session_' + Date.now(); const newSession = { id: newId, title: 'שיחה חדשה', history: [] }; sessions.unshift(newSession); if (sessions.length > 20) sessions.pop(); currentSessionId = newId; history = []; saveSessionsToStorage(); renderMessages(); welcome(); renderSessionsList(); } function loadSession(id) { const found = sessions.find(s => s.id === id); if (found) { currentSessionId = found.id; history = [...found.history]; saveSessionsToStorage(); renderMessages(); if (history.length === 0) welcome(); renderSessionsList(); } } function deleteSession(id) { sessions = sessions.filter(s => s.id !== id); if (currentSessionId === id) { if (sessions.length > 0) { loadSession(sessions[0].id); } else { startNewSession(); } } else { saveSessionsToStorage(); renderSessionsList(); } } // --- Settings & History Modal --- const settings = document.createElement('div'); settings.id = 'bina-ai-settings'; settings.innerHTML = ` <div id="bina-ai-settings-box"> <h3>⚙️ הגדרות והיסטוריית שיחות</h3> <div style="font-size:11px;color:#777;margin-bottom:12px">האתר הנוכחי: <b>${SITE_HOST}</b></div> <div class="bina-provider"> <div class="bina-provider-title">🔵 Gemini API Key</div> <div style="font-size:11px;margin-bottom:3px;color:#555;">מפתח API:</div> <input type="password" id="gemini-key" placeholder="הכנס כאן את המפתח"> </div> <div class="bina-field"><label>הנחיות אישיות</label><textarea id="bina-persona" placeholder="לדוגמה: ענה בקצרה..."></textarea></div> <div class="bina-field" style="margin-top:15px;"> <label>📜 היסטוריית שיחות שמורות</label> <div id="bina-sessions-container" style="max-height:180px; overflow-y:auto; border:1px solid #ddd; border-radius:6px; margin-top:5px; background:#fafafa;"></div> </div> <div class="bina-buttons"><button id="bina-cancel">סגור</button><button id="bina-ai-save">שמור הגדרות</button></div> </div> `; document.body.appendChild(settings); function renderSessionsList() { const container = document.querySelector('#bina-sessions-container'); if (!container) return; if (sessions.length === 0) { container.innerHTML = '<div style="padding:10px; font-size:12px; color:#777; text-align:center;">אין שיחות שמורות</div>'; return; } container.innerHTML = ''; sessions.forEach(s => { const item = document.createElement('div'); item.className = 'session-item'; if (s.id === currentSessionId) { item.style.background = '#eef2ff'; item.style.fontWeight = 'bold'; } const titleSpan = document.createElement('span'); titleSpan.className = 'session-title'; titleSpan.textContent = s.title || 'שיחה ללא כותרת'; titleSpan.title = s.title; titleSpan.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const actionsDiv = document.createElement('div'); actionsDiv.className = 'session-actions'; const loadBtn = document.createElement('button'); loadBtn.className = 'load-btn'; loadBtn.textContent = 'המשך'; loadBtn.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const delBtn = document.createElement('button'); delBtn.textContent = 'מחק'; delBtn.onclick = () => { deleteSession(s.id); }; actionsDiv.appendChild(loadBtn); actionsDiv.appendChild(delBtn); item.appendChild(titleSpan); item.appendChild(actionsDiv); container.appendChild(item); }); } function loadSettings() { document.querySelector('#gemini-key').value = get(K.apiKey, ''); document.querySelector('#bina-persona').value = get(K.persona, ''); document.querySelector('#bina-ai-context').checked = get(K.context, true); renderSessionsList(); } function saveSettings() { set(K.apiKey, document.querySelector('#gemini-key').value.trim()); set(K.persona, document.querySelector('#bina-persona').value.trim()); set(K.context, document.querySelector('#bina-ai-context').checked); settings.classList.remove('open'); } document.querySelector('#bina-ai-settings-btn').onclick = () => { loadSettings(); settings.classList.add('open'); }; document.querySelector('#bina-cancel').onclick = () => settings.classList.remove('open'); document.querySelector('#bina-ai-save').onclick = saveSettings; document.querySelector('#bina-ai-new-btn').onclick = () => { startNewSession(); }; const messagesContainer = document.querySelector('#bina-ai-messages'); const sendButton = document.querySelector('#bina-ai-send'); function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>'); } function linkify(escapedText) { const urlRegex = /(https?:\/\/[^\s<>"')]+)/g; return escapedText.replace(urlRegex, raw => { const trimmed = raw.replace(/[).,;:!?]+$/, ''); const trailing = raw.slice(trimmed.length); return `<a href="${trimmed}" target="_blank" rel="noopener noreferrer">${trimmed}</a>${trailing}`; }); } function renderMessages() { messagesContainer.innerHTML = ''; history.forEach(m => { const type = m.role === 'user' ? 'bina-ai-user' : 'bina-ai-model'; const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(m.text)).replace(/\n/g, '<br>'); if (m.fileData && m.fileData.name) { contentHtml = `📎 <b>[קובץ: ${m.fileData.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); }); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function addMessage(text, type, fileInfo = null) { const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(text)).replace(/\n/g, '<br>'); if (fileInfo && fileInfo.name) { contentHtml = `📎 <b>[קובץ: ${fileInfo.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); messagesContainer.scrollTop = messagesContainer.scrollHeight; return div; } function welcome() { addMessage(`שלום! 🤖\nאני עוזר ה-AI של ${SITE_HOST}.\n\nשאל אותי כל שאלה, חפש אשכולות, בדוק פרופילים או צרף/הדבק קובץ ותמונה!`, 'bina-ai-model'); if (!get(K.apiKey)) { addMessage('⚠️ עדיין לא הוגדר מפתח API. פתח ⚙️ והוסף.', 'bina-ai-error'); } } button.onclick = () => panel.classList.toggle('open'); document.querySelector('#bina-ai-close').onclick = () => panel.classList.remove('open'); document.querySelector('#bina-ai-fullscreen').onclick = () => panel.classList.toggle('fullscreen'); async function send() { const text = input.value.trim(); const currentFile = attachedFile; if (!text && !currentFile) return; if (!get(K.apiKey)) { addMessage('⚠️ אין מפתח API פעיל. פתח ⚙️ והגדר.', 'bina-ai-error'); return; } if (!currentSessionId || sessions.length === 0) { startNewSession(); } const messageText = text || 'צורף קובץ לעיון'; addMessage(messageText, 'bina-ai-user', currentFile); history.push({ role: 'user', text: messageText, fileData: currentFile ? { mimeType: currentFile.mimeType, data: currentFile.data, name: currentFile.name } : null }); input.value = ''; attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; sendButton.disabled = true; const currentSession = sessions.find(s => s.id === currentSessionId); if (currentSession && currentSession.title === 'שיחה חדשה') { currentSession.title = messageText.slice(0, 25) + (messageText.length > 25 ? '...' : ''); } const stepMsg = addMessage('🔎 מחפש בנתוני הפורום והמשתמשים...', 'bina-ai-step'); try { const searchResults = await quickForumSearch(messageText); stepMsg.remove(); const pending = addMessage('🤖 מנתח תשובה...', 'bina-ai-model'); const answer = await callGemini(history, searchResults); pending.remove(); addMessage(answer, 'bina-ai-model'); history.push({ role: 'model', text: answer, fileData: null }); if (currentSession) { currentSession.history = [...history]; saveSessionsToStorage(); } } catch (e) { stepMsg.remove(); addMessage('⚠️ ' + (e?.message || String(e)), 'bina-ai-error'); } finally { sendButton.disabled = false; input.focus(); } } sendButton.onclick = send; input.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }); GM_registerMenuCommand('⚙️ הגדרות עוזר AI', () => { loadSettings(); settings.classList.add('open'); }); if (sessions.length > 0) { if (!currentSessionId || !sessions.some(s => s.id === currentSessionId)) { currentSessionId = sessions[0].id; } loadSession(currentSessionId); } else { startNewSession(); } })();כמה תמונות מהעוזר גרסת "חפרן" שעשיתי:



-
שלום וברכה,
כיון שזה היה חשוב לי, ישבתי על הקוד שלו שעות, וב"ה הצלחתי לעשות לו שטיפת מוח, כולל כמה וכמה שיפורים {כגון הסטוריית בקשות, וקישורים לחיצים וכן אפשרות לגרור את החלון ממקום למקום, וכן הוספתי אפשרות להוספת קבצים או תמונות לדיוק השאלה או הבקשה}.
הקוד המתוקן בספויילר:
// ==UserScript== // @name מתמחים טופ + בינה טופ - עוזר AI מושלם עם תמיכה בקבצים, תמונות והדבקה ישירה // @namespace https://mitmachim.top/ // @version 23.0.0 // @description עוזר AI צף עם תמיכה בצירוף והדבקת קבצים ותמונות, חיפוש משתמשים ואשכולות, היסטוריית שיחות וגרירת חלון // @author you // @match https://mitmachim.top/* // @match https://bina.top/* // @icon https://mitmachim.top/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect generativelanguage.googleapis.com // @connect mitmachim.top // @connect bina.top // @run-at document-end // ==/UserScript== (function () { 'use strict'; function getSiteOrigin() { const host = location.hostname.toLowerCase(); if (host === 'bina.top' || host.endsWith('.bina.top')) { return 'https://bina.top'; } return 'https://mitmachim.top'; } const SITE_ORIGIN = getSiteOrigin(); const SITE_HOST = new URL(SITE_ORIGIN).hostname; const K = { apiKey: 'mt_ai_gemini_key', context: 'mt_ai_context', persona: 'mt_ai_persona', sessions: 'mt_ai_sessions_list', currentSessionId: 'mt_ai_current_session_id' }; const get = (k, d = '') => GM_getValue(k, d); const set = (k, v) => GM_setValue(k, v); function request(details) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...details, onload: resolve, onerror: () => reject(new Error('שגיאת רשת')), ontimeout: () => reject(new Error('זמן קצוב עבר')) }); }); } async function fetchText(url) { const res = await request({ method: 'GET', url, timeout: 25000 }); if (res.status < 200 || res.status >= 300) throw new Error('שגיאה בטעינת נתונים'); return res.responseText; } function htmlToText(root) { if (!root) return ''; const clone = root.cloneNode(true); clone.querySelectorAll('script,style').forEach(x => x.remove()); clone.querySelectorAll('a[href]').forEach(a => { try { const href = new URL(a.getAttribute('href'), SITE_ORIGIN).href; const label = (a.textContent || '').trim(); const replacement = label && label !== href ? `${label} (${href})` : href; a.replaceWith(document.createTextNode(' ' + replacement + ' ')); } catch {} }); clone.querySelectorAll('br').forEach(br => br.replaceWith(document.createTextNode('\n'))); clone.querySelectorAll('p,div,li,blockquote,tr,h1,h2,h3,h4,h5,h6').forEach(b => { b.appendChild(document.createTextNode('\n')); }); return (clone.textContent || '') .replace(/[ \t]+/g, ' ') .replace(/ *\n */g, '\n') .replace(/\n{3,}/g, '\n\n') .trim(); } function extractSearchHits(data) { const arr = data?.posts || data?.matches || data?.results || []; if (!Array.isArray(arr)) return []; return arr.map(p => { const topic = p.topic || p; const title = topic?.title || p.title || '(ללא כותרת)'; const slug = topic?.slug || p.slug; const tid = topic?.tid || p.tid; let url = ''; if (slug) url = `${SITE_ORIGIN}/topic/${slug}`; else if (tid) url = `${SITE_ORIGIN}/topic/${tid}`; return { title, url }; }).filter(h => h.url); } async function legacySearchScrape(query) { try { const url = `${SITE_ORIGIN}/search?term=${encodeURIComponent(query)}`; const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer').forEach(x => x.remove()); const results = []; doc.querySelectorAll('a[href*="/topic/"]').forEach(a => { if (results.length >= 6) return; const href = a.getAttribute('href'); const text = (a.textContent || '').trim(); if (href && text && text.length > 3) { try { const absolute = new URL(href, url).href; if (!results.some(r => r.url === absolute)) { results.push({ title: text, url: absolute }); } } catch {} } }); return results; } catch { return []; } } async function searchUserProfile(query) { query = String(query || '').trim(); if (!query) return ''; try { const apiUrl = `${SITE_ORIGIN}/api/users?query=${encodeURIComponent(query)}&page=1`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); const users = data?.users || []; if (!users.length) return ''; let block = `משתמשים שנמצאו בפורום עבור "${query}":\n\n`; users.slice(0, 5).forEach(u => { const username = u.username || 'לא ידוע'; const fullname = u.fullname ? ` (${u.fullname})` : ''; const reputation = u.reputation !== undefined ? u.reputation : 'לא ידוע'; const postCount = u.postcount !== undefined ? u.postcount : 'לא ידוע'; const profileUrl = `${SITE_ORIGIN}/user/${u.userslug || username}`; block += `- משתמש: ${username}${fullname}\n קישור לפרופיל: ${profileUrl}\n מוניטין: ${reputation} | הודעות: ${postCount}\n\n`; }); return block; } catch { return ''; } } async function quickForumSearch(query) { query = String(query || '').trim(); if (!query) return ''; let userBlock = ''; if (query.includes('משתמש') || query.includes('פרופיל') || query.includes('מוניטין')) { const cleanName = query.replace(/משתמש|פרופיל|מוניטין|של/g, '').trim(); if (cleanName) { userBlock = await searchUserProfile(cleanName); } } let hits = []; try { const apiUrl = `${SITE_ORIGIN}/api/search?term=${encodeURIComponent(query)}`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); hits = extractSearchHits(data); } catch { hits = []; } if (!hits.length) { hits = await legacySearchScrape(query); } let block = userBlock ? userBlock + '\n' : ''; if (hits.length > 0) { const seen = new Set(); const uniqueHits = hits.filter(h => { if (seen.has(h.url)) return false; seen.add(h.url); return true; }); const topResults = uniqueHits.slice(0, 3); const enriched = await Promise.all(topResults.map(async r => { const snippet = await fetchTopicSnippet(r.url); return { ...r, snippet }; })); block += `תוצאות חיפוש אשכולות בפורום ${SITE_HOST} עבור "${query}":\n\n`; enriched.forEach((r, i) => { block += `--- נושא ${i + 1}: ${r.title} ---\nקישור מלא: ${r.url}\n`; block += r.snippet ? `תוכן הנושא:\n${r.snippet}\n\n` : '(לא ניתן היה לשלוף את תוכן הנושא)\n\n'; }); } return block; } async function fetchTopicSnippet(url, maxChars = 1400) { try { const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer,aside,form').forEach(x => x.remove()); const postEls = Array.from(doc.querySelectorAll('[component="post"]')); let text = ''; if (postEls.length) { const parts = postEls.slice(0, 8).map((post, i) => { const contentEl = post.querySelector('[component="post/content"]') || post; const authorEl = post.querySelector('[component="user/username"]') || post.querySelector('a[href*="/user/"]'); const author = authorEl ? authorEl.textContent.trim() : ''; const postText = htmlToText(contentEl); return postText ? `[פוסט ${i + 1}${author ? ' מאת ' + author : ''}]\n${postText}` : ''; }).filter(Boolean); text = parts.join('\n\n'); } if (!text) { const container = doc.querySelector('[component="topic"]') || doc.querySelector('main') || doc.body; text = htmlToText(container); } if (text.length > maxChars) text = text.slice(0, maxChars) + '\n...[הטקסט קוצץ]'; return text; } catch { return ''; } } function getPageContext() { try { const main = document.querySelector('[component="topic"]') || document.querySelector('main') || document.body; let text = htmlToText(main); if (text.length > 1500) { text = text.slice(0, 1500) + '\n...[הטקסט קוצץ]'; } return `אתר: ${SITE_HOST}\nכותרת: ${document.title}\nתוכן העמוד:\n${text}`; } catch { return ''; } } const MODEL_CANDIDATES = ['gemini-3.6-flash', 'gemini-3.5-flash-lite', 'gemini-2.5-flash-lite']; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function callGeminiWithModel(model, key, body) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`; const res = await request({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(body), timeout: 30000 }); let data; try { data = JSON.parse(res.responseText); } catch { throw new Error('תשובת השרת אינה תקינה.'); } if (res.status < 200 || res.status >= 300) { const err = new Error(data?.error?.message || `שגיאה (${res.status})`); err.status = res.status; throw err; } const parts = data?.candidates?.[0]?.content?.parts || []; const answer = parts.filter(p => !p.thought).map(p => p.text || '').join(''); const finishReason = data?.candidates?.[0]?.finishReason; if (finishReason === 'MAX_TOKENS' && answer.trim()) { return answer + '\n\n⚠️ (התשובה נקטעה עקב מגבלת אורך)'; } return answer || '(לא התקבלה תשובה)'; } async function callGemini(history, searchResults) { const key = get(K.apiKey, '').trim(); if (!key) throw new Error('לא הוגדר מפתח API בהגדרות.'); let systemText = `אתה עוזר AI לפורום ${SITE_HOST}. ענה בעברית ברורה, מדויקת ולעניין. באפשרותך לחפש ולהציג גם פרופילים, מוניטין ונתוני משתמשים בפורום במידת הצורך.`; const persona = get(K.persona, ''); if (persona) systemText += '\n\n' + persona; if (get(K.context, true)) { const context = getPageContext(); if (context) systemText += '\n\n' + context; } if (searchResults) { systemText += '\n\nמידע שנמצא בחיפוש בפורום ובמשתמשים:\n' + searchResults; } const contents = history.map(m => { const parts = []; if (m.fileData) { parts.push({ inlineData: { mimeType: m.fileData.mimeType, data: m.fileData.data } }); } parts.push({ text: m.text }); return { role: m.role === 'model' ? 'model' : 'user', parts }; }); const body = { contents, generationConfig: { maxOutputTokens: 2500 } }; if (systemText) { body.systemInstruction = { parts: [{ text: systemText }] }; } let lastError; for (const model of MODEL_CANDIDATES) { for (let attempt = 0; attempt < 2; attempt++) { try { return await callGeminiWithModel(model, key, body); } catch (e) { lastError = e; const retryable = e.status === 503 || e.status === 429; if (retryable && attempt === 0) { await sleep(1200); continue; } break; } } } throw lastError || new Error('כל המודלים נכשלו.'); } // --- UI Elements & Styling --- const style = document.createElement('style'); style.textContent = ` #bina-ai-button { position:fixed; bottom:22px; left:22px; width:58px; height:58px; border-radius:50%; border:0; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-size:27px; cursor:pointer; z-index:999999; box-shadow:0 5px 20px rgba(0,0,0,.3); } #bina-ai-panel { position:fixed; left:22px; bottom:92px; width:400px; max-width:92vw; height:560px; max-height:80vh; background:white; border-radius:15px; overflow:hidden; display:none; flex-direction:column; z-index:999999; box-shadow:0 10px 40px rgba(0,0,0,.3); direction:rtl; font-family:Arial,sans-serif; border:1px solid #ddd; } #bina-ai-panel.open { display:flex; } #bina-ai-panel.fullscreen { inset:0; width:100vw; height:100vh; max-width:none; max-height:none; border-radius:0; } #bina-ai-header { padding:11px; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-weight:bold; display:flex; justify-content:space-between; align-items:center; cursor:move; user-select:none; } #bina-ai-header button { border:0; background:transparent; color:white; font-size:15px; cursor:pointer; margin-right:4px; } #bina-ai-new-btn { background: #ffffff !important; color: #4f8cff !important; padding: 4px 10px; border-radius: 8px; font-size: 12px !important; font-weight: bold; margin-left: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.15); transition: all 0.2s; cursor: pointer; } #bina-ai-new-btn:hover { background: #f0f4ff !important; transform: translateY(-1px); } #bina-ai-info { padding:6px 10px; font-size:11px; background:#fafafa; border-bottom:1px solid #eee; color:#666; display:flex; justify-content:space-between; align-items:center; } #bina-ai-messages { flex:1; overflow-y:auto; padding:10px; background:#f5f6fa; display:flex; flex-direction:column; gap:8px; } .bina-ai-msg { padding:9px 11px; border-radius:10px; max-width:88%; font-size:13px; line-height:1.5; white-space:pre-wrap; word-break:break-word; } .bina-ai-user { align-self:flex-end; background:#4f8cff; color:white; } .bina-ai-model { align-self:flex-start; background:white; border:1px solid #ddd; color:#222; } .bina-ai-error { align-self:center; color:#a00000; background:#ffe3e3; border:1px solid #f0b0b0; } .bina-ai-step { align-self:center; color:#444; background:#eef2ff; font-size:11px; } .bina-ai-msg a { color:#2a5fd6; text-decoration:underline; word-break:break-all; } .bina-ai-user a { color:#eaf1ff; } #bina-ai-inputbar { display:flex; gap:6px; padding:8px; border-top:1px solid #ddd; background: #fff; align-items: center; } #bina-ai-input { flex:1; resize:none; height:40px; border:1px solid #ddd; border-radius:8px; padding:8px; font-family:inherit; } #bina-ai-send { border:0; border-radius:8px; padding:0 14px; background:#4f8cff; color:white; font-weight:bold; cursor:pointer; height:40px; } #bina-ai-file-btn { border:0; border-radius:8px; width:40px; height:40px; background:#f0f2f5; color:#555; font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center; } #bina-ai-file-btn:hover { background:#e4e6eb; } #bina-file-indicator { font-size: 11px; color: #4f8cff; padding: 2px 10px; background: #eef2ff; display: none; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; } #bina-ai-settings { position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,.45); z-index:1000000; } #bina-ai-settings.open { display:flex; } #bina-ai-settings-box { width:460px; max-width:92vw; max-height:88vh; overflow:auto; background:white; border-radius:15px; padding:18px; direction:rtl; font-family:Arial,sans-serif; } .bina-provider { border:1px solid #ddd; border-radius:10px; padding:10px; margin-bottom:9px; } .bina-provider-title { font-weight:bold; margin-bottom:8px; } .bina-provider input[type=password] { width:100%; box-sizing:border-box; padding:6px; margin-top:5px; border:1px solid #ddd; border-radius:6px; } .bina-field { margin-top:10px; } .bina-field label { display:block; font-weight:bold; font-size:12px; margin-bottom:5px; } .bina-field textarea { width:100%; box-sizing:border-box; padding:8px; border:1px solid #ddd; border-radius:8px; } .bina-buttons { display:flex; justify-content:flex-end; gap:8px; margin-top:15px; } .bina-buttons button { padding:8px 16px; border:0; border-radius:8px; cursor:pointer; } #bina-ai-save { background:#4f8cff; color:white; } .session-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee; font-size: 13px; } .session-item:hover { background: #f8f9fa; } .session-title { cursor: pointer; color: #2a5fd6; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: 10px; font-weight: 500; } .session-title:hover { text-decoration: underline; } .session-actions button { background: #ff4d4d; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 11px; margin-right: 4px; } .session-actions button.load-btn { background: #4f8cff; } `; document.head.appendChild(style); const button = document.createElement('button'); button.id = 'bina-ai-button'; button.textContent = '🤖'; button.title = `עוזר AI — ${SITE_HOST}`; document.body.appendChild(button); const panel = document.createElement('div'); panel.id = 'bina-ai-panel'; panel.innerHTML = ` <div id="bina-ai-header"> <div style="display:flex; align-items:center;"> <span>🤖 עוזר AI</span> <button id="bina-ai-new-btn" title="התחל שיחה חדשה">💬 שיחה חדשה</button> </div> <div> <button id="bina-ai-settings-btn" title="הגדרות והיסטוריית שיחות">⚙️</button> <button id="bina-ai-fullscreen" title="מסך מלא">⛶</button> <button id="bina-ai-close" title="סגור">✕</button> </div> </div> <div id="bina-ai-info"> <div>🌐 מחובר ל-${SITE_HOST}</div> <div> <label><input type="checkbox" id="bina-ai-context"> הקשר עמוד</label> </div> </div> <div id="bina-file-indicator"> <span id="bina-file-name">📎 קובץ צורף</span> <button id="bina-remove-file" style="background:none; border:none; color:#a00000; cursor:pointer; font-weight:bold;">הסר ✕</button> </div> <div id="bina-ai-messages"></div> <div id="bina-ai-inputbar"> <input type="file" id="bina-ai-file-input" style="display:none;" /> <button id="bina-ai-file-btn" type="button" title="צירוף קובץ או תמונה">📎</button> <textarea id="bina-ai-input" placeholder="שאל שאלה, חפש אשכול, בדוק משתמש או הדבק תמונה/קובץ (Ctrl+V)..."></textarea> <button id="bina-ai-send" type="button">שלח</button> </div> `; document.body.appendChild(panel); // --- Dragging Logic --- const header = document.querySelector('#bina-ai-header'); let isDragging = false, startX, startY, initialX, initialY; header.addEventListener('mousedown', e => { if (e.target.tagName === 'BUTTON') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; panel.style.bottom = 'auto'; panel.style.left = initialX + 'px'; panel.style.top = initialY + 'px'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; panel.style.left = (initialX + dx) + 'px'; panel.style.top = (initialY + dy) + 'px'; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } // --- File Attachment & Paste Logic --- let attachedFile = null; const fileInput = document.querySelector('#bina-ai-file-input'); const fileBtn = document.querySelector('#bina-ai-file-btn'); const fileIndicator = document.querySelector('#bina-file-indicator'); const fileNameSpan = document.querySelector('#bina-file-name'); const removeFileBtn = document.querySelector('#bina-remove-file'); const input = document.querySelector('#bina-ai-input'); fileBtn.onclick = () => fileInput.click(); function processFile(file) { if (!file) return; const reader = new FileReader(); reader.onload = function (uploadEvent) { const base64Data = uploadEvent.target.result.split(',')[1]; attachedFile = { mimeType: file.type || 'application/octet-stream', data: base64Data, name: file.name || 'קובץ מודבק' }; fileNameSpan.textContent = `📎 ${attachedFile.name} צורף בהצלחה`; fileIndicator.style.display = 'flex'; }; reader.readAsDataURL(file); } fileInput.onchange = (e) => { processFile(e.target.files[0]); }; // תמיכה בהדבקת קובץ/תמונה ישירות לתיבת הטקסט (Ctrl + V) input.addEventListener('paste', (e) => { const items = (e.clipboardData || e.originalEvent.clipboardData).items; for (let index in items) { const item = items[index]; if (item.kind === 'file') { const blob = item.getAsFile(); if (blob) { e.preventDefault(); processFile(blob); break; } } } }); removeFileBtn.onclick = () => { attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; }; // --- Sessions Management Logic --- let sessions = []; try { sessions = JSON.parse(get(K.sessions, '[]')); if (!Array.isArray(sessions)) sessions = []; } catch (e) { sessions = []; } let currentSessionId = get(K.currentSessionId, ''); let history = []; function saveSessionsToStorage() { set(K.sessions, JSON.stringify(sessions)); set(K.currentSessionId, currentSessionId); } function startNewSession() { const newId = 'session_' + Date.now(); const newSession = { id: newId, title: 'שיחה חדשה', history: [] }; sessions.unshift(newSession); if (sessions.length > 20) sessions.pop(); currentSessionId = newId; history = []; saveSessionsToStorage(); renderMessages(); welcome(); renderSessionsList(); } function loadSession(id) { const found = sessions.find(s => s.id === id); if (found) { currentSessionId = found.id; history = [...found.history]; saveSessionsToStorage(); renderMessages(); if (history.length === 0) welcome(); renderSessionsList(); } } function deleteSession(id) { sessions = sessions.filter(s => s.id !== id); if (currentSessionId === id) { if (sessions.length > 0) { loadSession(sessions[0].id); } else { startNewSession(); } } else { saveSessionsToStorage(); renderSessionsList(); } } // --- Settings & History Modal --- const settings = document.createElement('div'); settings.id = 'bina-ai-settings'; settings.innerHTML = ` <div id="bina-ai-settings-box"> <h3>⚙️ הגדרות והיסטוריית שיחות</h3> <div style="font-size:11px;color:#777;margin-bottom:12px">האתר הנוכחי: <b>${SITE_HOST}</b></div> <div class="bina-provider"> <div class="bina-provider-title">🔵 Gemini API Key</div> <div style="font-size:11px;margin-bottom:3px;color:#555;">מפתח API:</div> <input type="password" id="gemini-key" placeholder="הכנס כאן את המפתח"> </div> <div class="bina-field"><label>הנחיות אישיות</label><textarea id="bina-persona" placeholder="לדוגמה: ענה בקצרה..."></textarea></div> <div class="bina-field" style="margin-top:15px;"> <label>📜 היסטוריית שיחות שמורות</label> <div id="bina-sessions-container" style="max-height:180px; overflow-y:auto; border:1px solid #ddd; border-radius:6px; margin-top:5px; background:#fafafa;"></div> </div> <div class="bina-buttons"><button id="bina-cancel">סגור</button><button id="bina-ai-save">שמור הגדרות</button></div> </div> `; document.body.appendChild(settings); function renderSessionsList() { const container = document.querySelector('#bina-sessions-container'); if (!container) return; if (sessions.length === 0) { container.innerHTML = '<div style="padding:10px; font-size:12px; color:#777; text-align:center;">אין שיחות שמורות</div>'; return; } container.innerHTML = ''; sessions.forEach(s => { const item = document.createElement('div'); item.className = 'session-item'; if (s.id === currentSessionId) { item.style.background = '#eef2ff'; item.style.fontWeight = 'bold'; } const titleSpan = document.createElement('span'); titleSpan.className = 'session-title'; titleSpan.textContent = s.title || 'שיחה ללא כותרת'; titleSpan.title = s.title; titleSpan.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const actionsDiv = document.createElement('div'); actionsDiv.className = 'session-actions'; const loadBtn = document.createElement('button'); loadBtn.className = 'load-btn'; loadBtn.textContent = 'המשך'; loadBtn.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const delBtn = document.createElement('button'); delBtn.textContent = 'מחק'; delBtn.onclick = () => { deleteSession(s.id); }; actionsDiv.appendChild(loadBtn); actionsDiv.appendChild(delBtn); item.appendChild(titleSpan); item.appendChild(actionsDiv); container.appendChild(item); }); } function loadSettings() { document.querySelector('#gemini-key').value = get(K.apiKey, ''); document.querySelector('#bina-persona').value = get(K.persona, ''); document.querySelector('#bina-ai-context').checked = get(K.context, true); renderSessionsList(); } function saveSettings() { set(K.apiKey, document.querySelector('#gemini-key').value.trim()); set(K.persona, document.querySelector('#bina-persona').value.trim()); set(K.context, document.querySelector('#bina-ai-context').checked); settings.classList.remove('open'); } document.querySelector('#bina-ai-settings-btn').onclick = () => { loadSettings(); settings.classList.add('open'); }; document.querySelector('#bina-cancel').onclick = () => settings.classList.remove('open'); document.querySelector('#bina-ai-save').onclick = saveSettings; document.querySelector('#bina-ai-new-btn').onclick = () => { startNewSession(); }; const messagesContainer = document.querySelector('#bina-ai-messages'); const sendButton = document.querySelector('#bina-ai-send'); function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>'); } function linkify(escapedText) { const urlRegex = /(https?:\/\/[^\s<>"')]+)/g; return escapedText.replace(urlRegex, raw => { const trimmed = raw.replace(/[).,;:!?]+$/, ''); const trailing = raw.slice(trimmed.length); return `<a href="${trimmed}" target="_blank" rel="noopener noreferrer">${trimmed}</a>${trailing}`; }); } function renderMessages() { messagesContainer.innerHTML = ''; history.forEach(m => { const type = m.role === 'user' ? 'bina-ai-user' : 'bina-ai-model'; const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(m.text)).replace(/\n/g, '<br>'); if (m.fileData && m.fileData.name) { contentHtml = `📎 <b>[קובץ: ${m.fileData.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); }); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function addMessage(text, type, fileInfo = null) { const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(text)).replace(/\n/g, '<br>'); if (fileInfo && fileInfo.name) { contentHtml = `📎 <b>[קובץ: ${fileInfo.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); messagesContainer.scrollTop = messagesContainer.scrollHeight; return div; } function welcome() { addMessage(`שלום! 🤖\nאני עוזר ה-AI של ${SITE_HOST}.\n\nשאל אותי כל שאלה, חפש אשכולות, בדוק פרופילים או צרף/הדבק קובץ ותמונה!`, 'bina-ai-model'); if (!get(K.apiKey)) { addMessage('⚠️ עדיין לא הוגדר מפתח API. פתח ⚙️ והוסף.', 'bina-ai-error'); } } button.onclick = () => panel.classList.toggle('open'); document.querySelector('#bina-ai-close').onclick = () => panel.classList.remove('open'); document.querySelector('#bina-ai-fullscreen').onclick = () => panel.classList.toggle('fullscreen'); async function send() { const text = input.value.trim(); const currentFile = attachedFile; if (!text && !currentFile) return; if (!get(K.apiKey)) { addMessage('⚠️ אין מפתח API פעיל. פתח ⚙️ והגדר.', 'bina-ai-error'); return; } if (!currentSessionId || sessions.length === 0) { startNewSession(); } const messageText = text || 'צורף קובץ לעיון'; addMessage(messageText, 'bina-ai-user', currentFile); history.push({ role: 'user', text: messageText, fileData: currentFile ? { mimeType: currentFile.mimeType, data: currentFile.data, name: currentFile.name } : null }); input.value = ''; attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; sendButton.disabled = true; const currentSession = sessions.find(s => s.id === currentSessionId); if (currentSession && currentSession.title === 'שיחה חדשה') { currentSession.title = messageText.slice(0, 25) + (messageText.length > 25 ? '...' : ''); } const stepMsg = addMessage('🔎 מחפש בנתוני הפורום והמשתמשים...', 'bina-ai-step'); try { const searchResults = await quickForumSearch(messageText); stepMsg.remove(); const pending = addMessage('🤖 מנתח תשובה...', 'bina-ai-model'); const answer = await callGemini(history, searchResults); pending.remove(); addMessage(answer, 'bina-ai-model'); history.push({ role: 'model', text: answer, fileData: null }); if (currentSession) { currentSession.history = [...history]; saveSessionsToStorage(); } } catch (e) { stepMsg.remove(); addMessage('⚠️ ' + (e?.message || String(e)), 'bina-ai-error'); } finally { sendButton.disabled = false; input.focus(); } } sendButton.onclick = send; input.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }); GM_registerMenuCommand('⚙️ הגדרות עוזר AI', () => { loadSettings(); settings.classList.add('open'); }); if (sessions.length > 0) { if (!currentSessionId || !sessions.some(s => s.id === currentSessionId)) { currentSessionId = sessions[0].id; } loadSession(currentSessionId); } else { startNewSession(); } })();כמה תמונות מהעוזר גרסת "חפרן" שעשיתי:



-
שלום וברכה,
כיון שזה היה חשוב לי, ישבתי על הקוד שלו שעות, וב"ה הצלחתי לעשות לו שטיפת מוח, כולל כמה וכמה שיפורים {כגון הסטוריית בקשות, וקישורים לחיצים וכן אפשרות לגרור את החלון ממקום למקום, וכן הוספתי אפשרות להוספת קבצים או תמונות לדיוק השאלה או הבקשה}.
הקוד המתוקן בספויילר:
// ==UserScript== // @name מתמחים טופ + בינה טופ - עוזר AI מושלם עם תמיכה בקבצים, תמונות והדבקה ישירה // @namespace https://mitmachim.top/ // @version 23.0.0 // @description עוזר AI צף עם תמיכה בצירוף והדבקת קבצים ותמונות, חיפוש משתמשים ואשכולות, היסטוריית שיחות וגרירת חלון // @author you // @match https://mitmachim.top/* // @match https://bina.top/* // @icon https://mitmachim.top/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @connect generativelanguage.googleapis.com // @connect mitmachim.top // @connect bina.top // @run-at document-end // ==/UserScript== (function () { 'use strict'; function getSiteOrigin() { const host = location.hostname.toLowerCase(); if (host === 'bina.top' || host.endsWith('.bina.top')) { return 'https://bina.top'; } return 'https://mitmachim.top'; } const SITE_ORIGIN = getSiteOrigin(); const SITE_HOST = new URL(SITE_ORIGIN).hostname; const K = { apiKey: 'mt_ai_gemini_key', context: 'mt_ai_context', persona: 'mt_ai_persona', sessions: 'mt_ai_sessions_list', currentSessionId: 'mt_ai_current_session_id' }; const get = (k, d = '') => GM_getValue(k, d); const set = (k, v) => GM_setValue(k, v); function request(details) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...details, onload: resolve, onerror: () => reject(new Error('שגיאת רשת')), ontimeout: () => reject(new Error('זמן קצוב עבר')) }); }); } async function fetchText(url) { const res = await request({ method: 'GET', url, timeout: 25000 }); if (res.status < 200 || res.status >= 300) throw new Error('שגיאה בטעינת נתונים'); return res.responseText; } function htmlToText(root) { if (!root) return ''; const clone = root.cloneNode(true); clone.querySelectorAll('script,style').forEach(x => x.remove()); clone.querySelectorAll('a[href]').forEach(a => { try { const href = new URL(a.getAttribute('href'), SITE_ORIGIN).href; const label = (a.textContent || '').trim(); const replacement = label && label !== href ? `${label} (${href})` : href; a.replaceWith(document.createTextNode(' ' + replacement + ' ')); } catch {} }); clone.querySelectorAll('br').forEach(br => br.replaceWith(document.createTextNode('\n'))); clone.querySelectorAll('p,div,li,blockquote,tr,h1,h2,h3,h4,h5,h6').forEach(b => { b.appendChild(document.createTextNode('\n')); }); return (clone.textContent || '') .replace(/[ \t]+/g, ' ') .replace(/ *\n */g, '\n') .replace(/\n{3,}/g, '\n\n') .trim(); } function extractSearchHits(data) { const arr = data?.posts || data?.matches || data?.results || []; if (!Array.isArray(arr)) return []; return arr.map(p => { const topic = p.topic || p; const title = topic?.title || p.title || '(ללא כותרת)'; const slug = topic?.slug || p.slug; const tid = topic?.tid || p.tid; let url = ''; if (slug) url = `${SITE_ORIGIN}/topic/${slug}`; else if (tid) url = `${SITE_ORIGIN}/topic/${tid}`; return { title, url }; }).filter(h => h.url); } async function legacySearchScrape(query) { try { const url = `${SITE_ORIGIN}/search?term=${encodeURIComponent(query)}`; const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer').forEach(x => x.remove()); const results = []; doc.querySelectorAll('a[href*="/topic/"]').forEach(a => { if (results.length >= 6) return; const href = a.getAttribute('href'); const text = (a.textContent || '').trim(); if (href && text && text.length > 3) { try { const absolute = new URL(href, url).href; if (!results.some(r => r.url === absolute)) { results.push({ title: text, url: absolute }); } } catch {} } }); return results; } catch { return []; } } async function searchUserProfile(query) { query = String(query || '').trim(); if (!query) return ''; try { const apiUrl = `${SITE_ORIGIN}/api/users?query=${encodeURIComponent(query)}&page=1`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); const users = data?.users || []; if (!users.length) return ''; let block = `משתמשים שנמצאו בפורום עבור "${query}":\n\n`; users.slice(0, 5).forEach(u => { const username = u.username || 'לא ידוע'; const fullname = u.fullname ? ` (${u.fullname})` : ''; const reputation = u.reputation !== undefined ? u.reputation : 'לא ידוע'; const postCount = u.postcount !== undefined ? u.postcount : 'לא ידוע'; const profileUrl = `${SITE_ORIGIN}/user/${u.userslug || username}`; block += `- משתמש: ${username}${fullname}\n קישור לפרופיל: ${profileUrl}\n מוניטין: ${reputation} | הודעות: ${postCount}\n\n`; }); return block; } catch { return ''; } } async function quickForumSearch(query) { query = String(query || '').trim(); if (!query) return ''; let userBlock = ''; if (query.includes('משתמש') || query.includes('פרופיל') || query.includes('מוניטין')) { const cleanName = query.replace(/משתמש|פרופיל|מוניטין|של/g, '').trim(); if (cleanName) { userBlock = await searchUserProfile(cleanName); } } let hits = []; try { const apiUrl = `${SITE_ORIGIN}/api/search?term=${encodeURIComponent(query)}`; const raw = await fetchText(apiUrl); const data = JSON.parse(raw); hits = extractSearchHits(data); } catch { hits = []; } if (!hits.length) { hits = await legacySearchScrape(query); } let block = userBlock ? userBlock + '\n' : ''; if (hits.length > 0) { const seen = new Set(); const uniqueHits = hits.filter(h => { if (seen.has(h.url)) return false; seen.add(h.url); return true; }); const topResults = uniqueHits.slice(0, 3); const enriched = await Promise.all(topResults.map(async r => { const snippet = await fetchTopicSnippet(r.url); return { ...r, snippet }; })); block += `תוצאות חיפוש אשכולות בפורום ${SITE_HOST} עבור "${query}":\n\n`; enriched.forEach((r, i) => { block += `--- נושא ${i + 1}: ${r.title} ---\nקישור מלא: ${r.url}\n`; block += r.snippet ? `תוכן הנושא:\n${r.snippet}\n\n` : '(לא ניתן היה לשלוף את תוכן הנושא)\n\n'; }); } return block; } async function fetchTopicSnippet(url, maxChars = 1400) { try { const html = await fetchText(url); const doc = new DOMParser().parseFromString(html, 'text/html'); doc.querySelectorAll('script,style,nav,header,footer,aside,form').forEach(x => x.remove()); const postEls = Array.from(doc.querySelectorAll('[component="post"]')); let text = ''; if (postEls.length) { const parts = postEls.slice(0, 8).map((post, i) => { const contentEl = post.querySelector('[component="post/content"]') || post; const authorEl = post.querySelector('[component="user/username"]') || post.querySelector('a[href*="/user/"]'); const author = authorEl ? authorEl.textContent.trim() : ''; const postText = htmlToText(contentEl); return postText ? `[פוסט ${i + 1}${author ? ' מאת ' + author : ''}]\n${postText}` : ''; }).filter(Boolean); text = parts.join('\n\n'); } if (!text) { const container = doc.querySelector('[component="topic"]') || doc.querySelector('main') || doc.body; text = htmlToText(container); } if (text.length > maxChars) text = text.slice(0, maxChars) + '\n...[הטקסט קוצץ]'; return text; } catch { return ''; } } function getPageContext() { try { const main = document.querySelector('[component="topic"]') || document.querySelector('main') || document.body; let text = htmlToText(main); if (text.length > 1500) { text = text.slice(0, 1500) + '\n...[הטקסט קוצץ]'; } return `אתר: ${SITE_HOST}\nכותרת: ${document.title}\nתוכן העמוד:\n${text}`; } catch { return ''; } } const MODEL_CANDIDATES = ['gemini-3.6-flash', 'gemini-3.5-flash-lite', 'gemini-2.5-flash-lite']; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function callGeminiWithModel(model, key, body) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`; const res = await request({ method: 'POST', url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(body), timeout: 30000 }); let data; try { data = JSON.parse(res.responseText); } catch { throw new Error('תשובת השרת אינה תקינה.'); } if (res.status < 200 || res.status >= 300) { const err = new Error(data?.error?.message || `שגיאה (${res.status})`); err.status = res.status; throw err; } const parts = data?.candidates?.[0]?.content?.parts || []; const answer = parts.filter(p => !p.thought).map(p => p.text || '').join(''); const finishReason = data?.candidates?.[0]?.finishReason; if (finishReason === 'MAX_TOKENS' && answer.trim()) { return answer + '\n\n⚠️ (התשובה נקטעה עקב מגבלת אורך)'; } return answer || '(לא התקבלה תשובה)'; } async function callGemini(history, searchResults) { const key = get(K.apiKey, '').trim(); if (!key) throw new Error('לא הוגדר מפתח API בהגדרות.'); let systemText = `אתה עוזר AI לפורום ${SITE_HOST}. ענה בעברית ברורה, מדויקת ולעניין. באפשרותך לחפש ולהציג גם פרופילים, מוניטין ונתוני משתמשים בפורום במידת הצורך.`; const persona = get(K.persona, ''); if (persona) systemText += '\n\n' + persona; if (get(K.context, true)) { const context = getPageContext(); if (context) systemText += '\n\n' + context; } if (searchResults) { systemText += '\n\nמידע שנמצא בחיפוש בפורום ובמשתמשים:\n' + searchResults; } const contents = history.map(m => { const parts = []; if (m.fileData) { parts.push({ inlineData: { mimeType: m.fileData.mimeType, data: m.fileData.data } }); } parts.push({ text: m.text }); return { role: m.role === 'model' ? 'model' : 'user', parts }; }); const body = { contents, generationConfig: { maxOutputTokens: 2500 } }; if (systemText) { body.systemInstruction = { parts: [{ text: systemText }] }; } let lastError; for (const model of MODEL_CANDIDATES) { for (let attempt = 0; attempt < 2; attempt++) { try { return await callGeminiWithModel(model, key, body); } catch (e) { lastError = e; const retryable = e.status === 503 || e.status === 429; if (retryable && attempt === 0) { await sleep(1200); continue; } break; } } } throw lastError || new Error('כל המודלים נכשלו.'); } // --- UI Elements & Styling --- const style = document.createElement('style'); style.textContent = ` #bina-ai-button { position:fixed; bottom:22px; left:22px; width:58px; height:58px; border-radius:50%; border:0; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-size:27px; cursor:pointer; z-index:999999; box-shadow:0 5px 20px rgba(0,0,0,.3); } #bina-ai-panel { position:fixed; left:22px; bottom:92px; width:400px; max-width:92vw; height:560px; max-height:80vh; background:white; border-radius:15px; overflow:hidden; display:none; flex-direction:column; z-index:999999; box-shadow:0 10px 40px rgba(0,0,0,.3); direction:rtl; font-family:Arial,sans-serif; border:1px solid #ddd; } #bina-ai-panel.open { display:flex; } #bina-ai-panel.fullscreen { inset:0; width:100vw; height:100vh; max-width:none; max-height:none; border-radius:0; } #bina-ai-header { padding:11px; background:linear-gradient(135deg,#4f8cff,#7c5cff); color:white; font-weight:bold; display:flex; justify-content:space-between; align-items:center; cursor:move; user-select:none; } #bina-ai-header button { border:0; background:transparent; color:white; font-size:15px; cursor:pointer; margin-right:4px; } #bina-ai-new-btn { background: #ffffff !important; color: #4f8cff !important; padding: 4px 10px; border-radius: 8px; font-size: 12px !important; font-weight: bold; margin-left: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.15); transition: all 0.2s; cursor: pointer; } #bina-ai-new-btn:hover { background: #f0f4ff !important; transform: translateY(-1px); } #bina-ai-info { padding:6px 10px; font-size:11px; background:#fafafa; border-bottom:1px solid #eee; color:#666; display:flex; justify-content:space-between; align-items:center; } #bina-ai-messages { flex:1; overflow-y:auto; padding:10px; background:#f5f6fa; display:flex; flex-direction:column; gap:8px; } .bina-ai-msg { padding:9px 11px; border-radius:10px; max-width:88%; font-size:13px; line-height:1.5; white-space:pre-wrap; word-break:break-word; } .bina-ai-user { align-self:flex-end; background:#4f8cff; color:white; } .bina-ai-model { align-self:flex-start; background:white; border:1px solid #ddd; color:#222; } .bina-ai-error { align-self:center; color:#a00000; background:#ffe3e3; border:1px solid #f0b0b0; } .bina-ai-step { align-self:center; color:#444; background:#eef2ff; font-size:11px; } .bina-ai-msg a { color:#2a5fd6; text-decoration:underline; word-break:break-all; } .bina-ai-user a { color:#eaf1ff; } #bina-ai-inputbar { display:flex; gap:6px; padding:8px; border-top:1px solid #ddd; background: #fff; align-items: center; } #bina-ai-input { flex:1; resize:none; height:40px; border:1px solid #ddd; border-radius:8px; padding:8px; font-family:inherit; } #bina-ai-send { border:0; border-radius:8px; padding:0 14px; background:#4f8cff; color:white; font-weight:bold; cursor:pointer; height:40px; } #bina-ai-file-btn { border:0; border-radius:8px; width:40px; height:40px; background:#f0f2f5; color:#555; font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center; } #bina-ai-file-btn:hover { background:#e4e6eb; } #bina-file-indicator { font-size: 11px; color: #4f8cff; padding: 2px 10px; background: #eef2ff; display: none; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; } #bina-ai-settings { position:fixed; inset:0; display:none; align-items:center; justify-content:center; background:rgba(0,0,0,.45); z-index:1000000; } #bina-ai-settings.open { display:flex; } #bina-ai-settings-box { width:460px; max-width:92vw; max-height:88vh; overflow:auto; background:white; border-radius:15px; padding:18px; direction:rtl; font-family:Arial,sans-serif; } .bina-provider { border:1px solid #ddd; border-radius:10px; padding:10px; margin-bottom:9px; } .bina-provider-title { font-weight:bold; margin-bottom:8px; } .bina-provider input[type=password] { width:100%; box-sizing:border-box; padding:6px; margin-top:5px; border:1px solid #ddd; border-radius:6px; } .bina-field { margin-top:10px; } .bina-field label { display:block; font-weight:bold; font-size:12px; margin-bottom:5px; } .bina-field textarea { width:100%; box-sizing:border-box; padding:8px; border:1px solid #ddd; border-radius:8px; } .bina-buttons { display:flex; justify-content:flex-end; gap:8px; margin-top:15px; } .bina-buttons button { padding:8px 16px; border:0; border-radius:8px; cursor:pointer; } #bina-ai-save { background:#4f8cff; color:white; } .session-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee; font-size: 13px; } .session-item:hover { background: #f8f9fa; } .session-title { cursor: pointer; color: #2a5fd6; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-left: 10px; font-weight: 500; } .session-title:hover { text-decoration: underline; } .session-actions button { background: #ff4d4d; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 11px; margin-right: 4px; } .session-actions button.load-btn { background: #4f8cff; } `; document.head.appendChild(style); const button = document.createElement('button'); button.id = 'bina-ai-button'; button.textContent = '🤖'; button.title = `עוזר AI — ${SITE_HOST}`; document.body.appendChild(button); const panel = document.createElement('div'); panel.id = 'bina-ai-panel'; panel.innerHTML = ` <div id="bina-ai-header"> <div style="display:flex; align-items:center;"> <span>🤖 עוזר AI</span> <button id="bina-ai-new-btn" title="התחל שיחה חדשה">💬 שיחה חדשה</button> </div> <div> <button id="bina-ai-settings-btn" title="הגדרות והיסטוריית שיחות">⚙️</button> <button id="bina-ai-fullscreen" title="מסך מלא">⛶</button> <button id="bina-ai-close" title="סגור">✕</button> </div> </div> <div id="bina-ai-info"> <div>🌐 מחובר ל-${SITE_HOST}</div> <div> <label><input type="checkbox" id="bina-ai-context"> הקשר עמוד</label> </div> </div> <div id="bina-file-indicator"> <span id="bina-file-name">📎 קובץ צורף</span> <button id="bina-remove-file" style="background:none; border:none; color:#a00000; cursor:pointer; font-weight:bold;">הסר ✕</button> </div> <div id="bina-ai-messages"></div> <div id="bina-ai-inputbar"> <input type="file" id="bina-ai-file-input" style="display:none;" /> <button id="bina-ai-file-btn" type="button" title="צירוף קובץ או תמונה">📎</button> <textarea id="bina-ai-input" placeholder="שאל שאלה, חפש אשכול, בדוק משתמש או הדבק תמונה/קובץ (Ctrl+V)..."></textarea> <button id="bina-ai-send" type="button">שלח</button> </div> `; document.body.appendChild(panel); // --- Dragging Logic --- const header = document.querySelector('#bina-ai-header'); let isDragging = false, startX, startY, initialX, initialY; header.addEventListener('mousedown', e => { if (e.target.tagName === 'BUTTON') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; panel.style.bottom = 'auto'; panel.style.left = initialX + 'px'; panel.style.top = initialY + 'px'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; panel.style.left = (initialX + dx) + 'px'; panel.style.top = (initialY + dy) + 'px'; } function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } // --- File Attachment & Paste Logic --- let attachedFile = null; const fileInput = document.querySelector('#bina-ai-file-input'); const fileBtn = document.querySelector('#bina-ai-file-btn'); const fileIndicator = document.querySelector('#bina-file-indicator'); const fileNameSpan = document.querySelector('#bina-file-name'); const removeFileBtn = document.querySelector('#bina-remove-file'); const input = document.querySelector('#bina-ai-input'); fileBtn.onclick = () => fileInput.click(); function processFile(file) { if (!file) return; const reader = new FileReader(); reader.onload = function (uploadEvent) { const base64Data = uploadEvent.target.result.split(',')[1]; attachedFile = { mimeType: file.type || 'application/octet-stream', data: base64Data, name: file.name || 'קובץ מודבק' }; fileNameSpan.textContent = `📎 ${attachedFile.name} צורף בהצלחה`; fileIndicator.style.display = 'flex'; }; reader.readAsDataURL(file); } fileInput.onchange = (e) => { processFile(e.target.files[0]); }; // תמיכה בהדבקת קובץ/תמונה ישירות לתיבת הטקסט (Ctrl + V) input.addEventListener('paste', (e) => { const items = (e.clipboardData || e.originalEvent.clipboardData).items; for (let index in items) { const item = items[index]; if (item.kind === 'file') { const blob = item.getAsFile(); if (blob) { e.preventDefault(); processFile(blob); break; } } } }); removeFileBtn.onclick = () => { attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; }; // --- Sessions Management Logic --- let sessions = []; try { sessions = JSON.parse(get(K.sessions, '[]')); if (!Array.isArray(sessions)) sessions = []; } catch (e) { sessions = []; } let currentSessionId = get(K.currentSessionId, ''); let history = []; function saveSessionsToStorage() { set(K.sessions, JSON.stringify(sessions)); set(K.currentSessionId, currentSessionId); } function startNewSession() { const newId = 'session_' + Date.now(); const newSession = { id: newId, title: 'שיחה חדשה', history: [] }; sessions.unshift(newSession); if (sessions.length > 20) sessions.pop(); currentSessionId = newId; history = []; saveSessionsToStorage(); renderMessages(); welcome(); renderSessionsList(); } function loadSession(id) { const found = sessions.find(s => s.id === id); if (found) { currentSessionId = found.id; history = [...found.history]; saveSessionsToStorage(); renderMessages(); if (history.length === 0) welcome(); renderSessionsList(); } } function deleteSession(id) { sessions = sessions.filter(s => s.id !== id); if (currentSessionId === id) { if (sessions.length > 0) { loadSession(sessions[0].id); } else { startNewSession(); } } else { saveSessionsToStorage(); renderSessionsList(); } } // --- Settings & History Modal --- const settings = document.createElement('div'); settings.id = 'bina-ai-settings'; settings.innerHTML = ` <div id="bina-ai-settings-box"> <h3>⚙️ הגדרות והיסטוריית שיחות</h3> <div style="font-size:11px;color:#777;margin-bottom:12px">האתר הנוכחי: <b>${SITE_HOST}</b></div> <div class="bina-provider"> <div class="bina-provider-title">🔵 Gemini API Key</div> <div style="font-size:11px;margin-bottom:3px;color:#555;">מפתח API:</div> <input type="password" id="gemini-key" placeholder="הכנס כאן את המפתח"> </div> <div class="bina-field"><label>הנחיות אישיות</label><textarea id="bina-persona" placeholder="לדוגמה: ענה בקצרה..."></textarea></div> <div class="bina-field" style="margin-top:15px;"> <label>📜 היסטוריית שיחות שמורות</label> <div id="bina-sessions-container" style="max-height:180px; overflow-y:auto; border:1px solid #ddd; border-radius:6px; margin-top:5px; background:#fafafa;"></div> </div> <div class="bina-buttons"><button id="bina-cancel">סגור</button><button id="bina-ai-save">שמור הגדרות</button></div> </div> `; document.body.appendChild(settings); function renderSessionsList() { const container = document.querySelector('#bina-sessions-container'); if (!container) return; if (sessions.length === 0) { container.innerHTML = '<div style="padding:10px; font-size:12px; color:#777; text-align:center;">אין שיחות שמורות</div>'; return; } container.innerHTML = ''; sessions.forEach(s => { const item = document.createElement('div'); item.className = 'session-item'; if (s.id === currentSessionId) { item.style.background = '#eef2ff'; item.style.fontWeight = 'bold'; } const titleSpan = document.createElement('span'); titleSpan.className = 'session-title'; titleSpan.textContent = s.title || 'שיחה ללא כותרת'; titleSpan.title = s.title; titleSpan.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const actionsDiv = document.createElement('div'); actionsDiv.className = 'session-actions'; const loadBtn = document.createElement('button'); loadBtn.className = 'load-btn'; loadBtn.textContent = 'המשך'; loadBtn.onclick = () => { loadSession(s.id); settings.classList.remove('open'); }; const delBtn = document.createElement('button'); delBtn.textContent = 'מחק'; delBtn.onclick = () => { deleteSession(s.id); }; actionsDiv.appendChild(loadBtn); actionsDiv.appendChild(delBtn); item.appendChild(titleSpan); item.appendChild(actionsDiv); container.appendChild(item); }); } function loadSettings() { document.querySelector('#gemini-key').value = get(K.apiKey, ''); document.querySelector('#bina-persona').value = get(K.persona, ''); document.querySelector('#bina-ai-context').checked = get(K.context, true); renderSessionsList(); } function saveSettings() { set(K.apiKey, document.querySelector('#gemini-key').value.trim()); set(K.persona, document.querySelector('#bina-persona').value.trim()); set(K.context, document.querySelector('#bina-ai-context').checked); settings.classList.remove('open'); } document.querySelector('#bina-ai-settings-btn').onclick = () => { loadSettings(); settings.classList.add('open'); }; document.querySelector('#bina-cancel').onclick = () => settings.classList.remove('open'); document.querySelector('#bina-ai-save').onclick = saveSettings; document.querySelector('#bina-ai-new-btn').onclick = () => { startNewSession(); }; const messagesContainer = document.querySelector('#bina-ai-messages'); const sendButton = document.querySelector('#bina-ai-send'); function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>'); } function linkify(escapedText) { const urlRegex = /(https?:\/\/[^\s<>"')]+)/g; return escapedText.replace(urlRegex, raw => { const trimmed = raw.replace(/[).,;:!?]+$/, ''); const trailing = raw.slice(trimmed.length); return `<a href="${trimmed}" target="_blank" rel="noopener noreferrer">${trimmed}</a>${trailing}`; }); } function renderMessages() { messagesContainer.innerHTML = ''; history.forEach(m => { const type = m.role === 'user' ? 'bina-ai-user' : 'bina-ai-model'; const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(m.text)).replace(/\n/g, '<br>'); if (m.fileData && m.fileData.name) { contentHtml = `📎 <b>[קובץ: ${m.fileData.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); }); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function addMessage(text, type, fileInfo = null) { const div = document.createElement('div'); div.className = 'bina-ai-msg ' + type; let contentHtml = linkify(escapeHtml(text)).replace(/\n/g, '<br>'); if (fileInfo && fileInfo.name) { contentHtml = `📎 <b>[קובץ: ${fileInfo.name}]</b><br>` + contentHtml; } div.innerHTML = contentHtml; messagesContainer.appendChild(div); messagesContainer.scrollTop = messagesContainer.scrollHeight; return div; } function welcome() { addMessage(`שלום! 🤖\nאני עוזר ה-AI של ${SITE_HOST}.\n\nשאל אותי כל שאלה, חפש אשכולות, בדוק פרופילים או צרף/הדבק קובץ ותמונה!`, 'bina-ai-model'); if (!get(K.apiKey)) { addMessage('⚠️ עדיין לא הוגדר מפתח API. פתח ⚙️ והוסף.', 'bina-ai-error'); } } button.onclick = () => panel.classList.toggle('open'); document.querySelector('#bina-ai-close').onclick = () => panel.classList.remove('open'); document.querySelector('#bina-ai-fullscreen').onclick = () => panel.classList.toggle('fullscreen'); async function send() { const text = input.value.trim(); const currentFile = attachedFile; if (!text && !currentFile) return; if (!get(K.apiKey)) { addMessage('⚠️ אין מפתח API פעיל. פתח ⚙️ והגדר.', 'bina-ai-error'); return; } if (!currentSessionId || sessions.length === 0) { startNewSession(); } const messageText = text || 'צורף קובץ לעיון'; addMessage(messageText, 'bina-ai-user', currentFile); history.push({ role: 'user', text: messageText, fileData: currentFile ? { mimeType: currentFile.mimeType, data: currentFile.data, name: currentFile.name } : null }); input.value = ''; attachedFile = null; fileInput.value = ''; fileIndicator.style.display = 'none'; sendButton.disabled = true; const currentSession = sessions.find(s => s.id === currentSessionId); if (currentSession && currentSession.title === 'שיחה חדשה') { currentSession.title = messageText.slice(0, 25) + (messageText.length > 25 ? '...' : ''); } const stepMsg = addMessage('🔎 מחפש בנתוני הפורום והמשתמשים...', 'bina-ai-step'); try { const searchResults = await quickForumSearch(messageText); stepMsg.remove(); const pending = addMessage('🤖 מנתח תשובה...', 'bina-ai-model'); const answer = await callGemini(history, searchResults); pending.remove(); addMessage(answer, 'bina-ai-model'); history.push({ role: 'model', text: answer, fileData: null }); if (currentSession) { currentSession.history = [...history]; saveSessionsToStorage(); } } catch (e) { stepMsg.remove(); addMessage('⚠️ ' + (e?.message || String(e)), 'bina-ai-error'); } finally { sendButton.disabled = false; input.focus(); } } sendButton.onclick = send; input.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }); GM_registerMenuCommand('⚙️ הגדרות עוזר AI', () => { loadSettings(); settings.classList.add('open'); }); if (sessions.length > 0) { if (!currentSessionId || !sessions.some(s => s.id === currentSessionId)) { currentSessionId = sessions[0].id; } loadSession(currentSessionId); } else { startNewSession(); } })();כמה תמונות מהעוזר גרסת "חפרן" שעשיתי:



@מוטל-2 ממש נראה טוב רק בפעם הבאה תעדכן לפני שאתה מפרסם גרסה משודרגת לדבר שיצרתי
-
-אוקיי חברים,-
אז בניתי תוסף שהופך את מתמחים טופ לחכם הרבה יותר. מרגע שמתקינים אותו, יש כפתור
צף בכל עמוד בפורום - ומאחוריו עוזר AI אמיתי שיודע לחפש, לבדוק ולהמליץ בשבילכם, ולא סתם לענות שאלות כללי
מה הוא יודע לעשות?- לחפש בפורום אשכולות אמיתיים לפי נושא
- לבדוק מוניטין ופרופיל של משתמש
- להמליץ מה לכתוב בתגובה
- פריט רשימה
- "לראות" את העמוד שאתם נמצאים בו כרגע כדי לתת תשובות רלוונטיות
- אפשר לחבר בו-זמנית כמה ספקי AI (Gemini, Grok, ChatGPT) עם המפתח האישי שלכם, והשאלה נשלחת לכולם במקביל - התשובה שמגיעה ראשונה היא זו שמוצגת.
התקנה
יש להתקין כמובן את התוסף של Tampermonkey מהקישור הזה
ואז יש להתקין את הסקריפט מכאן
בהצלחה
@יודע-ומייבין
לא עובד
ניסיתי גם גימיני וגם גרוק
בגרוק כתב שתשובת גרוק לא תקינה
ובגימיני הוא חשב מלא זמן ובסוף רשם שגיאה -
@יודע-ומייבין
לא עובד
ניסיתי גם גימיני וגם גרוק
בגרוק כתב שתשובת גרוק לא תקינה
ובגימיני הוא חשב מלא זמן ובסוף רשם שגיאה -
@יודע-ומייבין
הגרסא ש @מוטל-2 עשה כן עובדת? -
@המפתיע רבותי אין צורך כל שניה לומר שיש תקלות אני יודע שזה תקול ובעזרה עד יום שישי צמוד לכנסת שבת אני יוציא גרסה משודרגת ולא שזה אכפת לי פשוט אין ענין כל רגע לומר שיש תקלה
-
@יודע-ומייבין
הגרסא ש @מוטל-2 עשה כן עובדת?@מה-זה-משנה-אה כרגע כן
-
@מה-זה-משנה-אה כרגע כן
@יאיר-דניאל
ניסיתי וגם זה לא -
@מוטל-2 ממש נראה טוב רק בפעם הבאה תעדכן לפני שאתה מפרסם גרסה משודרגת לדבר שיצרתי
דרך אגב, קרה למישהו תקלות בקוד שלי?
אשמח לקבל הערות,
כי לפי הנראה זה עובד מצויין.... -
דרך אגב, קרה למישהו תקלות בקוד שלי?
אשמח לקבל הערות,
כי לפי הנראה זה עובד מצויין....@מוטל-2 ממש הגזמת זה בסדר
-
@יאיר-דניאל
ניסיתי וגם זה לא@מה-זה-משנה-אה
מה הבעיה שזה עשה לך? -
@מוטל-2 ממש הגזמת זה בסדר
@יודע-ומייבין
אני מחזיר את הפוסט -
@יודע-ומייבין
אני מחזיר את הפוסט@מוטל-2 עובד מצויין!!!

-
@מוטל-2 עובד מצויין!!!

@יאיר-דניאל @יודע-ומייבין לא שואל ומבין כי לצערנו לא קיים כזה משתמש
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות

