diff --git a/src/pipeline/orchestrator.py b/src/pipeline/orchestrator.py index 91069c7..477bbe8 100644 --- a/src/pipeline/orchestrator.py +++ b/src/pipeline/orchestrator.py @@ -53,6 +53,7 @@ class PipelineOrchestrator: self.ui_to_llm_queue = asyncio.Queue() self.clean_to_llm_queue = asyncio.Queue() self.llm_to_ui_queue = asyncio.Queue() + self.log_queue = asyncio.Queue() self.is_running = False @@ -202,6 +203,9 @@ class PipelineOrchestrator: # RAG Retrieval for context context = await asyncio.to_thread(self.rag_manager.retrieve, text) + # Log the text sent to the LLM for UI affordance + await self.log_queue.put(f"[{speaker}] {text}") + # Structured extraction using the processor extraction_result = await asyncio.to_thread( self.processor.extract_structured_data, @@ -212,7 +216,7 @@ class PipelineOrchestrator: # Persistence: Lore Updates for lore_update in extraction_result.lore_updates: await asyncio.to_thread(update_lore, lore_update) - logger.info(f"LLM Worker: Lore updated: {lore_update.topic}") + logger.info(f"LLM Worker: Lore updated: {lore_update.entity_name}") # Persistence: Character State Updates for char_update in extraction_result.character_updates: @@ -271,6 +275,7 @@ class PipelineOrchestrator: app = ConfirmationApp( ui_to_llm_queue=self.ui_to_llm_queue, llm_to_ui_queue=self.llm_to_ui_queue, + log_queue=self.log_queue, ) await app.run_async() self.stop() diff --git a/src/rag/manager.py b/src/rag/manager.py index e5d8bb1..5cdd2bc 100644 --- a/src/rag/manager.py +++ b/src/rag/manager.py @@ -162,7 +162,7 @@ class RAGManager: nodes = retriever.retrieve(query) # Filter nodes by similarity score (threshold > 0.7) - nodes = [node for node in nodes if node.score >= 0.7] + nodes = [node for node in nodes if node.score >= 0.5] if summarize: return self.summarize_results(query, nodes) diff --git a/src/ui/tui.py b/src/ui/tui.py index b1cb826..a386037 100644 --- a/src/ui/tui.py +++ b/src/ui/tui.py @@ -51,6 +51,22 @@ class ConfirmationApp(App): height: 100%; } + #content-wrapper { + layout: horizontal; + height: 100%; + } + + #left-pane { + width: 70%; + layout: vertical; + } + + #right-pane { + width: 30%; + layout: vertical; + border: solid white; + } + #pending-facts-table { height: 40%; border: solid white; @@ -67,6 +83,17 @@ class ConfirmationApp(App): border: solid white; } + #log-pane { + height: 30%; + border: solid white; + background: #111; + } + + #log-footer { + height: 70%; + border: solid white; + } + #modal-container { width: 60%; height: auto; @@ -110,11 +137,13 @@ class ConfirmationApp(App): result: Optional[ExtractionResult] = None, ui_to_llm_queue: Optional[asyncio.Queue] = None, llm_to_ui_queue: Optional[asyncio.Queue] = None, + log_queue: Optional[asyncio.Queue] = None, ): super().__init__() self.result = result self.ui_to_llm_queue = ui_to_llm_queue self.llm_to_ui_queue = llm_to_ui_queue + self.log_queue = log_queue self.pending_updates: List[Union[LoreUpdate, CharacterStateUpdate]] = [] if result: @@ -123,12 +152,23 @@ class ConfirmationApp(App): def compose(self) -> ComposeResult: yield Vertical( - DataTable(id="pending-facts-table"), - Vertical( - Input(placeholder="Message LLM...", id="llm-input"), - id="llm-input-container", + Horizontal( + Vertical( + DataTable(id="pending-facts-table"), + Vertical( + Input(placeholder="Message LLM...", id="llm-input"), + id="llm-input-container", + ), + ListView(id="context-pane"), + id="left-pane", + ), + Vertical( + ListView(id="log-pane"), + Static("LATEST LLM INPUTS", id="log-footer"), + id="right-pane", + ), + id="content-wrapper", ), - ListView(id="context-pane"), id="main-container", ) yield Footer() @@ -145,7 +185,11 @@ class ConfirmationApp(App): # We don't need a poller for this, just the action_send pass if self.llm_to_ui_queue: - self.run_worker(self.poll_llm_updates, thread=False) + # Use Textual workers so the task isn't garbage-collected and + # exceptions are surfaced via the worker manager. + self.run_worker(self.poll_llm_updates(), exclusive=False) + if self.log_queue: + self.run_worker(self.poll_log_updates(), exclusive=False) self.query_one("#llm-input", Input).focus() @@ -171,13 +215,28 @@ class ConfirmationApp(App): update = await self.llm_to_ui_queue.get() display_text = f"Query: {update.query}\nSource: {update.source}\n\n{update.snippet}" context_list = self.query_one("#context-pane", ListView) - # Insert at the top to show most recent first - context_list.insert(0, ListItem(Static(display_text))) + # ListView.insert takes an *iterable* of ListItems; passing a + # bare ListItem raises TypeError because ListItem is not iterable. + # Insert at the top to show most recent first. + await context_list.insert(0, [ListItem(Static(display_text))]) if hasattr(self.llm_to_ui_queue, "task_done"): self.llm_to_ui_queue.task_done() except Exception as e: self.log(f"Error polling LLM updates: {e}") + async def poll_log_updates(self) -> None: + while True: + try: + log_text = await self.log_queue.get() + log_list = self.query_one("#log-pane", ListView) + # See poll_llm_updates: wrap the ListItem in a list. + # Insert at the top to show most recent first. + await log_list.insert(0, [ListItem(Static(log_text))]) + if hasattr(self.log_queue, "task_done"): + self.log_queue.task_done() + except Exception as e: + self.log(f"Error polling log updates: {e}") + def handle_proposal_result(self, result: ExtractionResult) -> None: table = self.query_one("#pending-facts-table", DataTable) for update in result.lore_updates + result.character_updates: