// // SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // "use client" import React, { useState, useEffect } from "react" import { Settings, Database, Save, Eye, EyeOff, Search as SearchIcon, Cpu, HardDrive, Server, RefreshCw, Check, X } from "lucide-react" import { GraphDBType } from "@/lib/graph-db-service" import { listFilesInS3 } from "@/utils/s3-storage" import { useToast } from "@/hooks/use-toast" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" export function SettingsModal() { const { toast } = useToast() const [isOpen, setIsOpen] = useState(false) const [activeTab, setActiveTab] = useState("models") const [dbUrl, setDbUrl] = useState("") const [dbUsername, setDbUsername] = useState("") const [dbPassword, setDbPassword] = useState("") const [vectorDbHost, setVectorDbHost] = useState("") const [vectorDbPort, setVectorDbPort] = useState("") const [showPassword, setShowPassword] = useState(false) // Graph DB settings const [graphDbType, setGraphDbType] = useState("arangodb") const [neo4jUrl, setNeo4jUrl] = useState("") const [neo4jUser, setNeo4jUser] = useState("") const [neo4jPassword, setNeo4jPassword] = useState("") const [arangoUrl, setArangoUrl] = useState("http://localhost:8529") const [arangoDb, setArangoDb] = useState("txt2kg") const [arangoUser, setArangoUser] = useState("") const [arangoPassword, setArangoPassword] = useState("") // Vector DB settings - changed from Milvus to Pinecone const [pineconeApiKey, setPineconeApiKey] = useState("") const [pineconeEnvironment, setPineconeEnvironment] = useState("") const [pineconeIndex, setPineconeIndex] = useState("") // S3 Storage settings const [s3Endpoint, setS3Endpoint] = useState("") const [s3Bucket, setS3Bucket] = useState("") const [s3AccessKey, setS3AccessKey] = useState("") const [s3SecretKey, setS3SecretKey] = useState("") const [isConnecting, setIsConnecting] = useState(false) const [isS3Connected, setIsS3Connected] = useState(false) const [s3FileCount, setS3FileCount] = useState(0) const [s3Error, setS3Error] = useState(null) // Embeddings model settings const [embeddingsProvider, setEmbeddingsProvider] = useState("local") const [nvidiaEmbeddingsModel, setNvidiaEmbeddingsModel] = useState("nvidia/llama-3.2-nv-embedqa-1b-v2") // Ollama model configuration const [availableOllamaModels, setAvailableOllamaModels] = useState([]) const [selectedOllamaModels, setSelectedOllamaModels] = useState([]) const [isLoadingOllamaModels, setIsLoadingOllamaModels] = useState(false) const [ollamaConnectionStatus, setOllamaConnectionStatus] = useState<'idle' | 'connected' | 'error'>('idle') const [ollamaError, setOllamaError] = useState(null) // Listen for open-settings event useEffect(() => { const handleOpenSettings = (event: CustomEvent) => { const { tab } = event.detail setIsOpen(true) if (tab) { setActiveTab(tab) } } window.addEventListener('open-settings', handleOpenSettings as EventListener) return () => { window.removeEventListener('open-settings', handleOpenSettings as EventListener) } }, []) // Automatically fetch Ollama models when modal opens and models tab is active useEffect(() => { if (isOpen && activeTab === "models" && ollamaConnectionStatus === 'idle') { fetchOllamaModels() } }, [isOpen, activeTab]) // Load saved settings when modal opens useEffect(() => { if (isOpen) { const storedDbUrl = localStorage.getItem("NEO4J_URL") || "" const storedDbUsername = localStorage.getItem("NEO4J_USERNAME") || "" const storedDbPassword = localStorage.getItem("NEO4J_PASSWORD") || "" const storedVectorDbHost = localStorage.getItem("VECTOR_DB_HOST") || "" const storedVectorDbPort = localStorage.getItem("VECTOR_DB_PORT") || "" setDbUrl(storedDbUrl) setDbUsername(storedDbUsername) setDbPassword(storedDbPassword) setVectorDbHost(storedVectorDbHost) setVectorDbPort(storedVectorDbPort) // Load embeddings settings const storedEmbeddingsProvider = localStorage.getItem("embeddings_provider") || "local" const storedNvidiaModel = localStorage.getItem("nvidia_embeddings_model") || "nvidia/llama-3.2-nv-embedqa-1b-v2" setEmbeddingsProvider(storedEmbeddingsProvider) setNvidiaEmbeddingsModel(storedNvidiaModel) // Load Ollama model configuration const storedSelectedModels = localStorage.getItem("selected_ollama_models") if (storedSelectedModels) { try { setSelectedOllamaModels(JSON.parse(storedSelectedModels)) } catch (e) { console.error("Error parsing stored Ollama models:", e) } } // Load S3 settings const savedS3Endpoint = localStorage.getItem("S3_ENDPOINT") || "" const savedS3Bucket = localStorage.getItem("S3_BUCKET") || "" const savedS3AccessKey = localStorage.getItem("S3_ACCESS_KEY") || "" const savedS3SecretKey = localStorage.getItem("S3_SECRET_KEY") || "" const s3Connected = localStorage.getItem("S3_CONNECTED") === "true" setS3Endpoint(savedS3Endpoint) setS3Bucket(savedS3Bucket) setS3AccessKey(savedS3AccessKey) setS3SecretKey(savedS3SecretKey) setIsS3Connected(s3Connected) } // Load graph DB type const storedGraphDbType = localStorage.getItem("graph_db_type") || "arangodb" setGraphDbType(storedGraphDbType as GraphDBType) // Load Neo4j settings setNeo4jUrl(localStorage.getItem("neo4j_url") || "") setNeo4jUser(localStorage.getItem("neo4j_user") || "") setNeo4jPassword(localStorage.getItem("neo4j_password") || "") // Load ArangoDB settings setArangoUrl(localStorage.getItem("arango_url") || "http://localhost:8529") setArangoDb(localStorage.getItem("arango_db") || "txt2kg") setArangoUser(localStorage.getItem("arango_user") || "") setArangoPassword(localStorage.getItem("arango_password") || "") setPineconeApiKey(localStorage.getItem("pinecone_api_key") || "") setPineconeEnvironment(localStorage.getItem("pinecone_environment") || "") setPineconeIndex(localStorage.getItem("pinecone_index") || "") }, [isOpen]) // Save database settings const saveDbSettings = async (e: React.FormEvent) => { e.preventDefault() // Save graph DB type localStorage.setItem("graph_db_type", graphDbType) // Save Neo4j settings localStorage.setItem("neo4j_url", neo4jUrl) localStorage.setItem("neo4j_user", neo4jUser) localStorage.setItem("neo4j_password", neo4jPassword) // Save ArangoDB settings localStorage.setItem("arango_url", arangoUrl) localStorage.setItem("arango_db", arangoDb) localStorage.setItem("arango_user", arangoUser) localStorage.setItem("arango_password", arangoPassword) // Sync settings with server try { await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ settings: { graph_db_type: graphDbType, neo4j_url: neo4jUrl, neo4j_user: neo4jUser, neo4j_password: neo4jPassword, arango_url: arangoUrl, arango_db: arangoDb, arango_user: arangoUser, arango_password: arangoPassword } }), }); } catch (error) { console.error('Error syncing settings:', error); toast({ variant: "destructive", title: "Error", description: "Failed to sync settings with server" }); } toast({ title: "Success", description: "Graph database settings saved" }); setIsOpen(false) } // Save vector database settings const saveVectorDbSettings = async (e: React.FormEvent) => { e.preventDefault() localStorage.setItem("pinecone_api_key", pineconeApiKey) localStorage.setItem("pinecone_environment", pineconeEnvironment) localStorage.setItem("pinecone_index", pineconeIndex) // Sync settings with server try { await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ settings: { pinecone_api_key: pineconeApiKey, pinecone_environment: pineconeEnvironment, pinecone_index: pineconeIndex, } }), }); } catch (error) { console.error('Error syncing settings:', error); toast({ variant: "destructive", title: "Error", description: "Failed to sync settings with server" }); } toast({ title: "Success", description: "Vector database settings saved" }) } // Save S3 settings and check connection const saveS3Settings = async (e: React.FormEvent) => { e.preventDefault() setIsConnecting(true) setS3Error(null) try { // Save S3 settings to localStorage localStorage.setItem("S3_ENDPOINT", s3Endpoint) localStorage.setItem("S3_BUCKET", s3Bucket) localStorage.setItem("S3_ACCESS_KEY", s3AccessKey) localStorage.setItem("S3_SECRET_KEY", s3SecretKey) // Set these in window for runtime access window.process = window.process || {} window.process.env = window.process.env || {} window.process.env.S3_ENDPOINT = s3Endpoint window.process.env.S3_BUCKET = s3Bucket window.process.env.S3_ACCESS_KEY = s3AccessKey window.process.env.S3_SECRET_KEY = s3SecretKey // Try to list files to verify connection const files = await listFilesInS3() setS3FileCount(files.length) setIsS3Connected(true) // Save connection status to localStorage localStorage.setItem("S3_CONNECTED", "true") // Dispatch event to notify other components window.dispatchEvent(new CustomEvent('s3ConnectionChanged', { detail: { isConnected: true } })) toast({ title: "Success", description: `Connected to S3 bucket. Found ${files.length} files.` }) } catch (error) { console.error("Failed to connect to S3:", error) setIsS3Connected(false) // Save connection status to localStorage localStorage.setItem("S3_CONNECTED", "false") // Dispatch event to notify other components window.dispatchEvent(new CustomEvent('s3ConnectionChanged', { detail: { isConnected: false } })) setS3Error(error instanceof Error ? error.message : "Could not connect to S3 storage") toast({ variant: "destructive", title: "S3 Connection Failed", description: error instanceof Error ? error.message : "Unknown error" }) } finally { setIsConnecting(false) } } // Fetch available Ollama models const fetchOllamaModels = async () => { setIsLoadingOllamaModels(true) setOllamaError(null) try { const response = await fetch('/api/ollama?action=test-connection') const data = await response.json() if (data.connected && data.models) { setAvailableOllamaModels(data.models) setOllamaConnectionStatus('connected') // If no models are selected yet, select all by default if (selectedOllamaModels.length === 0) { setSelectedOllamaModels(data.models) } } else { setOllamaConnectionStatus('error') setOllamaError(data.error || 'Failed to connect to Ollama') setAvailableOllamaModels([]) } } catch (error) { console.error('Error fetching Ollama models:', error) setOllamaConnectionStatus('error') setOllamaError(error instanceof Error ? error.message : 'Unknown error') setAvailableOllamaModels([]) } finally { setIsLoadingOllamaModels(false) } } // Save Ollama model settings const saveOllamaSettings = (e: React.FormEvent) => { e.preventDefault() // Save selected models to localStorage localStorage.setItem("selected_ollama_models", JSON.stringify(selectedOllamaModels)) // Dispatch event to notify model selector to refresh if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('ollama-models-updated', { detail: { selectedModels: selectedOllamaModels } })) } toast({ title: "Success", description: "Ollama model settings saved" }) } // Toggle Ollama model selection const toggleOllamaModel = (modelName: string) => { setSelectedOllamaModels(prev => { if (prev.includes(modelName)) { return prev.filter(m => m !== modelName) } else { return [...prev, modelName] } }) } // Save embeddings settings const saveEmbeddingsSettings = (e: React.FormEvent) => { e.preventDefault(); // Save embeddings provider to localStorage localStorage.setItem("embeddings_provider", embeddingsProvider); // If using NVIDIA API, also save the model if (embeddingsProvider === "nvidia") { localStorage.setItem("nvidia_embeddings_model", nvidiaEmbeddingsModel); } // Save to environment variables (this works in development; in production needs server-side implementation) process.env.EMBEDDINGS_PROVIDER = embeddingsProvider; if (embeddingsProvider === "nvidia") { process.env.NVIDIA_EMBEDDINGS_MODEL = nvidiaEmbeddingsModel; } // Reset the EmbeddingsService instance to pick up new settings try { // Import dynamically to avoid issues with circular dependencies import("@/lib/embeddings").then(({ EmbeddingsService }) => { EmbeddingsService.reset(); console.log("EmbeddingsService reset successfully"); // Dispatch a custom event to notify components that embeddings settings have changed if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent('embeddings-settings-changed')); } }); } catch (error) { console.error("Error resetting EmbeddingsService:", error); } toast({ title: "Success", description: "Embeddings settings saved" }) } return (
Settings
Configure your API keys and DB connections
{activeTab === "graph" && (
{graphDbType === "neo4j" && (

Neo4j Configuration

setNeo4jUrl(e.target.value)} placeholder="bolt://localhost:7687" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setNeo4jUser(e.target.value)} placeholder="neo4j" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setNeo4jPassword(e.target.value)} placeholder="password" className="w-full bg-background border border-border/60 rounded-md p-2 pr-8 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
)} {graphDbType === "arangodb" && (

ArangoDB Configuration

setArangoUrl(e.target.value)} placeholder="http://localhost:8529" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setArangoDb(e.target.value)} placeholder="txt2kg" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setArangoUser(e.target.value)} placeholder="root" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setArangoPassword(e.target.value)} placeholder="password" className="w-full bg-background border border-border/60 rounded-md p-2 pr-8 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
)}
)} {activeTab === "vectordb" && (
setPineconeApiKey(e.target.value)} placeholder="Enter your Pinecone API key" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setPineconeEnvironment(e.target.value)} placeholder="us-west1-gcp" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
setPineconeIndex(e.target.value)} placeholder="knowledge-graph" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />
)} {activeTab === "s3" && (
setS3Endpoint(e.target.value)} required className="h-8 text-sm" />
setS3Bucket(e.target.value)} required className="h-8 text-sm" />
setS3AccessKey(e.target.value)} required className="h-8 text-sm" />
setS3SecretKey(e.target.value)} required className="h-8 text-sm" />
{s3Error && (
{s3Error}
)} {isS3Connected && (
Connected {s3FileCount} {s3FileCount === 1 ? 'file' : 'files'} in bucket
)}
)} {activeTab === "embeddings" && (
{embeddingsProvider === "nvidia" && (
setNvidiaEmbeddingsModel(e.target.value)} placeholder="nvidia/llama-3.2-nv-embedqa-1b-v2" className="w-full bg-background border border-border/60 rounded-md p-2 text-sm text-foreground focus:ring-1 focus:ring-primary/50 focus:border-primary transition-colors" />

NVIDIA API key is configured via environment variables

)}
)} {activeTab === "models" && (

Select models for triple extraction dropdown

{ollamaConnectionStatus === 'error' && ollamaError && (
Connection Error

{ollamaError}

)} {ollamaConnectionStatus === 'connected' && (
Connected • {availableOllamaModels.length} model{availableOllamaModels.length !== 1 ? 's' : ''} found
)} {availableOllamaModels.length > 0 && (
{selectedOllamaModels.length} of {availableOllamaModels.length} selected
{availableOllamaModels.map((model) => ( ))}
|
)} {availableOllamaModels.length === 0 && ollamaConnectionStatus === 'idle' && (

Click "Refresh" to load available models

)}
)}
) }