Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
56e44f8
pop up mode and chat buble toggle
pladisdev Nov 2, 2025
82db2b0
potential audio fix
pladisdev Nov 2, 2025
579683d
linux sh and better docker support
pladisdev Nov 2, 2025
fad6d1a
fixed linux build?
pladisdev Nov 2, 2025
a12de82
linux fix part 2
pladisdev Nov 2, 2025
5d79afe
docker fix part 1
pladisdev Nov 2, 2025
0f3af89
Initial plan
Copilot Nov 2, 2025
d5d90f6
Initial plan
Copilot Nov 2, 2025
cbf5fcd
Initial plan
Copilot Nov 2, 2025
5a5d1d5
Initial plan
Copilot Nov 2, 2025
207ba9b
Initial plan
Copilot Nov 2, 2025
76b9912
Initial plan
Copilot Nov 2, 2025
366b640
Initial plan
Copilot Nov 2, 2025
ebc7bb7
Update frontend/src/pages/YappersPage.jsx
pladisdev Nov 2, 2025
be9a086
Initial plan
Copilot Nov 2, 2025
3fc9146
Update frontend/src/pages/YappersPage.jsx
pladisdev Nov 2, 2025
914b789
Replace sys.platform with platform.system() for OS detection
Copilot Nov 2, 2025
938886f
Fix race condition in popup avatar lifecycle
Copilot Nov 2, 2025
7737a2f
Extract magic numbers to named constants for better readability
Copilot Nov 2, 2025
3ea0d45
Extract magic number -2.5px into avatarActiveOffset setting
Copilot Nov 2, 2025
ee4de2a
Fix audio cleanup in popup mode when play() fails
Copilot Nov 2, 2025
3ae0527
Merge pull request #17 from pladisdev/copilot/sub-pr-8-bf786502-ac5d-…
pladisdev Nov 2, 2025
25dc303
Extract hex opacity calculation to utility function
Copilot Nov 2, 2025
730dc88
Fix audio error handlers to clean up tracking references
Copilot Nov 2, 2025
bb266bd
Merge pull request #16 from pladisdev/copilot/sub-pr-8-4be2dd0e-06ec-…
pladisdev Nov 2, 2025
7d70a13
Merge pull request #15 from pladisdev/copilot/sub-pr-8-9e407134-0238-…
pladisdev Nov 2, 2025
d1f450d
Merge pull request #14 from pladisdev/copilot/sub-pr-8-please-work
pladisdev Nov 2, 2025
5436727
Merge pull request #13 from pladisdev/copilot/sub-pr-8-one-more-time
pladisdev Nov 2, 2025
d8c850e
Merge pull request #12 from pladisdev/copilot/sub-pr-8-yet-again
pladisdev Nov 2, 2025
1405787
Merge pull request #11 from pladisdev/copilot/sub-pr-8-another-one
pladisdev Nov 2, 2025
1ad53b1
Merge pull request #10 from pladisdev/copilot/sub-pr-8-again
pladisdev Nov 2, 2025
3956aed
Merge branch 'main' into development
pladisdev Nov 2, 2025
a0b1f82
update flow for builds
pladisdev Nov 2, 2025
a2d0cea
Merge branch 'main' into development
pladisdev Nov 2, 2025
6adf5b1
better build and release file
pladisdev Nov 2, 2025
57df8ef
workflow fix
pladisdev Nov 2, 2025
6bbabff
Merge branch 'main' into development
pladisdev Nov 2, 2025
7672520
build fix
pladisdev Nov 2, 2025
d56e12e
Merge branch 'development' of https://github.com/pladisdev/chat-yappe…
pladisdev Nov 2, 2025
5aca8cf
Merge branch 'main' into development
pladisdev Nov 2, 2025
f0cb455
usernames in chat bubbles
pladisdev Nov 6, 2025
dd0ffab
Merge branch 'development' of https://github.com/pladisdev/chat-yappe…
pladisdev Nov 6, 2025
4f534c8
twitch authentifcation
pladisdev Nov 6, 2025
9095db5
Merge branch 'main' into development
pladisdev Nov 6, 2025
979acca
tts limit
pladisdev Nov 22, 2025
008f0e0
notifcation that user needs to click page, quick status
pladisdev Nov 22, 2025
c09bdcc
update readme, version
pladisdev Nov 22, 2025
1cb53f6
Merge branch 'main' into development
pladisdev Nov 22, 2025
ee60426
emergency twitch fix
pladisdev Nov 22, 2025
56967e2
Merge branch 'main' into development
pladisdev Nov 22, 2025
cfcf2dd
Merge branch 'main' into development
pladisdev Nov 22, 2025
1aa6813
yep
pladisdev Nov 22, 2025
5844084
dang
pladisdev Nov 22, 2025
4a6a8e5
better twitch logging
pladisdev Nov 23, 2025
693bfbc
animations and avatar layout editor
pladisdev Nov 23, 2025
326928e
fix spin animation
pladisdev Nov 23, 2025
a80e583
twitch fix part 2
pladisdev Nov 23, 2025
69800d3
Merge branch 'main' into development
pladisdev Nov 23, 2025
365ef8e
twitch fix, better fonts, cleanup
pladisdev Nov 25, 2025
61d9dcf
Merge branch 'main' into development
pladisdev Nov 25, 2025
046c52f
fixed imports
pladisdev Nov 25, 2025
7b33d13
fixed things
pladisdev Nov 25, 2025
638fb4e
Merge branch 'main' into development
pladisdev Nov 25, 2025
59a7722
undefined fix
pladisdev Nov 25, 2025
4a09f0b
twitch test fix
pladisdev Nov 25, 2025
99b9642
another mock fix
pladisdev Nov 25, 2025
15efb3c
another fix for twitch creds tests
pladisdev Nov 25, 2025
6ca3832
better logging
pladisdev Nov 25, 2025
08355e7
twitch fixes?
pladisdev Nov 25, 2025
0d7bb86
another twitch fix
pladisdev Nov 25, 2025
484caaf
Merge branch 'main' into development
pladisdev Nov 25, 2025
a45dcfc
fix twitch io 3.x
pladisdev Nov 25, 2025
478e8ea
Merge branch 'development' of https://github.com/pladisdev/chat-yappe…
pladisdev Nov 25, 2025
6ad992d
fix pipelines for python to use 3.10
pladisdev Nov 25, 2025
2fcaa47
Merge branch 'main' into development
pladisdev Nov 25, 2025
4e39aa1
attempt to fix edge tts
pladisdev Feb 14, 2026
01b85b8
fixed build and release
pladisdev Feb 14, 2026
7a1ee84
annother wix fix
pladisdev Feb 14, 2026
8589ceb
global wix fix
pladisdev Feb 14, 2026
0f9441b
Merge branch 'main' into development
pladisdev Feb 14, 2026
ca487ed
update to edge-tts
pladisdev Apr 5, 2026
a7b0ddf
Merge branch 'development' of https://github.com/pladisdev/chat-yappe…
pladisdev Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 30 additions & 45 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ jobs:
Write-Host "Executable size: $([math]::Round($size, 2)) MB"
shell: powershell

