1334 lines
53 KiB
JavaScript
1334 lines
53 KiB
JavaScript
// 默认自定义模型配置(OpenAI 兼容接口)
|
||
const DEFAULT_AI = {
|
||
apiBaseUrl: 'https://ai.88531.cn/v1',
|
||
model: 'mimo-v2.6-flash'
|
||
};
|
||
|
||
// 弹窗界面功能
|
||
class AutoCommentManager {
|
||
constructor() {
|
||
this.init();
|
||
}
|
||
|
||
async init() {
|
||
await this.loadClickMode(); // 加载点击模式设置
|
||
await this.loadDelayConfig(); // 加载评论间隔设置
|
||
await this.loadRegion(); // 加载按地区搜索设置
|
||
await this.loadApiKey(); // 加载API密钥
|
||
await this.loadModelConfig(); // 加载自定义模型配置
|
||
await this.loadCommentMode(); // 加载评论方式(固定评论池 / 自定义文字 / 图片 / AI)
|
||
await this.loadAiPrompt(); // 加载AI提示词
|
||
await this.loadUserComments(); // 加载自定义文字库
|
||
await this.loadImagePreviews(); // 加载已上传的评论图片
|
||
this.bindEvents();
|
||
this.updateAiStatus();
|
||
this.loadCommentLogs(); // 加载评论记录
|
||
this.initAuth(); // 授权状态 / 公告 / 更新提示
|
||
|
||
// 小红书页面记录新评论后自动刷新列表
|
||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||
if (namespace === 'local' && changes.commentLogs) {
|
||
this.renderCommentLogs(changes.commentLogs.newValue || []);
|
||
}
|
||
});
|
||
}
|
||
|
||
bindEvents() {
|
||
// 搜索功能
|
||
document.getElementById('search-btn').addEventListener('click', () => {
|
||
this.searchXiaohongshu();
|
||
});
|
||
|
||
document.getElementById('keyword-input').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') {
|
||
this.searchXiaohongshu();
|
||
}
|
||
});
|
||
|
||
// 按地区搜索
|
||
const regionSelect = document.getElementById('region-select');
|
||
if (regionSelect) {
|
||
regionSelect.addEventListener('change', (e) => {
|
||
this.onRegionChange(e.target.value);
|
||
});
|
||
}
|
||
|
||
const regionCustom = document.getElementById('region-custom');
|
||
if (regionCustom) {
|
||
regionCustom.addEventListener('input', (e) => {
|
||
this.saveRegion({ regionCustom: e.target.value });
|
||
});
|
||
regionCustom.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') {
|
||
this.searchXiaohongshu();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 自动点击功能
|
||
document.getElementById('auto-click-btn').addEventListener('click', () => {
|
||
this.triggerAutoClick();
|
||
});
|
||
|
||
// 测试元素检测功能
|
||
document.getElementById('test-detection-btn').addEventListener('click', () => {
|
||
this.testElementDetection();
|
||
});
|
||
|
||
// 点击模式选择器
|
||
document.getElementById('expand-content').addEventListener('change', (e) => {
|
||
this.saveClickMode(e.target.checked);
|
||
});
|
||
|
||
// 评论间隔:输入即保存
|
||
['delay-min', 'delay-max'].forEach((id) => {
|
||
const input = document.getElementById(id);
|
||
if (input) {
|
||
input.addEventListener('input', () => {
|
||
this.saveDelayConfig();
|
||
});
|
||
}
|
||
});
|
||
|
||
// 评论方式选择(固定评论池 / 自定义文字 / 图片 / AI 按笔记生成)
|
||
document.querySelectorAll('input[name="comment-mode"]').forEach((radio) => {
|
||
radio.addEventListener('change', (e) => {
|
||
this.saveCommentMode(e.target.value);
|
||
});
|
||
});
|
||
|
||
// 自定义文字库:输入即保存
|
||
const userCommentsArea = document.getElementById('user-comments');
|
||
if (userCommentsArea) {
|
||
userCommentsArea.addEventListener('input', () => {
|
||
this.saveUserComments();
|
||
});
|
||
}
|
||
|
||
const saveUserCommentsBtn = document.getElementById('save-user-comments-btn');
|
||
if (saveUserCommentsBtn) {
|
||
saveUserCommentsBtn.addEventListener('click', () => {
|
||
this.saveUserComments(true);
|
||
});
|
||
}
|
||
|
||
// 图片回复:选择图片即压缩保存
|
||
const imageInput = document.getElementById('comment-image-input');
|
||
if (imageInput) {
|
||
imageInput.addEventListener('change', (e) => {
|
||
this.handleImageFiles(e.target.files);
|
||
imageInput.value = ''; // 允许重复选择同一文件
|
||
});
|
||
}
|
||
|
||
// 评论记录:手动刷新
|
||
const refreshLogBtn = document.getElementById('refresh-log-btn');
|
||
if (refreshLogBtn) {
|
||
refreshLogBtn.addEventListener('click', () => {
|
||
this.loadCommentLogs();
|
||
});
|
||
}
|
||
|
||
// 授权:卡密激活
|
||
const activateBtn = document.getElementById('activate-btn');
|
||
if (activateBtn) {
|
||
activateBtn.addEventListener('click', () => {
|
||
this.activateCard();
|
||
});
|
||
}
|
||
const cardInput = document.getElementById('card-key-input');
|
||
if (cardInput) {
|
||
cardInput.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') this.activateCard();
|
||
});
|
||
}
|
||
|
||
// 授权:购买(展开支付方式选择)
|
||
const buyBtn = document.getElementById('buy-btn');
|
||
if (buyBtn) {
|
||
buyBtn.addEventListener('click', () => {
|
||
const row = document.getElementById('pay-select-row');
|
||
if (row) row.hidden = !row.hidden;
|
||
});
|
||
}
|
||
const payCancelBtn = document.getElementById('pay-cancel-btn');
|
||
if (payCancelBtn) {
|
||
payCancelBtn.addEventListener('click', () => {
|
||
const row = document.getElementById('pay-select-row');
|
||
if (row) row.hidden = true;
|
||
});
|
||
}
|
||
document.querySelectorAll('.pay-btn[data-pay]').forEach((btn) => {
|
||
btn.addEventListener('click', () => {
|
||
this.startBuyFlow(btn.dataset.pay);
|
||
});
|
||
});
|
||
|
||
// API密钥配置
|
||
document.getElementById('api-key').addEventListener('input', (e) => {
|
||
this.saveApiKey(e.target.value);
|
||
this.updateAiStatus();
|
||
});
|
||
|
||
// 自定义模型:API地址与模型名,输入即保存
|
||
const baseUrlInput = document.getElementById('api-base-url');
|
||
if (baseUrlInput) {
|
||
baseUrlInput.addEventListener('input', (e) => {
|
||
this.saveModelConfig({ apiBaseUrl: e.target.value });
|
||
});
|
||
baseUrlInput.addEventListener('change', () => this.updateAiStatus());
|
||
}
|
||
|
||
const modelInput = document.getElementById('model-name');
|
||
if (modelInput) {
|
||
modelInput.addEventListener('input', (e) => {
|
||
this.saveModelConfig({ modelName: e.target.value });
|
||
});
|
||
modelInput.addEventListener('change', () => this.updateAiStatus());
|
||
}
|
||
|
||
// 测试AI按钮
|
||
document.getElementById('test-ai-btn').addEventListener('click', () => {
|
||
this.testAiGeneration();
|
||
});
|
||
|
||
// AI提示词保存
|
||
const savePromptBtn = document.getElementById('save-prompt-btn');
|
||
if (savePromptBtn) {
|
||
savePromptBtn.addEventListener('click', () => {
|
||
this.saveAiPrompt();
|
||
});
|
||
}
|
||
|
||
// AI提示词恢复默认
|
||
const resetPromptBtn = document.getElementById('reset-prompt-btn');
|
||
if (resetPromptBtn) {
|
||
resetPromptBtn.addEventListener('click', () => {
|
||
this.resetAiPromptToDefault();
|
||
});
|
||
}
|
||
}
|
||
|
||
async searchXiaohongshu() {
|
||
if (!(await this.isFeatureAllowed())) return;
|
||
|
||
const keyword = document.getElementById('keyword-input').value.trim();
|
||
|
||
if (!keyword) {
|
||
alert('请输入搜索关键词');
|
||
return;
|
||
}
|
||
|
||
// 按地区搜索:地区拼进关键词(如「咖啡 上海」)
|
||
const region = this.getSearchRegion();
|
||
const finalKeyword = region ? `${keyword} ${region}` : keyword;
|
||
|
||
// 构建小红书搜索URL
|
||
const encodedKeyword = encodeURIComponent(finalKeyword);
|
||
const searchUrl = `https://www.xiaohongshu.com/search_result?keyword=${encodedKeyword}`;
|
||
|
||
console.log('准备打开搜索页面:', searchUrl);
|
||
|
||
// 在新标签页打开搜索结果
|
||
chrome.tabs.create({ url: searchUrl }, (tab) => {
|
||
console.log('新标签页已创建:', tab.id);
|
||
|
||
if (chrome.runtime.lastError) {
|
||
console.error('创建标签页失败:', chrome.runtime.lastError);
|
||
return;
|
||
}
|
||
|
||
// 等待页面加载完成后发送自动点击消息
|
||
chrome.tabs.onUpdated.addListener(function listener(tabId, changeInfo) {
|
||
console.log('标签页更新:', tabId, changeInfo);
|
||
|
||
if (tabId === tab.id && changeInfo.status === 'complete') {
|
||
console.log('页面加载完成,准备发送自动点击消息');
|
||
|
||
// 页面加载完成,延迟一下确保DOM完全渲染
|
||
setTimeout(() => {
|
||
console.log('发送自动点击消息到标签页:', tab.id);
|
||
|
||
chrome.tabs.sendMessage(tab.id, {
|
||
action: 'autoClickNotes',
|
||
keyword: finalKeyword
|
||
}, (response) => {
|
||
if (chrome.runtime.lastError) {
|
||
console.error('发送消息失败:', chrome.runtime.lastError);
|
||
} else {
|
||
console.log('消息发送成功,响应:', response);
|
||
}
|
||
});
|
||
}, 3000); // 增加延迟时间
|
||
|
||
// 移除监听器
|
||
chrome.tabs.onUpdated.removeListener(listener);
|
||
}
|
||
});
|
||
});
|
||
|
||
// 清空输入框
|
||
document.getElementById('keyword-input').value = '';
|
||
|
||
// 侧边栏常驻不关闭,改为在面板内显示状态
|
||
this.showActionStatus(true, region
|
||
? `✓ 已按地区「${region}」打开搜索页,稍后自动开始评论流程`
|
||
: '✓ 已打开搜索页,稍后自动开始评论流程');
|
||
}
|
||
|
||
// 获取当前选择的地区(空字符串 = 不限)
|
||
getSearchRegion() {
|
||
const select = document.getElementById('region-select');
|
||
if (!select) return '';
|
||
|
||
if (select.value === '__custom__') {
|
||
return (document.getElementById('region-custom')?.value || '').trim();
|
||
}
|
||
return select.value;
|
||
}
|
||
|
||
// 地区下拉切换(含自定义输入框显隐)
|
||
onRegionChange(value) {
|
||
const customInput = document.getElementById('region-custom');
|
||
const isCustom = value === '__custom__';
|
||
if (customInput) {
|
||
customInput.hidden = !isCustom;
|
||
if (isCustom) customInput.focus();
|
||
}
|
||
this.saveRegion({ regionSelectVal: value });
|
||
}
|
||
|
||
// 保存地区设置
|
||
async saveRegion(changes) {
|
||
try {
|
||
await chrome.storage.local.set(changes);
|
||
console.log('保存地区设置:', Object.keys(changes).join(', '));
|
||
} catch (error) {
|
||
console.error('保存地区设置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载地区设置
|
||
async loadRegion() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['regionSelectVal', 'regionCustom']);
|
||
const select = document.getElementById('region-select');
|
||
const customInput = document.getElementById('region-custom');
|
||
if (!select) return;
|
||
|
||
const saved = result.regionSelectVal || '';
|
||
const hasOption = Array.from(select.options).some((o) => o.value === saved);
|
||
const value = hasOption ? saved : '';
|
||
select.value = value;
|
||
|
||
const isCustom = value === '__custom__';
|
||
if (customInput) {
|
||
customInput.value = result.regionCustom || '';
|
||
customInput.hidden = !isCustom;
|
||
}
|
||
console.log('加载地区设置:', value, result.regionCustom || '');
|
||
} catch (error) {
|
||
console.error('加载地区设置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 面板内联状态提示(替代弹窗关闭/alert)
|
||
showActionStatus(ok, text) {
|
||
const el = document.getElementById('action-status');
|
||
if (!el) return;
|
||
el.textContent = text;
|
||
el.className = 'action-status ' + (ok ? 'ok' : 'fail');
|
||
}
|
||
|
||
// 手动触发自动点击功能
|
||
async triggerAutoClick() {
|
||
try {
|
||
if (!(await this.isFeatureAllowed())) return;
|
||
|
||
// 获取当前活动标签页
|
||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||
|
||
if (!tab) {
|
||
alert('无法获取当前标签页');
|
||
return;
|
||
}
|
||
|
||
// 检查是否在小红书页面
|
||
if (!tab.url || !tab.url.includes('xiaohongshu.com')) {
|
||
alert('请在小红书页面上使用此功能');
|
||
return;
|
||
}
|
||
|
||
// 发送自动点击消息
|
||
await chrome.tabs.sendMessage(tab.id, {
|
||
action: 'autoClickNotes'
|
||
});
|
||
|
||
this.showActionStatus(true, '✓ 已开始自动点击笔记');
|
||
|
||
} catch (error) {
|
||
console.error('触发自动点击失败:', error);
|
||
this.showActionStatus(false, '✗ 触发失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 测试元素检测功能
|
||
async testElementDetection() {
|
||
try {
|
||
// 获取当前活动标签页
|
||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||
|
||
if (!tab) {
|
||
alert('无法获取当前标签页');
|
||
return;
|
||
}
|
||
|
||
// 检查是否在小红书页面
|
||
if (!tab.url || !tab.url.includes('xiaohongshu.com')) {
|
||
alert('请在小红书页面上使用此功能');
|
||
return;
|
||
}
|
||
|
||
console.log('发送测试元素检测消息到标签页:', tab.id);
|
||
|
||
// 发送测试消息
|
||
const response = await chrome.tabs.sendMessage(tab.id, {
|
||
action: 'testElementDetection'
|
||
});
|
||
|
||
if (response && response.success) {
|
||
let message = `元素检测结果:\n\n主要选择器: ${response.primarySelector}\n主要选择器找到: ${response.primaryElementsCount} 个元素\n\n总元素: ${response.totalElements}\n可见元素: ${response.visibleElements}`;
|
||
|
||
// 添加第一个元素的详细分析
|
||
if (response.firstElementAnalysis) {
|
||
const analysis = response.firstElementAnalysis;
|
||
message += `\n\n第一个元素分析:\n- 标签: ${analysis.tagName}.${analysis.className}`;
|
||
message += `\n- 笔记索引: ${analysis.noteIndex || '未知'}`;
|
||
message += `\n- 选择器: ${analysis.selector || '未知'}`;
|
||
|
||
// 显示目标链接检测结果
|
||
if (analysis.targetLink) {
|
||
if (analysis.targetLink.found) {
|
||
message += `\n- 目标链接: ✅ 已找到`;
|
||
message += `\n - 链接: ${analysis.targetLink.href}`;
|
||
message += `\n - 文本: ${analysis.targetLink.text}`;
|
||
message += `\n - 类名: ${analysis.targetLink.className}`;
|
||
} else {
|
||
message += `\n- 目标链接: ❌ ${analysis.targetLink.message}`;
|
||
}
|
||
}
|
||
|
||
if (analysis.links && analysis.links.length > 0) {
|
||
message += `\n- 所有链接 (${analysis.links.length}个):`;
|
||
analysis.links.forEach((link, index) => {
|
||
message += `\n ${index + 1}. ${link.text} (${link.href})`;
|
||
});
|
||
}
|
||
|
||
if (analysis.buttons && analysis.buttons.length > 0) {
|
||
message += `\n- 按钮 (${analysis.buttons.length}个):`;
|
||
analysis.buttons.forEach((btn, index) => {
|
||
message += `\n ${index + 1}. ${btn.text} (${btn.className})`;
|
||
});
|
||
}
|
||
|
||
if (analysis.clickableDivs && analysis.clickableDivs.length > 0) {
|
||
message += `\n- 可点击div (${analysis.clickableDivs.length}个):`;
|
||
analysis.clickableDivs.forEach((div, index) => {
|
||
message += `\n ${index + 1}. ${div.text} (${div.className})`;
|
||
});
|
||
}
|
||
}
|
||
|
||
message += `\n\n前5个元素详情:\n${response.elementDetails.map(el =>
|
||
`- ${el.tagName}.${el.className} (链接:${el.hasLink}, 图片:${el.hasImage}, 文本长度:${el.textLength})`
|
||
).join('\n')}`;
|
||
|
||
alert(message);
|
||
} else {
|
||
alert('测试失败,请检查控制台');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('测试元素检测失败:', error);
|
||
alert('测试失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 保存用户选择的点击模式
|
||
async saveClickMode(shouldExpand) {
|
||
try {
|
||
await chrome.storage.local.set({ expandContent: shouldExpand });
|
||
console.log('保存点击模式:', shouldExpand);
|
||
} catch (error) {
|
||
console.error('保存点击模式失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载用户选择的点击模式
|
||
async loadClickMode() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['expandContent']);
|
||
const shouldExpand = result.expandContent !== false; // 默认展开内容
|
||
document.getElementById('expand-content').checked = shouldExpand;
|
||
console.log('加载点击模式:', shouldExpand);
|
||
} catch (error) {
|
||
console.error('加载点击模式失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载评论间隔(分钟,默认 1~2 分钟随机)
|
||
async loadDelayConfig() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['commentDelayMin', 'commentDelayMax']);
|
||
const min = Number(result.commentDelayMin);
|
||
const max = Number(result.commentDelayMax);
|
||
const minInput = document.getElementById('delay-min');
|
||
const maxInput = document.getElementById('delay-max');
|
||
if (minInput) minInput.value = Number.isFinite(min) && min >= 0 ? min : 1;
|
||
if (maxInput) maxInput.value = Number.isFinite(max) && max >= 0 ? max : 2;
|
||
console.log('加载评论间隔:', minInput?.value, '~', maxInput?.value, '分钟');
|
||
} catch (error) {
|
||
console.error('加载评论间隔失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存评论间隔
|
||
async saveDelayConfig() {
|
||
try {
|
||
const minInput = document.getElementById('delay-min');
|
||
const maxInput = document.getElementById('delay-max');
|
||
let min = Number(minInput?.value);
|
||
let max = Number(maxInput?.value);
|
||
if (!Number.isFinite(min) || min < 0) min = 1;
|
||
if (!Number.isFinite(max) || max < 0) max = 2;
|
||
if (max < min) {
|
||
// 自动纠正顺序
|
||
[min, max] = [max, min];
|
||
if (minInput) minInput.value = min;
|
||
if (maxInput) maxInput.value = max;
|
||
}
|
||
await chrome.storage.local.set({ commentDelayMin: min, commentDelayMax: max });
|
||
console.log('保存评论间隔:', min, '~', max, '分钟');
|
||
} catch (error) {
|
||
console.error('保存评论间隔失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存API密钥
|
||
async saveApiKey(apiKey) {
|
||
try {
|
||
await chrome.storage.local.set({ apiKey: apiKey });
|
||
console.log('保存API密钥:', apiKey);
|
||
} catch (error) {
|
||
console.error('保存API密钥失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载API密钥
|
||
async loadApiKey() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['apiKey']);
|
||
const apiKey = result.apiKey || ''; // 默认API密钥
|
||
document.getElementById('api-key').value = apiKey;
|
||
console.log('加载API密钥:', apiKey);
|
||
} catch (error) {
|
||
console.error('加载API密钥失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载评论方式(custom / text / image / ai)
|
||
async loadCommentMode() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['commentMode']);
|
||
const validModes = ['custom', 'text', 'image', 'ai'];
|
||
const mode = validModes.includes(result.commentMode) ? result.commentMode : 'custom';
|
||
const radio = document.getElementById(`mode-${mode}`);
|
||
if (radio) radio.checked = true;
|
||
this.updateModePanels(mode);
|
||
await this.updateModeHint(mode);
|
||
} catch (error) {
|
||
console.error('加载评论方式失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存评论方式(写入后已打开的小红书页面会立即切换)
|
||
async saveCommentMode(mode) {
|
||
try {
|
||
await chrome.storage.local.set({ commentMode: mode });
|
||
console.log('保存评论方式:', mode);
|
||
this.updateModePanels(mode);
|
||
await this.updateModeHint(mode);
|
||
} catch (error) {
|
||
console.error('保存评论方式失败:', error);
|
||
}
|
||
}
|
||
|
||
// 根据评论方式显示/隐藏对应的配置面板
|
||
updateModePanels(mode) {
|
||
const textPanel = document.getElementById('text-comment-panel');
|
||
const imagePanel = document.getElementById('image-comment-panel');
|
||
if (textPanel) textPanel.style.display = mode === 'text' ? 'block' : 'none';
|
||
if (imagePanel) imagePanel.style.display = mode === 'image' ? 'block' : 'none';
|
||
}
|
||
|
||
// 加载自定义文字库
|
||
async loadUserComments() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['userComments']);
|
||
const lines = result.userComments || [];
|
||
const textarea = document.getElementById('user-comments');
|
||
if (textarea) textarea.value = lines.join('\n');
|
||
this.updateUserCommentsCount(lines.length);
|
||
console.log('加载自定义文字库:', lines.length, '条');
|
||
} catch (error) {
|
||
console.error('加载自定义文字库失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存自定义文字库(一行一条)
|
||
async saveUserComments(showFeedback = false) {
|
||
try {
|
||
const textarea = document.getElementById('user-comments');
|
||
const lines = (textarea?.value || '')
|
||
.split('\n')
|
||
.map((line) => line.trim())
|
||
.filter((line) => line.length > 0);
|
||
await chrome.storage.local.set({ userComments: lines });
|
||
this.updateUserCommentsCount(lines.length);
|
||
console.log('保存自定义文字库:', lines.length, '条');
|
||
if (showFeedback) {
|
||
this.showActionStatus(true, `✓ 文字库已保存,共 ${lines.length} 条`);
|
||
}
|
||
} catch (error) {
|
||
console.error('保存自定义文字库失败:', error);
|
||
if (showFeedback) {
|
||
this.showActionStatus(false, '✗ 文字库保存失败: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新文字库条数显示
|
||
updateUserCommentsCount(count) {
|
||
const el = document.getElementById('user-comments-count');
|
||
if (el) el.textContent = count > 0 ? `共 ${count} 条` : '暂无内容';
|
||
}
|
||
|
||
// 处理选择的图片文件(压缩后保存到本地存储)
|
||
async handleImageFiles(fileList) {
|
||
const files = Array.from(fileList || []).filter((f) => f.type.startsWith('image/'));
|
||
if (files.length === 0) return;
|
||
|
||
try {
|
||
this.showActionStatus(true, `⏳ 正在处理 ${files.length} 张图片…`);
|
||
const result = await chrome.storage.local.get(['commentImages']);
|
||
const images = result.commentImages || [];
|
||
|
||
for (const file of files) {
|
||
const dataUrl = await this.compressImage(file);
|
||
images.push({
|
||
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||
name: file.name,
|
||
dataUrl
|
||
});
|
||
}
|
||
|
||
await chrome.storage.local.set({ commentImages: images });
|
||
this.renderImagePreviews(images);
|
||
this.showActionStatus(true, `✓ 已添加 ${files.length} 张图片,共 ${images.length} 张`);
|
||
await this.updateModeHint('image');
|
||
} catch (error) {
|
||
console.error('处理评论图片失败:', error);
|
||
this.showActionStatus(false, '✗ 图片处理失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 压缩图片:超过 400KB 或边长超过 1280px 时压缩为 JPEG
|
||
async compressImage(file) {
|
||
const shouldCompress = file.size > 400 * 1024;
|
||
const originalDataUrl = await new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = () => resolve(reader.result);
|
||
reader.onerror = reject;
|
||
reader.readAsDataURL(file);
|
||
});
|
||
|
||
if (!shouldCompress) return originalDataUrl;
|
||
|
||
const img = await new Promise((resolve, reject) => {
|
||
const image = new Image();
|
||
image.onload = () => resolve(image);
|
||
image.onerror = reject;
|
||
image.src = originalDataUrl;
|
||
});
|
||
|
||
const maxSide = 1280;
|
||
let { width, height } = img;
|
||
if (width > maxSide || height > maxSide) {
|
||
const scale = Math.min(maxSide / width, maxSide / height);
|
||
width = Math.round(width * scale);
|
||
height = Math.round(height * scale);
|
||
}
|
||
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = width;
|
||
canvas.height = height;
|
||
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
|
||
return canvas.toDataURL('image/jpeg', 0.85);
|
||
}
|
||
|
||
// 渲染图片预览列表
|
||
async loadImagePreviews() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['commentImages']);
|
||
this.renderImagePreviews(result.commentImages || []);
|
||
} catch (error) {
|
||
console.error('加载评论图片失败:', error);
|
||
}
|
||
}
|
||
|
||
renderImagePreviews(images) {
|
||
const list = document.getElementById('image-preview-list');
|
||
if (!list) return;
|
||
|
||
if (!images || images.length === 0) {
|
||
list.innerHTML = '<div class="image-preview-empty">暂无图片</div>';
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = '';
|
||
images.forEach((image) => {
|
||
const item = document.createElement('div');
|
||
item.className = 'image-preview-item';
|
||
|
||
const img = document.createElement('img');
|
||
img.src = image.dataUrl;
|
||
img.alt = image.name || '';
|
||
|
||
const del = document.createElement('button');
|
||
del.type = 'button';
|
||
del.className = 'image-delete-btn';
|
||
del.textContent = '×';
|
||
del.title = '删除这张图片';
|
||
del.addEventListener('click', () => this.deleteCommentImage(image.id));
|
||
|
||
item.appendChild(img);
|
||
item.appendChild(del);
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
// 删除一张评论图片
|
||
async deleteCommentImage(id) {
|
||
try {
|
||
const result = await chrome.storage.local.get(['commentImages']);
|
||
const images = (result.commentImages || []).filter((img) => img.id !== id);
|
||
await chrome.storage.local.set({ commentImages: images });
|
||
this.renderImagePreviews(images);
|
||
this.showActionStatus(true, `✓ 已删除,剩余 ${images.length} 张`);
|
||
await this.updateModeHint('image');
|
||
} catch (error) {
|
||
console.error('删除评论图片失败:', error);
|
||
}
|
||
}
|
||
|
||
// ===== 授权系统(CardAuth) =====
|
||
|
||
// 功能门槛:有效授权 或 试用期内可用;试用结束锁定
|
||
async isFeatureAllowed() {
|
||
try {
|
||
const status = await chrome.runtime.sendMessage({ action: 'authStatus' });
|
||
const state = status && status.status ? status.status.state : 'locked';
|
||
if (state === 'locked') {
|
||
this.showActionStatus(false, '✗ 试用已结束,请购买授权或输入卡密激活');
|
||
return false;
|
||
}
|
||
return true;
|
||
} catch (error) {
|
||
// 后台不可达时不阻断功能
|
||
console.error('授权状态检查失败:', error);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// 初始化授权区:状态 / 公告
|
||
async initAuth() {
|
||
await this.renderAuthStatus();
|
||
await this.loadAnnouncements();
|
||
|
||
// 后台复验或购买激活后自动刷新
|
||
chrome.storage.onChanged.addListener((changes, namespace) => {
|
||
if (namespace !== 'local') return;
|
||
if (changes.licenseState) this.renderAuthStatus();
|
||
if (changes.announcements) this.renderAnnouncements((changes.announcements.newValue || {}).list || []);
|
||
});
|
||
}
|
||
|
||
// 渲染授权状态徽章
|
||
async renderAuthStatus() {
|
||
const badge = document.getElementById('auth-badge');
|
||
const detail = document.getElementById('auth-detail');
|
||
if (!badge || !detail) return;
|
||
|
||
try {
|
||
const status = await chrome.runtime.sendMessage({ action: 'authStatus' });
|
||
const s = status && status.status ? status.status : { state: 'locked' };
|
||
|
||
badge.classList.remove('authorized', 'trial', 'locked');
|
||
this.stopTrialCountdown();
|
||
|
||
if (s.state === 'authorized') {
|
||
badge.classList.add('authorized');
|
||
badge.textContent = '已授权';
|
||
const license = s.license || {};
|
||
if (license.isPermanent) {
|
||
detail.textContent = '永久授权 · 卡密 ' + this.maskCardKey(license.cardKey);
|
||
} else if (license.expireTime) {
|
||
const days = license.remainingDays != null ? ` · 剩余 ${license.remainingDays} 天` : '';
|
||
detail.textContent = `到期 ${license.expireTime}${days}`;
|
||
} else {
|
||
detail.textContent = '授权有效';
|
||
}
|
||
} else if (s.state === 'trial') {
|
||
badge.classList.add('trial');
|
||
badge.textContent = '试用中';
|
||
this.startTrialCountdown(s.trialEndAt || (Date.now() + 3600000));
|
||
} else {
|
||
badge.classList.add('locked');
|
||
badge.textContent = '未授权';
|
||
detail.textContent = '试用已结束:请购买授权或输入卡密激活后使用';
|
||
}
|
||
} catch (error) {
|
||
console.error('获取授权状态失败:', error);
|
||
badge.classList.add('locked');
|
||
badge.textContent = '未知';
|
||
detail.textContent = '授权状态获取失败,请重新打开侧边栏';
|
||
}
|
||
}
|
||
|
||
// 试用倒计时(精确到秒,每秒刷新)
|
||
startTrialCountdown(endAt) {
|
||
this.stopTrialCountdown();
|
||
|
||
const tick = () => {
|
||
const remain = endAt - Date.now();
|
||
const detail = document.getElementById('auth-detail');
|
||
|
||
if (remain <= 0) {
|
||
this.stopTrialCountdown();
|
||
this.renderAuthStatus(); // 到期后刷新为锁定状态
|
||
return;
|
||
}
|
||
|
||
if (detail) {
|
||
detail.textContent = `免费试用剩余 ${this.formatRemain(remain)} · 试用结束后需购买授权或输入卡密激活`;
|
||
}
|
||
};
|
||
|
||
tick();
|
||
this.trialTimer = setInterval(tick, 1000);
|
||
}
|
||
|
||
stopTrialCountdown() {
|
||
if (this.trialTimer) {
|
||
clearInterval(this.trialTimer);
|
||
this.trialTimer = null;
|
||
}
|
||
}
|
||
|
||
// 毫秒 → HH:MM:SS
|
||
formatRemain(ms) {
|
||
const totalSec = Math.max(0, Math.floor(ms / 1000));
|
||
const pad = (n) => String(n).padStart(2, '0');
|
||
return `${pad(Math.floor(totalSec / 3600))}:${pad(Math.floor((totalSec % 3600) / 60))}:${pad(totalSec % 60)}`;
|
||
}
|
||
|
||
maskCardKey(key) {
|
||
if (!key) return '';
|
||
const k = String(key);
|
||
return k.length > 10 ? k.slice(0, 8) + '****' + k.slice(-4) : k;
|
||
}
|
||
|
||
// 手动输入卡密激活
|
||
async activateCard() {
|
||
const input = document.getElementById('card-key-input');
|
||
const btn = document.getElementById('activate-btn');
|
||
const cardKey = (input?.value || '').trim();
|
||
if (!cardKey) {
|
||
this.showOrderStatus(false, '请输入卡密');
|
||
return;
|
||
}
|
||
|
||
if (btn) { btn.disabled = true; btn.textContent = '激活中…'; }
|
||
this.showOrderStatus(true, '⏳ 正在验证卡密…');
|
||
|
||
try {
|
||
const res = await chrome.runtime.sendMessage({ action: 'authActivate', cardKey });
|
||
if (res && res.ok && res.licenseState && res.licenseState.valid) {
|
||
input.value = '';
|
||
this.showOrderStatus(true, `✓ 激活成功!${res.licenseState.isPermanent ? '永久授权' : '到期 ' + res.licenseState.expireTime}`);
|
||
await this.renderAuthStatus();
|
||
} else {
|
||
const msg = (res && res.licenseState && res.licenseState.message) || (res && res.message) || '激活失败';
|
||
this.showOrderStatus(false, '✗ ' + msg);
|
||
}
|
||
} catch (error) {
|
||
this.showOrderStatus(false, '✗ 激活失败: ' + error.message);
|
||
} finally {
|
||
if (btn) { btn.disabled = false; btn.textContent = '激活'; }
|
||
}
|
||
}
|
||
|
||
// 购买流程:下单(带机器码)→ 打开支付页 → 轮询订单 → 自动发卡激活
|
||
async startBuyFlow(payType) {
|
||
const payRow = document.getElementById('pay-select-row');
|
||
if (payRow) payRow.hidden = true;
|
||
|
||
this.showOrderStatus(true, '⏳ 正在创建订单…');
|
||
try {
|
||
const res = await chrome.runtime.sendMessage({ action: 'authCreateOrder', payType });
|
||
if (!res || !res.ok) {
|
||
this.showOrderStatus(false, '✗ 创建订单失败: ' + ((res && res.message) || '未知错误'));
|
||
return;
|
||
}
|
||
|
||
const order = res.order;
|
||
this.showOrderStatus(true, `订单 ${order.orderNo}(¥${order.amount})已创建,请在新页面完成支付,支付成功后自动激活…`);
|
||
if (res.payUrl) {
|
||
chrome.tabs.create({ url: res.payUrl });
|
||
}
|
||
|
||
this.startOrderPolling(order.orderNo);
|
||
} catch (error) {
|
||
this.showOrderStatus(false, '✗ 购买失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 轮询订单:支付+自动发卡后自动激活
|
||
startOrderPolling(orderNo) {
|
||
if (this.orderPollTimer) {
|
||
clearInterval(this.orderPollTimer);
|
||
this.orderPollTimer = null;
|
||
}
|
||
|
||
let attempts = 0;
|
||
const maxAttempts = 120; // 5秒 × 120 = 最多10分钟
|
||
|
||
this.orderPollTimer = setInterval(async () => {
|
||
attempts++;
|
||
if (attempts > maxAttempts) {
|
||
clearInterval(this.orderPollTimer);
|
||
this.orderPollTimer = null;
|
||
this.showOrderStatus(false, `✗ 等待支付超时。订单号 ${orderNo},支付后可凭订单号到购买中心查询,或用收到的卡密在此激活`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo });
|
||
if (!res || !res.ok) return;
|
||
|
||
if (res.activated) {
|
||
clearInterval(this.orderPollTimer);
|
||
this.orderPollTimer = null;
|
||
this.showOrderStatus(true, `✓ 支付成功,已自动发卡并激活生效!${res.expireTime ? '到期 ' + res.expireTime : ''}`);
|
||
await this.renderAuthStatus();
|
||
const input = document.getElementById('card-key-input');
|
||
if (input && res.cardKey) input.value = res.cardKey;
|
||
} else if (res.paid && res.cardKey && !res.activated) {
|
||
clearInterval(this.orderPollTimer);
|
||
this.orderPollTimer = null;
|
||
this.showOrderStatus(false, '✗ 已支付但激活失败: ' + ((res.licenseState && res.licenseState.message) || '请手动输入卡密激活'));
|
||
} else if (attempts % 6 === 0) {
|
||
this.showOrderStatus(true, `⏳ 等待支付中…(订单 ${orderNo},已等待 ${attempts * 5} 秒)`);
|
||
}
|
||
} catch (error) {
|
||
console.error('订单轮询失败:', error);
|
||
}
|
||
}, 5000);
|
||
}
|
||
|
||
showOrderStatus(ok, text) {
|
||
const el = document.getElementById('order-status');
|
||
if (!el) return;
|
||
el.textContent = text;
|
||
el.className = 'order-status ' + (ok ? 'ok' : 'fail');
|
||
}
|
||
|
||
// 拉取并渲染公告
|
||
async loadAnnouncements() {
|
||
try {
|
||
const res = await chrome.runtime.sendMessage({ action: 'authGetAnnouncements' });
|
||
const ann = res && res.announcements ? res.announcements : null;
|
||
const cached = ann || (await chrome.storage.local.get(['announcements'])).announcements;
|
||
if (cached) {
|
||
this.renderAnnouncements(cached.list || []);
|
||
await this.maybeShowPopupAnnouncements(cached.popup || []);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载公告失败:', error);
|
||
}
|
||
}
|
||
|
||
renderAnnouncements(list) {
|
||
const box = document.getElementById('announcement-box');
|
||
const container = document.getElementById('announcement-list');
|
||
if (!box || !container) return;
|
||
|
||
if (!list || list.length === 0) {
|
||
box.hidden = true;
|
||
return;
|
||
}
|
||
|
||
box.hidden = false;
|
||
container.innerHTML = '';
|
||
list.slice(0, 10).forEach((item) => {
|
||
const div = document.createElement('div');
|
||
div.className = 'announcement-item';
|
||
if (item.created_at) {
|
||
const time = document.createElement('span');
|
||
time.className = 'announcement-time';
|
||
time.textContent = String(item.created_at).slice(0, 16);
|
||
div.appendChild(time);
|
||
}
|
||
// 后台字段:title + content
|
||
if (item.title) {
|
||
const strong = document.createElement('b');
|
||
strong.textContent = item.title;
|
||
div.appendChild(strong);
|
||
div.appendChild(document.createElement('br'));
|
||
}
|
||
div.appendChild(document.createTextNode(item.content || ''));
|
||
container.appendChild(div);
|
||
});
|
||
}
|
||
|
||
// 弹窗公告:每条只弹一次
|
||
async maybeShowPopupAnnouncements(popupList) {
|
||
if (!popupList || popupList.length === 0) return;
|
||
|
||
const result = await chrome.storage.local.get(['seenPopupAnnouncements']);
|
||
const seen = result.seenPopupAnnouncements || [];
|
||
const unseen = popupList.filter((item) => !seen.includes(String(item.id)));
|
||
|
||
if (unseen.length === 0) return;
|
||
|
||
const texts = unseen.map((item) => {
|
||
const title = item.title || '';
|
||
const content = item.content || '';
|
||
return [title, content].filter(Boolean).join('\n');
|
||
}).filter(Boolean);
|
||
if (texts.length > 0) {
|
||
alert('📢 公告\n\n' + texts.join('\n\n—\n\n'));
|
||
}
|
||
await chrome.storage.local.set({
|
||
seenPopupAnnouncements: [...seen, ...unseen.map((item) => String(item.id))]
|
||
});
|
||
}
|
||
|
||
// ===== 评论记录 =====
|
||
|
||
// 从存储加载评论记录
|
||
async loadCommentLogs() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['commentLogs']);
|
||
this.renderCommentLogs(result.commentLogs || []);
|
||
} catch (error) {
|
||
console.error('加载评论记录失败:', error);
|
||
}
|
||
}
|
||
|
||
// 计算今日/本月/总计统计
|
||
computeLogStats(logs) {
|
||
const now = new Date();
|
||
const todayKey = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`;
|
||
const monthKey = `${now.getFullYear()}-${now.getMonth()}`;
|
||
let today = 0;
|
||
let month = 0;
|
||
logs.forEach((record) => {
|
||
const d = new Date(record.timestamp);
|
||
if (`${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` === todayKey) today++;
|
||
if (`${d.getFullYear()}-${d.getMonth()}` === monthKey) month++;
|
||
});
|
||
return { today, month, total: logs.length };
|
||
}
|
||
|
||
// 渲染统计 + 列表
|
||
renderCommentLogs(logs) {
|
||
const stats = this.computeLogStats(logs);
|
||
const setStat = (id, value) => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.textContent = value;
|
||
};
|
||
setStat('stat-today', stats.today);
|
||
setStat('stat-month', stats.month);
|
||
setStat('stat-total', stats.total);
|
||
|
||
const list = document.getElementById('log-list');
|
||
if (!list) return;
|
||
|
||
if (!logs || logs.length === 0) {
|
||
list.innerHTML = '<div class="log-empty">暂无评论记录,成功评论后会自动出现在这里</div>';
|
||
return;
|
||
}
|
||
|
||
const modeLabels = { custom: '固定池', text: '文字', image: '图片', ai: 'AI' };
|
||
list.innerHTML = '';
|
||
logs.slice(0, 100).forEach((record) => {
|
||
const item = document.createElement('div');
|
||
item.className = 'log-item' + (record.url ? '' : ' no-url');
|
||
item.title = record.url ? '点击打开笔记' : '无笔记链接';
|
||
|
||
if (record.thumb) {
|
||
const thumb = document.createElement('img');
|
||
thumb.className = 'log-thumb';
|
||
thumb.src = record.thumb;
|
||
item.appendChild(thumb);
|
||
}
|
||
|
||
const body = document.createElement('div');
|
||
body.className = 'log-item-body';
|
||
|
||
const title = document.createElement('div');
|
||
title.className = 'log-item-title';
|
||
title.textContent = record.title || '无标题笔记';
|
||
|
||
const comment = document.createElement('div');
|
||
comment.className = 'log-item-comment';
|
||
comment.textContent = record.comment || '';
|
||
|
||
const meta = document.createElement('div');
|
||
meta.className = 'log-item-meta';
|
||
const time = new Date(record.timestamp);
|
||
const pad = (n) => String(n).padStart(2, '0');
|
||
const tag = document.createElement('span');
|
||
tag.className = 'log-mode-tag';
|
||
tag.textContent = modeLabels[record.mode] || record.mode || '';
|
||
meta.appendChild(tag);
|
||
meta.appendChild(document.createTextNode(
|
||
`${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(time.getDate())} ${pad(time.getHours())}:${pad(time.getMinutes())}`
|
||
));
|
||
|
||
body.appendChild(title);
|
||
body.appendChild(comment);
|
||
body.appendChild(meta);
|
||
item.appendChild(body);
|
||
|
||
if (record.url) {
|
||
const openIcon = document.createElement('span');
|
||
openIcon.className = 'log-open-icon';
|
||
openIcon.textContent = '↗';
|
||
item.appendChild(openIcon);
|
||
item.addEventListener('click', () => {
|
||
chrome.tabs.create({ url: record.url });
|
||
});
|
||
}
|
||
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
// 提示当前配置是否真正生效
|
||
async updateModeHint(mode) {
|
||
const hint = document.getElementById('mode-hint');
|
||
if (!hint) return;
|
||
|
||
const aiRadio = document.getElementById('mode-ai');
|
||
const current = mode || (aiRadio && aiRadio.checked ? 'ai' : 'custom');
|
||
const keyInput = document.getElementById('api-key');
|
||
const hasKey = !!(keyInput && keyInput.value && keyInput.value.trim());
|
||
|
||
let storageInfo = {};
|
||
try {
|
||
storageInfo = await chrome.storage.local.get(['userComments', 'commentImages']);
|
||
} catch (error) {
|
||
console.error('读取提示信息失败:', error);
|
||
}
|
||
const textCount = (storageInfo.userComments || []).length;
|
||
const imageCount = (storageInfo.commentImages || []).length;
|
||
|
||
hint.classList.remove('warn');
|
||
|
||
if (current === 'ai' && !hasKey) {
|
||
hint.textContent = '⚠ AI 模式需要先填 API 密钥,否则会退回固定评论';
|
||
hint.classList.add('warn');
|
||
} else if (current === 'text' && textCount === 0) {
|
||
hint.textContent = '⚠ 自定义文字模式还没有文字内容,请在下方填写并保存';
|
||
hint.classList.add('warn');
|
||
} else if (current === 'image' && imageCount === 0) {
|
||
hint.textContent = '⚠ 图片回复模式还没有上传图片,请在下方选择图片';
|
||
hint.classList.add('warn');
|
||
} else if (current === 'custom' && hasKey) {
|
||
hint.textContent = '⚠ 当前是固定评论池,下方模型配置不会生效;要按笔记生成请点「AI 按笔记生成」';
|
||
hint.classList.add('warn');
|
||
} else if (current === 'ai') {
|
||
hint.textContent = '✓ 每篇笔记都会调用下方模型,按笔记内容生成评论';
|
||
} else if (current === 'text') {
|
||
hint.textContent = `✓ 每篇笔记发送一条自定义文字(共 ${textCount} 条循环)`;
|
||
} else if (current === 'image') {
|
||
hint.textContent = `✓ 每篇笔记发送一张图片(共 ${imageCount} 张循环)`;
|
||
} else {
|
||
hint.textContent = '固定评论池 = 循环发预设短评;下方模型配置暂不生效';
|
||
}
|
||
}
|
||
|
||
// 加载自定义模型配置(API地址 + 模型名)
|
||
async loadModelConfig() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['apiBaseUrl', 'modelName']);
|
||
const baseUrl = (result.apiBaseUrl || '').trim() || DEFAULT_AI.apiBaseUrl;
|
||
const modelName = (result.modelName || '').trim() || DEFAULT_AI.model;
|
||
|
||
const baseUrlInput = document.getElementById('api-base-url');
|
||
const modelInput = document.getElementById('model-name');
|
||
if (baseUrlInput) baseUrlInput.value = baseUrl;
|
||
if (modelInput) modelInput.value = modelName;
|
||
|
||
// 首次使用时写入默认值
|
||
if (!result.apiBaseUrl || !result.modelName) {
|
||
await chrome.storage.local.set({ apiBaseUrl: baseUrl, modelName: modelName });
|
||
}
|
||
console.log('加载模型配置:', baseUrl, modelName);
|
||
} catch (error) {
|
||
console.error('加载模型配置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存自定义模型配置
|
||
async saveModelConfig(changes) {
|
||
try {
|
||
await chrome.storage.local.set(changes);
|
||
console.log('保存模型配置:', Object.keys(changes).join(', '));
|
||
} catch (error) {
|
||
console.error('保存模型配置失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载AI提示词
|
||
async loadAiPrompt() {
|
||
try {
|
||
const result = await chrome.storage.local.get(['aiPrompt']);
|
||
const defaultPrompt = '根据笔记内容来生成符合笔记的评论。最后只需要输出评论内容,不要输出你的推理,不要输出任何与评论无关的内容,不要输出#相关的内容,不要输出笔记关键词相关的内容。评论不要超过300字。这个是小红书的笔记内容:\n\n{{content}}\n\n';
|
||
const prompt = result.aiPrompt || defaultPrompt;
|
||
const textarea = document.getElementById('ai-prompt');
|
||
if (textarea) textarea.value = prompt;
|
||
console.log('加载AI提示词');
|
||
} catch (error) {
|
||
console.error('加载AI提示词失败:', error);
|
||
}
|
||
}
|
||
|
||
// 保存AI提示词
|
||
async saveAiPrompt() {
|
||
try {
|
||
const textarea = document.getElementById('ai-prompt');
|
||
const prompt = (textarea?.value || '').trim();
|
||
await chrome.storage.local.set({ aiPrompt: prompt });
|
||
console.log('保存AI提示词');
|
||
alert('提示词已保存');
|
||
} catch (error) {
|
||
console.error('保存AI提示词失败:', error);
|
||
alert('保存提示词失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 恢复默认AI提示词
|
||
async resetAiPromptToDefault() {
|
||
try {
|
||
const defaultPrompt = '根据笔记内容来生成符合笔记的评论。最后只需要输出评论内容,不要输出你的推理,不要输出任何与评论无关的内容,不要输出#相关的内容,不要输出笔记关键词相关的内容。评论不要超过300字。这个是小红书的笔记内容:\n\n{{content}}\n\n';
|
||
await chrome.storage.local.set({ aiPrompt: defaultPrompt });
|
||
const textarea = document.getElementById('ai-prompt');
|
||
if (textarea) textarea.value = defaultPrompt;
|
||
console.log('恢复默认AI提示词');
|
||
alert('已恢复默认提示词');
|
||
} catch (error) {
|
||
console.error('恢复默认AI提示词失败:', error);
|
||
alert('恢复默认提示词失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// 更新AI状态显示
|
||
updateAiStatus() {
|
||
const apiKey = document.getElementById('api-key').value;
|
||
const isEnabled = apiKey && apiKey.length > 0;
|
||
const modelName = (document.getElementById('model-name')?.value || '').trim() || DEFAULT_AI.model;
|
||
const baseUrl = (document.getElementById('api-base-url')?.value || '').trim();
|
||
|
||
const statusIndicator = document.querySelector('.status-indicator');
|
||
const statusText = document.querySelector('.status-text');
|
||
const testBtn = document.getElementById('test-ai-btn');
|
||
|
||
if (isEnabled) {
|
||
statusIndicator.textContent = '🟢';
|
||
statusText.textContent = `已启用 · ${modelName} · ${baseUrl}`;
|
||
testBtn.disabled = false;
|
||
} else {
|
||
statusIndicator.textContent = '🔴';
|
||
statusText.textContent = '未配置API密钥';
|
||
testBtn.disabled = true;
|
||
}
|
||
|
||
this.updateModeHint();
|
||
}
|
||
|
||
// 测试AI生成功能(走后台调用自定义模型)
|
||
async testAiGeneration() {
|
||
const apiKey = document.getElementById('api-key').value;
|
||
const baseUrl = document.getElementById('api-base-url')?.value;
|
||
const modelName = document.getElementById('model-name')?.value;
|
||
const resultEl = document.getElementById('test-ai-result');
|
||
const testBtn = document.getElementById('test-ai-btn');
|
||
|
||
const showResult = (ok, text) => {
|
||
if (!resultEl) {
|
||
alert(text);
|
||
return;
|
||
}
|
||
resultEl.textContent = text;
|
||
resultEl.className = 'test-ai-result ' + (ok ? 'ok' : 'fail');
|
||
};
|
||
|
||
if (!apiKey || apiKey.length === 0) {
|
||
showResult(false, '✗ 请先配置API密钥');
|
||
return;
|
||
}
|
||
|
||
if (testBtn) {
|
||
testBtn.disabled = true;
|
||
testBtn.textContent = '测试中…';
|
||
}
|
||
showResult(true, '⏳ 请求中…');
|
||
|
||
try {
|
||
const apiResult = await chrome.runtime.sendMessage({
|
||
action: 'callAiApi',
|
||
apiKey: apiKey,
|
||
baseUrl: baseUrl,
|
||
model: modelName,
|
||
prompt: '你好,请简单回复一下',
|
||
maxTokens: 300,
|
||
timeoutMs: 60000
|
||
});
|
||
|
||
if (apiResult && apiResult.ok) {
|
||
const reply = String(apiResult.content).slice(0, 40);
|
||
showResult(true, `✓ 成功 · ${apiResult.model} · ${reply}`);
|
||
} else {
|
||
showResult(false, `✗ 失败 · ${(apiResult && apiResult.error) || '后台无响应,请重新加载插件'}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('AI测试失败:', error);
|
||
showResult(false, '✗ 失败 · ' + error.message);
|
||
} finally {
|
||
if (testBtn) {
|
||
testBtn.disabled = false;
|
||
testBtn.textContent = '测试AI';
|
||
}
|
||
this.updateAiStatus();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
new AutoCommentManager();
|
||
}); |