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
9 changes: 4 additions & 5 deletions openless-all/app/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,11 +1150,6 @@ async fn validate_asr_transcription(config: &ProviderConfig, model: &str) -> Res

fn asr_transcriptions_url(base_url: &str) -> Result<String, String> {
let parsed = reqwest::Url::parse(base_url.trim()).map_err(|_| "endpointInvalid".to_string())?;
let host = parsed.host_str().unwrap_or_default();
let localhost = host.eq_ignore_ascii_case("localhost") || host == "127.0.0.1";
if parsed.scheme() != "https" && !localhost {
return Err("endpointMustUseHttps".to_string());
}

// Work on the URL path only so we don't corrupt query parameters.
let mut url = parsed.clone();
Expand Down Expand Up @@ -3756,6 +3751,10 @@ mod tests {
asr_transcriptions_url("https://api.openai.com/v1?api-version=2024-12-01").unwrap(),
"https://api.openai.com/v1/audio/transcriptions?api-version=2024-12-01"
);
assert_eq!(
asr_transcriptions_url("http://192.168.1.10:8000/v1").unwrap(),
"http://192.168.1.10:8000/v1/audio/transcriptions"
);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ export const en: typeof zhCN = {
validateSuccess: 'Connection check passed.',
validateFailed: 'Connection check failed.',
providerHttpStatus: 'Provider returned HTTP {{status}}. Check the API key permissions or endpoint.',
endpointMustUseHttps: 'Endpoint must use HTTPS (localhost/127.0.0.1 are allowed for local testing).',
endpointMustUseHttps: 'HTTP endpoints are allowed, but API keys and audio content may leak in transit.',
endpointInvalid: 'Endpoint format is invalid.',
responseTooLarge: 'Provider response is too large to validate safely.',
asrInvalidJson: 'ASR response is not valid JSON.',
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ export const ja: typeof zhCN = {
validateSuccess: '接続チェックに合格しました。',
validateFailed: '接続チェックに失敗しました。',
providerHttpStatus: 'サプライヤーが {{status}} を返しました。API Key 権限またはエンドポイントを確認してください。',
endpointMustUseHttps: 'Endpoint は HTTPS を使用する必要があります(localhost/127.0.0.1 を除く)。',
endpointMustUseHttps: 'HTTP Endpoint も使用できますが、API Key と音声内容が通信中に漏えいする可能性があります。',
endpointInvalid: 'Endpoint の形式が無効です。',
responseTooLarge: 'サプライヤーの応答が大きすぎるため、安全のため検証を停止しました。',
asrInvalidJson: 'ASR の応答が有効な JSON ではありません。',
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ export const ko: typeof zhCN = {
validateSuccess: '연결 확인을 통과했습니다.',
validateFailed: '연결 확인에 실패했습니다.',
providerHttpStatus: '공급자가 {{status}} 를 반환했습니다. API Key 권한 또는 Endpoint 를 확인해 주세요.',
endpointMustUseHttps: 'Endpoint 는 HTTPS 를 사용해야 합니다(localhost/127.0.0.1 제외).',
endpointMustUseHttps: 'HTTP Endpoint 를 사용할 수 있지만, API Key 와 음성 내용이 전송 중 유출될 수 있습니다.',
endpointInvalid: 'Endpoint 형식이 올바르지 않습니다.',
responseTooLarge: '공급자 응답이 너무 커서 안전을 위해 검증을 중단했습니다.',
asrInvalidJson: 'ASR 응답이 유효한 JSON 이 아닙니다.',
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ export const zhCN = {
validateSuccess: '连接检查通过。',
validateFailed: '连接检查未通过。',
providerHttpStatus: '供应商接口返回 {{status}},请检查 API Key 权限或 Endpoint。',
endpointMustUseHttps: 'Endpoint 必须使用 HTTPS(本地 localhost/127.0.0.1 测试除外)。',
endpointMustUseHttps: '允许使用 HTTP Endpoint,但请注意:API Key 和音频内容可能在传输中泄漏。',
endpointInvalid: 'Endpoint 格式不合法。',
responseTooLarge: '供应商响应过大,已停止验证以保证安全。',
asrInvalidJson: 'ASR 响应不是有效 JSON。',
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ export const zhTW: typeof zhCN = {
validateSuccess: '連接檢查通過。',
validateFailed: '連接檢查未通過。',
providerHttpStatus: '供應商接口返回 {{status}},請檢查 API Key 權限或 Endpoint。',
endpointMustUseHttps: 'Endpoint 必須使用 HTTPS(本地 localhost/127.0.0.1 測試除外)。',
endpointMustUseHttps: '允許使用 HTTP Endpoint,但請注意:API Key 和音訊內容可能在傳輸中外洩。',
endpointInvalid: 'Endpoint 格式不合法。',
responseTooLarge: '供應商響應過大,已停止驗證以保證安全。',
asrInvalidJson: 'ASR 響應不是有效 JSON。',
Expand Down
93 changes: 51 additions & 42 deletions openless-all/app/src/pages/settings/ProvidersSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -714,55 +714,64 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue

const inputType = mask && !revealed ? 'password' : 'text';
const disabled = !loaded;
const showInsecureAsrEndpointWarning = account === 'asr.endpoint'
&& value.trim().toLowerCase().startsWith('http://');

return (
<SettingRow label={label}>
<div style={{ display: 'flex', gap: 6, alignItems: 'center', width: '100%', maxWidth: 420 }}>
<input
type={inputType}
value={value}
placeholder={loaded ? placeholder : t('common.loading')}
onChange={handleChange}
onBlur={onBlur}
disabled={disabled}
style={{ ...inputStyle, fontFamily: mono ? 'var(--ol-font-mono)' : 'inherit' }}
/>
{defaultValue && !value && loaded && (
<button onClick={fillDefault} title={t('settings.providers.fillDefault')} style={iconBtnStyle} disabled={!loaded}>
<Icon name="check" size={13} />
</button>
)}
{trailing}
{mask && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 5, width: '100%', maxWidth: 420 }}>
<div style={{ display: 'flex', gap: 6, alignItems: 'center', width: '100%' }}>
<input
type={inputType}
value={value}
placeholder={loaded ? placeholder : t('common.loading')}
onChange={handleChange}
onBlur={onBlur}
disabled={disabled}
style={{ ...inputStyle, fontFamily: mono ? 'var(--ol-font-mono)' : 'inherit' }}
/>
{defaultValue && !value && loaded && (
<button onClick={fillDefault} title={t('settings.providers.fillDefault')} style={iconBtnStyle} disabled={!loaded}>
<Icon name="check" size={13} />
</button>
)}
{trailing}
{mask && (
<button
onClick={() => setRevealed(r => !r)}
title={revealed ? t('common.hide') : t('common.show')}
style={iconBtnStyle}
disabled={disabled}
>
<Icon name="eye" size={14} />
</button>
)}
<button
onClick={() => setRevealed(r => !r)}
title={revealed ? t('common.hide') : t('common.show')}
onClick={onCopy}
title={t('common.copy')}
style={iconBtnStyle}
disabled={disabled}
disabled={!value || disabled}
>
<Icon name="eye" size={14} />
<Icon name="copy" size={14} />
</button>
)}
<button
onClick={onCopy}
title={t('common.copy')}
style={iconBtnStyle}
disabled={!value || disabled}
>
<Icon name="copy" size={14} />
</button>
{/* readError 是字段无法读取的持续错误,留在原位提示用户该字段不可用;
其它瞬态状态(saving / saved / saveError / copied / copyError)都通过
emitSaved 发到右上角统一 toast,不再内联占位。 */}
{status === 'readError' && (
<span
style={{
fontSize: 11,
color: 'var(--ol-warn)',
whiteSpace: 'nowrap',
}}
>
{t('settings.providers.readFailed')}
{/* readError 是字段无法读取的持续错误,留在原位提示用户该字段不可用;
其它瞬态状态(saving / saved / saveError / copied / copyError)都通过
emitSaved 发到右上角统一 toast,不再内联占位。 */}
{status === 'readError' && (
<span
style={{
fontSize: 11,
color: 'var(--ol-warn)',
whiteSpace: 'nowrap',
}}
>
{t('settings.providers.readFailed')}
</span>
)}
</div>
{showInsecureAsrEndpointWarning && (
<span style={{ fontSize: 11, color: 'var(--ol-warn)', lineHeight: 1.45 }}>
{t('settings.providers.endpointMustUseHttps')}
</span>
)}
</div>
Expand Down
Loading