// process/prompt-pool.js // 翻译提示词池管理系统 - AI生成版本 /** * 翻译提示词池管理器 - AI生成版本 + 智能健康管理 */ class TranslationPromptPool { constructor() { this.storageKey = 'paperBurnerPromptPool'; this.healthConfigKey = 'paperBurnerPromptHealthConfig'; this.defaultGenerationCount = 10; this.promptPool = this.loadPromptPool(); this.healthConfig = this.loadHealthConfig(); this.activeRequestsQueue = new Map(); // 跟踪正在进行的请求 // 启动健康监控和复活机制 this.startHealthMonitoring(); // 一次性迁移:如果历史数据只有使用次数,没有请求统计,则将其回填为成功请求 this.migrateUsageToRequestsIfNeeded(); // 后端模式:异步从后端同步提示词池与健康配置(不阻塞) try { if (typeof window !== 'undefined' && window.storageAdapter && window.storageAdapter.isFrontendMode === false && typeof window.storageAdapter.loadPromptPool === 'function') { this._syncFromBackend(); } } catch (e) { console.warn('[PromptPool] 初始化后端同步失败(忽略):', e); } } /** * 加载健康管理配置 */ loadHealthConfig() { try { const stored = localStorage.getItem(this.healthConfigKey); const defaultConfig = { maxConsecutiveFailures: 2, // 最大连续失败次数 deactivationEnabled: true, // 是否启用失活机制 resurrectionTimeMinutes: 15, // 复活时间(分钟) resurrectionEnabled: true, // 是否启用自动复活 switchOnFailure: true, // 失败时是否自动切换 queueManagementEnabled: true // 是否启用队列管理 }; return stored ? { ...defaultConfig, ...JSON.parse(stored) } : defaultConfig; } catch (error) { console.error('Failed to load health config:', error); return { maxConsecutiveFailures: 2, deactivationEnabled: true, resurrectionTimeMinutes: 15, resurrectionEnabled: true, switchOnFailure: true, queueManagementEnabled: true }; } } /** * 保存健康管理配置 */ saveHealthConfig() { try { this._persistLocalOnly(); this._persistToBackend(); this._notifyUpdated(); } catch (error) { console.error('Failed to save health config:', error); } } /** * 启动健康监控定时器 */ startHealthMonitoring() { // 每分钟检查一次复活条件 setInterval(() => { if (this.healthConfig.resurrectionEnabled) { this.checkForResurrection(); } }, 60000); // 1分钟 } /** * 从本地存储加载提示词池 */ loadPromptPool() { try { const stored = localStorage.getItem(this.storageKey); if (!stored) return []; const parsed = JSON.parse(stored); if (!Array.isArray(parsed)) return []; const dedupedMap = new Map(); parsed.forEach(item => { if (!item || (!item.systemPrompt && !item.userPromptTemplate)) return; const key = this.getVariationKey(item); if (!key) return; if (!dedupedMap.has(key)) { dedupedMap.set(key, item); return; } const existing = dedupedMap.get(key); const existingUsage = existing?.usage_count || 0; const candidateUsage = item?.usage_count || 0; const existingRequests = existing?.healthStatus?.totalRequests || 0; const candidateRequests = item?.healthStatus?.totalRequests || 0; if (candidateUsage > existingUsage || (candidateUsage === existingUsage && candidateRequests > existingRequests)) { dedupedMap.set(key, item); } }); const deduped = Array.from(dedupedMap.values()); if (deduped.length !== parsed.length) { console.info('[PromptPool] 去重提示词条目:', parsed.length - deduped.length); try { localStorage.setItem(this.storageKey, JSON.stringify(deduped)); } catch (persistError) { console.warn('[PromptPool] 去重结果写回失败:', persistError); } } return deduped; } catch (error) { console.error('Failed to load prompt pool:', error); return []; } } /** * 保存提示词池到本地存储 */ savePromptPool() { try { this._persistLocalOnly(); this._persistToBackend(); // fire-and-forget this._notifyUpdated(); } catch (error) { console.error('Failed to save prompt pool:', error); } } /** * 迁移历史统计:当 totalRequests=0 且 success/failure 均为0,但 usage_count>0 时, * 将 usage_count 视为历史成功请求数进行回填,避免出现“使用>0,请求=0”的误导显示。 */ migrateUsageToRequestsIfNeeded() { let changed = false; try { this.promptPool.forEach(p => { const h = p.healthStatus; if (!h) return; if ((h.totalRequests || 0) === 0 && (h.successCount || 0) === 0 && (h.failureCount || 0) === 0 && (p.usage_count || 0) > 0) { h.totalRequests = p.usage_count; h.successCount = p.usage_count; changed = true; } }); if (changed) this.savePromptPool(); } catch (e) { console.warn('migrateUsageToRequestsIfNeeded failed:', e); } } /** * 使用AI生成提示词变体 * @param {string} referenceSystemPrompt - 参考的系统提示词 * @param {string} referenceUserPrompt - 参考的用户提示词 * @param {number} count - 生成数量,默认10个 * @param {number} similarity - 相似度控制 (0.1-0.9,0.1=差异很大,0.9=非常相似) * @param {string} apiModel - 使用的AI模型 * @param {string} apiKey - API密钥 * @returns {Promise} 生成的提示词变体数组 */ async generateVariationsWithAI(referenceSystemPrompt, referenceUserPrompt, count = this.defaultGenerationCount, similarity = 0.7, apiModel = 'deepseek', apiKey, options = {}) { if (!apiKey) { throw new Error('需要提供API密钥来生成提示词变体'); } // 构建AI生成提示词的系统提示(风格锁定:仅改写措辞/语序,不改变风格与约束) let aiSystemPrompt = options.generatorSystemPrompt || `你是资深提示词工程师,负责在不改变风格与约束的前提下,生成同风格的改写变体。 任务:基于参考提示词,生成 ${count} 个“同风格、同约束、同输出要求”的翻译提示词变体;仅在措辞、语序、句式、连接词与段落组织上做改写,以提升稳定性与一致性。 强制要求: 1. 返回 JSON,包含字段 variations(数组)。 2. 每个变体包含:name(名称)、systemPrompt(系统提示)、userPromptTemplate(用户提示模板)、description(简要说明)。 3. 相似度控制:${similarity}(${this.getSimilarityDescription(similarity)})。注意:风格与约束必须完全一致,仅体现措辞与语序差异。 4. userPromptTemplate 必须且仅出现一次占位符:\${targetLangName} 与 \${content}。 5. 严禁改变参考提示词的风格、语气、规则、术语偏好与输出格式要求;严禁引入“学术/商务/口语/文学”等风格标签的切换或暗示。 6. 允许调整表述顺序、同义替换与句式变化,但要保持语义与约束等价。 7. 仅输出严格的 JSON 对象(不包含 Markdown 代码块、注释或额外文本)。 8. 使用单行(minified)JSON 输出:不得换行、不得缩进。 JSON 格式示例: { "variations": [ { "name": "同风格改写-1", "systemPrompt": "(与参考风格一致,仅改写措辞/语序)你是专业翻译助手...", "userPromptTemplate": "请将以下内容翻译为\${targetLangName}:\\n\\n\${content}", "description": "风格锁定;仅做同义改写与语序调整" } ] }`; let aiUserPrompt = options.generatorUserPrompt || `参考提示词(须保持风格/规则/输出要求一致): **系统提示:** ${referenceSystemPrompt} **用户提示模板:** ${referenceUserPrompt} 请基于以上参考提示词生成 ${count} 个“同风格改写”变体:保持风格、语气、规则与输出要求不变,仅做措辞/语序/句式的等价改写。相似度:${similarity}(${this.getSimilarityDescription(similarity)})。 每个变体必须包含 systemPrompt 与 userPromptTemplate 两个字段;并确保 userPromptTemplate 都包含且仅包含一次 \${targetLangName} 与 \${content} 占位符。严格输出为单行 JSON(无任何额外文字/提示/代码块)。`; try { // 调用AI API生成变体 const aiResponse = await this.callAIForGeneration(aiSystemPrompt, aiUserPrompt, apiModel, apiKey); // 解析AI返回的JSON const parsedResponse = this.parseAIResponse(aiResponse); // 验证和处理生成的变体 const processedVariations = this.processGeneratedVariations(parsedResponse.variations); return processedVariations; } catch (error) { console.error('AI生成提示词变体失败:', error); throw new Error(`生成提示词变体失败: ${error.message}`); } } /** * 获取相似度描述 */ getSimilarityDescription(similarity) { // 在风格锁定前提下,对“措辞/语序”的差异程度描述 if (similarity <= 0.3) return '改写幅度很大(语序与措辞差异显著)'; if (similarity <= 0.5) return '改写幅度中等(保持等价约束,表达有明显变化)'; if (similarity <= 0.7) return '改写幅度适中(整体表达有所变化)'; return '改写幅度较小(细微措辞/顺序调整)'; } /** * 调用AI API生成变体(复用现有的翻译API调用逻辑) */ async callAIForGeneration(systemPrompt, userPrompt, apiModel, apiKey) { // 统一从 translation.js 的构建逻辑获取配置,避免端点不一致 if (typeof callTranslationApi !== 'function') { throw new Error('callTranslationApi函数不可用,请确保 api.js 已加载'); } // 优先使用独立的生成配置构建器 let apiConfig; if (typeof window !== 'undefined' && typeof window.buildPromptPoolGenerationConfig === 'function') { apiConfig = window.buildPromptPoolGenerationConfig(apiModel, apiKey); } else if (typeof processModule !== 'undefined' && typeof processModule.buildCustomApiConfig === 'function') { // 回退(自定义站点):尽量使用 buildCustomApiConfig 补全端点 if (apiModel.includes(':')) { const separatorIndex = apiModel.indexOf(':'); const siteId = apiModel.slice(0, separatorIndex); const modelId = apiModel.slice(separatorIndex + 1); const allSites = typeof loadAllCustomSourceSites === 'function' ? loadAllCustomSourceSites() : {}; const site = allSites[siteId]; if (!site) throw new Error(`未找到源站点配置:${siteId}`); apiConfig = processModule.buildCustomApiConfig( apiKey, site.apiEndpoint || site.apiBaseUrl, modelId || site.modelId, site.requestFormat || 'openai', site.temperature !== undefined ? site.temperature : 0.5, site.max_tokens !== undefined ? site.max_tokens : 8000, { endpointMode: site.endpointMode || 'auto' } ); } else { throw new Error('未能构建预设模型的配置,请检查脚本加载顺序'); } } else { throw new Error('缺少构建 API 配置的函数,请检查脚本加载顺序'); } const requestBody = apiConfig.bodyBuilder ? apiConfig.bodyBuilder(systemPrompt, userPrompt) : { model: apiConfig.modelName, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt } ], temperature: 0.8, max_tokens: 4000 }; try { const result = await callTranslationApi(apiConfig, requestBody); return result; } catch (error) { throw new Error(`API调用失败: ${error.message}`); } } /** * 构建生成API配置(集成现有的模型管理系统) */ buildGenerationApiConfig(apiModel, apiKey) { // 已由独立模块统一实现,保留兼容入口 if (typeof window !== 'undefined' && typeof window.buildPromptPoolGenerationConfig === 'function') { return window.buildPromptPoolGenerationConfig(apiModel, apiKey); } throw new Error('构建API配置失败:缺少 buildPromptPoolGenerationConfig'); } /** * 解析AI返回的JSON响应 */ parseAIResponse(responseText) { if (!responseText || typeof responseText !== 'string') { throw new Error('AI响应为空或不是文本'); } // 预清理:去掉 ...、代码块围栏、前后杂项 let text = responseText .replace(/[\s\S]*?<\/think>/gi, '') .replace(/```json/gi, '```') .trim(); // 尝试1:直接 JSON.parse(兼容单行/多行) try { return JSON.parse(text); } catch {} // 尝试2:```json 或 ``` 包裹的内容(逐块尝试) try { const blocks = text.match(/```\s*[\s\S]*?```/g); if (blocks) { for (const b of blocks) { const body = b.replace(/```/g, '').trim(); // 允许裸换行修复 const obj = (function(raw){ let s = raw.replace(/,\s*(\}|\])/g, '$1'); let out = '', inStr=false, esc=false; for (let i=0;i first) { let candidate = text.slice(first, last + 1); candidate = candidate.replace(/,\s*(\}|\])/g, '$1'); try { return JSON.parse(candidate); } catch {} } } // 尝试5:如果文本中包含有效的 JSON 片段行,拼接重尝试(保守) try { const lines = text.split(/\n|\r/).map(l => l.trim()); // 仅保留可能是 JSON 的行 const filtered = lines.filter(l => /^[\[\]{},:\"\w\s\-\$]/.test(l)).join(''); if (filtered.includes('{') && filtered.includes('}')) { const cleaned = filtered.replace(/,\s*(\}|\])/g, '$1'); const obj = JSON.parse(cleaned); const norm = (function(o){ if (!o) return null; if (Array.isArray(o)) return {variations:o}; if (Array.isArray(o.variations)) return {variations:o.variations}; if (o.name && o.systemPrompt && o.userPromptTemplate) return {variations:[o]}; return null; })(obj); if (norm) return norm; } } catch {} console.error('解析AI响应失败:', responseText); throw new Error('AI返回的不是有效的JSON格式'); } /** * 处理和验证生成的变体 */ processGeneratedVariations(variations) { if (!Array.isArray(variations)) { throw new Error('AI返回的variations不是数组格式'); } const processedVariations = []; const baseId = Date.now(); variations.forEach((variation, index) => { try { // 验证必要字段 if (!variation.name || !variation.systemPrompt || !variation.userPromptTemplate) { console.warn(`跳过无效变体 ${index}:`, variation); return; } // 占位符修复策略 let userTemplate = variation.userPromptTemplate || ''; let targetLangCount = (userTemplate.match(/\$\{targetLangName\}/g) || []).length; let contentCount = (userTemplate.match(/\$\{content\}/g) || []).length; if (targetLangCount === 0) { userTemplate = `所需语言:\${targetLangName}\n` + userTemplate; targetLangCount = 1; } if (contentCount === 0) { userTemplate = userTemplate.replace(/\s*$/, '') + `\n\n需要翻译的内容:\${content}`; contentCount = 1; } variation.userPromptTemplate = userTemplate; // 构建标准变体对象 const processedVariation = { id: `${baseId}_${index}`, name: variation.name || `同风格改写-${index+1}`, systemPrompt: variation.systemPrompt, userPromptTemplate: variation.userPromptTemplate, description: variation.description || '', // 固定为通用类别,避免引入“学术/商务/口语”等风格标签 category: 'general', tags: this.generateTags(variation.name, variation.description || ''), created_at: new Date().toISOString(), usage_count: 0, isActive: false, userSelected: null, aiGenerated: true, // 健康状态信息 healthStatus: { status: 'healthy', // healthy, degraded, failed, deactivated totalRequests: 0, successCount: 0, failureCount: 0, consecutiveFailures: 0, lastUsed: null, lastSuccess: null, lastFailure: null, deactivatedAt: null, deactivationReason: null, requestHistory: [], // 最近20次请求的记录 averageResponseTime: 0 } }; processedVariations.push(processedVariation); } catch (error) { console.warn(`处理变体 ${index} 时出错:`, error); } }); if (processedVariations.length === 0) { throw new Error('没有生成有效的提示词变体'); } return processedVariations; } /** * 生成用于去重的提示词键 */ getVariationKey(prompt) { if (!prompt) return ''; const system = typeof prompt.systemPrompt === 'string' ? prompt.systemPrompt.trim() : ''; const user = typeof prompt.userPromptTemplate === 'string' ? prompt.userPromptTemplate.trim() : ''; if (!system && !user) return ''; return `${system}||${user}`; } /** * 推断变体类别 */ inferCategory(name, description) { const text = `${name} ${description}`.toLowerCase(); if (text.includes('学术') || text.includes('论文') || text.includes('研究')) return 'academic'; if (text.includes('技术') || text.includes('专业') || text.includes('工程')) return 'technical'; if (text.includes('商务') || text.includes('正式') || text.includes('商业')) return 'business'; if (text.includes('通俗') || text.includes('口语') || text.includes('简单')) return 'casual'; if (text.includes('文学') || text.includes('创意') || text.includes('艺术')) return 'literary'; return 'general'; } /** * 生成标签 */ generateTags(name, description) { const text = `${name} ${description}`.toLowerCase(); const tags = []; if (text.includes('准确') || text.includes('精确')) tags.push('准确'); if (text.includes('详细') || text.includes('全面')) tags.push('详细'); if (text.includes('简洁') || text.includes('简单')) tags.push('简洁'); if (text.includes('专业') || text.includes('权威')) tags.push('专业'); if (text.includes('创意') || text.includes('创新')) tags.push('创意'); if (text.includes('AI生成')) tags.push('AI生成'); return tags.length > 0 ? tags : ['AI生成']; } /** * 记录提示词使用结果 * @param {string} promptId - 提示词ID * @param {boolean} success - 是否成功 * @param {number} responseTime - 响应时间(ms) * @param {string} error - 错误信息(如果失败) * @param {Object} requestInfo - 请求相关信息 */ recordPromptUsage(promptId, success, responseTime = 0, error = null, requestInfo = {}) { const promptIndex = this.promptPool.findIndex(p => p.id === promptId); if (promptIndex === -1) return; const prompt = this.promptPool[promptIndex]; const now = new Date().toISOString(); // 确保healthStatus存在 if (!prompt.healthStatus) { prompt.healthStatus = { status: 'healthy', totalRequests: 0, successCount: 0, failureCount: 0, consecutiveFailures: 0, lastUsed: null, lastSuccess: null, lastFailure: null, deactivatedAt: null, deactivationReason: null, requestHistory: [], averageResponseTime: 0 }; } const health = prompt.healthStatus; // 更新基础统计 health.totalRequests++; health.lastUsed = now; // 创建请求记录 const requestRecord = { timestamp: now, success: success, responseTime: responseTime, error: error, consecutiveFailureCount: health.consecutiveFailures, ...requestInfo }; // 添加到历史记录(保持最近20条) health.requestHistory.unshift(requestRecord); if (health.requestHistory.length > 20) { health.requestHistory = health.requestHistory.slice(0, 20); } if (success) { health.successCount++; health.consecutiveFailures = 0; health.lastSuccess = now; // 更新平均响应时间 health.averageResponseTime = this.calculateAverageResponseTime(health.requestHistory); // 如果之前处于降级状态,考虑恢复 if (health.status === 'degraded') { health.status = 'healthy'; console.log(`[PromptPool] 提示词 ${prompt.name} 恢复健康状态`); } } else { health.failureCount++; health.consecutiveFailures++; health.lastFailure = now; console.warn(`[PromptPool] 提示词 ${prompt.name} 失败,连续失败次数: ${health.consecutiveFailures}`); // 更新健康状态 this.updatePromptHealthStatus(promptId); // 如果启用了切换机制,处理队列替换 if (this.healthConfig.switchOnFailure && this.healthConfig.queueManagementEnabled) { this.handleQueueReplacement(promptId); } // 会话锁场景:若当前会话锁定提示词即为失败项,则打破锁,允许后续任务挑选新提示词 if (typeof window !== 'undefined' && window.promptPoolUI && typeof window.promptPoolUI.breakSessionLockIfMatches === 'function') { window.promptPoolUI.breakSessionLockIfMatches(promptId); } } this.savePromptPool(); // 通知UI更新(如果存在) if (typeof window !== 'undefined' && window.promptPoolUI && window.promptPoolUI.updateHealthDisplay) { window.promptPoolUI.updateHealthDisplay(promptId); } } /** * 更新提示词健康状态 */ updatePromptHealthStatus(promptId) { const prompt = this.promptPool.find(p => p.id === promptId); if (!prompt || !prompt.healthStatus) return; const health = prompt.healthStatus; const config = this.healthConfig; // 检查是否需要失活 if (config.deactivationEnabled && health.consecutiveFailures >= config.maxConsecutiveFailures) { health.status = 'deactivated'; health.deactivatedAt = new Date().toISOString(); health.deactivationReason = `连续失败${health.consecutiveFailures}次`; prompt.isActive = false; console.warn(`[PromptPool] 提示词 ${prompt.name} 已失活: ${health.deactivationReason}`); // 触发UI通知 this.notifyPromptDeactivated(prompt); } else if (health.consecutiveFailures >= Math.floor(config.maxConsecutiveFailures / 2)) { // 进入降级状态 health.status = 'degraded'; console.warn(`[PromptPool] 提示词 ${prompt.name} 进入降级状态`); } } /** * 处理队列替换逻辑 */ handleQueueReplacement(failedPromptId) { try { // 获取使用失败提示词的待处理请求 const pendingRequests = this.activeRequestsQueue.get(failedPromptId) || []; if (pendingRequests.length > 0) { // 选择新的提示词 const newPrompt = this.selectHealthyPrompt(failedPromptId); if (newPrompt) { console.log(`[PromptPool] 替换队列中的 ${pendingRequests.length} 个请求,从 ${failedPromptId} 到 ${newPrompt.id}`); // 将请求转移到新提示词 pendingRequests.forEach(request => { request.promptId = newPrompt.id; request.prompt = newPrompt; request.replacedFrom = failedPromptId; request.replacedAt = new Date().toISOString(); }); // 更新队列 this.activeRequestsQueue.set(newPrompt.id, (this.activeRequestsQueue.get(newPrompt.id) || []).concat(pendingRequests)); this.activeRequestsQueue.delete(failedPromptId); } else { console.warn(`[PromptPool] 没有可用的健康提示词来替换失败的 ${failedPromptId}`); } } } catch (error) { console.error('[PromptPool] 队列替换失败:', error); } } /** * 将请求入队(在任务开始前调用)。仅记录待开始的请求,便于失败时迁移。 * @param {string} promptId * @param {{requestId:string, model?:string, meta?:Object}} request */ enqueueRequest(promptId, request) { try { const arr = this.activeRequestsQueue.get(promptId) || []; arr.push({ ...request, enqueuedAt: Date.now() }); this.activeRequestsQueue.set(promptId, arr); } catch (e) { console.warn('enqueueRequest failed:', e); } } /** * 请求出队(在任务开始执行时或完成时调用,防止被当作待迁移)。 * @param {string} promptId * @param {string} requestId */ dequeueRequest(promptId, requestId) { try { const arr = this.activeRequestsQueue.get(promptId) || []; const next = arr.filter(r => r.requestId !== requestId); if (next.length === 0) this.activeRequestsQueue.delete(promptId); else this.activeRequestsQueue.set(promptId, next); } catch (e) { console.warn('dequeueRequest failed:', e); } } /** * 选择健康的提示词 */ selectHealthyPrompt(excludePromptId = null) { const healthyPrompts = this.promptPool.filter(prompt => prompt.isActive && prompt.userSelected === true && prompt.id !== excludePromptId && prompt.healthStatus && ['healthy', 'degraded'].includes(prompt.healthStatus.status) ); if (healthyPrompts.length === 0) return null; // 优先选择完全健康的 const fullyHealthy = healthyPrompts.filter(p => p.healthStatus.status === 'healthy'); if (fullyHealthy.length > 0) { // 按成功率排序 fullyHealthy.sort((a, b) => { const aSuccessRate = a.healthStatus.totalRequests > 0 ? a.healthStatus.successCount / a.healthStatus.totalRequests : 1; const bSuccessRate = b.healthStatus.totalRequests > 0 ? b.healthStatus.successCount / b.healthStatus.totalRequests : 1; return bSuccessRate - aSuccessRate; }); return fullyHealthy[0]; } // 如果没有完全健康的,选择降级状态中最好的 healthyPrompts.sort((a, b) => b.healthStatus.successCount - a.healthStatus.successCount); return healthyPrompts[0]; } /** * 检查复活条件 */ checkForResurrection() { const now = new Date(); const resurrectionTimeMs = this.healthConfig.resurrectionTimeMinutes * 60 * 1000; const deactivatedPrompts = this.promptPool.filter(prompt => prompt.healthStatus && prompt.healthStatus.status === 'deactivated' && prompt.healthStatus.deactivatedAt ); deactivatedPrompts.forEach(prompt => { const deactivatedAt = new Date(prompt.healthStatus.deactivatedAt); const timeSinceDeactivation = now - deactivatedAt; if (timeSinceDeactivation >= resurrectionTimeMs) { this.resurrectPrompt(prompt.id); } }); } /** * 复活提示词 */ resurrectPrompt(promptId) { const prompt = this.promptPool.find(p => p.id === promptId); if (!prompt || !prompt.healthStatus) return; const health = prompt.healthStatus; // 重置健康状态 health.status = 'healthy'; health.consecutiveFailures = 0; health.deactivatedAt = null; health.deactivationReason = null; // 如果用户之前选择了这个提示词,重新激活 if (prompt.userSelected === true) { prompt.isActive = true; } console.log(`[PromptPool] 提示词 ${prompt.name} 已自动复活`); this.savePromptPool(); // 通知UI this.notifyPromptResurrected(prompt); } /** * 计算平均响应时间 */ calculateAverageResponseTime(history) { if (!history || history.length === 0) return 0; const validTimes = history.filter(record => record.success && record.responseTime > 0); if (validTimes.length === 0) return 0; const totalTime = validTimes.reduce((sum, record) => sum + record.responseTime, 0); return Math.round(totalTime / validTimes.length); } /** * 通知UI提示词失活 */ notifyPromptDeactivated(prompt) { if (typeof window !== 'undefined' && window.promptPoolUI && window.promptPoolUI.showNotification) { window.promptPoolUI.showNotification( `提示词"${prompt.name}"因连续失败已自动失活`, 'warning' ); } } /** * 通知UI提示词复活 */ notifyPromptResurrected(prompt) { if (typeof window !== 'undefined' && window.promptPoolUI && window.promptPoolUI.showNotification) { window.promptPoolUI.showNotification( `提示词"${prompt.name}"已自动复活`, 'success' ); } } /** * 批量添加变体到提示词池 */ addVariationsToPool(variations) { if (!Array.isArray(variations) || variations.length === 0) { return; } const existingKeys = new Set(this.promptPool.map(item => this.getVariationKey(item)).filter(Boolean)); const newItems = []; for (const variation of variations) { if (!variation) continue; const key = this.getVariationKey(variation); if (!key || existingKeys.has(key)) continue; existingKeys.add(key); newItems.push(variation); } if (newItems.length === 0) { return; } this.promptPool.push(...newItems); this.savePromptPool(); } /** * 更新提示词项目 */ updatePromptItem(id, updates) { const index = this.promptPool.findIndex(item => item.id === id); if (index !== -1) { this.promptPool[index] = { ...this.promptPool[index], ...updates }; this.savePromptPool(); return true; } return false; } /** * 批量更新提示词项目 * @param {string[]} ids * @param {Object} updates */ updatePromptItems(ids, updates) { let changed = false; this.promptPool = this.promptPool.map(item => { if (ids.includes(item.id)) { changed = true; return { ...item, ...updates }; } return item; }); if (changed) this.savePromptPool(); return changed; } /** * 批量复活提示词 * @param {string[]} ids */ resurrectPrompts(ids) { ids.forEach(id => this.resurrectPrompt(id)); } /** * 删除提示词项目 */ deletePromptItem(id) { const index = this.promptPool.findIndex(item => item.id === id); if (index !== -1) { this.promptPool.splice(index, 1); this.savePromptPool(); return true; } return false; } /** * 获取激活的提示词列表(健康状态感知) */ getActivePrompts() { return this.promptPool.filter(item => item.isActive && item.userSelected === true && item.healthStatus && ['healthy', 'degraded'].includes(item.healthStatus.status) ); } /** * 获取所有提示词 */ getAllPrompts() { return this.promptPool; } /** * 清空提示词池 */ clearPool() { this.promptPool = []; this.savePromptPool(); } /** * 智能随机选择一个激活的提示词(带健康管理) */ getRandomActivePrompt() { const activePrompts = this.getActivePrompts(); if (activePrompts.length === 0) return null; // 按健康状态和成功率权重选择 const weightedPrompts = this.calculatePromptWeights(activePrompts); const selectedPrompt = this.weightedRandomSelect(weightedPrompts); if (selectedPrompt) { // 更新使用次数 this.updatePromptItem(selectedPrompt.id, { usage_count: selectedPrompt.usage_count + 1 }); // 记录选择到队列(用于失败时的队列管理) this.recordPromptSelection(selectedPrompt.id); } return selectedPrompt; } /** * 按轮换方式选择激活的提示词(带健康管理) */ getRotationActivePrompt() { const activePrompts = this.getActivePrompts(); if (activePrompts.length === 0) return null; // 优先选择健康状态最好的,使用次数最少的 activePrompts.sort((a, b) => { // 首先按健康状态排序 const healthPriority = { 'healthy': 0, 'degraded': 1 }; const aPriority = healthPriority[a.healthStatus.status] || 2; const bPriority = healthPriority[b.healthStatus.status] || 2; if (aPriority !== bPriority) { return aPriority - bPriority; } // 然后按使用次数排序 return a.usage_count - b.usage_count; }); const selectedPrompt = activePrompts[0]; // 更新使用次数 this.updatePromptItem(selectedPrompt.id, { usage_count: selectedPrompt.usage_count + 1 }); // 记录选择到队列 this.recordPromptSelection(selectedPrompt.id); return selectedPrompt; } /** * 计算提示词的权重(基于健康状态和成功率) */ calculatePromptWeights(prompts) { return prompts.map(prompt => { let weight = 1; if (prompt.healthStatus) { const health = prompt.healthStatus; // 健康状态权重 if (health.status === 'healthy') { weight *= 1.0; } else if (health.status === 'degraded') { weight *= 0.5; // 降级状态降低权重 } // 成功率权重 if (health.totalRequests > 0) { const successRate = health.successCount / health.totalRequests; weight *= (0.5 + successRate); // 0.5-1.5范围 } // 响应时间权重(响应时间越短权重越高) if (health.averageResponseTime > 0) { const timeWeight = Math.max(0.1, 1 - (health.averageResponseTime / 10000)); // 10秒为基准 weight *= timeWeight; } } return { prompt, weight: Math.max(0.1, weight) }; }); } /** * 权重随机选择 */ weightedRandomSelect(weightedPrompts) { if (weightedPrompts.length === 0) return null; if (weightedPrompts.length === 1) return weightedPrompts[0].prompt; const totalWeight = weightedPrompts.reduce((sum, item) => sum + item.weight, 0); let random = Math.random() * totalWeight; for (const item of weightedPrompts) { random -= item.weight; if (random <= 0) { return item.prompt; } } // 回退到最后一个 return weightedPrompts[weightedPrompts.length - 1].prompt; } /** * 记录提示词选择(用于队列管理) */ recordPromptSelection(promptId) { // 这里可以记录提示词被选择用于某个翻译任务 // 在实际翻译失败时,可以使用这些信息进行队列替换 } /** * 获取健康管理配置 */ getHealthConfig() { return this.healthConfig; } /** * 更新健康管理配置 */ updateHealthConfig(newConfig) { this.healthConfig = { ...this.healthConfig, ...newConfig }; this.saveHealthConfig(); } /** * 获取提示词健康统计 */ getHealthStats() { const stats = { total: this.promptPool.length, active: 0, healthy: 0, degraded: 0, deactivated: 0, totalRequests: 0, totalSuccesses: 0, totalFailures: 0, averageSuccessRate: 0 }; this.promptPool.forEach(prompt => { if (prompt.isActive && prompt.userSelected === true) { stats.active++; } if (prompt.healthStatus) { const health = prompt.healthStatus; switch (health.status) { case 'healthy': stats.healthy++; break; case 'degraded': stats.degraded++; break; case 'deactivated': stats.deactivated++; break; } stats.totalRequests += health.totalRequests; stats.totalSuccesses += health.successCount; stats.totalFailures += health.failureCount; } }); stats.averageSuccessRate = stats.totalRequests > 0 ? (stats.totalSuccesses / stats.totalRequests) : 0; return stats; } // ==================== 持久化与后端同步 ==================== _persistLocalOnly() { try { localStorage.setItem(this.storageKey, JSON.stringify(this.promptPool)); localStorage.setItem(this.healthConfigKey, JSON.stringify(this.healthConfig)); } catch (e) { console.warn('[PromptPool] 本地持久化失败(忽略):', e); } } async _persistToBackend() { try { if (typeof window === 'undefined') return; const sa = window.storageAdapter; if (!sa || sa.isFrontendMode !== false || typeof sa.savePromptPool !== 'function') return; await sa.savePromptPool({ prompts: this.promptPool, healthConfig: this.healthConfig }); } catch (e) { console.warn('[PromptPool] 后端保存失败(忽略):', e?.message || e); } } async _syncFromBackend() { try { const sa = window.storageAdapter; const data = await sa.loadPromptPool(); if (data && Array.isArray(data.prompts)) { this.promptPool = data.prompts; } if (data && data.healthConfig) { this.healthConfig = { ...this.healthConfig, ...data.healthConfig }; } this._persistLocalOnly(); this._notifyUpdated(); console.log('[PromptPool] 已从后端同步最新提示词池'); } catch (e) { console.warn('[PromptPool] 拉取后端提示词池失败(忽略):', e?.message || e); } } _notifyUpdated() { try { if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('pb:prompt-pool-updated')); } } catch {} } } // 全局实例 if (typeof window !== 'undefined') { window.translationPromptPool = new TranslationPromptPool(); } // 将类添加到processModule对象 if (typeof processModule !== 'undefined') { processModule.TranslationPromptPool = TranslationPromptPool; }