- name: Install WiX Toolset v4
- name: Install WiX Toolset v5
run: |
Write-Host "Installing WiX Toolset v4..."
dotnet tool install --global wix
Write-Host "Installing WiX Toolset v5..."
dotnet tool install --global wix --version 5.0.1

# Ensure WiX is in PATH
$env:PATH = "$env:USERPROFILE\.dotnet\tools;$env:PATH"
Expand All @@ -128,10 +128,9 @@ jobs:
wix --version

Write-Host "Installing WiX UI extension globally..."
wix extension add --global WixToolset.UI.wixext
wix extension add --global WixToolset.UI.wixext/5.0.1

Write-Host "Verifying UI extension..."
wix extension list
wix extension list --global

Comment on lines 119 to 135
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR title indicates an edge-tts update, but this workflow change also upgrades WiX to v5 and changes release/build dependencies (including de-emphasizing Docker). If these are intentional, consider splitting them into a separate PR or updating the PR title/description so reviewers can assess CI/release changes explicitly.

Copilot uses AI. Check for mistakes.
Write-Host "WiX installation complete"
Expand All @@ -145,7 +144,16 @@ jobs:
shell: powershell

- name: Build MSI Installer
run: python deployment/build_msi.py
run: |
# Ensure WiX is in PATH
$env:PATH = "$env:USERPROFILE\.dotnet\tools;$env:PATH"

