1// Favicon service for dynamic status indication
2// Modifies the server-injected favicon to show a colored dot when the agent state changes
3
4type FaviconStatus = "working" | "ready";
5
6let currentStatus: FaviconStatus = "ready";
7let originalSVG: string | null = null;
8
9// Get the existing favicon link (injected by server)
10function getFaviconLink(): HTMLLinkElement | null {
11 return document.querySelector('link[rel="icon"]');
12}
13
14// Extract and decode the SVG from the data URI
15function extractSVGFromDataURI(dataURI: string): string | null {
16 if (!dataURI.startsWith("data:image/svg+xml,")) {
17 return null;
18 }
19 try {
20 return decodeURIComponent(dataURI.substring("data:image/svg+xml,".length));
21 } catch {
22 return null;
23 }
24}
25
26// Add a status dot to the SVG
27function addStatusDot(svg: string, status: FaviconStatus): string {
28 // Remove the closing </svg> tag
29 const closingTagIndex = svg.lastIndexOf("</svg>");
30 if (closingTagIndex === -1) {
31 return svg;
32 }
33
34 const svgWithoutClose = svg.substring(0, closingTagIndex);
35
36 // Add the status dot in the bottom-right corner
37 // New viewBox is 400x400, so position dot near bottom-right at ~350,350
38 const dotColor = status === "working" ? "#f59e0b" : "#22c55e";
39 const statusDot = `
40 <circle cx="340" cy="340" r="50" fill="white"/>
41 <circle cx="340" cy="340" r="40" fill="${dotColor}"/>
42`;
43
44 return svgWithoutClose + statusDot + "</svg>";
45}
46
47// Update the favicon to reflect the current status
48export function setFaviconStatus(status: FaviconStatus): void {
49 if (status === currentStatus && originalSVG !== null) {
50 return;
51 }
52
53 const link = getFaviconLink();
54 if (!link) {
55 return;
56 }
57
58 // Capture the original SVG on first call
59 if (originalSVG === null) {
60 const extracted = extractSVGFromDataURI(link.href);
61 if (extracted) {
62 originalSVG = extracted;
63 } else {
64 // If we can't extract SVG, give up
65 return;
66 }
67 }
68
69 currentStatus = status;
70
71 // Generate new SVG with status dot
72 const newSVG = addStatusDot(originalSVG, status);
73 const newDataURI = "data:image/svg+xml," + encodeURIComponent(newSVG);
74
75 // Update the favicon
76 link.href = newDataURI;
77}
78
79// Initialize the favicon service (call on app start)
80export function initializeFavicon(): void {
81 // Wait a tick for the server-injected favicon to be present
82 setTimeout(() => {
83 setFaviconStatus("ready");
84 }, 0);
85}