left0ver's picture
Publish com.left0ver.agentcade 0.1.0
ad88834 verified
Raw History Blame Contribute Delete
2.8 kB
import asyncio
import logging
import math
import time
from collections.abc import Awaitable, Callable
from contextlib import suppress
from agentcade.expressions import segmented_progress_vector
from observability import describe
logger = logging.getLogger(__name__)
class ProgressDisplay:
def __init__(self, show: Callable[[str], Awaitable[None]], lock: asyncio.Lock):
self.show = show
self.lock = lock
self.task: asyncio.Task | None = None
self.token = 0
self.active = False
self.step = 0
async def frame(self, token: int, step: int) -> None:
async with self.lock:
if self.active and token == self.token:
await self.show(segmented_progress_vector(step))
self.step = step
async def start(self) -> int:
await self.cancel()
self.active = True
self.step = 0
await self.frame(self.token, 0)
return self.token
def advance(self, token: int, seconds: float) -> None:
if not math.isfinite(seconds) or seconds <= 0:
raise ValueError("预计生成时间必须是有限正数")
if not self.active or token != self.token:
return
if self.task is not None:
raise RuntimeError("当前进度任务已经开始计时")
self.task = asyncio.create_task(self.animate(token, seconds))
async def animate(self, token: int, seconds: float) -> None:
started = time.monotonic()
while self.active and token == self.token and self.step < 9:
deadline = started + (self.step + 1) * seconds / 10
await asyncio.sleep(max(0, deadline - time.monotonic()))
step = min(9, int((time.monotonic() - started) * 10 / seconds))
await self.frame(token, step)
async def stop_timer(self) -> None:
task, self.task = self.task, None
if task is not None:
task.cancel()
try:
with suppress(asyncio.CancelledError):
await task
except Exception as error:
# 进度画面只是提示,显示失败不影响后面的输出。
logger.warning("进度显示失败:%s", describe(error), exc_info=True)
async def finish(self) -> None:
await self.stop_timer()
if self.active:
await self.frame(self.token, 10)
self.active = False
self.token += 1
await asyncio.sleep(0.25)
async def cancel(self) -> None:
self.active = False
self.token += 1
await self.stop_timer()
async with self.lock:
pass
async def check(self) -> None:
if self.task is not None and self.task.done():
await self.stop_timer()