import { Router } from 'express'; import { body } from 'express-validator'; import { aiService, type ChatMessage } from '../ai/index.js'; import { authenticate } from '../middleware/auth.js'; import { asyncHandler, AppError } from '../middleware/error.js'; import { validateRequest } from '../middleware/validate.js'; import { HTTP_STATUS, ERROR_CODES } from '@media-manager/shared'; const router = Router(); router.use(authenticate); // 检查 AI 服务是否可用的中间件 const checkAIAvailable = asyncHandler(async (_req, _res, next) => { if (!aiService.isAvailable()) { throw new AppError('AI 服务未配置', HTTP_STATUS.SERVICE_UNAVAILABLE, ERROR_CODES.SERVICE_UNAVAILABLE); } next(); }); // ==================== 基础 API ==================== // 检查 AI 服务状态 router.get( '/status', asyncHandler(async (_req, res) => { res.json({ success: true, data: { available: aiService.isAvailable(), models: aiService.isAvailable() ? aiService.getAvailableModels() : null, }, }); }) ); // 通用聊天接口 router.post( '/chat', checkAIAvailable, [ body('prompt').notEmpty().withMessage('提示内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { prompt, systemPrompt, model } = req.body; const response = await aiService.chat(prompt, systemPrompt, model); res.json({ success: true, data: response }); }) ); // 高级聊天补全接口 router.post( '/chat/completion', checkAIAvailable, [ body('messages').isArray({ min: 1 }).withMessage('消息列表不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { messages, model, temperature, maxTokens, topP, responseFormat } = req.body; const response = await aiService.chatCompletion({ messages: messages as ChatMessage[], model, temperature, maxTokens, topP, responseFormat, }); res.json({ success: true, data: response }); }) ); // 流式聊天接口 (SSE) router.post( '/chat/stream', checkAIAvailable, [ body('messages').isArray({ min: 1 }).withMessage('消息列表不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { messages, model, temperature, maxTokens } = req.body; // 设置 SSE 响应头 res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); try { await aiService.chatCompletionStream( { messages: messages as ChatMessage[], model, temperature, maxTokens, }, (chunk, done) => { if (done) { res.write(`data: [DONE]\n\n`); res.end(); } else { res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`); } } ); } catch (error) { res.write(`data: ${JSON.stringify({ error: (error as Error).message })}\n\n`); res.end(); } }) ); // 快速聊天(使用快速模型) router.post( '/chat/quick', checkAIAvailable, [ body('prompt').notEmpty().withMessage('提示内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { prompt, systemPrompt } = req.body; const response = await aiService.quickChat(prompt, systemPrompt); res.json({ success: true, data: response }); }) ); // 代码聊天(使用代码模型) router.post( '/chat/code', checkAIAvailable, [ body('prompt').notEmpty().withMessage('提示内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { prompt, systemPrompt } = req.body; const response = await aiService.codeChat(prompt, systemPrompt); res.json({ success: true, data: response }); }) ); // 推理聊天(使用推理模型) router.post( '/chat/reasoning', checkAIAvailable, [ body('prompt').notEmpty().withMessage('提示内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { prompt, systemPrompt } = req.body; const response = await aiService.reasoningChat(prompt, systemPrompt); res.json({ success: true, data: response }); }) ); // ==================== 视觉理解 ==================== // 图像分析 router.post( '/vision/analyze', checkAIAvailable, [ body('prompt').notEmpty().withMessage('提示内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { prompt, imageUrl, imageBase64, model, maxTokens } = req.body; if (!imageUrl && !imageBase64) { throw new AppError('必须提供图片URL或Base64数据', HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION); } const response = await aiService.analyzeImage({ prompt, imageUrl, imageBase64, model, maxTokens, }); res.json({ success: true, data: response }); }) ); // 登录状态分析(专用于浏览器登录辅助) router.post( '/vision/login-status', checkAIAvailable, [ body('imageBase64').notEmpty().withMessage('截图数据不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { imageBase64, platform } = req.body; const result = await aiService.analyzeLoginStatus(imageBase64, platform); res.json({ success: true, data: result }); }) ); // 账号信息提取(从截图中提取账号信息) router.post( '/vision/extract-account', checkAIAvailable, [ body('imageBase64').notEmpty().withMessage('截图数据不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { imageBase64, platform } = req.body; const result = await aiService.extractAccountInfo(imageBase64, platform); res.json({ success: true, data: result }); }) ); // 页面操作指导(AI 指导用户进行操作 - 基于截图) router.post( '/vision/operation-guide', checkAIAvailable, [ body('imageBase64').notEmpty().withMessage('截图数据不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), body('goal').notEmpty().withMessage('操作目标不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { imageBase64, platform, goal } = req.body; const result = await aiService.getPageOperationGuide(imageBase64, platform, goal); res.json({ success: true, data: result }); }) ); // 页面操作指导(AI 指导用户进行操作 - 基于 HTML) router.post( '/html/operation-guide', checkAIAvailable, [ body('html').notEmpty().withMessage('HTML 内容不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), body('goal').notEmpty().withMessage('操作目标不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { html, platform, goal } = req.body; const result = await aiService.analyzeHtmlForOperation(html, platform, goal); res.json({ success: true, data: result }); }) ); // ==================== 发布辅助 ==================== // 分析发布页面状态(检测验证码、发布结果等) router.post( '/publish/analyze-status', checkAIAvailable, [ body('imageBase64').notEmpty().withMessage('截图数据不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { imageBase64, platform } = req.body; const result = await aiService.analyzePublishStatus(imageBase64, platform); res.json({ success: true, data: result }); }) ); // 分析发布页面 HTML 获取操作指导 router.post( '/publish/operation-guide', checkAIAvailable, [ body('html').notEmpty().withMessage('HTML 内容不能为空'), body('platform').notEmpty().withMessage('平台名称不能为空'), body('currentStatus').notEmpty().withMessage('当前状态不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { html, platform, currentStatus } = req.body; const result = await aiService.analyzePublishPageHtml(html, platform, currentStatus); res.json({ success: true, data: result }); }) ); // ==================== 文本嵌入 ==================== // 生成嵌入向量 router.post( '/embedding', checkAIAvailable, [ body('input').notEmpty().withMessage('输入文本不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { input, model, dimensions } = req.body; const embeddings = await aiService.createEmbedding({ input, model, dimensions }); res.json({ success: true, data: embeddings }); }) ); // 计算文本相似度 router.post( '/similarity', checkAIAvailable, [ body('text1').notEmpty().withMessage('文本1不能为空'), body('text2').notEmpty().withMessage('文本2不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { text1, text2 } = req.body; const similarity = await aiService.calculateSimilarity(text1, text2); res.json({ success: true, data: { similarity } }); }) ); // ==================== 文本处理 ==================== // 文本摘要 router.post( '/summarize', checkAIAvailable, [ body('content').notEmpty().withMessage('内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { content, maxLength } = req.body; const summary = await aiService.summarize(content, maxLength); res.json({ success: true, data: summary }); }) ); // 文本翻译 router.post( '/translate', checkAIAvailable, [ body('text').notEmpty().withMessage('文本不能为空'), body('targetLang').notEmpty().withMessage('目标语言不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { text, targetLang, sourceLang } = req.body; const translated = await aiService.translate(text, targetLang, sourceLang); res.json({ success: true, data: translated }); }) ); // 关键词提取 router.post( '/keywords', checkAIAvailable, [ body('text').notEmpty().withMessage('文本不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { text, count } = req.body; const keywords = await aiService.extractKeywords(text, count); res.json({ success: true, data: keywords }); }) ); // 内容审核 router.post( '/moderate', checkAIAvailable, [ body('content').notEmpty().withMessage('内容不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const { content } = req.body; const result = await aiService.moderateContent(content); res.json({ success: true, data: result }); }) ); // ==================== 自媒体业务场景 ==================== // 生成标题 router.post( '/generate-title', checkAIAvailable, [ body('description').notEmpty().withMessage('描述不能为空'), body('platform').notEmpty().withMessage('平台不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const titles = await aiService.generateTitle({ description: req.body.description, platform: req.body.platform, maxLength: req.body.maxLength, }); res.json({ success: true, data: titles }); }) ); // 生成标签 router.post( '/generate-tags', checkAIAvailable, [ body('title').notEmpty().withMessage('标题不能为空'), body('platform').notEmpty().withMessage('平台不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const tags = await aiService.generateTags({ title: req.body.title, description: req.body.description, platform: req.body.platform, maxTags: req.body.maxTags, }); res.json({ success: true, data: tags }); }) ); // 优化描述 router.post( '/optimize-description', checkAIAvailable, [ body('original').notEmpty().withMessage('原始描述不能为空'), body('platform').notEmpty().withMessage('平台不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const optimized = await aiService.optimizeDescription({ original: req.body.original, platform: req.body.platform, maxLength: req.body.maxLength, }); res.json({ success: true, data: optimized }); }) ); // 生成评论回复 router.post( '/generate-reply', checkAIAvailable, [ body('comment').notEmpty().withMessage('评论内容不能为空'), body('authorName').notEmpty().withMessage('评论作者不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const replies = await aiService.generateReply({ comment: req.body.comment, authorName: req.body.authorName, context: req.body.context, tone: req.body.tone, }); res.json({ success: true, data: replies }); }) ); // 推荐发布时间 router.post( '/recommend-time', checkAIAvailable, [ body('platform').notEmpty().withMessage('平台不能为空'), body('contentType').notEmpty().withMessage('内容类型不能为空'), validateRequest, ], asyncHandler(async (req, res) => { const times = await aiService.recommendPublishTime({ platform: req.body.platform, contentType: req.body.contentType, targetAudience: req.body.targetAudience, }); res.json({ success: true, data: times }); }) ); export default router;