virtuai-cli 0.7.5__tar.gz → 0.7.6__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 (23) hide show
  1. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/PKG-INFO +1 -1
  2. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/pyproject.toml +1 -1
  3. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/__init__.py +1 -1
  4. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/tui.py +4 -1
  5. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/widgets.py +42 -2
  6. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  7. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/README.md +0 -0
  8. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/setup.cfg +0 -0
  9. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/__init__.py +0 -0
  10. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/ask.py +0 -0
  11. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/command.py +0 -0
  12. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/history.py +0 -0
  13. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/chat/sse.py +0 -0
  14. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/config.py +0 -0
  15. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/executor.py +0 -0
  16. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/main.py +0 -0
  17. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/runner.py +0 -0
  18. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli/security.py +0 -0
  19. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
  20. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  21. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  22. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/src/virtuai_cli.egg-info/requires.txt +0 -0
  23. {virtuai_cli-0.7.5 → virtuai_cli-0.7.6}/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.7.5
3
+ Version: 0.7.6
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.7.5"
7
+ version = "0.7.6"
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.7.5"
2
+ __version__ = "0.7.6"
@@ -176,10 +176,13 @@ class ChatApp(App):
176
176
  self._runner_task = asyncio.create_task(self._run_ws())
177
177
 
178
178
  def _tick_status(self) -> None:
179
+ # Only repaint when something is actually animating — otherwise we'd
180
+ # force a Textual reflow 10x/sec across the whole scrollback, which
181
+ # makes long conversations laggy.
179
182
  moving = self._streaming or self._connection_state in ("connecting", "reconnecting")
180
183
  if moving:
181
184
  self._spinner_idx = (self._spinner_idx + 1) % len(self._SPINNER)
182
- self._refresh_status_bar()
185
+ self._refresh_status_bar()
183
186
 
184
187
  def _refresh_status_bar(self) -> None:
185
188
  try:
@@ -200,19 +200,52 @@ class UserBubble(Static):
200
200
 
201
201
 
202
202
  class TextSegment(Static):
203
- """A streaming markdown segment of assistant text."""
203
+ """A streaming markdown segment of assistant text.
204
+
205
+ Renders are throttled to ~20 Hz: the markdown AST is rebuilt from the
206
+ full buffer on every paint (Rich/mistletoe parse from scratch), so
207
+ rendering once per token on a long response is O(n²) and dominates CPU.
208
+ With throttling, bursts of tokens coalesce into a single render and any
209
+ final pending render is flushed via mark_final()/flush().
210
+ """
204
211
 
205
212
  DEFAULT_CSS = """
206
213
  TextSegment { margin: 0 0 0 2; }
207
214
  """
208
215
 
216
+ _RENDER_INTERVAL = 0.05 # seconds (= 20 fps)
217
+
209
218
  def __init__(self) -> None:
210
219
  super().__init__("")
211
220
  self._buf = ""
221
+ self._last_render_t: float = 0.0
222
+ self._render_pending: bool = False
212
223
 
213
224
  def append(self, chunk: str) -> None:
214
225
  self._buf += chunk
215
- self.update(Markdown(normalize_artifacts(self._buf)))
226
+ if self._render_pending:
227
+ # A flush is already scheduled and will pick up the new content.
228
+ return
229
+ now = time.monotonic()
230
+ elapsed = now - self._last_render_t
231
+ if elapsed >= self._RENDER_INTERVAL:
232
+ self._flush()
233
+ else:
234
+ self._render_pending = True
235
+ self.set_timer(self._RENDER_INTERVAL - elapsed, self._flush)
236
+
237
+ def flush(self) -> None:
238
+ """Force a render now — used to finalize the segment at end-of-stream."""
239
+ self._flush()
240
+
241
+ def _flush(self) -> None:
242
+ self._render_pending = False
243
+ self._last_render_t = time.monotonic()
244
+ try:
245
+ self.update(Markdown(normalize_artifacts(self._buf)))
246
+ except Exception:
247
+ # Widget might be unmounted by the time a scheduled flush fires.
248
+ pass
216
249
 
217
250
  @property
218
251
  def content(self) -> str:
@@ -585,4 +618,11 @@ class AssistantTurn(Vertical):
585
618
 
586
619
  async def mark_final(self) -> None:
587
620
  await self._stop_thinking()
621
+ # Force any throttled TextSegment to do its final render so the user
622
+ # doesn't see the last sub-50ms of tokens missing after stream-end.
623
+ for seg in self.query(TextSegment):
624
+ try:
625
+ seg.flush()
626
+ except Exception:
627
+ pass
588
628
  self._final = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.7.5
3
+ Version: 0.7.6
4
4
  Summary: Run VirtuAI deep agents on your local machine
5
5
  Author-email: uCloudStore <lmoreno@ucloudstore.com>
6
6
  License: Proprietary
File without changes
File without changes