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