| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697 |
- import axios from 'axios';
- import crypto from 'crypto';
- import config from '../../config/index.js';
- import { enhanceFormsOfWords } from './enhanceFormsOfWords.js';
- /**
- * AI平台接口类
- * 定义了所有AI平台需要实现的方法
- */
- class AIProvider {
- /**
- * 生成文章
- * @param {string} content - 生成文章的提示内容
- * @returns {Promise<string>} - 返回生成的文章JSON字符串
- */
- async generateArticle(content) {
- throw new Error('Method not implemented');
- }
- }
- /**
- * 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;
- }
- }
- }
- /**
- * 火山云AI平台实现
- */
- class VolcesAIProvider extends AIProvider {
- /**
- * 创建火山云AI提供者实例
- * @param {string} version - 版本号
- */
- constructor(version) {
- super();
-
- if (version.indexOf("deepseek")>0){
- version=version.substring(7);
- }
- // 获取当前版本的配置,如果版本不存在则使用1.5版本
- const currentConfig = {
- apikey: config.huoshancloud.apikeyHLR,
- model: version
- };
-
- 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) {
- let postJSON = {
- "model": this.model,
- "messages": [
- {
- "role": "system",
- "content": "你是一个专业的AI助手,请以JSON格式回复。"
- },
- {
- "role": "user",
- "content": content
- }
- ]
- };
- if (this.version.indexOf("deepseek")>=0 || this.version.indexOf("seed-1")>=0){
- postJSON.response_format={
- "type": "json_object"
- };
- postJSON.thinking={
- "type": "disabled"
- };
- }
- try {
- console.log(`火山云${this.version}`);
- // 移除encodeURI,直接使用原始URL
- const response = await axios.post(this.url, postJSON, { headers: this.headers });
- return response.data.choices[0].message.content;
- } catch (error) {
- console.error("VolcesAI API error:", error);
- // 添加更详细的错误日志
- if (error.response) {
- console.error("错误详情:", {
- status: error.response.status,
- data: error.response.data
- });
- }
- throw error;
- }
- }
- }
- /**
- * 讯飞星火平台实现 (spark X1版本)
- */
- class XunFeiYunAIProvider extends AIProvider {
- constructor() {
- super();
- // 使用API Key和Secret拼接成"AK:SK"格式
- this.apiKey = `${config.xfyun.apikey}:${config.xfyun.apisecret}`;
- this.headers = {
- "Content-Type": "application/json",
- "Authorization": `Bearer ${this.apiKey}`
- };
- this.url = "https://spark-api-open.xf-yun.com/v2/chat/completions";
- }
- async generateArticle(content) {
- const postJSON = {
- "model": "x1",
- "messages": [
- {
- "role": "user",
- "content": content
- }
- ],
- "temperature": 0.7,
- "max_tokens": 2048,
- "user": "123456" // 固定用户ID
- };
- try {
- // 增加重试机制
- let retries = 3;
- let lastError = null;
-
- while (retries > 0) {
- try {
- console.log(`讯飞云`);
- const response = await axios.post(this.url, postJSON, {
- headers: this.headers,
- timeout: 60000 // 增加到60秒超时
- });
-
- // 处理OpenAI兼容格式的响应
- if (response.data?.choices?.[0]?.message?.content) {
- return response.data.choices[0].message.content;
- }
- console.error("XunFeiYun X1 API response:", response.data);
- throw new Error("Invalid response format from XunFeiYun X1 API");
- } catch (error) {
- lastError = error;
- retries--;
-
- if (retries > 0) {
- console.warn(`API请求失败,剩余重试次数: ${retries},等待2秒后重试...`);
- await new Promise(resolve => setTimeout(resolve, 2000));
- continue;
- }
-
- // 更详细的错误日志
- if (error.response) {
- console.error("XFYun API error response:", {
- status: error.response.status,
- data: error.response.data,
- headers: error.response.headers
- });
- } else if (error.request) {
- console.error("XFYun API request error:", {
- method: error.config.method,
- url: error.config.url,
- headers: error.config.headers,
- data: error.config.data
- });
- } else {
- console.error("XFYun API setup error:", error.message);
- }
-
- throw error;
- }
- }
-
- throw lastError || new Error("API请求失败");
- } catch (error) {
- console.error("XFYun API error:", error);
- throw error;
- }
- }
- }
- /**
- * 阿里云通义平台实现
- */
- class AliyunAIProvider extends AIProvider {
- constructor(model = 'qwen-plus') {
- super();
- let url="https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
-
- this.accessKey = config.aliyun.apikeyHLR;
-
- this.headers = {
- "Content-Type": "application/json",
- "Accept": "application/json"
- };
- this.url = url;
- this.model = model;
- }
- /**
- * 生成文章
- * @param {string} content - 生成文章的提示内容
- * @returns {Promise<string>} - 返回生成的文章内容
- */
- async generateArticle(content) {
- // 设置请求头中的API密钥
- this.headers["Authorization"] = `Bearer ${this.accessKey}`;
-
- const postJSON = {
- "model": this.model,
- "messages": [
- {
- "role": "user",
- "content": content
- }
- ],
- "temperature": 0.7,
- "top_p": 0.8
- };
- try {
- console.log(`阿里云 ${this.model}`);
- const response = await axios.post(this.url, postJSON, { headers: this.headers });
-
- // 根据阿里云通义API的返回格式提取内容
- if (response.data && response.data.choices && response.data.choices[0] && response.data.choices[0].message) {
- let responseContent = response.data.choices[0].message.content;
-
- // 处理可能的Markdown代码块标记
- if (responseContent.startsWith('```json') || responseContent.startsWith('```')) {
- console.log("阿里云返回了Markdown代码块,正在处理...");
- // 移除开头的```json或```
- responseContent = responseContent.replace(/^```(?:json)?\s*\n/, '');
- // 移除结尾的```
- responseContent = responseContent.replace(/\n```\s*$/, '');
- }
-
- return responseContent;
- } else {
- throw new Error("Unexpected response format from Aliyun API");
- }
- } catch (error) {
- console.error("Aliyun AI API error:", error);
- throw error;
- }
- }
- }
- /**
- * 腾讯混元大模型平台实现
- */
- class TencentHunyuanAIProvider extends AIProvider {
- constructor(model = 'hunyuan-turbos-latest') {
- super();
- this.headers = {
- "Authorization": "Bearer " + config.tencentcloudHunyuan.apikey,
- "Content-Type": "application/json"
- };
- this.url = "https://api.hunyuan.cloud.tencent.com/v1/chat/completions";
- this.model = model;
- }
- async generateArticle(content) {
- const postJSON = {
- "model": this.model,
- "messages": [
- {
- "role": "user",
- "content": content,
- }
- ]
- };
- try {
- console.log(`腾讯云${this.model}`);
- 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(model = 'deepseek-v3') {
- super();
- this.headers = {
- "Authorization": "Bearer " + config.baiducloud.apikey,
- "Content-Type": "application/json"
- };
- this.url = 'https://qianfan.baidubce.com/v2/chat/completions';
- this.model = model;
- }
- async generateArticle(content) {
- const postJSON = {
- "model": this.model,
- "messages": [
- {
- "role": "user",
- "content": content,
- }
- ],
- "temperature": 0.8,
- "top_p": 0.8,
- "penalty_score": 1
- };
- try {
- // 增加重试机制
- let retries = 3;
- let lastError = null;
- let backoffTime = 1000; // 初始等待时间1秒
-
- while (retries > 0) {
- try {
- console.log(`百度千帆 ${this.model}`);
- const response = await axios.post(this.url, postJSON, {
- headers: this.headers,
- timeout: 60000 // 60秒超时
- });
-
- // 处理响应格式
- if (response.data?.choices?.[0]?.message?.content) {
- return response.data.choices[0].message.content;
- } else if (response.data?.result) {
- return response.data.result;
- } else {
- console.error("百度千帆API响应格式异常:", response.data);
- throw new Error("Unexpected response format from Baidu API");
- }
- } catch (error) {
- lastError = error;
-
- // 特别处理429错误(请求过多)
- if (error.response && error.response.status === 429) {
- console.warn(`百度千帆API限流,等待${backoffTime/1000}秒后重试...`);
- await new Promise(resolve => setTimeout(resolve, backoffTime));
- backoffTime *= 2; // 指数退避策略
- retries--;
- continue;
- }
-
- // 处理其他错误
- retries--;
- if (retries > 0) {
- console.warn(`API请求失败,剩余重试次数: ${retries},等待${backoffTime/1000}秒后重试...`);
- await new Promise(resolve => setTimeout(resolve, backoffTime));
- backoffTime *= 2; // 指数退避策略
- continue;
- }
-
- // 详细记录错误信息
- if (error.response) {
- console.error("百度千帆API错误响应:", {
- status: error.response.status,
- data: error.response.data,
- headers: error.response.headers
- });
- } else if (error.request) {
- console.error("百度千帆API请求错误:", {
- method: error.config.method,
- url: error.config.url,
- headers: error.config.headers,
- data: error.config.data
- });
- } else {
- console.error("百度千帆API设置错误:", error.message);
- }
-
- throw error;
- }
- }
-
- throw lastError || new Error("百度千帆API请求失败,已达到最大重试次数");
- } catch (error) {
- console.error("Baidu API error:", error);
- throw error;
- }
- }
- }
- /**
- * Openrouter平台实现
- */
- class OpenrouterProvider extends AIProvider {
- constructor() {
- super();
- this.headers = {
- "Authorization": "Bearer " + config.openroutercloud.apikey,
- "Content-Type": "application/json"
- };
- this.url = "https://openrouter.ai/api/v1/chat/completions";
- this.model = "moonshotai/kimi-k2:free";
- }
-
- async generateArticle(content) {
- const postJSON = {
- "model": this.model,
- "messages": [
- {
- "role": "user",
- "content": content,
- }
- ]
- };
- try {
- console.log(`Openrouter ${this.model}`);
- 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;
- }
- }
- }
- /**
- * AI提供者工厂类
- * 根据配置创建不同的AI平台实例
- */
- class AIProviderFactory {
- /**
- * 获取AI提供者实例
- * @param {string} provider - AI提供者名称
- * @returns {AIProvider} - 返回对应的AI提供者实例
- */
- static getProvider(provider) {
- const providerLower = provider.toLowerCase();
-
- // 处理传统提供者名称
- switch (providerLower) {
- case 'openai':
- return new OpenAIProvider();
- case 'openrouter-moonshotai/kimi-k2':
- return new OpenrouterProvider("moonshotai/kimi-k2:free");
- case 'doubao-1-5-pro-32k-250115':
- return new VolcesAIProvider(providerLower);
- case 'doubao-seed-1-6-250615':
- return new VolcesAIProvider(providerLower);
- case 'doubao-deepseek-v3-250324':
- return new VolcesAIProvider("deepseek-v3-250324");
- case 'doubao-deepseek-r1-250528':
- return new VolcesAIProvider("deepseek-r1-250528");
- case 'doubao-kimi-k2-250711':
- return new VolcesAIProvider("kimi-k2-250711");
- case 'ali-qwen-plus-2025-07-14':
- return new AliyunAIProvider("qwen-plus-2025-07-14");
- case 'ali-qwen-max':
- return new AliyunAIProvider("qwen-max");
- case 'llama-4-scout-17b-16e-instruct':
- return new AliyunAIProvider(providerLower);
- case 'llama-4-maverick-17b-128e-instruct':
- return new AliyunAIProvider(providerLower);
- case 'ali-deepseek-r1-0528':
- return new AliyunAIProvider("deepseek-r1-0528");
- case 'ali-deepseek-v3':
- return new AliyunAIProvider("deepseek-v3");
- case 'ali-moonshot-kimi-k2-instruct':
- return new AliyunAIProvider("Moonshot-Kimi-K2-Instruct");
-
- case 'xf-yun-spark-x1':
- return new XunFeiYunAIProvider();
- case 'tencent-hunyuan-turbos-latest':
- return new TencentHunyuanAIProvider("hunyuan-turbos-latest");
- case 'tencent-hunyuan-t1-latest':
- return new TencentHunyuanAIProvider("hunyuan-t1-latest");
- case 'baidu-deepseek-v3':
- return new BaiduAIProvider();
- case 'baidu-deepseek-r1':
- return new BaiduAIProvider("deepseek-r1");
- case 'baidu-ernie-4.5-turbo-vl-32k-preview':
- return new BaiduAIProvider("ernie-4.5-turbo-vl-32k-preview");
- default:
- return new VolcesAIProvider("doubao-1-5-pro-32k-250115"); // 默认使用火山云1.5
- }
- }
- }
- /**
- * 生成文章的主函数
- * @param {string} content - 生成文章的提示内容
- * @param {string} provider - AI提供者名称,默认为'volces'
- * @returns {Promise<string>} - 返回生成的文章JSON字符串
- */
- async function generateArticle(content, provider) {
- try {
- const aiProvider = AIProviderFactory.getProvider(provider);
- const result = await aiProvider.generateArticle(content);
- return result;
- } catch (error) {
- console.error("Generate article error:", error);
- throw error;
- }
- }
- /**
- * 验证并修复JSON结构
- * @param {string} jsonString - 需要验证和修复的JSON字符串
- * @returns {string} - 返回修复后的JSON字符串
- */
- function validateAndFixJSON(jsonString) {
- // 如果输入不是字符串,直接返回
- if (typeof jsonString !== 'string') {
- console.error("输入不是字符串类型");
- return jsonString;
- }
-
- // 预处理:移除Markdown代码块标记
- let processedJson = jsonString;
-
- // 检查并移除Markdown代码块标记
- if (processedJson.includes('```')) {
- console.log("检测到Markdown代码块,尝试移除标记");
-
- // 处理可能的多行代码块
- const codeBlockRegex = /```(?:json)?\s*\n([\s\S]*?)\n```/;
- const match = processedJson.match(codeBlockRegex);
-
- if (match && match[1]) {
- // 提取代码块内容
- processedJson = match[1];
- console.log("成功提取代码块内容");
- } else {
- // 如果正则匹配失败,尝试简单替换
- processedJson = processedJson.replace(/^```(?:json)?\s*\n/, '');
- processedJson = processedJson.replace(/\n```\s*$/, '');
- }
- }
-
- try {
- // 尝试解析JSON
- const parsed = JSON.parse(processedJson);
- return processedJson;
- } catch (error) {
- console.error("JSON解析错误,尝试修复:", error);
-
- // 尝试修复常见的JSON错误
- let fixedJson = processedJson;
-
- // 修复缺少引号的键
- fixedJson = fixedJson.replace(/(\s*?)(\w+)(\s*?):/g, '"$2":');
-
- // 修复单引号,但不影响缩写
- fixedJson = fixedJson.replace(/(?<!\w)'(?!\w)/g, '"');
-
- // 修复尾部逗号
- fixedJson = fixedJson.replace(/,\s*}/g, '}');
- fixedJson = fixedJson.replace(/,\s*\]/g, ']');
-
- // 尝试解析修复后的JSON
- try {
- JSON.parse(fixedJson);
- console.log("JSON修复成功");
- return fixedJson;
- } catch (error2) {
- console.error("JSON修复失败:", error2);
-
- // 最后尝试:如果内容看起来像JSON但解析失败,尝试提取{}之间的内容
- const jsonObjectRegex = /\{[\s\S]*\}/;
- const objectMatch = jsonString.match(jsonObjectRegex);
-
- if (objectMatch) {
- try {
- const extractedJson = objectMatch[0];
- JSON.parse(extractedJson);
- console.log("通过提取{}内容成功修复JSON");
- return extractedJson;
- } catch (error3) {
- console.error("提取{}内容后仍解析失败", error3);
- throw new Error("JSON解析失败:提取{}内容后仍无法解析");
- }
- }
-
- // 如果所有尝试都失败,抛出错误
- throw new Error("JSON解析失败:无法修复格式错误");
- }
- }
- }
- /**
- * 标准化文章字段,将修正版本的内容应用到标准字段中
- * @param {string|Object} jsonInput - 包含文章内容的JSON字符串或对象
- * @returns {string|Object} - 返回标准化后的JSON字符串或对象,与输入类型保持一致
- */
- function normalizeArticleFields(jsonInput) {
- // 判断输入是字符串还是对象
- const isString = typeof jsonInput === 'string';
-
- // 如果是字符串,先解析为对象
- let json = isString ? JSON.parse(jsonInput) : jsonInput;
-
- if (json.ArticleEnglishCorrected){
- json.ArticleEnglish=json.ArticleEnglishCorrected;
- delete json.ArticleEnglishCorrected;
- }
- if (json.ArticleChineseCorrected){
- json.ArticleChinese=json.ArticleChineseCorrected;
- delete json.ArticleChineseCorrected;
- }
-
- // 确保ArticleEnglish数组中只包含英文句子,ArticleChinese数组中只包含中文句子
- if (json.ArticleEnglish && Array.isArray(json.ArticleEnglish)) {
- const englishSentences = [];
- const chineseSentences = [];
-
- // 遍历ArticleEnglish数组,分离英文和中文句子
- json.ArticleEnglish.forEach(sentence => {
- // 检查句子是否包含中文字符
- if (/[\u4e00-\u9fa5]/.test(sentence)) {
- chineseSentences.push(sentence);
- } else {
- englishSentences.push(sentence);
- }
- });
-
- // 更新ArticleEnglish数组,只保留英文句子
- json.ArticleEnglish = englishSentences;
-
- // 如果ArticleChinese不存在或不是数组,则创建它
- if (!json.ArticleChinese || !Array.isArray(json.ArticleChinese)) {
- json.ArticleChinese = [];
- }
-
- // 将中文句子添加到ArticleChinese数组中
- chineseSentences.forEach(sentence => {
- if (!json.ArticleChinese.includes(sentence)) {
- json.ArticleChinese.push(sentence);
- }
- });
- }
-
- // 根据输入类型返回相应的结果
- return isString ? JSON.stringify(json) : json;
- }
- // 默认导出,保持向后兼容性
- export default {
- generateArticle,
- enhanceFormsOfWords,
- validateAndFixJSON,
- normalizeArticleFields
- };
|