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
+67 -8
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(
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: