import axios from 'axios'; import config from '../../config/index.js'; /** * AI平台接口类 * 定义了所有AI平台需要实现的方法 */ class AIProvider { /** * 生成文章 * @param {string} content - 生成文章的提示内容 * @returns {Promise} - 返回生成的文章JSON字符串 */ async generateArticle(content) { throw new Error('Method not implemented'); } } /** * 火山云AI平台实现 */ class VolcesAIProvider extends AIProvider { /** * 创建火山云AI提供者实例 * @param {string} version - 版本号,如'1.5'或'1.6' */ constructor(version = '1-5') { super(); // 根据版本选择对应的API密钥和模型 const versionConfig = { '1-5': { apikey: config.huoshancloud.apikeyHLR, model: "doubao-1-5-pro-32k-250115" }, '1-6': { apikey: config.huoshancloud.apikeyHLR, model: "doubao-seed-1-6-250615" }, }; // 获取当前版本的配置,如果版本不存在则使用1.5版本 const currentConfig = versionConfig[version] || versionConfig['1-5']; this.version = version; this.headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36', "Authorization": "Bearer " + currentConfig.apikey, "Content-Type": "application/json" }; this.url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"; this.model = currentConfig.model; } async generateArticle(content) { const postJSON = { "model": this.model, "messages": [ { "role": "user", "content": content, } ] }; try { console.log(`火山云${this.version}`); const response = await axios.post(encodeURI(this.url), postJSON, { headers: this.headers }); return response.data.choices[0].message.content; } catch (error) { console.error("VolcesAI API error:", error); throw error; } } } // 为了保持向后兼容性,创建1.5和1.6版本的类别名 class VolcesAIProvider1_5 extends VolcesAIProvider { constructor() { super('1-5'); } } class VolcesAIProvider1_6 extends VolcesAIProvider { constructor() { super('1-6'); } } /** * OpenAI平台实现 */ class OpenAIProvider extends AIProvider { constructor() { super(); this.headers = { "Authorization": "Bearer " + config.openai.apikey, "Content-Type": "application/json" }; this.url = "https://api.openai.com/v1/chat/completions"; this.model = "gpt-3.5-turbo"; } async generateArticle(content) { const postJSON = { "model": this.model, "messages": [ { "role": "user", "content": content, } ] }; try { const response = await axios.post(this.url, postJSON, { headers: this.headers }); return response.data.choices[0].message.content; } catch (error) { console.error("OpenAI API error:", error); throw error; } } } /** * 百度文心一言平台实现 */ class BaiduAIProvider extends AIProvider { constructor() { super(); this.headers = { "Content-Type": "application/json" }; this.url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions"; this.model = "ernie-bot-4"; this.accessToken = null; } async getAccessToken() { if (this.accessToken) return this.accessToken; const tokenUrl = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${config.baidu.apiKey}&client_secret=${config.baidu.secretKey}`; try { const response = await axios.post(tokenUrl); this.accessToken = response.data.access_token; return this.accessToken; } catch (error) { console.error("Baidu access token error:", error); throw error; } } async generateArticle(content) { const accessToken = await this.getAccessToken(); const apiUrl = `${this.url}?access_token=${accessToken}`; const postJSON = { "model": this.model, "messages": [ { "role": "user", "content": content, } ] }; try { const response = await axios.post(apiUrl, postJSON, { headers: this.headers }); return response.data.result; } catch (error) { console.error("Baidu AI API error:", error); throw error; } } } /** * AI提供者工厂类 * 根据配置创建不同的AI平台实例 */ class AIProviderFactory { /** * 获取AI提供者实例 * @param {string} provider - AI提供者名称,如'volces', 'volces1-5', 'volces1-6', 'openai', 'baidu' * @returns {AIProvider} - 返回对应的AI提供者实例 */ static getProvider(provider = 'volces1-5') { const providerLower = provider.toLowerCase(); // 处理火山云通用提供者格式:volces:版本号 if (providerLower.startsWith('volces:')) { const version = providerLower.split(':')[1]; return new VolcesAIProvider(version); } // 处理传统提供者名称 switch (providerLower) { case 'volces': case 'volces1-5': return new VolcesAIProvider1_5('1-5'); case 'volces1-6': return new VolcesAIProvider1_6('1-6'); case 'openai': return new OpenAIProvider(); case 'baidu': return new BaiduAIProvider(); default: return new VolcesAIProvider('1-5'); // 默认使用火山云1.5 } } } /** * 生成文章的主函数 * @param {string} content - 生成文章的提示内容 * @param {string} provider - AI提供者名称,默认为'volces' * @returns {Promise} - 返回生成的文章JSON字符串 */ export async function generateArticle(content, provider = 'volces1-5') { try { const aiProvider = AIProviderFactory.getProvider(provider); const result = await aiProvider.generateArticle(content); return result; } catch (error) { console.error("Generate article error:", error); throw error; } } /** * 计算两个字符串之间的Levenshtein距离(编辑距离) * @param {string} a - 第一个字符串 * @param {string} b - 第二个字符串 * @returns {number} - 编辑距离 */ function levenshteinDistance(a, b) { const matrix = []; // 初始化矩阵 for (let i = 0; i <= b.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= a.length; j++) { matrix[0][j] = j; } // 填充矩阵 for (let i = 1; i <= b.length; i++) { for (let j = 1; j <= a.length; j++) { if (b.charAt(i - 1) === a.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min( matrix[i - 1][j - 1] + 1, // 替换 matrix[i][j - 1] + 1, // 插入 matrix[i - 1][j] + 1 // 删除 ); } } } return matrix[b.length][a.length]; } /** * 增强FormsOfWords,检测文章中单词的变形形式和拼写错误 * @param {Object} jsonObj - 解析后的JSON对象 * @param {string} userWords - 用户提供的单词列表,逗号分隔 * @returns {Object} - 增强后的JSON对象 */ export function enhanceFormsOfWords(jsonObj, userWords) { if (!jsonObj || !userWords) return jsonObj; // 将用户提供的单词转换为数组并去除空格 const userWordsList = userWords.split(',').map(word => word.trim().toLowerCase()); // 如果没有ArticleEnglish或FormsOfWords,直接返回 if (!jsonObj.ArticleEnglish || !Array.isArray(jsonObj.ArticleEnglish)) { return jsonObj; } // 确保FormsOfWords存在 if (!jsonObj.FormsOfWords) { jsonObj.FormsOfWords = []; } // 从文章中提取所有单词 const allWordsInArticle = []; jsonObj.ArticleEnglish.forEach(sentence => { // 移除标点符号,分割成单词 const words = sentence.toLowerCase() .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, " ") .replace(/\s+/g, " ") .split(" "); words.forEach(word => { if (word) allWordsInArticle.push(word.trim()); }); }); // 常见的后缀列表 const commonSuffixes = [ 'ed', 'ing', 's', 'es', 'er', 'est', 'ful', 'ly', 'ment', 'ness', 'ity', 'tion', 'sion', 'ation', 'able', 'ible', 'al', 'ial', 'ic', 'ical', 'ious', 'ous', 'ive', 'less', 'y' ]; // 不规则动词变化表(部分常见的) const irregularVerbs = { 'go': ['went', 'gone', 'goes', 'going'], 'be': ['am', 'is', 'are', 'was', 'were', 'been', 'being'], 'do': ['did', 'done', 'does', 'doing'], 'have': ['has', 'had', 'having'], 'say': ['said', 'says', 'saying'], 'make': ['made', 'makes', 'making'], 'take': ['took', 'taken', 'takes', 'taking'], 'come': ['came', 'comes', 'coming'], 'see': ['saw', 'seen', 'sees', 'seeing'], 'know': ['knew', 'known', 'knows', 'knowing'], 'get': ['got', 'gotten', 'gets', 'getting'], 'give': ['gave', 'given', 'gives', 'giving'], 'find': ['found', 'finds', 'finding'], 'think': ['thought', 'thinks', 'thinking'], 'tell': ['told', 'tells', 'telling'], 'become': ['became', 'becomes', 'becoming'], 'show': ['showed', 'shown', 'shows', 'showing'], 'leave': ['left', 'leaves', 'leaving'], 'feel': ['felt', 'feels', 'feeling'], 'put': ['puts', 'putting'], 'bring': ['brought', 'brings', 'bringing'], 'begin': ['began', 'begun', 'begins', 'beginning'], 'keep': ['kept', 'keeps', 'keeping'], 'hold': ['held', 'holds', 'holding'], 'write': ['wrote', 'written', 'writes', 'writing'], 'stand': ['stood', 'stands', 'standing'], 'hear': ['heard', 'hears', 'hearing'], 'let': ['lets', 'letting'], 'mean': ['meant', 'means', 'meaning'], 'set': ['sets', 'setting'], 'meet': ['met', 'meets', 'meeting'], 'run': ['ran', 'runs', 'running'], 'pay': ['paid', 'pays', 'paying'], 'sit': ['sat', 'sits', 'sitting'], 'speak': ['spoke', 'spoken', 'speaks', 'speaking'], 'lie': ['lay', 'lain', 'lies', 'lying'], 'lead': ['led', 'leads', 'leading'], 'read': ['read', 'reads', 'reading'], 'sleep': ['slept', 'sleeps', 'sleeping'], 'win': ['won', 'wins', 'winning'], 'understand': ['understood', 'understands', 'understanding'], 'draw': ['drew', 'drawn', 'draws', 'drawing'], 'sing': ['sang', 'sung', 'sings', 'singing'], 'fall': ['fell', 'fallen', 'falls', 'falling'], 'fly': ['flew', 'flown', 'flies', 'flying'], 'grow': ['grew', 'grown', 'grows', 'growing'], 'lose': ['lost', 'loses', 'losing'], 'teach': ['taught', 'teaches', 'teaching'], 'eat': ['ate', 'eaten', 'eats', 'eating'], 'drink': ['drank', 'drunk', 'drinks', 'drinking'] }; // 不规则形容词比较级和最高级 const irregularAdjectives = { 'good': ['better', 'best'], 'bad': ['worse', 'worst'], 'far': ['further', 'furthest', 'farther', 'farthest'], 'little': ['less', 'least'], 'many': ['more', 'most'], 'much': ['more', 'most'] }; // 收集所有单词形式 const allForms = new Set(); userWordsList.forEach(originalWord => { // 添加原始单词 allForms.add(originalWord); // 检查不规则动词 if (irregularVerbs[originalWord]) { irregularVerbs[originalWord].forEach(form => allForms.add(form)); } // 检查不规则形容词 if (irregularAdjectives[originalWord]) { irregularAdjectives[originalWord].forEach(form => allForms.add(form)); } // 检查文章中的所有单词,寻找可能的变形和拼写错误 allWordsInArticle.forEach(articleWord => { // 检查是否是原始单词 if (articleWord === originalWord) { allForms.add(articleWord); return; } // 检查拼写错误,使用更严格的条件 // 1. 对于短单词(长度<=4),只接受编辑距离为1的情况 // 2. 对于中等长度单词(4<长度<=8),只接受编辑距离为1的情况 // 3. 对于长单词(长度>8),允许编辑距离为2,但有额外限制 if (originalWord.length <= 8) { // 短单词和中等长度单词使用相同的严格条件 if (articleWord[0] === originalWord[0] && // 首字母必须相同 Math.abs(articleWord.length - originalWord.length) <= 1 && // 长度差不超过1 levenshteinDistance(originalWord, articleWord) === 1) { // 编辑距离恰好为1 allForms.add(articleWord); } } else { // 长单词(长度>8)的条件 if (articleWord[0] === originalWord[0]) { // 首字母必须相同 const editDistance = levenshteinDistance(originalWord, articleWord); if (editDistance === 1) { // 编辑距离为1的情况,长度差不超过1 if (Math.abs(articleWord.length - originalWord.length) <= 1) { allForms.add(articleWord); } } else if (editDistance === 2) { // 编辑距离为2的情况,需要更严格的条件 // 长度差不超过1且单词长度大于8 if (Math.abs(articleWord.length - originalWord.length) <= 1) { allForms.add(articleWord); } } } } // 检查是否是通过添加后缀形成的变形 for (const suffix of commonSuffixes) { if (articleWord.endsWith(suffix)) { const stem = articleWord.slice(0, -suffix.length); // 处理双写字母的情况(如:running -> run) if (stem.length > 0 && stem[stem.length-1] === stem[stem.length-2]) { const possibleStem = stem.slice(0, -1); if (possibleStem === originalWord) { allForms.add(articleWord); continue; } } // 处理去e加ing的情况(如:writing -> write) if (suffix === 'ing' && originalWord.endsWith('e') && stem + 'e' === originalWord) { allForms.add(articleWord); continue; } // 处理y变i的情况(如:studies -> study) if ((suffix === 'es' || suffix === 'ed') && originalWord.endsWith('y') && stem + 'y' === originalWord) { allForms.add(articleWord); continue; } // 直接比较 if (stem === originalWord) { allForms.add(articleWord); } } } }); }); // 更新FormsOfWords jsonObj.FormsOfWords = Array.from(new Set([...jsonObj.FormsOfWords, ...allForms])); return jsonObj; } /** * 校验和修复JSON结构 * @param {string} jsonString - JSON字符串 * @returns {string} - 修复后的JSON字符串 */ export function validateAndFixJSON(jsonString) { try { //console.log(jsonString); // 解析JSON字符串为对象 let jsonObj = JSON.parse(jsonString); // 校验和修复Question数组中的每个问题对象 if (jsonObj.Question && Array.isArray(jsonObj.Question)) { jsonObj.Question = jsonObj.Question.map(question => { // 创建一个修复后的问题对象 const fixedQuestion = {}; // 确保QuestionEnglish字段存在 if (question.QuestionEnglish) { fixedQuestion.QuestionEnglish = question.QuestionEnglish; } // 检查QuestionChinese字段,如果不存在但有第二个QuestionEnglish,则使用它 if (question.QuestionChinese) { fixedQuestion.QuestionChinese = question.QuestionChinese; } else if (Object.keys(question).filter(key => key === 'QuestionEnglish').length > 1) { // 找到第二个QuestionEnglish的值 const keys = Object.keys(question); let foundFirst = false; for (const key of keys) { if (key === 'QuestionEnglish') { if (foundFirst) { fixedQuestion.QuestionChinese = question[key]; break; } foundFirst = true; } } } // 确保OptionsEnglish字段存在且为数组 if (question.OptionsEnglish && Array.isArray(question.OptionsEnglish)) { fixedQuestion.OptionsEnglish = question.OptionsEnglish; } else { fixedQuestion.OptionsEnglish = ["A.", "B.", "C.", "D."]; } // 确保OptionsChinese字段存在且为数组 if (question.OptionsChinese && Array.isArray(question.OptionsChinese)) { fixedQuestion.OptionsChinese = question.OptionsChinese; } else { fixedQuestion.OptionsChinese = ["A.", "B.", "C.", "D."]; } // 确保Answer字段存在 if (question.Answer) { fixedQuestion.Answer = question.Answer; } else { fixedQuestion.Answer = "A"; } return fixedQuestion; }); } // 确保其他必要字段存在 if (!jsonObj.ArticleEnglish || !Array.isArray(jsonObj.ArticleEnglish)) { jsonObj.ArticleEnglish = ["No content available"]; } if (!jsonObj.ArticleChinese || !Array.isArray(jsonObj.ArticleChinese)) { jsonObj.ArticleChinese = ["无可用内容"]; } if (!jsonObj.FormsOfWords || !Array.isArray(jsonObj.FormsOfWords)) { jsonObj.FormsOfWords = []; } else { // 处理FormsOfWords数组,提取所有单词 const processedFormsOfWords = []; for (const item of jsonObj.FormsOfWords) { if (typeof item !== 'string') { continue; // 跳过非字符串项 } // 处理冒号分隔格式:"word1: word2" if (item.includes(':')) { const [leftWord, rightWord] = item.split(':').map(word => word.trim()); if (leftWord) processedFormsOfWords.push(leftWord); if (rightWord) processedFormsOfWords.push(rightWord); continue; } // 处理括号分隔格式:"word1(word2)" 或 "word1(word2, word3)" const bracketMatch = item.match(/^([^(]+)\(([^)]+)\)$/); if (bracketMatch) { const outsideWord = bracketMatch[1].trim(); const insideWords = bracketMatch[2].split(',').map(word => word.trim()); if (outsideWord) processedFormsOfWords.push(outsideWord); for (const word of insideWords) { if (word) processedFormsOfWords.push(word); } continue; } // 如果不符合上述格式,检查是否包含逗号 if (item.includes(',')) { // 如果包含逗号,按逗号分割并添加每个单词 const words = item.split(',').map(word => word.trim()); for (const word of words) { if (word) processedFormsOfWords.push(word); } } else { // 单个单词,直接添加 processedFormsOfWords.push(item); } } // 去除空字符串并去重 const uniqueFormsOfWords = [...new Set(processedFormsOfWords.filter(word => word))]; // 用去重后的数组替换原数组 jsonObj.FormsOfWords = uniqueFormsOfWords; } // 将修复后的对象转回JSON字符串 return JSON.stringify(jsonObj); } catch (jsonError) { console.error("JSON解析或修复错误:", jsonError); // 如果解析失败,保留原始结果 return jsonString; } } export default { generateArticle, enhanceFormsOfWords, validateAndFixJSON };