diff --git a/app/main.py b/app/main.py
index e69de29..e40a838 100644
--- a/app/main.py
+++ b/app/main.py
@@ -0,0 +1,21 @@
+# app/main.py
+import sys
+import PySide6.QtAsyncio as QtAsyncio
+from PySide6.QtWidgets import QApplication
+
+from app.ui.main_window import MainWindow
+
+
+def main():
+ app = QApplication(sys.argv)
+ app.setApplicationName("Spanish Assistant")
+
+ window = MainWindow()
+ window.show()
+
+ # QtAsyncio handles the combined Qt and Python asyncio event loop
+ QtAsyncio.run()
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/app/ui/chat_tab.py b/app/ui/chat_tab.py
index e69de29..8925231 100644
--- a/app/ui/chat_tab.py
+++ b/app/ui/chat_tab.py
@@ -0,0 +1,79 @@
+#app/ui/chat_tab.py
+
+import asyncio
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import (
+ QFrame,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+class ChatTab(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._init_ui()
+
+ def _init_ui(self):
+ layout = QVBoxLayout(self)
+
+ # 1. Chat History Display
+ self.chat_display = QTextEdit(self)
+ self.chat_display.setReadOnly(True)
+ self.chat_display.setPlaceholderText(
+ "Su conversación aparecerá aquí... (Your conversation will appear here...)"
+ )
+ layout.addWidget(self.chat_display, stretch=4)
+
+ # 2. Feedback Panel (Pronunciation & WER Metrics)
+ self.feedback_panel = QFrame(self)
+ self.feedback_panel.setFrameShape(QFrame.Shape.StyledPanel)
+ feedback_layout = QVBoxLayout(self.feedback_panel)
+
+ self.feedback_label = QLabel("Análisis de Pronunciación:", self)
+ self.feedback_content = QLabel("Mantén presionado el botón o habla para evaluar.", self)
+
+ feedback_layout.addWidget(self.feedback_label)
+ feedback_layout.addWidget(self.feedback_content)
+ layout.addWidget(self.feedback_panel, stretch=1)
+
+ # 3. User Input Controls
+ input_layout = QHBoxLayout()
+
+ self.text_input = QLineEdit(self)
+ self.text_input.setPlaceholderText("Escribe un mensaje o habla...")
+ self.text_input.returnPressed.connect(self._on_send)
+
+ self.btn_send = QPushButton("Enviar", self)
+ self.btn_send.clicked.connect(self._on_send)
+
+ self.btn_record = QPushButton("🎤 Hablar (Hold Space)", self)
+ self.btn_record.clicked.connect(self._on_record_clicked)
+
+ input_layout.addWidget(self.text_input, stretch=3)
+ input_layout.addWidget(self.btn_send)
+ input_layout.addWidget(self.btn_record)
+
+ layout.addLayout(input_layout)
+
+ def _on_send(self):
+ text = self.text_input.text().strip()
+ if text:
+ self.chat_display.append(f"Tú: {text}")
+ self.text_input.clear()
+ # Schedule asynchronous processing via asyncio
+ asyncio.create_task(self._simulate_assistant_response(text))
+
+ def _on_record_clicked(self):
+ self.chat_display.append("[Escuchando audio local vía mlx-whisper...]")
+
+ async def _simulate_assistant_response(self, user_text: str):
+ # Async coroutine demonstrating QtAsyncio responsiveness
+ await asyncio.sleep(1.0)
+ response = f"¡Hola! Recibí tu mensaje: '{user_text}'. ¿Cómo te va?"
+ self.chat_display.append(f"Asistente: {response}")
\ No newline at end of file
diff --git a/app/ui/control_tab.py b/app/ui/control_tab.py
index e69de29..4016048 100644
--- a/app/ui/control_tab.py
+++ b/app/ui/control_tab.py
@@ -0,0 +1,43 @@
+
+# app/ui/control_tab.py
+from PySide6.QtWidgets import (
+ QGroupBox,
+ QHBoxLayout,
+ QLabel,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+class ControlTab(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._init_ui()
+
+ def _init_ui(self):
+ layout = QVBoxLayout(self)
+
+ # Server Control Group
+ server_group = QGroupBox("Estado del Servidor LLM Local (llama.cpp / Gemma 4)", self)
+ server_layout = QHBoxLayout(server_group)
+
+ self.status_label = QLabel("Estado: Detenido (Stopped)", self)
+ self.btn_toggle_server = QPushButton("Iniciar Servidor", self)
+
+ server_layout.addWidget(self.status_label)
+ server_layout.addWidget(self.btn_toggle_server)
+ layout.addWidget(server_group)
+
+ # Log Output Viewer
+ log_group = QGroupBox("Registros del Sistema (System Logs)", self)
+ log_layout = QVBoxLayout(log_group)
+
+ self.log_viewer = QTextEdit(self)
+ self.log_viewer.setReadOnly(True)
+ self.log_viewer.append("[SYSTEM] Entorno PySide6 inicializado correctamente.")
+ self.log_viewer.append("[SYSTEM] Esperando conexión con base de datos SQLite...")
+
+ log_layout.addWidget(self.log_viewer)
+ layout.addWidget(log_group)
diff --git a/app/ui/main_window.py b/app/ui/main_window.py
index e69de29..71e2b07 100644
--- a/app/ui/main_window.py
+++ b/app/ui/main_window.py
@@ -0,0 +1,33 @@
+# app/ui/main_window.py
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QMainWindow, QStatusBar, QTabWidget, QVBoxLayout, QWidget
+
+from app.ui.chat_tab import ChatTab
+from app.ui.control_tab import ControlTab
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+
+ self.setWindowTitle("Spanish Voice Practice Assistant")
+ self.resize(1000, 700)
+
+ # Main Layout Setup
+ central_widget = QWidget(self)
+ self.setCentralWidget(central_widget)
+ main_layout = QVBoxLayout(central_widget)
+
+ # Tab Widget Initialization
+ self.tabs = QTabWidget(self)
+ self.chat_tab = ChatTab(self)
+ self.control_tab = ControlTab(self)
+
+ self.tabs.addTab(self.chat_tab, "Conversación")
+ self.tabs.addTab(self.control_tab, "Control & Logs")
+ main_layout.addWidget(self.tabs)
+
+ # Status Bar Setup
+ self.status_bar = QStatusBar(self)
+ self.setStatusBar(self.status_bar)
+ self.status_bar.showMessage("Listo (Ready) | Connected to Local Environment")
\ No newline at end of file
diff --git a/doc/images/image-01.png b/doc/images/image-01.png
new file mode 100644
index 0000000..c213bb6
Binary files /dev/null and b/doc/images/image-01.png differ
diff --git a/doc/notes.md b/doc/notes.md
index 567b21f..dee2187 100644
--- a/doc/notes.md
+++ b/doc/notes.md
@@ -22,7 +22,13 @@
- [13.1. Errors and Fixes](#131-errors-and-fixes)
- [13.1.1. Step-by-Step Resolution Commands](#1311-step-by-step-resolution-commands)
- [13.1.2. I got the following errors uv sync](#1312-i-got-the-following-errors-uv-sync)
-- [Create basic PySide6 boilerplate for main.py, main\_window.py, and the tab modules](#create--basic-pyside6-boilerplate-for-mainpy-main_windowpy-and-the-tab-modules)
+- [14. Create basic PySide6 boilerplate for main.py, main\_window.py, and the tab modules](#14-create--basic-pyside6-boilerplate-for-mainpy-main_windowpy-and-the-tab-modules)
+ - [14.1. Entry Point: app/main.py](#141-entry-point-appmainpy)
+ - [14.2. Main Window: app/ui/main\_window.py](#142-main-window-appuimain_windowpy)
+ - [14.3. Chat Tab: app/ui/chat\_tab.py](#143-chat-tab-appuichat_tabpy)
+ - [14.4. Control Tab: app/ui/control\_tab.py](#144-control-tab-appuicontrol_tabpy)
+ - [14.5. Verification](#145-verification)
+ - [14.6. GUI First run](#146-gui-first-run)
# 1. Executive Overview
@@ -788,11 +794,222 @@ You should see All core modules imported successfully!, with zero warnings or er
git tag -a v0.1.1 -m "uv set all modules loaded "
-# Create basic PySide6 boilerplate for main.py, main_window.py, and the tab modules
+# 14. Create basic PySide6 boilerplate for main.py, main_window.py, and the tab modules
Here is a complete set of boilerplate code tailored specifically for 141_Spanish_Assistant.
It uses PySide6.QtAsyncio for non-blocking execution (allowing STT, TTS, and LLM calls to run seamlessly in the background without freezing the GUI).
-1. Entry Point: app/main.py
+## 14.1. Entry Point: app/main.py
This file initializes the PySide6 application, creates the MainWindow, and delegates execution to QtAsyncio.
+
+```zsh
+# app/main.py
+import sys
+import PySide6.QtAsyncio as QtAsyncio
+from PySide6.QtWidgets import QApplication
+
+from app.ui.main_window import MainWindow
+
+
+def main():
+ app = QApplication(sys.argv)
+ app.setApplicationName("Spanish Assistant")
+
+ window = MainWindow()
+ window.show()
+
+ # QtAsyncio handles the combined Qt and Python asyncio event loop
+ QtAsyncio.run()
+
+
+if __name__ == "__main__":
+ main()
+```
+## 14.2. Main Window: app/ui/main_window.py
+Sets up the primary container with a QTabWidget hosting Tab 1 (Conversación) and Tab 2 (Control & Database).
+
+```zsh
+# app/ui/main_window.py
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QMainWindow, QStatusBar, QTabWidget, QVBoxLayout, QWidget
+
+from app.ui.chat_tab import ChatTab
+from app.ui.control_tab import ControlTab
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+
+ self.setWindowTitle("Spanish Voice Practice Assistant")
+ self.resize(1000, 700)
+
+ # Main Layout Setup
+ central_widget = QWidget(self)
+ self.setCentralWidget(central_widget)
+ main_layout = QVBoxLayout(central_widget)
+
+ # Tab Widget Initialization
+ self.tabs = QTabWidget(self)
+ self.chat_tab = ChatTab(self)
+ self.control_tab = ControlTab(self)
+
+ self.tabs.addTab(self.chat_tab, "Conversación")
+ self.tabs.addTab(self.control_tab, "Control & Logs")
+ main_layout.addWidget(self.tabs)
+
+ # Status Bar Setup
+ self.status_bar = QStatusBar(self)
+ self.setStatusBar(self.status_bar)
+ self.status_bar.showMessage("Listo (Ready) | Connected to Local Environment")
+ ```
+ ## 14.3. Chat Tab: app/ui/chat_tab.py
+Contains the conversation log, feedback display panel, and voice/text user input triggers.
+
+```zsh
+#app/ui/chat_tab.py
+
+import asyncio
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import (
+ QFrame,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+class ChatTab(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._init_ui()
+
+ def _init_ui(self):
+ layout = QVBoxLayout(self)
+
+ # 1. Chat History Display
+ self.chat_display = QTextEdit(self)
+ self.chat_display.setReadOnly(True)
+ self.chat_display.setPlaceholderText(
+ "Su conversación aparecerá aquí... (Your conversation will appear here...)"
+ )
+ layout.addWidget(self.chat_display, stretch=4)
+
+ # 2. Feedback Panel (Pronunciation & WER Metrics)
+ self.feedback_panel = QFrame(self)
+ self.feedback_panel.setFrameShape(QFrame.Shape.StyledPanel)
+ feedback_layout = QVBoxLayout(self.feedback_panel)
+
+ self.feedback_label = QLabel("Análisis de Pronunciación:", self)
+ self.feedback_content = QLabel("Mantén presionado el botón o habla para evaluar.", self)
+
+ feedback_layout.addWidget(self.feedback_label)
+ feedback_layout.addWidget(self.feedback_content)
+ layout.addWidget(self.feedback_panel, stretch=1)
+
+ # 3. User Input Controls
+ input_layout = QHBoxLayout()
+
+ self.text_input = QLineEdit(self)
+ self.text_input.setPlaceholderText("Escribe un mensaje o habla...")
+ self.text_input.returnPressed.connect(self._on_send)
+
+ self.btn_send = QPushButton("Enviar", self)
+ self.btn_send.clicked.connect(self._on_send)
+
+ self.btn_record = QPushButton("🎤 Hablar (Hold Space)", self)
+ self.btn_record.clicked.connect(self._on_record_clicked)
+
+ input_layout.addWidget(self.text_input, stretch=3)
+ input_layout.addWidget(self.btn_send)
+ input_layout.addWidget(self.btn_record)
+
+ layout.addLayout(input_layout)
+
+ def _on_send(self):
+ text = self.text_input.text().strip()
+ if text:
+ self.chat_display.append(f"Tú: {text}")
+ self.text_input.clear()
+ # Schedule asynchronous processing via asyncio
+ asyncio.create_task(self._simulate_assistant_response(text))
+
+ def _on_record_clicked(self):
+ self.chat_display.append("[Escuchando audio local vía mlx-whisper...]")
+
+ async def _simulate_assistant_response(self, user_text: str):
+ # Async coroutine demonstrating QtAsyncio responsiveness
+ await asyncio.sleep(1.0)
+ response = f"¡Hola! Recibí tu mensaje: '{user_text}'. ¿Cómo te va?"
+ self.chat_display.append(f"Asistente: {response}")
+ ```
+
+ ## 14.4. Control Tab: app/ui/control_tab.py
+Dedicated to system logs, llama.cpp server configuration, and local SQLite data inspection.
+
+```zsh
+
+# app/ui/control_tab.py
+from PySide6.QtWidgets import (
+ QGroupBox,
+ QHBoxLayout,
+ QLabel,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+)
+
+
+class ControlTab(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self._init_ui()
+
+ def _init_ui(self):
+ layout = QVBoxLayout(self)
+
+ # Server Control Group
+ server_group = QGroupBox("Estado del Servidor LLM Local (llama.cpp / Gemma 4)", self)
+ server_layout = QHBoxLayout(server_group)
+
+ self.status_label = QLabel("Estado: Detenido (Stopped)", self)
+ self.btn_toggle_server = QPushButton("Iniciar Servidor", self)
+
+ server_layout.addWidget(self.status_label)
+ server_layout.addWidget(self.btn_toggle_server)
+ layout.addWidget(server_group)
+
+ # Log Output Viewer
+ log_group = QGroupBox("Registros del Sistema (System Logs)", self)
+ log_layout = QVBoxLayout(log_group)
+
+ self.log_viewer = QTextEdit(self)
+ self.log_viewer.setReadOnly(True)
+ self.log_viewer.append("[SYSTEM] Entorno PySide6 inicializado correctamente.")
+ self.log_viewer.append("[SYSTEM] Esperando conexión con base de datos SQLite...")
+
+ log_layout.addWidget(self.log_viewer)
+ layout.addWidget(log_group)
+```
+
+## 14.5. Verification
+Run the boilerplate using uv:
+
+```zsh
+uv run python -m app.main
+```
+This will launch a GUI window featuring tabbed navigation, text interaction, and async-ready buttons.
+
+If you'd like to get a sense of how asynchronous Qt event loops handle UI reactivity without freezing, this tutorial provides a great hands-on walkthrough.
+
+Async event loop integration with Qt
+This video demonstrates how to run an infinite or asynchronous task inside a Qt application without locking up the user interface.
+## 14.6. GUI First run
+
+
\ No newline at end of file
diff --git a/doc/notes.pdf b/doc/notes.pdf
index 120070a..ca8eb06 100644
Binary files a/doc/notes.pdf and b/doc/notes.pdf differ