flock-core 0.4.0b43__py3-none-any.whl → 0.4.0b45__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (44) hide show
  1. flock/cli/manage_agents.py +19 -4
  2. flock/core/api/__init__.py +1 -2
  3. flock/core/api/endpoints.py +150 -218
  4. flock/core/api/main.py +134 -653
  5. flock/core/api/service.py +214 -0
  6. flock/core/flock.py +192 -134
  7. flock/core/flock_agent.py +31 -0
  8. flock/webapp/app/api/agent_management.py +135 -164
  9. flock/webapp/app/api/execution.py +76 -85
  10. flock/webapp/app/api/flock_management.py +60 -33
  11. flock/webapp/app/chat.py +233 -0
  12. flock/webapp/app/config.py +6 -3
  13. flock/webapp/app/dependencies.py +95 -0
  14. flock/webapp/app/main.py +320 -906
  15. flock/webapp/app/services/flock_service.py +183 -161
  16. flock/webapp/run.py +176 -100
  17. flock/webapp/static/css/chat.css +227 -0
  18. flock/webapp/static/css/components.css +167 -0
  19. flock/webapp/static/css/header.css +39 -0
  20. flock/webapp/static/css/layout.css +46 -0
  21. flock/webapp/static/css/sidebar.css +127 -0
  22. flock/webapp/templates/base.html +6 -1
  23. flock/webapp/templates/chat.html +60 -0
  24. flock/webapp/templates/chat_settings.html +20 -0
  25. flock/webapp/templates/flock_editor.html +1 -1
  26. flock/webapp/templates/partials/_agent_detail_form.html +8 -7
  27. flock/webapp/templates/partials/_agent_list.html +3 -3
  28. flock/webapp/templates/partials/_agent_manager_view.html +3 -4
  29. flock/webapp/templates/partials/_chat_container.html +9 -0
  30. flock/webapp/templates/partials/_chat_messages.html +13 -0
  31. flock/webapp/templates/partials/_chat_settings_form.html +65 -0
  32. flock/webapp/templates/partials/_execution_form.html +2 -2
  33. flock/webapp/templates/partials/_execution_view_container.html +1 -1
  34. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  35. flock/webapp/templates/partials/_registry_viewer_content.html +3 -3
  36. flock/webapp/templates/partials/_sidebar.html +17 -1
  37. flock/webapp/templates/registry_viewer.html +3 -3
  38. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/METADATA +1 -1
  39. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/RECORD +42 -31
  40. flock/webapp/static/css/custom.css +0 -612
  41. flock/webapp/templates/partials/_agent_manager_view_old.html +0 -19
  42. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/WHEEL +0 -0
  43. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/entry_points.txt +0 -0
  44. {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b45.dist-info}/licenses/LICENSE +0 -0
@@ -1,55 +1,80 @@
1
+ # src/flock/webapp/app/api/agent_management.py
2
+ import json
1
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
2
5
 
3
- from fastapi import APIRouter, Form, Request
6
+ from fastapi import ( # Added Depends and HTTPException
7
+ APIRouter,
8
+ Depends,
9
+ Form,
10
+ Request,
11
+ )
4
12
  from fastapi.responses import HTMLResponse
5
13
  from fastapi.templating import Jinja2Templates
6
14
 
15
+ if TYPE_CHECKING:
16
+ from flock.core.flock import Flock
17
+
18
+ # Import the dependency to get the current Flock instance
19
+ from flock.webapp.app.dependencies import (
20
+ get_flock_instance,
21
+ get_optional_flock_instance,
22
+ )
23
+
24
+ # Import service functions that now take app_state (or directly the Flock instance)
7
25
  from flock.webapp.app.services.flock_service import (
8
26
  add_agent_to_current_flock_service,
9
- get_current_flock_instance,
10
- get_registered_items_service,
27
+ get_registered_items_service, # This is fine as it doesn't depend on current_flock
11
28
  remove_agent_from_current_flock_service,
12
29
  update_agent_in_current_flock_service,
13
30
  )
