Make connection checks exercise the selected model end to end
The settings validation button now uses the currently selected LLM model for a real chat-completion probe instead of only listing models/auth state. The frontend message was simplified to reflect validation success without conflating it with model-count discovery, which remains handled by the dedicated fetch-models action. Constraint: Keep the change minimal and local; preserve the separate fetch-models workflow for listing available models. Rejected: Keep auth-only validation | it would keep missing the failure mode reported in issue #136. Rejected: Add a broader provider refactor | unnecessary for the narrow validation behavior change. Confidence: high Scope-risk: narrow Directive: Validation should probe the selected model path; model enumeration belongs to fetch-models. Tested: npm ci; npm run build; cargo test --lib Not-tested: Live provider endpoint behavior against real credentials on the user’s machine
This commit is contained in:
1 parent
08e48cc852
commit
420fe65473
5 files changed
+40
-24
No files matched your search
@@ -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::{OpenAICompatibleConfig, OpenAICompatibleLLMProvider};
|
||||
use crate::types::{
|
||||
CredentialsStatus, DictationSession, DictionaryEntry, HotkeyCapability, HotkeyStatus,
|
||||
PolishMode, QaHotkeyBinding, UserPreferences,
|
||||
@@ -88,7 +89,6 @@ pub fn read_credential(account: String) -> Result<Option<String>, String> {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProviderCheckResult {
|
||||
ok: bool,
|
||||
model_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -98,13 +98,18 @@ pub struct ProviderModelsResult {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn validate_provider_credentials(kind: String) -> Result<ProviderCheckResult, String> {
|
||||
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]
|
||||
@@ -141,6 +146,26 @@ fn read_openai_provider_config(kind: &str) -> Result<ProviderConfig, String> {
|
||||
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())
|
||||
.unwrap_or_else(|| "deepseek-v3-2".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| e.to_string())
|
||||
}
|
||||
|
||||
async fn fetch_provider_models(config: &ProviderConfig) -> Result<Vec<String>, String> {
|
||||
let url = models_url(&config.base_url);
|
||||
log::info!("[provider-check] GET {url}");
|
||||
@@ -454,10 +479,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())?;
|
||||
|
||||
@@ -298,7 +298,7 @@ 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',
|
||||
@@ -307,7 +307,7 @@ export const en: typeof zhCN = {
|
||||
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.',
|
||||
|
||||
@@ -296,7 +296,7 @@ export const zhCN = {
|
||||
accessKeyLabel: 'Access Key',
|
||||
resourceIdLabel: '资源 ID',
|
||||
toolsLabel: '连接检查',
|
||||
toolsDesc: '先保存上方配置,再验证鉴权或拉取模型;失败时仍可手动填写模型 ID。',
|
||||
toolsDesc: '先保存上方配置,再验证当前模型连通性或拉取模型;失败时仍可手动填写模型 ID。',
|
||||
validate: '验证',
|
||||
validating: '验证中…',
|
||||
fetchModels: '拉取模型',
|
||||
@@ -305,7 +305,7 @@ export const zhCN = {
|
||||
modelsLoaded: '已拉取 {{count}} 个模型。',
|
||||
selectModel: '选择一个模型写入上方字段',
|
||||
modelSaved: '已保存模型 {{model}}。',
|
||||
validateSuccess: '鉴权成功,可用模型 {{count}} 个。',
|
||||
validateSuccess: '连接检查通过。',
|
||||
providerHttpStatus: '供应商接口返回 {{status}},请检查 API Key 权限或 Endpoint。',
|
||||
apiKeyMissing: 'API Key 为空。',
|
||||
endpointMissing: 'Endpoint 为空。',
|
||||
|
||||
@@ -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<string | null> {
|
||||
}
|
||||
|
||||
export function validateProviderCredentials(kind: 'llm' | 'asr'): Promise<ProviderCheckResult> {
|
||||
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<ProviderModelsResult> {
|
||||
|
||||
@@ -428,12 +428,7 @@ 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) {
|
||||
setResult('error', providerErrorMessage(error, t));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user