use indexmap::IndexMap; use tauri::State; use crate::app_config::AppType; use crate::error::AppError; use crate::provider::Provider; use crate::services::{EndpointLatency, ProviderService, ProviderSortUpdate, SpeedtestService}; use crate::store::AppState; use std::str::FromStr; #[tauri::command] pub fn get_providers( state: State<'_, AppState>, app: String, ) -> Result, String> { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::list(state.inner(), app_type).map_err(|e| e.to_string()) } #[tauri::command] pub fn get_current_provider(state: State<'_, AppState>, app: String) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::current(state.inner(), app_type).map_err(|e| e.to_string()) } #[tauri::command] pub fn add_provider( state: State<'_, AppState>, app: String, provider: Provider, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::add(state.inner(), app_type, provider).map_err(|e| e.to_string()) } #[tauri::command] pub fn update_provider( state: State<'_, AppState>, app: String, provider: Provider, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::update(state.inner(), app_type, provider).map_err(|e| e.to_string()) } #[tauri::command] pub fn delete_provider( state: State<'_, AppState>, app: String, id: String, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::delete(state.inner(), app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) } #[tauri::command] pub fn remove_provider_from_live_config( state: tauri::State<'_, AppState>, app: String, id: String, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::remove_from_live_config(state.inner(), app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) } fn switch_provider_internal(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> { ProviderService::switch(state, app_type, id) } #[cfg_attr(not(feature = "test-hooks"), doc(hidden))] pub fn switch_provider_test_hook( state: &AppState, app_type: AppType, id: &str, ) -> Result<(), AppError> { switch_provider_internal(state, app_type, id) } #[tauri::command] pub fn switch_provider( state: State<'_, AppState>, app: String, id: String, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; switch_provider_internal(&state, app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) } fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result { ProviderService::import_default_config(state, app_type) } #[cfg_attr(not(feature = "test-hooks"), doc(hidden))] pub fn import_default_config_test_hook( state: &AppState, app_type: AppType, ) -> Result { import_default_config_internal(state, app_type) } #[tauri::command] pub fn import_default_config(state: State<'_, AppState>, app: String) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; import_default_config_internal(&state, app_type).map_err(Into::into) } #[allow(non_snake_case)] #[tauri::command] pub async fn queryProviderUsage( state: State<'_, AppState>, #[allow(non_snake_case)] providerId: String, // 使用 camelCase 匹配前端 app: String, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::query_usage(state.inner(), app_type, &providerId) .await .map_err(|e| e.to_string()) } #[allow(non_snake_case)] #[allow(clippy::too_many_arguments)] #[tauri::command] pub async fn testUsageScript( state: State<'_, AppState>, #[allow(non_snake_case)] providerId: String, app: String, #[allow(non_snake_case)] scriptCode: String, timeout: Option, #[allow(non_snake_case)] apiKey: Option, #[allow(non_snake_case)] baseUrl: Option, #[allow(non_snake_case)] accessToken: Option, #[allow(non_snake_case)] userId: Option, #[allow(non_snake_case)] templateType: Option, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::test_usage_script( state.inner(), app_type, &providerId, &scriptCode, timeout.unwrap_or(10), apiKey.as_deref(), baseUrl.as_deref(), accessToken.as_deref(), userId.as_deref(), templateType.as_deref(), ) .await .map_err(|e| e.to_string()) } #[tauri::command] pub fn read_live_provider_settings(app: String) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::read_live_settings(app_type).map_err(|e| e.to_string()) } #[tauri::command] pub async fn test_api_endpoints( urls: Vec, #[allow(non_snake_case)] timeoutSecs: Option, ) -> Result, String> { SpeedtestService::test_endpoints(urls, timeoutSecs) .await .map_err(|e| e.to_string()) } #[tauri::command] pub fn get_custom_endpoints( state: State<'_, AppState>, app: String, #[allow(non_snake_case)] providerId: String, ) -> Result, String> { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::get_custom_endpoints(state.inner(), app_type, &providerId) .map_err(|e| e.to_string()) } #[tauri::command] pub fn add_custom_endpoint( state: State<'_, AppState>, app: String, #[allow(non_snake_case)] providerId: String, url: String, ) -> Result<(), String> { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::add_custom_endpoint(state.inner(), app_type, &providerId, url) .map_err(|e| e.to_string()) } #[tauri::command] pub fn remove_custom_endpoint( state: State<'_, AppState>, app: String, #[allow(non_snake_case)] providerId: String, url: String, ) -> Result<(), String> { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::remove_custom_endpoint(state.inner(), app_type, &providerId, url) .map_err(|e| e.to_string()) } #[tauri::command] pub fn update_endpoint_last_used( state: State<'_, AppState>, app: String, #[allow(non_snake_case)] providerId: String, url: String, ) -> Result<(), String> { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::update_endpoint_last_used(state.inner(), app_type, &providerId, url) .map_err(|e| e.to_string()) } #[tauri::command] pub fn update_providers_sort_order( state: State<'_, AppState>, app: String, updates: Vec, ) -> Result { let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?; ProviderService::update_sort_order(state.inner(), app_type, updates).map_err(|e| e.to_string()) } use crate::provider::UniversalProvider; use std::collections::HashMap; use tauri::{AppHandle, Emitter}; #[derive(Clone, serde::Serialize)] pub struct UniversalProviderSyncedEvent { pub action: String, pub id: String, } fn emit_universal_provider_synced(app: &AppHandle, action: &str, id: &str) { let _ = app.emit( "universal-provider-synced", UniversalProviderSyncedEvent { action: action.to_string(), id: id.to_string(), }, ); } #[tauri::command] pub fn get_universal_providers( state: State<'_, AppState>, ) -> Result, String> { ProviderService::list_universal(state.inner()).map_err(|e| e.to_string()) } #[tauri::command] pub fn get_universal_provider( state: State<'_, AppState>, id: String, ) -> Result, String> { ProviderService::get_universal(state.inner(), &id).map_err(|e| e.to_string()) } #[tauri::command] pub fn upsert_universal_provider( app: AppHandle, state: State<'_, AppState>, provider: UniversalProvider, ) -> Result { let id = provider.id.clone(); let result = ProviderService::upsert_universal(state.inner(), provider).map_err(|e| e.to_string())?; emit_universal_provider_synced(&app, "upsert", &id); Ok(result) } #[tauri::command] pub fn delete_universal_provider( app: AppHandle, state: State<'_, AppState>, id: String, ) -> Result { let result = ProviderService::delete_universal(state.inner(), &id).map_err(|e| e.to_string())?; emit_universal_provider_synced(&app, "delete", &id); Ok(result) } #[tauri::command] pub fn sync_universal_provider( app: AppHandle, state: State<'_, AppState>, id: String, ) -> Result { let result = ProviderService::sync_universal_to_apps(state.inner(), &id).map_err(|e| e.to_string())?; emit_universal_provider_synced(&app, "sync", &id); Ok(result) } #[tauri::command] pub fn import_opencode_providers_from_live(state: State<'_, AppState>) -> Result { crate::services::provider::import_opencode_providers_from_live(state.inner()) .map_err(|e| e.to_string()) } #[tauri::command] pub fn get_opencode_live_provider_ids() -> Result, String> { crate::opencode_config::get_providers() .map(|providers| providers.keys().cloned().collect()) .map_err(|e| e.to_string()) }