import { extension_settings, getContext } from '/scripts/extensions.js'; import { saveSettingsDebounced, eventSource, event_types, saveChatConditional, reloadCurrentChat } from '/script.js'; import { SlashCommand } from '/scripts/slash-commands/SlashCommand.js'; let availableModels = []; let isFetchingModels = false; // 插件名称 const extensionName = 'ST-Amily2-Chat-Optimisation'; const extensionFolderPath = `scripts/extensions/third-party/ST-Amily2-Chat-Optimisation`; // === 动态密码生成器 === function generateDynamicPassword(date = new Date()) { // 种子值 const seed = { a: 1103515245, c: 12345, m: 2147483647, }; // 核心哈希算法 function customHash(input) { let hash = 0; for(let i = 0; i < input.length; i++) { hash = ((hash << 5) - hash) + input.charCodeAt(i); hash |= 0; // 转为32位整型 } return hash >>> 0; // 确保为正整数 } // 使用传入的日期作为基准 const month = date.getMonth() + 1; const day = date.getDate(); const year = date.getFullYear(); const baseInput = `${month}-${day}-AMILY_${year}`; // 生成伪随机种子 const str1 = `SD${customHash(baseInput)}`; const str2 = `V${customHash(str1)}`; // 使用线性同余算法生成密码 function lcgRandom(params) { return function() { params.seed = (params.a * params.seed + params.c) % params.m; return params.seed; }; } const combinedSeed = customHash(str2) % seed.m; const randFunc = lcgRandom({ a: seed.a, c: seed.c, m: seed.m, seed: combinedSeed }); // 密码字符集(移除易混淆字符) const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // 生成密码段 const segments = []; for (let segIdx = 0; segIdx < 3; segIdx++) { let segment = ''; for (let i = 0; i < 4; i++) { const randValue = Math.abs(randFunc()); segment += chars.charAt(randValue % chars.length); } segments.push(segment); } return segments.join('-'); } // === 开发者使用的密码工具 === function getPasswordForDate(date = new Date()) { return generateDynamicPassword(date); } // 密码有效期设置(默认为7天) const PASSWORD_VALIDITY_DAYS = 7; // 开发者提示 - 在控制台显示今日密码 console.warn("[Amily2号] 开发者提示:今日密码 - ", getPasswordForDate()); console.log(`[Amily2号] 密码有效期为: ${PASSWORD_VALIDITY_DAYS}天`); // ================ 实际使用的授权配置 ================ const AUTH_CONFIG = { expiryDate: new Date('2024-12-31'), validityDays: PASSWORD_VALIDITY_DAYS }; // 默认设置 const defaultSettings = { enabled: true, activated: false, apiUrl: 'http://localhost:5001/v1', apiKey: '', model: 'deepseek-r1-250528', maxTokens: 12000, temperature: 1.2, contextMessages: 2, systemPrompt: '', mainPrompt: '', showOptimizationToast: true, suppressToast: false, }; // 授权状态变量 window.pluginAuthStatus = { authorized: false, expired: false }; // ============= 新增函数: 获取模型列表 ============= async function fetchSupportedModels() { const settings = extension_settings[extensionName]; if (!settings.apiUrl) { toastr.error('请先配置API URL', '获取模型失败'); return []; } if (isFetchingModels) { toastr.info('正在获取模型列表,请稍候...', '获取模型'); return; } isFetchingModels = true; try { const originalButtonText = $('#amily2_refresh_models').html(); $('#amily2_refresh_models') .prop('disabled', true) .html(' 加载中'); let modelListUrl = settings.apiUrl; if (!modelListUrl.endsWith('/v1/models')) { if (modelListUrl.endsWith('/')) { modelListUrl += 'v1/models'; } else { modelListUrl += '/v1/models'; } } const headers = { 'Content-Type': 'application/json' }; if (settings.apiKey) { headers['Authorization'] = `Bearer ${settings.apiKey}`; } console.log('发送模型列表请求到:', modelListUrl); const response = await fetch(modelListUrl, { method: 'GET', headers: headers }); if (!response.ok) { throw new Error(`API返回错误: ${response.status} ${response.statusText}`); } const data = await response.json(); let models = []; if (Array.isArray(data)) { models = data.map(m => m.id || m); } else if (data.data && Array.isArray(data.data)) { models = data.data.map(m => m.id); } else if (data.models && Array.isArray(data.models)) { models = data.models; } else { throw new Error('未知的模型列表格式'); } availableModels = models.filter(m => !m.includes('embed') && !m.includes('search') && !m.includes('similarity') && !m.includes('audio') ); availableModels.sort(); console.log(`获取模型列表成功 (${availableModels.length}个):`, availableModels); toastr.success(`成功获取 ${availableModels.length} 个可用模型`, '模型加载完成'); return availableModels; } catch (error) { console.error('获取模型列表失败:', error); toastr.error(`获取模型失败: ${error.message}`, '错误'); return []; } finally { isFetchingModels = false; $('#amily2_refresh_models') .prop('disabled', false) .html(' 刷新模型'); } } // ============= 新增函数: 填充模型下拉菜单 ============= function populateModelDropdown() { const modelSelect = $('#amily2_model'); const modelNotes = $('#amily2_model_notes'); modelSelect.empty(); const currentModel = extension_settings[extensionName].model || ''; if (availableModels.length === 0) { modelSelect.append(''); modelNotes.html('请检查API配置后点击"刷新模型"按钮'); return; } const defaultOption = $('') .val('') .text('-- 选择模型 --'); modelSelect.append(defaultOption); availableModels.forEach(model => { const option = $('') .val(model) .text(model); if (model === currentModel) { option.attr('selected', 'selected'); } modelSelect.append(option); }); if (currentModel && modelSelect.val() === currentModel) { modelNotes.html(`已选择: ${currentModel}`); } else { modelNotes.html(`已加载 ${availableModels.length} 个可用模型`); } } // 加载设置 async function loadSettings() { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } // ===== 授权过期检查 ===== const now = new Date(); window.pluginAuthStatus.expired = now > AUTH_CONFIG.expiryDate; if (window.pluginAuthStatus.expired) { localStorage.removeItem('plugin_activated'); localStorage.removeItem('plugin_auth_code'); localStorage.removeItem('plugin_valid_until'); console.log('[Amily2号] 检测到授权过期,已清理本地存储'); } // 合并默认设置 extension_settings[extensionName] = { ...defaultSettings, ...extension_settings[extensionName] }; // 检查授权状态 window.pluginAuthStatus.authorized = await checkAuthorization(); // 更新UI updateUI(); // 自动加载模型 if (window.pluginAuthStatus.authorized && extension_settings[extensionName].apiUrl) { const cachedModels = localStorage.getItem('amily2_cached_models'); if (cachedModels) { availableModels = JSON.parse(cachedModels); console.log('从缓存加载模型列表:', availableModels.length); populateModelDropdown(); } setTimeout(() => { if (availableModels.length === 0) { toastr.info('正在自动加载模型列表...', '模型初始化'); $('#amily2_refresh_models').click(); } }, 1500); } $('#amily2_api_url').on('input', function() { localStorage.removeItem('amily2_cached_models'); availableModels = []; populateModelDropdown(); }); } // 检查授权状态(无UI更新) function checkAuthorization() { const now = new Date(); window.pluginAuthStatus.expired = now > AUTH_CONFIG.expiryDate; const activated = localStorage.getItem('plugin_activated') === 'true'; const savedAuthCode = localStorage.getItem('plugin_auth_code'); const validUntil = localStorage.getItem('plugin_valid_until'); let withinValidityPeriod = false; if (validUntil) { const validUntilDate = new Date(validUntil); withinValidityPeriod = now <= validUntilDate; console.log(`[Amily2号] 授权有效期检查: 当前时间: ${now.toISOString()} 授权有效期至: ${validUntilDate.toISOString()} 是否在有效期内: ${withinValidityPeriod}`); } let passwordMatches = false; if (savedAuthCode) { const today = new Date(); for (let i = 0; i < AUTH_CONFIG.validityDays; i++) { const checkDate = new Date(); checkDate.setDate(today.getDate() - i); const passwordForDay = getPasswordForDate(checkDate); if (savedAuthCode === passwordForDay) { passwordMatches = true; console.log(`[Amily2号] 密码匹配: ${savedAuthCode} 对应第${i+1}天前`); break; } } } window.pluginAuthStatus.authorized = activated && !window.pluginAuthStatus.expired && passwordMatches && withinValidityPeriod; return window.pluginAuthStatus.authorized; } // 激活授权 async function activatePluginAuthorization(authCode) { let isValidCode = false; const today = new Date(); for (let i = 0; i < AUTH_CONFIG.validityDays; i++) { const checkDate = new Date(); checkDate.setDate(today.getDate() - i); const passwordForDay = getPasswordForDate(checkDate); if (authCode === passwordForDay) { isValidCode = true; console.log(`[Amily2号] 输入的密码匹配第${i+1}天前的有效密码`); break; } } if (!isValidCode) { toastr.error('授权码无效', '激活失败'); return false; } const now = new Date(); if (now > AUTH_CONFIG.expiryDate) { toastr.error('授权已过期', '激活失败'); return false; } const validUntil = new Date(); validUntil.setDate(now.getDate() + AUTH_CONFIG.validityDays); localStorage.setItem('plugin_valid_until', validUntil.toISOString()); localStorage.setItem('plugin_auth_code', authCode); localStorage.setItem('plugin_activated', 'true'); toastr.success(`授权激活成功,有效期至 ${validUntil.toLocaleDateString()}`, 'Amily2号启用'); window.pluginAuthStatus.authorized = true; $('#auth_panel').hide(); $('.plugin-features').show(); extension_settings[extensionName].enabled = true; saveSettings(); return true; } // 显示过期信息 function displayExpiryInfo() { const now = new Date(); const daysLeft = Math.ceil((AUTH_CONFIG.expiryDate - now) / (1000 * 60 * 60 * 24)); const validUntil = localStorage.getItem('plugin_valid_until'); if (window.pluginAuthStatus.expired) { return '
授权已过期
'; } else { let validUntilHtml = ''; if (validUntil) { const validUntilDate = new Date(validUntil); validUntilHtml = `当前授权有效期至: ${validUntilDate.toLocaleDateString()}`; } return `
授权有效期: ${daysLeft}天 有效期至: ${AUTH_CONFIG.expiryDate.toLocaleDateString()} ${validUntilHtml}
`; } } // ============= 配置验证函数 ============= function validateSettings() { const settings = extension_settings[extensionName] || {}; const errors = []; if (!settings.apiUrl) { errors.push('API URL未配置'); } else if (!/^https?:\/\//.test(settings.apiUrl)) { errors.push('API URL必须以http://或https://开头'); } if (settings.apiKey) { if (settings.apiKey.length < 8) { errors.push('API密钥太短(至少8位)'); } if (/(key|secret|password)/i.test(settings.apiKey)) { toastr.warning('请注意:API Key包含敏感关键词("key", "secret", "password")', '安全提醒', { timeOut: 5000 }); } } if (!settings.model) { errors.push('未选择模型'); } if (settings.maxTokens < 100 || settings.maxTokens > 20000) { errors.push(`Token数超限 (${settings.maxTokens}) - 必须在100-20000之间`); } return errors.length ? errors : null; } // ============= 统一的设置项事件处理 ============= function saveSettings() { if (!window.pluginAuthStatus.authorized) return false; // 确保在未授权时不保存 const validationErrors = validateSettings(); if (validationErrors) { const errorHtml = validationErrors.map(err => `
❌ ${err}
`).join(''); toastr.error(`配置存在错误:${errorHtml}`, '设置未保存', { timeOut: 8000, extendedTimeOut: 0, preventDuplicates: true }); return false; } saveSettingsDebounced(); return true; } // 统一处理所有设置项变更 $('[id^="amily2_"]').on('change', function() { if (!window.pluginAuthStatus.authorized) return; // 获取设置名称(从ID转换) const settingName = this.id.replace('amily2_', ''); // 根据控件类型获取值 let value; if ($(this).is(':checkbox')) { value = $(this).prop('checked'); } else if (settingName === 'max_tokens' || settingName === 'context_messages') { value = parseInt($(this).val()); } else if (settingName === 'temperature') { value = parseFloat($(this).val()); } else { value = $(this).val(); } // 更新设置 extension_settings[extensionName][settingName] = value; // 更新显示值(如果适用) if (settingName === 'enabled') { extension_settings[extensionName].enabled = value; } else if (settingName === 'max_tokens') { $('#amily2_max_tokens_value').text(value); } else if (settingName === 'temperature') { $('#amily2_temperature_value').text(value); } else if (settingName === 'context_messages') { $('#amily2_context_messages_value').text(value); } else if (settingName === 'show_toast') { extension_settings[extensionName].suppressToast = false; } console.log(`[Amily2设置] ${settingName} 更新为:`, value); // 尝试保存,保存失败则恢复原值 if (!saveSettings()) { // 恢复原始值 const originalValue = defaultSettings[settingName]; if ($(this).is(':checkbox')) { $(this).prop('checked', originalValue); } else { $(this).val(originalValue); } // 特殊控件恢复 if (settingName === 'max_tokens') { $('#amily2_max_tokens_value').text(originalValue); } else if (settingName === 'temperature') { $('#amily2_temperature_value').text(originalValue); } else if (settingName === 'context_messages') { $('#amily2_context_messages_value').text(originalValue); } } }); // ============= 更新UI ============= function updateUI() { const authStatus = window.pluginAuthStatus; if (!authStatus.authorized) { $('#amily2_enabled').prop('checked', false); $('#amily2_enabled').prop('disabled', true); $('[id^="amily2_"]').not('#auth_input, #auth_submit').prop('disabled', true); toastr.warning('插件未授权,功能已禁用', 'Amily2号'); } else { const settings = extension_settings[extensionName]; $('#amily2_enabled').prop('disabled', false); $('[id^="amily2_"]').prop('disabled', false); $('#amily2_enabled').prop('checked', settings.enabled); $('#amily2_api_url').val(settings.apiUrl); $('#amily2_api_key').val(settings.apiKey); populateModelDropdown(); $('#amily2_max_tokens').val(settings.maxTokens); $('#amily2_max_tokens_value').text(settings.maxTokens); $('#amily2_temperature').val(settings.temperature); $('#amily2_temperature_value').text(settings.temperature); $('#amily2_context_messages').val(settings.contextMessages); $('#amily2_context_messages_value').text(settings.contextMessages); $('#amily2_main_prompt').val(settings.mainPrompt); $('#amily2_system_prompt').val(settings.systemPrompt); if ($('#amily2_show_toast').length) { $('#amily2_show_toast').prop('checked', settings.showOptimizationToast); extension_settings[extensionName].suppressToast = settings.suppressToast; } } } // 检查最新消息 async function checkLatestMessage() { const context = getContext(); const chat = context.chat || []; if (!chat || chat.length === 0) { console.log('[聊天回复检查器] 没有聊天记录'); return { message: null, previousMessages: [] }; } const latestMessage = chat[chat.length - 1]; console.log('[聊天回复检查器] 检查消息:', { isUser: latestMessage.is_user, messageLength: latestMessage.mes?.length, messagePreview: latestMessage.mes?.substring(0, 50) + '...' }); if (latestMessage.is_user) { console.log('[聊天回复检查器] 跳过用户消息'); return { message: latestMessage, previousMessages: [] }; } // 获取上下文消息(根据用户设置) const settings = extension_settings[extensionName]; const contextCount = settings.contextMessages || 2; const startIndex = Math.max(0, chat.length - contextCount - 1); const previousMessages = chat.slice(startIndex, chat.length - 1); console.log('[聊天回复检查器] 上下文设置:', { contextMessages: settings.contextMessages, contextCount: contextCount, chatLength: chat.length, startIndex: startIndex, previousMessagesCount: previousMessages.length }); console.log('[聊天回复检查器] 获取上下文消息:', { previousMessages: previousMessages.length, startIndex: startIndex }); return { message: latestMessage, previousMessages }; } // 使用API检查和修复消息(添加防止无限重试) async function checkAndFixWithAPI(latestMessage, previousMessages, isRetry = false, retryCount = 0) { const settings = extension_settings[extensionName]; if (!settings.apiUrl) { console.error('[聊天回复检查器] 未配置API URL'); return null; } // 优先使用主要提示词,如果为空则使用系统提示词 const usePrompt = settings.mainPrompt || settings.systemPrompt; if (!usePrompt) { console.error('[聊天回复检查器] 未配置主要或系统提示词'); toastr.error('请配置主要提示词或系统提示词', '聊天回复检查器'); return null; } // 构建检查内容 let checkContent = `请检查并优化以下文本:\n\n"${latestMessage.mes}"\n\n`; // 始终提供上下文参考(让AI自主判断是否需要考虑) if (previousMessages.length > 0) { checkContent += '上下文参考:\n'; const recentMessages = previousMessages.slice(-2); recentMessages.forEach((msg, index) => { const speaker = msg.is_user ? '用户' : 'AI'; checkContent += `${speaker}: "${msg.mes}"\n`; }); checkContent += '\n'; } checkContent += '请按照系统提示的格式分析并回复。'; // 构建请求消息(使用优先的提示词) const messages = [ { role: 'system', content: usePrompt }, { role: 'user', content: checkContent } ]; try { // 确保URL格式正确 let apiUrl = settings.apiUrl; // 针对不同API提供商处理URL if (apiUrl.includes('ark.cn-beijing.volces.com')) { // 火山引擎 ARK API if (!apiUrl.endsWith('/completion')) { apiUrl = apiUrl.replace(/\/completion$/, ''); if (apiUrl.endsWith('/')) { apiUrl += 'completion'; } else { apiUrl += '/completion'; } } } else if (!apiUrl.endsWith('/chat/completions')) { // 标准 OpenAI 格式 if (apiUrl.endsWith('/v1')) { apiUrl = apiUrl + '/chat/completions'; } else if (apiUrl.endsWith('/')) { apiUrl = apiUrl + 'v1/chat/completions'; } else { apiUrl = apiUrl + '/v1/chat/completions'; } } const requestBody = { model: settings.model, messages: messages, max_tokens: settings.maxTokens, temperature: settings.temperature, stream: false }; console.log('[聊天回复检查器] API请求:', { url: apiUrl, model: settings.model, messagesCount: messages.length, isRetry: isRetry, retryCount: retryCount }); const headers = { 'Content-Type': 'application/json' }; // 只有在有API Key时才添加Authorization头 if (settings.apiKey) { headers['Authorization'] = `Bearer ${settings.apiKey}`; } const response = await fetch(apiUrl, { method: 'POST', headers: headers, body: JSON.stringify(requestBody) }); if (!response.ok) { const errorText = await response.text(); console.error('[聊天回复检查器] API请求失败详情:', { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers.entries()), errorBody: errorText }); throw new Error(`API请求失败: ${response.status} ${response.statusText} - ${errorText}`); } const data = await response.json(); const apiResponse = data.choices?.[0]?.message?.content; if (!apiResponse) { console.error('[聊天回复检查器] API响应格式错误:', data); throw new Error('API返回的消息为空'); } console.log('[聊天回复检查器] API返回内容:', apiResponse); // 检查API是否返回错误信息 if (apiResponse.includes('无法生成回复') || apiResponse.includes('请尝试修改') || apiResponse.includes('内容过滤') || apiResponse.includes('违反政策')) { console.log('[聊天回复检查器] API返回错误信息', apiResponse); // 添加重试限制:最多4次 if (!isRetry && retryCount < 4) { const nextRetryCount = retryCount + 1; console.log(`[聊天回复检查器] API错误,开始第${nextRetryCount}次重试...`); toastr.info(`API优化失败,正在重试 (${nextRetryCount}/4)`, '聊天回复检查器'); // 增加延迟(随重试次数增加) await new Promise(resolve => setTimeout(resolve, 1000 * nextRetryCount)); return await checkAndFixWithAPI(latestMessage, previousMessages, true, nextRetryCount); } else { console.log('[聊天回复检查器] API重试次数已达上限,放弃优化'); toastr.warning('API优化失败已达到最大重试次数', '聊天回复检查器'); return null; } } const hasThinkTag = apiResponse.includes('think'); const hasContentTag = apiResponse.includes('content'); if (has888Tag && has666Tag) { // 匹配think标签 const thinkMatch = apiResponse.match(/([\s\S]*?)<\/think>/); // 匹配content标签 const contentMatch = apiResponse.match(/([\s\S]*?)<\/content>/); if (!thinkMatch) { console.log('[聊天回复检查器] API响应格式错误,未找到888标签'); return null; } const thinkContent = thinkMatch[1].trim(); const fixedContent = contentMatch ? contentMatch[1].trim() : ''; console.log('[聊天回复检查器] 分析结果:', thinkContent); console.log('[聊天回复检查器] 修复内容:', fixedContent); // 在界面显示分析结果(带"不再显示"选项) const settings = extension_settings[extensionName]; if (thinkContent && settings.showOptimizationToast && !settings.suppressToast) { // 构建弹窗内容 const toastContent = `
${thinkContent.substring(0, 100)}${thinkContent.length > 100 ? "..." : ""}
`; // 显示带选项的弹窗 const toast = toastr.info(toastContent, 'AI优化分析', { timeOut: 0, // 不会自动关闭 extendedTimeOut: 0, preventDuplicates: true, closeButton: true, tapToDismiss: false, onclick: null, onShown: function() { // 绑定"不再显示"复选框的事件 $('#amily2_dont_show_again').on('change', function() { if (this.checked) { // 更新设置 extension_settings[extensionName].suppressToast = true; saveSettings(); toastr.remove(toast); toastr.success('已隐藏优化通知', '设置更新'); } }); } }); } // 如果修复内容为空,则不需要修复 if (!fixedContent) { console.log('[聊天回复检查器] API判定:不需要优化'); return null; } console.log('[聊天回复检查器] API判定:需要优化'); // 返回完整的API响应,包含标签 return apiResponse; } else { // 如果没有标签格式 console.log('[聊天回复检查器] API返回普通文本格式'); // 如果返回"无需改进"类似内容,则不修复 if (apiResponse.includes('无需改进') || apiResponse.includes('不需要改进') || apiResponse.includes('质量良好') || apiResponse.includes('没有问题')) { console.log('[聊天回复检查器] API判定:不需要优化'); return null; } console.log('[聊天回复检查器] API判定:需要优化'); return apiResponse; } } catch (error) { console.error('[聊天回复检查器] API调用出错:', error); toastr.error(`API调用失败: ${error.message}`, '聊天回复检查器', {timeOut: 8000}); return null; } } // 存储已处理的消息,防止循环修复 const processedMessages = new Set(); // 处理消息接收事件(在渲染前拦截) async function onMessageReceived(data) { console.log('[聊天回复检查器] 消息接收事件触发:', { data, eventType: 'onMessageReceived' }); const settings = extension_settings[extensionName]; console.log('[聊天回复检查器] 当前设置:', { enabled: settings.enabled, hasApiUrl: !!settings.apiUrl, apiUrl: settings.apiUrl }); if (!settings.enabled) { console.log('[聊天回复检查器] 插件未启用,跳过检查'); return; } if (!settings.apiUrl) { console.log('[聊天回复检查器] 未配置API URL,跳过检查'); return; } const context = getContext(); const chat = context.chat; if (!chat || chat.length === 0) { console.log('[聊天回复检查器] 没有聊天记录'); return; } const latestMessage = chat[chat.length - 1]; // 只处理AI的回复 if (latestMessage.is_user) { console.log('[聊天回复检查器] 跳过用户消息'); return; } // 跳过第一条消息(通常是系统消息或角色介绍) if (chat.length <= 1) { console.log('[聊天回复检查器] 跳过第一条消息'); return; } // 跳过过短的消息(可能是系统消息) if (latestMessage.mes.length < 10) { console.log('[聊天回复检查器] 跳过过短的消息'); return; } // 防循环检查:为消息生成唯一标识 const messageKey = `${chat.length}-${latestMessage.mes.substring(0, 50)}`; if (processedMessages.has(messageKey)) { console.log('[聊天回复检查器] 消息已处理过,跳过检查避免循环'); return; } // 标记消息为已处理 processedMessages.add(messageKey); // 清理过期的标记(保留最近10条) if (processedMessages.size > 50) { const entries = Array.from(processedMessages); processedMessages.clear(); entries.slice(-50).forEach(id => processedMessages.add(id)); } // 获取上下文消息 const contextCount = settings.contextMessages || 2; const startIndex = Math.max(0, chat.length - 1 - contextCount); const previousMessages = chat.slice(startIndex, chat.length - 1); console.log('[聊天回复检查器] 开始检查生成的回复...'); // 使用API检查和修复(添加初始重试次数0) const fixedMessage = await checkAndFixWithAPI(latestMessage, previousMessages, false, 0); if (fixedMessage && fixedMessage !== latestMessage.mes) { console.log('[聊天回复检查器] 内容已优化,显示优化版本'); // 直接修改消息内容,不需要重新加载 latestMessage.mes = fixedMessage; console.log('[聊天回复检查器] 回复已在显示前优化'); } else { console.log('[聊天回复检查器] 内容无需优化,正常显示'); } } // 手动检查命令(使用API检查) async function checkCommand() { const settings = extension_settings[extensionName]; if (!settings.apiUrl) { toastr.error('请先配置API URL', '聊天回复检查器'); return ''; } const checkResult = await checkLatestMessage(); if (!checkResult.message) { toastr.info('没有可检查的消息', '聊天回复检查器'); return ''; } if (checkResult.message.is_user) { toastr.info('最新消息是用户消息,无需检查', '聊天回复检查器'); return ''; } toastr.info('正在使用API检查回复...', '聊天回复检查器'); // 添加重试初始参数 const fixedMessage = await checkAndFixWithAPI(checkResult.message, checkResult.previousMessages, false, 0); if (fixedMessage && fixedMessage !== checkResult.message.mes) { toastr.warning('检测到问题,建议使用修复功能', '聊天回复检查器'); } else { toastr.success('未检测到问题', '聊天回复检查器'); } return ''; } // 手动修复命令 async function fixCommand() { const settings = extension_settings[extensionName]; if (!settings.apiUrl) { toastr.error('请先配置API URL', '聊天回复检查器'); return ''; } const context = getContext(); const chat = context.chat; if (!chat || chat.length === 0) { toastr.info('没有可修复的消息', '聊天回复检查器'); return ''; } const latestMessage = chat[chat.length - 1]; if (latestMessage.is_user) { toastr.info('最新消息是用户消息,无需修复', '聊天回复检查器'); return ''; } // 获取上下文消息 const contextCount = settings.contextMessages || 2; const startIndex = Math.max(0, chat.length - 1 - contextCount); const previousMessages = chat.slice(startIndex, chat.length - 1); toastr.info('正在检查并修复回复...', '聊天回复检查器'); // 添加重试初始参数 const fixedMessage = await checkAndFixWithAPI(latestMessage, previousMessages, false, 0); if (fixedMessage && fixedMessage !== latestMessage.mes) { latestMessage.mes = fixedMessage; await saveChatConditional(); await reloadCurrentChat(); toastr.success('回复已修复', '聊天回复检查器'); } else { toastr.info('未检测到需要修复的问题', '聊天回复检查器'); } return ''; } // 测试命令(使用API测试) async function testReplyChecker() { const settings = extension_settings[extensionName]; if (!settings.apiUrl) { toastr.error('请先配置API URL', '聊天回复检查器'); return ''; } const context = getContext(); const chat = context.chat; if (!chat || chat.length < 2) { toastr.warning('需要至少2条消息才能测试', '聊天回复检查器'); return ''; } // 获取倒数第二条AI消息 let testMessage = null; for (let i = chat.length - 2; i >= 0; i--) { if (!chat[i].is_user) { testMessage = chat[i].mes; break; } } if (!testMessage) { toastr.warning('没有找到可用于测试的AI消息', '聊天回复检查器'); return ''; } const lastMessage = chat[chat.length - 1]; if (lastMessage.is_user) { toastr.warning('最后一条消息是用户消息,无法测试', '聊天回复检查器'); return ''; } // 临时修改最后一条消息来模拟重复 const originalMessage = lastMessage.mes; lastMessage.mes = testMessage + '\n\n' + testMessage; toastr.info('正在使用API测试检测功能...', '聊天回复检查器'); // 获取上下文消息 const contextCount = settings.contextMessages || 2; const startIndex = Math.max(0, chat.length - contextCount - 1); const previousMessages = chat.slice(startIndex, chat.length - 1); // 使用API检查(添加重试初始参数) const fixedMessage = await checkAndFixWithAPI(lastMessage, previousMessages, false, 0); // 恢复原始消息 lastMessage.mes = originalMessage; if (fixedMessage && fixedMessage !== (testMessage + '\n\n' + testMessage)) { toastr.success('测试成功!API检测到重复内容并提供了修复建议', '聊天回复检查器'); } else { toastr.warning('测试结果:API未检测到问题,请检查API配置或提示词', '聊天回复检查器'); } return ''; } jQuery(async () => { // 加载设置 await loadSettings(); // 添加设置面板HTML const settingsHtml = await $.get(`${extensionFolderPath}/settings.html`); $('#expiry_info').html(displayExpiryInfo()); $('#extensions_settings').append(settingsHtml); // 添加华丽的CSS样式 addAuthStyles(); // 注册激活按钮事件 $('#auth_submit').on('click', async function() { const authCode = $('#auth_input').val().trim(); if (!authCode) { toastr.error('请输入授权码', '验证失败'); return; } const success = await activatePluginAuthorization(authCode); if (success) { // 隐藏授权面板,显示功能面板 $('#auth_panel').slideUp(400); setTimeout(() => { $('.plugin-features').slideDown(400); }, 400); } }); // ============= 模型下拉菜单事件 ============= // 在API URL输入框的change事件中添加 $('#amily2_api_url').on('change', function() { const url = $(this).val(); if (url && !/^https?:\/\//.test(url)) { $(this).css('border', '2px solid #ff5252'); toastr.error('API URL必须以http://或https://开头'); } else { $(this).css('border', ''); } }); // 在Token输入框的change事件中添加 $('#amily2_max_tokens').on('change', function() { const tokens = parseInt($(this).val()); if (tokens < 100 || tokens > 20000) { $(this).siblings('label').css('color', '#ff5252'); } else { $(this).siblings('label').css('color', ''); } }); $('#amily2_model').on('change', function() { const selectedModel = $(this).val(); extension_settings[extensionName].model = selectedModel; saveSettings(); // 更新状态信息 if (selectedModel && selectedModel.length > 0) { $('#amily2_model_notes').html(`已选择模型: ${selectedModel}`); } else { $('#amily2_model_notes').html('请选择一个模型'); } }); // ============= 刷新模型按钮事件 ============= $('#amily2_refresh_models').on('click', async function() { // 添加视觉反馈 - 按钮动画 $(this).addClass('pulse'); setTimeout(() => $(this).removeClass('pulse'), 500); // 获取模型列表 await fetchSupportedModels(); // 填充下拉菜单 populateModelDropdown(); // 缓存模型列表 if (availableModels.length > 0) { localStorage.setItem('amily2_cached_models', JSON.stringify(availableModels)); } }); // 绑定设置事件(所有其他设置项) $('#amily2_enabled').on('change', function() { if (!window.pluginAuthStatus.authorized) return; extension_settings[extensionName].enabled = $(this).prop('checked'); saveSettings(); }); $('#amily2_api_url').on('input', function() { extension_settings[extensionName].apiUrl = String($(this).val()); saveSettings(); }); $('#amily2_api_key').on('input', function() { extension_settings[extensionName].apiKey = String($(this).val()); saveSettings(); }); $('#amily2_max_tokens').on('input', function() { extension_settings[extensionName].maxTokens = parseInt(String($(this).val())); $('#amily2_max_tokens_value').text(extension_settings[extensionName].maxTokens); saveSettings(); }); $('#amily2_temperature').on('input', function() { extension_settings[extensionName].temperature = parseFloat(String($(this).val())); $('#amily2_temperature_value').text(extension_settings[extensionName].temperature); saveSettings(); }); $('#amily2_context_messages').on('input', function() { const newValue = parseInt(String($(this).val()), 10); extension_settings[extensionName].contextMessages = newValue; $('#amily2_context_messages_value').text(newValue); console.log('[聊天回复检查器] 上下文消息数量已更新为:', newValue); saveSettings(); }); // 新增主要提示词事件绑定 $('#amily2_main_prompt').on('input', function() { extension_settings[extensionName].mainPrompt = $(this).val(); saveSettings(); }); // 系统提示词事件绑定 $('#amily2_system_prompt').on('input', function() { extension_settings[extensionName].systemPrompt = $(this).val(); saveSettings(); }); // 新增开关状态更新 $('#amily2_show_toast').on('change', function() { extension_settings[extensionName].showOptimizationToast = $(this).prop('checked'); saveSettings(); // 如果重新开启通知,清除抑制状态 if ($(this).prop('checked')) { extension_settings[extensionName].suppressToast = false; saveSettings(); } }); // 新增重置开关 $('#amily2_reset_toast').on('click', function() { extension_settings[extensionName].showOptimizationToast = true; extension_settings[extensionName].suppressToast = false; saveSettings(); toastr.success('通知设置已重置', 'Amily2号'); $('#amily2_show_toast').prop('checked', true); }); // 绑定测试按钮 $('#amily2_test').on('click', checkCommand); $('#amily2_fix_now').on('click', fixCommand); // 监听消息生成完成但未渲染的事件 if (!window.amily2EventsRegistered) { eventSource.on(event_types.MESSAGE_RECEIVED, onMessageReceived); eventSource.on(event_types.IMPERSONATE_READY, onMessageReceived); window.amily2EventsRegistered = true; console.log('Amily2消息事件监听已注册'); } // 注册斜杆命令 SlashCommand.registerCommand(SlashCommand.fromProps({ name: 'check-reply', callback: checkCommand, helpString: '检查最新的AI回复是否有问题', })); SlashCommand.registerCommand(SlashCommand.fromProps({ name: 'fix-reply', callback: fixCommand, helpString: '修复最新的AI回复中的问题', })); SlashCommand.registerCommand(SlashCommand.fromProps({ name: 'test-reply-checker', callback: testReplyChecker, helpString: '测试聊天回复检查器功能', })); // 更新UI updateUI(); console.log('Amily2号优化助手已加载'); }); // ============= 添加华丽CSS样式 ============= function addAuthStyles() { const style = document.createElement('style'); style.textContent = ` .flex-container { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; } #amily2_model { flex: 1; height: 42px; padding: 0 15px; background: rgba(50, 50, 75, 0.5); border: 1px solid rgba(255,255,255,0.15); color: white; border-radius: 8px; font-size: 0.95rem; appearance: auto; outline: none; transition: all 0.3s; } #amily2_model:focus { border-color: #4CAF50; box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3); } #amily2_refresh_models { height: 42px; padding: 0 15px; display: flex; align-items: center; background: linear-gradient(to right, #4CAF50, #8BC34A); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; font-size: 14px; transition: all 0.3s; justify-content: center; } #amily2_refresh_models:hover { background: linear-gradient(to right, #43A047, #7CB342); } #amily2_refresh_models:disabled { background: #9E9E9E; cursor: not-allowed; opacity: 0.7; } /* 按钮脉冲动画 */ .pulse { animation: pulseAnimation 0.6s ease; } @keyframes pulseAnimation { 0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(76, 175, 80, 0); } 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); } } /* 旋转动画 */ .fa-spinner { animation: spin 1.5s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #amily2_model_notes { margin-top: 8px; font-size: 0.85em; color: #aaa; min-height: 1.2em; } #auth_panel { background: linear-gradient(135deg, #1a237e, #4a148c); padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); color: #ffffff; position: relative; overflow: hidden; transform: perspective(1000px) rotateX(5deg); transition: all 0.5s ease; } #auth_panel:before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(circle at center, rgba(255,255,255,0.1) 0%, transparent 70%); animation: rotate 20s linear infinite; } .auth-header { position: relative; z-index: 2; text-align: center; margin-bottom: 20px; } .auth-title { font-size: 1.8rem; font-weight: 700; margin-bottom: 5px; background: linear-gradient(to right, #ff9800, #ff5722); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 2px 5px rgba(0,0,0,0.2); } .auth-subtitle { font-size: 1rem; color: #e0e0e0; margin-bottom: 15px; } .auth-code-input { position: relative; z-index: 2; display: flex; gap: 10px; margin-bottom: 15px; } #auth_input { flex: 1; padding: 15px; border-radius: 8px; background: rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 1.1rem; backdrop-filter: blur(5px); box-shadow: 0 4px 10px rgba(0,0,0,0.1); transition: all 0.3s ease; } #auth_input:focus { background: rgba(255, 255, 255, 0.25); border-color: #ff9800; outline: none; box-shadow: 0 4px 15px rgba(255,152,0,0.3); } #auth_submit { background: linear-gradient(to right, #ff9800, #ff5722); color: white; border: none; border-radius: 8px; padding: 0 25px; cursor: pointer; font-weight: 600; box-shadow: 0 4px 10px rgba(255,152,0,0.4); transition: all 0.3s ease; } #auth_submit:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(255,152,0,0.5); } .auth-footer { position: relative; z-index: 2; text-align: center; margin-top: 15px; font-size: 0.9rem; color: #bdbdbd; } .auth-status { background: rgba(0, 0, 0, 0.3); padding: 8px 15px; border-radius: 6px; font-weight: 500; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; } .valid { color: #76ff03; border-left: 3px solid #76ff03; } .expired { color: #ff5252; border-left: 3px solid #ff5252; } .plugin-features { display: none; background: rgba(30,30,46,0.8); backdrop-filter: blur(10px); border-radius: 12px; padding: 20px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); margin-bottom: 20px; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } `; document.head.appendChild(style); }