Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 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
9a96471
update to new version
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

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 }}
Comment on lines +672 to +679
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/push step is commented out, but the docker-build job still proceeds to generate release files and posts a PR comment claiming the image was built successfully. Either re-enable the build/push step or guard/disable the downstream steps so CI doesn't report a Docker image/tag that doesn't exist.

Copilot uses AI. Check for mistakes.
# 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 }}

- 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"
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,18 +225,21 @@ chat-yapper/

## Changelog

### v1.3.1 (Latest)
### v1.3.2 (Latest)
- Fix to edge-tts

### v1.3.1
- **New Features:**
- Twitch fix
- Allow random avatar assignment
- More fonts to select from

### v1.3.0 (Latest)
### v1.3.0
- Better control of avatar placement in Avatar Layout Editor
- Select and Adjust speaking animations for crowd mode
- Added idle animations for crowd mode

### v1.2.2 (Latest)
### v1.2.2
- Quick status view
- Limit concurrent TTS messages
- Some more twitch fixes and improved notifcations
Expand Down
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 never used in this module; it should be removed to avoid lint warnings and keep imports minimal.

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...")

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.

Auto-updating dependencies at runtime via pip install --upgrade edge-tts has significant security and operational risks (downloads arbitrary code, requires network + write permissions, and will likely fail in the PyInstaller-built executable environment). Prefer surfacing a clear error with upgrade instructions (or pin/ship a compatible version) rather than attempting an in-app upgrade.

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 guidance logs
_edge_tts_update_attempted = False
async def try_update_edge_tts():
"""Log manual remediation guidance instead of attempting a runtime package update."""
global _edge_tts_update_attempted
if _edge_tts_update_attempted:
logger.info("edge-tts upgrade guidance already provided this session, skipping")
return False
_edge_tts_update_attempted = True
python_exe = sys.executable or "python"
logger.error(
"Automatic runtime updates for edge-tts are disabled for security and reliability "
"reasons. Please upgrade the dependency outside the application and restart it."
)
logger.info(
f"Manual upgrade command: {python_exe} -m pip install --upgrade edge-tts"
)
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:
Comment on lines +396 to +412
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-retry path (including the edge-tts update attempt) is untested. There are existing pytest tests for TTS providers; add a unit test that mocks edge_tts.Communicate.save to raise the 403 error and asserts the retry behavior (and that the updater is called at most once).

Copilot uses AI. Check for mistakes.
logger.error("edge-tts update failed or requires restart. Please restart the application.")
raise

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
2 changes: 1 addition & 1 deletion backend/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
This file is automatically updated during CI/CD builds
"""

__version__ = "1.3.1"
__version__ = "1.3.2"
2 changes: 1 addition & 1 deletion deployment/ChatYapper.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<!-- Product Definition -->
<Package Name="Chat Yapper"
Manufacturer="Pladis Development"
Version="1.3.1"
Version="1.3.2"
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
Language="1033"
Compressed="yes"
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