flock-core 0.4.0b43__py3-none-any.whl → 0.4.0b44__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.
- flock/core/api/__init__.py +1 -2
- flock/core/api/endpoints.py +149 -217
- flock/core/api/main.py +134 -653
- flock/core/api/service.py +214 -0
- flock/core/flock.py +192 -134
- flock/webapp/app/api/agent_management.py +135 -164
- flock/webapp/app/api/execution.py +76 -85
- flock/webapp/app/api/flock_management.py +60 -33
- flock/webapp/app/chat.py +233 -0
- flock/webapp/app/config.py +6 -3
- flock/webapp/app/dependencies.py +95 -0
- flock/webapp/app/main.py +320 -906
- flock/webapp/app/services/flock_service.py +183 -161
- flock/webapp/run.py +176 -100
- flock/webapp/static/css/chat.css +227 -0
- flock/webapp/static/css/components.css +167 -0
- flock/webapp/static/css/header.css +39 -0
- flock/webapp/static/css/layout.css +46 -0
- flock/webapp/static/css/sidebar.css +127 -0
- flock/webapp/templates/base.html +6 -1
- flock/webapp/templates/chat.html +60 -0
- flock/webapp/templates/chat_settings.html +20 -0
- flock/webapp/templates/flock_editor.html +1 -1
- flock/webapp/templates/partials/_agent_detail_form.html +4 -4
- flock/webapp/templates/partials/_agent_list.html +2 -2
- flock/webapp/templates/partials/_agent_manager_view.html +3 -4
- flock/webapp/templates/partials/_chat_container.html +9 -0
- flock/webapp/templates/partials/_chat_messages.html +13 -0
- flock/webapp/templates/partials/_chat_settings_form.html +65 -0
- flock/webapp/templates/partials/_execution_form.html +2 -2
- flock/webapp/templates/partials/_execution_view_container.html +1 -1
- flock/webapp/templates/partials/_flock_properties_form.html +2 -2
- flock/webapp/templates/partials/_registry_viewer_content.html +3 -3
- flock/webapp/templates/partials/_sidebar.html +17 -1
- flock/webapp/templates/registry_viewer.html +3 -3
- {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b44.dist-info}/METADATA +1 -1
- {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b44.dist-info}/RECORD +40 -29
- flock/webapp/static/css/custom.css +0 -612
- flock/webapp/templates/partials/_agent_manager_view_old.html +0 -19
- {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b44.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b44.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b43.dist-info → flock_core-0.4.0b44.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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
agent =
|
|
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
|
-
|
|
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":
|
|
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(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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,
|
|
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),
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
118
|
-
},
|
|
119
|
-
)
|
|
131
|
+
"registered_tools": registered_tools, "current_tools": tools,
|
|
132
|
+
})
|
|
120
133
|
|
|
121
134
|
agent_config = {
|
|
122
|
-
"name": agent_name,
|
|
123
|
-
"
|
|
124
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
179
|
-
"
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
#
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
"
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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(
|
|
23
|
-
|
|
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(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
)
|