mirror of
https://github.com/SerTimBerrners-Lee/talkis.git
synced 2026-07-09 17:29:15 +00:00
Prepare v0.1.16 multi-platform release
This commit is contained in:
parent
af206402f3
commit
d681b15f9a
16 changed files with 395 additions and 114 deletions
119
.github/workflows/release.yml
vendored
119
.github/workflows/release.yml
vendored
|
|
@ -14,11 +14,30 @@ permissions:
|
|||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-release-macos:
|
||||
runs-on: macos-latest
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: macos
|
||||
runner: macos-latest
|
||||
release_script: build:release:macos
|
||||
updater_platform: darwin-aarch64
|
||||
- platform: windows
|
||||
runner: windows-latest
|
||||
release_script: build:release:windows
|
||||
updater_platform: windows-x86_64
|
||||
- platform: linux
|
||||
runner: ubuntu-22.04
|
||||
release_script: build:release:linux
|
||||
updater_platform: linux-x86_64
|
||||
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
TALKIS_STT_RELEASE: "1"
|
||||
TALKIS_UPDATER_PLATFORM: ${{ matrix.updater_platform }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
|
@ -32,6 +51,16 @@ jobs:
|
|||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install Linux Tauri dependencies
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev \
|
||||
patchelf
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
|
@ -61,29 +90,46 @@ jobs:
|
|||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
bun run smoke:hotkey
|
||||
|
||||
- name: Build macOS bundles
|
||||
run: |
|
||||
bun run prepare:sidecars
|
||||
bun run build
|
||||
bun run tauri build
|
||||
- name: Build bundles
|
||||
run: bun run ${{ matrix.release_script }}
|
||||
env:
|
||||
TALKIS_STT_RELEASE: "1"
|
||||
TALKIS_SKIP_BEFORE_BUILD: "1"
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Archive macOS app bundle
|
||||
if: matrix.platform == 'macos'
|
||||
run: ditto -c -k --sequesterRsrc --keepParent "src-tauri/target/release/bundle/macos/Talkis.app" "Talkis-${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}-macos.zip"
|
||||
|
||||
- name: Upload release bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: talkis-${{ matrix.platform }}-release
|
||||
path: |
|
||||
src-tauri/target/release/bundle/**/*
|
||||
Talkis-*-macos.zip
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish release
|
||||
needs: build
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download release bundles
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
|
||||
- name: Create updater metadata
|
||||
run: bash scripts/create-updater-latest-json.sh "${RELEASE_TAG#v}" "${RELEASE_TAG}"
|
||||
env:
|
||||
BUILD_ROOT: src-tauri/target/release/bundle
|
||||
|
||||
- name: Post-process macOS release artifacts
|
||||
run: bash scripts/postprocess-macos-release.sh "${RELEASE_TAG#v}"
|
||||
env:
|
||||
BUILD_ROOT: src-tauri/target/release/bundle
|
||||
|
||||
- name: Archive macOS app bundle
|
||||
run: ditto -c -k --sequesterRsrc --keepParent "src-tauri/target/release/bundle/macos/Talkis.app" "Talkis-${{ env.RELEASE_TAG }}-macos.zip"
|
||||
BUILD_ROOT: release-artifacts
|
||||
TALKIS_REQUIRED_UPDATER_PLATFORMS: darwin-aarch64,windows-x86_64,linux-x86_64
|
||||
|
||||
- name: Publish GitHub release assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
|
@ -93,31 +139,20 @@ jobs:
|
|||
body: |
|
||||
Automated release for ${{ env.RELEASE_TAG }}.
|
||||
|
||||
Current workflow publishes all currently supported release artifacts and updater metadata, which is macOS only right now.
|
||||
Windows and Linux are intentionally disabled until platform-specific support is implemented.
|
||||
This release publishes native Tauri bundles built on macOS, Windows, and Linux runners.
|
||||
Windows/Linux builds are unsigned in this workflow.
|
||||
draft: false
|
||||
prerelease: ${{ contains(env.RELEASE_TAG, '-') }}
|
||||
files: |
|
||||
src-tauri/target/release/bundle/dmg/*.dmg
|
||||
src-tauri/target/release/bundle/macos/*.app.tar.gz
|
||||
src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
|
||||
src-tauri/target/release/bundle/latest.json
|
||||
Talkis-${{ env.RELEASE_TAG }}-macos.zip
|
||||
release-artifacts/**/*.dmg
|
||||
release-artifacts/**/*.app.tar.gz
|
||||
release-artifacts/**/*.exe
|
||||
release-artifacts/**/*.msi
|
||||
release-artifacts/**/*.AppImage
|
||||
release-artifacts/**/*.deb
|
||||
release-artifacts/**/*.rpm
|
||||
release-artifacts/**/*.zip
|
||||
release-artifacts/**/*.sig
|
||||
release-artifacts/**/latest.json
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
disabled-platform-placeholders:
|
||||
if: ${{ false }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: windows-latest
|
||||
target: windows
|
||||
- runner: ubuntu-22.04
|
||||
target: linux
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- name: Placeholder
|
||||
run: |
|
||||
echo "${{ matrix.target }} releases are intentionally disabled until platform support is implemented."
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Talkis
|
||||
|
||||
Talkis is a lightweight macOS voice-to-text app built with Tauri.
|
||||
Talkis is a lightweight desktop voice-to-text app built with Tauri.
|
||||
|
||||
It sits in a small floating widget, listens while you hold a hotkey, sends audio for transcription, cleans up the text with an LLM, and pastes the result into the active app.
|
||||
|
||||
|
|
@ -59,24 +59,29 @@ Supported managed local models include:
|
|||
|
||||
Local STT is transcription-only. Talkis does not run LLM/style cleanup for local STT mode unless a separate LLM endpoint is configured in custom mode.
|
||||
|
||||
## macOS only
|
||||
## Supported platforms
|
||||
|
||||
Talkis is currently designed for macOS.
|
||||
Talkis release automation builds native bundles for:
|
||||
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
|
||||
The app relies on:
|
||||
|
||||
- microphone access
|
||||
- accessibility permission for automatic text pasting (via CGEvent)
|
||||
- accessibility permission on macOS for automatic text pasting (via CGEvent)
|
||||
- best-effort paste simulation on Windows/Linux
|
||||
- a global hotkey
|
||||
|
||||
## Setup
|
||||
|
||||
Before first use, make sure you have:
|
||||
|
||||
1. macOS
|
||||
1. macOS, Windows, or Linux
|
||||
2. One of the access modes configured (subscription, own key, or local model)
|
||||
3. Microphone access enabled
|
||||
4. Accessibility access enabled for Talkis
|
||||
4. Accessibility access enabled for Talkis on macOS
|
||||
|
||||
### 1. Open the settings window
|
||||
|
||||
|
|
@ -259,7 +264,10 @@ bunx tsc --noEmit
|
|||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
bun run check:release
|
||||
bun run tauri build
|
||||
bun run build:release
|
||||
bun run build:release:macos
|
||||
bun run build:release:windows
|
||||
bun run build:release:linux
|
||||
bun run logs
|
||||
bun run logs:clear
|
||||
```
|
||||
|
|
@ -270,9 +278,9 @@ The repository includes a GitHub Actions workflow at `.github/workflows/release.
|
|||
|
||||
- The canonical release process is documented in `docs/release/rule.md`
|
||||
- Before every release, refresh `README.md` and create a release review file from `docs/release/review-template.md`
|
||||
- Push a tag like `v0.1.15` to build and publish a GitHub Release
|
||||
- Push a tag like `v0.1.16` to build and publish a GitHub Release
|
||||
- Or run the workflow manually and provide a tag
|
||||
- The current workflow publishes macOS release artifacts and updater metadata
|
||||
- The current workflow publishes macOS, Windows, and Linux release artifacts plus updater metadata
|
||||
- For macOS release builds, move `Talkis.app` to `Applications` before granting Accessibility access
|
||||
|
||||
Optional macOS signing/notarization secrets:
|
||||
|
|
@ -284,7 +292,7 @@ Optional macOS signing/notarization secrets:
|
|||
- `APPLE_PASSWORD`
|
||||
- `APPLE_TEAM_ID`
|
||||
|
||||
Without these secrets, the workflow can still produce unsigned macOS release artifacts.
|
||||
Without Apple secrets, the workflow can still produce unsigned macOS release artifacts. Windows and Linux builds are currently unsigned.
|
||||
|
||||
## Tech stack
|
||||
|
||||
|
|
@ -298,4 +306,4 @@ Without these secrets, the workflow can still produce unsigned macOS release art
|
|||
|
||||
## Status
|
||||
|
||||
Talkis is an active work in progress. Current version: **0.1.15**.
|
||||
Talkis is an active work in progress. Current version: **0.1.16**.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ Copy this file to `docs/release/review-vX.Y.Z.md` for each release.
|
|||
|
||||
- `bun run check:release`
|
||||
- `bun run build:release:macos`
|
||||
- Native/GitHub Windows build:
|
||||
- Native/GitHub Linux build:
|
||||
- Additional manual checks:
|
||||
|
||||
## Manual review
|
||||
|
|
|
|||
58
docs/release/review-v0.1.16.md
Normal file
58
docs/release/review-v0.1.16.md
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Release Review v0.1.16
|
||||
|
||||
## Release
|
||||
|
||||
- Version: 0.1.16
|
||||
- Release branch: main
|
||||
- Target tag: v0.1.16
|
||||
- Reviewer: Codex
|
||||
- Date: 2026-05-12
|
||||
|
||||
## Scope
|
||||
|
||||
- Key changes included in this release:
|
||||
- Enables GitHub Actions release builds for macOS, Windows, and Linux.
|
||||
- Adds platform-specific release scripts for macOS, Windows, and Linux.
|
||||
- Generates updater metadata for `darwin-aarch64`, `windows-x86_64`, and `linux-x86_64`.
|
||||
- Updates onboarding/runtime info so Windows and Linux do not require macOS Accessibility or Applications-folder handling.
|
||||
- Marks Qwen and NVIDIA local runtimes as macOS-only while keeping Whisper available on Windows/Linux.
|
||||
- User-facing changes:
|
||||
- GitHub Release assets should include Windows and Linux installers in addition to macOS artifacts.
|
||||
- Onboarding text now reflects platform-specific permissions.
|
||||
- Risky areas:
|
||||
- First Windows/Linux release artifacts are built only in GitHub Actions native runners.
|
||||
- Windows and Linux builds are unsigned.
|
||||
- Updater metadata depends on `.sig` files being produced for all required platforms.
|
||||
|
||||
## Checks run
|
||||
|
||||
- `bun run check:release`: passed
|
||||
- `bun run build:release:macos`: not run locally before tagging; GitHub Actions will build native release bundles.
|
||||
- Native/GitHub Windows build: pending GitHub Actions for `v0.1.16`
|
||||
- Native/GitHub Linux build: pending GitHub Actions for `v0.1.16`
|
||||
- Additional manual checks:
|
||||
- `scripts/create-updater-latest-json.sh` tested against synthetic macOS/Windows/Linux updater artifacts and signatures.
|
||||
- `.github/workflows/release.yml` parsed as valid YAML.
|
||||
|
||||
## Manual review
|
||||
|
||||
- Hotkey flow: smoke tests passed.
|
||||
- Onboarding permissions: reviewed platform-specific macOS/Windows/Linux copy and gating.
|
||||
- Widget position and notice behavior: not functionally changed.
|
||||
- Transcription quality and short-utterance handling: not functionally changed.
|
||||
- README refreshed: yes.
|
||||
|
||||
## Findings
|
||||
|
||||
- Blockers: none before tag publish.
|
||||
- Non-blocking issues:
|
||||
- Windows/Linux release artifacts still need confirmation from the first Actions run.
|
||||
- Windows/Linux installers are unsigned.
|
||||
- Follow-ups after release:
|
||||
- Verify `v0.1.16` GitHub Release contains Windows `.exe`/`.msi`, Linux `.AppImage`/`.deb`, matching `.sig` files, and `latest.json`.
|
||||
- If GitHub Actions fails on a native runner dependency, fix the workflow and re-run the tag workflow before announcing the release.
|
||||
|
||||
## Decision
|
||||
|
||||
- Ready for `main` merge: yes
|
||||
- Ready for tag publish: yes
|
||||
|
|
@ -19,6 +19,7 @@ This file defines the mandatory release workflow for Talkis. Follow it for every
|
|||
4. Run the release checks locally:
|
||||
- `bun run check:release`
|
||||
- `TAURI_SIGNING_PRIVATE_KEY_PATH=~/.tauri/talkis-updater.key bun run build:release:macos`
|
||||
- On native Windows/Linux runners, run `bun run build:release:windows` and `bun run build:release:linux` before claiming those artifacts are ready.
|
||||
- If the updater private key is password-protected, also set `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`.
|
||||
5. Perform a detailed self-review of the full release diff.
|
||||
6. Write the review results to `docs/release/review-vX.Y.Z.md` using the review template.
|
||||
|
|
@ -35,11 +36,11 @@ This file defines the mandatory release workflow for Talkis. Follow it for every
|
|||
- Widget position, notices, and onboarding permissions behave correctly.
|
||||
- Short or noisy recordings do not paste obvious hallucinated text.
|
||||
- `bun run check:release` passes.
|
||||
- Local production build passes via `bun run build:release:macos`.
|
||||
- Local production build passes via `bun run build:release:macos`; Windows/Linux production builds pass on their native runners or in GitHub Actions.
|
||||
- Version numbers and release tag match.
|
||||
- The GitHub Actions release workflow still matches the documented process.
|
||||
- GitHub repository secrets include `TAURI_SIGNING_PRIVATE_KEY` and, if the key is password-protected, `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`.
|
||||
- GitHub Release includes `latest.json`, the macOS updater `.app.tar.gz`, and its `.sig` file.
|
||||
- GitHub Release includes `latest.json`, macOS `.app.tar.gz`, Windows `.exe`, Linux `.AppImage`, and matching `.sig` files.
|
||||
|
||||
## GitHub Actions release source of truth
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "talkis",
|
||||
"private": true,
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.13",
|
||||
"scripts": {
|
||||
|
|
@ -16,7 +16,10 @@
|
|||
"check:versions": "bash scripts/check-version-sync.sh",
|
||||
"check:release": "bun run check:versions && bun run check && bun run smoke:hotkey && bun run build",
|
||||
"postprocess:macos-release": "VERSION=$(python3 -c \"import json; print(json.load(open('package.json'))['version'])\") && BUILD_ROOT=src-tauri/target/release/bundle bash scripts/postprocess-macos-release.sh \"$VERSION\"",
|
||||
"build:release:macos": "bash scripts/build-release-macos.sh",
|
||||
"build:release": "bun scripts/build-release.mjs",
|
||||
"build:release:macos": "bun scripts/build-release.mjs macos",
|
||||
"build:release:windows": "bun scripts/build-release.mjs windows",
|
||||
"build:release:linux": "bun scripts/build-release.mjs linux",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"logs": "tail -f ~/.talkis/talkis.log",
|
||||
|
|
|
|||
|
|
@ -5,17 +5,4 @@ set -euo pipefail
|
|||
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
|
||||
export TALKIS_STT_RELEASE=1
|
||||
|
||||
if [[ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" && -n "${TAURI_SIGNING_PRIVATE_KEY_PATH:-}" ]]; then
|
||||
if [[ ! -f "${TAURI_SIGNING_PRIVATE_KEY_PATH}" ]]; then
|
||||
echo "TAURI_SIGNING_PRIVATE_KEY_PATH does not point to a file: ${TAURI_SIGNING_PRIVATE_KEY_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export TAURI_SIGNING_PRIVATE_KEY
|
||||
TAURI_SIGNING_PRIVATE_KEY="$(cat "${TAURI_SIGNING_PRIVATE_KEY_PATH}")"
|
||||
fi
|
||||
|
||||
bun run prepare:sidecars
|
||||
bun run build
|
||||
TALKIS_SKIP_BEFORE_BUILD=1 bun run tauri build
|
||||
bun run postprocess:macos-release
|
||||
bun scripts/build-release.mjs macos
|
||||
|
|
|
|||
60
scripts/build-release.mjs
Normal file
60
scripts/build-release.mjs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const rootDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
|
||||
function detectPlatform() {
|
||||
if (process.platform === "darwin") return "macos";
|
||||
if (process.platform === "win32") return "windows";
|
||||
if (process.platform === "linux") return "linux";
|
||||
throw new Error(`Unsupported release platform: ${process.platform}`);
|
||||
}
|
||||
|
||||
const platform = process.argv[2] || process.env.TALKIS_RELEASE_PLATFORM || detectPlatform();
|
||||
const bundleTargets = {
|
||||
macos: ["app", "dmg"],
|
||||
windows: ["nsis"],
|
||||
linux: ["appimage", "deb"],
|
||||
};
|
||||
|
||||
if (!Object.hasOwn(bundleTargets, platform)) {
|
||||
throw new Error(`Unsupported release platform: ${platform}`);
|
||||
}
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
TALKIS_STT_RELEASE: process.env.TALKIS_STT_RELEASE || "1",
|
||||
TALKIS_SKIP_BEFORE_BUILD: "1",
|
||||
};
|
||||
|
||||
if (platform === "macos") {
|
||||
env.MACOSX_DEPLOYMENT_TARGET = process.env.MACOSX_DEPLOYMENT_TARGET || "11.0";
|
||||
|
||||
if (!env.TAURI_SIGNING_PRIVATE_KEY && env.TAURI_SIGNING_PRIVATE_KEY_PATH) {
|
||||
if (!existsSync(env.TAURI_SIGNING_PRIVATE_KEY_PATH)) {
|
||||
throw new Error(`TAURI_SIGNING_PRIVATE_KEY_PATH does not point to a file: ${env.TAURI_SIGNING_PRIVATE_KEY_PATH}`);
|
||||
}
|
||||
|
||||
env.TAURI_SIGNING_PRIVATE_KEY = readFileSync(env.TAURI_SIGNING_PRIVATE_KEY_PATH, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
function run(command, args) {
|
||||
execFileSync(command, args, {
|
||||
cwd: rootDir,
|
||||
env,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
run("bun", ["run", "prepare:sidecars"]);
|
||||
run("bun", ["run", "build"]);
|
||||
run("bun", ["run", "tauri", "build", "--bundles", bundleTargets[platform].join(",")]);
|
||||
|
||||
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(", ")}`);
|
||||
|
|
@ -6,42 +6,72 @@ VERSION="${1:?usage: create-updater-latest-json.sh <version> <release-tag>}"
|
|||
RELEASE_TAG="${2:?usage: create-updater-latest-json.sh <version> <release-tag>}"
|
||||
REPOSITORY="${GITHUB_REPOSITORY:-SerTimBerrners-Lee/talkis}"
|
||||
BUILD_ROOT="${BUILD_ROOT:-src-tauri/target/release/bundle}"
|
||||
MACOS_BUNDLE_DIR="${BUILD_ROOT}/macos"
|
||||
LATEST_JSON_PATH="${BUILD_ROOT}/latest.json"
|
||||
REQUIRED_PLATFORMS="${TALKIS_REQUIRED_UPDATER_PLATFORMS:-}"
|
||||
|
||||
UPDATER_ARCHIVE="$(find "${MACOS_BUNDLE_DIR}" -maxdepth 1 -type f -name "*.app.tar.gz" | head -n 1)"
|
||||
|
||||
if [[ -z "${UPDATER_ARCHIVE}" ]]; then
|
||||
echo "Updater archive not found in ${MACOS_BUNDLE_DIR}" >&2
|
||||
if [[ ! -d "${BUILD_ROOT}" ]]; then
|
||||
echo "Build root not found: ${BUILD_ROOT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SIGNATURE_PATH="${UPDATER_ARCHIVE}.sig"
|
||||
|
||||
if [[ ! -f "${SIGNATURE_PATH}" ]]; then
|
||||
echo "Updater signature not found at ${SIGNATURE_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UPDATER_FILE="$(basename "${UPDATER_ARCHIVE}")"
|
||||
SIGNATURE="$(tr -d '\n' < "${SIGNATURE_PATH}")"
|
||||
URL="https://github.com/${REPOSITORY}/releases/download/${RELEASE_TAG}/${UPDATER_FILE}"
|
||||
PUB_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
|
||||
python3 - "$LATEST_JSON_PATH" "$VERSION" "$PUB_DATE" "$SIGNATURE" "$URL" <<'PY'
|
||||
python3 - "$BUILD_ROOT" "$LATEST_JSON_PATH" "$VERSION" "$PUB_DATE" "$REPOSITORY" "$RELEASE_TAG" "$REQUIRED_PLATFORMS" <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
path, version, pub_date, signature, url = sys.argv[1:]
|
||||
build_root, path, version, pub_date, repository, release_tag, required_platforms = sys.argv[1:]
|
||||
root = Path(build_root)
|
||||
|
||||
platform_specs = {
|
||||
"darwin-aarch64": ["*.app.tar.gz"],
|
||||
"windows-x86_64": ["*.exe", "*.msi"],
|
||||
"linux-x86_64": ["*.AppImage"],
|
||||
}
|
||||
|
||||
def find_artifact(patterns):
|
||||
for pattern in patterns:
|
||||
matches = sorted(
|
||||
candidate
|
||||
for candidate in root.rglob(pattern)
|
||||
if candidate.is_file()
|
||||
and not candidate.name.endswith(".sig")
|
||||
)
|
||||
if matches:
|
||||
return matches[0]
|
||||
return None
|
||||
|
||||
platforms = {}
|
||||
|
||||
for platform, patterns in platform_specs.items():
|
||||
artifact = find_artifact(patterns)
|
||||
if artifact is None:
|
||||
continue
|
||||
|
||||
signature_path = artifact.with_name(f"{artifact.name}.sig")
|
||||
if not signature_path.is_file():
|
||||
raise SystemExit(f"Updater signature not found for {platform}: {signature_path}")
|
||||
|
||||
signature = signature_path.read_text(encoding="utf-8").strip()
|
||||
url = f"https://github.com/{repository}/releases/download/{release_tag}/{artifact.name}"
|
||||
platforms[platform] = {
|
||||
"signature": signature,
|
||||
"url": url,
|
||||
}
|
||||
|
||||
required = [item.strip() for item in required_platforms.split(",") if item.strip()]
|
||||
missing = [platform for platform in required if platform not in platforms]
|
||||
if missing:
|
||||
raise SystemExit(f"Missing required updater platforms: {', '.join(missing)}")
|
||||
|
||||
if not platforms:
|
||||
raise SystemExit(f"No updater artifacts found in {root}")
|
||||
|
||||
payload = {
|
||||
"version": version,
|
||||
"pub_date": pub_date,
|
||||
"platforms": {
|
||||
"darwin-aarch64": {
|
||||
"signature": signature,
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
"platforms": platforms,
|
||||
}
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
|
|
|
|||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -4654,7 +4654,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "talkis"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.2",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "talkis"
|
||||
version = "0.1.15"
|
||||
description = "Talkis - Voice to Text macOS Widget"
|
||||
version = "0.1.16"
|
||||
description = "Talkis - Voice to Text Desktop Widget"
|
||||
authors = ["trixter"]
|
||||
edition = "2021"
|
||||
default-run = "talkis"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use serde::Serialize;
|
|||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppRuntimeInfo {
|
||||
platform: String,
|
||||
executable_path: String,
|
||||
bundle_path: String,
|
||||
launched_via_translocation: bool,
|
||||
|
|
@ -10,6 +11,18 @@ pub struct AppRuntimeInfo {
|
|||
should_move_to_applications: bool,
|
||||
}
|
||||
|
||||
fn platform_name() -> &'static str {
|
||||
if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
|
||||
fn build_runtime_info() -> Result<AppRuntimeInfo, String> {
|
||||
let executable_path = std::env::current_exe().map_err(|e| e.to_string())?;
|
||||
let executable_path_str = executable_path.to_string_lossy().to_string();
|
||||
|
|
@ -24,13 +37,14 @@ fn build_runtime_info() -> Result<AppRuntimeInfo, String> {
|
|||
|
||||
// In dev builds, never warn about Applications — the binary is
|
||||
// expected to live wherever target-dir points.
|
||||
let should_move = if cfg!(dev) {
|
||||
let should_move = if cfg!(dev) || !cfg!(target_os = "macos") {
|
||||
false
|
||||
} else {
|
||||
launched_via_translocation || launched_from_mounted_volume
|
||||
};
|
||||
|
||||
Ok(AppRuntimeInfo {
|
||||
platform: platform_name().to_string(),
|
||||
executable_path: executable_path_str,
|
||||
bundle_path: bundle_path_str,
|
||||
launched_via_translocation,
|
||||
|
|
|
|||
|
|
@ -109,6 +109,21 @@ fn simulate_cmd_v() -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn paste_shortcut_label() -> &'static str {
|
||||
"Cmd+V via CGEvent"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn paste_shortcut_label() -> &'static str {
|
||||
"Ctrl+V via input simulation"
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn paste_shortcut_label() -> &'static str {
|
||||
"Ctrl+V via best-effort input simulation"
|
||||
}
|
||||
|
||||
/// Paste text by writing to clipboard and simulating Cmd+V
|
||||
#[tauri::command]
|
||||
pub async fn paste_text(app: tauri::AppHandle, text: String) -> Result<(), String> {
|
||||
|
|
@ -140,7 +155,7 @@ pub async fn paste_text(app: tauri::AppHandle, text: String) -> Result<(), Strin
|
|||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
|
||||
logger::log_info("PASTE", "Simulating Cmd+V via CGEvent");
|
||||
logger::log_info("PASTE", &format!("Simulating {}", paste_shortcut_label()));
|
||||
simulate_cmd_v()?;
|
||||
|
||||
std::thread::sleep(Duration::from_millis(350));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Talkis",
|
||||
"mainBinaryName": "Talkis",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.16",
|
||||
"identifier": "com.trixter.talkis",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run prepare:sidecars && bun run dev",
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": ["app"],
|
||||
"targets": "all",
|
||||
"createUpdaterArtifacts": true,
|
||||
"externalBin": [
|
||||
"binaries/ffmpeg",
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ interface PermissionScreenProps {
|
|||
}
|
||||
|
||||
interface AppRuntimeInfo {
|
||||
platform: "macos" | "windows" | "linux" | "unknown";
|
||||
executablePath: string;
|
||||
bundlePath: string;
|
||||
launchedViaTranslocation: boolean;
|
||||
|
|
@ -110,6 +111,38 @@ interface AppRuntimeInfo {
|
|||
shouldMoveToApplications: boolean;
|
||||
}
|
||||
|
||||
type DesktopPlatform = AppRuntimeInfo["platform"];
|
||||
|
||||
function detectDesktopPlatform(): DesktopPlatform {
|
||||
if (typeof navigator === "undefined") {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const value = `${navigator.platform} ${navigator.userAgent}`.toLowerCase();
|
||||
|
||||
if (value.includes("mac")) return "macos";
|
||||
if (value.includes("win")) return "windows";
|
||||
if (value.includes("linux") || value.includes("x11")) return "linux";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function microphoneHelpText(platform: DesktopPlatform): string {
|
||||
if (platform === "macos") {
|
||||
return "Если микрофон был отклонен, откройте Системные настройки -> Конфиденциальность -> Микрофон и включите Talkis.";
|
||||
}
|
||||
|
||||
if (platform === "windows") {
|
||||
return "Если микрофон был отклонен, откройте Параметры -> Конфиденциальность и безопасность -> Микрофон и разрешите доступ для Talkis.";
|
||||
}
|
||||
|
||||
if (platform === "linux") {
|
||||
return "Если микрофон недоступен, проверьте системные настройки звука и разрешения браузерного WebView для записи.";
|
||||
}
|
||||
|
||||
return "Если микрофон был отклонен, откройте системные настройки приватности и разрешите доступ для Talkis.";
|
||||
}
|
||||
|
||||
export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
||||
const [micStatus, setMicStatus] = useState<PermissionStatus>("unknown");
|
||||
const [accStatus, setAccStatus] = useState<PermissionStatus>("unknown");
|
||||
|
|
@ -190,6 +223,11 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!requiresAccessibility) {
|
||||
setAccStatus("granted");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke("reset_accessibility_permission");
|
||||
} catch (e) {
|
||||
|
|
@ -213,7 +251,7 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
|
||||
const { nextMicStatus, nextAccStatus } = await refreshAllPermissions();
|
||||
|
||||
if (nextMicStatus !== "granted" || nextAccStatus !== "granted") {
|
||||
if (nextMicStatus !== "granted" || (requiresAccessibility && nextAccStatus !== "granted")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -221,12 +259,25 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
onComplete();
|
||||
};
|
||||
|
||||
const canContinue = micStatus === "granted" && accStatus === "granted";
|
||||
const platform = runtimeInfo?.platform ?? detectDesktopPlatform();
|
||||
const requiresAccessibility = platform === "macos";
|
||||
// In dev mode the binary lives in the build target dir (e.g. /Volumes/...),
|
||||
// which is not /Applications — but that's expected, so skip the warning.
|
||||
// which is not /Applications - but that's expected, so skip the warning.
|
||||
const shouldShowInstallWarning = import.meta.env.DEV
|
||||
? false
|
||||
: Boolean(runtimeInfo?.shouldMoveToApplications);
|
||||
const pastePermissionTitle = requiresAccessibility ? "Универсальный доступ" : "Вставка текста";
|
||||
const pastePermissionDescription = requiresAccessibility
|
||||
? "Нужен для глобальной горячей клавиши и вставки текста."
|
||||
: platform === "linux"
|
||||
? "Talkis использует буфер обмена и Ctrl+V. В некоторых Wayland/X11 окружениях автоматическая вставка может быть ограничена."
|
||||
: "Talkis использует буфер обмена и Ctrl+V. Отдельное системное разрешение обычно не требуется.";
|
||||
const pastePermissionHelpText = shouldShowInstallWarning
|
||||
? `Приложение запущено не из Applications: ${runtimeInfo?.bundlePath ?? "-"}`
|
||||
: platform === "linux"
|
||||
? "Если вставка не сработает, скопированный текст останется в буфере обмена и его можно вставить вручную."
|
||||
: undefined;
|
||||
const canContinue = micStatus === "granted" && (!requiresAccessibility || accStatus === "granted");
|
||||
const canCompleteOnboarding = canContinue && !shouldShowInstallWarning;
|
||||
|
||||
return (
|
||||
|
|
@ -328,9 +379,9 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
|
||||
<PermissionRow
|
||||
icon={<Keyboard size={16} strokeWidth={1.8} />}
|
||||
title="Универсальный доступ"
|
||||
description="Нужен для глобальной горячей клавиши и вставки текста."
|
||||
status={shouldShowInstallWarning ? "denied" : accStatus}
|
||||
title={pastePermissionTitle}
|
||||
description={pastePermissionDescription}
|
||||
status={requiresAccessibility && shouldShowInstallWarning ? "denied" : requiresAccessibility ? accStatus : "granted"}
|
||||
onAction={() => {
|
||||
if (shouldShowInstallWarning) {
|
||||
void openPath("/Applications");
|
||||
|
|
@ -339,9 +390,7 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
|
||||
void handleAccessibilityRequest();
|
||||
}}
|
||||
helpText={shouldShowInstallWarning
|
||||
? `Приложение запущено не из Applications: ${runtimeInfo?.bundlePath ?? "—"}`
|
||||
: undefined}
|
||||
helpText={pastePermissionHelpText}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -361,7 +410,7 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
<AlertCircle size={14} style={{ color: "var(--text-low)", flexShrink: 0, marginTop: 1 }} />
|
||||
<div style={{ fontSize: 12, color: "var(--text-mid)", lineHeight: 1.6 }}>
|
||||
{micStatus === "denied"
|
||||
? "Если микрофон был отклонен, откройте Системные настройки → Конфиденциальность → Микрофон и включите Talkis."
|
||||
? microphoneHelpText(platform)
|
||||
: shouldShowInstallWarning
|
||||
? "После перемещения приложения в Applications откройте его заново."
|
||||
: "macOS применяет доступ не мгновенно. После изменения настройки вернитесь в приложение."}
|
||||
|
|
@ -376,7 +425,7 @@ export function PermissionScreen({ onComplete }: PermissionScreenProps) {
|
|||
? "Сначала запустите из Applications."
|
||||
: canContinue
|
||||
? "Все доступы выданы."
|
||||
: "Выдайте оба разрешения для продолжения."}
|
||||
: requiresAccessibility ? "Выдайте оба разрешения для продолжения." : "Разрешите микрофон для продолжения."}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleContinue}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,21 @@ import xAiAvatar from "../../../assets/adapters/xai.png";
|
|||
|
||||
const IS_DEV = import.meta.env.DEV;
|
||||
type LocalRuntimeKind = "whisper" | "nvidia" | "qwen";
|
||||
type DesktopPlatform = "macos" | "windows" | "linux" | "unknown";
|
||||
|
||||
function detectDesktopPlatform(): DesktopPlatform {
|
||||
if (typeof navigator === "undefined") {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const value = `${navigator.platform} ${navigator.userAgent}`.toLowerCase();
|
||||
|
||||
if (value.includes("mac")) return "macos";
|
||||
if (value.includes("win")) return "windows";
|
||||
if (value.includes("linux") || value.includes("x11")) return "linux";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const LOCAL_RUNTIME_ENDPOINTS: Record<LocalRuntimeKind, string> = {
|
||||
whisper: "http://127.0.0.1:8000",
|
||||
|
|
@ -942,6 +957,7 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
|
|||
const hasActiveSubscription = cloudProfile?.subscription.active === true;
|
||||
const isCloudMode = !settings.useOwnKey;
|
||||
const isCustom = settings.provider === "custom";
|
||||
const desktopPlatform = detectDesktopPlatform();
|
||||
const activeModelMode: "cloud" | "api" | "local" = isCloudMode ? "cloud" : isCustom ? "local" : "api";
|
||||
const isApiMode = activeModelMode === "api";
|
||||
const isLocalMode = activeModelMode === "local";
|
||||
|
|
@ -1328,7 +1344,8 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
|
|||
const getLocalModelStatus = (model: LocalModelOption) => {
|
||||
const actionState = localModelActionStates[model.id];
|
||||
const cachedState = settings.localModels?.[model.id];
|
||||
const isRuntimeReady = model.runtimeReady === true;
|
||||
const isPlatformSupported = model.runtimeKind === "whisper" || desktopPlatform === "macos";
|
||||
const isRuntimeReady = model.runtimeReady === true && isPlatformSupported;
|
||||
const isInstalled = isRuntimeReady && (localInstalledModelSet.has(model.model) || cachedState?.status === "downloaded");
|
||||
const isSelected = localSttTargetModel === model.model && isInstalled;
|
||||
|
||||
|
|
@ -1342,7 +1359,9 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
|
|||
: `${runtimeName} runtime-слот подготовлен, но движок еще не встроен в сборку.`,
|
||||
status: "unsupported" as const,
|
||||
color: "var(--text-low)",
|
||||
message: model.unavailableReason || `${runtimeName} sidecar запускается отдельно от Whisper, но скачивание и распознавание для этой линейки будут включены после подключения локального engine.`,
|
||||
message: !isPlatformSupported
|
||||
? "Эта локальная модель пока доступна только на macOS. Для Windows и Linux оставлен Whisper runtime."
|
||||
: model.unavailableReason || `${runtimeName} sidecar запускается отдельно от Whisper, но скачивание и распознавание для этой линейки будут включены после подключения локального engine.`,
|
||||
isInstalled: false,
|
||||
isSelected: false,
|
||||
};
|
||||
|
|
@ -1913,7 +1932,7 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
|
|||
const isExpanded = expandedLocalModel === model.id;
|
||||
const modelStatus = getLocalModelStatus(model);
|
||||
const modelActionState = localModelActionStates[model.id];
|
||||
const isRuntimeReady = model.runtimeReady === true;
|
||||
const isRuntimeReady = getLocalModelStatus(model).status !== "unsupported" && model.runtimeReady === true;
|
||||
const isDownloaded = modelStatus.isInstalled;
|
||||
const isModelBusy = modelStatus.status === "installing" || modelStatus.status === "deleting";
|
||||
const isInstallDisabled = isModelBusy || !isRuntimeReady;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue