Skip to content

Commit 72f7f39

Browse files
Lukas Geigerclaude
andcommitted
fix: wire model_combo signal in AIAssistantPanel + add PWA PNG icons
- ai_panel.py: connect model_combo.currentIndexChanged to _on_model_changed(); new @slot(int) maps index → AIModel enum, calls ai_service.set_model(). set_ai_service() now syncs model immediately on first call. Fixes: selecting a model in the dropdown had no effect. - tests/test_ai_panel_model_selection.py: 5 TDD tests covering signal wiring, initial sync, all 3 model indices, and no-crash without service. - web_companion: replace SVG icon placeholders with PNG icons (192/512, maskable) in manifest.webmanifest for proper PWA installability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 371ba5c commit 72f7f39

8 files changed

Lines changed: 129 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
2020
- `web_companion/tests/pwa.test.mjs`: auf 15 PWA-Tests erweitert (20/20 grün mit library-Tests)
2121

2222
### Geändert / Changed
23+
- `src/gui/panels/ai_panel.py`: `model_combo.currentIndexChanged` mit `_on_model_changed()` verdrahtet; `set_ai_service()` synchronisiert das Modell sofort beim ersten Aufruf; neuer Test `tests/test_ai_panel_model_selection.py` (5 Fälle).
2324
- Gemeinsamer Pfad-Resolver `src/core/app_paths.py` für `SettingsManager`, `ProjectManager` und `ProfilerBridge`.
2425
- Neue Regressionstests für Settings-, Recent-Projects- und Dateiindex-Pfad plus aktualisierter Linux-Plattform-Smoke.
2526
- README, Contribution- und Security-Dokumentation auf `dev-bricks/DevCenter` aktualisiert.

src/gui/panels/ai_panel.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
from PySide6.QtCore import Qt, Signal, QThread, Slot
1313
from PySide6.QtGui import QFont, QTextCursor
1414
from typing import Optional
15+
from modules.ai_assistant.ai_service import AIModel
16+
17+
18+
_MODEL_MAP = [AIModel.CLAUDE_SONNET, AIModel.CLAUDE_OPUS, AIModel.CLAUDE_HAIKU]
1519

1620

1721
class AIWorker(QThread):
@@ -79,6 +83,7 @@ def _setup_ui(self):
7983
self.model_combo = QComboBox()
8084
self.model_combo.addItems(["Claude Sonnet", "Claude Opus", "Claude Haiku"])
8185
self.model_combo.setCurrentIndex(0)
86+
self.model_combo.currentIndexChanged.connect(self._on_model_changed)
8287
header.addWidget(self.model_combo)
8388

8489
layout.addLayout(header)
@@ -182,7 +187,7 @@ def _get_welcome_html(self) -> str:
182187
def set_ai_service(self, ai_service):
183188
"""Setzt den AI-Service"""
184189
self._ai_service = ai_service
185-
190+
186191
if ai_service and ai_service.is_available():
187192
self.generate_btn.setEnabled(True)
188193
self.review_btn.setEnabled(True)
@@ -191,6 +196,15 @@ def set_ai_service(self, ai_service):
191196
self.generate_btn.setEnabled(False)
192197
self.review_btn.setEnabled(False)
193198
self.explain_btn.setEnabled(False)
199+
200+
# Aktuell ausgewähltes Modell sofort synchronisieren
201+
self._on_model_changed(self.model_combo.currentIndex())
202+
203+
@Slot(int)
204+
def _on_model_changed(self, index: int):
205+
"""Aktualisiert das AI-Modell wenn der Nutzer die Auswahl ändert."""
206+
if self._ai_service and 0 <= index < len(_MODEL_MAP):
207+
self._ai_service.set_model(_MODEL_MAP[index])
194208

195209
def set_context(self, code: str, file_name: str = ""):
196210
"""Setzt den Code-Kontext aus dem Editor"""
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
TDD-Test: AIAssistantPanel model_combo.currentIndexChanged Signal-Verdrahtung
4+
5+
Testziel: Wenn der Nutzer im Modell-Dropdown eine andere Auswahl trifft,
6+
wird AIService.set_model() mit dem korrekten AIModel-Enum-Wert aufgerufen.
7+
8+
Ausführung (erfordert Display-Umgebung oder xvfb):
9+
pytest tests/test_ai_panel_model_selection.py -v
10+
"""
11+
12+
import sys
13+
import os
14+
import pytest
15+
from unittest.mock import MagicMock, call
16+
17+
# Sicherstellt, dass src/ im Importpfad liegt
18+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
19+
20+
21+
@pytest.fixture(scope="module")
22+
def qt_app():
23+
"""Erstellt eine QApplication-Instanz (einmalig pro Testmodul)."""
24+
from PySide6.QtWidgets import QApplication
25+
app = QApplication.instance() or QApplication(sys.argv)
26+
yield app
27+
28+
29+
@pytest.fixture
30+
def mock_ai_service():
31+
"""Mock für AIService mit is_available() == True."""
32+
svc = MagicMock()
33+
svc.is_available.return_value = True
34+
return svc
35+
36+
37+
def test_model_combo_signals_wired(qt_app, mock_ai_service):
38+
"""Nach set_ai_service() führt Combo-Wechsel auf Index 1 zu set_model(CLAUDE_OPUS)."""
39+
from gui.panels.ai_panel import AIAssistantPanel
40+
from modules.ai_assistant.ai_service import AIModel
41+
42+
panel = AIAssistantPanel()
43+
panel.set_ai_service(mock_ai_service)
44+
45+
panel.model_combo.setCurrentIndex(1) # Claude Opus
46+
47+
mock_ai_service.set_model.assert_called_with(AIModel.CLAUDE_OPUS)
48+
49+
50+
def test_model_combo_initial_sync(qt_app, mock_ai_service):
51+
"""set_ai_service() synchronisiert das aktuell ausgewählte Modell (Index 0 → SONNET)."""
52+
from gui.panels.ai_panel import AIAssistantPanel
53+
from modules.ai_assistant.ai_service import AIModel
54+
55+
panel = AIAssistantPanel()
56+
panel.set_ai_service(mock_ai_service)
57+
58+
mock_ai_service.set_model.assert_called_with(AIModel.CLAUDE_SONNET)
59+
60+
61+
def test_model_combo_haiku(qt_app, mock_ai_service):
62+
"""Combo-Wechsel auf Index 2 ergibt set_model(CLAUDE_HAIKU)."""
63+
from gui.panels.ai_panel import AIAssistantPanel
64+
from modules.ai_assistant.ai_service import AIModel
65+
66+
panel = AIAssistantPanel()
67+
panel.set_ai_service(mock_ai_service)
68+
mock_ai_service.reset_mock()
69+
70+
panel.model_combo.setCurrentIndex(2) # Claude Haiku
71+
72+
mock_ai_service.set_model.assert_called_with(AIModel.CLAUDE_HAIKU)
73+
74+
75+
def test_model_combo_no_service_no_crash(qt_app):
76+
"""Combo-Wechsel ohne gesetzten AI-Service löst keinen Fehler aus."""
77+
from gui.panels.ai_panel import AIAssistantPanel
78+
79+
panel = AIAssistantPanel()
80+
# kein set_ai_service() — _ai_service ist None
81+
panel.model_combo.setCurrentIndex(1) # darf nicht crashen
82+
83+
84+
def test_model_combo_all_indices(qt_app, mock_ai_service):
85+
"""Alle drei Combo-Indizes mappen korrekt auf die AIModel-Enum-Werte."""
86+
from gui.panels.ai_panel import AIAssistantPanel
87+
from modules.ai_assistant.ai_service import AIModel
88+
89+
panel = AIAssistantPanel()
90+
panel.set_ai_service(mock_ai_service)
91+
mock_ai_service.reset_mock()
92+
93+
expected = [AIModel.CLAUDE_SONNET, AIModel.CLAUDE_OPUS, AIModel.CLAUDE_HAIKU]
94+
for idx, model in enumerate(expected):
95+
panel.model_combo.setCurrentIndex(idx)
96+
mock_ai_service.set_model.assert_called_with(model)

web_companion/icons/Icon-192.png

11.6 KB
Loading

web_companion/icons/Icon-512.png

42.3 KB
Loading
11.6 KB
Loading
42.3 KB
Loading

web_companion/manifest.webmanifest

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,25 @@
1111
"theme_color": "#0f1720",
1212
"icons": [
1313
{
14-
"src": "./icons/app.svg",
15-
"sizes": "any",
16-
"type": "image/svg+xml",
17-
"purpose": "any"
14+
"src": "./icons/Icon-192.png",
15+
"sizes": "192x192",
16+
"type": "image/png"
1817
},
1918
{
20-
"src": "./icons/app-maskable.svg",
21-
"sizes": "any",
22-
"type": "image/svg+xml",
19+
"src": "./icons/Icon-512.png",
20+
"sizes": "512x512",
21+
"type": "image/png"
22+
},
23+
{
24+
"src": "./icons/Icon-maskable-192.png",
25+
"sizes": "192x192",
26+
"type": "image/png",
27+
"purpose": "maskable"
28+
},
29+
{
30+
"src": "./icons/Icon-maskable-512.png",
31+
"sizes": "512x512",
32+
"type": "image/png",
2333
"purpose": "maskable"
2434
}
2535
]

0 commit comments

Comments
 (0)