Implement RPC contract validation baseline
This commit is contained in:
173
apps/web/src/hooks/useServerEvents.ts
Normal file
173
apps/web/src/hooks/useServerEvents.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* React hook for subscribing to server events
|
||||
*/
|
||||
|
||||
import { useEffect, useCallback, useRef } from "react";
|
||||
import type { ServerEvent } from "@mosaiciq/contracts/rpc";
|
||||
|
||||
type EventListener = (event: ServerEvent) => void;
|
||||
type EventType = ServerEvent["type"];
|
||||
|
||||
export function useServerEvents(
|
||||
eventType: EventType,
|
||||
callback: (data: ServerEvent) => void,
|
||||
deps: React.DependencyList = []
|
||||
) {
|
||||
const callbackRef = useRef(callback);
|
||||
|
||||
// Keep callback ref updated
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if mosaic API is available
|
||||
if (!window.mosaic) {
|
||||
console.warn("[useServerEvents] mosaic API not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if on method exists
|
||||
if (typeof window.mosaic.on !== "function") {
|
||||
console.warn("[useServerEvents] mosaic.on method not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe to events
|
||||
const unsubscribe = window.mosaic.on(eventType, (data: unknown) => {
|
||||
const event = data as ServerEvent;
|
||||
callbackRef.current(event);
|
||||
});
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [eventType, ...deps]);
|
||||
}
|
||||
|
||||
export function useMultiServerEvents(
|
||||
eventTypes: EventType[],
|
||||
callback: (event: ServerEvent) => void,
|
||||
deps: React.DependencyList = []
|
||||
) {
|
||||
const callbackRef = useRef(callback);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.mosaic || typeof window.mosaic.on !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsubscribes: Array<() => void> = [];
|
||||
|
||||
for (const eventType of eventTypes) {
|
||||
const unsubscribe = window.mosaic.on(eventType, (data: unknown) => {
|
||||
const event = data as ServerEvent;
|
||||
callbackRef.current(event);
|
||||
});
|
||||
unsubscribes.push(unsubscribe);
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const unsubscribe of unsubscribes) {
|
||||
unsubscribe();
|
||||
}
|
||||
};
|
||||
}, [eventTypes, ...deps]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for agent-specific events
|
||||
*/
|
||||
export function useAgentEvents(
|
||||
agentId: string | null,
|
||||
onProgress?: (progress: number, action: string) => void,
|
||||
onCompleted?: (output: unknown) => void,
|
||||
onFailed?: (error: string) => void,
|
||||
onStreaming?: (chunk: string) => void
|
||||
) {
|
||||
const handleEvent = useCallback((event: ServerEvent) => {
|
||||
if (!agentId) return;
|
||||
|
||||
if (event.type === "agent.progress" && event.data.agentId === agentId) {
|
||||
onProgress?.(event.data.progress, event.data.action);
|
||||
} else if (event.type === "agent.completed" && event.data.agentId === agentId) {
|
||||
onCompleted?.(event.data.output);
|
||||
} else if (event.type === "agent.failed" && event.data.agentId === agentId) {
|
||||
onFailed?.(event.data.error);
|
||||
} else if (event.type === "agent.streaming" && event.data.agentId === agentId) {
|
||||
onStreaming?.(event.data.chunk);
|
||||
}
|
||||
}, [agentId, onProgress, onCompleted, onFailed, onStreaming]);
|
||||
|
||||
useMultiServerEvents(
|
||||
["agent.progress", "agent.completed", "agent.failed", "agent.streaming"],
|
||||
handleEvent,
|
||||
[agentId]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for all agent events (for agent list updates)
|
||||
*/
|
||||
export function useAllAgentEvents(
|
||||
onAgentUpdate?: (agentId: string, update: { status?: string; progress?: number; action?: string }) => void
|
||||
) {
|
||||
const handleEvent = useCallback((event: ServerEvent) => {
|
||||
if (event.type === "agent.progress") {
|
||||
onAgentUpdate?.(event.data.agentId, {
|
||||
progress: event.data.progress,
|
||||
action: event.data.action,
|
||||
status: "running"
|
||||
});
|
||||
} else if (event.type === "agent.completed") {
|
||||
onAgentUpdate?.(event.data.agentId, {
|
||||
status: "completed",
|
||||
progress: 100
|
||||
});
|
||||
} else if (event.type === "agent.failed") {
|
||||
onAgentUpdate?.(event.data.agentId, {
|
||||
status: "failed"
|
||||
});
|
||||
} else if (event.type === "agent.started") {
|
||||
onAgentUpdate?.(event.data.agentId, {
|
||||
status: "running",
|
||||
progress: 0
|
||||
});
|
||||
}
|
||||
}, [onAgentUpdate]);
|
||||
|
||||
useMultiServerEvents(
|
||||
["agent.progress", "agent.completed", "agent.failed", "agent.started"],
|
||||
handleEvent,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for validation events
|
||||
*/
|
||||
export function useValidationEvents(
|
||||
companyId: string | null,
|
||||
onValidationUpdated?: (data: {
|
||||
sectionId?: string;
|
||||
validationState: "verified" | "flagged" | "unverified" | "failed";
|
||||
agentId: string;
|
||||
notes?: string;
|
||||
}) => void
|
||||
) {
|
||||
useServerEvents("validation.updated", (event) => {
|
||||
if (companyId && event.data.companyId === companyId) {
|
||||
onValidationUpdated?.(event.data as {
|
||||
sectionId?: string;
|
||||
validationState: "verified" | "flagged" | "unverified" | "failed";
|
||||
agentId: string;
|
||||
notes?: string;
|
||||
});
|
||||
}
|
||||
}, [companyId, onValidationUpdated]);
|
||||
}
|
||||
Reference in New Issue
Block a user