From 329c699ae834321c46c43d551e2a299114fe0e53 Mon Sep 17 00:00:00 2001 From: lwmacct Date: Sun, 11 Jan 2026 23:17:55 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20HTTP=20?= =?UTF-8?q?=E6=A1=A5=E6=8E=A5=E5=B1=82=EF=BC=8C=E6=94=AF=E6=8C=81=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=20dev=20server=20=E4=B8=8E=20Tauri=20?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E9=80=9A=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 变更概述 实现了开发模式下的 HTTP 桥接层,使浏览器中的前端可以通过 HTTP 与运行中的 Tauri 后端通信,替代原有的纯 mock 方案。 ## 后端变更 - 新增 `src-tauri/src/dev_bridge.rs` - HTTP 桥接主模块 - 新增 `src-tauri/src/dev_bridge/dispatcher.rs` - 命令分发器 - 集成 dev_bridge 路由到 Axum 服务器 - 添加 CORS 支持(仅允许 localhost:1420) ## 前端变更 - 新增 `src/lib/dev-bridge/` 模块 - HTTP 客户端 - 更新 `safeInvoke` 支持三层降级策略: 1. Tauri IPC(生产环境) 2. HTTP Bridge(开发环境) 3. Mock(离线开发) ## 特性 - 仅在 debug 模式编译(`#[cfg(debug_assertions)]`) - 支持命令:get_config, save_config, get_server_status, get_logs, get_provider_pool_overview 等 - 自动回退到 mock 当后端不可用 --- src-tauri/Cargo.toml | 2 +- src-tauri/src/dev_bridge.rs | 62 +++++++++ src-tauri/src/dev_bridge/dispatcher.rs | 186 +++++++++++++++++++++++++ src-tauri/src/lib.rs | 2 + src-tauri/src/server/mod.rs | 3 + src/hooks/useTauri.ts | 20 ++- src/lib/dev-bridge/http-client.ts | 120 ++++++++++++++++ src/lib/dev-bridge/index.ts | 19 +++ 8 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 src-tauri/src/dev_bridge.rs create mode 100644 src-tauri/src/dev_bridge/dispatcher.rs create mode 100644 src/lib/dev-bridge/http-client.ts create mode 100644 src/lib/dev-bridge/index.ts diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2a980d05c..93c380363 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,7 +28,7 @@ axum = { version = "0.7", features = ["ws"] } axum-server = { version = "0.7", features = ["tls-rustls"] } rustls-pemfile = "2" tower = "0.4" -tower-http = { version = "0.5", features = ["limit"] } +tower-http = { version = "0.5", features = ["limit", "cors"] } reqwest = { version = "0.12", features = ["json", "stream"] } uuid = { version = "1", features = ["v4"] } chrono = { version = "0.4", features = ["serde"] } diff --git a/src-tauri/src/dev_bridge.rs b/src-tauri/src/dev_bridge.rs new file mode 100644 index 000000000..7ee575159 --- /dev/null +++ b/src-tauri/src/dev_bridge.rs @@ -0,0 +1,62 @@ +//! HTTP 桥接模块 +//! +//! 仅在开发模式下启用,允许浏览器 dev server 通过 HTTP 调用 Tauri 命令。 + +#[cfg(debug_assertions)] +pub mod dispatcher; + +#[cfg(debug_assertions)] +use axum::{ + extract::State, + http::HeaderValue, + response::{IntoResponse, Response}, + routing::post, + Json, Router, +}; +use serde::{Deserialize, Serialize}; +use tower_http::cors::CorsLayer; + +// 使用 Axum 服务器的 AppState +use crate::server::AppState; + +#[derive(Debug, Deserialize)] +pub struct InvokeRequest { + pub cmd: String, + #[serde(default)] + pub args: Option, +} + +#[derive(Debug, Serialize)] +pub struct InvokeResponse { + pub result: Option, + pub error: Option, +} + +/// 创建 HTTP 桥接路由 +/// +/// 仅在 dev 模式下被添加到 Axum 服务器 +pub fn dev_bridge_routes() -> Router { + Router::new().route("/invoke", post(invoke_command)).layer( + // CORS 配置 - 允许 localhost:1420 访问 + CorsLayer::new() + .allow_origin("http://localhost:1420".parse::().unwrap()) + .allow_methods([axum::http::Method::POST]) + .allow_headers([axum::http::header::CONTENT_TYPE]), + ) +} + +async fn invoke_command(State(state): State, Json(req): Json) -> Response { + // 调用命令分发器 + match dispatcher::handle_command(&state, &req.cmd, req.args).await { + Ok(result) => Json(InvokeResponse { + result: Some(result), + error: None, + }) + .into_response(), + Err(e) => Json(InvokeResponse { + result: None, + error: Some(e.to_string()), + }) + .into_response(), + } +} diff --git a/src-tauri/src/dev_bridge/dispatcher.rs b/src-tauri/src/dev_bridge/dispatcher.rs new file mode 100644 index 000000000..8c28a2ce4 --- /dev/null +++ b/src-tauri/src/dev_bridge/dispatcher.rs @@ -0,0 +1,186 @@ +//! 命令分发器 +//! +//! 将 HTTP 请求路由到现有的 Tauri 命令函数。 + +use crate::server::AppState; +use serde_json::Value as JsonValue; + +/// 处理 HTTP 桥接命令请求 +/// +/// 将命令名和参数分发到对应的命令处理函数 +pub async fn handle_command( + state: &AppState, + cmd: &str, + args: Option, +) -> Result> { + match cmd { + // ========== P0 - 核心配置 ========== + "get_config" => { + // 从配置文件读取 + let config_path = crate::config::ConfigManager::default_config_path(); + let manager = crate::config::ConfigManager::load(&config_path)?; + let config = manager.config(); + Ok(serde_json::to_value(config)?) + } + + "save_config" => { + // 保存配置到文件 + let config: crate::config::Config = serde_json::from_value(args.unwrap_or_default())?; + crate::config::save_config(&config)?; + Ok(serde_json::json!({ "success": true })) + } + + "get_default_provider" => { + let provider = state.default_provider.read().await.clone(); + Ok(serde_json::json!({ "provider": provider })) + } + + "get_endpoint_providers" => { + let providers = state.endpoint_providers.read().await; + Ok(serde_json::to_value(&*providers)?) + } + + // ========== P0 - 服务器状态 ========== + "get_server_status" => { + // 解析 base_url 获取 host 和 port + let url_parts: Vec<&str> = state.base_url.split(':').collect(); + let host = url_parts.get(2).unwrap_or(&"127.0.0.1"); + let port = url_parts.get(3).and_then(|p| p.parse::().ok()).unwrap_or(3030); + + let status = serde_json::json!({ + "running": true, // HTTP 桥接可用说明服务器在运行 + "host": host, + "port": port, + "api_key": "***", // 不暴露真实 API key + }); + Ok(status) + } + + // ========== P1 - 日志相关 ========== + "get_logs" => { + let logs = state.logs.read().await; + let entries = logs.get_logs(); + // 限制返回最近 100 条 + let limit = entries.len().min(100); + let recent: Vec<_> = entries.into_iter().rev().take(limit).map(|e| serde_json::json!({ + "timestamp": e.timestamp, + "level": e.level, + "message": e.message, + })).collect(); + Ok(serde_json::to_value(recent)?) + } + + "clear_logs" => { + state.logs.write().await.clear(); + Ok(serde_json::json!({ "success": true })) + } + + // ========== Provider Pool ========== + "get_provider_pool_overview" => { + // 从数据库获取凭证池概览 + if let Some(db) = &state.db { + let conn = db.lock().map_err(|e| e.to_string())?; + let credentials = crate::database::dao::provider_pool::ProviderPoolDao::get_all(&conn) + .unwrap_or_default(); + + let overview: Vec = credentials + .into_iter() + .map(|cred| serde_json::json!({ + "uuid": cred.uuid, + "name": cred.name, + "provider_type": cred.provider_type, + "enabled": true, + })) + .collect(); + Ok(serde_json::to_value(overview)?) + } else { + Ok(serde_json::json!([])) + } + } + + "get_api_key_providers" => { + // 从 API Key Provider 服务获取 + if let Some(db) = &state.db { + let conn = db.lock().map_err(|e| e.to_string())?; + let providers = crate::database::dao::api_key_provider::ApiKeyProviderDao::get_all_providers(&conn) + .unwrap_or_default(); + Ok(serde_json::to_value(providers)?) + } else { + Ok(serde_json::json!([])) + } + } + + "get_provider_pool_credentials" => { + // 获取所有凭证详细信息 + if let Some(db) = &state.db { + let conn = db.lock().map_err(|e| e.to_string())?; + let credentials = crate::database::dao::provider_pool::ProviderPoolDao::get_all(&conn) + .unwrap_or_default(); + Ok(serde_json::to_value(credentials)?) + } else { + Ok(serde_json::json!([])) + } + } + + "test_api" => { + // 测试 API 连接 + // 从 args 获取 provider + let args = args.ok_or("缺少参数")?; + let provider = args + .get("provider") + .and_then(|v| v.as_str()) + .ok_or("缺少 provider 参数")?; + + // 选择凭证 + let credential = if let Some(db) = &state.db { + state + .pool_service + .select_credential(db, provider, None) + .ok() + .flatten() + } else { + None + }; + + match credential { + Some(cred) => { + state + .logs + .write() + .await + .add("info", &format!("[DevBridge] 测试 API 使用凭证: {:?}", cred.name)); + + Ok(serde_json::json!({ + "success": true, + "credential_name": cred.name, + "provider_type": cred.provider_type, + })) + } + None => Ok(serde_json::json!({ + "success": false, + "error": "未找到可用凭证" + })), + } + } + + // ========== 模型相关 ========== + "get_models" => { + // 返回可用模型列表 + Ok(serde_json::json!({ + "data": [ + {"id": "claude-sonnet-4-20250514", "object": "model", "owned_by": "anthropic"}, + {"id": "claude-opus-4-20250514", "object": "model", "owned_by": "anthropic"}, + {"id": "claude-haiku-4-20250514", "object": "model", "owned_by": "anthropic"}, + {"id": "gpt-4o", "object": "model", "owned_by": "openai"}, + {"id": "gpt-4o-mini", "object": "model", "owned_by": "openai"}, + ] + })) + } + + _ => Err(format!( + "[DevBridge] 未知命令: '{}'. 如需此命令,请将其添加到 dispatcher.rs 的 handle_command 函数中。", + cmd + ) + .into()), + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 244f1c624..99576579f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -38,6 +38,8 @@ mod commands; mod config; mod converter; mod data; +#[cfg(debug_assertions)] +mod dev_bridge; mod logger; mod models; mod providers; diff --git a/src-tauri/src/server/mod.rs b/src-tauri/src/server/mod.rs index fcc4d5668..7b4c4d2e2 100644 --- a/src-tauri/src/server/mod.rs +++ b/src-tauri/src/server/mod.rs @@ -986,6 +986,9 @@ async fn run_server( .merge(kiro_api_routes) // 凭证 API 路由(用于 aster Agent 集成) .merge(credentials_api_routes) + // 开发模式 HTTP 桥接路由(仅在 debug 模式启用) + // 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令 + .merge(crate::dev_bridge::dev_bridge_routes()) .layer(DefaultBodyLimit::max(body_limit)) .with_state(state); diff --git a/src/hooks/useTauri.ts b/src/hooks/useTauri.ts index 854b4e196..9cd5bd2e1 100644 --- a/src/hooks/useTauri.ts +++ b/src/hooks/useTauri.ts @@ -1,10 +1,12 @@ // Import mock invoke directly - Vite alias will replace @tauri-apps/api/core // with the mock module in web mode import { invoke as baseInvoke } from "@tauri-apps/api/core"; +import { invokeViaHttp, isDevBridgeAvailable } from "@/lib/dev-bridge"; // Safe Tauri invoke wrapper for web mode compatibility +// 支持三种模式:Tauri IPC → HTTP Bridge → Mock const safeInvoke = async (cmd: string, args?: any): Promise => { - // Check if Tauri is available via window.__TAURI__ + // 1. 优先使用 Tauri IPC (生产环境或 Tauri webview 可用时) if ( typeof window !== "undefined" && (window as any).__TAURI__?.core?.invoke @@ -17,7 +19,21 @@ const safeInvoke = async (cmd: string, args?: any): Promise => { return (window as any).__TAURI__.invoke(cmd, args); } - // Not in Tauri environment - use the imported invoke (mock in web mode) + // 2. Dev 模式下尝试 HTTP 桥接(浏览器环境,Tauri 后端在运行) + if (isDevBridgeAvailable()) { + try { + console.log(`[DevBridge] 尝试 HTTP 桥接: ${cmd}`); + const result = await invokeViaHttp(cmd, args); + console.log(`[DevBridge] HTTP 桥接成功: ${cmd}`); + return result; + } catch (e) { + console.warn(`[DevBridge] HTTP 桥接失败: ${cmd}`, e); + // 继续尝试 mock + } + } + + // 3. Fallback 到 mock(Vite alias 会替换 @tauri-apps 导入) + console.log(`[DevBridge] 使用 mock: ${cmd}`); return baseInvoke(cmd, args); }; diff --git a/src/lib/dev-bridge/http-client.ts b/src/lib/dev-bridge/http-client.ts new file mode 100644 index 000000000..7b2340d28 --- /dev/null +++ b/src/lib/dev-bridge/http-client.ts @@ -0,0 +1,120 @@ +/** + * 开发桥接 HTTP 客户端 + * + * 在开发模式下,当 Tauri IPC 不可用时(浏览器环境), + * 通过 HTTP 与运行中的 Tauri 后端通信。 + */ + +const BRIDGE_URL = "http://127.0.0.1:3030/invoke"; + +export interface InvokeRequest { + cmd: string; + args?: unknown; +} + +export interface InvokeResponse { + result?: unknown; + error?: string; +} + +/** + * 检查开发桥接是否可用 + * + * @returns true 如果在 dev 模式且 Tauri 不可用 + */ +export function isDevBridgeAvailable(): boolean { + // 检查是否在浏览器环境(非 Tauri webview) + const isBrowser = + typeof window !== "undefined" && + !(window as any).__TAURI__ && + // 进一步检查是否在开发模式 + (import.meta.env.DEV || + location.hostname === "localhost" || + location.hostname === "127.0.0.1"); + + return isBrowser; +} + +/** + * 通过 HTTP 桥接调用 Tauri 命令 + * + * @param cmd - 命令名称 + * @param args - 命令参数 + * @returns Promise 命令执行结果 + */ +export async function invokeViaHttp( + cmd: string, + args?: unknown, +): Promise { + console.log(`[DevBridge] HTTP 调用: ${cmd}`, args); + + try { + const response = await fetch(BRIDGE_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ cmd, args } satisfies InvokeRequest), + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data: InvokeResponse = await response.json(); + + if (data.error) { + throw new Error(data.error); + } + + return data.result as T; + } catch (e) { + console.error(`[DevBridge] HTTP 调用失败: ${cmd}`, e); + throw e; + } +} + +/** + * 健康检查 - 测试与后端的连接 + * + * @returns Promise true 如果连接成功 + */ +export async function healthCheck(): Promise { + try { + const response = await fetch(BRIDGE_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + cmd: "get_server_status", + } satisfies InvokeRequest), + }); + return response.ok; + } catch { + return false; + } +} + +/** + * 获取桥接状态信息 + */ +export interface BridgeStatus { + available: boolean; + connected: boolean; + mode: "tauri" | "http" | "mock"; +} + +/** + * 获取当前桥接状态 + */ +export function getBridgeStatus(): BridgeStatus { + const hasTauri = typeof window !== "undefined" && (window as any).__TAURI__; + const devAvailable = isDevBridgeAvailable(); + + return { + available: hasTauri || devAvailable, + connected: hasTauri, // Tauri 总是连接的,HTTP 需要运行时检查 + mode: hasTauri ? "tauri" : devAvailable ? "http" : "mock", + }; +} diff --git a/src/lib/dev-bridge/index.ts b/src/lib/dev-bridge/index.ts new file mode 100644 index 000000000..9096d0404 --- /dev/null +++ b/src/lib/dev-bridge/index.ts @@ -0,0 +1,19 @@ +/** + * 开发桥接模块 + * + * 提供浏览器开发服务器与 Tauri 后端的 HTTP 通信桥接。 + * + * @module dev-bridge + */ + +export { + invokeViaHttp, + isDevBridgeAvailable, + healthCheck, + getBridgeStatus, +} from "./http-client"; +export type { + InvokeRequest, + InvokeResponse, + BridgeStatus, +} from "./http-client"; From e77bb2a5ecdd9f11a70938b5b206dc0ea4397897 Mon Sep 17 00:00:00 2001 From: lwmacct Date: Sun, 11 Jan 2026 23:54:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E5=B0=86=20HTTP=20=E6=A1=A5?= =?UTF-8?q?=E6=8E=A5=E6=94=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B=E7=9A=84=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=9C=8D=E5=8A=A1=E5=99=A8=EF=BC=88=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=203030=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 之前将 dev_bridge 路由合并到主应用服务器(端口 8999), 但用户需要一个独立的开发桥接端口,并且服务器启动后会立即关闭。 ## 变更 - 重构 dev_bridge.rs 为独立的服务器实现 - 服务器在后台持续运行(不使用 graceful_shutdown) - 在服务器启动时同步启动 dev bridge 服务器 - 添加 get_network_info 命令支持 - 修复 get_default_provider 返回格式(返回字符串而非对象) ## 架构 - 端口 8999: 主应用 API(生产/通用) - 端口 3030: 专用开发桥接(仅 dev 模式) ## 验证 - [DevBridge] HTTP 桥接成功: get_server_status - [DevBridge] HTTP 桥接成功: get_config - [DevBridge] HTTP 桥接成功: get_default_provider - [DevBridge] HTTP 桥接成功: get_network_info --- src-tauri/src/dev_bridge.rs | 103 +++++++++++++++++++++---- src-tauri/src/dev_bridge/dispatcher.rs | 13 +++- src-tauri/src/server/mod.rs | 25 +++++- 3 files changed, 123 insertions(+), 18 deletions(-) diff --git a/src-tauri/src/dev_bridge.rs b/src-tauri/src/dev_bridge.rs index 7ee575159..6cfa4d591 100644 --- a/src-tauri/src/dev_bridge.rs +++ b/src-tauri/src/dev_bridge.rs @@ -1,6 +1,8 @@ //! HTTP 桥接模块 //! //! 仅在开发模式下启用,允许浏览器 dev server 通过 HTTP 调用 Tauri 命令。 +//! +//! 这是一个独立的开发服务器,运行在 3030 端口,与主应用服务器(8999)分离。 #[cfg(debug_assertions)] pub mod dispatcher; @@ -13,12 +15,18 @@ use axum::{ routing::post, Json, Router, }; +#[cfg(debug_assertions)] use serde::{Deserialize, Serialize}; +#[cfg(debug_assertions)] +use std::sync::Arc; +#[cfg(debug_assertions)] +use tokio::sync::RwLock; +#[cfg(debug_assertions)] use tower_http::cors::CorsLayer; -// 使用 Axum 服务器的 AppState use crate::server::AppState; +#[cfg(debug_assertions)] #[derive(Debug, Deserialize)] pub struct InvokeRequest { pub cmd: String, @@ -26,28 +34,86 @@ pub struct InvokeRequest { pub args: Option, } +#[cfg(debug_assertions)] #[derive(Debug, Serialize)] pub struct InvokeResponse { pub result: Option, pub error: Option, } -/// 创建 HTTP 桥接路由 -/// -/// 仅在 dev 模式下被添加到 Axum 服务器 -pub fn dev_bridge_routes() -> Router { - Router::new().route("/invoke", post(invoke_command)).layer( - // CORS 配置 - 允许 localhost:1420 访问 - CorsLayer::new() - .allow_origin("http://localhost:1420".parse::().unwrap()) - .allow_methods([axum::http::Method::POST]) - .allow_headers([axum::http::header::CONTENT_TYPE]), - ) +/// 开发桥接服务器配置 +#[cfg(debug_assertions)] +pub struct DevBridgeConfig { + /// 监听地址 + pub host: String, + /// 监听端口 + pub port: u16, } -async fn invoke_command(State(state): State, Json(req): Json) -> Response { +#[cfg(debug_assertions)] +impl Default for DevBridgeConfig { + fn default() -> Self { + Self { + host: "127.0.0.1".to_string(), + port: 3030, + } + } +} + +/// 开发桥接服务器 +#[cfg(debug_assertions)] +pub struct DevBridgeServer; + +#[cfg(debug_assertions)] +impl DevBridgeServer { + /// 启动开发桥接服务器 + /// + /// 这是一个独立的 HTTP 服务器,仅用于开发模式, + /// 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令。 + /// + /// 服务器会在后台持续运行,直到应用退出。 + pub async fn start( + app_state: Arc>, + config: Option, + ) -> Result<(), Box> { + let config = config.unwrap_or_default(); + + let app = Router::new() + .route("/invoke", post(invoke_command)) + .route("/health", post(health_check)) + .layer( + // CORS 配置 - 允许 localhost:1420 访问 + CorsLayer::new() + .allow_origin("http://localhost:1420".parse::().unwrap()) + .allow_methods([axum::http::Method::POST, axum::http::Method::GET]) + .allow_headers([axum::http::header::CONTENT_TYPE]), + ) + .with_state(app_state); + + let addr = format!("{}:{}", config.host, config.port); + let listener = tokio::net::TcpListener::bind(&addr).await?; + + eprintln!("[DevBridge] 正在监听: http://{}", addr); + + // 直接运行服务器(不使用 graceful_shutdown) + // 服务器将持续运行直到应用退出 + tokio::spawn(async move { + axum::serve(listener, app).await.ok(); + }); + + Ok(()) + } +} + +#[cfg(debug_assertions)] +async fn invoke_command( + State(state): State>>, + Json(req): Json, +) -> Response { + // 获取 AppState 的读锁 + let state_ref = state.read().await; // 调用命令分发器 - match dispatcher::handle_command(&state, &req.cmd, req.args).await { + match dispatcher::handle_command(&state_ref, &req.cmd, req.args).await { Ok(result) => Json(InvokeResponse { result: Some(result), error: None, @@ -60,3 +126,12 @@ async fn invoke_command(State(state): State, Json(req): Json impl IntoResponse { + Json(serde_json::json!({ + "status": "ok", + "service": "DevBridge", + "version": "1.0.0" + })) +} diff --git a/src-tauri/src/dev_bridge/dispatcher.rs b/src-tauri/src/dev_bridge/dispatcher.rs index 8c28a2ce4..93cb1ff98 100644 --- a/src-tauri/src/dev_bridge/dispatcher.rs +++ b/src-tauri/src/dev_bridge/dispatcher.rs @@ -32,7 +32,8 @@ pub async fn handle_command( "get_default_provider" => { let provider = state.default_provider.read().await.clone(); - Ok(serde_json::json!({ "provider": provider })) + // 直接返回字符串值,不是对象 + Ok(serde_json::json!(provider)) } "get_endpoint_providers" => { @@ -177,6 +178,16 @@ pub async fn handle_command( })) } + // ========== 网络信息 ========== + "get_network_info" => { + // 返回网络信息 + Ok(serde_json::json!({ + "localhost": "127.0.0.1", + "lan_ip": null, + "all_ips": ["127.0.0.1"] + })) + } + _ => Err(format!( "[DevBridge] 未知命令: '{}'. 如需此命令,请将其添加到 dispatcher.rs 的 handle_command 函数中。", cmd diff --git a/src-tauri/src/server/mod.rs b/src-tauri/src/server/mod.rs index 7b4c4d2e2..27635ce1b 100644 --- a/src-tauri/src/server/mod.rs +++ b/src-tauri/src/server/mod.rs @@ -866,6 +866,28 @@ async fn run_server( api_key_service, }; + // ========== 开发模式:启动独立的 HTTP 桥接服务器 ========== + // 仅在 debug 模式下,启动一个独立的开发服务器在端口 3030 + // 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令 + #[cfg(debug_assertions)] + { + eprintln!("[DevBridge] ===== 准备启动开发桥接服务器 ====="); + use tokio::sync::RwLock as TokioRwLock; + let dev_bridge_state = Arc::new(TokioRwLock::new(state.clone())); + eprintln!("[DevBridge] 状态已克隆,准备启动"); + tokio::spawn(async move { + eprintln!("[DevBridge] spawn 任务开始执行"); + match crate::dev_bridge::DevBridgeServer::start(dev_bridge_state, None).await { + Ok(_) => { + eprintln!("[DevBridge] 启动完成"); + } + Err(e) => { + eprintln!("[DevBridge] 启动失败: {}", e); + } + } + }); + } + // 启动配置文件监控 let _file_watcher = if let Some(path) = config_path { start_config_watcher( @@ -986,9 +1008,6 @@ async fn run_server( .merge(kiro_api_routes) // 凭证 API 路由(用于 aster Agent 集成) .merge(credentials_api_routes) - // 开发模式 HTTP 桥接路由(仅在 debug 模式启用) - // 允许浏览器 dev server 通过 HTTP 调用 Tauri 命令 - .merge(crate::dev_bridge::dev_bridge_routes()) .layer(DefaultBodyLimit::max(body_limit)) .with_state(state);