feat: implement core D&D helpers logic and system architecture

This commit is contained in:
2026-05-25 22:14:58 -07:00
parent 5bb483431f
commit 685586318f
36 changed files with 1137 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
import asyncio
import logging
from src.llm.models import ExtractionResult
from src.llm.processor import LLMProcessor
from src.stt.listener import AudioListener
from src.stt.transcriber import Transcriber
from src.ui.tui import ConfirmationApp
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PipelineOrchestrator:
def __init__(self, loop: asyncio.AbstractEventLoop):
self.loop = loop
# Modules
self.listener = AudioListener(loop=self.loop)
self.transcriber = Transcriber()
self.processor = LLMProcessor()
# Queues
self.transcript_queue = asyncio.Queue()
self.proposal_queue = asyncio.Queue()
self.is_running = False
async def stt_worker(self):
"""
Worker that handles STT: Audio -> Text.
"""
logger.info("STT Worker started.")
while self.is_running:
try:
# Get audio chunk from listener
audio_chunk = await self.listener.get_chunk()
# Transcribe
text = self.transcriber.transcribe(audio_chunk)
if text:
logger.info(f"Transcribed: {text}")
await self.transcript_queue.put(text)
except Exception as e:
logger.error(f"STT Worker error: {e}")
# Small sleep to prevent tight loop if get_chunk is fast
await asyncio.sleep(0.1)
async def llm_worker(self):
"""
Worker that handles LLM: Text -> Proposal.
"""
logger.info("LLM Worker started.")
while self.is_running:
try:
# Get raw text from transcript queue
raw_text = await self.transcript_queue.get()
logger.info(f"Processing text: {raw_text}")
# Process via LLM (Filter -> Extract)
result = self.processor.process_pipeline(raw_text)
if (
result.lore_updates
or result.character_updates
or result.significant_events
):
logger.info("Proposal generated. Putting into proposal queue.")
await self.proposal_queue.put(result)
else:
logger.info("No relevant game data extracted.")
except Exception as e:
logger.error(f"LLM Worker error: {e}")
# Small sleep
await asyncio.sleep(0.1)
async def tui_worker(self):
"""
Worker that handles TUI: Proposal -> Persistence.
"""
logger.info("TUI Worker started.")
while self.is_running:
try:
# Get proposal from queue
result = await self.proposal_queue.get()
logger.info("Proposal received. Launching TUI for confirmation.")
# Launch TUI (Note: Textual's run() is blocking)
# We need to run the TUI in a way that doesn't block the overall event loop
# or we accept that the system pauses for confirmation.
# Given the requirement for "Non-blocking", but TUI is a focus-modal,
# we launch it.
# To integrate Textual with asyncio, we can use its async support.
# However, ConfirmationApp is designed as a standard Textual app.
# Since we want to bridge the asyncio loop, we'll run the TUI.
# Note: In a real high-performance pipeline, we'd use an async TUI
# that updates widgets in real-time. For now, we follow the
# a confirmation screen pattern.
# we will use the run() method, but since we are in an async loop,
# we might need to wrap it or use an async variant.
# For this integration, we'll use the run() method as defined
# in ConfirmationApp, which will take over the terminal.
ConfirmationApp(result).run()
except Exception as e:
logger.error(f"TUI Worker error: {e}")
# Small sleep
await asyncio.sleep(0.1)
async def run(self):
"""
Starts the pipeline workers and the audio listener.
"""
self.is_running = True
self.listener.start()
# Start workers as background tasks
tasks = [
asyncio.create_task(self.stt_worker()),
asyncio.create_task(self.llm_worker()),
asyncio.create_task(self.tui_worker()),
]
try:
# Keep the loop running
while self.is_running:
await asyncio.sleep(1)
except asyncio.CancelledError:
pass
finally:
self.is_running = False
self.listener.stop()
for task in tasks:
task.cancel()
# Wait for tasks to complete
await asyncio.gather(*tasks, return_exceptions=True)
def stop(self):
"""
Stops the pipeline.
"""
self.is_running = False