Enhance Swagger documentation and web UI navigation
Enhanced API Swagger documentation and improved web interface navigation with dropdown menus and better organization. API Changes (api/main.py): ========================== - Enhanced FastAPI app description with architecture diagram - Added detailed rate limiting information - Added server configurations (production + local) - Added contact and license information - Enhanced all endpoint descriptions with: * Detailed parameter descriptions * Response descriptions * Error responses * Rate limit information * Usage examples - Added Field descriptions to all Pydantic models - Added schema examples for better Swagger UI - Enhanced LLM endpoints with AI rate limiting details - Added status codes (201, 404, 429, 500) to endpoints - Improved startup message with docs URLs Swagger UI Improvements: - Better organized endpoint groups (Root, Health, Items, Users, LLM) - Detailed request/response schemas - Interactive examples for all endpoints - Rate limiting documentation - Architecture overview in description Web Changes (web/templates/base.html): ====================================== - Added dropdown menu for API documentation with: * API Root (/) * Swagger UI (/docs) * ReDoc (/redoc) * OpenAPI JSON (/openapi.json) - Added emoji icons to all menu items for better UX - Added tooltips (title attributes) to all links - Renamed "API Config" to "Settings" for clarity - Added CSS for dropdown menu functionality - Improved footer text - Better visual hierarchy with icons Navigation Menu: - 🏠 Home - 📦 Items - 👥 Users - 🤖 LLM Chat - 📚 API Docs (dropdown with 4 options) - ⚙️ Settings All endpoints now have comprehensive documentation visible in Swagger UI at https://commandware.it/api/docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
401
api/main.py
401
api/main.py
@@ -1,8 +1,9 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException, status
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
import os
|
import os
|
||||||
@@ -14,24 +15,93 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "your-api-key")
|
|||||||
DEFAULT_MODEL = os.getenv("DEFAULT_LLM_MODEL", "your-model-id")
|
DEFAULT_MODEL = os.getenv("DEFAULT_LLM_MODEL", "your-model-id")
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="API Demo Application",
|
title="API7 Enterprise Demo API",
|
||||||
description="Demo API with Swagger documentation",
|
description="""
|
||||||
version="1.0.0"
|
## API7 Enterprise Demo Application
|
||||||
|
|
||||||
|
This API demonstrates the capabilities of API7 Enterprise Gateway with:
|
||||||
|
|
||||||
|
* **CRUD Operations** - Items and Users management
|
||||||
|
* **LLM Integration** - AI-powered chat with rate limiting
|
||||||
|
* **Health Checks** - Kubernetes-ready endpoints
|
||||||
|
* **Rate Limiting** - Standard (100 req/min) and AI-based (100 tokens/min)
|
||||||
|
* **CORS** - Cross-origin resource sharing enabled
|
||||||
|
* **Proxy Rewrite** - Automatic /api prefix removal by API7 Gateway
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
Client → Ingress (NGINX) → API7 Gateway → Backend API
|
||||||
|
↓
|
||||||
|
• Rate Limiting
|
||||||
|
• CORS
|
||||||
|
• Proxy Rewrite (/api → /)
|
||||||
|
• Service Discovery
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
- **Standard API** (\`/items\`, \`/users\`): 100 requests per 60 seconds per IP
|
||||||
|
- **LLM API** (\`/llm/*\`): 100 tokens per 60 seconds (AI-based rate limiting)
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- **Swagger UI**: [/docs](/docs)
|
||||||
|
- **ReDoc**: [/redoc](/redoc)
|
||||||
|
- **OpenAPI JSON**: [/openapi.json](/openapi.json)
|
||||||
|
""",
|
||||||
|
version="1.0.0",
|
||||||
|
contact={
|
||||||
|
"name": "CommandWare",
|
||||||
|
"url": "https://commandware.it",
|
||||||
|
},
|
||||||
|
license_info={
|
||||||
|
"name": "MIT",
|
||||||
|
},
|
||||||
|
servers=[
|
||||||
|
{
|
||||||
|
"url": "https://commandware.it/api",
|
||||||
|
"description": "Production server (via API7 Gateway)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8080",
|
||||||
|
"description": "Local development server"
|
||||||
|
}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
class Item(BaseModel):
|
class Item(BaseModel):
|
||||||
id: Optional[int] = None
|
id: Optional[int] = Field(None, description="Item ID (auto-generated)")
|
||||||
name: str
|
name: str = Field(..., description="Item name", example="Laptop")
|
||||||
description: Optional[str] = None
|
description: Optional[str] = Field(None, description="Item description", example="High-performance laptop")
|
||||||
price: float
|
price: float = Field(..., description="Item price", example=999.99, gt=0)
|
||||||
in_stock: bool = True
|
in_stock: bool = Field(True, description="Stock availability")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"name": "Laptop",
|
||||||
|
"description": "High-performance laptop",
|
||||||
|
"price": 999.99,
|
||||||
|
"in_stock": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
id: Optional[int] = None
|
id: Optional[int] = Field(None, description="User ID (auto-generated)")
|
||||||
username: str
|
username: str = Field(..., description="Username", example="john_doe", min_length=3)
|
||||||
email: str
|
email: str = Field(..., description="Email address", example="john@example.com")
|
||||||
active: bool = True
|
active: bool = Field(True, description="User active status")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"username": "john_doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"active": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# In-memory storage
|
# In-memory storage
|
||||||
items_db = [
|
items_db = [
|
||||||
@@ -46,54 +116,150 @@ users_db = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Root endpoint
|
# Root endpoint
|
||||||
@app.get("/")
|
@app.get(
|
||||||
|
"/",
|
||||||
|
tags=["Root"],
|
||||||
|
summary="API Information",
|
||||||
|
response_description="API metadata and links"
|
||||||
|
)
|
||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint with API information"""
|
"""
|
||||||
|
**Root endpoint** with API information and navigation links.
|
||||||
|
|
||||||
|
Returns API version, documentation links, and current timestamp.
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"message": "Welcome to API Demo",
|
"message": "Welcome to API7 Enterprise Demo API",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"docs": "/docs",
|
"documentation": {
|
||||||
|
"swagger": "/docs",
|
||||||
|
"redoc": "/redoc",
|
||||||
|
"openapi": "/openapi.json"
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"items": "/items",
|
||||||
|
"users": "/users",
|
||||||
|
"llm": "/llm"
|
||||||
|
},
|
||||||
"timestamp": datetime.now().isoformat()
|
"timestamp": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
@app.get("/health")
|
@app.get(
|
||||||
|
"/health",
|
||||||
|
tags=["Health"],
|
||||||
|
summary="Health Check",
|
||||||
|
response_description="Service health status"
|
||||||
|
)
|
||||||
async def health():
|
async def health():
|
||||||
"""Health check endpoint"""
|
"""
|
||||||
return {"status": "healthy", "service": "api", "timestamp": datetime.now().isoformat()}
|
**Health check endpoint** for Kubernetes liveness probe.
|
||||||
|
|
||||||
|
Returns service health status and timestamp.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"status": "healthy",
|
||||||
|
"service": "api",
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
# Readiness check
|
# Readiness check
|
||||||
@app.get("/ready")
|
@app.get(
|
||||||
|
"/ready",
|
||||||
|
tags=["Health"],
|
||||||
|
summary="Readiness Check",
|
||||||
|
response_description="Service readiness status"
|
||||||
|
)
|
||||||
async def ready():
|
async def ready():
|
||||||
"""Readiness check endpoint"""
|
"""
|
||||||
return {"status": "ready", "service": "api", "timestamp": datetime.now().isoformat()}
|
**Readiness check endpoint** for Kubernetes readiness probe.
|
||||||
|
|
||||||
|
Returns service readiness status and timestamp.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"status": "ready",
|
||||||
|
"service": "api",
|
||||||
|
"timestamp": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
# Items endpoints
|
# Items endpoints
|
||||||
@app.get("/items", response_model=List[Item], tags=["Items"])
|
@app.get(
|
||||||
|
"/items",
|
||||||
|
response_model=List[Item],
|
||||||
|
tags=["Items"],
|
||||||
|
summary="List all items",
|
||||||
|
response_description="List of all items in inventory"
|
||||||
|
)
|
||||||
async def get_items():
|
async def get_items():
|
||||||
"""Get all items"""
|
"""
|
||||||
|
**Get all items** from the inventory.
|
||||||
|
|
||||||
|
Returns a list of all available items with their details.
|
||||||
|
|
||||||
|
**Rate Limit**: 100 requests per 60 seconds per IP (via API7 Gateway)
|
||||||
|
"""
|
||||||
return items_db
|
return items_db
|
||||||
|
|
||||||
@app.get("/items/{item_id}", response_model=Item, tags=["Items"])
|
@app.get(
|
||||||
|
"/items/{item_id}",
|
||||||
|
response_model=Item,
|
||||||
|
tags=["Items"],
|
||||||
|
summary="Get item by ID",
|
||||||
|
response_description="Item details",
|
||||||
|
responses={404: {"description": "Item not found"}}
|
||||||
|
)
|
||||||
async def get_item(item_id: int):
|
async def get_item(item_id: int):
|
||||||
"""Get a specific item by ID"""
|
"""
|
||||||
|
**Get a specific item** by ID.
|
||||||
|
|
||||||
|
- **item_id**: The ID of the item to retrieve
|
||||||
|
|
||||||
|
Returns item details if found, otherwise 404 error.
|
||||||
|
"""
|
||||||
item = next((item for item in items_db if item["id"] == item_id), None)
|
item = next((item for item in items_db if item["id"] == item_id), None)
|
||||||
if item is None:
|
if item is None:
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
return item
|
return item
|
||||||
|
|
||||||
@app.post("/items", response_model=Item, tags=["Items"])
|
@app.post(
|
||||||
|
"/items",
|
||||||
|
response_model=Item,
|
||||||
|
tags=["Items"],
|
||||||
|
summary="Create new item",
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
response_description="Created item with auto-generated ID"
|
||||||
|
)
|
||||||
async def create_item(item: Item):
|
async def create_item(item: Item):
|
||||||
"""Create a new item"""
|
"""
|
||||||
|
**Create a new item** in the inventory.
|
||||||
|
|
||||||
|
The ID will be auto-generated. Provide:
|
||||||
|
- **name**: Item name (required)
|
||||||
|
- **description**: Item description (optional)
|
||||||
|
- **price**: Item price (required, must be > 0)
|
||||||
|
- **in_stock**: Stock availability (default: true)
|
||||||
|
"""
|
||||||
new_id = max([i["id"] for i in items_db]) + 1 if items_db else 1
|
new_id = max([i["id"] for i in items_db]) + 1 if items_db else 1
|
||||||
item_dict = item.dict()
|
item_dict = item.dict()
|
||||||
item_dict["id"] = new_id
|
item_dict["id"] = new_id
|
||||||
items_db.append(item_dict)
|
items_db.append(item_dict)
|
||||||
return item_dict
|
return item_dict
|
||||||
|
|
||||||
@app.put("/items/{item_id}", response_model=Item, tags=["Items"])
|
@app.put(
|
||||||
|
"/items/{item_id}",
|
||||||
|
response_model=Item,
|
||||||
|
tags=["Items"],
|
||||||
|
summary="Update item",
|
||||||
|
response_description="Updated item",
|
||||||
|
responses={404: {"description": "Item not found"}}
|
||||||
|
)
|
||||||
async def update_item(item_id: int, item: Item):
|
async def update_item(item_id: int, item: Item):
|
||||||
"""Update an existing item"""
|
"""
|
||||||
|
**Update an existing item** by ID.
|
||||||
|
|
||||||
|
- **item_id**: The ID of the item to update
|
||||||
|
- Provide updated item data (name, description, price, in_stock)
|
||||||
|
"""
|
||||||
for idx, existing_item in enumerate(items_db):
|
for idx, existing_item in enumerate(items_db):
|
||||||
if existing_item["id"] == item_id:
|
if existing_item["id"] == item_id:
|
||||||
item_dict = item.dict()
|
item_dict = item.dict()
|
||||||
@@ -102,32 +268,84 @@ async def update_item(item_id: int, item: Item):
|
|||||||
return item_dict
|
return item_dict
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
@app.delete("/items/{item_id}", tags=["Items"])
|
@app.delete(
|
||||||
|
"/items/{item_id}",
|
||||||
|
tags=["Items"],
|
||||||
|
summary="Delete item",
|
||||||
|
status_code=status.HTTP_200_OK,
|
||||||
|
response_description="Deletion confirmation",
|
||||||
|
responses={404: {"description": "Item not found"}}
|
||||||
|
)
|
||||||
async def delete_item(item_id: int):
|
async def delete_item(item_id: int):
|
||||||
"""Delete an item"""
|
"""
|
||||||
|
**Delete an item** from the inventory.
|
||||||
|
|
||||||
|
- **item_id**: The ID of the item to delete
|
||||||
|
|
||||||
|
Returns confirmation message if successful.
|
||||||
|
"""
|
||||||
for idx, item in enumerate(items_db):
|
for idx, item in enumerate(items_db):
|
||||||
if item["id"] == item_id:
|
if item["id"] == item_id:
|
||||||
items_db.pop(idx)
|
items_db.pop(idx)
|
||||||
return {"message": "Item deleted successfully"}
|
return {"message": "Item deleted successfully", "id": item_id}
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
# Users endpoints
|
# Users endpoints
|
||||||
@app.get("/users", response_model=List[User], tags=["Users"])
|
@app.get(
|
||||||
|
"/users",
|
||||||
|
response_model=List[User],
|
||||||
|
tags=["Users"],
|
||||||
|
summary="List all users",
|
||||||
|
response_description="List of all users"
|
||||||
|
)
|
||||||
async def get_users():
|
async def get_users():
|
||||||
"""Get all users"""
|
"""
|
||||||
|
**Get all users** from the system.
|
||||||
|
|
||||||
|
Returns a list of all registered users.
|
||||||
|
|
||||||
|
**Rate Limit**: 100 requests per 60 seconds per IP (via API7 Gateway)
|
||||||
|
"""
|
||||||
return users_db
|
return users_db
|
||||||
|
|
||||||
@app.get("/users/{user_id}", response_model=User, tags=["Users"])
|
@app.get(
|
||||||
|
"/users/{user_id}",
|
||||||
|
response_model=User,
|
||||||
|
tags=["Users"],
|
||||||
|
summary="Get user by ID",
|
||||||
|
response_description="User details",
|
||||||
|
responses={404: {"description": "User not found"}}
|
||||||
|
)
|
||||||
async def get_user(user_id: int):
|
async def get_user(user_id: int):
|
||||||
"""Get a specific user by ID"""
|
"""
|
||||||
|
**Get a specific user** by ID.
|
||||||
|
|
||||||
|
- **user_id**: The ID of the user to retrieve
|
||||||
|
|
||||||
|
Returns user details if found, otherwise 404 error.
|
||||||
|
"""
|
||||||
user = next((user for user in users_db if user["id"] == user_id), None)
|
user = next((user for user in users_db if user["id"] == user_id), None)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@app.post("/users", response_model=User, tags=["Users"])
|
@app.post(
|
||||||
|
"/users",
|
||||||
|
response_model=User,
|
||||||
|
tags=["Users"],
|
||||||
|
summary="Create new user",
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
response_description="Created user with auto-generated ID"
|
||||||
|
)
|
||||||
async def create_user(user: User):
|
async def create_user(user: User):
|
||||||
"""Create a new user"""
|
"""
|
||||||
|
**Create a new user** in the system.
|
||||||
|
|
||||||
|
The ID will be auto-generated. Provide:
|
||||||
|
- **username**: Username (required, min 3 characters)
|
||||||
|
- **email**: Email address (required)
|
||||||
|
- **active**: User active status (default: true)
|
||||||
|
"""
|
||||||
new_id = max([u["id"] for u in users_db]) + 1 if users_db else 1
|
new_id = max([u["id"] for u in users_db]) + 1 if users_db else 1
|
||||||
user_dict = user.dict()
|
user_dict = user.dict()
|
||||||
user_dict["id"] = new_id
|
user_dict["id"] = new_id
|
||||||
@@ -136,22 +354,52 @@ async def create_user(user: User):
|
|||||||
|
|
||||||
# LLM endpoints
|
# LLM endpoints
|
||||||
class LLMRequest(BaseModel):
|
class LLMRequest(BaseModel):
|
||||||
prompt: str
|
prompt: str = Field(..., description="The prompt to send to the LLM", example="What is API7 Enterprise?")
|
||||||
max_tokens: Optional[int] = 150
|
max_tokens: Optional[int] = Field(150, description="Maximum tokens in response", example=150, ge=1, le=4096)
|
||||||
temperature: Optional[float] = 0.7
|
temperature: Optional[float] = Field(0.7, description="Sampling temperature (0-2)", example=0.7, ge=0, le=2)
|
||||||
model: Optional[str] = DEFAULT_MODEL
|
model: Optional[str] = Field(DEFAULT_MODEL, description="Model to use", example="videogame-expert")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"prompt": "What is API7 Enterprise?",
|
||||||
|
"max_tokens": 150,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"model": "videogame-expert"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LLMResponse(BaseModel):
|
class LLMResponse(BaseModel):
|
||||||
response: str
|
response: str = Field(..., description="LLM generated response")
|
||||||
tokens_used: int
|
tokens_used: int = Field(..., description="Total tokens used")
|
||||||
model: str
|
model: str = Field(..., description="Model used for generation")
|
||||||
timestamp: str
|
timestamp: str = Field(..., description="Response timestamp")
|
||||||
|
|
||||||
@app.post("/llm/chat", response_model=LLMResponse, tags=["LLM"])
|
@app.post(
|
||||||
|
"/llm/chat",
|
||||||
|
response_model=LLMResponse,
|
||||||
|
tags=["LLM"],
|
||||||
|
summary="LLM Chat Completion",
|
||||||
|
response_description="LLM generated response",
|
||||||
|
responses={
|
||||||
|
429: {"description": "Rate limit exceeded (100 tokens per 60 seconds)"},
|
||||||
|
500: {"description": "LLM service error"}
|
||||||
|
}
|
||||||
|
)
|
||||||
async def llm_chat(request: LLMRequest):
|
async def llm_chat(request: LLMRequest):
|
||||||
"""
|
"""
|
||||||
LLM Chat endpoint - connects to OpenAI-compatible API (Open WebUI)
|
**LLM Chat endpoint** - connects to OpenAI-compatible API (Open WebUI).
|
||||||
This endpoint is rate limited by AI token usage via API7 Gateway
|
|
||||||
|
This endpoint uses **AI-based rate limiting** via API7 Gateway:
|
||||||
|
- **Limit**: 100 tokens per 60 seconds
|
||||||
|
- **Strategy**: total_tokens (input + output)
|
||||||
|
- **Error**: HTTP 429 when limit exceeded
|
||||||
|
|
||||||
|
Provide:
|
||||||
|
- **prompt**: Your question or prompt
|
||||||
|
- **max_tokens**: Maximum response length (default: 150)
|
||||||
|
- **temperature**: Randomness level 0-2 (default: 0.7)
|
||||||
|
- **model**: Model to use (default: configured model)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
@@ -189,30 +437,67 @@ async def llm_chat(request: LLMRequest):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=f"LLM service error: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"LLM service error: {str(e)}")
|
||||||
|
|
||||||
@app.get("/llm/models", tags=["LLM"])
|
@app.get(
|
||||||
|
"/llm/models",
|
||||||
|
tags=["LLM"],
|
||||||
|
summary="List available LLM models",
|
||||||
|
response_description="List of available models"
|
||||||
|
)
|
||||||
async def list_llm_models():
|
async def list_llm_models():
|
||||||
"""List available LLM models"""
|
"""
|
||||||
|
**List available LLM models**.
|
||||||
|
|
||||||
|
Returns the list of models available through the configured LLM provider (Open WebUI).
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"models": [
|
"models": [
|
||||||
{"id": "videogame-expert", "name": "Videogame Expert", "max_tokens": 4096, "provider": "Open WebUI"}
|
{
|
||||||
|
"id": "videogame-expert",
|
||||||
|
"name": "Videogame Expert",
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"provider": "Open WebUI",
|
||||||
|
"description": "Specialized model for videogame-related questions"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"default_model": DEFAULT_MODEL,
|
"default_model": DEFAULT_MODEL,
|
||||||
|
"provider": "Open WebUI",
|
||||||
"timestamp": datetime.now().isoformat()
|
"timestamp": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.get("/llm/health", tags=["LLM"])
|
@app.get(
|
||||||
|
"/llm/health",
|
||||||
|
tags=["LLM"],
|
||||||
|
summary="LLM service health check",
|
||||||
|
response_description="LLM service health status"
|
||||||
|
)
|
||||||
async def llm_health():
|
async def llm_health():
|
||||||
"""LLM service health check"""
|
"""
|
||||||
|
**LLM service health check**.
|
||||||
|
|
||||||
|
Returns the health status of the LLM integration, including:
|
||||||
|
- Provider information
|
||||||
|
- Endpoint configuration
|
||||||
|
- Rate limit settings
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"service": "llm-api",
|
"service": "llm-api",
|
||||||
"provider": "Open WebUI",
|
"provider": "Open WebUI",
|
||||||
"endpoint": OPENAI_API_BASE,
|
"endpoint": OPENAI_API_BASE,
|
||||||
"default_model": DEFAULT_MODEL,
|
"default_model": DEFAULT_MODEL,
|
||||||
"rate_limit": "ai-rate-limiting enabled (100 tokens/60s)",
|
"rate_limit": {
|
||||||
|
"enabled": True,
|
||||||
|
"limit": 100,
|
||||||
|
"window": "60 seconds",
|
||||||
|
"strategy": "total_tokens",
|
||||||
|
"managed_by": "API7 Gateway (ai-rate-limiting plugin)"
|
||||||
|
},
|
||||||
"timestamp": datetime.now().isoformat()
|
"timestamp": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
port = int(os.getenv("PORT", 8080))
|
port = int(os.getenv("PORT", 8080))
|
||||||
|
print(f"Starting API server on port {port}")
|
||||||
|
print(f"Swagger UI: http://localhost:{port}/docs")
|
||||||
|
print(f"ReDoc: http://localhost:{port}/redoc")
|
||||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||||
|
|||||||
@@ -5,6 +5,52 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{% block title %}API Demo{% endblock %}</title>
|
<title>{% block title %}API Demo{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="/static/css/style.css" />
|
<link rel="stylesheet" href="/static/css/style.css" />
|
||||||
|
<style>
|
||||||
|
/* Dropdown Menu Styles */
|
||||||
|
.nav-menu {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #2c3e50;
|
||||||
|
min-width: 200px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.3);
|
||||||
|
z-index: 1000;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:hover {
|
||||||
|
background-color: #34495e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropbtn {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
@@ -13,15 +59,21 @@
|
|||||||
<h2>🚀 API7EE Demo</h2>
|
<h2>🚀 API7EE Demo</h2>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav-menu">
|
<ul class="nav-menu">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/" title="Home page">🏠 Home</a></li>
|
||||||
<li><a href="/items">Items</a></li>
|
<li><a href="/items" title="Manage items inventory">📦 Items</a></li>
|
||||||
<li><a href="/users">Users</a></li>
|
<li><a href="/users" title="Manage users">👥 Users</a></li>
|
||||||
<li><a href="/llm">LLM Chat</a></li>
|
<li><a href="/llm" title="AI-powered chat">🤖 LLM Chat</a></li>
|
||||||
<li><a href="/api/docs" target="_blank">API Docs</a></li>
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropbtn" title="API Documentation">📚 API Docs ▾</a>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/api/" target="_blank" title="API root endpoint">API Root</a>
|
||||||
|
<a href="/api/docs" target="_blank" title="Swagger UI documentation">Swagger UI</a>
|
||||||
|
<a href="/api/redoc" target="_blank" title="ReDoc documentation">ReDoc</a>
|
||||||
|
<a href="/api/openapi.json" target="_blank" title="OpenAPI JSON schema">OpenAPI JSON</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" onclick="openApiConfig(event)"
|
<a href="#" onclick="openApiConfig(event)" title="Configure API settings">⚙️ Settings</a>
|
||||||
>⚙️ API Config</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +137,7 @@
|
|||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p>© 2025 API7EE Demo | Powered by FastAPI & API7</p>
|
<p>© 2025 API7EE Demo | Powered by FastAPI & API7 Enterprise Gateway</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user