ngpt 1.5.1__py3-none-any.whl → 1.6.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.
ngpt/cli.py CHANGED
@@ -18,6 +18,7 @@ try:
18
18
  from prompt_toolkit.widgets import TextArea
19
19
  from prompt_toolkit.layout.margins import ScrollbarMargin
20
20
  from prompt_toolkit.filters import to_filter
21
+ from prompt_toolkit.history import InMemoryHistory
21
22
  import shutil
22
23
  HAS_PROMPT_TOOLKIT = True
23
24
  except ImportError:
@@ -82,6 +83,183 @@ def check_config(config):
82
83
 
83
84
  return True
84
85
 
86
+ def interactive_chat_session(client, web_search=False, no_stream=False):
87
+ """Run an interactive chat session with conversation history."""
88
+ # Define ANSI color codes for terminal output
89
+ COLORS = {
90
+ "reset": "\033[0m",
91
+ "bold": "\033[1m",
92
+ "cyan": "\033[36m",
93
+ "green": "\033[32m",
94
+ "yellow": "\033[33m",
95
+ "blue": "\033[34m",
96
+ "magenta": "\033[35m",
97
+ "gray": "\033[90m",
98
+ "bg_blue": "\033[44m",
99
+ "bg_cyan": "\033[46m"
100
+ }
101
+
102
+ # Get terminal width for better formatting
103
+ try:
104
+ term_width = shutil.get_terminal_size().columns
105
+ except:
106
+ term_width = 80 # Default fallback
107
+
108
+ # Improved visual header with better layout
109
+ header = f"{COLORS['cyan']}{COLORS['bold']}🤖 nGPT Interactive Chat Session 🤖{COLORS['reset']}"
110
+ print(f"\n{header}")
111
+
112
+ # Create a separator line - use a consistent separator length for all lines
113
+ separator_length = min(40, term_width - 10)
114
+ separator = f"{COLORS['gray']}{'─' * separator_length}{COLORS['reset']}"
115
+ print(separator)
116
+
117
+ # Group commands into categories with better formatting
118
+ print(f"\n{COLORS['cyan']}Navigation:{COLORS['reset']}")
119
+ print(f" {COLORS['yellow']}↑/↓{COLORS['reset']} : Browse input history")
120
+
121
+ print(f"\n{COLORS['cyan']}Session Commands:{COLORS['reset']}")
122
+ print(f" {COLORS['yellow']}history{COLORS['reset']} : Show conversation history")
123
+ print(f" {COLORS['yellow']}clear{COLORS['reset']} : Reset conversation")
124
+ print(f" {COLORS['yellow']}exit{COLORS['reset']} : End session")
125
+
126
+ print(f"\n{separator}\n")
127
+
128
+ # Custom separator - use the same length for consistency
129
+ def print_separator():
130
+ print(f"\n{separator}\n")
131
+
132
+ # Initialize conversation history
133
+ system_prompt = "You are a helpful assistant."
134
+ conversation = []
135
+ system_message = {"role": "system", "content": system_prompt}
136
+ conversation.append(system_message)
137
+
138
+ # Initialize prompt_toolkit history
139
+ prompt_history = InMemoryHistory() if HAS_PROMPT_TOOLKIT else None
140
+
141
+ # Decorative chat headers with rounded corners
142
+ def user_header():
143
+ return f"{COLORS['cyan']}{COLORS['bold']}╭─ 👤 You {COLORS['reset']}"
144
+
145
+ def ngpt_header():
146
+ return f"{COLORS['green']}{COLORS['bold']}╭─ 🤖 nGPT {COLORS['reset']}"
147
+
148
+ # Function to display conversation history
149
+ def display_history():
150
+ if len(conversation) <= 1: # Only system message
151
+ print(f"\n{COLORS['yellow']}No conversation history yet.{COLORS['reset']}")
152
+ return
153
+
154
+ print(f"\n{COLORS['cyan']}{COLORS['bold']}Conversation History:{COLORS['reset']}")
155
+ print(separator)
156
+
157
+ # Skip system message
158
+ message_count = 0
159
+ for i, msg in enumerate(conversation):
160
+ if msg["role"] == "system":
161
+ continue
162
+
163
+ if msg["role"] == "user":
164
+ message_count += 1
165
+ print(f"\n{user_header()}")
166
+ print(f"{COLORS['cyan']}│ [{message_count}] {COLORS['reset']}{msg['content']}")
167
+ elif msg["role"] == "assistant":
168
+ print(f"\n{ngpt_header()}")
169
+ print(f"{COLORS['green']}│ {COLORS['reset']}{msg['content']}")
170
+
171
+ print(f"\n{separator}") # Consistent separator at the end
172
+
173
+ # Function to clear conversation history
174
+ def clear_history():
175
+ nonlocal conversation
176
+ conversation = [{"role": "system", "content": system_prompt}]
177
+ print(f"\n{COLORS['yellow']}Conversation history cleared.{COLORS['reset']}")
178
+ print(separator) # Add separator for consistency
179
+
180
+ try:
181
+ while True:
182
+ # Get user input
183
+ if HAS_PROMPT_TOOLKIT:
184
+ # Custom styling for prompt_toolkit
185
+ style = Style.from_dict({
186
+ 'prompt': 'ansicyan bold',
187
+ 'input': 'ansiwhite',
188
+ })
189
+
190
+ # Create key bindings for Ctrl+C handling
191
+ kb = KeyBindings()
192
+ @kb.add('c-c')
193
+ def _(event):
194
+ event.app.exit(result=None)
195
+ raise KeyboardInterrupt()
196
+
197
+ # Get user input with styled prompt - using proper HTML formatting
198
+ user_input = pt_prompt(
199
+ HTML("<ansicyan><b>╭─ 👤 You:</b></ansicyan> "),
200
+ style=style,
201
+ key_bindings=kb,
202
+ history=prompt_history
203
+ )
204
+ else:
205
+ user_input = input(f"{user_header()}: {COLORS['reset']}")
206
+
207
+ # Check for exit commands
208
+ if user_input.lower() in ('exit', 'quit', 'bye'):
209
+ print(f"\n{COLORS['green']}Ending chat session. Goodbye!{COLORS['reset']}")
210
+ break
211
+
212
+ # Check for special commands
213
+ if user_input.lower() == 'history':
214
+ display_history()
215
+ continue
216
+
217
+ if user_input.lower() == 'clear':
218
+ clear_history()
219
+ continue
220
+
221
+ # Skip empty messages but don't raise an error
222
+ if not user_input.strip():
223
+ continue
224
+
225
+ # Add user message to conversation
226
+ user_message = {"role": "user", "content": user_input}
227
+ conversation.append(user_message)
228
+
229
+ # Print assistant indicator with formatting
230
+ if not no_stream:
231
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
232
+ else:
233
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
234
+
235
+ # Get AI response with conversation history
236
+ response = client.chat(
237
+ prompt=user_input,
238
+ messages=conversation,
239
+ stream=not no_stream,
240
+ web_search=web_search
241
+ )
242
+
243
+ # Add AI response to conversation history
244
+ if response:
245
+ assistant_message = {"role": "assistant", "content": response}
246
+ conversation.append(assistant_message)
247
+
248
+ # Print response if not streamed
249
+ if no_stream:
250
+ print(response)
251
+
252
+ # Print separator between exchanges
253
+ print_separator()
254
+
255
+ except KeyboardInterrupt:
256
+ print(f"\n\n{COLORS['green']}Chat session ended by user. Goodbye!{COLORS['reset']}")
257
+ except Exception as e:
258
+ print(f"\n{COLORS['yellow']}Error during chat session: {str(e)}{COLORS['reset']}")
259
+ # Print traceback for debugging if it's a serious error
260
+ import traceback
261
+ traceback.print_exc()
262
+
85
263
  def main():
