Commit b92c6b3d authored by Your Name's avatar Your Name

feat: align user dashboard templates with admin, separate user/admin config visibility

- Make user_rotations.html and user_autoselects.html identical to admin templates
- Remove global config fallback for database users in handlers
- Separate provider/rotation visibility: users only see their own, admin only global
- Update version to 0.99.38
parent 2b07302d
......@@ -167,3 +167,4 @@ Thumbs.db
# Worktrees
.worktrees/
docs/superpowers/
......@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model
__version__ = "0.99.36"
__version__ = "0.99.38"
__all__ = [
# Config
"config",
......
......@@ -1971,10 +1971,15 @@ class RotationHandler:
# Load user-specific configs if user_id is provided
if user_id:
self._load_user_configs()
# Override config to only use user-specific configs with NO global fallback
self.rotations = {}
for rotation in self.user_rotations:
self.rotations[rotation['rotation_id']] = rotation['config']
else:
self.user_providers = {}
self.user_rotations = {}
self.user_autoselects = {}
self.rotations = self.config.rotations if hasattr(self.config, 'rotations') else {}
def _load_user_configs(self):
"""Load user-specific configurations from database"""
......@@ -1988,16 +1993,10 @@ class RotationHandler:
"""Reload user-specific configurations from database"""
if self.user_id:
self._load_user_configs()
def reload_user_configs(self):
"""Reload user-specific configurations from database"""
if self.user_id:
self._load_user_configs()
def reload_user_configs(self):
"""Reload user-specific configurations from database"""
if self.user_id:
self._load_user_configs()
# Refresh rotations dict after reload
self.rotations = {}
for rotation in self.user_rotations:
self.rotations[rotation['rotation_id']] = rotation['config']
def _get_provider_type(self, provider_id: str) -> str:
"""Get the provider type from configuration"""
......@@ -2101,7 +2100,9 @@ class RotationHandler:
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:
if self.user_id and model_id in self.rotations:
rotation_config = self.rotations[model_id]
elif model_id in self.config.rotations:
rotation_config = self.config.rotations[model_id]
# Check each default field
......@@ -2332,10 +2333,16 @@ class RotationHandler:
logger.info(f"User ID: {self.user_id}")
# Check for user-specific rotation config first
if self.user_id and rotation_id in self.user_rotations:
rotation_config = self.user_rotations[rotation_id]
logger.info(f"Using user-specific rotation config for {rotation_id}")
if self.user_id:
# Database user: ONLY use user-specific configs - NO global fallback
rotation_config = next((rot['config'] for rot in self.user_rotations if rot['rotation_id'] == rotation_id), None)
if rotation_config:
logger.info(f"Using user-specific rotation config for {rotation_id}")
else:
logger.error(f"User rotation {rotation_id} not found - NO global fallback")
raise HTTPException(status_code=400, detail=f"Rotation {rotation_id} not found for this user")
else:
# Admin user: use global config
rotation_config = self.config.get_rotation(rotation_id)
logger.info(f"Using global rotation config for {rotation_id}")
......@@ -3857,10 +3864,15 @@ class AutoselectHandler:
# Load user-specific configs if user_id is provided
if user_id:
self._load_user_configs()
# Override config to only use user-specific configs with NO global fallback
self.autoselects = {}
for autoselect in self.user_autoselects:
self.autoselects[autoselect['autoselect_id']] = autoselect['config']
else:
self.user_providers = {}
self.user_rotations = {}
self.user_autoselects = {}
self.autoselects = self.config.autoselect if hasattr(self.config, 'autoselect') else {}
def _load_user_configs(self):
"""Load user-specific configurations from database"""
......@@ -3869,6 +3881,15 @@ class AutoselectHandler:
self.user_providers = db.get_user_providers(self.user_id)
self.user_rotations = db.get_user_rotations(self.user_id)
self.user_autoselects = db.get_user_autoselects(self.user_id)
def reload_user_configs(self):
"""Reload user-specific configurations from database"""
if self.user_id:
self._load_user_configs()
# Refresh autoselects dict after reload
self.autoselects = {}
for autoselect in self.user_autoselects:
self.autoselects[autoselect['autoselect_id']] = autoselect['config']
def _get_skill_file_content(self) -> str:
"""Load the autoselect.md skill file content"""
......@@ -4158,9 +4179,9 @@ class AutoselectHandler:
return model_id
# Check if it's a rotation
elif selection_model in self.config.rotations:
elif (self.user_id and selection_model in self.rotations) or selection_model in self.config.rotations:
logger.info(f"Selection model '{selection_model}' is a rotation")
rotation_handler = RotationHandler()
rotation_handler = RotationHandler(user_id=self.user_id)
response = await rotation_handler.handle_rotation_request(selection_model, selection_request)
# Check if it's a provider/model format (e.g., "gemini/gemini-pro")
elif '/' in selection_model:
......@@ -4255,10 +4276,16 @@ class AutoselectHandler:
logger.warning(f"Response cache check failed: {cache_error}")
# Check for user-specific autoselect config first
if self.user_id and autoselect_id in self.user_autoselects:
autoselect_config = self.user_autoselects[autoselect_id]
logger.info(f"Using user-specific autoselect config for {autoselect_id}")
if self.user_id:
# Database user: ONLY use user-specific configs - NO global fallback
autoselect_config = next((aut['config'] for aut in self.user_autoselects if aut['autoselect_id'] == autoselect_id), None)
if autoselect_config:
logger.info(f"Using user-specific autoselect config for {autoselect_id}")
else:
logger.error(f"User autoselect {autoselect_id} not found - NO global fallback")
raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found for this user")
else:
# Admin user: use global config
autoselect_config = self.config.get_autoselect(autoselect_id)
logger.info(f"Using global autoselect config for {autoselect_id}")
......@@ -4560,9 +4587,9 @@ class AutoselectHandler:
request_data['stream'] = True
# Check if it's a rotation first
if selected_model_id in self.config.rotations:
if (self.user_id and selected_model_id in self.rotations) or selected_model_id in self.config.rotations:
logger.info(f"Proxying streaming request to rotation: {selected_model_id}")
rotation_handler = RotationHandler()
rotation_handler = RotationHandler(user_id=self.user_id)
response = await rotation_handler.handle_rotation_request(selected_model_id, request_data)
# Check if it's a provider/model format (e.g., "gemini/gemini-pro")
elif '/' in selected_model_id:
......
......@@ -4198,8 +4198,16 @@ async def dashboard_rotations(request: Request):
for rotation in user_rotations:
rotations_data["rotations"][rotation['rotation_id']] = rotation['config']
# Get available providers
available_providers = list(config.providers.keys()) if config else []
# Get available providers - user-specific for database users
if is_config_admin:
# Admin: use global providers
available_providers = list(config.providers.keys()) if config else []
else:
# Database user: use ONLY their own providers
from aisbf.database import get_database
db = DatabaseRegistry.get_config_database()
user_providers = db.get_user_providers(current_user_id)
available_providers = [p['provider_id'] for p in user_providers]
# Check for success parameter
success = request.query_params.get('success')
......@@ -4218,7 +4226,7 @@ async def dashboard_rotations(request: Request):
}
)
else:
# Database user: use user template with proper context
# Database user: use user template
return templates.TemplateResponse(
request=request,
name="dashboard/user_rotations.html",
......@@ -4226,9 +4234,8 @@ async def dashboard_rotations(request: Request):
"request": request,
"session": request.session,
"__version__": __version__,
"user_rotations_json": json.dumps(rotations_data),
"rotations_json": json.dumps(rotations_data),
"available_providers": json.dumps(available_providers),
"user_id": current_user_id,
"success": "Configuration saved successfully!" if success else None
}
)
......@@ -4399,44 +4406,42 @@ async def dashboard_autoselect(request: Request):
for autoselect in user_autoselects:
autoselect_data[autoselect['autoselect_id']] = autoselect['config']
# Get available rotations
available_rotations = list(config.rotations.keys()) if config else []
# Get available provider models
available_models = []
# Add rotation IDs
for rotation_id in available_rotations:
available_models.append({
'id': rotation_id,
'name': f'{rotation_id} (rotation)',
'type': 'rotation'
})
# Add provider models
providers_path = Path.home() / '.aisbf' / 'providers.json'
if not providers_path.exists():
providers_path = Path(__file__).parent / 'config' / 'providers.json'
if providers_path.exists():
with open(providers_path) as f:
providers_config = json.load(f)
providers_data = providers_config.get('providers', {})
for provider_id, provider in providers_data.items():
if 'models' in provider and isinstance(provider['models'], list):
for model in provider['models']:
model_id = f"{provider_id}/{model['name']}"
available_models.append({
'id': model_id,
'name': f"{model_id} (provider model)",
'type': 'provider'
})
# Check for success parameter
success = request.query_params.get('success')
if is_config_admin:
# Admin: use global rotations and providers
available_rotations = list(config.rotations.keys()) if config else []
available_models = []
# Add global rotation IDs
for rotation_id in available_rotations:
available_models.append({
'id': rotation_id,
'name': f'{rotation_id} (rotation)',
'type': 'rotation'
})
# Add global provider models
providers_path = Path.home() / '.aisbf' / 'providers.json'
if not providers_path.exists():
providers_path = Path(__file__).parent / 'config' / 'providers.json'
if providers_path.exists():
with open(providers_path) as f:
providers_config = json.load(f)
providers_data = providers_config.get('providers', {})
for provider_id, provider in providers_data.items():
if 'models' in provider and isinstance(provider['models'], list):
for model in provider['models']:
model_id = f"{provider_id}/{model['name']}"
available_models.append({
'id': model_id,
'name': f"{model_id} (provider model)",
'type': 'provider'
})
# Config admin: use admin template
return templates.TemplateResponse(
request=request,
......@@ -4451,16 +4456,16 @@ async def dashboard_autoselect(request: Request):
}
)
else:
# Database user: use user template with proper context
# Database user: use ONLY their own rotations and providers
from aisbf.database import get_database
db = DatabaseRegistry.get_config_database()
user_autoselects = db.get_user_autoselects(current_user_id)
# For database users, get available user rotations
# Get only user's own rotations
user_rotations = db.get_user_rotations(current_user_id)
available_rotations = [rot['rotation_id'] for rot in user_rotations]
# For database users, get available user providers
# Get only user's own providers
user_providers = db.get_user_providers(current_user_id)
available_models = []
......@@ -4483,21 +4488,22 @@ async def dashboard_autoselect(request: Request):
'name': f"{model_id} (provider model)",
'type': 'provider'
})
return templates.TemplateResponse(
request=request,
name="dashboard/user_autoselects.html",
context={
"request": request,
"session": request.session,
"__version__": __version__,
"user_autoselects_json": json.dumps(autoselect_data),
"available_rotations": json.dumps(available_rotations),
"available_models": json.dumps(available_models),
"user_id": current_user_id,
"success": "Configuration saved successfully!" if success else None
}
)
# Database user: use user template
return templates.TemplateResponse(
request=request,
name="dashboard/user_autoselects.html",
context={
"request": request,
"session": request.session,
"__version__": __version__,
"autoselect_json": json.dumps(autoselect_data),
"available_rotations": json.dumps(available_rotations),
"available_models": json.dumps(available_models),
"user_id": current_user_id,
"success": "Configuration saved successfully!" if success else None
}
)
@app.post("/dashboard/autoselect")
async def dashboard_autoselect_save(request: Request, config: str = Form(...)):
......
......@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aisbf"
version = "0.99.36"
version = "0.99.38"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md"
license = "GPL-3.0-or-later"
......
......@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup(
name="aisbf",
version="0.99.36",
version="0.99.38",
author="AISBF Contributors",
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",
......
This diff is collapsed.
This diff is collapsed.
#!/bin/bash
# Release Verification Script for AISBF v0.99.26
# Release Verification Script for AISBF v0.99.37
echo "================================================================================"
echo " AISBF v0.99.26 Release Verification"
echo " AISBF v0.99.37 Release Verification"
echo "================================================================================"
echo
......@@ -26,7 +26,7 @@ check() {
# 1. Check version numbers
echo "1. Checking version numbers..."
VERSION="0.99.26"
VERSION="0.99.37"
grep -q "version=\"$VERSION\"" setup.py
check "setup.py version is $VERSION"
......
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