chat-console 0.1.9.dev1__py3-none-any.whl → 0.1.95__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/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,56 +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
- /* Removed CSS for #send-button, #new-chat-button, #view-history-button, #settings-button */
262
- /* Removed CSS for #button-row */
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
182
+
183
+ #settings-panel { /* Add CSS for the new settings panel */
184
+ display: none; /* Hidden by default */
185
+ align: center middle;
186
+ width: 60;
187
+ height: auto;
188
+ background: $surface;
189
+ border: thick $primary;
190
+ padding: 1 2;
191
+ layer: settings; /* Ensure it's above other elements */
192
+ }
193
+
194
+ #settings-panel.visible { /* Class to show the panel */
195
+ display: block;
196
+ }
197
+
198
+ #settings-title {
199
+ width: 100%;
200
+ content-align: center middle;
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;
210
+ }
263
211
 
264
212
  """
265
213
 
266
- BINDINGS = [
267
- Binding("q", "quit", "Quit"),
268
- Binding("n", "action_new_conversation", "New Chat"),
269
- Binding("escape", "escape", "Cancel"),
270
- Binding("ctrl+c", "quit", "Quit"),
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),
271
220
  Binding("h", "view_history", "History", show=True, key_display="h"),
272
221
  Binding("s", "settings", "Settings", show=True, key_display="s"),
273
- ]
222
+ ] # Keep SimpleChatApp BINDINGS end
274
223
 
275
- current_conversation = reactive(None)
276
- is_generating = reactive(False)
224
+ current_conversation = reactive(None) # Keep SimpleChatApp reactive var
225
+ is_generating = reactive(False) # Keep SimpleChatApp reactive var
277
226
 
278
- def __init__(self, initial_text: Optional[str] = None):
279
- super().__init__()
280
- self.db = ChatDatabase()
281
- self.messages = []
282
- self.selected_model = CONFIG["default_model"]
283
- self.selected_style = CONFIG["default_style"]
284
- 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__
285
234
 
286
- def compose(self) -> ComposeResult:
235
+ def compose(self) -> ComposeResult: # Modify SimpleChatApp compose
287
236
  """Create the simplified application layout."""
288
237
  yield Header()
289
238
 
@@ -303,359 +252,413 @@ class SimpleChatApp(App):
303
252
  with Container(id="input-area"):
304
253
  yield Input(placeholder="Type your message here...", id="message-input")
305
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")
306
264
 
307
265
  yield Footer()
308
266
 
309
- async def on_mount(self) -> None:
310
- """Initialize the application on mount."""
311
- # Check API keys and services
312
- api_issues = []
313
- if not OPENAI_API_KEY:
314
- api_issues.append("- OPENAI_API_KEY is not set")
315
- if not ANTHROPIC_API_KEY:
316
- 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
317
275
 
318
- # Check Ollama availability and try to start if not running
319
- from app.utils import ensure_ollama_running
320
- if not ensure_ollama_running():
321
- api_issues.append("- Ollama server not running and could not be started")
322
- else:
323
- # Check for available models
324
- from app.api.ollama import OllamaClient
325
- try:
326
- ollama = OllamaClient()
327
- models = await ollama.get_available_models()
328
- if not models:
329
- api_issues.append("- No Ollama models found")
330
- except Exception:
331
- 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
332
290
 
333
- if api_issues:
334
- self.notify(
335
- "Service issues detected:\n" + "\n".join(api_issues) +
336
- "\n\nEnsure services are configured and running.",
337
- title="Service Warning",
338
- severity="warning",
339
- timeout=10
340
- )
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
341
299
 
342
- # Create a new conversation
343
- await self.create_new_conversation()
300
+ # Create a new conversation # Keep SimpleChatApp on_mount
301
+ await self.create_new_conversation() # Keep SimpleChatApp on_mount
344
302
 
345
- # If initial text was provided, send it
346
- if self.initial_text:
347
- input_widget = self.query_one("#message-input", Input)
348
- input_widget.value = self.initial_text
349
- await self.action_send_message()
350
- else:
351
- # Focus the input if no initial text
352
- 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
353
311
 
354
- async def create_new_conversation(self) -> None:
355
- """Create a new chat conversation."""
356
- # Create new conversation in database using selected model and style
357
- model = self.selected_model
358
- 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
359
317
 
360
- # Create a title for the new conversation
361
- 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
362
320
 
363
- # Create conversation in database using the correct method
364
- 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
365
323
 
366
- # Get the full conversation data
367
- 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
368
326
 
369
- # Set as current conversation
370
- 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
371
329
 
372
- # Update UI
373
- title = self.query_one("#conversation-title", Static)
374
- 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
375
333
 
376
- # Clear messages and update UI
377
- self.messages = []
378
- 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
379
337
 
380
- async def action_new_conversation(self) -> None:
381
- """Handle the new conversation action."""
382
- 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
383
341
 
384
- def action_escape(self) -> None:
385
- """Handle escape key."""
386
- if self.is_generating:
387
- self.is_generating = False
388
- self.notify("Generation stopped", severity="warning")
389
- loading = self.query_one("#loading-indicator")
390
- loading.add_class("hidden")
391
- elif self.screen is not self.screen_stack[-1]:
392
- # If we're in a sub-screen, pop it
393
- self.pop_screen()
394
-
395
- async def update_messages_ui(self) -> None:
396
- """Update the messages UI."""
397
- # Clear existing messages
398
- messages_container = self.query_one("#messages-container")
399
- 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
400
365
 
401
- # Add messages with a small delay between each
402
- for message in self.messages:
403
- display = MessageDisplay(message, highlight_code=CONFIG["highlight_code"])
404
- messages_container.mount(display)
405
- messages_container.scroll_end(animate=False)
406
- 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
407
372
 
408
- # Final scroll to bottom
409
- messages_container.scroll_end(animate=False)
410
-
411
- async def on_input_submitted(self, event: Input.Submitted) -> None:
412
- """Handle input submission."""
413
- 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
414
375
 
415
- async def action_send_message(self) -> None:
416
- """Initiate message sending."""
417
- input_widget = self.query_one("#message-input", Input)
418
- 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
419
384
 
420
- if not content or not self.current_conversation:
421
- return
385
+ if not content or not self.current_conversation: # Keep SimpleChatApp action_send_message
386
+ return # Keep SimpleChatApp action_send_message
422
387
 
423
- # Clear input
424
- input_widget.value = ""
388
+ # Clear input # Keep SimpleChatApp action_send_message
389
+ input_widget.value = "" # Keep SimpleChatApp action_send_message
425
390
 
426
- # Create user message
427
- user_message = Message(role="user", content=content)
428
- 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
429
394
 
430
- # Save to database
431
- self.db.add_message(
432
- self.current_conversation.id,
433
- "user",
434
- content
435
- )
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
436
401
 
437
- # Update UI
438
- await self.update_messages_ui()
402
+ # Update UI # Keep SimpleChatApp action_send_message
403
+ await self.update_messages_ui() # Keep SimpleChatApp action_send_message
439
404
 
440
- # Generate AI response
441
- await self.generate_response()
405
+ # Generate AI response # Keep SimpleChatApp action_send_message
406
+ await self.generate_response() # Keep SimpleChatApp action_send_message
442
407
 
443
- # Focus back on input
444
- input_widget.focus()
408
+ # Focus back on input # Keep SimpleChatApp action_send_message
409
+ input_widget.focus() # Keep SimpleChatApp action_send_message
445
410
 
446
- async def generate_response(self) -> None:
447
- """Generate an AI response."""
448
- if not self.current_conversation or not self.messages:
449
- 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
450
415
 
451
- self.is_generating = True
452
- loading = self.query_one("#loading-indicator")
453
- 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
454
419
 
455
- try:
456
- # Get conversation parameters
457
- model = self.selected_model
458
- 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
459
424
 
460
- # Convert messages to API format
461
- api_messages = []
462
- for msg in self.messages:
463
- api_messages.append({
464
- "role": msg.role,
465
- "content": msg.content
466
- })
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
467
432
 
468
- # Get appropriate client
469
- try:
470
- client = BaseModelClient.get_client_for_model(model)
471
- if client is None:
472
- raise Exception(f"No client available for model: {model}")
473
- except Exception as e:
474
- self.notify(f"Failed to initialize model client: {str(e)}", severity="error")
475
- 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
476
441
 
477
- # Start streaming response
478
- assistant_message = Message(role="assistant", content="Thinking...")
479
- self.messages.append(assistant_message)
480
- messages_container = self.query_one("#messages-container")
481
- message_display = MessageDisplay(assistant_message, highlight_code=CONFIG["highlight_code"])
482
- messages_container.mount(message_display)
483
- 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
484
449
 
485
- # Add small delay to show thinking state
486
- 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
487
452
 
488
- # Stream chunks to the UI with synchronization
489
- 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
490
455
 
491
- async def update_ui(content: str):
492
- if not self.is_generating:
493
- 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
494
459
 
495
- async with update_lock:
496
- try:
497
- # Clear thinking indicator on first content
498
- if assistant_message.content == "Thinking...":
499
- 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
500
465
 
501
- # Update message with full content so far
502
- assistant_message.content = content
503
- # Update UI with full content
504
- await message_display.update_content(content)
505
- # Force a refresh and scroll
506
- self.refresh(layout=True)
507
- await asyncio.sleep(0.05) # Longer delay for UI stability
508
- messages_container.scroll_end(animate=False)
509
- # Force another refresh to ensure content is visible
510
- self.refresh(layout=True)
511
- except Exception as e:
512
- 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
513
478
 
514
- # Generate the response with timeout and cleanup
515
- generation_task = None
516
- try:
517
- # Create a task for the response generation
518
- generation_task = asyncio.create_task(
519
- generate_streaming_response(
520
- api_messages,
521
- model,
522
- style,
523
- client,
524
- update_ui
525
- )
526
- )
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
527
492
 
528
- # Wait for response with timeout
529
- 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
530
495
 
531
- # Save to database only if we got a complete response
532
- if self.is_generating and full_response:
533
- self.db.add_message(
534
- self.current_conversation.id,
535
- "assistant",
536
- full_response
537
- )
538
- # Force a final refresh
539
- self.refresh(layout=True)
540
- 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
541
506
 
542
- except asyncio.TimeoutError:
543
- logger.error("Response generation timed out")
544
- error_msg = "Response generation timed out. The model may be busy or unresponsive. Please try again."
545
- 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
546
511
 
547
- # Remove the incomplete message
548
- if self.messages and self.messages[-1].role == "assistant":
549
- 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
550
515
 
551
- # Update UI to remove the incomplete message
552
- 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
553
518
 
554
- finally:
555
- # Ensure task is properly cancelled and cleaned up
556
- if generation_task:
557
- if not generation_task.done():
558
- generation_task.cancel()
559
- try:
560
- await generation_task
561
- except (asyncio.CancelledError, Exception) as e:
562
- 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
563
528
 
564
- # Force a final UI refresh
565
- self.refresh(layout=True)
529
+ # Force a final UI refresh # Keep SimpleChatApp generate_response
530
+ self.refresh(layout=True) # Keep SimpleChatApp generate_response
566
531
 
567
- except Exception as e:
568
- self.notify(f"Error generating response: {str(e)}", severity="error")
569
- # Add error message
570
- error_msg = f"Error generating response: {str(e)}"
571
- self.messages.append(Message(role="assistant", content=error_msg))
572
- await self.update_messages_ui()
573
- finally:
574
- self.is_generating = False
575
- loading = self.query_one("#loading-indicator")
576
- 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
577
542
 
578
- def on_model_selector_model_selected(self, event: ModelSelector.ModelSelected) -> None:
579
- """Handle model selection"""
580
- self.selected_model = event.model_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
581
546
 
582
- def on_style_selector_style_selected(self, event: StyleSelector.StyleSelected) -> None:
583
- """Handle style selection"""
584
- self.selected_style = event.style_id
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
585
550
 
586
- async def on_button_pressed(self, event: Button.Pressed) -> None:
551
+ async def on_button_pressed(self, event: Button.Pressed) -> None: # Modify SimpleChatApp on_button_pressed
587
552
  """Handle button presses."""
588
553
  button_id = event.button.id
589
554
 
590
- if button_id == "send-button":
591
- await self.action_send_message()
592
- elif button_id == "new-chat-button":
593
- await self.create_new_conversation()
594
- elif button_id == "settings-button":
595
- self.push_screen(SettingsScreen())
596
- elif button_id == "view-history-button":
597
- 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()
598
591
 
599
- async def view_chat_history(self) -> None:
600
- """Show chat history in a popup."""
601
- # Get recent conversations
602
- conversations = self.db.get_all_conversations(limit=CONFIG["max_history_items"])
603
- if not conversations:
604
- self.notify("No chat history found", severity="warning")
605
- 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
606
599
 
607
- async def handle_selection(selected_id: int) -> None:
608
- if not selected_id:
609
- 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
610
603
 
611
- # Get full conversation
612
- conversation_data = self.db.get_conversation(selected_id)
613
- if not conversation_data:
614
- self.notify("Could not load conversation", severity="error")
615
- 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
616
609
 
617
- # Update current conversation
618
- 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
619
612
 
620
- # Update title
621
- title = self.query_one("#conversation-title", Static)
622
- 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
623
616
 
624
- # Load messages
625
- self.messages = [Message(**msg) for msg in self.current_conversation.messages]
626
- 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
627
620
 
628
- # Update model and style selectors
629
- self.selected_model = self.current_conversation.model
630
- 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
631
624
 
632
- self.push_screen(HistoryScreen(conversations, handle_selection))
625
+ self.push_screen(HistoryScreen(conversations, handle_selection)) # Keep SimpleChatApp view_chat_history
633
626
 
634
- async def action_view_history(self) -> None:
635
- """Action to view chat history 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
- await self.view_chat_history()
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
640
633
 
641
- def action_settings(self) -> None:
642
- """Action to open settings via key binding."""
634
+ def action_settings(self) -> None: # Modify SimpleChatApp action_settings
635
+ """Action to open/close settings panel via key binding."""
643
636
  # Only trigger if message input is not focused
644
637
  input_widget = self.query_one("#message-input", Input)
645
638
  if not input_widget.has_focus:
646
- self.push_screen(SettingsScreen())
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
647
650
 
648
- def main(initial_text: Optional[str] = typer.Argument(None, help="Initial text to start the chat with")):
649
- """Entry point for the chat-cli application"""
650
- # When no argument is provided, typer passes the ArgumentInfo object
651
- # When an argument is provided, typer passes the actual value
652
- if isinstance(initial_text, typer.models.ArgumentInfo):
653
- initial_value = None # No argument provided
654
- else:
655
- 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
656
659
 
657
- app = SimpleChatApp(initial_text=initial_value)
658
- app.run()
660
+ app = SimpleChatApp(initial_text=initial_value) # Keep main function
661
+ app.run() # Keep main function
659
662
 
660
- if __name__ == "__main__":
661
- typer.run(main)
663
+ if __name__ == "__main__": # Keep main function entry point
664
+ typer.run(main) # Keep main function entry point
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
1
  Metadata-Version: 2.4
2
2
  Name: chat-console
3
- Version: 0.1.9.dev1
3
+ Version: 0.1.95
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
@@ -1,7 +1,7 @@
1
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=7RbLeFWRwZEqZvYijvUTvZmngXGeKVZx7MH4mPiW2jA,23972
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
@@ -12,12 +12,12 @@ app/api/openai.py,sha256=1fYgFXXL6yj_7lQ893Yj28RYG4M8d6gt_q1gzhhjcig,3641
12
12
  app/ui/__init__.py,sha256=RndfbQ1Tv47qdSiuQzvWP96lPS547SDaGE-BgOtiP_w,55
13
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.9.dev1.dist-info/licenses/LICENSE,sha256=srHZ3fvcAuZY1LHxE7P6XWju2njRCHyK6h_ftEbzxSE,1057
19
- chat_console-0.1.9.dev1.dist-info/METADATA,sha256=VQorzuMOChtzRwlOf88fnHEdbtDpUi-PMIYGDVoK9AY,2926
20
- chat_console-0.1.9.dev1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
- chat_console-0.1.9.dev1.dist-info/entry_points.txt,sha256=kkVdEc22U9PAi2AeruoKklfkng_a_aHAP6VRVwrAD7c,67
22
- chat_console-0.1.9.dev1.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
23
- chat_console-0.1.9.dev1.dist-info/RECORD,,
18
+ chat_console-0.1.95.dist-info/licenses/LICENSE,sha256=srHZ3fvcAuZY1LHxE7P6XWju2njRCHyK6h_ftEbzxSE,1057
19
+ chat_console-0.1.95.dist-info/METADATA,sha256=A2RQkU8TQgvEuL0rC9kK36i7laNoynOau9vI4rr4dGY,2922
20
+ chat_console-0.1.95.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
21
+ chat_console-0.1.95.dist-info/entry_points.txt,sha256=kkVdEc22U9PAi2AeruoKklfkng_a_aHAP6VRVwrAD7c,67
22
+ chat_console-0.1.95.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
23
+ chat_console-0.1.95.dist-info/RECORD,,