ngpt 2.16.4__tar.gz → 2.16.6__tar.gz

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.
Files changed (57) hide show
  1. {ngpt-2.16.4 → ngpt-2.16.6}/PKG-INFO +1 -1
  2. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/cli.md +32 -6
  3. {ngpt-2.16.4 → ngpt-2.16.6}/docs/examples/advanced.md +119 -111
  4. {ngpt-2.16.4 → ngpt-2.16.6}/docs/examples/cli_components.md +37 -4
  5. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/library_usage.md +23 -9
  6. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/interactive.py +46 -6
  7. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/chat.py +1 -1
  8. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/code.py +1 -1
  9. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/rewrite.py +1 -3
  10. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/shell.py +1 -1
  11. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/text.py +1 -3
  12. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/renderers.py +11 -5
  13. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/ui.py +8 -2
  14. {ngpt-2.16.4 → ngpt-2.16.6}/pyproject.toml +1 -1
  15. {ngpt-2.16.4 → ngpt-2.16.6}/uv.lock +1 -1
  16. {ngpt-2.16.4 → ngpt-2.16.6}/.github/workflows/python-publish.yml +0 -0
  17. {ngpt-2.16.4 → ngpt-2.16.6}/.gitignore +0 -0
  18. {ngpt-2.16.4 → ngpt-2.16.6}/.python-version +0 -0
  19. {ngpt-2.16.4 → ngpt-2.16.6}/COMMIT_GUIDELINES.md +0 -0
  20. {ngpt-2.16.4 → ngpt-2.16.6}/CONTRIBUTING.md +0 -0
  21. {ngpt-2.16.4 → ngpt-2.16.6}/LICENSE +0 -0
  22. {ngpt-2.16.4 → ngpt-2.16.6}/README.md +0 -0
  23. {ngpt-2.16.4 → ngpt-2.16.6}/docs/CONTRIBUTING.md +0 -0
  24. {ngpt-2.16.4 → ngpt-2.16.6}/docs/LICENSE.md +0 -0
  25. {ngpt-2.16.4 → ngpt-2.16.6}/docs/README.md +0 -0
  26. {ngpt-2.16.4 → ngpt-2.16.6}/docs/_config.yml +0 -0
  27. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/README.md +0 -0
  28. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/cli_config.md +0 -0
  29. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/client.md +0 -0
  30. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/config.md +0 -0
  31. {ngpt-2.16.4 → ngpt-2.16.6}/docs/api/logging.md +0 -0
  32. {ngpt-2.16.4 → ngpt-2.16.6}/docs/assets/css/style.scss +0 -0
  33. {ngpt-2.16.4 → ngpt-2.16.6}/docs/configuration.md +0 -0
  34. {ngpt-2.16.4 → ngpt-2.16.6}/docs/examples/README.md +0 -0
  35. {ngpt-2.16.4 → ngpt-2.16.6}/docs/examples/basic.md +0 -0
  36. {ngpt-2.16.4 → ngpt-2.16.6}/docs/examples/integrations.md +0 -0
  37. {ngpt-2.16.4 → ngpt-2.16.6}/docs/installation.md +0 -0
  38. {ngpt-2.16.4 → ngpt-2.16.6}/docs/overview.md +0 -0
  39. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/README.md +0 -0
  40. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/cli_config.md +0 -0
  41. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/cli_framework.md +0 -0
  42. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/cli_usage.md +0 -0
  43. {ngpt-2.16.4 → ngpt-2.16.6}/docs/usage/gitcommsg.md +0 -0
  44. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/__init__.py +0 -0
  45. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/__main__.py +0 -0
  46. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/__init__.py +0 -0
  47. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/args.py +0 -0
  48. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/config_manager.py +0 -0
  49. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/formatters.py +0 -0
  50. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/main.py +0 -0
  51. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/__init__.py +0 -0
  52. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/cli/modes/gitcommsg.py +0 -0
  53. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/client.py +0 -0
  54. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/utils/__init__.py +0 -0
  55. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/utils/cli_config.py +0 -0
  56. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/utils/config.py +0 -0
  57. {ngpt-2.16.4 → ngpt-2.16.6}/ngpt/utils/log.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.16.4
3
+ Version: 2.16.6
4
4
  Summary: Swiss army knife for LLMs: powerful CLI, interactive chatbot, and flexible Python library. Works with OpenAI, Ollama, Groq, Claude, and any OpenAI-compatible API.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -160,7 +160,7 @@ from ngpt.cli.renderers import prettify_streaming_markdown
160
160
  def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_text=None)
161
161
  ```
162
162
 
163
- Creates a streaming markdown renderer that updates in real-time.
163
+ Creates a streaming markdown renderer that updates in real-time with loading spinner functionality.
164
164
 
165
165
  **Parameters:**
166
166
  - `renderer` (str): Which renderer to use ('auto', 'rich', 'glow')
@@ -168,21 +168,47 @@ Creates a streaming markdown renderer that updates in real-time.
168
168
  - `header_text` (str, optional): Header text to display above the content
169
169
 
170
170
  **Returns:**
171
- - object: A streaming markdown renderer object with an `update_content` method
171
+ - tuple: (live_display, update_function, setup_spinner_func) if successful, (None, None, None) otherwise
172
+ - `live_display`: The rich.Live display object for controlling the display lifecycle
173
+ - `update_function`: Function to call with updated content that will refresh the display
174
+ - `setup_spinner_func`: Function to set up a spinner while waiting for first content
172
175
 
173
176
  **Example:**
174
177
  ```python
175
178
  from ngpt import NGPTClient, load_config
176
179
  from ngpt.cli.renderers import prettify_streaming_markdown
180
+ import threading
177
181
 
178
182
  client = NGPTClient(**load_config())
179
- streamer = prettify_streaming_markdown(renderer='rich')
180
183
 
181
- client.chat(
182
- "Explain quantum computing with code examples",
184
+ # Get components for streaming display with spinner
185
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(renderer='rich')
186
+
187
+ # Set up spinner (optional)
188
+ stop_spinner_event = threading.Event()
189
+ stop_spinner_func = None
190
+ if setup_spinner:
191
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Waiting for response...")
192
+
193
+ # The update_function will automatically:
194
+ # 1. Start the live display when first content arrives
195
+ # 2. Stop the spinner when first content arrives
196
+ # 3. Update the display with new content
197
+
198
+ # Use with client
199
+ response = client.chat(
200
+ "Explain quantum computing",
183
201
  stream=True,
184
- stream_callback=streamer.update_content
202
+ stream_callback=update_function
185
203
  )
204
+
205
+ # Ensure spinner is stopped if no content was received
206
+ if not stop_spinner_event.is_set():
207
+ stop_spinner_event.set()
208
+
209
+ # Stop the display when done
210
+ if live_display:
211
+ live_display.stop()
186
212
  ```
187
213
 
188
214
  ### `has_markdown_renderer`
@@ -217,38 +217,33 @@ def custom_streaming_markdown():
217
217
  # Create a Rich console
218
218
  console = Console()
219
219
 
220
- # Initialize the markdown renderer
221
- markdown_renderer = prettify_streaming_markdown(renderer='rich')
220
+ # Initialize a real-time markdown renderer
221
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(renderer='rich')
222
222
 
223
- # Start a conversation
224
- console.print("[bold green]Starting conversation with markdown rendering[/bold green]")
225
- console.print("[bold]Type 'exit' to quit[/bold]")
226
- console.print("-" * 50)
223
+ # Set up spinner for waiting period
224
+ import threading
225
+ stop_spinner_event = threading.Event()
226
+ if setup_spinner:
227
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Waiting for response...")
227
228
 
228
- messages = [
229
- {"role": "system", "content": "You are a helpful assistant with markdown capabilities. Use formatting like headers, lists, code blocks, and tables to present information clearly."}
230
- ]
229
+ # Start the live display
230
+ markdown_renderer.start()
231
231
 
232
- while True:
233
- user_input = input("You: ")
234
- if user_input.lower() in ['exit', 'quit', 'bye']:
235
- break
236
-
237
- # Add user message to history
238
- messages.append({"role": "user", "content": user_input})
239
-
240
- # Call the API with the custom renderer
241
- console.print("[bold blue]Assistant:[/bold blue]")
242
- response = client.chat(
243
- "",
244
- messages=messages,
245
- stream=True,
246
- markdown_format=True,
247
- stream_callback=markdown_renderer.update_content
248
- )
232
+ # Stream the response with real-time rendering
233
+ for chunk in client.chat(
234
+ "Explain quantum computing briefly",
235
+ stream=True
236
+ ):
237
+ full_response += chunk
238
+ update_function(full_response)
239
+
240
+ # Ensure spinner is stopped if still running
241
+ if not stop_spinner_event.is_set():
242
+ stop_spinner_event.set()
249
243
 
250
- # Add the response to history
251
- messages.append({"role": "assistant", "content": response})
244
+ # Stop the live display when done
245
+ if live_display:
246
+ live_display.stop()
252
247
 
253
248
  if __name__ == "__main__":
254
249
  custom_streaming_markdown()
@@ -584,101 +579,114 @@ def main():
584
579
  log_file=log_file
585
580
  )
586
581
 
587
- # Standard chat mode
588
- else:
589
- # Real-time prettified markdown mode
590
- if args.stream_prettify:
591
- streamer = prettify_streaming_markdown(renderer='rich')
582
+ # Real-time prettified markdown mode
583
+ if args.stream_prettify:
584
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(renderer='rich')
585
+
586
+ # Set up spinner for waiting period
587
+ import threading
588
+ stop_spinner_event = threading.Event()
589
+ if setup_spinner:
590
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Waiting for response...")
591
+
592
+ response = client.chat(
593
+ prompt,
594
+ temperature=args.temperature,
595
+ stream=True,
596
+ stream_callback=update_function,
597
+ markdown_format=True,
598
+ web_search=args.web_search,
599
+ messages=messages
600
+ )
601
+
602
+ # Ensure spinner is stopped if still running
603
+ if not stop_spinner_event.is_set():
604
+ stop_spinner_event.set()
605
+
606
+ # Stop the live display when done
607
+ if live_display:
608
+ live_display.stop()
609
+
610
+ if args.output:
611
+ with open(args.output, 'w') as f:
612
+ f.write(response)
613
+ print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
614
+
615
+ if log_file:
616
+ log_file.write(f"Assistant: {response}\n\n")
617
+
618
+ # Basic prettify mode
619
+ elif args.prettify:
620
+ from rich.markdown import Markdown
621
+ from rich.console import Console
622
+
623
+ console = Console()
624
+
625
+ if args.output:
626
+ # No streaming if saving to file
592
627
  response = client.chat(
593
628
  prompt,
594
- temperature=args.temperature,
595
- stream=True,
596
- stream_callback=streamer.update_content,
597
- markdown_format=True,
629
+ temperature=args.temperature,
630
+ stream=False,
598
631
  web_search=args.web_search,
632
+ markdown_format=True,
599
633
  messages=messages
600
634
  )
635
+ with open(args.output, 'w') as f:
636
+ f.write(response)
637
+ print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
601
638
 
602
- if args.output:
603
- with open(args.output, 'w') as f:
604
- f.write(response)
605
- print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
606
-
607
639
  if log_file:
608
640
  log_file.write(f"Assistant: {response}\n\n")
609
-
610
- # Basic prettify mode
611
- elif args.prettify:
612
- from rich.markdown import Markdown
613
- from rich.console import Console
641
+ else:
642
+ # Use rich to render markdown after completion
643
+ response = client.chat(
644
+ prompt,
645
+ temperature=args.temperature,
646
+ stream=False,
647
+ web_search=args.web_search,
648
+ markdown_format=True,
649
+ messages=messages
650
+ )
651
+ console.print(Markdown(response))
614
652
 
615
- console = Console()
653
+ if log_file:
654
+ log_file.write(f"Assistant: {response}\n\n")
655
+
656
+ # Simple mode
657
+ else:
658
+ if args.output:
659
+ # No streaming if saving to file
660
+ response = client.chat(
661
+ prompt,
662
+ temperature=args.temperature,
663
+ stream=False,
664
+ web_search=args.web_search,
665
+ messages=messages
666
+ )
667
+ with open(args.output, 'w') as f:
668
+ f.write(response)
669
+ print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
616
670
 
617
- if args.output:
618
- # No streaming if saving to file
619
- response = client.chat(
620
- prompt,
621
- temperature=args.temperature,
622
- stream=False,
623
- web_search=args.web_search,
624
- markdown_format=True,
625
- messages=messages
626
- )
627
- with open(args.output, 'w') as f:
628
- f.write(response)
629
- print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
630
-
631
- if log_file:
632
- log_file.write(f"Assistant: {response}\n\n")
633
- else:
634
- # Use rich to render markdown after completion
635
- response = client.chat(
636
- prompt,
637
- temperature=args.temperature,
638
- stream=False,
639
- web_search=args.web_search,
640
- markdown_format=True,
641
- messages=messages
642
- )
643
- console.print(Markdown(response))
644
-
645
- if log_file:
646
- log_file.write(f"Assistant: {response}\n\n")
647
-
648
- # Simple mode
671
+ if log_file:
672
+ log_file.write(f"Assistant: {response}\n\n")
649
673
  else:
650
- if args.output:
651
- # No streaming if saving to file
652
- response = client.chat(
653
- prompt,
654
- temperature=args.temperature,
655
- stream=False,
656
- web_search=args.web_search,
657
- messages=messages
658
- )
659
- with open(args.output, 'w') as f:
660
- f.write(response)
661
- print(f"{COLORS['green']}Response saved to {args.output}{COLORS['reset']}")
662
-
663
- if log_file:
664
- log_file.write(f"Assistant: {response}\n\n")
665
- else:
666
- # Stream to console
667
- full_response = ""
668
- for chunk in client.chat(
669
- prompt,
670
- temperature=args.temperature,
671
- stream=True,
672
- web_search=args.web_search,
673
- messages=messages
674
- ):
675
- print(chunk, end="", flush=True)
676
- full_response += chunk
677
- print() # Final newline
678
-
679
- if log_file:
680
- log_file.write(f"Assistant: {full_response}\n\n")
674
+ # Stream to console
675
+ full_response = ""
676
+ for chunk in client.chat(
677
+ prompt,
678
+ temperature=args.temperature,
679
+ stream=True,
680
+ web_search=args.web_search,
681
+ messages=messages
682
+ ):
683
+ print(chunk, end="", flush=True)
684
+ full_response += chunk
685
+ print() # Final newline
681
686
 
687
+ if log_file:
688
+ log_file.write(f"Assistant: {full_response}\n\n")
689
+
682
690
  if log_file:
683
691
  log_file.close()
684
692
 
@@ -260,17 +260,35 @@ CODE:
260
260
  # Use streaming markdown renderer if Rich is available
261
261
  if has_rich and has_markdown_renderer(renderer='rich'):
262
262
  # Create a streaming markdown renderer
263
- markdown_streamer = prettify_streaming_markdown(
263
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(
264
264
  renderer='rich',
265
265
  header_text=f"Documentation for {args.file}"
266
266
  )
267
267
 
268
+ # Setup spinner for waiting period
269
+ import threading
270
+ stop_spinner_event = threading.Event()
271
+ stop_spinner_func = None
272
+ if setup_spinner:
273
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Generating documentation...")
274
+
268
275
  # Stream the response with live updating
269
276
  try:
270
277
  for chunk in client.chat(prompt, stream=True):
271
278
  full_response += chunk
272
- markdown_streamer.update_content(full_response)
279
+ update_function(full_response)
280
+
281
+ # Ensure spinner is stopped if still running
282
+ if not stop_spinner_event.is_set():
283
+ stop_spinner_event.set()
284
+
285
+ # Stop the display when done
286
+ if live_display:
287
+ live_display.stop()
273
288
  except Exception as e:
289
+ # Ensure spinner is stopped on error
290
+ if not stop_spinner_event.is_set():
291
+ stop_spinner_event.set()
274
292
  print(f"\nError generating documentation: {e}", file=sys.stderr)
275
293
  sys.exit(1)
276
294
  else:
@@ -511,11 +529,18 @@ Return ONLY the improved text without explanations or notes."""
511
529
  if args.stream:
512
530
  # Stream with live updates
513
531
  if args.prettify:
514
- streamer = prettify_streaming_markdown(
532
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(
515
533
  renderer='rich',
516
534
  header_text="Improved Text"
517
535
  )
518
536
 
537
+ # Setup spinner for waiting period
538
+ import threading
539
+ stop_spinner_event = threading.Event()
540
+ stop_spinner_func = None
541
+ if setup_spinner:
542
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Improving text...")
543
+
519
544
  full_response = ""
520
545
  for chunk in client.chat(
521
546
  text,
@@ -523,7 +548,15 @@ Return ONLY the improved text without explanations or notes."""
523
548
  stream=True
524
549
  ):
525
550
  full_response += chunk
526
- streamer.update_content(full_response)
551
+ update_function(full_response)
552
+
553
+ # Ensure spinner is stopped if still running
554
+ if not stop_spinner_event.is_set():
555
+ stop_spinner_event.set()
556
+
557
+ # Stop the display when done
558
+ if live_display:
559
+ live_display.stop()
527
560
 
528
561
  improved_text = full_response
529
562
  else:
@@ -381,37 +381,51 @@ config = load_config()
381
381
  client = NGPTClient(**config)
382
382
 
383
383
  # Get the markdown streamer
384
- markdown_streamer = prettify_streaming_markdown(
384
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(
385
385
  renderer='rich',
386
386
  header_text="Streaming Response:"
387
387
  )
388
388
 
389
- # Start the live display
390
- if markdown_streamer[0]: # Check if setup was successful
391
- markdown_streamer[0].start()
389
+ # Set up spinner while waiting for first content
390
+ if live_display and setup_spinner:
391
+ import threading
392
+ stop_spinner_event = threading.Event()
393
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Waiting for response...")
394
+
395
+ # The live display will start automatically when the first content is received
396
+ # (no need to call live_display.start() manually)
392
397
 
393
- # Use the streamer with the client
398
+ # Use the update function with the client
394
399
  response = client.chat(
395
400
  "Explain quantum computing with code examples",
396
401
  stream=True,
397
402
  markdown_format=True,
398
- stream_callback=markdown_streamer[1]
403
+ stream_callback=update_function
399
404
  )
400
405
 
406
+ # Ensure spinner is stopped if no content was received
407
+ if not stop_spinner_event.is_set():
408
+ stop_spinner_event.set()
409
+
401
410
  # Stop the live display when done
402
- markdown_streamer[0].stop()
411
+ live_display.stop()
403
412
  ```
404
413
 
405
414
  For more control, you can access the live display and update function directly:
406
415
 
407
416
  ```python
408
- live_display, update_function = prettify_streaming_markdown(
417
+ live_display, update_function, setup_spinner = prettify_streaming_markdown(
409
418
  renderer='rich',
410
419
  header_text="Custom Header"
411
420
  )
412
421
 
413
422
  if live_display: # Check if setup was successful
414
- live_display.start()
423
+ # Optional: Set up a spinner while waiting for content
424
+ import threading
425
+ stop_spinner_event = threading.Event()
426
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Processing...")
427
+
428
+ # The first update_function call will automatically start the display and stop the spinner
415
429
 
416
430
  # Update the content manually
417
431
  update_function("# Header\nThis is *formatted* content")
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import shutil
3
3
  import traceback
4
+ import threading
5
+ import sys
4
6
  from .formatters import COLORS
5
7
  from .renderers import prettify_markdown, prettify_streaming_markdown
6
8
 
@@ -197,21 +199,53 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
197
199
  # Setup for stream-prettify
198
200
  stream_callback = None
199
201
  live_display = None
202
+ stop_spinner_func = None
203
+ stop_spinner_event = None
204
+ first_content_received = False
200
205
 
201
206
  if stream_prettify and should_stream:
202
207
  # Get the correct header for interactive mode
203
208
  header = ngpt_header()
204
- live_display, stream_callback = prettify_streaming_markdown(renderer, is_interactive=True, header_text=header)
209
+ live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True, header_text=header)
205
210
  if not live_display:
206
211
  # Fallback to normal prettify if live display setup failed
207
212
  prettify = True
208
213
  stream_prettify = False
209
214
  should_stream = False
210
215
  print(f"{COLORS['yellow']}Falling back to regular prettify mode.{COLORS['reset']}")
211
-
212
- # Start live display if using stream-prettify
213
- if stream_prettify and live_display:
214
- live_display.start()
216
+ else:
217
+ # Create a wrapper for the stream callback that handles spinner and live display
218
+ original_callback = stream_callback
219
+
220
+ def spinner_handling_callback(content):
221
+ nonlocal first_content_received
222
+
223
+ # On first content, stop the spinner and start the live display
224
+ if not first_content_received:
225
+ first_content_received = True
226
+
227
+ # Stop the spinner if it's running
228
+ if stop_spinner_func:
229
+ stop_spinner_func()
230
+
231
+ # Clear the spinner line completely
232
+ sys.stdout.write("\r" + " " * 100 + "\r")
233
+ sys.stdout.flush()
234
+
235
+ # Now start the live display
236
+ if live_display:
237
+ live_display.start()
238
+
239
+ # Call the original callback to update content
240
+ if original_callback:
241
+ original_callback(content)
242
+
243
+ # Use our wrapper callback
244
+ stream_callback = spinner_handling_callback
245
+
246
+ # Set up and start the spinner
247
+ stop_spinner_event = threading.Event()
248
+ stop_spinner_func = setup_spinner(stop_spinner_event, "Waiting for response...", color=COLORS['green'])
215
249
 
216
250
  # Get AI response with conversation history
217
251
  response = client.chat(
@@ -226,8 +260,14 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
226
260
  stream_callback=stream_callback
227
261
  )
228
262
 
263
+ # Ensure spinner is stopped if no content was received
264
+ if stop_spinner_event and not first_content_received:
265
+ stop_spinner_event.set()
266
+ sys.stdout.write("\r" + " " * 100 + "\r")
267
+ sys.stdout.flush()
268
+
229
269
  # Stop live display if using stream-prettify
230
- if stream_prettify and live_display:
270
+ if stream_prettify and live_display and first_content_received:
231
271
  live_display.stop()
232
272
 
233
273
  # Add AI response to conversation history
@@ -97,7 +97,7 @@ def chat_mode(client, args, logger=None):
97
97
  stop_spinner_event = None
98
98
  if args.stream_prettify and live_display:
99
99
  stop_spinner_event = threading.Event()
100
- stop_spinner_func = setup_spinner(stop_spinner_event)
100
+ stop_spinner_func = setup_spinner(stop_spinner_event, color=COLORS['cyan'])
101
101
 
102
102
  # Create a wrapper for the stream callback that will stop the spinner on first content
103
103
  original_callback = stream_callback
@@ -82,7 +82,7 @@ def code_mode(client, args, logger=None):
82
82
  stop_spinner_event = None
83
83
  if use_stream_prettify and live_display:
84
84
  stop_spinner_event = threading.Event()
85
- stop_spinner_func = setup_spinner(stop_spinner_event)
85
+ stop_spinner_func = setup_spinner(stop_spinner_event, color=COLORS['cyan'])
86
86
 
87
87
  # Create a wrapper for the stream callback that will stop the spinner on first content
88
88
  original_callback = stream_callback
@@ -131,8 +131,6 @@ def rewrite_mode(client, args, logger=None):
131
131
  {"role": "user", "content": input_text}
132
132
  ]
133
133
 
134
- print("\nSubmission successful. Waiting for response...")
135
-
136
134
  # Log the messages if logging is enabled
137
135
  if logger:
138
136
  logger.log("system", REWRITE_SYSTEM_PROMPT)
@@ -168,7 +166,7 @@ def rewrite_mode(client, args, logger=None):
168
166
  stop_spinner_event = None
169
167
  if args.stream_prettify and live_display:
170
168
  stop_spinner_event = threading.Event()
171
- stop_spinner_func = setup_spinner(stop_spinner_event)
169
+ stop_spinner_func = setup_spinner(stop_spinner_event, color=COLORS['cyan'])
172
170
 
173
171
  # Create a wrapper for the stream callback that will stop the spinner on first content
174
172
  original_callback = stream_callback
@@ -31,7 +31,7 @@ def shell_mode(client, args, logger=None):
31
31
  spinner_thread = threading.Thread(
32
32
  target=spinner,
33
33
  args=("Generating command...",),
34
- kwargs={"stop_event": stop_spinner}
34
+ kwargs={"stop_event": stop_spinner, "color": COLORS['cyan']}
35
35
  )
36
36
  spinner_thread.daemon = True
37
37
  spinner_thread.start()
@@ -21,8 +21,6 @@ def text_mode(client, args, logger=None):
21
21
  print("Exiting.")
22
22
  return
23
23
 
24
- print("\nSubmission successful. Waiting for response...")
25
-
26
24
  # Log the user message if logging is enabled
27
25
  if logger:
28
26
  logger.log("user", prompt)
@@ -70,7 +68,7 @@ def text_mode(client, args, logger=None):
70
68
  stop_spinner_event = None
71
69
  if args.stream_prettify and live_display:
72
70
  stop_spinner_event = threading.Event()
73
- stop_spinner_func = setup_spinner(stop_spinner_event)
71
+ stop_spinner_func = setup_spinner(stop_spinner_event, color=COLORS['cyan'])
74
72
 
75
73
  # Create a wrapper for the stream callback that will stop the spinner on first content
76
74
  original_callback = stream_callback
@@ -239,8 +239,14 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
239
239
  else:
240
240
  md_obj = Markdown("")
241
241
 
242
- # Initialize the Live display with an empty markdown
243
- live = Live(md_obj, console=console, refresh_per_second=10, auto_refresh=False)
242
+ # Initialize the Live display with vertical overflow handling
243
+ live = Live(
244
+ md_obj,
245
+ console=console,
246
+ refresh_per_second=10,
247
+ auto_refresh=False,
248
+ vertical_overflow="visible" # Attempt to make overflow visible
249
+ )
244
250
 
245
251
  # Track if this is the first content update
246
252
  first_update = True
@@ -259,7 +265,7 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
259
265
  sys.stdout.flush()
260
266
  live.start()
261
267
 
262
- # Update content
268
+ # Update content in live display
263
269
  if is_interactive and header_text:
264
270
  # Update the panel content
265
271
  md_obj.renderable = Markdown(content)
@@ -272,7 +278,7 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
272
278
  live.refresh()
273
279
 
274
280
  # Define a function to set up and start the spinner
275
- def setup_spinner(stop_event, message="Waiting for AI response..."):
281
+ def setup_spinner(stop_event, message="Waiting for AI response...", color=COLORS['cyan']):
276
282
  nonlocal stop_spinner_event, spinner_thread
277
283
  from .ui import spinner
278
284
  import threading
@@ -284,7 +290,7 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
284
290
  spinner_thread = threading.Thread(
285
291
  target=spinner,
286
292
  args=(message,),
287
- kwargs={"stop_event": stop_event}
293
+ kwargs={"stop_event": stop_event, "color": color}
288
294
  )
289
295
  spinner_thread.daemon = True
290
296
  spinner_thread.start()
@@ -1,6 +1,7 @@
1
1
  import sys
2
2
  import time
3
3
  import shutil
4
+ from .formatters import COLORS
4
5
 
5
6
  # Optional imports for enhanced UI
6
7
  try:
@@ -167,18 +168,23 @@ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color
167
168
  """
168
169
  char_duration = 0.2
169
170
 
171
+ # Apply color to message if provided
172
+ colored_message = message
173
+ if color:
174
+ colored_message = f"{color}{message}{COLORS['reset']}"
175
+
170
176
  if stop_event:
171
177
  i = 0
172
178
  while not stop_event.is_set():
173
179
  char = spinner_chars[i % len(spinner_chars)]
174
- print(f"\r{message} {char}", end="", flush=True)
180
+ print(f"\r{colored_message} {char}", end="", flush=True)
175
181
  i += 1
176
182
  time.sleep(char_duration)
177
183
  else:
178
184
  total_chars = int(duration / char_duration)
179
185
  for i in range(total_chars):
180
186
  char = spinner_chars[i % len(spinner_chars)]
181
- print(f"\r{message} {char}", end="", flush=True)
187
+ print(f"\r{colored_message} {char}", end="", flush=True)
182
188
  time.sleep(char_duration)
183
189
 
184
190
  # Clear the line when done
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ngpt"
3
- version = "2.16.4"
3
+ version = "2.16.6"
4
4
  description = "Swiss army knife for LLMs: powerful CLI, interactive chatbot, and flexible Python library. Works with OpenAI, Ollama, Groq, Claude, and any OpenAI-compatible API."
5
5
  authors = [
6
6
  {name = "nazDridoy", email = "nazdridoy399@gmail.com"},
@@ -134,7 +134,7 @@ wheels = [
134
134
 
135
135
  [[package]]
136
136
  name = "ngpt"
137
- version = "2.16.4"
137
+ version = "2.16.6"
138
138
  source = { editable = "." }
139
139
  dependencies = [
140
140
  { name = "prompt-toolkit" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes