Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 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
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
15 changes: 12 additions & 3 deletions backend/modules/persistent_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,27 @@ def get_database_session():
yield session

def get_settings() -> dict:
"""Get application settings from database"""
"""Get application settings from database, merged with defaults for any missing keys"""
import json
try:
# Load defaults
defaults = {}
if os.path.exists(DEFAULTS_PATH):
with open(DEFAULTS_PATH, 'r', encoding='utf-8') as f:
defaults = json.load(f)

with Session(engine) as session:
row = session.exec(select(Setting).where(Setting.key == "settings")).first()
if row:
settings = json.loads(row.value_json)
# Merge defaults with loaded settings (settings take precedence)
# This ensures new settings are available even in old databases
merged = {**defaults, **settings}
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The shallow merge strategy {**defaults, **settings} may not properly handle nested settings dictionaries. For example, if a new nested key is added to tts.monstertts in defaults but the database already has a tts object, the nested default keys won't be merged - the entire database tts object will override the defaults. Consider using a deep merge function to ensure nested settings defaults are properly merged.

Copilot uses AI. Check for mistakes.
logger.info(f"Loaded settings from database: {DB_PATH}")
return settings
return merged
else:
logger.error("No settings found in database!")
return {}
return defaults
Comment on lines +92 to +109
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The new settings merge logic lacks test coverage. Consider adding tests to verify:

  1. Settings are properly merged when database has old settings without new defaults
  2. Nested settings dictionaries are correctly merged (or add a deep merge if the shallow merge is insufficient)
  3. Database settings take precedence over defaults
  4. Defaults are returned when no database settings exist

Copilot uses AI. Check for mistakes.
except Exception as e:
logger.error(f"Error loading settings: {e}")
return {}
Expand Down
34 changes: 22 additions & 12 deletions backend/routers/avatars.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ async def api_upload_avatar(file: UploadFile, avatar_name: str = Form(...), avat

# Broadcast refresh message to all connected clients and regenerate slot assignments
# Import here to avoid circular imports
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -223,7 +224,8 @@ async def api_delete_avatar(avatar_id: int):
delete_avatar(avatar_id)

# Broadcast refresh message and regenerate slot assignments
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -255,7 +257,8 @@ async def api_delete_avatar_group(group_id: str):
"""Delete an entire avatar group (all avatars with the same group_id)"""
try:
result = delete_avatar_group(group_id)
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)
get_active_avatar_slots().clear()
Expand Down Expand Up @@ -283,7 +286,8 @@ async def api_update_avatar_position(group_id: str, position_data: dict):
spawn_position = position_data.get("spawn_position")
result = update_avatar_group_position(group_id, spawn_position)

from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)
get_active_avatar_slots().clear()
Expand All @@ -309,7 +313,8 @@ async def api_toggle_avatar_group_disabled(group_id: str):
"""Toggle the disabled status of an entire avatar group"""
try:
result = toggle_avatar_group_disabled(group_id)
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)
get_active_avatar_slots().clear()
Expand All @@ -335,7 +340,8 @@ async def api_toggle_avatar_group_disabled(group_id: str):
async def api_regenerate_avatar_slots():
"""Force regeneration of avatar slot assignments (re-randomize avatars)"""
try:
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -392,7 +398,7 @@ async def api_release_avatar_slot(slot_id: str):
async def api_get_avatar_queue():
"""Get current avatar message queue status"""
try:
from app import avatar_message_queue
from modules.queue_manager import avatar_message_queue
from modules.avatars import get_active_avatar_slots, get_avatar_slot_assignments

active_avatar_slots = get_active_avatar_slots()
Expand Down Expand Up @@ -447,7 +453,8 @@ async def api_create_avatar_slot(slot_data: dict):
)

# Broadcast update to all clients
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -492,7 +499,8 @@ async def api_update_configured_slot(slot_id: int, slot_data: dict):
return {"success": False, "error": "Slot not found"}

# Broadcast update to all clients
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -523,7 +531,8 @@ async def api_delete_configured_slot(slot_id: int):
return {"success": False, "error": "Slot not found"}

# Broadcast update to all clients
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand Down Expand Up @@ -551,7 +560,8 @@ async def api_delete_all_configured_slots():
count = delete_all_avatar_slots()

# Broadcast update to all clients
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id)

Expand All @@ -569,4 +579,4 @@ async def api_delete_all_configured_slots():
return {"success": True, "deleted_count": count}
except Exception as e:
logger.error(f"Failed to delete all avatar slots: {e}")
return {"success": False, "error": str(e)}
return {"success": False, "error": str(e)}
3 changes: 2 additions & 1 deletion backend/routers/config_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ async def import_config(
logger.info(f"Imported {stats['avatars_imported']} avatars, copied {stats['images_copied']} images")

# Regenerate avatar slot assignments
from app import hub, avatar_message_queue
from app import hub
from modules.queue_manager import avatar_message_queue
from modules.avatars import (
generate_avatar_slot_assignments, get_active_avatar_slots,
get_avatar_slot_assignments, get_avatar_assignments_generation_id
Expand Down
3 changes: 2 additions & 1 deletion backend/routers/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ async def test_parallel_limit():
duration = time.time() - start_time

# Import queue info
from app import active_tts_jobs, parallel_message_queue, total_active_tts_count
from app import active_tts_jobs, total_active_tts_count
from modules.queue_manager import parallel_message_queue

result = {
"success": True,
Expand Down
7 changes: 7 additions & 0 deletions backend/tests/test_clearchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@
"""
import pytest
import asyncio
import os
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The os import appears to be unused. The monkeypatch.setenv method in the fixture doesn't require the os module to be imported directly. Consider removing this import to keep the code clean.

Suggested change
import os

Copilot uses AI. Check for mistakes.
from unittest.mock import MagicMock, AsyncMock, patch
from modules.twitch_listener import TwitchBot


class TestClearChatEvents:
"""Test CLEARCHAT event parsing and handling"""

@pytest.fixture(autouse=True)
def mock_twitch_credentials(self, monkeypatch):
"""Mock Twitch credentials for all tests in this class"""
monkeypatch.setenv("TWITCH_CLIENT_ID", "test_client_id")
monkeypatch.setenv("TWITCH_CLIENT_SECRET", "test_client_secret")

def test_normalize_tags_dict(self):
"""Test tag normalization with dict input"""
from modules.twitch_listener import _normalize_tags
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.0"
__version__ = "1.3.1"
2 changes: 1 addition & 1 deletion frontend/src/components/settings/GeneralSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function GeneralSettings({ settings, setSettings, updateSettings, apiUrl }) {
const currentLimit = settings.parallelMessageLimit
const presetValues = [1, 2, 3, 4, 5, 6, 8, 10, 15, 20, null]

if (currentLimit !== null && !presetValues.includes(currentLimit)) {
if (currentLimit !== null && currentLimit !== undefined && !presetValues.includes(currentLimit)) {
setIsCustomMode(true)
setCustomLimit(currentLimit.toString())
} else {
Expand Down
Loading