mindroot 9.3.0__py3-none-any.whl → 9.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mindroot/coreplugins/admin/__init__.py +3 -1
- mindroot/coreplugins/admin/agent_router.py +250 -7
- mindroot/coreplugins/admin/asset_manager.py +164 -0
- mindroot/coreplugins/admin/command_router.py +236 -1
- mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
- mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
- mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
- mindroot/coreplugins/admin/mcp_routes.py +216 -0
- mindroot/coreplugins/admin/mod.py +62 -0
- mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
- mindroot/coreplugins/admin/persona_handler.py +15 -6
- mindroot/coreplugins/admin/persona_router.py +158 -2
- mindroot/coreplugins/admin/plugin_manager.py +105 -9
- mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
- mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
- mindroot/coreplugins/admin/plugin_routes.py +114 -0
- mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
- mindroot/coreplugins/admin/router.py +116 -15
- mindroot/coreplugins/admin/service_models.py +1 -1
- mindroot/coreplugins/admin/settings_router.py +1 -0
- mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
- mindroot/coreplugins/admin/static/css/dark.css +1 -0
- mindroot/coreplugins/admin/static/css/default.css +4 -0
- mindroot/coreplugins/admin/static/js/about-info.js +367 -0
- mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
- mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
- mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
- mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
- mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
- mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
- mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
- mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
- mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
- mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
- mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
- mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
- mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
- mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
- mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
- mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
- mindroot/coreplugins/admin/static/logo.png +0 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
- mindroot/coreplugins/agent/Assistant/agent.json +27 -11
- mindroot/coreplugins/agent/agent.py +2 -2
- mindroot/coreplugins/agent/command_parser.py +25 -10
- mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
- mindroot/coreplugins/chat/__init__.py +4 -1
- mindroot/coreplugins/chat/router.py +132 -20
- mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
- mindroot/coreplugins/chat/services.py +31 -1
- mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
- mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
- mindroot/coreplugins/chat/static/css/dark.css +24 -3
- mindroot/coreplugins/chat/static/css/default.css +24 -3
- mindroot/coreplugins/chat/static/css/main.css +1 -0
- mindroot/coreplugins/chat/static/js/action.js +137 -60
- mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
- mindroot/coreplugins/chat/static/js/chat.js +59 -16
- mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
- mindroot/coreplugins/chat/static/js/chatform.js +2 -2
- mindroot/coreplugins/chat/static/site.webmanifest +1 -1
- mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
- mindroot/coreplugins/chat/widget_manager.py +139 -0
- mindroot/coreplugins/chat/widget_routes.py +287 -0
- mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
- mindroot/coreplugins/email/__init__.py +2 -0
- mindroot/coreplugins/email/email_provider.py +2 -2
- mindroot/coreplugins/email/mod.py +100 -0
- mindroot/coreplugins/email/services.py +5 -3
- mindroot/coreplugins/email/smtp_handler.py +9 -3
- mindroot/coreplugins/email/test_email_service.py +75 -0
- mindroot/coreplugins/env_manager/mod.py +61 -25
- mindroot/coreplugins/home/router.py +37 -2
- mindroot/coreplugins/home/static/imgs/logo.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
- mindroot/coreplugins/home/templates/home.jinja2 +15 -6
- mindroot/coreplugins/index/indices/default/index.json +39 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -2
- mindroot/coreplugins/jwt_auth/mod.py +40 -17
- mindroot/coreplugins/l8n/__init__.py +6 -0
- mindroot/coreplugins/l8n/debug_loader.py +85 -0
- mindroot/coreplugins/l8n/debug_middleware.py +74 -0
- mindroot/coreplugins/l8n/l8n_constants.py +19 -0
- mindroot/coreplugins/l8n/language_detection.py +183 -0
- mindroot/coreplugins/l8n/middleware.py +151 -0
- mindroot/coreplugins/l8n/mod.py +277 -0
- mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
- mindroot/coreplugins/l8n/test_enhanced.py +298 -0
- mindroot/coreplugins/l8n/test_l8n.py +95 -0
- mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
- mindroot/coreplugins/l8n/test_middleware.py +272 -0
- mindroot/coreplugins/l8n/utils.py +232 -0
- mindroot/coreplugins/mcp_/__init__.py +14 -0
- mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
- mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
- mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
- mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
- mindroot/coreplugins/mcp_/mod.py +367 -0
- mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
- mindroot/coreplugins/mcp_/server_installer.py +79 -0
- mindroot/coreplugins/mcp_/setup.py +26 -0
- mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
- mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
- mindroot/coreplugins/persona/mod.py +12 -7
- mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
- mindroot/coreplugins/subscriptions/__init__.py +1 -0
- mindroot/coreplugins/subscriptions/mod.py +14 -3
- mindroot/coreplugins/subscriptions/router.py +3 -0
- mindroot/coreplugins/user_service/__init__.py +1 -2
- mindroot/coreplugins/user_service/admin_init.py +1 -0
- mindroot/coreplugins/user_service/email_service.py +72 -17
- mindroot/coreplugins/user_service/mod.py +10 -2
- mindroot/coreplugins/user_service/router.py +2 -0
- mindroot/lib/auth/api_key.py +28 -0
- mindroot/lib/cli/plugins.py +94 -0
- mindroot/lib/plugins/default_plugin_manifest.json +20 -0
- mindroot/lib/plugins/installation.py +5 -5
- mindroot/lib/plugins/l8n_static_handler.py +225 -0
- mindroot/lib/plugins/loader.py +33 -3
- mindroot/lib/plugins/loader_with_l8n.py +281 -0
- mindroot/lib/plugins/manifest.py +236 -24
- mindroot/lib/providers/commands.py +3 -1
- mindroot/lib/route_decorators.py +5 -5
- mindroot/lib/templates.py +183 -11
- mindroot/lib/utils/merge_arrays.py +1 -1
- mindroot/migrate.py +39 -20
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +42 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
- mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
- mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
- mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
- mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
- mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
- mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
- mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/index/default.json +0 -76
- mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
- mindroot/coreplugins/user_service/hooks.py +0 -23
- /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/WHEEL +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"","short_name":"","icons":[{"src":"/
|
|
1
|
+
{"name":"","short_name":"","icons":[{"src":"/imgs/logo.png","sizes":"192x192","type":"image/png"},{"src":"/imgs/logo.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
{% endblock %}
|
|
30
30
|
|
|
31
31
|
{% block head_favicon %}
|
|
32
|
-
<link rel="apple-touch-icon" sizes="180x180" href="/
|
|
33
|
-
<link rel="icon" type="image/png" sizes="32x32" href="/
|
|
34
|
-
<link rel="icon" type="image/png" sizes="16x16" href="/
|
|
32
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/imgs/logo.png">
|
|
33
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/imgs/logo.png">
|
|
34
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/imgs/logo.png">
|
|
35
35
|
{% endblock %}
|
|
36
36
|
|
|
37
37
|
{% block head_extra %}{% endblock %}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import nanoid
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Optional, List
|
|
6
|
+
|
|
7
|
+
class WidgetManager:
|
|
8
|
+
"""Manages secure widget tokens for chat embedding."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, widgets_dir: str = "data/widgets"):
|
|
11
|
+
self.widgets_dir = Path(widgets_dir)
|
|
12
|
+
self.widgets_dir.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
self._load_widgets()
|
|
14
|
+
|
|
15
|
+
def _load_widgets(self) -> None:
|
|
16
|
+
"""Load all widget tokens from storage."""
|
|
17
|
+
self.widgets = {}
|
|
18
|
+
for widget_file in self.widgets_dir.glob("*.json"):
|
|
19
|
+
try:
|
|
20
|
+
with open(widget_file, 'r') as f:
|
|
21
|
+
widget_data = json.load(f)
|
|
22
|
+
self.widgets[widget_data['token']] = widget_data
|
|
23
|
+
except json.JSONDecodeError:
|
|
24
|
+
print(f"Warning: Invalid JSON in widget file: {widget_file}")
|
|
25
|
+
except Exception as e:
|
|
26
|
+
print(f"Error loading widget file {widget_file}: {e}")
|
|
27
|
+
|
|
28
|
+
def create_widget_token(self, api_key: str, agent_name: str, base_url: str,
|
|
29
|
+
created_by: str, description: str = "",
|
|
30
|
+
styling: Optional[Dict] = None) -> str:
|
|
31
|
+
"""Create a new widget token and store its configuration.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
api_key: The API key to use for authentication
|
|
35
|
+
agent_name: The agent to use for chat sessions
|
|
36
|
+
base_url: The base URL for the MindRoot instance
|
|
37
|
+
created_by: Username of the creator
|
|
38
|
+
description: Optional description for the widget
|
|
39
|
+
styling: Optional styling configuration
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
The generated widget token
|
|
43
|
+
"""
|
|
44
|
+
token = nanoid.generate()
|
|
45
|
+
|
|
46
|
+
if styling is None:
|
|
47
|
+
styling = {
|
|
48
|
+
"position": "bottom-right",
|
|
49
|
+
"theme": "dark",
|
|
50
|
+
"width": "400px",
|
|
51
|
+
"height": "600px"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
widget_data = {
|
|
55
|
+
"token": token,
|
|
56
|
+
"api_key": api_key,
|
|
57
|
+
"agent_name": agent_name,
|
|
58
|
+
"base_url": base_url,
|
|
59
|
+
"created_at": datetime.utcnow().isoformat(),
|
|
60
|
+
"created_by": created_by,
|
|
61
|
+
"description": description,
|
|
62
|
+
"styling": styling
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Save to file
|
|
66
|
+
widget_file = self.widgets_dir / f"{token}.json"
|
|
67
|
+
with open(widget_file, 'w') as f:
|
|
68
|
+
json.dump(widget_data, f, indent=2)
|
|
69
|
+
|
|
70
|
+
self.widgets[token] = widget_data
|
|
71
|
+
return token
|
|
72
|
+
|
|
73
|
+
def get_widget_config(self, token: str) -> Optional[Dict]:
|
|
74
|
+
"""Retrieve widget configuration by token.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
token: The widget token
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Widget configuration dict if found, None otherwise
|
|
81
|
+
"""
|
|
82
|
+
return self.widgets.get(token)
|
|
83
|
+
|
|
84
|
+
def delete_widget_token(self, token: str) -> bool:
|
|
85
|
+
"""Delete a widget token and its configuration.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
token: The widget token to delete
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if deleted successfully, False if not found
|
|
92
|
+
"""
|
|
93
|
+
if token in self.widgets:
|
|
94
|
+
widget_file = self.widgets_dir / f"{token}.json"
|
|
95
|
+
try:
|
|
96
|
+
widget_file.unlink(missing_ok=True)
|
|
97
|
+
del self.widgets[token]
|
|
98
|
+
return True
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"Error deleting widget file {widget_file}: {e}")
|
|
101
|
+
return False
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def list_widget_tokens(self, created_by: Optional[str] = None) -> List[Dict]:
|
|
105
|
+
"""List all widget tokens, optionally filtered by creator.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
created_by: Optional username to filter by
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of widget configurations (with API keys hidden)
|
|
112
|
+
"""
|
|
113
|
+
widgets = []
|
|
114
|
+
|
|
115
|
+
for widget_data in self.widgets.values():
|
|
116
|
+
# Filter by creator if specified
|
|
117
|
+
if created_by and widget_data.get("created_by") != created_by:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Create safe copy without exposing API key
|
|
121
|
+
safe_widget = widget_data.copy()
|
|
122
|
+
safe_widget["api_key"] = "***hidden***"
|
|
123
|
+
widgets.append(safe_widget)
|
|
124
|
+
|
|
125
|
+
return sorted(widgets, key=lambda x: x.get("created_at", ""), reverse=True)
|
|
126
|
+
|
|
127
|
+
def validate_token(self, token: str) -> bool:
|
|
128
|
+
"""Check if a widget token is valid.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
token: The widget token to validate
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if valid, False otherwise
|
|
135
|
+
"""
|
|
136
|
+
return token in self.widgets
|
|
137
|
+
|
|
138
|
+
# Global widget manager instance
|
|
139
|
+
widget_manager = WidgetManager()
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Request, Response
|
|
2
|
+
from fastapi.responses import HTMLResponse
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
from .widget_manager import widget_manager
|
|
6
|
+
from lib.auth.api_key import verify_api_key
|
|
7
|
+
from lib.providers.services import service_manager
|
|
8
|
+
from lib.route_decorators import public_route
|
|
9
|
+
import nanoid
|
|
10
|
+
from .services import init_chat_session
|
|
11
|
+
from lib.session_files import save_session_data
|
|
12
|
+
import traceback
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
|
|
16
|
+
class WidgetTokenCreate(BaseModel):
|
|
17
|
+
api_key: str
|
|
18
|
+
agent_name: str
|
|
19
|
+
base_url: str
|
|
20
|
+
description: Optional[str] = ""
|
|
21
|
+
styling: Optional[dict] = None
|
|
22
|
+
|
|
23
|
+
class WidgetTokenResponse(BaseModel):
|
|
24
|
+
token: str
|
|
25
|
+
agent_name: str
|
|
26
|
+
base_url: str
|
|
27
|
+
description: str
|
|
28
|
+
created_at: str
|
|
29
|
+
created_by: str
|
|
30
|
+
styling: dict
|
|
31
|
+
|
|
32
|
+
@router.post("/widgets/create")
|
|
33
|
+
async def create_widget_token(request: Request, widget_request: WidgetTokenCreate):
|
|
34
|
+
"""Create a new widget token."""
|
|
35
|
+
try:
|
|
36
|
+
# Verify the API key is valid
|
|
37
|
+
api_key_data = await verify_api_key(widget_request.api_key)
|
|
38
|
+
if not api_key_data:
|
|
39
|
+
raise HTTPException(status_code=400, detail="Invalid API key")
|
|
40
|
+
|
|
41
|
+
# Verify the agent exists
|
|
42
|
+
try:
|
|
43
|
+
agent_data = await service_manager.get_agent_data(widget_request.agent_name)
|
|
44
|
+
if not agent_data:
|
|
45
|
+
raise HTTPException(status_code=400, detail="Agent not found")
|
|
46
|
+
except Exception:
|
|
47
|
+
raise HTTPException(status_code=400, detail="Agent not found")
|
|
48
|
+
|
|
49
|
+
# Get the current user
|
|
50
|
+
user = request.state.user
|
|
51
|
+
|
|
52
|
+
# Create the widget token
|
|
53
|
+
token = widget_manager.create_widget_token(
|
|
54
|
+
api_key=widget_request.api_key,
|
|
55
|
+
agent_name=widget_request.agent_name,
|
|
56
|
+
base_url=widget_request.base_url,
|
|
57
|
+
created_by=user.username,
|
|
58
|
+
description=widget_request.description,
|
|
59
|
+
styling=widget_request.styling
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
"success": True,
|
|
64
|
+
"token": token,
|
|
65
|
+
"embed_url": f"{widget_request.base_url}/chat/embed/{token}"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
except HTTPException:
|
|
69
|
+
raise
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
72
|
+
|
|
73
|
+
@router.get("/widgets/list")
|
|
74
|
+
async def list_widget_tokens(request: Request):
|
|
75
|
+
"""List widget tokens for the current user."""
|
|
76
|
+
try:
|
|
77
|
+
user = request.state.user
|
|
78
|
+
widgets = widget_manager.list_widget_tokens(created_by=user.username)
|
|
79
|
+
return {"success": True, "data": widgets}
|
|
80
|
+
except Exception as e:
|
|
81
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
82
|
+
|
|
83
|
+
@router.delete("/widgets/delete/{token}")
|
|
84
|
+
async def delete_widget_token(request: Request, token: str):
|
|
85
|
+
"""Delete a widget token."""
|
|
86
|
+
try:
|
|
87
|
+
# Verify the widget exists and belongs to the user
|
|
88
|
+
widget_config = widget_manager.get_widget_config(token)
|
|
89
|
+
if not widget_config:
|
|
90
|
+
raise HTTPException(status_code=404, detail="Widget token not found")
|
|
91
|
+
|
|
92
|
+
user = request.state.user
|
|
93
|
+
if widget_config.get("created_by") != user.username:
|
|
94
|
+
raise HTTPException(status_code=403, detail="Not authorized to delete this widget")
|
|
95
|
+
|
|
96
|
+
success = widget_manager.delete_widget_token(token)
|
|
97
|
+
if success:
|
|
98
|
+
return {"success": True, "message": "Widget token deleted successfully"}
|
|
99
|
+
else:
|
|
100
|
+
raise HTTPException(status_code=500, detail="Failed to delete widget token")
|
|
101
|
+
|
|
102
|
+
except HTTPException:
|
|
103
|
+
raise
|
|
104
|
+
except Exception as e:
|
|
105
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
106
|
+
|
|
107
|
+
@router.get("/chat/embed/{token}")
|
|
108
|
+
@public_route()
|
|
109
|
+
async def get_embed_script(token: str):
|
|
110
|
+
"""Generate the secure embed script for a widget token."""
|
|
111
|
+
try:
|
|
112
|
+
# Validate the widget token
|
|
113
|
+
widget_config = widget_manager.get_widget_config(token)
|
|
114
|
+
if not widget_config:
|
|
115
|
+
raise HTTPException(status_code=404, detail="Widget token not found")
|
|
116
|
+
|
|
117
|
+
# Generate the embed JavaScript
|
|
118
|
+
base_url = widget_config["base_url"]
|
|
119
|
+
styling = widget_config.get("styling", {})
|
|
120
|
+
|
|
121
|
+
# Create the embed script that doesn't expose the API key
|
|
122
|
+
embed_script = f'''
|
|
123
|
+
(function() {{
|
|
124
|
+
const config = {{
|
|
125
|
+
baseUrl: "{base_url}",
|
|
126
|
+
token: "{token}",
|
|
127
|
+
position: "{styling.get('position', 'bottom-right')}",
|
|
128
|
+
width: "{styling.get('width', '400px')}",
|
|
129
|
+
height: "{styling.get('height', '600px')}",
|
|
130
|
+
theme: "{styling.get('theme', 'dark')}"
|
|
131
|
+
}};
|
|
132
|
+
|
|
133
|
+
let chatContainer = null;
|
|
134
|
+
let chatIcon = null;
|
|
135
|
+
let isLoaded = false;
|
|
136
|
+
|
|
137
|
+
function createChatIcon() {{
|
|
138
|
+
if (chatIcon) return;
|
|
139
|
+
|
|
140
|
+
chatIcon = document.createElement("div");
|
|
141
|
+
chatIcon.id = "mindroot-chat-icon-" + config.token;
|
|
142
|
+
chatIcon.innerHTML = "💬";
|
|
143
|
+
|
|
144
|
+
const iconStyles = {{
|
|
145
|
+
position: "fixed",
|
|
146
|
+
bottom: "20px",
|
|
147
|
+
right: config.position.includes("left") ? "auto" : "20px",
|
|
148
|
+
left: config.position.includes("left") ? "20px" : "auto",
|
|
149
|
+
width: "60px",
|
|
150
|
+
height: "60px",
|
|
151
|
+
background: "#2196F3",
|
|
152
|
+
borderRadius: "50%",
|
|
153
|
+
display: "flex",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
justifyContent: "center",
|
|
156
|
+
cursor: "pointer",
|
|
157
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.3)",
|
|
158
|
+
zIndex: "10000",
|
|
159
|
+
fontSize: "24px",
|
|
160
|
+
color: "white",
|
|
161
|
+
transition: "all 0.3s ease"
|
|
162
|
+
}};
|
|
163
|
+
|
|
164
|
+
Object.assign(chatIcon.style, iconStyles);
|
|
165
|
+
chatIcon.addEventListener("click", toggleChat);
|
|
166
|
+
document.body.appendChild(chatIcon);
|
|
167
|
+
}}
|
|
168
|
+
|
|
169
|
+
function createChatContainer() {{
|
|
170
|
+
if (chatContainer) return;
|
|
171
|
+
|
|
172
|
+
chatContainer = document.createElement("div");
|
|
173
|
+
chatContainer.id = "mindroot-chat-container-" + config.token;
|
|
174
|
+
|
|
175
|
+
const containerStyles = {{
|
|
176
|
+
position: "fixed",
|
|
177
|
+
bottom: "90px",
|
|
178
|
+
right: config.position.includes("left") ? "auto" : "20px",
|
|
179
|
+
left: config.position.includes("left") ? "20px" : "auto",
|
|
180
|
+
width: config.width,
|
|
181
|
+
height: config.height,
|
|
182
|
+
background: "white",
|
|
183
|
+
borderRadius: "12px",
|
|
184
|
+
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
|
|
185
|
+
zIndex: "10001",
|
|
186
|
+
display: "none",
|
|
187
|
+
overflow: "hidden"
|
|
188
|
+
}};
|
|
189
|
+
|
|
190
|
+
Object.assign(chatContainer.style, containerStyles);
|
|
191
|
+
document.body.appendChild(chatContainer);
|
|
192
|
+
}}
|
|
193
|
+
|
|
194
|
+
function toggleChat() {{
|
|
195
|
+
if (!chatContainer) createChatContainer();
|
|
196
|
+
|
|
197
|
+
const isVisible = chatContainer.style.display !== "none";
|
|
198
|
+
|
|
199
|
+
if (isVisible) {{
|
|
200
|
+
chatContainer.style.display = "none";
|
|
201
|
+
}} else {{
|
|
202
|
+
if (!isLoaded) {{
|
|
203
|
+
// Create iframe and load the secure session
|
|
204
|
+
const iframe = document.createElement("iframe");
|
|
205
|
+
iframe.style.cssText = "width: 100%; height: 100%; border: none; border-radius: 12px;";
|
|
206
|
+
iframe.src = config.baseUrl + "/chat/widget/" + config.token + "/session";
|
|
207
|
+
chatContainer.appendChild(iframe);
|
|
208
|
+
isLoaded = true;
|
|
209
|
+
}}
|
|
210
|
+
chatContainer.style.display = "block";
|
|
211
|
+
}}
|
|
212
|
+
}}
|
|
213
|
+
|
|
214
|
+
function init() {{
|
|
215
|
+
if (document.readyState === "loading") {{
|
|
216
|
+
document.addEventListener("DOMContentLoaded", function() {{
|
|
217
|
+
createChatIcon();
|
|
218
|
+
}});
|
|
219
|
+
}} else {{
|
|
220
|
+
createChatIcon();
|
|
221
|
+
}}
|
|
222
|
+
}}
|
|
223
|
+
|
|
224
|
+
init();
|
|
225
|
+
}})();
|
|
226
|
+
'''
|
|
227
|
+
|
|
228
|
+
return Response(
|
|
229
|
+
content=embed_script,
|
|
230
|
+
media_type="application/javascript",
|
|
231
|
+
headers={
|
|
232
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
233
|
+
"Pragma": "no-cache",
|
|
234
|
+
"Expires": "0"
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
except HTTPException:
|
|
239
|
+
raise
|
|
240
|
+
except Exception as e:
|
|
241
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@router.get("/chat/widget/{token}/session")
|
|
245
|
+
@public_route()
|
|
246
|
+
async def create_widget_session(token: str):
|
|
247
|
+
"""Create a secure chat session for a widget token."""
|
|
248
|
+
try:
|
|
249
|
+
# Validate the widget token
|
|
250
|
+
widget_config = widget_manager.get_widget_config(token)
|
|
251
|
+
if not widget_config:
|
|
252
|
+
raise HTTPException(status_code=404, detail="Widget token not found")
|
|
253
|
+
|
|
254
|
+
# Verify the API key from the widget config
|
|
255
|
+
api_key_data = await verify_api_key(widget_config["api_key"])
|
|
256
|
+
if not api_key_data:
|
|
257
|
+
raise HTTPException(status_code=401, detail="Invalid API key in widget configuration")
|
|
258
|
+
|
|
259
|
+
# Create mock user and generate session ID
|
|
260
|
+
class MockUser:
|
|
261
|
+
def __init__(self, username):
|
|
262
|
+
self.username = username
|
|
263
|
+
user = MockUser(api_key_data['username'])
|
|
264
|
+
session_id = nanoid.generate()
|
|
265
|
+
agent_name = widget_config["agent_name"]
|
|
266
|
+
|
|
267
|
+
# Initialize the chat session (this was missing!)
|
|
268
|
+
await init_chat_session(user, agent_name, session_id)
|
|
269
|
+
|
|
270
|
+
# Create and save access token for authentication
|
|
271
|
+
from coreplugins.jwt_auth.middleware import create_access_token
|
|
272
|
+
token_data = create_access_token({"sub": api_key_data['username']})
|
|
273
|
+
await save_session_data(session_id, "access_token", token_data)
|
|
274
|
+
|
|
275
|
+
# Now redirect to the session WITHOUT exposing the API key
|
|
276
|
+
redirect_url = f"/session/{agent_name}/{session_id}?embed=true"
|
|
277
|
+
|
|
278
|
+
return Response(
|
|
279
|
+
status_code=302,
|
|
280
|
+
headers={"Location": redirect_url}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
trace = traceback.format_exc()
|
|
285
|
+
print(e, trace)
|
|
286
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
287
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from lib.providers.services import service
|
|
2
|
+
from .email_provider import EmailProvider
|
|
3
|
+
from .services import init_provider as _init_provider, get_provider as _get_provider
|
|
4
|
+
from typing import Dict
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# Global provider instance
|
|
11
|
+
_provider = None
|
|
12
|
+
|
|
13
|
+
@service()
|
|
14
|
+
async def init_email_provider(config: Dict = None, context=None) -> bool:
|
|
15
|
+
"""Initialize the email provider with config"""
|
|
16
|
+
global _provider
|
|
17
|
+
|
|
18
|
+
if config is None:
|
|
19
|
+
# Use environment variables for default config
|
|
20
|
+
config = {
|
|
21
|
+
'smtp_server': os.getenv('SMTP_SERVER', 'smtp.gmail.com'),
|
|
22
|
+
'smtp_port': int(os.getenv('SMTP_PORT', '587')),
|
|
23
|
+
'use_tls': os.getenv('SMTP_USE_TLS', 'true').lower() == 'true',
|
|
24
|
+
'email': os.getenv('SMTP_EMAIL', ''),
|
|
25
|
+
'password': os.getenv('SMTP_PASSWORD', ''),
|
|
26
|
+
'imap_server': os.getenv('IMAP_SERVER', 'imap.gmail.com'),
|
|
27
|
+
'imap_port': int(os.getenv('IMAP_PORT', '993'))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if not config['email'] or not config['password']:
|
|
31
|
+
logger.warning("Email provider not initialized: missing SMTP_EMAIL or SMTP_PASSWORD")
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
_provider = EmailProvider(config)
|
|
36
|
+
logger.info("Email provider initialized successfully")
|
|
37
|
+
return True
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Failed to initialize email provider: {e}")
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
@service()
|
|
43
|
+
async def send_email(to: str, subject: str, body: str, context=None) -> Dict:
|
|
44
|
+
"""Service to send an email
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
to: Recipient email address
|
|
48
|
+
subject: Email subject
|
|
49
|
+
body: Email body (can be plain text or HTML - auto-detected)
|
|
50
|
+
context: Optional context
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dict with success status and error info
|
|
54
|
+
"""
|
|
55
|
+
global _provider
|
|
56
|
+
|
|
57
|
+
if _provider is None:
|
|
58
|
+
# Try to initialize with default config
|
|
59
|
+
success = await init_email_provider(context=context)
|
|
60
|
+
if not success:
|
|
61
|
+
return {
|
|
62
|
+
"success": False,
|
|
63
|
+
"error": "Email provider not initialized"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
result = await _provider.send_email(
|
|
68
|
+
to=to,
|
|
69
|
+
subject=subject,
|
|
70
|
+
body=body
|
|
71
|
+
)
|
|
72
|
+
return result
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Error sending email: {e}")
|
|
75
|
+
return {
|
|
76
|
+
"success": False,
|
|
77
|
+
"error": str(e)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@service()
|
|
81
|
+
async def check_emails(folder: str = "INBOX", criteria: Dict = None, context=None) -> Dict:
|
|
82
|
+
"""Service to check for new emails"""
|
|
83
|
+
global _provider
|
|
84
|
+
|
|
85
|
+
if _provider is None:
|
|
86
|
+
success = await init_email_provider(context=context)
|
|
87
|
+
if not success:
|
|
88
|
+
return {
|
|
89
|
+
"success": False,
|
|
90
|
+
"error": "Email provider not initialized"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
return await _provider.check_emails(folder=folder, criteria=criteria)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Error checking emails: {e}")
|
|
97
|
+
return {
|
|
98
|
+
"success": False,
|
|
99
|
+
"error": str(e)
|
|
100
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
|
-
from email_provider import EmailProvider
|
|
2
|
+
from .email_provider import EmailProvider
|
|
3
3
|
|
|
4
4
|
# Global provider instance
|
|
5
5
|
_provider = None
|
|
@@ -16,10 +16,12 @@ async def get_provider() -> EmailProvider:
|
|
|
16
16
|
raise RuntimeError("Email provider not initialized. Call init_provider first.")
|
|
17
17
|
return _provider
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Removed @service() decorator - this is now just a helper function
|
|
20
|
+
# The actual service is in mod.py
|
|
21
|
+
async def send_email_helper(config: Dict, to: str, subject: str, body: str,
|
|
20
22
|
reply_to_message: Dict = None,
|
|
21
23
|
headers: Dict = None) -> Dict:
|
|
22
|
-
"""
|
|
24
|
+
"""Helper function to send an email (not a service)"""
|
|
23
25
|
provider = await get_provider()
|
|
24
26
|
return await provider.send_email(
|
|
25
27
|
to=to,
|
|
@@ -36,7 +36,7 @@ class SMTPHandler:
|
|
|
36
36
|
try:
|
|
37
37
|
server = await self.connect()
|
|
38
38
|
|
|
39
|
-
msg = MIMEMultipart()
|
|
39
|
+
msg = MIMEMultipart('alternative')
|
|
40
40
|
msg['From'] = self.email
|
|
41
41
|
msg['To'] = to
|
|
42
42
|
msg['Subject'] = subject
|
|
@@ -52,14 +52,20 @@ class SMTPHandler:
|
|
|
52
52
|
if not subject.lower().startswith('re:'):
|
|
53
53
|
msg['Subject'] = f"Re: {subject}"
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
# Detect if body contains HTML
|
|
56
|
+
if '<html>' in body.lower() or '<p>' in body.lower() or '<br>' in body.lower():
|
|
57
|
+
# HTML email
|
|
58
|
+
msg.attach(MIMEText(body, 'html'))
|
|
59
|
+
else:
|
|
60
|
+
# Plain text email
|
|
61
|
+
msg.attach(MIMEText(body, 'plain'))
|
|
56
62
|
|
|
57
63
|
server.send_message(msg)
|
|
58
64
|
server.quit()
|
|
59
65
|
|
|
60
66
|
return {
|
|
61
67
|
"success": True,
|
|
62
|
-
"message_id": msg
|
|
68
|
+
"message_id": msg.get('Message-ID'),
|
|
63
69
|
"error": None
|
|
64
70
|
}
|
|
65
71
|
|