Compare commits
No commits in common. "main" and "v0.1.1" have entirely different histories.
10 changed files with 0 additions and 3216 deletions
|
|
@ -1 +0,0 @@
|
|||
# 141 Spanish Assistant
|
||||
21
app/main.py
21
app/main.py
|
|
@ -1,21 +0,0 @@
|
|||
# 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()
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
#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("<b>Análisis de Pronunciación:</b>", 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"<b>Tú:</b> {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("<i>[Escuchando audio local vía mlx-whisper...]</i>")
|
||||
|
||||
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"<b>Asistente:</b> {response}")
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
|
||||
# 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: <b>Detenido (Stopped)</b>", 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)
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# 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")
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
1015
doc/notes.md
1015
doc/notes.md
File diff suppressed because it is too large
Load diff
BIN
doc/notes.pdf
BIN
doc/notes.pdf
Binary file not shown.
|
|
@ -1,45 +0,0 @@
|
|||
[project]
|
||||
name = "spanish-assistant"
|
||||
version = "0.1.0"
|
||||
description = "Local, offline Spanish speech practice assistant running on Apple Silicon"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
authors = [
|
||||
{ name = "Stephen", email = "stephen.lohning@oxnee.com" }
|
||||
]
|
||||
dependencies = [
|
||||
# GUI Framework
|
||||
"pyside6>=6.6.0",
|
||||
|
||||
# Local Speech-to-Text (Apple MLX Engine)
|
||||
"mlx-whisper>=0.2.0",
|
||||
|
||||
# Local Text-to-Speech (Kokoro 82M via ONNX Runtime)
|
||||
"kokoro-onnx>=0.5.0",
|
||||
"soundfile>=0.12.1",
|
||||
|
||||
# Audio Recording & Playback
|
||||
"sounddevice>=0.4.6",
|
||||
"numpy>=2.0.2",
|
||||
|
||||
# HTTP Client for local llama.cpp server
|
||||
"requests>=2.31.0",
|
||||
|
||||
# Pronunciation Feedback & Text Analytics
|
||||
"editdistance>=0.8.0",
|
||||
"jiwer>=3.0.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
# Configures Hatchling to recognise the app/ directory
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["app"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.0.0",
|
||||
"black>=24.0.0",
|
||||
]
|
||||
Loading…
Reference in a new issue