# Verify WiX is accessible
wix --version

# Build MSI
python deployment/build_msi.py
shell: powershell
env:
VITE_APP_VERSION: ${{ steps.get_version.outputs.app_version }}

Expand Down Expand Up @@ -256,7 +264,7 @@ jobs:
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [build, linux-build, docker-build]
needs: [build, linux-build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
Expand Down Expand Up @@ -286,14 +294,6 @@ jobs:
name: linux-build
path: ./linux-build

- name: Download Docker artifacts
if: needs.docker-build.result == 'success'
uses: actions/download-artifact@v4
with:
name: docker-release-files
path: ./docker-release
continue-on-error: true

- name: Get MSI filename
id: msi_info
run: |
Expand All @@ -308,17 +308,6 @@ jobs:
echo "MSI file not found, using default name"
fi

- name: Check Docker build status
id: docker_status
run: |
if [ "${{ needs.docker-build.result }}" == "success" ]; then
echo "docker_success=true" >> $GITHUB_OUTPUT
echo "Docker build succeeded"
else
echo "docker_success=false" >> $GITHUB_OUTPUT
echo "Docker build failed or was skipped - release will not include Docker files"
fi

- name: Create Release
uses: softprops/action-gh-release@v1
with:
Expand Down Expand Up @@ -680,19 +669,20 @@ jobs:
type=raw,value=${{ steps.docker_version.outputs.docker_tag }}
type=raw,value=latest,enable=${{ steps.docker_version.outputs.is_release == 'true' }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
APP_VERSION=${{ steps.docker_version.outputs.app_version }}
# Commented out to speed up CI - Docker build takes too long
# - name: Build and push Docker image
# uses: docker/build-push-action@v5
# with:
# context: .
# file: ./docker/Dockerfile
# push: true
# tags: ${{ steps.meta.outputs.tags }}
# labels: ${{ steps.meta.outputs.labels }}
# platforms: linux/amd64,linux/arm64
# cache-from: type=gha
# cache-to: type=gha,mode=max
# build-args: |
# APP_VERSION=${{ steps.docker_version.outputs.app_version }}
Comment on lines +672 to +685
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker build step is commented out, but the job still logs into GHCR, computes metadata, and later posts a PR comment claiming an image was built/published. Either disable the whole docker-build job, or gate the downstream steps/comments/artifacts on actually building/pushing the image to avoid misleading CI output.

Copilot uses AI. Check for mistakes.

- name: Generate docker-compose.yml for release
if: steps.docker_version.outputs.is_release == 'true'
Expand Down Expand Up @@ -979,7 +969,7 @@ jobs:
build-status-check:
name: Build Status Check
runs-on: ubuntu-latest
needs: [build, linux-build, docker-build]
needs: [build, linux-build]
if: always()

steps:
Expand All @@ -995,9 +985,4 @@ jobs:
echo "Cannot merge to main until build succeeds"
exit 1
fi
if [ "${{ needs.docker-build.result }}" == "success" ]; then
echo "[PASS] Docker build succeeded"
elif [ "${{ needs.docker-build.result }}" == "failure" ]; then
echo "[WARN] Docker build failed - continuing anyway (Docker build is optional)"
fi
echo "[PASS] Required builds succeeded - safe to merge"
78 changes: 74 additions & 4 deletions backend/modules/tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
import uuid
import time
import sys
import subprocess
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subprocess is imported but not used in this module; please remove it to avoid lint/packaging noise.

Suggested change
import subprocess

Copilot uses AI. Check for mistakes.
from dataclasses import dataclass
import aiohttp
import random
Expand All @@ -20,6 +22,56 @@ def reset_fallback_stats():
fallback_voice_stats.clear()
fallback_selection_count = 0

# Track edge-tts update attempts to avoid repeated updates
_edge_tts_update_attempted = False

async def try_update_edge_tts():
"""Attempt to update edge-tts package when API compatibility issues occur"""
global _edge_tts_update_attempted

if _edge_tts_update_attempted:
logger.info("edge-tts update already attempted this session, skipping")
return False

_edge_tts_update_attempted = True
logger.info("Attempting to update edge-tts package to fix API compatibility...")

Comment on lines +27 to +38
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _edge_tts_update_attempted guard is a shared global without synchronization. With concurrent synth requests, multiple tasks can still race into try_update_edge_tts() and run upgrades simultaneously. Consider guarding with an asyncio.Lock (or similar) to ensure only one update attempt runs at a time.

Suggested change
async def try_update_edge_tts():
"""Attempt to update edge-tts package when API compatibility issues occur"""
global _edge_tts_update_attempted
if _edge_tts_update_attempted:
logger.info("edge-tts update already attempted this session, skipping")
return False
_edge_tts_update_attempted = True
logger.info("Attempting to update edge-tts package to fix API compatibility...")
_edge_tts_update_lock = asyncio.Lock()
async def try_update_edge_tts():
"""Attempt to update edge-tts package when API compatibility issues occur"""
global _edge_tts_update_attempted
async with _edge_tts_update_lock:
if _edge_tts_update_attempted:
logger.info("edge-tts update already attempted this session, skipping")
return False
_edge_tts_update_attempted = True
logger.info("Attempting to update edge-tts package to fix API compatibility...")

Copilot uses AI. Check for mistakes.
try:
# Run pip upgrade in subprocess
python_exe = sys.executable
result = await asyncio.create_subprocess_exec(
python_exe, "-m", "pip", "install", "--upgrade", "edge-tts",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await result.communicate()

if result.returncode == 0:
logger.info(f"edge-tts successfully updated: {stdout.decode()}")

# Reload the edge_tts module
try:
import importlib
global edge_tts
if edge_tts:
importlib.reload(edge_tts)
logger.info("edge-tts module reloaded successfully")
else:
import edge_tts as new_edge_tts
edge_tts = new_edge_tts
logger.info("edge-tts module imported successfully")
return True
except Exception as e:
logger.warning(f"edge-tts updated but module reload failed: {e}")
logger.info("Restart the application to use the updated edge-tts")
return False
else:
logger.error(f"edge-tts update failed: {stderr.decode()}")
return False
except Exception as e:
logger.error(f"Failed to update edge-tts: {e}")
return False
Comment on lines +25 to +73
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a runtime pip install --upgrade edge-tts inside the server process. That can break reproducible deployments (conflicts with the pinned edge-tts==... in requirements), fail in read-only/container environments, and expands the attack surface by fetching code at runtime. Consider removing the auto-upgrade and instead surface a clear error/instruction (or gate it behind an explicit admin setting/env var and pin to an expected version).

Suggested change
# Track edge-tts update attempts to avoid repeated updates
_edge_tts_update_attempted = False
async def try_update_edge_tts():
"""Attempt to update edge-tts package when API compatibility issues occur"""
global _edge_tts_update_attempted
if _edge_tts_update_attempted:
logger.info("edge-tts update already attempted this session, skipping")
return False
_edge_tts_update_attempted = True
logger.info("Attempting to update edge-tts package to fix API compatibility...")
try:
# Run pip upgrade in subprocess
python_exe = sys.executable
result = await asyncio.create_subprocess_exec(
python_exe, "-m", "pip", "install", "--upgrade", "edge-tts",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await result.communicate()
if result.returncode == 0:
logger.info(f"edge-tts successfully updated: {stdout.decode()}")
# Reload the edge_tts module
try:
import importlib
global edge_tts
if edge_tts:
importlib.reload(edge_tts)
logger.info("edge-tts module reloaded successfully")
else:
import edge_tts as new_edge_tts
edge_tts = new_edge_tts
logger.info("edge-tts module imported successfully")
return True
except Exception as e:
logger.warning(f"edge-tts updated but module reload failed: {e}")
logger.info("Restart the application to use the updated edge-tts")
return False
else:
logger.error(f"edge-tts update failed: {stderr.decode()}")
return False
except Exception as e:
logger.error(f"Failed to update edge-tts: {e}")
return False
# Track edge-tts update attempts to avoid repeated update instructions
_edge_tts_update_attempted = False
async def try_update_edge_tts():
"""Log a one-time instruction to update edge-tts outside the running process."""
global _edge_tts_update_attempted
if _edge_tts_update_attempted:
logger.info("edge-tts update already flagged this session, skipping")
return False
_edge_tts_update_attempted = True
logger.error(
"edge-tts appears incompatible, but automatic runtime upgrades are disabled. "
"Please update the pinned 'edge-tts' dependency through your normal build/deploy "
"process and restart the application."
)
return False

Copilot uses AI. Check for mistakes.

# Provider 1: MonsterAPI TTS (async, great quality)
try:
import aiohttp
Expand Down Expand Up @@ -341,9 +393,28 @@ async def synth(self, job: TTSJob) -> str:

outpath = os.path.join(AUDIO_DIR, f"{uuid.uuid4()}.{job.audio_format}")

# Helper function to attempt synthesis with retry logic
async def attempt_synthesis(voice_id: str, retry_on_403: bool = True):
try:
communicate = edge_tts.Communicate(job.text, voice_id)
await communicate.save(outpath)
return True
except Exception as e:
# Check if it's a 403 error (API compatibility issue)
error_str = str(e)
if "403" in error_str and "Invalid response status" in error_str and retry_on_403:
logger.warning(f"Edge TTS returned 403 error - API may need updating")
# Attempt to update edge-tts
if await try_update_edge_tts():
logger.info("Retrying synthesis after edge-tts update...")
# Retry once after update (without further retries)
return await attempt_synthesis(voice_id, retry_on_403=False)
else:
logger.error("edge-tts update failed or requires restart. Please restart the application.")
raise
Comment on lines +396 to +414
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New 403-detection + auto-update retry logic isn’t covered by existing unit tests. Add a unit test that mocks edge_tts.Communicate.save to raise the 403 error once, asserts try_update_edge_tts() is invoked at most once per session, and verifies synthesis is retried (or that a clear error is surfaced when update fails).

Copilot uses AI. Check for mistakes.

try:
communicate = edge_tts.Communicate(job.text, job.voice)
await communicate.save(outpath)
await attempt_synthesis(job.voice)
except edge_tts.exceptions.NoAudioReceived as e:
logger.error(f"Edge TTS NoAudioReceived error - Voice: {job.voice}, Text: '{job.text[:50]}...'")

Expand All @@ -358,8 +429,7 @@ async def synth(self, job: TTSJob) -> str:
if job.voice != self.voice_id:
logger.warning(f"Voice '{job.voice}' appears to be invalid or deprecated. Retrying with default voice: {self.voice_id}")
try:
communicate = edge_tts.Communicate(job.text, self.voice_id)
await communicate.save(outpath)
await attempt_synthesis(self.voice_id)
logger.info(f"Successfully synthesized with fallback voice: {self.voice_id}")
except edge_tts.exceptions.NoAudioReceived:
raise RuntimeError(f"Edge TTS failed with both '{job.voice}' and fallback '{self.voice_id}'. The voices may be invalid or Edge TTS service is unavailable. Please refresh your voice list in Settings → TTS → Edge TTS.")
Expand Down
5 changes: 4 additions & 1 deletion deployment/build_msi.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ def check_wix_installed():
def check_wix_extensions():
"""Check if required WiX extensions are installed"""
try:
# Check both local and global extensions
result = subprocess.run("wix extension list", shell=True, capture_output=True, text=True)
if "WixToolset.UI.wixext" in result.stdout:
global_result = subprocess.run("wix extension list --global", shell=True, capture_output=True, text=True)

if "WixToolset.UI.wixext" in result.stdout or "WixToolset.UI.wixext" in global_result.stdout:
print("WiX UI extension found")
return True
else:
Expand Down
17 changes: 10 additions & 7 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/src/app.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { WebSocketProvider } from './WebSocketContext'
import YappersPage from './pages/YappersPage'
import SettingsPage from './pages/SettingsPage'
Expand All @@ -19,9 +19,9 @@ export default function App() {
<WebSocketProvider>
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<Routes>
<Route path="/" element={<Navigate to="/settings" replace />} />
<Route path="/yappers" element={<YappersPage />} />
<Route path="/settings" element={<SettingsPage />} />
{/* No default route - accessing root shows nothing */}
</Routes>
</Router>
</WebSocketProvider>
Expand Down
3 changes: 3 additions & 0 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { fileURLToPath } from 'url'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

// Load environment variables
const backendPort = process.env.PORT || 8008
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ python-multipart>=0.0.6
python-dotenv>=1.0.0

# TTS and audio
edge-tts==7.2.7
edge-tts==7.2.8
mutagen>=1.45.0

# Streaming platforms
Expand Down
Loading