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,6 +1,6 @@
|
|
|
1
1
|
from lib.providers.hooks import hook
|
|
2
2
|
from lib.route_decorators import public_route, public_routes
|
|
3
|
-
from starlette.routing import Mount
|
|
3
|
+
from starlette.routing import Mount, Route
|
|
4
4
|
import json
|
|
5
5
|
print("--- Hello from JWT mod ---")
|
|
6
6
|
|
|
@@ -8,19 +8,42 @@ print("--- Hello from JWT mod ---")
|
|
|
8
8
|
async def startup(app, context):
|
|
9
9
|
print('Running startup hook')
|
|
10
10
|
print('Registering public routes:')
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
|
|
12
|
+
def register_route(route_path, route_obj):
|
|
13
|
+
"""Helper function to register a route if it's marked as public"""
|
|
14
|
+
if hasattr(route_obj, 'endpoint') and hasattr(route_obj.endpoint, '__public_route__'):
|
|
15
|
+
print(f"Found public route: {route_path}")
|
|
16
|
+
public_routes.add(route_path)
|
|
17
|
+
return True
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
def process_routes(routes, path_prefix=""):
|
|
21
|
+
"""Recursively process routes and sub-routes"""
|
|
22
|
+
for route in routes:
|
|
23
|
+
if isinstance(route, Mount):
|
|
24
|
+
# Handle mounted sub-applications
|
|
25
|
+
mount_path = path_prefix + route.path.rstrip('/')
|
|
26
|
+
print(f"Processing mount: {mount_path}")
|
|
27
|
+
|
|
28
|
+
# Process sub-routes within the mount
|
|
29
|
+
if hasattr(route, 'routes'):
|
|
30
|
+
for sub_route in route.routes:
|
|
31
|
+
if isinstance(sub_route, Route):
|
|
32
|
+
full_path = mount_path + sub_route.path
|
|
33
|
+
if not register_route(full_path, sub_route):
|
|
34
|
+
print(f"Skipping private route: {full_path}")
|
|
35
|
+
else:
|
|
36
|
+
print(f"Skipping non-route in mount: {sub_route}")
|
|
37
|
+
|
|
38
|
+
elif isinstance(route, Route):
|
|
39
|
+
# Handle direct routes
|
|
40
|
+
full_path = path_prefix + route.path
|
|
41
|
+
if not register_route(full_path, route):
|
|
42
|
+
print(f"Skipping private route: {full_path}")
|
|
43
|
+
else:
|
|
44
|
+
print(f"Skipping unknown route type: {route}")
|
|
45
|
+
|
|
46
|
+
# Process all routes
|
|
47
|
+
process_routes(app.routes)
|
|
48
|
+
|
|
49
|
+
print(f"Final public routes registered: {public_routes}")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug script to simulate the plugin loader behavior.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import os
|
|
8
|
+
import importlib
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Add the mindroot path
|
|
12
|
+
sys.path.insert(0, '/files/mindroot/src/mindroot')
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
print("Simulating plugin loader behavior...")
|
|
16
|
+
|
|
17
|
+
# Simulate what the loader does
|
|
18
|
+
plugin_name = 'l8n'
|
|
19
|
+
category = 'core'
|
|
20
|
+
|
|
21
|
+
# Get plugin path (simulating get_plugin_path)
|
|
22
|
+
plugin_dir = '/files/mindroot/src/mindroot/coreplugins/l8n'
|
|
23
|
+
print(f"Plugin directory: {plugin_dir}")
|
|
24
|
+
|
|
25
|
+
# Check for middleware.py
|
|
26
|
+
middleware_path = os.path.join(plugin_dir, 'middleware.py')
|
|
27
|
+
print(f"Middleware path: {middleware_path}")
|
|
28
|
+
print(f"Middleware exists: {os.path.exists(middleware_path)}")
|
|
29
|
+
|
|
30
|
+
if os.path.exists(middleware_path):
|
|
31
|
+
# Simulate get_plugin_import_path
|
|
32
|
+
plugin_import_path = f'coreplugins.{plugin_name}'
|
|
33
|
+
print(f"Plugin import path: {plugin_import_path}")
|
|
34
|
+
|
|
35
|
+
# Try to import the module (this is what the loader does)
|
|
36
|
+
print("\nAttempting to import module...")
|
|
37
|
+
try:
|
|
38
|
+
module = importlib.import_module(plugin_import_path)
|
|
39
|
+
print(f"✅ Module imported: {module}")
|
|
40
|
+
except ImportError as e:
|
|
41
|
+
print(f"First import failed: {e}")
|
|
42
|
+
try:
|
|
43
|
+
module = importlib.import_module(f"{plugin_import_path}.mod")
|
|
44
|
+
print(f"✅ Module imported via .mod: {module}")
|
|
45
|
+
except ImportError as e2:
|
|
46
|
+
print(f"❌ Both imports failed: {e2}")
|
|
47
|
+
raise
|
|
48
|
+
|
|
49
|
+
# Check if module has middleware
|
|
50
|
+
print(f"\nModule has middleware: {hasattr(module, 'middleware')}")
|
|
51
|
+
|
|
52
|
+
if hasattr(module, 'middleware'):
|
|
53
|
+
middleware_func = module.middleware
|
|
54
|
+
print(f"Middleware type: {type(middleware_func)}")
|
|
55
|
+
print(f"Middleware callable: {callable(middleware_func)}")
|
|
56
|
+
|
|
57
|
+
# This is what causes the error - let's check what we're actually getting
|
|
58
|
+
print(f"Middleware repr: {repr(middleware_func)}")
|
|
59
|
+
|
|
60
|
+
# Check if it's actually a function
|
|
61
|
+
import inspect
|
|
62
|
+
print(f"Is function: {inspect.isfunction(middleware_func)}")
|
|
63
|
+
print(f"Is coroutine function: {inspect.iscoroutinefunction(middleware_func)}")
|
|
64
|
+
|
|
65
|
+
if inspect.isfunction(middleware_func):
|
|
66
|
+
print(f"Function signature: {inspect.signature(middleware_func)}")
|
|
67
|
+
|
|
68
|
+
# Try to simulate what FastAPI does
|
|
69
|
+
print("\nSimulating FastAPI middleware registration...")
|
|
70
|
+
try:
|
|
71
|
+
# This is essentially what BaseHTTPMiddleware does
|
|
72
|
+
if callable(middleware_func):
|
|
73
|
+
print("✅ Middleware function is callable")
|
|
74
|
+
else:
|
|
75
|
+
print("❌ Middleware function is not callable")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"❌ Error checking callable: {e}")
|
|
78
|
+
else:
|
|
79
|
+
print("❌ Module does not have middleware attribute")
|
|
80
|
+
print(f"Module attributes: {dir(module)}")
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"❌ Error during simulation: {e}")
|
|
84
|
+
import traceback
|
|
85
|
+
traceback.print_exc()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug script to test middleware import and functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Add the mindroot path
|
|
10
|
+
sys.path.insert(0, '/files/mindroot/src/mindroot')
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
print("Testing middleware import...")
|
|
14
|
+
import coreplugins.l8n.middleware as mw
|
|
15
|
+
print(f"✅ Middleware imported successfully")
|
|
16
|
+
print(f"✅ Has middleware function: {hasattr(mw, 'middleware')}")
|
|
17
|
+
print(f"✅ Middleware type: {type(mw.middleware)}")
|
|
18
|
+
|
|
19
|
+
# Test if the function is callable
|
|
20
|
+
import inspect
|
|
21
|
+
print(f"✅ Function signature: {inspect.signature(mw.middleware)}")
|
|
22
|
+
print(f"✅ Is coroutine function: {inspect.iscoroutinefunction(mw.middleware)}")
|
|
23
|
+
|
|
24
|
+
# Test basic functionality
|
|
25
|
+
print("\nTesting basic middleware functionality...")
|
|
26
|
+
|
|
27
|
+
# Mock request and call_next
|
|
28
|
+
class MockRequest:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.url = type('obj', (object,), {'path': '/test'})
|
|
31
|
+
self.query_params = {}
|
|
32
|
+
self.cookies = {}
|
|
33
|
+
self.headers = {}
|
|
34
|
+
self.state = type('obj', (object,), {})
|
|
35
|
+
|
|
36
|
+
class MockResponse:
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.cookies_set = {}
|
|
39
|
+
def set_cookie(self, **kwargs):
|
|
40
|
+
self.cookies_set.update(kwargs)
|
|
41
|
+
|
|
42
|
+
async def mock_call_next(request):
|
|
43
|
+
return MockResponse()
|
|
44
|
+
|
|
45
|
+
# Test the middleware
|
|
46
|
+
import asyncio
|
|
47
|
+
|
|
48
|
+
async def test_middleware():
|
|
49
|
+
request = MockRequest()
|
|
50
|
+
try:
|
|
51
|
+
response = await mw.middleware(request, mock_call_next)
|
|
52
|
+
print(f"✅ Middleware executed successfully")
|
|
53
|
+
print(f"✅ Request language set: {hasattr(request.state, 'language')}")
|
|
54
|
+
if hasattr(request.state, 'language'):
|
|
55
|
+
print(f"✅ Language: {request.state.language}")
|
|
56
|
+
return True
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"❌ Middleware execution failed: {e}")
|
|
59
|
+
import traceback
|
|
60
|
+
traceback.print_exc()
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
# Run the test
|
|
64
|
+
result = asyncio.run(test_middleware())
|
|
65
|
+
|
|
66
|
+
if result:
|
|
67
|
+
print("\n🎉 All middleware tests passed!")
|
|
68
|
+
else:
|
|
69
|
+
print("\n💥 Middleware tests failed!")
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"❌ Error during testing: {e}")
|
|
73
|
+
import traceback
|
|
74
|
+
traceback.print_exc()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
L8N_AVAILABLE = True
|
|
5
|
+
|
|
6
|
+
# Base directory for l8n plugin
|
|
7
|
+
L8N_DIR = Path(os.environ.get('MR_SOURCE_DIR', '/files/mindroot/src/mindroot/coreplugins/l8n'))
|
|
8
|
+
|
|
9
|
+
# Base directory for localized files
|
|
10
|
+
LOCALIZED_FILES_DIR = L8N_DIR / "localized_files"
|
|
11
|
+
|
|
12
|
+
# Base directory for translations
|
|
13
|
+
TRANSLATIONS_DIR = L8N_DIR / "translations"
|
|
14
|
+
|
|
15
|
+
# Global cache for translations
|
|
16
|
+
# Structure: {plugin_path: {language: {key: translation}}}
|
|
17
|
+
TRANSLATIONS = {}
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
def get_current_language_from_request() -> str:
|
|
5
|
+
"""
|
|
6
|
+
Get the current language from various request sources.
|
|
7
|
+
|
|
8
|
+
This function checks multiple sources in order of priority:
|
|
9
|
+
1. Environment variable MINDROOT_LANGUAGE
|
|
10
|
+
2. FastAPI request context (if available)
|
|
11
|
+
3. HTTP Accept-Language header
|
|
12
|
+
4. URL parameters (?lang=es)
|
|
13
|
+
5. Cookie values
|
|
14
|
+
6. User preferences from database/session
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Language code (e.g., 'en', 'es', 'fr')
|
|
18
|
+
"""
|
|
19
|
+
# 1. Check environment variable first (for testing/override)
|
|
20
|
+
env_lang = os.environ.get('MINDROOT_LANGUAGE')
|
|
21
|
+
if env_lang:
|
|
22
|
+
return env_lang
|
|
23
|
+
|
|
24
|
+
# 2. Try to get language from FastAPI request context
|
|
25
|
+
try:
|
|
26
|
+
from contextvars import ContextVar
|
|
27
|
+
import contextvars
|
|
28
|
+
|
|
29
|
+
# Try to find request context
|
|
30
|
+
request_lang = _get_language_from_fastapi_context()
|
|
31
|
+
if request_lang:
|
|
32
|
+
return request_lang
|
|
33
|
+
except (ImportError, Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
# 3. Fallback to default language
|
|
37
|
+
return 'en'
|
|
38
|
+
|
|
39
|
+
def _get_language_from_fastapi_context() -> Optional[str]:
|
|
40
|
+
"""
|
|
41
|
+
Extract language from FastAPI request context.
|
|
42
|
+
|
|
43
|
+
This function attempts to access the current FastAPI request
|
|
44
|
+
and extract language information from various sources.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Language code if found, None otherwise
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Try to get the current request from FastAPI context
|
|
51
|
+
from starlette.requests import Request
|
|
52
|
+
import contextvars
|
|
53
|
+
|
|
54
|
+
# This is a placeholder - in a real implementation, we would need
|
|
55
|
+
# to access the actual request context that MindRoot uses
|
|
56
|
+
# For now, we'll implement the detection logic structure
|
|
57
|
+
|
|
58
|
+
# Check if there's a way to get the current request
|
|
59
|
+
# This would need to be integrated with MindRoot's request handling
|
|
60
|
+
|
|
61
|
+
return None # Placeholder
|
|
62
|
+
|
|
63
|
+
except (ImportError, Exception):
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _parse_accept_language_header(accept_language: str) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Parse the Accept-Language header and return the preferred language.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
accept_language: Accept-Language header value
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Preferred language code
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
'en-US,en;q=0.9,es;q=0.8,fr;q=0.7' -> 'en'
|
|
78
|
+
"""
|
|
79
|
+
if not accept_language:
|
|
80
|
+
return 'en'
|
|
81
|
+
|
|
82
|
+
# Parse the Accept-Language header
|
|
83
|
+
languages = []
|
|
84
|
+
for lang_range in accept_language.split(','):
|
|
85
|
+
lang_range = lang_range.strip()
|
|
86
|
+
if ';q=' in lang_range:
|
|
87
|
+
lang, quality = lang_range.split(';q=', 1)
|
|
88
|
+
try:
|
|
89
|
+
quality = float(quality)
|
|
90
|
+
except ValueError:
|
|
91
|
+
quality = 1.0
|
|
92
|
+
else:
|
|
93
|
+
lang = lang_range
|
|
94
|
+
quality = 1.0
|
|
95
|
+
|
|
96
|
+
# Extract just the language code (e.g., 'en' from 'en-US')
|
|
97
|
+
lang_code = lang.split('-')[0].lower()
|
|
98
|
+
languages.append((lang_code, quality))
|
|
99
|
+
|
|
100
|
+
# Sort by quality (highest first)
|
|
101
|
+
languages.sort(key=lambda x: x[1], reverse=True)
|
|
102
|
+
|
|
103
|
+
# Return the highest quality language
|
|
104
|
+
if languages:
|
|
105
|
+
return languages[0][0]
|
|
106
|
+
|
|
107
|
+
return 'en'
|
|
108
|
+
|
|
109
|
+
def set_language_for_request(language: str):
|
|
110
|
+
"""
|
|
111
|
+
Set the language for the current request context.
|
|
112
|
+
|
|
113
|
+
This function would be called by middleware or route handlers
|
|
114
|
+
to set the language for the current request.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
language: Language code to set
|
|
118
|
+
"""
|
|
119
|
+
# Set environment variable as a simple implementation
|
|
120
|
+
os.environ['MINDROOT_LANGUAGE'] = language
|
|
121
|
+
|
|
122
|
+
# In a real implementation, this would set the language
|
|
123
|
+
# in the request context or thread-local storage
|
|
124
|
+
|
|
125
|
+
def get_supported_languages() -> list:
|
|
126
|
+
"""
|
|
127
|
+
Get the list of supported languages.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of supported language codes
|
|
131
|
+
"""
|
|
132
|
+
# This could be configured via environment variables or config files
|
|
133
|
+
supported = os.environ.get('MINDROOT_SUPPORTED_LANGUAGES', 'en,es,fr,de,it,pt,ru,zh,ja,ko')
|
|
134
|
+
return [lang.strip() for lang in supported.split(',')]
|
|
135
|
+
|
|
136
|
+
def is_language_supported(language: str) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Check if a language is supported.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
language: Language code to check
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if language is supported, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
return language in get_supported_languages()
|
|
147
|
+
|
|
148
|
+
def get_fallback_language(language: str) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Get a fallback language if the requested language is not supported.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
language: Requested language code
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Fallback language code
|
|
157
|
+
"""
|
|
158
|
+
if is_language_supported(language):
|
|
159
|
+
return language
|
|
160
|
+
|
|
161
|
+
# Language family fallbacks
|
|
162
|
+
fallbacks = {
|
|
163
|
+
'en-us': 'en',
|
|
164
|
+
'en-gb': 'en',
|
|
165
|
+
'es-es': 'es',
|
|
166
|
+
'es-mx': 'es',
|
|
167
|
+
'fr-fr': 'fr',
|
|
168
|
+
'fr-ca': 'fr',
|
|
169
|
+
'de-de': 'de',
|
|
170
|
+
'de-at': 'de',
|
|
171
|
+
'pt-br': 'pt',
|
|
172
|
+
'pt-pt': 'pt',
|
|
173
|
+
'zh-cn': 'zh',
|
|
174
|
+
'zh-tw': 'zh',
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Try fallback
|
|
178
|
+
fallback = fallbacks.get(language.lower())
|
|
179
|
+
if fallback and is_language_supported(fallback):
|
|
180
|
+
return fallback
|
|
181
|
+
|
|
182
|
+
# Default to English
|
|
183
|
+
return 'en'
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from fastapi import Request
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from .language_detection import (
|
|
5
|
+
_parse_accept_language_header,
|
|
6
|
+
get_fallback_language,
|
|
7
|
+
set_language_for_request
|
|
8
|
+
)
|
|
9
|
+
except ImportError:
|
|
10
|
+
# For standalone testing
|
|
11
|
+
from language_detection import (
|
|
12
|
+
_parse_accept_language_header,
|
|
13
|
+
get_fallback_language,
|
|
14
|
+
set_language_for_request
|
|
15
|
+
)
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
# Global variable to store the current request language
|
|
19
|
+
# This will be set by the middleware for each request
|
|
20
|
+
_current_request_language = None
|
|
21
|
+
|
|
22
|
+
def get_request_language() -> str:
|
|
23
|
+
"""
|
|
24
|
+
Get the language that was detected for the current request.
|
|
25
|
+
|
|
26
|
+
This function is called by the monkey patch system to get the
|
|
27
|
+
language for template translation.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Language code for the current request
|
|
31
|
+
"""
|
|
32
|
+
global _current_request_language
|
|
33
|
+
print(f"L8n: Current request language is '{_current_request_language}'")
|
|
34
|
+
# If we have a request-specific language, use it
|
|
35
|
+
if _current_request_language:
|
|
36
|
+
return _current_request_language
|
|
37
|
+
|
|
38
|
+
# Fallback to environment variable or default
|
|
39
|
+
print("L8n: No request language set, falling back to environment variable or default")
|
|
40
|
+
return os.environ.get('MINDROOT_LANGUAGE', 'en')
|
|
41
|
+
|
|
42
|
+
def detect_language_from_request(request: Request) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Detect the preferred language from a FastAPI request.
|
|
45
|
+
|
|
46
|
+
Checks multiple sources in order of priority:
|
|
47
|
+
1. URL parameter (?lang=es)
|
|
48
|
+
2. Cookie value (mindroot_language)
|
|
49
|
+
3. Accept-Language header
|
|
50
|
+
4. Environment variable
|
|
51
|
+
5. Default to 'en'
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
request: FastAPI Request object
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Detected language code
|
|
58
|
+
"""
|
|
59
|
+
# 1. Check URL parameter first (highest priority)
|
|
60
|
+
print(f"L8n: Checking URL parameters for language: {request.query_params}")
|
|
61
|
+
url_lang = request.query_params.get('lang')
|
|
62
|
+
if url_lang:
|
|
63
|
+
|
|
64
|
+
return get_fallback_language(url_lang.lower())
|
|
65
|
+
|
|
66
|
+
# 2. Check cookie value
|
|
67
|
+
print(f"L8n: Checking cookies for language: {request.cookies}")
|
|
68
|
+
cookie_lang = request.cookies.get('mindroot_language')
|
|
69
|
+
if cookie_lang:
|
|
70
|
+
print(f"L8n: Found language in cookie: {cookie_lang}")
|
|
71
|
+
return get_fallback_language(cookie_lang.lower())
|
|
72
|
+
|
|
73
|
+
# 3. Check Accept-Language header
|
|
74
|
+
print(f"L8n: Accept-Language header: {request.headers.get('accept-language')}")
|
|
75
|
+
accept_language = request.headers.get('accept-language')
|
|
76
|
+
if accept_language:
|
|
77
|
+
print(f"L8n: Parsing Accept-Language header: {accept_language}")
|
|
78
|
+
parsed_lang = _parse_accept_language_header(accept_language)
|
|
79
|
+
if parsed_lang:
|
|
80
|
+
print(f"L8n: Parsed language from header: {parsed_lang}")
|
|
81
|
+
return get_fallback_language(parsed_lang)
|
|
82
|
+
|
|
83
|
+
# 4. Check environment variable
|
|
84
|
+
env_lang = os.environ.get('MINDROOT_LANGUAGE')
|
|
85
|
+
if env_lang:
|
|
86
|
+
return get_fallback_language(env_lang.lower())
|
|
87
|
+
|
|
88
|
+
# 5. Default to English
|
|
89
|
+
return 'en'
|
|
90
|
+
|
|
91
|
+
async def middleware(request: Request, call_next):
|
|
92
|
+
"""
|
|
93
|
+
L8n middleware for language detection and setting.
|
|
94
|
+
|
|
95
|
+
This middleware runs early in the request pipeline to:
|
|
96
|
+
1. Detect the preferred language for the request
|
|
97
|
+
2. Store it globally for use by the template system
|
|
98
|
+
3. Set it in the request state for other components
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
request: FastAPI Request object
|
|
102
|
+
call_next: Next middleware/handler in the chain
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Response from the next handler
|
|
106
|
+
"""
|
|
107
|
+
global _current_request_language
|
|
108
|
+
try:
|
|
109
|
+
print("L8n middleware: Starting language detection")
|
|
110
|
+
# Detect the language for this request
|
|
111
|
+
detected_language = detect_language_from_request(request)
|
|
112
|
+
|
|
113
|
+
print(f"L8n: Detected language '{detected_language}' for {request.url.path}")
|
|
114
|
+
# Store it globally for the template system
|
|
115
|
+
_current_request_language = detected_language
|
|
116
|
+
|
|
117
|
+
# Also store it in request state for other components
|
|
118
|
+
request.state.language = detected_language
|
|
119
|
+
|
|
120
|
+
# Set it using the language detection system
|
|
121
|
+
set_language_for_request(detected_language)
|
|
122
|
+
|
|
123
|
+
# Debug logging (can be removed in production)
|
|
124
|
+
print(f"L8n: Detected language '{detected_language}' for {request.url.path}")
|
|
125
|
+
|
|
126
|
+
# Process the request
|
|
127
|
+
response = await call_next(request)
|
|
128
|
+
|
|
129
|
+
# Optionally set a cookie to remember the language preference
|
|
130
|
+
# Only set if it was explicitly requested via URL parameter
|
|
131
|
+
if request.query_params.get('lang'):
|
|
132
|
+
response.set_cookie(
|
|
133
|
+
key="mindroot_language",
|
|
134
|
+
value=detected_language,
|
|
135
|
+
max_age=30 * 24 * 60 * 60, # 30 days
|
|
136
|
+
httponly=True,
|
|
137
|
+
samesite="lax"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return response
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"L8n middleware error: {e}")
|
|
144
|
+
# If there's an error, continue with default language
|
|
145
|
+
_current_request_language = 'en'
|
|
146
|
+
request.state.language = 'en'
|
|
147
|
+
return await call_next(request)
|
|
148
|
+
|
|
149
|
+
finally:
|
|
150
|
+
# Clean up the global variable after the request
|
|
151
|
+
_current_request_language = None
|