86
264
  parser = argparse.ArgumentParser(description="nGPT - A CLI tool for interacting with custom OpenAI API endpoints")
87
265
 
@@ -109,10 +287,11 @@ def main():
109
287
  # Mode flags (mutually exclusive)
110
288
  mode_group = parser.add_argument_group('Modes (mutually exclusive)')
111
289
  mode_exclusive_group = mode_group.add_mutually_exclusive_group()
290
+ mode_exclusive_group.add_argument('-i', '--interactive', action='store_true', help='Start an interactive chat session')
112
291
  mode_exclusive_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
113
292
  mode_exclusive_group.add_argument('-c', '--code', action='store_true', help='Generate code')
114
293
  mode_exclusive_group.add_argument('-t', '--text', action='store_true', help='Enter multi-line text input (submit with Ctrl+D)')
115
- mode_exclusive_group.add_argument('-i', '--interactive', action='store_true', help='Start an interactive chat session')
294
+ # Note: --show-config is handled separately and implicitly acts as a mode
116
295
 
117
296
  # Language option for code mode
118
297
  parser.add_argument('--language', default="python", help='Programming language to generate code in (for code mode)')
@@ -229,7 +408,7 @@ def main():
229
408
 
230
409
  return
231
410
 
232
- # Check if prompt is required but not provided
411
+ # For interactive mode, we'll allow continuing without a specific prompt
233
412
  if not args.prompt and not (args.shell or args.code or args.text or args.interactive):
