diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b22abc --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# Enclava + +**Secure AI Platform with Privacy-First LLM Services** + +Enclava is a comprehensive AI platform that provides secure chatbot services, document retrieval (RAG), and LLM integrations with Trusted Execution Environment (TEE) support via privateMode.ai. + +## Key Features + +- **AI Chatbots** - Customizable chatbots with prompt templates and RAG integration (openai compatible) +- **RAG System** - Document upload, processing, and semantic search with Qdrant +- **TEE Security** - Privacy-protected LLM inference via confidential computing +- **OpenAI Compatible** - Standard API endpoints for seamless integration with existing tools +- **Budget Management** - Built-in spend tracking and usage limits + +## Quick Start + +### Prerequisites + +- Docker and Docker Compose +- Git +- [privatemode.ai](https://privatemode.ai) api key + +### 1. Clone Repository + +```bash +git clone +cd enclava +``` + +### 2. Configure Environment + +```bash +# Copy example environment file +cp .env.example .env + +# Edit .env with your settings +vim .env +``` + +**Required Configuration:** +```bash +# Security +JWT_SECRET=your-super-secret-jwt-key-here-change-in-production + +# PrivateMode.ai API Key (optional but recommended) +PRIVATEMODE_API_KEY=your-privatemode-api-key + +# Base URL for CORS and frontend +BASE_URL=localhost +``` + +### 3. Deploy with Docker + +```bash +# Start all services +docker compose up --build + +# Or run in background +docker compose up --build -d +``` + +### 4. Access Application + +- **Main Application**: http://localhost +- **API Documentation**: http://localhost/docs (backend API) +- **Qdrant Dashboard**: http://localhost:56333/dashboard + +### 5. Default Login + +- **Username**: `admin` +- **Password**: `admin123` + +*Change default credentials immediately in production!* + +## Documentation + +For comprehensive documentation, API references, and advanced configuration: + +**[docs.enclava.ai](https://docs.enclava.ai)** + +## Architecture + +- **Frontend**: Next.js (React/TypeScript) with Tailwind CSS +- **Backend**: FastAPI (Python) with async/await patterns +- **Database**: PostgreSQL with automatic migrations +- **Vector DB**: Qdrant for document embeddings +- **Cache**: Redis for sessions and performance +- **LLM Service**: Native secure LLM service with TEE support + +## Services + +| Service | Port | Purpose | +|---------|------|---------| +| Nginx (Main) | 80 | Reverse proxy and main access | +| Backend API | 58000 | FastAPI application (internal) | +| Frontend | 3000 | Next.js application (internal) | +| PostgreSQL | 5432 | Primary database | +| Redis | 6379 | Caching and sessions | +| Qdrant | 56333 | Vector database for RAG | + + +## Configuration + +### Environment Variables + +See `.env.example` for all available configuration options. + + +## Support + +- **Documentation**: [docs.enclava.ai](https://docs.enclava.ai) +- **Issues**: Use the GitHub issue tracker +- **Security**: Report security issues privately + + + +--- + diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 1183c17..5f2d381 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -58,8 +58,7 @@ class Settings(BaseSettings): # LLM Service Configuration (replaced LiteLLM) # LLM service configuration is now handled in app/services/llm/config.py - # LLM Service Security - LLM_ENCRYPTION_KEY: Optional[str] = None # Key for encrypting LLM provider API keys + # LLM Service Security (removed encryption - credentials handled by proxy) # Plugin System Security PLUGIN_ENCRYPTION_KEY: Optional[str] = None # Key for encrypting plugin secrets and configurations diff --git a/backend/app/services/llm/config.py b/backend/app/services/llm/config.py index 5043ef8..8ac8fb8 100644 --- a/backend/app/services/llm/config.py +++ b/backend/app/services/llm/config.py @@ -126,9 +126,6 @@ def create_default_config() -> LLMServiceConfig: class EnvironmentVariables: """Environment variables used by LLM service""" - # Encryption - LLM_ENCRYPTION_KEY: Optional[str] = None - # Provider API keys PRIVATEMODE_API_KEY: Optional[str] = None OPENAI_API_KEY: Optional[str] = None @@ -140,7 +137,6 @@ class EnvironmentVariables: def __post_init__(self): """Load values from environment""" - self.LLM_ENCRYPTION_KEY = os.getenv("LLM_ENCRYPTION_KEY") self.PRIVATEMODE_API_KEY = os.getenv("PRIVATEMODE_API_KEY") self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") self.ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") @@ -204,15 +200,9 @@ class ConfigurationManager: config = self.get_config() return [name for name, provider in config.providers.items() if provider.enabled] - def get_api_key(self, provider_name: str, encrypted: bool = False) -> Optional[str]: + def get_api_key(self, provider_name: str) -> Optional[str]: """Get API key for provider""" - api_key = self._env_vars.get_api_key(provider_name) - - if api_key and encrypted: - from .security import security_manager - return security_manager.encrypt_api_key(api_key) - - return api_key + return self._env_vars.get_api_key(provider_name) def _validate_configuration(self): """Validate current configuration""" diff --git a/backend/app/services/llm/security.py b/backend/app/services/llm/security.py index 99e6b98..0d24f62 100644 --- a/backend/app/services/llm/security.py +++ b/backend/app/services/llm/security.py @@ -1,7 +1,7 @@ """ LLM Security Manager -Handles API key encryption, prompt injection detection, and audit logging. +Handles prompt injection detection and audit logging. Provides comprehensive security for LLM interactions. """ @@ -12,10 +12,6 @@ import logging import hashlib from typing import Dict, Any, List, Optional, Tuple from datetime import datetime -from cryptography.fernet import Fernet -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -import base64 from app.core.config import settings @@ -26,51 +22,8 @@ class SecurityManager: """Manages security for LLM operations""" def __init__(self): - self._fernet = None - self._setup_encryption() self._setup_prompt_injection_patterns() - def _setup_encryption(self): - """Setup Fernet encryption for API keys""" - try: - # Get encryption key from environment or generate one - encryption_key = os.getenv("LLM_ENCRYPTION_KEY") - - if not encryption_key: - # Generate a key if none exists (for development) - # In production, this should be set as an environment variable - logger.warning("LLM_ENCRYPTION_KEY not set, generating temporary key") - key = Fernet.generate_key() - encryption_key = key.decode() - logger.info(f"Generated temporary encryption key: {encryption_key}") - else: - # Validate the key format - try: - key = encryption_key.encode() - Fernet(key) # Test if key is valid - except Exception: - # Key might be a password, derive Fernet key from it - key = self._derive_key_from_password(encryption_key) - - self._fernet = Fernet(key if isinstance(key, bytes) else key.encode()) - logger.info("Encryption system initialized successfully") - - except Exception as e: - logger.error(f"Failed to setup encryption: {e}") - raise RuntimeError("Encryption setup failed") - - def _derive_key_from_password(self, password: str) -> bytes: - """Derive Fernet key from password using PBKDF2""" - # Use a fixed salt for consistency (in production, store this securely) - salt = b"enclava_llm_salt" - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=100000, - ) - key = base64.urlsafe_b64encode(kdf.derive(password.encode())) - return key def _setup_prompt_injection_patterns(self): """Setup patterns for prompt injection detection""" @@ -131,30 +84,6 @@ class SecurityManager: self.compiled_patterns = [re.compile(pattern) for pattern in self.injection_patterns] logger.info(f"Initialized {len(self.injection_patterns)} prompt injection patterns") - def encrypt_api_key(self, api_key: str) -> str: - """Encrypt an API key for secure storage""" - try: - if not api_key: - raise ValueError("API key cannot be empty") - - encrypted = self._fernet.encrypt(api_key.encode()) - return base64.urlsafe_b64encode(encrypted).decode() - except Exception as e: - logger.error(f"Failed to encrypt API key: {e}") - raise SecurityError("API key encryption failed") - - def decrypt_api_key(self, encrypted_key: str) -> str: - """Decrypt an API key for use""" - try: - if not encrypted_key: - raise ValueError("Encrypted key cannot be empty") - - decoded = base64.urlsafe_b64decode(encrypted_key.encode()) - decrypted = self._fernet.decrypt(decoded) - return decrypted.decode() - except Exception as e: - logger.error(f"Failed to decrypt API key: {e}") - raise SecurityError("API key decryption failed") def validate_prompt_security(self, messages: List[Dict[str, str]]) -> Tuple[bool, float, List[str]]: """ diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx index 0dc5a7d..8b461a9 100644 --- a/frontend/src/app/admin/page.tsx +++ b/frontend/src/app/admin/page.tsx @@ -327,9 +327,9 @@ export default function AdminPage() {
-
))} diff --git a/frontend/src/app/llm/page.tsx b/frontend/src/app/llm/page.tsx index 9847740..c629160 100644 --- a/frontend/src/app/llm/page.tsx +++ b/frontend/src/app/llm/page.tsx @@ -53,8 +53,18 @@ interface APIKey { interface Model { id: string - name: string - provider: string + object: string + created?: number + owned_by?: string + permission?: any[] + root?: string + parent?: string + provider?: string + capabilities?: string[] + context_window?: number + max_output_tokens?: number + supports_streaming?: boolean + supports_function_calling?: boolean } export default function LLMPage() { @@ -108,12 +118,9 @@ function LLMPageContent() { }) ]) - console.log('API keys data:', keysData) setApiKeys(keysData.api_keys || []) - console.log('API keys state updated, count:', keysData.api_keys?.length || 0) setModels(modelsData.data || []) - console.log('Data fetch completed successfully') } catch (error) { console.error('Error fetching data:', error) toast({ @@ -315,7 +322,7 @@ function LLMPageContent() { API Keys - @@ -440,15 +447,53 @@ function LLMPageContent() {
- {models.map((model) => ( -
-

{model.id}

-

Provider: {model.owned_by}

- - {model.object} - -
- ))} + {models.map((model) => { + // Helper function to get provider from model ID + const getProviderFromModel = (modelId: string): string => { + if (modelId.startsWith('privatemode-')) return 'PrivateMode.ai' + if (modelId.startsWith('gpt-') || modelId.includes('openai')) return 'OpenAI' + if (modelId.startsWith('claude-') || modelId.includes('anthropic')) return 'Anthropic' + if (modelId.startsWith('gemini-') || modelId.includes('google')) return 'Google' + if (modelId.includes('cohere')) return 'Cohere' + if (modelId.includes('mistral')) return 'Mistral' + if (modelId.includes('llama') && !modelId.startsWith('privatemode-')) return 'Meta' + return model.owned_by || 'Unknown' + } + + return ( +
+

{model.id}

+

+ Provider: {getProviderFromModel(model.id)} +

+
+ + {model.object || 'model'} + + {model.supports_streaming && ( + + Streaming + + )} + {model.supports_function_calling && ( + + Functions + + )} + {model.capabilities?.includes('tee') && ( + + TEE + + )} +
+ {model.context_window && ( +

+ Context: {model.context_window.toLocaleString()} tokens +

+ )} +
+ ) + })} {models.length === 0 && (
No models available. Check your LLM platform configuration. diff --git a/frontend/src/app/modules/page.tsx b/frontend/src/app/modules/page.tsx deleted file mode 100644 index 04da754..0000000 --- a/frontend/src/app/modules/page.tsx +++ /dev/null @@ -1,399 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Switch } from "@/components/ui/switch"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { - Database, - Play, - Square, - RefreshCw, - Settings, - Activity, - AlertTriangle, - CheckCircle, - Clock, - Cpu, - BarChart3 -} from "lucide-react"; -import { useToast } from "@/hooks/use-toast"; -import { ProtectedRoute } from '@/components/auth/ProtectedRoute' -import { useModules, triggerModuleRefresh } from '@/contexts/ModulesContext' -import { apiClient } from '@/lib/api-client' - -interface Module { - name: string; - status: "loaded" | "failed" | "disabled"; - dependencies: string[]; - config: Record; - metrics?: { - requests_processed: number; - average_response_time: number; - error_rate: number; - last_activity: string; - }; - health?: { - status: "healthy" | "warning" | "error"; - message: string; - uptime: number; - }; -} - -interface ModuleStats { - total_modules: number; - loaded_modules: number; - failed_modules: number; - system_health: "healthy" | "warning" | "error"; -} - -export default function ModulesPage() { - return ( - - - - ) -} - -function ModulesPageContent() { - const { toast } = useToast(); - const { modules: contextModules, isLoading: contextLoading, refreshModules } = useModules(); - const [stats, setStats] = useState(null); - const [actionLoading, setActionLoading] = useState(null); - - // Transform context modules to match existing interface - const modules: Module[] = contextModules.map(module => ({ - name: module.name, - status: module.initialized && module.enabled ? "loaded" : - !module.enabled ? "disabled" : "failed", - dependencies: [], // Not provided in current API - config: module.stats || {}, - metrics: { - requests_processed: module.stats?.total_requests || 0, - average_response_time: module.stats?.avg_analysis_time || 0, - error_rate: module.stats?.errors || 0, - last_activity: new Date().toISOString(), - }, - health: { - status: module.initialized && module.enabled ? "healthy" : "error", - message: module.initialized && module.enabled ? "Module is running" : - !module.enabled ? "Module is disabled" : "Module failed to initialize", - uptime: module.stats?.uptime || 0, - } - })); - - const loading = contextLoading; - - useEffect(() => { - // Calculate stats from context modules - setStats({ - total_modules: contextModules.length, - loaded_modules: contextModules.filter(m => m.initialized && m.enabled).length, - failed_modules: contextModules.filter(m => !m.initialized || !m.enabled).length, - system_health: contextModules.some(m => !m.initialized) ? "warning" : "healthy" - }); - }, [contextModules]); - - - const handleModuleAction = async (moduleName: string, action: "start" | "stop" | "restart" | "reload") => { - try { - setActionLoading(`${moduleName}-${action}`); - - const responseData = await apiClient.post(`/api-internal/v1/modules/${moduleName}/${action}`, {}); - - toast({ - title: "Success", - description: `Module ${moduleName} ${action}ed successfully`, - }); - - // Refresh modules context and trigger navigation update - await refreshModules(); - - // Trigger navigation refresh if the response indicates it's needed - if (responseData.refreshRequired) { - triggerModuleRefresh(); - } - } catch (error) { - console.error(`Failed to ${action} module:`, error); - toast({ - title: "Error", - description: error instanceof Error ? error.message : `Failed to ${action} module`, - variant: "destructive", - }); - } finally { - setActionLoading(null); - } - }; - - const handleModuleToggle = async (moduleName: string, enabled: boolean) => { - await handleModuleAction(moduleName, enabled ? "start" : "stop"); - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case "loaded": - return ; - case "failed": - return ; - case "disabled": - return ; - default: - return ; - } - }; - - const getStatusBadge = (status: string) => { - const variants: Record = { - loaded: "default", - failed: "destructive", - disabled: "secondary" - }; - return {status}; - }; - - const formatUptime = (seconds: number) => { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - return `${hours}h ${minutes}m`; - }; - - if (loading) { - return ( -
-
-
-
-
- ); - } - - return ( -
-
-
-

Module Manager

-

- Manage dynamic modules and monitor system performance -

-
-
- - -
-
- - {/* System Health Alert */} - {stats && stats.system_health !== "healthy" && ( - - - - System health: {stats.system_health}. Some modules may not be functioning properly. - - - )} - - {/* Statistics Cards */} -
- - - Total Modules - - - -
{stats?.total_modules || 0}
-
-
- - - - Loaded - - - -
{stats?.loaded_modules || 0}
-
-
- - - - Failed - - - -
{stats?.failed_modules || 0}
-
-
- - - - System Health - {getStatusIcon(stats?.system_health || "unknown")} - - -
{stats?.system_health || "Unknown"}
-
-
-
- - {/* Modules List */} - - - Overview - Performance - Configuration - - - - {modules.map((module) => ( - - -
-
- {getStatusIcon(module.status)} -
- {module.name} - - Dependencies: {module.dependencies.length > 0 ? module.dependencies.join(", ") : "None"} - -
-
-
- {getStatusBadge(module.status)} - handleModuleToggle(module.name, checked)} - disabled={actionLoading?.startsWith(module.name)} - /> -
-
-
- -
- - - {module.name === 'signal' && ( - - )} - {module.name === 'zammad' && ( - - )} -
- - {module.health && ( -
-
- Health: {module.health.status} -
-
- Uptime: {formatUptime(module.health.uptime)} -
-
- Message: {module.health.message} -
-
- )} -
-
- ))} -
- - - {modules - .filter((module) => module.metrics) - .map((module) => ( - - - - - {module.name} Performance - - - -
-
-
{module.metrics?.requests_processed || 0}
-

Requests Processed

-
-
-
{module.metrics?.average_response_time || 0}ms
-

Avg Response Time

-
-
-
{(module.metrics?.error_rate || 0).toFixed(2)}%
-

Error Rate

-
-
-
- {module.metrics?.last_activity ? - new Date(module.metrics.last_activity).toLocaleString() : - "Never" - } -
-

Last Activity

-
-
-
-
- ))} -
- - - {modules.map((module) => ( - - - - - {module.name} Configuration - - - -
-                  {JSON.stringify(module.config, null, 2)}
-                
-
-
- ))} -
-
-
- ); -} \ No newline at end of file diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index 558a9fd..9b644d4 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -25,10 +25,15 @@ import { Server, AlertTriangle, CheckCircle, - Info + Info, + Square, + Clock, + Play } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { apiClient } from "@/lib/api-client"; +import { useModules, triggerModuleRefresh } from '@/contexts/ModulesContext'; +import { Badge } from '@/components/ui/badge'; interface SystemSettings { // Security Settings @@ -95,17 +100,78 @@ interface SystemSettings { }; } +interface Module { + name: string; + status: "loaded" | "failed" | "disabled"; + dependencies: string[]; + config: Record; + metrics?: { + requests_processed: number; + average_response_time: number; + error_rate: number; + last_activity: string; + }; + health?: { + status: "healthy" | "warning" | "error"; + message: string; + uptime: number; + }; +} + +interface ModuleStats { + total_modules: number; + loaded_modules: number; + failed_modules: number; + system_health: "healthy" | "warning" | "error"; +} + function SettingsPageContent() { const { toast } = useToast(); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); const [isDirty, setIsDirty] = useState(false); + + // Modules state + const { modules: contextModules, isLoading: modulesLoading, refreshModules } = useModules(); + const [moduleStats, setModuleStats] = useState(null); + const [actionLoading, setActionLoading] = useState(null); useEffect(() => { fetchSettings(); }, []); + // Transform context modules to match existing interface + const modules: Module[] = contextModules.map(module => ({ + name: module.name, + status: module.initialized && module.enabled ? "loaded" : + !module.enabled ? "disabled" : "failed", + dependencies: [], // Not provided in current API + config: module.stats || {}, + metrics: { + requests_processed: module.stats?.total_requests || 0, + average_response_time: module.stats?.avg_analysis_time || 0, + error_rate: module.stats?.errors || 0, + last_activity: new Date().toISOString(), + }, + health: { + status: module.initialized && module.enabled ? "healthy" : "error", + message: module.initialized && module.enabled ? "Module is running" : + !module.enabled ? "Module is disabled" : "Module failed to initialize", + uptime: module.stats?.uptime || 0, + } + })); + + useEffect(() => { + // Calculate stats from context modules + setModuleStats({ + total_modules: contextModules.length, + loaded_modules: contextModules.filter(m => m.initialized && m.enabled).length, + failed_modules: contextModules.filter(m => !m.initialized || !m.enabled).length, + system_health: contextModules.some(m => !m.initialized) ? "warning" : "healthy" + }); + }, [contextModules]); + const fetchSettings = async () => { try { setLoading(true); @@ -202,6 +268,68 @@ function SettingsPageContent() { } }; + const handleModuleAction = async (moduleName: string, action: "start" | "stop" | "restart" | "reload") => { + try { + setActionLoading(`${moduleName}-${action}`); + + const responseData = await apiClient.post(`/api-internal/v1/modules/${moduleName}/${action}`, {}); + + toast({ + title: "Success", + description: `Module ${moduleName} ${action}ed successfully`, + }); + + // Refresh modules context and trigger navigation update + await refreshModules(); + + // Trigger navigation refresh if the response indicates it's needed + if (responseData.refreshRequired) { + triggerModuleRefresh(); + } + } catch (error) { + console.error(`Failed to ${action} module:`, error); + toast({ + title: "Error", + description: error instanceof Error ? error.message : `Failed to ${action} module`, + variant: "destructive", + }); + } finally { + setActionLoading(null); + } + }; + + const handleModuleToggle = async (moduleName: string, enabled: boolean) => { + await handleModuleAction(moduleName, enabled ? "start" : "stop"); + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case "loaded": + return ; + case "failed": + return ; + case "disabled": + return ; + default: + return ; + } + }; + + const getStatusBadge = (status: string) => { + const variants: Record = { + loaded: "default", + failed: "destructive", + disabled: "secondary" + }; + return {status}; + }; + + const formatUptime = (seconds: number) => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + return `${hours}h ${minutes}m`; + }; + if (loading) { return (
@@ -261,10 +389,11 @@ function SettingsPageContent() { )} - + Security API Notifications + Modules @@ -849,6 +978,135 @@ function SettingsPageContent() { + + {/* System Health Alert */} + {moduleStats && moduleStats.system_health !== "healthy" && ( + + + + System health: {moduleStats.system_health}. Some modules may not be functioning properly. + + + )} + + {/* Statistics Cards */} +
+ + + Total Modules + + + +
{moduleStats?.total_modules || 0}
+
+
+ + + + Loaded + + + +
{moduleStats?.loaded_modules || 0}
+
+
+ + + + Failed + + + +
{moduleStats?.failed_modules || 0}
+
+
+ + + + System Health + {getStatusIcon(moduleStats?.system_health || "unknown")} + + +
{moduleStats?.system_health || "Unknown"}
+
+
+
+ + {/* Modules List */} +
+
+

Module Overview

+
+ +
+
+ + {modules.map((module) => ( + + +
+
+ {getStatusIcon(module.status)} +
+ {module.name} + + Dependencies: {module.dependencies.length > 0 ? module.dependencies.join(", ") : "None"} + +
+
+
+ {getStatusBadge(module.status)} + handleModuleToggle(module.name, checked)} + disabled={actionLoading?.startsWith(module.name)} + /> +
+
+
+ +
+ + +
+ + {module.health && ( +
+
+ Health: {module.health.status} +
+
+ Uptime: {formatUptime(module.health.uptime)} +
+
+ Message: {module.health.message} +
+
+ )} +
+
+ ))} +
+
diff --git a/frontend/src/components/chatbot/ChatbotManager.tsx b/frontend/src/components/chatbot/ChatbotManager.tsx index 26fa4d6..73ad336 100644 --- a/frontend/src/components/chatbot/ChatbotManager.tsx +++ b/frontend/src/components/chatbot/ChatbotManager.tsx @@ -19,7 +19,6 @@ import { Plus, Settings, Trash2, - Copy, Play, Bot, Brain, @@ -27,7 +26,9 @@ import { BookOpen, Palette, Key, - Globe + Globe, + Copy, + Link } from "lucide-react" import { useToast } from "@/hooks/use-toast" import { ChatInterface } from "./ChatInterface" @@ -684,11 +685,12 @@ export function ChatbotManager() { - + Basic Personality Knowledge Advanced + Integration @@ -925,6 +927,115 @@ export function ChatbotManager() { />
+ + + {editingChatbot && ( +
+
+ +
+

API Integration

+

+ Use this OpenAI-compatible endpoint to integrate your chatbot into external applications +

+
+
+ +
+
+ +

+ Standard OpenAI API interface for seamless integration with existing tools and libraries +

+
+ + +
+
+ +
+
+
+ +
+
+

+ Authentication Required +

+

+ These endpoints require API key authentication. Create and manage API keys from the{" "} + + . +

+
+
+
+ +
+

Example Usage

+ +
+ +
+
+                              {`curl -X POST "${window.location.origin}/api/v1/chatbot/external/${editingChatbot.id}/chat/completions" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "Content-Type: application/json" \\
+  -d '{
+    "messages": [{"role": "user", "content": "Hello!"}],
+    "max_tokens": 150,
+    "temperature": 0.7
+  }'`}
+                            
+ +
+
+
+
+
+ )} +
@@ -985,23 +1096,50 @@ export function ChatbotManager() {
-
- - - - - +
+
+ + + + +
+ +
+ +
+ + +
+
diff --git a/frontend/src/components/ui/navigation.tsx b/frontend/src/components/ui/navigation.tsx index 3680eb7..7ca0bea 100644 --- a/frontend/src/components/ui/navigation.tsx +++ b/frontend/src/components/ui/navigation.tsx @@ -79,7 +79,6 @@ const Navigation = () => { label: "Settings", children: [ { href: "/settings", label: "System Settings" }, - { href: "/modules", label: "Modules" }, { href: "/plugins", label: "Plugins" }, { href: "/prompt-templates", label: "Prompt Templates" }, ]