Compare commits
1 Commits
v1.4.8
...
feature/ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a01dfb3fa |
@@ -1,100 +0,0 @@
|
|||||||
name: Build App (Linux)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Release tag to build (e.g. v1.4.5)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-linux:
|
|
||||||
name: Build App (Linux)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
NODE_VERSION: "20"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
run: |
|
|
||||||
TAG="${{ github.event.inputs.tag }}"
|
|
||||||
if [ -z "$TAG" ]; then
|
|
||||||
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -1 | sed 's|.*refs/tags/||')
|
|
||||||
fi
|
|
||||||
echo "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
|
||||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils rpm
|
|
||||||
|
|
||||||
- name: Install npm dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build Tauri app
|
|
||||||
run: npm run tauri build
|
|
||||||
|
|
||||||
- name: Upload to release
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y jq
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.tag.outputs.tag }}"
|
|
||||||
echo "Release tag: ${TAG}"
|
|
||||||
|
|
||||||
echo "Waiting for release ${TAG} to be available..."
|
|
||||||
RELEASE_ID=""
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/tags/${TAG}")
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
|
||||||
|
|
||||||
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
|
||||||
echo "Found release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
|
||||||
echo "ERROR: Failed to find release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
find src-tauri/target/release/bundle -type f \( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) | while IFS= read -r file; do
|
|
||||||
filename=$(basename "$file")
|
|
||||||
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
|
||||||
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
|
||||||
|
|
||||||
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
|
||||||
if [ -n "${ASSET_ID}" ]; then
|
|
||||||
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
-T "$file" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
|
||||||
echo "Upload response: HTTP ${HTTP_CODE}"
|
|
||||||
done
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
name: Build App (macOS)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Release tag to build (e.g. v1.4.5)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos:
|
|
||||||
name: Build App (macOS)
|
|
||||||
runs-on: macos-latest
|
|
||||||
env:
|
|
||||||
NODE_VERSION: "20"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
run: |
|
|
||||||
TAG="${{ github.event.inputs.tag }}"
|
|
||||||
if [ -z "$TAG" ]; then
|
|
||||||
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | head -1 | sed 's|.*refs/tags/||')
|
|
||||||
fi
|
|
||||||
echo "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
|
||||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: brew install --quiet create-dmg || true
|
|
||||||
|
|
||||||
- name: Install npm dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build Tauri app
|
|
||||||
run: npm run tauri build
|
|
||||||
|
|
||||||
- name: Upload to release
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
which jq || brew install jq
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.tag.outputs.tag }}"
|
|
||||||
echo "Release tag: ${TAG}"
|
|
||||||
|
|
||||||
echo "Waiting for release ${TAG} to be available..."
|
|
||||||
RELEASE_ID=""
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/tags/${TAG}")
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
|
||||||
|
|
||||||
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
|
||||||
echo "Found release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
|
||||||
echo "ERROR: Failed to find release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
find src-tauri/target/release/bundle -type f -name "*.dmg" | while IFS= read -r file; do
|
|
||||||
filename=$(basename "$file")
|
|
||||||
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
|
||||||
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
|
||||||
|
|
||||||
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
|
||||||
if [ -n "${ASSET_ID}" ]; then
|
|
||||||
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
-T "$file" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
|
||||||
echo "Upload response: HTTP ${HTTP_CODE}"
|
|
||||||
done
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
name: Build App (Windows)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Release tag to build (e.g. v1.4.5)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows:
|
|
||||||
name: Build App (Windows)
|
|
||||||
runs-on: windows-latest
|
|
||||||
env:
|
|
||||||
NODE_VERSION: "20"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
$TAG = "${{ github.event.inputs.tag }}"
|
|
||||||
if (-not $TAG) {
|
|
||||||
$TAG = (git ls-remote --tags --sort=-v:refname origin 'refs/tags/v*' | Select-Object -First 1) -replace '.*refs/tags/', ''
|
|
||||||
}
|
|
||||||
Write-Host "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $env:GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
if (Get-Command rustup -ErrorAction SilentlyContinue) {
|
|
||||||
rustup default stable
|
|
||||||
} else {
|
|
||||||
Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
|
|
||||||
.\rustup-init.exe -y --default-toolchain stable
|
|
||||||
echo "$env:USERPROFILE\.cargo\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Install npm dependencies
|
|
||||||
shell: powershell
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Build Tauri app
|
|
||||||
shell: powershell
|
|
||||||
run: npm run tauri build
|
|
||||||
|
|
||||||
- name: Upload to release
|
|
||||||
shell: powershell
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
$REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
||||||
$Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" }
|
|
||||||
$TAG = "${{ steps.tag.outputs.tag }}"
|
|
||||||
Write-Host "Release tag: ${TAG}"
|
|
||||||
|
|
||||||
Write-Host "Waiting for release ${TAG} to be available..."
|
|
||||||
$RELEASE_ID = $null
|
|
||||||
|
|
||||||
for ($i = 1; $i -le 30; $i++) {
|
|
||||||
try {
|
|
||||||
$release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop
|
|
||||||
$RELEASE_ID = $release.id
|
|
||||||
|
|
||||||
if ($RELEASE_ID) {
|
|
||||||
Write-Host "Found release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
Write-Host "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $RELEASE_ID) {
|
|
||||||
Write-Host "ERROR: Failed to find release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Get-ChildItem -Path src-tauri\target\release\bundle -Recurse -Include *.msi,*-setup.exe | ForEach-Object {
|
|
||||||
$filename = $_.Name
|
|
||||||
$encodedName = [System.Uri]::EscapeDataString($filename)
|
|
||||||
$size = [math]::Round($_.Length / 1MB, 1)
|
|
||||||
Write-Host "Uploading ${filename} (${size} MB)..."
|
|
||||||
|
|
||||||
try {
|
|
||||||
$assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers
|
|
||||||
$existing = $assets | Where-Object { $_.name -eq $filename }
|
|
||||||
if ($existing) {
|
|
||||||
Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
$uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}"
|
|
||||||
$result = curl.exe --fail --silent --show-error `
|
|
||||||
-X POST `
|
|
||||||
-H "Authorization: token $env:BUILD_TOKEN" `
|
|
||||||
-H "Content-Type: application/octet-stream" `
|
|
||||||
-T "$($_.FullName)" `
|
|
||||||
"$uploadUrl" 2>&1
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Write-Host "Upload successful: ${filename}"
|
|
||||||
} else {
|
|
||||||
Write-Host "WARNING: Upload failed for ${filename}: ${result}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
name: Build Sidecar (Linux)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Sidecar release tag to build (e.g. sidecar-v1.0.3)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-sidecar-linux:
|
|
||||||
name: Build Sidecar (Linux)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.11"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
run: |
|
|
||||||
TAG="${{ github.event.inputs.tag }}"
|
|
||||||
if [ -z "$TAG" ]; then
|
|
||||||
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/sidecar-v*' | head -1 | sed 's|.*refs/tags/||')
|
|
||||||
fi
|
|
||||||
echo "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
run: |
|
|
||||||
if command -v uv &> /dev/null; then
|
|
||||||
echo "uv already installed: $(uv --version)"
|
|
||||||
else
|
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
||||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
run: uv python install ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y portaudio19-dev
|
|
||||||
|
|
||||||
- name: Build sidecar (CUDA)
|
|
||||||
run: |
|
|
||||||
uv sync --frozen || uv sync
|
|
||||||
uv run pyinstaller local-transcription-headless.spec
|
|
||||||
|
|
||||||
- name: Package sidecar (CUDA)
|
|
||||||
run: |
|
|
||||||
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cuda.zip .
|
|
||||||
|
|
||||||
- name: Build sidecar (CPU)
|
|
||||||
run: |
|
|
||||||
rm -rf dist/local-transcription-backend build/
|
|
||||||
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
|
|
||||||
# Run pyinstaller directly from venv to prevent uv run from
|
|
||||||
# re-resolving torch back to the CUDA version via pyproject.toml sources
|
|
||||||
.venv/bin/pyinstaller local-transcription-headless.spec
|
|
||||||
|
|
||||||
- name: Package sidecar (CPU)
|
|
||||||
run: |
|
|
||||||
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cpu.zip .
|
|
||||||
|
|
||||||
- name: Upload to sidecar release
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get install -y jq
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.tag.outputs.tag }}"
|
|
||||||
|
|
||||||
echo "Waiting for sidecar release ${TAG} to be available..."
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/tags/${TAG}")
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
|
||||||
|
|
||||||
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
|
||||||
echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
|
||||||
echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for file in sidecar-*.zip; do
|
|
||||||
filename=$(basename "$file")
|
|
||||||
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
|
||||||
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
|
||||||
|
|
||||||
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
|
||||||
if [ -n "${ASSET_ID}" ]; then
|
|
||||||
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
-T "$file" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
|
||||||
echo "Upload response: HTTP ${HTTP_CODE}"
|
|
||||||
done
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
name: Build Sidecar (macOS)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Sidecar release tag to build (e.g. sidecar-v1.0.3)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-sidecar-macos:
|
|
||||||
name: Build Sidecar (macOS)
|
|
||||||
runs-on: macos-latest
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.11"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
run: |
|
|
||||||
TAG="${{ github.event.inputs.tag }}"
|
|
||||||
if [ -z "$TAG" ]; then
|
|
||||||
TAG=$(git ls-remote --tags --sort=-v:refname origin 'refs/tags/sidecar-v*' | head -1 | sed 's|.*refs/tags/||')
|
|
||||||
fi
|
|
||||||
echo "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
run: |
|
|
||||||
if command -v uv &> /dev/null; then
|
|
||||||
echo "uv already installed: $(uv --version)"
|
|
||||||
else
|
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
||||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
run: uv python install ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: brew install portaudio
|
|
||||||
|
|
||||||
- name: Build sidecar (CPU)
|
|
||||||
env:
|
|
||||||
UV_NO_SOURCES: "1"
|
|
||||||
run: |
|
|
||||||
# UV_NO_SOURCES bypasses pyproject.toml's [tool.uv.sources] which forces
|
|
||||||
# torch from the CUDA index (no macOS ARM wheels there).
|
|
||||||
# Default PyPI torch includes MPS (Apple Silicon GPU) support.
|
|
||||||
uv sync
|
|
||||||
.venv/bin/pyinstaller local-transcription-headless.spec
|
|
||||||
|
|
||||||
- name: Package sidecar (CPU)
|
|
||||||
run: |
|
|
||||||
cd dist/local-transcription-backend && zip -r ../../sidecar-macos-aarch64-cpu.zip .
|
|
||||||
|
|
||||||
- name: Upload to sidecar release
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
which jq || brew install jq
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.tag.outputs.tag }}"
|
|
||||||
|
|
||||||
echo "Waiting for sidecar release ${TAG} to be available..."
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/tags/${TAG}")
|
|
||||||
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
|
||||||
|
|
||||||
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
|
||||||
echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
|
||||||
echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for file in sidecar-*.zip; do
|
|
||||||
filename=$(basename "$file")
|
|
||||||
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
|
||||||
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
|
||||||
|
|
||||||
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
|
||||||
if [ -n "${ASSET_ID}" ]; then
|
|
||||||
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/octet-stream" \
|
|
||||||
-T "$file" \
|
|
||||||
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
|
||||||
echo "Upload response: HTTP ${HTTP_CODE}"
|
|
||||||
done
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
name: Build Sidecar (Windows)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
tag:
|
|
||||||
description: 'Sidecar release tag to build (e.g. sidecar-v1.0.3)'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-sidecar-windows:
|
|
||||||
name: Build Sidecar (Windows)
|
|
||||||
runs-on: windows-latest
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.11"
|
|
||||||
steps:
|
|
||||||
- name: Determine tag
|
|
||||||
id: tag
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
$TAG = "${{ github.event.inputs.tag }}"
|
|
||||||
if (-not $TAG) {
|
|
||||||
$TAG = (git ls-remote --tags --sort=-v:refname origin 'refs/tags/sidecar-v*' | Select-Object -First 1) -replace '.*refs/tags/', ''
|
|
||||||
}
|
|
||||||
Write-Host "Building for tag: ${TAG}"
|
|
||||||
echo "tag=${TAG}" >> $env:GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.tag.outputs.tag }}
|
|
||||||
|
|
||||||
- name: Install uv
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
if (Get-Command uv -ErrorAction SilentlyContinue) {
|
|
||||||
Write-Host "uv already installed: $(uv --version)"
|
|
||||||
} else {
|
|
||||||
irm https://astral.sh/uv/install.ps1 | iex
|
|
||||||
# Add both possible uv install locations to PATH
|
|
||||||
$uvPaths = @(
|
|
||||||
"$env:USERPROFILE\.local\bin",
|
|
||||||
"$env:USERPROFILE\.cargo\bin",
|
|
||||||
"$env:LOCALAPPDATA\uv\bin"
|
|
||||||
)
|
|
||||||
foreach ($p in $uvPaths) {
|
|
||||||
if (Test-Path $p) {
|
|
||||||
echo $p | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
shell: powershell
|
|
||||||
run: uv python install ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- name: Install 7-Zip
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
|
|
||||||
choco install 7zip -y
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Build sidecar (CUDA)
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
uv sync --frozen
|
|
||||||
if ($LASTEXITCODE -ne 0) { uv sync }
|
|
||||||
uv run pyinstaller local-transcription-headless.spec
|
|
||||||
|
|
||||||
- name: Package sidecar (CUDA)
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
7z a -tzip -mx=5 sidecar-windows-x86_64-cuda.zip .\dist\local-transcription-backend\*
|
|
||||||
|
|
||||||
- name: Build sidecar (CPU)
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
Remove-Item -Recurse -Force dist\local-transcription-backend, build -ErrorAction SilentlyContinue
|
|
||||||
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
|
|
||||||
# Run pyinstaller directly from venv to prevent uv run from
|
|
||||||
# re-resolving torch back to the CUDA version via pyproject.toml sources
|
|
||||||
.venv\Scripts\pyinstaller.exe local-transcription-headless.spec
|
|
||||||
|
|
||||||
- name: Package sidecar (CPU)
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
7z a -tzip -mx=5 sidecar-windows-x86_64-cpu.zip .\dist\local-transcription-backend\*
|
|
||||||
|
|
||||||
- name: Upload to sidecar release
|
|
||||||
shell: powershell
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
$REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
|
||||||
$Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" }
|
|
||||||
$TAG = "${{ steps.tag.outputs.tag }}"
|
|
||||||
|
|
||||||
Write-Host "Waiting for sidecar release ${TAG} to be available..."
|
|
||||||
$RELEASE_ID = $null
|
|
||||||
|
|
||||||
for ($i = 1; $i -le 30; $i++) {
|
|
||||||
try {
|
|
||||||
$release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop
|
|
||||||
$RELEASE_ID = $release.id
|
|
||||||
|
|
||||||
if ($RELEASE_ID) {
|
|
||||||
Write-Host "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
Write-Host "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $RELEASE_ID) {
|
|
||||||
Write-Host "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Get-ChildItem -Path . -Filter "sidecar-*.zip" | ForEach-Object {
|
|
||||||
$filename = $_.Name
|
|
||||||
$encodedName = [System.Uri]::EscapeDataString($filename)
|
|
||||||
$size = [math]::Round($_.Length / 1MB, 1)
|
|
||||||
Write-Host "Uploading ${filename} (${size} MB)..."
|
|
||||||
|
|
||||||
try {
|
|
||||||
$assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers
|
|
||||||
$existing = $assets | Where-Object { $_.name -eq $filename }
|
|
||||||
if ($existing) {
|
|
||||||
Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
$uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}"
|
|
||||||
$result = curl.exe --fail --silent --show-error `
|
|
||||||
-X POST `
|
|
||||||
-H "Authorization: token $env:BUILD_TOKEN" `
|
|
||||||
-H "Content-Type: application/octet-stream" `
|
|
||||||
-T "$($_.FullName)" `
|
|
||||||
"$uploadUrl" 2>&1
|
|
||||||
if ($LASTEXITCODE -eq 0) {
|
|
||||||
Write-Host "Upload successful: ${filename}"
|
|
||||||
} else {
|
|
||||||
Write-Host "WARNING: Upload failed for ${filename}: ${result}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
425
.gitea/workflows/build-sidecar.yml
Normal file
425
.gitea/workflows/build-sidecar.yml
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
name: Build Sidecars
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'client/**'
|
||||||
|
- 'server/**'
|
||||||
|
- 'backend/**'
|
||||||
|
- 'pyproject.toml'
|
||||||
|
- 'local-transcription-headless.spec'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump-sidecar-version:
|
||||||
|
name: Bump sidecar version and tag
|
||||||
|
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.bump.outputs.version }}
|
||||||
|
tag: ${{ steps.bump.outputs.tag }}
|
||||||
|
has_changes: ${{ steps.check_changes.outputs.has_changes }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Check for backend changes
|
||||||
|
id: check_changes
|
||||||
|
run: |
|
||||||
|
# If triggered by workflow_dispatch, always build
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Check if relevant files changed in this commit
|
||||||
|
CHANGED=$(git diff --name-only HEAD~1 HEAD -- client/ server/ backend/ pyproject.toml local-transcription-headless.spec 2>/dev/null || echo "")
|
||||||
|
if [ -n "$CHANGED" ]; then
|
||||||
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend changes detected: $CHANGED"
|
||||||
|
else
|
||||||
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "No backend changes detected, skipping sidecar build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Configure git
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
run: |
|
||||||
|
git config user.name "Gitea Actions"
|
||||||
|
git config user.email "actions@gitea.local"
|
||||||
|
|
||||||
|
- name: Bump sidecar patch version
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
id: bump
|
||||||
|
run: |
|
||||||
|
# Read current version from pyproject.toml
|
||||||
|
CURRENT=$(grep '^version = ' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
||||||
|
echo "Current sidecar version: ${CURRENT}"
|
||||||
|
|
||||||
|
# Increment patch number
|
||||||
|
MAJOR=$(echo "${CURRENT}" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "${CURRENT}" | cut -d. -f2)
|
||||||
|
PATCH=$(echo "${CURRENT}" | cut -d. -f3)
|
||||||
|
NEW_PATCH=$((PATCH + 1))
|
||||||
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||||
|
echo "New sidecar version: ${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Update pyproject.toml
|
||||||
|
sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" pyproject.toml
|
||||||
|
|
||||||
|
# Update version.py
|
||||||
|
sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py
|
||||||
|
sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py
|
||||||
|
|
||||||
|
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "tag=sidecar-v${NEW_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Commit and tag
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
NEW_VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
TAG="${{ steps.bump.outputs.tag }}"
|
||||||
|
git add pyproject.toml version.py
|
||||||
|
git commit -m "chore: bump sidecar version to ${NEW_VERSION} [skip ci]"
|
||||||
|
git tag "${TAG}"
|
||||||
|
|
||||||
|
REMOTE_URL=$(git remote get-url origin | sed "s|://|://gitea-actions:${BUILD_TOKEN}@|")
|
||||||
|
git pull --rebase "${REMOTE_URL}" main || true
|
||||||
|
git push "${REMOTE_URL}" HEAD:main
|
||||||
|
git push "${REMOTE_URL}" "${TAG}"
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
if: steps.check_changes.outputs.has_changes == 'true'
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
TAG="${{ steps.bump.outputs.tag }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version }}"
|
||||||
|
RELEASE_NAME="Sidecar v${VERSION}"
|
||||||
|
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${RELEASE_NAME}\", \"body\": \"Automated sidecar build.\", \"draft\": false, \"prerelease\": false}" \
|
||||||
|
"${REPO_API}/releases"
|
||||||
|
echo "Created release: ${RELEASE_NAME}"
|
||||||
|
|
||||||
|
# ── Linux sidecar (CUDA + CPU) ──
|
||||||
|
|
||||||
|
build-sidecar-linux:
|
||||||
|
name: Build Sidecar (Linux)
|
||||||
|
needs: bump-sidecar-version
|
||||||
|
if: needs.bump-sidecar-version.outputs.has_changes == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.11"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-sidecar-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
run: |
|
||||||
|
if command -v uv &> /dev/null; then
|
||||||
|
echo "uv already installed: $(uv --version)"
|
||||||
|
else
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y portaudio19-dev
|
||||||
|
|
||||||
|
- name: Build sidecar (CUDA)
|
||||||
|
run: |
|
||||||
|
uv sync --frozen || uv sync
|
||||||
|
uv run pyinstaller local-transcription-headless.spec
|
||||||
|
|
||||||
|
- name: Package sidecar (CUDA)
|
||||||
|
run: |
|
||||||
|
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cuda.zip .
|
||||||
|
|
||||||
|
- name: Build sidecar (CPU)
|
||||||
|
run: |
|
||||||
|
rm -rf dist/local-transcription-backend build/
|
||||||
|
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
|
||||||
|
uv run pyinstaller local-transcription-headless.spec
|
||||||
|
|
||||||
|
- name: Package sidecar (CPU)
|
||||||
|
run: |
|
||||||
|
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cpu.zip .
|
||||||
|
|
||||||
|
- name: Upload to sidecar release
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y jq
|
||||||
|
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
TAG="${{ needs.bump-sidecar-version.outputs.tag }}"
|
||||||
|
|
||||||
|
echo "Waiting for sidecar release ${TAG} to be available..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/tags/${TAG}")
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
||||||
|
echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
||||||
|
echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in sidecar-*.zip; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
||||||
|
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
||||||
|
|
||||||
|
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
||||||
|
if [ -n "${ASSET_ID}" ]; then
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-T "$file" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
||||||
|
echo "Upload response: HTTP ${HTTP_CODE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Windows sidecar (CUDA + CPU) ──
|
||||||
|
|
||||||
|
build-sidecar-windows:
|
||||||
|
name: Build Sidecar (Windows)
|
||||||
|
needs: bump-sidecar-version
|
||||||
|
if: needs.bump-sidecar-version.outputs.has_changes == 'true'
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.11"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-sidecar-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
if (Get-Command uv -ErrorAction SilentlyContinue) {
|
||||||
|
Write-Host "uv already installed: $(uv --version)"
|
||||||
|
} else {
|
||||||
|
irm https://astral.sh/uv/install.ps1 | iex
|
||||||
|
# Add both possible uv install locations to PATH
|
||||||
|
$uvPaths = @(
|
||||||
|
"$env:USERPROFILE\.local\bin",
|
||||||
|
"$env:USERPROFILE\.cargo\bin",
|
||||||
|
"$env:LOCALAPPDATA\uv\bin"
|
||||||
|
)
|
||||||
|
foreach ($p in $uvPaths) {
|
||||||
|
if (Test-Path $p) {
|
||||||
|
echo $p | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
shell: powershell
|
||||||
|
run: uv python install ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Install 7-Zip
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
if (-not (Get-Command 7z -ErrorAction SilentlyContinue)) {
|
||||||
|
choco install 7zip -y
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Build sidecar (CUDA)
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
uv sync --frozen
|
||||||
|
if ($LASTEXITCODE -ne 0) { uv sync }
|
||||||
|
uv run pyinstaller local-transcription-headless.spec
|
||||||
|
|
||||||
|
- name: Package sidecar (CUDA)
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
7z a -tzip -mx=5 sidecar-windows-x86_64-cuda.zip .\dist\local-transcription-backend\*
|
||||||
|
|
||||||
|
- name: Build sidecar (CPU)
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
Remove-Item -Recurse -Force dist\local-transcription-backend, build -ErrorAction SilentlyContinue
|
||||||
|
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
|
||||||
|
uv run pyinstaller local-transcription-headless.spec
|
||||||
|
|
||||||
|
- name: Package sidecar (CPU)
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
7z a -tzip -mx=5 sidecar-windows-x86_64-cpu.zip .\dist\local-transcription-backend\*
|
||||||
|
|
||||||
|
- name: Upload to sidecar release
|
||||||
|
shell: powershell
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||||
|
$Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" }
|
||||||
|
$TAG = "${{ needs.bump-sidecar-version.outputs.tag }}"
|
||||||
|
|
||||||
|
Write-Host "Waiting for sidecar release ${TAG} to be available..."
|
||||||
|
$RELEASE_ID = $null
|
||||||
|
|
||||||
|
for ($i = 1; $i -le 30; $i++) {
|
||||||
|
try {
|
||||||
|
$release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop
|
||||||
|
$RELEASE_ID = $release.id
|
||||||
|
|
||||||
|
if ($RELEASE_ID) {
|
||||||
|
Write-Host "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
Write-Host "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $RELEASE_ID) {
|
||||||
|
Write-Host "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Get-ChildItem -Path . -Filter "sidecar-*.zip" | ForEach-Object {
|
||||||
|
$filename = $_.Name
|
||||||
|
$encodedName = [System.Uri]::EscapeDataString($filename)
|
||||||
|
$size = [math]::Round($_.Length / 1MB, 1)
|
||||||
|
Write-Host "Uploading ${filename} (${size} MB)..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
$assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers
|
||||||
|
$existing = $assets | Where-Object { $_.name -eq $filename }
|
||||||
|
if ($existing) {
|
||||||
|
Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
$uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}"
|
||||||
|
$result = curl.exe --fail --silent --show-error `
|
||||||
|
-X POST `
|
||||||
|
-H "Authorization: token $env:BUILD_TOKEN" `
|
||||||
|
-H "Content-Type: application/octet-stream" `
|
||||||
|
-T "$($_.FullName)" `
|
||||||
|
"$uploadUrl" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "Upload successful: ${filename}"
|
||||||
|
} else {
|
||||||
|
Write-Host "WARNING: Upload failed for ${filename}: ${result}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── macOS sidecar (CPU only — no CUDA on macOS) ──
|
||||||
|
|
||||||
|
build-sidecar-macos:
|
||||||
|
name: Build Sidecar (macOS)
|
||||||
|
needs: bump-sidecar-version
|
||||||
|
if: needs.bump-sidecar-version.outputs.has_changes == 'true'
|
||||||
|
runs-on: macos-latest
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: "3.11"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-sidecar-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
run: |
|
||||||
|
if command -v uv &> /dev/null; then
|
||||||
|
echo "uv already installed: $(uv --version)"
|
||||||
|
else
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: brew install portaudio
|
||||||
|
|
||||||
|
- name: Build sidecar (CPU)
|
||||||
|
run: |
|
||||||
|
# --no-sources bypasses pyproject.toml's [tool.uv.sources] which forces
|
||||||
|
# torch from the CUDA index (no macOS ARM wheels there)
|
||||||
|
# Default PyPI torch includes MPS (Apple Silicon GPU) support
|
||||||
|
uv sync --no-sources
|
||||||
|
uv run pyinstaller local-transcription-headless.spec
|
||||||
|
|
||||||
|
- name: Package sidecar (CPU)
|
||||||
|
run: |
|
||||||
|
cd dist/local-transcription-backend && zip -r ../../sidecar-macos-aarch64-cpu.zip .
|
||||||
|
|
||||||
|
- name: Upload to sidecar release
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
which jq || brew install jq
|
||||||
|
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
TAG="${{ needs.bump-sidecar-version.outputs.tag }}"
|
||||||
|
|
||||||
|
echo "Waiting for sidecar release ${TAG} to be available..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
RELEASE_JSON=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/tags/${TAG}")
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -n "${RELEASE_ID}" ] && [ "${RELEASE_ID}" != "null" ]; then
|
||||||
|
echo "Found sidecar release: ${TAG} (ID: ${RELEASE_ID})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Attempt ${i}/30: Release not ready yet, retrying in 10s..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
||||||
|
echo "ERROR: Failed to find sidecar release for tag ${TAG} after 30 attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in sidecar-*.zip; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
||||||
|
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
||||||
|
|
||||||
|
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
||||||
|
if [ -n "${ASSET_ID}" ]; then
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-T "$file" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
||||||
|
echo "Upload response: HTTP ${HTTP_CODE}"
|
||||||
|
done
|
||||||
@@ -25,9 +25,11 @@ jobs:
|
|||||||
- name: Bump patch version
|
- name: Bump patch version
|
||||||
id: bump
|
id: bump
|
||||||
run: |
|
run: |
|
||||||
|
# Read current version from package.json
|
||||||
CURRENT=$(grep '"version"' package.json | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
CURRENT=$(grep '"version"' package.json | head -1 | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||||
echo "Current version: ${CURRENT}"
|
echo "Current version: ${CURRENT}"
|
||||||
|
|
||||||
|
# Increment patch number
|
||||||
MAJOR=$(echo "${CURRENT}" | cut -d. -f1)
|
MAJOR=$(echo "${CURRENT}" | cut -d. -f1)
|
||||||
MINOR=$(echo "${CURRENT}" | cut -d. -f2)
|
MINOR=$(echo "${CURRENT}" | cut -d. -f2)
|
||||||
PATCH=$(echo "${CURRENT}" | cut -d. -f3)
|
PATCH=$(echo "${CURRENT}" | cut -d. -f3)
|
||||||
@@ -35,9 +37,16 @@ jobs:
|
|||||||
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
||||||
echo "New version: ${NEW_VERSION}"
|
echo "New version: ${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Update package.json
|
||||||
sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" package.json
|
sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" package.json
|
||||||
|
|
||||||
|
# Update src-tauri/tauri.conf.json
|
||||||
sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" src-tauri/tauri.conf.json
|
sed -i "s/\"version\": \"${CURRENT}\"/\"version\": \"${NEW_VERSION}\"/" src-tauri/tauri.conf.json
|
||||||
|
|
||||||
|
# Update src-tauri/Cargo.toml
|
||||||
sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" src-tauri/Cargo.toml
|
sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" src-tauri/Cargo.toml
|
||||||
|
|
||||||
|
# Update version.py
|
||||||
sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py
|
sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py
|
||||||
sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py
|
sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py
|
||||||
|
|
||||||
@@ -73,19 +82,219 @@ jobs:
|
|||||||
"${REPO_API}/releases"
|
"${REPO_API}/releases"
|
||||||
echo "Created release: ${RELEASE_NAME}"
|
echo "Created release: ${RELEASE_NAME}"
|
||||||
|
|
||||||
- name: Trigger per-OS app builds
|
# ── Platform builds (run after version bump) ──
|
||||||
|
|
||||||
|
build-linux:
|
||||||
|
name: Build App (Linux)
|
||||||
|
needs: bump-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NODE_VERSION: "20"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||||
|
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils rpm
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Tauri app
|
||||||
|
run: npm run tauri build
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
env:
|
env:
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt-get install -y jq
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
TAG="${{ steps.bump.outputs.tag }}"
|
TAG="${{ needs.bump-version.outputs.tag }}"
|
||||||
|
echo "Release tag: ${TAG}"
|
||||||
|
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/tags/${TAG}" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
||||||
|
echo "ERROR: Failed to find release for tag ${TAG}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Release ID: ${RELEASE_ID}"
|
||||||
|
|
||||||
|
find src-tauri/target/release/bundle -type f \( -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" \) | while IFS= read -r file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
||||||
|
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
||||||
|
|
||||||
|
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
||||||
|
if [ -n "${ASSET_ID}" ]; then
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
for workflow in build-app-linux.yml build-app-windows.yml build-app-macos.yml; do
|
|
||||||
echo "Dispatching ${workflow} for ${TAG}..."
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
-H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/octet-stream" \
|
||||||
-d "{\"ref\": \"main\", \"inputs\": {\"tag\": \"${TAG}\"}}" \
|
-T "$file" \
|
||||||
"${REPO_API}/actions/workflows/${workflow}/dispatches")
|
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
||||||
echo " -> HTTP ${HTTP_CODE}"
|
echo "Upload response: HTTP ${HTTP_CODE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
build-windows:
|
||||||
|
name: Build App (Windows)
|
||||||
|
needs: bump-version
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
NODE_VERSION: "20"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
if (Get-Command rustup -ErrorAction SilentlyContinue) {
|
||||||
|
rustup default stable
|
||||||
|
} else {
|
||||||
|
Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile rustup-init.exe
|
||||||
|
.\rustup-init.exe -y --default-toolchain stable
|
||||||
|
echo "$env:USERPROFILE\.cargo\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
shell: powershell
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Tauri app
|
||||||
|
shell: powershell
|
||||||
|
run: npm run tauri build
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
shell: powershell
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$REPO_API = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||||
|
$Headers = @{ "Authorization" = "token $env:BUILD_TOKEN" }
|
||||||
|
$TAG = "${{ needs.bump-version.outputs.tag }}"
|
||||||
|
Write-Host "Release tag: ${TAG}"
|
||||||
|
|
||||||
|
$release = Invoke-RestMethod -Uri "${REPO_API}/releases/tags/${TAG}" -Headers $Headers -ErrorAction Stop
|
||||||
|
$RELEASE_ID = $release.id
|
||||||
|
Write-Host "Release ID: ${RELEASE_ID}"
|
||||||
|
|
||||||
|
Get-ChildItem -Path src-tauri\target\release\bundle -Recurse -Include *.msi,*-setup.exe | ForEach-Object {
|
||||||
|
$filename = $_.Name
|
||||||
|
$encodedName = [System.Uri]::EscapeDataString($filename)
|
||||||
|
$size = [math]::Round($_.Length / 1MB, 1)
|
||||||
|
Write-Host "Uploading ${filename} (${size} MB)..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
$assets = Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets" -Headers $Headers
|
||||||
|
$existing = $assets | Where-Object { $_.name -eq $filename }
|
||||||
|
if ($existing) {
|
||||||
|
Invoke-RestMethod -Uri "${REPO_API}/releases/${RELEASE_ID}/assets/$($existing.id)" -Method Delete -Headers $Headers
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
$uploadUrl = "${REPO_API}/releases/${RELEASE_ID}/assets?name=${encodedName}"
|
||||||
|
$result = curl.exe --fail --silent --show-error `
|
||||||
|
-X POST `
|
||||||
|
-H "Authorization: token $env:BUILD_TOKEN" `
|
||||||
|
-H "Content-Type: application/octet-stream" `
|
||||||
|
-T "$($_.FullName)" `
|
||||||
|
"$uploadUrl" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Host "Upload successful: ${filename}"
|
||||||
|
} else {
|
||||||
|
Write-Host "WARNING: Upload failed for ${filename}: ${result}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build-macos:
|
||||||
|
name: Build App (macOS)
|
||||||
|
needs: bump-version
|
||||||
|
runs-on: macos-latest
|
||||||
|
env:
|
||||||
|
NODE_VERSION: "20"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.bump-version.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||||
|
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: brew install --quiet create-dmg || true
|
||||||
|
|
||||||
|
- name: Install npm dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build Tauri app
|
||||||
|
run: npm run tauri build
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
env:
|
||||||
|
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
which jq || brew install jq
|
||||||
|
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||||
|
TAG="${{ needs.bump-version.outputs.tag }}"
|
||||||
|
echo "Release tag: ${TAG}"
|
||||||
|
|
||||||
|
RELEASE_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/tags/${TAG}" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -z "${RELEASE_ID}" ] || [ "${RELEASE_ID}" = "null" ]; then
|
||||||
|
echo "ERROR: Failed to find release for tag ${TAG}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Release ID: ${RELEASE_ID}"
|
||||||
|
|
||||||
|
find src-tauri/target/release/bundle -type f -name "*.dmg" | while IFS= read -r file; do
|
||||||
|
filename=$(basename "$file")
|
||||||
|
encoded_name=$(echo "$filename" | sed 's/ /%20/g')
|
||||||
|
echo "Uploading ${filename} ($(du -h "$file" | cut -f1))..."
|
||||||
|
|
||||||
|
ASSET_ID=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets" | jq -r ".[] | select(.name == \"${filename}\") | .id // empty")
|
||||||
|
if [ -n "${ASSET_ID}" ]; then
|
||||||
|
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${BUILD_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-T "$file" \
|
||||||
|
"${REPO_API}/releases/${RELEASE_ID}/assets?name=${encoded_name}")
|
||||||
|
echo "Upload response: HTTP ${HTTP_CODE}"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
name: Sidecar Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- 'client/**'
|
|
||||||
- 'server/**'
|
|
||||||
- 'backend/**'
|
|
||||||
- 'pyproject.toml'
|
|
||||||
- 'local-transcription-headless.spec'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
bump-sidecar-version:
|
|
||||||
name: Bump sidecar version and tag
|
|
||||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.bump.outputs.version }}
|
|
||||||
tag: ${{ steps.bump.outputs.tag }}
|
|
||||||
has_changes: ${{ steps.check_changes.outputs.has_changes }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: Check for backend changes
|
|
||||||
id: check_changes
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
CHANGED=$(git diff --name-only HEAD~1 HEAD -- client/ server/ backend/ pyproject.toml local-transcription-headless.spec 2>/dev/null || echo "")
|
|
||||||
if [ -n "$CHANGED" ]; then
|
|
||||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "Backend changes detected: $CHANGED"
|
|
||||||
else
|
|
||||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "No backend changes detected, skipping sidecar build"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Configure git
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
run: |
|
|
||||||
git config user.name "Gitea Actions"
|
|
||||||
git config user.email "actions@gitea.local"
|
|
||||||
|
|
||||||
- name: Bump sidecar patch version
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
id: bump
|
|
||||||
run: |
|
|
||||||
CURRENT=$(grep '^version = ' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
|
||||||
echo "Current sidecar version: ${CURRENT}"
|
|
||||||
|
|
||||||
MAJOR=$(echo "${CURRENT}" | cut -d. -f1)
|
|
||||||
MINOR=$(echo "${CURRENT}" | cut -d. -f2)
|
|
||||||
PATCH=$(echo "${CURRENT}" | cut -d. -f3)
|
|
||||||
NEW_PATCH=$((PATCH + 1))
|
|
||||||
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
|
|
||||||
echo "New sidecar version: ${NEW_VERSION}"
|
|
||||||
|
|
||||||
sed -i "s/^version = \"${CURRENT}\"/version = \"${NEW_VERSION}\"/" pyproject.toml
|
|
||||||
sed -i "s/__version__ = \"${CURRENT}\"/__version__ = \"${NEW_VERSION}\"/" version.py
|
|
||||||
sed -i "s/__version_info__ = .*/__version_info__ = (${MAJOR}, ${MINOR}, ${NEW_PATCH})/" version.py
|
|
||||||
|
|
||||||
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
echo "tag=sidecar-v${NEW_VERSION}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Commit and tag
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
NEW_VERSION="${{ steps.bump.outputs.version }}"
|
|
||||||
TAG="${{ steps.bump.outputs.tag }}"
|
|
||||||
git add pyproject.toml version.py
|
|
||||||
git commit -m "chore: bump sidecar version to ${NEW_VERSION} [skip ci]"
|
|
||||||
git tag "${TAG}"
|
|
||||||
|
|
||||||
REMOTE_URL=$(git remote get-url origin | sed "s|://|://gitea-actions:${BUILD_TOKEN}@|")
|
|
||||||
git pull --rebase "${REMOTE_URL}" main || true
|
|
||||||
git push "${REMOTE_URL}" HEAD:main
|
|
||||||
git push "${REMOTE_URL}" "${TAG}"
|
|
||||||
|
|
||||||
- name: Create Gitea release
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.bump.outputs.tag }}"
|
|
||||||
VERSION="${{ steps.bump.outputs.version }}"
|
|
||||||
RELEASE_NAME="Sidecar v${VERSION}"
|
|
||||||
|
|
||||||
curl -s -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"tag_name\": \"${TAG}\", \"name\": \"${RELEASE_NAME}\", \"body\": \"Automated sidecar build.\", \"draft\": false, \"prerelease\": false}" \
|
|
||||||
"${REPO_API}/releases"
|
|
||||||
echo "Created release: ${RELEASE_NAME}"
|
|
||||||
|
|
||||||
- name: Trigger per-OS sidecar builds
|
|
||||||
if: steps.check_changes.outputs.has_changes == 'true'
|
|
||||||
env:
|
|
||||||
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
|
|
||||||
run: |
|
|
||||||
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="${{ steps.bump.outputs.tag }}"
|
|
||||||
|
|
||||||
for workflow in build-sidecar-linux.yml build-sidecar-windows.yml build-sidecar-macos.yml; do
|
|
||||||
echo "Dispatching ${workflow} for ${TAG}..."
|
|
||||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
||||||
-H "Authorization: token ${BUILD_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"ref\": \"main\", \"inputs\": {\"tag\": \"${TAG}\"}}" \
|
|
||||||
"${REPO_API}/actions/workflows/${workflow}/dispatches")
|
|
||||||
echo " -> HTTP ${HTTP_CODE}"
|
|
||||||
done
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -63,6 +63,3 @@ dist/
|
|||||||
|
|
||||||
# Tauri
|
# Tauri
|
||||||
src-tauri/target/
|
src-tauri/target/
|
||||||
|
|
||||||
# Windows NTFS alternate data streams
|
|
||||||
*:Zone.Identifier
|
|
||||||
|
|||||||
BIN
2025-live-transcription-research.md:Zone.Identifier
Normal file
BIN
2025-live-transcription-research.md:Zone.Identifier
Normal file
Binary file not shown.
27
CLAUDE.md
27
CLAUDE.md
@@ -64,14 +64,8 @@ local-transcription/
|
|||||||
│ ├── web_display.py # FastAPI OBS display server (WebSocket + HTML)
|
│ ├── web_display.py # FastAPI OBS display server (WebSocket + HTML)
|
||||||
│ └── nodejs/ # Optional multi-user sync server
|
│ └── nodejs/ # Optional multi-user sync server
|
||||||
├── .gitea/workflows/ # CI/CD
|
├── .gitea/workflows/ # CI/CD
|
||||||
│ ├── release.yml # Coordinator: version bump, tag, release creation
|
│ ├── release.yml # Tauri app builds (Linux/Windows/macOS)
|
||||||
│ ├── build-app-linux.yml # Linux Tauri app build (triggered by v* tag)
|
│ └── build-sidecar.yml # Python sidecar builds (CUDA + CPU)
|
||||||
│ ├── build-app-windows.yml # Windows Tauri app build (triggered by v* tag)
|
|
||||||
│ ├── build-app-macos.yml # macOS Tauri app build (triggered by v* tag)
|
|
||||||
│ ├── sidecar-release.yml # Sidecar coordinator: version bump, tag, release
|
|
||||||
│ ├── build-sidecar-linux.yml # Linux sidecar build (triggered by sidecar-v* tag)
|
|
||||||
│ ├── build-sidecar-windows.yml # Windows sidecar build (triggered by sidecar-v* tag)
|
|
||||||
│ └── build-sidecar-macos.yml # macOS sidecar build (triggered by sidecar-v* tag)
|
|
||||||
├── config/default_config.yaml # Default settings template
|
├── config/default_config.yaml # Default settings template
|
||||||
├── main.py # Legacy PySide6 GUI entry point
|
├── main.py # Legacy PySide6 GUI entry point
|
||||||
├── main_cli.py # CLI version for testing
|
├── main_cli.py # CLI version for testing
|
||||||
@@ -211,21 +205,12 @@ Uses Svelte 5 runes throughout (`$state`, `$derived`, `$effect`, `$props`). No S
|
|||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
Eight Gitea Actions workflows in `.gitea/workflows/`, split into coordinators and per-OS builders:
|
Two Gitea Actions workflows in `.gitea/workflows/`:
|
||||||
|
|
||||||
**App release (Tauri):**
|
- **`release.yml`**: Triggers on push to `main`. Auto-bumps version, builds Tauri app on Linux/Windows/macOS, uploads `.deb`, `.rpm`, `.msi`, `.dmg` to Gitea release.
|
||||||
- **`release.yml`**: Coordinator. Triggers on push to `main`. Auto-bumps version in package.json/tauri.conf.json/Cargo.toml/version.py, commits, tags `v{VERSION}`, creates Gitea release.
|
- **`build-sidecar.yml`**: Triggers on changes to `client/`, `server/`, `backend/`, `pyproject.toml`. Builds headless Python sidecar via PyInstaller. CUDA + CPU for Linux/Windows, CPU-only for macOS.
|
||||||
- **`build-app-linux.yml`**: Triggers on `v*` tag push or `workflow_dispatch`. Builds Tauri app, uploads `.deb`/`.rpm`/`.AppImage`.
|
|
||||||
- **`build-app-windows.yml`**: Triggers on `v*` tag push or `workflow_dispatch`. Builds Tauri app, uploads `.msi`/`*-setup.exe`.
|
|
||||||
- **`build-app-macos.yml`**: Triggers on `v*` tag push or `workflow_dispatch`. Builds Tauri app, uploads `.dmg`.
|
|
||||||
|
|
||||||
**Sidecar release (Python backend):**
|
Both require a `BUILD_TOKEN` secret (Gitea API token with release write access).
|
||||||
- **`sidecar-release.yml`**: Coordinator. Triggers on push to `main` with changes in `client/`, `server/`, `backend/`, `pyproject.toml`, or `local-transcription-headless.spec`. Bumps version in pyproject.toml/version.py, tags `sidecar-v{VERSION}`, creates Gitea release.
|
|
||||||
- **`build-sidecar-linux.yml`**: Triggers on `sidecar-v*` tag push or `workflow_dispatch`. Builds CUDA + CPU sidecars via PyInstaller.
|
|
||||||
- **`build-sidecar-windows.yml`**: Triggers on `sidecar-v*` tag push or `workflow_dispatch`. Builds CUDA + CPU sidecars via PyInstaller.
|
|
||||||
- **`build-sidecar-macos.yml`**: Triggers on `sidecar-v*` tag push or `workflow_dispatch`. Builds CPU-only sidecar via PyInstaller.
|
|
||||||
|
|
||||||
All per-OS build workflows can be re-run independently via `workflow_dispatch` with an optional `tag` input. All require a `BUILD_TOKEN` secret (Gitea API token with release write access).
|
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
|
|||||||
BIN
DEEPGRAM_PROXY_PLAN.md:Zone.Identifier
Normal file
BIN
DEEPGRAM_PROXY_PLAN.md:Zone.Identifier
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "local-transcription",
|
"name": "local-transcription",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.8",
|
"version": "1.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "local-transcription"
|
name = "local-transcription"
|
||||||
version = "1.0.3"
|
version = "1.0.0"
|
||||||
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
|
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
497
src-tauri/Cargo.lock
generated
497
src-tauri/Cargo.lock
generated
@@ -47,15 +47,6 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
|
||||||
dependencies = [
|
|
||||||
"derive_arbitrary",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atk"
|
name = "atk"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
@@ -316,10 +307,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -349,16 +338,6 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@@ -382,9 +361,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types 0.5.0",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -395,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -536,17 +515,6 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.20"
|
version = "0.99.20"
|
||||||
@@ -824,15 +792,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared 0.1.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -840,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared 0.3.1",
|
"foreign-types-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -854,12 +813,6 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1269,25 +1222,6 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h2"
|
|
||||||
version = "0.4.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-waker",
|
|
||||||
"bytes",
|
|
||||||
"fnv",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"http",
|
|
||||||
"indexmap 2.13.1",
|
|
||||||
"slab",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1398,7 +1332,6 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"h2",
|
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
@@ -1409,38 +1342,6 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-rustls"
|
|
||||||
version = "0.27.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
|
||||||
dependencies = [
|
|
||||||
"http",
|
|
||||||
"hyper",
|
|
||||||
"hyper-util",
|
|
||||||
"rustls",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hyper-tls"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-util",
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tower-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -1459,11 +1360,9 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"system-configuration",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-registry",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1867,12 +1766,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -1881,12 +1774,8 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-transcription"
|
name = "local-transcription"
|
||||||
version = "1.4.5"
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"futures-util",
|
|
||||||
"reqwest 0.12.28",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
@@ -1894,8 +1783,6 @@ dependencies = [
|
|||||||
"tauri-plugin-dialog",
|
"tauri-plugin-dialog",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tokio",
|
|
||||||
"zip",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2024,23 +1911,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -2262,50 +2132,6 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.76"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types 0.3.2",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-probe"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.112"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2901,49 +2727,6 @@ version = "0.8.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "reqwest"
|
|
||||||
version = "0.12.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"bytes",
|
|
||||||
"encoding_rs",
|
|
||||||
"futures-core",
|
|
||||||
"futures-util",
|
|
||||||
"h2",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"hyper",
|
|
||||||
"hyper-rustls",
|
|
||||||
"hyper-tls",
|
|
||||||
"hyper-util",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"mime",
|
|
||||||
"native-tls",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project-lite",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_urlencoded",
|
|
||||||
"sync_wrapper",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tokio-util",
|
|
||||||
"tower",
|
|
||||||
"tower-http",
|
|
||||||
"tower-service",
|
|
||||||
"url",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-streams 0.4.2",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
@@ -2974,7 +2757,7 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-streams 0.5.0",
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3002,20 +2785,6 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ring"
|
|
||||||
version = "0.17.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"getrandom 0.2.17",
|
|
||||||
"libc",
|
|
||||||
"untrusted",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
@@ -3031,64 +2800,12 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls"
|
|
||||||
version = "0.23.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"rustls-webpki",
|
|
||||||
"subtle",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-pki-types"
|
|
||||||
version = "1.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
|
||||||
dependencies = [
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-webpki"
|
|
||||||
version = "0.103.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
|
||||||
dependencies = [
|
|
||||||
"ring",
|
|
||||||
"rustls-pki-types",
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@@ -3098,15 +2815,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schannel"
|
|
||||||
version = "0.1.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@@ -3164,29 +2872,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework"
|
|
||||||
version = "3.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"core-foundation 0.10.1",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
"security-framework-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "security-framework-sys"
|
|
||||||
version = "2.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selectors"
|
name = "selectors"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -3329,18 +3014,6 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_urlencoded"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
|
||||||
dependencies = [
|
|
||||||
"form_urlencoded",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.18.0"
|
version = "3.18.0"
|
||||||
@@ -3621,12 +3294,6 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "subtle"
|
|
||||||
version = "2.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swift-rs"
|
name = "swift-rs"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -3680,27 +3347,6 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.11.0",
|
|
||||||
"core-foundation 0.9.4",
|
|
||||||
"system-configuration-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "system-configuration-sys"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
|
||||||
dependencies = [
|
|
||||||
"core-foundation-sys",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -3722,7 +3368,7 @@ checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"block2",
|
"block2",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
@@ -3799,7 +3445,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest 0.13.2",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -4073,19 +3719,6 @@ dependencies = [
|
|||||||
"toml 0.9.12+spec-1.1.0",
|
"toml 0.9.12+spec-1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempfile"
|
|
||||||
version = "3.27.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
|
||||||
dependencies = [
|
|
||||||
"fastrand",
|
|
||||||
"getrandom 0.4.2",
|
|
||||||
"once_cell",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tendril"
|
name = "tendril"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -4197,45 +3830,11 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-macros"
|
|
||||||
version = "2.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-native-tls"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
|
||||||
dependencies = [
|
|
||||||
"native-tls",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-rustls"
|
|
||||||
version = "0.26.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
|
||||||
dependencies = [
|
|
||||||
"rustls",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -4517,12 +4116,6 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "untrusted"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -4572,12 +4165,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -4736,19 +4323,6 @@ dependencies = [
|
|||||||
"wasmparser",
|
"wasmparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-streams"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -5025,17 +4599,6 @@ dependencies = [
|
|||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-registry"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link 0.2.1",
|
|
||||||
"windows-result 0.4.1",
|
|
||||||
"windows-strings 0.5.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -5081,15 +4644,6 @@ dependencies = [
|
|||||||
"windows-targets 0.42.2",
|
"windows-targets 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.52.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
@@ -5578,12 +5132,6 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize"
|
|
||||||
version = "1.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -5617,37 +5165,8 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zip"
|
|
||||||
version = "2.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
|
||||||
dependencies = [
|
|
||||||
"arbitrary",
|
|
||||||
"crc32fast",
|
|
||||||
"crossbeam-utils",
|
|
||||||
"displaydoc",
|
|
||||||
"flate2",
|
|
||||||
"indexmap 2.13.1",
|
|
||||||
"memchr",
|
|
||||||
"thiserror 2.0.18",
|
|
||||||
"zopfli",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zopfli"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"crc32fast",
|
|
||||||
"log",
|
|
||||||
"simd-adler32",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "local-transcription"
|
name = "local-transcription"
|
||||||
version = "1.4.8"
|
version = "1.4.0"
|
||||||
description = "Real-time speech-to-text transcription for streamers"
|
description = "Real-time speech-to-text transcription for streamers"
|
||||||
authors = ["Local Transcription Contributors"]
|
authors = ["Local Transcription Contributors"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -19,9 +19,3 @@ tauri-plugin-dialog = "2"
|
|||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
|
||||||
futures-util = "0.3"
|
|
||||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
|
||||||
bytes = "1"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
chrono = "0.4"
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 41 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 739 B After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 52 KiB |
@@ -1,75 +1,9 @@
|
|||||||
mod sidecar;
|
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use tauri::Manager;
|
|
||||||
|
|
||||||
/// App log directory, set during setup.
|
|
||||||
static LOG_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
|
|
||||||
|
|
||||||
/// Write a log message to the app's log file (for debugging).
|
|
||||||
#[tauri::command]
|
|
||||||
fn write_log(message: String) {
|
|
||||||
if let Some(log_dir) = LOG_DIR.get() {
|
|
||||||
let log_path = log_dir.join("frontend.log");
|
|
||||||
use std::io::Write;
|
|
||||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(&log_path)
|
|
||||||
{
|
|
||||||
let _ = writeln!(f, "[{}] {}", chrono::Local::now().format("%H:%M:%S%.3f"), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eprintln!("[frontend] {}", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.manage(sidecar::ManagedSidecar(Mutex::new(
|
|
||||||
sidecar::SidecarManager::new(),
|
|
||||||
)))
|
|
||||||
.setup(|app| {
|
|
||||||
let resource_dir = app
|
|
||||||
.path()
|
|
||||||
.resource_dir()
|
|
||||||
.expect("failed to resolve resource dir");
|
|
||||||
let data_dir = app
|
|
||||||
.path()
|
|
||||||
.app_data_dir()
|
|
||||||
.expect("failed to resolve app data dir");
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&data_dir).expect("failed to create app data dir");
|
|
||||||
|
|
||||||
// Set up logging
|
|
||||||
LOG_DIR.set(data_dir.clone()).ok();
|
|
||||||
let log_path = data_dir.join("app.log");
|
|
||||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(&log_path)
|
|
||||||
{
|
|
||||||
use std::io::Write;
|
|
||||||
let _ = writeln!(f, "\n=== App started at {} ===", chrono::Local::now());
|
|
||||||
let _ = writeln!(f, "Resource dir: {}", resource_dir.display());
|
|
||||||
let _ = writeln!(f, "Data dir: {}", data_dir.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
sidecar::init_dirs(resource_dir, data_dir);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
sidecar::check_sidecar,
|
|
||||||
sidecar::download_sidecar,
|
|
||||||
sidecar::check_sidecar_update,
|
|
||||||
sidecar::get_sidecar_port,
|
|
||||||
sidecar::start_sidecar,
|
|
||||||
sidecar::stop_sidecar,
|
|
||||||
write_log,
|
|
||||||
])
|
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,580 +0,0 @@
|
|||||||
use std::io::BufRead;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tauri::{AppHandle, Emitter};
|
|
||||||
|
|
||||||
const REPO_API: &str =
|
|
||||||
"https://repo.anhonesthost.net/api/v1/repos/streamer-tools/local-transcription";
|
|
||||||
|
|
||||||
const BINARY_NAME: &str = if cfg!(windows) {
|
|
||||||
"local-transcription-backend.exe"
|
|
||||||
} else {
|
|
||||||
"local-transcription-backend"
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Directory state (initialised once during Tauri setup)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static DIRS: std::sync::OnceLock<SidecarDirs> = std::sync::OnceLock::new();
|
|
||||||
|
|
||||||
struct SidecarDirs {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
resource_dir: PathBuf,
|
|
||||||
data_dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called from Tauri `setup` to persist the resource / data directories.
|
|
||||||
pub fn init_dirs(resource_dir: PathBuf, data_dir: PathBuf) {
|
|
||||||
let _ = DIRS.set(SidecarDirs {
|
|
||||||
resource_dir,
|
|
||||||
data_dir,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_dir() -> &'static PathBuf {
|
|
||||||
&DIRS.get().expect("sidecar::init_dirs not called").data_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Version helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fn version_file() -> PathBuf {
|
|
||||||
data_dir().join("sidecar-version.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_installed_version() -> Option<String> {
|
|
||||||
std::fs::read_to_string(version_file())
|
|
||||||
.ok()
|
|
||||||
.map(|s| s.trim().to_string())
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sidecar_dir_for_version(version: &str) -> PathBuf {
|
|
||||||
data_dir().join(format!("sidecar-{version}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn binary_path_for_version(version: &str) -> PathBuf {
|
|
||||||
sidecar_dir_for_version(version).join(BINARY_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Gitea API types
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct GiteaRelease {
|
|
||||||
tag_name: String,
|
|
||||||
assets: Vec<GiteaAsset>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct GiteaAsset {
|
|
||||||
name: String,
|
|
||||||
browser_download_url: String,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Platform / arch detection
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fn platform_token() -> &'static str {
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
"windows"
|
|
||||||
} else if cfg!(target_os = "macos") {
|
|
||||||
"macos"
|
|
||||||
} else {
|
|
||||||
"linux"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arch_token() -> &'static str {
|
|
||||||
if cfg!(target_arch = "aarch64") {
|
|
||||||
"aarch64"
|
|
||||||
} else {
|
|
||||||
"x86_64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the expected asset prefix, e.g. `sidecar-linux-x86_64-cuda`.
|
|
||||||
fn asset_prefix(variant: &str) -> String {
|
|
||||||
format!("sidecar-{}-{}-{}", platform_token(), arch_token(), variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tauri commands
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Returns `true` when a sidecar binary is installed and the file exists.
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn check_sidecar() -> bool {
|
|
||||||
if let Some(version) = read_installed_version() {
|
|
||||||
binary_path_for_version(&version).exists()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Download progress payload emitted via `sidecar-download-progress`.
|
|
||||||
#[derive(Clone, Serialize)]
|
|
||||||
struct DownloadProgress {
|
|
||||||
downloaded: u64,
|
|
||||||
total: u64,
|
|
||||||
phase: String, // "downloading" | "extracting" | "done" | "error"
|
|
||||||
message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Download & install the latest sidecar release.
|
|
||||||
///
|
|
||||||
/// `variant` is typically `"cuda"` or `"cpu"`.
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn download_sidecar(app: AppHandle, variant: String) -> Result<String, String> {
|
|
||||||
use futures_util::StreamExt;
|
|
||||||
|
|
||||||
let emit = |progress: DownloadProgress| {
|
|
||||||
let _ = app.emit("sidecar-download-progress", progress);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1. Fetch releases from Gitea (filter to sidecar-v* tags) ---------------
|
|
||||||
emit(DownloadProgress {
|
|
||||||
downloaded: 0,
|
|
||||||
total: 0,
|
|
||||||
phase: "downloading".into(),
|
|
||||||
message: "Fetching release info...".into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let releases_url = format!("{REPO_API}/releases?limit=20");
|
|
||||||
let client = reqwest::Client::new();
|
|
||||||
let releases: Vec<GiteaRelease> = client
|
|
||||||
.get(&releases_url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to fetch releases: {e}"))?
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to parse releases: {e}"))?;
|
|
||||||
|
|
||||||
// Find the latest release whose tag starts with `sidecar-v`
|
|
||||||
let release = releases
|
|
||||||
.into_iter()
|
|
||||||
.find(|r| r.tag_name.starts_with("sidecar-v"))
|
|
||||||
.ok_or_else(|| "No sidecar release found".to_string())?;
|
|
||||||
|
|
||||||
let version = release.tag_name.clone(); // e.g. "sidecar-v1.0.2"
|
|
||||||
|
|
||||||
// 2. Find matching asset ----------------------------------------------------
|
|
||||||
let prefix = asset_prefix(&variant);
|
|
||||||
let asset = release
|
|
||||||
.assets
|
|
||||||
.iter()
|
|
||||||
.find(|a| a.name.starts_with(&prefix) && a.name.ends_with(".zip"))
|
|
||||||
.ok_or_else(|| {
|
|
||||||
format!(
|
|
||||||
"No asset matching '{}' in release {}. Available: {}",
|
|
||||||
prefix,
|
|
||||||
version,
|
|
||||||
release
|
|
||||||
.assets
|
|
||||||
.iter()
|
|
||||||
.map(|a| a.name.as_str())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ")
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let total_size = asset.size;
|
|
||||||
let download_url = asset.browser_download_url.clone();
|
|
||||||
|
|
||||||
// 3. Stream download ---------------------------------------------------------
|
|
||||||
emit(DownloadProgress {
|
|
||||||
downloaded: 0,
|
|
||||||
total: total_size,
|
|
||||||
phase: "downloading".into(),
|
|
||||||
message: format!("Downloading {}...", asset.name),
|
|
||||||
});
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.get(&download_url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Download request failed: {e}"))?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(format!("Download failed with status {}", response.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmp_zip = data_dir().join("_sidecar_download.zip");
|
|
||||||
let mut file = tokio::fs::File::create(&tmp_zip)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Cannot create temp file: {e}"))?;
|
|
||||||
|
|
||||||
let mut stream = response.bytes_stream();
|
|
||||||
let mut downloaded: u64 = 0;
|
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
while let Some(chunk) = stream.next().await {
|
|
||||||
let chunk = chunk.map_err(|e| format!("Download stream error: {e}"))?;
|
|
||||||
file.write_all(&chunk)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Write error: {e}"))?;
|
|
||||||
downloaded += chunk.len() as u64;
|
|
||||||
|
|
||||||
emit(DownloadProgress {
|
|
||||||
downloaded,
|
|
||||||
total: total_size,
|
|
||||||
phase: "downloading".into(),
|
|
||||||
message: format!(
|
|
||||||
"Downloading... {:.1} / {:.1} MB",
|
|
||||||
downloaded as f64 / 1_048_576.0,
|
|
||||||
total_size as f64 / 1_048_576.0
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
file.flush()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Flush error: {e}"))?;
|
|
||||||
drop(file);
|
|
||||||
|
|
||||||
// 4. Extract zip -------------------------------------------------------------
|
|
||||||
emit(DownloadProgress {
|
|
||||||
downloaded,
|
|
||||||
total: total_size,
|
|
||||||
phase: "extracting".into(),
|
|
||||||
message: "Extracting sidecar...".into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let dest_dir = sidecar_dir_for_version(&version);
|
|
||||||
if dest_dir.exists() {
|
|
||||||
std::fs::remove_dir_all(&dest_dir)
|
|
||||||
.map_err(|e| format!("Cannot clean old dir: {e}"))?;
|
|
||||||
}
|
|
||||||
std::fs::create_dir_all(&dest_dir)
|
|
||||||
.map_err(|e| format!("Cannot create sidecar dir: {e}"))?;
|
|
||||||
|
|
||||||
// Extraction is blocking I/O -- offload to a spawn_blocking thread.
|
|
||||||
let zip_path = tmp_zip.clone();
|
|
||||||
let dest = dest_dir.clone();
|
|
||||||
tokio::task::spawn_blocking(move || extract_zip(&zip_path, &dest))
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Join error: {e}"))?
|
|
||||||
.map_err(|e| format!("Extraction error: {e}"))?;
|
|
||||||
|
|
||||||
// Remove the temp zip
|
|
||||||
let _ = std::fs::remove_file(&tmp_zip);
|
|
||||||
|
|
||||||
// 5. Set executable permissions on Unix -------------------------------------
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
let bin = dest_dir.join(BINARY_NAME);
|
|
||||||
if bin.exists() {
|
|
||||||
let mut perms = std::fs::metadata(&bin)
|
|
||||||
.map_err(|e| format!("metadata error: {e}"))?
|
|
||||||
.permissions();
|
|
||||||
perms.set_mode(0o755);
|
|
||||||
std::fs::set_permissions(&bin, perms)
|
|
||||||
.map_err(|e| format!("chmod error: {e}"))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Write version file & clean up old versions ----------------------------
|
|
||||||
std::fs::write(version_file(), &version)
|
|
||||||
.map_err(|e| format!("Failed to write version file: {e}"))?;
|
|
||||||
|
|
||||||
cleanup_old_versions(&version);
|
|
||||||
|
|
||||||
emit(DownloadProgress {
|
|
||||||
downloaded,
|
|
||||||
total: total_size,
|
|
||||||
phase: "done".into(),
|
|
||||||
message: "Sidecar installed successfully".into(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if there is a newer sidecar release than the installed one.
|
|
||||||
/// Returns `Some(tag_name)` when an update is available, or `None`.
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn check_sidecar_update() -> Result<Option<String>, String> {
|
|
||||||
let installed = match read_installed_version() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let releases_url = format!("{REPO_API}/releases?limit=20");
|
|
||||||
let releases: Vec<GiteaRelease> = reqwest::Client::new()
|
|
||||||
.get(&releases_url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to fetch releases: {e}"))?
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to parse releases: {e}"))?;
|
|
||||||
|
|
||||||
let latest = releases
|
|
||||||
.iter()
|
|
||||||
.find(|r| r.tag_name.starts_with("sidecar-v"));
|
|
||||||
|
|
||||||
match latest {
|
|
||||||
Some(rel) if rel.tag_name != installed => Ok(Some(rel.tag_name.clone())),
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Zip extraction helper
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fn extract_zip(zip_path: &std::path::Path, dest: &std::path::Path) -> Result<(), String> {
|
|
||||||
let file =
|
|
||||||
std::fs::File::open(zip_path).map_err(|e| format!("Cannot open zip: {e}"))?;
|
|
||||||
let mut archive =
|
|
||||||
zip::ZipArchive::new(file).map_err(|e| format!("Invalid zip: {e}"))?;
|
|
||||||
|
|
||||||
for i in 0..archive.len() {
|
|
||||||
let mut entry = archive
|
|
||||||
.by_index(i)
|
|
||||||
.map_err(|e| format!("Zip entry error: {e}"))?;
|
|
||||||
let entry_path = match entry.enclosed_name() {
|
|
||||||
Some(p) => p.to_owned(),
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let out_path = dest.join(&entry_path);
|
|
||||||
|
|
||||||
if entry.is_dir() {
|
|
||||||
std::fs::create_dir_all(&out_path)
|
|
||||||
.map_err(|e| format!("mkdir error: {e}"))?;
|
|
||||||
} else {
|
|
||||||
if let Some(parent) = out_path.parent() {
|
|
||||||
std::fs::create_dir_all(parent)
|
|
||||||
.map_err(|e| format!("mkdir error: {e}"))?;
|
|
||||||
}
|
|
||||||
let mut outfile = std::fs::File::create(&out_path)
|
|
||||||
.map_err(|e| format!("create file error: {e}"))?;
|
|
||||||
std::io::copy(&mut entry, &mut outfile)
|
|
||||||
.map_err(|e| format!("copy error: {e}"))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Cleanup old versions
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
fn cleanup_old_versions(current_version: &str) {
|
|
||||||
let data = data_dir();
|
|
||||||
let current_dir_name = format!("sidecar-{current_version}");
|
|
||||||
if let Ok(entries) = std::fs::read_dir(data) {
|
|
||||||
for entry in entries.flatten() {
|
|
||||||
let name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
if name.starts_with("sidecar-v") // e.g. sidecar-v1.0.1
|
|
||||||
&& name != current_dir_name
|
|
||||||
&& entry.path().is_dir()
|
|
||||||
{
|
|
||||||
let _ = std::fs::remove_dir_all(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// SidecarManager — launch / stop / query the backend process
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct ReadyEvent {
|
|
||||||
event: String,
|
|
||||||
port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SidecarManager {
|
|
||||||
child: Option<std::process::Child>,
|
|
||||||
port: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SidecarManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
child: None,
|
|
||||||
port: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` when the child process is still alive.
|
|
||||||
pub fn is_running(&mut self) -> bool {
|
|
||||||
match &mut self.child {
|
|
||||||
Some(child) => match child.try_wait() {
|
|
||||||
Ok(Some(_)) => {
|
|
||||||
// Process has exited
|
|
||||||
self.child = None;
|
|
||||||
self.port = None;
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Ok(None) => true,
|
|
||||||
Err(_) => false,
|
|
||||||
},
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start the sidecar if it is not already running. Returns the port.
|
|
||||||
pub fn ensure_running(&mut self) -> Result<u16, String> {
|
|
||||||
if self.is_running() {
|
|
||||||
return self
|
|
||||||
.port
|
|
||||||
.ok_or_else(|| "Sidecar running but port unknown".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_dev = cfg!(debug_assertions)
|
|
||||||
|| std::env::var("LOCAL_TRANSCRIPTION_DEV")
|
|
||||||
.map(|v| v == "1")
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
let mut cmd = if is_dev {
|
|
||||||
self.build_dev_command()?
|
|
||||||
} else {
|
|
||||||
self.build_prod_command()?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hide the console window on Windows in release mode.
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
use std::os::windows::process::CommandExt;
|
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
|
||||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.stdout(std::process::Stdio::piped());
|
|
||||||
cmd.stderr(std::process::Stdio::piped());
|
|
||||||
|
|
||||||
let mut child = cmd.spawn().map_err(|e| format!("Failed to spawn sidecar: {e}"))?;
|
|
||||||
|
|
||||||
// Wait for the `{"event":"ready","port":...}` line on stdout.
|
|
||||||
let stdout = child
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.ok_or("Failed to capture sidecar stdout")?;
|
|
||||||
|
|
||||||
let port = Self::wait_for_ready(stdout)?;
|
|
||||||
|
|
||||||
self.child = Some(child);
|
|
||||||
self.port = Some(port);
|
|
||||||
Ok(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop the sidecar process if running.
|
|
||||||
pub fn stop(&mut self) {
|
|
||||||
if let Some(mut child) = self.child.take() {
|
|
||||||
let _ = child.kill();
|
|
||||||
let _ = child.wait();
|
|
||||||
}
|
|
||||||
self.port = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the port the sidecar is listening on, if known.
|
|
||||||
pub fn port(&self) -> Option<u16> {
|
|
||||||
self.port
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- private helpers -------------------------------------------------------
|
|
||||||
|
|
||||||
fn build_dev_command(&self) -> Result<std::process::Command, String> {
|
|
||||||
let mut cmd = std::process::Command::new("python");
|
|
||||||
cmd.args(["-m", "backend.main_headless"]);
|
|
||||||
|
|
||||||
// Try to find the project root (parent of src-tauri)
|
|
||||||
if let Some(dirs) = DIRS.get() {
|
|
||||||
let project_root = dirs
|
|
||||||
.resource_dir
|
|
||||||
.parent() // src-tauri
|
|
||||||
.and_then(|p| p.parent()); // project root
|
|
||||||
if let Some(root) = project_root {
|
|
||||||
cmd.current_dir(root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_prod_command(&self) -> Result<std::process::Command, String> {
|
|
||||||
let version = read_installed_version()
|
|
||||||
.ok_or("No sidecar version installed")?;
|
|
||||||
let bin = binary_path_for_version(&version);
|
|
||||||
if !bin.exists() {
|
|
||||||
return Err(format!("Sidecar binary not found at {}", bin.display()));
|
|
||||||
}
|
|
||||||
let mut cmd = std::process::Command::new(&bin);
|
|
||||||
cmd.current_dir(
|
|
||||||
bin.parent()
|
|
||||||
.ok_or("Cannot determine sidecar parent dir")?,
|
|
||||||
);
|
|
||||||
Ok(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wait_for_ready(stdout: std::process::ChildStdout) -> Result<u16, String> {
|
|
||||||
let reader = std::io::BufReader::new(stdout);
|
|
||||||
let timeout = std::time::Duration::from_secs(120);
|
|
||||||
let start = std::time::Instant::now();
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
if start.elapsed() > timeout {
|
|
||||||
return Err("Timed out waiting for sidecar ready event".into());
|
|
||||||
}
|
|
||||||
let line = line.map_err(|e| format!("IO error reading stdout: {e}"))?;
|
|
||||||
if let Ok(evt) = serde_json::from_str::<ReadyEvent>(&line) {
|
|
||||||
if evt.event == "ready" {
|
|
||||||
return Ok(evt.port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ignore other lines (e.g. log output)
|
|
||||||
}
|
|
||||||
Err("Sidecar process exited before sending ready event".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tauri-managed SidecarManager state & commands
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Wrapper so we can store `SidecarManager` in Tauri's managed state.
|
|
||||||
pub struct ManagedSidecar(pub Mutex<SidecarManager>);
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn get_sidecar_port(state: tauri::State<'_, ManagedSidecar>) -> Result<Option<u16>, String> {
|
|
||||||
let mut mgr = state
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Lock error: {e}"))?;
|
|
||||||
// Refresh running status before returning port
|
|
||||||
if !mgr.is_running() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Ok(mgr.port())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn start_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<u16, String> {
|
|
||||||
let mut mgr = state
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Lock error: {e}"))?;
|
|
||||||
mgr.ensure_running()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub fn stop_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<(), String> {
|
|
||||||
let mut mgr = state
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Lock error: {e}"))?;
|
|
||||||
mgr.stop();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"productName": "Local Transcription",
|
"productName": "Local Transcription",
|
||||||
"version": "1.4.8",
|
"version": "1.4.0",
|
||||||
"identifier": "net.anhonesthost.local-transcription",
|
"identifier": "com.localtranscription.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:1420",
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
|
||||||
"icons/icon.ico",
|
"icons/icon.ico",
|
||||||
"icons/icon.png"
|
"icons/icon.png"
|
||||||
]
|
]
|
||||||
|
|||||||
222
src/App.svelte
222
src/App.svelte
@@ -5,20 +5,13 @@
|
|||||||
import Controls from "$lib/components/Controls.svelte";
|
import Controls from "$lib/components/Controls.svelte";
|
||||||
import TranscriptionDisplay from "$lib/components/TranscriptionDisplay.svelte";
|
import TranscriptionDisplay from "$lib/components/TranscriptionDisplay.svelte";
|
||||||
import Settings from "$lib/components/Settings.svelte";
|
import Settings from "$lib/components/Settings.svelte";
|
||||||
import SidecarSetup from "$lib/components/SidecarSetup.svelte";
|
|
||||||
import { backendStore } from "$lib/stores/backend";
|
import { backendStore } from "$lib/stores/backend";
|
||||||
import { configStore } from "$lib/stores/config";
|
import { configStore } from "$lib/stores/config";
|
||||||
|
|
||||||
type SidecarState = "checking" | "needs_setup" | "starting" | "connected";
|
|
||||||
|
|
||||||
let showSettings = $state(false);
|
let showSettings = $state(false);
|
||||||
let sidecarState = $state<SidecarState>("checking");
|
|
||||||
let debugLog = $state("");
|
|
||||||
|
|
||||||
let obsDisplayUrl = $derived(backendStore.obsUrl);
|
let obsDisplayUrl = $derived(backendStore.obsUrl);
|
||||||
let syncDisplayUrl = $derived(backendStore.syncUrl);
|
let syncDisplayUrl = $derived(backendStore.syncUrl);
|
||||||
let isConnected = $derived(backendStore.connectionState === "connected");
|
|
||||||
let connectionState = $derived(backendStore.connectionState);
|
|
||||||
|
|
||||||
function openSettings() {
|
function openSettings() {
|
||||||
showSettings = true;
|
showSettings = true;
|
||||||
@@ -28,72 +21,9 @@
|
|||||||
showSettings = false;
|
showSettings = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tauriInvoke: ((cmd: string, args?: Record<string, unknown>) => Promise<unknown>) | null = null;
|
|
||||||
|
|
||||||
function log(msg: string) {
|
|
||||||
console.log(`[App] ${msg}`);
|
|
||||||
debugLog = msg;
|
|
||||||
// Also write to file via Tauri if available
|
|
||||||
tauriInvoke?.("write_log", { message: msg });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkAndLaunchSidecar() {
|
|
||||||
try {
|
|
||||||
log("Importing Tauri API...");
|
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
|
||||||
tauriInvoke = invoke;
|
|
||||||
|
|
||||||
log("Checking if sidecar is installed...");
|
|
||||||
sidecarState = "checking";
|
|
||||||
const installed = await invoke<boolean>("check_sidecar");
|
|
||||||
log(`Sidecar installed: ${installed}`);
|
|
||||||
|
|
||||||
if (!installed) {
|
|
||||||
sidecarState = "needs_setup";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await launchSidecar();
|
|
||||||
} catch (err) {
|
|
||||||
// Not running in Tauri (browser dev mode) - skip sidecar check
|
|
||||||
// and connect directly to localhost:8081
|
|
||||||
log(`Tauri not available (${err}), using dev mode`);
|
|
||||||
sidecarState = "starting";
|
|
||||||
backendStore.setPort(8081);
|
|
||||||
backendStore.connect();
|
|
||||||
configStore.loadConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function launchSidecar() {
|
|
||||||
try {
|
|
||||||
const { invoke } = await import("@tauri-apps/api/core");
|
|
||||||
|
|
||||||
log("Starting sidecar...");
|
|
||||||
sidecarState = "starting";
|
|
||||||
await invoke("start_sidecar");
|
|
||||||
|
|
||||||
log("Getting sidecar port...");
|
|
||||||
const port = await invoke<number>("get_sidecar_port");
|
|
||||||
log(`Sidecar ready on port ${port}`);
|
|
||||||
backendStore.setPort(port);
|
|
||||||
backendStore.connect();
|
|
||||||
configStore.loadConfig();
|
|
||||||
} catch (err) {
|
|
||||||
// If sidecar launch fails, still try connecting to default port
|
|
||||||
log(`Sidecar launch failed: ${err}, trying default port`);
|
|
||||||
sidecarState = "starting";
|
|
||||||
backendStore.connect();
|
|
||||||
configStore.loadConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onSidecarReady() {
|
|
||||||
await launchSidecar();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkAndLaunchSidecar();
|
backendStore.connect();
|
||||||
|
configStore.loadConfig();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
backendStore.disconnect();
|
backendStore.disconnect();
|
||||||
@@ -101,141 +31,33 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if sidecarState === "checking"}
|
<div class="app-shell">
|
||||||
<div class="connecting-overlay" style="background:#1e1e1e;color:#e0e0e0;display:flex;align-items:center;justify-content:center;height:100%;width:100%;">
|
<Header onSettingsClick={openSettings} />
|
||||||
<div class="connecting-content" style="text-align:center;">
|
<StatusBar />
|
||||||
<div class="connecting-icon">
|
|
||||||
<div class="spinner"></div>
|
<div class="display-links">
|
||||||
</div>
|
<span class="link-label">OBS:</span>
|
||||||
<h2 style="font-size:20px;margin:16px 0 8px;">Local Transcription</h2>
|
<a href={obsDisplayUrl} target="_blank" rel="noopener">{obsDisplayUrl}</a>
|
||||||
<p style="color:#a0a0a0;font-size:14px;">Checking setup...</p>
|
{#if syncDisplayUrl}
|
||||||
{#if debugLog}
|
<span class="link-separator">|</span>
|
||||||
<p style="color:#707070;font-size:11px;margin-top:12px;">{debugLog}</p>
|
<span class="link-label">Sync:</span>
|
||||||
{/if}
|
<a href={syncDisplayUrl} target="_blank" rel="noopener"
|
||||||
</div>
|
>{syncDisplayUrl}</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else if sidecarState === "needs_setup"}
|
<TranscriptionDisplay />
|
||||||
<SidecarSetup onComplete={onSidecarReady} />
|
<Controls />
|
||||||
|
|
||||||
{:else if !isConnected}
|
<div class="version-label">v{backendStore.version}</div>
|
||||||
<div class="connecting-overlay" style="background:#1e1e1e;color:#e0e0e0;display:flex;align-items:center;justify-content:center;height:100%;width:100%;">
|
</div>
|
||||||
<div class="connecting-content" style="text-align:center;">
|
|
||||||
<div class="connecting-icon">
|
|
||||||
{#if connectionState === "error"}
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#e74c3c" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
||||||
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
{:else}
|
|
||||||
<div class="spinner"></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<h2 style="font-size:20px;margin:16px 0 8px;">Local Transcription</h2>
|
|
||||||
{#if connectionState === "error"}
|
|
||||||
<p style="color:#a0a0a0;">Cannot connect to backend</p>
|
|
||||||
<p class="hint">Make sure the Python backend is running:<br>
|
|
||||||
<code>uv run python -m backend.main_headless</code></p>
|
|
||||||
{:else}
|
|
||||||
<p style="color:#a0a0a0;">Connecting to backend...</p>
|
|
||||||
{/if}
|
|
||||||
{#if debugLog}
|
|
||||||
<p style="color:#707070;font-size:11px;margin-top:12px;">{debugLog}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else}
|
{#if showSettings}
|
||||||
<div class="app-shell">
|
<Settings onClose={closeSettings} />
|
||||||
<Header onSettingsClick={openSettings} />
|
|
||||||
<StatusBar />
|
|
||||||
|
|
||||||
<div class="display-links">
|
|
||||||
<span class="link-label">OBS:</span>
|
|
||||||
<a href={obsDisplayUrl} target="_blank" rel="noopener">{obsDisplayUrl}</a>
|
|
||||||
{#if syncDisplayUrl}
|
|
||||||
<span class="link-separator">|</span>
|
|
||||||
<span class="link-label">Sync:</span>
|
|
||||||
<a href={syncDisplayUrl} target="_blank" rel="noopener"
|
|
||||||
>{syncDisplayUrl}</a
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TranscriptionDisplay />
|
|
||||||
<Controls />
|
|
||||||
|
|
||||||
<div class="version-label">v{backendStore.version}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if showSettings}
|
|
||||||
<Settings onClose={closeSettings} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.connecting-overlay {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-content {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-content h2 {
|
|
||||||
margin: 16px 0 8px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-content p {
|
|
||||||
margin: 4px 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-content .hint {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-content code {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connecting-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border: 3px solid var(--border-color);
|
|
||||||
border-top-color: var(--accent-color, #4CAF50);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 0.8s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-shell {
|
.app-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,384 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onComplete: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { onComplete }: Props = $props();
|
|
||||||
|
|
||||||
type SetupState = "choose" | "downloading" | "error" | "success";
|
|
||||||
|
|
||||||
let setupState = $state<SetupState>("choose");
|
|
||||||
let variant = $state<"cpu" | "cuda">("cpu");
|
|
||||||
let progress = $state(0);
|
|
||||||
let progressMessage = $state("");
|
|
||||||
let errorMessage = $state("");
|
|
||||||
|
|
||||||
let unlisten: (() => void) | null = null;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
return () => {
|
|
||||||
if (unlisten) {
|
|
||||||
unlisten();
|
|
||||||
unlisten = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
async function startDownload() {
|
|
||||||
setupState = "downloading";
|
|
||||||
progress = 0;
|
|
||||||
progressMessage = "Starting download...";
|
|
||||||
errorMessage = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Listen for progress events from the Tauri backend
|
|
||||||
unlisten = await listen<{ progress: number; message: string }>(
|
|
||||||
"sidecar-download-progress",
|
|
||||||
(event) => {
|
|
||||||
progress = event.payload.progress;
|
|
||||||
progressMessage = event.payload.message;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await invoke("download_sidecar", { variant });
|
|
||||||
|
|
||||||
// Download complete
|
|
||||||
setupState = "success";
|
|
||||||
if (unlisten) {
|
|
||||||
unlisten();
|
|
||||||
unlisten = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Brief pause to show success, then proceed
|
|
||||||
setTimeout(() => {
|
|
||||||
onComplete();
|
|
||||||
}, 1500);
|
|
||||||
} catch (err) {
|
|
||||||
setupState = "error";
|
|
||||||
errorMessage = err instanceof Error ? err.message : String(err);
|
|
||||||
if (unlisten) {
|
|
||||||
unlisten();
|
|
||||||
unlisten = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function retry() {
|
|
||||||
setupState = "choose";
|
|
||||||
progress = 0;
|
|
||||||
progressMessage = "";
|
|
||||||
errorMessage = "";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="setup-overlay">
|
|
||||||
<div class="setup-card">
|
|
||||||
<div class="setup-header">
|
|
||||||
<h1 class="app-title">Local Transcription</h1>
|
|
||||||
<h2 class="setup-heading">First-Time Setup</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if setupState === "choose"}
|
|
||||||
<p class="setup-description">
|
|
||||||
The app needs to download its transcription engine before you can start.
|
|
||||||
Choose the version that best fits your hardware.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="variant-options">
|
|
||||||
<label class="variant-option" class:selected={variant === "cpu"}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="variant"
|
|
||||||
value="cpu"
|
|
||||||
bind:group={variant}
|
|
||||||
/>
|
|
||||||
<div class="variant-info">
|
|
||||||
<span class="variant-name">Standard (CPU)</span>
|
|
||||||
<span class="variant-desc">Works on all computers (~500 MB download)</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="variant-option" class:selected={variant === "cuda"}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="variant"
|
|
||||||
value="cuda"
|
|
||||||
bind:group={variant}
|
|
||||||
/>
|
|
||||||
<div class="variant-info">
|
|
||||||
<span class="variant-name">GPU Accelerated (CUDA)</span>
|
|
||||||
<span class="variant-desc">Faster transcription with NVIDIA GPU (~2 GB download)</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="download-btn" onclick={startDownload}>
|
|
||||||
Download & Install
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{:else if setupState === "downloading"}
|
|
||||||
<div class="progress-section">
|
|
||||||
<p class="progress-message">{progressMessage}</p>
|
|
||||||
<div class="progress-bar-track">
|
|
||||||
<div
|
|
||||||
class="progress-bar-fill"
|
|
||||||
style="width: {progress}%"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<p class="progress-percent">{Math.round(progress)}%</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else if setupState === "error"}
|
|
||||||
<div class="error-section">
|
|
||||||
<div class="error-icon">
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#f44336" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
||||||
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="error-title">Download Failed</p>
|
|
||||||
<p class="error-message">{errorMessage}</p>
|
|
||||||
<button class="retry-btn" onclick={retry}>
|
|
||||||
Try Again
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else if setupState === "success"}
|
|
||||||
<div class="success-section">
|
|
||||||
<div class="success-icon">
|
|
||||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#4CAF50" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="10"/>
|
|
||||||
<polyline points="16 9 10.5 15 8 12.5"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="success-title">Setup Complete</p>
|
|
||||||
<p class="success-message">The transcription engine is ready to go.</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.setup-overlay {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #1e1e1e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-card {
|
|
||||||
background-color: #2a2a2a;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 40px;
|
|
||||||
max-width: 480px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 20px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-title {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #e0e0e0;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-heading {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #a0a0a0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setup-description {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #a0a0a0;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-options {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border: 2px solid #444;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-option:hover {
|
|
||||||
background-color: #333;
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-option.selected {
|
|
||||||
border-color: #4CAF50;
|
|
||||||
background-color: rgba(76, 175, 80, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-option input[type="radio"] {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.variant-desc {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Progress state */
|
|
||||||
.progress-section {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-message {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #a0a0a0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar-track {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar-fill {
|
|
||||||
height: 100%;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-percent {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #707070;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error state */
|
|
||||||
.error-section {
|
|
||||||
text-align: center;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #f44336;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #a0a0a0;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px 28px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: white;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.retry-btn:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Success state */
|
|
||||||
.success-section {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #4CAF50;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-message {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #a0a0a0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Version information for Local Transcription."""
|
"""Version information for Local Transcription."""
|
||||||
|
|
||||||
__version__ = "1.4.8"
|
__version__ = "1.4.0"
|
||||||
__version_info__ = (1, 4, 8)
|
__version_info__ = (1, 4, 0)
|
||||||
|
|
||||||
# Version history:
|
# Version history:
|
||||||
# 1.4.0 - Auto-update feature:
|
# 1.4.0 - Auto-update feature:
|
||||||
|
|||||||
Reference in New Issue
Block a user