UNPKG

cerevox

Version:

TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments

1,311 lines (1,297 loc) 50.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AI = void 0; const constants_1 = require("../utils/constants"); const base_1 = require("./base"); const session_1 = require("./session"); const path_1 = __importDefault(require("path")); const coze_1 = require("../utils/coze"); const uuid_1 = require("uuid"); async function waitForWorkflowFinish(taskUrl, apiKey = process.env.CEREVOX_API_KEY, logger, onProgress, interval = 1500) { while (true) { const resData = await (0, coze_1.queryWorkflowRunHistory)(taskUrl, apiKey); logger.debug(`get workflow status: ${JSON.stringify(resData)}`); console.debug(`get workflow status: ${JSON.stringify(resData)}`); onProgress?.(resData); if (resData.status === 'Fail') { const errMsg = JSON.stringify({ ...resData, debug_url: undefined, }); throw new Error(`Video generation failed: ${errMsg}`); } if (resData.status === 'Success') { return { ...JSON.parse(resData.data.Output), // debug_url: resData.debug_url, }; } await new Promise(resolve => setTimeout(resolve, interval || 1500)); } } async function waitForVideoTaskComplete(sandbox, taskUrl, logger, onProgress, interval = 1500) { const headers = { 'Content-Type': 'application/json', }; while (true) { const response = await sandbox.request(taskUrl, { method: 'GET', headers: headers, }); if (!response.ok) { throw new Error(`Video generation failed: ${response.statusText}`); } const data = await response.json(); logger.debug(`get video task status: ${JSON.stringify(data)}`); onProgress?.(data); if (data.status === 'succeeded' || data.status === 'succeed') { return data; } if (data.status === 'failed' || data.status === 'Fail' || !response.ok) { throw new Error(`Video generation failed: ${JSON.stringify(data)}`); } if (data.status === 'canceled') { throw new Error(`Video generation canceled: ${JSON.stringify(data)}`); } await new Promise(resolve => setTimeout(resolve, interval || 1500)); } } async function waitForMusicTaskComplete(sandbox, taskUrl, logger, onProgress, interval = 1500) { const headers = { 'Content-Type': 'application/json', }; while (true) { const response = await sandbox.request(taskUrl, { method: 'GET', headers: headers, }); if (!response.ok) { throw new Error(`Video generation failed: ${response.statusText}`); } const data = await response.json(); logger.debug(`get music task status: ${JSON.stringify(data)}`); onProgress?.(data); const result = data.Result; if (result?.Status === 2) { return result.SongDetail; } if (result?.Status >= 3) { throw new Error(`BGM generation failed: ${JSON.stringify(data)}`); } await new Promise(resolve => setTimeout(resolve, interval || 1500)); } } let AI = class AI extends base_1.BaseClass { constructor(session) { super(session.getLogger().level); this.session = session; } async getModels() { const res = await this.session.sandbox.request(`/ai/models`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); const data = await res.json(); return data; } async generateImageSeries(options) { try { if (Array.isArray(options.prompts)) { const count = options.prompts.length; options.prompts = options.prompts .map((p, i) => `图片${i + 1}: ${p.trim()}`) .join('\n'); options.prompts = `生成${count}张图片\n${options.prompts.trim()}`; } if (options.refPrefix) { options.prompts = `${options.refPrefix} 参考以上图片,执行以下各场景绘图: ${options.prompts}; `; } const max_count = options.max_count ?? 15; const payload = { prompt: options.prompts, size: options.size ?? '1280x720', watermark: options.watermark ?? false, image: options.image, max_images: max_count, }; // 启动心跳机制 let heartbeatInterval = null; if (options.onProgress) { heartbeatInterval = setInterval(() => { options.onProgress?.({}); }, 60000); // 每60秒调用一次 } try { const res = await this.session.sandbox.request('/ai/image/sequential_generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const data = await res.json(); // 清除心跳定时器 if (heartbeatInterval) { clearInterval(heartbeatInterval); } if (data.data) { const urls = data.data .map((item) => item.url) .filter((url) => url?.trim()); for (const url of urls) { this.session.track('Image Generated', { url }); } return { prompt: options.prompts, data, urls, }; } return data; } catch (error) { // 确保在出错时也清除心跳定时器 if (heartbeatInterval) { clearInterval(heartbeatInterval); } throw error; } } catch (error) { this.logger.error('generateImage error', error); return { error: `generateImage error: ${error}` }; } } async generateImage(options) { try { const model = options.type === 'banana' ? 'Cloudsway-MaaS_Banana' : 'Doubao-Seedream-4.0'; const res = await this.session.sandbox.request(`/ai/image/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ model, ...options, }), }); const data = await res.json(); if (data.data?.[0]?.url) { this.session.track('Image Generated', data); return { url: data.data?.[0]?.url, }; } else if (data.url) { // banana this.session.track('Image Generated', data); return data; } throw new Error(`generateImage error: ${JSON.stringify(data)}`); } catch (error) { this.logger.error('generateImage error', error); this.session.track('Image Generated', { error: error.message }); throw error; } } async generateLineSketch(options) { try { const res = await this.session.sandbox.request(`/ai/image/line_sketch`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); if (data.url) { this.session.track('Line Sketch Generated', data); return data; } throw new Error(`generateLineSketch error: ${JSON.stringify(data)}`); } catch (error) { this.logger.error('generateLineSketch error', error); this.session.track('Line Sketch Generated', { error: error.message }); throw error; } } async extendVideo(options) { try { const waitForFinish = options.waitForFinish ?? true; const res = await this.session.sandbox.request(`/ai/video/extend`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); if (data.taskUrl && waitForFinish) { return this.waitForVideoTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, timeout: 300000, }); } return data; } catch (error) { this.logger.error('Extend Video error', error); return { error: error.message }; } } async referencesToVideo(options) { try { const waitForFinish = options.waitForFinish ?? true; const watermark = options.watermark ?? true; const resolution = '720p'; const ratio = options.size === '1280x720' ? '16:9' : '9:16'; const mute = options.mute ?? false; if (options.type === 'lite') { options.prompt = `${options.prompt} --rs ${resolution} --rt ${ratio} --camerafixed false --watermark ${watermark} --mute ${mute}`; } const res = await this.session.sandbox.request(`/ai/references/to/video/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ...options, resolution, mute, }), }); const data = await res.json(); if (data.taskUrl && waitForFinish) { return this.waitForVideoTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, timeout: 300000, }); } return data; } catch (error) { this.logger.error('Reference to Video error', error); return { error: error.message }; } } // 用自己的模型生成 video async generateZeroCutVideo(options) { try { const waitForFinish = options.waitForFinish ?? true; const model = options.model ?? 'zero-1.0-fast'; const parameters = { prompt: options.prompt, duration: options.duration, start_frame: options.startFrame, highlight_frame: options.highlightFrame, end_frame: options.endFrame, resolution: options.resolution ?? (model === 'zero-1.0' ? '1080p' : '720p'), }; const workflowId = options.model === 'zero-1.0' ? '7570606875394572288' : '7571823801022414911'; const result = await this.runCozeWorkflow(workflowId, parameters, options.onProgress, 1500, waitForFinish); return result; } catch (error) { this.logger.error('Generate ZeroCut Video error', error); return { error: error.message, content: null }; } } async framesToVideo(options) { try { const watermark = options.watermark ?? true; const waitForFinish = options.waitForFinish ?? true; if (options.type === 'lite' || options.type === 'pro') { options.prompt = `${options.prompt} --ratio adaptive --camerafixed false --watermark ${watermark}`; } if (options.type === 'zero' || options.type === 'zero-fast') { const model = options.type === 'zero' ? 'zero-1.0' : 'zero-1.0-fast'; const result = await this.generateZeroCutVideo({ model, prompt: options.prompt, duration: options.duration, startFrame: options.start_frame, highlightFrame: options.end_frame, resolution: options.resolution, onProgress: options.onProgress, waitForFinish, }); if (result.content) { return { url: result.content.video_url, duration: options.duration, resolution: result.content.resolution || '1080p', ratio: 'auto', }; } return result; } const res = await this.session.sandbox.request(`/ai/frames/to/video/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); if (data.taskUrl && waitForFinish) { return this.waitForVideoTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, timeout: 300000, }); } return data; } catch (error) { this.logger.error('framesToVideo error', error); return { error: error.message }; } } async generateMusic(options) { const outputSchema = { name: 'Music', schema: { type: 'object', properties: { type: { type: 'string', enum: ['bgm', 'song:doubao', 'song:minimax'], description: 'The type of the music. bgm for background music, song for full song.', }, duration: { type: 'number', description: 'The duration of the generated music in seconds.', }, lyrics: { type: 'string', description: 'The lyrics of the music.', }, genre: { type: 'string', enum: [ 'Folk', 'Pop', 'Rock', 'Chinese Style', 'Hip Hop/Rap', 'R&B/Soul', 'Punk', 'Electronic', 'Jazz', 'Reggae', 'DJ', 'Pop Punk', 'Disco', 'Future Bass', 'Pop Rap', 'Trap Rap', 'R&B Rap', 'Chinoiserie Electronic', 'GuFeng Music', 'Pop Rock', 'Jazz Pop', 'Bossa Nova', 'Contemporary R&B', ], description: 'The genre of the music.', }, mood: { type: 'string', enum: [ 'Happy', 'Dynamic/Energetic', 'Sentimental/Melancholic/Lonely', 'Inspirational/Hopeful', 'Nostalgic/Memory', 'Excited', 'Sorrow/Sad', 'Chill', 'Relaxing', 'Romantic', 'Miss', 'Groovy/Funky', 'Dreamy/Ethereal', 'Calm/Relaxing', ], description: 'The mood of the music.', }, gender: { type: 'string', enum: ['Female', 'Male'], description: 'The gender of the singer.', }, timbre: { type: 'string', enum: [ 'Warm', 'Bright', 'Husky', 'Electrified voice', 'Sweet_AUDIO_TIMBRE', 'Cute_AUDIO_TIMBRE', 'Loud and sonorous', 'Powerful', 'Sexy/Lazy', ], description: 'The timbre of the music.', }, }, required: ['type', 'duration'], }, }; const systemPrompt = `你是一位专业的音乐制作人,根据用户的描述创作音乐 音乐有两种类型,一种是带歌词的(Song),另一种是纯音乐(BGM),根据用户具体要求创作 ## 创作规则 - 分析用户描述,确定音乐的类型、时长、流派、情绪、性别、音色等 - 如果用户要求是生成 BGM,无歌词,否则应为用户生成歌词 - 如果用户提供了具体歌词,根据歌词创作,可少量改写,但主歌(verse)应严格遵守用户歌词内容 - 如果用户未提供具体歌词或者仅提供了歌词主题,你应该根据歌曲长度,帮助用户创作完整歌词 ### 完整歌词通常包括以下桥段: - 前奏: intro,歌曲开始的音乐部分,主要用于引导歌曲的整体氛围 - 主歌: verse,通常在前奏之后,歌曲中叙述歌曲故事或主题的部分 - 副歌: chorus,一般在主歌之后,旋律有记忆点和感染力,是整首歌的高潮,进一步强化歌曲的主题和情感 - 间奏: inst,歌曲中的纯音乐段落,用于连接不同的演唱部分 - 尾奏: outro,歌曲结束后的音乐段落,用于营造歌曲结束的氛围 - 桥段: bridge,通常出现在歌曲中段或接近结尾处,是一个过渡部分,用于连接不同的歌曲段落。 * 歌词示例 \`\`\`txt [intro] [verse] 记得那一天 那一天我们相恋 说好彼此都不说再见 遵守诺言 用心去相恋 我为你撑伞 你为我取暖 [inst] [chorus] 当我把心交给你的那一天 你却消失在我的眼前 事到如今已经过了好多年 是否你还像从前 [outro] \`\`\` ## ‼️ 注意事项 - 歌词内容会被完整唱出,所以**不要**加入任何括号内的补充说明文字,包括[inst]桥段,[inst]桥段不需要任何文字 - genre、mood、timbre 是枚举字符串,只能选择其中一项,要严格遵守输出 schema 定义! ## 乐曲长度 - 如果用户要求生成的是 BGM,默认 30 秒,最少 30 秒,最多 120 秒 - 如果用户要求生成的是歌曲,默认 60 秒,最少 30 秒,最多 120 秒 ## 输出 根据 schema 输出合法 JSON `; const creationPayload = { model: 'Doubao-Seed-1.6-flash', messages: [ { role: 'system', content: systemPrompt.trim(), }, { role: 'user', content: options.prompt.trim(), }, ], response_format: { type: 'json_schema', json_schema: outputSchema, }, }; const completion = await this.getCompletions(creationPayload); // console.log(completion); const creationResult = completion.choices[0]?.message?.content; if (!creationResult) { throw new Error('Failed to generate music'); } try { const musicParams = JSON.parse(creationResult); console.log('generate music params ->', JSON.stringify(musicParams, null, 2)); const { type, duration, lyrics, genre, mood, gender, timbre } = musicParams; if (!type || !duration) { throw new Error('Invalid music parameters'); } if (type === 'bgm') { const finalPrompt = `${options.prompt} --- ${lyrics ? `lyrics: ${lyrics}` : ''} ${genre ? `genre: ${genre}` : ''} ${mood ? `mood: ${mood}` : ''} ${gender ? `gender: ${gender}` : ''} ${timbre ? `timbre: ${timbre}` : ''}`; return this.generateBGM({ duration, prompt: finalPrompt, onProgress: options.onProgress, }); } else { const model = type.split(':')[1]; return this.generateSong({ type: model, lyrics, duration, genre, mood, gender, timbre, skipCopyCheck: options.skipCopyCheck, onProgress: options.onProgress, }); } } catch (ex) { throw new Error('Failed to parse music creation result'); } } async generateSong(options) { try { const waitForFinish = options.waitForFinish ?? true; const payload = { Lyrics: options.lyrics, Duration: options.duration, Genre: options.genre ?? undefined, Mood: options.mood ?? undefined, Gender: options.gender ?? undefined, Timbre: options.timbre ?? undefined, SkipCopyCheck: options.skipCopyCheck, }; console.log('generate-song payload ->', JSON.stringify(payload, null, 2)); if (options.type === 'doubao') { const res = await this.session.sandbox.request(`/ai/music/song/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const data = await res.json(); if (waitForFinish && data.taskUrl) { const result = await this.waitForMusicTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, }); if (result) { return { ...result, lyrics: options.lyrics, }; } } return data; } else { const prompt = `歌曲 genre:${options.genre || 'auto'} 歌曲 mood:${options.mood || 'auto'} 歌手 gender:${options.gender || 'auto'} 歌曲 timbre:${options.timbre || 'auto'} 歌曲时长约${options.duration}秒`; const res = await this.session.sandbox.request('/ai/music/song/generate/minimax', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt, lyrics: options.lyrics, }), }); const data = await res.json(); const url = data.data.audio; if (url) { this.session.track('Song Generated', { id: data.data.id, duration: data.data.duration, prompt, }); const captions = await this.voiceToCaptions({ url, }); return { url, duration: data.extra_info.music_duration / 1000, lyrics: options.lyrics, captions, prompt, }; } return data; } } catch (error) { this.logger.error('generateBGM error', error); return { error: error.message }; } } async generateBGM(options) { try { const waitForFinish = options.waitForFinish ?? true; const res = await this.session.sandbox.request(`/ai/music/bgm/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ Text: options.prompt, Duration: options.duration, Segments: options.segments, }), }); const data = await res.json(); if (waitForFinish && data.taskUrl) { return this.waitForMusicTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, }); } return data; } catch (error) { this.logger.error('generateBGM error', error); return { error: error.message }; } } async voiceToCaptions(options) { try { const res = await this.session.sandbox.request(`/ai/voice_to_caption`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: options.url, }), }); const data = await res.json(); this.session.track('Caption Generated', data); return data; } catch (error) { this.logger.error('voiceToCaption error', error); return { error: error.message }; } } // 火山引擎版本 async textToSpeechVolc(options) { const speech_rate = options.speed ?? 0; // -50 ~ 100 const loudness_rate = options.volume ?? 0; // -50 ~ 100 const emotion = options.emotion ?? 'storytelling'; const explicit_language = options.explicit_language ?? 'zh'; // 随机停顿 300 ~ 500 ms 模拟人对话 const silence_duration = options.silence_duration ?? Math.floor(Math.random() * 200 + 300); try { const res = await this.session.sandbox.request(`/ai/text_to_speech/generate_volc`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ speaker: options.speaker, text: options.text, speech_rate, loudness_rate, emotion, silence_duration, explicit_language, context_texts: options.context_texts, }), }); const data = await res.json(); this.session.track('TTS Generated', data); if (data.url && options.voice_to_caption) { try { const res = await this.session.sandbox.request(`/ai/voice_to_caption`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: data.url, }), }); data.captions = await res.json(); this.session.track('Caption Generated', data.captions); } catch (error) { this.logger.error('voiceToCaption error', error); } } return data; } catch (error) { this.logger.error('textToSpeechVolc error', error); return { error: error.message }; } } // minimax 版本的 tts async textToSpeech(options) { // TODO 支持分句生成 try { const res = await this.session.sandbox.request(`/ai/text_to_speech/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); this.session.track('TTS Generated', data); if (data.url && options.voice_to_caption) { try { const res = await this.session.sandbox.request(`/ai/voice_to_caption`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: data.url, }), }); data.captions = await res.json(); this.session.track('Caption Generated', data.captions); } catch (error) { this.logger.error('voiceToCaption error', error); } } return data; } catch (error) { this.logger.error('textToSpeech error', error); return { error: error.message }; } } async searchText(options) { try { options.count = options.count || 10; options.offset = options.offset || 0; options.type = options.type || 'smart'; const res = await this.session.sandbox.request(`/ai/search/text?q=${encodeURIComponent(options.q)}&count=${options.count}&offset=${options.offset}&type=${options.type}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); const data = await res.json(); return data; } catch (error) { this.logger.error('searchText error', error); return { error: error.message }; } } async searchImage(options) { try { options.count = options.count || 10; options.offset = options.offset || 0; const res = await this.session.sandbox.request(`/ai/search/image?q=${encodeURIComponent(options.q)}&count=${options.count}&offset=${options.offset}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); const data = await res.json(); return data; } catch (error) { this.logger.error('searchImage error', error); return { error: error.message }; } } async generateSoundEffect(options) { try { const res = await this.session.sandbox.request(`/ai/sound_effect/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); return data; } catch (error) { this.logger.error('generateSoundEffect error', error); return { error: error.message }; } } async getCompletions(options) { try { const res = await this.session.sandbox.request(`/ai/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(options), }); const data = await res.json(); return data; } catch (error) { this.logger.error(`getCompositions error: ${error.message}`); return { error: error.message }; } } async actionImitation(options) { try { const waitForFinish = options.waitForFinish ?? true; const res = await this.session.sandbox.request(`/ai/imitation/volcano`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ image_url: options.imageUrl, video_url: options.videoUrl, }), }); const data = await res.json(); if (data.taskUrl && waitForFinish) { return this.waitForVideoTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, timeout: 300000, }); } return data; } catch (error) { this.logger.error(`actionImitation error: ${error.message}`); return { error: error.message }; } } async voSync(options) { try { const { videoUrl, audioUrl, audioInMs = 0, audioFadeOutMs = 0 } = options; const res = await this.session.sandbox.request(`/ai/vosync`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ video_url: videoUrl, audio_url: audioUrl, audio_in_ms: audioInMs, audio_fade_out_ms: audioFadeOutMs, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error(`voSync error: ${error.message}`); return { error: error.message }; } } async voiceDesign(options) { try { const res = await this.session.sandbox.request(`/ai/voice/design`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: options.prompt, preview_text: options.previewText, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error(`voiceDesign error: ${error.message}`); return { error: error.message }; } } async lipSync(options) { try { const waitForFinish = options.waitForFinish ?? true; const type = options.type || 'pixv'; if (options.ref_photo_url && type !== 'vidu') { throw new Error('ref_photo_url is only supported for vidu model'); } const endpoint = type === 'vidu' ? '/ai/lipsync' : type === 'pixv' ? '/ai/lipsync/pixverse' : '/ai/lipsync/volcano'; const req_key = type === 'basic' ? 'realman_change_lips_basic_chimera' : 'realman_change_lips'; const res = await this.session.sandbox.request(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ video_url: options.videoUrl, audio_url: options.audioUrl, audio_in_ms: options.audioInMs || 0, pad_audio: options.pad_audio, ref_photo_url: options.ref_photo_url, // 这个参数只对 vidu 生效,可以多人对口型 // 后三个参数只对 basic 和 lite 生效 req_key, separate_vocal: false, open_scenedet: false, }), }); const data = await res.json(); if (data.taskUrl && waitForFinish) { return this.waitForVideoTaskComplete({ taskUrl: data.taskUrl, onProgress: options.onProgress, timeout: 300000, }); } return data; } catch (error) { this.logger.error('generateSoundEffect error', error); return { error: error.message }; } } async concatMedia(options) { try { const res = await this.session.sandbox.request(`/ai/media/concat`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ urls: options.mediaUrls, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('getMediaMetadata error', error); return { error: error.message }; } } async getMediaMetadata(url) { try { const res = await this.session.sandbox.request(`/ai/metadata`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('getMediaMetadata error', error); return { error: error.message }; } } async mediaTrim(options) { try { const res = await this.session.sandbox.request(`/ai/media/trim`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: options.mediaUrl, startTime: options.startTime, endTime: options.endTime, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('mediaTrim error', error); return { error: error.message }; } } async mixAudios(options) { try { const res = await this.session.sandbox.request(`/ai/audio/mix`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ voice: options.voiceUrl, bgm: options.bgmUrl, gain: options.gain || -15, ducking: options.ducking || false, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('mixAudios error', error); return { error: error.message }; } } async muteVideo(options) { try { const res = await this.session.sandbox.request(`/ai/media/mute`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: options.mediaUrl, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('muteVideo error', error); return { error: error.message }; } } async padAudio(options) { try { const res = await this.session.sandbox.request(`/ai/audio/pad`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ audio: options.audioUrl, inMs: options.inMs || 0, fadeOutMs: options.fadeOutMs || 0, duration: options.duration || 0, }), }); const data = await res.json(); return data; } catch (error) { this.logger.error('padAudio error', error); return { error: error.message }; } } async transferToCoze(options) { try { if (!options.mediaUrl && !options.text) { throw new Error('mediaUrl or text is required'); } let buffer; let filename; if (options.mediaUrl) { // eslint-disable-next-line custom/no-fetch-in-src const res = await fetch(options.mediaUrl); if (!res.ok) { throw new Error(`Failed to fetch media: ${res.statusText}`); } buffer = Buffer.from(await res.arrayBuffer()); const url = new URL(options.mediaUrl); filename = path_1.default.basename(url.pathname); } else { filename = `${(0, uuid_1.v4)()}.txt`; buffer = Buffer.from(options.text || ''); } const data = await (0, coze_1.transferToCoze)(buffer, filename, this.session.sandbox.token); return data; } catch (error) { this.logger.error('transferToCoze error', error); return { error: error.message }; } } async generateVideoByTemplate(options) { return this.runCozeWorkflow(options.templateId, options.parameters, undefined, 1500, options.waitForFinish ?? true); } async getCozeWorkflowInfo(workflowId) { return (0, coze_1.getWorkflowInfo)(workflowId, this.session.sandbox.token); } async runCozeWorkflow(workflowId, parameters, onProgress, interval = 1500, waitForFinish = true) { const apiKey = this.session.sandbox.token; const process = await (0, coze_1.runWorkflow)(workflowId, { api_key: this.session.sandbox.token, sandbox_id: this.session.sandbox.sandboxId, ...parameters, }, true, apiKey); console.log({ 'zerocut-process': process }); const executeId = process.execute_id; const taskUrl = `https://api.coze.cn/v1/workflows/${workflowId}/run_histories/${executeId}`; this.session.sandbox.request('/ai/workflow/save', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ workflowId, executeId, debugUrl: process.debug_url, }), }); if (!waitForFinish) { return { taskUrl, ...process, }; } return this.waitForWorkflowFinish({ taskUrl, onProgress, interval, }); } async waitForWorkflowFinish(options) { try { const { taskUrl, onProgress, interval = 1500, timeout = 900000, } = options; const result = await Promise.race([ waitForWorkflowFinish(taskUrl, this.session.sandbox.token, this.logger, onProgress, interval), new Promise(resolve => { setTimeout(() => { resolve({ status: 'running', reason: `wait timeout ${timeout}ms`, taskUrl, }); }, timeout); }), ]); if (result?.content) { this.session.track('Video Generated', { taskUrl, }); return { url: result.content.video_url, duration: result.duration, resolution: result.resolution, ratio: 'auto', }; } this.session.track('Video Creation Failed', { result: JSON.stringify(result), }); throw new Error(`Video Creation Failed: ${JSON.stringify(result)}`); } catch (error) { this.logger.error('waitForWorkflowFinish error', error); this.session.track('Video Creation Failed', { reason: error.message, }); throw error; } } async waitForVideoTaskComplete(options) { try { const { taskUrl, onProgress, interval = 1500, traceWorkflow = true, timeout = 900000, } = options; if (taskUrl.startsWith('https://api.coze.cn/v1/workflows/')) { return this.waitForWorkflowFinish(options); } if (traceWorkflow) { // 启动一个coze工作流作为记录和监控 this.runCozeWorkflow('7574320984539791400', { taskUrl, }, undefined, undefined, false).catch(error => { this.logger.error('runCozeWorkflow error', error); }); } const result = await Promise.race([ waitForVideoTaskComplete(this.session.sandbox, taskUrl, this.logger, onProgress, interval), new Promise(resolve => { setTimeout(() => { resolve({ status: 'running', reason: `wait timeout ${timeout}ms`, taskUrl, }); }, timeout); }), ]); if (result && result.content?.video_url) { this.session.track('Video Generated', { id: result.id, model: result.model, fps: result.framespersecond, duration: result.duration, resolution: result.resolution, ratio: result.ratio, cost: result.usage?.total_tokens, }); return { url: result.content.video_url, last_frame_url: result.content?.last_frame_url, duration: result.duration, resolution: result.resolution, ratio: result.ratio, }; } this.session.track('Video Creation Failed', { result: JSON.stringify(result), }); throw new Error(`Video Creation Failed: ${JSON.stringify(result)}`); } catch (error) { this.logger.error('waitForVideoTaskComplete error', error); this.session.track('Video Creation Failed', { reason: error.message, }); throw error; } } async waitForMusicTaskComplete(options) { try { const { taskUrl, onProgress, interval = 1500 } = options; const result = await waitForMusicTaskComplete(this.session.sandbox, taskUrl, this.logger, onProgress, interval); if (result) { this.session.track('Music Generated', { id: result.TaskID, duration: result.Duration, prompt: result.Prompt, captions: result.Captions, }); return { url: result.AudioUrl, duration: result.Duration, prompt: result.Prompt, captions: result.Captions, }; } this.session.track('Music Creation Failed', { result: '', }); } catch (error) { this.logger.error('waitForMusicTaskComplete error', error); this.session.track('Music Creation Failed', { reason: error.message, }); throw error; } } }; exports.AI = AI; exports.AI = AI = __decorate([ (0, base_1.Logger)({ VERSION: constants_1.VERSION }), __metadata("design:paramtypes", [session_1.Session]) ], AI); //# sourceMappingURL=ai.js.map