新增: 后台会话系统通知与托盘运行态

- 完成/失败/需介入三类系统通知,主窗聚焦静默
- 同类通知五秒合并,用户主动停止不通知
- 托盘菜单与悬浮提示实时显运行中会话数
- 关窗遇运行会话提示后台续跑,每进程一次
- 侧栏常驻徽章显后台运行会话数
This commit is contained in:
lxy
2026-10-01 19:35:20 +08:00
parent 66c6553cdf
commit e1dd0bc3a3
13 changed files with 441 additions and 24 deletions
+23 -1
View File
@@ -28,6 +28,9 @@ impl tracing_subscriber::fmt::time::FormatTime for LocalTimer {
// Phase3 跨端隧道:TunnelClient trait(connect/send_raw_event/disconnect 方法)需在作用域内
use df_tunnel::TunnelClient;
/// 关窗进行中提示哨兵:tray-hide-notice 每进程只发一次(反复关窗不重复弹气泡)。
static CLOSE_NOTICE_SENT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// 初始化文件日志:追加到 %TEMP%/devflow-trace.log,RUST_LOG 控制级别(默认 info)
@@ -56,6 +59,8 @@ pub fn run() {
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
// P1 bg 化:系统通知插件(Rust 侧 notify.rs 条件发送:主窗可见聚焦不发 + 同类节流 + 失败静默)
.plugin(tauri_plugin_notification::init())
// Phase3 单实例:二次启动不新开进程,唤起已有实例主窗口(防多实例抢 MCP 18765 端口)
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
tray::show_main(app);
@@ -509,13 +514,30 @@ pub fn run() {
// 关主窗口不退出,隐藏到托盘常驻(托盘「退出」菜单才真正结束进程)。
// 仅拦 main:approval-popup / ai-detached / fe-detached 等 JS 创建窗口必须放行
// 真正关闭,否则 close 被吞成隐藏僵尸窗,浮窗「正在关闭…」卡死、新审批推给不可见窗口。
// P1 bg 化:有 AI 会话运行中时,首次关窗 emit tray-hide-notice 提示后台仍在跑(每进程一次),
// 异步等 2 秒再 hide(同步 sleep 卡主线程渲染,气泡画不出来);无运行会话保持立即 hide 零回归。
.on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
if window.label() != "main" {
return; // 非主窗口放行真正销毁
}
api.prevent_close(); // 拦截真正关闭
let _ = window.hide(); // 隐藏到托盘
let app = window.app_handle().clone();
let active = app.state::<AppState>().conv_states.active_convs().len();
if active == 0 {
let _ = window.hide(); // 无运行会话:隐藏到托盘(原行为)
return;
}
// 有运行会话:每进程一次给主窗发提示事件(payload 带运行数),前端画气泡
if !CLOSE_NOTICE_SENT.swap(true, std::sync::atomic::Ordering::SeqCst) {
let _ = app.emit_to("main", "tray-hide-notice", serde_json::json!({ "active": active }));
}
// 异步等 2 秒再 hide:气泡可见窗口,禁用 std::thread::sleep(同步阻塞卡渲染)
let win = window.clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let _ = win.hide();
});
}
})
.run(tauri::generate_context!())