feat: Alpha Launch v0.2.0 - Complete UX Overhaul 🚀🎤 (#39)
* feat: prepare for v0.2.0-alpha release * feat: prepare for v0.2.0-alpha release * chore: remove CHANGELOG.md, use GitHub Releases instead * refactor: use existing About dialog instead of Settings section * style: enhance About dialog UI readability * style: fix formatting with black and isort * feat: show VAD and silence timeout settings for Whisper engine - Renamed 'VOSK Settings' to 'Recognition Settings' - VAD Sensitivity and Silence Timeout now apply to both engines - Added tooltips explaining each setting - VOSK-specific model info only shown when VOSK selected * fix: resolve overlapping UI elements in settings dialog * fix: save VAD and silence timeout settings for both engines Previously these settings were only saved when VOSK was selected. Now they persist correctly for Whisper as well. * fix: show download progress when selecting undownloaded model Previously when auto-applying settings, models would download silently in the background without showing the progress dialog. Now: - Download progress dialog appears for undownloaded models - Model icons update correctly after download completes * feat: add download progress, speed, ETA and cancel button - Show real download progress percentage for VOSK models - Display download speed (MB/s) and estimated time remaining - Add Cancel button to abort downloads - Update status during extraction phase - Whisper downloads still pulse (uses internal downloader) * fix: defer model download until needed, don't block startup - App now starts instantly even if models aren't downloaded - Models are downloaded on-demand when selected in Settings - If user tries to dictate without a model, plays error sound - Added model_ready property to check if engine is usable * fix: improve first-run UX without downloaded models - Settings dialog now defaults to smallest available model, not saved config - Only show saved model if it's actually downloaded - Add desktop notification when trying to dictate without a model - Prevents auto-downloading large models on first launch * feat: add download progress tracking for Whisper models - Implement custom Whisper model download with progress/speed/ETA - Uses OpenAI's CDN URLs directly instead of whisper.load_model() - Progress callback works the same as VOSK downloads - Supports cancellation during download * fix: enable download progress callback for Whisper models Progress callback was only being set for VOSK downloads, not Whisper. Now both engines show real-time progress, speed, and ETA. * fix: update tests for defer_download and config manager mocking - test_main: Mock ConfigManager to prevent saved config from overriding test values - test_settings_dialog: Mock ModelDownloadDialog to prevent GTK init errors - test_speech_recognition: Use proper MockVoskModule class instead of MagicMock, pass defer_download=False for download test - test_whisper_support: Pass defer_download=False to trigger model loading in test * fix: resolve test failures for CI - test_main: Fix ConfigManager patch path to use source module - test_settings_dialog: Mock _is_vosk_model_downloaded and _is_whisper_model_downloaded to skip download flow, add missing dialog attributes - test_speech_recognition: Replace recognizer with properly configured mock that returns valid JSON, fixing test isolation issues when running all tests together * fix: mock _download_whisper_model in whisper test to avoid requests exception handling issue * fix: add gir1.2-girepository-2.0 dep for PyGObject on Ubuntu 24.04 - Add missing girepository-2.0 package for newer Ubuntu runners - Use --no-build-isolation for PyGObject to use system packages * fix: skip PyGObject pip install, use system python3-gi package - Use --no-deps for editable install to skip PyGObject - Install other deps explicitly without PyGObject - Add python3-gi-cairo for complete GTK support - Removes problematic girepository-2.0 build requirement * fix: install PyGObject via pip with proper build deps - Remove python3-gi/python3-gi-cairo (doesn't work with setup-python) - Install PyGObject from pip after installing build dependencies - Simplify venv setup without --system-site-packages hack * Fix GObject * fix: set GI_TYPELIB_PATH for pip-installed PyGObject to find AppIndicator3
This commit is contained in:
@@ -5,33 +5,105 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: '3.10'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
name: Build and Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build twine wheel
|
||||
pip install build twine wheel setuptools
|
||||
|
||||
- name: Extract version from tag
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/v}
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Building version: $VERSION"
|
||||
|
||||
- name: Verify version consistency
|
||||
run: |
|
||||
# Get version from Python package
|
||||
PACKAGE_VERSION=$(python -c "from src.vocalinux.version import __version__; print(__version__)")
|
||||
TAG_VERSION=${{ steps.get_version.outputs.VERSION }}
|
||||
echo "Package version: $PACKAGE_VERSION"
|
||||
echo "Tag version: $TAG_VERSION"
|
||||
# Allow alpha/beta suffixes in package version
|
||||
if [[ ! "$PACKAGE_VERSION" == *"$TAG_VERSION"* ]] && [[ ! "$TAG_VERSION" == *"$PACKAGE_VERSION"* ]]; then
|
||||
echo "Warning: Version mismatch between tag ($TAG_VERSION) and package ($PACKAGE_VERSION)"
|
||||
fi
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
echo "Built packages:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
||||
body: |
|
||||
## Installation
|
||||
|
||||
### Quick Install (Recommended)
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh | bash
|
||||
```
|
||||
|
||||
### From Source
|
||||
```bash
|
||||
git clone https://github.com/jatinkrmalik/vocalinux.git
|
||||
cd vocalinux
|
||||
./install.sh
|
||||
```
|
||||
|
||||
### From PyPI (if available)
|
||||
```bash
|
||||
pip install vocalinux
|
||||
```
|
||||
|
||||
---
|
||||
See [CHANGELOG](https://github.com/jatinkrmalik/vocalinux/releases) for detailed changes.
|
||||
|
||||
# Optional: publish to PyPI when ready
|
||||
# publish-pypi:
|
||||
# name: Publish to PyPI
|
||||
# needs: build-and-release
|
||||
# runs-on: ubuntu-latest
|
||||
# if: ${{ !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/setup-python@v5
|
||||
# with:
|
||||
# python-version: ${{ env.PYTHON_VERSION }}
|
||||
# - run: pip install build twine
|
||||
# - run: python -m build
|
||||
# - uses: pypa/gh-action-pypi-publish@release/v1
|
||||
# with:
|
||||
# password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
name: Vocalinux Unified Pipeline
|
||||
name: Vocalinux CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
workflow_dispatch:
|
||||
|
||||
# Define path filters for different components
|
||||
env:
|
||||
WEB_FILES_FILTER: 'web/**'
|
||||
PYTHON_FILES_FILTER: 'src/**,tests/**,setup.py'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
@@ -25,106 +14,61 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
python-lint:
|
||||
name: Python Lint
|
||||
name: 🔍 Lint Python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# This step checks if Python files were changed
|
||||
- name: Check for Python file changes
|
||||
uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
python:
|
||||
- '${{ env.PYTHON_FILES_FILTER }}'
|
||||
|
||||
# Skip the rest of the job if no Python files were changed
|
||||
- name: Skip if no Python files changed
|
||||
if: steps.changes.outputs.python != 'true' && github.event_name != 'workflow_dispatch' && !contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
echo "No Python files were changed. Skipping Python linting."
|
||||
exit 0
|
||||
|
||||
- name: Set up Python
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'setup.py'
|
||||
|
||||
- name: Install development dependencies
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
- name: Install linting tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install flake8 black isort
|
||||
pip install flake8 black isort
|
||||
|
||||
- name: Lint with flake8
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
- name: Lint with flake8 (critical errors)
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 src/ tests/ --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings
|
||||
flake8 src/ tests/ --count --exit-zero --max-complexity=10 --max-line-length=100 --statistics
|
||||
|
||||
- name: Check formatting with black
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
black --check src/ tests/
|
||||
black --check --diff src/ tests/
|
||||
|
||||
- name: Check import sorting with isort
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
isort --check-only --profile black src/ tests/
|
||||
isort --check-only --diff --profile black src/ tests/
|
||||
|
||||
python-test:
|
||||
name: Python Test
|
||||
name: 🧪 Test Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: python-lint
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
python-version: ['3.8', '3.10', '3.11']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# This step checks if Python files were changed
|
||||
- name: Check for Python file changes
|
||||
uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
python:
|
||||
- '${{ env.PYTHON_FILES_FILTER }}'
|
||||
|
||||
# Skip the rest of the job if no Python files were changed
|
||||
- name: Skip if no Python files changed
|
||||
if: steps.changes.outputs.python != 'true' && github.event_name != 'workflow_dispatch' && !contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
echo "No Python files were changed. Skipping Python tests."
|
||||
exit 0
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'setup.py'
|
||||
|
||||
- name: Install system dependencies
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
xdotool \
|
||||
python3-gi \
|
||||
gir1.2-appindicator3-0.1 \
|
||||
libcairo2-dev \
|
||||
pkg-config \
|
||||
@@ -135,74 +79,55 @@ jobs:
|
||||
gir1.2-gtk-3.0 \
|
||||
portaudio19-dev
|
||||
|
||||
- name: Cache apt packages
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: ${{ runner.os }}-apt-${{ hashFiles('**/apt-packages.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-apt-
|
||||
- name: Install GObject introspection dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
# For Ubuntu 24.04+ (or the latest GitHub Actions runner image)
|
||||
sudo apt-get install -y gir1.2-glib-2.0 libgirepository-2.0-dev pkg-config
|
||||
# Alternatively, for older Ubuntu versions (like 22.04), you might need:
|
||||
# sudo apt-get install -y libgirepository1.0-dev pkg-config
|
||||
|
||||
- name: Set up virtual environment
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
# Install the package in development mode with dev dependencies
|
||||
python -m pip install -e ".[dev]"
|
||||
python -m pip install pytest pytest-cov
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
# Install PyGObject from pip (with build deps installed above)
|
||||
pip install PyGObject
|
||||
pip install -e ".[dev]"
|
||||
echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $GITHUB_ENV
|
||||
echo "PATH=$VIRTUAL_ENV/bin:$PATH" >> $GITHUB_ENV
|
||||
|
||||
- name: Run pytest suite with coverage
|
||||
if: steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
# Make sure Python can find the 'src' module
|
||||
PYTHONPATH=$PWD pytest --junitxml=junit.xml --cov=src tests/
|
||||
# Set GI_TYPELIB_PATH so pip-installed PyGObject can find system typelibs (AppIndicator3, etc.)
|
||||
export GI_TYPELIB_PATH=/usr/lib/x86_64-linux-gnu/girepository-1.0:/usr/lib/girepository-1.0
|
||||
PYTHONPATH=$PWD pytest --junitxml=junit-${{ matrix.python-version }}.xml --cov=src --cov-report=xml tests/
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
if: ${{ !cancelled() && (steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]')) }}
|
||||
uses: codecov/test-results-action@v1
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-${{ matrix.python-version }}
|
||||
path: junit-${{ matrix.python-version }}.xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.python-version == '3.10'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: coverage.xml
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: ${{ steps.changes.outputs.python == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[python]') }}
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
web-validate:
|
||||
name: Web Validation
|
||||
web-build:
|
||||
name: 🌐 Build Website
|
||||
runs-on: ubuntu-latest
|
||||
# Use GitHub's path filtering to determine if this job should run
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]') }}
|
||||
if: contains(github.event.head_commit.message, '[web]') || github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# This step checks if web files were changed
|
||||
- name: Check for web file changes
|
||||
uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
web:
|
||||
- '${{ env.WEB_FILES_FILTER }}'
|
||||
|
||||
# Skip the rest of the job if no web files were changed
|
||||
- name: Skip if no web files changed
|
||||
if: steps.changes.outputs.web != 'true' && github.event_name != 'workflow_dispatch' && !contains(github.event.head_commit.message, '[web]')
|
||||
run: |
|
||||
echo "No web files were changed. Skipping web validation."
|
||||
exit 0
|
||||
|
||||
# Continue with web validation if files were changed
|
||||
- name: Setup Node.js
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
@@ -210,90 +135,47 @@ jobs:
|
||||
cache-dependency-path: './web/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: |
|
||||
if [ -f "package-lock.json" ]; then
|
||||
npm ci
|
||||
else
|
||||
npm install
|
||||
fi
|
||||
run: npm ci || npm install
|
||||
|
||||
- name: Lint
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: npm run lint || echo "Linting failed but continuing with the build"
|
||||
run: npm run lint || true
|
||||
|
||||
- name: Type check
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: npm run test:types || echo "Type checking failed but continuing with the build"
|
||||
|
||||
- name: Format check
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: npm run format:check || echo "Format checking failed but continuing with the build"
|
||||
run: npm run test:types || true
|
||||
|
||||
- name: Build
|
||||
if: steps.changes.outputs.web == 'true' || github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: npm run build
|
||||
|
||||
deploy-website:
|
||||
name: Deploy Website
|
||||
name: 🚀 Deploy Website
|
||||
runs-on: ubuntu-latest
|
||||
needs: [python-test, web-validate]
|
||||
# Only deploy on pushes to main branch
|
||||
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
|
||||
needs: [python-test, web-build]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, '[web]')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# This step checks if web files were changed
|
||||
- name: Check for web file changes
|
||||
uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
web:
|
||||
- '${{ env.WEB_FILES_FILTER }}'
|
||||
|
||||
# Skip the rest of the job if no web files were changed
|
||||
- name: Skip if no web files changed
|
||||
if: steps.changes.outputs.web != 'true' && !contains(github.event.head_commit.message, '[web]')
|
||||
run: |
|
||||
echo "No web files were changed. Skipping website deployment."
|
||||
exit 0
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.changes.outputs.web == 'true' || contains(github.event.head_commit.message, '[web]')
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: './web/package-lock.json'
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.web == 'true' || contains(github.event.head_commit.message, '[web]')
|
||||
- name: Install and build
|
||||
working-directory: ./web
|
||||
run: |
|
||||
if [ -f "package-lock.json" ]; then
|
||||
npm ci
|
||||
else
|
||||
npm install
|
||||
fi
|
||||
|
||||
- name: Build website
|
||||
if: steps.changes.outputs.web == 'true' || contains(github.event.head_commit.message, '[web]')
|
||||
working-directory: ./web
|
||||
run: npm run deploy
|
||||
npm ci || npm install
|
||||
npm run deploy
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
if: steps.changes.outputs.web == 'true' || contains(github.event.head_commit.message, '[web]')
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./web/out
|
||||
publish_branch: gh-pages
|
||||
cname: vocalinux.com
|
||||
force_orphan: true
|
||||
force_orphan: true
|
||||
|
||||
Reference in New Issue
Block a user