From 9c59f9b2b13200579470a12746af69371bb0ea8a Mon Sep 17 00:00:00 2001 From: lwmacct Date: Sun, 11 Jan 2026 22:40:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Vite=20web=20?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=A8=A1=E5=BC=8F=20mock=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 Vite alias 拦截 @tauri-apps/* 导入,使用 mock 模块替代真实的 Tauri API, 使得 pnpm run dev 可以在浏览器中独立运行,无需启动 Tauri 后端。 主要变更: - 新增 src/lib/tauri-mock/ 模块化 mock 系统 - 修复 vite.config.ts 的 ES 模块 __dirname 兼容性 - 移除旧的 src/lib/tauri-mock.ts 文件 - 更新 useTauri.ts 使用静态导入 - 添加 200+ Tauri 命令的 mock 实现 --- package.json | 1 - src/hooks/useTauri.ts | 15 +- src/lib/tauri-mock.ts | 134 ---- src/lib/tauri-mock/core.ts | 668 +++++++++++++++++++ src/lib/tauri-mock/event.ts | 106 +++ src/lib/tauri-mock/index.ts | 39 ++ src/lib/tauri-mock/plugin-deep-link.ts | 45 ++ src/lib/tauri-mock/plugin-dialog.ts | 49 ++ src/lib/tauri-mock/plugin-global-shortcut.ts | 55 ++ src/lib/tauri-mock/plugin-shell.ts | 19 + src/lib/tauri-mock/window.ts | 154 +++++ src/main.tsx | 2 +- vite.config.ts | 29 + 13 files changed, 1171 insertions(+), 145 deletions(-) delete mode 100644 src/lib/tauri-mock.ts create mode 100644 src/lib/tauri-mock/core.ts create mode 100644 src/lib/tauri-mock/event.ts create mode 100644 src/lib/tauri-mock/index.ts create mode 100644 src/lib/tauri-mock/plugin-deep-link.ts create mode 100644 src/lib/tauri-mock/plugin-dialog.ts create mode 100644 src/lib/tauri-mock/plugin-global-shortcut.ts create mode 100644 src/lib/tauri-mock/plugin-shell.ts create mode 100644 src/lib/tauri-mock/window.ts diff --git a/package.json b/package.json index a67b344c9..0921af82c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", - "tauri:dev": "tauri dev --config src-tauri/tauri.conf.json", "tauri:dev:headless": "tauri dev --config src-tauri/tauri.conf.headless.json", "lint": "eslint src --max-warnings 0", "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", diff --git a/src/hooks/useTauri.ts b/src/hooks/useTauri.ts index bfa9e0437..854b4e196 100644 --- a/src/hooks/useTauri.ts +++ b/src/hooks/useTauri.ts @@ -1,3 +1,7 @@ +// 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"; + // Safe Tauri invoke wrapper for web mode compatibility const safeInvoke = async (cmd: string, args?: any): Promise => { // Check if Tauri is available via window.__TAURI__ @@ -13,15 +17,8 @@ const safeInvoke = async (cmd: string, args?: any): Promise => { return (window as any).__TAURI__.invoke(cmd, args); } - // Try to use real Tauri API (dynamic import) - try { - const { invoke } = await import("@tauri-apps/api/core"); - return invoke(cmd, args); - } catch (_e) { - // Not in Tauri environment, return mock data for development - console.warn(`[useTauri] Tauri API not available for command: ${cmd}`); - throw new Error(`Tauri API not available. Command: ${cmd}`); - } + // Not in Tauri environment - use the imported invoke (mock in web mode) + return baseInvoke(cmd, args); }; export interface ServerStatus { diff --git a/src/lib/tauri-mock.ts b/src/lib/tauri-mock.ts deleted file mode 100644 index b41b6e77e..000000000 --- a/src/lib/tauri-mock.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Tauri API Mock for Web Development Mode - * - * Provides mock implementations of Tauri APIs when running in web mode (npm run dev) - * This allows the app to run in browser without Tauri backend - */ - -export const isTauriAvailable = () => { - return typeof window !== "undefined" && "__TAURI__" in window; -}; - -export const mockTauriAPI = () => { - if (typeof window === "undefined") return; - if (isTauriAvailable()) return; // Already have real Tauri - - console.log("[Mock] Initializing Tauri API mock for web mode"); - - // Mock Tauri global - (window as any).__TAURI__ = { - invoke: async (cmd: string, args?: any) => { - console.log(`[Mock] Tauri invoke: ${cmd}`, args); - - // Return mock data based on command - switch (cmd) { - case "get_config": - return { - language: "zh", - theme: "system", - proxy: "", - minimize_to_tray: false, - launch_on_startup: false, - tls: { - enable: false, - cert_path: null, - key_path: null, - }, - remote_management: { - allow_remote: false, - secret_key: null, - disable_control_panel: false, - }, - quota_exceeded: { - switch_project: true, - switch_preview_model: false, - cooldown_seconds: 60, - }, - }; - - case "save_config": - console.log("[Mock] Config saved:", args); - return { success: true }; - - case "get_providers": - return []; - - case "get_credentials": - return []; - - case "check_server_status": - case "get_server_status": - return { - running: false, - host: "127.0.0.1", - port: 8787, - requests: 0, - uptime_secs: 0, - }; - - case "start_server": - return "Server started (mock)"; - - case "stop_server": - return "Server stopped (mock)"; - - case "get_default_provider": - return "openai"; - - case "set_default_provider": - return "Provider set (mock)"; - - case "get_available_models": - return []; - - case "get_network_info": - return { - local_ip: "127.0.0.1", - public_ip: null, - hostname: "localhost", - }; - - default: - console.warn(`[Mock] Unhandled Tauri command: ${cmd}`); - return null; - } - }, - - event: { - listen: async (event: string, _handler: any) => { - console.log(`[Mock] Tauri listen: ${event}`); - // Return unlisten function - return () => { - console.log(`[Mock] Tauri unlisten: ${event}`); - }; - }, - - emit: async (event: string, payload?: any) => { - console.log(`[Mock] Tauri emit: ${event}`, payload); - }, - - once: async (event: string, _handler: any) => { - console.log(`[Mock] Tauri once: ${event}`); - return () => {}; - }, - }, - - tauri: { - invoke: async (cmd: string, args?: any) => { - return (window as any).__TAURI__.invoke(cmd, args); - }, - }, - }; - - // Mock @tauri-apps/api modules - (window as any).__TAURI_INVOKE__ = (window as any).__TAURI__.invoke; - - console.log("[Mock] Tauri API mock initialized"); - console.log("[Mock] Running in WEB MODE - some features may not work"); - console.log("[Mock] For full functionality, run: npm run tauri dev"); -}; - -// Auto-initialize in development mode -if (import.meta.env.DEV && !isTauriAvailable()) { - mockTauriAPI(); -} diff --git a/src/lib/tauri-mock/core.ts b/src/lib/tauri-mock/core.ts new file mode 100644 index 000000000..bc90936e1 --- /dev/null +++ b/src/lib/tauri-mock/core.ts @@ -0,0 +1,668 @@ +/** + * Mock for @tauri-apps/api/core + */ + +// 模拟的命令处理器 +const mockCommands = new Map any>(); + +// 默认 mock 数据 +const defaultMocks: Record = { + // 配置相关 + get_config: () => ({ + server: { + host: "127.0.0.1", + port: 8787, + api_key: "", + tls: { + enable: false, + cert_path: null, + key_path: null, + }, + }, + providers: { + kiro: { + enabled: false, + credentials_path: null, + region: null, + }, + gemini: { + enabled: false, + credentials_path: null, + }, + qwen: { + enabled: false, + credentials_path: null, + }, + openai: { + enabled: false, + api_key: null, + base_url: null, + }, + claude: { + enabled: false, + api_key: null, + base_url: null, + }, + }, + default_provider: "kiro", + remote_management: { + allow_remote: false, + secret_key: null, + disable_control_panel: false, + }, + quota_exceeded: { + switch_project: true, + switch_preview_model: false, + cooldown_seconds: 60, + }, + ampcode: { + upstream_url: null, + model_mappings: [], + restrict_management_to_localhost: true, + }, + credential_pool: { + kiro: [], + gemini: [], + qwen: [], + openai: [], + claude: [], + gemini_api_keys: [], + vertex_api_keys: [], + codex: [], + iflow: [], + }, + proxy_url: null, + minimize_to_tray: false, + language: "zh", + experimental: { + screenshot_chat: { + enabled: false, + shortcut: "", + }, + }, + }), + + save_config: (config: any) => { + console.log("[Mock] Config saved:", config); + return { success: true }; + }, + + // Provider 相关 + get_providers: () => [], + get_credentials: () => [], + get_default_provider: () => "kiro", + set_default_provider: (args: any) => { + const provider = args?.provider ?? args; + console.log("[Mock] Default provider set to:", provider); + return provider; + }, + get_available_models: () => [], + + // 服务器相关 + get_server_status: () => ({ + running: false, + host: "127.0.0.1", + port: 8787, + requests: 0, + uptime_secs: 0, + }), + check_server_status: () => ({ + running: false, + host: "127.0.0.1", + port: 8787, + requests: 0, + uptime_secs: 0, + }), + start_server: () => "Server started (mock)", + stop_server: () => "Server stopped (mock)", + + // 网络相关 + get_network_info: () => ({ + localhost: "127.0.0.1", + lan_ip: "192.168.1.100", + all_ips: ["127.0.0.1", "192.168.1.100"], + }), + + // Agent 相关 + list_agent_sessions: () => [], + agent_list_sessions: () => [], + get_agent_process_status: () => ({ running: false }), + agent_get_process_status: () => ({ running: false }), + agent_start_process: () => ({ success: true }), + agent_stop_process: () => ({ success: true }), + agent_create_session: () => ({ session_id: "mock-session-id" }), + agent_send_message: () => ({ message_id: "mock-message-id" }), + agent_get_session: () => ({ session: null }), + agent_delete_session: () => ({ success: true }), + agent_get_session_messages: () => [], + agent_chat_stream: () => ({}), + agent_terminal_command_response: () => ({}), + agent_term_scrollback_response: () => ({}), + native_agent_chat_stream: () => ({}), + + // Goose Agent + goose_agent_init: () => ({ success: true }), + goose_agent_status: () => ({ initialized: false }), + goose_agent_reset: () => ({ success: true }), + goose_agent_create_session: () => ({ session_id: "mock-goose-session" }), + goose_agent_send_message: () => ({ message_id: "mock-message-id" }), + goose_agent_extend_system_prompt: () => ({ success: true }), + goose_agent_list_providers: () => [], + + // 终端相关 + create_terminal_session: () => ({ uuid: "mock-terminal-uuid" }), + terminal_create_session: () => ({ uuid: "mock-terminal-uuid" }), + terminal_write: () => ({}), + terminal_resize: () => ({}), + terminal_close: () => ({}), + read_terminal_output: () => [], + list_terminal_sessions: () => [], + + // 技能相关 + get_all_skills: () => [], + get_skills_for_app: () => [], + get_skill_repos: () => [], + add_skill_repo: () => ({ success: true }), + remove_skill_repo: () => ({ success: true }), + get_installed_proxycast_skills: () => [], + install_skill_for_app: () => ({ success: true }), + uninstall_skill_for_app: () => ({ success: true }), + enable_skill: () => ({ success: true }), + disable_skill: () => ({ success: true }), + + // 插件相关 + get_plugins_with_ui: () => [], + get_plugin_status: () => ({ + enabled: true, + plugin_count: 0, + plugins_dir: "/mock/plugins", + }), + get_plugins: () => [], + list_installed_plugins: () => [], + enable_plugin: () => ({ success: true }), + disable_plugin: () => ({ success: true }), + reload_plugins: () => ({ success: true }), + unload_plugin: () => ({ success: true }), + uninstall_plugin: () => ({ success: true }), + launch_plugin_ui: () => ({}), + + // 凭证池相关 + get_relay_providers: () => [], + list_relay_providers: () => [], + get_pool_overview: () => [], + get_provider_pool_overview: () => [], + get_provider_pool_credentials: () => [], + add_provider_pool_credential: () => ({ success: true }), + update_provider_pool_credential: () => ({ success: true }), + delete_provider_pool_credential: () => ({ success: true }), + toggle_provider_pool_credential: () => ({ success: true }), + reset_provider_pool_credential: () => ({ success: true }), + reset_provider_pool_health: () => ({ success: true }), + check_provider_pool_credential_health: () => ({ healthy: false }), + check_provider_pool_type_health: () => ({ healthy: false }), + + // API Key Provider 相关 + get_api_key_providers: () => [], + get_api_key_provider: () => null, + add_custom_api_key_provider: () => ({ success: true }), + update_api_key_provider: () => ({ success: true }), + delete_custom_api_key_provider: () => ({ success: true }), + add_api_key: () => ({ success: true }), + delete_api_key: () => ({ success: true }), + toggle_api_key: () => ({ success: true }), + update_api_key_alias: () => ({ success: true }), + get_next_api_key: () => null, + record_api_key_usage: () => ({}), + record_api_key_error: () => ({}), + get_provider_ui_state: () => null, + set_provider_ui_state: () => ({}), + update_provider_sort_orders: () => ({ success: true }), + export_api_key_providers: () => ({ config: "{}" }), + import_api_key_providers: () => ({ success: true }), + get_legacy_api_key_credentials: () => [], + migrate_legacy_api_key_credentials: () => ({ success: true }), + delete_legacy_api_key_credential: () => ({ success: true }), + get_local_kiro_credential_uuid: () => null, + + // OAuth 凭证相关 + add_kiro_oauth_credential: () => ({ success: true }), + add_kiro_from_json: () => ({ success: true }), + add_gemini_oauth_credential: () => ({ success: true }), + add_qwen_oauth_credential: () => ({ success: true }), + add_openai_key_credential: () => ({ success: true }), + add_claude_key_credential: () => ({ success: true }), + add_gemini_api_key_credential: () => ({ success: true }), + add_antigravity_oauth_credential: () => ({ success: true }), + add_codex_oauth_credential: () => ({ success: true }), + add_claude_oauth_credential: () => ({ success: true }), + add_iflow_oauth_credential: () => ({ success: true }), + add_iflow_cookie_credential: () => ({ success: true }), + start_kiro_builder_id_login: () => ({ success: true }), + poll_kiro_builder_id_auth: () => ({ status: "pending" }), + cancel_kiro_builder_id_login: () => ({ success: true }), + add_kiro_from_builder_id_auth: () => ({ success: true }), + start_kiro_social_auth_login: () => ({ success: true }), + exchange_kiro_social_auth_token: () => ({ success: true }), + cancel_kiro_social_auth_login: () => ({ success: true }), + start_kiro_social_auth_callback_server: () => ({ success: true }), + refresh_pool_credential_token: () => ({ success: true }), + get_pool_credential_oauth_status: () => ({ status: "unknown" }), + migrate_private_config_to_pool: () => ({ success: true }), + get_credential_health: () => ({ healthy: false }), + get_all_credential_health: () => [], + get_kiro_credential_fingerprint: () => ({ fingerprint: "" }), + switch_kiro_to_local: () => ({ success: true }), + + // Playwright 相关 + check_playwright_available: () => ({ available: false }), + install_playwright: () => ({ success: true }), + start_kiro_playwright_login: () => ({ success: true }), + cancel_kiro_playwright_login: () => ({ success: true }), + + // OAuth 插件相关 + init_oauth_plugin_system: () => ({}), + enable_oauth_plugin: () => ({ success: true }), + disable_oauth_plugin: () => ({ success: true }), + uninstall_oauth_plugin: () => ({ success: true }), + update_oauth_plugin: () => ({ success: true }), + update_oauth_plugin_config: () => ({ success: true }), + reload_oauth_plugins: () => ({ success: true }), + + // 连接相关 + list_connections: () => [], + connection_list: () => [], + get_oauth_url: () => ({ url: "https://example.com/oauth" }), + save_oauth_credential: () => ({ success: true }), + get_oauth_credentials: () => [], + get_all_oauth_credentials: () => [], + reload_oauth_credentials: () => ({ success: true }), + refresh_oauth_token: () => ({ success: true }), + get_oauth_env_variables: () => [], + get_oauth_token_file_hash: () => ({ hash: "" }), + check_and_reload_oauth_credentials: () => ({ + changed: false, + new_hash: "", + reloaded: false, + }), + + // 模型相关 + get_model_registry: () => [], + refresh_model_registry: () => ({ success: true }), + search_models: () => [], + get_all_provider_models: () => ({}), + get_model_preferences: () => [], + toggle_model_favorite: () => ({ success: true }), + hide_model: () => ({ success: true }), + record_model_usage: () => ({}), + get_model_sync_state: () => ({ syncing: false, last_sync_at: null }), + get_models_for_provider: () => [], + get_models_by_tier: () => [], + get_provider_alias_config: () => ({ alias: {} }), + get_all_alias_configs: () => ({}), + + // Orchestrator 相关 + init_orchestrator: () => ({}), + get_orchestrator_config: () => ({ config: {} }), + update_orchestrator_config: () => ({ success: true }), + get_pool_stats: () => ({ stats: {} }), + get_tier_models: () => [], + get_all_models: () => [], + update_orchestrator_credentials: () => ({ success: true }), + add_orchestrator_credential: () => ({ success: true }), + remove_orchestrator_credential: () => ({ success: true }), + mark_credential_unhealthy: () => ({ success: true }), + mark_credential_healthy: () => ({ success: true }), + update_credential_load: () => ({ success: true }), + select_model: () => ({ model: "" }), + quick_select_model: () => ({ model: "" }), + select_model_for_task: () => ({ model: "" }), + list_strategies: () => [], + list_service_tiers: () => [], + list_task_hints: () => [], + + // MCP 相关 + get_mcp_servers: () => [], + add_mcp_server: () => ({ success: true }), + update_mcp_server: () => ({ success: true }), + delete_mcp_server: () => ({ success: true }), + toggle_mcp_server: () => ({ success: true }), + import_mcp_from_app: () => ({ success: true }), + sync_all_mcp_to_live: () => ({ success: true }), + sync_from_external_config: () => ({ success: true }), + + // Switch Provider 相关 + get_switch_providers: () => [], + add_switch_provider: () => ({ success: true }), + delete_switch_provider: () => ({ success: true }), + update_switch_provider: () => ({ success: true }), + get_current_switch_provider: () => null, + read_live_provider_settings: () => ({}), + + // Flow Monitor 相关 + subscribe_flow_events: () => ({ success: true }), + query_flows: () => ({ flows: [], total: 0 }), + query_flows_with_expression: () => ({ flows: [], total: 0 }), + get_flow_detail: () => ({ flow: null }), + search_flows: () => ({ flows: [] }), + get_flow_stats: () => ({ stats: {} }), + update_flow_annotations: () => ({ success: true }), + cleanup_flows: () => ({ deleted_count: 0 }), + get_recent_flows: () => [], + toggle_flow_starred: () => ({ success: true }), + get_all_flow_tags: () => [], + delete_flow: () => ({ success: true }), + delete_flows: () => ({ success: true }), + get_flow_monitor_debug_info: () => ({ info: {} }), + create_test_flows: () => ({ created_count: 0 }), + get_enhanced_stats: () => ({ stats: {} }), + get_request_trend: () => ({ trend: [] }), + get_token_distribution: () => ({ distribution: {} }), + get_latency_histogram: () => ({ histogram: [] }), + export_stats_report: () => ({ report: "" }), + get_threshold_config: () => ({ config: {} }), + update_threshold_config: () => ({ success: true }), + get_request_rate: () => ({ rate: 0 }), + set_rate_window: () => ({ success: true }), + + // 系统信息相关 + subscribe_sysinfo: () => ({ success: true }), + unsubscribe_sysinfo: () => ({ success: true }), + + // Session 相关 + update_session: () => ({ success: true }), + add_flow_to_session: () => ({ success: true }), + remove_flow_from_session: () => ({ success: true }), + unarchive_session: () => ({ success: true }), + archive_session: () => ({ success: true }), + delete_session: () => ({ success: true }), + + // Bookmark 相关 + remove_bookmark: () => ({ success: true }), + + // Intercept 相关 + intercept_config_set: () => ({ success: true }), + intercept_continue: () => ({ success: true }), + intercept_cancel: () => ({ success: true }), + + // Quick Filter 相关 + delete_quick_filter: () => ({ success: true }), + + // Telemetry 相关 + get_request_logs: () => ({ logs: [] }), + get_request_log_detail: () => ({ log: null }), + clear_request_logs: () => ({ success: true }), + get_stats_summary: () => ({ summary: {} }), + get_stats_by_provider: () => ({ stats: [] }), + get_stats_by_model: () => ({ stats: [] }), + get_token_summary: () => ({ summary: {} }), + get_token_stats_by_provider: () => ({ stats: [] }), + get_token_stats_by_model: () => ({ stats: [] }), + get_token_stats_by_day: () => ({ stats: [] }), + + // Routes 相关 + get_available_routes: () => ({ routes: [] }), + get_route_curl_examples: () => ({ examples: [] }), + + // Prompts 相关 + get_prompts: () => [], + upsert_prompt: () => ({ success: true }), + add_prompt: () => ({ success: true }), + update_prompt: () => ({ success: true }), + delete_prompt: () => ({ success: true }), + enable_prompt: () => ({ success: true }), + import_prompt_from_file: () => ({ success: true }), + get_current_prompt_file_content: () => ({ content: "" }), + auto_import_prompt: () => ({ success: true }), + switch_prompt: () => ({ success: true }), + + // Browser Interceptor 相关 + get_browser_interceptor_state: () => ({ running: false }), + start_browser_interceptor: () => ({ success: true }), + stop_browser_interceptor: () => ({ success: true }), + restore_normal_browser_behavior: () => ({ success: true }), + temporary_disable_interceptor: () => ({ success: true }), + get_intercepted_urls: () => ({ urls: [] }), + get_interceptor_history: () => ({ history: [] }), + copy_intercepted_url_to_clipboard: () => ({ success: true }), + open_url_in_fingerprint_browser: () => ({ success: true }), + dismiss_intercepted_url: () => ({ success: true }), + update_browser_interceptor_config: () => ({ success: true }), + get_default_browser_interceptor_config: () => ({ config: {} }), + validate_browser_interceptor_config: () => ({ valid: true }), + is_browser_interceptor_running: () => ({ running: false }), + get_browser_interceptor_statistics: () => ({ stats: {} }), + + // Notification 相关 + show_notification: () => ({}), + show_url_intercept_notification: () => ({}), + show_status_notification: () => ({}), + + // Window 相关 + get_window_size: () => ({ width: 1280, height: 800 }), + set_window_size: () => ({}), + get_window_size_options: () => ({ options: [] }), + set_window_size_by_option: () => ({}), + toggle_fullscreen: () => ({}), + is_fullscreen: () => ({ fullscreen: false }), + resize_for_flow_monitor: () => ({}), + restore_window_size: () => ({}), + toggle_window_size: () => ({}), + center_window: () => ({}), + + // Usage 相关 + get_kiro_usage: () => ({ usage: {} }), + + // Resilience 相关 + get_retry_config: () => ({ config: {} }), + update_retry_config: () => ({ success: true }), + get_failover_config: () => ({ config: {} }), + update_failover_config: () => ({ success: true }), + get_switch_log: () => ({ logs: [] }), + clear_switch_log: () => ({ success: true }), + + // Machine ID 相关 + get_current_machine_id: () => ({ machine_id: "" }), + set_machine_id: () => ({ success: true }), + generate_random_machine_id: () => ({ machine_id: "" }), + validate_machine_id: () => ({ valid: true }), + check_admin_privileges: () => ({ is_admin: false }), + get_os_type: () => ({ os_type: "linux" }), + backup_machine_id_to_file: () => ({ success: true }), + restore_machine_id_from_file: () => ({ success: true }), + format_machine_id: () => ({ formatted: "" }), + detect_machine_id_format: () => ({ format: "unknown" }), + convert_machine_id_format: () => ({ converted: "" }), + get_machine_id_history: () => ({ history: [] }), + clear_machine_id_override: () => ({ success: true }), + copy_machine_id_to_clipboard: () => ({ success: true }), + paste_machine_id_from_clipboard: () => ({ machine_id: "" }), + get_system_info: () => ({ info: {} }), + + // Injection 相关 + get_injection_config: () => ({ config: {} }), + set_injection_enabled: () => ({ success: true }), + add_injection_rule: () => ({ success: true }), + remove_injection_rule: () => ({ success: true }), + update_injection_rule: () => ({ success: true }), + get_injection_rules: () => ({ rules: [] }), + + // Plugin SDK 相关 + plugin_notification: () => ({}), + plugin_event_emit: () => ({}), + plugin_storage_set: () => ({ success: true }), + plugin_storage_delete: () => ({ success: true }), + plugin_credential_update: () => ({ success: true }), + plugin_credential_delete: () => ({ success: true }), + plugin_credential_refresh: () => ({ success: true }), + plugin_config_set: () => ({ success: true }), + plugin_rpc_connect: () => ({ success: true }), + plugin_rpc_disconnect: () => ({ success: true }), + + // OAuth 登录相关 + start_antigravity_oauth_login: () => ({ success: true }), + get_antigravity_auth_url_and_wait: () => ({ url: "" }), + start_codex_oauth_login: () => ({ success: true }), + get_codex_auth_url_and_wait: () => ({ url: "" }), + start_claude_oauth_login: () => ({ success: true }), + get_claude_oauth_auth_url_and_wait: () => ({ url: "" }), + claude_oauth_with_cookie: () => ({ success: true }), + start_qwen_device_code_login: () => ({ success: true }), + get_qwen_device_code_and_wait: () => ({ code: "" }), + start_iflow_oauth_login: () => ({ success: true }), + get_iflow_auth_url_and_wait: () => ({ url: "" }), + start_gemini_oauth_login: () => ({ success: true }), + get_gemini_auth_url_and_wait: () => ({ url: "" }), + exchange_gemini_code: () => ({ success: true }), + + // File System 相关 + reveal_in_finder: () => ({}), + open_with_default_app: () => ({}), + delete_file: () => ({ success: true }), + create_file: () => ({ success: true }), + create_directory: () => ({ success: true }), + rename_file: () => ({ success: true }), + list_dir: (args: any) => ({ + path: args?.path ?? "~", + parentPath: null, + entries: [], + error: null, + }), + + // Log 相关 + get_logs: () => [], + clear_logs: () => ({}), + + // Test 相关 + test_api: () => ({ success: true, status: 200, body: "", time_ms: 0 }), + + // Kiro Credentials 相关 + get_kiro_credentials: () => ({ loaded: false }), + refresh_kiro_token: () => ({ success: true }), + reload_credentials: () => ({ success: true }), + get_env_variables: () => [], + get_token_file_hash: () => ({ hash: "" }), + check_and_reload_credentials: () => ({ + changed: false, + new_hash: "", + reloaded: false, + }), + + // Gemini Credentials 相关 + get_gemini_credentials: () => ({ loaded: false }), + reload_gemini_credentials: () => ({ success: true }), + refresh_gemini_token: () => ({ success: true }), + get_gemini_env_variables: () => [], + get_gemini_token_file_hash: () => ({ hash: "" }), + check_and_reload_gemini_credentials: () => ({ + changed: false, + new_hash: "", + reloaded: false, + }), + + // Qwen Credentials 相关 + get_qwen_credentials: () => ({ loaded: false }), + reload_qwen_credentials: () => ({ success: true }), + refresh_qwen_token: () => ({ success: true }), + get_qwen_env_variables: () => [], + get_qwen_token_file_hash: () => ({ hash: "" }), + check_and_reload_qwen_credentials: () => ({ + changed: false, + new_hash: "", + reloaded: false, + }), + + // OpenAI Custom 相关 + get_openai_custom_status: () => ({ + enabled: false, + has_api_key: false, + base_url: "", + }), + set_openai_custom_config: () => ({ success: true }), + + // Claude Custom 相关 + get_claude_custom_status: () => ({ + enabled: false, + has_api_key: false, + base_url: "", + }), + set_claude_custom_config: () => ({ success: true }), + + // API Compatibility Check 相关 + check_api_compatibility: () => ({ + provider: "", + overall_status: "ok", + checked_at: "", + results: [], + warnings: [], + }), + + // Endpoint Providers 相关 + get_endpoint_providers: () => ({}), + set_endpoint_provider: () => ({ provider: "" }), + + // Experimental Features 相关 + get_experimental_config: () => ({ + screenshot_chat: { enabled: false, shortcut: "" }, + }), + save_experimental_config: () => ({}), + validate_shortcut: () => ({ valid: true }), + update_screenshot_shortcut: () => ({ success: true }), + + // Screenshot Chat 相关 + send_screenshot_chat: () => ({ success: true }), + close_screenshot_chat_window: () => ({}), + + // Update 相关 + download_update: () => ({ success: true }), + skip_update_version: () => ({}), + close_update_window: () => ({}), + set_update_check_settings: () => ({ success: true }), + test_update_window: () => ({}), + + // Auto Fix 相关 + auto_fix_configuration: () => ({ success: true }), + + // Check Config Sync 相关 + check_config_sync_status: () => ({ status: "synced" }), +}; + +/** + * Mock invoke function + */ +export async function invoke( + cmd: string, + args?: Record, +): Promise { + console.log(`[Mock] invoke: ${cmd}`, args); + + // 检查是否有自定义 mock + if (mockCommands.has(cmd)) { + const handler = mockCommands.get(cmd)!; + return handler(args); + } + + // 使用默认 mock + if (cmd in defaultMocks) { + return defaultMocks[cmd](args); + } + + console.warn(`[Mock] Unhandled command: ${cmd}`); + return undefined as T; +} + +/** + * Register a mock command handler + */ +export function mockCommand(cmd: string, handler: (...args: any[]) => any) { + mockCommands.set(cmd, handler); +} + +/** + * Clear all mock commands + */ +export function clearMocks() { + mockCommands.clear(); +} + +// 导出类型以保持兼容 +export type { InvokeOptions } from "@tauri-apps/api/core"; diff --git a/src/lib/tauri-mock/event.ts b/src/lib/tauri-mock/event.ts new file mode 100644 index 000000000..b95f53e24 --- /dev/null +++ b/src/lib/tauri-mock/event.ts @@ -0,0 +1,106 @@ +/** + * Mock for @tauri-apps/api/event + */ + +type EventCallback = (event: T) => void; +type UnlistenFn = () => void; + +// 存储事件监听器 +const listeners = new Map>>(); + +/** + * Mock listen function + */ +export async function listen( + event: string, + handler: EventCallback, +): Promise { + console.log(`[Mock] listen: ${event}`); + + if (!listeners.has(event)) { + listeners.set(event, new Set()); + } + + listeners.get(event)!.add(handler); + + // 返回 unlisten 函数 + return () => { + const set = listeners.get(event); + if (set) { + set.delete(handler); + if (set.size === 0) { + listeners.delete(event); + } + } + console.log(`[Mock] unlisten: ${event}`); + }; +} + +/** + * Mock once function + */ +export async function once( + event: string, + handler: EventCallback, +): Promise { + console.log(`[Mock] once: ${event}`); + + const wrappedHandler = (data: T) => { + handler(data); + // 自动移除监听器 + const set = listeners.get(event); + if (set) { + set.delete(wrappedHandler); + } + }; + + return listen(event, wrappedHandler); +} + +/** + * Mock emit function - 用于触发事件 + */ +export async function emit(event: string, payload?: any): Promise { + console.log(`[Mock] emit: ${event}`, payload); + + const set = listeners.get(event); + if (set) { + set.forEach((handler) => { + try { + handler({ event, payload }); + } catch (e) { + console.error(`[Mock] Error in event handler for ${event}:`, e); + } + }); + } +} + +/** + * 手动触发一个事件(用于测试) + */ +export function triggerEvent(event: string, payload?: any) { + emit(event, payload); +} + +/** + * 清除所有事件监听器 + */ +export function clearAllListeners() { + listeners.clear(); +} + +// 导出类型 +export type { UnlistenFn }; + +// 重新导出 EventTarget 等类型(如果需要) +export type TauriEvent = { + event: Event; + payload: T; +}; + +export type EventTarget = any; + +// 导出 TAURI_BACKEND_COMPAT 的空实现 +export const TAURI_BACKEND_COMPAT = { + loadTauriCompat: async () => {}, +}; diff --git a/src/lib/tauri-mock/index.ts b/src/lib/tauri-mock/index.ts new file mode 100644 index 000000000..ec053284c --- /dev/null +++ b/src/lib/tauri-mock/index.ts @@ -0,0 +1,39 @@ +/** + * Tauri Mock System - 统一入口 + * + * 在浏览器开发模式下拦截所有 @tauri-apps/* 包的导入 + * 并提供 mock 实现 + */ + +// 检查是否在 Tauri 环境中 +export function isTauriAvailable(): boolean { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +// 初始化 mock 系统 +export function initMockSystem() { + if (isTauriAvailable()) { + console.log("[Mock] Running in Tauri environment, skipping mock"); + return; + } + + console.log("[Mock] Initializing Tauri Mock System for web mode"); + console.log("[Mock] Running in WEB MODE - some features may not work"); + console.log("[Mock] For full functionality, run: pnpm run tauri:dev"); +} + +// 自动初始化 +if (import.meta.env.DEV && !isTauriAvailable()) { + initMockSystem(); +} + +// 导出所有 mock 模块 +export * from "./core"; +export * from "./event"; +export * from "./window"; + +// 导出插件模块,使用别名避免命名冲突 +export { open as openFileDialog, save as saveDialog } from "./plugin-dialog"; +export { open as openShell } from "./plugin-shell"; +export * from "./plugin-deep-link"; +export * from "./plugin-global-shortcut"; diff --git a/src/lib/tauri-mock/plugin-deep-link.ts b/src/lib/tauri-mock/plugin-deep-link.ts new file mode 100644 index 000000000..ffebd924a --- /dev/null +++ b/src/lib/tauri-mock/plugin-deep-link.ts @@ -0,0 +1,45 @@ +/** + * Mock for @tauri-apps/plugin-deep-link + */ + +type UrlCallback = (urls: string[]) => void; +type UnlistenFn = () => void; + +/** + * Mock onOpenUrl function + * 在浏览器环境中,deep link 通常不可用 + */ +export async function onOpenUrl(handler: UrlCallback): Promise { + console.log("[Mock] Deep link onOpenUrl registered"); + + // 模拟从 URL 获取 deep link 参数 + if (typeof window !== "undefined") { + const urlParams = new URLSearchParams(window.location.search); + const deepLinkUrl = urlParams.get("proxycast"); + + if (deepLinkUrl) { + console.log("[Mock] Deep link URL from params:", deepLinkUrl); + setTimeout(() => handler([deepLinkUrl]), 100); + } + } + + // 返回 unlisten 函数 + return () => { + console.log("[Mock] Deep link unlisten"); + }; +} + +/** + * Mock getUrls function (获取当前打开的 URL) + */ +export async function getUrls(): Promise { + console.log("[Mock] Deep link getUrls"); + + if (typeof window !== "undefined") { + const urlParams = new URLSearchParams(window.location.search); + const deepLinkUrl = urlParams.get("proxycast"); + return deepLinkUrl ? [deepLinkUrl] : []; + } + + return []; +} diff --git a/src/lib/tauri-mock/plugin-dialog.ts b/src/lib/tauri-mock/plugin-dialog.ts new file mode 100644 index 000000000..0de9488f2 --- /dev/null +++ b/src/lib/tauri-mock/plugin-dialog.ts @@ -0,0 +1,49 @@ +/** + * Mock for @tauri-apps/plugin-dialog + */ + +export interface OpenDialogOptions { + title?: string; + defaultPath?: string; + filters?: Array<{ name: string; extensions: string[] }>; + multiple?: boolean; + directory?: boolean; + recursive?: boolean; +} + +export interface SaveDialogOptions { + title?: string; + defaultPath?: string; + filters?: Array<{ name: string; extensions: string[] }>; +} + +/** + * Mock open function (file picker) + */ +export async function open( + options?: OpenDialogOptions, +): Promise { + console.log("[Mock] Dialog open:", options); + + // 在浏览器中返回 null(用户取消) + // 可以通过 prompt 来模拟用户输入 + if (options?.directory) { + return "/mock/path/to/directory"; + } + + if (options?.multiple) { + return ["/mock/path/to/file1.txt", "/mock/path/to/file2.txt"]; + } + + return "/mock/path/to/file.txt"; +} + +/** + * Mock save function + */ +export async function save( + options?: SaveDialogOptions, +): Promise { + console.log("[Mock] Dialog save:", options); + return "/mock/path/to/saved/file.txt"; +} diff --git a/src/lib/tauri-mock/plugin-global-shortcut.ts b/src/lib/tauri-mock/plugin-global-shortcut.ts new file mode 100644 index 000000000..65b3b9b0a --- /dev/null +++ b/src/lib/tauri-mock/plugin-global-shortcut.ts @@ -0,0 +1,55 @@ +/** + * Mock for @tauri-apps/plugin-global-shortcut + */ + +type ShortcutHandler = () => void; + +// 存储快捷键监听器 +const shortcuts = new Map(); + +/** + * Mock register function + */ +export async function register( + shortcut: string, + handler: ShortcutHandler, +): Promise { + console.log(`[Mock] Global shortcut registered: ${shortcut}`); + shortcuts.set(shortcut, handler); +} + +/** + * Mock unregister function + */ +export async function unregister(shortcut: string): Promise { + console.log(`[Mock] Global shortcut unregistered: ${shortcut}`); + shortcuts.delete(shortcut); +} + +/** + * Mock unregisterAll function + */ +export async function unregisterAll(): Promise { + console.log("[Mock] All global shortcuts unregistered"); + shortcuts.clear(); +} + +/** + * Mock isRegistered function + */ +export async function isRegistered(shortcut: string): Promise { + return shortcuts.has(shortcut); +} + +/** + * 手动触发快捷键(用于测试) + */ +export function triggerShortcut(shortcut: string) { + const handler = shortcuts.get(shortcut); + if (handler) { + console.log(`[Mock] Triggering shortcut: ${shortcut}`); + handler(); + } else { + console.warn(`[Mock] Shortcut not found: ${shortcut}`); + } +} diff --git a/src/lib/tauri-mock/plugin-shell.ts b/src/lib/tauri-mock/plugin-shell.ts new file mode 100644 index 000000000..f55d50e57 --- /dev/null +++ b/src/lib/tauri-mock/plugin-shell.ts @@ -0,0 +1,19 @@ +/** + * Mock for @tauri-apps/plugin-shell + */ + +/** + * Mock open function (opens URL in external browser) + */ +export async function open(path: string, openWith?: string): Promise { + console.log("[Mock] Shell open:", path, openWith ? `with: ${openWith}` : ""); + + // 在浏览器开发环境中,直接在当前标签页打开 + if (typeof window !== "undefined") { + if (path.startsWith("http://") || path.startsWith("https://")) { + window.open(path, "_blank"); + } else { + console.warn("[Mock] Non-HTTP URL, not opening:", path); + } + } +} diff --git a/src/lib/tauri-mock/window.ts b/src/lib/tauri-mock/window.ts new file mode 100644 index 000000000..3ad692738 --- /dev/null +++ b/src/lib/tauri-mock/window.ts @@ -0,0 +1,154 @@ +/** + * Mock for @tauri-apps/api/window + */ + +import { emit } from "./event"; + +export interface WindowOptions { + width?: number; + height?: number; + x?: number; + y?: number; + title?: string; + visible?: boolean; + resizable?: boolean; + decorations?: boolean; + alwaysOnTop?: boolean; + skipTaskbar?: boolean; + fullscreen?: boolean; + maximized?: boolean; + minimizable?: boolean; + maximizable?: boolean; + closable?: boolean; + center?: boolean; +} + +class MockWindow { + label: string; + options: WindowOptions; + + constructor(label: string, options: WindowOptions = {}) { + this.label = label; + this.options = options; + } + + async emit(event: string, payload?: any): Promise { + console.log(`[Mock] Window ${this.label} emit: ${event}`, payload); + return emit(event, payload); + } + + async listen(event: string, handler: any): Promise<() => void> { + console.log(`[Mock] Window ${this.label} listen: ${event}`); + const { listen } = await import("./event"); + return listen(event, handler); + } + + // 窗口控制方法 + async show(): Promise { + console.log(`[Mock] Window ${this.label} show`); + this.options.visible = true; + } + + async hide(): Promise { + console.log(`[Mock] Window ${this.label} hide`); + this.options.visible = false; + } + + async close(): Promise { + console.log(`[Mock] Window ${this.label} close`); + } + + async minimize(): Promise { + console.log(`[Mock] Window ${this.label} minimize`); + } + + async maximize(): Promise { + console.log(`[Mock] Window ${this.label} maximize`); + this.options.maximized = true; + } + + async unmaximize(): Promise { + console.log(`[Mock] Window ${this.label} unmaximize`); + this.options.maximized = false; + } + + async center(): Promise { + console.log(`[Mock] Window ${this.label} center`); + } + + async setTitle(title: string): Promise { + console.log(`[Mock] Window ${this.label} setTitle: ${title}`); + this.options.title = title; + } + + async resize(width: number, height: number): Promise { + console.log(`[Mock] Window ${this.label} resize: ${width}x${height}`); + this.options.width = width; + this.options.height = height; + } + + async setPosition(x: number, y: number): Promise { + console.log(`[Mock] Window ${this.label} setPosition: ${x},${y}`); + this.options.x = x; + this.options.y = y; + } + + async isVisible(): Promise { + return this.options.visible ?? true; + } + + async isMaximized(): Promise { + return this.options.maximized ?? false; + } + + async isFullscreen(): Promise { + return this.options.fullscreen ?? false; + } + + async isDecorated(): Promise { + return this.options.decorations ?? true; + } + + async isResizable(): Promise { + return this.options.resizable ?? true; + } + + async onFocusChanged( + _handler: (focused: boolean) => void, + ): Promise<() => void> { + console.log(`[Mock] Window ${this.label} onFocusChanged`); + // 返回 unlisten 函数 + return () => {}; + } + + async onScaleChanged(_handler: (scale: number) => void): Promise<() => void> { + console.log(`[Mock] Window ${this.label} onScaleChanged`); + return () => {}; + } + + async onThemeChanged(_handler: (theme: string) => void): Promise<() => void> { + console.log(`[Mock] Window ${this.label} onThemeChanged`); + return () => {}; + } +} + +// 当前窗口实例 +let currentWindow: MockWindow | null = null; + +export function getCurrentWindow(): MockWindow { + if (!currentWindow) { + currentWindow = new MockWindow("main", { visible: true }); + } + return currentWindow; +} + +export function getAllWindows(): MockWindow[] { + return [getCurrentWindow()]; +} + +export async function getCurrentWindowLabel(): Promise { + return getCurrentWindow().label; +} + +// 导出常用函数 +export const appWindow = getCurrentWindow(); diff --git a/src/main.tsx b/src/main.tsx index feef293bb..78a9a9548 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import { RootRouter } from "./RootRouter"; import "./index.css"; // Initialize Tauri mock for web mode -import "./lib/tauri-mock"; +import "./lib/tauri-mock/index"; // Initialize i18n configuration import "./i18n/config"; diff --git a/vite.config.ts b/vite.config.ts index b8472d8f1..51f774b8b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,6 +3,13 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; import path from "path"; +import { fileURLToPath } from "url"; + +// ES 模块中获取 __dirname 的方式 +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// 获取 Tauri mock 目录路径 +const tauriMockDir = path.resolve(__dirname, "./src/lib/tauri-mock"); export default defineConfig(({ mode }) => ({ plugins: [ @@ -16,8 +23,30 @@ export default defineConfig(({ mode }) => ({ resolve: { alias: { "@": path.resolve(__dirname, "./src"), + // 拦截所有 @tauri-apps/* 导入,重定向到 mock 模块 + // 这样在浏览器开发模式下可以使用 mock 实现 + "@tauri-apps/api/core": path.resolve(tauriMockDir, "core.ts"), + "@tauri-apps/api/event": path.resolve(tauriMockDir, "event.ts"), + "@tauri-apps/api/window": path.resolve(tauriMockDir, "window.ts"), + "@tauri-apps/api/app": path.resolve(tauriMockDir, "window.ts"), + "@tauri-apps/api/path": path.resolve(tauriMockDir, "window.ts"), + // 拦截 Tauri 插件 + "@tauri-apps/plugin-dialog": path.resolve(tauriMockDir, "plugin-dialog.ts"), + "@tauri-apps/plugin-shell": path.resolve(tauriMockDir, "plugin-shell.ts"), + "@tauri-apps/plugin-deep-link": path.resolve(tauriMockDir, "plugin-deep-link.ts"), + "@tauri-apps/plugin-global-shortcut": path.resolve(tauriMockDir, "plugin-global-shortcut.ts"), }, }, + optimizeDeps: { + // 排除 Tauri 包的预构建,确保 alias 生效 + exclude: [ + "@tauri-apps/api", + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-shell", + "@tauri-apps/plugin-deep-link", + "@tauri-apps/plugin-global-shortcut", + ], + }, clearScreen: false, server: { port: 1420,