File size: 2,802 Bytes
ad88834
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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()