import React, { useState, useCallback, useEffect } from "react"; import { KnowledgeGraph } from "@/types"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Sparkles, Shield, GitBranch, Play, Clock, CheckCircle, AlertCircle, Trash2, } from "lucide-react"; import { useNotification } from "@/context/NotificationContext"; import { EnrichResults } from "../traces/EnrichResults"; import { PerturbResults } from "../traces/PerturbResults"; import CausalResults from "../traces/CausalResults"; const API_BASE = "/api"; async function fetchApi( endpoint: string, options?: RequestInit ): Promise { const response = await fetch(`${API_BASE}${endpoint}`, { headers: { "Content-Type": "application/json", ...options?.headers, }, ...options, }); if (!response.ok) { throw new Error(`API Error: ${response.statusText}`); } return response.json(); } interface AdvancedProcessingViewProps { knowledgeGraph: KnowledgeGraph; onBack?: () => void; } interface StageState { status: "idle" | "running" | "completed" | "error"; progress: number; taskId?: string; error?: string; } interface PipelineStageConfig { id: string; name: string; description: string; icon: React.ComponentType<{ className?: string }>; apiEndpoint: (kgId: string) => string; benefit: string; } const PIPELINE_STAGES: PipelineStageConfig[] = [ { id: "enrich", name: "Prompt Reconstruction", description: "Reconstruct original prompts from conversation patterns", icon: Sparkles, apiEndpoint: (kgId) => `/knowledge-graphs/${kgId}/enrich`, benefit: "Understand conversation context and intent", }, { id: "perturb", name: "Perturbation Testing", description: "Test graph robustness through systematic variations", icon: Shield, apiEndpoint: (kgId) => `/knowledge-graphs/${kgId}/perturb`, benefit: "Validate relationship reliability and stability", }, { id: "causal", name: "Causal Analysis", description: "Analyze causal relationships and dependencies", icon: GitBranch, apiEndpoint: (kgId) => `/knowledge-graphs/${kgId}/analyze`, benefit: "Discover cause-and-effect patterns", }, ]; export const AdvancedProcessingView: React.FC = ({ knowledgeGraph: initialKnowledgeGraph, onBack: _onBack, }) => { const { showNotification } = useNotification(); const [knowledgeGraph, setKnowledgeGraph] = useState(initialKnowledgeGraph); const [stageStates, setStageStates] = useState>({ enrich: { status: knowledgeGraph.is_enriched ? "completed" : "idle", progress: knowledgeGraph.is_enriched ? 100 : 0, }, perturb: { status: knowledgeGraph.is_perturbed ? "completed" : "idle", progress: knowledgeGraph.is_perturbed ? 100 : 0, }, causal: { status: knowledgeGraph.is_analyzed ? "completed" : "idle", progress: knowledgeGraph.is_analyzed ? 100 : 0, }, }); const [stageResults, setStageResults] = useState>({}); const [loadingResults, setLoadingResults] = useState>( {} ); const [knowledgeGraphData, setKnowledgeGraphData] = useState(null); const [progressTimers, setProgressTimers] = useState< Record >({}); const anyStageRunning = Object.values(stageStates).some( (state) => state.status === "running" ); // Estimated durations for each stage (in seconds) const STAGE_DURATIONS = { enrich: 45, // Prompt Reconstruction typically takes ~45 seconds perturb: 90, // Perturbation Testing takes ~90 seconds causal: 60, // Causal Analysis takes ~60 seconds }; // Start estimated progress animation const startEstimatedProgress = useCallback( (stageId: string) => { // Clear any existing timer if (progressTimers[stageId]) { clearInterval(progressTimers[stageId]); } const duration = STAGE_DURATIONS[stageId as keyof typeof STAGE_DURATIONS] || 60; const interval = 1000; // Update every second const increment = 100 / ((duration * 1000) / interval); // Progress per interval let currentProgress = 0; const timer = setInterval(() => { // Add some randomness to make progress feel more realistic const randomVariation = (Math.random() - 0.5) * 0.5; // ±0.25% variation currentProgress += increment + randomVariation; // Cap at 95% to avoid reaching 100% before completion if (currentProgress >= 95) { currentProgress = 95; } // Ensure progress doesn't go backwards if (currentProgress < 0) { currentProgress = 0; } setStageStates((prev) => ({ ...prev, [stageId]: { ...prev[stageId], progress: Math.round(currentProgress), } as StageState, })); }, interval); setProgressTimers((prev) => ({ ...prev, [stageId]: timer, })); }, [progressTimers] ); // Stop progress animation const stopEstimatedProgress = useCallback( (stageId: string) => { if (progressTimers[stageId]) { clearInterval(progressTimers[stageId]); setProgressTimers((prev) => { const updated = { ...prev }; delete updated[stageId]; return updated; }); } }, [progressTimers] ); // Cleanup timers on unmount useEffect(() => { return () => { Object.values(progressTimers).forEach((timer) => clearInterval(timer)); }; }, [progressTimers]); // Function to refresh knowledge graph data const refreshKnowledgeGraph = useCallback(async () => { try { console.log("🔄 Refreshing knowledge graph status..."); const response = await fetchApi( `/knowledge-graphs/${knowledgeGraph.kg_id}/status` ); console.log("📡 KG Status API Response:", response); setKnowledgeGraph((prev) => { console.log("📝 Previous KG state:", prev); const updated = { ...prev, is_enriched: response.is_enriched, is_perturbed: response.is_perturbed, is_analyzed: response.is_analyzed, status: response.status, }; console.log("✅ Updated KG state:", updated); return updated; }); } catch (error) { console.error("❌ Error refreshing knowledge graph status:", error); } }, [knowledgeGraph.kg_id]); const pollTaskStatus = useCallback( async (stageId: string, taskId: string) => { const maxAttempts = 60; // 5 minutes at 5-second intervals let attempts = 0; const poll = async (): Promise => { try { const response = await fetchApi(`/tasks/${taskId}/status`); const { status } = response; // Note: We're using estimated progress instead of backend progress // The progress is managed by the startEstimatedProgress function if (status === "COMPLETED") { console.log(`🎉 Stage ${stageId} completed! Updating state...`); // Stop estimated progress animation and set to 100% stopEstimatedProgress(stageId); setStageStates((prev) => ({ ...prev, [stageId]: { ...prev[stageId], status: "completed", progress: 100, } as StageState, })); showNotification({ title: `${ PIPELINE_STAGES.find((s) => s.id === stageId)?.name } completed successfully`, message: "The processing stage has been completed successfully.", type: "success", }); // Refresh the knowledge graph data to get updated status flags console.log( `🔄 Calling refreshKnowledgeGraph for stage ${stageId}...` ); await refreshKnowledgeGraph(); console.log(`✅ Finished refreshing KG for stage ${stageId}`); return; } if (status === "FAILED") { // Stop estimated progress animation on failure stopEstimatedProgress(stageId); setStageStates((prev) => ({ ...prev, [stageId]: { ...prev[stageId], status: "error", error: response.error || "Task failed", progress: 0, } as StageState, })); showNotification({ title: `${ PIPELINE_STAGES.find((s) => s.id === stageId)?.name } failed`, message: "The processing stage encountered an error.", type: "error", }); return; } attempts++; if (attempts < maxAttempts && status === "RUNNING") { setTimeout(poll, 5000); } } catch (error) { console.error("Error polling task status:", error); attempts++; if (attempts < maxAttempts) { setTimeout(poll, 5000); } } }; poll(); }, [showNotification, refreshKnowledgeGraph] ); const runStage = async (stage: PipelineStageConfig) => { try { setStageStates((prev) => ({ ...prev, [stage.id]: { ...prev[stage.id], status: "running", progress: 0, error: undefined, }, })); // Start estimated progress animation startEstimatedProgress(stage.id); const response = await fetchApi( stage.apiEndpoint(knowledgeGraph.kg_id), { method: "POST", } ); const { task_id } = response; if (task_id) { setStageStates((prev) => ({ ...prev, [stage.id]: { ...prev[stage.id], taskId: task_id, } as StageState, })); pollTaskStatus(stage.id, task_id); } } catch (error: any) { console.error(`Error running ${stage.name}:`, error); // Stop progress animation on error stopEstimatedProgress(stage.id); setStageStates((prev) => ({ ...prev, [stage.id]: { ...prev[stage.id], status: "error", error: error.message, progress: 0, } as StageState, })); showNotification({ title: `Failed to start ${stage.name}`, message: "Unable to start the processing stage. Please try again.", type: "error", }); } }; const clearStage = async (stage: PipelineStageConfig) => { try { const response = await fetchApi( `/knowledge-graphs/${knowledgeGraph.kg_id}/stage-results/${stage.id}`, { method: "DELETE", } ); const { cleared_stages, new_status } = response; // Update knowledge graph status setKnowledgeGraph((prev) => ({ ...prev, status: new_status, is_enriched: new_status === "enriched" || new_status === "perturbed" || new_status === "analyzed", is_perturbed: new_status === "perturbed" || new_status === "analyzed", is_analyzed: new_status === "analyzed", })); // Clear results and reset stage states for cleared stages const updatedStageStates: Record = {}; const updatedStageResults: Record = {}; cleared_stages.forEach((clearedStage: string) => { updatedStageStates[clearedStage] = { status: "idle", progress: 0, error: undefined, }; updatedStageResults[clearedStage] = undefined; }); setStageStates((prev) => ({ ...prev, ...updatedStageStates })); setStageResults((prev) => { const newResults = { ...prev }; cleared_stages.forEach((clearedStage: string) => { delete newResults[clearedStage]; }); return newResults; }); showNotification({ title: `${stage.name} cleared successfully`, message: `Cleared ${cleared_stages.join(", ")} stage(s) as requested.`, type: "success", }); } catch (error: any) { console.error(`Error clearing ${stage.name}:`, error); showNotification({ title: `Failed to clear ${stage.name}`, message: "Unable to clear the stage results. Please try again.", type: "error", }); } }; const fetchStageResults = useCallback( async (stageId: string) => { if (loadingResults[stageId]) return; setLoadingResults((prev) => ({ ...prev, [stageId]: true })); try { const response = await fetchApi( `/knowledge-graphs/${knowledgeGraph.kg_id}/stage-results/${stageId}` ); setStageResults((prev) => ({ ...prev, [stageId]: response })); } catch (error) { console.error("Error fetching stage results:", error); showNotification({ title: "Failed to fetch results", message: "Unable to retrieve the results for this stage.", type: "error", }); } finally { setLoadingResults((prev) => ({ ...prev, [stageId]: false })); } }, [loadingResults, knowledgeGraph.kg_id, showNotification] ); // Fetch knowledge graph data for component name mapping useEffect(() => { const fetchKnowledgeGraphData = async () => { try { const response = await fetchApi<{ entities: Array<{ id: string; name: string; type: string }>; relations: Array<{ id: string; type: string; source: string; target: string; }>; }>(`/knowledge-graphs/${knowledgeGraph.kg_id}`); setKnowledgeGraphData(response); } catch (error) { console.error("Error fetching knowledge graph data:", error); // Don't show notification for this as it's not critical - causal analysis will still work with IDs } }; fetchKnowledgeGraphData(); }, [knowledgeGraph.kg_id]); // Auto-fetch results when stage completes useEffect(() => { console.log("🔍 Auto-fetch effect triggered. StageStates:", stageStates); PIPELINE_STAGES.forEach((stage) => { const state = stageStates[stage.id]; console.log( `📊 Stage ${stage.id} state:`, state, `Results exist:`, !!stageResults[stage.id], `Loading:`, !!loadingResults[stage.id] ); if ( state?.status === "completed" && !stageResults[stage.id] && !loadingResults[stage.id] ) { console.log(`🚀 Fetching results for completed stage: ${stage.id}`); fetchStageResults(stage.id); } }); }, [stageStates, stageResults, loadingResults, fetchStageResults]); const renderStageContent = (stage: PipelineStageConfig) => { const state = stageStates[stage.id] || { status: "idle" as const, progress: 0, }; return (
{/* Progress */} {state.status === "running" && (
Progress {Math.round(state.progress)}%
)} {/* Error Display */} {state.error && (

{state.error}

)} {/* Results Section */} {state.status === "completed" && stageResults[stage.id] ? (
{/* Results Header with Actions */}

{stage.name} Completed

Results are ready to view

{/* Action Buttons */}
{/* Results Content */}
{stage.id === "enrich" && ( )} {stage.id === "perturb" && ( )} {stage.id === "causal" && ( )}
) : ( /* Actions for non-completed stages */
{state.status === "idle" && ( <>

Ready to Run {stage.name}

{stage.description}

)} {state.status === "completed" && !stageResults[stage.id] && ( <>

No Results Available

The stage completed but no results were found. Try running again.

)} {state.status === "error" && ( <>

Error Occurred

{state.error || "An error occurred during processing"}

)} {state.status === "running" && ( <>

Running {stage.name}

Processing in progress... {Math.round(state.progress)}%

)} {loadingResults[stage.id] && ( <>

Loading Results

Fetching {stage.name.toLowerCase()} results...

)}
)}
); }; return (
{PIPELINE_STAGES.map((stage) => { const state = stageStates[stage.id]; const StageIcon = stage.icon; return ( {stage.name} {state?.status === "completed" && ( )} {state?.status === "running" && ( )} {state?.status === "error" && ( )}

{stage.description}

); })}
{PIPELINE_STAGES.map((stage) => (
{renderStageContent(stage)}
))}
); };