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
4 changes: 2 additions & 2 deletions openless-all/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openless-all/app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "openless-app",
"private": true,
"version": "1.3.11",
"version": "1.3.12",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "openless"
version = "1.3.11"
version = "1.3.12"
description = "OpenLess — local voice input that types where your cursor is"
authors = ["OpenLess"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/src/coding_agent/opencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! [`CodingAgentEvent::Completed`],`cost_usd = None`(OpenCode CLI 不在 JSON 里给单次成本)。

use std::process::Stdio;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;

Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/src/coordinator/dictation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ fn finalize_polished_text(
polished: String,
translation_active: bool,
_raw_uses_llm: bool,
mode: PolishMode,
_mode: PolishMode,
polish_error: &Option<String>,
chinese_script_preference: crate::types::ChineseScriptPreference,
correction_rules: &[crate::types::CorrectionRule],
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "OpenLess",
"version": "1.3.11",
"version": "1.3.12",
"identifier": "com.openless.app",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
137 changes: 65 additions & 72 deletions openless-all/app/src/components/AutoUpdate.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// 自动更新共用模块 — Settings 的"关于"section 和 AutoUpdateGate 共用同一套
// 状态机 + 对话框 UI。各自调用 useAutoUpdate(),dialog 渲染条件相同。
// 自动更新共用模块 — Settings 的"关于"section 和 footer 按钮共用同一套
// 状态机 + 对话框 UI。两边各自调用 useAutoUpdate(),dialog 渲染条件相同。
//
// 渠道感知:check 走 appCheckUpdateWithChannel()(Rust 按渠道拼 manifest URL)。
// 桌面:download/install 复用 plugin-updater 的 Update 类。
// Android:download/install 走 appDownloadAndInstallAndroidUpdate(minisign + 系统安装器)。
// Android 后台 Gate:发现更新后 autoInstallAndroid 跳过确认,直接下载并打开系统安装器。

import { useEffect, useRef, useState } from 'react';
import type { DownloadEvent } from '@tauri-apps/plugin-updater';
Expand Down Expand Up @@ -71,8 +70,6 @@ type AndroidUpdatePayload = {
export function useAutoUpdate(): UseAutoUpdate {
const updateRef = useRef<Update | null>(null);
const androidUpdateRef = useRef<AndroidUpdatePayload | null>(null);
/** True only while this hook instance initiated an Android download/install. */
const androidDownloadActiveRef = useRef(false);
const [status, setStatus] = useState<UpdateStatus>('idle');
const [version, setVersion] = useState('');
const [downloaded, setDownloaded] = useState(0);
Expand Down Expand Up @@ -110,7 +107,6 @@ export function useAutoUpdate(): UseAutoUpdate {
contentLength: number | null;
phase: string;
}>('android-update:progress', (event) => {
if (!androidDownloadActiveRef.current) return;
setDownloaded(event.payload.downloaded);
setContentLength(event.payload.contentLength);
if (event.payload.phase === 'installing') {
Expand Down Expand Up @@ -141,62 +137,6 @@ export function useAutoUpdate(): UseAutoUpdate {
androidUpdateRef.current = { url, signature, version: metadata.version };
};

const installAndroidUpdate = async () => {
const payload = androidUpdateRef.current;
if (!payload) return;
resetProgress();
setStatus('downloading');
androidDownloadActiveRef.current = true;
try {
await appDownloadAndInstallAndroidUpdate(payload);
androidUpdateRef.current = null;
setStatus('downloaded');
} catch (error) {
console.error('[updater] failed to install android update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] android install failed (v${payload.version}): ${msg}`);
setErrorMessage(msg);
setStatus('installError');
} finally {
androidDownloadActiveRef.current = false;
}
};

const installUpdate = async () => {
if (isAndroid()) {
await installAndroidUpdate();
return;
}

const update = updateRef.current;
if (!update) return;
resetProgress();
setStatus('downloading');
try {
await update.download((event: DownloadEvent) => {
if (event.event === 'Started') {
resetProgress();
setContentLength(event.data.contentLength ?? null);
} else if (event.event === 'Progress') {
setDownloaded(value => value + event.data.chunkLength);
} else if (event.event === 'Finished') {
setStatus('installing');
}
});
setStatus('installing');
await update.install();
await closeUpdate();
setStatus('downloaded');
} catch (error) {
console.error('[updater] failed to install update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] install failed (v${update.version}): ${msg}`);
setErrorMessage(msg);
await closeUpdate();
setStatus('installError');
}
};

const checkForUpdates = async (channel?: UpdateChannel, options?: CheckUpdateOptions) => {
setStatus('checking');
setVersion('');
Expand All @@ -221,9 +161,20 @@ export function useAutoUpdate(): UseAutoUpdate {
setVersion(metadata.version);
if (options?.autoInstallAndroid) {
try {
await installAndroidUpdate();
// 复用 storeAndroidMetadata 的 rawJson 解析(提取 url/signature/version)
const raw = metadata.rawJson ?? {};
const url = typeof raw.url === 'string' ? raw.url : '';
const signature = typeof raw.signature === 'string' ? raw.signature : '';
if (!url || !signature) {
console.warn('[auto-update] android manifest missing url/signature, falling back to manual update');
setStatus('available');
return;
}
await appDownloadAndInstallAndroidUpdate({ url, signature, version: metadata.version });
setStatus('downloaded');
} catch (error) {
console.warn('[auto-update] android auto-install failed', error);
setStatus('available');
}
return;
}
Expand All @@ -250,6 +201,55 @@ export function useAutoUpdate(): UseAutoUpdate {
}
};

const installUpdate = async () => {
if (isAndroid()) {
const payload = androidUpdateRef.current;
if (!payload) return;
resetProgress();
setStatus('downloading');
try {
await appDownloadAndInstallAndroidUpdate(payload);
androidUpdateRef.current = null;
setStatus('downloaded');
} catch (error) {
console.error('[updater] failed to install android update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] android install failed (v${payload.version}): ${msg}`);
setErrorMessage(msg);
setStatus('installError');
}
return;
}

const update = updateRef.current;
if (!update) return;
resetProgress();
setStatus('downloading');
try {
await update.download((event: DownloadEvent) => {
if (event.event === 'Started') {
resetProgress();
setContentLength(event.data.contentLength ?? null);
} else if (event.event === 'Progress') {
setDownloaded(value => value + event.data.chunkLength);
} else if (event.event === 'Finished') {
setStatus('installing');
}
});
setStatus('installing');
await update.install();
await closeUpdate();
setStatus('downloaded');
} catch (error) {
console.error('[updater] failed to install update', error);
const msg = error instanceof Error ? error.message : String(error);
void logClientError(`[updater] install failed (v${update.version}): ${msg}`);
setErrorMessage(msg);
await closeUpdate();
setStatus('installError');
}
};

const dismissDialog = async () => {
if (busy) return;
await closeUpdate();
Expand Down Expand Up @@ -301,17 +301,10 @@ export function UpdateDialog({
const installing = status === 'installing';
const installError = status === 'installError';
const androidInstalled = isAndroid() && status === 'downloaded';
const installLabel = isAndroid()
? t('settings.about.updateDialog.androidInstall')
: t('settings.about.updateDialog.install');
return (
<div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.18)', display: 'grid', placeItems: 'center', zIndex: 40 }}>
<div style={{ width: 360, borderRadius: 16, background: 'var(--ol-surface)', border: '0.5px solid var(--ol-line-strong)', boxShadow: '0 18px 42px rgba(0,0,0,0.18)', padding: 18 }}>
<div style={{ fontSize: 15, fontWeight: 650, marginBottom: 8 }}>
{androidInstalled
? t('settings.about.updateDialog.androidInstalled.title')
: t(`settings.about.updateDialog.${status}.title`)}
</div>
<div style={{ fontSize: 15, fontWeight: 650, marginBottom: 8 }}>{t(`settings.about.updateDialog.${status}.title`)}</div>
<div style={{ fontSize: 12, color: 'var(--ol-ink-3)', lineHeight: 1.6, marginBottom: 14, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{androidInstalled
? t('settings.about.updateDialog.androidInstalled.desc', { version, defaultValue: '系统安装器已打开,请按提示完成安装。安装后重新打开 OpenLess 即可使用 {{version}}。' })
Expand All @@ -335,7 +328,7 @@ export function UpdateDialog({
)}
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
{status === 'available' && <Btn size="sm" onClick={onClose}>{t('common.cancel')}</Btn>}
{status === 'available' && <Btn variant="blue" size="sm" onClick={onInstall}>{installLabel}</Btn>}
{status === 'available' && <Btn variant="blue" size="sm" onClick={onInstall}>{t('settings.about.updateDialog.install')}</Btn>}
{(downloading || installing) && <Btn size="sm" disabled>{installing ? t('settings.about.updateDialog.installingLabel') : t('settings.about.updateDialog.downloadingLabel')}</Btn>}
{status === 'downloaded' && <Btn size="sm" onClick={onClose}>{t('settings.about.updateDialog.later')}</Btn>}
{status === 'downloaded' && !androidInstalled && <Btn variant="blue" size="sm" onClick={restartApp}>{t('settings.about.updateDialog.restartNow')}</Btn>}
Expand Down
Loading