refactor(ui): rewrite ConfirmationApp with three-pane layout
- Implement Pending Facts, LLM Input, and Context Pane using Textual - Add keyboard shortcuts for Accept, Reject, and Edit actions
This commit is contained in:
+182
-273
@@ -3,6 +3,8 @@ from typing import List, Optional, Union
|
|||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container, Horizontal, Vertical
|
from textual.containers import Container, Horizontal, Vertical
|
||||||
|
from textual.message import Message
|
||||||
|
from textual.screen import ModalScreen
|
||||||
from textual.widgets import (
|
from textual.widgets import (
|
||||||
Button,
|
Button,
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -19,61 +21,88 @@ from src.persistence.characters import update_character_state
|
|||||||
from src.persistence.lore import update_lore
|
from src.persistence.lore import update_lore
|
||||||
|
|
||||||
|
|
||||||
|
class EditModal(ModalScreen):
|
||||||
|
def __init__(self, initial_text: str, on_save: callable):
|
||||||
|
super().__init__()
|
||||||
|
self.initial_text = initial_text
|
||||||
|
self.on_save = on_save
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Vertical(id="modal-container"):
|
||||||
|
yield Label("Edit Fact Content:")
|
||||||
|
yield Input(value=self.initial_text, id="edit-input")
|
||||||
|
with Horizontal(id="modal-actions"):
|
||||||
|
yield Button("Save", id="btn-save")
|
||||||
|
yield Button("Cancel", id="btn-cancel")
|
||||||
|
|
||||||
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
if event.button.id == "btn-save":
|
||||||
|
edit_input = self.query_one("#edit-input", Input)
|
||||||
|
self.on_save(edit_input.value)
|
||||||
|
self.dismiss()
|
||||||
|
elif event.button.id == "btn-cancel":
|
||||||
|
self.dismiss()
|
||||||
|
|
||||||
|
|
||||||
class ConfirmationApp(App):
|
class ConfirmationApp(App):
|
||||||
CSS = """
|
CSS = """
|
||||||
#main-container {
|
#main-container {
|
||||||
|
layout: vertical;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#top-pane {
|
#pending-facts-table {
|
||||||
height: 70%;
|
height: 40%;
|
||||||
|
border: solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#left-pane {
|
#llm-input-container {
|
||||||
|
height: 10%;
|
||||||
|
border: solid white;
|
||||||
|
padding: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pane {
|
||||||
|
height: 50%;
|
||||||
|
border: solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal-container {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
border: solid;
|
|
||||||
padding: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#right-pane {
|
|
||||||
width: 40%;
|
|
||||||
border: solid;
|
|
||||||
padding: 1;
|
|
||||||
layout: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
#middle-pane {
|
|
||||||
height: 30%;
|
|
||||||
border: solid;
|
|
||||||
padding: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#details-container {
|
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-bottom: 1;
|
border: double white;
|
||||||
}
|
background: #222;
|
||||||
|
padding: 2;
|
||||||
#actions-container {
|
|
||||||
height: auto;
|
|
||||||
layout: horizontal;
|
|
||||||
align: center middle;
|
align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
#edit-container {
|
#modal-actions {
|
||||||
display: none;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
layout: vertical;
|
margin-top: 1;
|
||||||
border: solid;
|
align: right;
|
||||||
padding: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
#edit-input {
|
||||||
margin: 0 1;
|
margin: 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#llm-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListItem Static {
|
||||||
|
border: solid grey;
|
||||||
|
margin: 1 0;
|
||||||
|
padding: 1;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("q", "quit", "Quit"),
|
("q", "quit", "Quit"),
|
||||||
|
("a", "accept", "Accept"),
|
||||||
|
("r", "reject", "Reject"),
|
||||||
|
("e", "edit", "Edit"),
|
||||||
|
("enter", "send", "Send"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -81,287 +110,167 @@ class ConfirmationApp(App):
|
|||||||
result: Optional[ExtractionResult] = None,
|
result: Optional[ExtractionResult] = None,
|
||||||
proposal_queue: Optional[asyncio.Queue] = None,
|
proposal_queue: Optional[asyncio.Queue] = None,
|
||||||
context_queue: Optional[asyncio.Queue] = None,
|
context_queue: Optional[asyncio.Queue] = None,
|
||||||
|
query_queue: Optional[asyncio.Queue] = None,
|
||||||
|
response_queue: Optional[asyncio.Queue] = None,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.result = result
|
self.result = result
|
||||||
self.proposal_queue = proposal_queue
|
self.proposal_queue = proposal_queue
|
||||||
self.context_queue = context_queue
|
self.context_queue = context_queue
|
||||||
|
self.query_queue = query_queue
|
||||||
|
self.response_queue = response_queue
|
||||||
self.pending_updates: List[Union[LoreUpdate, CharacterStateUpdate]] = []
|
self.pending_updates: List[Union[LoreUpdate, CharacterStateUpdate]] = []
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
# Populate pending updates from result
|
|
||||||
self.pending_updates.extend(result.lore_updates)
|
self.pending_updates.extend(result.lore_updates)
|
||||||
self.pending_updates.extend(result.character_updates)
|
self.pending_updates.extend(result.character_updates)
|
||||||
|
|
||||||
self.selected_index = -1
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Container(
|
yield Vertical(
|
||||||
|
DataTable(id="pending-facts-table"),
|
||||||
Vertical(
|
Vertical(
|
||||||
Horizontal(
|
Input(placeholder="Message LLM...", id="llm-input"),
|
||||||
Vertical(
|
id="llm-input-container",
|
||||||
DataTable(id="update-table"),
|
|
||||||
id="left-pane",
|
|
||||||
),
|
|
||||||
Vertical(
|
|
||||||
Vertical(
|
|
||||||
Label("Details:", id="details-label"),
|
|
||||||
Static("No update selected", id="details-text"),
|
|
||||||
id="details-container",
|
|
||||||
),
|
|
||||||
Vertical(
|
|
||||||
Label("Edit Value:"),
|
|
||||||
Input(id="edit-input"),
|
|
||||||
Button("Save Edit", id="save-edit"),
|
|
||||||
id="edit-container",
|
|
||||||
),
|
|
||||||
Horizontal(
|
|
||||||
Button("Accept", id="btn-accept"),
|
|
||||||
Button("Reject", id="btn-reject"),
|
|
||||||
Button("Edit", id="btn-edit"),
|
|
||||||
id="actions-container",
|
|
||||||
),
|
|
||||||
id="right-pane",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Horizontal(
|
|
||||||
ListView(id="context-pane"),
|
|
||||||
id="middle-pane",
|
|
||||||
),
|
|
||||||
id="main-container",
|
|
||||||
),
|
),
|
||||||
Footer(),
|
ListView(id="context-pane"),
|
||||||
|
id="main-container",
|
||||||
)
|
)
|
||||||
|
yield Footer()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
table = self.query_one("#update-table", DataTable)
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
table.cursor_type = "row"
|
table.cursor_type = "row"
|
||||||
table.add_columns("Type", "Target", "Update")
|
table.add_columns("Type", "Target", "Content")
|
||||||
|
|
||||||
for i, update in enumerate(self.pending_updates):
|
for i, update in enumerate(self.pending_updates):
|
||||||
if isinstance(update, LoreUpdate):
|
self.add_update_to_table(update, i)
|
||||||
table.add_row(
|
|
||||||
"Lore", update.entity_name or "General", update.content, key=str(i)
|
|
||||||
)
|
|
||||||
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(i))
|
|
||||||
|
|
||||||
if self.pending_updates:
|
|
||||||
self.handle_row_highlight(0)
|
|
||||||
self.query_one("#btn-accept", Button).focus()
|
|
||||||
|
|
||||||
if self.proposal_queue:
|
if self.proposal_queue:
|
||||||
self.run_worker(self.poll_proposal_queue, thread=False)
|
self.run_worker(self.poll_proposal_queue, thread=False)
|
||||||
|
|
||||||
if self.context_queue:
|
if self.context_queue:
|
||||||
self.run_worker(self.poll_context_queue, thread=False)
|
self.run_worker(self.poll_context_queue, thread=False)
|
||||||
|
if self.response_queue:
|
||||||
|
self.run_worker(self.poll_response_queue, thread=False)
|
||||||
|
|
||||||
async def poll_proposal_queue(self) -> None:
|
self.query_one("#llm-input", Input).focus()
|
||||||
"""
|
|
||||||
Background worker that polls the proposal queue for new extraction results.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
result = await self.proposal_queue.get()
|
|
||||||
self.add_result(result)
|
|
||||||
# Signal that the item has been processed
|
|
||||||
if hasattr(self.proposal_queue, "task_done"):
|
|
||||||
self.proposal_queue.task_done()
|
|
||||||
except Exception as e:
|
|
||||||
# Log error but keep the worker running
|
|
||||||
self.log(f"Error polling proposal queue: {e}")
|
|
||||||
|
|
||||||
async def poll_context_queue(self) -> None:
|
def add_update_to_table(
|
||||||
"""
|
self, update: Union[LoreUpdate, CharacterStateUpdate], index: int
|
||||||
Background worker that polls the context queue for new RAG updates.
|
) -> None:
|
||||||
"""
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
update = await self.context_queue.get()
|
|
||||||
context_pane = self.query_one("#context-pane", ListView)
|
|
||||||
|
|
||||||
# Format the update for display
|
|
||||||
display_text = f"Query: {update.query}\nSource: {update.source}\n\n{update.snippet}"
|
|
||||||
|
|
||||||
# Add a new ListItem widget to the top of the ListView for 'most recent'
|
|
||||||
context_pane.mount(ListItem(Label(display_text), index=0))
|
|
||||||
|
|
||||||
if hasattr(self.context_queue, "task_done"):
|
|
||||||
self.context_queue.task_done()
|
|
||||||
except Exception as e:
|
|
||||||
self.log(f"Error polling context queue: {e}")
|
|
||||||
|
|
||||||
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)
|
|
||||||
if isinstance(update, LoreUpdate):
|
if isinstance(update, LoreUpdate):
|
||||||
details_text.update(
|
table.add_row(
|
||||||
f"Category: {update.category}\nTarget: {update.entity_name}\nContent: {update.content}"
|
"Lore", update.entity_name or "General", update.content, key=str(index)
|
||||||
)
|
)
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
|
||||||
details_text.update(
|
|
||||||
f"Character: {update.character_name}\nHP Change: {update.hp_change}\nAdded Effects: {update.status_effects_added}\nRemoved Effects: {update.status_effects_removed}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reset to detail view
|
|
||||||
self.query_one("#edit-container", Vertical).styles.display = "none"
|
|
||||||
self.query_one("#details-container", Vertical).styles.display = "block"
|
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
||||||
if self.selected_index == -1:
|
|
||||||
return
|
|
||||||
|
|
||||||
update = self.pending_updates[self.selected_index]
|
|
||||||
|
|
||||||
if event.button.id == "btn-accept":
|
|
||||||
if isinstance(update, LoreUpdate):
|
|
||||||
update_lore(update)
|
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
|
||||||
update_character_state(update)
|
|
||||||
|
|
||||||
self.remove_update(self.selected_index)
|
|
||||||
|
|
||||||
elif event.button.id == "btn-reject":
|
|
||||||
self.remove_update(self.selected_index)
|
|
||||||
|
|
||||||
elif event.button.id == "btn-edit":
|
|
||||||
self.show_edit_mode(update)
|
|
||||||
|
|
||||||
elif event.button.id == "save-edit":
|
|
||||||
self.save_edit(update)
|
|
||||||
|
|
||||||
def show_edit_mode(self, update: Union[LoreUpdate, CharacterStateUpdate]) -> None:
|
|
||||||
edit_input = self.query_one("#edit-input", Input)
|
|
||||||
|
|
||||||
if isinstance(update, LoreUpdate):
|
|
||||||
edit_input.value = update.content
|
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
|
||||||
# For simplicity, only allow editing HP change in this TUI
|
|
||||||
edit_input.value = str(update.hp_change or 0)
|
|
||||||
|
|
||||||
self.query_one("#edit-container", Vertical).styles.display = "block"
|
|
||||||
self.query_one("#details-container", Vertical).styles.display = "none"
|
|
||||||
|
|
||||||
def save_edit(self, update: Union[LoreUpdate, CharacterStateUpdate]) -> None:
|
|
||||||
new_val = self.query_one("#edit-input", Input).value
|
|
||||||
|
|
||||||
if isinstance(update, LoreUpdate):
|
|
||||||
update.content = new_val
|
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
|
||||||
try:
|
|
||||||
update.hp_change = int(new_val)
|
|
||||||
except ValueError:
|
|
||||||
# Ignore invalid integer input
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Refresh the table
|
|
||||||
table = self.query_one("#update-table", DataTable)
|
|
||||||
# Textual DataTable doesn't have a simple 'update_row', so we clear and refill
|
|
||||||
# or we can use update_cell.
|
|
||||||
|
|
||||||
# Update the table row
|
|
||||||
if isinstance(update, LoreUpdate):
|
|
||||||
table.update_cell(self.selected_index, 2, update.content)
|
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
elif isinstance(update, CharacterStateUpdate):
|
||||||
change_text = f"HP: {update.hp_change or 0}"
|
change_text = f"HP: {update.hp_change or 0}"
|
||||||
if update.status_effects_added:
|
if update.status_effects_added:
|
||||||
change_text += f", Added: {', '.join(update.status_effects_added)}"
|
change_text += f", Added: {', '.join(update.status_effects_added)}"
|
||||||
if update.status_effects_removed:
|
if update.status_effects_removed:
|
||||||
change_text += f", Removed: {', '.join(update.status_effects_removed)}"
|
change_text += f", Removed: {', '.join(update.status_effects_removed)}"
|
||||||
table.update_cell(self.selected_index, 2, change_text)
|
table.add_row("Char", update.character_name, change_text, key=str(index))
|
||||||
|
|
||||||
self.show_edit_mode(update) # just to refresh the value maybe? No,’
|
async def poll_proposal_queue(self) -> None:
|
||||||
# Actually let's go back to detail view
|
while True:
|
||||||
self.query_one("#edit-container", Vertical).styles.display = "none"
|
try:
|
||||||
self.query_one("#details-container", Vertical).styles.display = "block"
|
result = await self.proposal_queue.get()
|
||||||
|
self.handle_proposal_result(result)
|
||||||
|
if hasattr(self.proposal_queue, "task_done"):
|
||||||
|
self.proposal_queue.task_done()
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error polling proposal queue: {e}")
|
||||||
|
|
||||||
# Update details text
|
def handle_proposal_result(self, result: ExtractionResult) -> None:
|
||||||
details_text = self.query_one("#details-text", Static)
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
|
for update in result.lore_updates + result.character_updates:
|
||||||
|
index = len(self.pending_updates)
|
||||||
|
self.pending_updates.append(update)
|
||||||
|
self.add_update_to_table(update, index)
|
||||||
|
|
||||||
|
async def poll_context_queue(self) -> None:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
update = await self.context_queue.get()
|
||||||
|
display_text = f"Query: {update.query}\nSource: {update.source}\n\n{update.snippet}"
|
||||||
|
context_list = self.query_one("#context-pane", ListView)
|
||||||
|
context_list.append(ListItem(Static(display_text)))
|
||||||
|
if hasattr(self.context_queue, "task_done"):
|
||||||
|
self.context_queue.task_done()
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error polling context queue: {e}")
|
||||||
|
|
||||||
|
async def poll_response_queue(self) -> None:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
answer = await self.response_queue.get()
|
||||||
|
self.notify(answer)
|
||||||
|
if hasattr(self.response_queue, "task_done"):
|
||||||
|
self.response_queue.task_done()
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Error polling response queue: {e}")
|
||||||
|
|
||||||
|
def action_send(self) -> None:
|
||||||
|
input_widget = self.query_one("#llm-input", Input)
|
||||||
|
text = input_widget.value
|
||||||
|
if text and self.query_queue:
|
||||||
|
self.query_queue.put_nowait(text)
|
||||||
|
input_widget.value = ""
|
||||||
|
|
||||||
|
def action_accept(self) -> None:
|
||||||
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
|
row_index = table.cursor_row
|
||||||
|
if row_index < 0 or row_index >= len(self.pending_updates):
|
||||||
|
return
|
||||||
|
|
||||||
|
update = self.pending_updates[row_index]
|
||||||
if isinstance(update, LoreUpdate):
|
if isinstance(update, LoreUpdate):
|
||||||
details_text.update(
|
update_lore(update)
|
||||||
f"Category: {update.category}\nTarget: {update.entity_name}\nContent: {update.content}"
|
|
||||||
)
|
|
||||||
elif isinstance(update, CharacterStateUpdate):
|
elif isinstance(update, CharacterStateUpdate):
|
||||||
details_text.update(
|
update_character_state(update)
|
||||||
f"Character: {update.character_name}\nHP Change: {update.hp_change}\nAdded Effects: {update.status_effects_added}\nRemoved Effects: {update.status_effects_removed}"
|
|
||||||
)
|
self.remove_update(row_index)
|
||||||
|
|
||||||
|
def action_reject(self) -> None:
|
||||||
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
|
row_index = table.cursor_row
|
||||||
|
if row_index < 0 or row_index >= len(self.pending_updates):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.remove_update(row_index)
|
||||||
|
|
||||||
|
def action_edit(self) -> None:
|
||||||
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
|
row_index = table.cursor_row
|
||||||
|
if row_index < 0 or row_index >= len(self.pending_updates):
|
||||||
|
return
|
||||||
|
|
||||||
|
update = self.pending_updates[row_index]
|
||||||
|
initial_text = ""
|
||||||
|
if isinstance(update, LoreUpdate):
|
||||||
|
initial_text = update.content
|
||||||
|
elif isinstance(update, CharacterStateUpdate):
|
||||||
|
initial_text = str(update.hp_change or 0)
|
||||||
|
|
||||||
|
def save_callback(new_text: str):
|
||||||
|
if isinstance(update, LoreUpdate):
|
||||||
|
update.content = new_text
|
||||||
|
elif isinstance(update, CharacterStateUpdate):
|
||||||
|
try:
|
||||||
|
update.hp_change = int(new_text)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update the table
|
||||||
|
self.refresh_table()
|
||||||
|
|
||||||
|
self.push_screen(EditModal(initial_text, save_callback))
|
||||||
|
|
||||||
def remove_update(self, index: int) -> None:
|
def remove_update(self, index: int) -> None:
|
||||||
# Remove from the pending list
|
|
||||||
del self.pending_updates[index]
|
del self.pending_updates[index]
|
||||||
|
self.refresh_table()
|
||||||
|
|
||||||
# Clear and refill the table
|
def refresh_table(self) -> None:
|
||||||
table = self.query_one("#update-table", DataTable)
|
table = self.query_one("#pending-facts-table", DataTable)
|
||||||
table.clear()
|
table.clear()
|
||||||
|
|
||||||
for i, update in enumerate(self.pending_updates):
|
for i, update in enumerate(self.pending_updates):
|
||||||
if isinstance(update, LoreUpdate):
|
self.add_update_to_table(update, i)
|
||||||
table.add_row(
|
|
||||||
"Lore", update.entity_name or "General", update.content, key=str(i)
|
|
||||||
)
|
|
||||||
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(i))
|
|
||||||
|
|
||||||
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