Source code for mindroot.coreplugins.chat.widget_routes
from fastapi import APIRouter, HTTPException, Request, Response
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
from typing import Optional, List
from .widget_manager import widget_manager
from lib.auth.api_key import verify_api_key
from lib.providers.services import service_manager
from lib.route_decorators import public_route
import nanoid
from .services import init_chat_session
from lib.session_files import save_session_data
import traceback
router = APIRouter()
[docs]
class WidgetTokenCreate(BaseModel):
api_key: str
agent_name: str
base_url: str
description: Optional[str] = ''
styling: Optional[dict] = None
[docs]
class WidgetTokenResponse(BaseModel):
token: str
agent_name: str
base_url: str
description: str
created_at: str
created_by: str
styling: dict
[docs]
@router.post('/widgets/create')
async def create_widget_token(request: Request, widget_request: WidgetTokenCreate):
"""Create a new widget token."""
try:
api_key_data = await verify_api_key(widget_request.api_key)
if not api_key_data:
raise HTTPException(status_code=400, detail='Invalid API key')
try:
agent_data = await service_manager.get_agent_data(widget_request.agent_name)
if not agent_data:
raise HTTPException(status_code=400, detail='Agent not found')
except Exception:
raise HTTPException(status_code=400, detail='Agent not found')
user = request.state.user
token = widget_manager.create_widget_token(api_key=widget_request.api_key, agent_name=widget_request.agent_name, base_url=widget_request.base_url, created_by=user.username, description=widget_request.description, styling=widget_request.styling)
return {'success': True, 'token': token, 'embed_url': f'{widget_request.base_url}/chat/embed/{token}'}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
[docs]
@router.get('/widgets/list')
async def list_widget_tokens(request: Request):
"""List widget tokens for the current user."""
try:
user = request.state.user
widgets = widget_manager.list_widget_tokens(created_by=user.username)
return {'success': True, 'data': widgets}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
[docs]
@router.delete('/widgets/delete/{token}')
async def delete_widget_token(request: Request, token: str):
"""Delete a widget token."""
try:
widget_config = widget_manager.get_widget_config(token)
if not widget_config:
raise HTTPException(status_code=404, detail='Widget token not found')
user = request.state.user
if widget_config.get('created_by') != user.username:
raise HTTPException(status_code=403, detail='Not authorized to delete this widget')
success = widget_manager.delete_widget_token(token)
if success:
return {'success': True, 'message': 'Widget token deleted successfully'}
else:
raise HTTPException(status_code=500, detail='Failed to delete widget token')
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
[docs]
@router.get('/chat/embed/{token}')
@public_route()
async def get_embed_script(token: str):
"""Generate the secure embed script for a widget token."""
try:
widget_config = widget_manager.get_widget_config(token)
if not widget_config:
raise HTTPException(status_code=404, detail='Widget token not found')
base_url = widget_config['base_url']
styling = widget_config.get('styling', {})
embed_script = f'''\n(function() {{\n const config = {{\n baseUrl: "{base_url}",\n token: "{token}",\n position: "{styling.get('position', 'bottom-right')}",\n width: "{styling.get('width', '400px')}",\n height: "{styling.get('height', '600px')}",\n theme: "{styling.get('theme', 'dark')}"\n }};\n \n let chatContainer = null;\n let chatIcon = null;\n let isLoaded = false;\n let isMobile = false;\n\n function detectMobile() {{\n const userAgent = navigator.userAgent || navigator.vendor || window.opera || "";\n return /android|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent.toLowerCase());\n }}\n \n function createChatIcon() {{\n if (chatIcon) return;\n \n chatIcon = document.createElement("div");\n chatIcon.id = "mindroot-chat-icon-" + config.token;\n chatIcon.innerHTML = "💬";\n \n const iconSize = isMobile ? 80 : 60;\n const fontSize = isMobile ? 28 : 24;\n const iconStyles = {{\n position: "fixed",\n bottom: isMobile ? "30px" : "20px",\n right: config.position.includes("left") ? "auto" : "20px",\n left: config.position.includes("left") ? "20px" : "auto",\n width: iconSize + "px",\n height: iconSize + "px",\n background: "#2196F3",\n borderRadius: "50%",\n display: "flex",\n alignItems: "center",\n justifyContent: "center",\n cursor: "pointer",\n boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",\n zIndex: "10000",\n fontSize: fontSize + "px",\n color: "white",\n transition: "all 0.3s ease"\n }};\n \n Object.assign(chatIcon.style, iconStyles);\n chatIcon.addEventListener("click", toggleChat);\n document.body.appendChild(chatIcon);\n }}\n \n function createChatContainer() {{\n if (chatContainer) return;\n \n chatContainer = document.createElement("div");\n chatContainer.id = "mindroot-chat-container-" + config.token;\n \n const containerStyles = isMobile ? {{\n position: "fixed",\n top: "0px",\n left: "0",\n right: "0",\n bottom: "0",\n width: "100vw",\n height: "100vh",\n background: "white",\n borderRadius: "0",\n boxShadow: "none",\n zIndex: "10001",\n display: "none",\n flexDirection: "column",\n overflow: "hidden"\n }} : {{\n position: "fixed",\n bottom: "90px",\n right: config.position.includes("left") ? "auto" : "20px",\n left: config.position.includes("left") ? "20px" : "auto",\n width: config.width,\n height: config.height,\n background: "white",\n borderRadius: "12px",\n boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",\n zIndex: "10001",\n display: "none",\n overflow: "hidden"\n }};\n \n Object.assign(chatContainer.style, containerStyles);\n document.body.appendChild(chatContainer);\n }}\n \n function toggleChat() {{\n if (!chatContainer) createChatContainer();\n \n const isVisible = chatContainer.style.display !== "none";\n \n if (isVisible) {{\n chatContainer.style.display = "none";\n if (isMobile) {{\n document.body.style.removeProperty("overflow");\n }}\n }} else {{\n if (!isLoaded) {{\n // Create iframe and load the secure session\n const iframe = document.createElement("iframe");\n if (isMobile) {{\n iframe.style.cssText = "width: 100%; height: 100%; border: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0;";\n // Add viewport meta tag if not present\n if (!document.querySelector('meta[name="viewport"]')) {{\n const meta = document.createElement('meta');\n meta.name = 'viewport';\n meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';\n document.head.appendChild(meta);\n }}\n }} else {{\n iframe.style.cssText = "width: 100%; height: 100%; border: none; border-radius: 12px;";\n }}\n iframe.src = config.baseUrl + "/chat/widget/" + config.token + "/session";\n iframe.setAttribute('allow', 'microphone');\n chatContainer.appendChild(iframe);\n isLoaded = true;\n }}\n chatContainer.style.display = "block";\n if (isMobile) {{\n document.body.style.overflow = "hidden";\n }}\n }}\n }}\n \n function init() {{\n const boot = () => {{\n isMobile = detectMobile();\n createChatIcon();\n }};\n\n if (document.readyState === "loading") {{\n document.addEventListener("DOMContentLoaded", boot);\n }} else {{\n boot();\n }}\n }}\n \n init();\n}})();\n'''
return Response(content=embed_script, media_type='application/javascript', headers={'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0'})
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
[docs]
@router.get('/chat/widget/{token}/session')
@public_route()
async def create_widget_session(token: str):
"""Create a secure chat session for a widget token."""
try:
widget_config = widget_manager.get_widget_config(token)
if not widget_config:
raise HTTPException(status_code=404, detail='Widget token not found')
api_key_data = await verify_api_key(widget_config['api_key'])
if not api_key_data:
raise HTTPException(status_code=401, detail='Invalid API key in widget configuration')
class MockUser:
def __init__(self, username):
self.username = username
user = MockUser(api_key_data['username'])
session_id = nanoid.generate()
agent_name = widget_config['agent_name']
await init_chat_session(user, agent_name, session_id)
from coreplugins.jwt_auth.middleware import create_access_token
token_data = create_access_token({'sub': api_key_data['username']})
await save_session_data(session_id, 'access_token', token_data)
redirect_url = f'/session/{agent_name}/{session_id}?embed=true'
return Response(status_code=302, headers={'Location': redirect_url})
except Exception as e:
trace = traceback.format_exc()
raise HTTPException(status_code=500, detail=str(e))