mindroot 9.3.0__py3-none-any.whl → 9.5.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.
Files changed (183) hide show
  1. mindroot/coreplugins/admin/__init__.py +3 -1
  2. mindroot/coreplugins/admin/agent_router.py +250 -7
  3. mindroot/coreplugins/admin/asset_manager.py +164 -0
  4. mindroot/coreplugins/admin/command_router.py +236 -1
  5. mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
  6. mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
  7. mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
  8. mindroot/coreplugins/admin/mcp_routes.py +216 -0
  9. mindroot/coreplugins/admin/mod.py +62 -0
  10. mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
  11. mindroot/coreplugins/admin/persona_handler.py +15 -6
  12. mindroot/coreplugins/admin/persona_router.py +158 -2
  13. mindroot/coreplugins/admin/plugin_manager.py +63 -0
  14. mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
  15. mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
  16. mindroot/coreplugins/admin/plugin_routes.py +114 -0
  17. mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
  18. mindroot/coreplugins/admin/router.py +116 -15
  19. mindroot/coreplugins/admin/service_models.py +1 -1
  20. mindroot/coreplugins/admin/settings_router.py +1 -0
  21. mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
  22. mindroot/coreplugins/admin/static/css/dark.css +1 -0
  23. mindroot/coreplugins/admin/static/css/default.css +4 -0
  24. mindroot/coreplugins/admin/static/js/about-info.js +367 -0
  25. mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
  26. mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
  27. mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
  28. mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
  29. mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
  30. mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
  31. mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
  32. mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
  33. mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
  34. mindroot/coreplugins/admin/static/js/registry-manager-old.js +385 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  36. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  37. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  39. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  40. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  41. mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
  42. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  43. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  44. mindroot/coreplugins/admin/static/logo.png +0 -0
  45. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  46. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  47. mindroot/coreplugins/agent/agent.py +2 -2
  48. mindroot/coreplugins/agent/command_parser.py +25 -10
  49. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  50. mindroot/coreplugins/chat/__init__.py +4 -1
  51. mindroot/coreplugins/chat/router.py +132 -20
  52. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  53. mindroot/coreplugins/chat/services.py +31 -1
  54. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  55. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  56. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  57. mindroot/coreplugins/chat/static/css/default.css +24 -3
  58. mindroot/coreplugins/chat/static/css/main.css +1 -0
  59. mindroot/coreplugins/chat/static/js/action.js +137 -60
  60. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  61. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  62. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  63. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  64. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  65. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  66. mindroot/coreplugins/chat/widget_manager.py +139 -0
  67. mindroot/coreplugins/chat/widget_routes.py +287 -0
  68. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  69. mindroot/coreplugins/email/__init__.py +2 -0
  70. mindroot/coreplugins/email/email_provider.py +2 -2
  71. mindroot/coreplugins/email/mod.py +100 -0
  72. mindroot/coreplugins/email/services.py +5 -3
  73. mindroot/coreplugins/email/smtp_handler.py +9 -3
  74. mindroot/coreplugins/email/test_email_service.py +75 -0
  75. mindroot/coreplugins/env_manager/mod.py +61 -25
  76. mindroot/coreplugins/home/router.py +37 -2
  77. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  82. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  83. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  84. mindroot/coreplugins/index/indices/default/index.json +6 -6
  85. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  86. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  87. mindroot/coreplugins/l8n/__init__.py +6 -0
  88. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  89. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  90. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  91. mindroot/coreplugins/l8n/language_detection.py +183 -0
  92. mindroot/coreplugins/l8n/middleware.py +151 -0
  93. mindroot/coreplugins/l8n/mod.py +277 -0
  94. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  95. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  96. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  97. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  98. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  99. mindroot/coreplugins/l8n/utils.py +232 -0
  100. mindroot/coreplugins/mcp_/__init__.py +14 -0
  101. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  102. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  103. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  104. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  105. mindroot/coreplugins/mcp_/mod.py +367 -0
  106. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  107. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  108. mindroot/coreplugins/mcp_/setup.py +26 -0
  109. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  110. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  111. mindroot/coreplugins/persona/mod.py +12 -7
  112. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  113. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  114. mindroot/coreplugins/subscriptions/mod.py +14 -3
  115. mindroot/coreplugins/subscriptions/router.py +3 -0
  116. mindroot/coreplugins/user_service/__init__.py +1 -2
  117. mindroot/coreplugins/user_service/admin_init.py +1 -0
  118. mindroot/coreplugins/user_service/email_service.py +72 -17
  119. mindroot/coreplugins/user_service/mod.py +10 -2
  120. mindroot/coreplugins/user_service/router.py +2 -0
  121. mindroot/lib/auth/api_key.py +28 -0
  122. mindroot/lib/cli/plugins.py +94 -0
  123. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  124. mindroot/lib/plugins/installation.py +5 -5
  125. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  126. mindroot/lib/plugins/loader.py +33 -3
  127. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  128. mindroot/lib/plugins/manifest.py +236 -24
  129. mindroot/lib/providers/commands.py +3 -1
  130. mindroot/lib/route_decorators.py +5 -5
  131. mindroot/lib/templates.py +183 -11
  132. mindroot/lib/utils/merge_arrays.py +1 -1
  133. mindroot/migrate.py +39 -20
  134. mindroot/registry/data_access.py +1 -1
  135. mindroot/server.py +42 -13
  136. mindroot/server_missing_normal_args.py +197 -0
  137. mindroot/server_prev.py +173 -0
  138. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
  139. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +144 -112
  140. mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
  141. mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
  142. mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
  143. mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
  144. mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
  145. mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
  146. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
  147. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  148. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  149. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  150. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  151. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  152. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
  153. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  154. mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
  155. mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
  156. mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
  157. mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
  158. mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
  159. mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
  160. mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
  161. mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
  162. mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
  163. mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
  164. mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
  165. mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
  166. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
  167. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  168. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  169. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  170. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  171. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  172. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
  173. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  174. mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
  175. mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
  176. mindroot/coreplugins/index/default.json +0 -76
  177. mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
  178. mindroot/coreplugins/user_service/hooks.py +0 -23
  179. /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
  180. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- {"name":"","short_name":"","icons":[{"src":"/static/favicon/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/static/favicon/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
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="/chat/static/favicon/apple-touch-icon.png">
33
- <link rel="icon" type="image/png" sizes="32x32" href="/chat/static/favicon/favicon-32x32.png">
34
- <link rel="icon" type="image/png" sizes="16x16" href="/chat/static/favicon/favicon-16x16.png">
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
+
@@ -3,7 +3,7 @@
3
3
  {% endblock %}
4
4
 
5
5
  {% block content %}
6
- <details>
6
+ <details data-inject-category="advanced">
7
7
  <summary><span class="material-icons">checklist</span>
8
8
  <span>Checklist Helper</span>
9
9
  </summary>
@@ -0,0 +1,2 @@
1
+ from .mod import *
2
+ from .services import *
@@ -1,6 +1,6 @@
1
1
  from typing import Dict, List
2
- from smtp_handler import SMTPHandler
3
- from imap_handler import IMAPHandler
2
+ from .smtp_handler import SMTPHandler
3
+ from .imap_handler import IMAPHandler
4
4
  import logging
5
5
 
6
6
  logger = logging.getLogger(__name__)
@@ -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
- async def send_email(config: Dict, to: str, subject: str, body: str,
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
- """Service to send an email"""
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
- msg.attach(MIMEText(body, 'plain'))
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['Message-ID'],
68
+ "message_id": msg.get('Message-ID'),
63
69
  "error": None
64
70
  }
65
71