14
31
 
15
32
  router = APIRouter()
16
- BASE_DIR = Path(__file__).resolve().parent.parent.parent
33
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent # Points to flock-ui/
17
34
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
18
35
 
19
36
 
20
37
  @router.get("/htmx/agent-list", response_class=HTMLResponse)
21
38
  async def htmx_get_agent_list(
22
- request: Request, message: str = None, success: bool = None
39
+ request: Request,
40
+ message: str = None,
41
+ success: bool = None,
42
+ # Use Depends to get the current flock instance.
43
+ # Use get_optional_flock_instance if the route should work even without a flock loaded.
44
+ current_flock: "Flock | None" = Depends(get_optional_flock_instance)
23
45
  ):
24
- flock = get_current_flock_instance()
25
- if not flock:
26
- return HTMLResponse("<p class='error'>No Flock loaded.</p>")
46
+ if not current_flock:
47
+ # If used in a context where flock might not be loaded (e.g. main agent manager view before load)
48
+ # it should be handled by the page template or a higher-level redirect.
49
+ # For a partial, returning an error or empty state is reasonable.
50
+ return HTMLResponse("<div id='agent-list-container'><p class='error'>No Flock loaded to display agents.</p></div>", headers={"HX-Retarget": "#agent-list-container", "HX-Reswap": "innerHTML"})
51
+
27
52
  return templates.TemplateResponse(
28
53
  "partials/_agent_list.html",
29
54
  {
30
55
  "request": request,
31
- "flock": flock,
56
+ "flock": current_flock, # Pass the injected flock instance
32
57
  "message": message,
33
58
  "success": success,
34
59
  },
35
60
  )
36
61
 
37
62
 
38
- @router.get(
39
- "/htmx/agents/{agent_name}/details-form", response_class=HTMLResponse
40
- )
41
- async def htmx_get_agent_details_form(request: Request, agent_name: str):
42
- flock = get_current_flock_instance()
43
- if not flock:
44
- return HTMLResponse("<p class='error'>No Flock loaded.</p>")
45
- agent = flock.agents.get(agent_name)
63
+ @router.get("/htmx/agents/{agent_name}/details-form", response_class=HTMLResponse)
64
+ async def htmx_get_agent_details_form(
65
+ request: Request,
66
+ agent_name: str,
67
+ current_flock: "Flock" = Depends(get_flock_instance) # Expect flock to be loaded
68
+ ):
69
+ # flock instance is now injected by FastAPI
70
+ agent = current_flock.agents.get(agent_name)
46
71
  if not agent:
47
72
  return HTMLResponse(
48
- f"<p class='error'>Agent '{agent_name}' not found.</p>"
73
+ f"<p class='error'>Agent '{agent_name}' not found in the current Flock.</p>"
49
74
  )
50
75
 
51
76
  registered_tools = get_registered_items_service("tool")
52
- current_tools = (
77
+ current_agent_tools = (
53
78
  [tool.__name__ for tool in agent.tools] if agent.tools else []
54
79
  )
55
80
 
@@ -60,22 +85,23 @@ async def htmx_get_agent_details_form(request: Request, agent_name: str):
60
85
  "agent": agent,
61
86
  "is_new": False,
62
87
  "registered_tools": registered_tools,
63
- "current_tools": current_tools,
88
+ "current_tools": current_agent_tools,
64
89
  },
65
90
  )
66
91
 
67
92
 
68
93
  @router.get("/htmx/agents/new-agent-form", response_class=HTMLResponse)
69
- async def htmx_get_new_agent_form(request: Request):
70
- flock = get_current_flock_instance()
71
- if not flock:
72
- return HTMLResponse("<p class='error'>No Flock loaded.</p>")
94
+ async def htmx_get_new_agent_form(
95
+ request: Request,
96
+ current_flock: "Flock" = Depends(get_flock_instance) # Expect flock for context, even for new agent
97
+ ):
98
+ # current_flock is injected, primarily to ensure context if needed by template/tools list
73
99
  registered_tools = get_registered_items_service("tool")
