migration of chatbot api to openai compatibility

This commit is contained in:
2025-08-26 09:11:22 +02:00
parent c07ed1744f
commit 10cdf06ae1
12 changed files with 495 additions and 1246 deletions

View File

@@ -5,7 +5,6 @@ Public API v1 package - for external clients
from fastapi import APIRouter from fastapi import APIRouter
from ..v1.llm import router as llm_router from ..v1.llm import router as llm_router
from ..v1.chatbot import router as chatbot_router from ..v1.chatbot import router as chatbot_router
from ..v1.tee import router as tee_router
from ..v1.openai_compat import router as openai_router from ..v1.openai_compat import router as openai_router
# Create public API router # Create public API router
@@ -19,6 +18,3 @@ public_api_router.include_router(llm_router, prefix="/llm", tags=["public-llm"])
# Include public chatbot API (external chatbot integrations) # Include public chatbot API (external chatbot integrations)
public_api_router.include_router(chatbot_router, prefix="/chatbot", tags=["public-chatbot"]) public_api_router.include_router(chatbot_router, prefix="/chatbot", tags=["public-chatbot"])
# Include TEE routes (public TEE services if applicable)
public_api_router.include_router(tee_router, prefix="/tee", tags=["public-tee"])

View File

@@ -5,7 +5,6 @@ API v1 package
from fastapi import APIRouter from fastapi import APIRouter
from .auth import router as auth_router from .auth import router as auth_router
from .llm import router as llm_router from .llm import router as llm_router
from .tee import router as tee_router
from .modules import router as modules_router from .modules import router as modules_router
from .platform import router as platform_router from .platform import router as platform_router
from .users import router as users_router from .users import router as users_router
@@ -29,9 +28,6 @@ api_router.include_router(auth_router, prefix="/auth", tags=["authentication"])
# Include LLM proxy routes # Include LLM proxy routes
api_router.include_router(llm_router, prefix="/llm", tags=["llm"]) api_router.include_router(llm_router, prefix="/llm", tags=["llm"])
# Include TEE routes
api_router.include_router(tee_router, prefix="/tee", tags=["tee"])
# Include modules routes # Include modules routes
api_router.include_router(modules_router, prefix="/modules", tags=["modules"]) api_router.include_router(modules_router, prefix="/modules", tags=["modules"])

View File

