Skip to content

Commit e550001

Browse files
authored
Merge pull request #62 from rmusser01/dev
fixes for buttons, and chat editing/deleting messages
2 parents 5d411bc + 4f3e6f0 commit e550001

File tree

4 files changed

+49
-43
lines changed

4 files changed

+49
-43
lines changed

tldw_chatbook/Event_Handlers/Chat_Events/chat_events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ async def handle_chat_action_button_pressed(app: 'TldwCli', button: Button, acti
458458
# to render it as is, without trying to parse for markup.
459459
static_text_widget.update(Text(new_text))
460460
# --- DO NOT REMOVE ---
461-
static_text_widget.update(escape_markup(new_text)) # Update display with escaped text
461+
#static_text_widget.update(escape_markup(new_text)) # Update display with escaped text
462462

463463
static_text_widget.display = True
464464
action_widget._editing = False
@@ -1054,7 +1054,7 @@ async def handle_chat_load_selected_button_pressed(app: 'TldwCli', event: Button
10541054
app.notify("Unexpected error loading chat.", severity="error")
10551055

10561056

1057-
async def perform_chat_conversation_search(app: 'TldwCli', event: Button.Pressed) -> None:
1057+
async def perform_chat_conversation_search(app: 'TldwCli') -> None:
10581058
loguru_logger.debug("Performing chat conversation search...")
10591059
try:
10601060
search_bar = app.query_one("#chat-conversation-search-bar", Input)

tldw_chatbook/Event_Handlers/notes_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ async def _note_import_callback(app: 'TldwCli', selected_path: Optional[Path]) -
288288
app.notify("Note import cancelled.", severity="information", timeout=2)
289289

290290

291-
async def handle_notes_import_button_pressed(app: 'TldwCli') -> None:
291+
async def handle_notes_import_button_pressed(app: 'TldwCli', event: Button.Pressed) -> None:
292292
logger_instance = getattr(app, 'loguru_logger', logger)
293293
logger_instance.info("Notes 'Import Note' button pressed.")
294294

tldw_chatbook/Widgets/chat_message.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from textual.app import ComposeResult
1111
from textual.containers import Horizontal, Vertical
1212
from textual.css.query import QueryError
13+
from textual.message import Message
1314
from textual.widget import Widget
1415
from textual.widgets import Static, Button, Label # Added Label
1516
from textual.reactive import reactive
@@ -23,6 +24,13 @@
2324
class ChatMessage(Widget):
2425
"""A widget to display a single chat message with action buttons."""
2526

27+
class Action(Message):
28+
"""Posted when a button on the message is pressed."""
29+
def __init__(self, message_widget: "ChatMessage", button: Button) -> None:
30+
super().__init__()
31+
self.message_widget = message_widget
32+
self.button = button
33+
2634
DEFAULT_CSS = """
2735
ChatMessage {
2836
width: 100%;
@@ -168,17 +176,21 @@ def watch__generation_complete_internal(self, complete: bool) -> None:
168176
if self.has_class("-ai"):
169177
try:
170178
actions_container = self.query_one(".message-actions")
171-
continue_button = self.query_one("#continue-response-button", Button)
172-
173179
if complete:
174-
actions_container.remove_class("-generating") # Makes the bar visible via CSS
175-
actions_container.styles.display = "block" # Ensures bar is visible
176-
continue_button.display = True # Makes continue button visible
180+
actions_container.remove_class("-generating")
181+
actions_container.styles.display = "block"
177182
else:
178-
# This state typically occurs during initialization if generation_complete=False
179-
actions_container.add_class("-generating") # Hides the bar via CSS
180-
# actions_container.styles.display = "none" # CSS rule should handle this
181-
continue_button.display = False # Hides continue button
183+
actions_container.add_class("-generating")
184+
185+
# Separately handle the continue button in its own try...except block
186+
# This prevents an error here from stopping the whole function.
187+
try:
188+
continue_button = self.query_one("#continue-response-button", Button)
189+
continue_button.display = complete
190+
except QueryError:
191+
# It's okay if the continue button doesn't exist, as it's commented out.
192+
logging.debug("Continue button not found in ChatMessage, skipping visibility toggle.")
193+
182194
except QueryError as qe:
183195
# This might happen if the query runs before the widget is fully composed or if it's being removed.
184196
logging.debug(f"ChatMessage (ID: {self.id}, Role: {self.role}): QueryError in watch__generation_complete_internal: {qe}. Widget might not be fully ready or is not an AI message with these components.")
@@ -191,6 +203,13 @@ def watch__generation_complete_internal(self, complete: bool) -> None:
191203
except QueryError:
192204
pass # Expected for non-AI messages as the button isn't composed.
193205

206+
def on_button_pressed(self, event: Button.Pressed) -> None:
207+
"""Called when a button inside this message is pressed."""
208+
# Post our custom Action message so the app can handle it.
209+
# The message carries the button and this widget instance.
210+
self.post_message(self.Action(self, event.button))
211+
# Stop the event from bubbling up to the app's on_button_pressed.
212+
event.stop()
194213

195214
def mark_generation_complete(self):
196215
"""

tldw_chatbook/app.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from textual.widgets import (
2020
Static, Button, Input, Header, RichLog, TextArea, Select, ListView, Checkbox, Collapsible, ListItem, Label
2121
)
22+
2223
from textual.containers import Container
2324
from textual.reactive import reactive
2425
from textual.worker import Worker, WorkerState
@@ -645,6 +646,21 @@ def compose_content_area(self) -> ComposeResult:
645646
yield EvalsWindow(self, id="evals-window", classes="window")
646647
yield CodingWindow(self, id="coding-window", classes="window")
647648

649+
@on(ChatMessage.Action)
650+
async def handle_chat_message_action(self, event: ChatMessage.Action) -> None:
651+
"""Handles actions (edit, copy, etc.) from within a ChatMessage widget."""
652+
button_classes = " ".join(event.button.classes) # Get class string for logging
653+
self.loguru_logger.debug(
654+
f"ChatMessage.Action received for button "
655+
f"(Classes: {button_classes}, Label: '{event.button.label}') "
656+
f"on message role: {event.message_widget.role}"
657+
)
658+
# The event directly gives us the context we need.
659+
# Now we call the existing handler function with the correct arguments.
660+
await chat_events.handle_chat_action_button_pressed(
661+
self, event.button, event.message_widget
662+
)
663+
648664
# --- Watcher for CCP Active View ---
649665
def watch_ccp_active_view(self, old_view: Optional[str], new_view: str) -> None:
650666
loguru_logger.debug(f"CCP active view changing from '{old_view}' to: '{new_view}'")
@@ -1990,17 +2006,7 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
19902006
await tab_events.handle_tab_button_pressed(self, event)
19912007
return
19922008

1993-
# 2. Handle dynamically generated ChatMessage action buttons
1994-
# This needs to check the current tab, as ChatMessages can appear in Chat and CCP tabs.
1995-
if self.current_tab in [TAB_CHAT, TAB_CCP]:
1996-
action_widget = self._get_chat_message_widget_from_button(event.button)
1997-
if action_widget:
1998-
self.loguru_logger.debug(
1999-
f"Button '{button_id}' is inside a ChatMessage, dispatching to action handler.")
2000-
await chat_events.handle_chat_action_button_pressed(self, event.button, action_widget)
2001-
return
2002-
2003-
# 3. Use the handler map for all other tab-specific buttons
2009+
# 2. Use the handler map for all other tab-specific buttons
20042010
current_tab_handlers = self.button_handler_map.get(self.current_tab, {})
20052011
handler = current_tab_handlers.get(button_id)
20062012

@@ -2021,28 +2027,9 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
20212027
self.loguru_logger.error(f"Handler for button '{button_id}' is not callable: {handler}")
20222028
return # The button press was handled (or an error occurred).
20232029

2024-
# 4. Fallback for unmapped buttons
2030+
# 3. Fallback for unmapped buttons
20252031
self.loguru_logger.warning(f"Unhandled button press for ID '{button_id}' on tab '{self.current_tab}'.")
20262032

2027-
def _get_chat_message_widget_from_button(self, button: Button) -> Optional[ChatMessage]:
2028-
"""Helper to find the parent ChatMessage widget from an action button within it."""
2029-
self.loguru_logger.debug(f"_get_chat_message_widget_from_button searching for parent of button ID: {button.id}, Classes: {button.classes}")
2030-
node: Optional[DOMNode] = button.parent
2031-
depth = 0
2032-
max_depth = 5 # Safety break
2033-
while node is not None and depth < max_depth:
2034-
self.loguru_logger.debug(f" Traversal depth {depth}: current node is {type(node)}, id: {getattr(node, 'id', 'N/A')}, classes: {getattr(node, 'classes', '')}")
2035-
if isinstance(node, ChatMessage):
2036-
self.loguru_logger.debug(f" Found ChatMessage parent!")
2037-
return node
2038-
node = node.parent
2039-
depth += 1
2040-
if depth >= max_depth:
2041-
self.loguru_logger.warning(f" _get_chat_message_widget_from_button reached max depth for button: {button.id}")
2042-
else:
2043-
self.loguru_logger.warning(f" _get_chat_message_widget_from_button could not find parent ChatMessage for button: {button.id}")
2044-
return None
2045-
20462033
async def on_text_area_changed(self, event: TextArea.Changed) -> None:
20472034
"""Handles text area changes, e.g., for live updates to character data."""
20482035
control_id = event.control.id

0 commit comments

Comments
 (0)