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,442 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
from ...h2ogpte import H2OGPTE
|
|
8
|
+
from ...h2ogpte_async import H2OGPTEAsync
|
|
9
|
+
from ...h2ogpte import PartialChatMessage
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class H2OGPTEClient:
|
|
15
|
+
def __init__(self, address: str, api_key: str):
|
|
16
|
+
self.address = address
|
|
17
|
+
self.api_key = api_key
|
|
18
|
+
self.client = H2OGPTE(address=address, api_key=api_key)
|
|
19
|
+
self.async_client = H2OGPTEAsync(address=address, api_key=api_key)
|
|
20
|
+
self.username = None
|
|
21
|
+
self.current_collection_id = None
|
|
22
|
+
self.current_collection_name = None
|
|
23
|
+
self.current_chat_session_id = None
|
|
24
|
+
self.current_chat_session_name = None
|
|
25
|
+
|
|
26
|
+
async def test_connection_and_get_meta(self) -> bool:
|
|
27
|
+
try:
|
|
28
|
+
import concurrent.futures
|
|
29
|
+
|
|
30
|
+
def get_meta():
|
|
31
|
+
return self.client.get_meta()
|
|
32
|
+
|
|
33
|
+
loop = asyncio.get_event_loop()
|
|
34
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
35
|
+
meta = await asyncio.wait_for(
|
|
36
|
+
loop.run_in_executor(executor, get_meta), timeout=5.0
|
|
37
|
+
)
|
|
38
|
+
self.username = meta.username
|
|
39
|
+
console.print(
|
|
40
|
+
f"[green]✓[/green] Connected as user: [cyan]{self.username}[/cyan]"
|
|
41
|
+
)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
except asyncio.TimeoutError:
|
|
45
|
+
console.print(f"[red]Connection timeout (5s) - network may be slow[/red]")
|
|
46
|
+
return False
|
|
47
|
+
except Exception as e:
|
|
48
|
+
console.print(f"[red]Connection failed: {e}[/red]")
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
async def create_collection(
|
|
52
|
+
self, name: str, description: str = ""
|
|
53
|
+
) -> Optional[str]:
|
|
54
|
+
try:
|
|
55
|
+
collection_id = await self.async_client.create_collection(
|
|
56
|
+
name=name, description=description
|
|
57
|
+
)
|
|
58
|
+
self.current_collection_id = collection_id
|
|
59
|
+
self.current_collection_name = name
|
|
60
|
+
console.print(f"[green]✓[/green] Collection '[cyan]{name}[/cyan]' created")
|
|
61
|
+
return collection_id
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
console.print(f"[red]Error creating collection: {e}[/red]")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
async def create_chat_session(
|
|
68
|
+
self, collection_id: Optional[str] = None, session_name: str = "chat-session"
|
|
69
|
+
) -> Optional[str]:
|
|
70
|
+
try:
|
|
71
|
+
chat_session_id = await self.async_client.create_chat_session(collection_id)
|
|
72
|
+
self.current_chat_session_id = chat_session_id
|
|
73
|
+
self.current_chat_session_name = session_name
|
|
74
|
+
console.print(
|
|
75
|
+
f"[green]✓[/green] Chat session '[cyan]{session_name}[/cyan]' created"
|
|
76
|
+
)
|
|
77
|
+
return chat_session_id
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
console.print(f"[red]Error creating chat session: {e}[/red]")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
async def rename_chat_session(self, session_id: str, session_name: str) -> bool:
|
|
84
|
+
try:
|
|
85
|
+
await self.async_client.rename_chat_session(session_id, session_name)
|
|
86
|
+
self.current_chat_session_name = session_name
|
|
87
|
+
console.print(
|
|
88
|
+
f"[green]✓[/green] Chat session renamed to '[cyan]{session_name}[/cyan]'"
|
|
89
|
+
)
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
console.print(f"[red]Error renaming chat session: {e}[/red]")
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
async def query_streaming(
|
|
97
|
+
self,
|
|
98
|
+
message: str,
|
|
99
|
+
timeout: int = 2400,
|
|
100
|
+
use_agent: bool = False,
|
|
101
|
+
agent_type: str = None,
|
|
102
|
+
) -> Optional[Dict[str, Any]]:
|
|
103
|
+
if not self.current_chat_session_id:
|
|
104
|
+
console.print("[red]No active chat session. Create one first.[/red]")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
import re
|
|
109
|
+
|
|
110
|
+
accumulated_content = ""
|
|
111
|
+
displayed_turn_count = 0
|
|
112
|
+
last_turn_title = None
|
|
113
|
+
header_shown = False
|
|
114
|
+
final_response_content = ""
|
|
115
|
+
|
|
116
|
+
def callback(chat_message):
|
|
117
|
+
nonlocal accumulated_content, displayed_turn_count, last_turn_title, header_shown, final_response_content
|
|
118
|
+
|
|
119
|
+
if not isinstance(chat_message, PartialChatMessage):
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
content = chat_message.content
|
|
123
|
+
if not content:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
accumulated_content += content
|
|
127
|
+
final_response_content = (
|
|
128
|
+
accumulated_content # Keep track of full content for final response
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if use_agent:
|
|
132
|
+
if not header_shown:
|
|
133
|
+
console.print("[blue]Agentic Analysis[/blue]")
|
|
134
|
+
header_shown = True
|
|
135
|
+
|
|
136
|
+
chunks = accumulated_content.split("ENDOFTURN\n")
|
|
137
|
+
completed_chunks = len(chunks) - 1
|
|
138
|
+
while displayed_turn_count < completed_chunks:
|
|
139
|
+
chunk = chunks[displayed_turn_count]
|
|
140
|
+
turn_title = None
|
|
141
|
+
|
|
142
|
+
turn_title_match = re.search(
|
|
143
|
+
r"<(?:stream_)?turn_title>(.*?)</(?:stream_)?turn_title>",
|
|
144
|
+
chunk,
|
|
145
|
+
re.DOTALL,
|
|
146
|
+
)
|
|
147
|
+
if turn_title_match:
|
|
148
|
+
turn_title = turn_title_match.group(1).strip()
|
|
149
|
+
else:
|
|
150
|
+
lines = chunk.strip().split("\n")
|
|
151
|
+
for line in lines:
|
|
152
|
+
if line.strip():
|
|
153
|
+
turn_title = line.strip()
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
if turn_title:
|
|
157
|
+
turn_title = re.sub(
|
|
158
|
+
r"^[\*#\s\t\n]+", "", turn_title
|
|
159
|
+
).strip()
|
|
160
|
+
turn_title = re.sub(r"\*+$", "", turn_title).strip()
|
|
161
|
+
|
|
162
|
+
if turn_title and turn_title != last_turn_title:
|
|
163
|
+
console.print(f" • {turn_title}")
|
|
164
|
+
last_turn_title = turn_title
|
|
165
|
+
|
|
166
|
+
displayed_turn_count += 1
|
|
167
|
+
|
|
168
|
+
if len(chunks) > displayed_turn_count and chunks[-1].strip():
|
|
169
|
+
current_chunk = chunks[-1]
|
|
170
|
+
|
|
171
|
+
turn_title_match = re.search(
|
|
172
|
+
r"<(?:stream_)?turn_title>(.*?)</(?:stream_)?turn_title>",
|
|
173
|
+
current_chunk,
|
|
174
|
+
re.DOTALL,
|
|
175
|
+
)
|
|
176
|
+
if turn_title_match:
|
|
177
|
+
new_title = turn_title_match.group(1).strip()
|
|
178
|
+
else:
|
|
179
|
+
lines = current_chunk.strip().split("\n")
|
|
180
|
+
new_title = None
|
|
181
|
+
for line in lines:
|
|
182
|
+
if line.strip():
|
|
183
|
+
new_title = line.strip()
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
if new_title:
|
|
187
|
+
cleaned_title = re.sub(
|
|
188
|
+
r"^[\*#\s\t\n]+", "", new_title
|
|
189
|
+
).strip()
|
|
190
|
+
cleaned_title = re.sub(r"\*+$", "", cleaned_title).strip()
|
|
191
|
+
|
|
192
|
+
if cleaned_title and cleaned_title != last_turn_title:
|
|
193
|
+
console.print(f" • {cleaned_title}")
|
|
194
|
+
last_turn_title = cleaned_title
|
|
195
|
+
else:
|
|
196
|
+
console.print(content, end="")
|
|
197
|
+
|
|
198
|
+
if use_agent:
|
|
199
|
+
console.print("\n", end="")
|
|
200
|
+
else:
|
|
201
|
+
console.print("\n[bold green]H2OGPTE:[/bold green] ", end="")
|
|
202
|
+
|
|
203
|
+
query_params = {"timeout": timeout, "llm": "auto", "callback": callback}
|
|
204
|
+
|
|
205
|
+
if use_agent:
|
|
206
|
+
query_params["llm_args"] = {
|
|
207
|
+
"use_agent": True,
|
|
208
|
+
}
|
|
209
|
+
if agent_type:
|
|
210
|
+
query_params["llm_args"]["agent_type"] = agent_type
|
|
211
|
+
|
|
212
|
+
async with self.async_client.connect(
|
|
213
|
+
self.current_chat_session_id
|
|
214
|
+
) as session:
|
|
215
|
+
reply = await session.query(message, **query_params)
|
|
216
|
+
|
|
217
|
+
usage = await self.async_client.list_chat_message_meta_part(
|
|
218
|
+
reply.id, "usage_stats"
|
|
219
|
+
)
|
|
220
|
+
usage_dict = json.loads(usage.content)
|
|
221
|
+
|
|
222
|
+
if use_agent:
|
|
223
|
+
console.print("\n[blue]Final Response:[/blue]")
|
|
224
|
+
|
|
225
|
+
if hasattr(reply, "content") and reply.content:
|
|
226
|
+
response_content = reply.content
|
|
227
|
+
if response_content:
|
|
228
|
+
console.print(response_content)
|
|
229
|
+
else:
|
|
230
|
+
console.print("No response content available.")
|
|
231
|
+
|
|
232
|
+
console.print()
|
|
233
|
+
else:
|
|
234
|
+
console.print()
|
|
235
|
+
|
|
236
|
+
return usage_dict
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
console.print(f"\n[red]Query error: {e}[/red]")
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
async def upload_files(self, file_paths: List[Path]) -> Dict[str, str]:
|
|
243
|
+
if not self.current_collection_id:
|
|
244
|
+
console.print("[red]No active collection. Create one first.[/red]")
|
|
245
|
+
return {}
|
|
246
|
+
|
|
247
|
+
uploaded_files = {}
|
|
248
|
+
upload_ids = []
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
import concurrent.futures
|
|
252
|
+
|
|
253
|
+
for file_path in file_paths:
|
|
254
|
+
|
|
255
|
+
def upload_file():
|
|
256
|
+
with open(file_path, "rb") as f:
|
|
257
|
+
return self.client.upload(file_path.name, f)
|
|
258
|
+
|
|
259
|
+
loop = asyncio.get_event_loop()
|
|
260
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
261
|
+
upload_id = await loop.run_in_executor(executor, upload_file)
|
|
262
|
+
uploaded_files[str(file_path)] = upload_id
|
|
263
|
+
upload_ids.append(upload_id)
|
|
264
|
+
console.print(f"[green]✓[/green] Uploaded: {file_path.name}")
|
|
265
|
+
|
|
266
|
+
last_seen_update = None
|
|
267
|
+
|
|
268
|
+
def callback(job):
|
|
269
|
+
nonlocal last_seen_update
|
|
270
|
+
if not job:
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
current_update = job.last_update_date
|
|
274
|
+
if last_seen_update is not None and current_update <= last_seen_update:
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
last_seen_update = current_update
|
|
278
|
+
for s in job.statuses:
|
|
279
|
+
if not s:
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
status_text = getattr(s, "status", None)
|
|
283
|
+
if status_text:
|
|
284
|
+
console.print(f"[blue] {status_text}...[/blue]")
|
|
285
|
+
|
|
286
|
+
def ingest_uploads():
|
|
287
|
+
ingest_result = self.client.ingest_uploads(
|
|
288
|
+
self.current_collection_id, upload_ids, callback=callback
|
|
289
|
+
)
|
|
290
|
+
console.print(f"[green]✓[/green] Ingested {len(upload_ids)} files")
|
|
291
|
+
return ingest_result
|
|
292
|
+
|
|
293
|
+
loop = asyncio.get_event_loop()
|
|
294
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
295
|
+
await loop.run_in_executor(executor, ingest_uploads)
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
console.print(f"[red]Upload error: {e}[/red]")
|
|
299
|
+
|
|
300
|
+
return uploaded_files
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class RAGManager:
|
|
304
|
+
def __init__(self):
|
|
305
|
+
self.client: Optional[H2OGPTEClient] = None
|
|
306
|
+
self.connected = False
|
|
307
|
+
|
|
308
|
+
async def auto_reconnect(self, settings) -> bool:
|
|
309
|
+
if not settings.rag.endpoint:
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
api_key = settings.get_rag_api_key()
|
|
313
|
+
if not api_key:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
console.print(f"[blue]Reconnecting to {settings.rag.endpoint}...[/blue]")
|
|
317
|
+
return await self.connect_and_get_user(settings.rag.endpoint, api_key)
|
|
318
|
+
|
|
319
|
+
async def connect_and_get_user(self, address: str, api_key: str) -> bool:
|
|
320
|
+
console.print(f"[blue]Connecting to H2OGPTE at {address}...[/blue]")
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
self.client = H2OGPTEClient(address, api_key)
|
|
324
|
+
|
|
325
|
+
if await self.client.test_connection_and_get_meta():
|
|
326
|
+
self.connected = True
|
|
327
|
+
console.print("[green]✓[/green] Successfully connected to H2OGPTE")
|
|
328
|
+
return True
|
|
329
|
+
else:
|
|
330
|
+
self.connected = False
|
|
331
|
+
console.print("[red]✗[/red] Failed to connect to H2OGPTE")
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
except ImportError as e:
|
|
335
|
+
console.print(f"[red]✗[/red] {e}")
|
|
336
|
+
return False
|
|
337
|
+
except Exception as e:
|
|
338
|
+
console.print(f"[red]✗[/red] Unexpected error: {e}")
|
|
339
|
+
self.connected = False
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
async def switch_to_collection(self, collection_name: str) -> bool:
|
|
343
|
+
if not self.connected or not self.client:
|
|
344
|
+
console.print("[red]Not connected to H2OGPTE. Use /register first.[/red]")
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
collection_id = await self.client.create_collection(
|
|
348
|
+
name=collection_name, description=f"CLI-Collection: {collection_name}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if collection_id:
|
|
352
|
+
self.client.current_chat_session_id = None
|
|
353
|
+
self.client.current_chat_session_name = None
|
|
354
|
+
console.print("[green]✓[/green] Collection switched successfully")
|
|
355
|
+
return True
|
|
356
|
+
else:
|
|
357
|
+
console.print("[red]✗[/red] Failed to switch to collection")
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
async def get_username(self) -> Optional[str]:
|
|
361
|
+
if self.client:
|
|
362
|
+
return self.client.username
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
async def get_collection_name(self) -> Optional[str]:
|
|
366
|
+
if self.client:
|
|
367
|
+
return self.client.current_collection_name
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
async def get_chat_session_name(self) -> Optional[str]:
|
|
371
|
+
if self.client:
|
|
372
|
+
return self.client.current_chat_session_name
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
async def create_chat_session(self, session_name: str) -> bool:
|
|
376
|
+
if not self.connected or not self.client:
|
|
377
|
+
console.print("[red]Not connected to H2OGPTE. Use /register first.[/red]")
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
session_id = await self.client.create_chat_session(
|
|
381
|
+
collection_id=self.client.current_collection_id, session_name=session_name
|
|
382
|
+
)
|
|
383
|
+
return session_id is not None
|
|
384
|
+
|
|
385
|
+
async def create_chat_session_with_name(self, session_name: str) -> Optional[str]:
|
|
386
|
+
if not self.connected or not self.client:
|
|
387
|
+
console.print("[red]Not connected to H2OGPTE. Use /register first.[/red]")
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
session_id = await self.client.create_chat_session(
|
|
391
|
+
collection_id=self.client.current_collection_id, session_name=session_name
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if session_id and session_name != "chat-session":
|
|
395
|
+
await self.client.rename_chat_session(session_id, session_name)
|
|
396
|
+
|
|
397
|
+
return session_id
|
|
398
|
+
|
|
399
|
+
async def send_message(
|
|
400
|
+
self, message: str, use_agent: bool = False, agent_type: str = None
|
|
401
|
+
) -> Optional[Dict[str, Any]]:
|
|
402
|
+
if not self.connected or not self.client:
|
|
403
|
+
console.print("[red]Not connected to H2OGPTE. Use /register first.[/red]")
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
if not self.client.current_collection_id:
|
|
407
|
+
console.print("[blue]Creating default collection...[/blue]")
|
|
408
|
+
collection_id = await self.client.create_collection(
|
|
409
|
+
name="CLI-Collection",
|
|
410
|
+
description="CLI automatically created collection",
|
|
411
|
+
)
|
|
412
|
+
if not collection_id:
|
|
413
|
+
console.print("[red]Failed to create collection[/red]")
|
|
414
|
+
return None
|
|
415
|
+
|
|
416
|
+
if not self.client.current_chat_session_id:
|
|
417
|
+
import datetime
|
|
418
|
+
|
|
419
|
+
session_name = f"chat-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
420
|
+
console.print(f"[blue]Creating chat session '{session_name}'...[/blue]")
|
|
421
|
+
session_id = await self.client.create_chat_session(
|
|
422
|
+
collection_id=self.client.current_collection_id,
|
|
423
|
+
session_name=session_name,
|
|
424
|
+
)
|
|
425
|
+
if session_id:
|
|
426
|
+
await self.client.rename_chat_session(session_id, session_name)
|
|
427
|
+
|
|
428
|
+
return await self.client.query_streaming(
|
|
429
|
+
message, use_agent=use_agent, agent_type=agent_type
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
async def upload_files(self, paths: List[Path]) -> Dict[str, str]:
|
|
433
|
+
if not self.connected or not self.client:
|
|
434
|
+
console.print("[red]Not connected to H2OGPTE. Use /register first.[/red]")
|
|
435
|
+
return {}
|
|
436
|
+
|
|
437
|
+
console.print(f"[blue]Uploading {len(paths)} file(s)...[/blue]")
|
|
438
|
+
return await self.client.upload_files(paths)
|
|
439
|
+
|
|
440
|
+
async def close(self):
|
|
441
|
+
self.connected = False
|
|
442
|
+
self.client = None
|
h2ogpte/cli/main.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from rich.traceback import install
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from .core.app import initialize_app
|
|
7
|
+
from .commands.dispatcher import dispatch_command
|
|
8
|
+
|
|
9
|
+
install(show_locals=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def main_loop():
|
|
13
|
+
try:
|
|
14
|
+
app = initialize_app()
|
|
15
|
+
app.settings.ensure_directories()
|
|
16
|
+
app.ui.show_welcome()
|
|
17
|
+
await app.try_auto_reconnect()
|
|
18
|
+
|
|
19
|
+
exit_attempts = 0
|
|
20
|
+
while True:
|
|
21
|
+
try:
|
|
22
|
+
command = app.ui.prompt.get_input("❯ ")
|
|
23
|
+
|
|
24
|
+
if not command.strip():
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
if command in ["/exit", "/quit"]:
|
|
28
|
+
exit_attempts += 1
|
|
29
|
+
if exit_attempts > 1 and not sys.stdin.isatty():
|
|
30
|
+
app.console.print(
|
|
31
|
+
"[cyan]Non-interactive mode: Exiting without confirmation[/cyan]"
|
|
32
|
+
)
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
should_continue = await dispatch_command(command)
|
|
36
|
+
if not should_continue:
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
if command not in ["/exit", "/quit"]:
|
|
40
|
+
exit_attempts = 0
|
|
41
|
+
|
|
42
|
+
except KeyboardInterrupt:
|
|
43
|
+
app.console.print(
|
|
44
|
+
"\n[yellow]⚠ Interrupted. Use /exit to quit or continue typing.[/yellow]"
|
|
45
|
+
)
|
|
46
|
+
continue
|
|
47
|
+
except EOFError:
|
|
48
|
+
app.console.print("\n[cyan]Goodbye![/cyan]")
|
|
49
|
+
break
|
|
50
|
+
except Exception as e:
|
|
51
|
+
app.console.print(f"[red]Unexpected error: {e}[/red]")
|
|
52
|
+
app.console.print("[dim]Type /exit to quit or try again[/dim]")
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
await app.cleanup()
|
|
57
|
+
except Exception as e:
|
|
58
|
+
app.console.print(
|
|
59
|
+
f"[yellow]Warning: Could not clean up properly: {e}[/yellow]"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
Console().print(f"[red]Critical error in main loop: {e}[/red]")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
try:
|
|
68
|
+
try:
|
|
69
|
+
loop = asyncio.get_running_loop()
|
|
70
|
+
Console().print(
|
|
71
|
+
"[yellow]Running in existing event loop, creating task...[/yellow]"
|
|
72
|
+
)
|
|
73
|
+
task = loop.create_task(main_loop())
|
|
74
|
+
loop.run_until_complete(task)
|
|
75
|
+
except RuntimeError:
|
|
76
|
+
asyncio.run(main_loop())
|
|
77
|
+
except KeyboardInterrupt:
|
|
78
|
+
Console().print("\n[yellow]Interrupted[/yellow]")
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
console = Console()
|
|
82
|
+
console.print(f"[red]Fatal error: {e}[/red]")
|
|
83
|
+
import traceback
|
|
84
|
+
|
|
85
|
+
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
File without changes
|