flock-core 0.4.0b42__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 -133
- 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 +178 -97
- 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.0b42.dist-info → flock_core-0.4.0b44.dist-info}/METADATA +1 -1
- {flock_core-0.4.0b42.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.0b42.dist-info → flock_core-0.4.0b44.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b42.dist-info → flock_core-0.4.0b44.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b42.dist-info → flock_core-0.4.0b44.dist-info}/licenses/LICENSE +0 -0
flock/core/api/__init__.py
CHANGED
flock/core/api/endpoints.py
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
# src/flock/core/api/endpoints.py
|
|
2
|
-
"""FastAPI endpoints for the Flock API."""
|
|
2
|
+
"""FastAPI endpoints for the Flock API, using FastAPI's dependency injection."""
|
|
3
3
|
|
|
4
|
-
import html # For escaping
|
|
5
4
|
import uuid
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
7
6
|
|
|
8
7
|
from fastapi import (
|
|
9
8
|
APIRouter,
|
|
10
9
|
BackgroundTasks,
|
|
10
|
+
Depends, # Crucial import for dependency injection
|
|
11
11
|
HTTPException,
|
|
12
|
-
Request as FastAPIRequest,
|
|
13
12
|
)
|
|
14
13
|
|
|
15
|
-
# Import HTMLResponse for the UI form endpoint
|
|
16
|
-
from fastapi.responses import HTMLResponse
|
|
17
|
-
|
|
18
14
|
from flock.core.logging.logging import get_logger
|
|
19
15
|
|
|
20
|
-
# Import
|
|
16
|
+
# Import the dependency provider functions
|
|
17
|
+
# This assumes you'll create 'src/flock/webapp/app/dependencies.py' (or similar)
|
|
18
|
+
# with get_flock_instance and get_run_store functions.
|
|
19
|
+
# For now, to make this file runnable in isolation for generation,
|
|
20
|
+
# we might need temporary stubs or to adjust the import path later.
|
|
21
|
+
# Let's assume a common location for dependencies.
|
|
22
|
+
from flock.webapp.app.dependencies import get_flock_instance, get_run_store
|
|
23
|
+
|
|
21
24
|
from .models import (
|
|
22
25
|
FlockAPIRequest,
|
|
23
26
|
FlockAPIResponse,
|
|
@@ -25,148 +28,196 @@ from .models import (
|
|
|
25
28
|
FlockBatchResponse,
|
|
26
29
|
)
|
|
27
30
|
|
|
28
|
-
# Import UI utils - assuming they are now in ui/utils.py
|
|
29
|
-
|
|
30
|
-
# Use TYPE_CHECKING to avoid circular imports for type hints
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from flock.core.flock import Flock
|
|
33
33
|
|
|
34
|
-
from .main import FlockAPI
|
|
35
34
|
from .run_store import RunStore
|
|
35
|
+
# We also need the _run_flock, _run_batch, _type_convert_inputs methods.
|
|
36
|
+
# These will be part of the Flock instance itself or helper functions
|
|
37
|
+
# that take Flock/RunStore as arguments. For now, let's assume they
|
|
38
|
+
# are methods on the Flock instance or can be called statically with it.
|
|
36
39
|
|
|
37
40
|
logger = get_logger("api.endpoints")
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
def create_api_router(
|
|
42
|
-
"""Creates the APIRouter
|
|
42
|
+
# The create_api_router function no longer needs to be passed the FlockAPI instance
|
|
43
|
+
# as dependencies will be injected directly into the route handlers.
|
|
44
|
+
def create_api_router() -> APIRouter:
|
|
45
|
+
"""Creates the APIRouter for core Flock API endpoints."""
|
|
43
46
|
router = APIRouter()
|
|
44
|
-
# Get dependencies from the main FlockAPI instance passed in
|
|
45
|
-
run_store: RunStore = flock_api.run_store
|
|
46
|
-
flock_instance: Flock = flock_api.flock
|
|
47
47
|
|
|
48
48
|
# --- API Endpoints ---
|
|
49
|
-
@router.post("/run/flock", response_model=FlockAPIResponse, tags=["API"])
|
|
50
|
-
async def
|
|
51
|
-
|
|
49
|
+
@router.post("/run/flock", response_model=FlockAPIResponse, tags=["Flock API Core"])
|
|
50
|
+
async def run_flock_workflow(
|
|
51
|
+
request_model: FlockAPIRequest, # Renamed from 'request' to avoid conflict with FastAPI's Request
|
|
52
|
+
background_tasks: BackgroundTasks,
|
|
53
|
+
flock_instance: "Flock" = Depends(get_flock_instance), # Injected
|
|
54
|
+
run_store: "RunStore" = Depends(get_run_store) # Injected
|
|
52
55
|
):
|
|
53
|
-
"""Run a flock workflow starting with the specified agent
|
|
56
|
+
"""Run a flock workflow starting with the specified agent."""
|
|
54
57
|
run_id = None
|
|
55
58
|
try:
|
|
56
59
|
run_id = str(uuid.uuid4())
|
|
57
|
-
run_store.create_run(run_id)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
run_store.create_run(run_id)
|
|
61
|
+
response_data = run_store.get_run(run_id)
|
|
62
|
+
|
|
63
|
+
if not response_data:
|
|
64
|
+
logger.error(f"Failed to create run record for run_id: {run_id}")
|
|
65
|
+
raise HTTPException(status_code=500, detail="Failed to initialize run.")
|
|
61
66
|
|
|
62
|
-
processed_inputs =
|
|
67
|
+
processed_inputs = request_model.inputs if request_model.inputs else {}
|
|
63
68
|
logger.info(
|
|
64
|
-
f"API request: run flock '{
|
|
69
|
+
f"API request: run flock '{request_model.agent_name}' (run_id: {run_id})",
|
|
65
70
|
inputs=processed_inputs,
|
|
66
71
|
)
|
|
67
72
|
|
|
68
|
-
|
|
73
|
+
# The _run_flock logic now needs to be called. It could be a method on
|
|
74
|
+
# flock_instance, or a static/helper function that takes flock_instance.
|
|
75
|
+
# Let's assume it's a method on flock_instance for now.
|
|
76
|
+
# To avoid direct calls to private-like methods from router,
|
|
77
|
+
# Flock class would expose a public method that handles this internal logic.
|
|
78
|
+
# For this refactoring step, let's assume a helper `_execute_flock_run` exists
|
|
79
|
+
# that we can call.
|
|
80
|
+
|
|
81
|
+
async def _execute_flock_run_task(run_id_task, agent_name_task, inputs_task):
|
|
82
|
+
# This is a simplified version of what was in FlockAPI._run_flock
|
|
83
|
+
try:
|
|
84
|
+
if agent_name_task not in flock_instance.agents:
|
|
85
|
+
raise ValueError(f"Starting agent '{agent_name_task}' not found")
|
|
86
|
+
# Type conversion would ideally be part of the Flock's run logic
|
|
87
|
+
# or a utility. For now, assume it's handled or simple.
|
|
88
|
+
# typed_inputs = flock_instance._type_convert_inputs(agent_name_task, inputs_task) # Example if kept on Flock
|
|
89
|
+
typed_inputs = inputs_task # Simplified for now
|
|
90
|
+
|
|
91
|
+
result = await flock_instance.run_async(
|
|
92
|
+
start_agent=agent_name_task, input=typed_inputs
|
|
93
|
+
)
|
|
94
|
+
run_store.update_run_result(run_id_task, result)
|
|
95
|
+
except Exception as e_task:
|
|
96
|
+
logger.error(f"Error in background flock run {run_id_task}: {e_task!s}", exc_info=True)
|
|
97
|
+
run_store.update_run_status(run_id_task, "failed", str(e_task))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if request_model.async_run:
|
|
69
101
|
logger.debug(
|
|
70
|
-
f"Running flock '{
|
|
102
|
+
f"Running flock '{request_model.agent_name}' asynchronously (run_id: {run_id})"
|
|
71
103
|
)
|
|
72
|
-
# Call the helper method on the passed FlockAPI instance
|
|
73
104
|
background_tasks.add_task(
|
|
74
|
-
|
|
105
|
+
_execute_flock_run_task,
|
|
75
106
|
run_id,
|
|
76
|
-
|
|
107
|
+
request_model.agent_name,
|
|
77
108
|
processed_inputs,
|
|
78
109
|
)
|
|
79
110
|
run_store.update_run_status(run_id, "running")
|
|
80
|
-
|
|
111
|
+
response_data.status = "running"
|
|
81
112
|
else:
|
|
82
113
|
logger.debug(
|
|
83
|
-
f"Running flock '{
|
|
114
|
+
f"Running flock '{request_model.agent_name}' synchronously (run_id: {run_id})"
|
|
84
115
|
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
run_id, request.agent_name, processed_inputs
|
|
116
|
+
await _execute_flock_run_task(
|
|
117
|
+
run_id, request_model.agent_name, processed_inputs
|
|
88
118
|
)
|
|
89
|
-
|
|
90
|
-
run_id
|
|
91
|
-
) # Fetch updated status/result
|
|
119
|
+
response_data = run_store.get_run(run_id)
|
|
92
120
|
|
|
93
|
-
|
|
121
|
+
if not response_data:
|
|
122
|
+
logger.error(f"Run data lost for run_id: {run_id} after execution.")
|
|
123
|
+
raise HTTPException(status_code=500, detail="Run data lost after execution.")
|
|
124
|
+
|
|
125
|
+
return response_data
|
|
94
126
|
except ValueError as ve:
|
|
95
|
-
logger.error(f"Value error starting run: {ve}")
|
|
96
|
-
if run_id:
|
|
97
|
-
run_store.update_run_status(run_id, "failed", str(ve))
|
|
127
|
+
logger.error(f"Value error starting run for agent '{request_model.agent_name}': {ve}", exc_info=True)
|
|
128
|
+
if run_id: run_store.update_run_status(run_id, "failed", str(ve))
|
|
98
129
|
raise HTTPException(status_code=400, detail=str(ve))
|
|
99
130
|
except Exception as e:
|
|
100
|
-
error_msg = f"Internal server error: {type(e).__name__}"
|
|
101
|
-
logger.error(f"Error starting run: {e!s}", exc_info=True)
|
|
102
|
-
if run_id:
|
|
103
|
-
run_store.update_run_status(run_id, "failed", error_msg)
|
|
131
|
+
error_msg = f"Internal server error during flock run: {type(e).__name__}"
|
|
132
|
+
logger.error(f"Error starting flock run: {e!s}", exc_info=True)
|
|
133
|
+
if run_id: run_store.update_run_status(run_id, "failed", error_msg)
|
|
104
134
|
raise HTTPException(status_code=500, detail=error_msg)
|
|
105
135
|
|
|
106
|
-
@router.post("/run/batch", response_model=FlockBatchResponse, tags=["API"])
|
|
107
|
-
async def
|
|
108
|
-
|
|
136
|
+
@router.post("/run/batch", response_model=FlockBatchResponse, tags=["Flock API Core"])
|
|
137
|
+
async def run_batch_workflow(
|
|
138
|
+
request_model: FlockBatchRequest, # Renamed from 'request'
|
|
139
|
+
background_tasks: BackgroundTasks,
|
|
140
|
+
flock_instance: "Flock" = Depends(get_flock_instance),
|
|
141
|
+
run_store: "RunStore" = Depends(get_run_store)
|
|
109
142
|
):
|
|
110
|
-
"""Run a batch of inputs through the flock workflow
|
|
143
|
+
"""Run a batch of inputs through the flock workflow."""
|
|
111
144
|
batch_id = None
|
|
112
145
|
try:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
raise ValueError(f"Agent '{request.agent_name}' not found")
|
|
146
|
+
if request_model.agent_name not in flock_instance.agents:
|
|
147
|
+
raise ValueError(f"Agent '{request_model.agent_name}' not found in current Flock.")
|
|
116
148
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
isinstance(request.batch_inputs, list)
|
|
120
|
-
and not request.batch_inputs
|
|
121
|
-
):
|
|
122
|
-
raise ValueError("Batch inputs list cannot be empty")
|
|
149
|
+
if isinstance(request_model.batch_inputs, list) and not request_model.batch_inputs:
|
|
150
|
+
raise ValueError("Batch inputs list cannot be empty if provided as a list.")
|
|
123
151
|
|
|
124
152
|
batch_id = str(uuid.uuid4())
|
|
125
|
-
run_store.create_batch(batch_id)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
153
|
+
run_store.create_batch(batch_id)
|
|
154
|
+
response_data = run_store.get_batch(batch_id)
|
|
155
|
+
|
|
156
|
+
if not response_data:
|
|
157
|
+
logger.error(f"Failed to create batch record for batch_id: {batch_id}")
|
|
158
|
+
raise HTTPException(status_code=500, detail="Failed to initialize batch run.")
|
|
129
159
|
|
|
130
|
-
# Log batch size for monitoring
|
|
131
160
|
batch_size = (
|
|
132
|
-
len(
|
|
133
|
-
if isinstance(
|
|
161
|
+
len(request_model.batch_inputs)
|
|
162
|
+
if isinstance(request_model.batch_inputs, list)
|
|
134
163
|
else "CSV/DataFrame"
|
|
135
164
|
)
|
|
136
165
|
logger.info(
|
|
137
|
-
f"API request: run batch with '{
|
|
166
|
+
f"API request: run batch with '{request_model.agent_name}' (batch_id: {batch_id})",
|
|
138
167
|
batch_size=batch_size,
|
|
139
168
|
)
|
|
140
169
|
|
|
141
|
-
|
|
170
|
+
async def _execute_flock_batch_task(batch_id_task, batch_request_task: FlockBatchRequest):
|
|
171
|
+
# This is a simplified version of what was in FlockAPI._run_batch
|
|
172
|
+
try:
|
|
173
|
+
# Directly use the flock_instance's run_batch_async method
|
|
174
|
+
results = await flock_instance.run_batch_async(
|
|
175
|
+
start_agent=batch_request_task.agent_name,
|
|
176
|
+
batch_inputs=batch_request_task.batch_inputs,
|
|
177
|
+
input_mapping=batch_request_task.input_mapping,
|
|
178
|
+
static_inputs=batch_request_task.static_inputs,
|
|
179
|
+
parallel=batch_request_task.parallel,
|
|
180
|
+
max_workers=batch_request_task.max_workers,
|
|
181
|
+
use_temporal=batch_request_task.use_temporal,
|
|
182
|
+
box_results=batch_request_task.box_results,
|
|
183
|
+
return_errors=batch_request_task.return_errors,
|
|
184
|
+
silent_mode=True, # API batch runs should be internally silent
|
|
185
|
+
write_to_csv=None # API handles CSV writing based on request if needed, not here
|
|
186
|
+
)
|
|
187
|
+
run_store.update_batch_result(batch_id_task, results)
|
|
188
|
+
logger.info(f"Batch run completed (batch_id: {batch_id_task})", num_results=len(results))
|
|
189
|
+
except Exception as e_task:
|
|
190
|
+
logger.error(f"Error in background flock batch {batch_id_task}: {e_task!s}", exc_info=True)
|
|
191
|
+
run_store.update_batch_status(batch_id_task, "failed", str(e_task))
|
|
192
|
+
|
|
142
193
|
logger.debug(
|
|
143
|
-
f"Running batch with '{
|
|
194
|
+
f"Running batch with '{request_model.agent_name}' asynchronously (batch_id: {batch_id})"
|
|
144
195
|
)
|
|
145
|
-
# Call the helper method on the passed FlockAPI instance
|
|
146
196
|
background_tasks.add_task(
|
|
147
|
-
|
|
197
|
+
_execute_flock_batch_task,
|
|
148
198
|
batch_id,
|
|
149
|
-
request
|
|
199
|
+
request_model, # Pass the Pydantic request model
|
|
150
200
|
)
|
|
151
201
|
run_store.update_batch_status(batch_id, "running")
|
|
152
|
-
|
|
202
|
+
response_data.status = "running"
|
|
153
203
|
|
|
154
|
-
return
|
|
204
|
+
return response_data
|
|
155
205
|
except ValueError as ve:
|
|
156
|
-
error_msg = f"Value error starting batch: {ve}"
|
|
157
|
-
logger.error(error_msg)
|
|
158
|
-
if batch_id:
|
|
159
|
-
run_store.update_batch_status(batch_id, "failed", str(ve))
|
|
206
|
+
error_msg = f"Value error starting batch for agent '{request_model.agent_name}': {ve}"
|
|
207
|
+
logger.error(error_msg, exc_info=True)
|
|
208
|
+
if batch_id: run_store.update_batch_status(batch_id, "failed", str(ve))
|
|
160
209
|
raise HTTPException(status_code=400, detail=str(ve))
|
|
161
210
|
except Exception as e:
|
|
162
|
-
error_msg = f"Internal server error: {type(e).__name__}: {e!s}"
|
|
211
|
+
error_msg = f"Internal server error during batch run: {type(e).__name__}: {e!s}"
|
|
163
212
|
logger.error(error_msg, exc_info=True)
|
|
164
|
-
if batch_id:
|
|
165
|
-
run_store.update_batch_status(batch_id, "failed", error_msg)
|
|
213
|
+
if batch_id: run_store.update_batch_status(batch_id, "failed", error_msg)
|
|
166
214
|
raise HTTPException(status_code=500, detail=error_msg)
|
|
167
215
|
|
|
168
|
-
@router.get("/run/{run_id}", response_model=FlockAPIResponse, tags=["API"])
|
|
169
|
-
async def get_run_status(
|
|
216
|
+
@router.get("/run/{run_id}", response_model=FlockAPIResponse, tags=["Flock API Core"])
|
|
217
|
+
async def get_run_status(
|
|
218
|
+
run_id: str,
|
|
219
|
+
run_store: "RunStore" = Depends(get_run_store)
|
|
220
|
+
):
|
|
170
221
|
"""Get the status of a specific run."""
|
|
171
222
|
logger.debug(f"API request: get status for run_id: {run_id}")
|
|
172
223
|
run_data = run_store.get_run(run_id)
|
|
@@ -175,148 +226,29 @@ def create_api_router(flock_api: "FlockAPI") -> APIRouter:
|
|
|
175
226
|
raise HTTPException(status_code=404, detail="Run not found")
|
|
176
227
|
return run_data
|
|
177
228
|
|
|
178
|
-
@router.get(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
Returns details including:
|
|
185
|
-
- Total number of items in the batch
|
|
186
|
-
- Number of completed items
|
|
187
|
-
- Percentage of completion
|
|
188
|
-
- Any partial results available (for running batches)
|
|
189
|
-
- Complete results (for completed batches)
|
|
190
|
-
"""
|
|
229
|
+
@router.get("/batch/{batch_id}", response_model=FlockBatchResponse, tags=["Flock API Core"])
|
|
230
|
+
async def get_batch_status(
|
|
231
|
+
batch_id: str,
|
|
232
|
+
run_store: "RunStore" = Depends(get_run_store)
|
|
233
|
+
):
|
|
234
|
+
"""Get the status and results of a specific batch run."""
|
|
191
235
|
logger.debug(f"API request: get status for batch_id: {batch_id}")
|
|
192
236
|
batch_data = run_store.get_batch(batch_id)
|
|
193
237
|
if not batch_data:
|
|
194
238
|
logger.warning(f"Batch ID not found: {batch_id}")
|
|
195
239
|
raise HTTPException(status_code=404, detail="Batch not found")
|
|
196
|
-
|
|
197
|
-
# Add useful info for client display
|
|
198
|
-
extra_info = {
|
|
199
|
-
"status": batch_data.status,
|
|
200
|
-
"completed_items": batch_data.completed_items,
|
|
201
|
-
"total_items": batch_data.total_items,
|
|
202
|
-
"progress_percentage": round(batch_data.progress_percentage, 1),
|
|
203
|
-
"has_partial_results": len(batch_data.results) > 0
|
|
204
|
-
and batch_data.status == "running",
|
|
205
|
-
"has_error": batch_data.error is not None,
|
|
206
|
-
}
|
|
207
|
-
logger.debug(f"Returning batch status: {extra_info}")
|
|
208
|
-
|
|
209
240
|
return batch_data
|
|
210
241
|
|
|
211
|
-
@router.get("/agents", tags=["API"])
|
|
212
|
-
async def list_agents(
|
|
213
|
-
""
|
|
242
|
+
@router.get("/agents", tags=["Flock API Core"])
|
|
243
|
+
async def list_agents(
|
|
244
|
+
flock_instance: "Flock" = Depends(get_flock_instance)
|
|
245
|
+
):
|
|
246
|
+
"""List all available agents in the currently loaded Flock."""
|
|
214
247
|
logger.debug("API request: list agents")
|
|
215
|
-
# Access flock instance via factory closure
|
|
216
248
|
agents_list = [
|
|
217
249
|
{"name": agent.name, "description": agent.description or agent.name}
|
|
218
250
|
for agent in flock_instance.agents.values()
|
|
219
251
|
]
|
|
220
252
|
return {"agents": agents_list}
|
|
221
253
|
|
|
222
|
-
# --- UI Form Endpoint ---
|
|
223
|
-
@router.post("/ui/run-agent-form", response_class=HTMLResponse, tags=["UI"])
|
|
224
|
-
async def run_flock_form(fastapi_req: FastAPIRequest):
|
|
225
|
-
"""Endpoint to handle form submissions from the UI."""
|
|
226
|
-
run_id = None
|
|
227
|
-
try:
|
|
228
|
-
form_data = await fastapi_req.form()
|
|
229
|
-
agent_name = form_data.get("agent_name")
|
|
230
|
-
if not agent_name:
|
|
231
|
-
logger.warning("UI form submission missing agent_name")
|
|
232
|
-
return HTMLResponse(
|
|
233
|
-
'<div id="result-content" class="error-message">Error: Agent name not provided.</div>',
|
|
234
|
-
status_code=400,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
logger.info(f"UI Form submission for agent: {agent_name}")
|
|
238
|
-
form_inputs = {}
|
|
239
|
-
# Access flock instance via factory closure
|
|
240
|
-
agent_def = flock_instance.agents.get(agent_name)
|
|
241
|
-
# Use helper from flock_api instance for parsing
|
|
242
|
-
defined_input_fields = (
|
|
243
|
-
flock_api._parse_input_spec(agent_def.input or "")
|
|
244
|
-
if agent_def
|
|
245
|
-
else []
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
for key, value in form_data.items():
|
|
249
|
-
if key.startswith("inputs."):
|
|
250
|
-
form_inputs[key[len("inputs.") :]] = value
|
|
251
|
-
for field in defined_input_fields: # Handle checkboxes
|
|
252
|
-
if (
|
|
253
|
-
field["html_type"] == "checkbox"
|
|
254
|
-
and field["name"] not in form_inputs
|
|
255
|
-
):
|
|
256
|
-
form_inputs[field["name"]] = False
|
|
257
|
-
elif (
|
|
258
|
-
field["html_type"] == "checkbox"
|
|
259
|
-
and field["name"] in form_inputs
|
|
260
|
-
):
|
|
261
|
-
form_inputs[field["name"]] = True
|
|
262
|
-
|
|
263
|
-
logger.debug(f"Parsed form inputs for UI run: {form_inputs}")
|
|
264
|
-
run_id = str(uuid.uuid4())
|
|
265
|
-
run_store.create_run(run_id)
|
|
266
|
-
logger.debug(
|
|
267
|
-
f"Running flock '{agent_name}' synchronously from UI (run_id: {run_id})"
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
# Call helper method on flock_api instance
|
|
271
|
-
await flock_api._run_flock(run_id, agent_name, form_inputs)
|
|
272
|
-
|
|
273
|
-
final_status = run_store.get_run(run_id)
|
|
274
|
-
if final_status and final_status.status == "completed":
|
|
275
|
-
# Use helper from flock_api instance for formatting
|
|
276
|
-
formatted_html = flock_api._format_result_to_html(
|
|
277
|
-
final_status.result
|
|
278
|
-
)
|
|
279
|
-
logger.info(f"UI run completed successfully (run_id: {run_id})")
|
|
280
|
-
return HTMLResponse(
|
|
281
|
-
f"<div id='result-content'>{formatted_html}</div>"
|
|
282
|
-
) # Wrap in target div
|
|
283
|
-
elif final_status and final_status.status == "failed":
|
|
284
|
-
logger.error(
|
|
285
|
-
f"UI run failed (run_id: {run_id}): {final_status.error}"
|
|
286
|
-
)
|
|
287
|
-
error_msg = html.escape(final_status.error or "Unknown error")
|
|
288
|
-
return HTMLResponse(
|
|
289
|
-
f"<div id='result-content' class='error-message'>Run Failed: {error_msg}</div>",
|
|
290
|
-
status_code=500,
|
|
291
|
-
)
|
|
292
|
-
else:
|
|
293
|
-
status_str = (
|
|
294
|
-
final_status.status if final_status else "Not Found"
|
|
295
|
-
)
|
|
296
|
-
logger.warning(
|
|
297
|
-
f"UI run {run_id} ended in unexpected state: {status_str}"
|
|
298
|
-
)
|
|
299
|
-
return HTMLResponse(
|
|
300
|
-
f"<div id='result-content' class='error-message'>Run ended unexpectedly. Status: {status_str}</div>",
|
|
301
|
-
status_code=500,
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
except ValueError as ve:
|
|
305
|
-
logger.error(f"Value error processing UI form run: {ve}")
|
|
306
|
-
if run_id:
|
|
307
|
-
run_store.update_run_status(run_id, "failed", str(ve))
|
|
308
|
-
return HTMLResponse(
|
|
309
|
-
f"<div id='result-content' class='error-message'>Error: {html.escape(str(ve))}</div>",
|
|
310
|
-
status_code=400,
|
|
311
|
-
)
|
|
312
|
-
except Exception as e:
|
|
313
|
-
error_msg = f"Internal server error: {type(e).__name__}"
|
|
314
|
-
logger.error(f"Error processing UI form run: {e!s}", exc_info=True)
|
|
315
|
-
if run_id:
|
|
316
|
-
run_store.update_run_status(run_id, "failed", error_msg)
|
|
317
|
-
return HTMLResponse(
|
|
318
|
-
f"<div id='result-content' class='error-message'>{html.escape(error_msg)}</div>",
|
|
319
|
-
status_code=500,
|
|
320
|
-
)
|
|
321
|
-
|
|
322
254
|
return router
|