1.7 KB81 lines
Blame
1"use client";
2
3import { useEffect, useRef } from "react";
4
5export type CanopyEventType =
6 | "run:created"
7 | "run:started"
8 | "run:completed"
9 | "run:cancelled"
10 | "step:started"
11 | "step:completed"
12 | "step:skipped"
13 | "log:append";
14
15export interface CanopyEvent {
16 type: CanopyEventType;
17 runId: number;
18 repoId: number;
19 stepId?: number;
20 stepIndex?: number;
21 status?: string;
22 run?: Record<string, unknown>;
23 step?: Record<string, unknown>;
24 log?: { stream: string; content: string; created_at: string };
25 ts: string;
26}
27
28interface UseCanopyEventsOptions {
29 scope: "run" | "repo" | "global";
30 runId?: number;
31 owner?: string;
32 repo?: string;
33 onEvent: (event: CanopyEvent) => void;
34 enabled?: boolean;
35}
36
37export function useCanopyEvents({
38 scope,
39 runId,
40 owner,
41 repo,
42 onEvent,
43 enabled = true,
44}: UseCanopyEventsOptions) {
45 const handlerRef = useRef(onEvent);
46 handlerRef.current = onEvent;
47
48 useEffect(() => {
49 if (!enabled) return;
50
51 const params = new URLSearchParams({ scope });
52 if (runId != null) params.set("runId", String(runId));
53 if (owner) params.set("owner", owner);
54 if (repo) params.set("repo", repo);
55
56 const es = new EventSource(`/api/canopy/events?${params}`);
57
58 const eventTypes: CanopyEventType[] = [
59 "run:created",
60 "run:started",
61 "run:completed",
62 "run:cancelled",
63 "step:started",
64 "step:completed",
65 "step:skipped",
66 "log:append",
67 ];
68
69 for (const type of eventTypes) {
70 es.addEventListener(type, (e: MessageEvent) => {
71 try {
72 const data = JSON.parse(e.data) as CanopyEvent;
73 handlerRef.current(data);
74 } catch {}
75 });
76 }
77
78 return () => es.close();
79 }, [scope, runId, owner, repo, enabled]);
80}
81