open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636295__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 (307) hide show
  1. open_swarm-0.1.1748636295.dist-info/METADATA +257 -0
  2. open_swarm-0.1.1748636295.dist-info/RECORD +89 -0
  3. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636295.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636295.dist-info/top_level.txt +1 -0
  6. swarm/__init__.py +2 -0
  7. swarm/agent/agent.py +49 -0
  8. swarm/auth.py +48 -113
  9. swarm/consumers.py +0 -19
  10. swarm/core.py +411 -0
  11. swarm/extensions/blueprint/__init__.py +16 -30
  12. swarm/extensions/blueprint/agent_utils.py +45 -0
  13. swarm/extensions/blueprint/blueprint_base.py +562 -0
  14. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  15. swarm/extensions/blueprint/django_utils.py +79 -181
  16. swarm/extensions/blueprint/interactive_mode.py +72 -67
  17. swarm/extensions/blueprint/output_utils.py +82 -0
  18. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  19. swarm/extensions/cli/cli_args.py +0 -6
  20. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  21. swarm/extensions/cli/commands/config_management.py +6 -5
  22. swarm/extensions/cli/commands/edit_config.py +7 -16
  23. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  24. swarm/extensions/cli/commands/validate_env.py +4 -11
  25. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  26. swarm/extensions/cli/interactive_shell.py +2 -16
  27. swarm/extensions/config/config_loader.py +345 -107
  28. swarm/{core → extensions/config}/config_manager.py +38 -50
  29. swarm/{core → extensions/config}/server_config.py +0 -32
  30. swarm/extensions/launchers/build_launchers.py +14 -0
  31. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  32. swarm/extensions/launchers/swarm_api.py +64 -8
  33. swarm/extensions/launchers/swarm_cli.py +300 -8
  34. swarm/extensions/mcp/__init__.py +1 -0
  35. swarm/extensions/mcp/cache_utils.py +32 -0
  36. swarm/extensions/mcp/mcp_client.py +233 -0
  37. swarm/extensions/mcp/mcp_tool_provider.py +135 -0
  38. swarm/extensions/mcp/mcp_utils.py +260 -0
  39. swarm/llm/chat_completion.py +166 -0
  40. swarm/serializers.py +5 -96
  41. swarm/settings.py +133 -85
  42. swarm/types.py +91 -0
  43. swarm/urls.py +74 -57
  44. swarm/utils/context_utils.py +4 -10
  45. swarm/utils/general_utils.py +0 -21
  46. swarm/utils/redact.py +36 -23
  47. swarm/views/api_views.py +39 -48
  48. swarm/views/chat_views.py +76 -236
  49. swarm/views/core_views.py +87 -80
  50. swarm/views/model_views.py +121 -64
  51. swarm/views/utils.py +439 -65
  52. swarm/views/web_views.py +2 -2
  53. open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
  54. open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
  55. open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
  56. swarm/blueprints/README.md +0 -68
  57. swarm/blueprints/blueprint_audit_status.json +0 -27
  58. swarm/blueprints/chatbot/README.md +0 -40
  59. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  60. swarm/blueprints/chatbot/metadata.json +0 -23
  61. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  62. swarm/blueprints/chucks_angels/README.md +0 -11
  63. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  64. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  65. swarm/blueprints/codey/CODEY.md +0 -15
  66. swarm/blueprints/codey/README.md +0 -115
  67. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  68. swarm/blueprints/codey/codey_cli.py +0 -373
  69. swarm/blueprints/codey/instructions.md +0 -17
  70. swarm/blueprints/codey/metadata.json +0 -23
  71. swarm/blueprints/common/operation_box_utils.py +0 -83
  72. swarm/blueprints/digitalbutlers/README.md +0 -11
  73. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  74. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  75. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  76. swarm/blueprints/divine_code/README.md +0 -3
  77. swarm/blueprints/divine_code/__init__.py +0 -10
  78. swarm/blueprints/divine_code/apps.py +0 -11
  79. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  80. swarm/blueprints/django_chat/apps.py +0 -6
  81. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  82. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  83. swarm/blueprints/django_chat/urls.py +0 -8
  84. swarm/blueprints/django_chat/views.py +0 -32
  85. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  86. swarm/blueprints/flock/README.md +0 -11
  87. swarm/blueprints/flock/__init__.py +0 -8
  88. swarm/blueprints/flock/blueprint_flock.py +0 -7
  89. swarm/blueprints/flock/test_basic.py +0 -3
  90. swarm/blueprints/geese/README.md +0 -10
  91. swarm/blueprints/geese/__init__.py +0 -8
  92. swarm/blueprints/geese/blueprint_geese.py +0 -384
  93. swarm/blueprints/geese/geese_cli.py +0 -102
  94. swarm/blueprints/jeeves/README.md +0 -41
  95. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  96. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  97. swarm/blueprints/jeeves/metadata.json +0 -24
  98. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  99. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  100. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  101. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  102. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  103. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  104. swarm/blueprints/poets/blueprint_poets.py +0 -546
  105. swarm/blueprints/poets/poets_cli.py +0 -23
  106. swarm/blueprints/rue_code/README.md +0 -8
  107. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  108. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  109. swarm/blueprints/stewie/apps.py +0 -12
  110. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  111. swarm/blueprints/stewie/models.py +0 -19
  112. swarm/blueprints/stewie/serializers.py +0 -10
  113. swarm/blueprints/stewie/settings.py +0 -17
  114. swarm/blueprints/stewie/urls.py +0 -11
  115. swarm/blueprints/stewie/views.py +0 -26
  116. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  117. swarm/blueprints/whinge_surf/README.md +0 -22
  118. swarm/blueprints/whinge_surf/__init__.py +0 -1
  119. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  120. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  121. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  122. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  123. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  124. swarm/blueprints/zeus/__init__.py +0 -2
  125. swarm/blueprints/zeus/apps.py +0 -4
  126. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  127. swarm/blueprints/zeus/zeus_cli.py +0 -13
  128. swarm/cli/async_input.py +0 -65
  129. swarm/cli/async_input_demo.py +0 -32
  130. swarm/core/agent_utils.py +0 -21
  131. swarm/core/blueprint_base.py +0 -769
  132. swarm/core/blueprint_discovery.py +0 -125
  133. swarm/core/blueprint_runner.py +0 -59
  134. swarm/core/blueprint_ux.py +0 -109
  135. swarm/core/build_launchers.py +0 -15
  136. swarm/core/cli/__init__.py +0 -1
  137. swarm/core/cli/commands/__init__.py +0 -1
  138. swarm/core/cli/commands/blueprint_management.py +0 -7
  139. swarm/core/cli/interactive_shell.py +0 -14
  140. swarm/core/cli/main.py +0 -50
  141. swarm/core/cli/utils/__init__.py +0 -1
  142. swarm/core/cli/utils/discover_commands.py +0 -18
  143. swarm/core/config_loader.py +0 -122
  144. swarm/core/output_utils.py +0 -193
  145. swarm/core/session_logger.py +0 -42
  146. swarm/core/slash_commands.py +0 -89
  147. swarm/core/swarm_api.py +0 -68
  148. swarm/core/swarm_cli.py +0 -216
  149. swarm/core/utils/__init__.py +0 -0
  150. swarm/extensions/blueprint/cli_handler.py +0 -197
  151. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  152. swarm/extensions/cli/utils/__init__.py +0 -1
  153. swarm/extensions/cli/utils/async_input.py +0 -46
  154. swarm/extensions/cli/utils/prompt_user.py +0 -3
  155. swarm/management/__init__.py +0 -0
  156. swarm/management/commands/__init__.py +0 -0
  157. swarm/management/commands/runserver.py +0 -58
  158. swarm/middleware.py +0 -65
  159. swarm/permissions.py +0 -38
  160. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  161. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  162. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  163. swarm/static/contrib/markedjs/marked.min.js +0 -6
  164. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  165. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  166. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  167. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  168. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  169. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  170. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  171. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  172. swarm/static/contrib/tabler-icons/download.svg +0 -21
  173. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  174. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  175. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  176. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  177. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  178. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  179. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  180. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  181. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  182. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  183. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  184. swarm/static/contrib/tabler-icons/message.svg +0 -21
  185. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  186. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  187. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  188. swarm/static/contrib/tabler-icons/search.svg +0 -19
  189. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  190. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  191. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  192. swarm/static/css/dropdown.css +0 -22
  193. swarm/static/htmx/htmx.min.js +0 -0
  194. swarm/static/js/dropdown.js +0 -23
  195. swarm/static/rest_mode/css/base.css +0 -470
  196. swarm/static/rest_mode/css/chat-history.css +0 -286
  197. swarm/static/rest_mode/css/chat.css +0 -251
  198. swarm/static/rest_mode/css/chatbot.css +0 -74
  199. swarm/static/rest_mode/css/chatgpt.css +0 -62
  200. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  201. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  202. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  203. swarm/static/rest_mode/css/general.css +0 -142
  204. swarm/static/rest_mode/css/layout.css +0 -167
  205. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  206. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  207. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  208. swarm/static/rest_mode/css/messages.css +0 -84
  209. swarm/static/rest_mode/css/messenger.css +0 -135
  210. swarm/static/rest_mode/css/settings.css +0 -91
  211. swarm/static/rest_mode/css/simple.css +0 -44
  212. swarm/static/rest_mode/css/slack.css +0 -58
  213. swarm/static/rest_mode/css/style.css +0 -156
  214. swarm/static/rest_mode/css/theme.css +0 -30
  215. swarm/static/rest_mode/css/toast.css +0 -40
  216. swarm/static/rest_mode/js/auth.js +0 -9
  217. swarm/static/rest_mode/js/blueprint.js +0 -41
  218. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  219. swarm/static/rest_mode/js/chatLogic.js +0 -79
  220. swarm/static/rest_mode/js/debug.js +0 -63
  221. swarm/static/rest_mode/js/events.js +0 -98
  222. swarm/static/rest_mode/js/main.js +0 -19
  223. swarm/static/rest_mode/js/messages.js +0 -264
  224. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  225. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  226. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  227. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  228. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  229. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  230. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  231. swarm/static/rest_mode/js/modules/state.js +0 -7
  232. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  233. swarm/static/rest_mode/js/modules/validation.js +0 -23
  234. swarm/static/rest_mode/js/rendering.js +0 -119
  235. swarm/static/rest_mode/js/settings.js +0 -130
  236. swarm/static/rest_mode/js/sidebar.js +0 -94
  237. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  238. swarm/static/rest_mode/js/slackLogic.js +0 -66
  239. swarm/static/rest_mode/js/splash.js +0 -76
  240. swarm/static/rest_mode/js/theme.js +0 -111
  241. swarm/static/rest_mode/js/toast.js +0 -36
  242. swarm/static/rest_mode/js/ui.js +0 -265
  243. swarm/static/rest_mode/js/validation.js +0 -57
  244. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  245. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  246. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  247. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  248. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  249. swarm/static/rest_mode/svg/attach.svg +0 -8
  250. swarm/static/rest_mode/svg/avatar.svg +0 -7
  251. swarm/static/rest_mode/svg/canvas.svg +0 -6
  252. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  253. swarm/static/rest_mode/svg/close.svg +0 -5
  254. swarm/static/rest_mode/svg/copy.svg +0 -4
  255. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  256. swarm/static/rest_mode/svg/edit.svg +0 -5
  257. swarm/static/rest_mode/svg/layout.svg +0 -9
  258. swarm/static/rest_mode/svg/logo.svg +0 -29
  259. swarm/static/rest_mode/svg/logout.svg +0 -5
  260. swarm/static/rest_mode/svg/mobile.svg +0 -5
  261. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  262. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  263. swarm/static/rest_mode/svg/plus.svg +0 -7
  264. swarm/static/rest_mode/svg/run_code.svg +0 -6
  265. swarm/static/rest_mode/svg/save.svg +0 -4
  266. swarm/static/rest_mode/svg/search.svg +0 -6
  267. swarm/static/rest_mode/svg/settings.svg +0 -4
  268. swarm/static/rest_mode/svg/speaker.svg +0 -5
  269. swarm/static/rest_mode/svg/stop.svg +0 -6
  270. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  271. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  272. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  273. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  274. swarm/static/rest_mode/svg/trash.svg +0 -10
  275. swarm/static/rest_mode/svg/undo.svg +0 -3
  276. swarm/static/rest_mode/svg/visible.svg +0 -8
  277. swarm/static/rest_mode/svg/voice.svg +0 -10
  278. swarm/templates/account/login.html +0 -22
  279. swarm/templates/account/signup.html +0 -32
  280. swarm/templates/base.html +0 -30
  281. swarm/templates/chat.html +0 -43
  282. swarm/templates/index.html +0 -35
  283. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  284. swarm/templates/rest_mode/components/header.html +0 -45
  285. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  286. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  287. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  288. swarm/templates/rest_mode/components/top_bar.html +0 -28
  289. swarm/templates/rest_mode/message_ui.html +0 -50
  290. swarm/templates/rest_mode/slackbot.html +0 -30
  291. swarm/templates/simple_blueprint_page.html +0 -24
  292. swarm/templates/websocket_partials/final_system_message.html +0 -3
  293. swarm/templates/websocket_partials/system_message.html +0 -4
  294. swarm/templates/websocket_partials/user_message.html +0 -5
  295. swarm/utils/ansi_box.py +0 -34
  296. swarm/utils/disable_tracing.py +0 -38
  297. swarm/utils/log_utils.py +0 -63
  298. swarm/utils/openai_patch.py +0 -33
  299. swarm/ux/ansi_box.py +0 -43
  300. swarm/ux/spinner.py +0 -53
  301. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636295.dist-info}/licenses/LICENSE +0 -0
  302. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  303. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  304. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  305. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  306. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  307. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
