diff --git a/chrom-ext/content.bk b/chrom-ext/content.bk
new file mode 100644
index 0000000..5af031c
--- /dev/null
+++ b/chrom-ext/content.bk
@@ -0,0 +1,353 @@
+(() => {
+ 'use strict';
+
+ let lastTweetText = '';
+ let sidebarHost = null;
+
+ // ===== DATA =====
+ const LANGS = [
+ { value: 'ja', label: 'Nhật' },
+ { value: 'vi', label: 'Việt' },
+ { value: 'en', label: 'English' },
+ { value: 'ko', label: 'Hàn' },
+ { value: 'cn', label: 'Trung' }
+ ];
+
+ 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' },
+ { value: 'PROVOCATIVE', text: 'provocative — Gợi mở suy nghĩ, thách thức giả định' },
+ { value: 'AUTHORITATIVE', text: 'authoritative — Tự tin, uy quyền' },
+ { value: 'SPICY', text: 'spicy — Tự tin, hơi đối đầu, chỉ thẳng' }
+ ];
+
+ 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' },
+ { value: 'SAVAGE', text: 'savage — Chửi tục OK, sass tối đa' }
+ ];
+
+ 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' },
+ { value: 'QUESTION', text: 'Đặt câu hỏi tiếp theo thông minh' },
+ { value: 'RELATE', text: 'Chia sẻ trải nghiệm cá nhân tương tự' },
+ { value: 'DEVIL_ADVOCATE', text: 'Phản biện công bằng, không thù địch' },
+ { value: 'EXPAND', text: 'Phân tích sâu 1 điểm' },
+ { value: 'VALIDATE', text: 'Khẳng định bằng chứng mạnh mẽ' },
+ { value: 'CTA', text: '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) {
+ return tone === 'EMPATHETIC' ? [...ANGLE_EMPATHY] : [...ANGLE_DEFAULT];
+ }
+
+ // ===== DOM =====
+ 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);
+ });
+
+ 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; }
+ });
+
+ // ===== 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; }
+ .tabs { display: flex; border-bottom: 1px solid #eff3f4; }
+ .tab-btn { flex: 1; padding: 10px; border: none; background: none; cursor: pointer; font-size: 14px; color: #536471;
+ border-bottom: 2px solid transparent; font-weight: 600; }
+ .tab-btn.active { color: #1d9bf0; border-bottom-color: #1d9bf0; }
+ .tab-content { padding: 16px 18px; flex: 1; overflow-y: auto; display: none; flex-direction: column; gap: 10px; }
+ .tab-content.active { display: flex; }
+ .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, input[type="text"], input[type="password"] { width: 100%; padding: 9px 10px; border-radius: 8px; border: 1px solid #cfd9de;
+ font-size: 14px; background: #fff; box-sizing: border-box; }
+ .hint-text { font-size: 12px; color: #888; line-height: 1.4; 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; }
+ .status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; }
+ .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; }
+ `;
+
+ const fab = document.createElement('button');
+ fab.className = 'fab'; fab.textContent = '🤖'; fab.title = 'AI Comment';
+
+ const drawer = document.createElement('div');
+ drawer.className = 'drawer';
+ drawer.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+ Nhập API của bạn. Dữ liệu được lưu trên trình duyệt này.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ shadow.appendChild(style);
+ shadow.appendChild(fab);
+ shadow.appendChild(drawer);
+
+ // Toggle 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'));
+
+ // Tab switching
+ const tabBtns = drawer.querySelectorAll('.tab-btn');
+ const tabContents = drawer.querySelectorAll('.tab-content');
+ tabBtns.forEach(btn => {
+ btn.addEventListener('click', () => {
+ tabBtns.forEach(b => b.classList.remove('active'));
+ tabContents.forEach(c => c.classList.remove('active'));
+ btn.classList.add('active');
+ drawer.querySelector(`#tab-${btn.dataset.tab}`).classList.add('active');
+ });
+ });
+
+ // ===== COMMENT TAB LOGIC =====
+ 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 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 : '';
+ }
+
+ langSel.addEventListener('change', () => updateTone(langSel.value, true));
+ toneSel.addEventListener('change', onToneChange);
+ angleSel.addEventListener('change', onAngleChange);
+ updateTone(langSel.value, false);
+
+ 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: ' + chrome.runtime.lastError.message;
+ runBtn.disabled = false;
+ }
+ }
+ );
+ });
+
+ // ===== CONFIG TAB LOGIC =====
+ const urlIn = drawer.querySelector('#cfg-url');
+ const keyIn = drawer.querySelector('#cfg-key');
+ const saveBtn = drawer.querySelector('#cfg-save');
+ const testBtn = drawer.querySelector('#cfg-test');
+ const cfgStatus= drawer.querySelector('#cfg-status');
+ const cfgBadge = drawer.querySelector('#cfg-missing');
+
+ function showCfgStatus(msg, isErr) {
+ cfgStatus.style.display = 'block';
+ cfgStatus.className = 'status ' + (isErr ? 'err' : 'ok');
+ cfgStatus.textContent = msg;
+ }
+
+ // Đọc config hiện tại
+ chrome.storage.local.get(['apiUrl', 'apiKey'], (cfg) => {
+ if (cfg.apiUrl) urlIn.value = cfg.apiUrl;
+ if (cfg.apiKey) keyIn.value = cfg.apiKey;
+ cfgBadge.style.display = (!cfg.apiUrl || !cfg.apiKey) ? 'inline-block' : 'none';
+ });
+
+ saveBtn.addEventListener('click', () => {
+ const url = urlIn.value.trim();
+ const key = keyIn.value.trim();
+ if (!url || !key) {
+ showCfgStatus('❌ URL và Key không được để trống!', true);
+ return;
+ }
+ try { new URL(url); } catch {
+ showCfgStatus('❌ URL không hợp lệ (cần có https://)', true);
+ return;
+ }
+ chrome.storage.local.set({ apiUrl: url, apiKey: key }, () => {
+ showCfgStatus('✅ Đã lưu! Bạn có thể đóng tab này và bấm Tạo Comment.', false);
+ cfgBadge.style.display = 'none';
+ });
+ });
+
+ testBtn.addEventListener('click', () => {
+ chrome.storage.local.get(['apiUrl', 'apiKey'], (cfg) => {
+ showCfgStatus(`URL: ${cfg.apiUrl || '(trống)'}\nKey: ${cfg.apiKey ? cfg.apiKey.slice(0,8)+'...' : '(trống)'}`, !cfg.apiUrl || !cfg.apiKey);
+ });
+ });
+ }
+
+ // ===== 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 = 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;
+ }
+})();
\ No newline at end of file
diff --git a/chrom-ext/content.js b/chrom-ext/content.js
index 5af031c..905ab5e 100644
--- a/chrom-ext/content.js
+++ b/chrom-ext/content.js
@@ -4,7 +4,7 @@
let lastTweetText = '';
let sidebarHost = null;
- // ===== DATA =====
+ // ===== DATA (giữ nguyên) =====
const LANGS = [
{ value: 'ja', label: 'Nhật' },
{ value: 'vi', label: 'Việt' },
@@ -62,7 +62,7 @@
return tone === 'EMPATHETIC' ? [...ANGLE_EMPATHY] : [...ANGLE_DEFAULT];
}
- // ===== DOM =====
+ // ===== A. BẮT CHUỘT PHẢI =====
document.addEventListener('contextmenu', (e) => {
const article = e.target.closest('article');
if (!article) { lastTweetText = ''; return; }
@@ -88,6 +88,68 @@
if (req.action === 'SHOW_ERROR') { showError(req.error); return true; }
});
+ // ===== HUMAN TYPING SIMULATION =====
+ async function simulateHumanTyping(text) {
+ // X dùng contenteditable div. Tìm ô reply đang mở.
+ let el =
+ document.querySelector('[data-testid="tweetTextarea_0"]') ||
+ document.querySelector('div[contenteditable="true"][role="textbox"]') ||
+ document.querySelector('div[contenteditable="true"][data-text="true"]');
+
+ if (!el) {
+ 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;
+ }
+
+ el.focus();
+ el.click();
+
+ // Di chuyển cursor về cuối nếu cần (optional)
+ const sel = window.getSelection();
+ sel.selectAllChildren(el);
+ sel.collapseToEnd();
+
+ const baseDelay = 35; // ms
+
+ for (let i = 0; i < text.length; i++) {
+ const char = text[i];
+
+ // Cách đáng tin cậy nhất trên X: execCommand insertText
+ const inserted = document.execCommand('insertText', false, char);
+
+ // Fallback nếu execCommand bị khước từ
+ if (!inserted) {
+ 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,
+ cancelable: true,
+ inputType: 'insertText',
+ data: char
+ }));
+
+ // Delay ngẫu nhiên
+ let delay = baseDelay + Math.random() * 70;
+ if (char === ' ') delay += 30 + Math.random() * 50;
+ if ('.!?,'.includes(char)) delay += 120 + Math.random() * 250;
+
+ await new Promise(r => setTimeout(r, delay));
+ }
+
+ // Final change event cho chắc
+ el.dispatchEvent(new Event('change', { bubbles: true }));
+ return true;
+ }
+
// ===== SIDEBAR =====
function openSidebar(tweetText) {
removeSidebar();
@@ -109,7 +171,7 @@
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;
+ .drawer { position: fixed; right: 0; top: 0; width: 442px; 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; }
@@ -138,6 +200,12 @@
.status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; }
.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; }
+ .typing-opts { margin-top: 8px; padding-top: 10px; border-top: 1px solid #eff3f4; display: none; }
+ .typing-opts.visible { display: flex; flex-direction: column; gap: 8px; }
+ .check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #0f1419; cursor: pointer; }
+ .check-row input { cursor: pointer; }
+ .btn-green { background: #17bf63 !important; }
+ .btn-green:hover { background: #15a857 !important; }
`;
const fab = document.createElement('button');
@@ -171,6 +239,18 @@
📋 Copy kết quả và dán vào ô reply!
+
+
+
+
+
+
+ 💡 Nếu chưa mở ô reply, hãy bấm Reply trước!
+
+
@@ -204,7 +284,7 @@
drawer.querySelector('.btn-x').addEventListener('click', () => drawer.classList.remove('open'));
requestAnimationFrame(() => drawer.classList.add('open'));
- // Tab switching
+ // Tabs
const tabBtns = drawer.querySelectorAll('.tab-btn');
const tabContents = drawer.querySelectorAll('.tab-content');
tabBtns.forEach(btn => {
@@ -225,6 +305,9 @@
const runBtn = drawer.querySelector('#ai-run');
const statusEl = drawer.querySelector('#ai-status');
const copyHint = drawer.querySelector('#ai-hint');
+ const typingOpts = drawer.querySelector('#ai-typing-opts');
+ const typingChk = drawer.querySelector('#ai-typing');
+ const pasteBtn = drawer.querySelector('#ai-paste');
function populateSelect(sel, items, selectedValue) {
sel.innerHTML = items.map(it => ``).join('');
@@ -264,6 +347,7 @@
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';
+ typingOpts.classList.remove('visible');
chrome.runtime.sendMessage(
{ action: 'GENERATE_COMMENT', data: { text: tweetText, lang, tone, angle } },
() => {
@@ -276,7 +360,27 @@
);
});
- // ===== CONFIG TAB LOGIC =====
+ // Paste button
+ pasteBtn.addEventListener('click', async () => {
+ const text = statusEl.textContent;
+ if (!text || text.startsWith('⏳')) {
+ alert('Chưa có nội dung để dán. Hãy tạo comment trước!');
+ return;
+ }
+ if (typingChk.checked) {
+ pasteBtn.disabled = true;
+ pasteBtn.textContent = '⌨️ Đang gõ...';
+ const ok = await simulateHumanTyping(text);
+ pasteBtn.disabled = false;
+ 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) =====
const urlIn = drawer.querySelector('#cfg-url');
const keyIn = drawer.querySelector('#cfg-key');
const saveBtn = drawer.querySelector('#cfg-save');
@@ -289,31 +393,20 @@
cfgStatus.className = 'status ' + (isErr ? 'err' : 'ok');
cfgStatus.textContent = msg;
}
-
- // Đọc config hiện tại
chrome.storage.local.get(['apiUrl', 'apiKey'], (cfg) => {
if (cfg.apiUrl) urlIn.value = cfg.apiUrl;
if (cfg.apiKey) keyIn.value = cfg.apiKey;
cfgBadge.style.display = (!cfg.apiUrl || !cfg.apiKey) ? 'inline-block' : 'none';
});
-
saveBtn.addEventListener('click', () => {
- const url = urlIn.value.trim();
- const key = keyIn.value.trim();
- if (!url || !key) {
- showCfgStatus('❌ URL và Key không được để trống!', true);
- return;
- }
- try { new URL(url); } catch {
- showCfgStatus('❌ URL không hợp lệ (cần có https://)', true);
- return;
- }
+ const url = urlIn.value.trim(), key = keyIn.value.trim();
+ if (!url || !key) { showCfgStatus('❌ URL và Key không được để trống!', true); return; }
+ try { new URL(url); } catch { showCfgStatus('❌ URL không hợp lệ', true); return; }
chrome.storage.local.set({ apiUrl: url, apiKey: key }, () => {
- showCfgStatus('✅ Đã lưu! Bạn có thể đóng tab này và bấm Tạo Comment.', false);
+ showCfgStatus('✅ Đã lưu!', false);
cfgBadge.style.display = 'none';
});
});
-
testBtn.addEventListener('click', () => {
chrome.storage.local.get(['apiUrl', 'apiKey'], (cfg) => {
showCfgStatus(`URL: ${cfg.apiUrl || '(trống)'}\nKey: ${cfg.apiKey ? cfg.apiKey.slice(0,8)+'...' : '(trống)'}`, !cfg.apiUrl || !cfg.apiKey);
@@ -329,10 +422,14 @@
const copyHint = s.querySelector('#ai-hint');
const runBtn = s.querySelector('#ai-run');
const drawer = s.querySelector('.drawer');
+ const typingOpts = s.querySelector('#ai-typing-opts');
+
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');
+ // Hiện nút paste + typing options
+ if (typingOpts) typingOpts.classList.add('visible');
}
function showError(msg) {
if (!sidebarHost) return;