234
413
  parser.print_help()
235
414
  return
@@ -243,7 +422,10 @@ def main():
243
422
 
244
423
  try:
245
424
  # Handle modes
246
- if args.shell:
425
+ if args.interactive:
426
+ # Interactive chat mode
427
+ interactive_chat_session(client, web_search=args.web_search, no_stream=args.no_stream)
428
+ elif args.shell:
247
429
  if args.prompt is None:
248
430
  try:
249
431
  print("Enter shell command description: ", end='')
@@ -279,139 +461,6 @@ def main():
279
461
  except subprocess.CalledProcessError as e:
280
462
  print(f"\nError:\n{e.stderr}")
281
463
 
282
- elif args.interactive:
283
- # Interactive chat mode
284
- conversation_history = []
285
- print("\033[94m\033[1m" + "Interactive Chat Mode" + "\033[0m")
286
- print("Type your messages and press Ctrl+D to send. Type 'exit', 'quit', or use Ctrl+C to exit.")
287
- print("Type 'clear' to start a new conversation.")
288
- if HAS_PROMPT_TOOLKIT:
289
- print("Use arrow keys to navigate, Enter for new line in multi-line mode.")
290
- print()
291
-
292
- try:
293
- while True:
294
- try:
295
- # Get user input with prompt_toolkit if available
296
- if HAS_PROMPT_TOOLKIT:
297
- # Create key bindings
298
- kb = KeyBindings()
299
-
300
- # Explicitly bind Ctrl+C to exit
301
- @kb.add('c-c')
302
- def _(event):
303
- event.app.exit(result=None)
304
- print("\nExiting interactive chat mode.")
305
- sys.exit(130)
306
-
307
- # Explicitly bind Ctrl+D to submit
308
- @kb.add('c-d')
309
- def _(event):
310
- event.app.exit(result=event.app.current_buffer.text)
311
-
312
- # Get terminal dimensions
313
- term_width, term_height = shutil.get_terminal_size()
314
-
315
- # Create a styled TextArea
316
- text_area = TextArea(
317
- style="class:input-area",
318
- multiline=True,
319
- wrap_lines=True,
320
- width=term_width - 4,
321
- height=min(10, term_height - 8),
322
- prompt=HTML("<ansiblue>>>> </ansiblue>"),
323
- scrollbar=True,
324
- focus_on_click=True,
325
- lexer=None,
326
- )
327
- text_area.window.right_margins = [ScrollbarMargin(display_arrows=True)]
328
-
329
- # Create a title bar
330
- title_bar = FormattedTextControl(
331
- HTML("<style bg='ansiblue' fg='ansiwhite'><b> NGPT Interactive Chat </b></style>")
332
- )
333
-
334
- # Create a status bar with key bindings and commands info
335
- status_bar = FormattedTextControl(
336
- HTML("<ansiblue><b>Ctrl+D</b></ansiblue>: Submit | <ansiblue><b>Ctrl+C</b></ansiblue>: Exit | Type <ansiblue><b>clear</b></ansiblue> to start new conversation")
337
- )
338
-
339
- # Create the layout
340
- layout = Layout(
341
- HSplit([
342
- Window(title_bar, height=1),
343
- Window(height=1, char="-", style="class:separator"),
344
- text_area,
345
- Window(height=1, char="-", style="class:separator"),
346
- Window(status_bar, height=1),
347
- ])
348
- )
349
-
350
- # Create a style
351
- style = Style.from_dict({
352
- "separator": "ansigray",
353
- "input-area": "bg:ansiblack fg:ansiwhite",
354
- "cursor": "bg:ansiwhite fg:ansiblack",
355
- })
356
-
357
- # Create and run the application
358
- app = Application(
359
- layout=layout,
360
- full_screen=False,
361
- key_bindings=kb,
362
- style=style,
363
- mouse_support=True,
364
- )
365
-
366
- user_input = app.run()
367
- if user_input is None:
368
- break
369
- else:
370
- # Fallback to standard input
371
- user_input = input("\033[1m\033[94m>>> \033[0m")
372
-
373
- # Handle special commands
374
- if user_input is None:
375
- break
376
- elif user_input.lower() in ['exit', 'quit', 'q']:
377
- print("Exiting interactive chat mode.")
378
- break
379
- elif user_input.lower() == 'clear':
380
- print("Starting a new conversation.")
381
- conversation_history = []
382
- continue
383
- elif not user_input.strip():
384
- continue
385
-
386
- # Add user message to conversation history
387
- conversation_history.append({"role": "user", "content": user_input})
388
-
389
- # Get response from the model
390
- print("\033[90m" + "AI is thinking..." + "\033[0m")
391
- response = client.chat(
392
- prompt=user_input,
393
- stream=not args.no_stream,
394
- messages=conversation_history,
395
- web_search=args.web_search
396
- )
397
-
398
- # Add assistant message to conversation history
399
- conversation_history.append({"role": "assistant", "content": response})
400
-
401
- # If no streaming, print the response
402
- if args.no_stream and response:
403
- print("\033[92m" + response + "\033[0m")
404
-
405
- print() # Add spacing between exchanges
406
-
407
- except KeyboardInterrupt:
408
- print("\nExiting interactive chat mode.")
409
- break
410
-
411
- except Exception as e:
412
- print(f"\nError in interactive mode: {e}")
413
- return
414
-
415
464
  elif args.code:
