added URL property to agents (link to agent) and fixed redirect base paths

This commit is contained in:
michael 2025-10-28 14:51:26 -05:00
parent cebc1cf649
commit d160b1bc90
5 changed files with 53 additions and 23 deletions

View file

@ -13,7 +13,16 @@ def get_base_path() -> str:
return BASE_PATH
def get_full_url(path: str) -> str:
"""Get full URL with base path prefix"""
"""
DEPRECATED: Use get_app_url() from main.py instead for consistency.
Get full URL with base path prefix.
"""
import warnings
warnings.warn(
"get_full_url() is deprecated. Use get_app_url() from main.py instead.",
DeprecationWarning,
stacklevel=2
)
path = path.lstrip("/") # Remove leading slash
if BASE_PATH:
return f"{BASE_PATH}/{path}"

View file

@ -337,7 +337,8 @@ def _agent_data_differs(existing_agent: dict, new_agent_data: dict) -> bool:
comparable_fields = [
"agent_description", "agent_purpose", "agent_version", "agent_status",
"agent_location", "agent_department", "agent_contact_person",
"agent_tags", "agent_userbase", "agent_capabilities", "agent_metadata"
"agent_tags", "agent_userbase", "agent_capabilities", "agent_metadata",
"url"
]
for field in comparable_fields:

39
main.py
View file

@ -214,6 +214,7 @@ def create_agent_response(agent: dict) -> models.AiAgentResponse:
agent_metadata=sanitize_metadata(agent.get("agent_metadata")) if agent.get("agent_metadata") else None,
agent_userbase=agent.get("agent_userbase"),
agent_capabilities=agent.get("agent_capabilities"),
url=agent.get("url"),
quality_audit_status=agent.get("quality_audit_status", False),
quality_audit_updated_by=agent.get("quality_audit_updated_by"),
quality_audit_updated_at=agent.get("quality_audit_updated_at"),
@ -272,6 +273,7 @@ def map_agent_collector_to_internal(collector_data: models.AgentCollectorCreate)
"agent_contact_person": collector_data.contact_person,
"agent_tags": collector_data.tags,
"agent_metadata": collector_data.metadata,
"url": collector_data.url,
# Usage tracking fields (new)
"usage_timeline": usage_timeline,
"conversation_count": collector_data.conversation_count,
@ -547,7 +549,7 @@ async def azure_token_exchange(request: Request):
async def agent_register_page(request: Request):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
return templates.TemplateResponse("agent_register.html", get_template_context(request, current_user))
@app.post("/agent-register", response_class=HTMLResponse)
@ -572,7 +574,7 @@ async def agent_register_form(
# Get user from cookie - require authentication
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
user_id = str(current_user["_id"])
@ -652,7 +654,7 @@ async def agent_register_form(
async def dashboard(request: Request):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
return templates.TemplateResponse("admin/dashboard.html", get_template_context(request, current_user))
@app.get("/user-management", response_class=HTMLResponse)
@ -664,8 +666,8 @@ async def logout(request: Request):
"""Logout user and clear all session data"""
# Clear MSAL session data if present
request.session.clear()
response = RedirectResponse(url=config.get_full_url(""), status_code=303)
response = RedirectResponse(url=get_app_url(""), status_code=303)
response.delete_cookie(key="access_token")
return response
@ -673,14 +675,14 @@ async def logout(request: Request):
async def profile_page(request: Request):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
return templates.TemplateResponse("profile.html", get_template_context(request, current_user))
@app.get("/agent-management", response_class=HTMLResponse)
@app.get("/agent-management", response_class=HTMLResponse)
async def agent_management_page(request: Request, view: Optional[str] = Query(None), success: Optional[str] = Query(None), error: Optional[str] = Query(None)):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
# Default to "all" view for regular users, "my" view can be specified via query param
if view == "my":
@ -712,7 +714,7 @@ async def agent_management_page(request: Request, view: Optional[str] = Query(No
async def admin_dashboard(request: Request):
current_user = await get_current_user_optional(request)
if not current_user or not current_user.get("is_admin"):
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
# Get statistics
all_users = await crud.get_all_users()
@ -745,8 +747,8 @@ async def admin_dashboard(request: Request):
async def search_page(request: Request, q: Optional[str] = Query(None)):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
search_results = {"agents": [], "users": []}
if q:
# Search agents using proper search function
@ -773,8 +775,8 @@ async def search_page(request: Request, q: Optional[str] = Query(None)):
async def edit_agent_form(request: Request, agent_id: str):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
agent = await crud.get_agent_by_id(agent_id)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
@ -793,8 +795,8 @@ async def edit_agent_form(request: Request, agent_id: str):
async def delete_agent_form(request: Request, agent_id: str):
current_user = await get_current_user_optional(request)
if not current_user:
return RedirectResponse(url=config.get_full_url("login"), status_code=303)
return RedirectResponse(url=get_app_url("login"), status_code=303)
# Check permission and delete
user_id = str(current_user["_id"]) if not current_user.get("is_admin") else None
deleted = await crud.delete_agent(agent_id, user_id)
@ -994,12 +996,14 @@ async def create_agent_collector(
if existing_agent:
# Agent exists - log usage AND update agent document with new usage data
internal_data = map_agent_collector_to_internal(agent)
print(f"🔗 URL DEBUG: Agent '{agent.name}' - URL in internal_data: {internal_data.get('url')}")
# Create usage record for fallback purposes (existing system)
await crud.create_agent_usage_record(agent.name, internal_data)
# Update agent document with new usage data (replace strategy)
update_fields = {
"url": internal_data.get("url"),
"usage_timeline": internal_data.get("usage_timeline"),
"conversation_count": internal_data.get("conversation_count"),
"unique_users": internal_data.get("unique_users"),
@ -1011,13 +1015,16 @@ async def create_agent_collector(
# Only update non-None usage fields
update_fields = {k: v for k, v in update_fields.items() if k == "updated_at" or v is not None}
print(f"🔗 URL DEBUG: Agent '{agent.name}' - update_fields after filter: {update_fields}")
print(f"🔗 URL DEBUG: Agent '{agent.name}' - URL in update_fields: {update_fields.get('url')}")
if len(update_fields) > 1: # More than just updated_at
from database import agents_collection
await agents_collection.update_one(
result = await agents_collection.update_one(
{"agent_name": agent.name},
{"$set": update_fields}
)
print(f"🔗 URL DEBUG: Agent '{agent.name}' - Update executed: matched={result.matched_count}, modified={result.modified_count}")
return models.AgentUsageTrackingResponse(
status="usage_logged",

View file

@ -24,6 +24,7 @@ class AiAgent(BaseModel):
agent_metadata: dict[str, str] | None = Field(default=None, title="Metadata associated with the agent")
agent_userbase: list[str] | None = Field(default=None, title="Userbase associated with the agent")
agent_capabilities: list[str] | None = Field(default=None, title="Capabilities of the agent")
url: str | None = Field(default=None, title="Direct link to create a conversation with this agent")
quality_audit_status: bool | None = Field(default=False, title="Quality audit status")
quality_audit_updated_by: str | None = Field(default=None, title="Admin user ID who updated quality audit")
quality_audit_updated_at: str | None = Field(default=None, title="Quality audit last update timestamp")
@ -74,6 +75,7 @@ class AiAgentCreate(BaseModel):
agent_metadata: Optional[dict[str, str]] = None
agent_userbase: Optional[list[str]] = None
agent_capabilities: Optional[list[str]] = None
url: Optional[str] = None
quality_audit_status: Optional[bool] = False
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
@ -98,6 +100,7 @@ class AiAgentResponse(BaseModel):
agent_metadata: Optional[dict[str, str]] = None
agent_userbase: Optional[list[str]] = None
agent_capabilities: Optional[list[str]] = None
url: Optional[str] = None
quality_audit_status: Optional[bool] = None
quality_audit_updated_by: Optional[str] = None
quality_audit_updated_at: Optional[str] = None
@ -131,6 +134,7 @@ class AgentCollectorCreate(BaseModel):
contact_person: Optional[str] = None
tags: Optional[list[str]] = None
metadata: Optional[dict] = None
url: Optional[str] = None
# Usage tracking fields (new)
usage_timeline: Optional[List[UsageTimelineEntry]] = None

View file

@ -425,6 +425,7 @@ let currentView = '{{ current_view }}';
let currentUserId = '{{ current_user._id|string }}';
let currentUserEmail = '{{ current_user.email }}';
let currentUserIsAdmin = {{ 'true' if current_user.is_admin else 'false' }};
const basePath = '{{ base_path }}';
// Helper function to check if current user can edit an agent
function canUserEditAgent(agent) {
@ -571,9 +572,9 @@ function switchToView(view) {
displayAgents(agents);
updateAgentCounts();
// Update URL without page reload
const newUrl = view === 'my' ? '/agent-management?view=my' : '/agent-management';
const newUrl = view === 'my' ? `${basePath}/agent-management?view=my` : `${basePath}/agent-management`;
window.history.pushState({view: view}, '', newUrl);
}
@ -607,7 +608,7 @@ function displayAgents(agentsToShow) {
<div>
<h6 class="mb-1 fw-bold">${agent.agent_name}</h6>
<p class="text-muted mb-2 small">${agent.agent_description || 'No description'}</p>
<div class="d-flex gap-2 align-items-center flex-wrap">
<div class="d-flex gap-2 align-items-center flex-wrap mb-1">
<span class="agent-status status-${agent.agent_status || 'Development'}">
${agent.agent_status || 'Development'}
</span>
@ -616,7 +617,13 @@ function displayAgents(agentsToShow) {
${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}
${agent.total_messages ? `<span class="badge bg-info text-white" title="Total Messages"><i class="fas fa-comments me-1"></i>${agent.total_messages.toLocaleString()}</span>` : ''}
${agent.unique_users ? `<span class="badge bg-primary" title="Unique Users"><i class="fas fa-users me-1"></i>${agent.unique_users}</span>` : ''}
${agent.url ? `<a href="${agent.url}" target="_blank" class="btn btn-sm btn-success" onclick="event.stopPropagation();" title="Start a conversation with this agent"><i class="fas fa-external-link-alt me-1"></i>Open Agent</a>` : ''}
</div>
${agent.agent_tags && agent.agent_tags.length > 0 ? `
<div class="d-flex gap-1 flex-wrap">
${agent.agent_tags.map(tag => `<span class="badge bg-secondary" style="font-size: 0.7rem;"><i class="fas fa-tag me-1"></i>${tag}</span>`).join('')}
</div>
` : ''}
</div>
<div class="text-end">
<small class="text-muted">${formatDate(agent.agent_created_at)}</small>
@ -675,6 +682,7 @@ async function showAgentDetails(agentId) {
const agent = await response.json();
console.log('DEBUG: Fresh agent data loaded:', agent);
console.log('DEBUG: Agent URL field:', agent.url);
console.log('DEBUG: Quality Audit Details:', {
status: agent.quality_audit_status,
updated_by: agent.quality_audit_updated_by,
@ -691,9 +699,10 @@ async function showAgentDetails(agentId) {
<tr><td><strong>Status:</strong></td><td><span class="agent-status status-${agent.agent_status || 'Development'}">${agent.agent_status || 'Development'}</span></td></tr>
<tr><td><strong>Version:</strong></td><td>${agent.agent_version || 'N/A'}</td></tr>
<tr><td><strong>Purpose:</strong></td><td>${agent.agent_purpose || 'N/A'}</td></tr>
${agent.url ? `<tr><td><strong>Agent Link:</strong></td><td><a href="${agent.url}" target="_blank" class="btn btn-sm btn-success"><i class="fas fa-external-link-alt me-1"></i>Open Agent</a></td></tr>` : ''}
<tr><td><strong>Quality Audit:</strong></td><td>
${agent.quality_audit_status ?
'<span class="badge bg-success"><i class="fas fa-certificate me-1"></i>Audited</span>' :
${agent.quality_audit_status ?
'<span class="badge bg-success"><i class="fas fa-certificate me-1"></i>Audited</span>' :
'<span class="badge bg-secondary">Not Audited</span>'
}
${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}