chat-console 0.4.2__py3-none-any.whl → 0.4.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
app/main.py CHANGED
@@ -179,173 +179,276 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
179
179
  os.makedirs(log_dir, exist_ok=True)
180
180
  LOG_FILE = os.path.join(log_dir, "textual.log") # Use absolute path
181
181
 
182
- CSS = """ # Keep SimpleChatApp CSS start
183
- #main-content { # Keep SimpleChatApp CSS
182
+ # Rams-inspired CSS following "As little design as possible"
183
+ CSS = """
184
+ /* Main layout - Clean foundation */
185
+ #main-content {
184
186
  width: 100%;
185
187
  height: 100%;
186
- padding: 0 1;
188
+ padding: 0;
189
+ background: #0C0C0C;
187
190
  }
188
191
 
192
+ /* App header with ASCII border aesthetic */
189
193
  #app-info-bar {
190
194
  width: 100%;
191
- height: 1;
192
- background: $surface-darken-3;
193
- color: $text-muted;
194
- padding: 0 1;
195
+ height: 3;
196
+ background: #0C0C0C;
197
+ color: #E8E8E8;
198
+ padding: 1 2;
199
+ border-bottom: solid #333333 1;
195
200
  }
196
201
 
197
202
  #version-info {
198
203
  width: auto;
199
204
  text-align: left;
205
+ color: #E8E8E8;
200
206
  }
201
207
 
202
208
  #model-info {
203
209
  width: 1fr;
204
210
  text-align: right;
211
+ color: #666666;
205
212
  }
206
213
 
207
- #conversation-title { # Keep SimpleChatApp CSS
208
- width: 100%; # Keep SimpleChatApp CSS
209
- height: 2;
210
- background: $surface-darken-2;
211
- color: $text;
212
- content-align: center middle;
213
- text-align: center;
214
- border-bottom: solid $primary-darken-2;
214
+ /* Conversation title - Functional hierarchy */
215
+ #conversation-title {
216
+ width: 100%;
217
+ height: 3;
218
+ background: #0C0C0C;
219
+ color: #E8E8E8;
220
+ content-align: left middle;
221
+ text-align: left;
222
+ padding: 0 2;
223
+ border-bottom: solid #333333 1;
215
224
  }
216
225
 
226
+ /* Action buttons - Minimal design */
217
227
  #action-buttons {
218
228
  width: 100%;
219
229
  height: auto;
220
- padding: 0 1; /* Corrected padding: 0 vertical, 1 horizontal */
221
- align-horizontal: center;
222
- background: $surface-darken-1;
230
+ padding: 2;
231
+ align-horizontal: left;
232
+ background: #0C0C0C;
233
+ border-bottom: solid #333333 1;
223
234
  }
224
235
 
225
236
  #new-chat-button, #change-title-button {
226
- margin: 0 1;
227
- min-width: 15;
237
+ margin: 0 1 0 0;
238
+ min-width: 12;
239
+ background: transparent;
240
+ color: #E8E8E8;
241
+ border: solid #333333 1;
242
+ padding: 1 2;
228
243
  }
229
244
 
230
- #messages-container { # Keep SimpleChatApp CSS
231
- width: 100%; # Keep SimpleChatApp CSS
245
+ #new-chat-button:hover, #change-title-button:hover {
246
+ background: #1A1A1A;
247
+ border: solid #33FF33 1;
248
+ }
249
+
250
+ /* Messages container - Purposeful spacing */
251
+ #messages-container {
252
+ width: 100%;
232
253
  height: 1fr;
233
- min-height: 10;
234
- border-bottom: solid $primary-darken-2;
254
+ min-height: 15;
235
255
  overflow: auto;
236
- padding: 0 1;
256
+ padding: 2;
257
+ background: #0C0C0C;
258
+ border-bottom: solid #333333 1;
237
259
  }
238
260
 
239
- #loading-indicator { # Keep SimpleChatApp CSS
240
- width: 100%; # Keep SimpleChatApp CSS
241
- height: 1;
242
- background: $primary-darken-1;
243
- color: $text;
261
+ /* Loading indicator - Unobtrusive */
262
+ #loading-indicator {
263
+ width: 100%;
264
+ height: 2;
265
+ background: #0C0C0C;
266
+ color: #666666;
244
267
  content-align: center middle;
245
268
  text-align: center;
246
- text-style: bold;
269
+ border-bottom: solid #333333 1;
270
+ padding: 0 2;
247
271
  }
248
272
 
249
- #loading-indicator.hidden { # Keep SimpleChatApp CSS
273
+ #loading-indicator.hidden {
250
274
  display: none;
251
275
  }
252
276
 
253
277
  #loading-indicator.model-loading {
254
- background: $warning;
255
- color: $text;
278
+ color: #33FF33;
256
279
  }
257
280
 
258
- #input-area { # Keep SimpleChatApp CSS
259
- width: 100%; # Keep SimpleChatApp CSS
281
+ /* Input area - Clean and functional */
282
+ #input-area {
283
+ width: 100%;
260
284
  height: auto;
261
- min-height: 4;
262
- max-height: 10;
263
- padding: 1;
285
+ min-height: 5;
286
+ max-height: 12;
287
+ padding: 2;
288
+ background: #0C0C0C;
264
289
  }
265
290
 
266
- #message-input { # Keep SimpleChatApp CSS
267
- width: 1fr; # Keep SimpleChatApp CSS
268
- min-height: 2;
291
+ #message-input {
292
+ width: 1fr;
293
+ min-height: 3;
269
294
  height: auto;
270
295
  margin-right: 1;
271
- border: solid $primary-darken-2;
296
+ border: solid #333333 1;
297
+ background: #0C0C0C;
298
+ color: #E8E8E8;
299
+ padding: 1;
272
300
  }
273
301
 
274
- #message-input:focus { # Keep SimpleChatApp CSS
275
- border: solid $primary;
302
+ #message-input:focus {
303
+ border: solid #33FF33 1;
304
+ outline: none;
276
305
  }
277
306
 
278
307
  /* Removed CSS for #send-button, #new-chat-button, #view-history-button, #settings-button */ # Keep SimpleChatApp CSS comment
279
308
  /* Removed CSS for #button-row */ # Keep SimpleChatApp CSS comment
280
309
 
281
- #settings-panel { /* Add CSS for the new settings panel */
282
- display: none; /* Hidden by default */
310
+ /* Settings panel - Clean modal design */
311
+ #settings-panel {
312
+ display: none;
283
313
  align: center middle;
284
314
  width: 60;
285
315
  height: auto;
286
- background: $surface;
287
- border: thick $primary;
288
- padding: 1 2;
289
- layer: settings; /* Ensure it's above other elements */
316
+ background: #0C0C0C;
317
+ border: solid #333333 1;
318
+ padding: 2;
319
+ layer: settings;
290
320
  }
291
321
 
292
- #settings-panel.visible { /* Class to show the panel */
322
+ #settings-panel.visible {
293
323
  display: block;
294
324
  }
295
325
 
296
326
  #settings-title {
297
327
  width: 100%;
298
- content-align: center middle;
328
+ content-align: left middle;
299
329
  padding-bottom: 1;
300
- border-bottom: thick $primary-darken-2; /* Correct syntax for bottom border */
330
+ border-bottom: solid #333333 1;
331
+ color: #E8E8E8;
332
+ margin-bottom: 2;
301
333
  }
302
334
 
303
335
  #settings-buttons {
304
336
  width: 100%;
305
337
  height: auto;
306
- align: center middle;
307
- padding-top: 1;
338
+ align: right middle;
339
+ padding-top: 2;
308
340
  }
309
341
 
310
- /* --- Title Input Modal CSS --- */
342
+ #settings-save-button, #settings-cancel-button {
343
+ background: transparent;
344
+ color: #E8E8E8;
345
+ border: solid #333333 1;
346
+ margin-left: 1;
347
+ padding: 1 2;
348
+ }
349
+
350
+ #settings-save-button:hover {
351
+ background: #1A1A1A;
352
+ border: solid #33FF33 1;
353
+ }
354
+
355
+ #settings-cancel-button:hover {
356
+ background: #1A1A1A;
357
+ border: solid #FF4444 1;
358
+ }
359
+
360
+ /* Title Input Modal - Minimal and focused */
311
361
  TitleInputModal {
312
362
  align: center middle;
313
363
  width: 60;
314
364
  height: auto;
315
- background: $surface;
316
- border: thick $primary;
317
- padding: 1 2;
318
- layer: modal; /* Ensure it's above other elements */
365
+ background: #0C0C0C;
366
+ border: solid #333333 1;
367
+ padding: 2;
368
+ layer: modal;
319
369
  }
320
370
 
321
371
  #modal-label {
322
372
  width: 100%;
323
- content-align: center middle;
373
+ content-align: left middle;
324
374
  padding-bottom: 1;
375
+ color: #E8E8E8;
376
+ border-bottom: solid #333333 1;
377
+ margin-bottom: 2;
325
378
  }
326
379
 
327
380
  #title-input {
328
381
  width: 100%;
329
- margin-bottom: 1;
382
+ margin-bottom: 2;
383
+ background: #0C0C0C;
384
+ color: #E8E8E8;
385
+ border: solid #333333 1;
386
+ padding: 1;
387
+ }
388
+
389
+ #title-input:focus {
390
+ border: solid #33FF33 1;
391
+ outline: none;
330
392
  }
331
393
 
332
394
  TitleInputModal Horizontal {
333
395
  width: 100%;
334
396
  height: auto;
335
- align: center middle;
397
+ align: right middle;
398
+ padding-top: 2;
399
+ }
400
+
401
+ #update-button, #cancel-button {
402
+ background: transparent;
403
+ color: #E8E8E8;
404
+ border: solid #333333 1;
405
+ margin-left: 1;
406
+ padding: 1 2;
407
+ }
408
+
409
+ #update-button:hover {
410
+ background: #1A1A1A;
411
+ border: solid #33FF33 1;
412
+ }
413
+
414
+ #cancel-button:hover {
415
+ background: #1A1A1A;
416
+ border: solid #FF4444 1;
417
+ }
418
+
419
+ /* Message Display - Clean typography */
420
+ MessageDisplay {
421
+ width: 100%;
422
+ height: auto;
423
+ margin: 1 0;
424
+ padding: 2;
425
+ text-wrap: wrap;
426
+ background: transparent;
427
+ }
428
+
429
+ MessageDisplay.user-message {
430
+ background: #1A1A1A;
431
+ border-left: solid #33FF33 2;
432
+ margin-left: 2;
433
+ margin-right: 8;
434
+ }
435
+
436
+ MessageDisplay.assistant-message {
437
+ background: transparent;
438
+ border-left: solid #666666 1;
439
+ margin-right: 2;
440
+ margin-left: 8;
336
441
  }
337
442
  """
338
443
 
339
444
  BINDINGS = [ # Keep SimpleChatApp BINDINGS, ensure Enter is not globally bound for settings
340
445
  Binding("q", "quit", "Quit", show=True, key_display="q"),
341
- # Removed binding for "n" (new chat) since there's a dedicated button
342
- Binding("c", "action_new_conversation", "New Chat", show=False, key_display="c", priority=True), # Keep alias with priority
343
- Binding("escape", "action_escape", "Cancel / Stop", show=True, key_display="esc"), # Updated to call our async method
446
+ Binding("c", "action_new_conversation", "New", show=True, key_display="c", priority=True),
447
+ Binding("escape", "action_escape", "Cancel", show=True, key_display="esc"),
344
448
  Binding("ctrl+c", "quit", "Quit", show=False),
345
- Binding("h", "view_history", "History", show=True, key_display="h", priority=True), # Add priority
346
- Binding("s", "settings", "Settings", show=True, key_display="s", priority=True), # Add priority
347
- # Removed binding for "t" (title update) since there's a dedicated button
348
- Binding("m", "model_browser", "Model Browser", show=True, key_display="m", priority=True), # Add model browser binding
449
+ Binding("h", "view_history", "History", show=True, key_display="h", priority=True),
450
+ Binding("s", "settings", "Settings", show=True, key_display="s", priority=True),
451
+ Binding("m", "model_browser", "Models", show=True, key_display="m", priority=True),
349
452
  ] # Keep SimpleChatApp BINDINGS end
350
453
 
351
454
  current_conversation = reactive(None) # Keep SimpleChatApp reactive var
@@ -376,26 +479,26 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
376
479
  yield Header()
377
480
 
378
481
  with Vertical(id="main-content"):
379
- # Add app info bar with version and model info
482
+ # Clean app header following Rams principles
380
483
  with Horizontal(id="app-info-bar"):
381
- yield Static(f"Chat Console v{__version__}", id="version-info") # Use imported version
382
- yield Static(f"Model: {self.selected_model}", id="model-info")
484
+ yield Static(f"Chat Console v{__version__}", id="version-info")
485
+ yield Static(self.selected_model, id="model-info")
383
486
 
384
487
  # Conversation title
385
488
  yield Static("New Conversation", id="conversation-title")
386
489
 
387
- # Add action buttons at the top for visibility
490
+ # Action buttons - Minimal and functional
388
491
  with Horizontal(id="action-buttons"):
389
- yield Button("+ New Chat", id="new-chat-button", variant="success")
390
- yield Button("✎ Change Title", id="change-title-button", variant="primary")
492
+ yield Button("", id="new-chat-button") # Solid circle for "new"
493
+ yield Button("✎", id="change-title-button") # Pencil for "edit"
391
494
 
392
495
  # Messages area
393
496
  with ScrollableContainer(id="messages-container"):
394
497
  # Will be populated with messages
395
498
  pass
396
499
 
397
- # Loading indicator
398
- yield Static("▪▪▪ Generating response...", id="loading-indicator", classes="hidden", markup=False)
500
+ # Minimal loading indicator
501
+ yield Static(" Generating", id="loading-indicator", classes="hidden", markup=False)
399
502
 
400
503
  # Input area
401
504
  with Container(id="input-area"):
@@ -483,8 +586,8 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
483
586
  model = self.selected_model # Keep SimpleChatApp create_new_conversation
484
587
  style = self.selected_style # Keep SimpleChatApp create_new_conversation
485
588
 
486
- # Create a title for the new conversation # Keep SimpleChatApp create_new_conversation
487
- title = f"New conversation ({datetime.now().strftime('%Y-%m-%d %H:%M')})" # Keep SimpleChatApp create_new_conversation
589
+ # Clean title following Rams principles
590
+ title = "New Conversation"
488
591
 
489
592
  # Create conversation in database using the correct method # Keep SimpleChatApp create_new_conversation
490
593
  log(f"Creating conversation with title: {title}, model: {model}, style: {style}") # Added log
@@ -580,22 +683,20 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
580
683
  # Optionally add other escape behaviors here if needed
581
684
 
582
685
  def update_app_info(self) -> None:
583
- """Update the displayed app information."""
686
+ """Update app info following clean information architecture"""
584
687
  try:
585
- # Update model info
586
688
  model_info = self.query_one("#model-info", Static)
587
- model_display = self.selected_model
588
-
589
- # Try to get a more readable name from config if available
689
+
690
+ # Clean model display - no unnecessary decoration
590
691
  if self.selected_model in CONFIG["available_models"]:
591
- provider = CONFIG["available_models"][self.selected_model]["provider"]
592
692
  display_name = CONFIG["available_models"][self.selected_model]["display_name"]
593
- model_display = f"{display_name} ({provider.capitalize()})"
693
+ model_display = display_name
694
+ else:
695
+ model_display = self.selected_model
594
696
 
595
- model_info.update(f"Model: {model_display}")
697
+ model_info.update(model_display)
596
698
  except Exception as e:
597
- # Silently handle errors to prevent crashes
598
- log.error(f"Error updating app info: {e}") # Log error instead of passing silently
699
+ log.error(f"Error updating app info: {e}")
599
700
  pass
600
701
 
601
702
  async def update_messages_ui(self) -> None: # Keep SimpleChatApp update_messages_ui
@@ -708,21 +809,32 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
708
809
  if client_type == OllamaClient:
709
810
  debug_log(f"Title generation with Ollama model detected: {selected_model_resolved}")
710
811
 
711
- # Try common small/fast models first if they exist
812
+ # Always try to use smalllm2:135m first, then fall back to other small models
712
813
  try:
713
- # Check if we have any smaller models available for faster title generation
814
+ # Check if we have smalllm2:135m or other smaller models available
714
815
  ollama_client = await OllamaClient.create()
715
816
  available_models = await ollama_client.get_available_models()
716
- small_model_options = ["gemma:2b", "phi3:mini", "llama3:8b", "orca-mini:3b", "phi2"]
717
817
 
818
+ # Use smalllm2:135m if available (extremely small and fast)
819
+ preferred_model = "smalllm2:135m"
820
+ fallback_models = ["tinyllama", "gemma:2b", "phi3:mini", "llama3:8b", "orca-mini:3b", "phi2"]
821
+
822
+ # First check for our preferred smallest model
718
823
  small_model_found = False
719
- for model_name in small_model_options:
720
- if any(model["id"] == model_name for model in available_models):
721
- debug_log(f"Found smaller Ollama model for title generation: {model_name}")
722
- title_model = model_name
723
- small_model_found = True
724
- break
725
-
824
+ if any(model["id"] == preferred_model for model in available_models):
825
+ debug_log(f"Found optimal small model for title generation: {preferred_model}")
826
+ title_model = preferred_model
827
+ small_model_found = True
828
+
829
+ # If not found, try fallbacks in order
830
+ if not small_model_found:
831
+ for model_name in fallback_models:
832
+ if any(model["id"] == model_name for model in available_models):
833
+ debug_log(f"Found alternative small model for title generation: {model_name}")
834
+ title_model = model_name
835
+ small_model_found = True
836
+ break
837
+
726
838
  if not small_model_found:
727
839
  # Use the current model if no smaller models found
728
840
  title_model = selected_model_resolved
@@ -1502,49 +1614,37 @@ class SimpleChatApp(App): # Keep SimpleChatApp class definition
1502
1614
  self.push_screen(ModelBrowserScreen())
1503
1615
 
1504
1616
  async def _animate_loading_task(self, loading_widget: Static) -> None:
1505
- """Animate the loading indicator with a simple text animation"""
1617
+ """Minimal loading animation following Rams principles"""
1506
1618
  try:
1507
- # Animation frames (simple text animation)
1619
+ # Minimal loading frames - "Less but better"
1508
1620
  frames = [
1509
- "▪▫▫ Generating response...",
1510
- "▪▪▫ Generating response...",
1511
- "▪▪▪ Generating response...",
1512
- "▫▪▪ Generating response...",
1513
- "▫▫▪ Generating response...",
1514
- "▫▫▫ Generating response..."
1621
+ " Generating",
1622
+ " Generating",
1623
+ " Generating",
1624
+ " Generating"
1515
1625
  ]
1516
1626
 
1517
1627
  while self.is_generating:
1518
1628
  try:
1519
- # Update the loading text with safety checks
1520
- if frames and len(frames) > 0:
1521
- frame_idx = self._loading_frame % len(frames)
1522
- loading_widget.update(frames[frame_idx])
1523
- else:
1524
- # Fallback if frames is empty
1525
- loading_widget.update("▪▪▪ Generating response...")
1526
-
1629
+ frame_idx = self._loading_frame % len(frames)
1630
+ loading_widget.update(frames[frame_idx])
1527
1631
  self._loading_frame += 1
1528
- # Small delay between frames
1529
- await asyncio.sleep(0.3)
1632
+ # Slower, less distracting animation
1633
+ await asyncio.sleep(0.8)
1530
1634
  except Exception as e:
1531
- # If any error occurs, use a simple fallback and continue
1532
1635
  log.error(f"Animation frame error: {str(e)}")
1533
1636
  try:
1534
- loading_widget.update("▪▪▪ Generating response...")
1637
+ loading_widget.update(" Generating")
1535
1638
  except:
1536
1639
  pass
1537
- await asyncio.sleep(0.3)
1640
+ await asyncio.sleep(0.8)
1538
1641
 
1539
1642
  except asyncio.CancelledError:
1540
- # Normal cancellation
1541
1643
  pass
1542
1644
  except Exception as e:
1543
- # Log any errors but don't crash
1544
1645
  log.error(f"Error in loading animation: {str(e)}")
1545
- # Reset to basic text
1546
1646
  try:
1547
- loading_widget.update("▪▪▪ Generating response...")
1647
+ loading_widget.update(" Generating")
1548
1648
  except:
1549
1649
  pass
1550
1650
 
@@ -1713,17 +1813,48 @@ class TitleInputModal(Static):
1713
1813
  self.notify(f"Failed to update title: {str(e)}", severity="error")
1714
1814
 
1715
1815
 
1716
- def main(initial_text: Optional[str] = typer.Argument(None, help="Initial text to start the chat with")): # Keep main function
1717
- """Entry point for the chat-cli application""" # Keep main function docstring
1718
- # When no argument is provided, typer passes the ArgumentInfo object # Keep main function
1719
- # When an argument is provided, typer passes the actual value # Keep main function
1720
- if isinstance(initial_text, typer.models.ArgumentInfo): # Keep main function
1721
- initial_value = None # No argument provided # Keep main function
1722
- else: # Keep main function
1723
- initial_value = str(initial_text) if initial_text is not None else None # Keep main function
1816
+ def main(
1817
+ initial_text: Optional[str] = typer.Argument(None, help="Initial text to start the chat with"),
1818
+ console: bool = typer.Option(False, "--console", "-c", help="Use pure console mode (no Textual)")
1819
+ ):
1820
+ """Entry point for the chat-cli application"""
1821
+ if console:
1822
+ # Launch pure console version
1823
+ import asyncio
1824
+ import sys
1825
+ import os
1724
1826
 
1725
- app = SimpleChatApp(initial_text=initial_value) # Keep main function
1726
- app.run() # Keep main function
1827
+ # Add current directory to path for console_chat import
1828
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
1829
+
1830
+ try:
1831
+ from console_chat import ConsoleUI
1832
+
1833
+ async def run_console():
1834
+ console_ui = ConsoleUI()
1835
+ if initial_text and not isinstance(initial_text, typer.models.ArgumentInfo):
1836
+ # If initial text provided, create conversation and add message
1837
+ await console_ui.create_new_conversation()
1838
+ await console_ui.generate_response(str(initial_text))
1839
+ await console_ui.run()
1840
+
1841
+ asyncio.run(run_console())
1842
+
1843
+ except ImportError:
1844
+ print("Error: Could not import console version. Make sure all dependencies are installed.")
1845
+ sys.exit(1)
1846
+ except Exception as e:
1847
+ print(f"Error running console version: {e}")
1848
+ sys.exit(1)
1849
+ else:
1850
+ # Original Textual version
1851
+ if isinstance(initial_text, typer.models.ArgumentInfo):
1852
+ initial_value = None
1853
+ else:
1854
+ initial_value = str(initial_text) if initial_text is not None else None
1855
+
1856
+ app = SimpleChatApp(initial_text=initial_value)
1857
+ app.run()
1727
1858
 
1728
1859
  if __name__ == "__main__": # Keep main function entry point
1729
1860
  typer.run(main) # Keep main function entry point