better agent
This commit is contained in:
50
cht/agent/buffer.py
Normal file
50
cht/agent/buffer.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Streaming text buffer — smooth reveal of bursty network chunks.
|
||||
|
||||
Accumulates text from the provider and reveals it at ~60fps with a
|
||||
~200ms target reveal pace, so the UI sees a smooth stream regardless
|
||||
of network burst patterns.
|
||||
|
||||
All methods must be called from the GTK main thread.
|
||||
"""
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
class StreamingTextBuffer:
|
||||
TICK_MS = 16 # ~60fps
|
||||
REVEAL_TARGET_MS = 200
|
||||
|
||||
def __init__(self, on_reveal: Callable[[str], None]):
|
||||
self._pending = ""
|
||||
self._on_reveal = on_reveal
|
||||
self._timer_id: int | None = None
|
||||
|
||||
def push(self, text: str):
|
||||
self._pending += text
|
||||
if self._timer_id is None:
|
||||
self._timer_id = GLib.timeout_add(self.TICK_MS, self._tick)
|
||||
|
||||
def _tick(self) -> bool:
|
||||
if not self._pending:
|
||||
self._timer_id = None
|
||||
return False
|
||||
n = max(1, len(self._pending) * self.TICK_MS // self.REVEAL_TARGET_MS)
|
||||
reveal, self._pending = self._pending[:n], self._pending[n:]
|
||||
self._on_reveal(reveal)
|
||||
return True
|
||||
|
||||
def flush(self):
|
||||
if self._pending:
|
||||
self._on_reveal(self._pending)
|
||||
self._pending = ""
|
||||
if self._timer_id is not None:
|
||||
GLib.source_remove(self._timer_id)
|
||||
self._timer_id = None
|
||||
|
||||
def cancel(self):
|
||||
self._pending = ""
|
||||
if self._timer_id is not None:
|
||||
GLib.source_remove(self._timer_id)
|
||||
self._timer_id = None
|
||||
Reference in New Issue
Block a user