virtuai-cli 0.3.0__tar.gz → 0.4.0__tar.gz

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.
Files changed (22) hide show
  1. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/PKG-INFO +1 -1
  2. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/pyproject.toml +1 -1
  3. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/__init__.py +1 -1
  4. virtuai_cli-0.4.0/src/virtuai_cli/chat/history.py +44 -0
  5. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/chat/tui.py +77 -0
  6. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  7. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/SOURCES.txt +1 -0
  8. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/README.md +0 -0
  9. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/setup.cfg +0 -0
  10. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/chat/__init__.py +0 -0
  11. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/chat/command.py +0 -0
  12. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/chat/sse.py +0 -0
  13. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/chat/widgets.py +0 -0
  14. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/config.py +0 -0
  15. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/executor.py +0 -0
  16. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/main.py +0 -0
  17. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/runner.py +0 -0
  18. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli/security.py +0 -0
  19. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  20. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  21. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/requires.txt +0 -0
  22. {virtuai_cli-0.3.0 → virtuai_cli-0.4.0}/src/virtuai_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Run VirtuAI deep agents on your local machine
5
5
  Author-email: uCloudStore <lmoreno@ucloudstore.com>
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "virtuai-cli"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Run VirtuAI deep agents on your local machine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,2 +1,2 @@
1
1
  """VirtuAI local CLI."""
2
- __version__ = "0.3.0"
2
+ __version__ = "0.4.0"
@@ -0,0 +1,44 @@
1
+ """HTTP helpers for browsing past conversations from the TUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import certifi
8
+ import httpx
9
+
10
+
11
+ _VERIFY = certifi.where()
12
+
13
+
14
+ async def list_conversations(
15
+ server_url: str,
16
+ token: str,
17
+ agent_id: str,
18
+ limit: int = 20,
19
+ ) -> list[dict]:
20
+ """Return up to `limit` of the user's recent conversations for this agent."""
21
+ headers = {"Authorization": f"Bearer {token}"}
22
+ url = f"{server_url.rstrip('/')}/api/web-chat/conversations/{agent_id}"
23
+ async with httpx.AsyncClient(verify=_VERIFY, timeout=15.0) as client:
24
+ resp = await client.get(url, headers=headers, params={"limit": limit, "offset": 0})
25
+ resp.raise_for_status()
26
+ return resp.json()
27
+
28
+
29
+ async def load_conversation(
30
+ server_url: str,
31
+ token: str,
32
+ agent_id: str,
33
+ session_id: str,
34
+ message_limit: int = 200,
35
+ ) -> Optional[dict]:
36
+ """Return {conversation, messages} for one session_id, or None if not found."""
37
+ headers = {"Authorization": f"Bearer {token}"}
38
+ url = f"{server_url.rstrip('/')}/api/web-chat/conversations/{agent_id}/{session_id}"
39
+ async with httpx.AsyncClient(verify=_VERIFY, timeout=30.0) as client:
40
+ resp = await client.get(url, headers=headers, params={"message_limit": message_limit})
41
+ if resp.status_code == 404:
42
+ return None
43
+ resp.raise_for_status()
44
+ return resp.json()
@@ -14,6 +14,7 @@ from textual.reactive import reactive
14
14
  from textual.widgets import Footer, Header, Input, Static
15
15
 
16
16
  from virtuai_cli import runner as ws_runner
17
+ from virtuai_cli.chat.history import list_conversations, load_conversation
17
18
  from virtuai_cli.chat.sse import stream_chat
18
19
  from virtuai_cli.chat.widgets import AssistantTurn, UserBubble
19
20
 
@@ -173,11 +174,17 @@ class ChatApp(App):
173
174
  await self._show_models()
174
175
  elif head == "/model":
175
176
  await self._set_model(arg)
177
+ elif head == "/history":
178
+ await self._show_history()
179
+ elif head == "/load":
180
+ await self._load_session(arg)
176
181
  elif head == "/help":
177
182
  await self._append(Static(
178
183
  "[b]Commands[/b]\n"
179
184
  " /help this list\n"
180
185
  " /clear, /new start a fresh conversation\n"
186
+ " /history list this agent's recent conversations\n"
187
+ " /load <id> load a past conversation by session_id\n"
181
188
  " /models list models available for this agent\n"
182
189
  " /model <id> switch the model for the next message\n"
183
190
  " /exit, /quit close the TUI\n"
@@ -201,6 +208,76 @@ class ChatApp(App):
201
208
  lines.append("\n[dim]Use [/dim][b]/model <id>[/b][dim] to switch.[/dim]")
202
209
  await self._append(Static("\n".join(lines)))
203
210
 
211
+ async def _show_history(self) -> None:
212
+ try:
213
+ convs = await list_conversations(self.server_url, self.token, self.agent_id, limit=20)
214
+ except Exception as exc:
215
+ await self._append(Static(f"[red]Could not load history:[/red] {exc}"))
216
+ return
217
+ if not convs:
218
+ await self._append(Static("[dim]No past conversations with this agent.[/dim]"))
219
+ return
220
+ lines = ["[b]Recent conversations[/b] [dim](most recent first)[/dim]"]
221
+ for c in convs:
222
+ sid = c.get("session_id", "")
223
+ title = c.get("title") or "(untitled)"
224
+ when = (c.get("last_message_at") or c.get("updated_at") or "")[:19]
225
+ count = c.get("message_count", 0)
226
+ marker = "[green]✓[/green] " if sid == self.session_id else " "
227
+ lines.append(f"{marker}[bold]{sid}[/bold] {when} · {count} msgs · {title}")
228
+ lines.append("\n[dim]Use [/dim][b]/load <session_id>[/b][dim] to switch.[/dim]")
229
+ await self._append(Static("\n".join(lines)))
230
+
231
+ async def _load_session(self, session_id: str) -> None:
232
+ if not session_id:
233
+ await self._append(Static(
234
+ "Usage: [b]/load <session_id>[/b] (see [b]/history[/b] for IDs)"
235
+ ))
236
+ return
237
+ if self._stream_task and not self._stream_task.done():
238
+ await self._append(Static(
239
+ "[yellow]Cancel the in-flight response (Esc) before loading another conversation.[/yellow]"
240
+ ))
241
+ return
242
+ try:
243
+ data = await load_conversation(self.server_url, self.token, self.agent_id, session_id)
244
+ except Exception as exc:
245
+ await self._append(Static(f"[red]Could not load conversation:[/red] {exc}"))
246
+ return
247
+ if data is None:
248
+ await self._append(Static(f"[red]Conversation not found:[/red] {session_id}"))
249
+ return
250
+
251
+ # Clear the screen and rebuild it with the loaded history.
252
+ scroll = self.query_one("#conversation", VerticalScroll)
253
+ for child in list(scroll.children):
254
+ await child.remove()
255
+
256
+ conv = data.get("conversation") or {}
257
+ messages = data.get("messages") or []
258
+ self.session_id = conv.get("session_id") or session_id
259
+ self._current_turn = None
260
+
261
+ header = (
262
+ f"[b]Loaded conversation[/b] {self.session_id}\n"
263
+ f"[dim]{conv.get('title') or '(untitled)'} · {len(messages)} message(s)[/dim]"
264
+ )
265
+ await scroll.mount(Static(header))
266
+
267
+ for msg in messages:
268
+ mtype = (msg.get("message_type") or "").lower()
269
+ content = msg.get("content") or ""
270
+ if mtype in ("user", "human"):
271
+ await scroll.mount(UserBubble(content))
272
+ else:
273
+ turn = AssistantTurn()
274
+ await scroll.mount(turn)
275
+ if content:
276
+ await turn.append_token(content)
277
+ turn.mark_final()
278
+ scroll.scroll_end(animate=False)
279
+ self._set_status(f"loaded {self.session_id}")
280
+
204
281
  async def _set_model(self, requested: str) -> None:
205
282
  if not requested:
206
283
  current = self.current_model_id or "(none)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Run VirtuAI deep agents on your local machine
5
5
  Author-email: uCloudStore <lmoreno@ucloudstore.com>
6
6
  License: Proprietary
@@ -14,6 +14,7 @@ src/virtuai_cli.egg-info/requires.txt
14
14
  src/virtuai_cli.egg-info/top_level.txt
15
15
  src/virtuai_cli/chat/__init__.py
16
16
  src/virtuai_cli/chat/command.py
17
+ src/virtuai_cli/chat/history.py
17
18
  src/virtuai_cli/chat/sse.py
18
19
  src/virtuai_cli/chat/tui.py
19
20
  src/virtuai_cli/chat/widgets.py
File without changes
File without changes