feat: 添加 Tauri Mac 原生应用支持

- 新增 mac-app/ 目录:Tauri 项目结构(Rust 外壳 + sidecar 进程管理)
- 前端添加 Tauri 环境检测,API 调用和 WebSocket 自动适配
- 后端支持 MEETING_DATA_DIR 环境变量指定数据目录
- 新增 /api/meetings/{id}/retranscribe 接口用于重新转录
- 修复轮询超时:2分钟→30分钟,适配长音频
- 添加 .gitignore 排除构建产物
This commit is contained in:
freedakgmail
2026-07-10 00:04:27 +08:00
parent 3ab8f22bae
commit 2752ea4e54
73 changed files with 5465 additions and 17 deletions
+4695
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "meeting-recorder"
version = "1.0.0"
description = "智能会议记录系统"
authors = ["freedak"]
edition = "2021"
[lib]
name = "meeting_recorder_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
dirs = "5"
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限以录制会议音频</string>
<key>NSAudioUsageDescription</key>
<string>需要音频权限以处理会议录音</string>
</dict>
</plist>
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
@@ -0,0 +1,13 @@
{
"identifier": "default",
"description": "Default capability for Meeting Recorder",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:allow-close",
"core:window:allow-destroy",
"shell:allow-spawn",
"shell:allow-execute",
"shell:allow-kill"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+11
View File
@@ -0,0 +1,11 @@
# 此目录存放 Tauri 应用图标
# 构建前需要放入以下文件:
# - 32x32.png
# - 128x128.png
# - 128x128@2x.png
# - icon.icns
#
# 可以使用 Tauri 的图标生成工具:
# npx @tauri-apps/cli icon path/to/your/icon.png
#
# 或从 https://tauri.app/v1/guides/build/icons/ 了解更多
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

+89
View File
@@ -0,0 +1,89 @@
use tauri::Manager;
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
use std::sync::Mutex;
use std::path::PathBuf;
struct BackendState {
child: Option<tauri_plugin_shell::process::CommandChild>,
}
fn get_app_data_dir(app: &tauri::AppHandle) -> PathBuf {
let dir = app
.path()
.app_data_dir()
.unwrap_or_else(|_| {
dirs::data_dir().unwrap_or_else(|| PathBuf::from("."))
});
std::fs::create_dir_all(&dir).ok();
dir
}
#[tauri::command]
fn kill_backend(state: tauri::State<'_, Mutex<BackendState>>) {
if let Some(child) = state.lock().unwrap().child.take() {
let _ = child.kill();
}
}
#[tauri::command]
fn get_backend_url() -> String {
"http://localhost:8501".to_string()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.manage(Mutex::new(BackendState { child: None }))
.setup(|app| {
let app_data_dir = get_app_data_dir(&app.handle());
let data_dir_str = app_data_dir.to_string_lossy().to_string();
println!("[tauri] App data dir: {}", data_dir_str);
let sidecar_command = app
.shell()
.sidecar("meeting-backend")
.expect("failed to find meeting-backend sidecar binary")
.env("MEETING_DATA_DIR", &data_dir_str);
let (mut rx, child) = sidecar_command
.spawn()
.expect("failed to spawn meeting-backend sidecar");
let state: tauri::State<'_, Mutex<BackendState>> = app.state();
state.lock().unwrap().child = Some(child);
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => {
println!("[backend] {}", String::from_utf8_lossy(&line))
}
CommandEvent::Stderr(line) => {
eprintln!("[backend] {}", String::from_utf8_lossy(&line))
}
CommandEvent::Terminated(payload) => {
eprintln!("[backend] terminated: {:?}", payload);
break;
}
_ => {}
}
}
});
Ok(())
})
.on_window_event(|window, event| {
if let tauri::WindowEvent::Destroyed = event {
let state: tauri::State<'_, Mutex<BackendState>> = window.state();
if let Some(child) = state.lock().unwrap().child.take() {
let _ = child.kill();
};
}
})
.invoke_handler(tauri::generate_handler![kill_backend, get_backend_url])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
meeting_recorder_lib::run()
}
+44
View File
@@ -0,0 +1,44 @@
{
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json",
"productName": "Meeting Recorder",
"version": "1.0.0",
"identifier": "com.meeting.recorder",
"build": {
"frontendDist": "../../frontend/dist",
"devUrl": "http://localhost:3000",
"beforeDevCommand": "cd ../frontend && npm run dev",
"beforeBuildCommand": "cd ../frontend && npm run build"
},
"app": {
"windows": [
{
"title": "智能会议记录系统",
"width": 1200,
"height": 800,
"minWidth": 900,
"minHeight": 600,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": ["dmg", "app"],
"externalBin": ["binaries/meeting-backend"],
"resources": [],
"macOS": {
"minimumSystemVersion": "12.0",
"infoPlist": "Info.plist"
},
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns"
]
}
}