74
100
  return templates.TemplateResponse(
75
101
  "partials/_agent_detail_form.html",
76
102
  {
77
103
  "request": request,
78
- "agent": None, # For new agent
104
+ "agent": None,
79
105
  "is_new": True,
80
106
  "registered_tools": registered_tools,
81
107
  "current_tools": [],
@@ -83,82 +109,54 @@ async def htmx_get_new_agent_form(request: Request):
83
109
  )
84
110
 
85
111
 
86
- @router.post(
87
- "/htmx/agents", response_class=HTMLResponse
88
- ) # For creating new agent
112
+ @router.post("/htmx/agents", response_class=HTMLResponse)
89
113
  async def htmx_create_agent(
90
114
  request: Request,
91
115
  agent_name: str = Form(...),
92
116
  agent_description: str = Form(""),
93
- agent_model: str = Form(None), # Can be empty to use Flock default
117
+ agent_model: str = Form(None),
94
118
  input_signature: str = Form(...),
95
119
  output_signature: str = Form(...),
96
120
  tools: list[str] = Form([]),
97
- ): # FastAPI handles list from multiple form fields with same name
98
- flock = get_current_flock_instance()
99
- if not flock:
100
- return HTMLResponse("<p class='error'>No Flock loaded.</p>")
101
-
102
- if (
103
- not agent_name.strip()
104
- or not input_signature.strip()
105
- or not output_signature.strip()
106
- ):
107
- # Render form again with error (or use a different target for error message)
121
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
122
+ ):
123
+ # The service function add_agent_to_current_flock_service now takes app_state
124
+ if (not agent_name.strip() or not input_signature.strip() or not output_signature.strip()):
108
125
  registered_tools = get_registered_items_service("tool")
109
126
  return templates.TemplateResponse(
110
127
  "partials/_agent_detail_form.html",
111
128
  {
112
- "request": request,
113
- "agent": None,
114
- "is_new": True,
129
+ "request": request, "agent": None, "is_new": True,
115
130
  "error_message": "Name, Input Signature, and Output Signature are required.",
116
- "registered_tools": registered_tools,
117
- "current_tools": tools, # Pass back selected tools
118
- },
119
- )
131
+ "registered_tools": registered_tools, "current_tools": tools,
132
+ })
120
133
 
121
134
  agent_config = {
122
- "name": agent_name,
123
- "description": agent_description,
124
- "model": agent_model
125
- if agent_model
126
- else None, # Pass None if empty string for FlockFactory
127
- "input": input_signature,
128
- "output": output_signature,
129
- "tools_names": tools, # Pass tool names
135
+ "name": agent_name, "description": agent_description,
136
+ "model": agent_model if agent_model and agent_model.strip() else None,
137
+ "input": input_signature, "output": output_signature, "tools_names": tools,
130
138
  }
131
- success = add_agent_to_current_flock_service(agent_config)
139
+ # Pass request.app.state to the service function
140
+ success = add_agent_to_current_flock_service(agent_config, request.app.state)
132
141
 
133
- # After action, re-render the agent list and clear the detail form
134
- # Set headers for HTMX to trigger multiple target updates
135
142
  response_headers = {}
136
143
  if success:
137
- response_headers["HX-Trigger"] = (
138
- "agentListChanged" # Custom event to refresh list
139
- )
144
+ response_headers["HX-Trigger"] = json.dumps({"agentListChanged": None, "notify": {"type":"success", "message": f"Agent '{agent_name}' created."}})
140
145
 
141
- # Render an empty detail form or a success message for the detail panel
142
- empty_detail_form = templates.TemplateResponse(
143
- "partials/_agent_detail_form.html",
144
- {
145
- "request": request,
146
- "agent": None,
147
- "is_new": True,
148
- "registered_tools": get_registered_items_service("tool"),
149
- "form_message": "Agent created successfully!"
150
- if success
151
- else "Failed to create agent.",
152
- "success": success,
153
- },
154
- ).body.decode()
155
146
 
156
- return HTMLResponse(content=empty_detail_form, headers=response_headers)
147
+ # Re-render the form or an empty state for the detail panel
148
+ # The agent list itself will be refreshed by the agentListChanged trigger
149
+ new_form_context = {
150
+ "request": request, "agent": None, "is_new": True,
151
+ "registered_tools": get_registered_items_service("tool"),
152
+ "current_tools": [], # Reset tools for new form
153
+ "form_message": "Agent created successfully!" if success else "Failed to create agent. Check logs.",
154
+ "success": success,
155
+ }
156
+ return templates.TemplateResponse("partials/_agent_detail_form.html", new_form_context, headers=response_headers)
157
157
 
158
158
 
159
- @router.put(
160
- "/htmx/agents/{original_agent_name}", response_class=HTMLResponse
161
- ) # For updating existing agent
159
+ @router.put("/htmx/agents/{original_agent_name}", response_class=HTMLResponse)
162
160
  async def htmx_update_agent(
163
161
  request: Request,
164
162
  original_agent_name: str,
@@ -168,103 +166,76 @@ async def htmx_update_agent(
168
166
  input_signature: str = Form(...),
169
167
  output_signature: str = Form(...),
170
168
  tools: list[str] = Form([]),
169
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
171
170
  ):
172
- flock = get_current_flock_instance()
173
- if not flock:
174
- return HTMLResponse("<p class='error'>No Flock loaded.</p>")
175
-
176
171
  agent_config = {
177
- "name": agent_name,
178
- "description": agent_description,
179
- "model": agent_model if agent_model else None,
180
- "input": input_signature,
181
- "output": output_signature,
182
- "tools_names": tools,
172
+ "name": agent_name, "description": agent_description,
173
+ "model": agent_model if agent_model and agent_model.strip() else None,
174
+ "input": input_signature, "output": output_signature, "tools_names": tools,
183
175
  }
184
- success = update_agent_in_current_flock_service(
185
- original_agent_name, agent_config
186
- )
176
+ # Pass request.app.state
177
+ success = update_agent_in_current_flock_service(original_agent_name, agent_config, request.app.state)
187
178
 
188
179
  response_headers = {}
189
180
  if success:
190
- response_headers["HX-Trigger"] = "agentListChanged"
181
+ response_headers["HX-Trigger"] = json.dumps({"agentListChanged": None, "notify": {"type":"success", "message": f"Agent '{agent_name}' updated."}})
191
182
 
192
- # Re-render the form with update message
193
- updated_agent = flock.agents.get(
194
- agent_name
195
- ) # Get the potentially renamed agent
196
- registered_tools = get_registered_items_service("tool")
197
- current_tools = (
198
- [tool.__name__ for tool in updated_agent.tools]
199
- if updated_agent and updated_agent.tools
200
- else []
201
- )
202
-
203
- updated_form = templates.TemplateResponse(
204
- "partials/_agent_detail_form.html",
205
- {
206
- "request": request,
207
- "agent": updated_agent, # Pass the updated agent
208
- "is_new": False,
209
- "form_message": "Agent updated successfully!"
210
- if success
211
- else "Failed to update agent.",
212
- "success": success,
213
- "registered_tools": registered_tools,
214
- "current_tools": current_tools,
215
- },
216
- ).body.decode()
217
- return HTMLResponse(content=updated_form, headers=response_headers)
218
183
 
184
+ # After update, get the (potentially renamed) agent from app.state's flock
185
+ updated_agent_instance: Flock | None = getattr(request.app.state, 'flock_instance', None)
186
+ updated_agent = updated_agent_instance.agents.get(agent_name) if updated_agent_instance else None
219
187
 
220
- @router.delete("/htmx/agents/{agent_name}", response_class=HTMLResponse)
221
- async def htmx_delete_agent(request: Request, agent_name: str):
222
- flock = get_current_flock_instance()
223
- if not flock:
224
- return HTMLResponse("") # Return empty to clear detail view
188
+ registered_tools = get_registered_items_service("tool")
189
+ current_agent_tools = []
190
+ if updated_agent and updated_agent.tools:
191
+ current_agent_tools = [tool.__name__ for tool in updated_agent.tools]
192
+
193
+ updated_form_context = {
194
+ "request": request, "agent": updated_agent, "is_new": False,
195
+ "form_message": "Agent updated successfully!" if success else "Failed to update agent. Check logs.",
196
+ "success": success,
197
+ "registered_tools": registered_tools, "current_tools": current_agent_tools,
198
+ }
199
+ return templates.TemplateResponse("partials/_agent_detail_form.html", updated_form_context, headers=response_headers)
225
200
 
226
- success = remove_agent_from_current_flock_service(agent_name)
227
201
 
202
+ @router.delete("/htmx/agents/{agent_name}", response_class=HTMLResponse)
203
+ async def htmx_delete_agent(
204
+ request: Request,
205
+ agent_name: str,
206
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
207
+ ):
208
+ # Pass request.app.state
209
+ success = remove_agent_from_current_flock_service(agent_name, request.app.state)
228
210
  response_headers = {}
211
+
229
212
  if success:
230
- response_headers["HX-Trigger"] = "agentListChanged"
231
- # Return an empty agent detail form to clear the panel
232
- # Also, the agent list will re-render due to HX-Trigger
233
- return HTMLResponse(
234
- templates.TemplateResponse(
235
- "partials/_agent_detail_form.html",
236
- {
237
- "request": request,
238
- "agent": None,
239
- "is_new": True,
240
- "form_message": f"Agent '{agent_name}' removed.",
241
- "success": True,
242
- "registered_tools": get_registered_items_service("tool"),
243
- },
244
- ).body.decode(),
245
- headers=response_headers,
246
- )
213
+ response_headers["HX-Trigger"] = json.dumps({"agentListChanged": None, "notify": {"type":"info", "message": f"Agent '{agent_name}' removed."}})
214
+ # Return an empty agent detail form to clear that panel
215
+ empty_form_context = {
216
+ "request": request, "agent": None, "is_new": True,
217
+ "registered_tools": get_registered_items_service("tool"),
218
+ "current_tools": [],
219
+ # "form_message": f"Agent '{agent_name}' removed.", # Message handled by notify
220
+ # "success": True, # Not strictly needed if form is cleared
221
+ }
222
+ return templates.TemplateResponse("partials/_agent_detail_form.html", empty_form_context, headers=response_headers)
247
223
  else:
248
- # If deletion fails, re-render the agent detail form with an error
249
- # This scenario should be rare unless the agent was already removed
250
- agent = flock.agents.get(
251
- agent_name
252
- ) # Should still exist if delete failed
224
+ # Deletion failed, re-render the form for the agent that failed to delete (if it still exists)
225
+ flock_instance_from_state: Flock | None = getattr(request.app.state, 'flock_instance', None)
226
+ agent_still_exists = flock_instance_from_state.agents.get(agent_name) if flock_instance_from_state else None
227
+
253
228
  registered_tools = get_registered_items_service("tool")
254
- current_tools = (
255
- [tool.__name__ for tool in agent.tools]
256
- if agent and agent.tools
257
- else []
258
- )
259
- return templates.TemplateResponse(
260
- "partials/_agent_detail_form.html",
261
- {
262
- "request": request,
263
- "agent": agent,
264
- "is_new": False,
265
- "form_message": f"Failed to remove agent '{agent_name}'.",
266
- "success": False,
267
- "registered_tools": registered_tools,
268
- "current_tools": current_tools,
269
- },
270
- )
229
+ current_tools = []
230
+ if agent_still_exists and agent_still_exists.tools:
231
+ current_tools = [tool.__name__ for tool in agent_still_exists.tools]
232
+
233
+ error_form_context = {
234
+ "request": request, "agent": agent_still_exists, "is_new": False,
235
+ "form_message": f"Failed to remove agent '{agent_name}'. It might have already been removed or an error occurred.",
236
+ "success": False,
237
+ "registered_tools": registered_tools, "current_tools": current_tools,
238
+ }
239
+ # Trigger a notification for the error as well
240
+ response_headers["HX-Trigger"] = json.dumps({"notify": {"type":"error", "message": f"Failed to remove agent '{agent_name}'."}})
241
+ return templates.TemplateResponse("partials/_agent_detail_form.html", error_form_context, headers=response_headers)
@@ -1,14 +1,28 @@
1
+ # src/flock/webapp/app/api/execution.py
1
2
  import json
2
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
3
5
 
4
- from fastapi import APIRouter, Request
6
+ from fastapi import APIRouter, Depends, Request # Added Depends
5
7
  from fastapi.responses import HTMLResponse
6
8
  from fastapi.templating import Jinja2Templates
7
9
 
10
+ if TYPE_CHECKING:
11
+ from flock.core.flock import Flock
12
+
13
+
8
14
  from flock.core.util.spliter import parse_schema
15
+
16
+ # Import the dependency to get the current Flock instance
17
+ from flock.webapp.app.dependencies import (
18
+ get_flock_instance,
19
+ get_optional_flock_instance,
20
+ )
21
+
22
+ # Service function now takes app_state
9
23
  from flock.webapp.app.services.flock_service import (
10
- get_current_flock_instance,
11
24
  run_current_flock_service,
25
+ # get_current_flock_instance IS NO LONGER IMPORTED
12
26
  )
13
27
  from flock.webapp.app.utils import pydantic_to_dict
14
28
 
@@ -17,32 +31,36 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
17
31
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
18
32
 
19
33
 
20
- # RENAMED this endpoint to avoid clash and for clarity
21
34
  @router.get("/htmx/execution-form-content", response_class=HTMLResponse)
22
- async def htmx_get_execution_form_content(request: Request): # Renamed function
23
- flock = get_current_flock_instance()
35
+ async def htmx_get_execution_form_content(
36
+ request: Request,
37
+ current_flock: "Flock | None" = Depends(get_optional_flock_instance) # Use optional if form can show 'no flock'
38
+ ):
39
+ # flock instance is injected
24
40
  return templates.TemplateResponse(
25
41
  "partials/_execution_form.html",
26
42
  {
27
43
  "request": request,
28
- "flock": flock,
44
+ "flock": current_flock, # Pass the injected flock instance
29
45
  "input_fields": [],
30
- "selected_agent_name": None,
46
+ "selected_agent_name": None, # Form starts with no agent selected
31
47
  },
32
48
  )
33
49
 
34
50
 
35
51
  @router.get("/htmx/agents/{agent_name}/input-form", response_class=HTMLResponse)
36
- async def htmx_get_agent_input_form(request: Request, agent_name: str):
37
- # ... (same as before) ...
38
- flock = get_current_flock_instance()
39
- if not flock:
40
- return HTMLResponse("")
41
- agent = flock.agents.get(agent_name)
52
+ async def htmx_get_agent_input_form(
53
+ request: Request,
54
+ agent_name: str,
55
+ current_flock: "Flock" = Depends(get_flock_instance) # Expect flock to be loaded
56
+ ):
57
+ # flock instance is injected
58
+ agent = current_flock.agents.get(agent_name)
42
59
  if not agent:
43
60
  return HTMLResponse(
44
- f"<p class='error'>Agent '{agent_name}' not found.</p>"
61
+ f"<p class='error'>Agent '{agent_name}' not found in the current Flock.</p>"
45
62
  )
63
+
46
64
  input_fields = []
47
65
  if agent.input and isinstance(agent.input, str):
48
66
  try:
@@ -53,21 +71,12 @@ async def htmx_get_agent_input_form(request: Request, agent_name: str):
53
71
  "type": type_str.lower(),
54
72
  "description": description or "",
55
73
  }
