akseljoonas commited on
Commit
4a5df81
·
1 Parent(s): 70d2074

Enhance user experience by clearing 'Waiting for approval' status immediately upon approval and refining markdown table styles for better readability and aesthetics.

Browse files
agent/utils/boot_timing.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared timing and color helpers for startup visual effects."""
2
+
3
+ import math
4
+
5
+
6
+ def settle_curve(progress: float, sharpness: float = 3.0) -> float:
7
+ """Return noise amount in range 1..0 for normalized progress 0..1."""
8
+ t = max(0.0, min(1.0, progress))
9
+ return math.exp(-sharpness * t)
10
+
11
+
12
+ def warm_gold_from_white(progress: float) -> tuple[int, int, int]:
13
+ """Interpolate from white to warm gold for progress 0..1."""
14
+ t = max(0.0, min(1.0, progress))
15
+ return 255, int(255 - 55 * t), int(255 - 175 * t)
agent/utils/crt_boot.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CRT / glitch boot sequence effect for CLI startup.
2
+
3
+ Simulates an old CRT terminal booting up: text appearing character by character
4
+ with noise artifacts, then settling into a clean display.
5
+ """
6
+
7
+ import random
8
+ import time
9
+
10
+ from rich.console import Console
11
+ from rich.text import Text
12
+ from rich.live import Live
13
+
14
+ from agent.utils.boot_timing import settle_curve
15
+
16
+
17
+ def _glitch_text(text: str, intensity: float, rng: random.Random) -> str:
18
+ """Add random glitch characters to text."""
19
+ glitch_chars = "█▓▒░┃┫┣╋╏╎─━┅┄"
20
+ result = list(text)
21
+ for i in range(len(result)):
22
+ if rng.random() < intensity:
23
+ result[i] = rng.choice(glitch_chars)
24
+ return "".join(result)
25
+
26
+
27
+ def run_boot_sequence(console: Console, boot_lines: list[tuple[str, str]]) -> None:
28
+ """Run the CRT boot sequence effect.
29
+
30
+ Args:
31
+ console: Rich console instance.
32
+ boot_lines: List of (text, rich_style) tuples to display.
33
+ """
34
+ term_height = min(console.height - 2, 40)
35
+ rng = random.Random(42)
36
+
37
+ with Live(console=console, refresh_per_second=30, transient=True) as live:
38
+ displayed_lines: list[tuple[str, str]] = []
39
+
40
+ for line_text, line_style in boot_lines:
41
+ if not line_text:
42
+ displayed_lines.append(("", ""))
43
+ continue
44
+
45
+ line_len = max(1, len(line_text))
46
+ # Type out each character
47
+ for char_idx in range(len(line_text) + 1):
48
+ result = Text()
49
+ progress = char_idx / line_len
50
+ noise = settle_curve(progress)
51
+ prev_glitch_chance = 0.01 + 0.06 * noise
52
+ prev_glitch_intensity = 0.02 + 0.12 * noise
53
+ scanline_chance = 0.005 + 0.03 * noise
54
+
55
+ # Render previously completed lines
56
+ for prev_text, prev_style in displayed_lines:
57
+ if rng.random() < prev_glitch_chance:
58
+ result.append(_glitch_text(prev_text, prev_glitch_intensity, rng), style=prev_style)
59
+ else:
60
+ result.append(prev_text, style=prev_style)
61
+ result.append("\n")
62
+
63
+ # Current line being typed
64
+ typed = line_text[:char_idx]
65
+ cursor = "█" if char_idx < len(line_text) else ""
66
+
67
+ # Noise after cursor
68
+ noise_tail = ""
69
+ if char_idx < len(line_text):
70
+ noise_len = rng.randint(0, int(1 + 5 * noise))
71
+ noise_tail = "".join(rng.choice("░▒▓") for _ in range(noise_len))
72
+
73
+ result.append(typed, style=line_style)
74
+ result.append(cursor, style="bold rgb(255,200,80)")
75
+ result.append(noise_tail, style="dim rgb(180,140,40)")
76
+ result.append("\n")
77
+
78
+ # Faint scanlines in remaining space
79
+ remaining = term_height - len(displayed_lines) - 2
80
+ for _ in range(max(0, remaining)):
81
+ if rng.random() < scanline_chance:
82
+ scan_len = rng.randint(5, 30)
83
+ result.append("─" * scan_len, style="dim rgb(180,140,40)")
84
+ result.append("\n")
85
+
86
+ live.update(result)
87
+
88
+ # Variable typing speed
89
+ if line_text[char_idx - 1:char_idx] in " .":
90
+ time.sleep(0.025)
91
+ else:
92
+ time.sleep(0.010)
93
+
94
+ displayed_lines.append((line_text, line_style))
95
+ time.sleep(0.06)
96
+
97
+ # Hold with blinking cursor
98
+ for frame in range(20):
99
+ result = Text()
100
+ for prev_text, prev_style in displayed_lines:
101
+ result.append(prev_text, style=prev_style)
102
+ result.append("\n")
103
+ if frame % 8 < 4:
104
+ result.append("█", style="rgb(255,200,80)")
105
+ live.update(result)
106
+ time.sleep(0.05)
107
+
108
+ # Print final clean frame
109
+ final = Text()
110
+ for prev_text, prev_style in displayed_lines:
111
+ final.append(prev_text, style=prev_style)
112
+ final.append("\n")
113
+ console.print(final)
agent/utils/particle_logo.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Particle coalesce effect for the HUGGING FACE AGENT logo.
2
+
3
+ Random particles swirl in from the edges, converge to form the text
4
+ "HUGGING FACE AGENT", hold briefly, then the final frame is printed.
5
+ Rendered with braille characters for high detail.
6
+
7
+ Based on Leandro's particle_coalesce.py demo.
8
+ """
9
+
10
+ import math
11
+ import random
12
+ import time
13
+
14
+ from rich.console import Console
15
+ from rich.text import Text
16
+ from rich.align import Align
17
+ from rich.live import Live
18
+
19
+ from agent.utils.braille import BrailleCanvas, text_to_pixels
20
+ from agent.utils.boot_timing import settle_curve, warm_gold_from_white
21
+
22
+
23
+ class Particle:
24
+ __slots__ = ("x", "y", "target_x", "target_y", "vx", "vy", "phase", "delay")
25
+
26
+ def __init__(self, x: float, y: float, target_x: float, target_y: float, delay: float = 0):
27
+ self.x = x
28
+ self.y = y
29
+ self.target_x = target_x
30
+ self.target_y = target_y
31
+ self.vx = 0.0
32
+ self.vy = 0.0
33
+ self.phase = random.uniform(0, math.pi * 2)
34
+ self.delay = delay
35
+
36
+ def update_converge(self, t: float, strength: float = 0.08, damping: float = 0.92):
37
+ """Move toward target with spring-like physics."""
38
+ if t < self.delay:
39
+ # Still in swirl phase
40
+ self.x += self.vx
41
+ self.y += self.vy
42
+ self.vx *= 0.99
43
+ self.vy *= 0.99
44
+ # Gentle spiral
45
+ angle = self.phase + t * 2
46
+ self.vx += math.cos(angle) * 0.3
47
+ self.vy += math.sin(angle) * 0.3
48
+ return
49
+
50
+ # Spring toward target
51
+ dx = self.target_x - self.x
52
+ dy = self.target_y - self.y
53
+ self.vx += dx * strength
54
+ self.vy += dy * strength
55
+ self.vx *= damping
56
+ self.vy *= damping
57
+ self.x += self.vx
58
+ self.y += self.vy
59
+
60
+ @property
61
+ def at_target(self) -> bool:
62
+ return abs(self.x - self.target_x) < 1.5 and abs(self.y - self.target_y) < 1.5
63
+
64
+
65
+ def run_particle_logo(console: Console, hold_seconds: float = 1.5) -> None:
66
+ """Run the particle coalesce effect."""
67
+ term_width = min(console.width, 120)
68
+ term_height = min(console.height - 4, 35)
69
+
70
+ canvas = BrailleCanvas(term_width, term_height)
71
+
72
+ # Get target positions from text
73
+ text_pixels_line1 = text_to_pixels("HUGGING FACE", scale=2)
74
+ text_pixels_line2 = text_to_pixels("AGENT", scale=2)
75
+
76
+ # Calculate dimensions for centering
77
+ def get_bounds(pixels):
78
+ if not pixels:
79
+ return 0, 0, 0, 0
80
+ xs = [p[0] for p in pixels]
81
+ ys = [p[1] for p in pixels]
82
+ return min(xs), max(xs), min(ys), max(ys)
83
+
84
+ min_x1, max_x1, min_y1, max_y1 = get_bounds(text_pixels_line1)
85
+ min_x2, max_x2, min_y2, max_y2 = get_bounds(text_pixels_line2)
86
+
87
+ w1, h1 = max_x1 - min_x1 + 1, max_y1 - min_y1 + 1
88
+ w2, h2 = max_x2 - min_x2 + 1, max_y2 - min_y2 + 1
89
+
90
+ total_h = h1 + 6 + h2 # gap between lines
91
+ start_y = (canvas.pixel_height - total_h) // 2
92
+
93
+ # Center line 1
94
+ offset_x1 = (canvas.pixel_width - w1) // 2 - min_x1
95
+ offset_y1 = start_y - min_y1
96
+ targets_1 = [(p[0] + offset_x1, p[1] + offset_y1) for p in text_pixels_line1]
97
+
98
+ # Center line 2
99
+ offset_x2 = (canvas.pixel_width - w2) // 2 - min_x2
100
+ offset_y2 = start_y + h1 + 6 - min_y2
101
+ targets_2 = [(p[0] + offset_x2, p[1] + offset_y2) for p in text_pixels_line2]
102
+
103
+ all_targets = targets_1 + targets_2
104
+
105
+ # Subsample for performance — take every Nth pixel
106
+ step = max(1, len(all_targets) // 1500)
107
+ sampled_targets = all_targets[::step]
108
+
109
+ # Create particles at random edge positions
110
+ rng = random.Random(42)
111
+ particles = []
112
+ pw, ph = canvas.pixel_width, canvas.pixel_height
113
+
114
+ for i, (tx, ty) in enumerate(sampled_targets):
115
+ # Spawn from random edge
116
+ side = rng.choice(["top", "bottom", "left", "right"])
117
+ if side == "top":
118
+ sx, sy = rng.uniform(0, pw), rng.uniform(-20, -5)
119
+ elif side == "bottom":
120
+ sx, sy = rng.uniform(0, pw), rng.uniform(ph + 5, ph + 20)
121
+ elif side == "left":
122
+ sx, sy = rng.uniform(-20, -5), rng.uniform(0, ph)
123
+ else:
124
+ sx, sy = rng.uniform(pw + 5, pw + 20), rng.uniform(0, ph)
125
+
126
+ delay = rng.uniform(0, 0.4) # staggered start
127
+ p = Particle(sx, sy, tx, ty, delay=delay)
128
+ # Initial velocity — gentle swirl
129
+ angle = math.atan2(ph / 2 - sy, pw / 2 - sx) + rng.gauss(0, 0.8)
130
+ speed = rng.uniform(1.0, 2.5)
131
+ p.vx = math.cos(angle) * speed
132
+ p.vy = math.sin(angle) * speed
133
+ particles.append(p)
134
+
135
+ # Also add some extra ambient particles that never converge
136
+ ambient = []
137
+ for _ in range(200):
138
+ ax = rng.uniform(0, pw)
139
+ ay = rng.uniform(0, ph)
140
+ ap = Particle(ax, ay, ax, ay)
141
+ ap.vx = rng.gauss(0, 1)
142
+ ap.vy = rng.gauss(0, 1)
143
+ ambient.append(ap)
144
+
145
+ # Timing: 1s converge + 2s hold = 3s total
146
+ fps = 24
147
+ converge_frames = int(fps * 0.9)
148
+ hold_frames = int(fps * hold_seconds)
149
+ total_frames = converge_frames + hold_frames
150
+
151
+ with Live(console=console, refresh_per_second=fps, transient=True) as live:
152
+ for frame in range(total_frames):
153
+ canvas.clear()
154
+ t = frame * 0.03
155
+
156
+ # Update ambient particles (always drifting)
157
+ for ap in ambient:
158
+ ap.x += ap.vx + math.sin(t + ap.phase) * 0.5
159
+ ap.y += ap.vy + math.cos(t + ap.phase * 1.3) * 0.5
160
+ # Wrap around
161
+ ap.x = ap.x % pw
162
+ ap.y = ap.y % ph
163
+
164
+ # Fade out ambient during hold phase
165
+ if frame < converge_frames:
166
+ alpha = 0.3 + 0.2 * math.sin(t * 2 + ap.phase)
167
+ else:
168
+ fade = (frame - converge_frames) / hold_frames
169
+ alpha = (0.3 + 0.2 * math.sin(t * 2 + ap.phase)) * (1 - fade)
170
+ if alpha > 0.25:
171
+ canvas.set_pixel(int(ap.x), int(ap.y))
172
+
173
+ if frame < converge_frames:
174
+ # Converge phase
175
+ progress = frame / converge_frames
176
+ noise = settle_curve(progress)
177
+ for p in particles:
178
+ p.update_converge(t, strength=0.06, damping=0.90)
179
+ canvas.set_pixel(int(p.x), int(p.y))
180
+
181
+ # Trail effect
182
+ trail_scale = 0.2 + 0.5 * noise
183
+ trail_x = int(p.x - p.vx * trail_scale)
184
+ trail_y = int(p.y - p.vy * trail_scale)
185
+ canvas.set_pixel(trail_x, trail_y)
186
+
187
+ # Color transitions from white to warm gold
188
+ r, g, b = warm_gold_from_white(progress)
189
+ else:
190
+ # Hold phase — settle into solid logo
191
+ settle_t = (frame - converge_frames) / hold_frames
192
+ for p in particles:
193
+ # Jitter decays to zero
194
+ jitter = (1 - settle_t) * 0.7
195
+ jx = p.target_x + math.sin(t * 3 + p.phase) * jitter
196
+ jy = p.target_y + math.cos(t * 3 + p.phase * 1.5) * jitter
197
+ canvas.set_pixel(int(jx), int(jy))
198
+ canvas.set_pixel(int(p.target_x), int(p.target_y))
199
+
200
+ r, g, b = 255, 200, 80
201
+
202
+ # Render with color
203
+ lines = canvas.render()
204
+ result = Text()
205
+ for line in lines:
206
+ for ch in line:
207
+ if ch == chr(0x2800):
208
+ result.append(ch)
209
+ else:
210
+ result.append(ch, style=f"rgb({r},{g},{b})")
211
+ result.append("\n")
212
+
213
+ live.update(Align.center(result))
214
+ time.sleep(1.0 / fps)
215
+
216
+ # Print final settled frame
217
+ canvas.clear()
218
+ for p in particles:
219
+ canvas.set_pixel(int(p.target_x), int(p.target_y))
220
+ final = Text()
221
+ for line in canvas.render():
222
+ for ch in line:
223
+ if ch == chr(0x2800):
224
+ final.append(ch)
225
+ else:
226
+ final.append(ch, style="rgb(255,200,80)")
227
+ final.append("\n")
228
+ console.print(Align.center(final))