h2ogpte 1.6.42__py3-none-any.whl → 1.6.43rc1__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.
- h2ogpte/__init__.py +1 -1
- h2ogpte/cli/__init__.py +0 -0
- h2ogpte/cli/commands/__init__.py +0 -0
- h2ogpte/cli/commands/command_handlers/__init__.py +0 -0
- h2ogpte/cli/commands/command_handlers/agent.py +41 -0
- h2ogpte/cli/commands/command_handlers/chat.py +37 -0
- h2ogpte/cli/commands/command_handlers/clear.py +8 -0
- h2ogpte/cli/commands/command_handlers/collection.py +67 -0
- h2ogpte/cli/commands/command_handlers/config.py +113 -0
- h2ogpte/cli/commands/command_handlers/disconnect.py +36 -0
- h2ogpte/cli/commands/command_handlers/exit.py +37 -0
- h2ogpte/cli/commands/command_handlers/help.py +8 -0
- h2ogpte/cli/commands/command_handlers/history.py +29 -0
- h2ogpte/cli/commands/command_handlers/rag.py +146 -0
- h2ogpte/cli/commands/command_handlers/research_agent.py +45 -0
- h2ogpte/cli/commands/command_handlers/session.py +77 -0
- h2ogpte/cli/commands/command_handlers/status.py +33 -0
- h2ogpte/cli/commands/dispatcher.py +79 -0
- h2ogpte/cli/core/__init__.py +0 -0
- h2ogpte/cli/core/app.py +105 -0
- h2ogpte/cli/core/config.py +199 -0
- h2ogpte/cli/core/encryption.py +104 -0
- h2ogpte/cli/core/session.py +171 -0
- h2ogpte/cli/integrations/__init__.py +0 -0
- h2ogpte/cli/integrations/agent.py +338 -0
- h2ogpte/cli/integrations/rag.py +442 -0
- h2ogpte/cli/main.py +90 -0
- h2ogpte/cli/ui/__init__.py +0 -0
- h2ogpte/cli/ui/hbot_prompt.py +435 -0
- h2ogpte/cli/ui/prompts.py +129 -0
- h2ogpte/cli/ui/status_bar.py +133 -0
- h2ogpte/cli/utils/__init__.py +0 -0
- h2ogpte/cli/utils/file_manager.py +411 -0
- h2ogpte/h2ogpte.py +471 -67
- h2ogpte/h2ogpte_async.py +482 -68
- h2ogpte/h2ogpte_sync_base.py +8 -1
- h2ogpte/rest_async/__init__.py +6 -3
- h2ogpte/rest_async/api/chat_api.py +29 -0
- h2ogpte/rest_async/api/collections_api.py +293 -0
- h2ogpte/rest_async/api/extractors_api.py +2874 -70
- h2ogpte/rest_async/api/prompt_templates_api.py +32 -32
- h2ogpte/rest_async/api_client.py +1 -1
- h2ogpte/rest_async/configuration.py +1 -1
- h2ogpte/rest_async/models/__init__.py +5 -2
- h2ogpte/rest_async/models/chat_completion.py +4 -2
- h2ogpte/rest_async/models/chat_completion_delta.py +5 -3
- h2ogpte/rest_async/models/chat_completion_request.py +1 -1
- h2ogpte/rest_async/models/chat_session.py +4 -2
- h2ogpte/rest_async/models/chat_settings.py +1 -1
- h2ogpte/rest_async/models/collection.py +4 -2
- h2ogpte/rest_async/models/collection_create_request.py +4 -2
- h2ogpte/rest_async/models/create_chat_session_request.py +87 -0
- h2ogpte/rest_async/models/extraction_request.py +1 -1
- h2ogpte/rest_async/models/extractor.py +4 -2
- h2ogpte/rest_async/models/guardrails_settings.py +8 -4
- h2ogpte/rest_async/models/guardrails_settings_create_request.py +1 -1
- h2ogpte/rest_async/models/process_document_job_request.py +1 -1
- h2ogpte/rest_async/models/question_request.py +1 -1
- h2ogpte/rest_async/models/{reset_and_share_prompt_template_request.py → reset_and_share_request.py} +6 -6
- h2ogpte/{rest_sync/models/reset_and_share_prompt_template_with_groups_request.py → rest_async/models/reset_and_share_with_groups_request.py} +6 -6
- h2ogpte/rest_async/models/summarize_request.py +1 -1
- h2ogpte/rest_async/models/update_collection_workspace_request.py +87 -0
- h2ogpte/rest_async/models/update_extractor_privacy_request.py +87 -0
- h2ogpte/rest_sync/__init__.py +6 -3
- h2ogpte/rest_sync/api/chat_api.py +29 -0
- h2ogpte/rest_sync/api/collections_api.py +293 -0
- h2ogpte/rest_sync/api/extractors_api.py +2874 -70
- h2ogpte/rest_sync/api/prompt_templates_api.py +32 -32
- h2ogpte/rest_sync/api_client.py +1 -1
- h2ogpte/rest_sync/configuration.py +1 -1
- h2ogpte/rest_sync/models/__init__.py +5 -2
- h2ogpte/rest_sync/models/chat_completion.py +4 -2
- h2ogpte/rest_sync/models/chat_completion_delta.py +5 -3
- h2ogpte/rest_sync/models/chat_completion_request.py +1 -1
- h2ogpte/rest_sync/models/chat_session.py +4 -2
- h2ogpte/rest_sync/models/chat_settings.py +1 -1
- h2ogpte/rest_sync/models/collection.py +4 -2
- h2ogpte/rest_sync/models/collection_create_request.py +4 -2
- h2ogpte/rest_sync/models/create_chat_session_request.py +87 -0
- h2ogpte/rest_sync/models/extraction_request.py +1 -1
- h2ogpte/rest_sync/models/extractor.py +4 -2
- h2ogpte/rest_sync/models/guardrails_settings.py +8 -4
- h2ogpte/rest_sync/models/guardrails_settings_create_request.py +1 -1
- h2ogpte/rest_sync/models/process_document_job_request.py +1 -1
- h2ogpte/rest_sync/models/question_request.py +1 -1
- h2ogpte/rest_sync/models/{reset_and_share_prompt_template_request.py → reset_and_share_request.py} +6 -6
- h2ogpte/{rest_async/models/reset_and_share_prompt_template_with_groups_request.py → rest_sync/models/reset_and_share_with_groups_request.py} +6 -6
- h2ogpte/rest_sync/models/summarize_request.py +1 -1
- h2ogpte/rest_sync/models/update_collection_workspace_request.py +87 -0
- h2ogpte/rest_sync/models/update_extractor_privacy_request.py +87 -0
- h2ogpte/session.py +3 -2
- h2ogpte/session_async.py +22 -6
- h2ogpte/types.py +6 -0
- {h2ogpte-1.6.42.dist-info → h2ogpte-1.6.43rc1.dist-info}/METADATA +5 -1
- {h2ogpte-1.6.42.dist-info → h2ogpte-1.6.43rc1.dist-info}/RECORD +98 -59
- h2ogpte-1.6.43rc1.dist-info/entry_points.txt +2 -0
- {h2ogpte-1.6.42.dist-info → h2ogpte-1.6.43rc1.dist-info}/WHEEL +0 -0
- {h2ogpte-1.6.42.dist-info → h2ogpte-1.6.43rc1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import termios
|
|
4
|
+
import tty
|
|
5
|
+
from typing import List, Optional, Dict
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HBOTPrompt:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.commands = {
|
|
14
|
+
"/help": "Show help information",
|
|
15
|
+
"/register": "Connect/disconnect H2OGPTE",
|
|
16
|
+
"/disconnect": "Disconnect and clear credentials",
|
|
17
|
+
"/upload": "Upload files to collection",
|
|
18
|
+
"/analyze": "Analyze directory",
|
|
19
|
+
"/agent": "Use AI agent mode",
|
|
20
|
+
"/research": "Deep research with AI agent",
|
|
21
|
+
"/config": "Configure settings",
|
|
22
|
+
"/status": "Show session status",
|
|
23
|
+
"/clear": "Clear screen",
|
|
24
|
+
"/exit": "Exit H2OGPTE CLI",
|
|
25
|
+
"/quit": "Exit H2OGPTE CLI",
|
|
26
|
+
"/history": "Show command history",
|
|
27
|
+
"/save": "Save session",
|
|
28
|
+
"/load": "Load session",
|
|
29
|
+
"/session": "Create chat session",
|
|
30
|
+
"/collection": "Create new collection",
|
|
31
|
+
}
|
|
32
|
+
self.max_suggestions_shown = 5
|
|
33
|
+
self.command_history = []
|
|
34
|
+
self.history_position = -1
|
|
35
|
+
self._status_bar = None
|
|
36
|
+
|
|
37
|
+
def getch(self):
|
|
38
|
+
fd = sys.stdin.fileno()
|
|
39
|
+
old_settings = termios.tcgetattr(fd)
|
|
40
|
+
try:
|
|
41
|
+
tty.setraw(sys.stdin.fileno())
|
|
42
|
+
ch = sys.stdin.read(1)
|
|
43
|
+
finally:
|
|
44
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
45
|
+
return ch
|
|
46
|
+
|
|
47
|
+
def get_matching_commands(self, text: str) -> List[str]:
|
|
48
|
+
if not text.startswith("/"):
|
|
49
|
+
return []
|
|
50
|
+
return [cmd for cmd in self.commands.keys() if cmd.startswith(text)]
|
|
51
|
+
|
|
52
|
+
def add_to_history(self, command: str):
|
|
53
|
+
if command and command.strip():
|
|
54
|
+
# Remove if it's already the last command to avoid duplicates
|
|
55
|
+
if self.command_history and self.command_history[-1] == command:
|
|
56
|
+
return
|
|
57
|
+
# Remove from middle if it exists
|
|
58
|
+
if command in self.command_history:
|
|
59
|
+
self.command_history.remove(command)
|
|
60
|
+
# Add to end
|
|
61
|
+
self.command_history.append(command)
|
|
62
|
+
# Keep only last 100 commands
|
|
63
|
+
if len(self.command_history) > 100:
|
|
64
|
+
self.command_history = self.command_history[-100:]
|
|
65
|
+
|
|
66
|
+
def update_suggestions(self, current_text: str, selected: int = 0):
|
|
67
|
+
matches = self.get_matching_commands(current_text)
|
|
68
|
+
|
|
69
|
+
# Clear lines below
|
|
70
|
+
print("\033[J", end="")
|
|
71
|
+
|
|
72
|
+
if matches and current_text.startswith("/"):
|
|
73
|
+
# Show suggestions without separator - cleaner look
|
|
74
|
+
print() # One newline to separate from input
|
|
75
|
+
|
|
76
|
+
# Calculate scrolling window
|
|
77
|
+
total_matches = len(matches)
|
|
78
|
+
|
|
79
|
+
if total_matches <= self.max_suggestions_shown:
|
|
80
|
+
# Show all matches if they fit
|
|
81
|
+
start_idx = 0
|
|
82
|
+
end_idx = total_matches
|
|
83
|
+
scroll_indicator = ""
|
|
84
|
+
else:
|
|
85
|
+
# Calculate scrolling window around selected item
|
|
86
|
+
if selected < self.max_suggestions_shown // 2:
|
|
87
|
+
# Near the beginning
|
|
88
|
+
start_idx = 0
|
|
89
|
+
end_idx = self.max_suggestions_shown
|
|
90
|
+
elif selected >= total_matches - (self.max_suggestions_shown // 2):
|
|
91
|
+
# Near the end
|
|
92
|
+
start_idx = total_matches - self.max_suggestions_shown
|
|
93
|
+
end_idx = total_matches
|
|
94
|
+
else:
|
|
95
|
+
# In the middle
|
|
96
|
+
start_idx = selected - (self.max_suggestions_shown // 2)
|
|
97
|
+
end_idx = start_idx + self.max_suggestions_shown
|
|
98
|
+
|
|
99
|
+
# Create scroll indicator
|
|
100
|
+
scroll_indicator = (
|
|
101
|
+
f" \033[90m[{selected + 1}/{total_matches}] ↕ scroll\033[0m"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Show the visible window of suggestions
|
|
105
|
+
for i in range(start_idx, end_idx):
|
|
106
|
+
cmd = matches[i]
|
|
107
|
+
desc = self.commands.get(cmd, "")
|
|
108
|
+
if i == selected:
|
|
109
|
+
# Highlighted selection
|
|
110
|
+
print(f"\033[K → \033[92m{cmd}\033[0m {desc}")
|
|
111
|
+
else:
|
|
112
|
+
# Normal display
|
|
113
|
+
print(f"\033[K \033[36m{cmd}\033[0m \033[90m{desc}\033[0m")
|
|
114
|
+
|
|
115
|
+
# Show scroll indicator if there are more items
|
|
116
|
+
if scroll_indicator:
|
|
117
|
+
print(f"\033[K{scroll_indicator}")
|
|
118
|
+
|
|
119
|
+
# Move cursor back to input line
|
|
120
|
+
lines_up = min(total_matches, self.max_suggestions_shown) + 1
|
|
121
|
+
if scroll_indicator:
|
|
122
|
+
lines_up += 1
|
|
123
|
+
print(f"\033[{lines_up}A", end="")
|
|
124
|
+
|
|
125
|
+
sys.stdout.flush()
|
|
126
|
+
|
|
127
|
+
def _show_status_bar(self):
|
|
128
|
+
if self._status_bar and sys.stdin.isatty():
|
|
129
|
+
self._status_bar.print_status_line()
|
|
130
|
+
|
|
131
|
+
def get_input(self, prompt_text: str = "❯ ") -> str:
|
|
132
|
+
# Non-interactive mode fallback
|
|
133
|
+
if not sys.stdin.isatty():
|
|
134
|
+
try:
|
|
135
|
+
user_input = input(prompt_text).strip()
|
|
136
|
+
return user_input if user_input else ""
|
|
137
|
+
except EOFError:
|
|
138
|
+
return "/exit"
|
|
139
|
+
|
|
140
|
+
# Show status bar right before input prompt
|
|
141
|
+
self._show_status_bar()
|
|
142
|
+
|
|
143
|
+
# Show prompt (no extra newline)
|
|
144
|
+
print(f"{prompt_text}", end="", flush=True)
|
|
145
|
+
|
|
146
|
+
current_text = ""
|
|
147
|
+
cursor_pos = 0
|
|
148
|
+
selected_suggestion = 0
|
|
149
|
+
|
|
150
|
+
while True:
|
|
151
|
+
char = self.getch()
|
|
152
|
+
|
|
153
|
+
# Handle special characters
|
|
154
|
+
if ord(char) == 3: # Ctrl+C
|
|
155
|
+
print("\033[J", end="") # Clear below
|
|
156
|
+
print() # Single newline
|
|
157
|
+
raise KeyboardInterrupt()
|
|
158
|
+
|
|
159
|
+
elif ord(char) == 4: # Ctrl+D
|
|
160
|
+
print("\033[J", end="") # Clear below
|
|
161
|
+
print() # Single newline
|
|
162
|
+
return "/exit"
|
|
163
|
+
|
|
164
|
+
elif char == "\r" or char == "\n": # Enter
|
|
165
|
+
matches = self.get_matching_commands(current_text)
|
|
166
|
+
|
|
167
|
+
# If there are suggestions and current text is incomplete, auto-complete first
|
|
168
|
+
if (
|
|
169
|
+
matches
|
|
170
|
+
and selected_suggestion < len(matches)
|
|
171
|
+
and current_text != matches[selected_suggestion]
|
|
172
|
+
):
|
|
173
|
+
# Auto-complete the selected suggestion
|
|
174
|
+
current_text = matches[selected_suggestion]
|
|
175
|
+
cursor_pos = len(current_text)
|
|
176
|
+
|
|
177
|
+
# Add a space for easy argument typing
|
|
178
|
+
current_text += " "
|
|
179
|
+
cursor_pos += 1
|
|
180
|
+
|
|
181
|
+
# Redraw with the completed command
|
|
182
|
+
print(f"\r{prompt_text}{current_text}", end="", flush=True)
|
|
183
|
+
|
|
184
|
+
# Clear suggestions and reset selection
|
|
185
|
+
print("\033[J", end="")
|
|
186
|
+
selected_suggestion = 0
|
|
187
|
+
continue # Don't return, let user continue typing or press Enter again
|
|
188
|
+
|
|
189
|
+
else:
|
|
190
|
+
# No suggestions or already completed - execute the command
|
|
191
|
+
print("\033[J", end="") # Clear suggestions
|
|
192
|
+
print() # Single newline only
|
|
193
|
+
command = current_text.strip()
|
|
194
|
+
if command:
|
|
195
|
+
self.add_to_history(command)
|
|
196
|
+
# Reset history position for next command
|
|
197
|
+
self.history_position = -1
|
|
198
|
+
return command
|
|
199
|
+
|
|
200
|
+
elif ord(char) == 127 or ord(char) == 8: # Backspace/Delete
|
|
201
|
+
if cursor_pos > 0:
|
|
202
|
+
# Reset history position when user modifies text
|
|
203
|
+
self.history_position = -1
|
|
204
|
+
|
|
205
|
+
# Delete character
|
|
206
|
+
current_text = (
|
|
207
|
+
current_text[: cursor_pos - 1] + current_text[cursor_pos:]
|
|
208
|
+
)
|
|
209
|
+
cursor_pos -= 1
|
|
210
|
+
|
|
211
|
+
# Clear and redraw the line properly
|
|
212
|
+
print(f"\r\033[K{prompt_text}{current_text}", end="", flush=True)
|
|
213
|
+
|
|
214
|
+
# Reset selection and update suggestions
|
|
215
|
+
selected_suggestion = 0
|
|
216
|
+
self.update_suggestions(current_text, selected_suggestion)
|
|
217
|
+
|
|
218
|
+
elif char == "\t": # Tab - auto-complete
|
|
219
|
+
matches = self.get_matching_commands(current_text)
|
|
220
|
+
if matches:
|
|
221
|
+
current_text = (
|
|
222
|
+
matches[selected_suggestion]
|
|
223
|
+
if selected_suggestion < len(matches)
|
|
224
|
+
else matches[0]
|
|
225
|
+
)
|
|
226
|
+
cursor_pos = len(current_text)
|
|
227
|
+
print(f"\r{prompt_text}{current_text}", end="", flush=True)
|
|
228
|
+
selected_suggestion = 0
|
|
229
|
+
self.update_suggestions(current_text, selected_suggestion)
|
|
230
|
+
|
|
231
|
+
elif char == "\033": # Escape sequence (arrow keys)
|
|
232
|
+
next1 = self.getch()
|
|
233
|
+
next2 = self.getch()
|
|
234
|
+
|
|
235
|
+
if next1 == "[":
|
|
236
|
+
if next2 == "A": # Up arrow
|
|
237
|
+
if current_text == "":
|
|
238
|
+
# Navigate command history ONLY when text is empty
|
|
239
|
+
if self.command_history:
|
|
240
|
+
if self.history_position == -1:
|
|
241
|
+
# From empty, go to most recent command
|
|
242
|
+
self.history_position = (
|
|
243
|
+
len(self.command_history) - 1
|
|
244
|
+
)
|
|
245
|
+
elif self.history_position > 0:
|
|
246
|
+
# Go to previous command in history
|
|
247
|
+
self.history_position -= 1
|
|
248
|
+
# else: already at oldest command, stay there
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
0
|
|
252
|
+
<= self.history_position
|
|
253
|
+
< len(self.command_history)
|
|
254
|
+
):
|
|
255
|
+
current_text = self.command_history[
|
|
256
|
+
self.history_position
|
|
257
|
+
]
|
|
258
|
+
cursor_pos = len(current_text)
|
|
259
|
+
print(
|
|
260
|
+
f"\r{prompt_text}{current_text}",
|
|
261
|
+
end="",
|
|
262
|
+
flush=True,
|
|
263
|
+
)
|
|
264
|
+
print("\033[J", end="") # Clear suggestions
|
|
265
|
+
else:
|
|
266
|
+
# Navigate command suggestions when user has typed something
|
|
267
|
+
matches = self.get_matching_commands(current_text)
|
|
268
|
+
if matches and selected_suggestion > 0:
|
|
269
|
+
selected_suggestion -= 1
|
|
270
|
+
self.update_suggestions(
|
|
271
|
+
current_text, selected_suggestion
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
elif next2 == "B": # Down arrow
|
|
275
|
+
if current_text == "":
|
|
276
|
+
# Navigate command history ONLY when text is empty
|
|
277
|
+
if self.command_history:
|
|
278
|
+
if self.history_position == -1:
|
|
279
|
+
# From empty, can't go down further, stay empty
|
|
280
|
+
pass
|
|
281
|
+
elif (
|
|
282
|
+
self.history_position
|
|
283
|
+
< len(self.command_history) - 1
|
|
284
|
+
):
|
|
285
|
+
# Go to next command in history
|
|
286
|
+
self.history_position += 1
|
|
287
|
+
current_text = self.command_history[
|
|
288
|
+
self.history_position
|
|
289
|
+
]
|
|
290
|
+
cursor_pos = len(current_text)
|
|
291
|
+
print(
|
|
292
|
+
f"\r{prompt_text}{current_text}",
|
|
293
|
+
end="",
|
|
294
|
+
flush=True,
|
|
295
|
+
)
|
|
296
|
+
print("\033[J", end="") # Clear suggestions
|
|
297
|
+
else:
|
|
298
|
+
# At newest command, go back to empty
|
|
299
|
+
self.history_position = -1
|
|
300
|
+
current_text = ""
|
|
301
|
+
cursor_pos = 0
|
|
302
|
+
print(f"\r{prompt_text}", end="", flush=True)
|
|
303
|
+
print("\033[J", end="") # Clear suggestions
|
|
304
|
+
else:
|
|
305
|
+
# Navigate command suggestions when user has typed something
|
|
306
|
+
matches = self.get_matching_commands(current_text)
|
|
307
|
+
if matches and selected_suggestion < len(matches) - 1:
|
|
308
|
+
selected_suggestion += 1
|
|
309
|
+
self.update_suggestions(
|
|
310
|
+
current_text, selected_suggestion
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
elif next2 == "C": # Right arrow
|
|
314
|
+
if cursor_pos < len(current_text):
|
|
315
|
+
cursor_pos += 1
|
|
316
|
+
print("\033[C", end="", flush=True)
|
|
317
|
+
|
|
318
|
+
elif next2 == "D": # Left arrow
|
|
319
|
+
if cursor_pos > 0:
|
|
320
|
+
cursor_pos -= 1
|
|
321
|
+
print("\033[D", end="", flush=True)
|
|
322
|
+
|
|
323
|
+
elif char.isprintable(): # Regular character
|
|
324
|
+
# Reset history position when user starts typing
|
|
325
|
+
self.history_position = -1
|
|
326
|
+
|
|
327
|
+
# Add character
|
|
328
|
+
current_text = (
|
|
329
|
+
current_text[:cursor_pos] + char + current_text[cursor_pos:]
|
|
330
|
+
)
|
|
331
|
+
cursor_pos += 1
|
|
332
|
+
|
|
333
|
+
# Redraw line
|
|
334
|
+
print(f"\r{prompt_text}{current_text}", end="", flush=True)
|
|
335
|
+
|
|
336
|
+
# Reset selection and update suggestions
|
|
337
|
+
selected_suggestion = 0
|
|
338
|
+
self.update_suggestions(current_text, selected_suggestion)
|
|
339
|
+
|
|
340
|
+
def confirm(self, message: str, default: bool = False) -> bool:
|
|
341
|
+
"""Get confirmation from user."""
|
|
342
|
+
if not sys.stdin.isatty():
|
|
343
|
+
console.print(f"{message} (non-interactive mode, using default: {default})")
|
|
344
|
+
return default
|
|
345
|
+
|
|
346
|
+
default_str = "Y/n" if default else "y/N"
|
|
347
|
+
try:
|
|
348
|
+
response = input(f"{message} ({default_str}): ").strip().lower()
|
|
349
|
+
if not response:
|
|
350
|
+
return default
|
|
351
|
+
return response in ["y", "yes", "true", "1"]
|
|
352
|
+
except (EOFError, KeyboardInterrupt):
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
def select_from_list(
|
|
356
|
+
self, choices: List[str], message: str = "Select an option:"
|
|
357
|
+
) -> str:
|
|
358
|
+
"""Present a selection list."""
|
|
359
|
+
if not choices:
|
|
360
|
+
return ""
|
|
361
|
+
|
|
362
|
+
console.print(f"{message}")
|
|
363
|
+
for i, choice in enumerate(choices, 1):
|
|
364
|
+
console.print(f" {i}. {choice}")
|
|
365
|
+
|
|
366
|
+
while True:
|
|
367
|
+
try:
|
|
368
|
+
selection = input("Enter choice number: ").strip()
|
|
369
|
+
if selection.isdigit():
|
|
370
|
+
idx = int(selection) - 1
|
|
371
|
+
if 0 <= idx < len(choices):
|
|
372
|
+
return choices[idx]
|
|
373
|
+
console.print("[red]Please enter a valid number[/red]")
|
|
374
|
+
except (EOFError, KeyboardInterrupt):
|
|
375
|
+
return choices[0] if choices else ""
|
|
376
|
+
|
|
377
|
+
def get_multiselect(
|
|
378
|
+
self, choices: List[str], message: str = "Select options:"
|
|
379
|
+
) -> List[str]:
|
|
380
|
+
"""Present a multi-selection list."""
|
|
381
|
+
if not choices:
|
|
382
|
+
return []
|
|
383
|
+
|
|
384
|
+
console.print(f"{message}")
|
|
385
|
+
console.print("[dim]Enter numbers separated by commas[/dim]")
|
|
386
|
+
for i, choice in enumerate(choices, 1):
|
|
387
|
+
console.print(f" {i}. {choice}")
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
selection = input("Enter choices: ").strip()
|
|
391
|
+
selected = []
|
|
392
|
+
for num in selection.split(","):
|
|
393
|
+
try:
|
|
394
|
+
idx = int(num.strip()) - 1
|
|
395
|
+
if 0 <= idx < len(choices):
|
|
396
|
+
selected.append(choices[idx])
|
|
397
|
+
except ValueError:
|
|
398
|
+
continue
|
|
399
|
+
return selected
|
|
400
|
+
except (EOFError, KeyboardInterrupt):
|
|
401
|
+
return []
|
|
402
|
+
|
|
403
|
+
def get_secret(self, message: str, default: str = "") -> str:
|
|
404
|
+
"""Get secret input (like API keys) - doesn't go to history."""
|
|
405
|
+
try:
|
|
406
|
+
import getpass
|
|
407
|
+
|
|
408
|
+
# Use getpass to hide input
|
|
409
|
+
user_input = getpass.getpass(f"{message}: ").strip()
|
|
410
|
+
return user_input if user_input else default
|
|
411
|
+
except (EOFError, KeyboardInterrupt):
|
|
412
|
+
return ""
|
|
413
|
+
|
|
414
|
+
def get_input_with_default(self, message: str, default: str = "") -> str:
|
|
415
|
+
"""Get input with a default value shown in prompt."""
|
|
416
|
+
try:
|
|
417
|
+
prompt_with_default = f"{message}"
|
|
418
|
+
if default:
|
|
419
|
+
prompt_with_default += f" [{default}]"
|
|
420
|
+
prompt_with_default += ": "
|
|
421
|
+
|
|
422
|
+
user_input = input(prompt_with_default).strip()
|
|
423
|
+
return user_input if user_input else default
|
|
424
|
+
except (EOFError, KeyboardInterrupt):
|
|
425
|
+
return default
|
|
426
|
+
|
|
427
|
+
def get_path(self, message: str = "Enter path:", only_directories: bool = False):
|
|
428
|
+
"""Get a file or directory path."""
|
|
429
|
+
from pathlib import Path
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
path_str = input(f"{message} ").strip()
|
|
433
|
+
return Path(path_str) if path_str else None
|
|
434
|
+
except (EOFError, KeyboardInterrupt):
|
|
435
|
+
return None
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.syntax import Syntax
|
|
5
|
+
from rich.markdown import Markdown
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UIManager:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.console = Console()
|
|
12
|
+
from .hbot_prompt import HBOTPrompt
|
|
13
|
+
from .status_bar import StatusBar
|
|
14
|
+
|
|
15
|
+
self.prompt = HBOTPrompt()
|
|
16
|
+
self.status_bar = StatusBar()
|
|
17
|
+
self.prompt._status_bar = self.status_bar
|
|
18
|
+
|
|
19
|
+
def show_welcome(self):
|
|
20
|
+
welcome_text = """
|
|
21
|
+
[bold cyan]╔═══════════════════════════════════════╗[/bold cyan]
|
|
22
|
+
[bold cyan]║[/bold cyan] [bold white]H2OGPTE CLI Tool[/bold white] [bold cyan]║[/bold cyan]
|
|
23
|
+
[bold cyan]║[/bold cyan] [dim]RAG & AI Agent Operations[/dim] [bold cyan]║[/bold cyan]
|
|
24
|
+
[bold cyan]╚═══════════════════════════════════════╝[/bold cyan]
|
|
25
|
+
|
|
26
|
+
[green]Type /help for commands or start typing your prompt[/green]
|
|
27
|
+
|
|
28
|
+
[dim]Live Suggestions:
|
|
29
|
+
• Type [white]/[/white] to see all commands below the input
|
|
30
|
+
• Use [white]↑↓[/white] arrows to navigate suggestions
|
|
31
|
+
• Press [white]Tab[/white] or [white]Enter[/white] to select[/dim]
|
|
32
|
+
"""
|
|
33
|
+
self.console.print(Panel(welcome_text, border_style="cyan"))
|
|
34
|
+
|
|
35
|
+
def show_help(self):
|
|
36
|
+
help_table = Table(title="Available Commands", show_header=True)
|
|
37
|
+
help_table.add_column("Command", style="cyan", no_wrap=True)
|
|
38
|
+
help_table.add_column("Description", style="white")
|
|
39
|
+
help_table.add_column("Usage", style="dim")
|
|
40
|
+
|
|
41
|
+
commands = [
|
|
42
|
+
("/help", "Show this help message", "/help"),
|
|
43
|
+
("/register", "Connect to H2OGPTE", "/register [address] [api_key]"),
|
|
44
|
+
("/register", "Clear credentials", "/register clear"),
|
|
45
|
+
("/disconnect", "Disconnect and clear creds", "/disconnect"),
|
|
46
|
+
("", "Chat with H2OGPTE (RAG)", "Type any message"),
|
|
47
|
+
("/collection", "Switch/create collection", "/collection [name]"),
|
|
48
|
+
("/upload", "Upload files to collection", "/upload <file_path>"),
|
|
49
|
+
("/analyze", "Analyze directory", "/analyze [directory]"),
|
|
50
|
+
("/agent", "Use AI agent mode", "/agent <message>"),
|
|
51
|
+
("/research", "Deep research with AI agent", "/research <query>"),
|
|
52
|
+
("/config", "Configure settings", "/config"),
|
|
53
|
+
("/status", "Show session status", "/status"),
|
|
54
|
+
("/history", "Show command history", "/history [n]"),
|
|
55
|
+
("/clear", "Clear screen", "/clear"),
|
|
56
|
+
("/save", "Save current session", "/save [filename]"),
|
|
57
|
+
("/load", "Load saved session", "/load <filename>"),
|
|
58
|
+
("/session", "Create chat session", "/session [name]"),
|
|
59
|
+
("/exit", "Exit H2OGPTE CLI", "/exit [y]"),
|
|
60
|
+
("/quit", "Exit H2OGPTE CLI (same as /exit)", "/quit [y]"),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for cmd, desc, usage in commands:
|
|
64
|
+
help_table.add_row(cmd, desc, usage)
|
|
65
|
+
|
|
66
|
+
self.console.print(help_table)
|
|
67
|
+
|
|
68
|
+
shortcuts_table = Table(title="Keyboard Shortcuts", show_header=False)
|
|
69
|
+
shortcuts_table.add_column("Key", style="cyan")
|
|
70
|
+
shortcuts_table.add_column("Action", style="white")
|
|
71
|
+
|
|
72
|
+
shortcuts = [
|
|
73
|
+
("Ctrl+C", "Interrupt current operation"),
|
|
74
|
+
("Ctrl+D", "Exit (when line is empty)"),
|
|
75
|
+
("Tab", "Autocomplete"),
|
|
76
|
+
("↑/↓", "Navigate suggestions"),
|
|
77
|
+
("Enter", "Select suggestion or execute"),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for key, action in shortcuts:
|
|
81
|
+
shortcuts_table.add_row(key, action)
|
|
82
|
+
|
|
83
|
+
self.console.print(shortcuts_table)
|
|
84
|
+
|
|
85
|
+
def show_error(self, message: str, details: Optional[str] = None):
|
|
86
|
+
error_panel = Panel(
|
|
87
|
+
f"[red bold]✗ Error:[/red bold] {message}", border_style="red", expand=False
|
|
88
|
+
)
|
|
89
|
+
self.console.print(error_panel)
|
|
90
|
+
|
|
91
|
+
if details:
|
|
92
|
+
self.console.print(f"[dim]{details}[/dim]")
|
|
93
|
+
|
|
94
|
+
def show_success(self, message: str):
|
|
95
|
+
self.console.print(f"[green]✓[/green] {message}")
|
|
96
|
+
|
|
97
|
+
def show_info(self, message: str):
|
|
98
|
+
self.console.print(f"[blue]ℹ[/blue] {message}")
|
|
99
|
+
|
|
100
|
+
def show_warning(self, message: str):
|
|
101
|
+
self.console.print(f"[yellow]⚠[/yellow] {message}")
|
|
102
|
+
|
|
103
|
+
def show_code(self, code: str, language: str = "python"):
|
|
104
|
+
syntax = Syntax(code, language, theme="monokai", line_numbers=True)
|
|
105
|
+
self.console.print(Panel(syntax, title=f"[{language}]", border_style="blue"))
|
|
106
|
+
|
|
107
|
+
def show_markdown(self, content: str):
|
|
108
|
+
md = Markdown(content)
|
|
109
|
+
self.console.print(Panel(md, border_style="green"))
|
|
110
|
+
|
|
111
|
+
def clear_screen(self):
|
|
112
|
+
self.console.clear()
|
|
113
|
+
|
|
114
|
+
def show_status_bar(self):
|
|
115
|
+
self.status_bar.print_status_line()
|
|
116
|
+
|
|
117
|
+
def update_status(
|
|
118
|
+
self,
|
|
119
|
+
connected: bool = None,
|
|
120
|
+
username: str = None,
|
|
121
|
+
collection: str = None,
|
|
122
|
+
session: str = None,
|
|
123
|
+
):
|
|
124
|
+
if connected is not None:
|
|
125
|
+
self.status_bar.update_connection_status(connected, username)
|
|
126
|
+
if collection is not None:
|
|
127
|
+
self.status_bar.update_collection(collection)
|
|
128
|
+
if session is not None:
|
|
129
|
+
self.status_bar.update_session(session)
|