chat-console 0.1.81.dev1__py3-none-any.whl → 0.1.95.dev1__py3-none-any.whl

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.
app/__init__.py CHANGED
@@ -3,4 +3,4 @@ Chat CLI
3
3
  A command-line interface for chatting with various LLM providers like ChatGPT and Claude.
4
4
  """
5
5
 
6
- __version__ = "1.0.0"
6
+ __version__ = "0.1.85"
app/main.py CHANGED
@@ -23,90 +23,9 @@ from app.ui.chat_interface import MessageDisplay
23
23
  from app.ui.model_selector import ModelSelector, StyleSelector
24
24
  from app.ui.chat_list import ChatList
25
25
  from app.api.base import BaseModelClient
26
- from app.utils import generate_streaming_response
26
+ from app.utils import generate_streaming_response, save_settings_to_config # Import save function
27
27
 
28
- class SettingsScreen(Screen):
29
- """Screen for model and style settings."""
30
-
31
- CSS = """
32
- #settings-container {
33
- width: 60;
34
- height: auto;
35
- background: $surface;
36
- border: solid $primary;
37
- padding: 1;
38
- }
39
-
40
- #title {
41
- width: 100%;
42
- height: 2;
43
- content-align: center middle;
44
- text-align: center;
45
- background: $surface-darken-2;
46
- border-bottom: solid $primary-darken-2;
47
- }
48
-
49
- #button-row {
50
- width: 100%;
51
- height: auto;
52
- align-horizontal: right;
53
- margin-top: 1;
54
- }
55
-
56
- #button-row Button {
57
- width: auto;
58
- min-width: 8;
59
- height: 2;
60
- margin-left: 1;
61
- border: solid $primary;
62
- color: $text;
63
- background: $primary-darken-1;
64
- content-align: center middle;
65
- }
66
- """
67
-
68
- def compose(self) -> ComposeResult:
69
- """Create the settings screen layout."""
70
- with Center():
71
- with Container(id="settings-container"):
72
- yield Static("Settings", id="title")
73
- yield ModelSelector(self.app.selected_model)
74
- yield StyleSelector(self.app.selected_style)
75
- with Horizontal(id="button-row"):
76
- yield Button("Cancel", variant="default")
77
- yield Button("Done", variant="primary")
78
-
79
- BINDINGS = [
80
- Binding("escape", "action_cancel", "Cancel"),
81
- ]
82
-
83
- def action_cancel(self) -> None:
84
- """Handle cancel action"""
85
- self.app.pop_screen()
86
-
87
- async def on_button_pressed(self, event: Button.Pressed) -> None:
88
- """Handle button presses in settings screen."""
89
- # Pop screen for both Done and Cancel
90
- self.app.pop_screen()
91
-
92
- # Only update settings if Done was pressed
93
- if event.button.label == "Done":
94
- try:
95
- # Save settings globally
96
- from app.utils import save_settings_to_config
97
- save_settings_to_config(self.app.selected_model, self.app.selected_style)
98
-
99
- # Update current conversation if one exists
100
- if self.app.current_conversation:
101
- self.app.db.update_conversation(
102
- self.app.current_conversation.id,
103
- model=self.app.selected_model,
104
- style=self.app.selected_style
105
- )
106
- self.app.current_conversation.model = self.app.selected_model
107
- self.app.current_conversation.style = self.app.selected_style
108
- except Exception as e:
109
- self.app.notify(f"Error updating settings: {str(e)}", severity="error")
28
+ # --- Remove SettingsScreen class entirely ---
110
29
 
111
30
  class HistoryScreen(Screen):
112
31
  """Screen for viewing chat history."""
@@ -117,49 +36,49 @@ class HistoryScreen(Screen):
117
36
 
118
37
  CSS = """
119
38
  #history-container {
120
- width: 80;
39
+ width: 80; # Keep HistoryScreen CSS
121
40
  height: 40;
122
41
  background: $surface;
123
42
  border: round $primary;
124
- padding: 1;
43
+ padding: 1; # Keep HistoryScreen CSS
125
44
  }
126
45
 
127
- #title {
128
- width: 100%;
46
+ #title { # Keep HistoryScreen CSS
47
+ width: 100%; # Keep HistoryScreen CSS
129
48
  content-align: center middle;
130
49
  text-align: center;
131
50
  padding-bottom: 1;
132
51
  }
133
52
 
