@yoyo-org/progressive-json
Version:
Stream and render JSON data as it arrives - perfect for AI responses, large datasets, and real-time updates
170 lines (169 loc) • 8.4 kB
JavaScript
/**
* Domain-specific abstractions for Talmud text streaming
* This module provides high-level hooks and utilities tailored for
* Talmud analysis streaming use cases
*/
import { useCallback, useMemo } from "react";
import { useProgressiveJson } from "../useProgressiveJson";
/**
* High-level hook for Talmud daf analysis streaming
* Abstracts away the complexity of progressive JSON and provides
* a clean, declarative API for Talmud-specific streaming
*/
export function useTalmudStream(options = {}) {
const { baseUrl = "http://proxyman.debug:8000", onAnalysisComplete } = options;
// Progressive JSON stream with Talmud-specific transforms
const { store, rawStore, isStreaming, streamError, startFetching, stop, updateOptions } = useProgressiveJson({
url: "", // Will be set dynamically
enabled: false, // Will be enabled when analysis starts
// Transform: enhance sugyot with streaming metadata
transform: useCallback((data) => {
if (!data.sugyot || !data.progress)
return data;
const enhancedSugyot = data.sugyot.map((sugya, index) => {
var _a;
const isCurrentSugya = data.progress.current_sugya === index + 1;
const isGeneratingBiur = data.progress.current_step === "generating_biur";
return {
...sugya,
isStreaming: isCurrentSugya && isGeneratingBiur,
streamingBiur: isCurrentSugya && isGeneratingBiur ? sugya.biur : "",
completeBiur: !isCurrentSugya || !isGeneratingBiur ? sugya.biur : "",
biurProgress: isCurrentSugya && isGeneratingBiur ?
(((_a = sugya.biur) === null || _a === void 0 ? void 0 : _a.length) || 0) / 500 * 100 : // Rough progress estimate
100
};
});
return {
...data,
sugyot: enhancedSugyot
};
}, []),
// Select: focus on the most relevant data for UI
select: useCallback((data) => ({
daf: data.daf,
masechet: data.masechet,
sugyot: data.sugyot,
progress: data.progress,
metadata: data.metadata
}), []),
// Compare: smart change detection
compare: useCallback((prev, next) => {
var _a, _b, _c, _d, _e, _f;
if (!prev || !next)
return false;
// Check key fields that matter for UI updates
return (((_a = prev.progress) === null || _a === void 0 ? void 0 : _a.current_sugya) === ((_b = next.progress) === null || _b === void 0 ? void 0 : _b.current_sugya) &&
((_c = prev.progress) === null || _c === void 0 ? void 0 : _c.current_step) === ((_d = next.progress) === null || _d === void 0 ? void 0 : _d.current_step) &&
((_e = prev.sugyot) === null || _e === void 0 ? void 0 : _e.length) === ((_f = next.sugyot) === null || _f === void 0 ? void 0 : _f.length) &&
prev.analysis_raw === next.analysis_raw);
}, []),
// Lifecycle hooks
onStreamStart: useCallback(() => {
// Could trigger UI loading states
}, []),
onStreamEnd: useCallback((data) => {
onAnalysisComplete === null || onAnalysisComplete === void 0 ? void 0 : onAnalysisComplete(data);
}, [onAnalysisComplete]),
onStreamError: useCallback((error) => {
console.error("Talmud stream error:", error);
}, [])
});
// Compute derived streaming state
const streamingState = useMemo(() => {
var _a, _b, _c, _d, _e, _f, _g, _h;
if (!store) {
return {
sugiyot: [],
isAnalyzing: false,
analysisProgress: 0,
totalSugiyot: 0,
currentStreamingSugya: null,
error: null,
currentStep: "",
statusMessage: "",
isStreamingRaw: isStreaming,
rawContent: (rawStore === null || rawStore === void 0 ? void 0 : rawStore.analysis_raw) || ""
};
}
const analysisProgress = ((_a = store.progress) === null || _a === void 0 ? void 0 : _a.total_sugiyot) > 0 ?
(store.progress.current_sugya / store.progress.total_sugiyot) * 100 : 0;
const currentStreamingSugya = ((_b = store.progress) === null || _b === void 0 ? void 0 : _b.current_step) === "generating_biur" && store.progress.current_sugya > 0 ?
store.progress.current_sugya - 1 : null;
return {
sugiyot: store.sugyot || [],
isAnalyzing: ((_c = store.progress) === null || _c === void 0 ? void 0 : _c.status) !== "completed" && ((_d = store.progress) === null || _d === void 0 ? void 0 : _d.status) !== "error",
analysisProgress,
totalSugiyot: ((_e = store.progress) === null || _e === void 0 ? void 0 : _e.total_sugiyot) || 0,
currentStreamingSugya,
error: ((_f = store.progress) === null || _f === void 0 ? void 0 : _f.status) === "error" ? store.progress.message : (streamError === null || streamError === void 0 ? void 0 : streamError.message) || null,
currentStep: ((_g = store.progress) === null || _g === void 0 ? void 0 : _g.current_step) || "",
statusMessage: ((_h = store.progress) === null || _h === void 0 ? void 0 : _h.message) || "",
isStreamingRaw: isStreaming,
rawContent: (rawStore === null || rawStore === void 0 ? void 0 : rawStore.analysis_raw) || ""
};
}, [store, rawStore, isStreaming, streamError]);
// High-level API methods
const startAnalysis = useCallback((dafNumber, masechet = "berakhot", includeBiur = true, biurLevel = 2) => {
const url = `${baseUrl}/stream/analyze-daf?daf_number=${dafNumber}&masechet=${masechet}&include_biur=${includeBiur}&biur_level=${biurLevel}`;
updateOptions({
url,
enabled: true
});
}, [baseUrl, updateOptions]);
const stopAnalysis = useCallback(() => {
stop();
updateOptions({ enabled: false });
}, [stop, updateOptions]);
const restartAnalysis = useCallback(() => {
stop();
setTimeout(() => startFetching(), 100); // Brief delay to ensure cleanup
}, [stop, startFetching]);
return {
// Streaming state
...streamingState,
// Raw data access
rawData: rawStore,
processedData: store,
// Control methods
startAnalysis,
stopAnalysis,
restartAnalysis,
// Advanced control
updateStreamOptions: updateOptions
};
}
/**
* Utility hook for streaming individual biur generation
* Provides a simpler API for biur-specific streaming
*/
export function useBiurStream(options = {}) {
const { baseUrl = "http://proxyman.debug:8000" } = options;
const { store, isStreaming, streamError, updateOptions } = useProgressiveJson({
url: "",
enabled: false,
transform: useCallback((data) => data, []),
compare: useCallback((prev, next) => {
return (prev === null || prev === void 0 ? void 0 : prev.biur) === (next === null || next === void 0 ? void 0 : next.biur) && (prev === null || prev === void 0 ? void 0 : prev.complete) === (next === null || next === void 0 ? void 0 : next.complete);
}, [])
});
const generateBiur = useCallback((dafNumber, sugyaIndex, level, sugyaTitle, sugyaContent, masechet = "berakhot") => {
const params = new URLSearchParams({
daf_number: dafNumber,
sugya_index: sugyaIndex.toString(),
level: level.toString(),
sugya_title: sugyaTitle,
sugya_content: sugyaContent,
masechet
});
const url = `${baseUrl}/stream/generate-biur?${params}`;
updateOptions({ url, enabled: true });
}, [baseUrl, updateOptions]);
return {
biur: (store === null || store === void 0 ? void 0 : store.biur) || "",
isComplete: (store === null || store === void 0 ? void 0 : store.complete) || false,
isStreaming,
error: (streamError === null || streamError === void 0 ? void 0 : streamError.message) || null,
generateBiur
};
}