ai.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. import { Router } from 'express';
  2. import { body } from 'express-validator';
  3. import { aiService, type ChatMessage } from '../ai/index.js';
  4. import { authenticate } from '../middleware/auth.js';
  5. import { asyncHandler, AppError } from '../middleware/error.js';
  6. import { validateRequest } from '../middleware/validate.js';
  7. import { HTTP_STATUS, ERROR_CODES } from '@media-manager/shared';
  8. const router = Router();
  9. router.use(authenticate);
  10. // 检查 AI 服务是否可用的中间件
  11. const checkAIAvailable = asyncHandler(async (_req, _res, next) => {
  12. if (!aiService.isAvailable()) {
  13. throw new AppError('AI 服务未配置', HTTP_STATUS.SERVICE_UNAVAILABLE, ERROR_CODES.SERVICE_UNAVAILABLE);
  14. }
  15. next();
  16. });
  17. // ==================== 基础 API ====================
  18. // 检查 AI 服务状态
  19. router.get(
  20. '/status',
  21. asyncHandler(async (_req, res) => {
  22. res.json({
  23. success: true,
  24. data: {
  25. available: aiService.isAvailable(),
  26. models: aiService.isAvailable() ? aiService.getAvailableModels() : null,
  27. },
  28. });
  29. })
  30. );
  31. // 通用聊天接口
  32. router.post(
  33. '/chat',
  34. checkAIAvailable,
  35. [
  36. body('prompt').notEmpty().withMessage('提示内容不能为空'),
  37. validateRequest,
  38. ],
  39. asyncHandler(async (req, res) => {
  40. const { prompt, systemPrompt, model } = req.body;
  41. const response = await aiService.chat(prompt, systemPrompt, model);
  42. res.json({ success: true, data: response });
  43. })
  44. );
  45. // 高级聊天补全接口
  46. router.post(
  47. '/chat/completion',
  48. checkAIAvailable,
  49. [
  50. body('messages').isArray({ min: 1 }).withMessage('消息列表不能为空'),
  51. validateRequest,
  52. ],
  53. asyncHandler(async (req, res) => {
  54. const { messages, model, temperature, maxTokens, topP, responseFormat } = req.body;
  55. const response = await aiService.chatCompletion({
  56. messages: messages as ChatMessage[],
  57. model,
  58. temperature,
  59. maxTokens,
  60. topP,
  61. responseFormat,
  62. });
  63. res.json({ success: true, data: response });
  64. })
  65. );
  66. // 流式聊天接口 (SSE)
  67. router.post(
  68. '/chat/stream',
  69. checkAIAvailable,
  70. [
  71. body('messages').isArray({ min: 1 }).withMessage('消息列表不能为空'),
  72. validateRequest,
  73. ],
  74. asyncHandler(async (req, res) => {
  75. const { messages, model, temperature, maxTokens } = req.body;
  76. // 设置 SSE 响应头
  77. res.setHeader('Content-Type', 'text/event-stream');
  78. res.setHeader('Cache-Control', 'no-cache');
  79. res.setHeader('Connection', 'keep-alive');
  80. res.setHeader('X-Accel-Buffering', 'no');
  81. try {
  82. await aiService.chatCompletionStream(
  83. {
  84. messages: messages as ChatMessage[],
  85. model,
  86. temperature,
  87. maxTokens,
  88. },
  89. (chunk, done) => {
  90. if (done) {
  91. res.write(`data: [DONE]\n\n`);
  92. res.end();
  93. } else {
  94. res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
  95. }
  96. }
  97. );
  98. } catch (error) {
  99. res.write(`data: ${JSON.stringify({ error: (error as Error).message })}\n\n`);
  100. res.end();
  101. }
  102. })
  103. );
  104. // 快速聊天(使用快速模型)
  105. router.post(
  106. '/chat/quick',
  107. checkAIAvailable,
  108. [
  109. body('prompt').notEmpty().withMessage('提示内容不能为空'),
  110. validateRequest,
  111. ],
  112. asyncHandler(async (req, res) => {
  113. const { prompt, systemPrompt } = req.body;
  114. const response = await aiService.quickChat(prompt, systemPrompt);
  115. res.json({ success: true, data: response });
  116. })
  117. );
  118. // 代码聊天(使用代码模型)
  119. router.post(
  120. '/chat/code',
  121. checkAIAvailable,
  122. [
  123. body('prompt').notEmpty().withMessage('提示内容不能为空'),
  124. validateRequest,
  125. ],
  126. asyncHandler(async (req, res) => {
  127. const { prompt, systemPrompt } = req.body;
  128. const response = await aiService.codeChat(prompt, systemPrompt);
  129. res.json({ success: true, data: response });
  130. })
  131. );
  132. // 推理聊天(使用推理模型)
  133. router.post(
  134. '/chat/reasoning',
  135. checkAIAvailable,
  136. [
  137. body('prompt').notEmpty().withMessage('提示内容不能为空'),
  138. validateRequest,
  139. ],
  140. asyncHandler(async (req, res) => {
  141. const { prompt, systemPrompt } = req.body;
  142. const response = await aiService.reasoningChat(prompt, systemPrompt);
  143. res.json({ success: true, data: response });
  144. })
  145. );
  146. // ==================== 视觉理解 ====================
  147. // 图像分析
  148. router.post(
  149. '/vision/analyze',
  150. checkAIAvailable,
  151. [
  152. body('prompt').notEmpty().withMessage('提示内容不能为空'),
  153. validateRequest,
  154. ],
  155. asyncHandler(async (req, res) => {
  156. const { prompt, imageUrl, imageBase64, model, maxTokens } = req.body;
  157. if (!imageUrl && !imageBase64) {
  158. throw new AppError('必须提供图片URL或Base64数据', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION);
  159. }
  160. const response = await aiService.analyzeImage({
  161. prompt,
  162. imageUrl,
  163. imageBase64,
  164. model,
  165. maxTokens,
  166. });
  167. res.json({ success: true, data: response });
  168. })
  169. );
  170. // 登录状态分析(专用于浏览器登录辅助)
  171. router.post(
  172. '/vision/login-status',
  173. checkAIAvailable,
  174. [
  175. body('imageBase64').notEmpty().withMessage('截图数据不能为空'),
  176. body('platform').notEmpty().withMessage('平台名称不能为空'),
  177. validateRequest,
  178. ],
  179. asyncHandler(async (req, res) => {
  180. const { imageBase64, platform } = req.body;
  181. const result = await aiService.analyzeLoginStatus(imageBase64, platform);
  182. res.json({ success: true, data: result });
  183. })
  184. );
  185. // 账号信息提取(从截图中提取账号信息)
  186. router.post(
  187. '/vision/extract-account',
  188. checkAIAvailable,
  189. [
  190. body('imageBase64').notEmpty().withMessage('截图数据不能为空'),
  191. body('platform').notEmpty().withMessage('平台名称不能为空'),
  192. validateRequest,
  193. ],
  194. asyncHandler(async (req, res) => {
  195. const { imageBase64, platform } = req.body;
  196. const result = await aiService.extractAccountInfo(imageBase64, platform);
  197. res.json({ success: true, data: result });
  198. })
  199. );
  200. // 页面操作指导(AI 指导用户进行操作 - 基于截图)
  201. router.post(
  202. '/vision/operation-guide',
  203. checkAIAvailable,
  204. [
  205. body('imageBase64').notEmpty().withMessage('截图数据不能为空'),
  206. body('platform').notEmpty().withMessage('平台名称不能为空'),
  207. body('goal').notEmpty().withMessage('操作目标不能为空'),
  208. validateRequest,
  209. ],
  210. asyncHandler(async (req, res) => {
  211. const { imageBase64, platform, goal } = req.body;
  212. const result = await aiService.getPageOperationGuide(imageBase64, platform, goal);
  213. res.json({ success: true, data: result });
  214. })
  215. );
  216. // 页面操作指导(AI 指导用户进行操作 - 基于 HTML)
  217. router.post(
  218. '/html/operation-guide',
  219. checkAIAvailable,
  220. [
  221. body('html').notEmpty().withMessage('HTML 内容不能为空'),
  222. body('platform').notEmpty().withMessage('平台名称不能为空'),
  223. body('goal').notEmpty().withMessage('操作目标不能为空'),
  224. validateRequest,
  225. ],
  226. asyncHandler(async (req, res) => {
  227. const { html, platform, goal } = req.body;
  228. const result = await aiService.analyzeHtmlForOperation(html, platform, goal);
  229. res.json({ success: true, data: result });
  230. })
  231. );
  232. // ==================== 发布辅助 ====================
  233. // 分析发布页面状态(检测验证码、发布结果等)
  234. router.post(
  235. '/publish/analyze-status',
  236. checkAIAvailable,
  237. [
  238. body('imageBase64').notEmpty().withMessage('截图数据不能为空'),
  239. body('platform').notEmpty().withMessage('平台名称不能为空'),
  240. validateRequest,
  241. ],
  242. asyncHandler(async (req, res) => {
  243. const { imageBase64, platform } = req.body;
  244. const result = await aiService.analyzePublishStatus(imageBase64, platform);
  245. res.json({ success: true, data: result });
  246. })
  247. );
  248. // 分析发布页面 HTML 获取操作指导
  249. router.post(
  250. '/publish/operation-guide',
  251. checkAIAvailable,
  252. [
  253. body('html').notEmpty().withMessage('HTML 内容不能为空'),
  254. body('platform').notEmpty().withMessage('平台名称不能为空'),
  255. body('currentStatus').notEmpty().withMessage('当前状态不能为空'),
  256. validateRequest,
  257. ],
  258. asyncHandler(async (req, res) => {
  259. const { html, platform, currentStatus } = req.body;
  260. const result = await aiService.analyzePublishPageHtml(html, platform, currentStatus);
  261. res.json({ success: true, data: result });
  262. })
  263. );
  264. // ==================== 文本嵌入 ====================
  265. // 生成嵌入向量
  266. router.post(
  267. '/embedding',
  268. checkAIAvailable,
  269. [
  270. body('input').notEmpty().withMessage('输入文本不能为空'),
  271. validateRequest,
  272. ],
  273. asyncHandler(async (req, res) => {
  274. const { input, model, dimensions } = req.body;
  275. const embeddings = await aiService.createEmbedding({ input, model, dimensions });
  276. res.json({ success: true, data: embeddings });
  277. })
  278. );
  279. // 计算文本相似度
  280. router.post(
  281. '/similarity',
  282. checkAIAvailable,
  283. [
  284. body('text1').notEmpty().withMessage('文本1不能为空'),
  285. body('text2').notEmpty().withMessage('文本2不能为空'),
  286. validateRequest,
  287. ],
  288. asyncHandler(async (req, res) => {
  289. const { text1, text2 } = req.body;
  290. const similarity = await aiService.calculateSimilarity(text1, text2);
  291. res.json({ success: true, data: { similarity } });
  292. })
  293. );
  294. // ==================== 文本处理 ====================
  295. // 文本摘要
  296. router.post(
  297. '/summarize',
  298. checkAIAvailable,
  299. [
  300. body('content').notEmpty().withMessage('内容不能为空'),
  301. validateRequest,
  302. ],
  303. asyncHandler(async (req, res) => {
  304. const { content, maxLength } = req.body;
  305. const summary = await aiService.summarize(content, maxLength);
  306. res.json({ success: true, data: summary });
  307. })
  308. );
  309. // 文本翻译
  310. router.post(
  311. '/translate',
  312. checkAIAvailable,
  313. [
  314. body('text').notEmpty().withMessage('文本不能为空'),
  315. body('targetLang').notEmpty().withMessage('目标语言不能为空'),
  316. validateRequest,
  317. ],
  318. asyncHandler(async (req, res) => {
  319. const { text, targetLang, sourceLang } = req.body;
  320. const translated = await aiService.translate(text, targetLang, sourceLang);
  321. res.json({ success: true, data: translated });
  322. })
  323. );
  324. // 关键词提取
  325. router.post(
  326. '/keywords',
  327. checkAIAvailable,
  328. [
  329. body('text').notEmpty().withMessage('文本不能为空'),
  330. validateRequest,
  331. ],
  332. asyncHandler(async (req, res) => {
  333. const { text, count } = req.body;
  334. const keywords = await aiService.extractKeywords(text, count);
  335. res.json({ success: true, data: keywords });
  336. })
  337. );
  338. // 内容审核
  339. router.post(
  340. '/moderate',
  341. checkAIAvailable,
  342. [
  343. body('content').notEmpty().withMessage('内容不能为空'),
  344. validateRequest,
  345. ],
  346. asyncHandler(async (req, res) => {
  347. const { content } = req.body;
  348. const result = await aiService.moderateContent(content);
  349. res.json({ success: true, data: result });
  350. })
  351. );
  352. // ==================== 自媒体业务场景 ====================
  353. // 生成标题
  354. router.post(
  355. '/generate-title',
  356. checkAIAvailable,
  357. [
  358. body('description').notEmpty().withMessage('描述不能为空'),
  359. body('platform').notEmpty().withMessage('平台不能为空'),
  360. validateRequest,
  361. ],
  362. asyncHandler(async (req, res) => {
  363. const titles = await aiService.generateTitle({
  364. description: req.body.description,
  365. platform: req.body.platform,
  366. maxLength: req.body.maxLength,
  367. });
  368. res.json({ success: true, data: titles });
  369. })
  370. );
  371. // 生成标签
  372. router.post(
  373. '/generate-tags',
  374. checkAIAvailable,
  375. [
  376. body('title').notEmpty().withMessage('标题不能为空'),
  377. body('platform').notEmpty().withMessage('平台不能为空'),
  378. validateRequest,
  379. ],
  380. asyncHandler(async (req, res) => {
  381. const tags = await aiService.generateTags({
  382. title: req.body.title,
  383. description: req.body.description,
  384. platform: req.body.platform,
  385. maxTags: req.body.maxTags,
  386. });
  387. res.json({ success: true, data: tags });
  388. })
  389. );
  390. // 优化描述
  391. router.post(
  392. '/optimize-description',
  393. checkAIAvailable,
  394. [
  395. body('original').notEmpty().withMessage('原始描述不能为空'),
  396. body('platform').notEmpty().withMessage('平台不能为空'),
  397. validateRequest,
  398. ],
  399. asyncHandler(async (req, res) => {
  400. const optimized = await aiService.optimizeDescription({
  401. original: req.body.original,
  402. platform: req.body.platform,
  403. maxLength: req.body.maxLength,
  404. });
  405. res.json({ success: true, data: optimized });
  406. })
  407. );
  408. // 生成评论回复
  409. router.post(
  410. '/generate-reply',
  411. checkAIAvailable,
  412. [
  413. body('comment').notEmpty().withMessage('评论内容不能为空'),
  414. body('authorName').notEmpty().withMessage('评论作者不能为空'),
  415. validateRequest,
  416. ],
  417. asyncHandler(async (req, res) => {
  418. const replies = await aiService.generateReply({
  419. comment: req.body.comment,
  420. authorName: req.body.authorName,
  421. context: req.body.context,
  422. tone: req.body.tone,
  423. });
  424. res.json({ success: true, data: replies });
  425. })
  426. );
  427. // 推荐发布时间
  428. router.post(
  429. '/recommend-time',
  430. checkAIAvailable,
  431. [
  432. body('platform').notEmpty().withMessage('平台不能为空'),
  433. body('contentType').notEmpty().withMessage('内容类型不能为空'),
  434. validateRequest,
  435. ],
  436. asyncHandler(async (req, res) => {
  437. const times = await aiService.recommendPublishTime({
  438. platform: req.body.platform,
  439. contentType: req.body.contentType,
  440. targetAudience: req.body.targetAudience,
  441. });
  442. res.json({ success: true, data: times });
  443. })
  444. );
  445. export default router;