ai-content.service.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. "use strict";
  2. /**
  3. * AI内容生成服务
  4. * 使用通义千问/DashScope API
  5. */
  6. var __importDefault = (this && this.__importDefault) || function (mod) {
  7. return (mod && mod.__esModule) ? mod : { "default": mod };
  8. };
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.aiContentService = exports.AIContentService = exports.generateTasks = void 0;
  11. const axios_1 = __importDefault(require("axios"));
  12. const config_1 = require("../../config");
  13. // 存储生成任务
  14. exports.generateTasks = new Map();
  15. // 内容类型分类
  16. const contentTypes = {
  17. '创作类': ['小说', '故事', '剧本', '诗歌', '散文'],
  18. '营销类': ['产品介绍', '广告文案', '朋友圈', '小红书', '抖音脚本'],
  19. '教育类': ['课件', '培训', '教程', '知识科普', '考试辅导'],
  20. '商务类': ['销售话术', '客服话术', '商务邮件', '合同条款', '方案PPT'],
  21. '媒体类': ['新闻播报', '天气预报', '体育解说', '财经评论', '娱乐八卦'],
  22. '生活类': ['生日祝福', '婚礼致辞', '节日问候', '朋友圈文案', '签名设计'],
  23. '专业类': ['法律文书', '医学说明', '技术文档', '产品手册', '操作指南'],
  24. };
  25. // 行业列表
  26. const industries = [
  27. '通用', '医疗健康', '教育培训', '金融服务', '电子商务',
  28. '法律服务', '新闻媒体', '餐饮美食', '房地产', '汽车销售'
  29. ];
  30. // 情感选项
  31. const emotions = ['开心', '悲伤', '激动', '平静', '紧张', '温柔', '愤怒', '恐惧', '惊讶'];
  32. // 支持的语言
  33. const languages = ['中文', '英语', '日语', '韩语', '法语', '德语', '西班牙语', '葡萄牙语', '俄语', '阿拉伯语'];
  34. // 质量评分维度
  35. const qualityDimensions = ['fluency', 'naturalness', 'emotion_consistency', 'topic_adherence', 'structural_integrity'];
  36. // 获取可用模型列表
  37. function getAvailableModels() {
  38. return config_1.config.models.getModelsByType('text').filter((m) => m.enabled !== false);
  39. }
  40. // 随机选择模型
  41. function getRandomModel() {
  42. const models = getAvailableModels();
  43. return models[Math.floor(Math.random() * models.length)].id;
  44. }
  45. class AIContentService {
  46. modelId;
  47. apiKey;
  48. baseUrl;
  49. constructor() {
  50. this.modelId = getRandomModel();
  51. const modelConfig = config_1.config.models.getModel(this.modelId);
  52. this.apiKey = modelConfig?.apiKey || '';
  53. this.baseUrl = modelConfig?.baseUrl || '';
  54. }
  55. /**
  56. * 调用 LLM API (流式)
  57. */
  58. async *streamLLM(prompt, systemPrompt) {
  59. if (!this.apiKey || !this.baseUrl) {
  60. throw new Error('未配置 AI API');
  61. }
  62. const fullPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
  63. try {
  64. const response = await axios_1.default.post(`${this.baseUrl}/chat/completions`, {
  65. model: this.modelId,
  66. messages: [{ role: 'user', content: fullPrompt }],
  67. stream: true,
  68. }, {
  69. headers: {
  70. 'Authorization': `Bearer ${this.apiKey}`,
  71. 'Content-Type': 'application/json',
  72. },
  73. timeout: 180000,
  74. responseType: 'stream',
  75. });
  76. let buffer = '';
  77. for await (const chunk of response.data) {
  78. buffer += chunk.toString();
  79. // 解析SSE格式的数据
  80. const lines = buffer.split('\n');
  81. buffer = lines.pop() || '';
  82. for (const line of lines) {
  83. if (line.startsWith('data:')) {
  84. const data = line.slice(5).trim();
  85. if (data && data !== '[DONE]') {
  86. try {
  87. const parsed = JSON.parse(data);
  88. const content = parsed.choices?.[0]?.delta?.content;
  89. if (content) {
  90. yield content;
  91. }
  92. }
  93. catch (e) {
  94. // 忽略解析错误
  95. }
  96. }
  97. }
  98. }
  99. }
  100. }
  101. catch (error) {
  102. console.error('❌ LLM 流式调用失败:', error.response?.data || error.message);
  103. throw new Error(error.message || 'AI 生成失败');
  104. }
  105. }
  106. /**
  107. * 调用 LLM API
  108. */
  109. async callLLM(prompt, systemPrompt) {
  110. if (!this.apiKey || !this.baseUrl) {
  111. throw new Error('未配置 AI API');
  112. }
  113. const fullPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
  114. try {
  115. const response = await axios_1.default.post(`${this.baseUrl}/chat/completions`, {
  116. model: this.modelId,
  117. messages: [{ role: 'user', content: fullPrompt }],
  118. }, {
  119. headers: {
  120. 'Authorization': `Bearer ${this.apiKey}`,
  121. 'Content-Type': 'application/json',
  122. },
  123. timeout: 120000,
  124. });
  125. const data = response.data;
  126. return data.choices?.[0]?.message?.content || '';
  127. }
  128. catch (error) {
  129. console.error('❌ LLM 调用失败:', error.response?.data || error.message);
  130. throw new Error(error.message || 'AI 生成失败');
  131. }
  132. }
  133. /**
  134. * 智能意图识别
  135. */
  136. async recognizeIntent(input) {
  137. const type = this.detectContentType(input);
  138. const industry = this.detectIndustry(input);
  139. return {
  140. type,
  141. industry,
  142. style: '正式',
  143. scale: input.length > 500 ? '长篇' : '短篇',
  144. confidence: 0.85,
  145. };
  146. }
  147. /**
  148. * 检测内容类型
  149. */
  150. detectContentType(input) {
  151. const keywords = {
  152. '小说': ['故事', '主角', '章节', '穿越', '都市', '玄幻'],
  153. '广告': ['推广', '优惠', '打折', '促销', '产品'],
  154. '培训': ['培训', '课程', '教学', '学员', '讲师'],
  155. '销售': ['客户', '订单', '成交', '跟进', '话术'],
  156. '祝福': ['祝福', '生日快乐', '节日', '贺卡'],
  157. };
  158. for (const [type, words] of Object.entries(keywords)) {
  159. if (words.some(w => input.includes(w))) {
  160. return { category: type, subType: type };
  161. }
  162. }
  163. return { category: '创作类', subType: '故事' };
  164. }
  165. /**
  166. * 检测行业
  167. */
  168. detectIndustry(input) {
  169. const keywords = {
  170. '医疗健康': ['医生', '医院', '药品', '健康', '疾病'],
  171. '教育培训': ['学校', '学生', '老师', '课程', '培训'],
  172. '金融服务': ['银行', '理财', '投资', '股票', '基金'],
  173. '电子商务': ['商品', '店铺', '买家', '电商', '快递'],
  174. '餐饮美食': ['餐厅', '美食', '菜品', '厨师', '食物'],
  175. };
  176. for (const [industry, words] of Object.entries(keywords)) {
  177. if (words.some(w => input.includes(w))) {
  178. return industry;
  179. }
  180. }
  181. return '通用';
  182. }
  183. /**
  184. * 获取内容类型列表
  185. */
  186. getContentTypes() {
  187. return contentTypes;
  188. }
  189. /**
  190. * 获取所有内容类型(扁平)
  191. */
  192. getAllContentTypes() {
  193. const all = [];
  194. Object.values(contentTypes).forEach(types => all.push(...types));
  195. return [...new Set(all)];
  196. }
  197. /**
  198. * 统一内容生成
  199. */
  200. async generateContent(prompt, targetLength = 2000) {
  201. // 系统性知识展开
  202. const finalPrompt = `你是一位专业的老师。请围绕用户的主题,系统性地讲解这个知识点。
  203. 用户主题:${prompt}
  204. 要求:
  205. 1. 首先分析这个主题涉及的核心领域和知识体系
  206. 2. 按照"大类 -> 小类 -> 具体知识点"的层次结构展开讲解
  207. 3. 每个知识点都要讲清楚"是什么"、"为什么"、"怎么用"
  208. 4. 内容要准确、全面、深入浅出
  209. 5. 直接返回正文内容,用清晰的章节标题组织结构
  210. 6. 目标字数:${targetLength}字左右,如果内容有价值可以超出`;
  211. console.log('🤖 [AI内容生成] 最终Prompt:', finalPrompt);
  212. console.log('🤖 [AI内容生成] 使用模型:', this.modelId);
  213. const content = await this.callLLM(finalPrompt);
  214. return {
  215. content,
  216. type: '通用',
  217. industry: '通用',
  218. wordCount: content.length,
  219. debug: {
  220. model: this.modelId,
  221. finalPrompt
  222. }
  223. };
  224. }
  225. /**
  226. * AI自动判断内容类型和行业
  227. */
  228. async detectTypeAndIndustry(prompt) {
  229. const detectPrompt = `分析以下内容需求,判断其类型和所属行业。
  230. 需求内容:${prompt}
  231. 请以JSON格式返回:
  232. {"type": "内容类型", "industry": "所属行业"}
  233. 内容类型选项:小说、故事、剧本、诗歌、散文、营销文案、教育内容、商务内容、媒体内容
  234. 行业选项:通用、医疗健康、教育培训、金融服务、电子商务、餐饮美食、法律服务、新闻媒体
  235. 只返回JSON,不要其他内容。`;
  236. try {
  237. const response = await this.callLLM(detectPrompt);
  238. const jsonMatch = response.match(/\{[\s\S]*\}/);
  239. if (jsonMatch) {
  240. const parsed = JSON.parse(jsonMatch[0]);
  241. return {
  242. type: parsed.type || '通用',
  243. industry: parsed.industry || '通用',
  244. };
  245. }
  246. }
  247. catch (e) {
  248. console.log('类型检测失败,使用默认类型');
  249. }
  250. return { type: '通用', industry: '通用' };
  251. }
  252. /**
  253. * 生成小说
  254. */
  255. async generateNovel(prompt, targetLength) {
  256. let content = '';
  257. const chapterCount = Math.ceil(targetLength / 2000);
  258. const chapters = [];
  259. const outlinePrompt = `根据以下需求,为小说生成大纲:
  260. 需求:${prompt}
  261. 章节数:${chapterCount}章
  262. 请以JSON格式返回:
  263. {"chapters": [{"title": "第X章标题", "description": "章节概要"}]}`;
  264. try {
  265. const outlineResponse = await this.callLLM(outlinePrompt);
  266. const jsonMatch = outlineResponse.match(/\{[\s\S]*\}/);
  267. if (jsonMatch) {
  268. const parsed = JSON.parse(jsonMatch[0]);
  269. const chaptersOutline = parsed.chapters || [];
  270. for (let i = 0; i < chaptersOutline.length; i++) {
  271. const chapter = chaptersOutline[i];
  272. const chapterPrompt = `续写小说章节:
  273. 章节标题:${chapter.title}
  274. 章节概要:${chapter.description}
  275. 要求:
  276. 1. 内容丰富、生动,不少于1500字
  277. 2. 包含人物对话、心理描写、场景描写
  278. 3. 情节紧凑,有吸引力
  279. 4. 直接返回正文内容`;
  280. try {
  281. const chapterContent = await this.callLLM(chapterPrompt);
  282. chapters.push(`【${chapter.title}】\n\n${chapterContent}`);
  283. }
  284. catch {
  285. chapters.push(`【${chapter.title}】\n\n[内容生成失败]`);
  286. }
  287. }
  288. }
  289. }
  290. catch {
  291. // 如果大纲生成失败,直接根据主题生成
  292. }
  293. if (chapters.length === 0) {
  294. // 降级:直接生成单段内容
  295. const fallbackPrompt = `根据以下主题,写一篇${targetLength}字的小说:
  296. 主题:${prompt}
  297. 要求:
  298. 1. 内容丰富、生动
  299. 2. 包含人物对话、心理描写、场景描写
  300. 3. 直接返回正文内容`;
  301. content = await this.callLLM(fallbackPrompt);
  302. }
  303. else {
  304. content = chapters.join('\n\n');
  305. }
  306. return content;
  307. }
  308. /**
  309. * 生成营销文案
  310. */
  311. async generateMarketing(prompt, targetLength) {
  312. const marketingPrompt = `根据以下需求,写一篇营销文案:
  313. 需求:${prompt}
  314. 要求:
  315. 1. 语言生动,有感染力
  316. 2. 符合目标受众喜好
  317. 3. 字数:${targetLength}字左右
  318. 4. 直接返回正文内容,不要其他说明`;
  319. return await this.callLLM(marketingPrompt);
  320. }
  321. /**
  322. * 生成教育培训内容
  323. */
  324. async generateEducation(prompt, targetLength) {
  325. const educationPrompt = `根据以下需求,生成教育培训内容:
  326. 需求:${prompt}
  327. 要求:
  328. 1. 结构清晰,易于理解
  329. 2. 实用性强
  330. 3. 字数:${targetLength}字左右
  331. 4. 直接返回正文内容`;
  332. return await this.callLLM(educationPrompt);
  333. }
  334. /**
  335. * 生成商务内容
  336. */
  337. async generateBusiness(prompt, targetLength) {
  338. const businessPrompt = `根据以下需求,生成商务内容:
  339. 需求:${prompt}
  340. 要求:
  341. 1. 语言专业得体
  342. 2. 目的明确
  343. 3. 字数:${targetLength}字左右
  344. 4. 直接返回正文内容`;
  345. return await this.callLLM(businessPrompt);
  346. }
  347. /**
  348. * 生成媒体内容
  349. */
  350. async generateMedia(prompt) {
  351. const mediaPrompt = `根据以下需求,生成媒体播报内容:
  352. 需求:${prompt}
  353. 要求:
  354. 1. 语言清晰流畅
  355. 2. 适合朗读或播报
  356. 3. 直接返回正文内容`;
  357. return await this.callLLM(mediaPrompt);
  358. }
  359. /**
  360. * 通用生成
  361. */
  362. async generateGeneric(prompt, targetLength) {
  363. const genericPrompt = `请用中文生成内容:${prompt},大约${targetLength}字,直接返回内容不要加标题`;
  364. return await this.callLLM(genericPrompt);
  365. }
  366. /**
  367. * 获取行业列表
  368. */
  369. getIndustries() {
  370. return industries;
  371. }
  372. /**
  373. * 行业适配
  374. */
  375. async adaptContent(content, industry) {
  376. const industryConfig = {
  377. '医疗健康': { terminology: true, compliance: '医疗广告法', sensitivity: 'high' },
  378. '教育培训': { terminology: true, compliance: '教育规范', sensitivity: 'medium' },
  379. '金融服务': { terminology: true, compliance: '金融监管', sensitivity: 'high' },
  380. '电子商务': { terminology: false, compliance: '电商法规', sensitivity: 'low' },
  381. };
  382. return {
  383. adapted: content,
  384. config: industryConfig[industry] || { terminology: false, compliance: '通用', sensitivity: 'low' },
  385. warnings: industryConfig[industry]?.sensitivity === 'high' ? ['需遵守相关法规'] : [],
  386. };
  387. }
  388. /**
  389. * 内容规划
  390. */
  391. async planContent(type, theme, targetLength) {
  392. const chapters = Math.ceil(targetLength / 5000);
  393. const outline = [];
  394. for (let i = 1; i <= chapters; i++) {
  395. outline.push({
  396. chapter: i,
  397. title: `第${i}章`,
  398. summary: `${theme} - 章节内容概要`,
  399. estimatedLength: Math.ceil(targetLength / chapters),
  400. });
  401. }
  402. return {
  403. type,
  404. theme,
  405. totalChapters: chapters,
  406. estimatedLength: targetLength,
  407. outline,
  408. };
  409. }
  410. /**
  411. * 生成大纲 - 使用 LLM
  412. */
  413. async generateOutline(type, theme, chapters) {
  414. const prompt = `请为一部${type}生成大纲。
  415. 主题:${theme}
  416. 章节数:${chapters}章
  417. 请以JSON格式返回,格式如下:
  418. {
  419. "outline": [
  420. {"id": "chapter-1", "title": "第1章:xxx", "description": "本章情节描述", "wordCount": xxx},
  421. ...
  422. ]
  423. }
  424. 要求:
  425. 1. 每章标题要体现本章核心情节
  426. 2. 描述要详细说明本章发生的关键事件
  427. 3. 每章预估字数3000-5000字
  428. 4. 章节之间要有逻辑衔接`;
  429. try {
  430. const response = await this.callLLM(prompt);
  431. // 尝试解析JSON
  432. const jsonMatch = response.match(/\{[\s\S]*\}/);
  433. if (jsonMatch) {
  434. const parsed = JSON.parse(jsonMatch[0]);
  435. return {
  436. outlineId: `outline-${Date.now()}`,
  437. outline: parsed.outline || [],
  438. theme,
  439. };
  440. }
  441. // 如果无法解析JSON,返回模拟数据
  442. throw new Error('无法解析LLM响应');
  443. }
  444. catch (error) {
  445. console.log('大纲生成失败,使用默认大纲:', error);
  446. // 返回默认大纲
  447. const outline = [];
  448. for (let i = 1; i <= chapters; i++) {
  449. outline.push({
  450. id: `chapter-${i}`,
  451. title: `第${i}章:${theme}的展开`,
  452. description: `详细描述第${i}章的情节发展,包括人物互动和故事推进`,
  453. wordCount: 3500 + Math.floor(Math.random() * 1500),
  454. });
  455. }
  456. return { outlineId: `outline-${Date.now()}`, outline, theme };
  457. }
  458. }
  459. /**
  460. * 生成角色设定 - 使用 LLM
  461. */
  462. async generateCharacters(type, genre) {
  463. const prompt = `为一个${type}项目生成角色设定。
  464. 题材风格:${genre}
  465. 请生成2-4个主要角色,以JSON格式返回:
  466. {
  467. "characters": [
  468. {"id": "char-1", "name": "角色名", "age": 年龄, "gender": "男/女", "personality": "性格特点", "role": "主角/配角", "avatar": ""},
  469. ...
  470. ]
  471. }
  472. 要求:
  473. 1. 主角性格要鲜明,有成长空间
  474. 2. 配角要有独特个性
  475. 3. 人物关系要合理`;
  476. try {
  477. const response = await this.callLLM(prompt);
  478. const jsonMatch = response.match(/\{[\s\S]*\}/);
  479. if (jsonMatch) {
  480. const parsed = JSON.parse(jsonMatch[0]);
  481. return parsed;
  482. }
  483. throw new Error('无法解析LLM响应');
  484. }
  485. catch (error) {
  486. console.log('角色生成失败,使用默认角色:', error);
  487. return {
  488. characters: [
  489. { id: 'char-1', name: '林浩', age: 28, gender: '男', personality: '正直勇敢,有责任心', role: '主角', avatar: '' },
  490. { id: 'char-2', name: '苏晴', age: 26, gender: '女', personality: '聪明机智,温柔体贴', role: '女主', avatar: '' },
  491. ],
  492. };
  493. }
  494. }
  495. /**
  496. * 分步生成内容 - 使用 LLM
  497. */
  498. async generateChunk(outlineId, chapterIndex, chapterTitle, previousContent) {
  499. const prompt = `请续写以下小说内容:
  500. ${previousContent ? `前文内容:\n${previousContent}\n\n` : ''}
  501. 请续写第${chapterIndex + 1}章内容。
  502. ${chapterTitle ? `章节标题:${chapterTitle}` : ''}
  503. 要求:
  504. 1. 内容要丰富、生动,不少于2000字
  505. 2. 包含人物对话、心理描写、场景描写
  506. 3. 情节要紧凑,有吸引力
  507. 4. 直接返回正文内容,不需要额外说明`;
  508. try {
  509. const content = await this.callLLM(prompt);
  510. return {
  511. chunkId: `chunk-${Date.now()}-${chapterIndex}`,
  512. chapterIndex,
  513. content: content,
  514. wordCount: content.length,
  515. status: 'completed',
  516. };
  517. }
  518. catch (error) {
  519. console.log('内容生成失败:', error);
  520. return {
  521. chunkId: `chunk-${Date.now()}-${chapterIndex}`,
  522. chapterIndex,
  523. content: `第${chapterIndex + 1}章内容\n\n[AI生成内容因接口问题暂未返回,请稍后重试...]`,
  524. wordCount: 0,
  525. status: 'error',
  526. };
  527. }
  528. }
  529. /**
  530. * 流式生成章节内容 - 使用 LLM SSE
  531. */
  532. async *streamGenerateChunk(outlineId, chapterIndex, chapterTitle, previousContent) {
  533. const prompt = `请续写以下小说内容:
  534. ${previousContent ? `前文内容:\n${previousContent}\n\n` : ''}
  535. 请续写第${chapterIndex + 1}章内容。
  536. ${chapterTitle ? `章节标题:${chapterTitle}` : ''}
  537. 要求:
  538. 1. 内容要丰富、生动,不少于2000字
  539. 2. 包含人物对话、心理描写、场景描写
  540. 3. 情节要紧凑,有吸引力
  541. 4. 直接返回正文内容,不需要额外说明`;
  542. const chunkId = `chunk-${Date.now()}-${chapterIndex}`;
  543. let fullContent = '';
  544. let charCount = 0;
  545. // 先发送开始信号
  546. yield {
  547. type: 'start',
  548. chunkId,
  549. chapterIndex,
  550. message: '开始生成...',
  551. };
  552. try {
  553. for await (const chunk of this.streamLLM(prompt)) {
  554. fullContent += chunk;
  555. charCount += chunk.length;
  556. // 实时发送内容片段
  557. yield {
  558. type: 'content',
  559. chunkId,
  560. chapterIndex,
  561. content: chunk,
  562. charCount,
  563. message: `已生成 ${charCount} 字...`,
  564. };
  565. }
  566. // 发送完成信号
  567. yield {
  568. type: 'done',
  569. chunkId,
  570. chapterIndex,
  571. content: fullContent,
  572. charCount: fullContent.length,
  573. wordCount: this.estimateWordCount(fullContent),
  574. status: 'completed',
  575. message: '生成完成!',
  576. };
  577. }
  578. catch (error) {
  579. console.error('流式生成失败:', error);
  580. yield {
  581. type: 'error',
  582. chunkId,
  583. chapterIndex,
  584. message: error.message || '生成失败',
  585. status: 'error',
  586. };
  587. }
  588. }
  589. /**
  590. * 估算字数(中文按字符,英文按单词)
  591. */
  592. estimateWordCount(text) {
  593. const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
  594. const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
  595. return chineseChars + Math.floor(englishWords * 0.5);
  596. }
  597. /**
  598. * 流式生成(模拟SSE)
  599. */
  600. async *streamGenerate(outlineId) {
  601. const chunks = ['内容开始...', '情节发展...', '高潮迭起...', '最终结局...'];
  602. for (const chunk of chunks) {
  603. await new Promise(resolve => setTimeout(resolve, 500));
  604. yield { type: 'chunk', content: chunk };
  605. }
  606. yield { type: 'done', content: '' };
  607. }
  608. /**
  609. * 创建生成任务
  610. */
  611. createTask(type, title) {
  612. return {
  613. taskId: `task-${Date.now()}`,
  614. type,
  615. title,
  616. status: 'pending',
  617. progress: 0,
  618. createdAt: new Date().toISOString(),
  619. };
  620. }
  621. /**
  622. * 获取任务列表
  623. */
  624. getTasks() {
  625. return [];
  626. }
  627. /**
  628. * 章节连贯性检查
  629. */
  630. async checkCoherence(chapter1, chapter2) {
  631. return {
  632. score: 0.8 + Math.random() * 0.15,
  633. issues: [],
  634. suggestions: [],
  635. };
  636. }
  637. /**
  638. * 生成衔接段
  639. */
  640. async generateContinuity(previousChapter, nextTopic) {
  641. return {
  642. content: `[衔接段] 时光飞逝,转眼间来到了${nextTopic}...`,
  643. wordCount: 200,
  644. };
  645. }
  646. /**
  647. * 敏感词检测
  648. */
  649. async checkSensitive(text) {
  650. const sensitiveWords = ['敏感词1', '敏感词2', '违规词'];
  651. const found = sensitiveWords.filter(w => text.includes(w));
  652. return {
  653. isClean: found.length === 0,
  654. foundWords: found,
  655. positions: found.map((w, i) => ({ word: w, index: text.indexOf(w) })),
  656. suggestions: found.length > 0 ? ['建议替换敏感词'] : [],
  657. };
  658. }
  659. /**
  660. * 质量评分
  661. */
  662. async scoreQuality(text) {
  663. const dimensions = {};
  664. qualityDimensions.forEach(dim => {
  665. dimensions[dim] = 70 + Math.random() * 25;
  666. });
  667. const overall = Object.values(dimensions).reduce((a, b) => a + b, 0) / Object.values(dimensions).length;
  668. return {
  669. overall: Math.round(overall),
  670. dimensions,
  671. report: '内容质量分析报告...',
  672. };
  673. }
  674. /**
  675. * 内容优化
  676. */
  677. async optimizeContent(text, target) {
  678. const prompt = `请优化以下内容,使其${target}:
  679. 原文:
  680. ${text}
  681. 要求:
  682. 1. 保持原文核心意思
  683. 2. 语言更加生动、流畅
  684. 3. 直接返回优化后的内容`;
  685. try {
  686. const optimized = await this.callLLM(prompt);
  687. return {
  688. original: text,
  689. optimized: optimized,
  690. improvements: ['语言更生动', '结构更清晰'],
  691. };
  692. }
  693. catch (error) {
  694. return {
  695. original: text,
  696. optimized: `[优化后] ${text}`,
  697. improvements: ['语言更生动', '结构更清晰'],
  698. };
  699. }
  700. }
  701. /**
  702. * 获取支持的语言
  703. */
  704. getLanguages() {
  705. return languages;
  706. }
  707. /**
  708. * 翻译并生成
  709. */
  710. async translateAndGenerate(text, targetLang, voiceStyle) {
  711. return {
  712. translated: `[${targetLang}] ${text}`,
  713. voiceStyle,
  714. audioUrl: `https://example.com/audio/translated-${Date.now()}.mp3`,
  715. };
  716. }
  717. /**
  718. * 智能匹配BGM
  719. */
  720. async matchBGM(contentType, mood, genre) {
  721. return {
  722. bgmId: `bgm-${Date.now()}`,
  723. name: `${mood}${genre}风格音乐`,
  724. url: 'https://example.com/bgm/matched.mp3',
  725. duration: 180,
  726. };
  727. }
  728. /**
  729. * 获取音效列表
  730. */
  731. getSounds() {
  732. return [
  733. { id: 'sound-1', name: '新闻开场', type: '转场' },
  734. { id: 'sound-2', name: '轻快背景', type: '氛围' },
  735. { id: 'sound-3', name: '紧张时刻', type: '情感' },
  736. ];
  737. }
  738. /**
  739. * 情感调节
  740. */
  741. async adjustEmotion(text, targetEmotion) {
  742. const prompt = `请将以下内容的情感调整为${targetEmotion}风格:
  743. 原文:
  744. ${text}
  745. 要求:
  746. 1. 保持原文核心意思
  747. 2. 情感表达更加${targetEmotion}
  748. 3. 直接返回调整后的内容`;
  749. try {
  750. const adjusted = await this.callLLM(prompt);
  751. return {
  752. original: text,
  753. adjusted: adjusted,
  754. emotion: targetEmotion,
  755. intensity: 0.8,
  756. };
  757. }
  758. catch (error) {
  759. return {
  760. original: text,
  761. adjusted: `[${targetEmotion}风格] ${text}`,
  762. emotion: targetEmotion,
  763. intensity: 0.8,
  764. };
  765. }
  766. }
  767. /**
  768. * 获取情感选项
  769. */
  770. getEmotions() {
  771. return emotions;
  772. }
  773. /**
  774. * 多角色对话生成
  775. */
  776. async generateDialogue(characters, scenario) {
  777. const charactersDesc = characters.map(c => `${c.name}(音色:${c.voice})`).join('、');
  778. const prompt = `请为以下角色生成一段对话:
  779. 角色:${charactersDesc}
  780. 场景:${scenario}
  781. 要求:
  782. 1. 对话自然流畅,符合各角色性格
  783. 2. 推动情节发展
  784. 3. 直接返回对话内容`;
  785. try {
  786. const dialogue = await this.callLLM(prompt);
  787. const lines = dialogue.split('\n').filter(line => line.trim());
  788. return {
  789. lines: lines.map((line, i) => ({
  790. character: characters[i % characters.length]?.name || '未知',
  791. voice: characters[i % characters.length]?.voice || '',
  792. dialogue: line,
  793. })),
  794. scenario,
  795. };
  796. }
  797. catch (error) {
  798. const lines = characters.map((char, i) => ({
  799. character: char.name,
  800. voice: char.voice,
  801. dialogue: `这是${char.name}的对话内容...`,
  802. }));
  803. return { lines, scenario };
  804. }
  805. }
  806. /**
  807. * SEO优化
  808. */
  809. async optimizeSEO(title, content, platform) {
  810. const prompt = `请为以下内容进行SEO优化:
  811. 标题:${title}
  812. 内容:${content.slice(0, 500)}...
  813. 目标平台:${platform}
  814. 请以JSON格式返回:
  815. {
  816. "optimizedTitle": "优化后的标题",
  817. "keywords": ["关键词1", "关键词2", "关键词3"],
  818. "suggestions": ["优化建议1", "优化建议2"]
  819. }`;
  820. try {
  821. const response = await this.callLLM(prompt);
  822. const jsonMatch = response.match(/\{[\s\S]*\}/);
  823. if (jsonMatch) {
  824. return JSON.parse(jsonMatch[0]);
  825. }
  826. }
  827. catch (error) { }
  828. return {
  829. optimizedTitle: `[SEO优化] ${title}`,
  830. keywords: ['关键词1', '关键词2', '关键词3'],
  831. suggestions: ['标题添加数字', '内容分段优化'],
  832. };
  833. }
  834. /**
  835. * 合规检查
  836. */
  837. async checkCompliance(text, industry) {
  838. return {
  839. passed: true,
  840. issues: [],
  841. warnings: industry === '医疗健康' ? ['注意医疗广告法规'] : [],
  842. };
  843. }
  844. /**
  845. * 内容分析报告
  846. */
  847. async generateAnalytics(contentId) {
  848. return {
  849. contentId,
  850. wordCount: 5000,
  851. readingTime: 15,
  852. emotionCurve: [0.3, 0.5, 0.8, 0.6, 0.4],
  853. keywords: ['关键词1', '关键词2', '关键词3'],
  854. reportUrl: `https://example.com/analytics/${contentId}`,
  855. };
  856. }
  857. /**
  858. * 智能续写
  859. */
  860. async continueContent(text, direction) {
  861. const prompt = `请续写以下内容,方向:${direction}:
  862. 原文:
  863. ${text}
  864. 要求:
  865. 1. 保持原文风格
  866. 2. 情节自然发展
  867. 3. 提供2-3个不同的续写方向
  868. 4. 以JSON格式返回:
  869. {
  870. "continuations": [
  871. {"content": "续写方向1", "score": 0.9},
  872. {"content": "续写方向2", "score": 0.8}
  873. ],
  874. "selected": 0
  875. }`;
  876. try {
  877. const response = await this.callLLM(prompt);
  878. const jsonMatch = response.match(/\{[\s\S]*\}/);
  879. if (jsonMatch) {
  880. return JSON.parse(jsonMatch[0]);
  881. }
  882. }
  883. catch (error) { }
  884. return {
  885. continuations: [
  886. { content: `续写方向1: ${text}...`, score: 0.9 },
  887. { content: `续写方向2: ${text}...`, score: 0.8 },
  888. ],
  889. selected: 0,
  890. };
  891. }
  892. /**
  893. * 异步内容生成(支持进度更新)
  894. */
  895. async generateContentAsync(taskId, prompt, targetLength = 2000) {
  896. const updateTask = (updates) => {
  897. const task = exports.generateTasks.get(taskId);
  898. if (task) {
  899. Object.assign(task, updates);
  900. }
  901. };
  902. try {
  903. // 阶段1:分析需求
  904. updateTask({ progress: 10, message: '正在分析需求...' });
  905. await new Promise(resolve => setTimeout(resolve, 500));
  906. // 阶段2:构建Prompt
  907. updateTask({ progress: 20, message: '正在构建生成Prompt...' });
  908. await new Promise(resolve => setTimeout(resolve, 300));
  909. // 系统性知识展开
  910. const finalPrompt = `你是一位专业的老师。请围绕用户的主题,系统性地讲解这个知识点。
  911. 用户主题:${prompt}
  912. 要求:
  913. 1. 首先分析这个主题涉及的核心领域和知识体系
  914. 2. 按照"大类 -> 小类 -> 具体知识点"的层次结构展开讲解
  915. 3. 每个知识点都要讲清楚"是什么"、"为什么"、"怎么用"
  916. 4. 内容要准确、全面、深入浅出
  917. 5. 直接返回正文内容,用清晰的章节标题组织结构
  918. 6. 目标字数:${targetLength}字左右,如果内容有价值可以超出`;
  919. // 打印完整 Prompt,方便调试
  920. console.log('🤖 [AI异步内容生成] ========== 完整Prompt ==========');
  921. console.log(finalPrompt);
  922. console.log('🤖 [AI异步内容生成] ========== Prompt结束 ==========');
  923. console.log('🤖 [AI异步内容生成] 使用模型:', this.modelId);
  924. // 阶段3:调用AI
  925. updateTask({ progress: 30, message: '正在调用AI生成内容...' });
  926. const content = await this.callLLM(finalPrompt);
  927. // 阶段4:整理结果
  928. updateTask({ progress: 80, message: '正在整理生成结果...' });
  929. await new Promise(resolve => setTimeout(resolve, 200));
  930. // 阶段5:完成
  931. updateTask({ progress: 100, message: '生成完成!', status: 'completed' });
  932. const result = {
  933. content,
  934. type: '通用',
  935. industry: '通用',
  936. wordCount: content.length,
  937. // 添加调试信息
  938. debug: {
  939. prompt, // 用户原始输入
  940. finalPrompt, // 发送给AI的完整Prompt
  941. model: this.modelId, // 使用的模型
  942. }
  943. };
  944. updateTask({ result });
  945. return result;
  946. }
  947. catch (error) {
  948. console.error('❌ 异步内容生成失败:', error);
  949. updateTask({ status: 'failed', message: '生成失败: ' + error.message });
  950. throw error;
  951. }
  952. }
  953. }
  954. exports.AIContentService = AIContentService;
  955. exports.aiContentService = new AIContentService();