paper-burner/js/process/prompt-pool.js

1218 lines
47 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.90.1=差异很大0.9=非常相似)
* @param {string} apiModel - 使用的AI模型
* @param {string} apiKey - API密钥
* @returns {Promise<Array>} 生成的提示词变体数组
*/
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. 使用单行minifiedJSON 输出:不得换行、不得缩进。
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响应为空或不是文本');
}
// 预清理:去掉 <think>...</think>、代码块围栏、前后杂项
let text = responseText
.replace(/<think>[\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<s.length;i++){
const ch=s[i];
if(inStr){
if(esc){out+=ch;esc=false;continue;}
if(ch==='\\'){out+=ch;esc=true;continue;}
if(ch==='"'){inStr=false;out+=ch;continue;}
if(ch==='\n'||ch==='\r'){out+='\\n';continue;}
out+=ch;
}else{
if(ch==='"'){inStr=true;out+=ch;continue;}
out+=ch;
}
}
try{return JSON.parse(out);}catch{return null;}
})(body);
if (obj) {
if (Array.isArray(obj)) return {variations: obj};
if (Array.isArray(obj.variations)) return {variations: obj.variations};
if (obj.name && obj.systemPrompt && obj.userPromptTemplate) return {variations:[obj]};
}
}
}
} catch {}
// 尝试3基于 "variations" 关键词做括号配对提取
const keyIdx = text.indexOf('"variations"');
if (keyIdx !== -1) {
// 从 keyIdx 往左找到最近的 '{'
let start = text.lastIndexOf('{', keyIdx);
if (start !== -1) {
// 自 start 往右做简单括号配对(不考虑字符串内花括号的极端情况,但一般足够)
let depth = 0, end = -1;
for (let i = start; i < text.length; i++) {
const ch = text[i];
if (ch === '{') depth++;
else if (ch === '}') depth--;
if (depth === 0) { end = i; break; }
}
if (end !== -1) {
let candidate = text.slice(start, end + 1);
// 去掉可能存在的尾随逗号
candidate = candidate.replace(/,\s*(\}|\])/g, '$1');
// 去掉可能存在的行内注释(简单处理)
candidate = candidate.replace(/(^|\n)\s*\/\/.*(?=\n|$)/g, '$1');
try { return JSON.parse(candidate); } catch {}
}
}
}
// 尝试4宽泛匹配第一个 {...} 快,做配对
{
const first = text.indexOf('{');
const last = text.lastIndexOf('}');
if (first !== -1 && last !== -1 && last > 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;
}