Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions openless-all/app/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -127,7 +128,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)]
Expand All @@ -137,13 +137,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]
Expand Down Expand Up @@ -180,6 +185,31 @@ 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())
.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<Vec<String>, String> {
let url = models_url(&config.base_url);
log::info!("[provider-check] GET {url}");
Expand Down Expand Up @@ -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())?;
Expand Down
5 changes: 3 additions & 2 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
5 changes: 3 additions & 2 deletions openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 为空。',
Expand Down
3 changes: 1 addition & 2 deletions openless-all/app/src/lib/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ const mockCredentialsStatus: CredentialsStatus = {

export interface ProviderCheckResult {
ok: boolean;
modelCount: number;
}

export interface ProviderModelsResult {
Expand Down Expand Up @@ -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> {
Expand Down
12 changes: 6 additions & 6 deletions openless-all/app/src/pages/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
};
Expand Down
Loading