* fix(windows): capsule/qa 透明窗口 acrylic 兜底 + CSS 可读性兜底
根因: tauri.conf.json 三个窗口都 transparent: true,但 lib.rs 只对 main
窗口调用 apply_mica。capsule / qa 在 Windows 上是裸 transparent,
Webview2 的 backdrop-filter 只能模糊页面内部 DOM 不能模糊 OS 桌面,
导致用户看到的胶囊 / QA 浮窗几乎完全透出桌面壁纸。
macOS 上 NSVisualEffectView (HudWindow vibrancy) 直接由系统合成器
混合桌面,所以 macOS 不需要额外处理。
修复:
1. lib.rs 在 setup 时对 capsule / qa webview 调用 apply_acrylic(
color=(30,32,38,140)) — 选 Acrylic 而非 Mica 因为 Acrylic 支持
Win10,更适合小浮窗。失败仅 log::warn 不 panic。
2. global.css 末尾追加 @supports not (backdrop-filter) 和
@media (prefers-reduced-transparency: reduce) 兜底,给 html/body
设实色背景。组件层 inline style 无法被覆盖,这只是二级防护。
Note: Capsule.tsx 和 FloatingShell.tsx 的 inline alpha 保留不动;
Acrylic 兜底通常足够。如果 Win10 实测仍偏透(Mica 不支持 Win10),
后续 PR 可考虑给 main 也加 Acrylic 作为 Mica fallback。
* style(windows): drop global html/body fallback rules from acrylic PR
Local code review flagged that `html, body { background: ... }` applies
to ALL windows including `main`, which would clobber the macOS
NSVisualEffectView vibrancy in `prefers-reduced-transparency: reduce`
mode.
Re-analysis confirms both rules are speculative:
- @supports never fires on modern Webview2 / WKWebView / Chromium
(all support backdrop-filter)
- prefers-reduced-transparency triggers OS-level vibrancy neutralization
on macOS already, but the CSS rule would still wrongly land on main
Acrylic in lib.rs is the canonical fix for the user-reported P0
(capsule/qa 透明遮罩看不清). The CSS layer is dropped to keep the PR
scoped to a single concern and avoid the cross-window collateral.
Refs review of #487
---------
Co-authored-by: baiqing <lbx12309@icloud.com>
After repo transfer to Open-Less org, author_association == 'OWNER' no longer matches any human user on org repos. Expand the gate to [OWNER, MEMBER] so any org member can trigger @claude. No other changes.
* feat(settings): cross-platform parity for local ASR
- AdvancedSection: warning collapses to inline 11px amber badge in
the title row's top-right; the enable-confirm popup is now a
centered fixed modal with backdrop blur(8px) and click-outside-
to-cancel (busy-guarded).
- Symmetric rows by platform — fixes the #400 bug where the Qwen3
row was rendered on Windows even though all qwen_engine /
local_provider / cache modules are #[cfg(target_os = "macos")],
so Enable on Windows would always error:
macOS: Qwen3 row enabled / Foundry row hidden
Windows: Foundry row enabled / Qwen3 row disabled with
notSupportedHere desc
- Providers ASR Card: replaces the plain-text localAsrActiveNotice
branch with an always-rendered dropdown that includes the
platform-native local engine (macOS=Qwen3, Windows=Foundry) as a
disabled option, plus a takeover hint underneath. Entire
dropdown locks when a local engine is active, so it can only be
released via Advanced (matches "启动之后供应商将被接管" UX).
- onLlmProviderChange / onAsrProviderChange now emit saving →
saved through emitSaved. Data was already being persisted; the
saved-toast was missing.
- LocalAsr embedded: hide Qwen3 model management (mirror /
engine-status / model list) on Windows — dead UI without the
Qwen3 backend. The shared error Card is kept unconditional
since Foundry handlers also call setError.
- i18n × 5 (zh-CN/zh-TW/en/ja/ko): shorten localAsrWarningShort;
rewrite qwen3Desc and foundryDesc to '启动之后,ASR 提供商将被接管';
add notSupportedHere and localAsrTakeoverHint.
* fix(settings): clear saving toast on provider-switch failure
pr_agent #403 caught: emitSaved('saving') with no try/catch — if
setActiveLlmProvider / setActiveAsrProvider / any later credential
write throws, the right-corner toast stays stuck on saving forever.
Wrap both onLlmProviderChange and onAsrProviderChange bodies in
try/catch. On error, emit 'failed' (and re-throw) only if seq still
matches — stale calls superseded by a newer switch shouldn't
overwrite the newer call's own saving state.
* fix(settings): provider-card visual cleanup + log-export permission + pr_agent #403
Fixes piled up in user feedback during local verify of the previous
commit; bundled into one commit to keep #403 small.
- capabilities/default.json: add `dialog:default` permission. The
About → Export error log button always failed because the Tauri 2
capabilities did not allow `tauri-plugin-dialog`'s `save()` command;
the JS rejected before export_error_log was ever invoked.
- ProvidersSection ASR Card (pr_agent #403 'Wrong active label'):
when committedAsrProvider is a local engine, selectedValue now
reflects the actually-active engine instead of being forced to
platformLocalAsr. Cross-machine config sync (macOS profile with
foundry-local-whisper / Windows profile with local-qwen3) used to
make the dropdown lie about which backend is running. Anomalous
local engines are added as disabled <option>s so the controlled
<select> still finds its value and doesn't fall back to the first
option.
- ProvidersSection: drop verbose desc on LLM and ASR provider rows.
'选择后将自动填入 Base URL 默认值。' is ~16 Chinese chars and
always wraps inside the 180px label column, leaving a stranded
second line on the left — the dropdown itself already conveys the
semantic, the desc was redundant.
- AdvancedSection: replace the [启用] / [已启用] Btn with the
shared Toggle component for Qwen3 and Foundry rows. Toggle off
switches back to volcengine. The Qwen3 row's Toggle truthfully
reflects committed state on Windows (so a cross-platform anomaly
shows ON even though it is non-interactive there). The standalone
'禁用本地 ASR' escape row is now only rendered when no inline
Toggle can disable the active engine — i.e., Linux / unsupported
platform, or anomaly where the row for the foreign engine is
hidden / non-interactive.
- PermissionsSection: every status pill (microphone, accessibility,
hotkey, Windows IME, network) now right-aligns inside its row via
`justifyContent: flex-end` + `width: 100%`. Pill text moved to the
right of inline messages so the green '已授权' / 'Authorized'
reads as a right-edge badge — closer to macOS System Settings
convention.
* fix(settings): keep Windows Qwen3 toggle visually OFF (pr_agent #403)
pr_agent 'Stale Windows state' caught: previous commit had `on=
{isOnLocalQwen3}` which truthfully reflected committed state, so a
profile synced from macOS with activeAsrProvider=local-qwen3 would
render Toggle ON next to a desc that says '本平台暂不支持'. The
visual contradiction was the concern.
Switch to `on={isMac && isOnLocalQwen3}` — Windows always renders
the row OFF + non-interactive, matching the unsupported desc. The
cross-platform anomaly state surfaces via the standalone '禁用本地
ASR' escape row, which is rendered exactly when no inline Toggle
can disable the active engine.
Comment updated to match the new behavior and document the
tradeoff.
* fix(localasr): scope Qwen3 mgmt UI to macOS only (pr_agent #403)
pr_agent 'Linux regression' caught: previous wrap was `!IS_WINDOWS`,
so a hypothetical Linux render path would still expose the Qwen3
mirror / model-list / engine-status UI even though Qwen3's backend
is `#[cfg(target_os = "macos")]`.
Swap the two wrappers to `IS_MAC` so the management cards only
materialize on macOS. Today's AdvancedSection guards LocalAsr
embedded with `platformSupported = isMac || isWin`, so Linux already
short-circuits before LocalAsr renders — but defense in depth here
costs nothing and keeps the comment accurate.
---------
Co-authored-by: baiqing <lbx12309@icloud.com>
* chore: ignore video-materials/ promo recordings
video-materials/ holds ~2.2 GB of promo recordings (promo /
promo-materials / promo-openless / promo-openless-v2 / SC). These
are local working assets that don't belong in the repo. Add to
.gitignore so future stash / commit operations can't accidentally
pull the binary blob in.
* feat(llm): Google Gemini provider with forced thinking-off + fix provider-switch credentials
Add Google Gemini as a native LLM provider hitting v1beta
:generateContent (single-shot) and :streamGenerateContent?alt=sse
(QA chat) with x-goog-api-key auth. Per-model thinkingConfig is
injected to keep thinking off for every supported ID — exact
matrix verified against ai.google.dev/gemini-api/docs/thinking:
gemini-2.5-flash, gemini-2.5-flash-lite thinkingBudget: 0
gemini-2.5-pro field omitted (Google: cannot disable)
gemini-3*-pro* thinkingLevel: "low"
gemini-3*-flash*, unknown future IDs thinkingLevel: "minimal"
Why native, not the OpenAI-compat shim: shim is beta and for 3.x
needs an extra_body workaround for thinking control; auth header
and contents shape differ enough that reusing
OpenAICompatibleLLMProvider would have been the worse compromise.
fix(settings): switching LLM preset previously only auto-filled
endpoint/model when those slots were empty. All providers share
ark.endpoint / ark.model_id (per-provider entries were never
introduced — see persistence.rs lookup_account), so for returning
users with non-empty slots, switching the dropdown updated active
provider but kept the OLD endpoint — polish silently kept calling
the previous provider. Now switching to any non-custom preset
overwrites endpoint/model to that preset's defaults. Trade-off:
user-customized endpoints within a preset are dropped on switch;
silent wrong-routing was a worse failure mode.
Backend:
- New module llm_gemini.rs (15 unit tests for thinking-config table,
URL builder, response parser, SSE shape, history role mapping)
- polish.rs extracts compose_polish_prompts / compose_translate_prompts
/ compose_qa_system_prompt as pub(crate) helpers shared by both
LLM paths so prompt assembly can't drift
- coordinator.rs's polish_text / translate_text / answer_chat_dispatch
open with `if get_active_llm() == "gemini"` early-return; existing
OpenAI-compat callsite stays untouched
Frontend:
- LLM_PRESETS gains gemini entry (baseUrl v1beta, model placeholder
gemini-2.5-flash)
- ProviderTools hidden when committedLlmProvider === 'gemini'
(its "Fetch models" button assumes OpenAI /models shape; native
/v1beta/models response shape doesn't match)
- Static geminiModelHint lists the 7 available IDs so users pick
correctly without auto-fetch
- 5 locales (zh-CN, zh-TW, en, ja, ko) gain presets.gemini and
geminiModelHint
Test plan:
- cargo test --lib 200/200 pass
- npm run build (tsc + vite) green
- Manual: switch to Gemini in Settings,
paste Google AI Studio key, dictate;
verify polish reaches v1beta and respects
the per-model thinking-off contract.
* docs: 2026-05-10 audit + beta logic review snapshots
Snapshot two audit artifacts produced 2026-05-10:
- docs/audit-2026-05-10-validated.md — 21 audit items run against
the live source tree at openless-all/app/src-tauri/src/ with a
CONFIRMED / FALSE_POSITIVE / PARTIAL verdict per item plus the
exact file:line range that supports each verdict.
- docs/logic-review-2026-05-10.md — 5-path logic review of
origin/beta @ 400097ad covering startup, press/release, end-of-
session, capsule emit, and shutdown. Notable: at 400097ad the
emit_capsule fix from PR #389 was NOT yet an ancestor, so the
cpal audio-callback → AppKit/Win32 SIGTRAP risk was still live
on that snapshot (subsequent merges have addressed it; this doc
remains the dated record).
Both files are pure snapshots — no code changes here.
* fix(llm-gemini): buffer SSE bytes across chunks to handle multibyte UTF-8 split
PR #398 pr_agent flagged: reqwest::chunk() can split a multi-byte
UTF-8 codepoint (CJK / emoji) across two network chunks, but the
streaming code decoded each chunk independently with from_utf8 →
half a 你 in the tail of one chunk would surface as
"non-utf8 SSE chunk" and abort the whole stream. This breaks
Gemini QA streaming for any Chinese-language answer that doesn't
happen to align with packet boundaries.
Fix: buffer raw bytes (Vec<u8>) until we see the SSE delimiter
b"\n\n", and only then decode the complete event content as UTF-8.
The delimiter \n\n is two ASCII bytes (0x0A 0x0A) which can never
appear inside a multi-byte UTF-8 sequence, so byte-level splitting
at that boundary is always safe.
Extracted the parsing as drain_complete_sse_events(&mut Vec<u8>) so
the invariant is testable without a TcpListener. Three new unit
tests cover:
- normal split-on-delimiter + remainder buffering
- multibyte UTF-8 split across chunks (the actual regression)
- invalid-UTF-8 event content gracefully skipped (warn, don't crash)
The same byte-level pattern fix applies to polish.rs's
chat_completion_history_streaming — left untouched here to keep
this PR scoped to the Gemini diff pr_agent reviewed. polish.rs
should get the same fix in a follow-up.
* feat(llm-gemini): teach ProviderTools to fetch native Gemini /v1beta/models
Drop the verbose static geminiModelHint paragraph and restore the
normal ProviderTools "Fetch models" button when the active provider
is Gemini. Users now read live model IDs from Google instead of
staring at a wall of i18n text below their model field.
Backend fetch_provider_models now branches on the base URL:
- Google's generativelanguage.googleapis.com → x-goog-api-key auth +
parse_gemini_model_ids() that walks `models[].name` and strips the
`models/` prefix so the result is a plain `gemini-2.5-flash` style
ID directly writable to ark.model_id.
- Anything else → unchanged Bearer auth + OpenAI `data[].id` shape.
Tests: parse_gemini_model_ids_strips_models_prefix_and_dedups +
is_gemini_base_url_matches_official_domain. cargo test --lib green
(32/32 in commands::tests subset).
i18n: settings.providers.geminiModelHint removed from all 5 locales.
The TS `typeof zhCN` constraint enforces the consistent removal.
* fix(llm-gemini): pr_agent #398 follow-ups — filter non-chat models + CRLF SSE delim
Two follow-up fixes for pr_agent advisories on PR #398:
1. parse_gemini_model_ids now filters by `supportedGenerationMethods`.
Google's /v1beta/models lists every family — embeddings,
TTS-image, predict-only — alongside generateContent-capable
chat models. Without filtering, a "Fetch models" click would
surface ids like `gemini-embedding-2`, and a user picking one
would write it into ark.model_id and have polish silently fail
on every dictation. Field is conservatively included when
missing (some preview models don't expose it yet).
2. drain_complete_sse_events now accepts both `\n\n` and `\r\n\r\n`
as event delimiters. Some HTTP/2 middleware and CDNs normalize
line endings to CRLF; the previous LF-only implementation would
never drain a complete event from those servers and report the
stream as empty even when data was received. Tied positions
can't happen by construction (\r\n\r\n contains no \n\n
substring), so "earliest match wins" is unambiguous.
Tests:
- parse_gemini_model_ids_filters_out_non_generate_content_families
- drain_complete_sse_events_handles_crlf_delimiter
- drain_complete_sse_events_picks_earliest_delimiter_when_mixed
---------
Co-authored-by: baiqing <lbx12309@icloud.com>
* feat(settings): hide local ASR behind new Advanced tab + system confirm dialog
Move local ASR options (local-qwen3 / foundry-local-whisper) out of
the main Providers → ASR dropdown into a new "Advanced" Settings tab,
so first-time users can't accidentally enable CPU inference and get
slower / less-accurate transcription than they expected. Enabling
local ASR now requires reading a strong yellow/amber warning and
confirming a system-level dialog (@tauri-apps/plugin-dialog ask()
with kind:'warning') — the in-page modal pattern wasn't loud enough.
Behavior changes:
- visibleAsrPresets now excludes local-qwen3 and foundry-local-whisper
(the dropdown is cloud-only)
- useEffect lookup uses ASR_PRESETS (all) so committedAsrProvider can
still resolve to a local provider when one is active
- When committedAsrProvider is local, the ASR Card replaces the
dropdown with a plain notice ("Local ASR (X) is currently active —
switch / disable in the Advanced tab"); existing
LocalAsrProviderHint rendering for the Card body is preserved
- New AdvancedSection component:
- Strong warning block (amber background + amber border + amber text,
fontWeight 500, ⚠️ icon) — meets user's "yellow/red obvious" ask
- Per-platform engine selector:
macOS : Qwen3-ASR only
Windows: Qwen3-ASR + Foundry Local Whisper
Linux : "Local ASR model integration is not supported on this platform"
- Enable buttons trigger Tauri ask() confirm dialog before switching
- When already on local: shows a "Disable local ASR" button
(switches to volcengine, no confirm needed) plus the existing
LocalAsrProviderHint for model status / download navigation
- SettingsSectionId gains 'advanced'; SECTION_ORDER appends it after
language so it sits last in the sidebar (matches macOS conventions
for Advanced/Experimental panes)
i18n: 5 locales (zh-CN, zh-TW, en, ja, ko) gain
- settings.sections.advanced
- settings.providers.localAsrActiveNotice
- settings.advanced.{localAsrTitle, localAsrDesc, localAsrWarningStrong,
qwen3Desc, foundryDesc, enable, alreadyActive, disableLocalLabel,
disableLocalDesc, disable, platformNotSupported,
confirmEnableLocalTitle, confirmEnableLocalBody}
Test plan:
- npm run build (tsc + vite) — green; the typeof zhCN constraint
catches any locale that drifted out of shape
- Manual: switch Settings tab to "Advanced", verify warning is
amber-on-amber and clearly visible, click "Enable" on Qwen3-ASR,
verify a system dialog (not a React modal) appears with kind:warning
iconography, cancel — verify nothing changed; confirm — verify the
Providers tab now shows "Local ASR is currently active" instead of
the dropdown
- Manual on macOS: confirm Foundry option does NOT appear
- Manual on Linux (if available): confirm "platform not supported"
message renders
* feat(advanced): inline LocalAsr model UI + page-top confirm popup + concise warning
User feedback: the standalone "Models" page (LocalAsr.tsx as a main
nav tab) was redundant once Advanced settings exists, the inline
amber warning was a wall of text, and clicking Enable triggered an
OS-native dialog that didn't feel like part of the app. Refactor
the whole local-ASR surface around what users actually need:
- "Models" main-nav tab removed (FloatingShell + NAVIGATE_LOCAL_ASR_EVENT
listener gone). Model management is now exclusively reachable via
Settings → Advanced.
- LocalAsr.tsx gains an `embedded` prop. When embedded, the outer
page padding/height, PageHeader, and standalone warning Card are
skipped — the host (AdvancedSection) supplies the chrome.
- AdvancedSection no longer calls @tauri-apps/plugin-dialog ask().
Enabling local ASR sets pendingTarget which renders a single
amber-bordered Card at the very top of the section with
Confirm / Cancel buttons. One box, in-app, dismissable. Disable
is direct (no confirm needed — going back to cloud is safe).
- Persistent inline warning shrunk to one line. The longer wording
(including the new "may drop words on under-spec hardware" risk
the user asked for) lives in the popup body so it's only seen
when actually consequential.
- ProvidersSection ASR Card body returns null when active is
local — the dropdown row's localAsrActiveNotice already points
to Advanced, no need to duplicate manage UI here.
i18n (5 locales): localAsrWarningStrong replaced by
localAsrWarningShort; confirmEnableLocalBody / Title shortened;
new `confirm` key for the popup CTA.
LocalAsrProviderHint and NAVIGATE_LOCAL_ASR_EVENT in Settings.tsx
are now unused but left in place to keep the diff focused; will
clean up in a follow-up commit if pr_agent flags them.
Test plan:
- npm run build (tsc + vite) — green
- Manual on macOS: Settings → Advanced shows one short amber line +
Enable Qwen3-ASR button + LocalAsr model UI inline. Click Enable
→ page-top popup appears (no OS dialog), with Confirm / Cancel.
Cancel → no change. Confirm → switches; Disable button appears,
still single-click no confirm.
- Manual: Providers → ASR with active=local-qwen3 shows only the
"Local ASR is currently active — switch in Advanced" notice,
no manage UI.
* fix(advanced): pr_agent #400 follow-up — keep disable accessible on unsupported platform
If a user enables local-qwen3 (or foundry-local-whisper) on a machine
that supports it, then their preferences sync to a Linux/unsupported
machine, the previous Advanced UI rendered only the
"platformNotSupported" message and hid the disable button — they had
no in-app path to switch back to cloud and dictation would fail
silently on every press.
Move the disable SettingRow out of the platformSupported branch.
It's gated only on isOnAnyLocal now, so:
- Supported platform + on local → enable Qwen3/Foundry rows + disable row
- Supported platform + on cloud → enable rows only
- Unsupported platform + on local → "not supported" notice + disable row
- Unsupported platform + on cloud → "not supported" notice only
This is the pr_agent advisory from PR #400 review @ d0a425a1.
---------
Co-authored-by: baiqing <lbx12309@icloud.com>
Auto-update was failing on Windows because OpenLessIme.dll is a TSF input-method
DLL: once registered, Windows maps it into every text-input client process
(browsers, IDEs, chat apps...). The installer's PREINSTALL hook tried to
overwrite the file directly while it was still mapped, hitting a sharing
violation and aborting the update.
NSIS hook (openless-ime-hooks.nsh):
- PREINSTALL now calls OPENLESS_IME_UNREGISTER_X86/X64 first to release the
COM/TSF registration. On a fresh install this is a harmless no-op (existing
unregister macros log the exit code without aborting).
- DLL bytes are written to OpenLessIme.dll.new and atomically renamed into
place. If the old DLL is still locked by a TSF client process, Delete fails
and we fall back to Rename /REBOOTOK + SetRebootFlag — Windows queues the
replacement via PendingFileRenameOperations and the new bytes activate after
the next reboot. Same TSF registration path is preserved, so re-registration
in POSTINSTALL stays valid across the reboot.
WiX (openless-ime.wxs):
- UnregisterOpenLessImeX86/X64 now also fire Before InstallFiles when a
previous version is installed (Installed AND NOT REMOVE). Original condition
(REMOVE="ALL", Before RemoveFiles) only covered the dedicated uninstall
sequence; auto-update / minor-upgrade skipped it entirely and ran straight
into the same sharing violation as NSIS.
Bumps to 1.2.24-5 (Beta-5).
Co-authored-by: baiqing <lbx12309@icloud.com>