feat: 添加 Vite web 开发模式 mock 系统

通过 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 实现
This commit is contained in:
lwmacct
2026-01-11 22:40:20 +08:00
parent ab9d802578
commit 9c59f9b2b1
13 changed files with 1171 additions and 145 deletions
-1
View File
@@ -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}\"",
+6 -9
View File
@@ -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<any> => {
// Check if Tauri is available via window.__TAURI__
@@ -13,15 +17,8 @@ const safeInvoke = async (cmd: string, args?: any): Promise<any> => {
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 {
-134
View File
@@ -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();
}
+668
View File
@@ -0,0 +1,668 @@
/**
* Mock for @tauri-apps/api/core
*/
// 模拟的命令处理器
const mockCommands = new Map<string, (...args: any[]) => any>();
// 默认 mock 数据
const defaultMocks: Record<string, any> = {
// 配置相关
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<T = any>(
cmd: string,
args?: Record<string, unknown>,
): Promise<T> {
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";
+106
View File
@@ -0,0 +1,106 @@
/**
* Mock for @tauri-apps/api/event
*/
type EventCallback<T> = (event: T) => void;
type UnlistenFn = () => void;
// 存储事件监听器
const listeners = new Map<string, Set<EventCallback<any>>>();
/**
* Mock listen function
*/
export async function listen<T = any>(
event: string,
handler: EventCallback<T>,
): Promise<UnlistenFn> {
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<T = any>(
event: string,
handler: EventCallback<T>,
): Promise<UnlistenFn> {
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<void> {
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<T> = {
event: Event;
payload: T;
};
export type EventTarget = any;
// 导出 TAURI_BACKEND_COMPAT 的空实现
export const TAURI_BACKEND_COMPAT = {
loadTauriCompat: async () => {},
};
+39
View File
@@ -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";
+45
View File
@@ -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<UnlistenFn> {
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<string[]> {
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 [];
}
+49
View File
@@ -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<string | string[] | null> {
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<string | null> {
console.log("[Mock] Dialog save:", options);
return "/mock/path/to/saved/file.txt";
}
@@ -0,0 +1,55 @@
/**
* Mock for @tauri-apps/plugin-global-shortcut
*/
type ShortcutHandler = () => void;
// 存储快捷键监听器
const shortcuts = new Map<string, ShortcutHandler>();
/**
* Mock register function
*/
export async function register(
shortcut: string,
handler: ShortcutHandler,
): Promise<void> {
console.log(`[Mock] Global shortcut registered: ${shortcut}`);
shortcuts.set(shortcut, handler);
}
/**
* Mock unregister function
*/
export async function unregister(shortcut: string): Promise<void> {
console.log(`[Mock] Global shortcut unregistered: ${shortcut}`);
shortcuts.delete(shortcut);
}
/**
* Mock unregisterAll function
*/
export async function unregisterAll(): Promise<void> {
console.log("[Mock] All global shortcuts unregistered");
shortcuts.clear();
}
/**
* Mock isRegistered function
*/
export async function isRegistered(shortcut: string): Promise<boolean> {
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}`);
}
}
+19
View File
@@ -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<void> {
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);
}
}
}
+154
View File
@@ -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<void> {
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<void> {
console.log(`[Mock] Window ${this.label} show`);
this.options.visible = true;
}
async hide(): Promise<void> {
console.log(`[Mock] Window ${this.label} hide`);
this.options.visible = false;
}
async close(): Promise<void> {
console.log(`[Mock] Window ${this.label} close`);
}
async minimize(): Promise<void> {
console.log(`[Mock] Window ${this.label} minimize`);
}
async maximize(): Promise<void> {
console.log(`[Mock] Window ${this.label} maximize`);
this.options.maximized = true;
}
async unmaximize(): Promise<void> {
console.log(`[Mock] Window ${this.label} unmaximize`);
this.options.maximized = false;
}
async center(): Promise<void> {
console.log(`[Mock] Window ${this.label} center`);
}
async setTitle(title: string): Promise<void> {
console.log(`[Mock] Window ${this.label} setTitle: ${title}`);
this.options.title = title;
}
async resize(width: number, height: number): Promise<void> {
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<void> {
console.log(`[Mock] Window ${this.label} setPosition: ${x},${y}`);
this.options.x = x;
this.options.y = y;
}
async isVisible(): Promise<boolean> {
return this.options.visible ?? true;
}
async isMaximized(): Promise<boolean> {
return this.options.maximized ?? false;
}
async isFullscreen(): Promise<boolean> {
return this.options.fullscreen ?? false;
}
async isDecorated(): Promise<boolean> {
return this.options.decorations ?? true;
}
async isResizable(): Promise<boolean> {
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<string> {
return getCurrentWindow().label;
}
// 导出常用函数
export const appWindow = getCurrentWindow();
+1 -1
View File
@@ -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";
+29
View File
@@ -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,