@@ -84,6 +86,10 @@ const router = createBrowserRouter([
},
],
},
+ {
+ path: "*",
+ element: ,
+ },
],
},
]);
diff --git a/graphrag-ui/src/pages/Setup.tsx b/graphrag-ui/src/pages/Setup.tsx
index 233d533..d5674ac 100644
--- a/graphrag-ui/src/pages/Setup.tsx
+++ b/graphrag-ui/src/pages/Setup.tsx
@@ -102,7 +102,7 @@ const [activeTab, setActiveTab] = useState("upload");
if (!ingestGraphName) return;
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const response = await fetch(`/ui/${ingestGraphName}/uploads/list`, {
headers: { Authorization: `Basic ${creds}` },
});
@@ -151,7 +151,7 @@ const [activeTab, setActiveTab] = useState("upload");
setUploadMessage("Uploading files...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const formData = new FormData();
filesArray.forEach((file) => formData.append("files", file));
@@ -200,7 +200,7 @@ const [activeTab, setActiveTab] = useState("upload");
setUploadMessage("Total size exceeds limit. Uploading files one by one...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
let uploadedCount = 0;
let failedCount = 0;
const totalFiles = filesArray.length;
@@ -273,7 +273,7 @@ const [activeTab, setActiveTab] = useState("upload");
console.log("Deleting file:", filename);
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Delete original file
const url = `/ui/${ingestGraphName}/uploads?filename=${encodeURIComponent(filename)}`;
@@ -301,7 +301,7 @@ const [activeTab, setActiveTab] = useState("upload");
if (!shouldDelete) return;
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const response = await fetch(`/ui/${ingestGraphName}/uploads`, {
method: "DELETE",
headers: { Authorization: `Basic ${creds}` },
@@ -323,7 +323,7 @@ const [activeTab, setActiveTab] = useState("upload");
if (!ingestGraphName) return;
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const response = await fetch(`/ui/${ingestGraphName}/cloud/list`, {
headers: { Authorization: `Basic ${creds}` },
});
@@ -345,7 +345,7 @@ const [activeTab, setActiveTab] = useState("upload");
setDownloadMessage("Downloading files from cloud storage...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Prepare request body based on provider
let requestBody: any = { provider: cloudProvider };
@@ -437,7 +437,7 @@ const [activeTab, setActiveTab] = useState("upload");
if (!ingestGraphName) return;
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Delete original file
const url = `/ui/${ingestGraphName}/cloud/delete?filename=${encodeURIComponent(filename)}`;
@@ -462,7 +462,7 @@ const [activeTab, setActiveTab] = useState("upload");
if (!shouldDelete) return;
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const response = await fetch(`/ui/${ingestGraphName}/cloud/delete`, {
method: "DELETE",
headers: { Authorization: `Basic ${creds}` },
@@ -485,7 +485,7 @@ const [activeTab, setActiveTab] = useState("upload");
setIsIngesting(true);
setIngestMessage("Ingesting documents into knowledge graph...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const folderPath = sourceType === "uploaded" ? `uploads/${ingestGraphName}` : `downloaded_files_cloud/${ingestGraphName}`;
// Use existing ingestJobData if available, otherwise construct from folder path
@@ -547,7 +547,7 @@ const [activeTab, setActiveTab] = useState("upload");
setIngestMessage("Step 1/2: Creating ingest job...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Step 1: Create ingest job
const createIngestConfig = {
@@ -643,7 +643,7 @@ const [activeTab, setActiveTab] = useState("upload");
console.log("fileCount:", fileCount);
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Call create_ingest to process files
const createIngestConfig = {
@@ -741,7 +741,7 @@ const [activeTab, setActiveTab] = useState("upload");
setIsIngesting(true);
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
let loadingInfo: any = {};
if (skipBDAProcessing) {
@@ -859,7 +859,7 @@ const [activeTab, setActiveTab] = useState("upload");
}
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const statusResponse = await fetch(`/ui/${graphName}/rebuild_status`, {
method: "GET",
headers: {
@@ -930,7 +930,7 @@ const [activeTab, setActiveTab] = useState("upload");
setRefreshMessage("Verifying rebuild status...");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Final status check to prevent race conditions
const statusCheckResponse = await fetch(`/ui/${refreshGraphName}/rebuild_status`, {
@@ -1000,9 +1000,9 @@ const [activeTab, setActiveTab] = useState("upload");
}
}, [refreshOpen, refreshGraphName]);
- // Load available graphs from localStorage on mount
+ // Load available graphs from sessionStorage on mount
useEffect(() => {
- const store = JSON.parse(localStorage.getItem("site") || "{}");
+ const store = JSON.parse(sessionStorage.getItem("site") || "{}");
if (store.graphs && Array.isArray(store.graphs)) {
setAvailableGraphs(store.graphs);
// Auto-select first graph if available
@@ -1036,8 +1036,8 @@ const [activeTab, setActiveTab] = useState("upload");
setStatusType("");
try {
- // Get credentials from localStorage
- const creds = localStorage.getItem("creds");
+ // Get credentials from sessionStorage
+ const creds = sessionStorage.getItem("creds");
if (!creds) {
throw new Error("Not authenticated. Please login first.");
}
@@ -1104,10 +1104,10 @@ const [activeTab, setActiveTab] = useState("upload");
setAvailableGraphs(prev => {
if (!prev.includes(newGraph)) {
const updated = [...prev, newGraph];
- // Update localStorage as well
- const store = JSON.parse(localStorage.getItem("site") || "{}");
+ // Update sessionStorage as well
+ const store = JSON.parse(sessionStorage.getItem("site") || "{}");
store.graphs = updated;
- localStorage.setItem("site", JSON.stringify(store));
+ sessionStorage.setItem("site", JSON.stringify(store));
return updated;
}
return prev;
@@ -1139,7 +1139,7 @@ const [activeTab, setActiveTab] = useState("upload");
Back to Chat
- Knowledge Graph Administration
+ Knowledge Graph Setup
Configure and manage your knowledge graphs
diff --git a/graphrag-ui/src/pages/setup/CustomizePrompts.tsx b/graphrag-ui/src/pages/setup/CustomizePrompts.tsx
index ef13dfc..d16fe59 100644
--- a/graphrag-ui/src/pages/setup/CustomizePrompts.tsx
+++ b/graphrag-ui/src/pages/setup/CustomizePrompts.tsx
@@ -2,6 +2,9 @@ import React, { useState, useEffect } from "react";
import { FileText, Save, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
+import ConfigScopeToggle from "@/components/ConfigScopeToggle";
+import { useRoles } from "@/hooks/useRoles";
+import { useLocation } from "react-router-dom";
const ALL_PROMPT_TYPES = [
{ id: "chatbot_response", name: "Chatbot Responses", description: "Customize how the chatbot responds to user questions" },
@@ -11,6 +14,9 @@ const ALL_PROMPT_TYPES = [
];
const CustomizePrompts = () => {
+ const location = useLocation();
+ const { isSuperuser, isGlobalDesigner } = useRoles(location.pathname);
+ const graphOnly = !isSuperuser && !isGlobalDesigner;
const [configuredProvider, setConfiguredProvider] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [expandedPrompt, setExpandedPrompt] = useState(null);
@@ -39,7 +45,9 @@ const CustomizePrompts = () => {
const [isSaving, setIsSaving] = useState(false);
const [saveMessage, setSaveMessage] = useState("");
const [saveMessageType, setSaveMessageType] = useState<"success" | "error" | "">("");
- const selectedGraph = localStorage.getItem("selectedGraph") || "";
+ const [configScope, setConfigScope] = useState<"global" | "graph">("global");
+ const [selectedGraph, setSelectedGraph] = useState(sessionStorage.getItem("selectedGraph") || "");
+ const [availableGraphs, setAvailableGraphs] = useState([]);
const handleSavePrompt = async (promptId: string) => {
setIsSaving(true);
@@ -47,7 +55,7 @@ const CustomizePrompts = () => {
setSaveMessageType("");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const query = selectedGraph ? `?graphname=${encodeURIComponent(selectedGraph)}` : "";
const response = await fetch(`/ui/prompts${query}`, {
method: "POST",
@@ -85,71 +93,92 @@ const CustomizePrompts = () => {
setPrompts(prev => ({ ...prev, [promptId]: value }));
};
- // Fetch prompts and configured LLM provider from server
- useEffect(() => {
- const fetchPrompts = async () => {
- setIsLoading(true);
- try {
- const creds = localStorage.getItem("creds");
- const query = selectedGraph ? `?graphname=${encodeURIComponent(selectedGraph)}` : "";
- const response = await fetch(`/ui/prompts${query}`, {
- headers: { Authorization: `Basic ${creds}` },
- });
+ const fetchPrompts = async (graphname?: string) => {
+ setIsLoading(true);
+ const effectiveGraph = graphname ?? selectedGraph;
+ try {
+ const creds = sessionStorage.getItem("creds");
+ const query = effectiveGraph ? `?graphname=${encodeURIComponent(effectiveGraph)}` : "";
+ const response = await fetch(`/ui/prompts${query}`, {
+ headers: { Authorization: `Basic ${creds}` },
+ });
- if (!response.ok) {
- throw new Error("Failed to fetch prompts");
- }
+ if (!response.ok) {
+ throw new Error("Failed to fetch prompts");
+ }
- const data = await response.json();
+ const data = await response.json();
- // Track which prompts this user is allowed to see (backend filters by role)
- setAvailablePromptIds(Object.keys(data.prompts));
+ // Track which prompts this user is allowed to see (backend filters by role)
+ setAvailablePromptIds(Object.keys(data.prompts));
- // Update prompts with fetched data (editable content only)
- setPrompts({
- chatbot_response: data.prompts.chatbot_response?.editable_content !== undefined
- ? data.prompts.chatbot_response.editable_content
- : (typeof data.prompts.chatbot_response === 'string' ? data.prompts.chatbot_response : ""),
- entity_relationship: data.prompts.entity_relationship?.editable_content !== undefined
- ? data.prompts.entity_relationship.editable_content
- : (typeof data.prompts.entity_relationship === 'string' ? data.prompts.entity_relationship : ""),
- community_summarization: data.prompts.community_summarization?.editable_content !== undefined
- ? data.prompts.community_summarization.editable_content
- : (typeof data.prompts.community_summarization === 'string' ? data.prompts.community_summarization : ""),
- query_generation: data.prompts.query_generation?.editable_content !== undefined
- ? data.prompts.query_generation.editable_content
- : (typeof data.prompts.query_generation === 'string' ? data.prompts.query_generation : ""),
- });
-
- // Store template variables separately
- setPromptTemplates({
- chatbot_response: data.prompts.chatbot_response?.template_variables || "",
- entity_relationship: data.prompts.entity_relationship?.template_variables || "",
- community_summarization: data.prompts.community_summarization?.template_variables || "",
- query_generation: data.prompts.query_generation?.template_variables || "",
- });
+ // Update prompts with fetched data (editable content only)
+ setPrompts({
+ chatbot_response: data.prompts.chatbot_response?.editable_content !== undefined
+ ? data.prompts.chatbot_response.editable_content
+ : (typeof data.prompts.chatbot_response === 'string' ? data.prompts.chatbot_response : ""),
+ entity_relationship: data.prompts.entity_relationship?.editable_content !== undefined
+ ? data.prompts.entity_relationship.editable_content
+ : (typeof data.prompts.entity_relationship === 'string' ? data.prompts.entity_relationship : ""),
+ community_summarization: data.prompts.community_summarization?.editable_content !== undefined
+ ? data.prompts.community_summarization.editable_content
+ : (typeof data.prompts.community_summarization === 'string' ? data.prompts.community_summarization : ""),
+ query_generation: data.prompts.query_generation?.editable_content !== undefined
+ ? data.prompts.query_generation.editable_content
+ : (typeof data.prompts.query_generation === 'string' ? data.prompts.query_generation : ""),
+ });
- // Set configured provider
- const providerMap: Record = {
- openai: "OpenAI",
- azure: "Azure OpenAI",
- genai: "Google GenAI (Gemini)",
- vertexai: "Google Vertex AI",
- bedrock: "AWS Bedrock",
- ollama: "Ollama",
- };
- const provider = data.configured_provider?.toLowerCase() || "openai";
- setConfiguredProvider(providerMap[provider] || data.configured_provider || "OpenAI");
- } catch (error) {
- console.error("Error loading prompts:", error);
- setConfiguredProvider("OpenAI");
- } finally {
- setIsLoading(false);
- }
- };
+ // Store template variables separately
+ setPromptTemplates({
+ chatbot_response: data.prompts.chatbot_response?.template_variables || "",
+ entity_relationship: data.prompts.entity_relationship?.template_variables || "",
+ community_summarization: data.prompts.community_summarization?.template_variables || "",
+ query_generation: data.prompts.query_generation?.template_variables || "",
+ });
- fetchPrompts();
- }, []);
+ // Set configured provider
+ const providerMap: Record = {
+ openai: "OpenAI",
+ azure: "Azure OpenAI",
+ genai: "Google GenAI (Gemini)",
+ vertexai: "Google Vertex AI",
+ bedrock: "AWS Bedrock",
+ ollama: "Ollama",
+ };
+ const provider = data.configured_provider?.toLowerCase() || "openai";
+ setConfiguredProvider(providerMap[provider] || data.configured_provider || "OpenAI");
+ } catch (error) {
+ console.error("Error loading prompts:", error);
+ setConfiguredProvider("OpenAI");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Fetch prompts and graph list on mount
+ useEffect(() => {
+ const site = JSON.parse(sessionStorage.getItem("site") || "{}");
+ const graphs = site.graphs || [];
+ setAvailableGraphs(graphs);
+ const storedGraph = sessionStorage.getItem("selectedGraph") || "";
+ if (graphOnly) {
+ // Graph admins must use graph-specific scope
+ setConfigScope("graph");
+ const graph = storedGraph || (graphs.length > 0 ? graphs[0] : "");
+ if (graph) {
+ setSelectedGraph(graph);
+ sessionStorage.setItem("selectedGraph", graph);
+ window.dispatchEvent(new Event("graphrag:selectedGraph"));
+ fetchPrompts(graph);
+ }
+ } else if (storedGraph) {
+ setConfigScope("graph");
+ setSelectedGraph(storedGraph);
+ fetchPrompts(storedGraph);
+ } else {
+ fetchPrompts("");
+ }
+ }, [graphOnly]);
return (
@@ -164,17 +193,43 @@ const CustomizePrompts = () => {
Customize Prompts
- Customize the three core prompts used by GraphRAG
-
-
- {selectedGraph
- ? `Editing prompt overrides for graph: ${selectedGraph}`
- : "Editing default prompts for all graphs"}
+ Customize the core prompts used by GraphRAG
+ {/* Config Scope Toggle */}
+ {
+ setConfigScope(scope);
+ setSaveMessage("");
+ setSaveMessageType("");
+ if (scope === "global") {
+ setSelectedGraph("");
+ sessionStorage.removeItem("selectedGraph");
+ window.dispatchEvent(new Event("graphrag:selectedGraph"));
+ fetchPrompts("");
+ } else if (selectedGraph) {
+ fetchPrompts(selectedGraph);
+ }
+ }}
+ onGraphChange={(value) => {
+ setConfigScope("graph");
+ setSelectedGraph(value);
+ sessionStorage.setItem("selectedGraph", value);
+ window.dispatchEvent(new Event("graphrag:selectedGraph"));
+ setSaveMessage("");
+ setSaveMessageType("");
+ fetchPrompts(value);
+ }}
+ graphSelectedHint="Only customized prompts are stored per graph. Others fall back to global defaults."
+ />
+
{/* Configured Provider - Read Only */}
diff --git a/graphrag-ui/src/pages/setup/GraphDBConfig.tsx b/graphrag-ui/src/pages/setup/GraphDBConfig.tsx
index 34c7f06..1d478cd 100644
--- a/graphrag-ui/src/pages/setup/GraphDBConfig.tsx
+++ b/graphrag-ui/src/pages/setup/GraphDBConfig.tsx
@@ -4,15 +4,31 @@ import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const GraphDBConfig = () => {
- const [hostname, setHostname] = useState("http://tigergraph");
- const [restppPort, setRestppPort] = useState("9000");
- const [gsPort, setGsPort] = useState("14240");
+ // Default values for fields — shown as placeholders, used if user leaves field empty
+ const DEFAULTS = {
+ hostname: "http://tigergraph",
+ restppPort: "9000",
+ gsPort: "14240",
+ username: "tigergraph",
+ defaultTimeout: "300",
+ defaultMemThreshold: "5000",
+ defaultThreadLimit: "8",
+ };
+
+ const [hostname, setHostname] = useState("");
+ const [restppPort, setRestppPort] = useState("");
+ const [gsPort, setGsPort] = useState("");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [getToken, setGetToken] = useState(false);
- const [defaultTimeout, setDefaultTimeout] = useState("300");
- const [defaultMemThreshold, setDefaultMemThreshold] = useState("5000");
- const [defaultThreadLimit, setDefaultThreadLimit] = useState("8");
+ const [useApiToken, setUseApiToken] = useState(false);
+ const [apiToken, setApiToken] = useState("");
+ const [defaultTimeout, setDefaultTimeout] = useState("");
+ const [defaultMemThreshold, setDefaultMemThreshold] = useState("");
+ const [defaultThreadLimit, setDefaultThreadLimit] = useState("");
+
+ /** Return the effective value: user input if non-empty, otherwise the default */
+ const effective = (value: string, key: keyof typeof DEFAULTS) => value || DEFAULTS[key];
// Track original values to detect changes
const [originalHostname, setOriginalHostname] = useState("");
@@ -33,7 +49,7 @@ const GraphDBConfig = () => {
const fetchConfig = async () => {
setIsLoading(true);
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
const response = await fetch("/ui/config", {
headers: { Authorization: `Basic ${creds}` },
});
@@ -46,19 +62,26 @@ const GraphDBConfig = () => {
const dbConfig = data.db_config;
if (dbConfig) {
- const loadedHostname = dbConfig.hostname || "http://tigergraph";
- setHostname(loadedHostname);
- setOriginalHostname(loadedHostname); // Track original hostname
- setRestppPort(dbConfig.restppPort || "9000");
- setGsPort(dbConfig.gsPort || "14240");
-
- setUsername(dbConfig.username || "");
- setOriginalUsername(dbConfig.username || "");
-
+ // Only populate state with non-default values; defaults show as placeholders
+ const h = dbConfig.hostname || "";
+ const u = dbConfig.username || "";
+ setHostname(h === DEFAULTS.hostname ? "" : h);
+ setOriginalHostname(h || DEFAULTS.hostname);
+ setRestppPort(dbConfig.restppPort === DEFAULTS.restppPort ? "" : (dbConfig.restppPort || ""));
+ setGsPort(dbConfig.gsPort === DEFAULTS.gsPort ? "" : (dbConfig.gsPort || ""));
+ setUsername(u === DEFAULTS.username ? "" : u);
+ setOriginalUsername(u || DEFAULTS.username);
+ setPassword(dbConfig.password || "");
setGetToken(dbConfig.getToken || false);
- setDefaultTimeout(String(dbConfig.default_timeout || 300));
- setDefaultMemThreshold(String(dbConfig.default_mem_threshold || 5000));
- setDefaultThreadLimit(String(dbConfig.default_thread_limit || 8));
+ const token = dbConfig.apiToken || "";
+ setApiToken(token);
+ setUseApiToken(!!token);
+ const t = String(dbConfig.default_timeout || "");
+ const m = String(dbConfig.default_mem_threshold || "");
+ const tl = String(dbConfig.default_thread_limit || "");
+ setDefaultTimeout(t === DEFAULTS.defaultTimeout ? "" : t);
+ setDefaultMemThreshold(m === DEFAULTS.defaultMemThreshold ? "" : m);
+ setDefaultThreadLimit(tl === DEFAULTS.defaultThreadLimit ? "" : tl);
}
} catch (error: any) {
console.error("Error fetching config:", error);
@@ -76,15 +99,18 @@ const GraphDBConfig = () => {
setConnectionTested(false);
try {
- const creds = localStorage.getItem("creds");
- const testConfig = {
- hostname,
- restppPort,
- gsPort,
- username,
+ const creds = sessionStorage.getItem("creds");
+ const testConfig: any = {
+ hostname: effective(hostname, "hostname"),
+ restppPort: effective(restppPort, "restppPort"),
+ gsPort: effective(gsPort, "gsPort"),
+ username: effective(username, "username"),
password,
getToken,
};
+ if (useApiToken && apiToken.trim()) {
+ testConfig.apiToken = apiToken.trim();
+ }
const response = await fetch("/ui/config/db/test", {
method: "POST",
@@ -124,18 +150,23 @@ const GraphDBConfig = () => {
setMessageType("");
try {
- const creds = localStorage.getItem("creds");
- const dbConfigData = {
- hostname,
- restppPort,
- gsPort,
- username,
+ const creds = sessionStorage.getItem("creds");
+ const effectiveHostname = effective(hostname, "hostname");
+ const effectiveUsername = effective(username, "username");
+ const dbConfigData: any = {
+ hostname: effectiveHostname,
+ restppPort: effective(restppPort, "restppPort"),
+ gsPort: effective(gsPort, "gsPort"),
+ username: effectiveUsername,
password,
getToken,
- default_timeout: parseInt(defaultTimeout),
- default_mem_threshold: parseInt(defaultMemThreshold),
- default_thread_limit: parseInt(defaultThreadLimit),
+ default_timeout: parseInt(effective(defaultTimeout, "defaultTimeout")),
+ default_mem_threshold: parseInt(effective(defaultMemThreshold, "defaultMemThreshold")),
+ default_thread_limit: parseInt(effective(defaultThreadLimit, "defaultThreadLimit")),
};
+ if (useApiToken && apiToken.trim()) {
+ dbConfigData.apiToken = apiToken.trim();
+ }
const response = await fetch("/ui/config/db", {
method: "POST",
@@ -154,8 +185,8 @@ const GraphDBConfig = () => {
setConnectionTested(false); // Reset after save
// Check if hostname or username changed from what was loaded
- const hostnameChanged = originalHostname && hostname !== originalHostname;
- const usernameChanged = originalUsername && username !== originalUsername;
+ const hostnameChanged = originalHostname && effectiveHostname !== originalHostname;
+ const usernameChanged = originalUsername && effectiveUsername !== originalUsername;
// If hostname OR username changed, redirect to login so services reconnect
if (hostnameChanged || usernameChanged) {
@@ -164,15 +195,15 @@ const GraphDBConfig = () => {
: "GraphDB username changed. Please relogin with the new credentials.";
setTimeout(() => {
- // Clear localStorage and redirect to login
- localStorage.removeItem("creds");
+ // Clear sessionStorage and redirect to login
+ sessionStorage.removeItem("creds");
alert(reason);
window.location.href = "/"; // Redirect to root (login page)
}, 2000); // Give user 2 seconds to see the success message
} else {
// Update originals after successful save (only if no redirect)
- setOriginalHostname(hostname);
- setOriginalUsername(username);
+ setOriginalHostname(effectiveHostname);
+ setOriginalUsername(effectiveUsername);
}
} else {
setMessage(result.detail || "Failed to save configuration");
@@ -196,7 +227,7 @@ const GraphDBConfig = () => {
- GraphDB Configuration
+ Graph Database Configuration
Configure your TigerGraph database connection and settings
@@ -276,43 +307,105 @@ const GraphDBConfig = () => {
-
-
-
- {
- setUsername(e.target.value);
- setConnectionTested(false);
- setMessage("");
- setMessageType("");
- }}
- />
-
+
+ {
+ setUseApiToken(e.target.checked);
+ if (!e.target.checked) setApiToken("");
+ setConnectionTested(false);
+ setMessage("");
+ setMessageType("");
+ }}
+ />
+
+
+ {useApiToken ? (
-
+ ) : (
+ <>
+
+
+
+ {
+ setGetToken(e.target.checked);
+ setConnectionTested(false);
+ setMessage("");
+ setMessageType("");
+ }}
+ />
+
+
+ >
+ )}
@@ -364,24 +457,6 @@ const GraphDBConfig = () => {
-
- {
- setGetToken(e.target.checked);
- setConnectionTested(false);
- setMessage("");
- setMessageType("");
- }}
- />
-
-
-
{message && (
{
)}
-
+
+
+ {!password.trim() && !(useApiToken && apiToken.trim()) && (
+
+ Enter password or API token to test connection
+
+ )}
diff --git a/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx b/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx
index fbdecc5..2db7598 100644
--- a/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx
+++ b/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx
@@ -9,18 +9,30 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
+import ConfigScopeToggle from "@/components/ConfigScopeToggle";
const GraphRAGConfig = () => {
+ const [selectedGraph, setSelectedGraph] = useState(sessionStorage.getItem("selectedGraph") || "");
+ const [availableGraphs, setAvailableGraphs] = useState([]);
const [reuseEmbedding, setReuseEmbedding] = useState(false);
const [eccUrl, setEccUrl] = useState("http://graphrag-ecc:8001");
const [chatHistoryUrl, setChatHistoryUrl] = useState("http://chat-history:8002");
-
+
// Default chunker (used when no chunker specified in document)
const [defaultChunker, setDefaultChunker] = useState("semantic");
-
+
// Retrieval settings
const [topK, setTopK] = useState("5");
const [numHops, setNumHops] = useState("2");
+ const [numSeenMin, setNumSeenMin] = useState("2");
+ const [communityLevel, setCommunityLevel] = useState("2");
+ const [docOnly, setDocOnly] = useState(false);
+
+ // Advanced ingestion settings
+ const [showAdvanced, setShowAdvanced] = useState(false);
+ const [loadBatchSize, setLoadBatchSize] = useState("500");
+ const [upsertDelay, setUpsertDelay] = useState("0");
+ const [tgConcurrency, setTgConcurrency] = useState("10");
// Chunker-specific settings
const [chunkSize, setChunkSize] = useState("1024");
@@ -28,22 +40,57 @@ const GraphRAGConfig = () => {
const [semanticMethod, setSemanticMethod] = useState("percentile");
const [semanticThreshold, setSemanticThreshold] = useState("0.95");
const [regexPattern, setRegexPattern] = useState("\\r?\\n");
-
+
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [message, setMessage] = useState("");
const [messageType, setMessageType] = useState<"success" | "error" | "">("");
+ // Scope: "global" edits global config, "graph" edits per-graph overrides
+ const [configScope, setConfigScope] = useState<"global" | "graph">("global");
+ const [graphOverrides, setGraphOverrides] = useState>({});
+
useEffect(() => {
+ const site = JSON.parse(sessionStorage.getItem("site") || "{}");
+ setAvailableGraphs(site.graphs || []);
fetchConfig();
}, []);
- const fetchConfig = async () => {
+ const applyGraphragConfig = (graphragConfig: any) => {
+ if (!graphragConfig) return;
+ setReuseEmbedding(graphragConfig.reuse_embedding || false);
+ setEccUrl(graphragConfig.ecc || "http://graphrag-ecc:8001");
+ setChatHistoryUrl(graphragConfig.chat_history_api || "http://chat-history:8002");
+ setDefaultChunker(graphragConfig.chunker || "semantic");
+ setTopK(String(graphragConfig.top_k ?? 5));
+ setNumHops(String(graphragConfig.num_hops ?? 2));
+ setNumSeenMin(String(graphragConfig.num_seen_min ?? 2));
+ setCommunityLevel(String(graphragConfig.community_level ?? 2));
+ setDocOnly(graphragConfig.doc_only || false);
+ setLoadBatchSize(String(graphragConfig.load_batch_size ?? 500));
+ setUpsertDelay(String(graphragConfig.upsert_delay ?? 0));
+ setTgConcurrency(String(graphragConfig.tg_concurrency ?? 10));
+
+ const chunkerConfig = graphragConfig.chunker_config || {};
+ setChunkSize(String(chunkerConfig.chunk_size || 1024));
+ setOverlapSize(String(chunkerConfig.overlap_size || 0));
+ setSemanticMethod(chunkerConfig.method || "percentile");
+ setSemanticThreshold(String(chunkerConfig.threshold || 0.95));
+ setRegexPattern(chunkerConfig.pattern || "\\r?\\n");
+ };
+
+ const fetchConfig = async (scope?: "global" | "graph", graphname?: string) => {
setIsLoading(true);
+ const effectiveScope = scope ?? configScope;
+ const effectiveGraph = graphname ?? selectedGraph;
try {
- const creds = localStorage.getItem("creds");
- const response = await fetch("/ui/config", {
+ const creds = sessionStorage.getItem("creds");
+ const params = new URLSearchParams();
+ if (effectiveGraph) params.set("graphname", effectiveGraph);
+ if (effectiveScope === "graph") params.set("scope", "graph");
+ const queryString = params.toString() ? `?${params.toString()}` : "";
+ const response = await fetch(`/ui/config${queryString}`, {
headers: { Authorization: `Basic ${creds}` },
});
@@ -52,22 +99,15 @@ const GraphRAGConfig = () => {
}
const data = await response.json();
- const graphragConfig = data.graphrag_config;
-
- if (graphragConfig) {
- setReuseEmbedding(graphragConfig.reuse_embedding || false);
- setEccUrl(graphragConfig.ecc || "http://graphrag-ecc:8001");
- setChatHistoryUrl(graphragConfig.chat_history_api || "http://chat-history:8002");
- setDefaultChunker(graphragConfig.chunker || "semantic");
- setTopK(String(graphragConfig.top_k ?? 5));
- setNumHops(String(graphragConfig.num_hops ?? 2));
-
- const chunkerConfig = graphragConfig.chunker_config || {};
- setChunkSize(String(chunkerConfig.chunk_size || 1024));
- setOverlapSize(String(chunkerConfig.overlap_size || 0));
- setSemanticMethod(chunkerConfig.method || "percentile");
- setSemanticThreshold(String(chunkerConfig.threshold || 0.95));
- setRegexPattern(chunkerConfig.pattern || "\\r?\\n");
+
+ if (effectiveScope === "graph" && data.graphrag_overrides) {
+ setGraphOverrides(data.graphrag_overrides);
+ // Show per-graph values: merge global + overrides for display
+ const merged = { ...data.graphrag_config, ...data.graphrag_overrides };
+ applyGraphragConfig(merged);
+ } else {
+ setGraphOverrides({});
+ applyGraphragConfig(data.graphrag_config);
}
} catch (error: any) {
console.error("Error fetching config:", error);
@@ -84,7 +124,7 @@ const GraphRAGConfig = () => {
setMessageType("");
try {
- const creds = localStorage.getItem("creds");
+ const creds = sessionStorage.getItem("creds");
// Prepare chunker config based on selected chunker type
const chunkerConfig: any = {};
@@ -102,7 +142,7 @@ const GraphRAGConfig = () => {
// but we keep it consistent
}
- const graphragConfigData = {
+ const graphragConfigData: any = {
reuse_embedding: reuseEmbedding,
ecc: eccUrl,
chat_history_api: chatHistoryUrl,
@@ -110,8 +150,19 @@ const GraphRAGConfig = () => {
chunker_config: chunkerConfig,
top_k: parseInt(topK),
num_hops: parseInt(numHops),
+ num_seen_min: parseInt(numSeenMin),
+ community_level: parseInt(communityLevel),
+ doc_only: docOnly,
+ load_batch_size: parseInt(loadBatchSize),
+ upsert_delay: parseInt(upsertDelay),
+ tg_concurrency: parseInt(tgConcurrency),
};
+ if (configScope === "graph") {
+ graphragConfigData.scope = "graph";
+ graphragConfigData.graphname = selectedGraph;
+ }
+
const response = await fetch("/ui/config/graphrag", {
method: "POST",
headers: {
@@ -163,6 +214,33 @@ const GraphRAGConfig = () => {
+ {/* Config Scope Toggle */}
+ {
+ setConfigScope(scope);
+ if (scope === "global") {
+ fetchConfig("global");
+ } else if (selectedGraph) {
+ fetchConfig("graph", selectedGraph);
+ }
+ }}
+ onGraphChange={(value) => {
+ setConfigScope("graph");
+ setSelectedGraph(value);
+ sessionStorage.setItem("selectedGraph", value);
+ window.dispatchEvent(new Event("graphrag:selectedGraph"));
+ fetchConfig("graph", value);
+ }}
+ graphSelectedHint={
+ Object.keys(graphOverrides).length > 0
+ ? `Overridden keys: ${Object.keys(graphOverrides).join(", ")}. Other settings are inherited from global.`
+ : "No per-graph overrides set. All settings are inherited from global defaults."
+ }
+ />
+
{/* Success/Error Message */}
{message && (
{
-
-
-
setEccUrl(e.target.value)}
- />
-
- Entity-Context-Community service endpoint
-
-
-
-
-
-
setChatHistoryUrl(e.target.value)}
- />
-
- Chat history service endpoint
-
-
-
+
+
+
+
+
setNumSeenMin(e.target.value)}
+ />
+
+ Minimum times a node must appear across retrievals to be included in results
+
+
+
+
+
+
setCommunityLevel(e.target.value)}
+ />
+
+ Community hierarchy level used for community search
+
+
+
+
+
+
+ setDocOnly(e.target.checked)}
+ />
+
+
+
+ Skip entity graph traversal and use only document chunks for hybrid search
+
+
@@ -428,6 +528,134 @@ const GraphRAGConfig = () => {
)}
+ {/* Advanced Ingestion Settings */}
+
+
+ {!showAdvanced && (
+
+ Performance tuning for document ingestion and batch processing.
+
+ )}
+
+ {showAdvanced && (
+
+
+ Performance tuning for document ingestion and batch processing.
+
+
+
+
+
+
setLoadBatchSize(e.target.value)}
+ />
+
+ Vertices per upsert batch
+
+
+
+
+
+
setUpsertDelay(e.target.value)}
+ />
+
+ Seconds between batches
+
+
+
+
+
+
setTgConcurrency(e.target.value)}
+ />
+
+ Max concurrent TigerGraph requests
+
+
+
+
+ )}
+
+
+ {/* Service Endpoints (global only) */}
+ {configScope !== "graph" && (
+
+
+ Service Endpoints
+
+
+ Configure internal service URLs. These are global settings and cannot be overridden per graph.
+
+
+
+
+
+
setEccUrl(e.target.value)}
+ />
+
+ Entity-Context-Community service endpoint
+
+
+
+
+
+
setChatHistoryUrl(e.target.value)}
+ />
+
+ Chat history service endpoint
+
+
+
+
+ )}
+