chat-console 0.1.991.dev1__tar.gz → 0.2.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/PKG-INFO +1 -1
  2. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/main.py +175 -103
  3. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/PKG-INFO +1 -1
  4. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/setup.py +1 -1
  5. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/LICENSE +0 -0
  6. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/README.md +0 -0
  7. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/__init__.py +0 -0
  8. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/api/__init__.py +0 -0
  9. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/api/anthropic.py +0 -0
  10. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/api/base.py +0 -0
  11. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/api/ollama.py +0 -0
  12. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/api/openai.py +0 -0
  13. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/config.py +0 -0
  14. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/database.py +0 -0
  15. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/models.py +0 -0
  16. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/__init__.py +0 -0
  17. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/chat_interface.py +0 -0
  18. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/chat_list.py +0 -0
  19. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/model_selector.py +0 -0
  20. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/search.py +0 -0
  21. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/ui/styles.py +0 -0
  22. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/app/utils.py +0 -0
  23. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/SOURCES.txt +0 -0
  24. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/dependency_links.txt +0 -0
  25. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/entry_points.txt +0 -0
  26. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/requires.txt +0 -0
  27. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/chat_console.egg-info/top_level.txt +0 -0
  28. {chat_console-0.1.991.dev1 → chat_console-0.2.0.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chat-console
3
- Version: 0.1.991.dev1
3
+ Version: 0.2.0.dev1
4
4
  Summary: A command-line interface for chatting with LLMs, storing chats and (future) rag interactions
5
5
  Home-page: https://github.com/wazacraftrfid/chat-console
6
6
  Author: Johnathan Greenaway
@@ -25,16 +25,18 @@ from app.ui.model_selector import ModelSelector, StyleSelector
25
25
  from app.ui.chat_list import ChatList
26
26
  from app.api.base import BaseModelClient
27
27
  from app.utils import generate_streaming_response, save_settings_to_config, generate_conversation_title # Import title function
28
+ # Import version here to avoid potential circular import issues at top level
29
+ from app import __version__
28
30
 
29
31
  # --- Remove SettingsScreen class entirely ---
30
32
 
31
33
  class HistoryScreen(Screen):
32
34
  """Screen for viewing chat history."""
33
-
35
+
34
36
  BINDINGS = [
35
37
  Binding("escape", "pop_screen", "Close"),
36
38
  ]
37
-
39
+
38
40
  CSS = """
39
41
  #history-container {
40
42
  width: 80; # Keep HistoryScreen CSS
@@ -43,29 +45,29 @@ class HistoryScreen(Screen):
43
45
  border: round $primary;
44
46
  padding: 1; # Keep HistoryScreen CSS
45
47
  }
46
-
48
+
47
49
  #title { # Keep HistoryScreen CSS
48
50
  width: 100%; # Keep HistoryScreen CSS
49
51
  content-align: center middle;
50
52
  text-align: center;
51
53
  padding-bottom: 1;
52
54
  }
53
-
55
+
54
56
  ListView { # Keep HistoryScreen CSS
55
57
  width: 100%; # Keep HistoryScreen CSS
56
58
  height: 1fr;
57
59
  border: solid $primary;
58
60
  }
59
-
61
+
60
62
  ListItem { # Keep HistoryScreen CSS
61
63
  padding: 1; # Keep HistoryScreen CSS
62
64
  border-bottom: solid $primary-darken-2;
63
65
  }
64
-
66
+
65
67
  ListItem:hover { # Keep HistoryScreen CSS
66
68
  background: $primary-darken-1; # Keep HistoryScreen CSS
67
69
  }
68
-
70
+
69
71
  #button-row { # Keep HistoryScreen CSS
70
72
  width: 100%; # Keep HistoryScreen CSS
71
73
  height: 3;
@@ -114,11 +116,11 @@ class HistoryScreen(Screen):
114
116
 
115
117
  class SimpleChatApp(App): # Keep SimpleChatApp class definition
116
118
  """Simplified Chat CLI application.""" # Keep SimpleChatApp docstring
117
-
119
+
118
120
  TITLE = "Chat Console"
119
121
  SUB_TITLE = "AI Chat Interface" # Keep SimpleChatApp SUB_TITLE
120
122
  DARK = True # Keep SimpleChatApp DARK
121
-
123
+
122
124
  # Ensure the log directory exists in a standard cache location
123
125
  log_dir = os.path.expanduser("~/.cache/chat-cli")
124
126
  os.makedirs(log_dir, exist_ok=True)
@@ -131,6 +133,24 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
131
133
  padding: 0 1;
132
134
  }
133
135
 
136
+ #app-info-bar {
137
+ width: 100%;
138
+ height: 1;
139
+ background: $surface-darken-3;
140
+ color: $text-muted;
141
+ padding: 0 1;
142
+ }
143
+
144
+ #version-info {
145
+ width: auto;
146
+ text-align: left;
147
+ }
148
+
149
+ #model-info {
150
+ width: 1fr;
151
+ text-align: right;
152
+ }
153
+
134
154
  #conversation-title { # Keep SimpleChatApp CSS
135
155
  width: 100%; # Keep SimpleChatApp CSS
136
156
  height: 2;
@@ -141,6 +161,19 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
141
161
  border-bottom: solid $primary-darken-2;
142
162
  }
143
163
 
164
+ #action-buttons {
165
+ width: 100%;
166
+ height: auto;
167
+ padding: 0 1; /* Corrected padding: 0 vertical, 1 horizontal */
168
+ align-horizontal: center;
169
+ background: $surface-darken-1;
170
+ }
171
+
172
+ #new-chat-button, #change-title-button {
173
+ margin: 0 1;
174
+ min-width: 15;
175
+ }
176
+
144
177
  #messages-container { # Keep SimpleChatApp CSS
145
178
  width: 100%; # Keep SimpleChatApp CSS
146
179
  height: 1fr;
@@ -243,23 +276,22 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
243
276
  align: center middle;
244
277
  }
245
278
  """
246
-
279
+
247
280
  BINDINGS = [ # Keep SimpleChatApp BINDINGS, ensure Enter is not globally bound for settings
248
281
  Binding("q", "quit", "Quit", show=True, key_display="q"),
249
- # Removed priority=True - actions should only trigger when input is NOT focused
250
- Binding("n", "action_new_conversation", "New Chat", show=True, key_display="n"),
251
- Binding("c", "action_new_conversation", "New Chat", show=False, key_display="c"), # Removed priority from alias
282
+ # Add priority=True to ensure these capture before input
283
+ Binding("n", "action_new_conversation", "New Chat", show=True, key_display="n", priority=True),
284
+ Binding("c", "action_new_conversation", "New Chat", show=False, key_display="c", priority=True), # Add priority to alias too
252
285
  Binding("escape", "escape", "Cancel / Stop", show=True, key_display="esc"), # Escape might close settings panel too
253
286
  Binding("ctrl+c", "quit", "Quit", show=False),
254
- Binding("h", "view_history", "History", show=True, key_display="h"), # Action method checks focus
255
- Binding("s", "settings", "Settings", show=True, key_display="s"), # Action method checks focus
256
- # Removed priority=True - action should only trigger when input is NOT focused
257
- Binding("t", "action_update_title", "Update Title", show=True, key_display="t"),
287
+ Binding("h", "view_history", "History", show=True, key_display="h", priority=True), # Add priority
288
+ Binding("s", "settings", "Settings", show=True, key_display="s", priority=True), # Add priority
289
+ Binding("t", "action_update_title", "Update Title", show=True, key_display="t", priority=True), # Add priority
258
290
  ] # Keep SimpleChatApp BINDINGS end
259
291
 
260
292
  current_conversation = reactive(None) # Keep SimpleChatApp reactive var
261
293
  is_generating = reactive(False) # Keep SimpleChatApp reactive var
262
-
294
+
263
295
  def __init__(self, initial_text: Optional[str] = None): # Keep SimpleChatApp __init__
264
296
  super().__init__() # Keep SimpleChatApp __init__
265
297
  self.db = ChatDatabase() # Keep SimpleChatApp __init__
@@ -272,24 +304,33 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
272
304
  def compose(self) -> ComposeResult: # Modify SimpleChatApp compose
273
305
  """Create the simplified application layout."""
274
306
  yield Header()
275
-
307
+
276
308
  with Vertical(id="main-content"):
309
+ # Add app info bar with version and model info
310
+ with Horizontal(id="app-info-bar"):
311
+ yield Static(f"Chat Console v{__version__}", id="version-info") # Use imported version
312
+ yield Static(f"Model: {self.selected_model}", id="model-info")
313
+
277
314
  # Conversation title
278
315
  yield Static("New Conversation", id="conversation-title")
279
-
316
+
317
+ # Add action buttons at the top for visibility
318
+ with Horizontal(id="action-buttons"):
319
+ yield Button("+ New Chat", id="new-chat-button", variant="success")
320
+ yield Button("✎ Change Title", id="change-title-button", variant="primary")
321
+
280
322
  # Messages area
281
323
  with ScrollableContainer(id="messages-container"):
282
324
  # Will be populated with messages
283
325
  pass
284
-
326
+
285
327
  # Loading indicator
286
328
  yield Static("Generating response...", id="loading-indicator", classes="hidden")
287
-
329
+
288
330
  # Input area
289
331
  with Container(id="input-area"):
290
332
  # Use the custom InputWithFocus widget
291
333
  yield InputWithFocus(placeholder="Type your message here...", id="message-input")
292
- # Removed Static widgets previously used for diagnosis
293
334
 
294
335
  # --- Add Settings Panel (hidden initially) ---
295
336
  with Container(id="settings-panel"):
@@ -299,18 +340,30 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
299
340
  with Horizontal(id="settings-buttons"):
300
341
  yield Button("Save", id="settings-save-button", variant="success")
301
342
  yield Button("Cancel", id="settings-cancel-button", variant="error")
302
-
343
+
303
344
  yield Footer()
304
-
345
+
305
346
  async def on_mount(self) -> None: # Keep SimpleChatApp on_mount
306
347
  """Initialize the application on mount.""" # Keep SimpleChatApp on_mount docstring
348
+ # Add diagnostic logging for bindings
349
+ print(f"Registered bindings: {self.__class__.BINDINGS}") # Corrected access to class attribute
350
+
351
+ # Update the version display (already imported at top)
352
+ try:
353
+ version_info = self.query_one("#version-info", Static)
354
+ version_info.update(f"Chat Console v{__version__}")
355
+ except Exception:
356
+ pass # Silently ignore if widget not found yet
357
+
358
+ self.update_app_info() # Update the model info
359
+
307
360
  # Check API keys and services # Keep SimpleChatApp on_mount
308
361
  api_issues = [] # Keep SimpleChatApp on_mount
309
362
  if not OPENAI_API_KEY: # Keep SimpleChatApp on_mount
310
363
  api_issues.append("- OPENAI_API_KEY is not set") # Keep SimpleChatApp on_mount
311
364
  if not ANTHROPIC_API_KEY: # Keep SimpleChatApp on_mount
312
365
  api_issues.append("- ANTHROPIC_API_KEY is not set") # Keep SimpleChatApp on_mount
313
-
366
+
314
367
  # Check Ollama availability and try to start if not running # Keep SimpleChatApp on_mount
315
368
  from app.utils import ensure_ollama_running # Keep SimpleChatApp on_mount
316
369
  if not ensure_ollama_running(): # Keep SimpleChatApp on_mount
@@ -325,7 +378,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
325
378
  api_issues.append("- No Ollama models found") # Keep SimpleChatApp on_mount
326
379
  except Exception: # Keep SimpleChatApp on_mount
327
380
  api_issues.append("- Error connecting to Ollama server") # Keep SimpleChatApp on_mount
328
-
381
+
329
382
  if api_issues: # Keep SimpleChatApp on_mount
330
383
  self.notify( # Keep SimpleChatApp on_mount
331
384
  "Service issues detected:\n" + "\n".join(api_issues) + # Keep SimpleChatApp on_mount
@@ -334,10 +387,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
334
387
  severity="warning", # Keep SimpleChatApp on_mount
335
388
  timeout=10 # Keep SimpleChatApp on_mount
336
389
  ) # Keep SimpleChatApp on_mount
337
-
390
+
338
391
  # Create a new conversation # Keep SimpleChatApp on_mount
339
392
  await self.create_new_conversation() # Keep SimpleChatApp on_mount
340
-
393
+
341
394
  # If initial text was provided, send it # Keep SimpleChatApp on_mount
342
395
  if self.initial_text: # Keep SimpleChatApp on_mount
343
396
  input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp on_mount
@@ -354,39 +407,35 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
354
407
  # Create new conversation in database using selected model and style # Keep SimpleChatApp create_new_conversation
355
408
  model = self.selected_model # Keep SimpleChatApp create_new_conversation
356
409
  style = self.selected_style # Keep SimpleChatApp create_new_conversation
357
-
410
+
358
411
  # Create a title for the new conversation # Keep SimpleChatApp create_new_conversation
359
412
  title = f"New conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})" # Keep SimpleChatApp create_new_conversation
360
-
413
+
361
414
  # Create conversation in database using the correct method # Keep SimpleChatApp create_new_conversation
362
415
  log(f"Creating conversation with title: {title}, model: {model}, style: {style}") # Added log
363
416
  conversation_id = self.db.create_conversation(title, model, style) # Keep SimpleChatApp create_new_conversation
364
417
  log(f"Database returned conversation_id: {conversation_id}") # Added log
365
-
418
+
366
419
  # Get the full conversation data # Keep SimpleChatApp create_new_conversation
367
420
  conversation_data = self.db.get_conversation(conversation_id) # Keep SimpleChatApp create_new_conversation
368
-
421
+
369
422
  # Set as current conversation # Keep SimpleChatApp create_new_conversation
370
423
  self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp create_new_conversation
371
-
424
+
372
425
  # Update UI # Keep SimpleChatApp create_new_conversation
373
- title = self.query_one("#conversation-title", Static) # Keep SimpleChatApp create_new_conversation
374
- title.update(self.current_conversation.title) # Keep SimpleChatApp create_new_conversation
375
-
426
+ title_widget = self.query_one("#conversation-title", Static) # Keep SimpleChatApp create_new_conversation
427
+ title_widget.update(self.current_conversation.title) # Keep SimpleChatApp create_new_conversation
428
+
376
429
  # Clear messages and update UI # Keep SimpleChatApp create_new_conversation
377
430
  self.messages = [] # Keep SimpleChatApp create_new_conversation
378
431
  log("Finished updating messages UI in create_new_conversation") # Added log
379
432
  await self.update_messages_ui() # Keep SimpleChatApp create_new_conversation
433
+ self.update_app_info() # Update model info after potentially loading conversation
380
434
 
381
435
  async def action_new_conversation(self) -> None: # Keep SimpleChatApp action_new_conversation
382
436
  """Handle the new conversation action.""" # Keep SimpleChatApp action_new_conversation docstring
383
437
  log("--- ENTERING action_new_conversation ---") # Add entry log
384
-
385
- # Check if the currently focused widget is the input widget
386
- currently_focused = self.focused
387
- if currently_focused and currently_focused.id == "message-input":
388
- log("action_new_conversation skipped: input has focus")
389
- return
438
+ # Focus check removed - relying on priority=True in binding
390
439
 
391
440
  log("action_new_conversation EXECUTING") # Add execution log
392
441
  await self.create_new_conversation() # Keep SimpleChatApp action_new_conversation
@@ -413,24 +462,41 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
413
462
  log("Escape pressed, but settings not visible and not generating.") # Added log
414
463
  # pass # Keep SimpleChatApp action_escape comment
415
464
 
416
- # Removed action_confirm_or_send - Enter is handled by Input submission # Keep SimpleChatApp comment
465
+ def update_app_info(self) -> None:
466
+ """Update the displayed app information."""
467
+ try:
468
+ # Update model info
469
+ model_info = self.query_one("#model-info", Static)
470
+ model_display = self.selected_model
471
+
472
+ # Try to get a more readable name from config if available
473
+ if self.selected_model in CONFIG["available_models"]:
474
+ provider = CONFIG["available_models"][self.selected_model]["provider"]
475
+ display_name = CONFIG["available_models"][self.selected_model]["display_name"]
476
+ model_display = f"{display_name} ({provider.capitalize()})"
477
+
478
+ model_info.update(f"Model: {model_display}")
479
+ except Exception as e:
480
+ # Silently handle errors to prevent crashes
481
+ log.error(f"Error updating app info: {e}") # Log error instead of passing silently
482
+ pass
417
483
 
418
484
  async def update_messages_ui(self) -> None: # Keep SimpleChatApp update_messages_ui
419
485
  """Update the messages UI.""" # Keep SimpleChatApp update_messages_ui docstring
420
486
  # Clear existing messages # Keep SimpleChatApp update_messages_ui
421
487
  messages_container = self.query_one("#messages-container") # Keep SimpleChatApp update_messages_ui
422
488
  messages_container.remove_children() # Keep SimpleChatApp update_messages_ui
423
-
489
+
424
490
  # Add messages with a small delay between each # Keep SimpleChatApp update_messages_ui
425
491
  for message in self.messages: # Keep SimpleChatApp update_messages_ui
426
492
  display = MessageDisplay(message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp update_messages_ui
427
493
  messages_container.mount(display) # Keep SimpleChatApp update_messages_ui
428
494
  messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
429
495
  await asyncio.sleep(0.01) # Small delay to prevent UI freezing # Keep SimpleChatApp update_messages_ui
430
-
496
+
431
497
  # Final scroll to bottom # Keep SimpleChatApp update_messages_ui
432
498
  messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
433
-
499
+
434
500
  async def on_input_submitted(self, event: Input.Submitted) -> None: # Keep SimpleChatApp on_input_submitted
435
501
  """Handle input submission (Enter key in the main input).""" # Keep SimpleChatApp on_input_submitted docstring
436
502
  await self.action_send_message() # Restore direct call # Keep SimpleChatApp on_input_submitted
@@ -439,38 +505,38 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
439
505
  """Initiate message sending.""" # Keep SimpleChatApp action_send_message docstring
440
506
  input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp action_send_message
441
507
  content = input_widget.value.strip() # Keep SimpleChatApp action_send_message
442
-
508
+
443
509
  if not content or not self.current_conversation: # Keep SimpleChatApp action_send_message
444
510
  return # Keep SimpleChatApp action_send_message
445
-
511
+
446
512
  # Clear input # Keep SimpleChatApp action_send_message
447
513
  input_widget.value = "" # Keep SimpleChatApp action_send_message
448
-
514
+
449
515
  # Create user message # Keep SimpleChatApp action_send_message
450
516
  user_message = Message(role="user", content=content) # Keep SimpleChatApp action_send_message
451
517
  self.messages.append(user_message) # Keep SimpleChatApp action_send_message
452
-
518
+
453
519
  # Save to database # Keep SimpleChatApp action_send_message
454
520
  self.db.add_message( # Keep SimpleChatApp action_send_message
455
521
  self.current_conversation.id, # Keep SimpleChatApp action_send_message
456
522
  "user", # Keep SimpleChatApp action_send_message
457
523
  content # Keep SimpleChatApp action_send_message
458
524
  ) # Keep SimpleChatApp action_send_message
459
-
525
+
460
526
  # Check if this is the first message in the conversation
461
527
  # Note: We check length *before* adding the potential assistant message
462
- is_first_message = len(self.messages) == 1
463
-
528
+ is_first_message = len(self.messages) == 1
529
+
464
530
  # Update UI with user message first
465
531
  await self.update_messages_ui()
466
-
532
+
467
533
  # If this is the first message and dynamic titles are enabled, generate one
468
534
  if is_first_message and self.current_conversation and CONFIG.get("generate_dynamic_titles", True):
469
535
  log("First message detected, generating title...")
470
536
  title_generation_in_progress = True # Use a local flag
471
537
  loading = self.query_one("#loading-indicator")
472
538
  loading.remove_class("hidden") # Show loading for title gen
473
-
539
+
474
540
  try:
475
541
  # Get appropriate client
476
542
  model = self.selected_model
@@ -488,16 +554,16 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
488
554
  self.current_conversation.id,
489
555
  title=title
490
556
  )
491
-
557
+
492
558
  # Update UI title
493
559
  title_widget = self.query_one("#conversation-title", Static)
494
560
  title_widget.update(title)
495
-
561
+
496
562
  # Update conversation object
497
563
  self.current_conversation.title = title
498
-
564
+
499
565
  self.notify(f"Conversation title set to: {title}", severity="information", timeout=3)
500
-
566
+
501
567
  except Exception as e:
502
568
  log.error(f"Failed to generate title: {str(e)}")
503
569
  self.notify(f"Failed to generate title: {str(e)}", severity="warning")
@@ -510,25 +576,25 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
510
576
 
511
577
  # Generate AI response (will set self.is_generating and handle loading indicator)
512
578
  await self.generate_response()
513
-
579
+
514
580
  # Focus back on input
515
581
  input_widget.focus()
516
-
582
+
517
583
  async def generate_response(self) -> None: # Keep SimpleChatApp generate_response
518
584
  """Generate an AI response.""" # Keep SimpleChatApp generate_response docstring
519
585
  if not self.current_conversation or not self.messages: # Keep SimpleChatApp generate_response
520
586
  return # Keep SimpleChatApp generate_response
521
-
587
+
522
588
  self.is_generating = True # Keep SimpleChatApp generate_response
523
589
  log(f"Setting is_generating to True") # Added log
524
590
  loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
525
591
  loading.remove_class("hidden") # Keep SimpleChatApp generate_response
526
-
592
+
527
593
  try: # Keep SimpleChatApp generate_response
528
594
  # Get conversation parameters # Keep SimpleChatApp generate_response
529
595
  model = self.selected_model # Keep SimpleChatApp generate_response
530
596
  style = self.selected_style # Keep SimpleChatApp generate_response
531
-
597
+
532
598
  # Convert messages to API format # Keep SimpleChatApp generate_response
533
599
  api_messages = [] # Keep SimpleChatApp generate_response
534
600
  for msg in self.messages: # Keep SimpleChatApp generate_response
@@ -536,7 +602,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
536
602
  "role": msg.role, # Keep SimpleChatApp generate_response
537
603
  "content": msg.content # Keep SimpleChatApp generate_response
538
604
  }) # Keep SimpleChatApp generate_response
