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
42 changes: 40 additions & 2 deletions openless-all/app/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,38 @@ async fn resolve_beta_manifest_endpoints() -> Result<Vec<url::Url>, String> {
Ok(vec![mirror_url, direct_url])
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetworkCheckResult {
pub online: bool,
pub latency_ms: Option<u64>,
}

#[tauri::command]
pub async fn check_network() -> NetworkCheckResult {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build();
let client = match client {
Ok(c) => c,
Err(_) => return NetworkCheckResult { online: false, latency_ms: None },
};
let start = std::time::Instant::now();
let endpoints = [
"https://apic.openless.top/health",
"https://github.com",
];
for url in &endpoints {
if let Ok(resp) = client.head(*url).send().await {
if resp.status().is_success() || resp.status().is_redirection() {
let ms = start.elapsed().as_millis() as u64;
return NetworkCheckResult { online: true, latency_ms: Some(ms) };
}
}
}
NetworkCheckResult { online: false, latency_ms: None }
}

#[tauri::command]
pub fn get_hotkey_status(coord: CoordinatorState<'_>) -> HotkeyStatus {
coord.hotkey_status()
Expand Down Expand Up @@ -2812,7 +2844,10 @@ pub struct GithubDeviceStartResponse {
#[tauri::command]
pub async fn github_device_flow_start() -> Result<GithubDeviceStartResponse, String> {
let client_id = get_github_oauth_client_id()?;
let client = reqwest::Client::new();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(15))
.build()
.map_err(|e| format!("build http client: {e}"))?;
let resp = client
.post("https://github.com/login/device/code")
.header("Accept", "application/json")
Expand Down Expand Up @@ -2857,7 +2892,10 @@ pub async fn github_device_flow_poll(
device_code: String,
) -> Result<GithubDevicePollResult, String> {
let client_id = get_github_oauth_client_id()?;
let client = reqwest::Client::new();
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(15))
.build()
.map_err(|e| format!("build http client: {e}"))?;
let token_resp = client
.post("https://github.com/login/oauth/access_token")
.header("Accept", "application/json")
Expand Down
1 change: 1 addition & 0 deletions openless-all/app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ pub fn run() {
commands::set_update_channel,
commands::fetch_latest_beta_release,
commands::app_check_update_with_channel,
commands::check_network,
commands::get_hotkey_status,
commands::get_hotkey_capability,
commands::set_shortcut_recording_active,
Expand Down
10 changes: 9 additions & 1 deletion openless-all/app/src/components/AutoUpdate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
import { isTauri, restartApp } from '../lib/ipc';
import { Btn } from '../pages/_atoms';

const UPDATE_CHECK_TIMEOUT_MS = 8_000; // 缩短超时,让镜像站慢的情况能更快 fallback
const UPDATE_CHECK_TIMEOUT_MS = 15_000;

interface AppUpdateMetadata {
rid: number;
Expand Down Expand Up @@ -44,6 +44,7 @@ export interface UseAutoUpdate {
contentLength: number | null;
checking: boolean;
busy: boolean;
errorMessage: string | null;
/** 触发"检查更新"。如果发现新版本,状态变为 'available',需要 caller 渲染对话框让用户确认下载。 */
checkForUpdates: () => Promise<void>;
/** 用户在对话框里确认 → 下载 + 安装。完成后状态变为 'downloaded',等用户点重启。 */
Expand All @@ -58,6 +59,7 @@ export function useAutoUpdate(): UseAutoUpdate {
const [version, setVersion] = useState('');
const [downloaded, setDownloaded] = useState(0);
const [contentLength, setContentLength] = useState<number | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

const checking = status === 'checking';
const busy = status === 'downloading' || status === 'installing';
Expand Down Expand Up @@ -89,6 +91,7 @@ export function useAutoUpdate(): UseAutoUpdate {
const checkForUpdates = async () => {
setStatus('checking');
setVersion('');
setErrorMessage(null);
resetProgress();
await closeUpdate();
try {
Expand Down Expand Up @@ -120,6 +123,8 @@ export function useAutoUpdate(): UseAutoUpdate {
setStatus('available');
} catch (error) {
console.error('[updater] failed to check update', error);
const msg = error instanceof Error ? error.message : String(error);
setErrorMessage(msg);
setStatus('error');
}
};
Expand All @@ -146,6 +151,8 @@ export function useAutoUpdate(): UseAutoUpdate {
setStatus('downloaded');
} catch (error) {
console.error('[updater] failed to install update', error);
const msg = error instanceof Error ? error.message : String(error);
setErrorMessage(msg);
await closeUpdate();
setStatus('error');
}
Expand All @@ -167,6 +174,7 @@ export function useAutoUpdate(): UseAutoUpdate {
contentLength,
checking,
busy,
errorMessage,
checkForUpdates,
installUpdate,
dismissDialog,
Expand Down
11 changes: 5 additions & 6 deletions openless-all/app/src/components/Capsule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,12 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }:
const ambient = state === 'recording' ? Math.min(1, Math.max(0, level)) : 0;
const scale = os === 'win' ? 1 : 1 + ambient * 0.018;
const shadowAlpha = 0.20 + ambient * 0.10;
const useBackdrop = true;

return (
// 假毛玻璃:半透明白底 + .ol-frost 噪点纹理 + 内描边高光 + 柔和阴影。
// 不写 backdrop-filter —— webview 模糊不了透明窗口背后的桌面(Tauri 上游限制)。
<div
className="ol-frost"
style={{
display: 'inline-flex',
alignItems: 'center',
Expand All @@ -258,13 +260,10 @@ function Pill({ os, state, level, insertedChars, message, onCancel, onConfirm }:
height: metrics.height,
boxSizing: metrics.boxSizing,
borderRadius: 999,
background: 'rgba(255, 255, 255, 0.85)',
backdropFilter: useBackdrop ? 'blur(28px) saturate(180%)' : 'none',
WebkitBackdropFilter: useBackdrop ? 'blur(28px) saturate(180%)' : 'none',
border: '1px solid rgba(255, 255, 255, 0.55)',
boxShadow: os === 'win'
? `0 10px 24px -14px rgba(0, 0, 0, ${(0.24 + ambient * 0.06).toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 0.5px 0 rgba(255, 255, 255, 0.55)`
: `0 18px 50px -10px rgba(0, 0, 0, ${shadowAlpha.toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 0.5px 0 rgba(255, 255, 255, 0.55)`,
? `0 10px 24px -14px rgba(0, 0, 0, ${(0.24 + ambient * 0.06).toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.8)`
: `0 18px 50px -10px rgba(0, 0, 0, ${shadowAlpha.toFixed(3)}), 0 0 0 0.5px rgba(0, 0, 0, 0.08), inset 0 1px 0 0 rgba(255, 255, 255, 0.8)`,
color: 'var(--ol-ink)',
fontFamily: 'var(--ol-font-sans)',
transform: `scale(${scale.toFixed(4)})`,
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ export const en: typeof zhCN = {
networkLabel: 'Network',
networkDesc: 'Required for cloud ASR / LLM calls. Disable for local-only mode.',
networkOk: 'Available',
networkOffline: 'Unavailable',
checking: 'Checking…',
granted: 'Granted',
notApplicable: 'Not required',
Expand Down Expand Up @@ -753,6 +754,7 @@ export const en: typeof zhCN = {
checkingUpdate: 'Checking…',
upToDate: 'You are already on the latest version.',
updateError: 'Update check or install failed. Please try again later.',
retryBtn: 'Retry',
openReleases: 'Open Releases',
source: 'Source',
docs: 'Docs',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ export const ja: typeof zhCN = {
networkLabel: 'ネットワーク',
networkDesc: 'クラウド ASR / LLM 呼び出しに必要。ローカルモードでは無効化可能。',
networkOk: '利用可能',
networkOffline: '利用不可',
checking: '確認中…',
granted: '許可済み',
notApplicable: '権限不要',
Expand Down Expand Up @@ -755,6 +756,7 @@ export const ja: typeof zhCN = {
checkingUpdate: '確認中…',
upToDate: '現在最新バージョンです。',
updateError: '確認またはアップデートに失敗しました。後で再試行してください。',
retryBtn: '再試行',
openReleases: 'Releases を開く',
source: 'ソース',
docs: 'ドキュメント',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,7 @@ export const ko: typeof zhCN = {
networkLabel: '네트워크',
networkDesc: '클라우드 ASR / LLM 호출에 필요. 로컬 모드에서는 비활성화 가능.',
networkOk: '사용 가능',
networkOffline: '사용 불가',
checking: '확인 중…',
granted: '허용됨',
notApplicable: '권한 불필요',
Expand Down Expand Up @@ -755,6 +756,7 @@ export const ko: typeof zhCN = {
checkingUpdate: '확인 중…',
upToDate: '현재 최신 버전입니다.',
updateError: '확인 또는 업데이트에 실패했습니다. 잠시 후 다시 시도하세요.',
retryBtn: '다시 시도',
openReleases: 'Releases 열기',
source: '소스',
docs: '문서',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ export const zhCN = {
networkLabel: '网络',
networkDesc: '云端 ASR / LLM 必需,本地模式可关。',
networkOk: '可用',
networkOffline: '不可用',
checking: '检查中…',
granted: '已授权',
notApplicable: '无需授权',
Expand Down Expand Up @@ -751,6 +752,7 @@ export const zhCN = {
checkingUpdate: '检查中…',
upToDate: '当前已是最新版本。',
updateError: '检查或更新失败,请稍后重试。',
retryBtn: '重试',
openReleases: '打开 Releases',
source: '源码',
docs: '文档',
Expand Down
2 changes: 2 additions & 0 deletions openless-all/app/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ export const zhTW: typeof zhCN = {
networkLabel: '網絡',
networkDesc: '雲端 ASR / LLM 調用所必需。本地模式可關閉。',
networkOk: '可用',
networkOffline: '不可用',
checking: '檢查中…',
granted: '已授權',
notApplicable: '無需授權',
Expand Down Expand Up @@ -753,6 +754,7 @@ export const zhTW: typeof zhCN = {
checkingUpdate: '檢查中…',
upToDate: '當前已是最新版本。',
updateError: '檢查或更新失敗,請稍後重試。',
retryBtn: '重試',
openReleases: '打開 Releases',
source: '源碼',
docs: '文檔',
Expand Down
12 changes: 12 additions & 0 deletions openless-all/app/src/lib/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,18 @@ export function getWindowsImeStatus(): Promise<WindowsImeStatus> {
return invokeOrMock('get_windows_ime_status', undefined, () => mockWindowsImeStatus);
}

export interface NetworkCheckResult {
online: boolean;
latencyMs: number | null;
}

export function checkNetwork(): Promise<NetworkCheckResult> {
return invokeOrMock<NetworkCheckResult>('check_network', undefined, () => ({
online: true,
latencyMs: 42,
}));
}

export function listMicrophoneDevices(): Promise<MicrophoneDevice[]> {
return invokeOrMock('list_microphone_devices', undefined, () => mockMicrophoneDevices);
}
Expand Down
9 changes: 7 additions & 2 deletions openless-all/app/src/pages/Marketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -580,8 +580,13 @@ export function Marketplace() {

{loadError && (
<Card padding={16} style={{ marginBottom: 12, borderColor: 'var(--ol-err)' }}>
<div style={{ fontSize: 12, color: 'var(--ol-err)' }}>
{t('marketplace.loadFailed', { err: loadError })}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
<div style={{ fontSize: 12, color: 'var(--ol-err)', flex: 1, wordBreak: 'break-word' }}>
{t('marketplace.loadFailed', { err: loadError })}
</div>
<Btn variant="blue" size="sm" onClick={() => void refresh()}>
{t('common.retry') ?? '重试'}
</Btn>
</div>
</Card>
)}
Expand Down
12 changes: 5 additions & 7 deletions openless-all/app/src/pages/QaPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export function QaPanel() {
}, [messages, status]);

return (
<div style={shellStyle}>
<div className="ol-frost" style={shellStyle}>
<Toolbar pinned={pinned} onTogglePin={onTogglePin} onClose={onClose} />
<div ref={scrollRef} style={contentStyle}>
{messages.length === 0 && status === 'idle' && (
Expand Down Expand Up @@ -611,20 +611,18 @@ function truncate(text: string, max: number): string {

// ── 样式 ──────────────────────────────────────────────────────────────

// 假毛玻璃外壳:玻璃质感(体渐变 + 高光扫面 + 噪点颗粒)全部由 .ol-frost 提供;
// 这里只管布局 + 内描边高光 + 柔和阴影。webview 模糊不了透明窗口背后的桌面
// (Tauri 上游限制),所以不写 background / backdrop-filter。
const shellStyle: CSSProperties = {
width: '100%',
height: '100vh',
display: 'flex',
flexDirection: 'column',
borderRadius: 14,
overflow: 'hidden',
// 浮窗 focus:false 在 macOS 上会让 backdrop-filter 不工作(透到桌面文字),所以
// 改成接近不透明的实色背景。blur 仅作锦上添花,不再依赖它保证可读性。
background: 'rgba(255, 255, 255, 0.97)',
backdropFilter: 'blur(24px) saturate(180%)',
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
border: '0.5px solid rgba(0, 0, 0, 0.08)',
boxShadow: 'var(--ol-shadow-lg)',
boxShadow: 'var(--ol-shadow-lg), inset 0 1px 0 0 rgba(255, 255, 255, 0.9)',
fontFamily: 'var(--ol-font-sans)',
color: 'var(--ol-ink)',
};
Expand Down
21 changes: 18 additions & 3 deletions openless-all/app/src/pages/settings/AboutUpdateControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@ export function AboutUpdateControl({ tagline }: { tagline: string }) {
{u.checking ? t('settings.about.checkingUpdate') : t('settings.about.checkUpdateBtn')}
</Btn>
</div>
{(u.status === 'none' || u.status === 'error') && (
<div style={{ fontSize: 11, color: u.status === 'error' ? 'var(--ol-err)' : 'var(--ol-ink-4)', marginTop: 4 }}>
{u.status === 'none' ? t('settings.about.upToDate') : t('settings.about.updateError')}
{u.status === 'none' && (
<div style={{ fontSize: 11, color: 'var(--ol-ink-4)', marginTop: 4 }}>
{t('settings.about.upToDate')}
</div>
)}
{u.status === 'error' && (
<div style={{ marginTop: 4 }}>
<div style={{ fontSize: 11, color: 'var(--ol-err)' }}>
{t('settings.about.updateError')}
</div>
{u.errorMessage && (
<div style={{ fontSize: 10.5, color: 'var(--ol-ink-4)', marginTop: 2, wordBreak: 'break-word' }}>
{u.errorMessage}
</div>
)}
<Btn variant="ghost" size="sm" onClick={u.checkForUpdates} style={{ marginTop: 4 }}>
{t('settings.about.retryBtn') ?? t('common.retry') ?? '重试'}
</Btn>
</div>
)}
{isDialogStatus(u.status) && (
Expand Down
Loading
Loading