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