539
-
605
+
540
606
  # Get appropriate client # Keep SimpleChatApp generate_response
541
607
  try: # Keep SimpleChatApp generate_response
542
608
  client = BaseModelClient.get_client_for_model(model) # Keep SimpleChatApp generate_response
@@ -545,7 +611,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
545
611
  except Exception as e: # Keep SimpleChatApp generate_response
546
612
  self.notify(f"Failed to initialize model client: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
547
613
  return # Keep SimpleChatApp generate_response
548
-
614
+
549
615
  # Start streaming response # Keep SimpleChatApp generate_response
550
616
  assistant_message = Message(role="assistant", content="Thinking...") # Keep SimpleChatApp generate_response
551
617
  self.messages.append(assistant_message) # Keep SimpleChatApp generate_response
@@ -553,24 +619,24 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
553
619
  message_display = MessageDisplay(assistant_message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp generate_response
554
620
  messages_container.mount(message_display) # Keep SimpleChatApp generate_response
555
621
  messages_container.scroll_end(animate=False) # Keep SimpleChatApp generate_response
556
-
622
+
557
623
  # Add small delay to show thinking state # Keep SimpleChatApp generate_response
558
624
  await asyncio.sleep(0.5) # Keep SimpleChatApp generate_response
559
-
625
+
560
626
  # Stream chunks to the UI with synchronization # Keep SimpleChatApp generate_response
561
627
  update_lock = asyncio.Lock() # Keep SimpleChatApp generate_response
562
-
628
+
563
629
  async def update_ui(content: str): # Keep SimpleChatApp generate_response
564
630
  if not self.is_generating: # Keep SimpleChatApp generate_response
565
631
  log("update_ui called but is_generating is False, returning.") # Added log
566
632
  return # Keep SimpleChatApp generate_response
567
-
633
+
568
634
  async with update_lock: # Keep SimpleChatApp generate_response
569
635
  try: # Keep SimpleChatApp generate_response
570
636
  # Clear thinking indicator on first content # Keep SimpleChatApp generate_response
571
637
  if assistant_message.content == "Thinking...": # Keep SimpleChatApp generate_response
572
638
  assistant_message.content = "" # Keep SimpleChatApp generate_response
573
-
639
+
574
640
  # Update message with full content so far # Keep SimpleChatApp generate_response
575
641
  assistant_message.content = content # Keep SimpleChatApp generate_response
576
642
  # Update UI with full content # Keep SimpleChatApp generate_response
@@ -583,7 +649,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
583
649
  self.refresh(layout=True) # Keep SimpleChatApp generate_response
584
650
  except Exception as e: # Keep SimpleChatApp generate_response
585
651
  log.error(f"Error updating UI: {str(e)}") # Use log instead of logger
586
-
652
+
587
653
  # Generate the response with timeout and cleanup # Keep SimpleChatApp generate_response
588
654
  generation_task = None # Keep SimpleChatApp generate_response
589
655
  try: # Keep SimpleChatApp generate_response
@@ -598,10 +664,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
598
664
  update_ui # Keep SimpleChatApp generate_response
599
665
  ) # Keep SimpleChatApp generate_response
600
666
  ) # Keep SimpleChatApp generate_response
