feat(FN-1650): merge fusion/fn-1650

This commit is contained in:
gsxdsm
2026-04-13 15:35:08 -07:00
parent 668a6e3894
commit ebbc463dc8
7 changed files with 557 additions and 15 deletions

View File

@@ -1,13 +1,23 @@
import { useState, useEffect, useRef } from "react";
/** Log entry from an agent's execution stream */
/**
* Log entry from an agent's execution stream.
*
* Note: SSE payloads from `/api/tasks/:id/logs/stream` contain `text` field
* (matching `AgentLogEntry` from `@fusion/core`). This interface normalizes
* to `text` for rendering. Legacy payloads with `content` are also supported
* for backward compatibility.
*/
export interface TranscriptEntry {
type: string;
content: string;
/** Canonical text content — matches `AgentLogEntry.text` */
text: string;
timestamp?: string;
/** Legacy field — normalized to `text` if present */
content?: string;
}
export function useLiveTranscript(taskId: string | undefined) {
export function useLiveTranscript(taskId: string | undefined, projectId?: string) {
const [entries, setEntries] = useState<TranscriptEntry[]>([]);
const [isConnected, setIsConnected] = useState(false);
const esRef = useRef<EventSource | null>(null);
@@ -19,14 +29,28 @@ export function useLiveTranscript(taskId: string | undefined) {
return;
}
const es = new EventSource(`/api/tasks/${encodeURIComponent(taskId)}/logs/stream`);
// Build stream URL with optional projectId for multi-project support
let url = `/api/tasks/${encodeURIComponent(taskId)}/logs/stream`;
if (projectId) {
url += `?projectId=${encodeURIComponent(projectId)}`;
}
const es = new EventSource(url);
esRef.current = es;
es.addEventListener("agent:log", (event) => {
try {
const entry = JSON.parse(event.data) as TranscriptEntry;
const raw = JSON.parse(event.data) as Partial<TranscriptEntry>;
// Normalize: canonical `text` field, with legacy `content` fallback
// This ensures both current SSE payloads and any legacy payloads render correctly
const entry: TranscriptEntry = {
type: raw.type ?? "text",
text: raw.text ?? raw.content ?? "",
timestamp: raw.timestamp,
content: raw.content,
};
setEntries(prev => [entry, ...prev]);
} catch { /* skip */ }
} catch { /* skip malformed events */ }
});
es.addEventListener("open", () => setIsConnected(true));
@@ -37,7 +61,7 @@ export function useLiveTranscript(taskId: string | undefined) {
esRef.current = null;
setIsConnected(false);
};
}, [taskId]);
}, [taskId, projectId]);
return { entries, isConnected };
}