56
- if "bool" in field_info["type"]:
57
- field_info["html_type"] = "checkbox"
58
- elif (
59
- "int" in field_info["type"] or "float" in field_info["type"]
60
- ):
61
- field_info["html_type"] = "number"
62
- elif (
63
- "list" in field_info["type"] or "dict" in field_info["type"]
64
- ):
74
+ if "bool" in field_info["type"]: field_info["html_type"] = "checkbox"
75
+ elif "int" in field_info["type"] or "float" in field_info["type"]: field_info["html_type"] = "number"
76
+ elif "list" in field_info["type"] or "dict" in field_info["type"]:
65
77
  field_info["html_type"] = "textarea"
66
- field_info["placeholder"] = (
67
- f"Enter JSON for {field_info['type']}"
68
- )
69
- else:
70
- field_info["html_type"] = "text"
78
+ field_info["placeholder"] = f"Enter JSON for {field_info['type']}"
79
+ else: field_info["html_type"] = "text"
71
80
  input_fields.append(field_info)
72
81
  except Exception as e:
73
82
  return HTMLResponse(
@@ -80,86 +89,68 @@ async def htmx_get_agent_input_form(request: Request, agent_name: str):
80
89
 
81
90
 
82
91
  @router.post("/htmx/run", response_class=HTMLResponse)
83
- async def htmx_run_flock(request: Request):
84
- # ... (same as before, ensure it uses the correct _results_display.html) ...
85
- flock = get_current_flock_instance()
86
- if not flock:
92
+ async def htmx_run_flock(
93
+ request: Request,
94
+ # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
95
+ ):
96
+ # The service function run_current_flock_service now takes app_state
97
+ # We retrieve current_flock from app_state inside the service or before calling if needed for validation here
98
+
99
+ # It's better to get flock from app_state here to validate before calling service
100
+ current_flock: Flock | None = getattr(request.app.state, 'flock_instance', None)
101
+
102
+ if not current_flock:
87
103
  return HTMLResponse("<p class='error'>No Flock loaded to run.</p>")
104
+
88
105
  form_data = await request.form()
89
106
  start_agent_name = form_data.get("start_agent_name")
107
+
90
108
  if not start_agent_name:
91
109
  return HTMLResponse("<p class='error'>Starting agent not selected.</p>")
92
- agent = flock.agents.get(start_agent_name)
110
+
111
+ agent = current_flock.agents.get(start_agent_name)
93
112
  if not agent:
94
113
  return HTMLResponse(
95
- f"<p class='error'>Agent '{start_agent_name}' not found.</p>"
114
+ f"<p class='error'>Agent '{start_agent_name}' not found in the current Flock.</p>"
96
115
  )
116
+
97
117
  inputs = {}
98
118
  if agent.input and isinstance(agent.input, str):
99
119
  try:
100
120
  parsed_spec = parse_schema(agent.input)
101
121
  for name, type_str, _ in parsed_spec:
102
- form_field_name = f"agent_input_{name}"
122
+ form_field_name = f"agent_input_{name}" # Matches the name in _dynamic_input_form_content.html
103
123
  raw_value = form_data.get(form_field_name)
104
- if raw_value is None and "bool" in type_str.lower():
105
- inputs[name] = False
106
- continue
107
- if raw_value is None:
108
- inputs[name] = None
109
- continue
124
+
125
+ if raw_value is None and "bool" in type_str.lower(): inputs[name] = False; continue
126
+ if raw_value is None: inputs[name] = None; continue
127
+
110
128
  if "int" in type_str.lower():
111
- try:
112
- inputs[name] = int(raw_value)
113
- except ValueError:
114
- return HTMLResponse(
115
- f"<p class='error'>Invalid integer for '{name}'.</p>"
116
- )
129
+ try: inputs[name] = int(raw_value)
130
+ except ValueError: return HTMLResponse(f"<p class='error'>Invalid integer for '{name}'.</p>")
117
131
  elif "float" in type_str.lower():
118
- try:
119
- inputs[name] = float(raw_value)
120
- except ValueError:
121
- return HTMLResponse(
122
- f"<p class='error'>Invalid float for '{name}'.</p>"
123
- )
132
+ try: inputs[name] = float(raw_value)
133
+ except ValueError: return HTMLResponse(f"<p class='error'>Invalid float for '{name}'.</p>")
124
134
  elif "bool" in type_str.lower():
125
- inputs[name] = raw_value.lower() in [
126
- "true",
127
- "on",
128
- "1",
129
- "yes",
130
- ]
135
+ inputs[name] = raw_value.lower() in ["true", "on", "1", "yes"]
131
136
  elif "list" in type_str.lower() or "dict" in type_str.lower():
132
- try:
133
- inputs[name] = json.loads(raw_value)
134
- except json.JSONDecodeError:
135
- return HTMLResponse(
136
- f"<p class='error'>Invalid JSON for '{name}'.</p>"
137
- )
138
- else:
139
- inputs[name] = raw_value
137
+ try: inputs[name] = json.loads(raw_value)
138
+ except json.JSONDecodeError: return HTMLResponse(f"<p class='error'>Invalid JSON for '{name}'.</p>")
139
+ else: inputs[name] = raw_value
140
140
  except Exception as e:
141
- return HTMLResponse(
142
- f"<p class='error'>Error processing inputs for {start_agent_name}: {e}</p>"
143
- )
141
+ return HTMLResponse(f"<p class='error'>Error processing inputs for {start_agent_name}: {e}</p>")
144
142
 
145
143
  try:
146
- # Run the flock service and get the result
147
- result_data = await run_current_flock_service(start_agent_name, inputs)
144
+ # Pass request.app.state to the service function
145
+ result_data = await run_current_flock_service(start_agent_name, inputs, request.app.state)
148
146
 
149
- # Detect Pydantic models and convert to dictionaries
150
147
  try:
151
- # Convert Pydantic models to dictionaries for JSON serialization
152
148
  result_data = pydantic_to_dict(result_data)
153
-
154
- # Test JSON serialization to catch any remaining issues
155
- try:
156
- json.dumps(result_data)
157
- except (TypeError, ValueError) as e:
158
- # If JSON serialization fails, convert to a string representation
159
- result_data = f"Error: Result contains non-serializable data: {e!s}\nOriginal result: {result_data!s}"
160
-
161
- except Exception as e:
162
- result_data = f"Error: Failed to process result data: {e!s}"
149
+ try: json.dumps(result_data)
150
+ except (TypeError, ValueError) as ser_e:
151
+ result_data = f"Error: Result contains non-serializable data: {ser_e!s}\nOriginal result: {result_data!s}"
152
+ except Exception as proc_e:
153
+ result_data = f"Error: Failed to process result data: {proc_e!s}"
163
154
 
164
155
  return templates.TemplateResponse(
165
156
  "partials/_results_display.html",
@@ -169,5 +160,5 @@ async def htmx_run_flock(request: Request):
169
160
  error_message = f"Error during execution: {e!s}"
170
161
  return templates.TemplateResponse(
171
162
  "partials/_results_display.html",
172
- {"request": request, "result_data": error_message},
163
+ {"request": request, "result_data": error_message}, # Display error in the same partial
173
164
  )