diff --git a/openless-all/app/src-tauri/src/commands.rs b/openless-all/app/src-tauri/src/commands.rs index f8ac06bc..ddbff79a 100644 --- a/openless-all/app/src-tauri/src/commands.rs +++ b/openless-all/app/src-tauri/src/commands.rs @@ -10,6 +10,7 @@ use tauri::{AppHandle, State}; use crate::coordinator::Coordinator; use crate::permissions::{self, PermissionStatus}; use crate::persistence::{CredentialAccount, CredentialsSnapshot, CredentialsVault}; +use crate::polish::{LLMError, OpenAICompatibleConfig, OpenAICompatibleLLMProvider}; use crate::types::{ CredentialsStatus, DictationSession, DictionaryEntry, HotkeyCapability, HotkeyStatus, PolishMode, QaHotkeyBinding, UserPreferences, @@ -127,7 +128,6 @@ pub fn read_credential(account: String) -> Result, String> { #[serde(rename_all = "camelCase")] pub struct ProviderCheckResult { ok: bool, - model_count: usize, } #[derive(Serialize)] @@ -137,13 +137,18 @@ pub struct ProviderModelsResult { #[tauri::command] pub async fn validate_provider_credentials(kind: String) -> Result { - let config = read_openai_provider_config(&kind)?; - fetch_provider_models(&config) - .await - .map(|models| ProviderCheckResult { - ok: true, - model_count: models.len(), - }) + match kind.as_str() { + "llm" => validate_llm_provider() + .await + .map(|()| ProviderCheckResult { ok: true }), + "asr" => { + let config = read_openai_provider_config(&kind)?; + fetch_provider_models(&config) + .await + .map(|_| ProviderCheckResult { ok: true }) + } + _ => Err(format!("unknown provider kind: {kind}")), + } } #[tauri::command] @@ -180,6 +185,31 @@ fn read_openai_provider_config(kind: &str) -> Result { Ok(ProviderConfig { base_url, api_key }) } +async fn validate_llm_provider() -> Result<(), String> { + let config = read_openai_provider_config("llm")?; + let model = CredentialsVault::get(CredentialAccount::ArkModelId) + .map_err(|e| e.to_string())? + .filter(|s| !s.is_empty()) + .ok_or_else(|| "llmModelMissing".to_string())?; + let provider = OpenAICompatibleLLMProvider::new(OpenAICompatibleConfig::new( + "ark", + "Doubao Ark", + config.base_url, + config.api_key, + model, + )); + provider + .polish("验证连接", PolishMode::Raw, &[], &[], None) + .await + .map(|_| ()) + .map_err(|e| match e { + LLMError::InvalidResponse { status, .. } => { + format!("providerHttpStatus:{status}") + } + other => other.to_string(), + }) +} + async fn fetch_provider_models(config: &ProviderConfig) -> Result, String> { let url = models_url(&config.base_url); log::info!("[provider-check] GET {url}"); @@ -493,10 +523,7 @@ pub fn get_qa_hotkey_label(coord: CoordinatorState<'_>) -> String { /// 传入 `None` 形式的字段不在这里支持——前端用 `binding == null` 时调下面的 /// "disable" 写法(写 prefs.qa_hotkey = None)即可。 #[tauri::command] -pub fn set_qa_hotkey( - coord: CoordinatorState<'_>, - binding: QaHotkeyBinding, -) -> Result<(), String> { +pub fn set_qa_hotkey(coord: CoordinatorState<'_>, binding: QaHotkeyBinding) -> Result<(), String> { let mut prefs = coord.prefs().get(); prefs.qa_hotkey = Some(binding); coord.prefs().set(prefs).map_err(|e| e.to_string())?; diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 4b17addc..157a63e5 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -298,16 +298,17 @@ export const en: typeof zhCN = { accessKeyLabel: 'Access Key', resourceIdLabel: 'Resource ID', toolsLabel: 'Connection check', - toolsDesc: 'Save the fields above, then validate credentials or fetch models. Manual model input remains available if fetching fails.', + toolsDesc: 'Save the fields above, then validate the selected model or fetch models. Manual model input remains available if fetching fails.', validate: 'Validate', validating: 'Validating…', fetchModels: 'Fetch models', loadingModels: 'Fetching models…', + modelMissing: 'No model is configured. Please enter a model ID first.', modelsEmpty: 'Credentials are valid, but no models were returned.', modelsLoaded: 'Fetched {{count}} models.', selectModel: 'Select a model to fill the field above', modelSaved: 'Saved model {{model}}.', - validateSuccess: 'Credentials are valid. {{count}} models available.', + validateSuccess: 'Connection check passed.', providerHttpStatus: 'Provider returned HTTP {{status}}. Check the API key permissions or endpoint.', apiKeyMissing: 'API Key is empty.', endpointMissing: 'Endpoint is empty.', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index b08578c2..c98358bb 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -296,16 +296,17 @@ export const zhCN = { accessKeyLabel: 'Access Key', resourceIdLabel: '资源 ID', toolsLabel: '连接检查', - toolsDesc: '先保存上方配置,再验证鉴权或拉取模型;失败时仍可手动填写模型 ID。', + toolsDesc: '先保存上方配置,再验证当前模型连通性或拉取模型;失败时仍可手动填写模型 ID。', validate: '验证', validating: '验证中…', fetchModels: '拉取模型', loadingModels: '拉取模型中…', + modelMissing: '未配置模型,请先填写模型 ID。', modelsEmpty: '鉴权成功,但没有返回可用模型。', modelsLoaded: '已拉取 {{count}} 个模型。', selectModel: '选择一个模型写入上方字段', modelSaved: '已保存模型 {{model}}。', - validateSuccess: '鉴权成功,可用模型 {{count}} 个。', + validateSuccess: '连接检查通过。', providerHttpStatus: '供应商接口返回 {{status}},请检查 API Key 权限或 Endpoint。', apiKeyMissing: 'API Key 为空。', endpointMissing: 'Endpoint 为空。', diff --git a/openless-all/app/src/lib/ipc.ts b/openless-all/app/src/lib/ipc.ts index b658a63e..8c9e7993 100644 --- a/openless-all/app/src/lib/ipc.ts +++ b/openless-all/app/src/lib/ipc.ts @@ -68,7 +68,6 @@ const mockCredentialsStatus: CredentialsStatus = { export interface ProviderCheckResult { ok: boolean; - modelCount: number; } export interface ProviderModelsResult { @@ -144,7 +143,7 @@ export function readCredential(account: string): Promise { } export function validateProviderCredentials(kind: 'llm' | 'asr'): Promise { - return invokeOrMock('validate_provider_credentials', { kind }, () => ({ ok: true, modelCount: 2 })); + return invokeOrMock('validate_provider_credentials', { kind }, () => ({ ok: true })); } export function listProviderModels(kind: 'llm' | 'asr'): Promise { diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 8fa38993..420b9f66 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -428,13 +428,13 @@ function ProviderTools({ kind, modelAccount, onModelSelected }: { kind: 'llm' | setResult('loading', t('settings.providers.validating')); try { const result = await validateProviderCredentials(kind); - setResult( - result.ok ? (result.modelCount === 0 ? 'empty' : 'success') : 'error', - result.modelCount === 0 - ? t('settings.providers.modelsEmpty') - : t('settings.providers.validateSuccess', { count: result.modelCount }), - ); + setResult(result.ok ? 'success' : 'error', t('settings.providers.validateSuccess')); } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (kind === 'llm' && message === 'llmModelMissing') { + setResult('empty', t('settings.providers.modelMissing')); + return; + } setResult('error', providerErrorMessage(error, t)); } };