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/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}")
@@ -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