Commit 76c5318f authored by Your Name's avatar Your Name

Add user-specific API endpoints and MCP configuration

- Added /api/user/* endpoints for authenticated users to access their own configurations
- Admin users get access to global + user configs, regular users get user-only
- Global tokens from aisbf.json have full access to all configurations
- Enhanced MCP with user-specific tools for authenticated users
- Updated user dashboard with comprehensive API endpoint documentation
- Updated README.md, DOCUMENTATION.md with new endpoint documentation
- Updated CHANGELOG.md with new features
- Bumped version to 0.9.1
parent 53558e03
...@@ -2,6 +2,21 @@ ...@@ -2,6 +2,21 @@
## [Unreleased] ## [Unreleased]
### Added ### Added
- **User-Specific API Endpoints**: New API endpoints for authenticated users to access their own configurations
- `GET /api/user/models` - List user's own models
- `GET /api/user/providers` - List user's provider configurations
- `GET /api/user/rotations` - List user's rotation configurations
- `GET /api/user/autoselects` - List user's autoselect configurations
- `POST /api/user/chat/completions` - Chat completions using user's own models
- `GET /api/user/{config_type}/models` - List models for specific config type
- Requires Bearer token or query parameter authentication
- Admin users get access to global + user configs, regular users get user-only configs
- Global tokens (in aisbf.json) have full access to all configurations
- **MCP User Configuration**: Enhanced MCP server with user-specific tools for authenticated users
- User can configure their own models, providers, autoselects, and rotations through MCP
- Admin users get access to both global and user tools
- Regular users get access to user-only tools
- **Dashboard API Documentation**: User dashboard now includes comprehensive API endpoint documentation
- **Adaptive Rate Limiting**: Intelligent rate limit management that learns from 429 responses - **Adaptive Rate Limiting**: Intelligent rate limit management that learns from 429 responses
- Per-provider adaptive rate limiters with learning capability - Per-provider adaptive rate limiters with learning capability
- Exponential backoff with jitter (configurable base and jitter factor) - Exponential backoff with jitter (configurable base and jitter factor)
......
...@@ -563,6 +563,68 @@ Cache backend can be configured via the web dashboard or configuration file: ...@@ -563,6 +563,68 @@ Cache backend can be configured via the web dashboard or configuration file:
- Supports both streaming and non-streaming responses - Supports both streaming and non-streaming responses
- `GET /api/autoselect/models` - List all models across all autoselect configurations - `GET /api/autoselect/models` - List all models across all autoselect configurations
### User-Specific API Endpoints
Authenticated users can access their own configurations via user-specific API endpoints. These endpoints require either a valid API token (generated in the user dashboard) or session authentication.
#### Authentication
**Option 1: Bearer Token (Recommended for API access)**
```bash
Authorization: Bearer YOUR_API_TOKEN
```
**Option 2: Query Parameter**
```bash
?token=YOUR_API_TOKEN
```
#### User API Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /api/user/models` | List available models from user's own configurations |
| `GET /api/user/providers` | List user's provider configurations |
| `GET /api/user/rotations` | List user's rotation configurations |
| `GET /api/user/autoselects` | List user's autoselect configurations |
| `POST /api/user/chat/completions` | Chat completions using user's own models |
| `GET /api/user/{config_type}/models` | List models for specific config type (provider, rotation, autoselect) |
**Access Control:**
- **Admin Users** have access to both global and user configurations when using user API endpoints
- **Regular Users** can only access their own configurations
- **Global Tokens** (configured in aisbf.json) have full access to all configurations
#### Example: Using User API with cURL
```bash
# List user models
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:17765/api/user/models
# Chat using user's own models
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "your-rotation/model", "messages": [{"role": "user", "content": "Hello"}]}' \
http://localhost:17765/api/user/chat/completions
```
### MCP (Model Context Protocol)
AISBF provides an MCP server for remote agent configuration and model access:
- **SSE Endpoint**: `GET /mcp` - Server-Sent Events for MCP communication
- **HTTP Endpoint**: `POST /mcp` - Direct HTTP transport for MCP
MCP tools include:
- `list_models` - List available models (user or global depending on auth)
- `chat_completions` - Send chat completion requests
- `get_providers` - Get provider configurations
- `get_rotations` - Get rotation configurations
- `get_autoselects` - Get autoselect configurations
- And more for authenticated users to manage their own configs
User tokens authenticate MCP requests, with admin users getting full access and regular users getting user-only access.
## Provider Support ## Provider Support
AISBF supports the following AI providers: AISBF supports the following AI providers:
......
...@@ -648,6 +648,69 @@ These endpoints are maintained for backward compatibility: ...@@ -648,6 +648,69 @@ These endpoints are maintained for backward compatibility:
- `GET /api/autoselect/models` - List all models across all autoselect configurations - `GET /api/autoselect/models` - List all models across all autoselect configurations
- `GET /api/{provider_id}/models` - List available models for a specific provider, rotation, or autoselect - `GET /api/{provider_id}/models` - List available models for a specific provider, rotation, or autoselect
### User-Specific API Endpoints
Authenticated users can access their own configurations via user-specific API endpoints. These endpoints require either a valid API token (generated in the user dashboard) or session authentication.
#### Authentication
**Option 1: Bearer Token (Recommended for API access)**
```bash
Authorization: Bearer YOUR_API_TOKEN
```
**Option 2: Query Parameter**
```bash
?token=YOUR_API_TOKEN
```
#### User API Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /api/user/models` | List available models from user's own configurations |
| `GET /api/user/providers` | List user's provider configurations |
| `GET /api/user/rotations` | List user's rotation configurations |
| `GET /api/user/autoselects` | List user's autoselect configurations |
| `POST /api/user/chat/completions` | Chat completions using user's own models |
| `GET /api/user/{config_type}/models` | List models for specific config type (provider, rotation, autoselect) |
**Admin Users** have access to both global and user configurations when using user API endpoints.
**Regular Users** can only access their own configurations.
**Global Tokens** (configured in aisbf.json) have full access to all configurations.
#### Example: Using User API with cURL
```bash
# List user models
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:17765/api/user/models
# Chat using user's own models
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "your-rotation/model", "messages": [{"role": "user", "content": "Hello"}]}' \
http://localhost:17765/api/user/chat/completions
```
### MCP (Model Context Protocol)
AISBF provides an MCP server for remote agent configuration and model access:
- **SSE Endpoint**: `GET /mcp` - Server-Sent Events for MCP communication
- **HTTP Endpoint**: `POST /mcp` - Direct HTTP transport for MCP
MCP tools include:
- `list_models` - List available models (user or global depending on auth)
- `chat_completions` - Send chat completion requests
- `get_providers` - Get provider configurations
- `get_rotations` - Get rotation configurations
- `get_autoselects` - Get autoselect configurations
- And more for authenticated users to manage their own configs
User tokens authenticate MCP requests, with admin users getting full access and regular users getting user-only access.
### Content Proxy ### Content Proxy
- `GET /api/proxy/{content_id}` - Proxy generated content (images, audio, etc.) - `GET /api/proxy/{content_id}` - Proxy generated content (images, audio, etc.)
......
...@@ -141,6 +141,16 @@ class AutoselectConfig(BaseModel): ...@@ -141,6 +141,16 @@ class AutoselectConfig(BaseModel):
pricing: Optional[Dict] = None pricing: Optional[Dict] = None
supported_parameters: Optional[List[str]] = None supported_parameters: Optional[List[str]] = None
default_parameters: Optional[Dict] = None default_parameters: Optional[Dict] = None
# Default settings for models in this autoselect
default_rate_limit: Optional[float] = None
default_max_request_tokens: Optional[int] = None
default_rate_limit_TPM: Optional[int] = None
default_rate_limit_TPH: Optional[int] = None
default_rate_limit_TPD: Optional[int] = None
default_context_size: Optional[int] = None
default_condense_context: Optional[int] = None
default_condense_method: Optional[Union[str, List[str]]] = None
default_error_cooldown: Optional[int] = None # Default cooldown period in seconds after 3 consecutive failures (default: 300)
# Response caching control # Response caching control
enable_response_cache: Optional[bool] = None # Enable/disable response caching for this autoselect (None = use global default) enable_response_cache: Optional[bool] = None # Enable/disable response caching for this autoselect (None = use global default)
......
...@@ -971,9 +971,10 @@ def get_context_config_for_model( ...@@ -971,9 +971,10 @@ def get_context_config_for_model(
Priority order for each field: Priority order for each field:
1. Rotation model config (if explicitly set) 1. Rotation model config (if explicitly set)
2. Provider model-specific config (if exists) 2. Model-specific config in provider (if exists)
3. Provider default config (fallback) 3. First model in provider (auto-derived from dynamic fetch)
4. System default (0 for condense_context, None for others) 4. Provider default config (fallback)
5. System default (0 for condense_context, None for others)
Args: Args:
model_name: Name of the model model_name: Name of the model
...@@ -1007,12 +1008,23 @@ def get_context_config_for_model( ...@@ -1007,12 +1008,23 @@ def get_context_config_for_model(
model_specific_config = model model_specific_config = model
break break
# Build base config from provider (model-specific > provider defaults) # Build base config from provider (model-specific > first model > provider defaults)
# context_size # context_size
if model_specific_config and model_specific_config.get('context_size') is not None: if model_specific_config and model_specific_config.get('context_size') is not None:
context_config['context_size'] = model_specific_config.get('context_size') context_config['context_size'] = model_specific_config.get('context_size')
elif hasattr(provider_config, 'default_context_size') and provider_config.default_context_size is not None: elif hasattr(provider_config, 'default_context_size') and provider_config.default_context_size is not None:
context_config['context_size'] = provider_config.default_context_size context_config['context_size'] = provider_config.default_context_size
else:
# Auto-derive from first model in provider (has context_size from dynamic fetch)
if provider_config.models and len(provider_config.models) > 0:
first_model = provider_config.models[0]
# Check for context_size in the first model (from dynamic fetch)
if hasattr(first_model, 'context_size') and first_model.context_size:
context_config['context_size'] = first_model.context_size
elif hasattr(first_model, 'context_window') and first_model.context_window:
context_config['context_size'] = first_model.context_window
elif hasattr(first_model, 'context_length') and first_model.context_length:
context_config['context_size'] = first_model.context_length
# condense_context # condense_context
if model_specific_config and model_specific_config.get('condense_context') is not None: if model_specific_config and model_specific_config.get('condense_context') is not None:
...@@ -1033,11 +1045,69 @@ def get_context_config_for_model( ...@@ -1033,11 +1045,69 @@ def get_context_config_for_model(
# Only override if the field is explicitly set in rotation config # Only override if the field is explicitly set in rotation config
if 'context_size' in rotation_model_config and rotation_model_config['context_size'] is not None: if 'context_size' in rotation_model_config and rotation_model_config['context_size'] is not None:
context_config['context_size'] = rotation_model_config['context_size'] context_config['context_size'] = rotation_model_config['context_size']
elif context_config.get('context_size') is None:
# If context_size is still None, check if rotation_model_config has default_context_size
# (This handles the case where rotation-level default should be used)
if 'default_context_size' in rotation_model_config and rotation_model_config['default_context_size'] is not None:
context_config['context_size'] = rotation_model_config['default_context_size']
if 'condense_context' in rotation_model_config and rotation_model_config['condense_context'] is not None: if 'condense_context' in rotation_model_config and rotation_model_config['condense_context'] is not None:
context_config['condense_context'] = rotation_model_config['condense_context'] context_config['condense_context'] = rotation_model_config['condense_context']
elif context_config.get('condense_context') == 0:
# If condense_context is still at default (0), check if rotation has default
if 'default_condense_context' in rotation_model_config and rotation_model_config['default_condense_context'] is not None:
context_config['condense_context'] = rotation_model_config['default_condense_context']
if 'condense_method' in rotation_model_config and rotation_model_config['condense_method'] is not None: if 'condense_method' in rotation_model_config and rotation_model_config['condense_method'] is not None:
context_config['condense_method'] = rotation_model_config['condense_method'] context_config['condense_method'] = rotation_model_config['condense_method']
return context_config # Step 3: Final fallback if context_size is still None
\ No newline at end of file # Use inference based on model name patterns
if context_config.get('context_size') is None:
context_config['context_size'] = _infer_context_size_from_model(model_name)
return context_config
def _infer_context_size_from_model(model_name: str) -> int:
"""
Infer context window size from model name patterns.
Args:
model_name: Name of the model
Returns:
Inferred context size in tokens
"""
model_lower = model_name.lower()
# Known model patterns
if 'gpt-4' in model_lower:
if 'turbo' in model_lower or '1106' in model_lower or '0125' in model_lower:
return 128000
return 8192
elif 'gpt-3.5' in model_lower:
if 'turbo' in model_lower and ('1106' in model_lower or '0125' in model_lower):
return 16385
return 4096
elif 'claude-3' in model_lower:
return 200000
elif 'claude-2' in model_lower:
return 100000
elif 'gemini' in model_lower:
if '1.5' in model_lower:
return 2000000 if 'pro' in model_lower else 1000000
elif '2.0' in model_lower:
return 1000000
return 32000
elif 'llama' in model_lower:
if '3' in model_lower:
return 128000
return 4096
elif 'mistral' in model_lower:
if 'large' in model_lower:
return 32000
return 8192
# Generic default
return 8192
\ No newline at end of file
...@@ -1505,7 +1505,8 @@ class RotationHandler: ...@@ -1505,7 +1505,8 @@ class RotationHandler:
Priority order: Priority order:
1. Model-specific settings (highest priority) 1. Model-specific settings (highest priority)
2. Rotation default settings 2. Rotation default settings
3. Provider default settings (lowest priority) 3. Provider default settings
4. Auto-derived from first model in provider (lowest priority)
Args: Args:
model: The model configuration dict model: The model configuration dict
...@@ -1539,9 +1540,108 @@ class RotationHandler: ...@@ -1539,9 +1540,108 @@ class RotationHandler:
provider_default = getattr(provider_config, f'default_{field}', None) provider_default = getattr(provider_config, f'default_{field}', None)
if provider_default is not None: if provider_default is not None:
model[field] = provider_default model[field] = provider_default
else:
# Auto-derive from first model in provider config if available
if provider_config and provider_config.models and len(provider_config.models) > 0:
first_model = provider_config.models[0]
# For context_size, check multiple field names (from dynamic fetch)
if field == 'context_size':
model_field = getattr(first_model, 'context_size', None)
if model_field is None:
model_field = getattr(first_model, 'context_window', None)
if model_field is None:
model_field = getattr(first_model, 'context_length', None)
else:
model_field = getattr(first_model, field, None)
if model_field is not None:
model[field] = model_field
return model return model
def _apply_defaults_to_autoselect_model(self, model_config: Dict, autoselect_config) -> Dict:
"""
Apply default settings to an autoselect model configuration.
Priority order:
1. Model-specific settings (highest priority)
2. Autoselect default settings
3. Auto-derived from first model in rotation (lowest priority)
Args:
model_config: The model configuration dict (typically a rotation_id from autoselect)
autoselect_config: The autoselect configuration
Returns:
Model dict with defaults applied
"""
import logging
logger = logging.getLogger(__name__)
# List of fields that can have defaults
default_fields = [
'rate_limit',
'max_request_tokens',
'rate_limit_TPM',
'rate_limit_TPH',
'rate_limit_TPD',
'context_size',
'condense_context',
'condense_method'
]
# First, check if the model_config is a rotation ID and get its settings
model_id = model_config.get('model_id') or model_config.get('name') or model_config.get('id', '')
# Try to get defaults from the referenced rotation (first model in the rotation)
if model_id in self.config.rotations:
rotation_config = self.config.rotations[model_id]
# Check each default field
for field in default_fields:
# If field is not set in model, try autoselect defaults, then rotation defaults
if field not in model_config or model_config.get(field) is None:
# Try autoselect defaults first
autoselect_default = getattr(autoselect_config, f'default_{field}', None)
if autoselect_default is not None:
model_config[field] = autoselect_default
logger.debug(f"Applied autoselect default_{field}: {autoselect_default} to model {model_id}")
else:
# Try rotation defaults
rotation_default = getattr(rotation_config, f'default_{field}', None)
if rotation_default is not None:
model_config[field] = rotation_default
logger.debug(f"Applied rotation default_{field}: {rotation_default} to model {model_id}")
else:
# Auto-derive from first provider in rotation, then first model
if rotation_config.providers and len(rotation_config.providers) > 0:
first_provider = rotation_config.providers[0]
provider_id = first_provider.get('provider_id')
provider_config = self.config.get_provider(provider_id)
if provider_config and provider_config.models and len(provider_config.models) > 0:
first_model = provider_config.models[0]
# Check for context_size, context_window, or context_length
if field == 'context_size':
model_field = getattr(first_model, 'context_size', None)
if model_field is None:
model_field = getattr(first_model, 'context_window', None)
if model_field is None:
model_field = getattr(first_model, 'context_length', None)
else:
model_field = getattr(first_model, field, None)
if model_field is not None:
model_config[field] = model_field
logger.debug(f"Auto-derived default_{field}: {model_field} from first model in {provider_id}")
else:
# Not a rotation, apply autoselect defaults directly
for field in default_fields:
if field not in model_config or model_config.get(field) is None:
autoselect_default = getattr(autoselect_config, f'default_{field}', None)
if autoselect_default is not None:
model_config[field] = autoselect_default
return model_config
async def _handle_chunked_rotation_request( async def _handle_chunked_rotation_request(
self, self,
handler, handler,
...@@ -2994,16 +3094,31 @@ class RotationHandler: ...@@ -2994,16 +3094,31 @@ class RotationHandler:
} }
# Add context window information # Add context window information
# Priority: model config in rotation > provider config > first model in provider
if model.get('context_size'): if model.get('context_size'):
model_dict['context_window'] = model['context_size'] model_dict['context_window'] = model['context_size']
elif provider_config: elif provider_config:
# Try to find in provider config # Try to find in provider config
found_in_provider = False
for pm in provider_config.models or []: for pm in provider_config.models or []:
if pm.name == model_name and hasattr(pm, 'context_size'): if pm.name == model_name and hasattr(pm, 'context_size') and pm.context_size:
model_dict['context_window'] = pm.context_size model_dict['context_window'] = pm.context_size
found_in_provider = True
break break
if 'context_window' not in model_dict: if not found_in_provider:
model_dict['context_window'] = self._infer_context_window(model_name, provider_config.type) # Auto-derive from first model in provider (which has context_size from dynamic fetch)
if provider_config.models and len(provider_config.models) > 0:
first_model = provider_config.models[0]
if hasattr(first_model, 'context_size') and first_model.context_size:
model_dict['context_window'] = first_model.context_size
elif hasattr(first_model, 'context_window') and first_model.context_window:
model_dict['context_window'] = first_model.context_window
elif hasattr(first_model, 'context_length') and first_model.context_length:
model_dict['context_window'] = first_model.context_length
else:
model_dict['context_window'] = self._infer_context_window(model_name, provider_config.type)
else:
model_dict['context_window'] = self._infer_context_window(model_name, provider_config.type)
# Add capabilities information # Add capabilities information
if model.get('capabilities'): if model.get('capabilities'):
......
This diff is collapsed.
This diff is collapsed.
...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" ...@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "aisbf" name = "aisbf"
version = "0.9.0" version = "0.9.1"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations" description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md" readme = "README.md"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
......
...@@ -49,7 +49,7 @@ class InstallCommand(_install): ...@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup( setup(
name="aisbf", name="aisbf",
version="0.9.0", version="0.9.1",
author="AISBF Contributors", author="AISBF Contributors",
author_email="stefy@nexlab.net", author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations", description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
......
...@@ -197,6 +197,50 @@ function renderAutoselectDetails(autoselectKey) { ...@@ -197,6 +197,50 @@ function renderAutoselectDetails(autoselectKey) {
<small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Choose from rotations or provider models</small> <small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Choose from rotations or provider models</small>
</div> </div>
<h4 style="margin-top: 20px; margin-bottom: 10px;">Default Settings</h4>
<p style="color: #a0a0a0; font-size: 14px; margin-bottom: 10px;">Default values for models in this autoselect (optional - auto-derived from first model if not set)</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Default Rate Limit (seconds)</label>
<input type="number" value="${autoselect.default_rate_limit || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_rate_limit', this.value ? parseFloat(this.value) : null)" step="0.1" placeholder="Optional">
</div>
<div class="form-group">
<label>Default Max Request Tokens</label>
<input type="number" value="${autoselect.default_max_request_tokens || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_max_request_tokens', this.value ? parseInt(this.value) : null)" placeholder="Optional">
</div>
<div class="form-group">
<label>Default Context Size</label>
<input type="number" value="${autoselect.default_context_size || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_context_size', this.value ? parseInt(this.value) : null)" placeholder="Optional">
</div>
<div class="form-group">
<label>Default Rate Limit TPM</label>
<input type="number" value="${autoselect.default_rate_limit_TPM || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_rate_limit_TPM', this.value ? parseInt(this.value) : null)" placeholder="Optional">
<small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Tokens per minute limit</small>
</div>
<div class="form-group">
<label>Default Rate Limit TPH</label>
<input type="number" value="${autoselect.default_rate_limit_TPH || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_rate_limit_TPH', this.value ? parseInt(this.value) : null)" placeholder="Optional">
<small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Tokens per hour limit</small>
</div>
<div class="form-group">
<label>Default Rate Limit TPD</label>
<input type="number" value="${autoselect.default_rate_limit_TPD || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_rate_limit_TPD', this.value ? parseInt(this.value) : null)" placeholder="Optional">
<small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Tokens per day limit</small>
</div>
<div class="form-group">
<label>Default Condense Context</label>
<input type="number" value="${autoselect.default_condense_context || ''}" onchange="updateAutoselect('${autoselectKey}', 'default_condense_context', this.value ? parseInt(this.value) : null)" placeholder="Optional">
<small style="color: #a0a0a0; font-size: 12px; display: block; margin-top: 5px;">Trigger context condensation at this token count</small>
</div>
</div>
<h4 style="margin-top: 20px; margin-bottom: 10px;">Available Models</h4> <h4 style="margin-top: 20px; margin-bottom: 10px;">Available Models</h4>
<p style="color: #a0a0a0; font-size: 14px; margin-bottom: 10px;">Define which models can be selected and their descriptions for AI analysis</p> <p style="color: #a0a0a0; font-size: 14px; margin-bottom: 10px;">Define which models can be selected and their descriptions for AI analysis</p>
<div id="models-${autoselectKey}"></div> <div id="models-${autoselectKey}"></div>
......
...@@ -15,6 +15,83 @@ ...@@ -15,6 +15,83 @@
<div class="alert alert-error">{{ error }}</div> <div class="alert alert-error">{{ error }}</div>
{% endif %} {% endif %}
<!-- API Documentation Section -->
<div class="card">
<h2>🔌 Your API Endpoints</h2>
<p>Use your API token to access your personal configurations. Include the token in the Authorization header:</p>
<pre style="background: #1a1a2e; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code>Authorization: Bearer YOUR_API_TOKEN</code></pre>
<div class="api-endpoints">
<h3>Your Models</h3>
<div class="endpoint">
<code class="method GET">GET</code>
<code class="url">/api/user/models</code>
<p>List all models from your providers, rotations, and autoselects</p>
</div>
<h3>Your Providers</h3>
<div class="endpoint">
<code class="method GET">GET</code>
<code class="url">/api/user/providers</code>
<p>List all your configured providers</p>
</div>
<h3>Your Rotations</h3>
<div class="endpoint">
<code class="method GET">GET</code>
<code class="url">/api/user/rotations</code>
<p>List all your configured rotations</p>
</div>
<h3>Your Autoselects</h3>
<div class="endpoint">
<code class="method GET">GET</code>
<code class="url">/api/user/autoselects</code>
<p>List all your configured autoselects</p>
</div>
<h3>Your Chat Completions</h3>
<div class="endpoint">
<code class="method POST">POST</code>
<code class="url">/api/user/chat/completions</code>
<p>Send chat requests using your configurations</p>
<p class="example">Example model formats:</p>
<ul>
<li><code>user-provider/myprovider/mymodel</code></li>
<li><code>user-rotation/myrotation</code></li>
<li><code>user-autoselect/myautoselect</code></li>
</ul>
</div>
<h3>MCP Tools</h3>
<div class="endpoint">
<code class="method GET">GET</code>
<code class="url">/mcp/tools</code>
<p>List available MCP tools for your configurations</p>
</div>
<div class="endpoint">
<code class="method POST">POST</code>
<code class="url">/mcp/tools/call</code>
<p>Call MCP tools to manage your configurations</p>
</div>
</div>
{% if session.role == 'admin' %}
<div class="admin-notice">
<h4>⚡ Admin Access</h4>
<p>As an admin user, you also have access to global configurations (providers, rotations, autoselects configured by the admin) in addition to your own user configurations.</p>
<p class="example">Admin model formats:</p>
<ul>
<li><code>provider/model</code> - global provider</li>
<li><code>rotation/myrotation</code> - global rotation</li>
<li><code>autoselect/myautoselect</code> - global autoselect</li>
</ul>
</div>
{% endif %}
<p class="note">⚠️ Note: Your API token is required for all these endpoints. Manage your tokens in the <a href="{{ url_for(request, '/dashboard/user/tokens') }}">API Tokens</a> section.</p>
</div>
<!-- Usage Statistics --> <!-- Usage Statistics -->
<div class="card"> <div class="card">
<h2>Usage Statistics</h2> <h2>Usage Statistics</h2>
...@@ -136,5 +213,116 @@ ...@@ -136,5 +213,116 @@
.table tbody tr:hover { .table tbody tr:hover {
background: #1a1a2e; background: #1a1a2e;
} }
/* API Endpoints Section */
.api-endpoints {
margin-top: 1rem;
}
.api-endpoints h3 {
margin-top: 1.5rem;
color: #e0e0e0;
font-size: 1.1rem;
}
.endpoint {
background: #0f3460;
padding: 1rem;
margin: 0.5rem 0;
border-radius: 4px;
border-left: 3px solid #667eea;
}
.endpoint .method {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-weight: bold;
font-size: 0.8rem;
margin-right: 0.5rem;
}
.endpoint .method.GET {
background: #28a745;
color: white;
}
.endpoint .method.POST {
background: #007bff;
color: white;
}
.endpoint .url {
color: #e0e0e0;
font-family: monospace;
}
.endpoint p {
margin: 0.5rem 0 0 0;
color: #a0a0a0;
font-size: 0.9rem;
}
.endpoint .example {
color: #667eea;
}
.endpoint ul {
margin: 0.5rem 0 0 0;
padding-left: 1.5rem;
color: #a0a0a0;
}
.endpoint code {
background: #1a1a2e;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-size: 0.9rem;
}
.note {
margin-top: 1rem;
padding: 1rem;
background: #1a1a2e;
border-radius: 4px;
color: #a0a0a0;
}
.note a {
color: #667eea;
}
.admin-notice {
margin-top: 1.5rem;
padding: 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
color: white;
}
.admin-notice h4 {
margin: 0 0 0.5rem 0;
color: white;
}
.admin-notice p {
margin: 0.5rem 0;
color: white;
}
.admin-notice .example {
color: #ffd700;
}
.admin-notice ul {
margin: 0.5rem 0 0 0;
padding-left: 1.5rem;
}
.admin-notice code {
background: rgba(0,0,0,0.3);
padding: 0.2rem 0.4rem;
border-radius: 3px;
}
</style> </style>
{% endblock %} {% endblock %}
...@@ -15,6 +15,74 @@ ...@@ -15,6 +15,74 @@
<div class="alert alert-error">{{ error }}</div> <div class="alert alert-error">{{ error }}</div>
{% endif %} {% endif %}
<!-- API Documentation -->
<div class="card">
<h2>🔌 How to Use Your Token</h2>
<p>Include your API token in the <code>Authorization</code> header when making requests:</p>
<pre style="background: #1a1a2e; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code>Authorization: Bearer YOUR_API_TOKEN</code></pre>
<h3>Available Endpoints:</h3>
<table class="endpoints-table">
<thead>
<tr>
<th>Method</th>
<th>Endpoint</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="method GET">GET</code></td>
<td><code>/api/user/models</code></td>
<td>List your models</td>
</tr>
<tr>
<td><code class="method GET">GET</code></td>
<td><code>/api/user/providers</code></td>
<td>List your providers</td>
</tr>
<tr>
<td><code class="method GET">GET</code></td>
<td><code>/api/user/rotations</code></td>
<td>List your rotations</td>
</tr>
<tr>
<td><code class="method GET">GET</code></td>
<td><code>/api/user/autoselects</code></td>
<td>List your autoselects</td>
</tr>
<tr>
<td><code class="method POST">POST</code></td>
<td><code>/api/user/chat/completions</code></td>
<td>Chat using your configs</td>
</tr>
<tr>
<td><code class="method GET">GET</code></td>
<td><code>/mcp/tools</code></td>
<td>List MCP tools</td>
</tr>
<tr>
<td><code class="method POST">POST</code></td>
<td><code>/mcp/tools/call</code></td>
<td>Call MCP tools</td>
</tr>
</tbody>
</table>
<h3>Example curl commands:</h3>
<pre style="background: #1a1a2e; padding: 1rem; border-radius: 4px; overflow-x: auto;"><code># List your models
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:17765/api/user/models
# List your providers
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:17765/api/user/providers
# Send a chat request
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "user-rotation/myrotation", "messages": [{"role": "user", "content": "Hello"}]}' \
http://localhost:17765/api/user/chat/completions</code></pre>
</div>
<div class="card"> <div class="card">
<h2>API Tokens</h2> <h2>API Tokens</h2>
<div id="tokens-list"> <div id="tokens-list">
...@@ -356,5 +424,42 @@ renderTokens(); ...@@ -356,5 +424,42 @@ renderTokens();
font-family: monospace; font-family: monospace;
word-break: break-all; word-break: break-all;
} }
/* API Endpoints Table */
.endpoints-table {
width: 100%;
margin: 1rem 0;
border-collapse: collapse;
}
.endpoints-table th,
.endpoints-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #333;
}
.endpoints-table th {
background: #1a1a2e;
font-weight: bold;
}
.endpoints-table .method {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 3px;
font-weight: bold;
font-size: 0.75rem;
}
.endpoints-table .method.GET {
background: #28a745;
color: white;
}
.endpoints-table .method.POST {
background: #007bff;
color: white;
}
</style> </style>
{% endblock %} {% endblock %}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment