ngpt 3.1.1__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ngpt might be problematic. Click here for more details.
- ngpt/cli/args.py +1 -1
- ngpt/cli/interactive.py +68 -18
- ngpt/cli/modes/chat.py +18 -2
- ngpt/cli/modes/code.py +18 -3
- ngpt/cli/modes/rewrite.py +17 -2
- ngpt/cli/modes/shell.py +19 -3
- ngpt/cli/modes/text.py +18 -2
- ngpt/cli/renderers.py +45 -10
- ngpt/cli/ui.py +17 -5
- ngpt/client.py +0 -12
- ngpt/utils/__init__.py +7 -1
- ngpt/utils/web_search.py +270 -0
- {ngpt-3.1.1.dist-info → ngpt-3.3.0.dist-info}/METADATA +5 -3
- ngpt-3.3.0.dist-info/RECORD +28 -0
- ngpt-3.1.1.dist-info/RECORD +0 -27
- {ngpt-3.1.1.dist-info → ngpt-3.3.0.dist-info}/WHEEL +0 -0
- {ngpt-3.1.1.dist-info → ngpt-3.3.0.dist-info}/entry_points.txt +0 -0
- {ngpt-3.1.1.dist-info → ngpt-3.3.0.dist-info}/licenses/LICENSE +0 -0
ngpt/cli/args.py
CHANGED
@@ -68,7 +68,7 @@ def setup_argument_parser():
|
|
68
68
|
global_group.add_argument('--model',
|
69
69
|
help='Model to use')
|
70
70
|
global_group.add_argument('--web-search', action='store_true',
|
71
|
-
help='Enable web search capability
|
71
|
+
help='Enable web search capability using DuckDuckGo to enhance prompts with relevant information')
|
72
72
|
global_group.add_argument('--temperature', type=float, default=0.7,
|
73
73
|
help='Set temperature (controls randomness, default: 0.7)')
|
74
74
|
global_group.add_argument('--top_p', type=float, default=1.0,
|
ngpt/cli/interactive.py
CHANGED
@@ -6,6 +6,7 @@ import sys
|
|
6
6
|
import time
|
7
7
|
from .formatters import COLORS
|
8
8
|
from .renderers import prettify_markdown, prettify_streaming_markdown
|
9
|
+
from ..utils import enhance_prompt_with_web_search
|
9
10
|
|
10
11
|
# Optional imports for enhanced UI
|
11
12
|
try:
|
@@ -64,9 +65,20 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
64
65
|
if logger:
|
65
66
|
print(f"{COLORS['green']}Logging conversation to: {logger.get_log_path()}{COLORS['reset']}")
|
66
67
|
|
68
|
+
# Display a note about web search if enabled
|
69
|
+
if web_search:
|
70
|
+
print(f"{COLORS['green']}Web search capability is enabled.{COLORS['reset']}")
|
71
|
+
|
72
|
+
# Display a note about markdown rendering only once at the beginning
|
73
|
+
if prettify and not no_stream and not stream_prettify:
|
74
|
+
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
75
|
+
|
67
76
|
# Custom separator - use the same length for consistency
|
68
77
|
def print_separator():
|
69
|
-
|
78
|
+
# Make sure there's exactly one newline before and after
|
79
|
+
# Use sys.stdout.write for direct control, avoiding any extra newlines
|
80
|
+
sys.stdout.write(f"\n{separator}\n")
|
81
|
+
sys.stdout.flush()
|
70
82
|
|
71
83
|
# Initialize conversation history
|
72
84
|
system_prompt = preprompt if preprompt else "You are a helpful assistant."
|
@@ -181,20 +193,49 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
181
193
|
# Log user message if logging is enabled
|
182
194
|
if logger:
|
183
195
|
logger.log("user", user_input)
|
196
|
+
|
197
|
+
# Enhance prompt with web search if enabled
|
198
|
+
enhanced_prompt = user_input
|
199
|
+
if web_search:
|
200
|
+
try:
|
201
|
+
print(f"{COLORS['cyan']}Searching the web...{COLORS['reset']}")
|
202
|
+
enhanced_prompt = enhance_prompt_with_web_search(user_input, logger=logger)
|
203
|
+
print(f"{COLORS['green']}Enhanced input with web search results.{COLORS['reset']}")
|
204
|
+
|
205
|
+
# Update the user message in conversation with enhanced prompt
|
206
|
+
for i in range(len(conversation) - 1, -1, -1):
|
207
|
+
if conversation[i]["role"] == "user" and conversation[i]["content"] == user_input:
|
208
|
+
conversation[i]["content"] = enhanced_prompt
|
209
|
+
break
|
210
|
+
|
211
|
+
# Log the enhanced prompt if logging is enabled
|
212
|
+
if logger:
|
213
|
+
# Use "web_search" role instead of "system" for clearer logs
|
214
|
+
logger.log("web_search", enhanced_prompt.replace(user_input, "").strip())
|
215
|
+
except Exception as e:
|
216
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
217
|
+
# Continue with the original prompt if web search fails
|
184
218
|
|
185
|
-
# Print assistant indicator with formatting
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
219
|
+
# Print assistant indicator with formatting - but only if we're not going to show a rich formatted box
|
220
|
+
# With Rich prettify, no header should be printed as the Rich panel already includes it
|
221
|
+
should_print_header = True
|
222
|
+
|
223
|
+
# Determine if we should print a header based on formatting options
|
224
|
+
if prettify and renderer != 'glow':
|
225
|
+
# Don't print header for Rich prettify
|
226
|
+
should_print_header = False
|
227
|
+
|
228
|
+
# Print the header if needed
|
229
|
+
if should_print_header:
|
230
|
+
if not no_stream and not stream_prettify:
|
231
|
+
print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
|
232
|
+
elif not stream_prettify:
|
233
|
+
print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
|
190
234
|
|
191
|
-
#
|
235
|
+
# Determine streaming behavior
|
192
236
|
if prettify and not no_stream and not stream_prettify:
|
193
|
-
print(f"\n{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
|
194
|
-
print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
|
195
237
|
should_stream = False
|
196
238
|
else:
|
197
|
-
# Regular behavior with stream-prettify taking precedence
|
198
239
|
should_stream = not no_stream
|
199
240
|
|
200
241
|
# Setup for stream-prettify
|
@@ -205,9 +246,15 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
205
246
|
first_content_received = False
|
206
247
|
|
207
248
|
if stream_prettify and should_stream:
|
208
|
-
#
|
209
|
-
|
210
|
-
|
249
|
+
# Don't pass the header_text for rich renderer as it already creates its own header,
|
250
|
+
# but pass it for other renderers like glow
|
251
|
+
if renderer == 'rich' or renderer == 'auto':
|
252
|
+
live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True)
|
253
|
+
else:
|
254
|
+
# Get the correct header for interactive mode for non-rich renderers
|
255
|
+
header = ngpt_header()
|
256
|
+
live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True, header_text=header)
|
257
|
+
|
211
258
|
if not live_display:
|
212
259
|
# Fallback to normal prettify if live display setup failed
|
213
260
|
prettify = True
|
@@ -229,8 +276,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
229
276
|
if stop_spinner_func:
|
230
277
|
stop_spinner_func()
|
231
278
|
|
232
|
-
# Clear the spinner line completely
|
233
|
-
|
279
|
+
# Clear the spinner line completely without leaving extra newlines
|
280
|
+
# Use direct terminal control to ensure consistency
|
281
|
+
sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
|
234
282
|
sys.stdout.flush()
|
235
283
|
|
236
284
|
# Now start the live display
|
@@ -250,10 +298,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
250
298
|
|
251
299
|
# Get AI response with conversation history
|
252
300
|
response = client.chat(
|
253
|
-
prompt=
|
301
|
+
prompt=enhanced_prompt,
|
254
302
|
messages=conversation,
|
255
303
|
stream=should_stream,
|
256
|
-
web_search=web_search,
|
257
304
|
temperature=temperature,
|
258
305
|
top_p=top_p,
|
259
306
|
max_tokens=max_tokens,
|
@@ -264,7 +311,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
264
311
|
# Ensure spinner is stopped if no content was received
|
265
312
|
if stop_spinner_event and not first_content_received:
|
266
313
|
stop_spinner_event.set()
|
267
|
-
|
314
|
+
# Clear the spinner line completely without leaving extra newlines
|
315
|
+
# Use direct terminal control to ensure consistency
|
316
|
+
sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
|
268
317
|
sys.stdout.flush()
|
269
318
|
|
270
319
|
# Stop live display if using stream-prettify
|
@@ -281,6 +330,7 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
281
330
|
# Print response if not streamed (either due to no_stream or prettify)
|
282
331
|
if no_stream or prettify:
|
283
332
|
if prettify:
|
333
|
+
# For pretty formatting with rich, don't print any header text as the rich renderer already includes it
|
284
334
|
prettify_markdown(response, renderer)
|
285
335
|
else:
|
286
336
|
print(response)
|
ngpt/cli/modes/chat.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
3
|
from ..ui import spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import sys
|
5
6
|
import threading
|
6
7
|
|
@@ -53,6 +54,21 @@ def chat_mode(client, args, logger=None):
|
|
53
54
|
# Log the user message if logging is enabled
|
54
55
|
if logger:
|
55
56
|
logger.log("user", prompt)
|
57
|
+
|
58
|
+
# Enhance prompt with web search if enabled
|
59
|
+
if args.web_search:
|
60
|
+
try:
|
61
|
+
original_prompt = prompt
|
62
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
63
|
+
print("Enhanced input with web search results.")
|
64
|
+
|
65
|
+
# Log the enhanced prompt if logging is enabled
|
66
|
+
if logger:
|
67
|
+
# Use "web_search" role instead of "system" for clearer logs
|
68
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
69
|
+
except Exception as e:
|
70
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
71
|
+
# Continue with the original prompt if web search fails
|
56
72
|
|
57
73
|
# Create messages array with preprompt if available
|
58
74
|
messages = None
|
@@ -87,7 +103,7 @@ def chat_mode(client, args, logger=None):
|
|
87
103
|
|
88
104
|
# If regular prettify is enabled with streaming, inform the user
|
89
105
|
if args.prettify and not args.no_stream:
|
90
|
-
print(f"{COLORS['yellow']}Note:
|
106
|
+
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
91
107
|
|
92
108
|
# Show a static message if live_display is not available
|
93
109
|
if args.stream_prettify and not live_display:
|
@@ -123,7 +139,7 @@ def chat_mode(client, args, logger=None):
|
|
123
139
|
if args.stream_prettify and live_display:
|
124
140
|
stream_callback = spinner_handling_callback
|
125
141
|
|
126
|
-
response = client.chat(prompt, stream=should_stream,
|
142
|
+
response = client.chat(prompt, stream=should_stream,
|
127
143
|
temperature=args.temperature, top_p=args.top_p,
|
128
144
|
max_tokens=args.max_tokens, messages=messages,
|
129
145
|
markdown_format=args.prettify or args.stream_prettify,
|
ngpt/cli/modes/code.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
|
3
3
|
from ..ui import spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import sys
|
5
6
|
import threading
|
6
7
|
|
@@ -25,6 +26,21 @@ def code_mode(client, args, logger=None):
|
|
25
26
|
# Log the user prompt if logging is enabled
|
26
27
|
if logger:
|
27
28
|
logger.log("user", prompt)
|
29
|
+
|
30
|
+
# Enhance prompt with web search if enabled
|
31
|
+
if args.web_search:
|
32
|
+
try:
|
33
|
+
original_prompt = prompt
|
34
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
35
|
+
print("Enhanced input with web search results.")
|
36
|
+
|
37
|
+
# Log the enhanced prompt if logging is enabled
|
38
|
+
if logger:
|
39
|
+
# Use "web_search" role instead of "system" for clearer logs
|
40
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
41
|
+
except Exception as e:
|
42
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
43
|
+
# Continue with the original prompt if web search fails
|
28
44
|
|
29
45
|
# Setup for streaming and prettify logic
|
30
46
|
stream_callback = None
|
@@ -62,7 +78,7 @@ def code_mode(client, args, logger=None):
|
|
62
78
|
if has_markdown_renderer(args.renderer):
|
63
79
|
should_stream = False
|
64
80
|
use_regular_prettify = True
|
65
|
-
print(f"{COLORS['yellow']}Note:
|
81
|
+
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
66
82
|
else:
|
67
83
|
# Renderer not available for prettify
|
68
84
|
print(f"{COLORS['yellow']}Warning: Renderer '{args.renderer}' not available for --prettify.{COLORS['reset']}")
|
@@ -110,8 +126,7 @@ def code_mode(client, args, logger=None):
|
|
110
126
|
|
111
127
|
generated_code = client.generate_code(
|
112
128
|
prompt=prompt,
|
113
|
-
language=args.language,
|
114
|
-
web_search=args.web_search,
|
129
|
+
language=args.language,
|
115
130
|
temperature=args.temperature,
|
116
131
|
top_p=args.top_p,
|
117
132
|
max_tokens=args.max_tokens,
|
ngpt/cli/modes/rewrite.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
from ..formatters import COLORS
|
6
6
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
7
7
|
from ..ui import get_multiline_input, spinner
|
8
|
+
from ...utils import enhance_prompt_with_web_search
|
8
9
|
|
9
10
|
# System prompt for rewriting text
|
10
11
|
REWRITE_SYSTEM_PROMPT = """You are an expert text editor and rewriter. Your task is to rewrite the user's text to improve readability and flow while carefully preserving the original meaning, tone, and style.
|
@@ -125,6 +126,21 @@ def rewrite_mode(client, args, logger=None):
|
|
125
126
|
print(f"{COLORS['yellow']}Error: Empty input. Please provide text to rewrite.{COLORS['reset']}")
|
126
127
|
return
|
127
128
|
|
129
|
+
# Enhance input with web search if enabled
|
130
|
+
if args.web_search:
|
131
|
+
try:
|
132
|
+
original_text = input_text
|
133
|
+
input_text = enhance_prompt_with_web_search(input_text, logger=logger)
|
134
|
+
print("Enhanced input with web search results.")
|
135
|
+
|
136
|
+
# Log the enhanced input if logging is enabled
|
137
|
+
if logger:
|
138
|
+
# Use "web_search" role instead of "system" for clearer logs
|
139
|
+
logger.log("web_search", input_text.replace(original_text, "").strip())
|
140
|
+
except Exception as e:
|
141
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance input with web search: {str(e)}{COLORS['reset']}")
|
142
|
+
# Continue with the original input if web search fails
|
143
|
+
|
128
144
|
# Set up messages array with system prompt and user content
|
129
145
|
messages = [
|
130
146
|
{"role": "system", "content": REWRITE_SYSTEM_PROMPT},
|
@@ -156,7 +172,7 @@ def rewrite_mode(client, args, logger=None):
|
|
156
172
|
|
157
173
|
# If regular prettify is enabled with streaming, inform the user
|
158
174
|
if args.prettify and not args.no_stream:
|
159
|
-
print(f"{COLORS['yellow']}Note:
|
175
|
+
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
160
176
|
|
161
177
|
# Show a static message if live_display is not available
|
162
178
|
if args.stream_prettify and not live_display:
|
@@ -195,7 +211,6 @@ def rewrite_mode(client, args, logger=None):
|
|
195
211
|
response = client.chat(
|
196
212
|
prompt=None, # Not used when messages are provided
|
197
213
|
stream=should_stream,
|
198
|
-
web_search=args.web_search,
|
199
214
|
temperature=args.temperature,
|
200
215
|
top_p=args.top_p,
|
201
216
|
max_tokens=args.max_tokens,
|
ngpt/cli/modes/shell.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..ui import spinner
|
3
|
+
from ...utils import enhance_prompt_with_web_search
|
3
4
|
import subprocess
|
4
5
|
import sys
|
5
6
|
import threading
|
@@ -26,6 +27,21 @@ def shell_mode(client, args, logger=None):
|
|
26
27
|
if logger:
|
27
28
|
logger.log("user", prompt)
|
28
29
|
|
30
|
+
# Enhance prompt with web search if enabled
|
31
|
+
if args.web_search:
|
32
|
+
try:
|
33
|
+
original_prompt = prompt
|
34
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
35
|
+
print("Enhanced input with web search results.")
|
36
|
+
|
37
|
+
# Log the enhanced prompt if logging is enabled
|
38
|
+
if logger:
|
39
|
+
# Use "web_search" role instead of "system" for clearer logs
|
40
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
41
|
+
except Exception as e:
|
42
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
43
|
+
# Continue with the original prompt if web search fails
|
44
|
+
|
29
45
|
# Start spinner while waiting for command generation
|
30
46
|
stop_spinner = threading.Event()
|
31
47
|
spinner_thread = threading.Thread(
|
@@ -37,9 +53,9 @@ def shell_mode(client, args, logger=None):
|
|
37
53
|
spinner_thread.start()
|
38
54
|
|
39
55
|
try:
|
40
|
-
command = client.generate_shell_command(prompt,
|
41
|
-
|
42
|
-
|
56
|
+
command = client.generate_shell_command(prompt,
|
57
|
+
temperature=args.temperature, top_p=args.top_p,
|
58
|
+
max_tokens=args.max_tokens)
|
43
59
|
finally:
|
44
60
|
# Stop the spinner
|
45
61
|
stop_spinner.set()
|
ngpt/cli/modes/text.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
3
|
from ..ui import get_multiline_input, spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import threading
|
5
6
|
import sys
|
6
7
|
|
@@ -25,6 +26,21 @@ def text_mode(client, args, logger=None):
|
|
25
26
|
if logger:
|
26
27
|
logger.log("user", prompt)
|
27
28
|
|
29
|
+
# Enhance prompt with web search if enabled
|
30
|
+
if args.web_search:
|
31
|
+
try:
|
32
|
+
original_prompt = prompt
|
33
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
34
|
+
print("Enhanced input with web search results.")
|
35
|
+
|
36
|
+
# Log the enhanced prompt if logging is enabled
|
37
|
+
if logger:
|
38
|
+
# Use "web_search" role instead of "system" for clearer logs
|
39
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
40
|
+
except Exception as e:
|
41
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
42
|
+
# Continue with the original prompt if web search fails
|
43
|
+
|
28
44
|
# Create messages array with preprompt if available
|
29
45
|
messages = None
|
30
46
|
if args.preprompt:
|
@@ -58,7 +74,7 @@ def text_mode(client, args, logger=None):
|
|
58
74
|
|
59
75
|
# If regular prettify is enabled with streaming, inform the user
|
60
76
|
if args.prettify and not args.no_stream:
|
61
|
-
print(f"{COLORS['yellow']}Note:
|
77
|
+
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
62
78
|
|
63
79
|
# Show a static message if live_display is not available
|
64
80
|
if args.stream_prettify and not live_display:
|
@@ -94,7 +110,7 @@ def text_mode(client, args, logger=None):
|
|
94
110
|
if args.stream_prettify and live_display:
|
95
111
|
stream_callback = spinner_handling_callback
|
96
112
|
|
97
|
-
response = client.chat(prompt, stream=should_stream,
|
113
|
+
response = client.chat(prompt, stream=should_stream,
|
98
114
|
temperature=args.temperature, top_p=args.top_p,
|
99
115
|
max_tokens=args.max_tokens, messages=messages,
|
100
116
|
markdown_format=args.prettify or args.stream_prettify,
|
ngpt/cli/renderers.py
CHANGED
@@ -168,8 +168,31 @@ def prettify_markdown(text, renderer='auto'):
|
|
168
168
|
# Use rich
|
169
169
|
try:
|
170
170
|
console = Console()
|
171
|
+
|
172
|
+
# Create a panel around the markdown for consistency with stream_prettify
|
173
|
+
from rich.panel import Panel
|
174
|
+
import rich.box
|
175
|
+
from rich.text import Text
|
176
|
+
|
177
|
+
# Get terminal dimensions
|
178
|
+
term_width = shutil.get_terminal_size().columns
|
179
|
+
|
180
|
+
# Create panel with similar styling to stream_prettify
|
181
|
+
clean_header = "🤖 nGPT"
|
182
|
+
panel_title = Text(clean_header, style="cyan bold")
|
183
|
+
|
171
184
|
md = Markdown(text)
|
172
|
-
|
185
|
+
panel = Panel(
|
186
|
+
md,
|
187
|
+
title=panel_title,
|
188
|
+
title_align="left",
|
189
|
+
border_style="cyan",
|
190
|
+
padding=(1, 1),
|
191
|
+
width=console.width - 4, # Make panel slightly narrower than console
|
192
|
+
box=rich.box.ROUNDED
|
193
|
+
)
|
194
|
+
|
195
|
+
console.print(panel)
|
173
196
|
return True
|
174
197
|
except Exception as e:
|
175
198
|
print(f"{COLORS['yellow']}Error using rich for markdown: {str(e)}{COLORS['reset']}")
|
@@ -237,7 +260,20 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
|
|
237
260
|
box=rich.box.ROUNDED
|
238
261
|
)
|
239
262
|
else:
|
240
|
-
|
263
|
+
# Always use a panel - even in non-interactive mode
|
264
|
+
clean_header = "🤖 nGPT"
|
265
|
+
panel_title = Text(clean_header, style="cyan bold")
|
266
|
+
|
267
|
+
padding = (1, 1) # Less horizontal padding (left, right)
|
268
|
+
md_obj = Panel(
|
269
|
+
Markdown(""),
|
270
|
+
title=panel_title,
|
271
|
+
title_align="left",
|
272
|
+
border_style="cyan",
|
273
|
+
padding=padding,
|
274
|
+
width=console.width - 4, # Make panel slightly narrower than console
|
275
|
+
box=rich.box.ROUNDED
|
276
|
+
)
|
241
277
|
|
242
278
|
# Get terminal dimensions for better display
|
243
279
|
term_width = shutil.get_terminal_size().columns
|
@@ -275,9 +311,8 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
|
|
275
311
|
# Start live display on first content
|
276
312
|
if first_update:
|
277
313
|
first_update = False
|
278
|
-
#
|
279
|
-
|
280
|
-
sys.stdout.flush()
|
314
|
+
# Let the spinner's clean_exit handle the cleanup
|
315
|
+
# No additional cleanup needed here
|
281
316
|
live.start()
|
282
317
|
|
283
318
|
# Update content in live display
|
@@ -304,17 +339,17 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
|
|
304
339
|
if not is_complete:
|
305
340
|
# Calculate approximate lines needed
|
306
341
|
content_lines = content.count('\n') + 1
|
307
|
-
available_height = display_height -
|
342
|
+
available_height = display_height - 4 # Account for panel borders and padding
|
308
343
|
|
309
344
|
if content_lines > available_height:
|
310
345
|
# If content is too big, show only the last part that fits
|
311
346
|
lines = content.split('\n')
|
312
347
|
truncated_content = '\n'.join(lines[-available_height:])
|
313
|
-
md_obj = Markdown(truncated_content)
|
348
|
+
md_obj.renderable = Markdown(truncated_content)
|
314
349
|
else:
|
315
|
-
md_obj = Markdown(content)
|
350
|
+
md_obj.renderable = Markdown(content)
|
316
351
|
else:
|
317
|
-
md_obj = Markdown(content)
|
352
|
+
md_obj.renderable = Markdown(content)
|
318
353
|
|
319
354
|
live.update(md_obj)
|
320
355
|
|
@@ -344,7 +379,7 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
|
|
344
379
|
spinner_thread = threading.Thread(
|
345
380
|
target=spinner,
|
346
381
|
args=(message,),
|
347
|
-
kwargs={"stop_event": stop_event, "color": color}
|
382
|
+
kwargs={"stop_event": stop_event, "color": color, "clean_exit": True}
|
348
383
|
)
|
349
384
|
spinner_thread.daemon = True
|
350
385
|
spinner_thread.start()
|
ngpt/cli/ui.py
CHANGED
@@ -155,7 +155,7 @@ def get_multiline_input():
|
|
155
155
|
print("\nInput cancelled by user. Exiting gracefully.")
|
156
156
|
return None
|
157
157
|
|
158
|
-
def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color=None, stop_event=None):
|
158
|
+
def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color=None, stop_event=None, clean_exit=False):
|
159
159
|
"""Display a spinner animation with a message.
|
160
160
|
|
161
161
|
Args:
|
@@ -165,6 +165,7 @@ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color
|
|
165
165
|
color: Optional color from COLORS dict to use for the message
|
166
166
|
stop_event: Optional threading.Event to signal when to stop the spinner
|
167
167
|
If provided, duration is ignored and spinner runs until event is set
|
168
|
+
clean_exit: When True, cleans up more aggressively to prevent blank lines
|
168
169
|
"""
|
169
170
|
char_duration = 0.2
|
170
171
|
|
@@ -173,19 +174,30 @@ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color
|
|
173
174
|
if color:
|
174
175
|
colored_message = f"{color}{message}{COLORS['reset']}"
|
175
176
|
|
177
|
+
# Save cursor position - will be needed for clean exit
|
178
|
+
if clean_exit:
|
179
|
+
# Start by printing a \r to ensure we begin at the start of the line
|
180
|
+
sys.stdout.write("\r")
|
181
|
+
sys.stdout.flush()
|
182
|
+
|
176
183
|
if stop_event:
|
177
184
|
i = 0
|
178
185
|
while not stop_event.is_set():
|
179
186
|
char = spinner_chars[i % len(spinner_chars)]
|
180
|
-
|
187
|
+
# Always use sys.stdout.write for consistent behavior
|
188
|
+
sys.stdout.write(f"\r{colored_message} {char}")
|
189
|
+
sys.stdout.flush()
|
181
190
|
i += 1
|
182
191
|
time.sleep(char_duration)
|
183
192
|
else:
|
184
193
|
total_chars = int(duration / char_duration)
|
185
194
|
for i in range(total_chars):
|
186
195
|
char = spinner_chars[i % len(spinner_chars)]
|
187
|
-
|
196
|
+
sys.stdout.write(f"\r{colored_message} {char}")
|
197
|
+
sys.stdout.flush()
|
188
198
|
time.sleep(char_duration)
|
189
199
|
|
190
|
-
# Clear the line when done
|
191
|
-
|
200
|
+
# Clear the line when done - use terminal width to clear the entire line
|
201
|
+
terminal_width = shutil.get_terminal_size().columns
|
202
|
+
sys.stdout.write("\r" + " " * terminal_width + "\r")
|
203
|
+
sys.stdout.flush()
|
ngpt/client.py
CHANGED
@@ -32,7 +32,6 @@ class NGPTClient:
|
|
32
32
|
max_tokens: Optional[int] = None,
|
33
33
|
top_p: float = 1.0,
|
34
34
|
messages: Optional[List[Dict[str, str]]] = None,
|
35
|
-
web_search: bool = False,
|
36
35
|
markdown_format: bool = False,
|
37
36
|
stream_callback: Optional[callable] = None,
|
38
37
|
**kwargs
|
@@ -47,7 +46,6 @@ class NGPTClient:
|
|
47
46
|
max_tokens: Maximum number of tokens to generate
|
48
47
|
top_p: Controls diversity via nucleus sampling
|
49
48
|
messages: Optional list of message objects to override default behavior
|
50
|
-
web_search: Whether to enable web search capability
|
51
49
|
markdown_format: If True, allow markdown-formatted responses, otherwise plain text
|
52
50
|
stream_callback: Optional callback function for streaming mode updates
|
53
51
|
**kwargs: Additional arguments to pass to the API
|
@@ -75,10 +73,6 @@ class NGPTClient:
|
|
75
73
|
"top_p": top_p,
|
76
74
|
}
|
77
75
|
|
78
|
-
# Conditionally add web_search
|
79
|
-
if web_search:
|
80
|
-
payload["web_search"] = True
|
81
|
-
|
82
76
|
# Add max_tokens if provided
|
83
77
|
if max_tokens is not None:
|
84
78
|
payload["max_tokens"] = max_tokens
|
@@ -180,7 +174,6 @@ class NGPTClient:
|
|
180
174
|
def generate_shell_command(
|
181
175
|
self,
|
182
176
|
prompt: str,
|
183
|
-
web_search: bool = False,
|
184
177
|
temperature: float = 0.4,
|
185
178
|
top_p: float = 0.95,
|
186
179
|
max_tokens: Optional[int] = None
|
@@ -190,7 +183,6 @@ class NGPTClient:
|
|
190
183
|
|
191
184
|
Args:
|
192
185
|
prompt: Description of the command to generate
|
193
|
-
web_search: Whether to enable web search capability
|
194
186
|
temperature: Controls randomness in the response
|
195
187
|
top_p: Controls diversity via nucleus sampling
|
196
188
|
max_tokens: Maximum number of tokens to generate
|
@@ -241,7 +233,6 @@ Command:"""
|
|
241
233
|
prompt=prompt,
|
242
234
|
stream=False,
|
243
235
|
messages=messages,
|
244
|
-
web_search=web_search,
|
245
236
|
temperature=temperature,
|
246
237
|
top_p=top_p,
|
247
238
|
max_tokens=max_tokens
|
@@ -254,7 +245,6 @@ Command:"""
|
|
254
245
|
self,
|
255
246
|
prompt: str,
|
256
247
|
language: str = "python",
|
257
|
-
web_search: bool = False,
|
258
248
|
temperature: float = 0.4,
|
259
249
|
top_p: float = 0.95,
|
260
250
|
max_tokens: Optional[int] = None,
|
@@ -268,7 +258,6 @@ Command:"""
|
|
268
258
|
Args:
|
269
259
|
prompt: Description of the code to generate
|
270
260
|
language: Programming language to generate code in
|
271
|
-
web_search: Whether to enable web search capability
|
272
261
|
temperature: Controls randomness in the response
|
273
262
|
top_p: Controls diversity via nucleus sampling
|
274
263
|
max_tokens: Maximum number of tokens to generate
|
@@ -315,7 +304,6 @@ Code:"""
|
|
315
304
|
prompt=prompt,
|
316
305
|
stream=stream,
|
317
306
|
messages=messages,
|
318
|
-
web_search=web_search,
|
319
307
|
temperature=temperature,
|
320
308
|
top_p=top_p,
|
321
309
|
max_tokens=max_tokens,
|
ngpt/utils/__init__.py
CHANGED
@@ -22,6 +22,11 @@ from .cli_config import (
|
|
22
22
|
get_cli_config_dir,
|
23
23
|
get_cli_config_path
|
24
24
|
)
|
25
|
+
from .web_search import (
|
26
|
+
enhance_prompt_with_web_search,
|
27
|
+
get_web_search_results,
|
28
|
+
format_web_search_results_for_prompt
|
29
|
+
)
|
25
30
|
|
26
31
|
__all__ = [
|
27
32
|
"create_logger", "Logger",
|
@@ -29,5 +34,6 @@ __all__ = [
|
|
29
34
|
"add_config_entry", "remove_config_entry", "DEFAULT_CONFIG", "DEFAULT_CONFIG_ENTRY",
|
30
35
|
"load_cli_config", "set_cli_config_option", "get_cli_config_option",
|
31
36
|
"unset_cli_config_option", "apply_cli_config", "list_cli_config_options",
|
32
|
-
"CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path"
|
37
|
+
"CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path",
|
38
|
+
"enhance_prompt_with_web_search", "get_web_search_results", "format_web_search_results_for_prompt"
|
33
39
|
]
|
ngpt/utils/web_search.py
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
"""
|
2
|
+
Web search utilities for nGPT using duckduckgo-search and trafilatura.
|
3
|
+
|
4
|
+
This module provides functionality to search the web and extract
|
5
|
+
information from search results to enhance AI prompts.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import re
|
9
|
+
from typing import List, Dict, Any, Optional
|
10
|
+
from duckduckgo_search import DDGS
|
11
|
+
from urllib.parse import urlparse
|
12
|
+
import requests
|
13
|
+
import sys
|
14
|
+
|
15
|
+
# Get actual logger from global context instead of using standard logging
|
16
|
+
from . import log
|
17
|
+
|
18
|
+
# Use a global variable to store the logger provided during runtime
|
19
|
+
_logger = None
|
20
|
+
|
21
|
+
def set_logger(logger):
|
22
|
+
"""Set the logger to use for this module."""
|
23
|
+
global _logger
|
24
|
+
_logger = logger
|
25
|
+
|
26
|
+
def get_logger():
|
27
|
+
"""Get the current logger or use a default."""
|
28
|
+
if _logger is not None:
|
29
|
+
return _logger
|
30
|
+
else:
|
31
|
+
# Default logging to stderr if no logger provided
|
32
|
+
class DefaultLogger:
|
33
|
+
def info(self, msg): print(f"INFO: {msg}", file=sys.stderr)
|
34
|
+
def error(self, msg): print(f"ERROR: {msg}", file=sys.stderr)
|
35
|
+
def warning(self, msg): print(f"WARNING: {msg}", file=sys.stderr)
|
36
|
+
def debug(self, msg): pass
|
37
|
+
return DefaultLogger()
|
38
|
+
|
39
|
+
def perform_web_search(query: str, max_results: int = 3) -> List[Dict[str, Any]]:
|
40
|
+
"""
|
41
|
+
Search the web using DuckDuckGo and return relevant results.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
query: The search query
|
45
|
+
max_results: Maximum number of results to return
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
List of dictionaries containing search results (title, url, snippet)
|
49
|
+
"""
|
50
|
+
logger = get_logger()
|
51
|
+
try:
|
52
|
+
ddgs = DDGS()
|
53
|
+
results = list(ddgs.text(query, max_results=max_results))
|
54
|
+
return results
|
55
|
+
except Exception as e:
|
56
|
+
logger.error(f"Error performing web search: {str(e)}")
|
57
|
+
logger.info("Web search encountered an issue, but will continue with available results")
|
58
|
+
return []
|
59
|
+
|
60
|
+
def extract_article_content(url: str, max_chars: int = 2000) -> Optional[str]:
|
61
|
+
"""
|
62
|
+
Extract and clean content from a webpage URL.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
url: The URL to extract content from
|
66
|
+
max_chars: Maximum number of characters to extract
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
Cleaned article text or None if extraction failed
|
70
|
+
"""
|
71
|
+
logger = get_logger()
|
72
|
+
try:
|
73
|
+
# Skip non-http URLs or suspicious domains
|
74
|
+
parsed_url = urlparse(url)
|
75
|
+
if not parsed_url.scheme.startswith('http'):
|
76
|
+
return None
|
77
|
+
|
78
|
+
# Browser-like user agent
|
79
|
+
headers = {
|
80
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
81
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
82
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
83
|
+
'Connection': 'keep-alive',
|
84
|
+
'Upgrade-Insecure-Requests': '1',
|
85
|
+
}
|
86
|
+
|
87
|
+
logger.info(f"Fetching content from {url}")
|
88
|
+
|
89
|
+
try:
|
90
|
+
# Try using trafilatura
|
91
|
+
import trafilatura
|
92
|
+
|
93
|
+
# Download with correct parameters
|
94
|
+
# trafilatura handles user-agent internally
|
95
|
+
downloaded = trafilatura.fetch_url(url)
|
96
|
+
|
97
|
+
if downloaded:
|
98
|
+
# Extract main content
|
99
|
+
content = trafilatura.extract(downloaded, include_comments=False,
|
100
|
+
include_tables=False,
|
101
|
+
no_fallback=False)
|
102
|
+
|
103
|
+
if content:
|
104
|
+
# Clean up content if needed
|
105
|
+
content = content.strip()
|
106
|
+
|
107
|
+
# Truncate if needed
|
108
|
+
if len(content) > max_chars:
|
109
|
+
content = content[:max_chars] + "..."
|
110
|
+
|
111
|
+
return content
|
112
|
+
|
113
|
+
# If trafilatura failed, try direct requests
|
114
|
+
logger.info(f"Trafilatura extraction failed for {url}, trying fallback method")
|
115
|
+
response = requests.get(url, headers=headers, timeout=10)
|
116
|
+
|
117
|
+
if response.status_code == 200:
|
118
|
+
# Very basic HTML cleaning
|
119
|
+
html_content = response.text
|
120
|
+
# Remove HTML tags
|
121
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
122
|
+
# Remove excess whitespace
|
123
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
124
|
+
|
125
|
+
if text:
|
126
|
+
if len(text) > max_chars:
|
127
|
+
text = text[:max_chars] + "..."
|
128
|
+
return text
|
129
|
+
|
130
|
+
else:
|
131
|
+
logger.error(f"Request to {url} returned status code {response.status_code}")
|
132
|
+
|
133
|
+
except ImportError:
|
134
|
+
logger.error("Trafilatura not installed. Install with 'pip install trafilatura'")
|
135
|
+
# Try direct requests only
|
136
|
+
try:
|
137
|
+
response = requests.get(url, headers=headers, timeout=10)
|
138
|
+
if response.status_code == 200:
|
139
|
+
# Very basic HTML cleaning
|
140
|
+
html_content = response.text
|
141
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
142
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
143
|
+
|
144
|
+
if text:
|
145
|
+
if len(text) > max_chars:
|
146
|
+
text = text[:max_chars] + "..."
|
147
|
+
return text
|
148
|
+
except Exception as req_error:
|
149
|
+
logger.error(f"Direct request fallback failed: {str(req_error)}")
|
150
|
+
|
151
|
+
except Exception as e:
|
152
|
+
logger.error(f"Error extracting content with trafilatura: {str(e)}")
|
153
|
+
# Try the requests fallback
|
154
|
+
try:
|
155
|
+
response = requests.get(url, headers=headers, timeout=10)
|
156
|
+
if response.status_code == 200:
|
157
|
+
html_content = response.text
|
158
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
159
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
160
|
+
|
161
|
+
if text:
|
162
|
+
if len(text) > max_chars:
|
163
|
+
text = text[:max_chars] + "..."
|
164
|
+
return text
|
165
|
+
except Exception as req_error:
|
166
|
+
logger.error(f"Direct request fallback failed: {str(req_error)}")
|
167
|
+
|
168
|
+
return None
|
169
|
+
except Exception as e:
|
170
|
+
logger.error(f"Error extracting content from {url}: {str(e)}")
|
171
|
+
return None
|
172
|
+
|
173
|
+
def get_web_search_results(query: str, max_results: int = 3, max_chars_per_result: int = 2000) -> Dict[str, Any]:
|
174
|
+
"""
|
175
|
+
Get formatted web search results ready to be included in AI prompts.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
query: The search query
|
179
|
+
max_results: Maximum number of results to include
|
180
|
+
max_chars_per_result: Maximum characters to include per result
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
Dictionary containing search results and metadata
|
184
|
+
"""
|
185
|
+
logger = get_logger()
|
186
|
+
search_results = perform_web_search(query, max_results)
|
187
|
+
enhanced_results = []
|
188
|
+
success_count = 0
|
189
|
+
failure_count = 0
|
190
|
+
|
191
|
+
for result in search_results:
|
192
|
+
content = extract_article_content(result['href'], max_chars_per_result)
|
193
|
+
|
194
|
+
enhanced_results.append({
|
195
|
+
'title': result.get('title', ''),
|
196
|
+
'url': result.get('href', ''),
|
197
|
+
'snippet': result.get('body', ''),
|
198
|
+
'content': content if content else result.get('body', '')
|
199
|
+
})
|
200
|
+
|
201
|
+
if content:
|
202
|
+
success_count += 1
|
203
|
+
else:
|
204
|
+
failure_count += 1
|
205
|
+
|
206
|
+
# Log a user-friendly summary
|
207
|
+
if search_results:
|
208
|
+
if failure_count > 0:
|
209
|
+
logger.info(f"Retrieved content from {success_count} out of {len(search_results)} sources")
|
210
|
+
else:
|
211
|
+
logger.info(f"Successfully retrieved content from all {success_count} sources")
|
212
|
+
else:
|
213
|
+
logger.error("No search results were found")
|
214
|
+
|
215
|
+
return {
|
216
|
+
'query': query,
|
217
|
+
'timestamp': 'current_time', # Could replace with actual timestamp
|
218
|
+
'results': enhanced_results
|
219
|
+
}
|
220
|
+
|
221
|
+
def format_web_search_results_for_prompt(search_results: Dict[str, Any]) -> str:
|
222
|
+
"""
|
223
|
+
Format web search results into a string to include in AI prompts.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
search_results: Dictionary of search results from get_web_search_results()
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
Formatted string to include in prompts
|
230
|
+
"""
|
231
|
+
query = search_results['query']
|
232
|
+
results = search_results['results']
|
233
|
+
|
234
|
+
formatted_text = f"[Web Search Results for: {query}]\n\n"
|
235
|
+
|
236
|
+
for i, result in enumerate(results, 1):
|
237
|
+
formatted_text += f"RESULT {i}: {result['title']}\n"
|
238
|
+
formatted_text += f"URL: {result['url']}\n"
|
239
|
+
formatted_text += f"CONTENT:\n{result['content']}\n\n"
|
240
|
+
|
241
|
+
formatted_text += f"[End of Web Search Results]\n\n"
|
242
|
+
formatted_text += "Consider the above information when answering the following question:\n\n"
|
243
|
+
|
244
|
+
return formatted_text
|
245
|
+
|
246
|
+
def enhance_prompt_with_web_search(prompt: str, max_results: int = 3, logger=None) -> str:
|
247
|
+
"""
|
248
|
+
Enhance a prompt with web search results.
|
249
|
+
|
250
|
+
Args:
|
251
|
+
prompt: The original user prompt
|
252
|
+
max_results: Maximum number of search results to include
|
253
|
+
logger: Optional logger to use
|
254
|
+
|
255
|
+
Returns:
|
256
|
+
Enhanced prompt with web search results prepended
|
257
|
+
"""
|
258
|
+
# Set the logger for this module
|
259
|
+
if logger is not None:
|
260
|
+
set_logger(logger)
|
261
|
+
|
262
|
+
logger = get_logger()
|
263
|
+
search_results = get_web_search_results(prompt, max_results)
|
264
|
+
formatted_results = format_web_search_results_for_prompt(search_results)
|
265
|
+
|
266
|
+
# Combine results with original prompt
|
267
|
+
enhanced_prompt = formatted_results + prompt
|
268
|
+
|
269
|
+
logger.info("Enhanced input with web search results")
|
270
|
+
return enhanced_prompt
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ngpt
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.3.0
|
4
4
|
Summary: Swiss army knife for LLMs: powerful CLI, interactive chatbot, and flexible Python library. Works with OpenAI, Ollama, Groq, Claude, Gemini, 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
|
@@ -28,10 +28,12 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
28
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
29
29
|
Classifier: Topic :: Utilities
|
30
30
|
Requires-Python: >=3.8
|
31
|
+
Requires-Dist: duckduckgo-search>=3.0.0
|
31
32
|
Requires-Dist: prompt-toolkit>=3.0.0
|
32
33
|
Requires-Dist: pyperclip>=1.8.0
|
33
34
|
Requires-Dist: requests>=2.31.0
|
34
35
|
Requires-Dist: rich>=10.0.0
|
36
|
+
Requires-Dist: trafilatura>=1.6.0
|
35
37
|
Description-Content-Type: text/markdown
|
36
38
|
|
37
39
|
# nGPT
|
@@ -63,7 +65,7 @@ Description-Content-Type: text/markdown
|
|
63
65
|
- 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, Claude, Gemini, and any compatible endpoint
|
64
66
|
- 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
|
65
67
|
- 📊 **Streaming Responses**: Real-time output for better user experience
|
66
|
-
- 🔍 **Web Search**:
|
68
|
+
- 🔍 **Web Search**: Enhance any model with contextual information from the web
|
67
69
|
- 📥 **Stdin Processing**: Process piped content by using `{}` placeholder in prompts
|
68
70
|
- 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
|
69
71
|
- ⚡ **Real-time Markdown**: Stream responses with live updating syntax highlighting and formatting
|
@@ -271,7 +273,7 @@ ngpt --list-models --provider Gemini
|
|
271
273
|
# With custom options
|
272
274
|
ngpt --api-key your-key --base-url http://your-endpoint --model your-model "Hello"
|
273
275
|
|
274
|
-
# Enable web search
|
276
|
+
# Enable web search capability to enhance prompts with web information
|
275
277
|
ngpt --web-search "What's the latest news about AI?"
|
276
278
|
|
277
279
|
# Generate and execute shell commands (using -s or --shell flag)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
|
2
|
+
ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
+
ngpt/client.py,sha256=1kn-kVQ2ZYhOlQ5OPM9c_btVKajfk1qb52QbMyGdYtU,14645
|
4
|
+
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
5
|
+
ngpt/cli/args.py,sha256=7ixh5anahTeHWsrEKwhaLA_dR3sJ-ZzO8NyGOeDmd3Q,11392
|
6
|
+
ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
|
7
|
+
ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
|
8
|
+
ngpt/cli/interactive.py,sha256=6SrnANvE5T9Luu-CSGPfW7lub4-yog9aqruAfs1XEIg,16392
|
9
|
+
ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
|
10
|
+
ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
|
11
|
+
ngpt/cli/ui.py,sha256=HoHDFpLiwMBP5wtMb8YYo244FMiqiPFRoBNcNGp6N0A,7310
|
12
|
+
ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
|
13
|
+
ngpt/cli/modes/chat.py,sha256=UlnZWqvxL_CSyVIDZKGVXNlG-KufDfo05E8-8xTcM_Y,6713
|
14
|
+
ngpt/cli/modes/code.py,sha256=4TpIV-EkyXb8d3XJKrsLs9Wxq5P9jC82z94PI2Zck_g,6730
|
15
|
+
ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
|
16
|
+
ngpt/cli/modes/rewrite.py,sha256=ftD-6M9iQ7g4rLdlKyyLTRiJWYtbz64LIG4PIByxmOk,11472
|
17
|
+
ngpt/cli/modes/shell.py,sha256=fxE9LEEo4arSn5-q_6zxdnUH7RlqifWmk-_kcA76OhM,4070
|
18
|
+
ngpt/cli/modes/text.py,sha256=gdn4opioZ6G3nvfrTkp-dpoD-Of_ZvjVVRggVd6edkg,5528
|
19
|
+
ngpt/utils/__init__.py,sha256=qu_66I1Vtav2f1LDiPn5J3DUsbK7o1CSScMcTkYqxoM,1179
|
20
|
+
ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
|
21
|
+
ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
|
22
|
+
ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
|
23
|
+
ngpt/utils/web_search.py,sha256=DXjf5zJW4jXbmpir6Oqv7abfrFxjqJqvnuAA9gJIFJk,9919
|
24
|
+
ngpt-3.3.0.dist-info/METADATA,sha256=1gGlQ6gzQwnnB8wfdmzcwlf3DRV9JNup7HgfXsSRo2Y,29100
|
25
|
+
ngpt-3.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
26
|
+
ngpt-3.3.0.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
|
27
|
+
ngpt-3.3.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
28
|
+
ngpt-3.3.0.dist-info/RECORD,,
|
ngpt-3.1.1.dist-info/RECORD
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
|
2
|
-
ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
-
ngpt/client.py,sha256=rLgDPmJe8_yi13-XUiHJ45z54rJVrupxWmeb-fQZGF4,15129
|
4
|
-
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
5
|
-
ngpt/cli/args.py,sha256=XQvofZs_WkbgUto3Dbx7Yw-AmPAQHh8kdHUe3uWy8w4,11382
|
6
|
-
ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
|
7
|
-
ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
|
8
|
-
ngpt/cli/interactive.py,sha256=Zep4yiGRFPVrJV1zp7xB5lXg7hVhXhbRlaVA4DgRBbQ,13241
|
9
|
-
ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
|
10
|
-
ngpt/cli/renderers.py,sha256=ovV0IexZyrjCk8LK5ZGVvf5K7wCLco4jRhDv_tp6Kvw,15184
|
11
|
-
ngpt/cli/ui.py,sha256=m8qtd4cCSHBGHPUlHVdBEfun1G1Se4vLKTSgnS7QOKE,6775
|
12
|
-
ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
|
13
|
-
ngpt/cli/modes/chat.py,sha256=oibPaN4LAqrzIbWpw--ql0TwU0u_jlLCeNuxxyO7wPg,5887
|
14
|
-
ngpt/cli/modes/code.py,sha256=AEsQKdL8mh4ImcI5R7TXzwEq_JBTkmS-L0AA_-dpHm0,5934
|
15
|
-
ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
|
16
|
-
ngpt/cli/modes/rewrite.py,sha256=CrzUHxUoZc48D3ooVIvDbCjIIuERO1v-ddG3ajhETQs,10646
|
17
|
-
ngpt/cli/modes/shell.py,sha256=QkprnOxMMTg2v5DIwcofDnnr3JPNfuk-YgSQaae5Xps,3311
|
18
|
-
ngpt/cli/modes/text.py,sha256=vCwZnVxIbsTCLE8YVJIPwZsGgk7VRs8v-eeaVwCOZqI,4702
|
19
|
-
ngpt/utils/__init__.py,sha256=E46suk2-QgYBI0Qrs6WXOajOUOebF3ETAFY7ah8DTWs,942
|
20
|
-
ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
|
21
|
-
ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
|
22
|
-
ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
|
23
|
-
ngpt-3.1.1.dist-info/METADATA,sha256=YPavkwb6Jg4oblYwYtBX3XrDmvcQWVhQZO2d9r5yByI,28992
|
24
|
-
ngpt-3.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
-
ngpt-3.1.1.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
|
26
|
-
ngpt-3.1.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
27
|
-
ngpt-3.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|