chat-console 0.1.96.dev1__py3-none-any.whl → 0.2.0__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 +222 -101
- app/ui/chat_interface.py +16 -13
- app/utils.py +25 -5
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/METADATA +1 -1
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/RECORD +9 -9
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/WHEEL +0 -0
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/entry_points.txt +0 -0
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {chat_console-0.1.96.dev1.dist-info → chat_console-0.2.0.dist-info}/top_level.txt +0 -0
app/main.py
CHANGED
@@ -19,21 +19,24 @@ from openai import OpenAI
|
|
19
19
|
from app.models import Message, Conversation
|
20
20
|
from app.database import ChatDatabase
|
21
21
|
from app.config import CONFIG, OPENAI_API_KEY, ANTHROPIC_API_KEY, OLLAMA_BASE_URL
|
22
|
-
|
22
|
+
# Import InputWithFocus as well
|
23
|
+
from app.ui.chat_interface import MessageDisplay, InputWithFocus
|
23
24
|
from app.ui.model_selector import ModelSelector, StyleSelector
|
24
25
|
from app.ui.chat_list import ChatList
|
25
26
|
from app.api.base import BaseModelClient
|
26
27
|
from app.utils import generate_streaming_response, save_settings_to_config, generate_conversation_title # Import title function
|
28
|
+
# Import version here to avoid potential circular import issues at top level
|
29
|
+
from app import __version__
|
27
30
|
|
28
31
|
# --- Remove SettingsScreen class entirely ---
|
29
32
|
|
30
33
|
class HistoryScreen(Screen):
|
31
34
|
"""Screen for viewing chat history."""
|
32
|
-
|
35
|
+
|
33
36
|
BINDINGS = [
|
34
37
|
Binding("escape", "pop_screen", "Close"),
|
35
38
|
]
|
36
|
-
|
39
|
+
|
37
40
|
CSS = """
|
38
41
|
#history-container {
|
39
42
|
width: 80; # Keep HistoryScreen CSS
|
@@ -42,29 +45,29 @@ class HistoryScreen(Screen):
|
|
42
45
|
border: round $primary;
|
43
46
|
padding: 1; # Keep HistoryScreen CSS
|
44
47
|
}
|
45
|
-
|
48
|
+
|
46
49
|
#title { # Keep HistoryScreen CSS
|
47
50
|
width: 100%; # Keep HistoryScreen CSS
|
48
51
|
content-align: center middle;
|
49
52
|
text-align: center;
|
50
53
|
padding-bottom: 1;
|
51
54
|
}
|
52
|
-
|
55
|
+
|
53
56
|
ListView { # Keep HistoryScreen CSS
|
54
57
|
width: 100%; # Keep HistoryScreen CSS
|
55
58
|
height: 1fr;
|
56
59
|
border: solid $primary;
|
57
60
|
}
|
58
|
-
|
61
|
+
|
59
62
|
ListItem { # Keep HistoryScreen CSS
|
60
63
|
padding: 1; # Keep HistoryScreen CSS
|
61
64
|
border-bottom: solid $primary-darken-2;
|
62
65
|
}
|
63
|
-
|
66
|
+
|
64
67
|
ListItem:hover { # Keep HistoryScreen CSS
|
65
68
|
background: $primary-darken-1; # Keep HistoryScreen CSS
|
66
69
|
}
|
67
|
-
|
70
|
+
|
68
71
|
#button-row { # Keep HistoryScreen CSS
|
69
72
|
width: 100%; # Keep HistoryScreen CSS
|
70
73
|
height: 3;
|
@@ -113,11 +116,16 @@ class HistoryScreen(Screen):
|
|
113
116
|
|
114
117
|
class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
115
118
|
"""Simplified Chat CLI application.""" # Keep SimpleChatApp docstring
|
116
|
-
|
117
|
-
TITLE = "Chat
|
119
|
+
|
120
|
+
TITLE = "Chat Console"
|
118
121
|
SUB_TITLE = "AI Chat Interface" # Keep SimpleChatApp SUB_TITLE
|
119
122
|
DARK = True # Keep SimpleChatApp DARK
|
120
|
-
|
123
|
+
|
124
|
+
# Ensure the log directory exists in a standard cache location
|
125
|
+
log_dir = os.path.expanduser("~/.cache/chat-cli")
|
126
|
+
os.makedirs(log_dir, exist_ok=True)
|
127
|
+
LOG_FILE = os.path.join(log_dir, "textual.log") # Use absolute path
|
128
|
+
|
121
129
|
CSS = """ # Keep SimpleChatApp CSS start
|
122
130
|
#main-content { # Keep SimpleChatApp CSS
|
123
131
|
width: 100%;
|
@@ -125,6 +133,24 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
125
133
|
padding: 0 1;
|
126
134
|
}
|
127
135
|
|
136
|
+
#app-info-bar {
|
137
|
+
width: 100%;
|
138
|
+
height: 1;
|
139
|
+
background: $surface-darken-3;
|
140
|
+
color: $text-muted;
|
141
|
+
padding: 0 1;
|
142
|
+
}
|
143
|
+
|
144
|
+
#version-info {
|
145
|
+
width: auto;
|
146
|
+
text-align: left;
|
147
|
+
}
|
148
|
+
|
149
|
+
#model-info {
|
150
|
+
width: 1fr;
|
151
|
+
text-align: right;
|
152
|
+
}
|
153
|
+
|
128
154
|
#conversation-title { # Keep SimpleChatApp CSS
|
129
155
|
width: 100%; # Keep SimpleChatApp CSS
|
130
156
|
height: 2;
|
@@ -135,6 +161,19 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
135
161
|
border-bottom: solid $primary-darken-2;
|
136
162
|
}
|
137
163
|
|
164
|
+
#action-buttons {
|
165
|
+
width: 100%;
|
166
|
+
height: auto;
|
167
|
+
padding: 0 1; /* Corrected padding: 0 vertical, 1 horizontal */
|
168
|
+
align-horizontal: center;
|
169
|
+
background: $surface-darken-1;
|
170
|
+
}
|
171
|
+
|
172
|
+
#new-chat-button, #change-title-button {
|
173
|
+
margin: 0 1;
|
174
|
+
min-width: 15;
|
175
|
+
}
|
176
|
+
|
138
177
|
#messages-container { # Keep SimpleChatApp CSS
|
139
178
|
width: 100%; # Keep SimpleChatApp CSS
|
140
179
|
height: 1fr;
|
@@ -237,21 +276,22 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
237
276
|
align: center middle;
|
238
277
|
}
|
239
278
|
"""
|
240
|
-
|
279
|
+
|
241
280
|
BINDINGS = [ # Keep SimpleChatApp BINDINGS, ensure Enter is not globally bound for settings
|
242
281
|
Binding("q", "quit", "Quit", show=True, key_display="q"),
|
243
|
-
|
244
|
-
Binding("
|
282
|
+
# Add priority=True to ensure these capture before input
|
283
|
+
Binding("n", "action_new_conversation", "New Chat", show=True, key_display="n", priority=True),
|
284
|
+
Binding("c", "action_new_conversation", "New Chat", show=False, key_display="c", priority=True), # Add priority to alias too
|
245
285
|
Binding("escape", "escape", "Cancel / Stop", show=True, key_display="esc"), # Escape might close settings panel too
|
246
286
|
Binding("ctrl+c", "quit", "Quit", show=False),
|
247
|
-
Binding("h", "view_history", "History", show=True, key_display="h"),
|
248
|
-
Binding("s", "settings", "Settings", show=True, key_display="s"),
|
249
|
-
Binding("t", "action_update_title", "Update Title", show=True, key_display="t"),
|
287
|
+
Binding("h", "view_history", "History", show=True, key_display="h", priority=True), # Add priority
|
288
|
+
Binding("s", "settings", "Settings", show=True, key_display="s", priority=True), # Add priority
|
289
|
+
Binding("t", "action_update_title", "Update Title", show=True, key_display="t", priority=True), # Add priority
|
250
290
|
] # Keep SimpleChatApp BINDINGS end
|
251
|
-
|
291
|
+
|
252
292
|
current_conversation = reactive(None) # Keep SimpleChatApp reactive var
|
253
293
|
is_generating = reactive(False) # Keep SimpleChatApp reactive var
|
254
|
-
|
294
|
+
|
255
295
|
def __init__(self, initial_text: Optional[str] = None): # Keep SimpleChatApp __init__
|
256
296
|
super().__init__() # Keep SimpleChatApp __init__
|
257
297
|
self.db = ChatDatabase() # Keep SimpleChatApp __init__
|
@@ -259,27 +299,38 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
259
299
|
self.selected_model = CONFIG["default_model"] # Keep SimpleChatApp __init__
|
260
300
|
self.selected_style = CONFIG["default_style"] # Keep SimpleChatApp __init__
|
261
301
|
self.initial_text = initial_text # Keep SimpleChatApp __init__
|
262
|
-
|
302
|
+
# Removed self.input_widget instance variable
|
303
|
+
|
263
304
|
def compose(self) -> ComposeResult: # Modify SimpleChatApp compose
|
264
305
|
"""Create the simplified application layout."""
|
265
306
|
yield Header()
|
266
|
-
|
307
|
+
|
267
308
|
with Vertical(id="main-content"):
|
309
|
+
# Add app info bar with version and model info
|
310
|
+
with Horizontal(id="app-info-bar"):
|
311
|
+
yield Static(f"Chat Console v{__version__}", id="version-info") # Use imported version
|
312
|
+
yield Static(f"Model: {self.selected_model}", id="model-info")
|
313
|
+
|
268
314
|
# Conversation title
|
269
315
|
yield Static("New Conversation", id="conversation-title")
|
270
|
-
|
316
|
+
|
317
|
+
# Add action buttons at the top for visibility
|
318
|
+
with Horizontal(id="action-buttons"):
|
319
|
+
yield Button("+ New Chat", id="new-chat-button", variant="success")
|
320
|
+
yield Button("✎ Change Title", id="change-title-button", variant="primary")
|
321
|
+
|
271
322
|
# Messages area
|
272
323
|
with ScrollableContainer(id="messages-container"):
|
273
324
|
# Will be populated with messages
|
274
325
|
pass
|
275
|
-
|
326
|
+
|
276
327
|
# Loading indicator
|
277
328
|
yield Static("Generating response...", id="loading-indicator", classes="hidden")
|
278
|
-
|
329
|
+
|
279
330
|
# Input area
|
280
331
|
with Container(id="input-area"):
|
281
|
-
|
282
|
-
|
332
|
+
# Use the custom InputWithFocus widget
|
333
|
+
yield InputWithFocus(placeholder="Type your message here...", id="message-input")
|
283
334
|
|
284
335
|
# --- Add Settings Panel (hidden initially) ---
|
285
336
|
with Container(id="settings-panel"):
|
@@ -289,18 +340,30 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
289
340
|
with Horizontal(id="settings-buttons"):
|
290
341
|
yield Button("Save", id="settings-save-button", variant="success")
|
291
342
|
yield Button("Cancel", id="settings-cancel-button", variant="error")
|
292
|
-
|
343
|
+
|
293
344
|
yield Footer()
|
294
|
-
|
345
|
+
|
295
346
|
async def on_mount(self) -> None: # Keep SimpleChatApp on_mount
|
296
347
|
"""Initialize the application on mount.""" # Keep SimpleChatApp on_mount docstring
|
348
|
+
# Add diagnostic logging for bindings
|
349
|
+
print(f"Registered bindings: {self.__class__.BINDINGS}") # Corrected access to class attribute
|
350
|
+
|
351
|
+
# Update the version display (already imported at top)
|
352
|
+
try:
|
353
|
+
version_info = self.query_one("#version-info", Static)
|
354
|
+
version_info.update(f"Chat Console v{__version__}")
|
355
|
+
except Exception:
|
356
|
+
pass # Silently ignore if widget not found yet
|
357
|
+
|
358
|
+
self.update_app_info() # Update the model info
|
359
|
+
|
297
360
|
# Check API keys and services # Keep SimpleChatApp on_mount
|
298
361
|
api_issues = [] # Keep SimpleChatApp on_mount
|
299
362
|
if not OPENAI_API_KEY: # Keep SimpleChatApp on_mount
|
300
363
|
api_issues.append("- OPENAI_API_KEY is not set") # Keep SimpleChatApp on_mount
|
301
364
|
if not ANTHROPIC_API_KEY: # Keep SimpleChatApp on_mount
|
302
365
|
api_issues.append("- ANTHROPIC_API_KEY is not set") # Keep SimpleChatApp on_mount
|
303
|
-
|
366
|
+
|
304
367
|
# Check Ollama availability and try to start if not running # Keep SimpleChatApp on_mount
|
305
368
|
from app.utils import ensure_ollama_running # Keep SimpleChatApp on_mount
|
306
369
|
if not ensure_ollama_running(): # Keep SimpleChatApp on_mount
|
@@ -315,7 +378,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
315
378
|
api_issues.append("- No Ollama models found") # Keep SimpleChatApp on_mount
|
316
379
|
except Exception: # Keep SimpleChatApp on_mount
|
317
380
|
api_issues.append("- Error connecting to Ollama server") # Keep SimpleChatApp on_mount
|
318
|
-
|
381
|
+
|
319
382
|
if api_issues: # Keep SimpleChatApp on_mount
|
320
383
|
self.notify( # Keep SimpleChatApp on_mount
|
321
384
|
"Service issues detected:\n" + "\n".join(api_issues) + # Keep SimpleChatApp on_mount
|
@@ -324,10 +387,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
324
387
|
severity="warning", # Keep SimpleChatApp on_mount
|
325
388
|
timeout=10 # Keep SimpleChatApp on_mount
|
326
389
|
) # Keep SimpleChatApp on_mount
|
327
|
-
|
390
|
+
|
328
391
|
# Create a new conversation # Keep SimpleChatApp on_mount
|
329
392
|
await self.create_new_conversation() # Keep SimpleChatApp on_mount
|
330
|
-
|
393
|
+
|
331
394
|
# If initial text was provided, send it # Keep SimpleChatApp on_mount
|
332
395
|
if self.initial_text: # Keep SimpleChatApp on_mount
|
333
396
|
input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp on_mount
|
@@ -335,72 +398,105 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
335
398
|
await self.action_send_message() # Keep SimpleChatApp on_mount
|
336
399
|
else: # Keep SimpleChatApp on_mount
|
337
400
|
# Focus the input if no initial text # Keep SimpleChatApp on_mount
|
401
|
+
# Removed assignment to self.input_widget
|
338
402
|
self.query_one("#message-input").focus() # Keep SimpleChatApp on_mount
|
339
|
-
|
403
|
+
|
340
404
|
async def create_new_conversation(self) -> None: # Keep SimpleChatApp create_new_conversation
|
341
405
|
"""Create a new chat conversation.""" # Keep SimpleChatApp create_new_conversation docstring
|
406
|
+
log("Entering create_new_conversation") # Added log
|
342
407
|
# Create new conversation in database using selected model and style # Keep SimpleChatApp create_new_conversation
|
343
408
|
model = self.selected_model # Keep SimpleChatApp create_new_conversation
|
344
409
|
style = self.selected_style # Keep SimpleChatApp create_new_conversation
|
345
|
-
|
410
|
+
|
346
411
|
# Create a title for the new conversation # Keep SimpleChatApp create_new_conversation
|
347
412
|
title = f"New conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})" # Keep SimpleChatApp create_new_conversation
|
348
|
-
|
413
|
+
|
349
414
|
# Create conversation in database using the correct method # Keep SimpleChatApp create_new_conversation
|
415
|
+
log(f"Creating conversation with title: {title}, model: {model}, style: {style}") # Added log
|
350
416
|
conversation_id = self.db.create_conversation(title, model, style) # Keep SimpleChatApp create_new_conversation
|
351
|
-
|
417
|
+
log(f"Database returned conversation_id: {conversation_id}") # Added log
|
418
|
+
|
352
419
|
# Get the full conversation data # Keep SimpleChatApp create_new_conversation
|
353
420
|
conversation_data = self.db.get_conversation(conversation_id) # Keep SimpleChatApp create_new_conversation
|
354
|
-
|
421
|
+
|
355
422
|
# Set as current conversation # Keep SimpleChatApp create_new_conversation
|
356
423
|
self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp create_new_conversation
|
357
|
-
|
424
|
+
|
358
425
|
# Update UI # Keep SimpleChatApp create_new_conversation
|
359
|
-
|
360
|
-
|
361
|
-
|
426
|
+
title_widget = self.query_one("#conversation-title", Static) # Keep SimpleChatApp create_new_conversation
|
427
|
+
title_widget.update(self.current_conversation.title) # Keep SimpleChatApp create_new_conversation
|
428
|
+
|
362
429
|
# Clear messages and update UI # Keep SimpleChatApp create_new_conversation
|
363
430
|
self.messages = [] # Keep SimpleChatApp create_new_conversation
|
431
|
+
log("Finished updating messages UI in create_new_conversation") # Added log
|
364
432
|
await self.update_messages_ui() # Keep SimpleChatApp create_new_conversation
|
365
|
-
|
433
|
+
self.update_app_info() # Update model info after potentially loading conversation
|
434
|
+
|
366
435
|
async def action_new_conversation(self) -> None: # Keep SimpleChatApp action_new_conversation
|
367
436
|
"""Handle the new conversation action.""" # Keep SimpleChatApp action_new_conversation docstring
|
437
|
+
log("--- ENTERING action_new_conversation ---") # Add entry log
|
438
|
+
# Focus check removed - relying on priority=True in binding
|
439
|
+
|
440
|
+
log("action_new_conversation EXECUTING") # Add execution log
|
368
441
|
await self.create_new_conversation() # Keep SimpleChatApp action_new_conversation
|
369
|
-
|
442
|
+
log("action_new_conversation finished") # Added log
|
443
|
+
|
370
444
|
def action_escape(self) -> None: # Modify SimpleChatApp action_escape
|
371
445
|
"""Handle escape key globally."""
|
446
|
+
log("action_escape triggered") # Added log
|
372
447
|
settings_panel = self.query_one("#settings-panel")
|
448
|
+
log(f"Settings panel visible: {settings_panel.has_class('visible')}") # Added log
|
373
449
|
if settings_panel.has_class("visible"):
|
450
|
+
log("Hiding settings panel") # Added log
|
374
451
|
# If settings panel is visible, hide it
|
375
452
|
settings_panel.remove_class("visible")
|
376
453
|
self.query_one("#message-input").focus() # Focus input after closing settings
|
377
454
|
elif self.is_generating:
|
455
|
+
log("Stopping generation") # Added log
|
378
456
|
# Otherwise, stop generation if running
|
379
457
|
self.is_generating = False # Keep SimpleChatApp action_escape
|
380
458
|
self.notify("Generation stopped", severity="warning") # Keep SimpleChatApp action_escape
|
381
459
|
loading = self.query_one("#loading-indicator") # Keep SimpleChatApp action_escape
|
382
460
|
loading.add_class("hidden") # Keep SimpleChatApp action_escape
|
383
|
-
|
461
|
+
else: # Optional: Add other escape behavior for the main screen if desired # Keep SimpleChatApp action_escape comment
|
462
|
+
log("Escape pressed, but settings not visible and not generating.") # Added log
|
384
463
|
# pass # Keep SimpleChatApp action_escape comment
|
385
464
|
|
386
|
-
|
465
|
+
def update_app_info(self) -> None:
|
466
|
+
"""Update the displayed app information."""
|
467
|
+
try:
|
468
|
+
# Update model info
|
469
|
+
model_info = self.query_one("#model-info", Static)
|
470
|
+
model_display = self.selected_model
|
471
|
+
|
472
|
+
# Try to get a more readable name from config if available
|
473
|
+
if self.selected_model in CONFIG["available_models"]:
|
474
|
+
provider = CONFIG["available_models"][self.selected_model]["provider"]
|
475
|
+
display_name = CONFIG["available_models"][self.selected_model]["display_name"]
|
476
|
+
model_display = f"{display_name} ({provider.capitalize()})"
|
477
|
+
|
478
|
+
model_info.update(f"Model: {model_display}")
|
479
|
+
except Exception as e:
|
480
|
+
# Silently handle errors to prevent crashes
|
481
|
+
log.error(f"Error updating app info: {e}") # Log error instead of passing silently
|
482
|
+
pass
|
387
483
|
|
388
484
|
async def update_messages_ui(self) -> None: # Keep SimpleChatApp update_messages_ui
|
389
485
|
"""Update the messages UI.""" # Keep SimpleChatApp update_messages_ui docstring
|
390
486
|
# Clear existing messages # Keep SimpleChatApp update_messages_ui
|
391
487
|
messages_container = self.query_one("#messages-container") # Keep SimpleChatApp update_messages_ui
|
392
488
|
messages_container.remove_children() # Keep SimpleChatApp update_messages_ui
|
393
|
-
|
489
|
+
|
394
490
|
# Add messages with a small delay between each # Keep SimpleChatApp update_messages_ui
|
395
491
|
for message in self.messages: # Keep SimpleChatApp update_messages_ui
|
396
492
|
display = MessageDisplay(message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp update_messages_ui
|
397
493
|
messages_container.mount(display) # Keep SimpleChatApp update_messages_ui
|
398
494
|
messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
|
399
495
|
await asyncio.sleep(0.01) # Small delay to prevent UI freezing # Keep SimpleChatApp update_messages_ui
|
400
|
-
|
496
|
+
|
401
497
|
# Final scroll to bottom # Keep SimpleChatApp update_messages_ui
|
402
498
|
messages_container.scroll_end(animate=False) # Keep SimpleChatApp update_messages_ui
|
403
|
-
|
499
|
+
|
404
500
|
async def on_input_submitted(self, event: Input.Submitted) -> None: # Keep SimpleChatApp on_input_submitted
|
405
501
|
"""Handle input submission (Enter key in the main input).""" # Keep SimpleChatApp on_input_submitted docstring
|
406
502
|
await self.action_send_message() # Restore direct call # Keep SimpleChatApp on_input_submitted
|
@@ -409,38 +505,38 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
409
505
|
"""Initiate message sending.""" # Keep SimpleChatApp action_send_message docstring
|
410
506
|
input_widget = self.query_one("#message-input", Input) # Keep SimpleChatApp action_send_message
|
411
507
|
content = input_widget.value.strip() # Keep SimpleChatApp action_send_message
|
412
|
-
|
508
|
+
|
413
509
|
if not content or not self.current_conversation: # Keep SimpleChatApp action_send_message
|
414
510
|
return # Keep SimpleChatApp action_send_message
|
415
|
-
|
511
|
+
|
416
512
|
# Clear input # Keep SimpleChatApp action_send_message
|
417
513
|
input_widget.value = "" # Keep SimpleChatApp action_send_message
|
418
|
-
|
514
|
+
|
419
515
|
# Create user message # Keep SimpleChatApp action_send_message
|
420
516
|
user_message = Message(role="user", content=content) # Keep SimpleChatApp action_send_message
|
421
517
|
self.messages.append(user_message) # Keep SimpleChatApp action_send_message
|
422
|
-
|
518
|
+
|
423
519
|
# Save to database # Keep SimpleChatApp action_send_message
|
424
520
|
self.db.add_message( # Keep SimpleChatApp action_send_message
|
425
521
|
self.current_conversation.id, # Keep SimpleChatApp action_send_message
|
426
522
|
"user", # Keep SimpleChatApp action_send_message
|
427
523
|
content # Keep SimpleChatApp action_send_message
|
428
524
|
) # Keep SimpleChatApp action_send_message
|
429
|
-
|
525
|
+
|
430
526
|
# Check if this is the first message in the conversation
|
431
527
|
# Note: We check length *before* adding the potential assistant message
|
432
|
-
is_first_message = len(self.messages) == 1
|
433
|
-
|
528
|
+
is_first_message = len(self.messages) == 1
|
529
|
+
|
434
530
|
# Update UI with user message first
|
435
531
|
await self.update_messages_ui()
|
436
|
-
|
532
|
+
|
437
533
|
# If this is the first message and dynamic titles are enabled, generate one
|
438
534
|
if is_first_message and self.current_conversation and CONFIG.get("generate_dynamic_titles", True):
|
439
535
|
log("First message detected, generating title...")
|
440
536
|
title_generation_in_progress = True # Use a local flag
|
441
537
|
loading = self.query_one("#loading-indicator")
|
442
538
|
loading.remove_class("hidden") # Show loading for title gen
|
443
|
-
|
539
|
+
|
444
540
|
try:
|
445
541
|
# Get appropriate client
|
446
542
|
model = self.selected_model
|
@@ -458,16 +554,16 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
458
554
|
self.current_conversation.id,
|
459
555
|
title=title
|
460
556
|
)
|
461
|
-
|
557
|
+
|
462
558
|
# Update UI title
|
463
559
|
title_widget = self.query_one("#conversation-title", Static)
|
464
560
|
title_widget.update(title)
|
465
|
-
|
561
|
+
|
466
562
|
# Update conversation object
|
467
563
|
self.current_conversation.title = title
|
468
|
-
|
564
|
+
|
469
565
|
self.notify(f"Conversation title set to: {title}", severity="information", timeout=3)
|
470
|
-
|
566
|
+
|
471
567
|
except Exception as e:
|
472
568
|
log.error(f"Failed to generate title: {str(e)}")
|
473
569
|
self.notify(f"Failed to generate title: {str(e)}", severity="warning")
|
@@ -480,24 +576,25 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
480
576
|
|
481
577
|
# Generate AI response (will set self.is_generating and handle loading indicator)
|
482
578
|
await self.generate_response()
|
483
|
-
|
579
|
+
|
484
580
|
# Focus back on input
|
485
581
|
input_widget.focus()
|
486
|
-
|
582
|
+
|
487
583
|
async def generate_response(self) -> None: # Keep SimpleChatApp generate_response
|
488
584
|
"""Generate an AI response.""" # Keep SimpleChatApp generate_response docstring
|
489
585
|
if not self.current_conversation or not self.messages: # Keep SimpleChatApp generate_response
|
490
586
|
return # Keep SimpleChatApp generate_response
|
491
|
-
|
587
|
+
|
492
588
|
self.is_generating = True # Keep SimpleChatApp generate_response
|
589
|
+
log(f"Setting is_generating to True") # Added log
|
493
590
|
loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
|
494
591
|
loading.remove_class("hidden") # Keep SimpleChatApp generate_response
|
495
|
-
|
592
|
+
|
496
593
|
try: # Keep SimpleChatApp generate_response
|
497
594
|
# Get conversation parameters # Keep SimpleChatApp generate_response
|
498
595
|
model = self.selected_model # Keep SimpleChatApp generate_response
|
499
596
|
style = self.selected_style # Keep SimpleChatApp generate_response
|
500
|
-
|
597
|
+
|
501
598
|
# Convert messages to API format # Keep SimpleChatApp generate_response
|
502
599
|
api_messages = [] # Keep SimpleChatApp generate_response
|
503
600
|
for msg in self.messages: # Keep SimpleChatApp generate_response
|
@@ -505,7 +602,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
505
602
|
"role": msg.role, # Keep SimpleChatApp generate_response
|
506
603
|
"content": msg.content # Keep SimpleChatApp generate_response
|
507
604
|
}) # Keep SimpleChatApp generate_response
|
508
|
-
|
605
|
+
|
509
606
|
# Get appropriate client # Keep SimpleChatApp generate_response
|
510
607
|
try: # Keep SimpleChatApp generate_response
|
511
608
|
client = BaseModelClient.get_client_for_model(model) # Keep SimpleChatApp generate_response
|
@@ -514,7 +611,7 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
514
611
|
except Exception as e: # Keep SimpleChatApp generate_response
|
515
612
|
self.notify(f"Failed to initialize model client: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
|
516
613
|
return # Keep SimpleChatApp generate_response
|
517
|
-
|
614
|
+
|
518
615
|
# Start streaming response # Keep SimpleChatApp generate_response
|
519
616
|
assistant_message = Message(role="assistant", content="Thinking...") # Keep SimpleChatApp generate_response
|
520
617
|
self.messages.append(assistant_message) # Keep SimpleChatApp generate_response
|
@@ -522,23 +619,24 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
522
619
|
message_display = MessageDisplay(assistant_message, highlight_code=CONFIG["highlight_code"]) # Keep SimpleChatApp generate_response
|
523
620
|
messages_container.mount(message_display) # Keep SimpleChatApp generate_response
|
524
621
|
messages_container.scroll_end(animate=False) # Keep SimpleChatApp generate_response
|
525
|
-
|
622
|
+
|
526
623
|
# Add small delay to show thinking state # Keep SimpleChatApp generate_response
|
527
624
|
await asyncio.sleep(0.5) # Keep SimpleChatApp generate_response
|
528
|
-
|
625
|
+
|
529
626
|
# Stream chunks to the UI with synchronization # Keep SimpleChatApp generate_response
|
530
627
|
update_lock = asyncio.Lock() # Keep SimpleChatApp generate_response
|
531
|
-
|
628
|
+
|
532
629
|
async def update_ui(content: str): # Keep SimpleChatApp generate_response
|
533
630
|
if not self.is_generating: # Keep SimpleChatApp generate_response
|
631
|
+
log("update_ui called but is_generating is False, returning.") # Added log
|
534
632
|
return # Keep SimpleChatApp generate_response
|
535
|
-
|
633
|
+
|
536
634
|
async with update_lock: # Keep SimpleChatApp generate_response
|
537
635
|
try: # Keep SimpleChatApp generate_response
|
538
636
|
# Clear thinking indicator on first content # Keep SimpleChatApp generate_response
|
539
637
|
if assistant_message.content == "Thinking...": # Keep SimpleChatApp generate_response
|
540
638
|
assistant_message.content = "" # Keep SimpleChatApp generate_response
|
541
|
-
|
639
|
+
|
542
640
|
# Update message with full content so far # Keep SimpleChatApp generate_response
|
543
641
|
assistant_message.content = content # Keep SimpleChatApp generate_response
|
544
642
|
# Update UI with full content # Keep SimpleChatApp generate_response
|
@@ -550,14 +648,15 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
550
648
|
# Force another refresh to ensure content is visible # Keep SimpleChatApp generate_response
|
551
649
|
self.refresh(layout=True) # Keep SimpleChatApp generate_response
|
552
650
|
except Exception as e: # Keep SimpleChatApp generate_response
|
553
|
-
|
554
|
-
|
651
|
+
log.error(f"Error updating UI: {str(e)}") # Use log instead of logger
|
652
|
+
|
555
653
|
# Generate the response with timeout and cleanup # Keep SimpleChatApp generate_response
|
556
654
|
generation_task = None # Keep SimpleChatApp generate_response
|
557
655
|
try: # Keep SimpleChatApp generate_response
|
558
656
|
# Create a task for the response generation # Keep SimpleChatApp generate_response
|
559
657
|
generation_task = asyncio.create_task( # Keep SimpleChatApp generate_response
|
560
658
|
generate_streaming_response( # Keep SimpleChatApp generate_response
|
659
|
+
self, # Pass the app instance
|
561
660
|
api_messages, # Keep SimpleChatApp generate_response
|
562
661
|
model, # Keep SimpleChatApp generate_response
|
563
662
|
style, # Keep SimpleChatApp generate_response
|
@@ -565,12 +664,13 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
565
664
|
update_ui # Keep SimpleChatApp generate_response
|
566
665
|
) # Keep SimpleChatApp generate_response
|
567
666
|
) # Keep SimpleChatApp generate_response
|
568
|
-
|
667
|
+
|
569
668
|
# Wait for response with timeout # Keep SimpleChatApp generate_response
|
570
669
|
full_response = await asyncio.wait_for(generation_task, timeout=60) # Longer timeout # Keep SimpleChatApp generate_response
|
571
|
-
|
670
|
+
|
572
671
|
# Save to database only if we got a complete response # Keep SimpleChatApp generate_response
|
573
672
|
if self.is_generating and full_response: # Keep SimpleChatApp generate_response
|
673
|
+
log("Generation finished, saving full response to DB") # Added log
|
574
674
|
self.db.add_message( # Keep SimpleChatApp generate_response
|
575
675
|
self.current_conversation.id, # Keep SimpleChatApp generate_response
|
576
676
|
"assistant", # Keep SimpleChatApp generate_response
|
@@ -579,57 +679,72 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
579
679
|
# Force a final refresh # Keep SimpleChatApp generate_response
|
580
680
|
self.refresh(layout=True) # Keep SimpleChatApp generate_response
|
581
681
|
await asyncio.sleep(0.1) # Wait for UI to update # Keep SimpleChatApp generate_response
|
582
|
-
|
682
|
+
elif not full_response:
|
683
|
+
log("Generation finished but full_response is empty/None") # Added log
|
684
|
+
|
583
685
|
except asyncio.TimeoutError: # Keep SimpleChatApp generate_response
|
584
|
-
|
686
|
+
log.error("Response generation timed out") # Use log instead of logger
|
585
687
|
error_msg = "Response generation timed out. The model may be busy or unresponsive. Please try again." # Keep SimpleChatApp generate_response
|
586
688
|
self.notify(error_msg, severity="error") # Keep SimpleChatApp generate_response
|
587
|
-
|
689
|
+
|
588
690
|
# Remove the incomplete message # Keep SimpleChatApp generate_response
|
589
691
|
if self.messages and self.messages[-1].role == "assistant": # Keep SimpleChatApp generate_response
|
590
692
|
self.messages.pop() # Keep SimpleChatApp generate_response
|
591
|
-
|
693
|
+
|
592
694
|
# Update UI to remove the incomplete message # Keep SimpleChatApp generate_response
|
593
695
|
await self.update_messages_ui() # Keep SimpleChatApp generate_response
|
594
|
-
|
696
|
+
|
595
697
|
finally: # Keep SimpleChatApp generate_response
|
596
698
|
# Ensure task is properly cancelled and cleaned up # Keep SimpleChatApp generate_response
|
597
699
|
if generation_task: # Keep SimpleChatApp generate_response
|
598
700
|
if not generation_task.done(): # Keep SimpleChatApp generate_response
|
701
|
+
log("Cancelling generation task") # Added log
|
599
702
|
generation_task.cancel() # Keep SimpleChatApp generate_response
|
600
703
|
try: # Keep SimpleChatApp generate_response
|
601
704
|
await generation_task # Keep SimpleChatApp generate_response
|
602
705
|
except (asyncio.CancelledError, Exception) as e: # Keep SimpleChatApp generate_response
|
603
|
-
|
604
|
-
|
706
|
+
log.error(f"Error cleaning up generation task: {str(e)}") # Use log instead of logger
|
707
|
+
|
605
708
|
# Force a final UI refresh # Keep SimpleChatApp generate_response
|
606
709
|
self.refresh(layout=True) # Keep SimpleChatApp generate_response
|
607
|
-
|
710
|
+
|
608
711
|
except Exception as e: # Keep SimpleChatApp generate_response
|
712
|
+
log.error(f"Exception during generate_response: {str(e)}") # Added log
|
609
713
|
self.notify(f"Error generating response: {str(e)}", severity="error") # Keep SimpleChatApp generate_response
|
610
714
|
# Add error message # Keep SimpleChatApp generate_response
|
611
715
|
error_msg = f"Error generating response: {str(e)}" # Keep SimpleChatApp generate_response
|
612
716
|
self.messages.append(Message(role="assistant", content=error_msg)) # Keep SimpleChatApp generate_response
|
613
717
|
await self.update_messages_ui() # Keep SimpleChatApp generate_response
|
614
718
|
finally: # Keep SimpleChatApp generate_response
|
719
|
+
log(f"Setting is_generating to False in finally block") # Added log
|
615
720
|
self.is_generating = False # Keep SimpleChatApp generate_response
|
616
721
|
loading = self.query_one("#loading-indicator") # Keep SimpleChatApp generate_response
|
617
722
|
loading.add_class("hidden") # Keep SimpleChatApp generate_response
|
618
|
-
|
723
|
+
|
619
724
|
def on_model_selector_model_selected(self, event: ModelSelector.ModelSelected) -> None: # Keep SimpleChatApp on_model_selector_model_selected
|
620
725
|
"""Handle model selection""" # Keep SimpleChatApp on_model_selector_model_selected docstring
|
621
726
|
self.selected_model = event.model_id # Keep SimpleChatApp on_model_selector_model_selected
|
622
|
-
|
727
|
+
self.update_app_info() # Update the displayed model info
|
728
|
+
|
623
729
|
def on_style_selector_style_selected(self, event: StyleSelector.StyleSelected) -> None: # Keep SimpleChatApp on_style_selector_style_selected
|
624
730
|
"""Handle style selection""" # Keep SimpleChatApp on_style_selector_style_selected docstring
|
625
731
|
self.selected_style = event.style_id # Keep SimpleChatApp on_style_selector_style_selected
|
626
|
-
|
732
|
+
|
627
733
|
async def on_button_pressed(self, event: Button.Pressed) -> None: # Modify SimpleChatApp on_button_pressed
|
628
734
|
"""Handle button presses."""
|
629
735
|
button_id = event.button.id
|
630
|
-
|
736
|
+
|
737
|
+
if button_id == "new-chat-button":
|
738
|
+
# Create a new chat
|
739
|
+
await self.create_new_conversation()
|
740
|
+
# Focus back on input after creating new chat
|
741
|
+
self.query_one("#message-input").focus()
|
742
|
+
elif button_id == "change-title-button":
|
743
|
+
# Change title
|
744
|
+
# Note: action_update_title already checks self.current_conversation
|
745
|
+
await self.action_update_title()
|
631
746
|
# --- Handle Settings Panel Buttons ---
|
632
|
-
|
747
|
+
elif button_id == "settings-cancel-button":
|
633
748
|
settings_panel = self.query_one("#settings-panel")
|
634
749
|
settings_panel.remove_class("visible")
|
635
750
|
self.query_one("#message-input").focus() # Focus input after closing
|
@@ -639,10 +754,10 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
639
754
|
# Get selected values (assuming selectors update self.selected_model/style directly via events)
|
640
755
|
model_to_save = self.selected_model
|
641
756
|
style_to_save = self.selected_style
|
642
|
-
|
757
|
+
|
643
758
|
# Save globally
|
644
759
|
save_settings_to_config(model_to_save, style_to_save)
|
645
|
-
|
760
|
+
|
646
761
|
# Update current conversation if one exists
|
647
762
|
if self.current_conversation:
|
648
763
|
self.db.update_conversation(
|
@@ -660,11 +775,11 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
660
775
|
settings_panel = self.query_one("#settings-panel")
|
661
776
|
settings_panel.remove_class("visible")
|
662
777
|
self.query_one("#message-input").focus() # Focus input after closing
|
663
|
-
|
778
|
+
|
664
779
|
# --- Keep other button logic if needed (currently none) ---
|
665
780
|
# elif button_id == "send-button": # Example if send button existed
|
666
781
|
# await self.action_send_message()
|
667
|
-
|
782
|
+
|
668
783
|
async def view_chat_history(self) -> None: # Keep SimpleChatApp view_chat_history
|
669
784
|
"""Show chat history in a popup.""" # Keep SimpleChatApp view_chat_history docstring
|
670
785
|
# Get recent conversations # Keep SimpleChatApp view_chat_history
|
@@ -672,32 +787,33 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
672
787
|
if not conversations: # Keep SimpleChatApp view_chat_history
|
673
788
|
self.notify("No chat history found", severity="warning") # Keep SimpleChatApp view_chat_history
|
674
789
|
return # Keep SimpleChatApp view_chat_history
|
675
|
-
|
790
|
+
|
676
791
|
async def handle_selection(selected_id: int) -> None: # Keep SimpleChatApp view_chat_history
|
677
792
|
if not selected_id: # Keep SimpleChatApp view_chat_history
|
678
793
|
return # Keep SimpleChatApp view_chat_history
|
679
|
-
|
794
|
+
|
680
795
|
# Get full conversation # Keep SimpleChatApp view_chat_history
|
681
796
|
conversation_data = self.db.get_conversation(selected_id) # Keep SimpleChatApp view_chat_history
|
682
797
|
if not conversation_data: # Keep SimpleChatApp view_chat_history
|
683
798
|
self.notify("Could not load conversation", severity="error") # Keep SimpleChatApp view_chat_history
|
684
799
|
return # Keep SimpleChatApp view_chat_history
|
685
|
-
|
800
|
+
|
686
801
|
# Update current conversation # Keep SimpleChatApp view_chat_history
|
687
802
|
self.current_conversation = Conversation.from_dict(conversation_data) # Keep SimpleChatApp view_chat_history
|
688
|
-
|
803
|
+
|
689
804
|
# Update title # Keep SimpleChatApp view_chat_history
|
690
805
|
title = self.query_one("#conversation-title", Static) # Keep SimpleChatApp view_chat_history
|
691
806
|
title.update(self.current_conversation.title) # Keep SimpleChatApp view_chat_history
|
692
|
-
|
807
|
+
|
693
808
|
# Load messages # Keep SimpleChatApp view_chat_history
|
694
809
|
self.messages = [Message(**msg) for msg in self.current_conversation.messages] # Keep SimpleChatApp view_chat_history
|
695
810
|
await self.update_messages_ui() # Keep SimpleChatApp view_chat_history
|
696
|
-
|
811
|
+
|
697
812
|
# Update model and style selectors # Keep SimpleChatApp view_chat_history
|
698
813
|
self.selected_model = self.current_conversation.model # Keep SimpleChatApp view_chat_history
|
699
814
|
self.selected_style = self.current_conversation.style # Keep SimpleChatApp view_chat_history
|
700
|
-
|
815
|
+
self.update_app_info() # Update info bar after loading history
|
816
|
+
|
701
817
|
self.push_screen(HistoryScreen(conversations, handle_selection)) # Keep SimpleChatApp view_chat_history
|
702
818
|
|
703
819
|
async def action_view_history(self) -> None: # Keep SimpleChatApp action_view_history
|
@@ -726,6 +842,11 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
|
|
726
842
|
|
727
843
|
async def action_update_title(self) -> None:
|
728
844
|
"""Allow users to manually change the conversation title"""
|
845
|
+
log("--- ENTERING action_update_title ---") # Add entry log
|
846
|
+
# Focus check removed - relying on priority=True in binding
|
847
|
+
|
848
|
+
log("action_update_title EXECUTING") # Add execution log
|
849
|
+
|
729
850
|
if not self.current_conversation:
|
730
851
|
self.notify("No active conversation", severity="warning")
|
731
852
|
return
|
app/ui/chat_interface.py
CHANGED
@@ -129,20 +129,23 @@ class MessageDisplay(RichLog):
|
|
129
129
|
|
130
130
|
class InputWithFocus(Input):
|
131
131
|
"""Enhanced Input that better handles focus and maintains cursor position"""
|
132
|
-
|
132
|
+
# Reverted on_key to default Input behavior for 'n' and 't'
|
133
|
+
# Let the standard Input handle key presses when focused.
|
134
|
+
# We will rely on focus checks within the App's action methods.
|
135
|
+
|
136
|
+
# Keep custom handling only for Enter submission if needed,
|
137
|
+
# but standard Input might already do this. Let's simplify
|
138
|
+
# and remove the custom on_key entirely for now unless
|
133
139
|
def on_key(self, event) -> None:
|
134
|
-
|
135
|
-
#
|
136
|
-
if event.
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
# Normal input handling for other keys
|
145
|
-
super().on_key(event)
|
140
|
+
# Let global hotkeys 'n' and 't' pass through even when input has focus
|
141
|
+
# by simply *not* stopping the event here.
|
142
|
+
if event.key == "n" or event.key == "t":
|
143
|
+
# Do nothing, allow the event to bubble up to the app level bindings.
|
144
|
+
return # Explicitly return to prevent further processing in this method
|
145
|
+
|
146
|
+
# For all other keys, the event continues to be processed by the Input
|
147
|
+
# widget's internal handlers (like _on_key shown in the traceback)
|
148
|
+
# because we didn't stop it in this method.
|
146
149
|
|
147
150
|
class ChatInterface(Container):
|
148
151
|
"""Main chat interface container"""
|
app/utils.py
CHANGED
@@ -4,10 +4,14 @@ import time
|
|
4
4
|
import asyncio
|
5
5
|
import subprocess
|
6
6
|
import logging
|
7
|
-
from typing import Optional, Dict, Any, List
|
7
|
+
from typing import Optional, Dict, Any, List, TYPE_CHECKING
|
8
8
|
from datetime import datetime
|
9
9
|
from .config import CONFIG, save_config
|
10
10
|
|
11
|
+
# Import SimpleChatApp for type hinting only if TYPE_CHECKING is True
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from .main import SimpleChatApp
|
14
|
+
|
11
15
|
# Set up logging
|
12
16
|
logging.basicConfig(level=logging.INFO)
|
13
17
|
logger = logging.getLogger(__name__)
|
@@ -74,7 +78,8 @@ async def generate_conversation_title(message: str, model: str, client: Any) ->
|
|
74
78
|
logger.error(f"Failed to generate title after multiple retries. Last error: {last_error}")
|
75
79
|
return f"Conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})"
|
76
80
|
|
77
|
-
|
81
|
+
# Modified signature to accept app instance
|
82
|
+
async def generate_streaming_response(app: 'SimpleChatApp', messages: List[Dict], model: str, style: str, client: Any, callback: Any) -> str:
|
78
83
|
"""Generate a streaming response from the model"""
|
79
84
|
logger.info(f"Starting streaming response with model: {model}")
|
80
85
|
full_response = ""
|
@@ -84,6 +89,11 @@ async def generate_streaming_response(messages: List[Dict], model: str, style: s
|
|
84
89
|
|
85
90
|
try:
|
86
91
|
async for chunk in client.generate_stream(messages, model, style):
|
92
|
+
# Check if generation was cancelled by the app (e.g., via escape key)
|
93
|
+
if not app.is_generating:
|
94
|
+
logger.info("Generation cancelled by app flag.")
|
95
|
+
break # Exit the loop immediately
|
96
|
+
|
87
97
|
if chunk: # Only process non-empty chunks
|
88
98
|
buffer.append(chunk)
|
89
99
|
current_time = time.time()
|
@@ -92,6 +102,10 @@ async def generate_streaming_response(messages: List[Dict], model: str, style: s
|
|
92
102
|
if current_time - last_update >= update_interval or len(''.join(buffer)) > 100:
|
93
103
|
new_content = ''.join(buffer)
|
94
104
|
full_response += new_content
|
105
|
+
# Check again before calling callback, in case it was cancelled during chunk processing
|
106
|
+
if not app.is_generating:
|
107
|
+
logger.info("Generation cancelled before UI update.")
|
108
|
+
break
|
95
109
|
await callback(full_response)
|
96
110
|
buffer = []
|
97
111
|
last_update = current_time
|
@@ -99,16 +113,22 @@ async def generate_streaming_response(messages: List[Dict], model: str, style: s
|
|
99
113
|
# Small delay to let UI catch up
|
100
114
|
await asyncio.sleep(0.05)
|
101
115
|
|
102
|
-
# Send any remaining content
|
103
|
-
if buffer:
|
116
|
+
# Send any remaining content if generation wasn't cancelled
|
117
|
+
if buffer and app.is_generating:
|
104
118
|
new_content = ''.join(buffer)
|
105
119
|
full_response += new_content
|
106
120
|
await callback(full_response)
|
107
121
|
|
108
|
-
|
122
|
+
if app.is_generating:
|
123
|
+
logger.info("Streaming response completed normally.")
|
124
|
+
else:
|
125
|
+
logger.info("Streaming response loop exited due to cancellation.")
|
126
|
+
|
109
127
|
return full_response
|
110
128
|
except Exception as e:
|
111
129
|
logger.error(f"Error in streaming response: {str(e)}")
|
130
|
+
# Ensure the app knows generation stopped on error
|
131
|
+
app.is_generating = False
|
112
132
|
raise
|
113
133
|
|
114
134
|
def ensure_ollama_running() -> bool:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: chat-console
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
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,23 +1,23 @@
|
|
1
1
|
app/__init__.py,sha256=OeqboIrx_Kjea0CY9Be8nLI-No1YWQfqbWIp-4lMOOI,131
|
2
2
|
app/config.py,sha256=sKNp6Za4ZfW-CZBOvEv0TncAS77AnKi86hTM51C4KQ4,5227
|
3
3
|
app/database.py,sha256=nt8CVuDpy6zw8mOYqDcfUmNw611t7Ln7pz22M0b6-MI,9967
|
4
|
-
app/main.py,sha256=
|
4
|
+
app/main.py,sha256=PMx9DcD2MAVyqeYanqu6mqr43RG4oXA9jFIdBwoQFQY,48174
|
5
5
|
app/models.py,sha256=4-y9Lytay2exWPFi0FDlVeRL3K2-I7E-jBqNzTfokqY,2644
|
6
|
-
app/utils.py,sha256=
|
6
|
+
app/utils.py,sha256=AgmLrmyikt1Y7KturNmZVK2eC6de-RfRadVbp3HmUAg,8434
|
7
7
|
app/api/__init__.py,sha256=A8UL84ldYlv8l7O-yKzraVFcfww86SgWfpl4p7R03-w,62
|
8
8
|
app/api/anthropic.py,sha256=x5PmBXEKe_ow2NWk8XdqSPR0hLOdCc_ypY5QAySeA78,4234
|
9
9
|
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=
|
13
|
+
app/ui/chat_interface.py,sha256=VwmVvltxS9l18DI9U7kL43t8kSPPNsrkkrrUSoGu16Q,13623
|
14
14
|
app/ui/chat_list.py,sha256=WQTYVNSSXlx_gQal3YqILZZKL9UiTjmNMIDX2I9pAMM,11205
|
15
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.
|
19
|
-
chat_console-0.
|
20
|
-
chat_console-0.
|
21
|
-
chat_console-0.
|
22
|
-
chat_console-0.
|
23
|
-
chat_console-0.
|
18
|
+
chat_console-0.2.0.dist-info/licenses/LICENSE,sha256=srHZ3fvcAuZY1LHxE7P6XWju2njRCHyK6h_ftEbzxSE,1057
|
19
|
+
chat_console-0.2.0.dist-info/METADATA,sha256=DPnX1Q1e7iTfNyyQRhJezaUXuHtq1E9mqb7k7gDe0GI,2921
|
20
|
+
chat_console-0.2.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
21
|
+
chat_console-0.2.0.dist-info/entry_points.txt,sha256=kkVdEc22U9PAi2AeruoKklfkng_a_aHAP6VRVwrAD7c,67
|
22
|
+
chat_console-0.2.0.dist-info/top_level.txt,sha256=io9g7LCbfmTG1SFKgEOGXmCFB9uMP2H5lerm0HiHWQE,4
|
23
|
+
chat_console-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|