'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { useGraphData } from '@/hooks/useGraphData'; import { useGraphStore } from '@/store/graph'; import { QueryPanel, QueryMode } from '@/components/query/QueryPanel'; import { AnswerCard } from '@/components/query/AnswerCard'; import { PathView, Hop } from '@/components/query/PathView'; import { SourceToggle } from '@/components/query/SourceToggle'; import { QueryHistory } from '@/components/query/QueryHistory'; import { EntityPanel } from '@/components/entities/EntityPanel'; import { GraphVisualization } from '@/components/graph/GraphVisualization'; import { Upload, Sparkles, PlusCircle } from 'lucide-react'; import Link from 'next/link'; import api from '@/lib/axios'; import { useDocumentsStore } from '@/store/documents'; import { useHistoryStore, HistoryItem } from '@/store/history'; interface QueryResult { answer: string; confidence?: number; method?: string; citations?: { doc: string; page?: string }[]; entities?: string[]; paths?: string[]; hops?: Hop[]; context?: string; conclusion?: string; } const LAST_SESSION_KEY = 'graphrag-last-session'; function loadLastSession(): { query: string; mode: QueryMode; result: QueryResult | null } | null { try { const raw = localStorage.getItem(LAST_SESSION_KEY); return raw ? JSON.parse(raw) : null; } catch { return null; } } function saveLastSession(query: string, mode: QueryMode, result: QueryResult | null) { try { localStorage.setItem(LAST_SESSION_KEY, JSON.stringify({ query, mode, result })); } catch {} } function clearLastSession() { try { localStorage.removeItem(LAST_SESSION_KEY); } catch {} } export function MainQueryView() { useGraphData(); const setHighlighted = useGraphStore((s) => s.setHighlighted); const selectEntity = useGraphStore((s) => s.selectEntity); const data = useGraphStore((s) => s.data); const selectedIds = useDocumentsStore((s) => s.selectedDocumentIds); const addHistoryItem = useHistoryStore((s) => s.addItem); const [query, setQuery] = useState(''); const [mode, setMode] = useState('hybrid'); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const scrollRef = useRef(null); const answerRef = useRef(null); useEffect(() => { const session = loadLastSession(); if (session) { setQuery(session.query); setMode(session.mode); setResult(session.result); if (session.result?.entities) { setHighlighted(session.result.entities, session.result.paths || []); } } }, []); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (query || result) { saveLastSession(query, mode, result); } }, [query, mode, result]); useEffect(() => { function handleAskEntity(e: Event) { const name = (e as CustomEvent).detail; if (name) setQuery(name); } window.addEventListener('graphrag:ask-entity', handleAskEntity); return () => window.removeEventListener('graphrag:ask-entity', handleAskEntity); }, []); const scrollToAnswer = useCallback(() => { requestAnimationFrame(() => { if (answerRef.current) { answerRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }, []); const runQuery = useCallback(async (overrideQuery?: string) => { const q = overrideQuery ?? query; if (!q.trim()) return; setLoading(true); setResult(null); try { const { data: res } = await api.post('/query/', { query: q, mode, document_ids: selectedIds.length > 0 ? selectedIds : undefined, }); const mapped: QueryResult = { answer: res.answer, confidence: res.confidence, method: res.strategy || res.method || mode, citations: res.sources?.map((s: string) => ({ doc: s })) || res.citations, entities: res.highlighted_entities, paths: res.paths, hops: res.hops, context: res.context, conclusion: res.conclusion, }; setResult(mapped); setHighlighted(mapped.entities || [], mapped.paths || []); addHistoryItem({ id: Date.now().toString(), query_text: q, retrieval_mode: (res.strategy || mode).toUpperCase(), answer_text: res.answer || '', answer_preview: (res.answer || '').substring(0, 200), response_time: res.response_time ?? 0, created_at: new Date().toISOString(), }); scrollToAnswer(); } catch (err: any) { const backendError = err.response?.data?.error; let errorMsg = backendError || 'Failed to process query. Please check your API keys and Neo4j connection.'; if (err.response?.status === 401) { errorMsg = 'Your session has expired. Please log in again.'; } setResult({ answer: `**Error:** ${errorMsg}`, method: mode }); scrollToAnswer(); } finally { setLoading(false); } }, [query, mode, selectedIds, setHighlighted, addHistoryItem, scrollToAnswer]); const handleNewChat = useCallback(() => { setQuery(''); setResult(null); setHighlighted([], []); selectEntity(null); clearLastSession(); }, [setHighlighted, selectEntity]); const handleHistorySelect = useCallback((queryText: string, item?: HistoryItem) => { setQuery(queryText); if (item?.answer_text) { const modeFromItem = (item.retrieval_mode || 'hybrid').toLowerCase() as QueryMode; setMode(modeFromItem); setResult({ answer: item.answer_text, method: item.retrieval_mode, confidence: undefined, }); scrollToAnswer(); } }, [scrollToAnswer]); function askEntity(name: string) { setQuery(name); selectEntity(null); setTimeout(() => runQuery(name), 50); } const empty = data.nodes.length === 0; return (
{/* Left Panel: Query + Answer */}
{/* New Chat button */}
runQuery()} loading={loading} />
{result?.hops && result.hops.length > 0 && ( )} {empty && !loading && (

Your graph is empty

Upload documents to build your knowledge graph.

Upload Document
)}
{result && ( )}
{/* Right Panel: Graph */}
{data.nodes.length > 0 ? ( ) : (
Graph will appear here once documents are processed.
)}
); }