refactor: 重构 Hedra 文件上传方式,使用文件路径而非文件内容传输
- 修改 HedraFileUploadRequest 使用 file_path 而不是 file_data - 添加 HedraFileUploadApiRequest 用于后端到 API 的请求 - 更新后端服务读取文件并转换为 API 请求格式 - 重构前端文件选择,使用 @tauri-apps/api/dialog 直接选择文件路径 - 移除复杂的文件转换和临时文件创建逻辑 - 简化文件上传流程,避免大文件在前后端间传输 这种方式更适合 Tauri 架构,避免了大文件传输的性能问题
This commit is contained in:
parent
538254ee38
commit
5fdf3c5a4b
|
|
@ -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 任务提交请求
|
||||||
|
|
|
||||||
|
|
@ -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 提交任务
|
||||||
|
|
|
||||||
|
|
@ -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> => {
|
||||||
const request: HedraFileUploadRequest = {
|
|
||||||
local_file: fileInfo.file,
|
|
||||||
purpose
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
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 = {
|
||||||
|
file_path: selected,
|
||||||
|
purpose
|
||||||
|
};
|
||||||
|
|
||||||
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) => {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取服务配置
|
* 获取服务配置
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue