flock-core 0.4.0b33__py3-none-any.whl → 0.4.0b35__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/__init__.py +27 -5
- flock/core/api/main.py +138 -39
- flock/core/util/spliter.py +139 -54
- flock/webapp/__init__.py +1 -0
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +270 -0
- flock/webapp/app/api/execution.py +173 -0
- flock/webapp/app/api/flock_management.py +102 -0
- flock/webapp/app/api/registry_viewer.py +30 -0
- flock/webapp/app/config.py +9 -0
- flock/webapp/app/main.py +571 -0
- flock/webapp/app/models_ui.py +7 -0
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/flock_service.py +291 -0
- flock/webapp/app/utils.py +85 -0
- flock/webapp/run.py +30 -0
- flock/webapp/static/css/custom.css +527 -0
- flock/webapp/templates/base.html +98 -0
- flock/webapp/templates/flock_editor.html +17 -0
- flock/webapp/templates/index.html +12 -0
- flock/webapp/templates/partials/_agent_detail_form.html +97 -0
- flock/webapp/templates/partials/_agent_list.html +24 -0
- flock/webapp/templates/partials/_agent_manager_view.html +15 -0
- flock/webapp/templates/partials/_agent_tools_checklist.html +14 -0
- flock/webapp/templates/partials/_create_flock_form.html +52 -0
- flock/webapp/templates/partials/_dashboard_flock_detail.html +18 -0
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +17 -0
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +29 -0
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +17 -0
- flock/webapp/templates/partials/_dynamic_input_form_content.html +22 -0
- flock/webapp/templates/partials/_execution_form.html +48 -0
- flock/webapp/templates/partials/_execution_view_container.html +19 -0
- flock/webapp/templates/partials/_flock_file_list.html +24 -0
- flock/webapp/templates/partials/_flock_properties_form.html +51 -0
- flock/webapp/templates/partials/_flock_upload_form.html +17 -0
- flock/webapp/templates/partials/_load_manage_view.html +88 -0
- flock/webapp/templates/partials/_registry_table.html +25 -0
- flock/webapp/templates/partials/_registry_viewer_content.html +47 -0
- flock/webapp/templates/partials/_results_display.html +35 -0
- flock/webapp/templates/partials/_sidebar.html +63 -0
- flock/webapp/templates/partials/_structured_data_view.html +40 -0
- flock/webapp/templates/registry_viewer.html +84 -0
- {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/METADATA +1 -1
- {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/RECORD +48 -8
- {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Form, Request
|
|
4
|
+
from fastapi.responses import HTMLResponse
|
|
5
|
+
from fastapi.templating import Jinja2Templates
|
|
6
|
+
|
|
7
|
+
from flock.webapp.app.services.flock_service import (
|
|
8
|
+
add_agent_to_current_flock_service,
|
|
9
|
+
get_current_flock_instance,
|
|
10
|
+
get_registered_items_service,
|
|
11
|
+
remove_agent_from_current_flock_service,
|
|
12
|
+
update_agent_in_current_flock_service,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
router = APIRouter()
|
|
16
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
17
|
+
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.get("/htmx/agent-list", response_class=HTMLResponse)
|
|
21
|
+
async def htmx_get_agent_list(
|
|
22
|
+
request: Request, message: str = None, success: bool = None
|
|
23
|
+
):
|
|
24
|
+
flock = get_current_flock_instance()
|
|
25
|
+
if not flock:
|
|
26
|
+
return HTMLResponse("<p class='error'>No Flock loaded.</p>")
|
|
27
|
+
return templates.TemplateResponse(
|
|
28
|
+
"partials/_agent_list.html",
|
|
29
|
+
{
|
|
30
|
+
"request": request,
|
|
31
|
+
"flock": flock,
|
|
32
|
+
"message": message,
|
|
33
|
+
"success": success,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
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)
|
|
46
|
+
if not agent:
|
|
47
|
+
return HTMLResponse(
|
|
48
|
+
f"<p class='error'>Agent '{agent_name}' not found.</p>"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
registered_tools = get_registered_items_service("tool")
|
|
52
|
+
current_tools = (
|
|
53
|
+
[tool.__name__ for tool in agent.tools] if agent.tools else []
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return templates.TemplateResponse(
|
|
57
|
+
"partials/_agent_detail_form.html",
|
|
58
|
+
{
|
|
59
|
+
"request": request,
|
|
60
|
+
"agent": agent,
|
|
61
|
+
"is_new": False,
|
|
62
|
+
"registered_tools": registered_tools,
|
|
63
|
+
"current_tools": current_tools,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@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>")
|
|
73
|
+
registered_tools = get_registered_items_service("tool")
|
|
74
|
+
return templates.TemplateResponse(
|
|
75
|
+
"partials/_agent_detail_form.html",
|
|
76
|
+
{
|
|
77
|
+
"request": request,
|
|
78
|
+
"agent": None, # For new agent
|
|
79
|
+
"is_new": True,
|
|
80
|
+
"registered_tools": registered_tools,
|
|
81
|
+
"current_tools": [],
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.post(
|
|
87
|
+
"/htmx/agents", response_class=HTMLResponse
|
|
88
|
+
) # For creating new agent
|
|
89
|
+
async def htmx_create_agent(
|
|
90
|
+
request: Request,
|
|
91
|
+
agent_name: str = Form(...),
|
|
92
|
+
agent_description: str = Form(""),
|
|
93
|
+
agent_model: str = Form(None), # Can be empty to use Flock default
|
|
94
|
+
input_signature: str = Form(...),
|
|
95
|
+
output_signature: str = Form(...),
|
|
96
|
+
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)
|
|
108
|
+
registered_tools = get_registered_items_service("tool")
|
|
109
|
+
return templates.TemplateResponse(
|
|
110
|
+
"partials/_agent_detail_form.html",
|
|
111
|
+
{
|
|
112
|
+
"request": request,
|
|
113
|
+
"agent": None,
|
|
114
|
+
"is_new": True,
|
|
115
|
+
"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
|
+
)
|
|
120
|
+
|
|
121
|
+
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
|
|
130
|
+
}
|
|
131
|
+
success = add_agent_to_current_flock_service(agent_config)
|
|
132
|
+
|
|
133
|
+
# After action, re-render the agent list and clear the detail form
|
|
134
|
+
# Set headers for HTMX to trigger multiple target updates
|
|
135
|
+
response_headers = {}
|
|
136
|
+
if success:
|
|
137
|
+
response_headers["HX-Trigger"] = (
|
|
138
|
+
"agentListChanged" # Custom event to refresh list
|
|
139
|
+
)
|
|
140
|
+
|
|
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
|
+
|
|
156
|
+
return HTMLResponse(content=empty_detail_form, headers=response_headers)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@router.put(
|
|
160
|
+
"/htmx/agents/{original_agent_name}", response_class=HTMLResponse
|
|
161
|
+
) # For updating existing agent
|
|
162
|
+
async def htmx_update_agent(
|
|
163
|
+
request: Request,
|
|
164
|
+
original_agent_name: str,
|
|
165
|
+
agent_name: str = Form(...),
|
|
166
|
+
agent_description: str = Form(""),
|
|
167
|
+
agent_model: str = Form(None),
|
|
168
|
+
input_signature: str = Form(...),
|
|
169
|
+
output_signature: str = Form(...),
|
|
170
|
+
tools: list[str] = Form([]),
|
|
171
|
+
):
|
|
172
|
+
flock = get_current_flock_instance()
|
|
173
|
+
if not flock:
|
|
174
|
+
return HTMLResponse("<p class='error'>No Flock loaded.</p>")
|
|
175
|
+
|
|
176
|
+
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,
|
|
183
|
+
}
|
|
184
|
+
success = update_agent_in_current_flock_service(
|
|
185
|
+
original_agent_name, agent_config
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
response_headers = {}
|
|
189
|
+
if success:
|
|
190
|
+
response_headers["HX-Trigger"] = "agentListChanged"
|
|
191
|
+
|
|
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
|
+
|
|
219
|
+
|
|
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
|
|
225
|
+
|
|
226
|
+
success = remove_agent_from_current_flock_service(agent_name)
|
|
227
|
+
|
|
228
|
+
response_headers = {}
|
|
229
|
+
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
|
+
)
|
|
247
|
+
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
|
|
253
|
+
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
|
+
)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Request
|
|
5
|
+
from fastapi.responses import HTMLResponse
|
|
6
|
+
from fastapi.templating import Jinja2Templates
|
|
7
|
+
|
|
8
|
+
from flock.core.util.spliter import parse_schema
|
|
9
|
+
from flock.webapp.app.services.flock_service import (
|
|
10
|
+
get_current_flock_instance,
|
|
11
|
+
run_current_flock_service,
|
|
12
|
+
)
|
|
13
|
+
from flock.webapp.app.utils import pydantic_to_dict
|
|
14
|
+
|
|
15
|
+
router = APIRouter()
|
|
16
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
17
|
+
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# RENAMED this endpoint to avoid clash and for clarity
|
|
21
|
+
@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()
|
|
24
|
+
return templates.TemplateResponse(
|
|
25
|
+
"partials/_execution_form.html",
|
|
26
|
+
{
|
|
27
|
+
"request": request,
|
|
28
|
+
"flock": flock,
|
|
29
|
+
"input_fields": [],
|
|
30
|
+
"selected_agent_name": None,
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@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)
|
|
42
|
+
if not agent:
|
|
43
|
+
return HTMLResponse(
|
|
44
|
+
f"<p class='error'>Agent '{agent_name}' not found.</p>"
|
|
45
|
+
)
|
|
46
|
+
input_fields = []
|
|
47
|
+
if agent.input and isinstance(agent.input, str):
|
|
48
|
+
try:
|
|
49
|
+
parsed_spec = parse_schema(agent.input)
|
|
50
|
+
for name, type_str, description in parsed_spec:
|
|
51
|
+
field_info = {
|
|
52
|
+
"name": name,
|
|
53
|
+
"type": type_str.lower(),
|
|
54
|
+
"description": description or "",
|
|
55
|
+
}
|
|
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
|
+
):
|
|
65
|
+
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"
|
|
71
|
+
input_fields.append(field_info)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return HTMLResponse(
|
|
74
|
+
f"<p class='error'>Error parsing input signature for {agent_name}: {e}</p>"
|
|
75
|
+
)
|
|
76
|
+
return templates.TemplateResponse(
|
|
77
|
+
"partials/_dynamic_input_form_content.html",
|
|
78
|
+
{"request": request, "input_fields": input_fields},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@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:
|
|
87
|
+
return HTMLResponse("<p class='error'>No Flock loaded to run.</p>")
|
|
88
|
+
form_data = await request.form()
|
|
89
|
+
start_agent_name = form_data.get("start_agent_name")
|
|
90
|
+
if not start_agent_name:
|
|
91
|
+
return HTMLResponse("<p class='error'>Starting agent not selected.</p>")
|
|
92
|
+
agent = flock.agents.get(start_agent_name)
|
|
93
|
+
if not agent:
|
|
94
|
+
return HTMLResponse(
|
|
95
|
+
f"<p class='error'>Agent '{start_agent_name}' not found.</p>"
|
|
96
|
+
)
|
|
97
|
+
inputs = {}
|
|
98
|
+
if agent.input and isinstance(agent.input, str):
|
|
99
|
+
try:
|
|
100
|
+
parsed_spec = parse_schema(agent.input)
|
|
101
|
+
for name, type_str, _ in parsed_spec:
|
|
102
|
+
form_field_name = f"agent_input_{name}"
|
|
103
|
+
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
|
|
110
|
+
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
|
+
)
|
|
117
|
+
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
|
+
)
|
|
124
|
+
elif "bool" in type_str.lower():
|
|
125
|
+
inputs[name] = raw_value.lower() in [
|
|
126
|
+
"true",
|
|
127
|
+
"on",
|
|
128
|
+
"1",
|
|
129
|
+
"yes",
|
|
130
|
+
]
|
|
131
|
+
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
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return HTMLResponse(
|
|
142
|
+
f"<p class='error'>Error processing inputs for {start_agent_name}: {e}</p>"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
# Run the flock service and get the result
|
|
147
|
+
result_data = await run_current_flock_service(start_agent_name, inputs)
|
|
148
|
+
|
|
149
|
+
# Detect Pydantic models and convert to dictionaries
|
|
150
|
+
try:
|
|
151
|
+
# Convert Pydantic models to dictionaries for JSON serialization
|
|
152
|
+
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}"
|
|
163
|
+
|
|
164
|
+
return templates.TemplateResponse(
|
|
165
|
+
"partials/_results_display.html",
|
|
166
|
+
{"request": request, "result_data": result_data},
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
error_message = f"Error during execution: {e!s}"
|
|
170
|
+
return templates.TemplateResponse(
|
|
171
|
+
"partials/_results_display.html",
|
|
172
|
+
{"request": request, "result_data": error_message},
|
|
173
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Form, Request
|
|
4
|
+
from fastapi.responses import HTMLResponse
|
|
5
|
+
from fastapi.templating import Jinja2Templates
|
|
6
|
+
|
|
7
|
+
from flock.webapp.app.services.flock_service import (
|
|
8
|
+
get_current_flock_filename,
|
|
9
|
+
get_current_flock_instance,
|
|
10
|
+
save_current_flock_to_file_service,
|
|
11
|
+
update_flock_properties_service,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent # Points to flock-ui/
|
|
16
|
+
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.get("/htmx/flock-properties-form", response_class=HTMLResponse)
|
|
20
|
+
async def htmx_get_flock_properties_form(
|
|
21
|
+
request: Request, update_message: str = None, success: bool = None
|
|
22
|
+
):
|
|
23
|
+
flock = get_current_flock_instance()
|
|
24
|
+
if not flock:
|
|
25
|
+
# This case should ideally not be reached if editor page properly redirects
|
|
26
|
+
return HTMLResponse(
|
|
27
|
+
"<div class='error'>Error: No flock loaded. Please load or create one first.</div>"
|
|
28
|
+
)
|
|
29
|
+
return templates.TemplateResponse(
|
|
30
|
+
"partials/_flock_properties_form.html",
|
|
31
|
+
{
|
|
32
|
+
"request": request,
|
|
33
|
+
"flock": flock,
|
|
34
|
+
"current_filename": get_current_flock_filename(),
|
|
35
|
+
"update_message": update_message,
|
|
36
|
+
"success": success,
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.post("/htmx/flock-properties", response_class=HTMLResponse)
|
|
42
|
+
async def htmx_update_flock_properties(
|
|
43
|
+
request: Request,
|
|
44
|
+
flock_name: str = Form(...),
|
|
45
|
+
default_model: str = Form(...),
|
|
46
|
+
description: str = Form(""),
|
|
47
|
+
):
|
|
48
|
+
success_update = update_flock_properties_service(
|
|
49
|
+
flock_name, default_model, description
|
|
50
|
+
)
|
|
51
|
+
flock = get_current_flock_instance() # Get updated instance
|
|
52
|
+
# Re-render the form with a message
|
|
53
|
+
return templates.TemplateResponse(
|
|
54
|
+
"partials/_flock_properties_form.html",
|
|
55
|
+
{
|
|
56
|
+
"request": request,
|
|
57
|
+
"flock": flock,
|
|
58
|
+
"current_filename": get_current_flock_filename(),
|
|
59
|
+
"update_message": "Flock properties updated!"
|
|
60
|
+
if success_update
|
|
61
|
+
else "Failed to update properties.",
|
|
62
|
+
"success": success_update,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@router.post("/htmx/save-flock", response_class=HTMLResponse)
|
|
68
|
+
async def htmx_save_flock(request: Request, save_filename: str = Form(...)):
|
|
69
|
+
if not save_filename.strip(): # Basic validation
|
|
70
|
+
flock = get_current_flock_instance()
|
|
71
|
+
return templates.TemplateResponse(
|
|
72
|
+
"partials/_flock_properties_form.html",
|
|
73
|
+
{
|
|
74
|
+
"request": request,
|
|
75
|
+
"flock": flock,
|
|
76
|
+
"current_filename": get_current_flock_filename(),
|
|
77
|
+
"save_message": "Filename cannot be empty.",
|
|
78
|
+
"success": False,
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if not (
|
|
83
|
+
save_filename.endswith(".yaml")
|
|
84
|
+
or save_filename.endswith(".yml")
|
|
85
|
+
or save_filename.endswith(".flock")
|
|
86
|
+
):
|
|
87
|
+
save_filename += ".flock.yaml" # Add default extension
|
|
88
|
+
|
|
89
|
+
success, message = save_current_flock_to_file_service(save_filename)
|
|
90
|
+
flock = get_current_flock_instance()
|
|
91
|
+
return templates.TemplateResponse(
|
|
92
|
+
"partials/_flock_properties_form.html",
|
|
93
|
+
{
|
|
94
|
+
"request": request,
|
|
95
|
+
"flock": flock,
|
|
96
|
+
"current_filename": get_current_flock_filename()
|
|
97
|
+
if success
|
|
98
|
+
else get_current_flock_filename(), # Update filename if save was successful
|
|
99
|
+
"save_message": message,
|
|
100
|
+
"success": success,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Request
|
|
4
|
+
from fastapi.responses import HTMLResponse
|
|
5
|
+
from fastapi.templating import Jinja2Templates
|
|
6
|
+
|
|
7
|
+
from flock.webapp.app.services.flock_service import get_registered_items_service
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
11
|
+
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@router.get("/htmx/{item_type}/table", response_class=HTMLResponse)
|
|
15
|
+
async def htmx_get_registry_table(request: Request, item_type: str):
|
|
16
|
+
valid_item_types = ["type", "tool", "component"]
|
|
17
|
+
if item_type not in valid_item_types:
|
|
18
|
+
return HTMLResponse(
|
|
19
|
+
"<p class='error'>Invalid item type requested.</p>", status_code=400
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
items = get_registered_items_service(item_type)
|
|
23
|
+
return templates.TemplateResponse(
|
|
24
|
+
"partials/_registry_table.html",
|
|
25
|
+
{
|
|
26
|
+
"request": request,
|
|
27
|
+
"item_type_display": item_type.capitalize() + "s",
|
|
28
|
+
"items": items,
|
|
29
|
+
},
|
|
30
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
FLOCK_FILES_DIR = Path(os.getenv("FLOCK_FILES_DIR", "./.flock_ui_projects"))
|
|
5
|
+
FLOCK_FILES_DIR.mkdir(parents=True, exist_ok=True)
|
|
6
|
+
|
|
7
|
+
# Global state for MVP - NOT SUITABLE FOR PRODUCTION/MULTI-USER
|
|
8
|
+
CURRENT_FLOCK_INSTANCE = None
|
|
9
|
+
CURRENT_FLOCK_FILENAME = None
|