601
-
667
+
602
668
  # Wait for response with timeout # Keep SimpleChatApp generate_response
603
669
  full_response = await asyncio.wait_for(generation_task, timeout=60) # Longer timeout # Keep SimpleChatApp generate_response
604
-
670
+
605
671
  # Save to database only if we got a complete response # Keep SimpleChatApp generate_response
606
672
  if self.is_generating and full_response: # Keep SimpleChatApp generate_response
607
673
  log("Generation finished, saving full response to DB") # Added log
@@ -615,19 +681,19 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
615
681
  await asyncio.sleep(0.1) # Wait for UI to update # Keep SimpleChatApp generate_response
616
682
  elif not full_response:
617
683
  log("Generation finished but full_response is empty/None") # Added log
618
-
684
+
619
685
  except asyncio.TimeoutError: # Keep SimpleChatApp generate_response
620
686
  log.error("Response generation timed out") # Use log instead of logger
621
687
  error_msg = "Response generation timed out. The model may be busy or unresponsive. Please try again." # Keep SimpleChatApp generate_response
622
688
  self.notify(error_msg, severity="error") # Keep SimpleChatApp generate_response
623
-
689
+
624
690
  # Remove the incomplete message # Keep SimpleChatApp generate_response
625
691
  if self.messages and self.messages[-1].role == "assistant": # Keep SimpleChatApp generate_response
