This commit is contained in:
fengchuanhn@gmail.com
2026-05-18 15:11:59 +08:00
parent 4771397301
commit cdbf605205
25 changed files with 1497 additions and 112 deletions

View File

@@ -0,0 +1,34 @@
//! 工作流生成语音历史SQLite
use tauri::State;
use crate::audio_db::GeneratedAudioRecord;
use crate::audio_store::AudioStore;
#[tauri::command]
pub async fn list_generated_audios(
store: State<'_, AudioStore>,
) -> Result<Vec<GeneratedAudioRecord>, String> {
store.list().await
}
#[tauri::command]
pub async fn insert_generated_audio(
store: State<'_, AudioStore>,
name: String,
file_path: String,
) -> Result<GeneratedAudioRecord, String> {
store.insert(name, file_path).await
}
#[tauri::command]
pub async fn delete_generated_audio(
store: State<'_, AudioStore>,
id: i64,
) -> Result<(), String> {
let deleted = store.delete(id).await?;
if !deleted {
return Err("记录不存在".into());
}
Ok(())
}

View File

@@ -0,0 +1,130 @@
//! 数字人形象SQLite 元数据 + 视频文件管理
use std::path::{Component, Path, PathBuf};
use tauri::{AppHandle, Manager, State};
use uuid::Uuid;
use crate::avatar_db::AvatarRecord;
use crate::avatar_store::AvatarStore;
fn normalize_path(path: &str) -> PathBuf {
Path::new(path)
.components()
.filter(|c| !matches!(c, Component::ParentDir))
.collect()
}
fn avatars_dir(app: &AppHandle) -> Result<PathBuf, String> {
let dir = app
.path()
.app_data_dir()
.map_err(|e| format!("无法获取应用数据目录: {e}"))?
.join("avatars");
Ok(dir)
}
fn is_under_avatars_dir(avatars_root: &Path, target: &Path) -> bool {
if let (Ok(root), Ok(canonical)) = (avatars_root.canonicalize(), target.canonicalize()) {
return canonical.starts_with(&root);
}
let root_s = avatars_root.to_string_lossy().to_lowercase();
let target_s = target.to_string_lossy().to_lowercase();
target_s.contains("avatars") && target_s.starts_with(&root_s)
}
#[tauri::command]
pub async fn list_avatars(store: State<'_, AvatarStore>) -> Result<Vec<AvatarRecord>, String> {
store.list().await
}
#[tauri::command]
pub async fn insert_avatar(
store: State<'_, AvatarStore>,
name: String,
file_path: String,
) -> Result<AvatarRecord, String> {
store.insert(name, file_path).await
}
#[tauri::command]
pub async fn update_avatar_name(
store: State<'_, AvatarStore>,
id: i64,
name: String,
) -> Result<(), String> {
store.update_name(id, name).await
}
#[tauri::command]
pub async fn avatar_name_exists(
store: State<'_, AvatarStore>,
name: String,
exclude_id: Option<i64>,
) -> Result<bool, String> {
store.name_exists(&name, exclude_id).await
}
/// 删除数据库记录并删除应用目录内的视频文件
#[tauri::command]
pub async fn delete_avatar(
app: AppHandle,
store: State<'_, AvatarStore>,
id: i64,
) -> Result<(), String> {
let file_path = store.delete(id).await?;
if let Some(path) = file_path {
delete_avatar_file_if_allowed(&app, &path).await?;
}
Ok(())
}
/// 将用户选择的视频复制到应用数据目录,返回保存后的绝对路径
#[tauri::command]
pub async fn import_avatar_video(
app: AppHandle,
source_path: String,
) -> Result<String, String> {
let src = normalize_path(source_path.trim());
if !src.is_file() {
return Err(format!("视频文件不存在: {}", src.display()));
}
let ext = src
.extension()
.and_then(|e| e.to_str())
.unwrap_or("mp4")
.to_lowercase();
let allowed = ["mp4", "mov", "webm", "mkv", "m4v", "avi"];
if !allowed.contains(&ext.as_str()) {
return Err("仅支持 mp4、mov、webm、mkv 等常见视频格式".into());
}
let dir = avatars_dir(&app)?;
tokio::fs::create_dir_all(&dir)
.await
.map_err(|e| format!("创建形象目录失败: {e}"))?;
let file_name = format!("{}.{}", Uuid::new_v4(), ext);
let dest = dir.join(&file_name);
tokio::fs::copy(&src, &dest)
.await
.map_err(|e| format!("复制视频失败: {e}"))?;
Ok(dest.to_string_lossy().to_string())
}
async fn delete_avatar_file_if_allowed(app: &AppHandle, path: &str) -> Result<(), String> {
let target = normalize_path(path);
let dir = avatars_dir(app)?;
if !is_under_avatars_dir(&dir, &target) {
return Err("不允许删除该路径下的文件".into());
}
if target.is_file() {
tokio::fs::remove_file(&target)
.await
.map_err(|e| format!("删除文件失败: {e}"))?;
}
Ok(())
}

View File

@@ -1,6 +1,8 @@
pub mod api;
pub mod app_config;
pub mod auth;
pub mod audio;
pub mod avatar;
pub mod fs_util;
pub mod nodejs;
pub mod quickjs;