|
2 | 2 | #
|
3 | 3 | # Imports
|
4 | 4 | #
|
5 |
| -# # 3rd-party Libraries |
| 5 | +# Standard Library Imports |
| 6 | +from typing import TYPE_CHECKING, Dict, Any, Optional |
| 7 | +import logging |
| 8 | + |
| 9 | +# 3rd-party Libraries |
| 10 | +from textual.widgets import ListItem, Input, ListView, TextArea, Button, Label |
| 11 | +from textual.css.query import QueryError |
| 12 | +from textual.app import App |
6 | 13 | #
|
7 | 14 | # Local Imports
|
| 15 | +from tldw_chatbook.DB.Client_Media_DB_v2 import MediaDatabase |
| 16 | +if TYPE_CHECKING: |
| 17 | + from tldw_chatbook.app import TldwCli # Assuming your app class is TldwCli |
8 | 18 | #
|
9 | 19 | ###########################################################################################################################
|
10 | 20 | #
|
11 |
| -## Functions: |
12 |
| - |
13 |
| - |
14 |
| - |
15 |
| - |
16 |
| - |
17 |
| - |
18 |
| - |
19 |
| - |
20 |
| - |
21 |
| - |
22 |
| - |
23 |
| - |
24 |
| - |
| 21 | +# Globals |
| 22 | +# |
| 23 | +logger = logging.getLogger(__name__) # Standard practice for logging |
| 24 | +# |
| 25 | +########################################################################################################################### |
| 26 | +# |
| 27 | +# Functions: |
| 28 | +# |
25 | 29 |
|
| 30 | +def _disable_media_copy_buttons(app: 'TldwCli'): |
| 31 | + """Helper to disable all media copy buttons and clear current sidebar media item.""" |
| 32 | + app.current_sidebar_media_item = None |
| 33 | + try: |
| 34 | + app.query_one("#chat-media-copy-title-button", Button).disabled = True |
| 35 | + app.query_one("#chat-media-copy-content-button", Button).disabled = True |
| 36 | + app.query_one("#chat-media-copy-author-button", Button).disabled = True |
| 37 | + app.query_one("#chat-media-copy-url-button", Button).disabled = True |
| 38 | + except QueryError as e: |
| 39 | + logger.warning(f"Could not find a media copy button to disable: {e}") |
| 40 | + |
| 41 | + |
| 42 | +async def perform_media_sidebar_search(app: 'TldwCli', search_term: str): |
| 43 | + """ |
| 44 | + Performs a search for media items and populates the results in the sidebar. |
| 45 | + This is now an async function. |
| 46 | + """ |
| 47 | + logger.debug(f"Performing media sidebar search for term: '{search_term}'") |
| 48 | + try: |
| 49 | + results_list_view = app.query_one("#chat-media-search-results-listview", ListView) |
| 50 | + # FIX: Query for the correct ID for the content display area. |
| 51 | + review_display = app.query_one("#chat-media-content-display", TextArea) |
| 52 | + except QueryError as e: |
| 53 | + logger.error(f"Error querying media search UI elements: {e}") |
| 54 | + app.notify(f"Error accessing media search UI: {e}", severity="error") |
| 55 | + return |
| 56 | + |
| 57 | + await results_list_view.clear() |
| 58 | + review_display.clear() |
| 59 | + _disable_media_copy_buttons(app) |
| 60 | + |
| 61 | + if not search_term: |
| 62 | + logger.debug("Search term is empty, clearing results.") |
| 63 | + return |
| 64 | + |
| 65 | + try: |
| 66 | + if not app.media_db: |
| 67 | + logger.error("app.media_db is not available.") |
| 68 | + app.notify("Media database service not initialized.", severity="error") |
| 69 | + return |
| 70 | + |
| 71 | + db_instance = app.media_db |
| 72 | + |
| 73 | + search_fields = ['title', 'content', 'author', 'keywords', 'notes'] |
| 74 | + media_types_filter = None |
| 75 | + |
| 76 | + logger.debug(f"Searching media DB with term: '{search_term}', fields: {search_fields}, types: {media_types_filter}") |
| 77 | + |
| 78 | + media_items = db_instance.search_media_db( |
| 79 | + search_query=search_term, |
| 80 | + search_fields=search_fields, |
| 81 | + media_types=media_types_filter, |
| 82 | + date_range=None, # No date range filtering |
| 83 | + must_have_keywords=None, |
| 84 | + must_not_have_keywords=None, |
| 85 | + sort_by="last_modified_desc", # Default sort order |
| 86 | + media_ids_filter=None, # No specific media IDs to filter |
| 87 | + page=1, # Default to first page |
| 88 | + results_per_page=100, # Limit results to 100 for performance |
| 89 | + include_trash=False, |
| 90 | + include_deleted=False, |
| 91 | + ) |
| 92 | + logger.debug(f"Found {len(media_items)} media items.") |
| 93 | + |
| 94 | + if not media_items: |
| 95 | + # FIX: Await the async append method. |
| 96 | + await results_list_view.append(ListItem(Label("No media found."))) |
| 97 | + else: |
| 98 | + for item_dict in media_items: |
| 99 | + if isinstance(item_dict, dict): |
| 100 | + title = item_dict.get('title', 'Untitled') |
| 101 | + media_id = item_dict.get('media_id', 'Unknown ID') |
| 102 | + display_label = f"{title} (ID: {media_id[:8]}...)" |
| 103 | + list_item = ListItem(Label(display_label)) |
| 104 | + setattr(list_item, 'media_data', item_dict) |
| 105 | + await results_list_view.append(list_item) |
| 106 | + else: |
| 107 | + logger.warning(f"Skipping non-dictionary item from DB search results: {item_dict}") |
| 108 | + |
| 109 | + except Exception as e: |
| 110 | + logger.error(f"Exception during media search: {e}", exc_info=True) |
| 111 | + app.notify(f"Error during media search: {e}", severity="error") |
| 112 | + await results_list_view.clear() |
| 113 | + # FIX: Await the async append method. |
| 114 | + await results_list_view.append(ListItem(Label(f"Search error."))) |
| 115 | + |
| 116 | + |
| 117 | +async def handle_chat_media_search_input_changed(app: 'TldwCli', input_widget: Input): |
| 118 | + """ |
| 119 | + Handles changes in the media search input, debouncing the search. |
| 120 | + """ |
| 121 | + search_term = input_widget.value.strip() |
| 122 | + logger.debug(f"Media search input changed. Current value: '{search_term}'") |
| 123 | + |
| 124 | + if hasattr(app, '_media_sidebar_search_timer') and app._media_sidebar_search_timer: |
| 125 | + app._media_sidebar_search_timer.stop() |
| 126 | + logger.debug("Stopped existing media search timer.") |
| 127 | + |
| 128 | + app._media_sidebar_search_timer = app.set_timer( |
| 129 | + 0.5, |
| 130 | + lambda: app.run_worker(perform_media_sidebar_search(app, search_term), exclusive=True) |
| 131 | + ) |
| 132 | + logger.debug(f"Set new media search timer for term: '{search_term}'") |
| 133 | + |
| 134 | + |
| 135 | +async def handle_chat_media_load_selected_button_pressed(app: 'TldwCli'): |
| 136 | + """ |
| 137 | + Loads the selected media item's details into the review display. |
| 138 | + """ |
| 139 | + logger.debug("Load Selected Media button pressed.") |
| 140 | + try: |
| 141 | + results_list_view = app.query_one("#chat-media-search-results-listview", ListView) |
| 142 | + # FIX: Query for the correct ID. |
| 143 | + review_display = app.query_one("#chat-media-content-display", TextArea) |
| 144 | + except QueryError as e: |
| 145 | + logger.error(f"Error querying media UI elements for load: {e}") |
| 146 | + _disable_media_copy_buttons(app) |
| 147 | + return |
| 148 | + |
| 149 | + highlighted_item = results_list_view.highlighted_child |
| 150 | + if highlighted_item is None or not hasattr(highlighted_item, 'media_data'): |
| 151 | + app.notify("No media item selected.", severity="warning") |
| 152 | + review_display.clear() |
| 153 | + _disable_media_copy_buttons(app) |
| 154 | + return |
| 155 | + |
| 156 | + media_data: Dict[str, Any] = getattr(highlighted_item, 'media_data') |
| 157 | + app.current_sidebar_media_item = media_data |
| 158 | + |
| 159 | + # Format details for display |
| 160 | + details_parts = [] |
| 161 | + if media_data.get('title'): details_parts.append(f"Title: {media_data['title']}") |
| 162 | + if media_data.get('author'): details_parts.append(f"Author: {media_data['author']}") |
| 163 | + if media_data.get('media_type'): details_parts.append(f"Type: {media_data['media_type']}") |
| 164 | + if media_data.get('url'): details_parts.append(f"URL: {media_data['url']}") |
| 165 | + details_parts.append("\n--- Content Snippet ---\n") |
| 166 | + content_preview = (media_data['content'][:500] + '...') if len(media_data.get('content', '')) > 500 else media_data.get('content') |
| 167 | + details_parts.append(content_preview or "No content available.") |
| 168 | + |
| 169 | + review_display.load_text("\n".join(details_parts)) |
| 170 | + logger.info(f"Successfully loaded media ID {media_data.get('media_id')} into review display.") |
| 171 | + |
| 172 | + # Enable copy buttons |
| 173 | + app.query_one("#chat-media-copy-title-button", Button).disabled = not bool(media_data.get('title')) |
| 174 | + app.query_one("#chat-media-copy-content-button", Button).disabled = not bool(media_data.get('content')) |
| 175 | + app.query_one("#chat-media-copy-author-button", Button).disabled = not bool(media_data.get('author')) |
| 176 | + app.query_one("#chat-media-copy-url-button", Button).disabled = not bool(media_data.get('url')) |
| 177 | + logger.debug("Media copy buttons state updated.") |
| 178 | + |
| 179 | + |
| 180 | +async def handle_chat_media_copy_title_button_pressed(app: 'TldwCli'): |
| 181 | + """Copies the title of the currently loaded sidebar media to clipboard.""" |
| 182 | + logger.debug("Copy Title button pressed.") |
| 183 | + if app.current_sidebar_media_item and 'title' in app.current_sidebar_media_item: |
| 184 | + title = str(app.current_sidebar_media_item['title']) |
| 185 | + app.copy_to_clipboard(title) |
| 186 | + app.notify("Title copied to clipboard.") |
| 187 | + logger.info(f"Copied title: '{title}'") |
| 188 | + else: |
| 189 | + app.notify("No media title to copy.", severity="warning") |
| 190 | + logger.warning("No media title available to copy.") |
| 191 | + |
| 192 | + |
| 193 | +async def handle_chat_media_copy_content_button_pressed(app: 'TldwCli'): |
| 194 | + """Copies the content of the currently loaded sidebar media to clipboard.""" |
| 195 | + logger.debug("Copy Content button pressed.") |
| 196 | + if app.current_sidebar_media_item and 'content' in app.current_sidebar_media_item: |
| 197 | + content = str(app.current_sidebar_media_item['content']) |
| 198 | + app.copy_to_clipboard(content) |
| 199 | + app.notify("Content copied to clipboard.") |
| 200 | + logger.info("Copied content (length: %s)", len(content)) |
| 201 | + else: |
| 202 | + app.notify("No media content to copy.", severity="warning") |
| 203 | + logger.warning("No media content available to copy.") |
| 204 | + |
| 205 | + |
| 206 | +async def handle_chat_media_copy_author_button_pressed(app: 'TldwCli'): |
| 207 | + """Copies the author of the currently loaded sidebar media to clipboard.""" |
| 208 | + logger.debug("Copy Author button pressed.") |
| 209 | + if app.current_sidebar_media_item and 'author' in app.current_sidebar_media_item: |
| 210 | + author = str(app.current_sidebar_media_item['author']) |
| 211 | + app.copy_to_clipboard(author) |
| 212 | + app.notify("Author copied to clipboard.") |
| 213 | + logger.info(f"Copied author: '{author}'") |
| 214 | + else: |
| 215 | + app.notify("No media author to copy.", severity="warning") |
| 216 | + logger.warning("No media author available to copy.") |
| 217 | + |
| 218 | + |
| 219 | +async def handle_chat_media_copy_url_button_pressed(app: 'TldwCli'): |
| 220 | + """Copies the URL of the currently loaded sidebar media to clipboard.""" |
| 221 | + logger.debug("Copy URL button pressed.") |
| 222 | + if app.current_sidebar_media_item and 'url' in app.current_sidebar_media_item and app.current_sidebar_media_item['url']: |
| 223 | + url = str(app.current_sidebar_media_item['url']) |
| 224 | + app.copy_to_clipboard(url) |
| 225 | + app.notify("URL copied to clipboard.") |
| 226 | + logger.info(f"Copied URL: '{url}'") |
| 227 | + else: |
| 228 | + app.notify("No media URL to copy.", severity="warning") |
| 229 | + logger.warning("No media URL available to copy.") |
| 230 | + |
| 231 | + |
| 232 | +# --- Button Handler Map --- |
| 233 | +CHAT_SIDEBAR_BUTTON_HANDLERS = { |
| 234 | + "chat-media-load-selected-button": handle_chat_media_load_selected_button_pressed, |
| 235 | + "chat-media-copy-title-button": handle_chat_media_copy_title_button_pressed, |
| 236 | + "chat-media-copy-content-button": handle_chat_media_copy_content_button_pressed, |
| 237 | + "chat-media-copy-author-button": handle_chat_media_copy_author_button_pressed, |
| 238 | + "chat-media-copy-url-button": handle_chat_media_copy_url_button_pressed, |
| 239 | +} |
26 | 240 |
|
27 | 241 |
|
28 | 242 | #
|
|
0 commit comments