Improve audio capture and LLM integration
- Implement Silero VAD for dynamic audio chunking - Add support for Ollama and vLLM backends - Harden extraction prompts for strict JSON output - Refactor TUI worker to handle proposals asynchronously
This commit is contained in:
+89
-13
@@ -1,4 +1,5 @@
|
||||
from typing import List, Union
|
||||
import asyncio
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container, Horizontal, Vertical
|
||||
@@ -17,13 +18,13 @@ class ConfirmationApp(App):
|
||||
|
||||
#left-pane {
|
||||
width: 40%;
|
||||
border: solid 1;
|
||||
border: solid;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#right-pane {
|
||||
width: 60%;
|
||||
border: solid 1;
|
||||
border: solid;
|
||||
padding: 1;
|
||||
layout: vertical;
|
||||
}
|
||||
@@ -43,7 +44,7 @@ class ConfirmationApp(App):
|
||||
display: none;
|
||||
height: auto;
|
||||
layout: vertical;
|
||||
border: solid 1;
|
||||
border: solid;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
@@ -56,14 +57,20 @@ class ConfirmationApp(App):
|
||||
("q", "quit", "Quit"),
|
||||
]
|
||||
|
||||
def __init__(self, result: ExtractionResult):
|
||||
def __init__(
|
||||
self,
|
||||
result: Optional[ExtractionResult] = None,
|
||||
proposal_queue: Optional[asyncio.Queue] = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.result = result
|
||||
self.proposal_queue = proposal_queue
|
||||
self.pending_updates: List[Union[LoreUpdate, CharacterStateUpdate]] = []
|
||||
|
||||
# Populate pending updates from result
|
||||
self.pending_updates.extend(result.lore_updates)
|
||||
self.pending_updates.extend(result.character_updates)
|
||||
if result:
|
||||
# Populate pending updates from result
|
||||
self.pending_updates.extend(result.lore_updates)
|
||||
self.pending_updates.extend(result.character_updates)
|
||||
|
||||
self.selected_index = -1
|
||||
|
||||
@@ -100,6 +107,7 @@ class ConfirmationApp(App):
|
||||
|
||||
def on_mount(self) -> None:
|
||||
table = self.query_one("#update-table", DataTable)
|
||||
table.cursor_type = "row"
|
||||
table.add_columns("Type", "Target", "Update")
|
||||
|
||||
for i, update in enumerate(self.pending_updates):
|
||||
@@ -117,8 +125,72 @@ class ConfirmationApp(App):
|
||||
)
|
||||
table.add_row("Char", update.character_name, change_text, key=str(i))
|
||||
|
||||
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
||||
self.selected_index = event.row
|
||||
if self.pending_updates:
|
||||
self.handle_row_highlight(0)
|
||||
self.query_one("#btn-accept", Button).focus()
|
||||
|
||||
if self.proposal_queue:
|
||||
self.run_worker(self.poll_proposal_queue, thread=False)
|
||||
|
||||
async def poll_proposal_queue(self) -> None:
|
||||
"""
|
||||
Background worker that polls the proposal queue for new extraction results.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
result = await self.proposal_queue.get()
|
||||
self.add_result(result)
|
||||
except Exception as e:
|
||||
# Log error but keep the worker running
|
||||
self.log(f"Error polling proposal queue: {e}")
|
||||
finally:
|
||||
# Signal that the item has been processed
|
||||
if hasattr(self.proposal_queue, "task_done"):
|
||||
self.proposal_queue.task_done()
|
||||
|
||||
def add_result(self, result: ExtractionResult) -> None:
|
||||
"""
|
||||
Adds results from the LLM processor to the TUI table.
|
||||
"""
|
||||
table = self.query_one("#update-table", DataTable)
|
||||
start_index = len(self.pending_updates)
|
||||
|
||||
for update in result.lore_updates + result.character_updates:
|
||||
self.pending_updates.append(update)
|
||||
actual_index = len(self.pending_updates) - 1
|
||||
|
||||
if isinstance(update, LoreUpdate):
|
||||
table.add_row(
|
||||
"Lore",
|
||||
update.entity_name or "General",
|
||||
update.content,
|
||||
key=str(actual_index),
|
||||
)
|
||||
elif isinstance(update, CharacterStateUpdate):
|
||||
change_text = f"HP: {update.hp_change or 0}"
|
||||
if update.status_effects_added:
|
||||
change_text += f", Added: {', '.join(update.status_effects_added)}"
|
||||
if update.status_effects_removed:
|
||||
change_text += (
|
||||
f", Removed: {', '.join(update.status_effects_removed)}"
|
||||
)
|
||||
table.add_row(
|
||||
"Char", update.character_name, change_text, key=str(actual_index)
|
||||
)
|
||||
|
||||
# If the table was previously empty and we added updates, focus the first one.
|
||||
if start_index == 0 and self.pending_updates:
|
||||
self.handle_row_highlight(0)
|
||||
self.query_one("#btn-accept", Button).focus()
|
||||
|
||||
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
||||
self.handle_row_highlight(event.cursor_row)
|
||||
|
||||
def handle_row_highlight(self, row: int) -> None:
|
||||
self.selected_index = row
|
||||
if self.selected_index < 0 or self.selected_index >= len(self.pending_updates):
|
||||
return
|
||||
|
||||
update = self.pending_updates[self.selected_index]
|
||||
|
||||
details_text = self.query_one("#details-text", Static)
|
||||
@@ -135,7 +207,7 @@ class ConfirmationApp(App):
|
||||
self.query_one("#edit-container", Vertical).styles.display = "none"
|
||||
self.query_one("#details-container", Vertical).styles.display = "block"
|
||||
|
||||
def on_button_clicked(self, event: Button.Clicked) -> None:
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
if self.selected_index == -1:
|
||||
return
|
||||
|
||||
@@ -237,5 +309,9 @@ class ConfirmationApp(App):
|
||||
)
|
||||
table.add_row("Char", update.character_name, change_text, key=str(i))
|
||||
|
||||
self.selected_index = -1
|
||||
self.query_one("#details-text", Static).update("No update selected")
|
||||
if self.pending_updates:
|
||||
self.handle_row_highlight(0)
|
||||
self.query_one("#btn-accept", Button).focus()
|
||||
else:
|
||||
self.selected_index = -1
|
||||
self.query_one("#details-text", Static).update("All updates processed.")
|
||||
|
||||
Reference in New Issue
Block a user