From 00624d81cf8b5eb41e3e57485c239d377188889a Mon Sep 17 00:00:00 2001 From: Xianren Studio Date: Thu, 13 Aug 2026 23:31:06 +0800 Subject: [PATCH] fix: rowid column shift in get_message_with_rowid; add file logging and frontend error reporting --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 1 + apps/desktop/Cargo.toml | 2 ++ apps/desktop/src/commands.rs | 9 +++++++++ apps/desktop/src/lib.rs | 10 ++++++++++ crates/core/src/sessions.rs | 4 ++-- ui/src/api.ts | 20 +++++++++++++++++++- ui/src/pages/SettingsPage.tsx | 14 +++++++++++++- 8 files changed, 77 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f8a1b9..8f860a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3743,6 +3743,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -4471,6 +4477,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.20", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -5522,6 +5541,7 @@ dependencies = [ name = "xianren-desktop" version = "0.1.0" dependencies = [ + "dirs 5.0.1", "futures", "reqwest 0.12.28", "serde", @@ -5531,6 +5551,7 @@ dependencies = [ "tauri-plugin-opener", "tokio", "tracing", + "tracing-appender", "tracing-subscriber", "uuid", "xianren-api", diff --git a/Cargo.toml b/Cargo.toml index 2389b12..8c69ca6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ serde_json = "1" tokio = { version = "1", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-appender = "0.2" thiserror = "2" anyhow = "1" reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls"] } diff --git a/apps/desktop/Cargo.toml b/apps/desktop/Cargo.toml index a5f5862..1c5f3ad 100644 --- a/apps/desktop/Cargo.toml +++ b/apps/desktop/Cargo.toml @@ -21,12 +21,14 @@ tauri = { version = "2", features = [] } tauri-plugin-opener = "2" tracing-subscriber.workspace = true tracing.workspace = true +tracing-appender.workspace = true serde.workspace = true serde_json.workspace = true tokio.workspace = true reqwest.workspace = true futures.workspace = true uuid.workspace = true +dirs.workspace = true xianren-core = { path = "../../crates/core" } xianren-engine = { path = "../../crates/engine" } xianren-download = { path = "../../crates/download" } diff --git a/apps/desktop/src/commands.rs b/apps/desktop/src/commands.rs index 2c184dd..3859ac0 100644 --- a/apps/desktop/src/commands.rs +++ b/apps/desktop/src/commands.rs @@ -19,6 +19,7 @@ pub struct AppInfo { pub version: String, pub platform: String, pub models_dir: String, + pub logs_dir: String, pub engine_bin: Option, pub engine_exists: bool, pub db_path: String, @@ -133,6 +134,7 @@ pub fn app_info(state: State<'_, App>) -> AppInfo { version: env!("CARGO_PKG_VERSION").to_string(), platform: std::env::consts::OS.to_string(), models_dir: state.core.models_dir.to_string_lossy().to_string(), + logs_dir: state.core.logs_dir.to_string_lossy().to_string(), engine_exists: engine_bin .as_ref() .is_some_and(|p| PathBuf::from(p).exists()), @@ -1416,3 +1418,10 @@ pub async fn server_status(state: State<'_, App>) -> Result Result<(), String> { tauri_plugin_opener::open_path(path, None::<&str>).map_err(|e| e.to_string()) } + +/// 前端上报运行时错误:写入本地日志便于排查。 +#[tauri::command] +pub fn report_error(source: String, message: String) -> Result<(), String> { + tracing::error!(source = %source, error = %message, "frontend reported error"); + Ok(()) +} diff --git a/apps/desktop/src/lib.rs b/apps/desktop/src/lib.rs index 2d65179..7402083 100644 --- a/apps/desktop/src/lib.rs +++ b/apps/desktop/src/lib.rs @@ -17,11 +17,20 @@ pub struct App { } pub fn run() { + let data_dir = dirs::data_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("XianrenStudio"); + let logs_dir = data_dir.join("logs"); + let _ = std::fs::create_dir_all(&logs_dir); + let file_appender = tracing_appender::rolling::daily(&logs_dir, "app.log"); + let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "info".into()), ) + .with_writer(non_blocking) + .with_ansi(false) .init(); tauri::Builder::default() @@ -115,6 +124,7 @@ pub fn run() { commands::edit_message, commands::list_message_versions, commands::apply_message_version, + commands::report_error, commands::download_enqueue, commands::server_start, commands::server_stop, diff --git a/crates/core/src/sessions.rs b/crates/core/src/sessions.rs index 1a2d75f..f8b92a5 100644 --- a/crates/core/src/sessions.rs +++ b/crates/core/src/sessions.rs @@ -148,11 +148,11 @@ pub fn get_message(db: &Connection, id: &str) -> Result> { pub fn get_message_with_rowid(db: &Connection, id: &str) -> Result> { let mut stmt = db.prepare( - "SELECT rowid, id, conversation_id, role, content, tokens_in, tokens_out, elapsed_ms, first_token_ms, images_json, created_at + "SELECT id, conversation_id, role, content, tokens_in, tokens_out, elapsed_ms, first_token_ms, images_json, created_at, rowid FROM messages WHERE id = ?1", )?; let mut rows = stmt.query_map(params![id], |row| { - let rowid: i64 = row.get(0)?; + let rowid: i64 = row.get(10)?; let msg = row_to_message(row)?; Ok((rowid, msg)) })?; diff --git a/ui/src/api.ts b/ui/src/api.ts index a1712b6..79ceabe 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -1,10 +1,28 @@ -import { invoke } from "@tauri-apps/api/core"; +import { invoke as rawInvoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; +/** 统一命令包装:失败时自动上报错误到本地日志,再抛出给界面。 */ +async function invoke(command: string, args?: Record): Promise { + try { + return await rawInvoke(command, args); + } catch (error) { + try { + await rawInvoke("report_error", { + source: command, + message: String(error), + }); + } catch { + // 上报失败不影响主流程 + } + throw error; + } +} + export interface AppInfo { version: string; platform: string; models_dir: string; + logs_dir: string; engine_bin: string | null; engine_exists: boolean; db_path: string; diff --git a/ui/src/pages/SettingsPage.tsx b/ui/src/pages/SettingsPage.tsx index 186eb2f..fe466fc 100644 --- a/ui/src/pages/SettingsPage.tsx +++ b/ui/src/pages/SettingsPage.tsx @@ -30,6 +30,19 @@ export default function SettingsPage() {
数据目录:{info?.db_path}
+
+ + 日志目录:{info?.logs_dir} + + {info?.logs_dir ? ( + + ) : null} +
引擎可用: {info?.engine_exists ? ( @@ -103,4 +116,3 @@ function SettingInput({ ); } -