| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- 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;
|