mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
migration of chatbot api to openai compatibility
This commit is contained in:
@@ -5,7 +5,6 @@ Public API v1 package - for external clients
|
||||
from fastapi import APIRouter
|
||||
from ..v1.llm import router as llm_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
|
||||
|
||||
# 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)
|
||||
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"])
|
||||
@@ -5,7 +5,6 @@ API v1 package
|
||||
from fastapi import APIRouter
|
||||
from .auth import router as auth_router
|
||||
from .llm import router as llm_router
|
||||
from .tee import router as tee_router
|
||||
from .modules import router as modules_router
|
||||
from .platform import router as platform_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
|
||||
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
|
||||
api_router.include_router(modules_router, prefix="/modules", tags=["modules"])
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ Chatbot API endpoints
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from typing import Dict, Any, List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update, delete
|
||||
from datetime import datetime
|
||||
@@ -42,6 +43,44 @@ class ChatRequest(BaseModel):
|
||||
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("/instances")
|
||||
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)}")
|
||||
|
||||
|
||||
@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}")
|
||||
async def get_chatbot_conversations(
|
||||
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)}")
|
||||
|
||||
|
||||
@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")
|
||||
async def create_chatbot_api_key(
|
||||
chatbot_id: str,
|
||||
@@ -668,7 +1010,7 @@ async def create_chatbot_api_key(
|
||||
"secret_key": full_key, # Only returned on creation
|
||||
"chatbot_id": chatbot_id,
|
||||
"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,
|
||||
"rate_limit_per_minute": new_api_key.rate_limit_per_minute,
|
||||
"created_at": new_api_key.created_at.isoformat()
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -299,7 +299,8 @@ class APIKey(Base):
|
||||
rate_limit_per_day=144000,
|
||||
allowed_models=[], # Will use chatbot's configured model
|
||||
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_chatbots=[chatbot_id],
|
||||
|
||||
@@ -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()
|
||||
@@ -70,6 +70,8 @@ function LLMPageContent() {
|
||||
const [models, setModels] = useState<Model[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
||||
const [showEditDialog, setShowEditDialog] = useState(false)
|
||||
const [editingKey, setEditingKey] = useState<APIKey | null>(null)
|
||||
const [showSecretKeyDialog, setShowSecretKeyDialog] = useState(false)
|
||||
const [newSecretKey, setNewSecretKey] = useState('')
|
||||
const { toast } = useToast()
|
||||
@@ -82,6 +84,13 @@ function LLMPageContent() {
|
||||
description: ''
|
||||
})
|
||||
|
||||
// Edit API Key form state
|
||||
const [editKey, setEditKey] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
is_active: true
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, []) // 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) => {
|
||||
try {
|
||||
console.log('Deleting API key with ID:', keyId)
|
||||
@@ -219,11 +261,11 @@ function LLMPageContent() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const protocol = window.location.protocol
|
||||
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}`
|
||||
return `${protocol}//${hostname}${portSuffix}/v1`
|
||||
return `${protocol}//${hostname}${portSuffix}/api/v1`
|
||||
}
|
||||
return 'http://localhost:3000/v1'
|
||||
return 'http://localhost/api/v1'
|
||||
}
|
||||
|
||||
const publicApiUrl = getPublicApiUrl()
|
||||
@@ -464,7 +506,7 @@ function LLMPageContent() {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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" />
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
@@ -536,6 +578,71 @@ function LLMPageContent() {
|
||||
</TabsContent>
|
||||
</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 */}
|
||||
<Dialog open={showSecretKeyDialog} onOpenChange={() => {}}>
|
||||
<DialogContent className="max-w-2xl" onPointerDownOutside={(e) => e.preventDefault()}>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Zap } from 'lucide-react'
|
||||
import ChatPlayground from '@/components/playground/ChatPlayground'
|
||||
import EmbeddingPlayground from '@/components/playground/EmbeddingPlayground'
|
||||
import TEEMonitor from '@/components/playground/TEEMonitor'
|
||||
import ModelSelector from '@/components/playground/ModelSelector'
|
||||
import { ProtectedRoute } from '@/components/auth/ProtectedRoute'
|
||||
|
||||
@@ -54,10 +53,9 @@ function PlaygroundContent() {
|
||||
</div>
|
||||
|
||||
<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="embeddings">Embeddings</TabsTrigger>
|
||||
<TabsTrigger value="tee">TEE Monitor</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="chat" className="mt-6">
|
||||
@@ -67,10 +65,6 @@ function PlaygroundContent() {
|
||||
<TabsContent value="embeddings" className="mt-6">
|
||||
<EmbeddingPlayground />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="tee" className="mt-6">
|
||||
<TEEMonitor />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { apiClient } from "@/lib/api-client";
|
||||
import ConfidentialityDashboard from "@/components/settings/ConfidentialityDashboard";
|
||||
|
||||
interface SystemSettings {
|
||||
// Security Settings
|
||||
@@ -261,10 +262,11 @@ function SettingsPageContent() {
|
||||
)}
|
||||
|
||||
<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="api">API</TabsTrigger>
|
||||
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
||||
<TabsTrigger value="confidentiality">Confidentiality</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="security" className="space-y-6">
|
||||
@@ -848,6 +850,11 @@ function SettingsPageContent() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
<TabsContent value="confidentiality" className="space-y-6">
|
||||
<ConfidentialityDashboard />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -70,10 +70,17 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
// Build conversation history in OpenAI format
|
||||
const conversationHistory = messages.map(msg => ({
|
||||
role: msg.role,
|
||||
content: msg.content
|
||||
}))
|
||||
|
||||
const data = await chatbotApi.sendMessage(
|
||||
chatbotId,
|
||||
messageToSend,
|
||||
conversationId || undefined
|
||||
conversationId || undefined,
|
||||
conversationHistory
|
||||
)
|
||||
|
||||
// Update conversation ID if it's a new conversation
|
||||
@@ -106,7 +113,7 @@ export function ChatInterface({ chatbotId, chatbotName, onClose }: ChatInterface
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [input, isLoading, chatbotId, conversationId, toast])
|
||||
}, [input, isLoading, chatbotId, conversationId, messages, toast])
|
||||
|
||||
const handleKeyPress = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
|
||||
@@ -1108,11 +1108,11 @@ export function ChatbotManager() {
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<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 className="bg-background p-3 rounded border overflow-x-auto">
|
||||
<code className="text-sm whitespace-nowrap">
|
||||
POST {config.getPublicApiUrl()}/chatbot/external/{apiKeyChatbot?.id}/chat
|
||||
POST {config.getPublicApiUrl()}/chatbot/external/{apiKeyChatbot?.id}/chat/completions
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
@@ -1245,20 +1245,31 @@ export function ChatbotManager() {
|
||||
{/* Usage Example */}
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<h4 className="font-medium mb-2">Usage Example</h4>
|
||||
|
||||
<div className="bg-background p-4 rounded border overflow-x-auto">
|
||||
<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 "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"message": "Hello, how can you help me?",
|
||||
"conversation_id": null
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello, how can you help me?"}
|
||||
],
|
||||
"max_tokens": 1000,
|
||||
"temperature": 0.7
|
||||
}'`}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="mt-3 p-3 bg-yellow-50 border border-yellow-200 rounded">
|
||||
<p className="text-sm text-yellow-800">
|
||||
<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
|
||||
|
||||
<div className="mt-3 p-3 bg-blue-50 border border-blue-200 rounded">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user