Add LLM input logging and UI log pane

- Add log_queue to PipelineOrchestrator and log LLM inputs to UI
- Use entity_name for lore update logs instead of topic
- Pass log_queue into ConfirmationApp to display logs in UI
- Introduce a log pane and left/right pane layout in the UI
- Poll and render log messages via a new poll_log_updates worker
- Run log polling with Textual workers to avoid GC issues
- Fix ListView insertion by wrapping ListItem in a list
- Relax RAG similarity threshold from 0.7 to 0.5
This commit is contained in:
2026-05-27 23:09:11 -07:00
parent 1098bdb2f9
commit 1cfba3a0ae
3 changed files with 74 additions and 10 deletions
+6 -1
View File
@@ -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()
+1 -1
View File
@@ -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)
+62 -3
View File
@@ -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(
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",
),
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: