diff --git a/config.py b/config.py index 96f8a97..c309b55 100644 --- a/config.py +++ b/config.py @@ -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}" diff --git a/crud.py b/crud.py index c2c8ccf..527a529 100644 --- a/crud.py +++ b/crud.py @@ -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: diff --git a/main.py b/main.py index 98115df..5e45005 100644 --- a/main.py +++ b/main.py @@ -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", diff --git a/models.py b/models.py index 23cac82..1a768ad 100644 --- a/models.py +++ b/models.py @@ -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 diff --git a/templates/agent_management.html b/templates/agent_management.html index fd9c9a2..5e1e6be 100644 --- a/templates/agent_management.html +++ b/templates/agent_management.html @@ -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) {
${agent.agent_name}

${agent.agent_description || 'No description'}

-
+
${agent.agent_status || 'Development'} @@ -616,7 +617,13 @@ function displayAgents(agentsToShow) { ${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''} ${agent.total_messages ? `${agent.total_messages.toLocaleString()}` : ''} ${agent.unique_users ? `${agent.unique_users}` : ''} + ${agent.url ? `Open Agent` : ''}
+ ${agent.agent_tags && agent.agent_tags.length > 0 ? ` +
+ ${agent.agent_tags.map(tag => `${tag}`).join('')} +
+ ` : ''}
${formatDate(agent.agent_created_at)} @@ -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) { Status:${agent.agent_status || 'Development'} Version:${agent.agent_version || 'N/A'} Purpose:${agent.agent_purpose || 'N/A'} + ${agent.url ? `Agent Link:Open Agent` : ''} Quality Audit: - ${agent.quality_audit_status ? - 'Audited' : + ${agent.quality_audit_status ? + 'Audited' : 'Not Audited' } ${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}