Skip to content

Commit cb60a03

Browse files
committed
让凭据状态按 active provider 判断,修复错误配置提示
get_credentials 不再把 ASR 配置状态硬编码为 Volcengine,而是返回 active provider 与通用 asrConfigured/llmConfigured。前端 provider setup prompt 与 Overview 改为读取通用状态并展示 active provider 名称,避免 Whisper-compatible 用户已配置却持续被提示未配置。配置完成判定统一为 API Key 可空但 endpoint/model 必须存在:云端 ASR 需 endpoint/model,LLM 需 endpoint/model。保留旧字段做兼容过渡。 Constraint: 最小修改原则,保持现有 provider 凭据存储结构不变 Rejected: 只按 ASR API key 标记已配置 | keyless provider 会被误判,且 endpoint/model 缺失仍不可运行 Rejected: 彻底移除旧 volcengineConfigured/arkConfigured 字段 | 需要分阶段兼容前端调用 Confidence: high Scope-risk: moderate Reversibility: clean Directive: Provider configured 表示具备 endpoint/model 基础配置;连接有效性仍由验证按钮负责 Tested: npm run -s build Tested: cargo test -q (74 passed) Related: #223
1 parent 6d1ff0f commit cb60a03

8 files changed

Lines changed: 227 additions & 22 deletions

File tree

