ngpt 2.9.0__py3-none-any.whl → 2.9.2__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.
- ngpt/cli/__init__.py +3 -0
- ngpt/cli/config_manager.py +71 -0
- ngpt/cli/formatters.py +239 -0
- ngpt/cli/interactive.py +275 -0
- ngpt/cli/main.py +593 -0
- ngpt/cli/modes/__init__.py +6 -0
- ngpt/cli/modes/chat.py +72 -0
- ngpt/cli/modes/code.py +97 -0
- ngpt/cli/modes/shell.py +46 -0
- ngpt/cli/modes/text.py +72 -0
- ngpt/cli/renderers.py +258 -0
- ngpt/cli/ui.py +155 -0
- ngpt/cli.py +1 -1745
- ngpt/cli_config.py +27 -8
- ngpt/utils/__init__.py +1 -0
- {ngpt-2.9.0.dist-info → ngpt-2.9.2.dist-info}/METADATA +4 -2
- ngpt-2.9.2.dist-info/RECORD +23 -0
- ngpt-2.9.0.dist-info/RECORD +0 -10
- {ngpt-2.9.0.dist-info → ngpt-2.9.2.dist-info}/WHEEL +0 -0
- {ngpt-2.9.0.dist-info → ngpt-2.9.2.dist-info}/entry_points.txt +0 -0
- {ngpt-2.9.0.dist-info → ngpt-2.9.2.dist-info}/licenses/LICENSE +0 -0
ngpt/cli/modes/code.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
from ..formatters import COLORS
|
2
|
+
from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
|
3
|
+
import sys
|
4
|
+
|
5
|
+
def code_mode(client, args):
|
6
|
+
"""Handle the code generation mode.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
client: The NGPTClient instance
|
10
|
+
args: The parsed command-line arguments
|
11
|
+
"""
|
12
|
+
if args.prompt is None:
|
13
|
+
try:
|
14
|
+
print("Enter code description: ", end='')
|
15
|
+
prompt = input()
|
16
|
+
except KeyboardInterrupt:
|
17
|
+
print("\nInput cancelled by user. Exiting gracefully.")
|
18
|
+
sys.exit(130)
|
19
|
+
else:
|
20
|
+
prompt = args.prompt
|
21
|
+
|
22
|
+
# Setup for streaming and prettify logic
|
23
|
+
stream_callback = None
|
24
|
+
live_display = None
|
25
|
+
should_stream = True # Default to streaming
|
26
|
+
use_stream_prettify = False
|
27
|
+
use_regular_prettify = False
|
28
|
+
|
29
|
+
# Determine final behavior based on flag priority
|
30
|
+
if args.stream_prettify:
|
31
|
+
# Highest priority: stream-prettify
|
32
|
+
if has_markdown_renderer('rich'):
|
33
|
+
should_stream = True
|
34
|
+
use_stream_prettify = True
|
35
|
+
live_display, stream_callback = prettify_streaming_markdown(args.renderer)
|
36
|
+
if not live_display:
|
37
|
+
# Fallback if live display fails
|
38
|
+
use_stream_prettify = False
|
39
|
+
use_regular_prettify = True
|
40
|
+
should_stream = False
|
41
|
+
print(f"{COLORS['yellow']}Live display setup failed. Falling back to regular prettify mode.{COLORS['reset']}")
|
42
|
+
else:
|
43
|
+
# Rich not available for stream-prettify
|
44
|
+
print(f"{COLORS['yellow']}Warning: Rich is not available for --stream-prettify. Install with: pip install \"ngpt[full]\".{COLORS['reset']}")
|
45
|
+
print(f"{COLORS['yellow']}Falling back to default streaming without prettify.{COLORS['reset']}")
|
46
|
+
should_stream = True
|
47
|
+
use_stream_prettify = False
|
48
|
+
elif args.no_stream:
|
49
|
+
# Second priority: no-stream
|
50
|
+
should_stream = False
|
51
|
+
use_regular_prettify = False # No prettify if no streaming
|
52
|
+
elif args.prettify:
|
53
|
+
# Third priority: prettify (requires disabling stream)
|
54
|
+
if has_markdown_renderer(args.renderer):
|
55
|
+
should_stream = False
|
56
|
+
use_regular_prettify = True
|
57
|
+
print(f"{COLORS['yellow']}Note: Streaming disabled to enable regular markdown rendering (--prettify).{COLORS['reset']}")
|
58
|
+
else:
|
59
|
+
# Renderer not available for prettify
|
60
|
+
print(f"{COLORS['yellow']}Warning: Renderer '{args.renderer}' not available for --prettify.{COLORS['reset']}")
|
61
|
+
show_available_renderers()
|
62
|
+
print(f"{COLORS['yellow']}Falling back to default streaming without prettify.{COLORS['reset']}")
|
63
|
+
should_stream = True
|
64
|
+
use_regular_prettify = False
|
65
|
+
# else: Default is should_stream = True
|
66
|
+
|
67
|
+
print("\nGenerating code...")
|
68
|
+
|
69
|
+
# Start live display if using stream-prettify
|
70
|
+
if use_stream_prettify and live_display:
|
71
|
+
live_display.start()
|
72
|
+
|
73
|
+
generated_code = client.generate_code(
|
74
|
+
prompt=prompt,
|
75
|
+
language=args.language,
|
76
|
+
web_search=args.web_search,
|
77
|
+
temperature=args.temperature,
|
78
|
+
top_p=args.top_p,
|
79
|
+
max_tokens=args.max_tokens,
|
80
|
+
# Request markdown from API if any prettify option is active
|
81
|
+
markdown_format=use_regular_prettify or use_stream_prettify,
|
82
|
+
stream=should_stream,
|
83
|
+
stream_callback=stream_callback
|
84
|
+
)
|
85
|
+
|
86
|
+
# Stop live display if using stream-prettify
|
87
|
+
if use_stream_prettify and live_display:
|
88
|
+
live_display.stop()
|
89
|
+
|
90
|
+
# Print non-streamed output if needed
|
91
|
+
if generated_code and not should_stream:
|
92
|
+
if use_regular_prettify:
|
93
|
+
print("\nGenerated code:")
|
94
|
+
prettify_markdown(generated_code, args.renderer)
|
95
|
+
else:
|
96
|
+
# Should only happen if --no-stream was used without prettify
|
97
|
+
print(f"\nGenerated code:\n{generated_code}")
|
ngpt/cli/modes/shell.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
from ..formatters import COLORS
|
2
|
+
import subprocess
|
3
|
+
import sys
|
4
|
+
|
5
|
+
def shell_mode(client, args):
|
6
|
+
"""Handle the shell command generation mode.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
client: The NGPTClient instance
|
10
|
+
args: The parsed command-line arguments
|
11
|
+
"""
|
12
|
+
if args.prompt is None:
|
13
|
+
try:
|
14
|
+
print("Enter shell command description: ", end='')
|
15
|
+
prompt = input()
|
16
|
+
except KeyboardInterrupt:
|
17
|
+
print("\nInput cancelled by user. Exiting gracefully.")
|
18
|
+
sys.exit(130)
|
19
|
+
else:
|
20
|
+
prompt = args.prompt
|
21
|
+
|
22
|
+
command = client.generate_shell_command(prompt, web_search=args.web_search,
|
23
|
+
temperature=args.temperature, top_p=args.top_p,
|
24
|
+
max_tokens=args.max_tokens)
|
25
|
+
if not command:
|
26
|
+
return # Error already printed by client
|
27
|
+
|
28
|
+
print(f"\nGenerated command: {command}")
|
29
|
+
|
30
|
+
try:
|
31
|
+
print("Do you want to execute this command? [y/N] ", end='')
|
32
|
+
response = input().lower()
|
33
|
+
except KeyboardInterrupt:
|
34
|
+
print("\nCommand execution cancelled by user.")
|
35
|
+
return
|
36
|
+
|
37
|
+
if response == 'y' or response == 'yes':
|
38
|
+
try:
|
39
|
+
try:
|
40
|
+
print("\nExecuting command... (Press Ctrl+C to cancel)")
|
41
|
+
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
42
|
+
print(f"\nOutput:\n{result.stdout}")
|
43
|
+
except KeyboardInterrupt:
|
44
|
+
print("\nCommand execution cancelled by user.")
|
45
|
+
except subprocess.CalledProcessError as e:
|
46
|
+
print(f"\nError:\n{e.stderr}")
|
ngpt/cli/modes/text.py
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
from ..formatters import COLORS
|
2
|
+
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
|
+
from ..ui import get_multiline_input
|
4
|
+
|
5
|
+
def text_mode(client, args):
|
6
|
+
"""Handle the multi-line text input mode.
|
7
|
+
|
8
|
+
Args:
|
9
|
+
client: The NGPTClient instance
|
10
|
+
args: The parsed command-line arguments
|
11
|
+
"""
|
12
|
+
if args.prompt is not None:
|
13
|
+
prompt = args.prompt
|
14
|
+
else:
|
15
|
+
prompt = get_multiline_input()
|
16
|
+
if prompt is None:
|
17
|
+
# Input was cancelled or empty
|
18
|
+
print("Exiting.")
|
19
|
+
return
|
20
|
+
|
21
|
+
print("\nSubmission successful. Waiting for response...")
|
22
|
+
|
23
|
+
# Create messages array with preprompt if available
|
24
|
+
messages = None
|
25
|
+
if args.preprompt:
|
26
|
+
messages = [
|
27
|
+
{"role": "system", "content": args.preprompt},
|
28
|
+
{"role": "user", "content": prompt}
|
29
|
+
]
|
30
|
+
|
31
|
+
# Set default streaming behavior based on --no-stream and --prettify arguments
|
32
|
+
should_stream = not args.no_stream and not args.prettify
|
33
|
+
|
34
|
+
# If stream-prettify is enabled
|
35
|
+
stream_callback = None
|
36
|
+
live_display = None
|
37
|
+
|
38
|
+
if args.stream_prettify:
|
39
|
+
should_stream = True # Enable streaming
|
40
|
+
# This is the standard mode, not interactive
|
41
|
+
live_display, stream_callback = prettify_streaming_markdown(args.renderer)
|
42
|
+
if not live_display:
|
43
|
+
# Fallback to normal prettify if live display setup failed
|
44
|
+
args.prettify = True
|
45
|
+
args.stream_prettify = False
|
46
|
+
should_stream = False
|
47
|
+
print(f"{COLORS['yellow']}Falling back to regular prettify mode.{COLORS['reset']}")
|
48
|
+
|
49
|
+
# If regular prettify is enabled with streaming, inform the user
|
50
|
+
if args.prettify and not args.no_stream:
|
51
|
+
print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
|
52
|
+
|
53
|
+
# Start live display if using stream-prettify
|
54
|
+
if args.stream_prettify and live_display:
|
55
|
+
live_display.start()
|
56
|
+
|
57
|
+
response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
|
58
|
+
temperature=args.temperature, top_p=args.top_p,
|
59
|
+
max_tokens=args.max_tokens, messages=messages,
|
60
|
+
markdown_format=args.prettify or args.stream_prettify,
|
61
|
+
stream_callback=stream_callback)
|
62
|
+
|
63
|
+
# Stop live display if using stream-prettify
|
64
|
+
if args.stream_prettify and live_display:
|
65
|
+
live_display.stop()
|
66
|
+
|
67
|
+
# Handle non-stream response or regular prettify
|
68
|
+
if (args.no_stream or args.prettify) and response:
|
69
|
+
if args.prettify:
|
70
|
+
prettify_markdown(response, args.renderer)
|
71
|
+
else:
|
72
|
+
print(response)
|
ngpt/cli/renderers.py
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
import sys
|
2
|
+
import os
|
3
|
+
import shutil
|
4
|
+
import subprocess
|
5
|
+
import tempfile
|
6
|
+
from .formatters import COLORS
|
7
|
+
|
8
|
+
# Try to import markdown rendering libraries
|
9
|
+
try:
|
10
|
+
import rich
|
11
|
+
from rich.markdown import Markdown
|
12
|
+
from rich.console import Console
|
13
|
+
from rich.live import Live
|
14
|
+
from rich.text import Text
|
15
|
+
from rich.panel import Panel
|
16
|
+
import rich.box
|
17
|
+
HAS_RICH = True
|
18
|
+
except ImportError:
|
19
|
+
HAS_RICH = False
|
20
|
+
|
21
|
+
# Try to import the glow command if available
|
22
|
+
def has_glow_installed():
|
23
|
+
"""Check if glow is installed in the system."""
|
24
|
+
return shutil.which("glow") is not None
|
25
|
+
|
26
|
+
HAS_GLOW = has_glow_installed()
|
27
|
+
|
28
|
+
def has_markdown_renderer(renderer='auto'):
|
29
|
+
"""Check if the specified markdown renderer is available.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
renderer (str): Which renderer to check: 'auto', 'rich', or 'glow'
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
bool: True if the renderer is available, False otherwise
|
36
|
+
"""
|
37
|
+
if renderer == 'auto':
|
38
|
+
return HAS_RICH or HAS_GLOW
|
39
|
+
elif renderer == 'rich':
|
40
|
+
return HAS_RICH
|
41
|
+
elif renderer == 'glow':
|
42
|
+
return HAS_GLOW
|
43
|
+
else:
|
44
|
+
return False
|
45
|
+
|
46
|
+
def show_available_renderers():
|
47
|
+
"""Show which markdown renderers are available and their status."""
|
48
|
+
print(f"\n{COLORS['cyan']}{COLORS['bold']}Available Markdown Renderers:{COLORS['reset']}")
|
49
|
+
|
50
|
+
if HAS_GLOW:
|
51
|
+
print(f" {COLORS['green']}✓ Glow{COLORS['reset']} - Terminal-based Markdown renderer")
|
52
|
+
else:
|
53
|
+
print(f" {COLORS['yellow']}✗ Glow{COLORS['reset']} - Not installed (https://github.com/charmbracelet/glow)")
|
54
|
+
|
55
|
+
if HAS_RICH:
|
56
|
+
print(f" {COLORS['green']}✓ Rich{COLORS['reset']} - Python library for terminal formatting (Recommended)")
|
57
|
+
else:
|
58
|
+
print(f" {COLORS['yellow']}✗ Rich{COLORS['reset']} - Not installed (pip install \"ngpt[full]\" or pip install rich)")
|
59
|
+
|
60
|
+
if not HAS_GLOW and not HAS_RICH:
|
61
|
+
print(f"\n{COLORS['yellow']}To enable prettified markdown output, install one of the above renderers.{COLORS['reset']}")
|
62
|
+
print(f"{COLORS['yellow']}For Rich: pip install \"ngpt[full]\" or pip install rich{COLORS['reset']}")
|
63
|
+
else:
|
64
|
+
renderers = []
|
65
|
+
if HAS_RICH:
|
66
|
+
renderers.append("rich")
|
67
|
+
if HAS_GLOW:
|
68
|
+
renderers.append("glow")
|
69
|
+
print(f"\n{COLORS['green']}Usage examples:{COLORS['reset']}")
|
70
|
+
print(f" ngpt --prettify \"Your prompt here\" {COLORS['gray']}# Beautify markdown responses{COLORS['reset']}")
|
71
|
+
print(f" ngpt -c --prettify \"Write a sort function\" {COLORS['gray']}# Syntax highlight generated code{COLORS['reset']}")
|
72
|
+
if renderers:
|
73
|
+
renderer = renderers[0]
|
74
|
+
print(f" ngpt --prettify --renderer={renderer} \"Your prompt\" {COLORS['gray']}# Specify renderer{COLORS['reset']}")
|
75
|
+
|
76
|
+
print("")
|
77
|
+
|
78
|
+
def warn_if_no_markdown_renderer(renderer='auto'):
|
79
|
+
"""Warn the user if the specified markdown renderer is not available.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
renderer (str): Which renderer to check: 'auto', 'rich', or 'glow'
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
bool: True if the renderer is available, False otherwise
|
86
|
+
"""
|
87
|
+
if has_markdown_renderer(renderer):
|
88
|
+
return True
|
89
|
+
|
90
|
+
if renderer == 'auto':
|
91
|
+
print(f"{COLORS['yellow']}Warning: No markdown rendering library available.{COLORS['reset']}")
|
92
|
+
print(f"{COLORS['yellow']}Install with: pip install \"ngpt[full]\"{COLORS['reset']}")
|
93
|
+
print(f"{COLORS['yellow']}Or install 'glow' from https://github.com/charmbracelet/glow{COLORS['reset']}")
|
94
|
+
elif renderer == 'rich':
|
95
|
+
print(f"{COLORS['yellow']}Warning: Rich is not available.{COLORS['reset']}")
|
96
|
+
print(f"{COLORS['yellow']}Install with: pip install \"ngpt[full]\" or pip install rich{COLORS['reset']}")
|
97
|
+
elif renderer == 'glow':
|
98
|
+
print(f"{COLORS['yellow']}Warning: Glow is not available.{COLORS['reset']}")
|
99
|
+
print(f"{COLORS['yellow']}Install from https://github.com/charmbracelet/glow{COLORS['reset']}")
|
100
|
+
else:
|
101
|
+
print(f"{COLORS['yellow']}Error: Invalid renderer '{renderer}'. Use 'auto', 'rich', or 'glow'.{COLORS['reset']}")
|
102
|
+
|
103
|
+
return False
|
104
|
+
|
105
|
+
def prettify_markdown(text, renderer='auto'):
|
106
|
+
"""Render markdown text with beautiful formatting using either Rich or Glow.
|
107
|
+
|
108
|
+
The function handles both general markdown and code blocks with syntax highlighting.
|
109
|
+
For code generation mode, it automatically wraps the code in markdown code blocks.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
text (str): Markdown text to render
|
113
|
+
renderer (str): Which renderer to use: 'auto', 'rich', or 'glow'
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
bool: True if rendering was successful, False otherwise
|
117
|
+
"""
|
118
|
+
# For 'auto', prefer rich if available, otherwise use glow
|
119
|
+
if renderer == 'auto':
|
120
|
+
if HAS_RICH:
|
121
|
+
return prettify_markdown(text, 'rich')
|
122
|
+
elif HAS_GLOW:
|
123
|
+
return prettify_markdown(text, 'glow')
|
124
|
+
else:
|
125
|
+
return False
|
126
|
+
|
127
|
+
# Use glow for rendering
|
128
|
+
elif renderer == 'glow':
|
129
|
+
if not HAS_GLOW:
|
130
|
+
print(f"{COLORS['yellow']}Warning: Glow is not available. Install from https://github.com/charmbracelet/glow{COLORS['reset']}")
|
131
|
+
# Fall back to rich if available
|
132
|
+
if HAS_RICH:
|
133
|
+
print(f"{COLORS['yellow']}Falling back to Rich renderer.{COLORS['reset']}")
|
134
|
+
return prettify_markdown(text, 'rich')
|
135
|
+
return False
|
136
|
+
|
137
|
+
# Use glow
|
138
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as temp:
|
139
|
+
temp_filename = temp.name
|
140
|
+
temp.write(text)
|
141
|
+
|
142
|
+
try:
|
143
|
+
# Execute glow on the temporary file
|
144
|
+
subprocess.run(["glow", temp_filename], check=True)
|
145
|
+
os.unlink(temp_filename)
|
146
|
+
return True
|
147
|
+
except Exception as e:
|
148
|
+
print(f"{COLORS['yellow']}Error using glow: {str(e)}{COLORS['reset']}")
|
149
|
+
os.unlink(temp_filename)
|
150
|
+
|
151
|
+
# Fall back to rich if available
|
152
|
+
if HAS_RICH:
|
153
|
+
print(f"{COLORS['yellow']}Falling back to Rich renderer.{COLORS['reset']}")
|
154
|
+
return prettify_markdown(text, 'rich')
|
155
|
+
return False
|
156
|
+
|
157
|
+
# Use rich for rendering
|
158
|
+
elif renderer == 'rich':
|
159
|
+
if not HAS_RICH:
|
160
|
+
print(f"{COLORS['yellow']}Warning: Rich is not available.{COLORS['reset']}")
|
161
|
+
print(f"{COLORS['yellow']}Install with: pip install \"ngpt[full]\" or pip install rich{COLORS['reset']}")
|
162
|
+
# Fall back to glow if available
|
163
|
+
if HAS_GLOW:
|
164
|
+
print(f"{COLORS['yellow']}Falling back to Glow renderer.{COLORS['reset']}")
|
165
|
+
return prettify_markdown(text, 'glow')
|
166
|
+
return False
|
167
|
+
|
168
|
+
# Use rich
|
169
|
+
try:
|
170
|
+
console = Console()
|
171
|
+
md = Markdown(text)
|
172
|
+
console.print(md)
|
173
|
+
return True
|
174
|
+
except Exception as e:
|
175
|
+
print(f"{COLORS['yellow']}Error using rich for markdown: {str(e)}{COLORS['reset']}")
|
176
|
+
return False
|
177
|
+
|
178
|
+
# Invalid renderer specified
|
179
|
+
else:
|
180
|
+
print(f"{COLORS['yellow']}Error: Invalid renderer '{renderer}'. Use 'auto', 'rich', or 'glow'.{COLORS['reset']}")
|
181
|
+
return False
|
182
|
+
|
183
|
+
def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_text=None):
|
184
|
+
"""Set up streaming markdown rendering.
|
185
|
+
|
186
|
+
This function creates a live display context for rendering markdown
|
187
|
+
that can be updated in real-time as streaming content arrives.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
renderer (str): Which renderer to use (currently only 'rich' is supported for streaming)
|
191
|
+
is_interactive (bool): Whether this is being used in interactive mode
|
192
|
+
header_text (str): Header text to include at the top (for interactive mode)
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
tuple: (live_display, update_function) if successful, (None, None) otherwise
|
196
|
+
"""
|
197
|
+
# Only warn if explicitly specifying a renderer other than 'rich' or 'auto'
|
198
|
+
if renderer != 'rich' and renderer != 'auto':
|
199
|
+
print(f"{COLORS['yellow']}Warning: Streaming prettify only supports 'rich' renderer currently.{COLORS['reset']}")
|
200
|
+
print(f"{COLORS['yellow']}Falling back to Rich renderer.{COLORS['reset']}")
|
201
|
+
|
202
|
+
# Always use rich for streaming prettify
|
203
|
+
renderer = 'rich'
|
204
|
+
|
205
|
+
if not HAS_RICH:
|
206
|
+
print(f"{COLORS['yellow']}Warning: Rich is not available for streaming prettify.{COLORS['reset']}")
|
207
|
+
print(f"{COLORS['yellow']}Install with: pip install \"ngpt[full]\" or pip install rich{COLORS['reset']}")
|
208
|
+
return None, None
|
209
|
+
|
210
|
+
try:
|
211
|
+
from rich.live import Live
|
212
|
+
from rich.markdown import Markdown
|
213
|
+
from rich.console import Console
|
214
|
+
from rich.text import Text
|
215
|
+
from rich.panel import Panel
|
216
|
+
import rich.box
|
217
|
+
|
218
|
+
console = Console()
|
219
|
+
|
220
|
+
# Create an empty markdown object to start with
|
221
|
+
if is_interactive and header_text:
|
222
|
+
# For interactive mode, include header in a panel
|
223
|
+
# Clean up the header text to avoid duplication - use just "🤖 nGPT" instead of "╭─ 🤖 nGPT"
|
224
|
+
clean_header = "🤖 nGPT"
|
225
|
+
panel_title = Text(clean_header, style="cyan bold")
|
226
|
+
|
227
|
+
# Create a nicer, more compact panel
|
228
|
+
padding = (1, 1) # Less horizontal padding (left, right)
|
229
|
+
md_obj = Panel(
|
230
|
+
Markdown(""),
|
231
|
+
title=panel_title,
|
232
|
+
title_align="left",
|
233
|
+
border_style="cyan",
|
234
|
+
padding=padding,
|
235
|
+
width=console.width - 4, # Make panel slightly narrower than console
|
236
|
+
box=rich.box.ROUNDED
|
237
|
+
)
|
238
|
+
else:
|
239
|
+
md_obj = Markdown("")
|
240
|
+
|
241
|
+
# Initialize the Live display with an empty markdown
|
242
|
+
live = Live(md_obj, console=console, refresh_per_second=10)
|
243
|
+
|
244
|
+
# Define an update function that will be called with new content
|
245
|
+
def update_content(content):
|
246
|
+
nonlocal md_obj
|
247
|
+
if is_interactive and header_text:
|
248
|
+
# Update the panel content
|
249
|
+
md_obj.renderable = Markdown(content)
|
250
|
+
live.update(md_obj)
|
251
|
+
else:
|
252
|
+
md_obj = Markdown(content)
|
253
|
+
live.update(md_obj)
|
254
|
+
|
255
|
+
return live, update_content
|
256
|
+
except Exception as e:
|
257
|
+
print(f"{COLORS['yellow']}Error setting up Rich streaming display: {str(e)}{COLORS['reset']}")
|
258
|
+
return None, None
|
ngpt/cli/ui.py
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
import sys
|
2
|
+
import shutil
|
3
|
+
from .formatters import COLORS
|
4
|
+
|
5
|
+
# Optional imports for enhanced UI
|
6
|
+
try:
|
7
|
+
from prompt_toolkit import prompt as pt_prompt
|
8
|
+
from prompt_toolkit.styles import Style
|
9
|
+
from prompt_toolkit.key_binding import KeyBindings
|
10
|
+
from prompt_toolkit.formatted_text import HTML
|
11
|
+
from prompt_toolkit.layout.containers import HSplit, Window
|
12
|
+
from prompt_toolkit.layout.layout import Layout
|
13
|
+
from prompt_toolkit.layout.controls import FormattedTextControl
|
14
|
+
from prompt_toolkit.application import Application
|
15
|
+
from prompt_toolkit.widgets import TextArea
|
16
|
+
from prompt_toolkit.layout.margins import ScrollbarMargin
|
17
|
+
from prompt_toolkit.filters import to_filter
|
18
|
+
from prompt_toolkit.history import InMemoryHistory
|
19
|
+
HAS_PROMPT_TOOLKIT = True
|
20
|
+
except ImportError:
|
21
|
+
HAS_PROMPT_TOOLKIT = False
|
22
|
+
|
23
|
+
def create_multiline_editor():
|
24
|
+
"""Create a multi-line editor with prompt_toolkit.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
tuple: (app, has_prompt_toolkit) - the editor application and a boolean
|
28
|
+
indicating if prompt_toolkit is available
|
29
|
+
"""
|
30
|
+
if not HAS_PROMPT_TOOLKIT:
|
31
|
+
return None, False
|
32
|
+
|
33
|
+
try:
|
34
|
+
# Create key bindings
|
35
|
+
kb = KeyBindings()
|
36
|
+
|
37
|
+
# Explicitly bind Ctrl+D to exit
|
38
|
+
@kb.add('c-d')
|
39
|
+
def _(event):
|
40
|
+
event.app.exit(result=event.app.current_buffer.text)
|
41
|
+
|
42
|
+
# Explicitly bind Ctrl+C to exit
|
43
|
+
@kb.add('c-c')
|
44
|
+
def _(event):
|
45
|
+
event.app.exit(result=None)
|
46
|
+
print("\nInput cancelled by user. Exiting gracefully.")
|
47
|
+
sys.exit(130)
|
48
|
+
|
49
|
+
# Get terminal dimensions
|
50
|
+
term_width, term_height = shutil.get_terminal_size()
|
51
|
+
|
52
|
+
# Create a styled TextArea
|
53
|
+
text_area = TextArea(
|
54
|
+
style="class:input-area",
|
55
|
+
multiline=True,
|
56
|
+
wrap_lines=True,
|
57
|
+
width=term_width - 10,
|
58
|
+
height=min(15, term_height - 10),
|
59
|
+
prompt=HTML("<ansicyan><b>> </b></ansicyan>"),
|
60
|
+
scrollbar=True,
|
61
|
+
focus_on_click=True,
|
62
|
+
lexer=None,
|
63
|
+
)
|
64
|
+
text_area.window.right_margins = [ScrollbarMargin(display_arrows=True)]
|
65
|
+
|
66
|
+
# Create a title bar
|
67
|
+
title_bar = FormattedTextControl(
|
68
|
+
HTML("<ansicyan><b> nGPT Multi-line Editor </b></ansicyan>")
|
69
|
+
)
|
70
|
+
|
71
|
+
# Create a status bar with key bindings info
|
72
|
+
status_bar = FormattedTextControl(
|
73
|
+
HTML("<ansiblue><b>Ctrl+D</b></ansiblue>: Submit | <ansiblue><b>Ctrl+C</b></ansiblue>: Cancel | <ansiblue><b>↑↓←→</b></ansiblue>: Navigate")
|
74
|
+
)
|
75
|
+
|
76
|
+
# Create the layout
|
77
|
+
layout = Layout(
|
78
|
+
HSplit([
|
79
|
+
Window(title_bar, height=1),
|
80
|
+
Window(height=1, char="─", style="class:separator"),
|
81
|
+
text_area,
|
82
|
+
Window(height=1, char="─", style="class:separator"),
|
83
|
+
Window(status_bar, height=1),
|
84
|
+
])
|
85
|
+
)
|
86
|
+
|
87
|
+
# Create a style
|
88
|
+
style = Style.from_dict({
|
89
|
+
"separator": "ansicyan",
|
90
|
+
"input-area": "fg:ansiwhite",
|
91
|
+
"cursor": "bg:ansiwhite fg:ansiblack",
|
92
|
+
})
|
93
|
+
|
94
|
+
# Create and return the application
|
95
|
+
app = Application(
|
96
|
+
layout=layout,
|
97
|
+
full_screen=False,
|
98
|
+
key_bindings=kb,
|
99
|
+
style=style,
|
100
|
+
mouse_support=True,
|
101
|
+
)
|
102
|
+
|
103
|
+
return app, True
|
104
|
+
|
105
|
+
except Exception as e:
|
106
|
+
print(f"Error creating editor: {e}")
|
107
|
+
return None, False
|
108
|
+
|
109
|
+
def get_multiline_input():
|
110
|
+
"""Get multi-line input from the user using either prompt_toolkit or standard input.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
str: The user's input text, or None if cancelled
|
114
|
+
"""
|
115
|
+
editor_app, has_editor = create_multiline_editor()
|
116
|
+
|
117
|
+
if has_editor and editor_app:
|
118
|
+
print("\033[94m\033[1m" + "Multi-line Input Mode" + "\033[0m")
|
119
|
+
print("Press Ctrl+D to submit, Ctrl+C to exit")
|
120
|
+
print("Use arrow keys to navigate, Enter for new line")
|
121
|
+
|
122
|
+
try:
|
123
|
+
prompt = editor_app.run()
|
124
|
+
if not prompt or not prompt.strip():
|
125
|
+
print("Empty prompt. Exiting.")
|
126
|
+
return None
|
127
|
+
return prompt
|
128
|
+
except KeyboardInterrupt:
|
129
|
+
print("\nInput cancelled by user. Exiting gracefully.")
|
130
|
+
return None
|
131
|
+
else:
|
132
|
+
# Fallback to standard input with a better implementation
|
133
|
+
print("Enter your multi-line prompt (press Ctrl+D to submit):")
|
134
|
+
if not HAS_PROMPT_TOOLKIT:
|
135
|
+
print("Note: Install 'prompt_toolkit' package for an enhanced input experience")
|
136
|
+
|
137
|
+
# Use a more robust approach for multiline input without prompt_toolkit
|
138
|
+
lines = []
|
139
|
+
try:
|
140
|
+
while True:
|
141
|
+
try:
|
142
|
+
line = input()
|
143
|
+
lines.append(line)
|
144
|
+
except EOFError: # Ctrl+D was pressed
|
145
|
+
break
|
146
|
+
|
147
|
+
prompt = "\n".join(lines)
|
148
|
+
if not prompt.strip():
|
149
|
+
print("Empty prompt. Exiting.")
|
150
|
+
return None
|
151
|
+
return prompt
|
152
|
+
|
153
|
+
except KeyboardInterrupt:
|
154
|
+
print("\nInput cancelled by user. Exiting gracefully.")
|
155
|
+
return None
|