diff --git a/src/app/api/rag/route.ts b/src/app/api/rag/route.ts new file mode 100644 index 0000000..2c79948 --- /dev/null +++ b/src/app/api/rag/route.ts @@ -0,0 +1,34 @@ +import { NextResponse } from "next/server"; + +const RAG_API = process.env.RAG_API_URL || "http://localhost:8099"; + +export async function GET() { + try { + const [statusRes, progressRes] = await Promise.all([ + fetch(`${RAG_API}/status`, { next: { revalidate: 0 } }), + fetch(`${RAG_API}/ingest-progress`, { next: { revalidate: 0 } }), + ]); + + const status = await statusRes.json(); + const progress = await progressRes.json(); + + return NextResponse.json({ + processed: status.processed ?? 0, + failed: status.failed ?? 0, + totalChunks: status.total_chunks ?? 0, + ingest: { + running: progress.running ?? false, + total: progress.total ?? 0, + done: progress.done ?? 0, + failed: progress.failed ?? 0, + currentFile: progress.current_file ?? "", + skipped: progress.skipped ?? 0, + }, + }); + } catch { + return NextResponse.json( + { error: "Failed to fetch RAG status" }, + { status: 500 } + ); + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index f3603ec..4a8089d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,6 +9,7 @@ import NetworkWidget from "@/components/NetworkWidget"; import SynologyWidget from "@/components/SynologyWidget"; import ServerStatsWidget from "@/components/ServerStatsWidget"; import GPUStatsWidget from "@/components/GPUStatsWidget"; +import RAGWidget from "@/components/RAGWidget"; import { Container } from "@/types"; export default function Home() { @@ -145,11 +146,7 @@ export default function Home() { dashboardUid="docker-containers" panelId={8} /> -
- - - -
+ diff --git a/src/components/RAGWidget.tsx b/src/components/RAGWidget.tsx new file mode 100644 index 0000000..a7af9ac --- /dev/null +++ b/src/components/RAGWidget.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Database, RefreshCw, CheckCircle, XCircle, FileText, Layers } from "lucide-react"; + +interface RAGData { + processed: number; + failed: number; + totalChunks: number; + ingest: { + running: boolean; + total: number; + done: number; + failed: number; + currentFile: string; + skipped: number; + }; +} + +export default function RAGWidget() { + const [data, setData] = useState(null); + const [error, setError] = useState(false); + + useEffect(() => { + const fetchData = async () => { + try { + const res = await fetch("/api/rag"); + if (!res.ok) throw new Error(); + setData(await res.json()); + setError(false); + } catch { + setError(true); + } + }; + fetchData(); + const interval = setInterval(fetchData, 5000); + return () => clearInterval(interval); + }, []); + + if (error) { + return ( +
+
+ + RAG API unreachable +
+
+ ); + } + + if (!data) { + return ( +
+
+
+ ); + } + + const { processed, failed, totalChunks, ingest } = data; + const pct = ingest.total > 0 ? Math.round(((ingest.done + ingest.failed) / ingest.total) * 100) : 0; + const shortFile = ingest.currentFile ? ingest.currentFile.split("/").pop()?.slice(0, 45) : ""; + + return ( +
+
+
+ +

RAG Pipeline

+
+ {ingest.running && ( + + + Ingesting + + )} + {!ingest.running && ( + + + Idle + + )} +
+ + {/* Stats row */} +
+
+
+ +
+
{processed.toLocaleString()}
+
Files
+
+
+
+ +
+
{totalChunks.toLocaleString()}
+
Chunks
+
+
+
+ +
+
{failed}
+
Failed
+
+
+ + {/* Progress bar (shows when ingesting) */} + {ingest.running && ingest.total > 0 && ( +
+
+ {ingest.done + ingest.failed}/{ingest.total} + {pct}% +
+
+
+
+ {shortFile && ( +
+ {shortFile} +
+ )} +
+ )} +
+ ); +}