openless-all/app/src-tauri/src/commands.rs

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use crate::polish::{LLMError, OpenAICompatibleConfig, OpenAICompatibleLLMProvide
1414
use crate::types::{
1515
ChineseScriptPreference, CredentialsStatus, DictationSession, DictionaryEntry,
1616
HotkeyCapability, HotkeyStatus, OutputLanguagePreference, PolishMode, QaHotkeyBinding,
17-
UserPreferences,
18-
VocabPresetStore, WindowsImeStatus,
17+
UserPreferences, VocabPresetStore, WindowsImeStatus,
1918
};
2019

2120
type CoordinatorState<'a> = State<'a, Arc<Coordinator>>;
@@ -100,16 +99,47 @@ pub fn get_windows_ime_status() -> WindowsImeStatus {
10099
#[tauri::command]
101100
pub fn get_credentials() -> CredentialsStatus {
102101
let snap = CredentialsVault::snapshot();
102+
let active_asr_provider = CredentialsVault::get_active_asr();
103+
let active_llm_provider = CredentialsVault::get_active_llm();
104+
let volcengine_configured = volcengine_configured(&snap);
105+
let asr_configured = asr_configured_for_provider(&active_asr_provider, &snap);
106+
let llm_configured = llm_configured_for_snapshot(&snap);
103107
CredentialsStatus {
104-
volcengine_configured: configured(&snap.volcengine_app_key)
105-
&& configured(&snap.volcengine_access_key)
106-
&& configured(&snap.volcengine_resource_id),
107-
ark_configured: configured(&snap.ark_api_key),
108+
active_asr_provider,
109+
active_llm_provider,
110+
asr_configured,
111+
llm_configured,
112+
volcengine_configured,
113+
ark_configured: llm_configured,
108114
}
109115
}
110116

117+
fn volcengine_configured(snap: &CredentialsSnapshot) -> bool {
118+
configured(&snap.volcengine_app_key)
119+
&& configured(&snap.volcengine_access_key)
120+
&& configured(&snap.volcengine_resource_id)
121+
}
122+
123+
fn asr_configured_for_provider(provider: &str, snap: &CredentialsSnapshot) -> bool {
124+
if provider == "volcengine" {
125+
return volcengine_configured(snap);
126+
}
127+
if provider == crate::asr::local::PROVIDER_ID {
128+
// 本地 ASR 不依赖云端凭据。
129+
return true;
130+
}
131+
configured(&snap.asr_endpoint) && configured(&snap.asr_model)
132+
}
133+
134+
fn llm_configured_for_snapshot(snap: &CredentialsSnapshot) -> bool {
135+
configured(&snap.ark_endpoint) && configured(&snap.ark_model_id)
136+
}
137+
111138
fn configured(field: &Option<String>) -> bool {
112-
field.as_ref().map(|s| !s.is_empty()).unwrap_or(false)
139+
field
140+
.as_ref()
141+
.map(|s| !s.trim().is_empty())
142+
.unwrap_or(false)
113143
}
114144

115145
#[tauri::command]
@@ -851,9 +881,11 @@ fn _ensure_snapshot_used(_: CredentialsSnapshot) {}
851881
#[cfg(test)]
852882
mod tests {
853883
use super::{
854-
asr_transcriptions_url, fetch_provider_models, models_url, parse_model_ids,
855-
persist_settings, ProviderConfig, SettingsWriter,
884+
asr_configured_for_provider, asr_transcriptions_url, fetch_provider_models,
885+
llm_configured_for_snapshot, models_url, parse_model_ids, persist_settings,
886+
ProviderConfig, SettingsWriter,
856887
};
888+
use crate::persistence::CredentialsSnapshot;
857889
use crate::types::{
858890
HotkeyBinding, HotkeyMode, HotkeyTrigger, QaHotkeyBinding, UserPreferences,
859891
};
@@ -869,6 +901,65 @@ mod tests {
869901
qa_refreshes: Mutex<u32>,
870902
}
871903

904+
fn snapshot() -> CredentialsSnapshot {
905+
CredentialsSnapshot::default()
906+
}
907+
908+
#[test]
909+
fn credentials_status_follows_active_asr_provider_requirements() {
910+
let volcengine = CredentialsSnapshot {
911+
volcengine_app_key: Some("app".into()),
912+
volcengine_access_key: Some("access".into()),
913+
volcengine_resource_id: Some("resource".into()),
914+
..snapshot()
915+
};
916+
assert!(asr_configured_for_provider("volcengine", &volcengine));
917+
918+
let whisper_key_only = CredentialsSnapshot {
919+
asr_api_key: Some("key".into()),
920+
..snapshot()
921+
};
922+
assert!(!asr_configured_for_provider("whisper", &whisper_key_only));
923+
924+
let whisper_keyless_ready = CredentialsSnapshot {
925+
asr_endpoint: Some("https://api.openai.com/v1".into()),
926+
asr_model: Some("whisper-1".into()),
927+
..snapshot()
928+
};
929+
assert!(asr_configured_for_provider(
930+
"whisper",
931+
&whisper_keyless_ready
932+
));
933+
934+
assert!(asr_configured_for_provider(
935+
crate::asr::local::PROVIDER_ID,
936+
&snapshot()
937+
));
938+
}
939+
940+
#[test]
941+
fn credentials_status_accepts_keyless_llm_with_endpoint_and_model() {
942+
let keyless_ready = CredentialsSnapshot {
943+
ark_endpoint: Some("http://localhost:11434/v1".into()),
944+
ark_model_id: Some("qwen".into()),
945+
..snapshot()
946+
};
947+
assert!(llm_configured_for_snapshot(&keyless_ready));
948+
949+
let key_without_endpoint = CredentialsSnapshot {
950+
ark_api_key: Some("key".into()),
951+
ark_model_id: Some("qwen".into()),
952+
..snapshot()
953+
};
954+
assert!(!llm_configured_for_snapshot(&key_without_endpoint));
955+
956+
let endpoint_without_model = CredentialsSnapshot {
957+
ark_endpoint: Some("http://localhost:11434/v1".into()),
958+
..snapshot()
959+
};
960+
assert!(!llm_configured_for_snapshot(&endpoint_without_model));
961+
}
962+
872963
impl SettingsWriter for FakeSettingsWriter {
873964
fn write_settings(&self, prefs: UserPreferences) -> Result<(), String> {
874965
*self.saved.lock().unwrap() = Some(prefs);

openless-all/app/src-tauri/src/persistence.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,9 @@ pub struct CredentialsSnapshot {
674674
pub volcengine_app_key: Option<String>,
675675
pub volcengine_access_key: Option<String>,
676676
pub volcengine_resource_id: Option<String>,
677+
pub asr_api_key: Option<String>,
678+
pub asr_endpoint: Option<String>,
679+
pub asr_model: Option<String>,
677680
pub ark_api_key: Option<String>,
678681
pub ark_model_id: Option<String>,
679682
pub ark_endpoint: Option<String>,
@@ -730,13 +733,21 @@ impl CredentialsVault {
730733
save_credentials(&root)
731734
}
732735

736+
pub fn get_active_llm() -> String {
737+
let _guard = credentials_lock().lock();
738+
load_credentials().active.llm
739+
}
740+
733741
pub fn snapshot() -> CredentialsSnapshot {
734742
let _guard = credentials_lock().lock();
735743
let root = load_credentials();
736744
CredentialsSnapshot {
737745
volcengine_app_key: lookup_account(&root, CredentialAccount::VolcengineAppKey),
738746
volcengine_access_key: lookup_account(&root, CredentialAccount::VolcengineAccessKey),
739747
volcengine_resource_id: lookup_account(&root, CredentialAccount::VolcengineResourceId),
748+
asr_api_key: lookup_account(&root, CredentialAccount::AsrApiKey),
749+
asr_endpoint: lookup_account(&root, CredentialAccount::AsrEndpoint),
750+
asr_model: lookup_account(&root, CredentialAccount::AsrModel),
740751
ark_api_key: lookup_account(&root, CredentialAccount::ArkApiKey),
741752
ark_model_id: lookup_account(&root, CredentialAccount::ArkModelId),
742753
ark_endpoint: lookup_account(&root, CredentialAccount::ArkEndpoint),

openless-all/app/src-tauri/src/types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ pub struct CapsulePayload {
548548
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
549549
#[serde(rename_all = "camelCase")]
550550
pub struct CredentialsStatus {
551+
pub active_asr_provider: String,
552+
pub active_llm_provider: String,
553+
pub asr_configured: bool,
554+
pub llm_configured: bool,
555+
// 兼容旧前端字段(逐步迁移中)
551556
pub volcengine_configured: bool,
552557
pub ark_configured: bool,
553558
}

openless-all/app/src/lib/ipc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ const mockHotkeyCapability: HotkeyCapability = {
7171
};
7272

7373
const mockCredentialsStatus: CredentialsStatus = {
74+
activeAsrProvider: 'volcengine',
75+
activeLlmProvider: 'ark',
76+
asrConfigured: true,
77+
llmConfigured: true,
7478
volcengineConfigured: true,
7579
arkConfigured: true,
7680
};

openless-all/app/src/lib/providerSetup.test.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,67 @@ function assertEqual(actual: boolean, expected: boolean, name: string) {
1010
}
1111

1212
assertEqual(
13-
areProvidersConfigured({ volcengineConfigured: true, arkConfigured: true }),
13+
areProvidersConfigured({
14+
activeAsrProvider: 'volcengine',
15+
activeLlmProvider: 'ark',
16+
asrConfigured: true,
17+
llmConfigured: true,
18+
volcengineConfigured: true,
19+
arkConfigured: true,
20+
}),
1421
true,
1522
'configured when ASR and LLM are both ready',
1623
);
1724

1825
assertEqual(
19-
areProvidersConfigured({ volcengineConfigured: false, arkConfigured: true }),
26+
areProvidersConfigured({
27+
activeAsrProvider: 'volcengine',
28+
activeLlmProvider: 'ark',
29+
asrConfigured: false,
30+
llmConfigured: true,
31+
volcengineConfigured: false,
32+
arkConfigured: true,
33+
}),
2034
false,
2135
'not configured when ASR provider is missing',
2236
);
2337

2438
assertEqual(
25-
areProvidersConfigured({ volcengineConfigured: true, arkConfigured: false }),
39+
areProvidersConfigured({
40+
activeAsrProvider: 'volcengine',
41+
activeLlmProvider: 'ark',
42+
asrConfigured: true,
43+
llmConfigured: false,
44+
volcengineConfigured: true,
45+
arkConfigured: false,
46+
}),
2647
false,
2748
'not configured when LLM provider is missing',
2849
);
2950

51+
assertEqual(
52+
areProvidersConfigured({
53+
activeAsrProvider: 'whisper',
54+
activeLlmProvider: 'ark',
55+
asrConfigured: true,
56+
llmConfigured: true,
57+
volcengineConfigured: false,
58+
arkConfigured: true,
59+
}),
60+
true,
61+
'configured when active ASR is non-volcengine but already ready',
62+
);
63+
3064
assertEqual(
3165
shouldShowProviderSetupPrompt(
32-
{ volcengineConfigured: false, arkConfigured: false },
66+
{
67+
activeAsrProvider: 'whisper',
68+
activeLlmProvider: 'ark',
69+
asrConfigured: false,
70+
llmConfigured: false,
71+
volcengineConfigured: false,
72+
arkConfigured: false,
73+
},
3374
null,
3475
),
3576
true,
@@ -38,7 +79,14 @@ assertEqual(
3879

3980
assertEqual(
4081
shouldShowProviderSetupPrompt(
41-
{ volcengineConfigured: false, arkConfigured: false },
82+
{
83+
activeAsrProvider: 'whisper',
84+
activeLlmProvider: 'ark',
85+
asrConfigured: false,
86+
llmConfigured: false,
87+
volcengineConfigured: false,
88+
arkConfigured: false,
89+
},
4290
'1',
4391
),
4492
false,
@@ -47,7 +95,14 @@ assertEqual(
4795

4896
assertEqual(
4997
shouldShowProviderSetupPrompt(
50-
{ volcengineConfigured: true, arkConfigured: true },
98+
{
99+
activeAsrProvider: 'whisper',
100+
activeLlmProvider: 'ark',
101+
asrConfigured: true,
102+
llmConfigured: true,
103+
volcengineConfigured: false,
104+
arkConfigured: true,
105+
},
51106
null,
52107
),
53108
false,

openless-all/app/src/lib/providerSetup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import type { CredentialsStatus } from './types';
33
export const PROVIDER_SETUP_PROMPT_DEFERRED_KEY = 'ol.providerSetupPromptDeferredThisSession';
44

55
export function areProvidersConfigured(credentials: CredentialsStatus): boolean {
6-
return credentials.volcengineConfigured && credentials.arkConfigured;
6+
const asrConfigured = credentials.asrConfigured ?? credentials.volcengineConfigured;
7+
const llmConfigured = credentials.llmConfigured ?? credentials.arkConfigured;
8+
return asrConfigured && llmConfigured;
79
}
810

911
export function shouldShowProviderSetupPrompt(

openless-all/app/src/lib/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ export interface CapsulePayload {
206206
}
207207

208208
export interface CredentialsStatus {
209+
activeAsrProvider: string;
210+
activeLlmProvider: string;
211+
asrConfigured: boolean;
212+
llmConfigured: boolean;
213+
/** 兼容旧字段(过渡期保留)。 */
209214
volcengineConfigured: boolean;
210215
arkConfigured: boolean;
211216
}

openless-all/app/src/pages/Overview.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,32 @@ interface OverviewProps {
2323
onOpenHistory?: () => void;
2424
}
2525

26+
const ASR_NAME_KEY_BY_ID: Record<string, string> = {
27+
volcengine: 'asrVolcengine',
28+
siliconflow: 'asrSiliconflow',
29+
zhipu: 'asrZhipu',
30+
groq: 'asrGroq',
31+
whisper: 'asrWhisper',
32+
'local-qwen3': 'asrLocalQwen3',
33+
};
34+
35+
const LLM_NAME_KEY_BY_ID: Record<string, string> = {
36+
ark: 'ark',
37+
deepseek: 'deepseek',
38+
siliconflow: 'siliconflow',
39+
openai: 'openai',
40+
custom: 'custom',
41+
};
42+
2643
export function Overview({ onOpenHistory }: OverviewProps) {
2744
const { t } = useTranslation();
2845
const modeLabel = useModeLabels();
2946
const [history, setHistory] = useState<DictationSession[]>([]);
3047
const [creds, setCreds] = useState<CredentialsStatus>({
48+
activeAsrProvider: 'volcengine',
49+
activeLlmProvider: 'ark',
50+
asrConfigured: false,
51+
llmConfigured: false,
3152
volcengineConfigured: false,
3253
arkConfigured: false,
3354
});
@@ -64,6 +85,17 @@ export function Overview({ onOpenHistory }: OverviewProps) {
6485
return buckets;
6586
}, [history]);
6687

88+
const asrProviderId = creds.activeAsrProvider || 'volcengine';
89+
const llmProviderId = creds.activeLlmProvider || 'ark';
90+
const asrNameKey = ASR_NAME_KEY_BY_ID[asrProviderId];
91+
const llmNameKey = LLM_NAME_KEY_BY_ID[llmProviderId];
92+
const asrProviderName = asrNameKey
93+
? t(`settings.providers.presets.${asrNameKey}`)
94+
: asrProviderId;
95+
const llmProviderName = llmNameKey
96+
? t(`settings.providers.presets.${llmNameKey}`)
97+
: llmProviderId;
98+
6799
return (
68100
<>
69101
<PageHeader
@@ -75,15 +107,15 @@ export function Overview({ onOpenHistory }: OverviewProps) {
75107
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 18 }}>
76108
<ProviderCard
77109
kind={t('overview.asrKind')}
78-
name={t('overview.asrName')}
79-
subname={t('overview.asrSubname')}
80-
configured={creds.volcengineConfigured}
110+
name={asrProviderName}
111+
subname={asrProviderId}
112+
configured={creds.asrConfigured}
81113
/>
82114
<ProviderCard
83115
kind={t('overview.llmKind')}
84-
name={t('overview.llmName')}
85-
subname={creds.arkConfigured ? t('overview.llmConfigured') : t('overview.llmNotConfigured')}
86-
configured={creds.arkConfigured}
116+
name={llmProviderName}
117+
subname={llmProviderId}
118+
configured={creds.llmConfigured}
87119
/>
88120
</div>
89121

0 commit comments

Comments
 (0)