| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- import axios from 'axios';
- import config from '../../config/index.js';
- /**
- * AI平台接口类
- * 定义了所有AI平台需要实现的方法
- */
- class AIProvider {
- /**
- * 生成文章
- * @param {string} content - 生成文章的提示内容
- * @returns {Promise<string>} - 返回生成的文章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<string>} - 返回生成的文章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
- };
|