134
- ListView {
135
- width: 100%;
53
+ ListView { # Keep HistoryScreen CSS
54
+ width: 100%; # Keep HistoryScreen CSS
136
55
  height: 1fr;
137
56
  border: solid $primary;
138
57
  }
139
58
 
140
- ListItem {
141
- padding: 1;
59
+ ListItem { # Keep HistoryScreen CSS
60
+ padding: 1; # Keep HistoryScreen CSS
142
61
  border-bottom: solid $primary-darken-2;
143
62
  }
144
63
 
145
- ListItem:hover {
146
- background: $primary-darken-1;
64
+ ListItem:hover { # Keep HistoryScreen CSS
65
+ background: $primary-darken-1; # Keep HistoryScreen CSS
147
66
  }
148
67
 
149
- #button-row {
150
- width: 100%;
68
+ #button-row { # Keep HistoryScreen CSS
69
+ width: 100%; # Keep HistoryScreen CSS
151
70
  height: 3;
152
71
  align-horizontal: center;
153
- margin-top: 1;
72
+ margin-top: 1; # Keep HistoryScreen CSS
154
73
  }
155
74
  """
156
75
 
157
- def __init__(self, conversations: List[dict], callback: Callable[[int], Awaitable[None]]):
158
- super().__init__()
159
- self.conversations = conversations
160
- self.callback = callback
76
+ def __init__(self, conversations: List[dict], callback: Callable[[int], Awaitable[None]]): # Keep HistoryScreen __init__
77
+ super().__init__() # Keep HistoryScreen __init__
78
+ self.conversations = conversations # Keep HistoryScreen __init__
79
+ self.callback = callback # Keep HistoryScreen __init__
161
80
 
162
- def compose(self) -> ComposeResult:
81
+ def compose(self) -> ComposeResult: # Keep HistoryScreen compose
163
82
  """Create the history screen layout."""
164
83
  with Center():
165
84
  with Container(id="history-container"):
@@ -168,7 +87,7 @@ class HistoryScreen(Screen):
168
87
  with Horizontal(id="button-row"):
169
88
  yield Button("Cancel", variant="primary")
170
89
 
171
- async def on_mount(self) -> None:
90
+ async def on_mount(self) -> None: # Keep HistoryScreen on_mount
172
91
  """Initialize the history list after mount."""
173
92
  list_view = self.query_one("#history-list", ListView)
174
93
  for conv in self.conversations:
@@ -181,33 +100,33 @@ class HistoryScreen(Screen):
181
100
  item.id = f"conv-{conv['id']}"
182
101
  await list_view.mount(item)
183
102
 
184
- async def on_list_view_selected(self, event: ListView.Selected) -> None:
103
+ async def on_list_view_selected(self, event: ListView.Selected) -> None: # Keep HistoryScreen on_list_view_selected
185
104
  """Handle conversation selection."""
186
105
  # Remove 'conv-' prefix to get the numeric ID
187
106
  conv_id = int(event.item.id.replace('conv-', ''))
188
107
  self.app.pop_screen()
189
108
  await self.callback(conv_id)
190
109
 
191
- def on_button_pressed(self, event: Button.Pressed) -> None:
110
+ def on_button_pressed(self, event: Button.Pressed) -> None: # Keep HistoryScreen on_button_pressed
192
111
  if event.button.label == "Cancel":
193
112
  self.app.pop_screen()
194
113
 
195
- class SimpleChatApp(App):
196
- """Simplified Chat CLI application."""
114
+ class SimpleChatApp(App): # Keep SimpleChatApp class definition
115
+ """Simplified Chat CLI application.""" # Keep SimpleChatApp docstring
197
116
 
198
- TITLE = "Chat CLI"
199
- SUB_TITLE = "AI Chat Interface"
200
- DARK = True
117
+ TITLE = "Chat CLI" # Keep SimpleChatApp TITLE
118
+ SUB_TITLE = "AI Chat Interface" # Keep SimpleChatApp SUB_TITLE
119
+ DARK = True # Keep SimpleChatApp DARK
201
120
 
202
- CSS = """
203
- #main-content {
121
+ CSS = """ # Keep SimpleChatApp CSS start
122
+ #main-content { # Keep SimpleChatApp CSS
204
123
  width: 100%;
205
124
  height: 100%;
206
125
  padding: 0 1;
207
126
  }
208
127
 
209
- #conversation-title {
210
- width: 100%;
128
+ #conversation-title { # Keep SimpleChatApp CSS
129
+ width: 100%; # Keep SimpleChatApp CSS
211
130
  height: 2;
212
131
  background: $surface-darken-2;
213
132
  color: $text;
@@ -216,8 +135,8 @@ class SimpleChatApp(App):
216
135
  border-bottom: solid $primary-darken-2;
217
136
  }
218
137
 
219
- #messages-container {
220
- width: 100%;
138
+ #messages-container { # Keep SimpleChatApp CSS
139
+ width: 100%; # Keep SimpleChatApp CSS
221
140
  height: 1fr;
222
141
  min-height: 10;
223
142
  border-bottom: solid $primary-darken-2;
@@ -225,8 +144,8 @@ class SimpleChatApp(App):
225
144
  padding: 0 1;
226
145
  }
227
146
 
228
- #loading-indicator {
229
- width: 100%;
147
+ #loading-indicator { # Keep SimpleChatApp CSS
148
+ width: 100%; # Keep SimpleChatApp CSS
230
149
  height: 1;
231
150
  background: $primary-darken-1;
232
151
  color: $text;
@@ -234,91 +153,86 @@ class SimpleChatApp(App):
234
153
  text-align: center;
235
154
  }
236
155
 
237
- #loading-indicator.hidden {
156
+ #loading-indicator.hidden { # Keep SimpleChatApp CSS
238
157
  display: none;
239
158
  }
240
159
 
241
- #input-area {
242
- width: 100%;
160
+ #input-area { # Keep SimpleChatApp CSS
161
+ width: 100%; # Keep SimpleChatApp CSS
243
162
  height: auto;
244
163
  min-height: 4;
245
164
  max-height: 10;
246
165
  padding: 1;
247
166
  }
248
167
 
249
- #message-input {
250
- width: 1fr;
168
+ #message-input { # Keep SimpleChatApp CSS
169
+ width: 1fr; # Keep SimpleChatApp CSS
251
170
  min-height: 2;
252
171
  height: auto;
253
172
  margin-right: 1;
254
173
  border: solid $primary-darken-2;
255
174
  }
256
175
 
257
- #message-input:focus {
176
+ #message-input:focus { # Keep SimpleChatApp CSS
258
177
  border: solid $primary;
259
178
  }
260
179
 
261
- #send-button {
262
- width: auto;
263
- min-width: 8;
264
- height: 2;
265
- color: #FFFFFF !important;
266
- background: $primary;
267
- border: solid $primary;
268
- content-align: center middle;
269
- text-style: bold; /* Add this line */
270
- }
271
-
180
+ /* Removed CSS for #send-button, #new-chat-button, #view-history-button, #settings-button */ # Keep SimpleChatApp CSS comment
181
+ /* Removed CSS for #button-row */ # Keep SimpleChatApp CSS comment
272
182
 
273
- #button-row {
274
- width: 100%;
183
+ #settings-panel { /* Add CSS for the new settings panel */
184
+ display: none; /* Hidden by default */
185
+ align: center middle;
186
+ width: 60;
275
187
  height: auto;
276
- align-horizontal: right;
188
+ background: $surface;
189
+ border: thick $primary;
190
+ padding: 1 2;
191
+ layer: settings; /* Ensure it's above other elements */
277
192
  }
278
193
 
279
- #new-chat-button {
280
- width: auto;
281
- min-width: 8;
282
- height: 2;
283
- color: #FFFFFF !important; /* Force white text */
284
- background: $success;
285
- border: solid $success-lighten-1;
286
- content-align: center middle;
287
- text-style: bold;
194
+ #settings-panel.visible { /* Class to show the panel */
195
+ display: block;
288
196
  }
289
197
 
290
- #view-history-button, #settings-button {
291
- width: auto;
292
- min-width: 8;
293
- height: 2;
294
- color: #FFFFFF !important; /* Force white text */
295
- background: $primary-darken-1;
296
- border: solid $primary;
297
- margin-right: 1;
198
+ #settings-title {
199
+ width: 100%;
298
200
  content-align: center middle;
299
- text-style: bold;
201
+ padding-bottom: 1;
202
+ border-bottom: thick $primary-darken-2; /* Correct syntax for bottom border */
203
+ }
204
+
205
+ #settings-buttons {
206
+ width: 100%;
207
+ height: auto;
208
+ align: center middle;
209
+ padding-top: 1;
300
210
  }
211
+
301
212
  """
302
213
 
303
- BINDINGS = [
304
- Binding("q", "quit", "Quit"),
305
- Binding("n", "action_new_conversation", "New Chat"),
306
- Binding("escape", "escape", "Cancel"),
307
- Binding("ctrl+c", "quit", "Quit"),
308
- ]
214
+ BINDINGS = [ # Keep SimpleChatApp BINDINGS, ensure Enter is not globally bound for settings
215
+ Binding("q", "quit", "Quit", show=True, key_display="q"),
216
+ Binding("n", "action_new_conversation", "New Chat", show=True, key_display="n"),
217
+ Binding("c", "action_new_conversation", "New Chat", show=False, key_display="c"),
218
+ Binding("escape", "escape", "Cancel / Stop", show=True, key_display="esc"), # Escape might close settings panel too
219
+ Binding("ctrl+c", "quit", "Quit", show=False),
220
+ Binding("h", "view_history", "History", show=True, key_display="h"),
221
+ Binding("s", "settings", "Settings", show=True, key_display="s"),
222
+ ] # Keep SimpleChatApp BINDINGS end
309
223
 
310
- current_conversation = reactive(None)
311
- is_generating = reactive(False)
224
+ current_conversation = reactive(None) # Keep SimpleChatApp reactive var
225
+ is_generating = reactive(False) # Keep SimpleChatApp reactive var
312
226
 
313
- def __init__(self, initial_text: Optional[str] = None):
314
- super().__init__()
315
- self.db = ChatDatabase()
316
- self.messages = []
317
- self.selected_model = CONFIG["default_model"]
318
- self.selected_style = CONFIG["default_style"]
319
- self.initial_text = initial_text
227
+ def __init__(self, initial_text: Optional[str] = None): # Keep SimpleChatApp __init__
228
+ super().__init__() # Keep SimpleChatApp __init__
229
+ self.db = ChatDatabase() # Keep SimpleChatApp __init__
230
+ self.messages = [] # Keep SimpleChatApp __init__
231
+ self.selected_model = CONFIG["default_model"] # Keep SimpleChatApp __init__
232
+ self.selected_style = CONFIG["default_style"] # Keep SimpleChatApp __init__
233
+ self.initial_text = initial_text # Keep SimpleChatApp __init__
320
234
 
321
- def compose(self) -> ComposeResult:
235
+ def compose(self) -> ComposeResult: # Modify SimpleChatApp compose
322
236
  """Create the simplified application layout."""
323
237
  yield Header()
324
238
 
@@ -337,350 +251,414 @@ class SimpleChatApp(App):
337
251
  # Input area
338
252
  with Container(id="input-area"):
339
253
  yield Input(placeholder="Type your message here...", id="message-input")
340
- yield Button("Send", id="send-button", variant="primary")
341
- with Horizontal(id="button-row"):
342
- yield Button("Settings", id="settings-button", variant="primary")
343
- yield Button("View History", id="view-history-button", variant="primary")
344
- yield Button("+ New Chat", id="new-chat-button")
254
+ # Removed Static widgets previously used for diagnosis
255
+
256
+ # --- Add Settings Panel (hidden initially) ---
257
+ with Container(id="settings-panel"):
258
+ yield Static("Settings", id="settings-title")
259
+ yield ModelSelector(self.selected_model)
260
+ yield StyleSelector(self.selected_style)
261
+ with Horizontal(id="settings-buttons"):
262
+ yield Button("Save", id="settings-save-button", variant="success")
263
+ yield Button("Cancel", id="settings-cancel-button", variant="error")
345
264
 
346
265
  yield Footer()
347
266
 
348
- async def on_mount(self) -> None:
349
- """Initialize the application on mount."""
350
- # Check API keys and services
351
- api_issues = []
352
- if not OPENAI_API_KEY:
353
- api_issues.append("- OPENAI_API_KEY is not set")
354
- if not ANTHROPIC_API_KEY:
355
- api_issues.append("- ANTHROPIC_API_KEY is not set")
267
+ async def on_mount(self) -> None: # Keep SimpleChatApp on_mount
268
+ """Initialize the application on mount.""" # Keep SimpleChatApp on_mount docstring
269
+ # Check API keys and services # Keep SimpleChatApp on_mount
270
+ api_issues = [] # Keep SimpleChatApp on_mount
271
+ if not OPENAI_API_KEY: # Keep SimpleChatApp on_mount
272
+ api_issues.append("- OPENAI_API_KEY is not set") # Keep SimpleChatApp on_mount
273
+ if not ANTHROPIC_API_KEY: # Keep SimpleChatApp on_mount
274
+ api_issues.append("- ANTHROPIC_API_KEY is not set") # Keep SimpleChatApp on_mount
356
275
 
357
- # Check Ollama availability and try to start if not running
358
- from app.utils import ensure_ollama_running
359
- if not ensure_ollama_running():
360
- api_issues.append("- Ollama server not running and could not be started")
361
- else:
362
- # Check for available models
363
- from app.api.ollama import OllamaClient
364
- try:
365
- ollama = OllamaClient()
366
- models = await ollama.get_available_models()
367
- if not models:
368
- api_issues.append("- No Ollama models found")
369
- except Exception:
370
- api_issues.append("- Error connecting to Ollama server")
276
+ # Check Ollama availability and try to start if not running # Keep SimpleChatApp on_mount
277
+ from app.utils import ensure_ollama_running # Keep SimpleChatApp on_mount
278
+ if not ensure_ollama_running(): # Keep SimpleChatApp on_mount
279
+ api_issues.append("- Ollama server not running and could not be started") # Keep SimpleChatApp on_mount
280
+ else: # Keep SimpleChatApp on_mount
281
+ # Check for available models # Keep SimpleChatApp on_mount
282
+ from app.api.ollama import OllamaClient # Keep SimpleChatApp on_mount
283
+ try: # Keep SimpleChatApp on_mount
284
+ ollama = OllamaClient() # Keep SimpleChatApp on_mount
285
+ models = await ollama.get_available_models() # Keep SimpleChatApp on_mount
286
+ if not models: # Keep SimpleChatApp on_mount
287
+ api_issues.append("- No Ollama models found") # Keep SimpleChatApp on_mount
288
+ except Exception: # Keep SimpleChatApp on_mount
289
+ api_issues.append("- Error connecting to Ollama server") # Keep SimpleChatApp on_mount
371
290
 
372
- if api_issues:
373
- self.notify(
374
- "Service issues detected:\n" + "\n".join(api_issues) +
375
- "\n\nEnsure services are configured and running.",
376
- title="Service Warning",
377
- severity="warning",
378
- timeout=10
379
- )
291
+ if api_issues: # Keep SimpleChatApp on_mount
292
+ self.notify( # Keep SimpleChatApp on_mount
293
+ "Service issues detected:\n" + "\n".join(api_issues) + # Keep SimpleChatApp on_mount
294
+ "\n\nEnsure services are configured and running.", # Keep SimpleChatApp on_mount
295
+ title="Service Warning", # Keep SimpleChatApp on_mount
296
+ severity="warning", # Keep SimpleChatApp on_mount
297
+ timeout=10 # Keep SimpleChatApp on_mount
298
+ ) # Keep SimpleChatApp on_mount
380
299
 
381
- # Create a new conversation
382
- await self.create_new_conversation()
300
+ # Create a new conversation # Keep SimpleChatApp on_mount
301
+ await self.create_new_conversation() # Keep SimpleChatApp on_mount
383
302
 
384
- # If initial text was provided, send it
385
- if self.initial_text:
386
- input_widget = self.query_one("#message-input", Input)
387
- input_widget.value = self.initial_text
388
- await self.action_send_message()
389
- else:
390
- # Focus the input if no initial text
391
- self.query_one("#message-input").focus()
303
+ # If initial text was provided, send it # Keep SimpleChatApp on_mount
304
+ if self.initial_text: # Keep SimpleChatApp on_mount
305
+ input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp on_mount
306
+ input_widget.value = self.initial_text # Keep SimpleChatApp on_mount
307
+ await self.action_send_message() # Keep SimpleChatApp on_mount
308
+ else: # Keep SimpleChatApp on_mount
309
+ # Focus the input if no initial text # Keep SimpleChatApp on_mount
310
+ self.query_one("#message-input").focus() # Keep SimpleChatApp on_mount
392
311
 
393
- async def create_new_conversation(self) -> None:
394
- """Create a new chat conversation."""
395
- # Create new conversation in database using selected model and style
396
- model = self.selected_model
397
- style = self.selected_style
312
+ async def create_new_conversation(self) -> None: # Keep SimpleChatApp create_new_conversation
313
+ """Create a new chat conversation.""" # Keep SimpleChatApp create_new_conversation docstring
314
+ # Create new conversation in database using selected model and style # Keep SimpleChatApp create_new_conversation
315
+ model = self.selected_model # Keep SimpleChatApp create_new_conversation
316
+ style = self.selected_style # Keep SimpleChatApp create_new_conversation
398
317
 
399
- # Create a title for the new conversation
400
- title = f"New conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})"
318
+ # Create a title for the new conversation # Keep SimpleChatApp create_new_conversation
319
+ title = f"New conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})" # Keep SimpleChatApp create_new_conversation
401
320
 
402
- # Create conversation in database using the correct method
403
- conversation_id = self.db.create_conversation(title, model, style)
321
+ # Create conversation in database using the correct method # Keep SimpleChatApp create_new_conversation
322
+ conversation_id = self.db.create_conversation(title, model, style) # Keep SimpleChatApp create_new_conversation
404
323
 
405
- # Get the full conversation data
406
- conversation_data = self.db.get_conversation(conversation_id)
324
+ # Get the full conversation data # Keep SimpleChatApp create_new_conversation
325
+ conversation_data = self.db.get_conversation(conversation_id) # Keep SimpleChatApp create_new_conversation
407
326
 
408
- # Set as current conversation
409
- self.current_conversation = Conversation.from_dict(conversation_data)
327
+ # Set as current conversation # Keep SimpleChatApp create_new_conversation
328
+ self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp create_new_conversation
410
329
 
411
- # Update UI
412
- title = self.query_one("#conversation-title", Static)
413
- title.update(self.current_conversation.title)
330
+ # Update UI # Keep SimpleChatApp create_new_conversation
331
+ title = self.query_one("#conversation-title", Static) # Keep SimpleChatApp create_new_conversation
332
+ title.update(self.current_conversation.title) # Keep SimpleChatApp create_new_conversation
414
333
 
415
- # Clear messages and update UI
416
- self.messages = []
417
- await self.update_messages_ui()
334
+ # Clear messages and update UI # Keep SimpleChatApp create_new_conversation
335
+ self.messages = [] # Keep SimpleChatApp create_new_conversation
336
+ await self.update_messages_ui() # Keep SimpleChatApp create_new_conversation
418
337
 
419
- async def action_new_conversation(self) -> None:
420
- """Handle the new conversation action."""
421
- await self.create_new_conversation()
338
+ async def action_new_conversation(self) -> None: # Keep SimpleChatApp action_new_conversation
339
+ """Handle the new conversation action.""" # Keep SimpleChatApp action_new_conversation docstring
340
+ await self.create_new_conversation() # Keep SimpleChatApp action_new_conversation
422
341
 
423
- def action_escape(self) -> None:
424
- """Handle escape key."""
425
- if self.is_generating:
426
- self.is_generating = False
427
- self.notify("Generation stopped", severity="warning")
428
- loading = self.query_one("#loading-indicator")
429
- loading.add_class("hidden")
430
- elif self.screen is not self.screen_stack[-1]:
431
- # If we're in a sub-screen, pop it
432
- self.pop_screen()
433
-
434
- async def update_messages_ui(self) -> None:
435
- """Update the messages UI."""
436
- # Clear existing messages
437
- messages_container = self.query_one("#messages-container")
438
- messages_container.remove_children()
342
+ def action_escape(self) -> None: # Modify SimpleChatApp action_escape
343
+ """Handle escape key globally."""
344
+ settings_panel = self.query_one("#settings-panel")
345
+ if settings_panel.has_class("visible"):
346
+ # If settings panel is visible, hide it
347
+ settings_panel.remove_class("visible")
348
+ self.query_one("#message-input").focus() # Focus input after closing settings
349
+ elif self.is_generating:
350
+ # Otherwise, stop generation if running
351
+ self.is_generating = False # Keep SimpleChatApp action_escape
352
+ self.notify("Generation stopped", severity="warning") # Keep SimpleChatApp action_escape
353
+ loading = self.query_one("#loading-indicator") # Keep SimpleChatApp action_escape
354
+ loading.add_class("hidden") # Keep SimpleChatApp action_escape
355
+ # else: # Optional: Add other escape behavior for the main screen if desired # Keep SimpleChatApp action_escape comment
356
+ # pass # Keep SimpleChatApp action_escape comment
357
+
358
+ # Removed action_confirm_or_send - Enter is handled by Input submission # Keep SimpleChatApp comment
359
+
360
+ async def update_messages_ui(self) -> None: # Keep SimpleChatApp update_messages_ui
361
+ """Update the messages UI.""" # Keep SimpleChatApp update_messages_ui docstring
362
+ # Clear existing messages # Keep SimpleChatApp update_messages_ui
363
+ messages_container = self.query_one("#messages-container") # Keep SimpleChatApp update_messages_ui
364
+ messages_container.remove_children() # Keep SimpleChatApp update_messages_ui
439
365
 
440
- # Add messages with a small delay between each
441
- for message in self.messages:
442
- display = MessageDisplay(message, highlight_code=CONFIG["highlight_code"])
443
- messages_container.mount(display)
444
- messages_container.scroll_end(animate=False)
445
- await asyncio.sleep(0.01) # Small delay to prevent UI freezing
366
+ # Add messages with a small delay between each # Keep SimpleChatApp update_messages_ui
367
+ for message in self.messages: # Keep SimpleChatApp update_messages_ui
368
+ display = MessageDisplay(message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp update_messages_ui
369
+ messages_container.mount(display) # Keep SimpleChatApp update_messages_ui
370
+ messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
371
+ await asyncio.sleep(0.01) # Small delay to prevent UI freezing # Keep SimpleChatApp update_messages_ui
446
372
 
447
- # Final scroll to bottom
448
- messages_container.scroll_end(animate=False)
449
-
450
- async def on_input_submitted(self, event: Input.Submitted) -> None:
451
- """Handle input submission."""
452
- await self.action_send_message()
373
+ # Final scroll to bottom # Keep SimpleChatApp update_messages_ui
374
+ messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
453
375
 
454
- async def action_send_message(self) -> None:
455
- """Initiate message sending."""
456
- input_widget = self.query_one("#message-input", Input)
457
- content = input_widget.value.strip()
376
+ async def on_input_submitted(self, event: Input.Submitted) -> None: # Keep SimpleChatApp on_input_submitted
377
+ """Handle input submission (Enter key in the main input).""" # Keep SimpleChatApp on_input_submitted docstring
378
+ await self.action_send_message() # Restore direct call # Keep SimpleChatApp on_input_submitted
379
+
380
+ async def action_send_message(self) -> None: # Keep SimpleChatApp action_send_message
381
+ """Initiate message sending.""" # Keep SimpleChatApp action_send_message docstring
382
+ input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp action_send_message
383
+ content = input_widget.value.strip() # Keep SimpleChatApp action_send_message
458
384
 
459
- if not content or not self.current_conversation:
460
- return
385
+ if not content or not self.current_conversation: # Keep SimpleChatApp action_send_message
386
+ return # Keep SimpleChatApp action_send_message
461
387
 
462
- # Clear input
463
- input_widget.value = ""
388
+ # Clear input # Keep SimpleChatApp action_send_message
389
+ input_widget.value = "" # Keep SimpleChatApp action_send_message
464
390
 
465
- # Create user message
466
- user_message = Message(role="user", content=content)
467
- self.messages.append(user_message)
391
+ # Create user message # Keep SimpleChatApp action_send_message
392
+ user_message = Message(role="user", content=content) # Keep SimpleChatApp action_send_message
393
+ self.messages.append(user_message) # Keep SimpleChatApp action_send_message
468
394
 
469
- # Save to database
470
- self.db.add_message(
471
- self.current_conversation.id,
472
- "user",
473
- content
474
- )
395
+ # Save to database # Keep SimpleChatApp action_send_message
396
+ self.db.add_message( # Keep SimpleChatApp action_send_message
397
+ self.current_conversation.id, # Keep SimpleChatApp action_send_message
398
+ "user", # Keep SimpleChatApp action_send_message
399
+ content # Keep SimpleChatApp action_send_message
400
+ ) # Keep SimpleChatApp action_send_message
475
401
 
476
- # Update UI
477
- await self.update_messages_ui()
402
+ # Update UI # Keep SimpleChatApp action_send_message
403
+ await self.update_messages_ui() # Keep SimpleChatApp action_send_message
478
404
 
479
- # Generate AI response
480
- await self.generate_response()
405
+ # Generate AI response # Keep SimpleChatApp action_send_message
406
+ await self.generate_response() # Keep SimpleChatApp action_send_message
481
407
 
482
- # Focus back on input
483
- input_widget.focus()
408
+ # Focus back on input # Keep SimpleChatApp action_send_message
409
+ input_widget.focus() # Keep SimpleChatApp action_send_message
484
410
 
485
- async def generate_response(self) -> None:
486
- """Generate an AI response."""
487
- if not self.current_conversation or not self.messages:
488
- return
411
+ async def generate_response(self) -> None: # Keep SimpleChatApp generate_response
412
+ """Generate an AI response.""" # Keep SimpleChatApp generate_response docstring
413
+ if not self.current_conversation or not self.messages: # Keep SimpleChatApp generate_response
414
+ return # Keep SimpleChatApp generate_response
489
415
 
490
- self.is_generating = True
491
- loading = self.query_one("#loading-indicator")
492
- loading.remove_class("hidden")
416
+ self.is_generating = True # Keep SimpleChatApp generate_response
417
+ loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
418
+ loading.remove_class("hidden") # Keep SimpleChatApp generate_response
493
419
 
494
- try:
495
- # Get conversation parameters
496
- model = self.selected_model
497
- style = self.selected_style
420
+ try: # Keep SimpleChatApp generate_response
421
+ # Get conversation parameters # Keep SimpleChatApp generate_response
422
+ model = self.selected_model # Keep SimpleChatApp generate_response
423
+ style = self.selected_style # Keep SimpleChatApp generate_response
498
424
 
499
- # Convert messages to API format
500
- api_messages = []
501
- for msg in self.messages:
502
- api_messages.append({
503
- "role": msg.role,
504
- "content": msg.content
505
- })
425
+ # Convert messages to API format # Keep SimpleChatApp generate_response
426
+ api_messages = [] # Keep SimpleChatApp generate_response
427
+ for msg in self.messages: # Keep SimpleChatApp generate_response
428
+ api_messages.append({ # Keep SimpleChatApp generate_response
429
+ "role": msg.role, # Keep SimpleChatApp generate_response
430
+ "content": msg.content # Keep SimpleChatApp generate_response
431
+ }) # Keep SimpleChatApp generate_response
506
432
 
507
- # Get appropriate client
508
- try:
509
- client = BaseModelClient.get_client_for_model(model)
510
- if client is None:
511
- raise Exception(f"No client available for model: {model}")
512
- except Exception as e:
513
- self.notify(f"Failed to initialize model client: {str(e)}", severity="error")
514
- return
433
+ # Get appropriate client # Keep SimpleChatApp generate_response
434
+ try: # Keep SimpleChatApp generate_response
435
+ client = BaseModelClient.get_client_for_model(model) # Keep SimpleChatApp generate_response
436
+ if client is None: # Keep SimpleChatApp generate_response
437
+ raise Exception(f"No client available for model: {model}") # Keep SimpleChatApp generate_response
438
+ except Exception as e: # Keep SimpleChatApp generate_response
439
+ self.notify(f"Failed to initialize model client: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
440
+ return # Keep SimpleChatApp generate_response
515
441
 
516
- # Start streaming response
517
- assistant_message = Message(role="assistant", content="Thinking...")
518
- self.messages.append(assistant_message)
519
- messages_container = self.query_one("#messages-container")
520
- message_display = MessageDisplay(assistant_message, highlight_code=CONFIG["highlight_code"])
521
- messages_container.mount(message_display)
522
- messages_container.scroll_end(animate=False)
442
+ # Start streaming response # Keep SimpleChatApp generate_response
443
+ assistant_message = Message(role="assistant", content="Thinking...") # Keep SimpleChatApp generate_response
444
+ self.messages.append(assistant_message) # Keep SimpleChatApp generate_response
445
+ messages_container = self.query_one("#messages-container") # Keep SimpleChatApp generate_response
446
+ message_display = MessageDisplay(assistant_message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp generate_response
447
+ messages_container.mount(message_display) # Keep SimpleChatApp generate_response
448
+ messages_container.scroll_end(animate=False) # Keep SimpleChatApp generate_response
523
449
 
524
- # Add small delay to show thinking state
525
- await asyncio.sleep(0.5)
450
+ # Add small delay to show thinking state # Keep SimpleChatApp generate_response
451
+ await asyncio.sleep(0.5) # Keep SimpleChatApp generate_response
526
452
 
527
- # Stream chunks to the UI with synchronization
528
- update_lock = asyncio.Lock()
453
+ # Stream chunks to the UI with synchronization # Keep SimpleChatApp generate_response
454
+ update_lock = asyncio.Lock() # Keep SimpleChatApp generate_response
529
455
 
530
- async def update_ui(content: str):
531
- if not self.is_generating:
532
- return
456
+ async def update_ui(content: str): # Keep SimpleChatApp generate_response
457
+ if not self.is_generating: # Keep SimpleChatApp generate_response
458
+ return # Keep SimpleChatApp generate_response
533
459
 
534
- async with update_lock:
535
- try:
536
- # Clear thinking indicator on first content
537
- if assistant_message.content == "Thinking...":
538
- assistant_message.content = ""
460
+ async with update_lock: # Keep SimpleChatApp generate_response
461
+ try: # Keep SimpleChatApp generate_response
462
+ # Clear thinking indicator on first content # Keep SimpleChatApp generate_response
463
+ if assistant_message.content == "Thinking...": # Keep SimpleChatApp generate_response
464
+ assistant_message.content = "" # Keep SimpleChatApp generate_response
539
465
 
540
- # Update message with full content so far
541
- assistant_message.content = content
542
- # Update UI with full content
543
- await message_display.update_content(content)
544
- # Force a refresh and scroll
545
- self.refresh(layout=True)
546
- await asyncio.sleep(0.05) # Longer delay for UI stability
547
- messages_container.scroll_end(animate=False)
548
- # Force another refresh to ensure content is visible
549
- self.refresh(layout=True)
550
- except Exception as e:
551
- logger.error(f"Error updating UI: {str(e)}")
466
+ # Update message with full content so far # Keep SimpleChatApp generate_response
467
+ assistant_message.content = content # Keep SimpleChatApp generate_response
468
+ # Update UI with full content # Keep SimpleChatApp generate_response
469
+ await message_display.update_content(content) # Keep SimpleChatApp generate_response
470
+ # Force a refresh and scroll # Keep SimpleChatApp generate_response
471
+ self.refresh(layout=True) # Keep SimpleChatApp generate_response
472
+ await asyncio.sleep(0.05) # Longer delay for UI stability # Keep SimpleChatApp generate_response
473
+ messages_container.scroll_end(animate=False) # Keep SimpleChatApp generate_response
474
+ # Force another refresh to ensure content is visible # Keep SimpleChatApp generate_response
475
+ self.refresh(layout=True) # Keep SimpleChatApp generate_response
476
+ except Exception as e: # Keep SimpleChatApp generate_response
477
+ logger.error(f"Error updating UI: {str(e)}") # Keep SimpleChatApp generate_response
552
478
 
553
- # Generate the response with timeout and cleanup
554
- generation_task = None
555
- try:
556
- # Create a task for the response generation
557
- generation_task = asyncio.create_task(
558
- generate_streaming_response(
559
- api_messages,
560
- model,
561
- style,
562
- client,
563
- update_ui
564
- )
565
- )
479
+ # Generate the response with timeout and cleanup # Keep SimpleChatApp generate_response
480
+ generation_task = None # Keep SimpleChatApp generate_response
481
+ try: # Keep SimpleChatApp generate_response
482
+ # Create a task for the response generation # Keep SimpleChatApp generate_response
483
+ generation_task = asyncio.create_task( # Keep SimpleChatApp generate_response
484
+ generate_streaming_response( # Keep SimpleChatApp generate_response
485
+ api_messages, # Keep SimpleChatApp generate_response
486
+ model, # Keep SimpleChatApp generate_response
487
+ style, # Keep SimpleChatApp generate_response
488
+ client, # Keep SimpleChatApp generate_response
489
+ update_ui # Keep SimpleChatApp generate_response
490
+ ) # Keep SimpleChatApp generate_response
491
+ ) # Keep SimpleChatApp generate_response
566
492
 
567
- # Wait for response with timeout
568
- full_response = await asyncio.wait_for(generation_task, timeout=60) # Longer timeout
493
+ # Wait for response with timeout # Keep SimpleChatApp generate_response
494
+ full_response = await asyncio.wait_for(generation_task, timeout=60) # Longer timeout # Keep SimpleChatApp generate_response
569
495
 
570
- # Save to database only if we got a complete response
571
- if self.is_generating and full_response:
572
- self.db.add_message(
573
- self.current_conversation.id,
574
- "assistant",
575
- full_response
576
- )
577
- # Force a final refresh
578
- self.refresh(layout=True)
579
- await asyncio.sleep(0.1) # Wait for UI to update
496
+ # Save to database only if we got a complete response # Keep SimpleChatApp generate_response
497
+ if self.is_generating and full_response: # Keep SimpleChatApp generate_response
498
+ self.db.add_message( # Keep SimpleChatApp generate_response
499
+ self.current_conversation.id, # Keep SimpleChatApp generate_response
500
+ "assistant", # Keep SimpleChatApp generate_response
501
+ full_response # Keep SimpleChatApp generate_response
502
+ ) # Keep SimpleChatApp generate_response
503
+ # Force a final refresh # Keep SimpleChatApp generate_response
504
+ self.refresh(layout=True) # Keep SimpleChatApp generate_response
505
+ await asyncio.sleep(0.1) # Wait for UI to update # Keep SimpleChatApp generate_response
580
506
 
581
- except asyncio.TimeoutError:
582
- logger.error("Response generation timed out")
583
- error_msg = "Response generation timed out. The model may be busy or unresponsive. Please try again."
584
- self.notify(error_msg, severity="error")
507
+ except asyncio.TimeoutError: # Keep SimpleChatApp generate_response
508
+ logger.error("Response generation timed out") # Keep SimpleChatApp generate_response
509
+ error_msg = "Response generation timed out. The model may be busy or unresponsive. Please try again." # Keep SimpleChatApp generate_response
510
+ self.notify(error_msg, severity="error") # Keep SimpleChatApp generate_response
585
511
 
586
- # Remove the incomplete message
587
- if self.messages and self.messages[-1].role == "assistant":
588
- self.messages.pop()
512
+ # Remove the incomplete message # Keep SimpleChatApp generate_response
513
+ if self.messages and self.messages[-1].role == "assistant": # Keep SimpleChatApp generate_response
514
+ self.messages.pop() # Keep SimpleChatApp generate_response
589
515
 
590
- # Update UI to remove the incomplete message
591
- await self.update_messages_ui()
516
+ # Update UI to remove the incomplete message # Keep SimpleChatApp generate_response
517
+ await self.update_messages_ui() # Keep SimpleChatApp generate_response
592
518
 
593
- finally:
594
- # Ensure task is properly cancelled and cleaned up
595
- if generation_task:
596
- if not generation_task.done():
597
- generation_task.cancel()
598
- try:
599
- await generation_task
600
- except (asyncio.CancelledError, Exception) as e:
601
- logger.error(f"Error cleaning up generation task: {str(e)}")
519
+ finally: # Keep SimpleChatApp generate_response
520
+ # Ensure task is properly cancelled and cleaned up # Keep SimpleChatApp generate_response
521
+ if generation_task: # Keep SimpleChatApp generate_response
522
+ if not generation_task.done(): # Keep SimpleChatApp generate_response
523
+ generation_task.cancel() # Keep SimpleChatApp generate_response
524
+ try: # Keep SimpleChatApp generate_response
525
+ await generation_task # Keep SimpleChatApp generate_response
526
+ except (asyncio.CancelledError, Exception) as e: # Keep SimpleChatApp generate_response
527
+ logger.error(f"Error cleaning up generation task: {str(e)}") # Keep SimpleChatApp generate_response
602
528
 
603
- # Force a final UI refresh
604
- self.refresh(layout=True)
529
+ # Force a final UI refresh # Keep SimpleChatApp generate_response
530
+ self.refresh(layout=True) # Keep SimpleChatApp generate_response
605
531
 
606
- except Exception as e:
607
- self.notify(f"Error generating response: {str(e)}", severity="error")
608
- # Add error message
609
- error_msg = f"Error generating response: {str(e)}"
610
- self.messages.append(Message(role="assistant", content=error_msg))
611
- await self.update_messages_ui()
612
- finally:
613
- self.is_generating = False
614
- loading = self.query_one("#loading-indicator")
615
- loading.add_class("hidden")
532
+ except Exception as e: # Keep SimpleChatApp generate_response
533
+ self.notify(f"Error generating response: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
534
+ # Add error message # Keep SimpleChatApp generate_response
535
+ error_msg = f"Error generating response: {str(e)}" # Keep SimpleChatApp generate_response
536
+ self.messages.append(Message(role="assistant", content=error_msg)) # Keep SimpleChatApp generate_response
537
+ await self.update_messages_ui() # Keep SimpleChatApp generate_response
538
+ finally: # Keep SimpleChatApp generate_response
539
+ self.is_generating = False # Keep SimpleChatApp generate_response
540
+ loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
541
+ loading.add_class("hidden") # Keep SimpleChatApp generate_response
616
542
 
617
- def on_model_selector_model_selected(self, event: ModelSelector.ModelSelected) -> None:
618
- """Handle model selection"""
619
- self.selected_model = event.model_id
620
-
621
- def on_style_selector_style_selected(self, event: StyleSelector.StyleSelected) -> None:
622
- """Handle style selection"""
623
- self.selected_style = event.style_id
543
+ def on_model_selector_model_selected(self, event: ModelSelector.ModelSelected) -> None: # Keep SimpleChatApp on_model_selector_model_selected
544
+ """Handle model selection""" # Keep SimpleChatApp on_model_selector_model_selected docstring
545
+ self.selected_model = event.model_id # Keep SimpleChatApp on_model_selector_model_selected
624
546
 
625
- async def on_button_pressed(self, event: Button.Pressed) -> None:
547
+ def on_style_selector_style_selected(self, event: StyleSelector.StyleSelected) -> None: # Keep SimpleChatApp on_style_selector_style_selected
548
+ """Handle style selection""" # Keep SimpleChatApp on_style_selector_style_selected docstring
549
+ self.selected_style = event.style_id # Keep SimpleChatApp on_style_selector_style_selected
550
+
551
+ async def on_button_pressed(self, event: Button.Pressed) -> None: # Modify SimpleChatApp on_button_pressed
626
552
  """Handle button presses."""
627
553
  button_id = event.button.id
628
554
 
629
- if button_id == "send-button":
630
- await self.action_send_message()
631
- elif button_id == "new-chat-button":
632
- await self.create_new_conversation()
633
- elif button_id == "settings-button":
634
- self.push_screen(SettingsScreen())
635
- elif button_id == "view-history-button":
636
- await self.view_chat_history()
555
+ # --- Handle Settings Panel Buttons ---
556
+ if button_id == "settings-cancel-button":
557
+ settings_panel = self.query_one("#settings-panel")
558
+ settings_panel.remove_class("visible")
559
+ self.query_one("#message-input").focus() # Focus input after closing
560
+ elif button_id == "settings-save-button":
561
+ # --- Save Logic ---
562
+ try:
563
+ # Get selected values (assuming selectors update self.selected_model/style directly via events)
564
+ model_to_save = self.selected_model
565
+ style_to_save = self.selected_style
566
+
567
+ # Save globally
568
+ save_settings_to_config(model_to_save, style_to_save)
569
+
570
+ # Update current conversation if one exists
571
+ if self.current_conversation:
572
+ self.db.update_conversation(
573
+ self.current_conversation.id,
574
+ model=model_to_save,
575
+ style=style_to_save
576
+ )
577
+ self.current_conversation.model = model_to_save
578
+ self.current_conversation.style = style_to_save
579
+ self.notify("Settings saved.", severity="information")
580
+ except Exception as e:
581
+ self.notify(f"Error saving settings: {str(e)}", severity="error")
582
+ finally:
583
+ # Hide panel regardless of save success/failure
584
+ settings_panel = self.query_one("#settings-panel")
585
+ settings_panel.remove_class("visible")
586
+ self.query_one("#message-input").focus() # Focus input after closing
587
+
588
+ # --- Keep other button logic if needed (currently none) ---
589
+ # elif button_id == "send-button": # Example if send button existed
590
+ # await self.action_send_message()
637
591
 
638
- async def view_chat_history(self) -> None:
639
- """Show chat history in a popup."""
640
- # Get recent conversations
641
- conversations = self.db.get_all_conversations(limit=CONFIG["max_history_items"])
642
- if not conversations:
643
- self.notify("No chat history found", severity="warning")
644
- return
592
+ async def view_chat_history(self) -> None: # Keep SimpleChatApp view_chat_history
593
+ """Show chat history in a popup.""" # Keep SimpleChatApp view_chat_history docstring
594
+ # Get recent conversations # Keep SimpleChatApp view_chat_history
595
+ conversations = self.db.get_all_conversations(limit=CONFIG["max_history_items"]) # Keep SimpleChatApp view_chat_history
596
+ if not conversations: # Keep SimpleChatApp view_chat_history
597
+ self.notify("No chat history found", severity="warning") # Keep SimpleChatApp view_chat_history
598
+ return # Keep SimpleChatApp view_chat_history
645
599
 
646
- async def handle_selection(selected_id: int) -> None:
647
- if not selected_id:
648
- return
600
+ async def handle_selection(selected_id: int) -> None: # Keep SimpleChatApp view_chat_history
601
+ if not selected_id: # Keep SimpleChatApp view_chat_history
602
+ return # Keep SimpleChatApp view_chat_history
649
603
 
650
- # Get full conversation
651
- conversation_data = self.db.get_conversation(selected_id)
652
- if not conversation_data:
653
- self.notify("Could not load conversation", severity="error")
654
- return
604
+ # Get full conversation # Keep SimpleChatApp view_chat_history
605
+ conversation_data = self.db.get_conversation(selected_id) # Keep SimpleChatApp view_chat_history
606
+ if not conversation_data: # Keep SimpleChatApp view_chat_history
607
+ self.notify("Could not load conversation", severity="error") # Keep SimpleChatApp view_chat_history
608
+ return # Keep SimpleChatApp view_chat_history
655
609
 
656
- # Update current conversation
657
- self.current_conversation = Conversation.from_dict(conversation_data)
610
+ # Update current conversation # Keep SimpleChatApp view_chat_history
611
+ self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp view_chat_history
658
612
 
659
- # Update title
660
- title = self.query_one("#conversation-title", Static)
661
- title.update(self.current_conversation.title)
613
+ # Update title # Keep SimpleChatApp view_chat_history
614
+ title = self.query_one("#conversation-title", Static) # Keep SimpleChatApp view_chat_history
615
+ title.update(self.current_conversation.title) # Keep SimpleChatApp view_chat_history
662
616
 
663
- # Load messages
664
- self.messages = [Message(**msg) for msg in self.current_conversation.messages]
665
- await self.update_messages_ui()
617
+ # Load messages # Keep SimpleChatApp view_chat_history
618
+ self.messages = [Message(**msg) for msg in self.current_conversation.messages] # Keep SimpleChatApp view_chat_history
619
+ await self.update_messages_ui() # Keep SimpleChatApp view_chat_history
666
620
 
667
- # Update model and style selectors
668
- self.selected_model = self.current_conversation.model
669
- self.selected_style = self.current_conversation.style
621
+ # Update model and style selectors # Keep SimpleChatApp view_chat_history
622
+ self.selected_model = self.current_conversation.model # Keep SimpleChatApp view_chat_history
623
+ self.selected_style = self.current_conversation.style # Keep SimpleChatApp view_chat_history
670
624
 
671
- self.push_screen(HistoryScreen(conversations, handle_selection))
625
+ self.push_screen(HistoryScreen(conversations, handle_selection)) # Keep SimpleChatApp view_chat_history
626
+
627
+ async def action_view_history(self) -> None: # Keep SimpleChatApp action_view_history
628
+ """Action to view chat history via key binding.""" # Keep SimpleChatApp action_view_history docstring
629
+ # Only trigger if message input is not focused # Keep SimpleChatApp action_view_history
630
+ input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp action_view_history
631
+ if not input_widget.has_focus: # Keep SimpleChatApp action_view_history
632
+ await self.view_chat_history() # Keep SimpleChatApp action_view_history
633
+
634
+ def action_settings(self) -> None: # Modify SimpleChatApp action_settings
635
+ """Action to open/close settings panel via key binding."""
636
+ # Only trigger if message input is not focused
637
+ input_widget = self.query_one("#message-input", Input)
638
+ if not input_widget.has_focus:
639
+ settings_panel = self.query_one("#settings-panel")
640
+ settings_panel.toggle_class("visible") # Toggle visibility class
641
+ if settings_panel.has_class("visible"):
642
+ # Try focusing the first element in the panel (e.g., ModelSelector)
643
+ try:
644
+ model_selector = settings_panel.query_one(ModelSelector)
645
+ model_selector.focus()
646
+ except Exception:
647
+ pass # Ignore if focus fails
648
+ else:
649
+ input_widget.focus() # Focus input when closing
672
650
 
673
- def main(initial_text: Optional[str] = typer.Argument(None, help="Initial text to start the chat with")):
674
- """Entry point for the chat-cli application"""
675
- # When no argument is provided, typer passes the ArgumentInfo object
676
- # When an argument is provided, typer passes the actual value
677
- if isinstance(initial_text, typer.models.ArgumentInfo):
678
- initial_value = None # No argument provided
679
- else:
680
- initial_value = str(initial_text) if initial_text is not None else None
651
+ def main(initial_text: Optional[str] = typer.Argument(None, help="Initial text to start the chat with")): # Keep main function
652
+ """Entry point for the chat-cli application""" # Keep main function docstring
653
+ # When no argument is provided, typer passes the ArgumentInfo object # Keep main function
654
+ # When an argument is provided, typer passes the actual value # Keep main function
655
+ if isinstance(initial_text, typer.models.ArgumentInfo): # Keep main function
656
+ initial_value = None # No argument provided # Keep main function
657
+ else: # Keep main function
658
+ initial_value = str(initial_text) if initial_text is not None else None # Keep main function
681
659
 
682
- app = SimpleChatApp(initial_text=initial_value)
683
- app.run()
660
+ app = SimpleChatApp(initial_text=initial_value) # Keep main function
661
+ app.run() # Keep main function
684
662
 
685
- if __name__ == "__main__":
686
- typer.run(main)
663
+ if __name__ == "__main__": # Keep main function entry point
664
+ typer.run(main) # Keep main function entry point
app/ui/chat_interface.py CHANGED
@@ -26,37 +26,18 @@ class SendButton(Button):
26
26
  """Custom send button implementation"""
27
27
 
28
28
  DEFAULT_CSS = """
29
+ /* Drastically simplified SendButton CSS */
29
30
  SendButton {
30
- width: auto;
31
- min-width: 20;
32
- height: 4;
33
- margin: 0 1;
34
- content-align: center middle;
35
- text-style: bold;
36
- border: none;
37
- background: $success;
38
- color: white;
39
- padding: 0 2;
40
- text-opacity: 100%;
41
- }
42
-
43
- SendButton:hover {
44
- background: $success-lighten-1;
45
- text-style: bold reverse;
31
+ color: white; /* Basic text color */
32
+ /* Removed most properties */
33
+ margin: 0 1; /* Keep margin for spacing */
46
34
  }
47
35
 
48
- SendButton:focus {
49
- background: $success-darken-1;
50
- text-style: bold reverse;
51
- }
52
-
53
- SendButton > .label {
54
- text-opacity: 100%;
55
- color: white;
56
- text-style: bold;
57
- text-align: center;
58
- width: 100%;
59
- font-size: 200%;
36
+ SendButton > .button--label {
37
+ color: white; /* Basic label color */
38
+ width: auto; /* Ensure label width isn't constrained */
39
+ height: auto; /* Ensure label height isn't constrained */
40
+ /* Removed most properties */
60
41
  }
61
42
  """
62
43
 
app/ui/model_selector.py CHANGED
@@ -142,6 +142,14 @@ class ModelSelector(Container):
142
142
  custom_input = self.query_one("#custom-model-input")
143
143
  custom_input.value = self.selected_model
144
144
  custom_input.remove_class("hide")
145
+
146
+ # Set initial focus on the provider selector after mount completes
147
+ def _focus_provider():
148
+ try:
149
+ self.query_one("#provider-select", Select).focus()
150
+ except Exception as e:
151
+ logger.error(f"Error setting focus in ModelSelector: {e}")
152
+ self.call_later(_focus_provider)
145
153
 
146
154
  async def _get_model_options(self, provider: str) -> List[tuple]:
147
155
  """Get model options for a specific provider"""
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: chat-console
3
- Version: 0.1.81.dev1
3
+ Version: 0.1.95.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
@@ -23,6 +23,7 @@ Dynamic: classifier
23
23
  Dynamic: description
24
24
  Dynamic: description-content-type
25
25
  Dynamic: home-page
26
+ Dynamic: license-file
26
27
  Dynamic: requires-dist
27
28
  Dynamic: requires-python
28
29
  Dynamic: summary
@@ -1,7 +1,7 @@
1
- app/__init__.py,sha256=u5X4kPcpqZ12ZLnhwwOCScNvftaknDTrb0DMXqR_iLc,130
1
+ app/__init__.py,sha256=OeqboIrx_Kjea0CY9Be8nLI-No1YWQfqbWIp-4lMOOI,131
2
2
  app/config.py,sha256=7C09kn2bmda9frTPfZ7f1JhagqHAZjGM5BYqZmhegYM,5190
3
3
  app/database.py,sha256=nt8CVuDpy6zw8mOYqDcfUmNw611t7Ln7pz22M0b6-MI,9967
4
- app/main.py,sha256=zhseBNDgQhKTKbyiJn9eUyUuS4Y7OCqlZGYfpU2iE3A,24366
4
+ app/main.py,sha256=voMYSVgz7_FCM9TCpPXCYU5tQEXuae38gN0qSlk2hCk,37523
5
5
  app/models.py,sha256=4-y9Lytay2exWPFi0FDlVeRL3K2-I7E-jBqNzTfokqY,2644
6
6
  app/utils.py,sha256=zK8aTPdadXomyG2Kgpi7WuC5XYwfShJj74bXWSLtyW0,4309
7
7
  app/api/__init__.py,sha256=A8UL84ldYlv8l7O-yKzraVFcfww86SgWfpl4p7R03-w,62
@@ -10,14 +10,14 @@ app/api/base.py,sha256=-6RSxSpqe-OMwkaq1wVWbu3pVkte-ZYy8rmdvt-Qh48,3953
10
10
  app/api/ollama.py,sha256=zFZ3g2sYncvMgcvx92jTCLkigIaDvTuhILcLiCrwisc,11640
11
11
  app/api/openai.py,sha256=1fYgFXXL6yj_7lQ893Yj28RYG4M8d6gt_q1gzhhjcig,3641
12
12
  app/ui/__init__.py,sha256=RndfbQ1Tv47qdSiuQzvWP96lPS547SDaGE-BgOtiP_w,55
13
- app/ui/chat_interface.py,sha256=2Vtw7yx6BcTQF6KCZQzci6fDA5nTH91QBaOI1N_3liE,13303
13
+ app/ui/chat_interface.py,sha256=4ahB7IpqL3hLzVPsfN6E4uC833_gMvRSzjSVfdjHGUY,13082
14
14
  app/ui/chat_list.py,sha256=WQTYVNSSXlx_gQal3YqILZZKL9UiTjmNMIDX2I9pAMM,11205
15
- app/ui/model_selector.py,sha256=xCuaohgYvebgP0Eel6-XzUn-7Y0SrJUArdTr-CDBZXc,12840
15
+ app/ui/model_selector.py,sha256=Aj1irAs9DQMn8wfcPsFZGxWmx0JTzHjSe7pVdDMwqTQ,13182
16
16
  app/ui/search.py,sha256=b-m14kG3ovqW1-i0qDQ8KnAqFJbi5b1FLM9dOnbTyIs,9763
17
17
  app/ui/styles.py,sha256=04AhPuLrOd2yenfRySFRestPeuTPeMLzhmMB67NdGvw,5615
18
- chat_console-0.1.81.dev1.dist-info/LICENSE,sha256=srHZ3fvcAuZY1LHxE7P6XWju2njRCHyK6h_ftEbzxSE,1057
19
- chat_console-0.1.81.dev1.dist-info/METADATA,sha256=IP3NA1CJ-z7EIU71TwL6dLau4YBS1F92FwPltD_KkJo,2905
20
- chat_console-0.1.81.dev1.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
21
- chat_console-0.1.81.dev1.dist-info/entry_points.txt,sha256=kkVdEc22U9PAi2AeruoKklfkng_a_aHAP6VRVwrAD7c,67
22
- chat_console-0.1.81.dev1.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
23
- chat_console-0.1.81.dev1.dist-info/RECORD,,
18
+ chat_console-0.1.95.dev1.dist-info/licenses/LICENSE,sha256=srHZ3fvcAuZY1LHxE7P6XWju2njRCHyK6h_ftEbzxSE,1057
19
+ chat_console-0.1.95.dev1.dist-info/METADATA,sha256=DEXWGynRBGRE5dJQ60JGRXWMxKFbN6uzdCwA8-vJNV0,2927
20
+ chat_console-0.1.95.dev1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
+ chat_console-0.1.95.dev1.dist-info/entry_points.txt,sha256=kkVdEc22U9PAi2AeruoKklfkng_a_aHAP6VRVwrAD7c,67
22
+ chat_console-0.1.95.dev1.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
23
+ chat_console-0.1.95.dev1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5