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.
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 +105 -9
  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-publish-old-delete.js +166 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  36. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  37. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  39. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  40. mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
  41. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  42. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  43. mindroot/coreplugins/admin/static/logo.png +0 -0
  44. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  45. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  46. mindroot/coreplugins/agent/agent.py +2 -2
  47. mindroot/coreplugins/agent/command_parser.py +25 -10
  48. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  49. mindroot/coreplugins/chat/__init__.py +4 -1
  50. mindroot/coreplugins/chat/router.py +132 -20
  51. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  52. mindroot/coreplugins/chat/services.py +31 -1
  53. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  54. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  55. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  56. mindroot/coreplugins/chat/static/css/default.css +24 -3
  57. mindroot/coreplugins/chat/static/css/main.css +1 -0
  58. mindroot/coreplugins/chat/static/js/action.js +137 -60
  59. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  60. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  61. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  62. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  63. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  64. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  65. mindroot/coreplugins/chat/widget_manager.py +139 -0
  66. mindroot/coreplugins/chat/widget_routes.py +287 -0
  67. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  68. mindroot/coreplugins/email/__init__.py +2 -0
  69. mindroot/coreplugins/email/email_provider.py +2 -2
  70. mindroot/coreplugins/email/mod.py +100 -0
  71. mindroot/coreplugins/email/services.py +5 -3
  72. mindroot/coreplugins/email/smtp_handler.py +9 -3
  73. mindroot/coreplugins/email/test_email_service.py +75 -0
  74. mindroot/coreplugins/env_manager/mod.py +61 -25
  75. mindroot/coreplugins/home/router.py +37 -2
  76. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  77. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  82. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  83. mindroot/coreplugins/index/indices/default/index.json +39 -6
  84. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  85. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  86. mindroot/coreplugins/l8n/__init__.py +6 -0
  87. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  88. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  89. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  90. mindroot/coreplugins/l8n/language_detection.py +183 -0
  91. mindroot/coreplugins/l8n/middleware.py +151 -0
  92. mindroot/coreplugins/l8n/mod.py +277 -0
  93. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  94. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  95. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  96. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  97. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  98. mindroot/coreplugins/l8n/utils.py +232 -0
  99. mindroot/coreplugins/mcp_/__init__.py +14 -0
  100. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  101. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  102. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  103. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  104. mindroot/coreplugins/mcp_/mod.py +367 -0
  105. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  106. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  107. mindroot/coreplugins/mcp_/setup.py +26 -0
  108. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  109. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  110. mindroot/coreplugins/persona/mod.py +12 -7
  111. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  112. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  113. mindroot/coreplugins/subscriptions/mod.py +14 -3
  114. mindroot/coreplugins/subscriptions/router.py +3 -0
  115. mindroot/coreplugins/user_service/__init__.py +1 -2
  116. mindroot/coreplugins/user_service/admin_init.py +1 -0
  117. mindroot/coreplugins/user_service/email_service.py +72 -17
  118. mindroot/coreplugins/user_service/mod.py +10 -2
  119. mindroot/coreplugins/user_service/router.py +2 -0
  120. mindroot/lib/auth/api_key.py +28 -0
  121. mindroot/lib/cli/plugins.py +94 -0
  122. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  123. mindroot/lib/plugins/installation.py +5 -5
  124. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  125. mindroot/lib/plugins/loader.py +33 -3
  126. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  127. mindroot/lib/plugins/manifest.py +236 -24
  128. mindroot/lib/providers/commands.py +3 -1
  129. mindroot/lib/route_decorators.py +5 -5
  130. mindroot/lib/templates.py +183 -11
  131. mindroot/lib/utils/merge_arrays.py +1 -1
  132. mindroot/migrate.py +39 -20
  133. mindroot/registry/data_access.py +1 -1
  134. mindroot/server.py +42 -13
  135. mindroot/server_missing_normal_args.py +197 -0
  136. mindroot/server_prev.py +173 -0
  137. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
  138. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
  139. mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
  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.6.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
  183. {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
- for route in app.routes:
12
- print(route)
13
- if isinstance(route, Mount):
14
- for sub_route in route.routes:
15
- if hasattr(sub_route, 'endpoint') and hasattr(sub_route.endpoint, '__public_route__'):
16
- print(f"Found public route: {route.path}{sub_route.path}")
17
- public_routes.add(f"{route.path}{sub_route.path}")
18
- else:
19
- print(f"Skipping private route: {route.path}{sub_route}")
20
- elif hasattr(route, 'endpoint') and hasattr(route.endpoint, '__public_route__'):
21
- print(f"Found public route: {route.path}")
22
- public_routes.add(route.path)
23
- else:
24
- print(f"Skipping private route: {route}")
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,6 @@
1
+ # Import all commands and services from mod.py
2
+
3
+ from .mod import *
4
+ from .middleware import middleware
5
+
6
+
@@ -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