返回 AiToEarn
drama-recap.mcp.ts
根目录 / project / aitoearn-backend / apps / aitoearn-ai / src / core / agent / mcp / volcengine / drama-recap.mcp.ts
1 import { createSdkMcpServer, McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk'
2 import { Injectable, Logger } from '@nestjs/common'
3 import { UserType } from '@yikart/common'
4 import { z } from 'zod'
5 import { AiAvailabilityService } from '../../../ai-availability'
6 import { DramaRecapService } from '../../../ai/aideo'
7 import { DramaRecapTaskStatus } from '../../../ai/libs/volcengine'
8 import { McpServerName } from '../../agent.constants'
9 import { errorResult, successResult, wrapTool } from '../mcp.utils'
10
11 const submitDramaRecapTaskSchema = z.object({
12 vids: z.array(z.string()),
13 dramaScriptTaskId: z.string().optional(),
14 recapText: z.string().optional(),
15 speakerConfig: z.object({
16 appId: z.string(),
17 cluster: z.string(),
18 voiceType: z.string(),
19 }).optional(),
20 isEraseSubtitle: z.boolean().optional(),
21 fontConfig: z.object({
22 color: z.string().optional(),
23 size: z.number().optional(),
24 name: z.string().optional(),
25 }).optional(),
26 recapStyle: z.string().optional(),
27 recapTextSpeed: z.number().min(0.5).max(2.0).optional(),
28 recapTextLength: z.number().max(5000).optional(),
29 pauseTime: z.number().min(1).max(1000).optional(),
30 allowRepeatMatch: z.boolean().optional(),
31 batchGenerateCount: z.number().min(1).optional(),
32 })
33
34 const getDramaRecapTaskStatusSchema = z.object({
35 taskId: z.string(),
36 })
37
38 export enum DramaRecapToolName {
39 SubmitDramaRecapTask = 'submitDramaRecapTask',
40 GetDramaRecapTaskStatus = 'getDramaRecapTaskStatus',
41 }
42
43 @Injectable()
44 export class DramaRecapMcp {
45 private readonly logger = new Logger(DramaRecapMcp.name)
46
47 constructor(
48 private readonly dramaRecapService: DramaRecapService,
49 private readonly aiAvailability: AiAvailabilityService,
50 ) { }
51
52 createSubmitDramaRecapTaskTool(userId: string, userType: UserType) {
53 return wrapTool(
54 this.logger,
55 DramaRecapToolName.SubmitDramaRecapTask,
56 `Submit a drama recap (narration) task to generate narrated videos from short drama clips.
57
58 **IMPORTANT: DO NOT call getVideoInfo before this task.**
59
60 **Parameters**:
61 - vids: Array of video VIDs (vid://xxx format). URLs auto-uploaded.
62 - dramaScriptTaskId: Optional (auto-generated if not provided)
63 - recapText: Custom narration text (optional, auto-generated if not provided)
64 - speakerConfig: Voice synthesis config (appId, cluster, voiceType)
65 - isEraseSubtitle: Erase subtitles (default: true)
66 - fontConfig: Subtitle font config (color, size, name)
67 - recapStyle: AI narration style (e.g., "humorous", "suspenseful", "light")
68 - recapTextSpeed: Speech speed (0.5-2.0, default: 1.2)
69 - recapTextLength: Expected text length in chars (max: 5000)
70 - pauseTime: Pause between sentences in ms (1-1000, default: 120)
71 - batchGenerateCount: Number of videos to generate (default: 1)
72
73 Processing time: ~10 minutes per 1-minute video. Returns taskId and dramaScriptTaskId.`,
74 submitDramaRecapTaskSchema.shape,
75 async ({ ...params }) => {
76 const result = await this.dramaRecapService.submitDramaRecapTask({
77 userId,
78 userType,
79 ...params,
80 })
81 return successResult(`Drama recap task submitted successfully. TaskId: ${result.taskId}, DramaScriptTaskId: ${result.dramaScriptTaskId}`)
82 },
83 this.aiAvailability,
84 )
85 }
86
87 createGetDramaRecapTaskStatusTool(userId: string, userType: UserType) {
88 return wrapTool(
89 this.logger,
90 DramaRecapToolName.GetDramaRecapTaskStatus,
91 `Get drama recap task status and results.
92
93 **Returns**: Status (Processing/Completed/Failed), outputVid and outputUrl on completion.
94
95 **Error Codes**: 400100 (Invalid params), 500100 (Internal error), 500110 (Slicing failed), 500111 (Audio extraction failed), 500120 (Script restoration failed), 500130 (Narration generation failed), 400200 (Duration too long, max 60min), 400201 (Too many words).`,
96 getDramaRecapTaskStatusSchema.shape,
97 async ({ taskId }) => {
98 const result = await this.dramaRecapService.getDramaRecapTask({
99 userId,
100 userType,
101 taskId,
102 })
103
104 if (result.status === DramaRecapTaskStatus.Completed) {
105 if (result.outputVid) {
106 return successResult(`Task completed successfully! Output video VID: ${result.outputVid}${result.outputUrl ? `, URL: ${result.outputUrl}` : ''}`)
107 }
108 else {
109 return successResult('Task completed successfully!')
110 }
111 }
112 else if (result.status === DramaRecapTaskStatus.Processing) {
113 return successResult('Task is still running. Please continue to wait...')
114 }
115 else {
116 return errorResult(`Task failed: ${result.errorMessage || 'Unknown error'}`)
117 }
118 },
119 this.aiAvailability,
120 )
121 }
122
123 createServer(userId: string, userType: UserType): McpSdkServerConfigWithInstance {
124 return createSdkMcpServer({
125 name: McpServerName.DramaRecap,
126 version: '1.0.0',
127 tools: [
128 this.createSubmitDramaRecapTaskTool(userId, userType),
129 this.createGetDramaRecapTaskStatusTool(userId, userType),
130 ],
131 })
132 }
133 }
134
134 lines TYPESCRIPT