416
465
  if args.prompt is None:
417
466
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 1.5.1
3
+ Version: 1.6.0
4
4
  Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -64,6 +64,9 @@ pip install ngpt
64
64
  # Chat with default settings
65
65
  ngpt "Tell me about quantum computing"
66
66
 
67
+ # Start an interactive chat session with conversation memory
68
+ ngpt -i
69
+
67
70
  # Return response without streaming
68
71
  ngpt -n "Tell me about quantum computing"
69
72
 
@@ -82,6 +85,7 @@ ngpt --text
82
85
  - ✅ **Dual Mode**: Use as a CLI tool or import as a Python library
83
86
  - 🪶 **Lightweight**: Minimal dependencies (just `requests`)
84
87
  - 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, and any compatible endpoint
88
+ - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
85
89
  - 📊 **Streaming Responses**: Real-time output for better user experience
86
90
  - 🔍 **Web Search**: Integrated with compatible API endpoints
87
91
  - ⚙️ **Multiple Configurations**: Cross-platform config system supporting different profiles
@@ -105,6 +109,9 @@ Requires Python 3.8 or newer.
105
109
  # Basic chat (default mode)
106
110
  ngpt "Hello, how are you?"
107
111
 
112
+ # Interactive chat session with conversation history
113
+ ngpt -i
114
+
108
115
  # Show version information
109
116
  ngpt -v
110
117
 
@@ -211,6 +218,7 @@ You can configure the client using the following options:
211
218
  | `--remove` | Remove the configuration at the specified index (requires --config and --config-index) |
212
219
  | `--show-config` | Show configuration details and exit |
213
220
  | `--all` | Used with `--show-config` to display all configurations |
221
+ | `-i, --interactive` | Start an interactive chat session with stylish UI, conversation history, and special commands |
214
222
  | `-s, --shell` | Generate and execute shell commands |
215
223
  | `-c, --code` | Generate clean code output |
216
224
  | `-t, --text` | Open interactive multiline editor for complex prompts |
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=brSdM8uFBbVktbpK6CDAFEzmKzBKb47OSK1G9ludDYg,27564
3
+ ngpt/client.py,sha256=O0dPYeQCJlpWZWBBsroo-5UxeyBVwqC6o3Pm8lRnDiY,10329
4
+ ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
+ ngpt-1.6.0.dist-info/METADATA,sha256=hqt6-L2cmyQ_GgoYEOf-_MVUlrm6n1JoxzgAqmVgFkc,10416
6
+ ngpt-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-1.6.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-1.6.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-1.6.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
- ngpt/cli.py,sha256=ZMBmNJfKiKAO88MecT7CDHH1uyqpupeCqGSX4-5LC_g,27117
3
- ngpt/client.py,sha256=O0dPYeQCJlpWZWBBsroo-5UxeyBVwqC6o3Pm8lRnDiY,10329
4
- ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
- ngpt-1.5.1.dist-info/METADATA,sha256=eHORtsw7QD5HwfIhqRzCojOTNtfBqZ6kXCFT_8VBRq8,10086
6
- ngpt-1.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-1.5.1.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-1.5.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-1.5.1.dist-info/RECORD,,
File without changes