refactor: 重构 Hedra 文件上传方式,使用文件路径而非文件内容传输

- 修改 HedraFileUploadRequest 使用 file_path 而不是 file_data
- 添加 HedraFileUploadApiRequest 用于后端到 API 的请求
- 更新后端服务读取文件并转换为 API 请求格式
- 重构前端文件选择,使用 @tauri-apps/api/dialog 直接选择文件路径
- 移除复杂的文件转换和临时文件创建逻辑
- 简化文件上传流程,避免大文件在前后端间传输

这种方式更适合 Tauri 架构,避免了大文件传输的性能问题
This commit is contained in:
imeepos 2025-08-01 11:21:12 +08:00
parent 538254ee38
commit 5fdf3c5a4b
5 changed files with 58 additions and 41 deletions

View File

@ -258,12 +258,19 @@ pub struct ComfyUISyncExecuteRequest {
pub max_wait_time: Option<u32>, pub max_wait_time: Option<u32>,
} }
/// Hedra 文件上传请求 /// Hedra 文件上传请求(前端到后端)
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HedraFileUploadRequest { pub struct HedraFileUploadRequest {
pub file_path: String,
pub purpose: Option<String>, // 'image', 'audio', 'video', 'voice'
}
/// Hedra 文件上传 API 请求(后端到 API
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HedraFileUploadApiRequest {
pub file_data: Vec<u8>, pub file_data: Vec<u8>,
pub filename: String, pub filename: String,
pub purpose: Option<String>, // 'image', 'audio', 'video', 'voice' pub purpose: Option<String>,
} }
/// Hedra 任务提交请求 /// Hedra 任务提交请求

View File

@ -525,7 +525,25 @@ impl BowongTextVideoAgentService {
/// Hedra 上传文件 /// Hedra 上传文件
pub async fn hedra_upload_file(&self, request: &HedraFileUploadRequest) -> Result<FileUploadResponse> { pub async fn hedra_upload_file(&self, request: &HedraFileUploadRequest) -> Result<FileUploadResponse> {
self.execute_request("hedra_upload_file", Some(request)).await // 读取文件内容
let file_data = std::fs::read(&request.file_path)
.map_err(|e| anyhow!("Failed to read file {}: {}", request.file_path, e))?;
// 获取文件名
let filename = std::path::Path::new(&request.file_path)
.file_name()
.and_then(|name| name.to_str())
.ok_or_else(|| anyhow!("Invalid file path: {}", request.file_path))?
.to_string();
// 构造 API 请求
let api_request = HedraFileUploadApiRequest {
file_data,
filename,
purpose: request.purpose.clone(),
};
self.execute_request("hedra_upload_file", Some(&api_request)).await
} }
/// Hedra 提交任务 /// Hedra 提交任务

View File

@ -14,6 +14,7 @@ import {
FileImage, FileImage,
FileAudio FileAudio
} from 'lucide-react'; } from 'lucide-react';
import { open } from '@tauri-apps/api/dialog';
import { useNotifications } from '../../components/NotificationSystem'; import { useNotifications } from '../../components/NotificationSystem';
import { createBowongTextVideoAgentService } from '../../services/bowongTextVideoAgentService'; import { createBowongTextVideoAgentService } from '../../services/bowongTextVideoAgentService';
import { import {
@ -104,14 +105,29 @@ const HedraLipSyncTool: React.FC = () => {
} }
}, [addNotification]); }, [addNotification]);
// 文件上传 // 文件选择和上传
const uploadFile = useCallback(async (fileInfo: HedraFileInfo, purpose: 'image' | 'audio'): Promise<string> => { const selectAndUploadFile = useCallback(async (purpose: 'image' | 'audio'): Promise<string> => {
try {
// 使用文件选择对话框
const selected = await open({
multiple: false,
filters: [{
name: purpose === 'image' ? '图片文件' : '音频文件',
extensions: purpose === 'image'
? ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
: ['mp3', 'wav', 'aac', 'flac', 'm4a', 'ogg']
}]
});
if (!selected || typeof selected !== 'string') {
throw new Error('未选择文件');
}
const request: HedraFileUploadRequest = { const request: HedraFileUploadRequest = {
local_file: fileInfo.file, file_path: selected,
purpose purpose
}; };
try {
const response = await bowongService.hedraUploadFile(request); const response = await bowongService.hedraUploadFile(request);
if (response.status && response.data) { if (response.status && response.data) {
return response.data; return response.data;
@ -119,7 +135,7 @@ const HedraLipSyncTool: React.FC = () => {
throw new Error(response.msg || '文件上传失败'); throw new Error(response.msg || '文件上传失败');
} }
} catch (error) { } catch (error) {
console.error('文件上传失败:', error); console.error('文件选择和上传失败:', error);
throw error; throw error;
} }
}, []); }, []);
@ -144,7 +160,7 @@ const HedraLipSyncTool: React.FC = () => {
imageFile: prev.imageFile ? { ...prev.imageFile, uploadStatus: 'uploading' } : null imageFile: prev.imageFile ? { ...prev.imageFile, uploadStatus: 'uploading' } : null
})); }));
const imageUrl = await uploadFile(state.imageFile, 'image'); const imageUrl = await selectAndUploadFile('image');
setState(prev => ({ setState(prev => ({
...prev, ...prev,
@ -157,7 +173,7 @@ const HedraLipSyncTool: React.FC = () => {
audioFile: prev.audioFile ? { ...prev.audioFile, uploadStatus: 'uploading' } : null audioFile: prev.audioFile ? { ...prev.audioFile, uploadStatus: 'uploading' } : null
})); }));
const audioUrl = await uploadFile(state.audioFile, 'audio'); const audioUrl = await selectAndUploadFile('audio');
setState(prev => ({ setState(prev => ({
...prev, ...prev,
@ -217,7 +233,7 @@ const HedraLipSyncTool: React.FC = () => {
message: error instanceof Error ? error.message : '口型合成任务失败' message: error instanceof Error ? error.message : '口型合成任务失败'
}); });
} }
}, [state.imageFile, state.audioFile, uploadFile, addNotification]); }, [selectAndUploadFile, addNotification]);
// 轮询任务状态 // 轮询任务状态
const pollTaskStatus = useCallback(async (taskId: string) => { const pollTaskStatus = useCallback(async (taskId: string) => {

View File

@ -606,15 +606,7 @@ export class BowongTextVideoAgentFastApiService implements BowongTextVideoAgentA
// ============================================================================ // ============================================================================
async hedraUploadFile(request: HedraFileUploadRequest): Promise<FileUploadResponse> { async hedraUploadFile(request: HedraFileUploadRequest): Promise<FileUploadResponse> {
// 将 File 对象转换为后端期望的格式 return this.invokeAPI<FileUploadResponse>('hedra_upload_file', request);
const fileData = await this.fileToBytes(request.local_file);
const backendRequest = {
file_data: Array.from(fileData), // 转换为数字数组
filename: request.local_file.name,
purpose: request.purpose || 'image'
};
return this.invokeAPI<FileUploadResponse>('hedra_upload_file', backendRequest);
} }
async hedraSubmitTask(request: HedraTaskSubmitRequest): Promise<TaskResponse> { async hedraSubmitTask(request: HedraTaskSubmitRequest): Promise<TaskResponse> {
@ -641,23 +633,7 @@ export class BowongTextVideoAgentFastApiService implements BowongTextVideoAgentA
// 工具方法 // 工具方法
// ============================================================================ // ============================================================================
/**
* File
*/
private async fileToBytes(file: File): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.result instanceof ArrayBuffer) {
resolve(new Uint8Array(reader.result));
} else {
reject(new Error('Failed to read file as ArrayBuffer'));
}
};
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(file);
});
}
/** /**
* *

View File

@ -545,7 +545,7 @@ export interface ComfyUISyncExecuteRequest {
* Hedra * Hedra
*/ */
export interface HedraFileUploadRequest { export interface HedraFileUploadRequest {
local_file: File; file_path: string;
purpose?: 'image' | 'audio' | 'video' | 'voice'; // 默认 'image' purpose?: 'image' | 'audio' | 'video' | 'voice'; // 默认 'image'
} }