@@ -3,9 +3,10 @@ Chatbot API endpoints
""" """
import asyncio import asyncio
import time
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete from sqlalchemy import select, update, delete
from datetime import datetime from datetime import datetime
@@ -42,6 +43,44 @@ class ChatRequest(BaseModel):
conversation_id: Optional[str] = None conversation_id: Optional[str] = None
# OpenAI-compatible models
class ChatMessage(BaseModel):
role: str = Field(..., description="Message role (system, user, assistant)")
content: str = Field(..., description="Message content")
class ChatbotChatCompletionRequest(BaseModel):
messages: List[ChatMessage] = Field(..., description="List of messages")
max_tokens: Optional[int] = Field(None, description="Maximum tokens to generate")
temperature: Optional[float] = Field(None, description="Temperature for sampling")
top_p: Optional[float] = Field(None, description="Top-p sampling parameter")
frequency_penalty: Optional[float] = Field(None, description="Frequency penalty")
presence_penalty: Optional[float] = Field(None, description="Presence penalty")
stop: Optional[List[str]] = Field(None, description="Stop sequences")
stream: Optional[bool] = Field(False, description="Stream response")
class ChatChoice(BaseModel):
index: int
message: ChatMessage
finish_reason: str
class ChatUsage(BaseModel):
prompt_tokens: int
completion_tokens: int
total_tokens: int
class ChatbotChatCompletionResponse(BaseModel):
id: str
object: str = "chat.completion"
created: int
model: str
choices: List[ChatChoice]
usage: ChatUsage
@router.get("/list") @router.get("/list")
@router.get("/instances") @router.get("/instances")
async def list_chatbots( async def list_chatbots(
@@ -330,6 +369,151 @@ async def chat_with_chatbot(
raise HTTPException(status_code=500, detail=f"Failed to process chat: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to process chat: {str(e)}")
@router.post("/{chatbot_id}/chat/completions", response_model=ChatbotChatCompletionResponse)
async def chatbot_chat_completions(
chatbot_id: str,
request: ChatbotChatCompletionRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""OpenAI-compatible chat completions endpoint for chatbot"""
user_id = current_user.get("id") if isinstance(current_user, dict) else current_user.id
log_api_request("chatbot_chat_completions", {
"user_id": user_id,
"chatbot_id": chatbot_id,
"messages_count": len(request.messages)
})
try:
# Get the chatbot instance
result = await db.execute(
select(ChatbotInstance)
.where(ChatbotInstance.id == chatbot_id)
.where(ChatbotInstance.created_by == str(user_id))
)
chatbot = result.scalar_one_or_none()
if not chatbot:
raise HTTPException(status_code=404, detail="Chatbot not found")
if not chatbot.is_active:
raise HTTPException(status_code=400, detail="Chatbot is not active")
# Find the last user message to extract conversation context
user_messages = [msg for msg in request.messages if msg.role == "user"]
if not user_messages:
raise HTTPException(status_code=400, detail="No user message found in conversation")
last_user_message = user_messages[-1].content
# Initialize conversation service
conversation_service = ConversationService(db)
# For OpenAI format, we'll try to find an existing conversation or create a new one
# We'll use a simple hash of the conversation messages as the conversation identifier
import hashlib
conv_hash = hashlib.md5(str([f"{msg.role}:{msg.content}" for msg in request.messages]).encode()).hexdigest()[:16]
# Get or create conversation
conversation = await conversation_service.get_or_create_conversation(
chatbot_id=chatbot_id,
user_id=str(user_id),
conversation_id=conv_hash
)
# Build conversation history from the request messages (excluding system messages for now)
conversation_history = []
for msg in request.messages:
if msg.role in ["user", "assistant"]:
conversation_history.append({
"role": msg.role,
"content": msg.content
})
# Get chatbot module and generate response
try:
chatbot_module = module_manager.modules.get("chatbot")
if not chatbot_module:
raise HTTPException(status_code=500, detail="Chatbot module not available")
# Merge chatbot config with request parameters
effective_config = dict(chatbot.config)
if request.temperature is not None:
effective_config["temperature"] = request.temperature
if request.max_tokens is not None:
effective_config["max_tokens"] = request.max_tokens
# Use the chatbot module to generate a response
response_data = await chatbot_module.chat(
chatbot_config=effective_config,
message=last_user_message,
conversation_history=conversation_history,
user_id=str(user_id)
)
response_content = response_data.get("response", "I'm sorry, I couldn't generate a response.")
except Exception as e:
# Use fallback response
fallback_responses = chatbot.config.get("fallback_responses", [
"I'm sorry, I'm having trouble processing your request right now."
])
response_content = fallback_responses[0] if fallback_responses else "I'm sorry, I couldn't process your request."
# Save the conversation messages
for msg in request.messages:
if msg.role == "user": # Only save the new user message
await conversation_service.add_message(
conversation_id=conversation.id,
role=msg.role,
content=msg.content,
metadata={}
)
# Save assistant message
assistant_message = await conversation_service.add_message(
conversation_id=conversation.id,
role="assistant",
content=response_content,
metadata={},
sources=response_data.get("sources")
)
# Calculate usage (simple approximation)
prompt_tokens = sum(len(msg.content.split()) for msg in request.messages)
completion_tokens = len(response_content.split())
total_tokens = prompt_tokens + completion_tokens
# Create OpenAI-compatible response
response_id = f"chatbot-{chatbot_id}-{int(time.time())}"
return ChatbotChatCompletionResponse(
id=response_id,
object="chat.completion",
created=int(time.time()),
model=chatbot.config.get("model", "unknown"),
choices=[
ChatChoice(
index=0,
message=ChatMessage(role="assistant", content=response_content),
finish_reason="stop"
)
],
usage=ChatUsage(
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=total_tokens
)
)
except HTTPException:
raise
except Exception as e:
await db.rollback()
log_api_request("chatbot_chat_completions_error", {"error": str(e), "user_id": user_id})
raise HTTPException(status_code=500, detail=f"Failed to process chat completions: {str(e)}")
@router.get("/conversations/{chatbot_id}") @router.get("/conversations/{chatbot_id}")
async def get_chatbot_conversations( async def get_chatbot_conversations(
chatbot_id: str, chatbot_id: str,
@@ -617,6 +801,164 @@ async def external_chat_with_chatbot(
raise HTTPException(status_code=500, detail=f"Failed to process chat: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to process chat: {str(e)}")
@router.post("/external/{chatbot_id}/chat/completions", response_model=ChatbotChatCompletionResponse)
async def external_chatbot_chat_completions(
chatbot_id: str,
request: ChatbotChatCompletionRequest,
api_key: APIKey = Depends(get_api_key_auth),
db: AsyncSession = Depends(get_db)
):
"""External OpenAI-compatible chat completions endpoint for chatbot with API key authentication"""
log_api_request("external_chatbot_chat_completions", {
"chatbot_id": chatbot_id,
"api_key_id": api_key.id,
"messages_count": len(request.messages)
})
try:
# Check if API key can access this chatbot
if not api_key.can_access_chatbot(chatbot_id):
raise HTTPException(status_code=403, detail="API key not authorized for this chatbot")
# Get the chatbot instance
result = await db.execute(
select(ChatbotInstance)
.where(ChatbotInstance.id == chatbot_id)
)
chatbot = result.scalar_one_or_none()
if not chatbot:
raise HTTPException(status_code=404, detail="Chatbot not found")
if not chatbot.is_active:
raise HTTPException(status_code=400, detail="Chatbot is not active")
# Find the last user message to extract conversation context
user_messages = [msg for msg in request.messages if msg.role == "user"]
if not user_messages:
raise HTTPException(status_code=400, detail="No user message found in conversation")
last_user_message = user_messages[-1].content
# Initialize conversation service
conversation_service = ConversationService(db)
# For OpenAI format, we'll try to find an existing conversation or create a new one
# We'll use a simple hash of the conversation messages as the conversation identifier
import hashlib
conv_hash = hashlib.md5(str([f"{msg.role}:{msg.content}" for msg in request.messages]).encode()).hexdigest()[:16]
# Get or create conversation with API key context
conversation = await conversation_service.get_or_create_conversation(
chatbot_id=chatbot_id,
user_id=f"api_key_{api_key.id}",
conversation_id=conv_hash,
title=f"API Chat {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
)
# Add API key metadata to conversation context if new
if not conversation.context_data.get("api_key_id"):
conversation.context_data = {"api_key_id": api_key.id}
await db.commit()
# Build conversation history from the request messages
conversation_history = []
for msg in request.messages:
if msg.role in ["user", "assistant"]:
conversation_history.append({
"role": msg.role,
"content": msg.content
})
# Get chatbot module and generate response
try:
chatbot_module = module_manager.modules.get("chatbot")
if not chatbot_module:
raise HTTPException(status_code=500, detail="Chatbot module not available")
# Merge chatbot config with request parameters
effective_config = dict(chatbot.config)
if request.temperature is not None:
effective_config["temperature"] = request.temperature
if request.max_tokens is not None:
effective_config["max_tokens"] = request.max_tokens
# Use the chatbot module to generate a response
response_data = await chatbot_module.chat(
chatbot_config=effective_config,
message=last_user_message,
conversation_history=conversation_history,
user_id=f"api_key_{api_key.id}"
)
response_content = response_data.get("response", "I'm sorry, I couldn't generate a response.")
sources = response_data.get("sources")
except Exception as e:
# Use fallback response
fallback_responses = chatbot.config.get("fallback_responses", [
"I'm sorry, I'm having trouble processing your request right now."
])
response_content = fallback_responses[0] if fallback_responses else "I'm sorry, I couldn't process your request."
sources = None
# Save the conversation messages
for msg in request.messages:
if msg.role == "user": # Only save the new user message
await conversation_service.add_message(
conversation_id=conversation.id,
role=msg.role,
content=msg.content,
metadata={"api_key_id": api_key.id}
)
# Save assistant message using conversation service
assistant_message = await conversation_service.add_message(
conversation_id=conversation.id,
role="assistant",
content=response_content,
metadata={"api_key_id": api_key.id},
sources=sources
)
# Update API key usage stats
prompt_tokens = sum(len(msg.content.split()) for msg in request.messages)
completion_tokens = len(response_content.split())
total_tokens = prompt_tokens + completion_tokens
api_key.update_usage(tokens_used=total_tokens, cost_cents=0)
await db.commit()
# Create OpenAI-compatible response
response_id = f"chatbot-{chatbot_id}-{int(time.time())}"
return ChatbotChatCompletionResponse(
id=response_id,
object="chat.completion",
created=int(time.time()),
model=chatbot.config.get("model", "unknown"),
choices=[
ChatChoice(
index=0,
message=ChatMessage(role="assistant", content=response_content),
finish_reason="stop"
)
],
usage=ChatUsage(
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=total_tokens
)
)
except HTTPException:
raise
except Exception as e:
await db.rollback()
log_api_request("external_chatbot_chat_completions_error", {"error": str(e), "chatbot_id": chatbot_id})
raise HTTPException(status_code=500, detail=f"Failed to process chat completions: {str(e)}")
@router.post("/{chatbot_id}/api-key") @router.post("/{chatbot_id}/api-key")
async def create_chatbot_api_key( async def create_chatbot_api_key(
chatbot_id: str, chatbot_id: str,
@@ -668,7 +1010,7 @@ async def create_chatbot_api_key(
"secret_key": full_key, # Only returned on creation "secret_key": full_key, # Only returned on creation
"chatbot_id": chatbot_id, "chatbot_id": chatbot_id,
"chatbot_name": chatbot.name, "chatbot_name": chatbot.name,
"endpoint": f"/api/v1/chatbot/external/{chatbot_id}/chat", "endpoint": f"/api/v1/chatbot/external/{chatbot_id}/chat/completions",
"scopes": new_api_key.scopes, "scopes": new_api_key.scopes,
"rate_limit_per_minute": new_api_key.rate_limit_per_minute, "rate_limit_per_minute": new_api_key.rate_limit_per_minute,
"created_at": new_api_key.created_at.isoformat() "created_at": new_api_key.created_at.isoformat()

View File

@@ -1,334 +0,0 @@
"""
TEE (Trusted Execution Environment) API endpoints
Handles Privatemode.ai TEE integration endpoints
"""
import logging
from typing import Dict, Any, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from app.services.tee_service import tee_service
from app.services.api_key_auth import get_current_api_key_user
from app.models.user import User
from app.models.api_key import APIKey
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/tee", tags=["tee"])
security = HTTPBearer()
class AttestationRequest(BaseModel):
"""Request model for attestation"""
nonce: Optional[str] = Field(None, description="Optional nonce for attestation")
class AttestationVerificationRequest(BaseModel):
"""Request model for attestation verification"""
report: str = Field(..., description="Attestation report")
signature: str = Field(..., description="Attestation signature")
certificate_chain: str = Field(..., description="Certificate chain")
nonce: Optional[str] = Field(None, description="Optional nonce")
class SecureSessionRequest(BaseModel):
"""Request model for secure session creation"""
capabilities: Optional[list] = Field(
default=["confidential_inference", "secure_memory", "attestation"],
description="Requested TEE capabilities"
)
@router.get("/health")
async def get_tee_health():
"""
Get TEE environment health status
Returns comprehensive health information about the TEE environment
including capabilities, status, and availability.
"""
try:
health_data = await tee_service.health_check()
return {
"success": True,
"data": health_data
}
except Exception as e:
logger.error(f"TEE health check failed: {e}")
raise HTTPException(
status_code=500,
detail="Failed to get TEE health status"
)
@router.get("/capabilities")
async def get_tee_capabilities(
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Get TEE environment capabilities
Returns detailed information about TEE capabilities including
supported features, encryption algorithms, and security properties.
Requires authentication.
"""
try:
user, api_key = current_user
capabilities = await tee_service.get_tee_capabilities()
return {
"success": True,
"data": capabilities
}
except Exception as e:
logger.error(f"Failed to get TEE capabilities: {e}")
raise HTTPException(
status_code=500,
detail="Failed to get TEE capabilities"
)
@router.post("/attestation")
async def get_attestation(
request: AttestationRequest,
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Get TEE attestation report
Generates a cryptographic attestation report that proves the integrity
and authenticity of the TEE environment. The report can be used to
verify that code is running in a genuine TEE.
Requires authentication.
"""
try:
user, api_key = current_user
attestation_data = await tee_service.get_attestation(request.nonce)
return {
"success": True,
"data": attestation_data
}
except Exception as e:
logger.error(f"Failed to get attestation: {e}")
raise HTTPException(
status_code=500,
detail="Failed to get TEE attestation"
)
@router.post("/attestation/verify")
async def verify_attestation(
request: AttestationVerificationRequest,
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Verify TEE attestation report
Verifies the authenticity and integrity of a TEE attestation report.
This includes validating the certificate chain, signature, and
measurements against known good values.
Requires authentication.
"""
try:
user, api_key = current_user
attestation_data = {
"report": request.report,
"signature": request.signature,
"certificate_chain": request.certificate_chain,
"nonce": request.nonce
}
verification_result = await tee_service.verify_attestation(attestation_data)
return {
"success": True,
"data": verification_result
}
except Exception as e:
logger.error(f"Failed to verify attestation: {e}")
raise HTTPException(
status_code=500,
detail="Failed to verify TEE attestation"
)
@router.post("/session")
async def create_secure_session(
request: SecureSessionRequest,
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Create a secure TEE session
Creates a secure session within the TEE environment with requested
capabilities. The session provides isolated execution context with
enhanced security properties.
Requires authentication.
"""
try:
user, api_key = current_user
session_data = await tee_service.create_secure_session(
user_id=str(user.id),
api_key_id=api_key.id
)
return {
"success": True,
"data": session_data
}
except Exception as e:
logger.error(f"Failed to create secure session: {e}")
raise HTTPException(
status_code=500,
detail="Failed to create TEE secure session"
)
@router.get("/metrics")
async def get_privacy_metrics(
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Get privacy and security metrics
Returns comprehensive metrics about TEE usage, privacy protection,
and security status including request counts, data encrypted,
and performance statistics.
Requires authentication.
"""
try:
user, api_key = current_user
metrics = await tee_service.get_privacy_metrics()
return {
"success": True,
"data": metrics
}
except Exception as e:
logger.error(f"Failed to get privacy metrics: {e}")
raise HTTPException(
status_code=500,
detail="Failed to get privacy metrics"
)
@router.get("/models")
async def list_tee_models(
current_user: tuple = Depends(get_current_api_key_user)
):
"""
List available TEE models
Returns a list of AI models available through the TEE environment.
These models provide confidential inference capabilities with
enhanced privacy and security properties.
Requires authentication.
"""
try:
user, api_key = current_user
models = await tee_service.list_tee_models()
return {
"success": True,
"data": models,
"count": len(models)
}
except Exception as e:
logger.error(f"Failed to list TEE models: {e}")
raise HTTPException(
status_code=500,
detail="Failed to list TEE models"
)
@router.get("/status")
async def get_tee_status(
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Get comprehensive TEE status
Returns combined status information including health, capabilities,
and metrics for a complete overview of the TEE environment.
Requires authentication.
"""
try:
user, api_key = current_user
# Get all status information
health_data = await tee_service.health_check()
capabilities = await tee_service.get_tee_capabilities()
metrics = await tee_service.get_privacy_metrics()
models = await tee_service.list_tee_models()
status_data = {
"health": health_data,
"capabilities": capabilities,
"metrics": metrics,
"models": {
"available": len(models),
"list": models
},
"summary": {
"tee_enabled": health_data.get("tee_enabled", False),
"secure_inference_available": len(models) > 0,
"attestation_available": health_data.get("attestation_available", False),
"privacy_score": metrics.get("privacy_score", 0)
}
}
return {
"success": True,
"data": status_data
}
except Exception as e:
logger.error(f"Failed to get TEE status: {e}")
raise HTTPException(
status_code=500,
detail="Failed to get TEE status"
)
@router.delete("/cache")
async def clear_attestation_cache(
current_user: tuple = Depends(get_current_api_key_user)
):
"""
Clear attestation cache
Manually clears the attestation cache to force fresh attestation
reports. This can be useful for debugging or when attestation
requirements change.
Requires authentication.
"""
try:
user, api_key = current_user
# Clear the cache
await tee_service.cleanup_expired_cache()
tee_service.attestation_cache.clear()
return {
"success": True,
"message": "Attestation cache cleared successfully"
}
except Exception as e:
logger.error(f"Failed to clear attestation cache: {e}")
raise HTTPException(
status_code=500,
detail="Failed to clear attestation cache"
)

View File

@@ -299,7 +299,8 @@ class APIKey(Base):
rate_limit_per_day=144000, rate_limit_per_day=144000,
allowed_models=[], # Will use chatbot's configured model allowed_models=[], # Will use chatbot's configured model
allowed_endpoints=[ allowed_endpoints=[
f"/api/v1/chatbot/external/{chatbot_id}/chat" f"/api/v1/chatbot/external/{chatbot_id}/chat",
f"/api/v1/chatbot/external/{chatbot_id}/chat/completions"
], ],
allowed_ips=[], allowed_ips=[],
allowed_chatbots=[chatbot_id], allowed_chatbots=[chatbot_id],

View File

@@ -1,363 +0,0 @@
"""
Trusted Execution Environment (TEE) Service
Handles Privatemode.ai TEE integration for confidential computing
"""
import asyncio
import json
import logging
import os
from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta
from enum import Enum
import aiohttp
from fastapi import HTTPException, status
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
import base64
from app.core.config import settings
logger = logging.getLogger(__name__)
class TEEStatus(str, Enum):
"""TEE environment status"""
HEALTHY = "healthy"
DEGRADED = "degraded"
OFFLINE = "offline"
UNKNOWN = "unknown"
class AttestationStatus(str, Enum):
"""Attestation verification status"""
VERIFIED = "verified"
FAILED = "failed"
PENDING = "pending"
EXPIRED = "expired"
class TEEService:
"""Service for managing Privatemode.ai TEE integration"""
def __init__(self):
self.privatemode_base_url = "http://privatemode-proxy:8080"
self.privatemode_api_key = settings.PRIVATEMODE_API_KEY
self.session: Optional[aiohttp.ClientSession] = None
self.timeout = aiohttp.ClientTimeout(total=300) # 5 minutes timeout
self.attestation_cache = {} # Cache for attestation results
self.attestation_ttl = timedelta(hours=1) # Cache TTL
async def _get_session(self) -> aiohttp.ClientSession:
"""Get or create aiohttp session"""
if self.session is None or self.session.closed:
self.session = aiohttp.ClientSession(
timeout=self.timeout,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.privatemode_api_key}"
}
)
return self.session
async def close(self):
"""Close the HTTP session"""
if self.session and not self.session.closed:
await self.session.close()
async def health_check(self) -> Dict[str, Any]:
"""Check TEE environment health"""
try:
session = await self._get_session()
async with session.get(f"{self.privatemode_base_url}/health") as response:
if response.status == 200:
health_data = await response.json()
return {
"status": TEEStatus.HEALTHY.value,
"timestamp": datetime.utcnow().isoformat(),
"tee_enabled": health_data.get("tee_enabled", False),
"attestation_available": health_data.get("attestation_available", False),
"secure_memory": health_data.get("secure_memory", False),
"details": health_data
}
else:
return {
"status": TEEStatus.DEGRADED.value,
"timestamp": datetime.utcnow().isoformat(),
"error": f"HTTP {response.status}"
}
except Exception as e:
logger.error(f"TEE health check error: {e}")
return {
"status": TEEStatus.OFFLINE.value,
"timestamp": datetime.utcnow().isoformat(),
"error": str(e)
}
async def get_attestation(self, nonce: Optional[str] = None) -> Dict[str, Any]:
"""Get TEE attestation report"""
try:
if not nonce:
nonce = base64.b64encode(os.urandom(32)).decode()
# Check cache first
cache_key = f"attestation_{nonce}"
if cache_key in self.attestation_cache:
cached_result = self.attestation_cache[cache_key]
if datetime.fromisoformat(cached_result["timestamp"]) + self.attestation_ttl > datetime.utcnow():
return cached_result
session = await self._get_session()
payload = {"nonce": nonce}
async with session.post(
f"{self.privatemode_base_url}/attestation",
json=payload
) as response:
if response.status == 200:
attestation_data = await response.json()
# Process attestation report
result = {
"status": AttestationStatus.VERIFIED.value,
"timestamp": datetime.utcnow().isoformat(),
"nonce": nonce,
"report": attestation_data.get("report"),
"signature": attestation_data.get("signature"),
"certificate_chain": attestation_data.get("certificate_chain"),
"measurements": attestation_data.get("measurements", {}),
"tee_type": attestation_data.get("tee_type", "unknown"),
"verified": True
}
# Cache the result
self.attestation_cache[cache_key] = result
return result
else:
error_text = await response.text()
logger.error(f"TEE attestation failed: {response.status} - {error_text}")
return {
"status": AttestationStatus.FAILED.value,
"timestamp": datetime.utcnow().isoformat(),
"nonce": nonce,
"error": error_text,
"verified": False
}
except Exception as e:
logger.error(f"TEE attestation error: {e}")
return {
"status": AttestationStatus.FAILED.value,
"timestamp": datetime.utcnow().isoformat(),
"nonce": nonce,
"error": str(e),
"verified": False
}
async def verify_attestation(self, attestation_data: Dict[str, Any]) -> Dict[str, Any]:
"""Verify TEE attestation report"""
try:
# Extract components
report = attestation_data.get("report")
signature = attestation_data.get("signature")
cert_chain = attestation_data.get("certificate_chain")
if not all([report, signature, cert_chain]):
return {
"verified": False,
"status": AttestationStatus.FAILED.value,
"error": "Missing required attestation components"
}
# Verify signature (simplified - in production, use proper certificate validation)
try:
# This is a placeholder for actual attestation verification
# In production, you would:
# 1. Validate the certificate chain
# 2. Verify the signature using the public key
# 3. Check measurements against known good values
# 4. Validate the nonce
verification_result = {
"verified": True,
"status": AttestationStatus.VERIFIED.value,
"timestamp": datetime.utcnow().isoformat(),
"certificate_valid": True,
"signature_valid": True,
"measurements_valid": True,
"nonce_valid": True
}
return verification_result
except Exception as verify_error:
logger.error(f"Attestation verification failed: {verify_error}")
return {
"verified": False,
"status": AttestationStatus.FAILED.value,
"error": str(verify_error)
}
except Exception as e:
logger.error(f"Attestation verification error: {e}")
return {
"verified": False,
"status": AttestationStatus.FAILED.value,
"error": str(e)
}
async def get_tee_capabilities(self) -> Dict[str, Any]:
"""Get TEE environment capabilities"""
try:
session = await self._get_session()
async with session.get(f"{self.privatemode_base_url}/capabilities") as response:
if response.status == 200:
capabilities = await response.json()
return {
"timestamp": datetime.utcnow().isoformat(),
"tee_type": capabilities.get("tee_type", "unknown"),
"secure_memory_size": capabilities.get("secure_memory_size", 0),
"encryption_algorithms": capabilities.get("encryption_algorithms", []),
"attestation_types": capabilities.get("attestation_types", []),
"key_management": capabilities.get("key_management", False),
"secure_storage": capabilities.get("secure_storage", False),
"network_isolation": capabilities.get("network_isolation", False),
"confidential_computing": capabilities.get("confidential_computing", False),
"details": capabilities
}
else:
return {
"timestamp": datetime.utcnow().isoformat(),
"error": f"Failed to get capabilities: HTTP {response.status}"
}
except Exception as e:
logger.error(f"TEE capabilities error: {e}")
return {
"timestamp": datetime.utcnow().isoformat(),
"error": str(e)
}
async def create_secure_session(self, user_id: str, api_key_id: int) -> Dict[str, Any]:
"""Create a secure TEE session"""
try:
session = await self._get_session()
payload = {
"user_id": user_id,
"api_key_id": api_key_id,
"timestamp": datetime.utcnow().isoformat(),
"requested_capabilities": [
"confidential_inference",
"secure_memory",
"attestation"
]
}
async with session.post(
f"{self.privatemode_base_url}/session",
json=payload
) as response:
if response.status == 201:
session_data = await response.json()
return {
"session_id": session_data.get("session_id"),
"status": "active",
"timestamp": datetime.utcnow().isoformat(),
"capabilities": session_data.get("capabilities", []),
"expires_at": session_data.get("expires_at"),
"attestation_token": session_data.get("attestation_token")
}
else:
error_text = await response.text()
logger.error(f"TEE session creation failed: {response.status} - {error_text}")
raise HTTPException(
status_code=response.status,
detail=f"Failed to create TEE session: {error_text}"
)
except aiohttp.ClientError as e:
logger.error(f"TEE session creation error: {e}")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="TEE service unavailable"
)
async def get_privacy_metrics(self) -> Dict[str, Any]:
"""Get privacy and security metrics"""
try:
session = await self._get_session()
async with session.get(f"{self.privatemode_base_url}/metrics") as response:
if response.status == 200:
metrics = await response.json()
return {
"timestamp": datetime.utcnow().isoformat(),
"requests_processed": metrics.get("requests_processed", 0),
"data_encrypted": metrics.get("data_encrypted", 0),
"attestations_verified": metrics.get("attestations_verified", 0),
"secure_sessions": metrics.get("secure_sessions", 0),
"uptime": metrics.get("uptime", 0),
"memory_usage": metrics.get("memory_usage", {}),
"performance": metrics.get("performance", {}),
"privacy_score": metrics.get("privacy_score", 0)
}
else:
return {
"timestamp": datetime.utcnow().isoformat(),
"error": f"Failed to get metrics: HTTP {response.status}"
}
except Exception as e:
logger.error(f"TEE metrics error: {e}")
return {
"timestamp": datetime.utcnow().isoformat(),
"error": str(e)
}
async def list_tee_models(self) -> List[Dict[str, Any]]:
"""List available TEE models"""
try:
session = await self._get_session()
async with session.get(f"{self.privatemode_base_url}/models") as response:
if response.status == 200:
models_data = await response.json()
models = []
for model in models_data.get("models", []):
models.append({
"id": model.get("id"),
"name": model.get("name"),
"type": model.get("type", "chat"),
"provider": "privatemode",
"tee_enabled": True,
"confidential_computing": True,
"secure_inference": True,
"attestation_required": model.get("attestation_required", False),
"max_tokens": model.get("max_tokens", 4096),
"cost_per_token": model.get("cost_per_token", 0.0),
"availability": model.get("availability", "available")
})
return models
else:
logger.error(f"Failed to get TEE models: HTTP {response.status}")
return []
except Exception as e:
logger.error(f"TEE models error: {e}")
return []
async def cleanup_expired_cache(self):
"""Clean up expired attestation cache entries"""
current_time = datetime.utcnow()
expired_keys = []
for key, cached_data in self.attestation_cache.items():
if datetime.fromisoformat(cached_data["timestamp"]) + self.attestation_ttl <= current_time:
expired_keys.append(key)
for key in expired_keys:
del self.attestation_cache[key]
logger.info(f"Cleaned up {len(expired_keys)} expired attestation cache entries")
# Global TEE service instance
tee_service = TEEService()

View File

@@ -70,6 +70,8 @@ function LLMPageContent() {
const [models, setModels] = useState<Model[]>([]) const [models, setModels] = useState<Model[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [showCreateDialog, setShowCreateDialog] = useState(false) const [showCreateDialog, setShowCreateDialog] = useState(false)
const [showEditDialog, setShowEditDialog] = useState(false)
const [editingKey, setEditingKey] = useState<APIKey | null>(null)
const [showSecretKeyDialog, setShowSecretKeyDialog] = useState(false) const [showSecretKeyDialog, setShowSecretKeyDialog] = useState(false)
const [newSecretKey, setNewSecretKey] = useState('') const [newSecretKey, setNewSecretKey] = useState('')
const { toast } = useToast() const { toast } = useToast()
@@ -82,6 +84,13 @@ function LLMPageContent() {
description: '' description: ''
}) })
// Edit API Key form state
const [editKey, setEditKey] = useState({
name: '',
description: '',
is_active: true
})
useEffect(() => { useEffect(() => {
fetchData() fetchData()
}, []) // eslint-disable-line react-hooks/exhaustive-deps }, []) // eslint-disable-line react-hooks/exhaustive-deps
@@ -159,6 +168,39 @@ function LLMPageContent() {
} }
} }
const openEditDialog = (apiKey: APIKey) => {
setEditingKey(apiKey)
setEditKey({
name: apiKey.name,
description: apiKey.description || '',
is_active: apiKey.is_active
})
setShowEditDialog(true)
}
const updateAPIKey = async () => {
if (!editingKey) return
try {
await apiClient.put(`/api-internal/v1/api-keys/${editingKey.id}`, editKey)
toast({
title: "Success",
description: "API key updated successfully"
})
setShowEditDialog(false)
setEditingKey(null)
fetchData()
} catch (error) {
toast({
title: "Error",
description: "Failed to update API key",
variant: "destructive"
})
}
}
const deleteAPIKey = async (keyId: number) => { const deleteAPIKey = async (keyId: number) => {
try { try {
console.log('Deleting API key with ID:', keyId) console.log('Deleting API key with ID:', keyId)
@@ -219,11 +261,11 @@ function LLMPageContent() {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const protocol = window.location.protocol const protocol = window.location.protocol
const hostname = window.location.hostname const hostname = window.location.hostname
const port = window.location.hostname === 'localhost' ? '3000' : window.location.port || (protocol === 'https:' ? '443' : '80') const port = window.location.port || (protocol === 'https:' ? '443' : '80')
const portSuffix = (protocol === 'https:' && port === '443') || (protocol === 'http:' && port === '80') ? '' : `:${port}` const portSuffix = (protocol === 'https:' && port === '443') || (protocol === 'http:' && port === '80') ? '' : `:${port}`
return `${protocol}//${hostname}${portSuffix}/v1` return `${protocol}//${hostname}${portSuffix}/api/v1`
} }
return 'http://localhost:3000/v1' return 'http://localhost/api/v1'
} }
const publicApiUrl = getPublicApiUrl() const publicApiUrl = getPublicApiUrl()
@@ -464,7 +506,7 @@ function LLMPageContent() {
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button variant="ghost" size="sm"> <Button variant="ghost" size="sm" onClick={() => openEditDialog(apiKey)}>
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
</Button> </Button>
<AlertDialog> <AlertDialog>
@@ -536,6 +578,71 @@ function LLMPageContent() {
</TabsContent> </TabsContent>
</Tabs> </Tabs>
{/* Edit API Key Dialog */}
<Dialog open={showEditDialog} onOpenChange={setShowEditDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Edit API Key</DialogTitle>
<DialogDescription>
Update the name, description, and status of your API key.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label htmlFor="edit-name">Name</Label>
<Input
id="edit-name"
value={editKey.name}
onChange={(e) => setEditKey(prev => ({ ...prev, name: e.target.value }))}
placeholder="e.g., Frontend Application"
/>
</div>
<div>
<Label htmlFor="edit-description">Description</Label>
<Textarea
id="edit-description"
value={editKey.description}
onChange={(e) => setEditKey(prev => ({ ...prev, description: e.target.value }))}
placeholder="Brief description of what this key is for"
/>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="edit-active"
checked={editKey.is_active}
onChange={(e) => setEditKey(prev => ({ ...prev, is_active: e.target.checked }))}
className="h-4 w-4 rounded border-gray-300"
/>
<Label htmlFor="edit-active" className="text-sm font-medium">
Active (uncheck to disable this API key)
</Label>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<div className="flex items-start gap-2">
<AlertTriangle className="h-4 w-4 text-blue-600 mt-0.5 flex-shrink-0" />
<div className="text-sm text-blue-700">
<span className="font-medium">Note:</span> You cannot change the model restrictions or expiration date after creation.
Create a new API key if you need different settings.
</div>
</div>
</div>
<div className="flex justify-end space-x-2">
<Button variant="outline" onClick={() => setShowEditDialog(false)}>
Cancel
</Button>
<Button onClick={updateAPIKey} disabled={!editKey.name.trim()}>
Update API Key
</Button>
</div>
</div>
</DialogContent>
</Dialog>
{/* Secret Key Display Dialog */} {/* Secret Key Display Dialog */}
<Dialog open={showSecretKeyDialog} onOpenChange={() => {}}> <Dialog open={showSecretKeyDialog} onOpenChange={() => {}}>
<DialogContent className="max-w-2xl" onPointerDownOutside={(e) => e.preventDefault()}> <DialogContent className="max-w-2xl" onPointerDownOutside={(e) => e.preventDefault()}>

View File

@@ -6,7 +6,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Zap } from 'lucide-react' import { Zap } from 'lucide-react'
import ChatPlayground from '@/components/playground/ChatPlayground' import ChatPlayground from '@/components/playground/ChatPlayground'
import EmbeddingPlayground from '@/components/playground/EmbeddingPlayground' import EmbeddingPlayground from '@/components/playground/EmbeddingPlayground'
import TEEMonitor from '@/components/playground/TEEMonitor'
import ModelSelector from '@/components/playground/ModelSelector' import ModelSelector from '@/components/playground/ModelSelector'
import { ProtectedRoute } from '@/components/auth/ProtectedRoute' import { ProtectedRoute } from '@/components/auth/ProtectedRoute'
@@ -54,10 +53,9 @@ function PlaygroundContent() {
</div> </div>
<Tabs value={activeTab} onValueChange={setActiveTab}> <Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="chat">Chat Completions</TabsTrigger> <TabsTrigger value="chat">Chat Completions</TabsTrigger>
<TabsTrigger value="embeddings">Embeddings</TabsTrigger> <TabsTrigger value="embeddings">Embeddings</TabsTrigger>
<TabsTrigger value="tee">TEE Monitor</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="chat" className="mt-6"> <TabsContent value="chat" className="mt-6">
@@ -67,10 +65,6 @@ function PlaygroundContent() {
<TabsContent value="embeddings" className="mt-6"> <TabsContent value="embeddings" className="mt-6">
<EmbeddingPlayground /> <EmbeddingPlayground />
</TabsContent> </TabsContent>
<TabsContent value="tee" className="mt-6">
<TEEMonitor />
</TabsContent>
</Tabs> </Tabs>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -29,6 +29,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { apiClient } from "@/lib/api-client"; import { apiClient } from "@/lib/api-client";
import ConfidentialityDashboard from "@/components/settings/ConfidentialityDashboard";
interface SystemSettings { interface SystemSettings {
// Security Settings // Security Settings
@@ -261,10 +262,11 @@ function SettingsPageContent() {
)} )}
<Tabs defaultValue="security" className="space-y-6"> <Tabs defaultValue="security" className="space-y-6">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="security">Security</TabsTrigger> <TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="api">API</TabsTrigger> <TabsTrigger value="api">API</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger> <TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="confidentiality">Confidentiality</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="security" className="space-y-6"> <TabsContent value="security" className="space-y-6">
@@ -848,6 +850,11 @@ function SettingsPageContent() {
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
<TabsContent value="confidentiality" className="space-y-6">
<ConfidentialityDashboard />
</TabsContent>
</Tabs> </Tabs>
</div> </div>
); );

View File

@@ -70,10 +70,17 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
setIsLoading(true) setIsLoading(true)
try { try {
// Build conversation history in OpenAI format
const conversationHistory = messages.map(msg => ({
role: msg.role,
content: msg.content
}))
const data = await chatbotApi.sendMessage( const data = await chatbotApi.sendMessage(
chatbotId, chatbotId,
messageToSend, messageToSend,
conversationId || undefined conversationId || undefined,
conversationHistory
) )
// Update conversation ID if it's a new conversation // Update conversation ID if it's a new conversation
@@ -106,7 +113,7 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
}, [input, isLoading, chatbotId, conversationId, toast]) }, [input, isLoading, chatbotId, conversationId, messages, toast])
const handleKeyPress = useCallback((e: React.KeyboardEvent) => { const handleKeyPress = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {

View File

@@ -1108,11 +1108,11 @@ export function ChatbotManager() {
<div className="bg-muted/50 p-4 rounded-lg"> <div className="bg-muted/50 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
<span className="font-medium">API Endpoint</span> <span className="font-medium">API Endpoint (Direct HTTP/curl)</span>
</div> </div>
<div className="bg-background p-3 rounded border overflow-x-auto"> <div className="bg-background p-3 rounded border overflow-x-auto">
<code className="text-sm whitespace-nowrap"> <code className="text-sm whitespace-nowrap">
POST {config.getPublicApiUrl()}/chatbot/external/{apiKeyChatbot?.id}/chat POST {config.getPublicApiUrl()}/chatbot/external/{apiKeyChatbot?.id}/chat/completions
</code> </code>
</div> </div>
<p className="text-sm text-muted-foreground mt-2"> <p className="text-sm text-muted-foreground mt-2">
@@ -1245,20 +1245,31 @@ export function ChatbotManager() {
{/* Usage Example */} {/* Usage Example */}
<div className="bg-muted/50 p-4 rounded-lg"> <div className="bg-muted/50 p-4 rounded-lg">
<h4 className="font-medium mb-2">Usage Example</h4> <h4 className="font-medium mb-2">Usage Example</h4>
<div className="bg-background p-4 rounded border overflow-x-auto"> <div className="bg-background p-4 rounded border overflow-x-auto">
<pre className="text-sm whitespace-pre-wrap break-all"> <pre className="text-sm whitespace-pre-wrap break-all">
{`curl -X POST "${config.getPublicApiUrl()}/chatbot/external/${apiKeyChatbot?.id}/chat" \\ {`curl -X POST "${config.getPublicApiUrl()}/chatbot/external/${apiKeyChatbot?.id}/chat/completions" \\
-H "Authorization: Bearer YOUR_API_KEY" \\ -H "Authorization: Bearer YOUR_API_KEY" \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-d '{ -d '{
"message": "Hello, how can you help me?", "messages": [
"conversation_id": null {"role": "user", "content": "Hello, how can you help me?"}
],
"max_tokens": 1000,
"temperature": 0.7
}'`} }'`}
</pre> </pre>
</div> </div>
<div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded">
<p className="text-sm text-yellow-800"> <div className="mt-3 p-3 bg-blue-50 border border-blue-200 rounded">
<strong>📌 Important:</strong> Use the unified API endpoint <code className="bg-yellow-100 px-1 rounded">{config.getAppUrl()}</code> which routes to the appropriate backend service via nginx <p className="text-sm text-blue-800">
<strong>💡 OpenAI Library Usage:</strong> For OpenAI Python/JavaScript libraries, use base_url without /chat/completions:
</p>
<code className="block mt-1 text-xs bg-blue-100 p-2 rounded">
base_url="{config.getPublicApiUrl()}/chatbot/external/{apiKeyChatbot?.id}"
</code>
<p className="text-xs text-blue-700 mt-1">
The OpenAI client automatically appends /chat/completions to the base_url
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,515 +0,0 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { Shield, Lock, Eye, RefreshCw, AlertTriangle, CheckCircle, XCircle } from 'lucide-react';
import { apiClient } from '@/lib/api-client';
interface TEEStatus {
health: {
tee_enabled: boolean;
attestation_available: boolean;
secure_execution: boolean;
memory_protection: boolean;
status: string;
};
capabilities: {
supported_features: string[];
encryption_algorithms: string[];
secure_memory_size: number;
max_concurrent_sessions: number;
};
metrics: {
total_requests: number;
secure_requests: number;
attestations_generated: number;
privacy_score: number;
data_encrypted_mb: number;
active_sessions: number;
avg_response_time_ms: number;
};
models: {
available: number;
list: Array<{
name: string;
provider: string;
privacy_level: string;
attestation_required: boolean;
}>;
};
summary: {
tee_enabled: boolean;
secure_inference_available: boolean;
attestation_available: boolean;
privacy_score: number;
};
}
interface AttestationData {
report: string;
signature: string;
certificate_chain: string;
measurements: Record<string, string>;
timestamp: string;
validity_period: number;
}
interface SecureSession {
session_id: string;
user_id: string;
capabilities: string[];
created_at: string;
expires_at: string;
status: string;
}
export default function TEEMonitor() {
const [teeStatus, setTeeStatus] = useState<TEEStatus | null>(null);
const [attestationData, setAttestationData] = useState<AttestationData | null>(null);
const [secureSession, setSecureSession] = useState<SecureSession | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false);
const fetchTEEStatus = async () => {
try {
const data = await apiClient.get('/api-internal/v1/tee/status');
if (data.success) {
setTeeStatus(data.data);
} else {
throw new Error('Failed to fetch TEE status');
}
} catch (err) {
console.error('Error fetching TEE status:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
}
};
const generateAttestation = async () => {
try {
const data = await apiClient.post('/api-internal/v1/tee/attestation', {
nonce: Date.now().toString()
});
if (data.success) {
setAttestationData(data.data);
} else {
throw new Error('Failed to generate attestation');
}
} catch (err) {
console.error('Error generating attestation:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
}
};
const createSecureSession = async () => {
try {
const data = await apiClient.post('/api-internal/v1/tee/session', {
capabilities: ['confidential_inference', 'secure_memory', 'attestation']
});
if (data.success) {
setSecureSession(data.data);
} else {
throw new Error('Failed to create secure session');
}
} catch (err) {
console.error('Error creating secure session:', err);
setError(err instanceof Error ? err.message : 'Unknown error');
}
};
const refreshData = async () => {
setRefreshing(true);
await fetchTEEStatus();
setRefreshing(false);
};
useEffect(() => {
const loadData = async () => {
setLoading(true);
await fetchTEEStatus();
setLoading(false);
};
loadData();
// Auto-refresh every 30 seconds
const interval = setInterval(refreshData, 30000);
return () => clearInterval(interval);
}, []);
const getStatusColor = (status: boolean) => {
return status ? 'bg-green-500' : 'bg-red-500';
};
const getStatusIcon = (status: boolean) => {
return status ? <CheckCircle className="w-4 h-4" /> : <XCircle className="w-4 h-4" />;
};
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="flex items-center space-x-2">
<RefreshCw className="w-6 h-6 animate-spin" />
<span>Loading TEE status...</span>
</div>
</div>
);
}
if (error) {
return (
<Alert className="border-red-200 bg-red-50">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
Error loading TEE status: {error}
<Button
variant="outline"
size="sm"
onClick={refreshData}
className="ml-2"
>
Retry
</Button>
</AlertDescription>
</Alert>
);
}
if (!teeStatus) {
return (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
No TEE status data available
</AlertDescription>
</Alert>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Shield className="w-6 h-6 text-blue-600" />
<h2 className="text-2xl font-bold">TEE Monitor</h2>
</div>
<Button
onClick={refreshData}
disabled={refreshing}
variant="outline"
size="sm"
>
{refreshing ? (
<RefreshCw className="w-4 h-4 animate-spin mr-2" />
) : (
<RefreshCw className="w-4 h-4 mr-2" />
)}
Refresh
</Button>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium">TEE Status</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${getStatusColor(teeStatus.summary.tee_enabled)}`} />
<span className="text-2xl font-bold">
{teeStatus.summary.tee_enabled ? 'Active' : 'Inactive'}
</span>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium">Privacy Score</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="text-2xl font-bold">{teeStatus.summary.privacy_score}%</div>
<Progress
value={teeStatus.summary.privacy_score}
className="h-2"
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium">Secure Models</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{teeStatus.models.available}</div>
<p className="text-sm text-muted-foreground">Available</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium">Active Sessions</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{teeStatus.metrics.active_sessions}</div>
<p className="text-sm text-muted-foreground">Secure sessions</p>
</CardContent>
</Card>
</div>
{/* Detailed Information */}
<Tabs defaultValue="status" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="status">Status</TabsTrigger>
<TabsTrigger value="attestation">Attestation</TabsTrigger>
<TabsTrigger value="session">Session</TabsTrigger>
<TabsTrigger value="models">Models</TabsTrigger>
</TabsList>
<TabsContent value="status" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Shield className="w-5 h-5 mr-2" />
Health Status
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between">
<span>TEE Enabled</span>
<div className="flex items-center space-x-2">
{getStatusIcon(teeStatus.health.tee_enabled)}
<Badge variant={teeStatus.health.tee_enabled ? "default" : "destructive"}>
{teeStatus.health.tee_enabled ? "Yes" : "No"}
</Badge>
</div>
</div>
<div className="flex items-center justify-between">
<span>Attestation Available</span>
<div className="flex items-center space-x-2">
{getStatusIcon(teeStatus.health.attestation_available)}
<Badge variant={teeStatus.health.attestation_available ? "default" : "destructive"}>
{teeStatus.health.attestation_available ? "Yes" : "No"}
</Badge>
</div>
</div>
<div className="flex items-center justify-between">
<span>Secure Execution</span>
<div className="flex items-center space-x-2">
{getStatusIcon(teeStatus.health.secure_execution)}
<Badge variant={teeStatus.health.secure_execution ? "default" : "destructive"}>
{teeStatus.health.secure_execution ? "Yes" : "No"}
</Badge>
</div>
</div>
<div className="flex items-center justify-between">
<span>Memory Protection</span>
<div className="flex items-center space-x-2">
{getStatusIcon(teeStatus.health.memory_protection)}
<Badge variant={teeStatus.health.memory_protection ? "default" : "destructive"}>
{teeStatus.health.memory_protection ? "Yes" : "No"}
</Badge>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Lock className="w-5 h-5 mr-2" />
Privacy Metrics
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center justify-between">
<span>Total Requests</span>
<span className="font-mono">{teeStatus.metrics.total_requests.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between">
<span>Secure Requests</span>
<span className="font-mono">{teeStatus.metrics.secure_requests.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between">
<span>Attestations Generated</span>
<span className="font-mono">{teeStatus.metrics.attestations_generated.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between">
<span>Data Encrypted</span>
<span className="font-mono">{teeStatus.metrics.data_encrypted_mb.toFixed(2)} MB</span>
</div>
<div className="flex items-center justify-between">
<span>Avg Response Time</span>
<span className="font-mono">{teeStatus.metrics.avg_response_time_ms}ms</span>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="attestation" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Eye className="w-5 h-5 mr-2" />
TEE Attestation
</CardTitle>
<CardDescription>
Generate and verify cryptographic attestation reports
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex space-x-2">
<Button onClick={generateAttestation} className="flex-1">
Generate Attestation
</Button>
</div>
{attestationData && (
<div className="space-y-3">
<Separator />
<div className="grid grid-cols-1 gap-3">
<div>
<label className="text-sm font-medium">Report ID</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm break-all">
{attestationData.report.substring(0, 64)}...
</div>
</div>
<div>
<label className="text-sm font-medium">Signature</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm break-all">
{attestationData.signature.substring(0, 64)}...
</div>
</div>
<div>
<label className="text-sm font-medium">Timestamp</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm">
{new Date(attestationData.timestamp).toLocaleString()}
</div>
</div>
<div>
<label className="text-sm font-medium">Validity Period</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm">
{attestationData.validity_period} seconds
</div>
</div>
</div>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="session" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Secure Session Management</CardTitle>
<CardDescription>
Create and manage secure TEE sessions
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex space-x-2">
<Button onClick={createSecureSession} className="flex-1">
Create Secure Session
</Button>
</div>
{secureSession && (
<div className="space-y-3">
<Separator />
<div className="grid grid-cols-1 gap-3">
<div>
<label className="text-sm font-medium">Session ID</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm">
{secureSession.session_id}
</div>
</div>
<div>
<label className="text-sm font-medium">Status</label>
<div className="mt-1">
<Badge variant={secureSession.status === 'active' ? 'default' : 'secondary'}>
{secureSession.status}
</Badge>
</div>
</div>
<div>
<label className="text-sm font-medium">Capabilities</label>
<div className="mt-1 flex flex-wrap gap-1">
{secureSession.capabilities.map((cap) => (
<Badge key={cap} variant="outline" className="text-xs">
{cap}
</Badge>
))}
</div>
</div>
<div>
<label className="text-sm font-medium">Created</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm">
{new Date(secureSession.created_at).toLocaleString()}
</div>
</div>
<div>
<label className="text-sm font-medium">Expires</label>
<div className="mt-1 p-2 bg-gray-50 rounded font-mono text-sm">
{new Date(secureSession.expires_at).toLocaleString()}
</div>
</div>
</div>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="models" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Available TEE Models</CardTitle>
<CardDescription>
AI models with confidential computing capabilities
</CardDescription>
</CardHeader>
<CardContent>
<ScrollArea className="h-80">
<div className="space-y-3">
{teeStatus.models.list.map((model, index) => (
<div key={index} className="p-3 border rounded-lg">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">{model.name}</h4>
<div className="flex items-center space-x-2">
<Badge variant="secondary">{model.provider}</Badge>
<Badge variant={model.privacy_level === 'high' ? 'default' : 'outline'}>
{model.privacy_level}
</Badge>
</div>
</div>
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
<span>Attestation Required:</span>
<Badge variant={model.attestation_required ? 'default' : 'secondary'}>
{model.attestation_required ? 'Yes' : 'No'}
</Badge>
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}