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}
+
+ )}
+
+ )}
+
+ );
+}