swarm/views/chat_views.py CHANGED
@@ -1,243 +1,83 @@
1
-
2
- # --- Content for src/swarm/views/chat_views.py ---
3
- import logging
4
- import json
5
- import uuid
6
- import time
7
- import asyncio
8
- from typing import Dict, Any, AsyncGenerator, List, Optional
9
-
10
- from django.shortcuts import render
11
- from django.http import StreamingHttpResponse, JsonResponse, Http404, HttpRequest, HttpResponse, HttpResponseBase
12
- from django.views import View
13
- from django.utils.decorators import method_decorator
1
+ """
2
+ Chat-related views for Open Swarm MCP Core.
3
+ """
4
+ import asyncio # Import asyncio
14
5
  from django.views.decorators.csrf import csrf_exempt
15
- from django.contrib.auth.decorators import login_required
16
- from django.conf import settings
17
- from django.urls import reverse # Needed for reverse() used in tests
18
-
19
- from rest_framework import status
20
- from rest_framework.views import APIView
21
6
  from rest_framework.response import Response
22
- from rest_framework.permissions import IsAuthenticated, AllowAny
23
- from rest_framework.exceptions import ValidationError, PermissionDenied, NotFound, APIException, ParseError, NotAuthenticated
24
- from rest_framework.request import Request # Import DRF Request
25
-
26
- # Utility to wrap sync functions for async execution
27
- from asgiref.sync import sync_to_async
28
-
29
- # Assuming serializers are in the same app
30
- from swarm.serializers import ChatCompletionRequestSerializer
31
- # Assuming utils are in the same app/directory level
32
- # Make sure these utils are async-safe or wrapped if they perform sync I/O
33
- from .utils import get_blueprint_instance, validate_model_access, get_available_blueprints
34
- # Import custom permission
35
- from swarm.auth import HasValidTokenOrSession # Keep this import
36
-
37
- logger = logging.getLogger(__name__)
38
- # Specific logger for debug prints, potentially configured differently
39
- print_logger = logging.getLogger('print_debug')
40
-
41
- # ==============================================================================
42
- # API Views (DRF based)
43
- # ==============================================================================
44
-
45
- class HealthCheckView(APIView):
46
- """ Simple health check endpoint. """
47
- permission_classes = [AllowAny]
48
- def get(self, request, *args, **kwargs):
49
- """ Returns simple 'ok' status. """
50
- return Response({"status": "ok"})
51
-
52
- class ChatCompletionsView(APIView):
53
- """
54
- Handles chat completion requests (/v1/chat/completions), compatible with OpenAI API spec.
55
- Supports both streaming and non-streaming responses.
56
- Uses asynchronous handling for potentially long-running blueprint operations.
57
- """
58
- # Default serializer class for request validation.
59
- serializer_class = ChatCompletionRequestSerializer
60
- # Default permission classes are likely set in settings.py
61
- # permission_classes = [IsAuthenticated] # Example default
62
-
63
- # --- Internal Helper Methods (Unchanged) ---
64
-
65
- async def _handle_non_streaming(self, blueprint_instance, messages: List[Dict[str, str]], request_id: str, model_name: str) -> Response:
66
- """ Handles non-streaming requests. """
67
- logger.info(f"[ReqID: {request_id}] Processing non-streaming request for model '{model_name}'.")
68
- final_response_data = None; start_time = time.time()
69
- try:
70
- # The blueprint's run method should be an async generator.
71
- async_generator = blueprint_instance.run(messages)
72
- async for chunk in async_generator:
73
- # Check if the chunk contains the expected final message list.
74
- if isinstance(chunk, dict) and "messages" in chunk and isinstance(chunk["messages"], list):
75
- final_response_data = chunk["messages"]
76
- logger.debug(f"[ReqID: {request_id}] Received final data chunk.")
77
- break # Stop after getting the final data
78
- else:
79
- logger.warning(f"[ReqID: {request_id}] Unexpected chunk format during non-streaming run: {chunk}")
80
-
81
- if not final_response_data or not isinstance(final_response_data, list) or not final_response_data:
82
- logger.error(f"[ReqID: {request_id}] Blueprint '{model_name}' did not return a valid final message list. Got: {final_response_data}")
83
- raise APIException("Blueprint did not return valid data.", code=status.HTTP_500_INTERNAL_SERVER_ERROR)
84
-
85
- if not isinstance(final_response_data[0], dict) or 'role' not in final_response_data[0]:
86
- logger.error(f"[ReqID: {request_id}] Blueprint '{model_name}' returned invalid message structure. Got: {final_response_data[0]}")
87
- raise APIException("Blueprint returned invalid message structure.", code=status.HTTP_500_INTERNAL_SERVER_ERROR)
88
-
89
- response_payload = { "id": f"chatcmpl-{request_id}", "object": "chat.completion", "created": int(time.time()), "model": model_name, "choices": [{"index": 0, "message": final_response_data[0], "logprobs": None, "finish_reason": "stop"}], "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, "system_fingerprint": None }
90
- end_time = time.time(); logger.info(f"[ReqID: {request_id}] Non-streaming request completed in {end_time - start_time:.2f}s.")
91
- return Response(response_payload, status=status.HTTP_200_OK)
92
- except APIException: raise
93
- except Exception as e: logger.error(f"[ReqID: {request_id}] Unexpected error during non-streaming blueprint execution: {e}", exc_info=True); raise APIException(f"Internal server error during generation: {e}", code=status.HTTP_500_INTERNAL_SERVER_ERROR) from e
94
-
95
- async def _handle_streaming(self, blueprint_instance, messages: List[Dict[str, str]], request_id: str, model_name: str) -> StreamingHttpResponse:
96
- """ Handles streaming requests using SSE. """
97
- logger.info(f"[ReqID: {request_id}] Processing streaming request for model '{model_name}'.")
98
- async def event_stream():
99
- start_time = time.time(); chunk_index = 0
100
- try:
101
- logger.debug(f"[ReqID: {request_id}] Getting async generator from blueprint.run()..."); async_generator = blueprint_instance.run(messages); logger.debug(f"[ReqID: {request_id}] Got async generator. Starting iteration...")
102
- async for chunk in async_generator:
103
- logger.debug(f"[ReqID: {request_id}] Received stream chunk {chunk_index}: {chunk}")
104
- if not isinstance(chunk, dict) or "messages" not in chunk or not isinstance(chunk["messages"], list) or not chunk["messages"] or not isinstance(chunk["messages"][0], dict): logger.warning(f"[ReqID: {request_id}] Skipping invalid chunk format: {chunk}"); continue
105
- delta_content = chunk["messages"][0].get("content"); delta = {"role": "assistant"}
106
- if delta_content is not None: delta["content"] = delta_content
107
- response_chunk = { "id": f"chatcmpl-{request_id}", "object": "chat.completion.chunk", "created": int(time.time()), "model": model_name, "choices": [{"index": 0, "delta": delta, "logprobs": None, "finish_reason": None}] }
108
- logger.debug(f"[ReqID: {request_id}] Sending SSE chunk {chunk_index}"); yield f"data: {json.dumps(response_chunk)}\n\n"; chunk_index += 1; await asyncio.sleep(0.01)
109
- logger.debug(f"[ReqID: {request_id}] Finished iterating stream. Sending [DONE]."); yield "data: [DONE]\n\n"; end_time = time.time(); logger.info(f"[ReqID: {request_id}] Streaming request completed in {end_time - start_time:.2f}s.")
110
- except APIException as e: logger.error(f"[ReqID: {request_id}] API error during streaming: {e}", exc_info=True); error_msg = f"API error: {e.detail}"; error_chunk = {"error": {"message": error_msg, "type": "api_error", "code": e.status_code}}; yield f"data: {json.dumps(error_chunk)}\n\n"; yield "data: [DONE]\n\n"
111
- except Exception as e: logger.error(f"[ReqID: {request_id}] Unexpected error during streaming: {e}", exc_info=True); error_msg = f"Internal server error: {str(e)}"; error_chunk = {"error": {"message": error_msg, "type": "internal_error"}}; yield f"data: {json.dumps(error_chunk)}\n\n"; yield "data: [DONE]\n\n"
112
- return StreamingHttpResponse(event_stream(), content_type="text/event-stream")
113
-
114
- # --- Restore Custom dispatch method (wrapping perform_authentication) ---
115
- @method_decorator(csrf_exempt)
116
- async def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponseBase:
117
- """
118
- Override DRF's dispatch method to specifically wrap the authentication step.
119
- """
120
- self.args = args
121
- self.kwargs = kwargs
122
- drf_request: Request = self.initialize_request(request, *args, **kwargs)
123
- self.request = drf_request
124
- self.headers = self.default_response_headers
125
-
126
- response = None
7
+ from rest_framework.decorators import api_view, authentication_classes, permission_classes
8
+ from rest_framework.permissions import IsAuthenticated
9
+ from swarm.auth import EnvOrTokenAuthentication
10
+ from swarm.utils.logger_setup import setup_logger
11
+ # Import utils but rename to avoid conflict if this module grows
12
+ from swarm.views import utils as view_utils
13
+ # from swarm.views.utils import parse_chat_request, serialize_swarm_response, get_blueprint_instance
14
+ # from swarm.views.utils import load_conversation_history, store_conversation_history, run_conversation
15
+ # from swarm.views.utils import config, llm_config # Import from utils
16
+
17
+ logger = setup_logger(__name__)
18
+
19
+ # Revert to standard sync def
20
+ @api_view(['POST'])
21
+ @csrf_exempt
22
+ @authentication_classes([EnvOrTokenAuthentication])
23
+ @permission_classes([IsAuthenticated])
24
+ def chat_completions(request): # Mark as sync again
25
+ """Handle chat completion requests via POST."""
26
+ if request.method != "POST":
27
+ return Response({"error": "Method not allowed. Use POST."}, status=405)
28
+ logger.info(f"Authenticated User: {request.user}")
29
+
30
+ # Use functions from view_utils
31
+ parse_result = view_utils.parse_chat_request(request)
32
+ if isinstance(parse_result, Response):
33
+ return parse_result
34
+
35
+ body, model, messages, context_vars, conversation_id, tool_call_id = parse_result
36
+ # Use llm_config loaded in utils
37
+ model_type = "llm" if model in view_utils.llm_config and view_utils.llm_config[model].get("passthrough") else "blueprint"
38
+ logger.info(f"Identified model type: {model_type} for model: {model}")
39
+
40
+ # --- Handle LLM Passthrough directly ---
41
+ if model_type == "llm":
42
+ logger.warning(f"LLM Passthrough requested for model '{model}'. This is not yet fully implemented in this view.")
43
+ # TODO: Implement direct call to Swarm core or LLM client for passthrough
44
+ return Response({"error": f"LLM passthrough for model '{model}' not implemented."}, status=501)
45
+
46
+ # --- Handle Blueprint ---
47
+ blueprint_instance = view_utils.get_blueprint_instance(model, context_vars)
48
+ if isinstance(blueprint_instance, Response): # Handle error response from get_blueprint_instance
49
+ return blueprint_instance
50
+ if blueprint_instance is None: # Handle case where get_blueprint_instance signaled non-blueprint
51
+ return Response({"error": f"Model '{model}' is not a loadable blueprint."}, status=404)
52
+
53
+
54
+ messages_extended = view_utils.load_conversation_history(conversation_id, messages, tool_call_id)
55
+
56
+ try:
57
+ # Use asyncio.run() to call the async run_conversation function
58
+ # This blocks the sync view until the async operation completes.
127
59
  try:
128
- # --- Wrap ONLY perform_authentication ---
129
- print_logger.debug(f"User before perform_authentication: {getattr(drf_request, 'user', 'N/A')}, Auth: {getattr(drf_request, 'auth', 'N/A')}")
130
- # This forces the synchronous DB access within perform_authentication into a thread
131
- await sync_to_async(self.perform_authentication)(drf_request)
132
- print_logger.debug(f"User after perform_authentication: {getattr(drf_request, 'user', 'N/A')}, Auth: {getattr(drf_request, 'auth', 'N/A')}")
133
- # --- End wrapping ---
134
-
135
- # Run permission and throttle checks synchronously after auth.
136
- # These checks operate on the now-populated request.user/auth attributes.
137
- self.check_permissions(drf_request)
138
- print_logger.debug("Permissions check passed.")
139
- self.check_throttles(drf_request)
140
- print_logger.debug("Throttles check passed.")
141
-
142
- # Find and execute the handler (e.g., post).
143
- if drf_request.method.lower() in self.http_method_names:
144
- handler = getattr(self, drf_request.method.lower(), self.http_method_not_allowed)
60
+ response_obj, updated_context = asyncio.run(
61
+ view_utils.run_conversation(blueprint_instance, messages_extended, context_vars)
62
+ )
63
+ except RuntimeError as e:
64
+ if "cannot be called from a running event loop" in str(e):
65
+ logger.error("Detected nested asyncio.run call. This can happen in certain test/server setups.")
66
+ # If already in a loop (e.g., certain test runners or ASGI servers),
67
+ # you might need a different way to run the async code, like ensure_future
68
+ # or adapting the server setup. For now, return an error.
69
+ return Response({"error": "Server configuration error: Nested event loop detected."}, status=500)
145
70
  else:
146
- handler = self.http_method_not_allowed
147
-
148
- # IMPORTANT: Await the handler if it's async (like self.post)
149
- if asyncio.iscoroutinefunction(handler):
150
- response = await handler(drf_request, *args, **kwargs)
151
- else:
152
- # Wrap sync handlers if any exist (like GET, OPTIONS).
153
- response = await sync_to_async(handler)(drf_request, *args, **kwargs)
154
-
155
- except Exception as exc:
156
- # Let DRF handle exceptions to generate appropriate responses
157
- response = self.handle_exception(exc)
158
-
159
- # Finalize response should now receive a valid Response/StreamingHttpResponse
160
- self.response = self.finalize_response(drf_request, response, *args, **kwargs)
161
- return self.response
162
-
163
- # --- POST Handler (Keep sync_to_async wrappers here too) ---
164
- async def post(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponseBase:
165
- """
166
- Handles POST requests for chat completions. Assumes dispatch has handled auth/perms.
167
- """
168
- request_id = str(uuid.uuid4())
169
- logger.info(f"[ReqID: {request_id}] Processing POST request.")
170
- print_logger.debug(f"[ReqID: {request_id}] User in post: {getattr(request, 'user', 'N/A')}, Auth: {getattr(request, 'auth', 'N/A')}")
171
-
172
- # --- Request Body Parsing & Validation ---
173
- try: request_data = request.data
174
- except ParseError as e: logger.error(f"[ReqID: {request_id}] Invalid request body format: {e.detail}"); raise e
175
- except json.JSONDecodeError as e: logger.error(f"[ReqID: {request_id}] JSON Decode Error: {e}"); raise ParseError(f"Invalid JSON body: {e}")
176
-
177
- # --- Serialization and Validation ---
178
- serializer = self.serializer_class(data=request_data)
179
- try:
180
- print_logger.debug(f"[ReqID: {request_id}] Validating request data: {request_data}")
181
- # Wrap sync is_valid call as it *might* do DB lookups
182
- await sync_to_async(serializer.is_valid)(raise_exception=True)
183
- print_logger.debug(f"[ReqID: {request_id}] Request data validation successful.")
184
- except ValidationError as e: print_logger.error(f"[ReqID: {request_id}] Request data validation FAILED: {e.detail}"); raise e
185
- except Exception as e: print_logger.error(f"[ReqID: {request_id}] Unexpected error during serializer validation: {e}", exc_info=True); raise APIException(f"Internal error during request validation: {e}", code=status.HTTP_500_INTERNAL_SERVER_ERROR) from e
186
-
187
- validated_data = serializer.validated_data
188
- model_name = validated_data['model']
189
- messages = validated_data['messages']
190
- stream = validated_data.get('stream', False)
191
- blueprint_params = validated_data.get('params', None)
192
-
193
- # --- Model Access Validation ---
194
- # This function likely performs sync DB lookups, so wrap it.
195
- print_logger.debug(f"[ReqID: {request_id}] Checking model access for user '{request.user}' and model '{model_name}'")
196
- try:
197
- access_granted = await sync_to_async(validate_model_access)(request.user, model_name)
198
- except Exception as e:
199
- logger.error(f"[ReqID: {request_id}] Error during model access validation for model '{model_name}': {e}", exc_info=True)
200
- raise APIException("Error checking model permissions.", code=status.HTTP_500_INTERNAL_SERVER_ERROR) from e
201
-
202
- if not access_granted:
203
- logger.warning(f"[ReqID: {request_id}] User '{request.user}' denied access to model '{model_name}'.")
204
- raise PermissionDenied(f"You do not have permission to access the model '{model_name}'.")
205
- print_logger.debug(f"[ReqID: {request_id}] Model access granted.")
206
-
207
- # --- Get Blueprint Instance ---
208
- # This function should ideally be async or sync-safe.
209
- print_logger.debug(f"[ReqID: {request_id}] Getting blueprint instance for '{model_name}' with params: {blueprint_params}")
210
- try:
211
- blueprint_instance = await get_blueprint_instance(model_name, params=blueprint_params)
212
- except Exception as e:
213
- logger.error(f"[ReqID: {request_id}] Error getting blueprint instance for '{model_name}': {e}", exc_info=True)
214
- raise APIException(f"Failed to load model '{model_name}': {e}", code=status.HTTP_500_INTERNAL_SERVER_ERROR) from e
215
-
216
- if blueprint_instance is None:
217
- logger.error(f"[ReqID: {request_id}] Blueprint '{model_name}' not found or failed to initialize (get_blueprint_instance returned None).")
218
- raise NotFound(f"The requested model (blueprint) '{model_name}' was not found or could not be initialized.")
219
-
220
- # --- Handle Streaming or Non-Streaming Response ---
221
- if stream:
222
- return await self._handle_streaming(blueprint_instance, messages, request_id, model_name)
223
- else:
224
- return await self._handle_non_streaming(blueprint_instance, messages, request_id, model_name)
71
+ raise e # Reraise other RuntimeErrors
225
72
 
73
+ serialized = view_utils.serialize_swarm_response(response_obj, model, updated_context)
226
74
 
227
- # ==============================================================================
228
- # Simple Django Views (Example for Web UI - if ENABLE_WEBUI=True)
229
- # ==============================================================================
75
+ if conversation_id:
76
+ serialized["conversation_id"] = conversation_id
77
+ # Storing history can remain synchronous for now unless it becomes a bottleneck
78
+ view_utils.store_conversation_history(conversation_id, messages_extended, response_obj)
230
79
 
231
- @method_decorator(csrf_exempt, name='dispatch') # Apply csrf_exempt if needed
232
- @method_decorator(login_required, name='dispatch') # Require login
233
- class IndexView(View):
234
- """ Renders the main chat interface page. """
235
- def get(self, request):
236
- """ Handles GET requests to render the index page. """
237
- # Assuming get_available_blueprints is sync safe
238
- available_blueprints = get_available_blueprints()
239
- context = {
240
- 'available_blueprints': available_blueprints,
241
- 'user': request.user, # User should be available here
242
- }
243
- return render(request, 'index.html', context)
80
+ return Response(serialized, status=200)
81
+ except Exception as e:
82
+ logger.error(f"Error during execution: {e}", exc_info=True)
83
+ return Response({"error": f"Error during execution: {str(e)}"}, status=500)
swarm/views/core_views.py CHANGED
@@ -11,101 +11,108 @@ from django.http import JsonResponse, HttpResponse
11
11
  from django.conf import settings
12
12
  from django.views.decorators.csrf import csrf_exempt
13
13
  from django.contrib.auth import authenticate, login
14
- from django.contrib.auth.forms import AuthenticationForm # Use standard auth form
15
-
16
14
 
17
15
  # Assuming blueprint discovery happens elsewhere and results are available if needed
18
16
  # from .utils import blueprints_metadata # Or however metadata is accessed
19
-
20
- # Use the current config loader
21
- from swarm.core import config_loader, server_config
17
+ from swarm.extensions.config.config_loader import load_server_config # Import if needed
22
18
 
23
19
  logger = logging.getLogger(__name__)
24
20
 
25
- # --- Web UI Views (if ENABLE_WEBUI is True) ---
21
+ # Placeholder for blueprint metadata if needed by index
22
+ # In a real app, this might be loaded dynamically or passed via context
23
+ try:
24
+ # Attempt to import the discovery function if views need dynamic data
25
+ from swarm.extensions.blueprint.blueprint_discovery import discover_blueprints
26
+ # Note: Calling discover_blueprints here might be too early or cause issues.
27
+ # It's often better handled in specific views that need it (like list_models)
28
+ # or passed via Django context processors.
29
+ # For now, provide an empty dict as fallback.
30
+ try:
31
+ # Use settings.BLUEPRINTS_DIR which should be configured
32
+ blueprints_metadata = discover_blueprints(directories=[str(settings.BLUEPRINTS_DIR)])
33
+ except Exception:
34
+ blueprints_metadata = {}
35
+ except ImportError:
36
+ blueprints_metadata = {}
37
+
26
38
 
39
+ @csrf_exempt
27
40
  def index(request):
28
- """Render the main index page (likely the chat UI)."""
29
- # This view might need context data like available models/blueprints
30
- # It should only be active if ENABLE_WEBUI is true (checked in urls.py)
31
- logger.debug(f"Index view called for user: {request.user}")
41
+ """Render the main index page with blueprint options."""
42
+ logger.debug("Rendering index page")
43
+ # Get blueprint names from the potentially loaded metadata
44
+ blueprint_names_list = list(blueprints_metadata.keys())
32
45
  context = {
33
- 'title': settings.SWARM_TITLE or "Open Swarm",
34
- 'description': settings.SWARM_DESCRIPTION or "A Swarm Framework Interface",
35
- # Add other context needed by the template
46
+ "dark_mode": request.session.get('dark_mode', True),
47
+ "enable_admin": os.getenv("ENABLE_ADMIN", "false").lower() in ("true", "1", "t"),
48
+ "blueprints": blueprint_names_list # Pass the list of names
36
49
  }
37
- # Ensure the template exists
38
- template_name = "swarm/index.html"
39
- # Check if template exists? Django handles TemplateDoesNotExist.
40
- return render(request, template_name, context)
41
-
42
- def custom_login(request):
43
- """Handles user login."""
44
- if request.method == 'POST':
45
- form = AuthenticationForm(request, data=request.POST)
46
- if form.is_valid():
47
- username = form.cleaned_data.get('username')
48
- password = form.cleaned_data.get('password')
49
- user = authenticate(username=username, password=password)
50
- if user is not None:
51
- login(request, user)
52
- logger.info(f"User '{username}' logged in successfully.")
53
- return redirect('/') # Redirect to index after login
54
- else:
55
- logger.warning(f"Login failed for user '{username}': Invalid credentials.")
56
- # Return form with error (AuthenticationForm handles this)
57
- else:
58
- logger.warning(f"Login form invalid: {form.errors.as_json()}")
59
- else:
60
- form = AuthenticationForm()
61
-
62
- # Only render if ENABLE_WEBUI is true (checked in urls.py)
63
- return render(request, 'swarm/login.html', {'form': form})
50
+ return render(request, "index.html", context)
64
51
 
52
+ DEFAULT_CONFIG = {
53
+ "llm": {
54
+ "default": {
55
+ "provider": "openai",
56
+ "model": "gpt-4o", # Example fallback model
57
+ "base_url": "https://api.openai.com/v1",
58
+ "api_key": "",
59
+ "temperature": 0.3
60
+ }
61
+ },
62
+ "blueprints": {},
63
+ "mcpServers": {}
64
+ }
65
65
 
66
66
  def serve_swarm_config(request):
67
- """Serves the swarm_config.json content."""
68
- # Find the config file used by the blueprint base or config loader
69
- # This logic might need refinement depending on where config is reliably found
70
- config_path = None
67
+ """Serve the swarm configuration file as JSON."""
71
68
  try:
72
- # Use the same logic as BlueprintBase if possible, or find_config_file
73
- config_path = config_loader.find_config_file(filename=config_loader.DEFAULT_CONFIG_FILENAME, start_dir=Path(settings.BASE_DIR).parent) # Search from project root
74
- if not config_path:
75
- # Fallback to location relative to settings? Unlikely to be correct.
76
- config_path = Path(settings.BASE_DIR) / '..' / config_loader.DEFAULT_CONFIG_FILENAME # Adjust relative path if needed
77
- config_path = config_path.resolve()
69
+ # Use load_server_config which handles finding the file
70
+ config_data = load_server_config()
71
+ return JsonResponse(config_data)
72
+ except (FileNotFoundError, ValueError, Exception) as e:
73
+ logger.error(f"Error serving swarm_config.json: {e}. Serving default.")
74
+ # Return a default config on error
75
+ return JsonResponse(DEFAULT_CONFIG, status=500)
76
+
78
77
 
79
- if config_path and config_path.exists():
80
- logger.info(f"Serving config from: {config_path}")
81
- # Load config to potentially redact sensitive info before serving
82
- config_data = config_loader.load_config(config_path)
83
- # Redact sensitive keys (e.g., api_key)
84
- if 'llm' in config_data:
85
- for profile in config_data['llm']: config_data['llm'][profile].pop('api_key', None)
86
- return JsonResponse(config_data)
87
- else:
88
- logger.error(f"Swarm config file not found at expected locations (tried: {config_path})")
89
- return JsonResponse({"error": "Configuration file not found."}, status=404)
78
+ @csrf_exempt
79
+ def custom_login(request):
80
+ """Handle custom login at /accounts/login/, redirecting to 'next' URL on success."""
81
+ from django.contrib.auth.models import User # Import here to avoid potential early init issues
82
+ if request.method == "POST":
83
+ username = request.POST.get("username")
84
+ password = request.POST.get("password")
85
+ user = authenticate(request, username=username, password=password)
86
+ if user is not None:
87
+ login(request, user)
88
+ next_url = request.GET.get("next", getattr(settings, 'LOGIN_REDIRECT_URL', '/')) # Use setting or fallback
89
+ logger.info(f"User '{username}' logged in successfully. Redirecting to {next_url}")
90
+ return redirect(next_url)
91
+ else:
92
+ # If ENABLE_API_AUTH is false, auto-login as testuser (for dev/test convenience)
93
+ enable_auth = os.getenv("ENABLE_API_AUTH", "true").lower() in ("true", "1", "t") # Default to TRUE
94
+ if not enable_auth:
95
+ try:
96
+ # Ensure test user exists and has a known password
97
+ user, created = User.objects.get_or_create(username="testuser")
98
+ if created or not user.has_usable_password():
99
+ user.set_password("testpass") # Set a default password
100
+ user.save()
90
101
 
91
- except Exception as e:
92
- logger.error(f"Error serving swarm config: {e}", exc_info=True)
93
- return JsonResponse({"error": "Failed to load or serve configuration."}, status=500)
102
+ if user.check_password("testpass"): # Check against the known password
103
+ login(request, user)
104
+ next_url = request.GET.get("next", getattr(settings, 'LOGIN_REDIRECT_URL', '/'))
105
+ logger.info(f"Auto-logged in as 'testuser' since ENABLE_API_AUTH is false")
106
+ return redirect(next_url)
107
+ else:
108
+ logger.warning("Auto-login failed: 'testuser' exists but password incorrect.")
94
109
 
95
- # --- Potentially other core API views if needed ---
96
- # Example: A view to list available blueprints (might duplicate CLI list command logic)
110
+ except Exception as auto_login_err:
111
+ logger.error(f"Error during testuser auto-login attempt: {auto_login_err}")
112
+ # If authentication failed (and auto-login didn't happen or failed)
113
+ logger.warning(f"Login failed for user '{username}'.")
114
+ return render(request, "account/login.html", {"error": "Invalid credentials"})
115
+ # If GET request
116
+ return render(request, "account/login.html")
97
117
 
98
- @csrf_exempt # If POST is needed and no CSRF token available from UI
99
- def list_available_blueprints_api(request):
100
- """API endpoint to list discoverable blueprints."""
101
- # Re-use discovery logic if possible, or adapt from CLI
102
- from swarm.extensions.blueprint.discovery import discover_blueprints # Assuming this exists
103
- try:
104
- bp_dir = Path(settings.BLUEPRINTS_DIR) # Assuming settings has BLUEPRINTS_DIR
105
- discovered = discover_blueprints(directories=[str(bp_dir)])
106
- # Format the response
107
- blueprint_list = [{"name": name, "description": meta.get("description", "N/A")} for name, meta in discovered.items()]
108
- return JsonResponse({"blueprints": blueprint_list})
109
- except Exception as e:
110
- logger.error(f"Error listing blueprints via API: {e}", exc_info=True)
111
- return JsonResponse({"error": "Failed to list blueprints."}, status=500)
118
+ # Add any other views that were originally in the main views.py if needed