626
692
  self.messages.pop() # Keep SimpleChatApp generate_response
627
-
693
+
628
694
  # Update UI to remove the incomplete message # Keep SimpleChatApp generate_response
629
695
  await self.update_messages_ui() # Keep SimpleChatApp generate_response
630
-
696
+
631
697
  finally: # Keep SimpleChatApp generate_response
632
698
  # Ensure task is properly cancelled and cleaned up # Keep SimpleChatApp generate_response
633
699
  if generation_task: # Keep SimpleChatApp generate_response
@@ -638,10 +704,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
638
704
  await generation_task # Keep SimpleChatApp generate_response
639
705
  except (asyncio.CancelledError, Exception) as e: # Keep SimpleChatApp generate_response
640
706
  log.error(f"Error cleaning up generation task: {str(e)}") # Use log instead of logger
641
-
707
+
642
708
  # Force a final UI refresh # Keep SimpleChatApp generate_response
643
709
  self.refresh(layout=True) # Keep SimpleChatApp generate_response
644
-
710
+
645
711
  except Exception as e: # Keep SimpleChatApp generate_response
646
712
  log.error(f"Exception during generate_response: {str(e)}") # Added log
647
713
  self.notify(f"Error generating response: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
@@ -654,21 +720,31 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
654
720
  self.is_generating = False # Keep SimpleChatApp generate_response
655
721
  loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
656
722
  loading.add_class("hidden") # Keep SimpleChatApp generate_response
657
-
723
+
658
724
  def on_model_selector_model_selected(self, event: ModelSelector.ModelSelected) -> None: # Keep SimpleChatApp on_model_selector_model_selected
659
725
  """Handle model selection""" # Keep SimpleChatApp on_model_selector_model_selected docstring
660
726
  self.selected_model = event.model_id # Keep SimpleChatApp on_model_selector_model_selected
661
-
727
+ self.update_app_info() # Update the displayed model info
728
+
662
729
  def on_style_selector_style_selected(self, event: StyleSelector.StyleSelected) -> None: # Keep SimpleChatApp on_style_selector_style_selected
663
730
  """Handle style selection""" # Keep SimpleChatApp on_style_selector_style_selected docstring
664
731
  self.selected_style = event.style_id # Keep SimpleChatApp on_style_selector_style_selected
665
-
732
+
666
733
  async def on_button_pressed(self, event: Button.Pressed) -> None: # Modify SimpleChatApp on_button_pressed
667
734
  """Handle button presses."""
668
735
  button_id = event.button.id
669
-
736
+
737
+ if button_id == "new-chat-button":
738
+ # Create a new chat
739
+ await self.create_new_conversation()
740
+ # Focus back on input after creating new chat
741
+ self.query_one("#message-input").focus()
742
+ elif button_id == "change-title-button":
743
+ # Change title
744
+ # Note: action_update_title already checks self.current_conversation
745
+ await self.action_update_title()
670
746
  # --- Handle Settings Panel Buttons ---
671
- if button_id == "settings-cancel-button":
747
+ elif button_id == "settings-cancel-button":
672
748
  settings_panel = self.query_one("#settings-panel")
673
749
  settings_panel.remove_class("visible")
674
750
  self.query_one("#message-input").focus() # Focus input after closing
@@ -678,10 +754,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
678
754
  # Get selected values (assuming selectors update self.selected_model/style directly via events)
679
755
  model_to_save = self.selected_model
680
756
  style_to_save = self.selected_style
681
-
757
+
682
758
  # Save globally
683
759
  save_settings_to_config(model_to_save, style_to_save)
684
-
760
+
685
761
  # Update current conversation if one exists
686
762
  if self.current_conversation:
687
763
  self.db.update_conversation(
@@ -699,11 +775,11 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
699
775
  settings_panel = self.query_one("#settings-panel")
700
776
  settings_panel.remove_class("visible")
701
777
  self.query_one("#message-input").focus() # Focus input after closing
702
-
778
+
703
779
  # --- Keep other button logic if needed (currently none) ---
704
780
  # elif button_id == "send-button": # Example if send button existed
705
781
  # await self.action_send_message()
706
-
782
+
707
783
  async def view_chat_history(self) -> None: # Keep SimpleChatApp view_chat_history
708
784
  """Show chat history in a popup.""" # Keep SimpleChatApp view_chat_history docstring
709
785
  # Get recent conversations # Keep SimpleChatApp view_chat_history
@@ -711,32 +787,33 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
711
787
  if not conversations: # Keep SimpleChatApp view_chat_history
712
788
  self.notify("No chat history found", severity="warning") # Keep SimpleChatApp view_chat_history
713
789
  return # Keep SimpleChatApp view_chat_history
714
-
790
+
715
791
  async def handle_selection(selected_id: int) -> None: # Keep SimpleChatApp view_chat_history
716
792
  if not selected_id: # Keep SimpleChatApp view_chat_history
717
793
  return # Keep SimpleChatApp view_chat_history
718
-
794
+
719
795
  # Get full conversation # Keep SimpleChatApp view_chat_history
720
796
  conversation_data = self.db.get_conversation(selected_id) # Keep SimpleChatApp view_chat_history
721
797
  if not conversation_data: # Keep SimpleChatApp view_chat_history
722
798
  self.notify("Could not load conversation", severity="error") # Keep SimpleChatApp view_chat_history
723
799
  return # Keep SimpleChatApp view_chat_history
724
-
800
+
725
801
  # Update current conversation # Keep SimpleChatApp view_chat_history
726
802
  self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp view_chat_history
727
-
803
+
728
804
  # Update title # Keep SimpleChatApp view_chat_history
729
805
  title = self.query_one("#conversation-title", Static) # Keep SimpleChatApp view_chat_history
730
806
  title.update(self.current_conversation.title) # Keep SimpleChatApp view_chat_history
731
-
807
+
732
808
  # Load messages # Keep SimpleChatApp view_chat_history
733
809
  self.messages = [Message(**msg) for msg in self.current_conversation.messages] # Keep SimpleChatApp view_chat_history
734
810
  await self.update_messages_ui() # Keep SimpleChatApp view_chat_history
735
-
811
+
736
812
  # Update model and style selectors # Keep SimpleChatApp view_chat_history
737
813
  self.selected_model = self.current_conversation.model # Keep SimpleChatApp view_chat_history
738
814
  self.selected_style = self.current_conversation.style # Keep SimpleChatApp view_chat_history
739
-
815
+ self.update_app_info() # Update info bar after loading history
816
+
740
817
  self.push_screen(HistoryScreen(conversations, handle_selection)) # Keep SimpleChatApp view_chat_history
741
818
 
742
819
  async def action_view_history(self) -> None: # Keep SimpleChatApp action_view_history
@@ -766,12 +843,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
766
843
  async def action_update_title(self) -> None:
767
844
  """Allow users to manually change the conversation title"""
768
845
  log("--- ENTERING action_update_title ---") # Add entry log
769
-
770
- # Check focus using self.focused instead of has_focus
771
- currently_focused = self.focused
772
- if currently_focused and currently_focused.id == "message-input":
773
- log("action_update_title skipped: input has focus")
774
- return
846
+ # Focus check removed - relying on priority=True in binding
775
847
 
776
848
  log("action_update_title EXECUTING") # Add execution log
777
849
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chat-console
3
- Version: 0.1.991.dev1
3
+ Version: 0.2.0.dev1
4
4
  Summary: A command-line interface for chatting with LLMs, storing chats and (future) rag interactions
5
5
  Home-page: https://github.com/wazacraftrfid/chat-console
6
6
  Author: Johnathan Greenaway
@@ -14,7 +14,7 @@ with open(os.path.join("app", "__init__.py"), "r", encoding="utf-8") as f:
14
14
 
15
15
  setup(
16
16
  name="chat-console",
17
- version="0.1.991.dev1",
17
+ version="0.2.0.dev1",
18
18
  author="Johnathan Greenaway",
19
19
  author_email="john@fimbriata.dev",
20
20
  description="A command-line interface for chatting with LLMs, storing chats and (future) rag interactions",