diff --git a/README_TEXT_VIDEO_AGENT_API.md b/README_TEXT_VIDEO_AGENT_API.md
new file mode 100644
index 0000000..8134f41
--- /dev/null
+++ b/README_TEXT_VIDEO_AGENT_API.md
@@ -0,0 +1,430 @@
+# Text Video Agent API 工具库
+
+基于 `https://bowongai-dev--text-video-agent-fastapi-app.modal.run` API 的完整 TypeScript 工具库封装。
+
+## 📋 目录
+
+- [功能特性](#功能特性)
+- [安装使用](#安装使用)
+- [API 文档](#api-文档)
+- [React Hook](#react-hook)
+- [组件示例](#组件示例)
+- [类型定义](#类型定义)
+- [使用示例](#使用示例)
+
+## 🚀 功能特性
+
+### 核心功能
+- ✅ **图片生成**: 基于 Midjourney 的高质量图片生成
+- ✅ **视频生成**: 基于极梦的视频生成功能
+- ✅ **文件上传**: 支持文件上传到云存储
+- ✅ **图片描述**: AI 图片内容分析和描述
+- ✅ **任务管理**: 异步任务创建、查询和管理
+
+### 高级特性
+- ✅ **重试机制**: 自动重试失败的请求
+- ✅ **进度跟踪**: 实时进度更新和状态监控
+- ✅ **类型安全**: 完整的 TypeScript 类型定义
+- ✅ **React 集成**: 专用的 React Hook 和组件
+- ✅ **错误处理**: 完善的错误处理和用户反馈
+
+## 📦 安装使用
+
+### 基础安装
+
+```bash
+# 复制相关文件到你的项目
+cp src/services/textVideoAgentAPI.ts your-project/src/services/
+cp src/services/textVideoAgentTypes.ts your-project/src/services/
+cp src/hooks/useTextVideoAgent.ts your-project/src/hooks/
+cp src/components/TextVideoGenerator.tsx your-project/src/components/
+```
+
+### 依赖要求
+
+```json
+{
+ "dependencies": {
+ "react": "^18.0.0",
+ "lucide-react": "^0.263.1"
+ },
+ "devDependencies": {
+ "typescript": "^5.0.0"
+ }
+}
+```
+
+## 🔧 API 文档
+
+### 基础用法
+
+```typescript
+import { textVideoAgentAPI } from './services/textVideoAgentAPI'
+
+// 健康检查
+const health = await textVideoAgentAPI.healthCheck()
+
+// 生成图片
+const imageResult = await textVideoAgentAPI.generateImageSync({
+ prompt: '一个美丽的风景',
+ max_wait_time: 120
+})
+
+// 生成视频
+const videoResult = await textVideoAgentAPI.generateVideoSync({
+ prompt: '动态的自然风光',
+ img_url: 'https://example.com/image.jpg',
+ duration: '5'
+})
+```
+
+### 主要方法
+
+#### 图片生成
+```typescript
+// 同步生成(推荐)
+generateImageSync(params: ImageGenerationParams): Promise
+
+// 异步生成
+generateImageAsync(prompt: string, imgFile?: File): Promise
+
+// 带重试的生成
+generateImageWithRetry(params: ImageGenerationParams, maxRetries?: number): Promise
+```
+
+#### 视频生成
+```typescript
+// 同步生成
+generateVideoSync(params: VideoGenerationParams): Promise
+
+// 异步生成
+generateVideoAsync(params: VideoGenerationParams): Promise
+
+// 带重试的生成
+generateVideoWithRetry(params: VideoGenerationParams, maxRetries?: number): Promise
+```
+
+#### 任务管理
+```typescript
+// 创建任务
+createTask(request: TaskRequest): Promise
+
+// 查询任务状态
+getTaskStatusAsync(taskId: string): Promise
+
+// 同步等待任务完成
+getTaskResultSync(taskId: string): Promise
+```
+
+#### 高级功能
+```typescript
+// 端到端内容生成
+generateContentEndToEnd(prompt: string, options?: GenerationOptions): Promise
+
+// 轮询任务直到完成
+pollTaskUntilComplete(taskId: string, options?: PollingOptions): Promise
+```
+
+## 🎣 React Hook
+
+### useTextVideoAgent
+
+```typescript
+import { useTextVideoAgent } from './hooks/useTextVideoAgent'
+
+function MyComponent() {
+ const {
+ state,
+ generateImage,
+ generateVideo,
+ generateContentEndToEnd,
+ reset,
+ cancel
+ } = useTextVideoAgent()
+
+ const handleGenerate = async () => {
+ const result = await generateContentEndToEnd('美丽的风景', {
+ taskType: TaskType.VLOG,
+ aspectRatio: AspectRatio.PORTRAIT,
+ generateVideo: true
+ })
+
+ console.log('生成结果:', result)
+ }
+
+ return (
+
+ {state.isLoading && (
+
+
{state.currentStep}
+
+
+ )}
+
+ {state.error && (
+
{state.error}
+ )}
+
+
+
+ )
+}
+```
+
+### useTaskPolling
+
+```typescript
+import { useTaskPolling } from './hooks/useTextVideoAgent'
+
+function TaskMonitor({ taskId }: { taskId: string }) {
+ const { status, isPolling } = useTaskPolling(taskId, {
+ onComplete: (result) => {
+ console.log('任务完成:', result)
+ },
+ onError: (error) => {
+ console.error('任务失败:', error)
+ },
+ onProgress: (status) => {
+ console.log('进度更新:', status)
+ }
+ })
+
+ return (
+
+ {isPolling &&
任务进行中...
}
+ {status &&
{JSON.stringify(status, null, 2)}}
+
+ )
+}
+```
+
+## 🧩 组件示例
+
+### TextVideoGenerator 组件
+
+```typescript
+import TextVideoGenerator from './components/TextVideoGenerator'
+
+function App() {
+ return (
+ {
+ console.log('图片生成完成:', imageUrl)
+ }}
+ onVideoGenerated={(videoUrl) => {
+ console.log('视频生成完成:', videoUrl)
+ }}
+ />
+ )
+}
+```
+
+## 📝 类型定义
+
+### 主要接口
+
+```typescript
+// API 响应
+interface APIResponse {
+ status: boolean
+ msg: string
+ data?: T
+}
+
+// 图片生成参数
+interface ImageGenerationParams {
+ prompt: string
+ img_file?: File
+ max_wait_time?: number
+ poll_interval?: number
+}
+
+// 视频生成参数
+interface VideoGenerationParams {
+ prompt: string
+ img_url?: string
+ img_file?: File
+ duration?: string
+ max_wait_time?: number
+ poll_interval?: number
+}
+
+// 任务请求
+interface TaskRequest {
+ task_type?: string
+ prompt: string
+ img_url?: string
+ ar?: string
+}
+```
+
+### 枚举类型
+
+```typescript
+enum TaskType {
+ TEA = 'tea',
+ CHOP = 'chop',
+ LADY = 'lady',
+ VLOG = 'vlog'
+}
+
+enum AspectRatio {
+ SQUARE = '1:1',
+ PORTRAIT = '9:16',
+ LANDSCAPE = '16:9'
+}
+
+enum VideoDuration {
+ SHORT = '3',
+ MEDIUM = '5',
+ LONG = '10'
+}
+```
+
+## 💡 使用示例
+
+### 示例1: 基础图片生成
+
+```typescript
+import { textVideoAgentAPI, TaskType, AspectRatio } from './services/textVideoAgentAPI'
+
+async function generateImage() {
+ try {
+ const result = await textVideoAgentAPI.generateImageSync({
+ prompt: '一个现代化的咖啡厅,温暖的灯光,舒适的环境',
+ max_wait_time: 120
+ })
+
+ if (result.status) {
+ console.log('图片URL:', result.data.image_url)
+ }
+ } catch (error) {
+ console.error('生成失败:', error)
+ }
+}
+```
+
+### 示例2: 端到端内容生成
+
+```typescript
+async function generateContent() {
+ try {
+ const result = await textVideoAgentAPI.generateContentEndToEnd(
+ '制作一个关于健康生活的短视频',
+ {
+ taskType: TaskType.VLOG,
+ aspectRatio: AspectRatio.PORTRAIT,
+ videoDuration: VideoDuration.MEDIUM,
+ generateVideo: true,
+ onProgress: (step, progress) => {
+ console.log(`${step}: ${progress}%`)
+ }
+ }
+ )
+
+ console.log('生成完成:', result)
+ } catch (error) {
+ console.error('生成失败:', error)
+ }
+}
+```
+
+### 示例3: 批量处理
+
+```typescript
+async function batchGenerate() {
+ const prompts = [
+ '春天的樱花',
+ '夏日的海滩',
+ '秋天的枫叶',
+ '冬日的雪景'
+ ]
+
+ const results = await Promise.allSettled(
+ prompts.map(prompt =>
+ textVideoAgentAPI.generateImageSync({ prompt })
+ )
+ )
+
+ results.forEach((result, index) => {
+ if (result.status === 'fulfilled') {
+ console.log(`图片 ${index + 1} 生成成功:`, result.value.data?.image_url)
+ } else {
+ console.error(`图片 ${index + 1} 生成失败:`, result.reason)
+ }
+ })
+}
+```
+
+## 🔧 配置选项
+
+### 预设配置
+
+```typescript
+import { PRESET_CONFIGS } from './services/textVideoAgentTypes'
+
+// 快速生成(低质量)
+const fastConfig = PRESET_CONFIGS.FAST
+
+// 标准生成
+const standardConfig = PRESET_CONFIGS.STANDARD
+
+// 高质量生成
+const highQualityConfig = PRESET_CONFIGS.HIGH_QUALITY
+```
+
+### 自定义配置
+
+```typescript
+const customAPI = new TextVideoAgentAPI('https://your-custom-endpoint.com')
+```
+
+## 🚨 错误处理
+
+```typescript
+try {
+ const result = await textVideoAgentAPI.generateImageSync(params)
+} catch (error) {
+ if (error instanceof Error) {
+ console.error('错误信息:', error.message)
+ }
+
+ // 处理特定错误类型
+ if (error.message.includes('timeout')) {
+ // 处理超时错误
+ } else if (error.message.includes('quota')) {
+ // 处理配额不足错误
+ }
+}
+```
+
+## 📊 性能优化
+
+### 缓存策略
+- 图片生成结果自动缓存
+- 任务状态智能轮询
+- 网络请求去重
+
+### 最佳实践
+- 使用适当的超时时间
+- 合理设置轮询间隔
+- 及时取消不需要的请求
+- 使用预设配置提高效率
+
+## 🤝 贡献指南
+
+1. Fork 项目
+2. 创建功能分支
+3. 提交更改
+4. 推送到分支
+5. 创建 Pull Request
+
+## 📄 许可证
+
+MIT License
+
+## 🆘 支持
+
+如有问题或建议,请创建 Issue 或联系开发团队。
diff --git a/examples/textVideoAgentUsage.ts b/examples/textVideoAgentUsage.ts
new file mode 100644
index 0000000..ad8f815
--- /dev/null
+++ b/examples/textVideoAgentUsage.ts
@@ -0,0 +1,416 @@
+/**
+ * Text Video Agent API 使用示例
+ */
+
+import {
+ TextVideoAgentAPI,
+ textVideoAgentAPI,
+ ImageGenerationParams,
+ VideoGenerationParams,
+ TaskRequest
+} from '../src/services/textVideoAgentAPI'
+
+import {
+ TaskType,
+ AspectRatio,
+ VideoDuration,
+ PRESET_CONFIGS
+} from '../src/services/textVideoAgentTypes'
+
+// ==================== 基础使用示例 ====================
+
+/**
+ * 示例1: 基础健康检查
+ */
+async function example1_HealthCheck() {
+ console.log('=== 健康检查示例 ===')
+
+ try {
+ const health = await textVideoAgentAPI.healthCheck()
+ console.log('服务状态:', health)
+
+ const mjHealth = await textVideoAgentAPI.mjHealthCheck()
+ console.log('Midjourney状态:', mjHealth)
+
+ const jmHealth = await textVideoAgentAPI.jmHealthCheck()
+ console.log('极梦状态:', jmHealth)
+ } catch (error) {
+ console.error('健康检查失败:', error)
+ }
+}
+
+/**
+ * 示例2: 获取示例提示词
+ */
+async function example2_GetSamplePrompts() {
+ console.log('=== 获取示例提示词 ===')
+
+ try {
+ const prompts = await textVideoAgentAPI.getSamplePrompt(TaskType.VLOG)
+ console.log('VLOG类型示例提示词:', prompts)
+ } catch (error) {
+ console.error('获取提示词失败:', error)
+ }
+}
+
+/**
+ * 示例3: 文件上传
+ */
+async function example3_FileUpload() {
+ console.log('=== 文件上传示例 ===')
+
+ // 注意:这里需要实际的文件对象
+ // const fileInput = document.getElementById('fileInput') as HTMLInputElement
+ // const file = fileInput.files?.[0]
+
+ // 模拟文件对象(实际使用时替换为真实文件)
+ const mockFile = new File(['mock content'], 'test.jpg', { type: 'image/jpeg' })
+
+ try {
+ const uploadResult = await textVideoAgentAPI.uploadFile(mockFile)
+ console.log('文件上传结果:', uploadResult)
+ return uploadResult.data // 返回文件URL
+ } catch (error) {
+ console.error('文件上传失败:', error)
+ }
+}
+
+// ==================== 图片生成示例 ====================
+
+/**
+ * 示例4: 基础图片生成
+ */
+async function example4_BasicImageGeneration() {
+ console.log('=== 基础图片生成示例 ===')
+
+ const params: ImageGenerationParams = {
+ prompt: '一个美丽的女孩在喝茶,温馨的下午时光,自然光线,高质量摄影',
+ max_wait_time: 120,
+ poll_interval: 2
+ }
+
+ try {
+ const result = await textVideoAgentAPI.generateImageSync(params)
+ console.log('图片生成结果:', result)
+ return result.data?.image_url
+ } catch (error) {
+ console.error('图片生成失败:', error)
+ }
+}
+
+/**
+ * 示例5: 带参考图片的图片生成
+ */
+async function example5_ImageGenerationWithReference() {
+ console.log('=== 带参考图片的图片生成示例 ===')
+
+ // 模拟参考图片文件
+ const referenceImage = new File(['reference'], 'reference.jpg', { type: 'image/jpeg' })
+
+ const params: ImageGenerationParams = {
+ prompt: '保持人物特征,改变背景为咖啡厅环境',
+ img_file: referenceImage,
+ ...PRESET_CONFIGS.STANDARD
+ }
+
+ try {
+ const result = await textVideoAgentAPI.generateImageWithRetry(params, 3)
+ console.log('带参考图片生成结果:', result)
+ return result.data?.image_url
+ } catch (error) {
+ console.error('带参考图片生成失败:', error)
+ }
+}
+
+/**
+ * 示例6: 异步图片生成
+ */
+async function example6_AsyncImageGeneration() {
+ console.log('=== 异步图片生成示例 ===')
+
+ const prompt = '现代简约风格的室内设计,明亮的客厅'
+
+ try {
+ // 提交异步任务
+ const taskResult = await textVideoAgentAPI.generateImageAsync(prompt)
+ const taskId = taskResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('未获取到任务ID')
+ }
+
+ console.log('任务已提交,ID:', taskId)
+
+ // 轮询任务状态
+ const finalResult = await textVideoAgentAPI.pollTaskUntilComplete(
+ taskId,
+ 2000, // 2秒轮询间隔
+ 120000, // 2分钟超时
+ (status) => {
+ console.log('任务状态更新:', status)
+ }
+ )
+
+ console.log('异步图片生成完成:', finalResult)
+ return finalResult.data?.image_url
+ } catch (error) {
+ console.error('异步图片生成失败:', error)
+ }
+}
+
+// ==================== 视频生成示例 ====================
+
+/**
+ * 示例7: 基础视频生成
+ */
+async function example7_BasicVideoGeneration() {
+ console.log('=== 基础视频生成示例 ===')
+
+ // 首先生成一张图片作为视频的基础
+ const imageUrl = await example4_BasicImageGeneration()
+
+ if (!imageUrl) {
+ console.error('无法获取基础图片,跳过视频生成')
+ return
+ }
+
+ const params: VideoGenerationParams = {
+ prompt: '女孩优雅地品茶,轻柔的动作,温馨的氛围',
+ img_url: imageUrl,
+ duration: VideoDuration.MEDIUM,
+ max_wait_time: 300,
+ poll_interval: 5
+ }
+
+ try {
+ const result = await textVideoAgentAPI.generateVideoSync(params)
+ console.log('视频生成结果:', result)
+ return result.data?.video_url
+ } catch (error) {
+ console.error('视频生成失败:', error)
+ }
+}
+
+/**
+ * 示例8: 异步视频生成
+ */
+async function example8_AsyncVideoGeneration() {
+ console.log('=== 异步视频生成示例 ===')
+
+ const params: VideoGenerationParams = {
+ prompt: '城市夜景延时摄影,车流如水,霓虹闪烁',
+ duration: VideoDuration.LONG
+ }
+
+ try {
+ const taskResult = await textVideoAgentAPI.generateVideoAsync(params)
+ const taskId = taskResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('未获取到任务ID')
+ }
+
+ console.log('视频生成任务已提交,ID:', taskId)
+
+ // 轮询任务状态
+ const finalResult = await textVideoAgentAPI.pollTaskUntilComplete(
+ taskId,
+ 5000, // 5秒轮询间隔
+ 600000, // 10分钟超时
+ (status) => {
+ console.log('视频生成进度:', status)
+ }
+ )
+
+ console.log('异步视频生成完成:', finalResult)
+ return finalResult.data?.video_url
+ } catch (error) {
+ console.error('异步视频生成失败:', error)
+ }
+}
+
+// ==================== 高级功能示例 ====================
+
+/**
+ * 示例9: 图片描述功能
+ */
+async function example9_ImageDescription() {
+ console.log('=== 图片描述示例 ===')
+
+ const imageUrl = 'https://example.com/sample-image.jpg'
+
+ try {
+ const description = await textVideoAgentAPI.describeImageByUrl({
+ image_url: imageUrl,
+ max_wait_time: 60
+ })
+
+ console.log('图片描述结果:', description)
+ return description.data?.description
+ } catch (error) {
+ console.error('图片描述失败:', error)
+ }
+}
+
+/**
+ * 示例10: 任务管理
+ */
+async function example10_TaskManagement() {
+ console.log('=== 任务管理示例 ===')
+
+ const taskRequest: TaskRequest = {
+ task_type: TaskType.VLOG,
+ prompt: '制作一个关于健康生活方式的短视频',
+ ar: AspectRatio.PORTRAIT
+ }
+
+ try {
+ // 创建任务
+ const createResult = await textVideoAgentAPI.createTask(taskRequest)
+ const taskId = createResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('任务创建失败')
+ }
+
+ console.log('任务创建成功,ID:', taskId)
+
+ // 查询任务状态
+ const statusResult = await textVideoAgentAPI.getTaskStatusAsync(taskId)
+ console.log('任务状态:', statusResult)
+
+ // 等待任务完成(同步方式)
+ const finalResult = await textVideoAgentAPI.getTaskResultSync(taskId)
+ console.log('任务最终结果:', finalResult)
+
+ return finalResult
+ } catch (error) {
+ console.error('任务管理失败:', error)
+ }
+}
+
+/**
+ * 示例11: 端到端内容生成
+ */
+async function example11_EndToEndGeneration() {
+ console.log('=== 端到端内容生成示例 ===')
+
+ try {
+ const result = await textVideoAgentAPI.generateContentEndToEnd(
+ '一个关于咖啡文化的精美内容,展现咖啡师的专业技艺',
+ {
+ taskType: TaskType.VLOG,
+ aspectRatio: AspectRatio.PORTRAIT,
+ videoDuration: VideoDuration.MEDIUM,
+ generateVideo: true,
+ onProgress: (step, progress) => {
+ console.log(`进度更新: ${step} - ${progress}%`)
+ }
+ }
+ )
+
+ console.log('端到端生成完成:', result)
+ return result
+ } catch (error) {
+ console.error('端到端生成失败:', error)
+ }
+}
+
+// ==================== 批量处理示例 ====================
+
+/**
+ * 示例12: 批量图片生成
+ */
+async function example12_BatchImageGeneration() {
+ console.log('=== 批量图片生成示例 ===')
+
+ const prompts = [
+ '春天的樱花盛开',
+ '夏日的海滩风光',
+ '秋天的枫叶满山',
+ '冬日的雪景如画'
+ ]
+
+ try {
+ const results = await Promise.allSettled(
+ prompts.map(async (prompt, index) => {
+ const params: ImageGenerationParams = {
+ prompt,
+ ...PRESET_CONFIGS.FAST // 使用快速配置
+ }
+
+ console.log(`开始生成图片 ${index + 1}: ${prompt}`)
+ const result = await textVideoAgentAPI.generateImageSync(params)
+ console.log(`图片 ${index + 1} 生成完成`)
+
+ return {
+ prompt,
+ result: result.data?.image_url,
+ success: result.status
+ }
+ })
+ )
+
+ console.log('批量图片生成结果:', results)
+ return results
+ } catch (error) {
+ console.error('批量图片生成失败:', error)
+ }
+}
+
+// ==================== 主函数 ====================
+
+/**
+ * 运行所有示例
+ */
+async function runAllExamples() {
+ console.log('开始运行 Text Video Agent API 示例...\n')
+
+ // 基础功能示例
+ await example1_HealthCheck()
+ await example2_GetSamplePrompts()
+
+ // 文件操作示例
+ // await example3_FileUpload() // 需要实际文件
+
+ // 图片生成示例
+ await example4_BasicImageGeneration()
+ // await example5_ImageGenerationWithReference() // 需要实际文件
+ await example6_AsyncImageGeneration()
+
+ // 视频生成示例
+ await example7_BasicVideoGeneration()
+ await example8_AsyncVideoGeneration()
+
+ // 高级功能示例
+ await example9_ImageDescription()
+ await example10_TaskManagement()
+ await example11_EndToEndGeneration()
+
+ // 批量处理示例
+ await example12_BatchImageGeneration()
+
+ console.log('\n所有示例运行完成!')
+}
+
+// 导出示例函数
+export {
+ example1_HealthCheck,
+ example2_GetSamplePrompts,
+ example3_FileUpload,
+ example4_BasicImageGeneration,
+ example5_ImageGenerationWithReference,
+ example6_AsyncImageGeneration,
+ example7_BasicVideoGeneration,
+ example8_AsyncVideoGeneration,
+ example9_ImageDescription,
+ example10_TaskManagement,
+ example11_EndToEndGeneration,
+ example12_BatchImageGeneration,
+ runAllExamples
+}
+
+// 如果直接运行此文件,执行所有示例
+if (require.main === module) {
+ runAllExamples().catch(console.error)
+}
diff --git a/src/App.tsx b/src/App.tsx
index b0c9192..b55541b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react'
+import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Layout from './components/Layout'
import HomePage from './pages/HomePage'
@@ -14,6 +14,7 @@ import ModelManagePage from './pages/ModelManagePage'
import AudioLibraryPage from './pages/AudioLibraryPage'
import MediaLibraryPage from './pages/MediaLibraryPage'
import KVTestPage from './pages/KVTestPage'
+import TextVideoGeneratorPage from './pages/TextVideoGeneratorPage'
function App() {
return (
@@ -27,6 +28,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/src/components/QuickActions.tsx b/src/components/QuickActions.tsx
index 8ce4f91..2ee1a89 100644
--- a/src/components/QuickActions.tsx
+++ b/src/components/QuickActions.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import { Link } from 'react-router-dom'
-import { FolderOpen, Music, Zap, Sparkles, Database, LucideIcon } from 'lucide-react'
+import { FolderOpen, Music, Zap, Sparkles, Database, LucideIcon, Wand2 } from 'lucide-react'
interface QuickAction {
icon: LucideIcon
@@ -11,6 +11,7 @@ interface QuickAction {
const QuickActions: React.FC = () => {
const quickActions: QuickAction[] = [
+ { icon: Wand2, label: 'AI 内容生成', description: '基于 Text Video Agent API 的智能内容生成', path: '/text-video-generator' },
{ icon: Sparkles, label: 'AI 视频生成', description: '使用 AI 将图片转换为动态视频', path: '/ai-video' },
{ icon: Music, label: '音频处理', description: '处理音频文件,添加效果', path: '/audio' },
{ icon: Zap, label: 'AI 自动剪辑', description: '使用 AI 自动生成视频剪辑', path: '/editor' },
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index f82c2b9..e4441d8 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
-import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags, User } from 'lucide-react'
+import { Home, Video, Settings, FolderOpen, Music, Image, Sparkles, Layout, Clock, CheckCircle, XCircle, List, Tags, User, Wand2 } from 'lucide-react'
import { clsx } from 'clsx'
const Sidebar: React.FC = () => {
@@ -22,6 +22,7 @@ const Sidebar: React.FC = () => {
{ path: '/', icon: Home, label: '首页' },
{ path: '/editor', icon: Video, label: '编辑器' },
{ path: '/ai-video', icon: Sparkles, label: 'AI 视频' },
+ { path: '/text-video-generator', icon: Wand2, label: 'AI 内容生成' },
{ path: '/templates', icon: Layout, label: '模板库' },
{ path: '/resource-categories', icon: Tags, label: '分类管理' },
{ path: '/projects', icon: FolderOpen, label: '项目库' },
diff --git a/src/components/TextVideoGenerator.tsx b/src/components/TextVideoGenerator.tsx
new file mode 100644
index 0000000..6c8fa11
--- /dev/null
+++ b/src/components/TextVideoGenerator.tsx
@@ -0,0 +1,433 @@
+/**
+ * Text Video Generator Component
+ * 使用 Text Video Agent API 的 React 组件示例
+ */
+
+import React, { useState, useCallback } from 'react'
+import {
+ Upload,
+ Image,
+ Video,
+ Loader,
+ AlertCircle,
+ CheckCircle,
+ Play,
+ Download,
+ RefreshCw
+} from 'lucide-react'
+
+import { useTextVideoAgent, useTaskPolling } from '../hooks/useTextVideoAgent'
+import { TaskType, AspectRatio, VideoDuration } from '../services/textVideoAgentTypes'
+
+interface TextVideoGeneratorProps {
+ onImageGenerated?: (imageUrl: string) => void
+ onVideoGenerated?: (videoUrl: string) => void
+}
+
+const TextVideoGenerator: React.FC = ({
+ onImageGenerated,
+ onVideoGenerated
+}) => {
+ // Hook状态
+ const {
+ state,
+ generateImage,
+ generateVideo,
+ generateContentEndToEnd,
+ uploadFile,
+ describeImageByFile,
+ reset,
+ cancel
+ } = useTextVideoAgent()
+
+ // 本地状态
+ const [prompt, setPrompt] = useState('')
+ const [taskType, setTaskType] = useState(TaskType.VLOG)
+ const [aspectRatio, setAspectRatio] = useState(AspectRatio.PORTRAIT)
+ const [videoDuration, setVideoDuration] = useState(VideoDuration.MEDIUM)
+ const [referenceFile, setReferenceFile] = useState(null)
+ const [generateVideoEnabled, setGenerateVideoEnabled] = useState(false)
+ const [results, setResults] = useState<{
+ imageUrl?: string
+ videoUrl?: string
+ taskId?: string
+ }>({})
+
+ // 处理文件上传
+ const handleFileUpload = useCallback(async (event: React.ChangeEvent) => {
+ const file = event.target.files?.[0]
+ if (!file) return
+
+ setReferenceFile(file)
+
+ // 自动分析图片内容
+ try {
+ const description = await describeImageByFile(file)
+ if (description) {
+ setPrompt(prev => prev ? `${prev}\n\n参考图片描述: ${description}` : description)
+ }
+ } catch (error) {
+ console.error('图片分析失败:', error)
+ }
+ }, [describeImageByFile])
+
+ // 生成内容
+ const handleGenerate = useCallback(async () => {
+ if (!prompt.trim()) {
+ alert('请输入生成提示词')
+ return
+ }
+
+ try {
+ const result = await generateContentEndToEnd(prompt, {
+ taskType,
+ aspectRatio,
+ videoDuration,
+ generateVideo: generateVideoEnabled
+ })
+
+ if (result) {
+ setResults(result)
+
+ if (result.imageUrl && onImageGenerated) {
+ onImageGenerated(result.imageUrl)
+ }
+
+ if (result.videoUrl && onVideoGenerated) {
+ onVideoGenerated(result.videoUrl)
+ }
+ }
+ } catch (error) {
+ console.error('生成失败:', error)
+ }
+ }, [
+ prompt,
+ taskType,
+ aspectRatio,
+ videoDuration,
+ generateVideoEnabled,
+ generateContentEndToEnd,
+ onImageGenerated,
+ onVideoGenerated
+ ])
+
+ // 只生成图片
+ const handleGenerateImageOnly = useCallback(async () => {
+ if (!prompt.trim()) {
+ alert('请输入生成提示词')
+ return
+ }
+
+ try {
+ const imageUrl = await generateImage({
+ prompt,
+ img_file: referenceFile || undefined,
+ max_wait_time: 120
+ })
+
+ if (imageUrl) {
+ setResults(prev => ({ ...prev, imageUrl }))
+ onImageGenerated?.(imageUrl)
+ }
+ } catch (error) {
+ console.error('图片生成失败:', error)
+ }
+ }, [prompt, referenceFile, generateImage, onImageGenerated])
+
+ // 基于图片生成视频
+ const handleGenerateVideoFromImage = useCallback(async () => {
+ if (!results.imageUrl) {
+ alert('请先生成图片')
+ return
+ }
+
+ try {
+ const videoUrl = await generateVideo({
+ prompt,
+ img_url: results.imageUrl,
+ duration: videoDuration,
+ max_wait_time: 300
+ })
+
+ if (videoUrl) {
+ setResults(prev => ({ ...prev, videoUrl }))
+ onVideoGenerated?.(videoUrl)
+ }
+ } catch (error) {
+ console.error('视频生成失败:', error)
+ }
+ }, [results.imageUrl, prompt, videoDuration, generateVideo, onVideoGenerated])
+
+ // 重置所有状态
+ const handleReset = useCallback(() => {
+ reset()
+ setPrompt('')
+ setReferenceFile(null)
+ setResults({})
+ }, [reset])
+
+ return (
+
+ {/* 标题 */}
+
+
AI 内容生成器
+
基于 Text Video Agent API 的智能内容生成工具
+
+
+ {/* 输入区域 */}
+
+ {/* 提示词输入 */}
+
+
+
+
+ {/* 参数配置 */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 参考图片上传 */}
+
+
+
+
+ {referenceFile && (
+
+ {referenceFile.name}
+
+ )}
+
+
+
+ {/* 生成选项 */}
+
+
+
+
+
+ {/* 状态显示 */}
+ {state.isLoading && (
+
+
+
+ {state.currentStep}
+
+ {state.progress > 0 && (
+
+ )}
+
+ )}
+
+ {/* 错误显示 */}
+ {state.error && (
+
+ )}
+
+ {/* 操作按钮 */}
+
+
+
+
+
+ {results.imageUrl && (
+
+ )}
+
+ {state.isLoading && (
+
+ )}
+
+
+
+
+ {/* 结果显示 */}
+ {(results.imageUrl || results.videoUrl) && (
+
+
生成结果
+
+ {/* 图片结果 */}
+ {results.imageUrl && (
+
+
+

+
+ )}
+
+ {/* 视频结果 */}
+ {results.videoUrl && (
+
+ )}
+
+ )}
+
+ )
+}
+
+export default TextVideoGenerator
diff --git a/src/components/WelcomeSection.tsx b/src/components/WelcomeSection.tsx
index a53c6a5..dd6d151 100644
--- a/src/components/WelcomeSection.tsx
+++ b/src/components/WelcomeSection.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import { Link } from 'react-router-dom'
-import { Plus } from 'lucide-react'
+import { Plus, Wand2 } from 'lucide-react'
const WelcomeSection: React.FC = () => {
return (
@@ -13,8 +13,15 @@ const WelcomeSection: React.FC = () => {
+
+ AI 内容生成
+
+
创建新项目
diff --git a/src/hooks/useTextVideoAgent.ts b/src/hooks/useTextVideoAgent.ts
new file mode 100644
index 0000000..31e89f1
--- /dev/null
+++ b/src/hooks/useTextVideoAgent.ts
@@ -0,0 +1,403 @@
+/**
+ * React Hook for Text Video Agent API
+ */
+
+import { useState, useCallback, useRef, useEffect } from 'react'
+import {
+ textVideoAgentAPI,
+ ImageGenerationParams,
+ VideoGenerationParams,
+ TaskRequest,
+ APIResponse
+} from '../services/textVideoAgentAPI'
+
+import {
+ TaskType,
+ AspectRatio,
+ VideoDuration,
+ TaskStatus as TaskStatusEnum
+} from '../services/textVideoAgentTypes'
+
+// Hook状态接口
+interface UseTextVideoAgentState {
+ isLoading: boolean
+ error: string | null
+ progress: number
+ currentStep: string
+ lastResult: any
+}
+
+// Hook返回值接口
+interface UseTextVideoAgentReturn {
+ // 状态
+ state: UseTextVideoAgentState
+
+ // 基础功能
+ healthCheck: () => Promise
+ uploadFile: (file: File) => Promise
+
+ // 图片生成
+ generateImage: (params: ImageGenerationParams) => Promise
+ generateImageAsync: (prompt: string, imgFile?: File) => Promise
+
+ // 视频生成
+ generateVideo: (params: VideoGenerationParams) => Promise
+ generateVideoAsync: (params: VideoGenerationParams) => Promise
+
+ // 任务管理
+ createTask: (request: TaskRequest) => Promise
+ getTaskStatus: (taskId: string) => Promise
+
+ // 高级功能
+ generateContentEndToEnd: (
+ prompt: string,
+ options?: {
+ taskType?: TaskType
+ aspectRatio?: AspectRatio
+ videoDuration?: VideoDuration
+ generateVideo?: boolean
+ }
+ ) => Promise<{ imageUrl?: string; videoUrl?: string; taskId: string } | null>
+
+ // 图片描述
+ describeImage: (imageUrl: string) => Promise
+ describeImageByFile: (file: File) => Promise
+
+ // 工具函数
+ reset: () => void
+ cancel: () => void
+}
+
+/**
+ * Text Video Agent API React Hook
+ */
+export function useTextVideoAgent(): UseTextVideoAgentReturn {
+ const [state, setState] = useState({
+ isLoading: false,
+ error: null,
+ progress: 0,
+ currentStep: '',
+ lastResult: null
+ })
+
+ const abortControllerRef = useRef(null)
+
+ // 更新状态的辅助函数
+ const updateState = useCallback((updates: Partial) => {
+ setState(prev => ({ ...prev, ...updates }))
+ }, [])
+
+ // 重置状态
+ const reset = useCallback(() => {
+ setState({
+ isLoading: false,
+ error: null,
+ progress: 0,
+ currentStep: '',
+ lastResult: null
+ })
+ }, [])
+
+ // 取消当前操作
+ const cancel = useCallback(() => {
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort()
+ abortControllerRef.current = null
+ }
+ updateState({ isLoading: false, currentStep: '已取消' })
+ }, [updateState])
+
+ // 通用的异步操作包装器
+ const withAsyncOperation = useCallback(async (
+ operation: () => Promise,
+ stepName: string
+ ): Promise => {
+ try {
+ updateState({
+ isLoading: true,
+ error: null,
+ currentStep: stepName,
+ progress: 0
+ })
+
+ abortControllerRef.current = new AbortController()
+
+ const result = await operation()
+
+ updateState({
+ isLoading: false,
+ progress: 100,
+ currentStep: '完成',
+ lastResult: result
+ })
+
+ return result
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : '未知错误'
+ updateState({
+ isLoading: false,
+ error: errorMessage,
+ currentStep: '错误'
+ })
+ console.error(`${stepName} 失败:`, error)
+ return null
+ } finally {
+ abortControllerRef.current = null
+ }
+ }, [updateState])
+
+ // 健康检查
+ const healthCheck = useCallback(async (): Promise => {
+ return withAsyncOperation(
+ () => textVideoAgentAPI.healthCheck(),
+ '健康检查'
+ ) as Promise
+ }, [withAsyncOperation])
+
+ // 文件上传
+ const uploadFile = useCallback(async (file: File): Promise => {
+ return withAsyncOperation(async () => {
+ const result = await textVideoAgentAPI.uploadFile(file)
+ return result.data || null
+ }, '上传文件')
+ }, [withAsyncOperation])
+
+ // 图片生成(同步)
+ const generateImage = useCallback(async (params: ImageGenerationParams): Promise => {
+ return withAsyncOperation(async () => {
+ updateState({ progress: 20 })
+ const result = await textVideoAgentAPI.generateImageWithRetry(params)
+ return result.data?.image_url || result.data?.url || null
+ }, '生成图片')
+ }, [withAsyncOperation, updateState])
+
+ // 图片生成(异步)
+ const generateImageAsync = useCallback(async (prompt: string, imgFile?: File): Promise => {
+ const taskResult = await textVideoAgentAPI.generateImageAsync(prompt, imgFile)
+ const taskId = taskResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('未获取到任务ID')
+ }
+
+ return taskId
+ }, [])
+
+ // 视频生成(同步)
+ const generateVideo = useCallback(async (params: VideoGenerationParams): Promise => {
+ return withAsyncOperation(async () => {
+ updateState({ progress: 20 })
+ const result = await textVideoAgentAPI.generateVideoWithRetry(params)
+ return result.data?.video_url || result.data?.url || null
+ }, '生成视频')
+ }, [withAsyncOperation, updateState])
+
+ // 视频生成(异步)
+ const generateVideoAsync = useCallback(async (params: VideoGenerationParams): Promise => {
+ const taskResult = await textVideoAgentAPI.generateVideoAsync(params)
+ const taskId = taskResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('未获取到任务ID')
+ }
+
+ return taskId
+ }, [])
+
+ // 创建任务
+ const createTask = useCallback(async (request: TaskRequest): Promise => {
+ const result = await textVideoAgentAPI.createTask(request)
+ const taskId = result.data?.task_id
+
+ if (!taskId) {
+ throw new Error('任务创建失败')
+ }
+
+ return taskId
+ }, [])
+
+ // 获取任务状态
+ const getTaskStatus = useCallback(async (taskId: string): Promise => {
+ const result = await textVideoAgentAPI.getTaskStatusAsync(taskId)
+ return result.data
+ }, [])
+
+ // 端到端内容生成
+ const generateContentEndToEnd = useCallback(async (
+ prompt: string,
+ options: {
+ taskType?: TaskType
+ aspectRatio?: AspectRatio
+ videoDuration?: VideoDuration
+ generateVideo?: boolean
+ } = {}
+ ): Promise<{ imageUrl?: string; videoUrl?: string; taskId: string } | null> => {
+ return withAsyncOperation(async () => {
+ const result = await textVideoAgentAPI.generateContentEndToEnd(prompt, {
+ ...options,
+ onProgress: (step, progress) => {
+ updateState({ currentStep: step, progress })
+ }
+ })
+ return result
+ }, '端到端内容生成')
+ }, [withAsyncOperation, updateState])
+
+ // 图片描述(通过URL)
+ const describeImage = useCallback(async (imageUrl: string): Promise => {
+ return withAsyncOperation(async () => {
+ const result = await textVideoAgentAPI.describeImageByUrl({ image_url: imageUrl })
+ return result.data?.description || null
+ }, '分析图片')
+ }, [withAsyncOperation])
+
+ // 图片描述(通过文件)
+ const describeImageByFile = useCallback(async (file: File): Promise => {
+ return withAsyncOperation(async () => {
+ const result = await textVideoAgentAPI.describeImageByFile({ img_file: file })
+ return result.data?.description || null
+ }, '分析图片')
+ }, [withAsyncOperation])
+
+ // 清理资源
+ useEffect(() => {
+ return () => {
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort()
+ }
+ }
+ }, [])
+
+ return {
+ state,
+ healthCheck,
+ uploadFile,
+ generateImage,
+ generateImageAsync,
+ generateVideo,
+ generateVideoAsync,
+ createTask,
+ getTaskStatus,
+ generateContentEndToEnd,
+ describeImage,
+ describeImageByFile,
+ reset,
+ cancel
+ }
+}
+
+/**
+ * 任务轮询Hook
+ */
+export function useTaskPolling(
+ taskId: string | null,
+ options: {
+ enabled?: boolean
+ interval?: number
+ maxDuration?: number
+ onComplete?: (result: any) => void
+ onError?: (error: string) => void
+ onProgress?: (status: any) => void
+ } = {}
+) {
+ const {
+ enabled = true,
+ interval = 2000,
+ maxDuration = 300000, // 5分钟
+ onComplete,
+ onError,
+ onProgress
+ } = options
+
+ const [status, setStatus] = useState(null)
+ const [isPolling, setIsPolling] = useState(false)
+ const intervalRef = useRef(null)
+ const startTimeRef = useRef(0)
+
+ const startPolling = useCallback(async () => {
+ if (!taskId || !enabled) return
+
+ setIsPolling(true)
+ startTimeRef.current = Date.now()
+
+ const poll = async () => {
+ try {
+ const result = await textVideoAgentAPI.getTaskStatusAsync(taskId)
+ const taskStatus = result.data
+
+ setStatus(taskStatus)
+ onProgress?.(taskStatus)
+
+ // 检查是否完成
+ if (taskStatus?.status === TaskStatusEnum.COMPLETED) {
+ setIsPolling(false)
+ onComplete?.(taskStatus)
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ intervalRef.current = null
+ }
+ return
+ }
+
+ // 检查是否失败
+ if (taskStatus?.status === TaskStatusEnum.FAILED) {
+ setIsPolling(false)
+ onError?.(taskStatus?.error || '任务失败')
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ intervalRef.current = null
+ }
+ return
+ }
+
+ // 检查是否超时
+ if (Date.now() - startTimeRef.current > maxDuration) {
+ setIsPolling(false)
+ onError?.('任务超时')
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ intervalRef.current = null
+ }
+ return
+ }
+ } catch (error) {
+ console.error('轮询任务状态失败:', error)
+ onError?.(error instanceof Error ? error.message : '轮询失败')
+ }
+ }
+
+ // 立即执行一次
+ await poll()
+
+ // 设置定时轮询
+ if (isPolling) {
+ intervalRef.current = setInterval(poll, interval)
+ }
+ }, [taskId, enabled, interval, maxDuration, onComplete, onError, onProgress])
+
+ const stopPolling = useCallback(() => {
+ setIsPolling(false)
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current)
+ intervalRef.current = null
+ }
+ }, [])
+
+ // 自动开始轮询
+ useEffect(() => {
+ if (taskId && enabled) {
+ startPolling()
+ }
+
+ return () => {
+ stopPolling()
+ }
+ }, [taskId, enabled, startPolling, stopPolling])
+
+ return {
+ status,
+ isPolling,
+ startPolling,
+ stopPolling
+ }
+}
diff --git a/src/pages/TextVideoGeneratorPage.tsx b/src/pages/TextVideoGeneratorPage.tsx
new file mode 100644
index 0000000..ade912d
--- /dev/null
+++ b/src/pages/TextVideoGeneratorPage.tsx
@@ -0,0 +1,213 @@
+/**
+ * Text Video Generator Page
+ * 基于 Text Video Agent API 的智能内容生成页面
+ */
+
+import React, { useState } from 'react'
+import { ArrowLeft, Info, ExternalLink } from 'lucide-react'
+import { Link } from 'react-router-dom'
+import TextVideoGenerator from '../components/TextVideoGenerator'
+
+const TextVideoGeneratorPage: React.FC = () => {
+ const [generatedContent, setGeneratedContent] = useState<{
+ images: string[]
+ videos: string[]
+ }>({
+ images: [],
+ videos: []
+ })
+
+ const handleImageGenerated = (imageUrl: string) => {
+ setGeneratedContent(prev => ({
+ ...prev,
+ images: [...prev.images, imageUrl]
+ }))
+ }
+
+ const handleVideoGenerated = (videoUrl: string) => {
+ setGeneratedContent(prev => ({
+ ...prev,
+ videos: [...prev.videos, videoUrl]
+ }))
+ }
+
+ return (
+
+ {/* 页面头部 */}
+
+
+
+
+
+
+ 返回首页
+
+
+
AI 内容生成器
+
基于 Text Video Agent API 的智能内容生成
+
+
+
+
+
+
+
+
+ {/* 主要内容区域 */}
+
+
+ {/* 左侧:生成器 */}
+
+
+
+
+ {/* 右侧:信息面板 */}
+
+ {/* 功能介绍 */}
+
+
+
+
功能介绍
+
+
+
+
🎨 图片生成
+
基于 Midjourney 的高质量图片生成,支持参考图片和自定义提示词
+
+
+
🎬 视频生成
+
基于极梦的视频生成功能,将静态图片转换为动态视频
+
+
+
🔄 端到端生成
+
一键完成从提示词到最终视频的完整生成流程
+
+
+
📊 实时进度
+
实时显示生成进度和状态,支持取消和重试
+
+
+
+
+ {/* 任务类型说明 */}
+
+
任务类型说明
+
+
+ Vlog
+ 生活记录风格
+
+
+ 茶文化
+ 茶艺相关内容
+
+
+ 人物
+ 人物肖像风格
+
+
+ 烹饪
+ 美食制作过程
+
+
+
+
+ {/* 生成统计 */}
+
+
本次会话统计
+
+
+ 生成图片
+ {generatedContent.images.length}
+
+
+ 生成视频
+ {generatedContent.videos.length}
+
+
+ 总计内容
+
+ {generatedContent.images.length + generatedContent.videos.length}
+
+
+
+
+
+ {/* 最近生成的内容 */}
+ {(generatedContent.images.length > 0 || generatedContent.videos.length > 0) && (
+
+
最近生成
+
+ {/* 最新的图片 */}
+ {generatedContent.images.slice(-2).map((imageUrl, index) => (
+
+

+
+
+ 生成的图片
+
+
+ {new Date().toLocaleTimeString()}
+
+
+
+ ))}
+
+ {/* 最新的视频 */}
+ {generatedContent.videos.slice(-2).map((_, index) => (
+
+
+ MP4
+
+
+
+ 生成的视频
+
+
+ {new Date().toLocaleTimeString()}
+
+
+
+ ))}
+
+
+ )}
+
+ {/* 使用提示 */}
+
+
💡 使用提示
+
+
• 提示词越详细,生成效果越好
+
• 可以上传参考图片提高生成质量
+
• 建议先生成图片,再基于图片生成视频
+
• 生成过程可能需要1-3分钟,请耐心等待
+
+
+
+
+
+
+ )
+}
+
+export default TextVideoGeneratorPage
diff --git a/src/services/textVideoAgentAPI.ts b/src/services/textVideoAgentAPI.ts
new file mode 100644
index 0000000..86901b7
--- /dev/null
+++ b/src/services/textVideoAgentAPI.ts
@@ -0,0 +1,565 @@
+/**
+ * Text Video Agent API 工具库
+ * 基于 https://bowongai-dev--text-video-agent-fastapi-app.modal.run 的API封装
+ */
+
+// 基础配置
+const API_BASE_URL = 'https://bowongai-dev--text-video-agent-fastapi-app.modal.run'
+
+// 通用响应接口
+export interface APIResponse {
+ status: boolean
+ msg: string
+ data?: T
+}
+
+// 文件上传响应
+export interface FileUploadResponse {
+ status: boolean
+ msg: string
+ data?: string // 文件URL
+}
+
+// 任务请求接口
+export interface TaskRequest {
+ task_type?: string // 任务类型如: tea, chop, lady, vlog
+ prompt: string // 生图的提示词
+ img_url?: string // 参考图
+ ar?: string // 生成图片,视频的分辨率,默认9:16
+}
+
+// 任务状态接口
+export interface TaskStatus {
+ task_id: string
+ status: 'pending' | 'running' | 'completed' | 'failed'
+ progress?: number
+ result?: any
+ error?: string
+}
+
+// 图片生成参数
+export interface ImageGenerationParams {
+ prompt: string
+ img_file?: File
+ max_wait_time?: number // 默认120秒
+ poll_interval?: number // 默认2秒
+}
+
+// 视频生成参数
+export interface VideoGenerationParams {
+ prompt: string
+ img_url?: string
+ img_file?: File
+ duration?: string // 默认5秒
+ max_wait_time?: number // 默认300秒
+ poll_interval?: number // 默认5秒
+}
+
+// 图片描述参数
+export interface ImageDescribeParams {
+ image_url?: string
+ img_file?: File
+ max_wait_time?: number // 默认120秒
+ poll_interval?: number // 默认2秒
+}
+
+/**
+ * HTTP请求工具类
+ */
+class HTTPClient {
+ private baseURL: string
+
+ constructor(baseURL: string) {
+ this.baseURL = baseURL
+ }
+
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise {
+ const url = `${this.baseURL}${endpoint}`
+
+ try {
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ ...options.headers,
+ },
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ const data = await response.json()
+ return data
+ } catch (error) {
+ console.error(`API request failed: ${url}`, error)
+ throw error
+ }
+ }
+
+ async get(endpoint: string, params?: Record): Promise {
+ const url = new URL(endpoint, this.baseURL)
+ if (params) {
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ url.searchParams.append(key, String(value))
+ }
+ })
+ }
+
+ return this.request(url.pathname + url.search)
+ }
+
+ async post(
+ endpoint: string,
+ data?: any,
+ options: RequestInit = {}
+ ): Promise {
+ return this.request(endpoint, {
+ method: 'POST',
+ ...options,
+ body: data,
+ })
+ }
+
+ async postJSON(endpoint: string, data?: any): Promise {
+ return this.post(endpoint, JSON.stringify(data), {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ }
+
+ async postFormData(endpoint: string, formData: FormData): Promise {
+ return this.post(endpoint, formData)
+ }
+
+ async postFormUrlEncoded(
+ endpoint: string,
+ data: Record
+ ): Promise {
+ const formData = new URLSearchParams()
+ Object.entries(data).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ formData.append(key, String(value))
+ }
+ })
+
+ return this.post(endpoint, formData, {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ })
+ }
+}
+
+/**
+ * Text Video Agent API 客户端
+ */
+export class TextVideoAgentAPI {
+ private client: HTTPClient
+
+ constructor(baseURL: string = API_BASE_URL) {
+ this.client = new HTTPClient(baseURL)
+ }
+
+ // ==================== 基础功能 ====================
+
+ /**
+ * 健康检查
+ */
+ async healthCheck(): Promise {
+ return this.client.get('/health')
+ }
+
+ /**
+ * 获取示例提示词
+ */
+ async getSamplePrompt(taskType?: string): Promise {
+ return this.client.get('/api/prompt/default', { task_type: taskType })
+ }
+
+ // ==================== 文件操作 ====================
+
+ /**
+ * 上传文件到COS
+ */
+ async uploadFile(file: File): Promise {
+ const formData = new FormData()
+ formData.append('file', file)
+ return this.client.postFormData('/api/file/upload', formData)
+ }
+
+ // ==================== Midjourney图片生成 ====================
+
+ /**
+ * Midjourney健康检查
+ */
+ async mjHealthCheck(): Promise {
+ return this.client.get('/api/mj/health')
+ }
+
+ /**
+ * 同步生成图片(阻塞等待)
+ */
+ async generateImageSync(params: ImageGenerationParams): Promise {
+ const formData = new FormData()
+ if (params.img_file) {
+ formData.append('img_file', params.img_file)
+ }
+
+ const queryParams = {
+ prompt: params.prompt,
+ max_wait_time: params.max_wait_time,
+ poll_interval: params.poll_interval,
+ }
+
+ const url = new URL('/api/mj/sync/image', this.client['baseURL'])
+ Object.entries(queryParams).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ url.searchParams.append(key, String(value))
+ }
+ })
+
+ return this.client.postFormData(url.pathname + url.search, formData)
+ }
+
+ /**
+ * 生成图片(推荐使用同步接口)
+ */
+ async generateImage(params: ImageGenerationParams): Promise {
+ const formData = new FormData()
+ formData.append('prompt', params.prompt)
+ if (params.img_file) {
+ formData.append('img_file', params.img_file)
+ }
+ if (params.max_wait_time) {
+ formData.append('max_wait_time', String(params.max_wait_time))
+ }
+ if (params.poll_interval) {
+ formData.append('poll_interval', String(params.poll_interval))
+ }
+
+ return this.client.postFormData('/api/mj/generate-image', formData)
+ }
+
+ /**
+ * 异步提交生图任务
+ */
+ async generateImageAsync(prompt: string, imgFile?: File): Promise {
+ const formData = new FormData()
+ if (imgFile) {
+ formData.append('img_file', imgFile)
+ }
+
+ return this.client.postFormData(`/api/mj/async/generate/image?prompt=${encodeURIComponent(prompt)}`, formData)
+ }
+
+ /**
+ * 查询异步任务状态
+ */
+ async queryImageTaskStatus(taskId: string): Promise {
+ return this.client.get('/api/mj/async/query/status', { task_id: taskId })
+ }
+
+ /**
+ * 通过URL获取图像描述
+ */
+ async describeImageByUrl(params: ImageDescribeParams): Promise {
+ if (!params.image_url) {
+ throw new Error('image_url is required')
+ }
+
+ return this.client.postFormUrlEncoded('/api/mj/sync/img/describe', {
+ image_url: params.image_url,
+ max_wait_time: params.max_wait_time,
+ poll_interval: params.poll_interval,
+ })
+ }
+
+ /**
+ * 通过文件获取图像描述
+ */
+ async describeImageByFile(params: ImageDescribeParams): Promise {
+ if (!params.img_file) {
+ throw new Error('img_file is required')
+ }
+
+ const formData = new FormData()
+ formData.append('img_file', params.img_file)
+ if (params.max_wait_time) {
+ formData.append('max_wait_time', String(params.max_wait_time))
+ }
+ if (params.poll_interval) {
+ formData.append('poll_interval', String(params.poll_interval))
+ }
+
+ return this.client.postFormData('/api/mj/sync/file/img/describe', formData)
+ }
+
+ // ==================== 极梦视频生成 ====================
+
+ /**
+ * 极梦健康检查
+ */
+ async jmHealthCheck(): Promise {
+ return this.client.get('/api/jm/health')
+ }
+
+ /**
+ * 同步生成视频(阻塞等待)
+ */
+ async generateVideoSync(params: VideoGenerationParams): Promise {
+ if (!params.img_url) {
+ throw new Error('img_url is required for sync video generation')
+ }
+
+ return this.client.postFormUrlEncoded('/api/jm/generate-video', {
+ prompt: params.prompt,
+ img_url: params.img_url,
+ duration: params.duration || '5',
+ max_wait_time: params.max_wait_time || 300,
+ poll_interval: params.poll_interval || 5,
+ })
+ }
+
+ /**
+ * 异步生成视频
+ */
+ async generateVideoAsync(params: VideoGenerationParams): Promise {
+ const formData = new FormData()
+ formData.append('prompt', params.prompt)
+
+ if (params.img_url) {
+ formData.append('img_url', params.img_url)
+ }
+
+ if (params.img_file) {
+ formData.append('img_file', params.img_file)
+ }
+
+ formData.append('duration', params.duration || '5')
+
+ return this.client.postFormData('/api/jm/async/generate/video', formData)
+ }
+
+ /**
+ * 查询视频生成任务状态
+ */
+ async queryVideoTaskStatus(taskId: string): Promise {
+ return this.client.get('/api/jm/async/query/status', { task_id: taskId })
+ }
+
+ // ==================== 任务管理 ====================
+
+ /**
+ * 创建异步任务(不阻塞)
+ */
+ async createTask(request: TaskRequest): Promise {
+ return this.client.postJSON('/api/task/create/task', request)
+ }
+
+ /**
+ * 同步查询任务结果(阻塞等待)
+ */
+ async getTaskResultSync(taskId: string): Promise {
+ return this.client.get(`/api/task/${taskId}`)
+ }
+
+ /**
+ * 异步查询任务状态(立即返回)
+ */
+ async getTaskStatusAsync(taskId: string): Promise {
+ return this.client.get(`/api/task/status/${taskId}`)
+ }
+
+ // ==================== 高级封装方法 ====================
+
+ /**
+ * 完整的图片生成流程(带重试机制)
+ */
+ async generateImageWithRetry(
+ params: ImageGenerationParams,
+ maxRetries: number = 3
+ ): Promise {
+ let lastError: Error | null = null
+
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ const result = await this.generateImageSync(params)
+ if (result.status) {
+ return result
+ }
+ lastError = new Error(result.msg || 'Generation failed')
+ } catch (error) {
+ lastError = error as Error
+ console.warn(`Image generation attempt ${i + 1} failed:`, error)
+
+ // 如果不是最后一次重试,等待一段时间再重试
+ if (i < maxRetries - 1) {
+ await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)))
+ }
+ }
+ }
+
+ throw lastError || new Error('All retry attempts failed')
+ }
+
+ /**
+ * 完整的视频生成流程(带重试机制)
+ */
+ async generateVideoWithRetry(
+ params: VideoGenerationParams,
+ maxRetries: number = 3
+ ): Promise {
+ let lastError: Error | null = null
+
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ const result = await this.generateVideoSync(params)
+ if (result.status) {
+ return result
+ }
+ lastError = new Error(result.msg || 'Generation failed')
+ } catch (error) {
+ lastError = error as Error
+ console.warn(`Video generation attempt ${i + 1} failed:`, error)
+
+ // 如果不是最后一次重试,等待一段时间再重试
+ if (i < maxRetries - 1) {
+ await new Promise(resolve => setTimeout(resolve, 5000 * (i + 1)))
+ }
+ }
+ }
+
+ throw lastError || new Error('All retry attempts failed')
+ }
+
+ /**
+ * 轮询任务状态直到完成
+ */
+ async pollTaskUntilComplete(
+ taskId: string,
+ pollInterval: number = 2000,
+ maxWaitTime: number = 300000, // 5分钟
+ onProgress?: (status: any) => void
+ ): Promise {
+ const startTime = Date.now()
+
+ while (Date.now() - startTime < maxWaitTime) {
+ try {
+ const status = await this.getTaskStatusAsync(taskId)
+
+ if (onProgress) {
+ onProgress(status)
+ }
+
+ if (status.data?.status === 'completed') {
+ return status
+ }
+
+ if (status.data?.status === 'failed') {
+ throw new Error(status.data?.error || 'Task failed')
+ }
+
+ // 等待下次轮询
+ await new Promise(resolve => setTimeout(resolve, pollInterval))
+ } catch (error) {
+ console.error('Error polling task status:', error)
+ throw error
+ }
+ }
+
+ throw new Error('Task polling timeout')
+ }
+
+ /**
+ * 端到端的内容生成流程
+ */
+ async generateContentEndToEnd(
+ prompt: string,
+ options: {
+ taskType?: string
+ referenceImageFile?: File
+ referenceImageUrl?: string
+ aspectRatio?: string
+ videoDuration?: string
+ generateVideo?: boolean
+ onProgress?: (step: string, progress: number) => void
+ } = {}
+ ): Promise<{
+ imageUrl?: string
+ videoUrl?: string
+ taskId: string
+ }> {
+ const { onProgress } = options
+
+ try {
+ // 步骤1: 创建任务
+ onProgress?.('创建任务', 10)
+ const taskRequest: TaskRequest = {
+ task_type: options.taskType || 'vlog',
+ prompt,
+ img_url: options.referenceImageUrl,
+ ar: options.aspectRatio || '9:16'
+ }
+
+ const taskResult = await this.createTask(taskRequest)
+ const taskId = taskResult.data?.task_id
+
+ if (!taskId) {
+ throw new Error('Failed to create task')
+ }
+
+ // 步骤2: 生成图片
+ onProgress?.('生成图片', 30)
+ const imageParams: ImageGenerationParams = {
+ prompt,
+ img_file: options.referenceImageFile,
+ max_wait_time: 120
+ }
+
+ const imageResult = await this.generateImageWithRetry(imageParams)
+ const imageUrl = imageResult.data?.image_url || imageResult.data?.url
+
+ if (!imageUrl) {
+ throw new Error('Failed to generate image')
+ }
+
+ let videoUrl: string | undefined
+
+ // 步骤3: 生成视频(如果需要)
+ if (options.generateVideo) {
+ onProgress?.('生成视频', 60)
+ const videoParams: VideoGenerationParams = {
+ prompt,
+ img_url: imageUrl,
+ duration: options.videoDuration || '5',
+ max_wait_time: 300
+ }
+
+ const videoResult = await this.generateVideoWithRetry(videoParams)
+ videoUrl = videoResult.data?.video_url || videoResult.data?.url
+ }
+
+ onProgress?.('完成', 100)
+
+ return {
+ imageUrl,
+ videoUrl,
+ taskId
+ }
+ } catch (error) {
+ console.error('End-to-end generation failed:', error)
+ throw error
+ }
+ }
+}
+
+// 导出单例实例
+export const textVideoAgentAPI = new TextVideoAgentAPI()
+
+// 导出默认实例
+export default textVideoAgentAPI
diff --git a/src/services/textVideoAgentTypes.ts b/src/services/textVideoAgentTypes.ts
new file mode 100644
index 0000000..45db317
--- /dev/null
+++ b/src/services/textVideoAgentTypes.ts
@@ -0,0 +1,311 @@
+/**
+ * Text Video Agent API 类型定义
+ */
+
+// 任务类型枚举
+export enum TaskType {
+ TEA = 'tea',
+ CHOP = 'chop',
+ LADY = 'lady',
+ VLOG = 'vlog'
+}
+
+// 任务状态枚举
+export enum TaskStatus {
+ PENDING = 'pending',
+ RUNNING = 'running',
+ COMPLETED = 'completed',
+ FAILED = 'failed'
+}
+
+// 长宽比枚举
+export enum AspectRatio {
+ SQUARE = '1:1',
+ PORTRAIT = '9:16',
+ LANDSCAPE = '16:9',
+ VERTICAL = '3:4',
+ HORIZONTAL = '4:3'
+}
+
+// 视频时长选项
+export enum VideoDuration {
+ SHORT = '3',
+ MEDIUM = '5',
+ LONG = '10'
+}
+
+// 扩展的API响应接口
+export interface ExtendedAPIResponse {
+ status: boolean
+ msg: string
+ data?: T
+ timestamp?: string
+ request_id?: string
+}
+
+// 图片生成结果
+export interface ImageGenerationResult {
+ image_url: string
+ thumbnail_url?: string
+ width?: number
+ height?: number
+ format?: string
+ size?: number
+ generation_time?: number
+ prompt_used?: string
+}
+
+// 视频生成结果
+export interface VideoGenerationResult {
+ video_url: string
+ thumbnail_url?: string
+ duration: number
+ width?: number
+ height?: number
+ format?: string
+ size?: number
+ generation_time?: number
+ prompt_used?: string
+ image_url_used?: string
+}
+
+// 任务详细信息
+export interface TaskDetail {
+ task_id: string
+ task_type: TaskType
+ status: TaskStatus
+ progress: number
+ created_at: string
+ updated_at: string
+ completed_at?: string
+ prompt: string
+ img_url?: string
+ ar: string
+ result?: ImageGenerationResult | VideoGenerationResult
+ error?: {
+ code: string
+ message: string
+ details?: any
+ }
+ metadata?: {
+ user_id?: string
+ session_id?: string
+ client_info?: any
+ }
+}
+
+// 图片描述结果
+export interface ImageDescriptionResult {
+ description: string
+ tags?: string[]
+ confidence?: number
+ language?: string
+ analysis_time?: number
+}
+
+// 文件上传结果
+export interface FileUploadResult {
+ file_url: string
+ file_name: string
+ file_size: number
+ file_type: string
+ upload_time: string
+ cdn_url?: string
+}
+
+// 健康检查结果
+export interface HealthCheckResult {
+ service: string
+ status: 'healthy' | 'unhealthy'
+ version?: string
+ uptime?: number
+ dependencies?: {
+ [key: string]: 'healthy' | 'unhealthy'
+ }
+}
+
+// 示例提示词结果
+export interface SamplePromptResult {
+ task_type: string
+ prompts: string[]
+ examples?: {
+ prompt: string
+ description: string
+ tags?: string[]
+ }[]
+}
+
+// 生成选项配置
+export interface GenerationOptions {
+ // 通用选项
+ quality?: 'low' | 'medium' | 'high' | 'ultra'
+ style?: 'realistic' | 'artistic' | 'cartoon' | 'anime'
+ mood?: 'bright' | 'dark' | 'neutral' | 'vibrant'
+
+ // 图片特定选项
+ image_format?: 'jpg' | 'png' | 'webp'
+ image_quality?: number // 1-100
+
+ // 视频特定选项
+ video_format?: 'mp4' | 'webm' | 'avi'
+ video_quality?: 'low' | 'medium' | 'high' | '4k'
+ frame_rate?: number // fps
+
+ // 高级选项
+ seed?: number
+ guidance_scale?: number
+ num_inference_steps?: number
+ negative_prompt?: string
+}
+
+// 批量处理请求
+export interface BatchRequest {
+ requests: {
+ id: string
+ type: 'image' | 'video'
+ prompt: string
+ options?: GenerationOptions
+ }[]
+ batch_options?: {
+ parallel_limit?: number
+ retry_failed?: boolean
+ notify_on_complete?: boolean
+ }
+}
+
+// 批量处理结果
+export interface BatchResult {
+ batch_id: string
+ total_requests: number
+ completed: number
+ failed: number
+ results: {
+ id: string
+ status: 'completed' | 'failed'
+ result?: ImageGenerationResult | VideoGenerationResult
+ error?: string
+ }[]
+}
+
+// 使用统计
+export interface UsageStats {
+ user_id?: string
+ period: 'daily' | 'weekly' | 'monthly'
+ images_generated: number
+ videos_generated: number
+ total_generation_time: number
+ total_cost?: number
+ quota_remaining?: number
+}
+
+// 错误类型
+export interface APIError {
+ code: string
+ message: string
+ details?: any
+ timestamp: string
+ request_id?: string
+ suggestion?: string
+}
+
+// 进度回调函数类型
+export type ProgressCallback = (step: string, progress: number, details?: any) => void
+
+// 事件回调函数类型
+export type EventCallback = (event: {
+ type: 'start' | 'progress' | 'complete' | 'error'
+ data: any
+ timestamp: string
+}) => void
+
+// 配置选项
+export interface APIConfig {
+ baseURL?: string
+ timeout?: number
+ retries?: number
+ apiKey?: string
+ userAgent?: string
+ debug?: boolean
+}
+
+// 缓存选项
+export interface CacheOptions {
+ enabled: boolean
+ ttl?: number // 缓存时间(秒)
+ maxSize?: number // 最大缓存条目数
+ storage?: 'memory' | 'localStorage' | 'sessionStorage'
+}
+
+// 高级配置
+export interface AdvancedConfig extends APIConfig {
+ cache?: CacheOptions
+ rateLimit?: {
+ enabled: boolean
+ requestsPerMinute?: number
+ requestsPerHour?: number
+ }
+ monitoring?: {
+ enabled: boolean
+ endpoint?: string
+ metrics?: string[]
+ }
+}
+
+// 导出所有类型的联合类型
+export type AllGenerationResults = ImageGenerationResult | VideoGenerationResult
+export type AllAPIResponses = ExtendedAPIResponse
+
+// 常用的预设配置
+export const PRESET_CONFIGS = {
+ // 快速生成(低质量,快速)
+ FAST: {
+ quality: 'low' as const,
+ max_wait_time: 60,
+ poll_interval: 1
+ },
+
+ // 标准生成(中等质量)
+ STANDARD: {
+ quality: 'medium' as const,
+ max_wait_time: 120,
+ poll_interval: 2
+ },
+
+ // 高质量生成(高质量,较慢)
+ HIGH_QUALITY: {
+ quality: 'high' as const,
+ max_wait_time: 300,
+ poll_interval: 5
+ },
+
+ // 超高质量生成(最高质量,最慢)
+ ULTRA: {
+ quality: 'ultra' as const,
+ max_wait_time: 600,
+ poll_interval: 10
+ }
+} as const
+
+// 常用的任务类型配置
+export const TASK_TYPE_CONFIGS = {
+ [TaskType.TEA]: {
+ description: '茶叶相关内容生成',
+ defaultAR: AspectRatio.PORTRAIT,
+ suggestedDuration: VideoDuration.MEDIUM
+ },
+ [TaskType.CHOP]: {
+ description: '切菜/烹饪相关内容生成',
+ defaultAR: AspectRatio.LANDSCAPE,
+ suggestedDuration: VideoDuration.SHORT
+ },
+ [TaskType.LADY]: {
+ description: '女性人物相关内容生成',
+ defaultAR: AspectRatio.PORTRAIT,
+ suggestedDuration: VideoDuration.MEDIUM
+ },
+ [TaskType.VLOG]: {
+ description: 'Vlog风格内容生成',
+ defaultAR: AspectRatio.PORTRAIT,
+ suggestedDuration: VideoDuration.LONG
+ }
+} as const
diff --git a/tests/textVideoAgentAPI.test.ts b/tests/textVideoAgentAPI.test.ts
new file mode 100644
index 0000000..1e98ccd
--- /dev/null
+++ b/tests/textVideoAgentAPI.test.ts
@@ -0,0 +1,442 @@
+/**
+ * Text Video Agent API 测试文件
+ */
+
+import { textVideoAgentAPI, TextVideoAgentAPI } from '../src/services/textVideoAgentAPI'
+import { TaskType, AspectRatio, VideoDuration } from '../src/services/textVideoAgentTypes'
+
+// Mock fetch for testing
+global.fetch = jest.fn()
+
+describe('TextVideoAgentAPI', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ describe('基础功能测试', () => {
+ test('健康检查', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Service is healthy'
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.healthCheck()
+
+ expect(fetch).toHaveBeenCalledWith(
+ expect.stringContaining('/health'),
+ expect.any(Object)
+ )
+ expect(result).toEqual(mockResponse)
+ })
+
+ test('获取示例提示词', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Success',
+ data: {
+ prompts: ['示例提示词1', '示例提示词2']
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.getSamplePrompt(TaskType.VLOG)
+
+ expect(fetch).toHaveBeenCalledWith(
+ expect.stringContaining('/api/prompt/default?task_type=vlog'),
+ expect.any(Object)
+ )
+ expect(result).toEqual(mockResponse)
+ })
+ })
+
+ describe('图片生成测试', () => {
+ test('同步生成图片', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Image generated successfully',
+ data: {
+ image_url: 'https://example.com/generated-image.jpg'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.generateImageSync({
+ prompt: '测试图片生成',
+ max_wait_time: 60
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.image_url).toBe('https://example.com/generated-image.jpg')
+ })
+
+ test('异步生成图片', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Task submitted',
+ data: {
+ task_id: 'test-task-123'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.generateImageAsync('测试异步图片生成')
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.task_id).toBe('test-task-123')
+ })
+
+ test('带重试的图片生成', async () => {
+ // 第一次失败
+ ;(fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'))
+
+ // 第二次成功
+ const mockResponse = {
+ status: true,
+ msg: 'Image generated successfully',
+ data: {
+ image_url: 'https://example.com/generated-image.jpg'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.generateImageWithRetry({
+ prompt: '测试重试机制',
+ max_wait_time: 60
+ }, 2)
+
+ expect(fetch).toHaveBeenCalledTimes(2)
+ expect(result).toEqual(mockResponse)
+ })
+ })
+
+ describe('视频生成测试', () => {
+ test('同步生成视频', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Video generated successfully',
+ data: {
+ video_url: 'https://example.com/generated-video.mp4'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.generateVideoSync({
+ prompt: '测试视频生成',
+ img_url: 'https://example.com/base-image.jpg',
+ duration: '5'
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.video_url).toBe('https://example.com/generated-video.mp4')
+ })
+
+ test('异步生成视频', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Video task submitted',
+ data: {
+ task_id: 'video-task-456'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.generateVideoAsync({
+ prompt: '测试异步视频生成',
+ duration: '5'
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.task_id).toBe('video-task-456')
+ })
+ })
+
+ describe('任务管理测试', () => {
+ test('创建任务', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Task created successfully',
+ data: {
+ task_id: 'new-task-789'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.createTask({
+ task_type: TaskType.VLOG,
+ prompt: '测试任务创建',
+ ar: AspectRatio.PORTRAIT
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.task_id).toBe('new-task-789')
+ })
+
+ test('查询任务状态', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Task status retrieved',
+ data: {
+ task_id: 'test-task-123',
+ status: 'completed',
+ progress: 100,
+ result: {
+ image_url: 'https://example.com/result.jpg'
+ }
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.getTaskStatusAsync('test-task-123')
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.status).toBe('completed')
+ })
+ })
+
+ describe('文件操作测试', () => {
+ test('文件上传', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'File uploaded successfully',
+ data: 'https://example.com/uploaded-file.jpg'
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' })
+ const result = await textVideoAgentAPI.uploadFile(mockFile)
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data).toBe('https://example.com/uploaded-file.jpg')
+ })
+ })
+
+ describe('图片描述测试', () => {
+ test('通过URL描述图片', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Image described successfully',
+ data: {
+ description: '这是一张美丽的风景照片'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const result = await textVideoAgentAPI.describeImageByUrl({
+ image_url: 'https://example.com/image.jpg'
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.description).toBe('这是一张美丽的风景照片')
+ })
+
+ test('通过文件描述图片', async () => {
+ const mockResponse = {
+ status: true,
+ msg: 'Image described successfully',
+ data: {
+ description: '这是一张人物照片'
+ }
+ }
+
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => mockResponse
+ })
+
+ const mockFile = new File(['image content'], 'portrait.jpg', { type: 'image/jpeg' })
+ const result = await textVideoAgentAPI.describeImageByFile({
+ img_file: mockFile
+ })
+
+ expect(result).toEqual(mockResponse)
+ expect(result.data?.description).toBe('这是一张人物照片')
+ })
+ })
+
+ describe('高级功能测试', () => {
+ test('端到端内容生成', async () => {
+ // Mock 创建任务
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ status: true,
+ data: { task_id: 'end-to-end-task' }
+ })
+ })
+
+ // Mock 图片生成
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ status: true,
+ data: { image_url: 'https://example.com/generated.jpg' }
+ })
+ })
+
+ // Mock 视频生成
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ status: true,
+ data: { video_url: 'https://example.com/generated.mp4' }
+ })
+ })
+
+ const result = await textVideoAgentAPI.generateContentEndToEnd(
+ '测试端到端生成',
+ {
+ taskType: TaskType.VLOG,
+ aspectRatio: AspectRatio.PORTRAIT,
+ videoDuration: VideoDuration.MEDIUM,
+ generateVideo: true
+ }
+ )
+
+ expect(result).toEqual({
+ imageUrl: 'https://example.com/generated.jpg',
+ videoUrl: 'https://example.com/generated.mp4',
+ taskId: 'end-to-end-task'
+ })
+ })
+
+ test('轮询任务直到完成', async () => {
+ // 第一次查询 - 进行中
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ status: true,
+ data: { status: 'running', progress: 50 }
+ })
+ })
+
+ // 第二次查询 - 完成
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ status: true,
+ data: {
+ status: 'completed',
+ progress: 100,
+ result: { image_url: 'https://example.com/final.jpg' }
+ }
+ })
+ })
+
+ const progressCallback = jest.fn()
+
+ const result = await textVideoAgentAPI.pollTaskUntilComplete(
+ 'test-task-id',
+ 100, // 快速轮询用于测试
+ 5000, // 5秒超时
+ progressCallback
+ )
+
+ expect(fetch).toHaveBeenCalledTimes(2)
+ expect(progressCallback).toHaveBeenCalledTimes(2)
+ expect(result.data?.status).toBe('completed')
+ })
+ })
+
+ describe('错误处理测试', () => {
+ test('网络错误处理', async () => {
+ ;(fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'))
+
+ await expect(textVideoAgentAPI.healthCheck()).rejects.toThrow('Network error')
+ })
+
+ test('HTTP错误处理', async () => {
+ ;(fetch as jest.Mock).mockResolvedValueOnce({
+ ok: false,
+ status: 500,
+ json: async () => ({ error: 'Internal server error' })
+ })
+
+ await expect(textVideoAgentAPI.healthCheck()).rejects.toThrow('HTTP error! status: 500')
+ })
+
+ test('参数验证', async () => {
+ await expect(
+ textVideoAgentAPI.describeImageByUrl({} as any)
+ ).rejects.toThrow('image_url is required')
+
+ await expect(
+ textVideoAgentAPI.describeImageByFile({} as any)
+ ).rejects.toThrow('img_file is required')
+ })
+ })
+
+ describe('自定义配置测试', () => {
+ test('自定义API端点', () => {
+ const customAPI = new TextVideoAgentAPI('https://custom-endpoint.com')
+ expect(customAPI).toBeInstanceOf(TextVideoAgentAPI)
+ })
+ })
+})
+
+// 集成测试(需要真实API)
+describe('集成测试 (需要真实API)', () => {
+ // 这些测试需要真实的API端点,通常在CI/CD环境中跳过
+ const shouldRunIntegrationTests = process.env.RUN_INTEGRATION_TESTS === 'true'
+
+ test.skipIf(!shouldRunIntegrationTests)('真实健康检查', async () => {
+ const result = await textVideoAgentAPI.healthCheck()
+ expect(result.status).toBe(true)
+ })
+
+ test.skipIf(!shouldRunIntegrationTests)('真实图片生成', async () => {
+ const result = await textVideoAgentAPI.generateImageSync({
+ prompt: '一朵美丽的花',
+ max_wait_time: 60
+ })
+
+ expect(result.status).toBe(true)
+ expect(result.data?.image_url).toBeDefined()
+ }, 120000) // 2分钟超时
+})
+
+export {}