Update
This commit is contained in:
+21
-37
@@ -1,6 +1,6 @@
|
|||||||
console.log('[XAI Background] ✅ Service worker started');
|
console.log('[XAI Background] ✅ Service worker started');
|
||||||
|
|
||||||
// ===== TẠO MENU =====
|
// ===== MENU =====
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
chrome.contextMenus.create({
|
chrome.contextMenus.create({
|
||||||
id: 'writeComment',
|
id: 'writeComment',
|
||||||
@@ -10,69 +10,53 @@ chrome.runtime.onInstalled.addListener(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
// FIRE & FORGET — không đợi content trả lời
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
if (info.menuItemId !== 'writeComment' || !tab?.id) return;
|
if (info.menuItemId !== 'writeComment' || !tab?.id) return;
|
||||||
try {
|
chrome.tabs.sendMessage(tab.id, { action: 'OPEN_FORM' }).catch(() => {});
|
||||||
await chrome.tabs.sendMessage(tab.id, { action: 'OPEN_FORM' });
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[XAI Background] ❌ Không gọi được content script:', err.message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== NHẬN YÊU CẦU TỪ CONTENT =====
|
// LUÔN GỌI sendResponse(), KHÔNG BAO GIỜ return true
|
||||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
if (request.action === 'GENERATE_COMMENT') {
|
if (request.action === 'GENERATE_COMMENT') {
|
||||||
// Gọi API async riêng, không await ở đây
|
|
||||||
handleGenerate(request.data, sender.tab.id);
|
handleGenerate(request.data, sender.tab.id);
|
||||||
|
|
||||||
// Đóng kênh ngay để tránh lỗi "channel closed"
|
|
||||||
sendResponse({ received: true });
|
sendResponse({ received: true });
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
sendResponse({ unknown: true });
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== GỌI API: ĐỌC CONFIG TỪ STORAGE =====
|
|
||||||
async function handleGenerate({ text, lang, tone, angle }, tabId) {
|
async function handleGenerate({ text, lang, tone, angle }, tabId) {
|
||||||
console.log('[XAI Background] ⏳ Đọc config từ storage...');
|
const cfg = await chrome.storage.local.get(['apiUrl', 'apiKey']);
|
||||||
|
if (!cfg.apiUrl || !cfg.apiKey) {
|
||||||
const config = await chrome.storage.local.get(['apiUrl', 'apiKey']);
|
|
||||||
|
|
||||||
if (!config.apiUrl || !config.apiKey) {
|
|
||||||
console.error('[XAI Background] ❌ Thiếu API config');
|
|
||||||
await chrome.tabs.sendMessage(tabId, {
|
await chrome.tabs.sendMessage(tabId, {
|
||||||
action: 'SHOW_ERROR',
|
action: 'SHOW_ERROR',
|
||||||
error: '⚠️ Chưa cấu hình API.\n\n👉 Bấm tab ⚙️ Config để nhập URL và Key.'
|
error: '⚠️ Chưa cấu hình API.\n\n👉 Bấm tab ⚙️ Config để nhập URL và Key.'
|
||||||
});
|
}).catch(() => {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
|
||||||
originalPost: text,
|
|
||||||
language: lang,
|
|
||||||
tone: tone?tone.toLowerCase():undefined,
|
|
||||||
angle: angle?angle.toLowerCase():undefined,
|
|
||||||
};
|
|
||||||
console.log('[XAI Background] 📤 Payload:', JSON.stringify(payload, null, 2));
|
|
||||||
console.log('[XAI Background] 🌐 URL:', config.apiUrl);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(config.apiUrl, {
|
const payload = {
|
||||||
|
originalPost: text,
|
||||||
|
language: lang,
|
||||||
|
tone: tone?tone.toLowerCase():undefined,
|
||||||
|
angle: angle?angle.toLowerCase():undefined,
|
||||||
|
};
|
||||||
|
const res = await fetch(cfg.apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${config.apiKey}`
|
'Authorization': `Bearer ${cfg.apiKey}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
console.log('[XAI Background] 📥 Response:', res.status, data);
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
|
||||||
const comment = data.comment || data.text || JSON.stringify(data);
|
const comment = data.comment || data.text || JSON.stringify(data);
|
||||||
await chrome.tabs.sendMessage(tabId, { action: 'SHOW_RESULT', comment });
|
await chrome.tabs.sendMessage(tabId, { action: 'SHOW_RESULT', comment }).catch(() => {});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[XAI Background] ❌ Lỗi fetch:', err.message);
|
await chrome.tabs.sendMessage(tabId, { action: 'SHOW_ERROR', error: err.message }).catch(() => {});
|
||||||
await chrome.tabs.sendMessage(tabId, { action: 'SHOW_ERROR', error: err.message });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+97
-83
@@ -3,14 +3,15 @@
|
|||||||
|
|
||||||
let lastTweetText = '';
|
let lastTweetText = '';
|
||||||
let sidebarHost = null;
|
let sidebarHost = null;
|
||||||
|
let isTyping = false; // chặn bấm paste nhiều lần
|
||||||
|
|
||||||
// ===== DATA (giữ nguyên) =====
|
// ===== DATA =====
|
||||||
const LANGS = [
|
const LANGS = [
|
||||||
{ value: 'vi', label: 'Việt' },
|
{ value: 'vi', label: 'Việt' },
|
||||||
{ value: 'ja', label: 'Nhật' },
|
|
||||||
{ value: 'en', label: 'English' },
|
{ value: 'en', label: 'English' },
|
||||||
{ value: 'ko', label: 'Hàn' },
|
{ value: 'ja', label: 'Nhật' },
|
||||||
{ value: 'cn', label: 'Trung' }
|
{ value: 'ko', label: 'Han' },
|
||||||
|
{ value: 'cn', label: 'TQ' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const TONE_BASE = [
|
const TONE_BASE = [
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
return tone === 'EMPATHETIC' ? [...ANGLE_EMPATHY] : [...ANGLE_DEFAULT];
|
return tone === 'EMPATHETIC' ? [...ANGLE_EMPATHY] : [...ANGLE_DEFAULT];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== A. BẮT CHUỘT PHẢI =====
|
// ===== A. CAPTURE TWEET =====
|
||||||
document.addEventListener('contextmenu', (e) => {
|
document.addEventListener('contextmenu', (e) => {
|
||||||
const article = e.target.closest('article');
|
const article = e.target.closest('article');
|
||||||
if (!article) { lastTweetText = ''; return; }
|
if (!article) { lastTweetText = ''; return; }
|
||||||
@@ -70,87 +71,95 @@
|
|||||||
article.querySelector('[data-testid="tweetText"]') ||
|
article.querySelector('[data-testid="tweetText"]') ||
|
||||||
article.querySelector('div[lang]') ||
|
article.querySelector('div[lang]') ||
|
||||||
article.querySelector('div[dir="auto"]');
|
article.querySelector('div[dir="auto"]');
|
||||||
lastTweetText = textEl
|
lastTweetText = textEl ? textEl.innerText.trim() : article.innerText.trim().slice(0, 600);
|
||||||
? textEl.innerText.trim()
|
|
||||||
: article.innerText.trim().slice(0, 600);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ===== B. LISTENER — FIX LỖI CHANNEL =====
|
||||||
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
||||||
if (req.action === 'OPEN_FORM') {
|
if (req.action === 'OPEN_FORM') {
|
||||||
if (!lastTweetText) {
|
if (!lastTweetText) {
|
||||||
alert('Không tìm thấy tweet. Hãy chuột phải vào phần chữ của tweet.');
|
sendResponse({ ok: false, reason: 'no_text' });
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
openSidebar(lastTweetText);
|
openSidebar(lastTweetText);
|
||||||
return true;
|
sendResponse({ ok: true });
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (req.action === 'SHOW_RESULT') { showResult(req.comment); return true; }
|
if (req.action === 'SHOW_RESULT') {
|
||||||
if (req.action === 'SHOW_ERROR') { showError(req.error); return true; }
|
showResult(req.comment);
|
||||||
|
sendResponse({ ok: true });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (req.action === 'SHOW_ERROR') {
|
||||||
|
showError(req.error);
|
||||||
|
sendResponse({ ok: true });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sendResponse({ ok: false, unknown: true });
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== HUMAN TYPING SIMULATION =====
|
// ===== C. TYPING ENGINE (VIẾT LẠI) =====
|
||||||
async function simulateHumanTyping(text) {
|
async function simulateHumanTyping(fullText) {
|
||||||
// X dùng contenteditable div. Tìm ô reply đang mở.
|
// Tìm ô reply của X
|
||||||
let el =
|
let editor =
|
||||||
document.querySelector('[data-testid="tweetTextarea_0"]') ||
|
document.querySelector('[data-testid="tweetTextarea_0"]') ||
|
||||||
document.querySelector('div[contenteditable="true"][role="textbox"]') ||
|
document.querySelector('div[contenteditable="true"][role="textbox"]');
|
||||||
document.querySelector('div[contenteditable="true"][data-text="true"]');
|
|
||||||
|
|
||||||
if (!el) {
|
if (!editor) {
|
||||||
alert('🔍 Không tìm thấy ô reply.\n\n👉 Hãy bấm nút "Reply" của tweet trước để ô nhập hiện ra, rồi thử lại!');
|
alert('🔍 Không tìm thấy ô reply.\n\n👉 Hãy bấm nút "Reply" của tweet trước để ô nhập hiện ra, rồi thử lại!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
el.focus();
|
editor.focus();
|
||||||
el.click();
|
editor.click();
|
||||||
|
|
||||||
|
// Reset sạch: xóa hết, tạo 1 TextNode duy nhất để dễ manipulate
|
||||||
|
editor.textContent = '';
|
||||||
|
const textNode = document.createTextNode('');
|
||||||
|
editor.appendChild(textNode);
|
||||||
|
|
||||||
// Di chuyển cursor về cuối nếu cần (optional)
|
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection();
|
||||||
sel.selectAllChildren(el);
|
const range = document.createRange();
|
||||||
sel.collapseToEnd();
|
range.setStart(textNode, 0);
|
||||||
|
range.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
const baseDelay = 35; // ms
|
for (let i = 0; i < fullText.length; i++) {
|
||||||
|
const char = fullText[i];
|
||||||
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
// 1. Thêm ký tự vào TextNode
|
||||||
const char = text[i];
|
textNode.nodeValue += char;
|
||||||
|
|
||||||
// Cách đáng tin cậy nhất trên X: execCommand insertText
|
// 2. Đẩy cursor về cuối
|
||||||
const inserted = document.execCommand('insertText', false, char);
|
range.setEnd(textNode, textNode.nodeValue.length);
|
||||||
|
range.collapse(false);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
// Fallback nếu execCommand bị khước từ
|
// 3. Báo cho React/Draft.js biết DOM đã đổi
|
||||||
if (!inserted) {
|
editor.dispatchEvent(new InputEvent('input', {
|
||||||
const range = sel.getRangeAt(0);
|
|
||||||
range.deleteContents();
|
|
||||||
const node = document.createTextNode(char);
|
|
||||||
range.insertNode(node);
|
|
||||||
range.setStartAfter(node);
|
|
||||||
range.collapse(true);
|
|
||||||
sel.removeAllRanges();
|
|
||||||
sel.addRange(range);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kích hoạt React re-render
|
|
||||||
el.dispatchEvent(new InputEvent('input', {
|
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
inputType: 'insertText',
|
inputType: 'insertText',
|
||||||
data: char
|
data: char
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Delay ngẫu nhiên
|
// 4. Delay như người gõ thật
|
||||||
let delay = baseDelay + Math.random() * 70;
|
let delay = 30 + Math.random() * 50;
|
||||||
if (char === ' ') delay += 30 + Math.random() * 50;
|
if (char === ' ') delay += 40 + Math.random() * 40;
|
||||||
if ('.!?,'.includes(char)) delay += 120 + Math.random() * 250;
|
if ('.!?,'.includes(char)) delay += 120 + Math.random() * 200;
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, delay));
|
await new Promise(r => setTimeout(r, delay));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final change event cho chắc
|
// Final change event
|
||||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
editor.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SIDEBAR =====
|
// ===== D. SIDEBAR UI =====
|
||||||
function openSidebar(tweetText) {
|
function openSidebar(tweetText) {
|
||||||
removeSidebar();
|
removeSidebar();
|
||||||
|
|
||||||
@@ -171,7 +180,7 @@
|
|||||||
cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: none; pointer-events: auto; z-index: 1;
|
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; }
|
transition: transform .15s ease; user-select: none; }
|
||||||
.fab:hover { transform: scale(1.08); }
|
.fab:hover { transform: scale(1.08); }
|
||||||
.drawer { position: fixed; right: 0; top: 0; width: 442px; max-width: 100vw; height: 100vh; background: #fff; color: #0f1419;
|
.drawer { position: fixed; right: 0; top: 0; width: 420px; 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;
|
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;
|
display: flex; flex-direction: column; pointer-events: auto; z-index: 2;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
||||||
@@ -195,17 +204,18 @@
|
|||||||
font-weight: 700; font-size: 15px; cursor: pointer; margin-top: 6px; }
|
font-weight: 700; font-size: 15px; cursor: pointer; margin-top: 6px; }
|
||||||
button.primary:hover { background: #1a8cd8; }
|
button.primary:hover { background: #1a8cd8; }
|
||||||
button.primary:disabled { background: #8ecdf7; cursor: default; }
|
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; }
|
button.green { background: #17bf63 !important; }
|
||||||
|
button.green:hover { background: #15a857 !important; }
|
||||||
|
.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; }
|
.status.ok { background: #e8f6fe; border: 1px solid #b4dffc; }
|
||||||
.status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; }
|
.status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; }
|
||||||
.copy-hint { font-size: 12px; color: #536471; text-align: center; display: none; }
|
.copy-hint { font-size: 12px; color: #536471; text-align: center; display: none; }
|
||||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; background: #ffad1f; color: #fff; font-size: 11px; font-weight: 700; }
|
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; background: #ffad1f; color: #fff; font-size: 11px; font-weight: 700; }
|
||||||
.typing-opts { margin-top: 8px; padding-top: 10px; border-top: 1px solid #eff3f4; display: none; }
|
.typing-opts { margin-top: 8px; padding-top: 10px; border-top: 1px solid #eff3f4; display: none; flex-direction: column; gap: 8px; }
|
||||||
.typing-opts.visible { display: flex; flex-direction: column; gap: 8px; }
|
.typing-opts.visible { display: flex; }
|
||||||
.check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #0f1419; cursor: pointer; }
|
.check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #0f1419; cursor: pointer; }
|
||||||
.check-row input { cursor: pointer; }
|
.check-row input { cursor: pointer; }
|
||||||
.btn-green { background: #17bf63 !important; }
|
|
||||||
.btn-green:hover { background: #15a857 !important; }
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const fab = document.createElement('button');
|
const fab = document.createElement('button');
|
||||||
@@ -220,7 +230,6 @@
|
|||||||
<button class="tab-btn" data-tab="config">⚙️ Config <span class="badge" id="cfg-missing" style="display:none">!</span></button>
|
<button class="tab-btn" data-tab="config">⚙️ Config <span class="badge" id="cfg-missing" style="display:none">!</span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB COMMENT -->
|
|
||||||
<div class="tab-content active" id="tab-comment">
|
<div class="tab-content active" id="tab-comment">
|
||||||
<div class="tweet-box">${escapeHtml(tweetText)}</div>
|
<div class="tweet-box">${escapeHtml(tweetText)}</div>
|
||||||
|
|
||||||
@@ -240,36 +249,30 @@
|
|||||||
<div class="status" id="ai-status"></div>
|
<div class="status" id="ai-status"></div>
|
||||||
<div class="copy-hint" id="ai-hint">📋 Copy kết quả và dán vào ô reply!</div>
|
<div class="copy-hint" id="ai-hint">📋 Copy kết quả và dán vào ô reply!</div>
|
||||||
|
|
||||||
<!-- === TYPING FEATURE === -->
|
|
||||||
<div class="typing-opts" id="ai-typing-opts">
|
<div class="typing-opts" id="ai-typing-opts">
|
||||||
<label class="check-row">
|
<label class="check-row">
|
||||||
<input type="checkbox" id="ai-typing" checked>
|
<input type="checkbox" id="ai-typing" checked>
|
||||||
<span>Giả lập gõ như người thật (từ từ)</span>
|
<span>Giả lập gõ như người thật (từ từ)</span>
|
||||||
</label>
|
</label>
|
||||||
<button class="primary btn-green" id="ai-paste">📥 Dán vào ô reply</button>
|
<button class="primary green" id="ai-paste">📥 Dán vào ô reply</button>
|
||||||
<div style="font-size:12px;color:#536471;text-align:center;">
|
<div style="font-size:12px;color:#536471;text-align:center;">
|
||||||
💡 Nếu chưa mở ô reply, hãy bấm Reply trước!
|
💡 Nếu chưa mở ô reply, hãy bấm Reply trước!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TAB CONFIG -->
|
|
||||||
<div class="tab-content" id="tab-config">
|
<div class="tab-content" id="tab-config">
|
||||||
<div style="font-size:13px; color:#536471; margin-bottom:6px;">
|
<div style="font-size:13px; color:#536471; margin-bottom:6px;">
|
||||||
Nhập API của bạn. Dữ liệu được lưu trên trình duyệt này.
|
Nhập API của bạn. Dữ liệu được lưu trên trình duyệt này.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>API URL</label>
|
<label>API URL</label>
|
||||||
<input type="text" id="cfg-url" placeholder="https://api.yoursite.com/v1/generate">
|
<input type="text" id="cfg-url" placeholder="https://api.yoursite.com/v1/generate">
|
||||||
|
|
||||||
<label>API Key</label>
|
<label>API Key</label>
|
||||||
<input type="password" id="cfg-key" placeholder="sk-...">
|
<input type="password" id="cfg-key" placeholder="sk-...">
|
||||||
|
|
||||||
<div style="display:flex; gap:8px;">
|
<div style="display:flex; gap:8px;">
|
||||||
<button class="primary" id="cfg-save" style="flex:1">💾 Lưu</button>
|
<button class="primary" id="cfg-save" style="flex:1">💾 Lưu</button>
|
||||||
<button class="primary" id="cfg-test" style="flex:1;background:#17bf63;">🧪 Test đọc</button>
|
<button class="primary green" id="cfg-test" style="flex:1">🧪 Test đọc</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status" id="cfg-status"></div>
|
<div class="status" id="cfg-status"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -278,7 +281,7 @@
|
|||||||
shadow.appendChild(fab);
|
shadow.appendChild(fab);
|
||||||
shadow.appendChild(drawer);
|
shadow.appendChild(drawer);
|
||||||
|
|
||||||
// Toggle drawer
|
// Toggle
|
||||||
const toggleDrawer = () => drawer.classList.toggle('open');
|
const toggleDrawer = () => drawer.classList.toggle('open');
|
||||||
fab.addEventListener('click', toggleDrawer);
|
fab.addEventListener('click', toggleDrawer);
|
||||||
drawer.querySelector('.btn-x').addEventListener('click', () => drawer.classList.remove('open'));
|
drawer.querySelector('.btn-x').addEventListener('click', () => drawer.classList.remove('open'));
|
||||||
@@ -296,7 +299,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== COMMENT TAB LOGIC =====
|
// ===== COMMENT TAB =====
|
||||||
const langSel = drawer.querySelector('#ai-lang');
|
const langSel = drawer.querySelector('#ai-lang');
|
||||||
const toneSel = drawer.querySelector('#ai-tone');
|
const toneSel = drawer.querySelector('#ai-tone');
|
||||||
const toneHint = drawer.querySelector('#ai-tone-hint');
|
const toneHint = drawer.querySelector('#ai-tone-hint');
|
||||||
@@ -346,8 +349,10 @@
|
|||||||
const tone = toneSel.value;
|
const tone = toneSel.value;
|
||||||
const angle = angleSel.value;
|
const angle = angleSel.value;
|
||||||
runBtn.disabled = true;
|
runBtn.disabled = true;
|
||||||
statusEl.style.display = 'block'; statusEl.className = 'status ok'; statusEl.textContent = '⏳ Đang gọi API...'; copyHint.style.display = 'none';
|
statusEl.style.display = 'block'; statusEl.className = 'status ok';
|
||||||
|
statusEl.textContent = '⏳ Đang gọi API...'; copyHint.style.display = 'none';
|
||||||
typingOpts.classList.remove('visible');
|
typingOpts.classList.remove('visible');
|
||||||
|
|
||||||
chrome.runtime.sendMessage(
|
chrome.runtime.sendMessage(
|
||||||
{ action: 'GENERATE_COMMENT', data: { text: tweetText, lang, tone, angle } },
|
{ action: 'GENERATE_COMMENT', data: { text: tweetText, lang, tone, angle } },
|
||||||
() => {
|
() => {
|
||||||
@@ -360,27 +365,37 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Paste button
|
// PASTE BUTTON
|
||||||
pasteBtn.addEventListener('click', async () => {
|
pasteBtn.addEventListener('click', async () => {
|
||||||
const text = statusEl.textContent;
|
const text = statusEl.textContent;
|
||||||
if (!text || text.startsWith('⏳')) {
|
if (!text || text.startsWith('⏳') || text.startsWith('Lỗi') || text.startsWith('⚠️')) {
|
||||||
alert('Chưa có nội dung để dán. Hãy tạo comment trước!');
|
alert('Chưa có nội dung hợp lệ để dán. Hãy tạo comment trước!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typingChk.checked) {
|
if (isTyping) return;
|
||||||
pasteBtn.disabled = true;
|
isTyping = true;
|
||||||
pasteBtn.textContent = '⌨️ Đang gõ...';
|
pasteBtn.disabled = true;
|
||||||
const ok = await simulateHumanTyping(text);
|
|
||||||
|
try {
|
||||||
|
if (typingChk.checked) {
|
||||||
|
pasteBtn.textContent = '⌨️ Đang gõ...';
|
||||||
|
const ok = await simulateHumanTyping(text);
|
||||||
|
if (ok) drawer.classList.remove('open'); // thu sidebar để người dùng thấy reply
|
||||||
|
} else {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
alert('✅ Đã copy vào clipboard!\n\nBạn tự dán vào ô reply nhé.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Typing error:', err);
|
||||||
|
alert('Lỗi khi nhập: ' + err.message);
|
||||||
|
} finally {
|
||||||
|
isTyping = false;
|
||||||
pasteBtn.disabled = false;
|
pasteBtn.disabled = false;
|
||||||
pasteBtn.textContent = '📥 Dán vào ô reply';
|
pasteBtn.textContent = '📥 Dán vào ô reply';
|
||||||
if (ok) drawer.classList.remove('open'); // thu sidebar để người dùng thấy ô reply
|
|
||||||
} else {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
alert('✅ Đã copy vào clipboard! Bạn tự dán nhé.');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== CONFIG TAB (giữ nguyên) =====
|
// ===== CONFIG TAB =====
|
||||||
const urlIn = drawer.querySelector('#cfg-url');
|
const urlIn = drawer.querySelector('#cfg-url');
|
||||||
const keyIn = drawer.querySelector('#cfg-key');
|
const keyIn = drawer.querySelector('#cfg-key');
|
||||||
const saveBtn = drawer.querySelector('#cfg-save');
|
const saveBtn = drawer.querySelector('#cfg-save');
|
||||||
@@ -414,7 +429,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== RESULT / ERROR =====
|
// ===== SHOW RESULT / ERROR =====
|
||||||
function showResult(comment) {
|
function showResult(comment) {
|
||||||
if (!sidebarHost) return;
|
if (!sidebarHost) return;
|
||||||
const s = sidebarHost.shadowRoot;
|
const s = sidebarHost.shadowRoot;
|
||||||
@@ -428,7 +443,6 @@
|
|||||||
if (copyHint) copyHint.style.display = 'block';
|
if (copyHint) copyHint.style.display = 'block';
|
||||||
if (runBtn) runBtn.disabled = false;
|
if (runBtn) runBtn.disabled = false;
|
||||||
if (drawer) drawer.classList.add('open');
|
if (drawer) drawer.classList.add('open');
|
||||||
// Hiện nút paste + typing options
|
|
||||||
if (typingOpts) typingOpts.classList.add('visible');
|
if (typingOpts) typingOpts.classList.add('visible');
|
||||||
}
|
}
|
||||||
function showError(msg) {
|
function showError(msg) {
|
||||||
|
|||||||
Reference in New Issue
Block a user