mirror of
https://github.com/SerTimBerrners-Lee/talkis.git
synced 2026-07-09 17:29:15 +00:00
Release v0.3.5
This commit is contained in:
parent
62352d8cb9
commit
b83ea7254f
13 changed files with 139 additions and 49 deletions
|
|
@ -40,6 +40,12 @@ It is designed for practical daily work: IDEs, chats, notes, CRM fields, email,
|
|||
|
||||
## Latest Changes
|
||||
|
||||
### v0.3.5
|
||||
|
||||
- Fixed local STT mode so managed localhost runtimes are called without a stale API bearer token and no longer show an API-key error for local runtime failures.
|
||||
- Improved widget notice layout so the status icon stays visually centered next to long messages.
|
||||
- Prevented call-capture startup errors from resetting the completed permissions onboarding state after restart.
|
||||
|
||||
### v0.3.4
|
||||
|
||||
- Fixed repeated permission prompts after restarting macOS: startup checks no longer trigger a microphone request, and Accessibility is not reset before re-requesting access.
|
||||
|
|
|
|||
54
docs/release/review-v0.3.5.md
Normal file
54
docs/release/review-v0.3.5.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Release Review v0.3.5
|
||||
|
||||
## Release
|
||||
|
||||
- Version: 0.3.5
|
||||
- Release branch: `release/v0.3.5`
|
||||
- Target tag: `v0.3.5`
|
||||
- Reviewer: Codex
|
||||
- Date: 2026-07-02
|
||||
|
||||
## Scope
|
||||
|
||||
- Key changes included in this release:
|
||||
- Local STT requests to localhost runtimes no longer attach the global API key as a bearer token.
|
||||
- Widget error mapping now treats local STT `401/403` responses as local runtime errors instead of API-key failures.
|
||||
- Widget notice icon layout uses a stable icon column so long messages stay visually aligned.
|
||||
- Call-capture permission-like startup errors no longer reset the completed permissions onboarding flag.
|
||||
- User-facing changes:
|
||||
- Local model users should no longer see "check API key" when local runtime auth-like errors occur.
|
||||
- Error notices render with a centered icon.
|
||||
- Restarting after a call-capture startup failure should not reopen the permissions onboarding only because the app reset its own flag.
|
||||
- Risky areas:
|
||||
- Local STT authorization handling.
|
||||
- Permission onboarding persistence after call-capture errors.
|
||||
- Widget notice compact/expanded sizing.
|
||||
|
||||
## Checks run
|
||||
|
||||
- `bun run check:release`: passed
|
||||
- `bun run build:release:macos`: local build reached `Talkis.app` and updater `.app.tar.gz`, then stopped because `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` is not present in the local environment; GitHub Actions is expected to run this step with repository secrets.
|
||||
- Native/GitHub Windows build: GitHub Actions after tag
|
||||
- Native/GitHub Linux build: GitHub Actions after tag
|
||||
- Additional manual checks:
|
||||
- `bunx tsc --noEmit`: passed before release prep
|
||||
- `cargo check`: passed before release prep
|
||||
|
||||
## Manual review
|
||||
|
||||
- Hotkey flow: no hotkey reducer changes in this release.
|
||||
- Onboarding permissions: reviewed `SettingsApp`, `PermissionScreen`, and widget call-capture error handling; removed app-side reset of completed onboarding from call-capture startup errors.
|
||||
- Widget position and notice behavior: notice bubble positioning unchanged; notice content layout reviewed for compact and expanded message states.
|
||||
- Transcription quality and short-utterance handling: no transcript filtering changes in this release.
|
||||
- README refreshed: yes, `v0.3.5` added to Latest Changes.
|
||||
|
||||
## Findings
|
||||
|
||||
- Blockers: local macOS updater signing needs `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`; release publication proceeds through GitHub Actions secrets.
|
||||
- Non-blocking issues: Windows/Linux artifacts are expected from GitHub Actions, not local macOS. Vite reports the existing large chunk warning during production builds.
|
||||
- Follow-ups after release: verify GitHub Actions artifacts and updater metadata after tag publish.
|
||||
|
||||
## Decision
|
||||
|
||||
- Ready for `main` merge: yes
|
||||
- Ready for tag publish: yes, via GitHub Actions release build
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "talkis",
|
||||
"private": true,
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.13",
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@ function detectPlatform() {
|
|||
|
||||
const platform = process.argv[2] || process.env.TALKIS_RELEASE_PLATFORM || detectPlatform();
|
||||
const bundleTargets = {
|
||||
macos: ["app", "dmg"],
|
||||
macos: ["app"],
|
||||
windows: ["nsis"],
|
||||
linux: ["appimage", "deb"],
|
||||
};
|
||||
const releaseOutputs = {
|
||||
macos: ["app", "dmg"],
|
||||
windows: bundleTargets.windows,
|
||||
linux: bundleTargets.linux,
|
||||
};
|
||||
|
||||
if (!Object.hasOwn(bundleTargets, platform)) {
|
||||
throw new Error(`Unsupported release platform: ${platform}`);
|
||||
|
|
@ -57,4 +62,4 @@ if (platform === "macos" && process.env.TALKIS_POSTPROCESS_MACOS_RELEASE !== "0"
|
|||
run("bun", ["run", "postprocess:macos-release"]);
|
||||
}
|
||||
|
||||
console.log(`Built ${platform} release bundles: ${bundleTargets[platform].join(", ")}`);
|
||||
console.log(`Built ${platform} release bundles: ${releaseOutputs[platform].join(", ")}`);
|
||||
|
|
|
|||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -4969,7 +4969,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talkis"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.2",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "talkis"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
description = "Talkis - Voice to Text Desktop Widget"
|
||||
authors = ["trixter"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -586,8 +586,13 @@ async fn transcribe_audio_bytes_internal(
|
|||
let whisper_key = req
|
||||
.whisper_api_key
|
||||
.as_ref()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or(&req.api_key);
|
||||
.or_else(|| {
|
||||
(!is_local_endpoint)
|
||||
.then_some(req.api_key.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
});
|
||||
|
||||
let stt_client = if is_local_endpoint {
|
||||
reqwest::Client::builder()
|
||||
|
|
@ -600,10 +605,12 @@ async fn transcribe_audio_bytes_internal(
|
|||
(*client).clone()
|
||||
};
|
||||
|
||||
let whisper_res = stt_client
|
||||
.post(&whisper_url)
|
||||
.bearer_auth(whisper_key)
|
||||
.multipart(form)
|
||||
let mut whisper_request = stt_client.post(&whisper_url).multipart(form);
|
||||
if let Some(whisper_key) = whisper_key {
|
||||
whisper_request = whisper_request.bearer_auth(whisper_key);
|
||||
}
|
||||
|
||||
let whisper_res = whisper_request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Talkis",
|
||||
"mainBinaryName": "Talkis",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"identifier": "com.trixter.talkis",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run prepare:sidecars && bun run dev",
|
||||
|
|
|
|||
|
|
@ -128,6 +128,10 @@ export const widget = {
|
|||
ru: "Не удалось запустить локальный runtime распознавания. Откройте Настройки -> Модели -> Локально и нажмите «Скачать» для нужной Whisper-модели.",
|
||||
en: "Couldn't start the local recognition runtime. Open Settings -> Models -> Local and click “Download” for the Whisper model you need.",
|
||||
},
|
||||
"widget.error.localRuntimeRejected": {
|
||||
ru: "Локальный runtime распознавания отклонил запрос. Перезапустите локальную модель или выберите её заново в Настройки -> Модели -> Локально.",
|
||||
en: "The local recognition runtime rejected the request. Restart the local model or select it again in Settings -> Models -> Local.",
|
||||
},
|
||||
"widget.error.regionUnsupported": {
|
||||
ru: "Сервис распознавания сейчас недоступен в вашем регионе. Попробуйте другой endpoint или VPN.",
|
||||
en: "The recognition service is currently unavailable in your region. Try a different endpoint or a VPN.",
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import {
|
|||
getHistory,
|
||||
getSettings,
|
||||
reconcileInterruptedProcessing,
|
||||
setPermissionsPassed,
|
||||
setSystemAudioPermissionPassed,
|
||||
type HistoryEntry,
|
||||
} from "../../lib/store";
|
||||
import {
|
||||
|
|
@ -653,26 +651,6 @@ export function Widget() {
|
|||
callMicPausedForVoiceRef.current = false;
|
||||
if (isCallCapturePermissionError(error)) {
|
||||
callSystemAudioPermissionReadyRef.current = false;
|
||||
void setSystemAudioPermissionPassed(false).catch((storeError) => {
|
||||
logError(
|
||||
"CALL_CAPTURE",
|
||||
`Failed to reset system audio permissions flag: ${
|
||||
storeError instanceof Error
|
||||
? storeError.message
|
||||
: String(storeError)
|
||||
}`,
|
||||
);
|
||||
});
|
||||
void setPermissionsPassed(false).catch((storeError) => {
|
||||
logError(
|
||||
"CALL_CAPTURE",
|
||||
`Failed to reset permissions flag: ${
|
||||
storeError instanceof Error
|
||||
? storeError.message
|
||||
: String(storeError)
|
||||
}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
setCallError(message);
|
||||
setCallSession(null);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|||
import type { ReactElement } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { AlertCircle, Info } from "lucide-react";
|
||||
|
||||
import { NOTICE_AREA_HEIGHT, NOTICE_WIDGET_WIDTH, WIDGET_NOTICE_EVENT, type WidgetNoticeState } from "./widgetConstants";
|
||||
|
||||
|
|
@ -49,6 +50,8 @@ export function WidgetNoticeOverlay(): ReactElement | null {
|
|||
const toggleExpanded = () => {
|
||||
setExpanded((value) => !value);
|
||||
};
|
||||
const Icon = notice.tone === "error" ? AlertCircle : Info;
|
||||
const iconColor = notice.tone === "error" ? "rgba(184,52,52,0.9)" : "rgba(0,0,0,0.58)";
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -100,20 +103,36 @@ export function WidgetNoticeOverlay(): ReactElement | null {
|
|||
}}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
expanded
|
||||
? { paddingRight: 4, whiteSpace: "pre-wrap", wordBreak: "break-word" }
|
||||
: {
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
paddingRight: 4,
|
||||
}
|
||||
}
|
||||
style={{
|
||||
minHeight: NOTICE_AREA_HEIGHT - 20,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "16px minmax(0, 1fr)",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{notice.message}
|
||||
<Icon
|
||||
size={15}
|
||||
strokeWidth={2.1}
|
||||
aria-hidden="true"
|
||||
style={{ flexShrink: 0, color: iconColor }}
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
expanded
|
||||
? { paddingRight: 4, whiteSpace: "pre-wrap", wordBreak: "break-word" }
|
||||
: {
|
||||
display: "-webkit-box",
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: "vertical",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
paddingRight: 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
{notice.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,9 +37,18 @@ function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|||
return btoa(binary);
|
||||
}
|
||||
|
||||
function toUserFacingErrorMessage(error: unknown): string {
|
||||
function isLocalSttSettings(settings: AppSettings): boolean {
|
||||
return (
|
||||
settings.useOwnKey &&
|
||||
settings.provider === "custom" &&
|
||||
/127\.0\.0\.1|localhost/i.test(settings.whisperEndpoint || "")
|
||||
);
|
||||
}
|
||||
|
||||
function toUserFacingErrorMessage(error: unknown, settings: AppSettings): string {
|
||||
const raw = formatErrorMessage(error);
|
||||
const normalized = raw.toLowerCase();
|
||||
const isLocalStt = isLocalSttSettings(settings);
|
||||
|
||||
const missingModelMatch = raw.match(/Model ['"]([^'"]+)['"] is not installed locally/i);
|
||||
if (missingModelMatch) {
|
||||
|
|
@ -63,6 +72,10 @@ function toUserFacingErrorMessage(error: unknown): string {
|
|||
}
|
||||
|
||||
if (normalized.includes("403") || normalized.includes("forbidden")) {
|
||||
if (isLocalStt) {
|
||||
return tn("widget.error.localRuntimeRejected");
|
||||
}
|
||||
|
||||
if (normalized.includes("subscription inactive") || normalized.includes("активная подписка") || normalized.includes("cloud mode")) {
|
||||
return tn("widget.error.subscriptionRequired");
|
||||
}
|
||||
|
|
@ -87,6 +100,10 @@ function toUserFacingErrorMessage(error: unknown): string {
|
|||
}
|
||||
|
||||
if (normalized.includes("401") || normalized.includes("unauthorized") || normalized.includes("invalid api key")) {
|
||||
if (isLocalStt) {
|
||||
return tn("widget.error.localRuntimeRejected");
|
||||
}
|
||||
|
||||
return tn("widget.error.authFailed");
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +375,7 @@ export async function processRecordingBlob({
|
|||
: String(error);
|
||||
logError("API", `Pipeline raw error: ${rawErrorMessage}`);
|
||||
|
||||
const userFacingErrorMessage = toUserFacingErrorMessage(error);
|
||||
const userFacingErrorMessage = toUserFacingErrorMessage(error, settings);
|
||||
await finishProcessing({
|
||||
...baseEntry,
|
||||
status: "failed",
|
||||
|
|
@ -449,7 +466,7 @@ export async function retryHistoryEntry(
|
|||
return { hasTranscription: false, updatedEntry: interrupted };
|
||||
}
|
||||
|
||||
const userFacingErrorMessage = toUserFacingErrorMessage(error);
|
||||
const userFacingErrorMessage = toUserFacingErrorMessage(error, retrySettings);
|
||||
const failedEntry: HistoryEntry = {
|
||||
...entry,
|
||||
status: "failed",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue