(() => { 'use strict'; let lastTweetText = ''; let sidebarHost = null; // ===== DATA ===== const LANGS = [ { value: 'vi', label: 'Việt' }, { value: 'en', label: 'English' }, { value: 'ja', label: 'Nhat' }, { value: 'ko', label: 'Han Quoc' }, { value: 'cn', label: 'Trung' } ]; // Tone base (tất cả ngôn ngữ đều có) const TONE_BASE = [ { value: 'professional', text: 'chuyên nghiệp, rõ ràng, đáng tin cậy' }, { value: 'casual', text: 'Giản dị, thân thiện' }, { value: 'hype', text: 'Hype — Hào hứng, tràn đầy năng lượng' }, { value: 'urgent', text: 'urgent' }, { value: 'humorous', text: 'Dí dỏm, hài hước' }, { value: 'informative', text: 'Thông tin, chính xác' }, { value: 'empathetic', text: 'empathetic — Đồng cảm, thấu hiểu cảm xúc, biết trân trọng người khác.' }, { value: 'provocative', text: 'provocative — Gợi mở suy nghĩ, hơi gây tranh cãi, thách thức các giả định.' }, { value: 'authoritative', text: 'authoritative — giọng tự tin, uy quyền, chuyên nghiệp' }, { value: 'spicy', text: 'spicy — Tự tin, hơi đối đầu. KHÔNG giận dữ — chỉ thẳng.' } ]; // Tone chỉ dành cho ja const TONE_JA_ONLY = [ { value: 'aggressive', text: 'aggressive — Cục súc, attack ideas mạnh' }, { value: 'profane', text: 'profane — Nói tục thoải mái, raw' }, { value: 'inflammatory', text: 'inflammatory — Kích động cao, controversial takes' }, { value: 'savage', text: 'savage — Chửi tục OK. Sass tối đa. Vui + ác + thông minh' } ]; const ANGLE_DEFAULT = [ { value: 'agree', text: 'Đồng ý' }, { value: 'challenge', text: 'Không đồng ý' }, { value: 'add_info', text: 'Thêm thông tin liên quan hữu ích' }, { value: 'funny', text: 'Hóm hỉnh, hài hước nhẹ nhàng, không gây khó chịu' }, { value: 'question', text: 'Đặt một câu hỏi tiếp theo thông minh' }, { value: 'relate', text: 'Chia sẻ một trải nghiệm hoặc cảm xúc cá nhân tương tự như bài đăng gốc.' }, { value: 'devil_advocate', text: 'Hãy đóng vai trò người phản biện. Trình bày quan điểm trái chiều một cách công bằng mà không tỏ ra thù địch.' }, { value: 'expand', text: 'expand — Chọn 1 điểm phân tích sâu hơn với nhiều sắc thái khác nhau.' }, { value: 'validate', text: 'validate — Khẳng định luận điểm = bằng chứng hoặc sự đồng tình mạnh mẽ, tăng cường độ tin cậy.' }, { value: 'cta', text: 'cta — Kết thúc bằng lời kêu gọi hành động nhẹ nhàng' } ]; const ANGLE_EMPATHY = [ { value: 'wish_recovery', text: 'Chúc hồi phục' }, { value: 'tribute', text: 'Tưởng nhớ / RIP' }, { value: 'solidarity', text: 'Đồng lòng / Đứng cùng' }, { value: 'personal_support',text: 'Hỗ trợ cá nhân' }, { value: 'shared_grief', text: 'Cùng nỗi buồn' } ]; function getTones(lang) { if (lang === 'ja') return [...TONE_BASE, ...TONE_JA_ONLY]; return [...TONE_BASE]; } function getAngles(tone) { if (tone === 'empathetic') return [...ANGLE_EMPATHY]; return [...ANGLE_DEFAULT]; } // ===== A. BẮT CHUỘT PHẢI ===== document.addEventListener('contextmenu', (e) => { const article = e.target.closest('article'); if (!article) { lastTweetText = ''; return; } const textEl = article.querySelector('[data-testid="tweetText"]') || article.querySelector('div[lang]') || article.querySelector('div[dir="auto"]'); lastTweetText = textEl ? textEl.innerText.trim() : article.innerText.trim().slice(0, 600); }); // ===== B. LẮNG NGHE BACKGROUND ===== chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { if (req.action === 'OPEN_FORM') { if (!lastTweetText) { alert('Không tìm thấy tweet. Hãy chuột phải vào phần chữ của tweet.'); return true; } openSidebar(lastTweetText); return true; } if (req.action === 'SHOW_RESULT') { showResult(req.comment); return true; } if (req.action === 'SHOW_ERROR') { showError(req.error); return true; } }); // ===== C. SIDEBAR ===== function openSidebar(tweetText) { removeSidebar(); const host = document.createElement('div'); host.id = 'x-ai-sidebar-host'; Object.assign(host.style, { position: 'fixed', top: '0', left: '0', width: '0', height: '0', zIndex: '2147483647', overflow: 'visible', pointerEvents: 'none' }); document.body.appendChild(host); sidebarHost = host; const shadow = host.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = ` .fab { position: fixed; right: 24px; bottom: 24px; width: 56px; height: 56px; border-radius: 50%; background: #1d9bf0; color: #fff; font-size: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: none; pointer-events: auto; z-index: 1; transition: transform .15s ease; user-select: none; } .fab:hover { transform: scale(1.08); } .drawer { position: fixed; right: 0; top: 0; width: 400px; max-width: 100vw; height: 100vh; background: #fff; color: #0f1419; box-shadow: -5px 0 25px rgba(0,0,0,0.15); transform: translateX(100%); transition: transform .25s ease; display: flex; flex-direction: column; pointer-events: auto; z-index: 2; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .drawer.open { transform: translateX(0); } .header { padding: 14px 18px; border-bottom: 1px solid #eff3f4; display: flex; justify-content: space-between; align-items: center; font-weight: 700; font-size: 16px; } .btn-x { background: none; border: none; font-size: 20px; cursor: pointer; color: #536471; padding: 4px; line-height: 1; } .body { padding: 16px 18px; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; } .tweet-box { background: #f7f9f9; border: 1px solid #cfd9de; border-radius: 10px; padding: 10px; font-size: 13px; line-height: 1.4; max-height: 150px; overflow-y: auto; color: #333; white-space: pre-wrap; word-break: break-word; } label { font-size: 12px; font-weight: 700; color: #536471; text-transform: uppercase; letter-spacing: .3px; margin-top: 4px; } select { width: 100%; padding: 9px 10px; border-radius: 8px; border: 1px solid #cfd9de; font-size: 14px; background: #fff; } .hint-text { font-size: 12px; color: #888; line-height: 1.4; margin-top: 2px; font-style: italic; } button.primary { width: 100%; padding: 10px; border-radius: 9999px; border: none; background: #1d9bf0; color: #fff; font-weight: 700; font-size: 15px; cursor: pointer; margin-top: 6px; } button.primary:hover { background: #1a8cd8; } button.primary:disabled { background: #8ecdf7; cursor: default; } .status { display: none; padding: 12px; border-radius: 10px; font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; } .status.ok { background: #e8f6fe; border: 1px solid #b4dffc; color: #0f1419; } .status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; } .copy-hint { font-size: 12px; color: #536471; text-align: center; margin-top: 4px; display: none; } `; const fab = document.createElement('button'); fab.className = 'fab'; fab.textContent = '🤖'; fab.title = 'AI Comment'; const drawer = document.createElement('div'); drawer.className = 'drawer'; drawer.innerHTML = `
✍️ AI Comment
${escapeHtml(tweetText)}
📋 Copy kết quả và dán vào ô reply nhé!
`; shadow.appendChild(style); shadow.appendChild(fab); shadow.appendChild(drawer); const toggleDrawer = () => drawer.classList.toggle('open'); fab.addEventListener('click', toggleDrawer); drawer.querySelector('.btn-x').addEventListener('click', () => drawer.classList.remove('open')); requestAnimationFrame(() => drawer.classList.add('open')); // Refs const langSel = drawer.querySelector('#ai-lang'); const toneSel = drawer.querySelector('#ai-tone'); const toneHint = drawer.querySelector('#ai-tone-hint'); const angleSel = drawer.querySelector('#ai-angle'); const angleHint= drawer.querySelector('#ai-angle-hint'); const runBtn = drawer.querySelector('#ai-run'); const statusEl = drawer.querySelector('#ai-status'); const copyHint = drawer.querySelector('#ai-hint'); function buildLangOptions() { return LANGS.map(l => ``).join(''); } function populateSelect(sel, items, selectedValue) { sel.innerHTML = items.map(it => `` ).join(''); if (selectedValue && items.find(i => i.value === selectedValue)) { sel.value = selectedValue; } } function updateTone(lang, keepValueIfValid) { const items = getTones(lang); const old = toneSel.value; populateSelect(toneSel, items, keepValueIfValid && items.find(i => i.value === old) ? old : null); onToneChange(); } function onToneChange() { const lang = langSel.value; const tone = toneSel.value; const tItem = getTones(lang).find(i => i.value === tone); toneHint.textContent = tItem ? tItem.text : ''; const oldAngle = angleSel.value; const aItems = getAngles(tone); populateSelect(angleSel, aItems, aItems.find(i => i.value === oldAngle) ? oldAngle : null); onAngleChange(); } function onAngleChange() { const tone = toneSel.value; const angle = angleSel.value; const aItem = getAngles(tone).find(i => i.value === angle); angleHint.textContent = aItem ? aItem.text : ''; } // Events langSel.addEventListener('change', () => { // Đổi lang: rebuild tone, nếu tone cũ không tồn tại trong list mới thì reset updateTone(langSel.value, true); }); toneSel.addEventListener('change', onToneChange); angleSel.addEventListener('change', onAngleChange); // Init updateTone(langSel.value, false); // Generate runBtn.addEventListener('click', () => { const lang = langSel.value; const tone = toneSel.value; const angle = angleSel.value; runBtn.disabled = true; statusEl.style.display = 'block'; statusEl.className = 'status ok'; statusEl.textContent = '⏳ Đang gọi API...'; copyHint.style.display = 'none'; chrome.runtime.sendMessage( { action: 'GENERATE_COMMENT', data: { text: tweetText, lang, tone, angle } }, () => { if (chrome.runtime.lastError) { statusEl.className = 'status err'; statusEl.textContent = 'Lỗi kết nối background: ' + chrome.runtime.lastError.message; runBtn.disabled = false; } } ); }); } // ===== D. RESULT / ERROR ===== function showResult(comment) { if (!sidebarHost) return; const s = sidebarHost.shadowRoot; const statusEl = s.querySelector('#ai-status'); const copyHint = s.querySelector('#ai-hint'); const runBtn = s.querySelector('#ai-run'); const drawer = s.querySelector('.drawer'); if (statusEl) { statusEl.style.display = 'block'; statusEl.className = 'status ok'; statusEl.textContent = comment; } if (copyHint) copyHint.style.display = 'block'; if (runBtn) runBtn.disabled = false; if (drawer) drawer.classList.add('open'); } function showError(msg) { if (!sidebarHost) return; const s = sidebarHost.shadowRoot; const statusEl = s.querySelector('#ai-status'); const runBtn = s.querySelector('#ai-run'); const drawer = s.querySelector('.drawer'); if (statusEl) { statusEl.style.display = 'block'; statusEl.className = 'status err'; statusEl.textContent = 'Lỗi: ' + msg; } if (runBtn) runBtn.disabled = false; if (drawer) drawer.classList.add('open'); } function removeSidebar() { if (sidebarHost) { sidebarHost.remove(); sidebarHost = null; } } function escapeHtml(text) { const d = document.createElement('div'); d.textContent = text; return d.innerHTML; } })();