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.

Files changed (48) hide show
  1. flock/__init__.py +27 -5
  2. flock/core/api/main.py +138 -39
  3. flock/core/util/spliter.py +139 -54
  4. flock/webapp/__init__.py +1 -0
  5. flock/webapp/app/__init__.py +0 -0
  6. flock/webapp/app/api/__init__.py +0 -0
  7. flock/webapp/app/api/agent_management.py +270 -0
  8. flock/webapp/app/api/execution.py +173 -0
  9. flock/webapp/app/api/flock_management.py +102 -0
  10. flock/webapp/app/api/registry_viewer.py +30 -0
  11. flock/webapp/app/config.py +9 -0
  12. flock/webapp/app/main.py +571 -0
  13. flock/webapp/app/models_ui.py +7 -0
  14. flock/webapp/app/services/__init__.py +0 -0
  15. flock/webapp/app/services/flock_service.py +291 -0
  16. flock/webapp/app/utils.py +85 -0
  17. flock/webapp/run.py +30 -0
  18. flock/webapp/static/css/custom.css +527 -0
  19. flock/webapp/templates/base.html +98 -0
  20. flock/webapp/templates/flock_editor.html +17 -0
  21. flock/webapp/templates/index.html +12 -0
  22. flock/webapp/templates/partials/_agent_detail_form.html +97 -0
  23. flock/webapp/templates/partials/_agent_list.html +24 -0
  24. flock/webapp/templates/partials/_agent_manager_view.html +15 -0
  25. flock/webapp/templates/partials/_agent_tools_checklist.html +14 -0
  26. flock/webapp/templates/partials/_create_flock_form.html +52 -0
  27. flock/webapp/templates/partials/_dashboard_flock_detail.html +18 -0
  28. flock/webapp/templates/partials/_dashboard_flock_file_list.html +17 -0
  29. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +29 -0
  30. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +17 -0
  31. flock/webapp/templates/partials/_dynamic_input_form_content.html +22 -0
  32. flock/webapp/templates/partials/_execution_form.html +48 -0
  33. flock/webapp/templates/partials/_execution_view_container.html +19 -0
  34. flock/webapp/templates/partials/_flock_file_list.html +24 -0
  35. flock/webapp/templates/partials/_flock_properties_form.html +51 -0
  36. flock/webapp/templates/partials/_flock_upload_form.html +17 -0
  37. flock/webapp/templates/partials/_load_manage_view.html +88 -0
  38. flock/webapp/templates/partials/_registry_table.html +25 -0
  39. flock/webapp/templates/partials/_registry_viewer_content.html +47 -0
  40. flock/webapp/templates/partials/_results_display.html +35 -0
  41. flock/webapp/templates/partials/_sidebar.html +63 -0
  42. flock/webapp/templates/partials/_structured_data_view.html +40 -0
  43. flock/webapp/templates/registry_viewer.html +84 -0
  44. {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/METADATA +1 -1
  45. {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/RECORD +48 -8
  46. {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/WHEEL +0 -0
  47. {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/entry_points.txt +0 -0
  48. {flock_core-0.4.0b33.dist-info → flock_core-0.4.0b35.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,571 @@
1
+ # ... (keep existing imports and app setup) ...
2
+ import json
3
+ import shutil
4
+ import urllib.parse
5
+ from pathlib import Path
6
+
7
+ from fastapi import FastAPI, File, Form, Query, Request, UploadFile
8
+ from fastapi.responses import HTMLResponse, RedirectResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi.templating import Jinja2Templates
11
+
12
+ from flock.webapp.app.api import (
13
+ agent_management,
14
+ execution,
15
+ flock_management,
16
+ registry_viewer,
17
+ )
18
+ from flock.webapp.app.config import FLOCK_FILES_DIR
19
+ from flock.webapp.app.services.flock_service import (
20
+ clear_current_flock,
21
+ create_new_flock_service,
22
+ get_available_flock_files,
23
+ get_current_flock_filename,
24
+ get_current_flock_instance,
25
+ get_flock_preview_service,
26
+ load_flock_from_file_service,
27
+ )
28
+
29
+ app = FastAPI(title="Flock UI")
30
+
31
+ BASE_DIR = Path(__file__).resolve().parent.parent
32
+ app.mount(
33
+ "/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static"
34
+ )
35
+ templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
36
+
37
+ app.include_router(
38
+ flock_management.router, prefix="/api/flocks", tags=["Flock Management API"]
39
+ )
40
+ app.include_router(
41
+ agent_management.router, prefix="/api/flocks", tags=["Agent Management API"]
42
+ )
43
+ # Ensure execution router is imported and included BEFORE it's referenced by the renamed route
44
+ app.include_router(
45
+ execution.router, prefix="/api/flocks", tags=["Execution API"]
46
+ )
47
+ app.include_router(
48
+ registry_viewer.router, prefix="/api/registry", tags=["Registry API"]
49
+ )
50
+
51
+
52
+ def get_base_context(
53
+ request: Request,
54
+ error: str = None,
55
+ success: str = None,
56
+ ui_mode: str = "standalone",
57
+ ) -> dict:
58
+ return {
59
+ "request": request,
60
+ "current_flock": get_current_flock_instance(),
61
+ "current_filename": get_current_flock_filename(),
62
+ "error_message": error,
63
+ "success_message": success,
64
+ "ui_mode": ui_mode,
65
+ }
66
+
67
+
68
+ # --- Main Page Routes ---
69
+ @app.get("/", response_class=HTMLResponse)
70
+ async def page_dashboard(
71
+ request: Request,
72
+ error: str = None,
73
+ success: str = None,
74
+ ui_mode: str = Query(
75
+ None
76
+ ), # Default to None to detect if it was explicitly passed
77
+ ):
78
+ # Determine effective ui_mode
79
+ effective_ui_mode = ui_mode
80
+ flock_is_preloaded = get_current_flock_instance() is not None
81
+
82
+ if effective_ui_mode is None: # ui_mode not in query parameters
83
+ if flock_is_preloaded:
84
+ # If a flock is preloaded (likely by API server) and no mode specified,
85
+ # default to scoped and redirect to make the URL explicit.
86
+ return RedirectResponse(url="/?ui_mode=scoped", status_code=307)
87
+ else:
88
+ effective_ui_mode = "standalone" # True standalone launch
89
+ elif effective_ui_mode == "scoped" and not flock_is_preloaded:
90
+ # If explicitly asked for scoped mode but no flock is loaded (e.g. user bookmarked URL after server restart)
91
+ # It will show the "scoped-no-flock-view". We could also redirect to standalone.
92
+ # For now, let it show the "no flock loaded in scoped mode" message.
93
+ pass
94
+
95
+ # Conditional flock clearing based on the *effective* ui_mode
96
+ if effective_ui_mode != "scoped":
97
+ # If we are about to enter standalone mode, and a flock might have been
98
+ # preloaded (e.g. user navigated from /?ui_mode=scoped to /?ui_mode=standalone),
99
+ # ensure it's cleared for a true standalone experience.
100
+ if flock_is_preloaded: # Clear only if one was there
101
+ clear_current_flock()
102
+
103
+ context = get_base_context(request, error, success, effective_ui_mode)
104
+
105
+ if effective_ui_mode == "scoped":
106
+ if get_current_flock_instance(): # Re-check, as clear_current_flock might have run if user switched modes
107
+ context["initial_content_url"] = (
108
+ "/api/flocks/htmx/flock-properties-form"
109
+ )
110
+ else:
111
+ context["initial_content_url"] = "/ui/htmx/scoped-no-flock-view"
112
+ else: # Standalone mode
113
+ context["initial_content_url"] = "/ui/htmx/load-flock-view"
114
+
115
+ return templates.TemplateResponse("base.html", context)
116
+
117
+
118
+ @app.get("/ui/editor/properties", response_class=HTMLResponse)
119
+ async def page_editor_properties(
120
+ request: Request,
121
+ success: str = None,
122
+ error: str = None,
123
+ ui_mode: str = Query("standalone"),
124
+ ):
125
+ # ... (same as before) ...
126
+ flock = get_current_flock_instance()
127
+ if not flock:
128
+ err_msg = "No flock loaded. Please load or create a flock first."
129
+ # Preserve ui_mode on redirect if it was passed
130
+ redirect_url = f"/?error={urllib.parse.quote(err_msg)}"
131
+ if ui_mode == "scoped":
132
+ redirect_url += "&ui_mode=scoped"
133
+ return RedirectResponse(url=redirect_url, status_code=303)
134
+ context = get_base_context(request, error, success, ui_mode)
135
+ context["initial_content_url"] = "/api/flocks/htmx/flock-properties-form"
136
+ return templates.TemplateResponse("base.html", context)
137
+
138
+
139
+ @app.get("/ui/editor/agents", response_class=HTMLResponse)
140
+ async def page_editor_agents(
141
+ request: Request,
142
+ success: str = None,
143
+ error: str = None,
144
+ ui_mode: str = Query("standalone"),
145
+ ):
146
+ # ... (same as before) ...
147
+ flock = get_current_flock_instance()
148
+ if not flock:
149
+ # Preserve ui_mode on redirect
150
+ redirect_url = (
151
+ f"/?error={urllib.parse.quote('No flock loaded for agent view.')}"
152
+ )
153
+ if ui_mode == "scoped":
154
+ redirect_url += "&ui_mode=scoped"
155
+ return RedirectResponse(url=redirect_url, status_code=303)
156
+ context = get_base_context(request, error, success, ui_mode)
157
+ context["initial_content_url"] = "/ui/htmx/agent-manager-view"
158
+ return templates.TemplateResponse("base.html", context)
159
+
160
+
161
+ @app.get("/ui/editor/execute", response_class=HTMLResponse)
162
+ async def page_editor_execute(
163
+ request: Request,
164
+ success: str = None,
165
+ error: str = None,
166
+ ui_mode: str = Query("standalone"),
167
+ ):
168
+ flock = get_current_flock_instance()
169
+ if not flock:
170
+ # Preserve ui_mode on redirect
171
+ redirect_url = (
172
+ f"/?error={urllib.parse.quote('No flock loaded to execute.')}"
173
+ )
174
+ if ui_mode == "scoped":
175
+ redirect_url += "&ui_mode=scoped"
176
+ return RedirectResponse(url=redirect_url, status_code=303)
177
+ context = get_base_context(request, error, success, ui_mode)
178
+ # UPDATED initial_content_url
179
+ context["initial_content_url"] = "/ui/htmx/execution-view-container"
180
+ return templates.TemplateResponse("base.html", context)
181
+
182
+
183
+ # ... (registry and create page routes remain the same) ...
184
+ @app.get("/ui/registry", response_class=HTMLResponse)
185
+ async def page_registry(
186
+ request: Request,
187
+ error: str = None,
188
+ success: str = None,
189
+ ui_mode: str = Query("standalone"),
190
+ ):
191
+ context = get_base_context(request, error, success, ui_mode)
192
+ context["initial_content_url"] = "/ui/htmx/registry-viewer"
193
+ return templates.TemplateResponse("base.html", context)
194
+
195
+
196
+ @app.get("/ui/create", response_class=HTMLResponse)
197
+ async def page_create(
198
+ request: Request,
199
+ error: str = None,
200
+ success: str = None,
201
+ ui_mode: str = Query("standalone"),
202
+ ):
203
+ clear_current_flock()
204
+ # Create page should arguably not be accessible in scoped mode directly via URL,
205
+ # as the sidebar link will be hidden. If accessed, treat as standalone.
206
+ context = get_base_context(
207
+ request, error, success, "standalone"
208
+ ) # Force standalone for direct access
209
+ context["initial_content_url"] = "/ui/htmx/create-flock-form"
210
+ return templates.TemplateResponse("base.html", context)
211
+
212
+
213
+ # --- HTMX Content Routes ---
214
+ @app.get("/ui/htmx/sidebar", response_class=HTMLResponse)
215
+ async def htmx_get_sidebar(
216
+ request: Request, ui_mode: str = Query("standalone")
217
+ ):
218
+ # ... (same as before) ...
219
+ return templates.TemplateResponse(
220
+ "partials/_sidebar.html",
221
+ {
222
+ "request": request,
223
+ "current_flock": get_current_flock_instance(),
224
+ "ui_mode": ui_mode,
225
+ },
226
+ )
227
+
228
+
229
+ @app.get("/ui/htmx/load-flock-view", response_class=HTMLResponse)
230
+ async def htmx_get_load_flock_view(
231
+ request: Request,
232
+ error: str = None,
233
+ success: str = None,
234
+ ui_mode: str = Query("standalone"),
235
+ ):
236
+ # ... (same as before) ...
237
+ # This view is part of the "standalone" functionality.
238
+ # If somehow accessed in scoped mode, it might be confusing, but let it render.
239
+ return templates.TemplateResponse(
240
+ "partials/_load_manage_view.html",
241
+ {
242
+ "request": request,
243
+ "error_message": error,
244
+ "success_message": success,
245
+ "ui_mode": ui_mode, # Pass for consistency, though not directly used in this partial
246
+ },
247
+ )
248
+
249
+
250
+ @app.get("/ui/htmx/dashboard-flock-file-list", response_class=HTMLResponse)
251
+ async def htmx_get_dashboard_flock_file_list_partial(request: Request):
252
+ # ... (same as before) ...
253
+ return templates.TemplateResponse(
254
+ "partials/_dashboard_flock_file_list.html",
255
+ {"request": request, "flock_files": get_available_flock_files()},
256
+ )
257
+
258
+
259
+ @app.get("/ui/htmx/dashboard-default-action-pane", response_class=HTMLResponse)
260
+ async def htmx_get_dashboard_default_action_pane(request: Request):
261
+ # ... (same as before) ...
262
+ return HTMLResponse("""
263
+ <article style="text-align:center; margin-top: 2rem; border: none; background: transparent;">
264
+ <p>Select a Flock from the list to view its details and load it into the editor.</p>
265
+ <hr>
266
+ <p>Or, create a new Flock or upload an existing one using the "Create New Flock" option in the sidebar.</p>
267
+ </article>
268
+ """)
269
+
270
+
271
+ @app.get(
272
+ "/ui/htmx/dashboard-flock-properties-preview/{filename}",
273
+ response_class=HTMLResponse,
274
+ )
275
+ async def htmx_get_dashboard_flock_properties_preview(
276
+ request: Request, filename: str
277
+ ):
278
+ # ... (same as before) ...
279
+ preview_flock_data = get_flock_preview_service(filename)
280
+ return templates.TemplateResponse(
281
+ "partials/_dashboard_flock_properties_preview.html",
282
+ {
283
+ "request": request,
284
+ "selected_filename": filename,
285
+ "preview_flock": preview_flock_data,
286
+ },
287
+ )
288
+
289
+
290
+ @app.get("/ui/htmx/create-flock-form", response_class=HTMLResponse)
291
+ async def htmx_get_create_flock_form(
292
+ request: Request,
293
+ error: str = None,
294
+ success: str = None,
295
+ ui_mode: str = Query("standalone"),
296
+ ):
297
+ # ... (same as before) ...
298
+ # This view is part of the "standalone" functionality.
299
+ return templates.TemplateResponse(
300
+ "partials/_create_flock_form.html",
301
+ {
302
+ "request": request,
303
+ "error_message": error,
304
+ "success_message": success,
305
+ "ui_mode": ui_mode, # Pass for consistency
306
+ },
307
+ )
308
+
309
+
310
+ @app.get("/ui/htmx/agent-manager-view", response_class=HTMLResponse)
311
+ async def htmx_get_agent_manager_view(request: Request):
312
+ # ... (same as before) ...
313
+ flock = get_current_flock_instance()
314
+ if not flock:
315
+ return HTMLResponse(
316
+ "<article class='error'><p>No flock loaded. Cannot manage agents.</p></article>"
317
+ )
318
+ return templates.TemplateResponse(
319
+ "partials/_agent_manager_view.html",
320
+ {"request": request, "flock": flock},
321
+ )
322
+
323
+
324
+ @app.get("/ui/htmx/registry-viewer", response_class=HTMLResponse)
325
+ async def htmx_get_registry_viewer(request: Request):
326
+ # ... (same as before) ...
327
+ return templates.TemplateResponse(
328
+ "partials/_registry_viewer_content.html", {"request": request}
329
+ )
330
+
331
+
332
+ # --- NEW HTMX ROUTE FOR THE EXECUTION VIEW CONTAINER ---
333
+ @app.get("/ui/htmx/execution-view-container", response_class=HTMLResponse)
334
+ async def htmx_get_execution_view_container(request: Request):
335
+ flock = get_current_flock_instance()
336
+ if not flock:
337
+ return HTMLResponse(
338
+ "<article class='error'><p>No Flock loaded. Cannot execute.</p></article>"
339
+ )
340
+ return templates.TemplateResponse(
341
+ "partials/_execution_view_container.html", {"request": request}
342
+ )
343
+
344
+
345
+ # A new HTMX route for scoped mode when no flock is initially loaded (should ideally not happen)
346
+ @app.get("/ui/htmx/scoped-no-flock-view", response_class=HTMLResponse)
347
+ async def htmx_scoped_no_flock_view(request: Request):
348
+ return HTMLResponse("""
349
+ <article style="text-align:center; margin-top: 2rem; border: none; background: transparent;">
350
+ <hgroup>
351
+ <h2>Scoped Flock Mode</h2>
352
+ <h3>No Flock Loaded</h3>
353
+ </hgroup>
354
+ <p>This UI is in a scoped mode, expecting a Flock to be pre-loaded.</p>
355
+ <p>Please ensure the calling application provides a Flock instance.</p>
356
+ </article>
357
+ """)
358
+
359
+
360
+ # Endpoint to launch the UI in scoped mode with a preloaded flock
361
+ @app.post("/ui/launch-scoped", response_class=RedirectResponse)
362
+ async def launch_scoped_ui(
363
+ request: Request,
364
+ flock_data: dict, # This would be the flock's JSON data
365
+ # Potentially also receive filename if it's from a saved file
366
+ ):
367
+ # Here, you would parse flock_data, create a Flock instance,
368
+ # and set it as the current flock using your flock_service methods.
369
+ # For now, let's assume flock_service has a method like:
370
+ # set_current_flock_from_data(data) -> bool (returns True if successful)
371
+
372
+ # This is a placeholder for actual flock loading logic
373
+ # from flock.core.entities.flock import Flock # Assuming Flock can be instantiated from dict
374
+ # from flock.webapp.app.services.flock_service import set_current_flock_instance, set_current_flock_filename
375
+
376
+ # try:
377
+ # # Assuming flock_data is a dict that can initialize a Flock object
378
+ # # You might need a more robust way to deserialize, e.g., using Pydantic models
379
+ # loaded_flock = Flock(**flock_data) # This is a simplistic example
380
+ # set_current_flock_instance(loaded_flock)
381
+ # # If the flock has a name or identifier, you might set it as well
382
+ # # set_current_flock_filename(flock_data.get("name", "scoped_flock")) # Example
383
+ #
384
+ # # Redirect to the agent editor or properties page in scoped mode
385
+ # # The page_dashboard will handle ui_mode=scoped and redirect/set initial content appropriately
386
+ # return RedirectResponse(url="/?ui_mode=scoped", status_code=303)
387
+ # except Exception as e:
388
+ # # Log error e
389
+ # # Redirect to an error page or the standalone dashboard with an error message
390
+ # error_msg = f"Failed to load flock for scoped view: {e}"
391
+ # return RedirectResponse(url=f"/?error={urllib.parse.quote(error_msg)}&ui_mode=standalone", status_code=303)
392
+
393
+ # For now, since we don't have the flock loading logic here,
394
+ # we'll just redirect. The calling service (`src/flock/core/api`)
395
+ # will need to ensure the flock is loaded into the webapp's session/state
396
+ # *before* redirecting to this UI.
397
+
398
+ # A more direct way if `load_flock_from_data_service` exists and sets it globally for the session:
399
+ # success = load_flock_from_data_service(flock_data, "scoped_runtime_flock") # example filename
400
+ # if success:
401
+ # return RedirectResponse(url="/ui/editor/agents?ui_mode=scoped", status_code=303) # or properties
402
+ # else:
403
+ # return RedirectResponse(url="/?error=Failed+to+load+scoped+flock&ui_mode=standalone", status_code=303)
404
+
405
+ # Given the current structure, the simplest way for an external service to "preload" a flock
406
+ # is to use the existing `load_flock_from_file_service` if the flock can be temporarily saved,
407
+ # or by enhancing `flock_service` to allow setting a Flock instance directly.
408
+ # Let's assume the flock is already loaded into the session by the calling API for now.
409
+ # The calling API will be responsible for calling a service function within the webapp's context.
410
+
411
+ # This endpoint's primary job is now to redirect to the UI in the correct mode.
412
+ # The actual loading of the flock should happen *before* this redirect,
413
+ # by the API server calling a service function within the webapp's context.
414
+
415
+ # For demonstration, let's imagine the calling API has already used a service
416
+ # to set the flock. We just redirect.
417
+ if get_current_flock_instance():
418
+ return RedirectResponse(
419
+ url="/ui/editor/agents?ui_mode=scoped", status_code=303
420
+ )
421
+ else:
422
+ # If no flock is loaded, go to the main page in scoped mode, which will show the "no flock" message.
423
+ return RedirectResponse(url="/?ui_mode=scoped", status_code=303)
424
+
425
+
426
+ # --- Action Routes ...
427
+ # The `load-flock-action/*` and `create-flock` POST routes should remain the same as they already
428
+ # correctly target `#main-content-area` and trigger `flockLoaded`.
429
+ # ... (rest of action routes: load-flock-action/by-name, by-upload, create-flock)
430
+ @app.post("/ui/load-flock-action/by-name", response_class=HTMLResponse)
431
+ async def ui_load_flock_by_name_action(
432
+ request: Request, selected_flock_filename: str = Form(...)
433
+ ):
434
+ loaded_flock = load_flock_from_file_service(selected_flock_filename)
435
+ response_headers = {}
436
+ if loaded_flock:
437
+ success_message = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
438
+ response_headers["HX-Push-Url"] = "/ui/editor/properties"
439
+ response_headers["HX-Trigger"] = json.dumps(
440
+ {
441
+ "flockLoaded": None,
442
+ "notify": {"type": "success", "message": success_message},
443
+ }
444
+ )
445
+ return templates.TemplateResponse(
446
+ "partials/_flock_properties_form.html",
447
+ {
448
+ "request": request,
449
+ "flock": loaded_flock,
450
+ "current_filename": get_current_flock_filename(),
451
+ },
452
+ headers=response_headers,
453
+ )
454
+ else:
455
+ error_message = (
456
+ f"Failed to load flock file '{selected_flock_filename}'."
457
+ )
458
+ response_headers["HX-Trigger"] = json.dumps(
459
+ {"notify": {"type": "error", "message": error_message}}
460
+ )
461
+ return templates.TemplateResponse(
462
+ "partials/_load_manage_view.html",
463
+ {"request": request, "error_message_inline": error_message},
464
+ headers=response_headers,
465
+ )
466
+
467
+
468
+ @app.post("/ui/load-flock-action/by-upload", response_class=HTMLResponse)
469
+ async def ui_load_flock_by_upload_action(
470
+ request: Request, flock_file_upload: UploadFile = File(...)
471
+ ):
472
+ error_message = None
473
+ filename_to_load = None
474
+ response_headers = {}
475
+ if flock_file_upload and flock_file_upload.filename:
476
+ if not flock_file_upload.filename.endswith((".yaml", ".yml", ".flock")):
477
+ error_message = "Invalid file type."
478
+ else:
479
+ upload_path = FLOCK_FILES_DIR / flock_file_upload.filename
480
+ try:
481
+ with upload_path.open("wb") as buffer:
482
+ shutil.copyfileobj(flock_file_upload.file, buffer)
483
+ filename_to_load = flock_file_upload.filename
484
+ except Exception as e:
485
+ error_message = f"Upload failed: {e}"
486
+ finally:
487
+ await flock_file_upload.close()
488
+ else:
489
+ error_message = "No file uploaded."
490
+
491
+ if filename_to_load and not error_message:
492
+ loaded_flock = load_flock_from_file_service(filename_to_load)
493
+ if loaded_flock:
494
+ success_message = (
495
+ f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
496
+ )
497
+ response_headers["HX-Push-Url"] = "/ui/editor/properties"
498
+ response_headers["HX-Trigger"] = json.dumps(
499
+ {
500
+ "flockLoaded": None,
501
+ "flockFileListChanged": None,
502
+ "notify": {"type": "success", "message": success_message},
503
+ }
504
+ )
505
+ return templates.TemplateResponse(
506
+ "partials/_flock_properties_form.html",
507
+ {
508
+ "request": request,
509
+ "flock": loaded_flock,
510
+ "current_filename": get_current_flock_filename(),
511
+ },
512
+ headers=response_headers,
513
+ )
514
+ else:
515
+ error_message = f"Failed to process uploaded '{filename_to_load}'."
516
+
517
+ response_headers["HX-Trigger"] = json.dumps(
518
+ {
519
+ "notify": {
520
+ "type": "error",
521
+ "message": error_message or "Upload failed.",
522
+ }
523
+ }
524
+ )
525
+ return templates.TemplateResponse(
526
+ "partials/_create_flock_form.html",
527
+ { # Changed target to create form on upload error
528
+ "request": request,
529
+ "error_message": error_message or "Upload action failed.",
530
+ },
531
+ headers=response_headers,
532
+ )
533
+
534
+
535
+ @app.post("/ui/create-flock", response_class=HTMLResponse)
536
+ async def ui_create_flock_action(
537
+ request: Request,
538
+ flock_name: str = Form(...),
539
+ default_model: str = Form(None),
540
+ description: str = Form(None),
541
+ ):
542
+ if not flock_name.strip():
543
+ return templates.TemplateResponse(
544
+ "partials/_create_flock_form.html",
545
+ {
546
+ "request": request,
547
+ "error_message": "Flock name cannot be empty.",
548
+ },
549
+ )
550
+ new_flock = create_new_flock_service(flock_name, default_model, description)
551
+ success_msg = (
552
+ f"New flock '{new_flock.name}' created. Configure properties and save."
553
+ )
554
+ response_headers = {
555
+ "HX-Push-Url": "/ui/editor/properties",
556
+ "HX-Trigger": json.dumps(
557
+ {
558
+ "flockLoaded": None,
559
+ "notify": {"type": "success", "message": success_msg},
560
+ }
561
+ ),
562
+ }
563
+ return templates.TemplateResponse(
564
+ "partials/_flock_properties_form.html",
565
+ {
566
+ "request": request,
567
+ "flock": new_flock,
568
+ "current_filename": get_current_flock_filename(),
569
+ },
570
+ headers=response_headers,
571
+ )
@@ -0,0 +1,7 @@
1
+ # Pydantic models specific to UI interactions, if needed.
2
+ # For MVP, we might not need many here, as we'll primarily pass basic dicts to flock_service.
3
+ # Example:
4
+ # from pydantic import BaseModel
5
+ # class SaveFlockRequest(BaseModel):
6
+ # current_flock_json: str # Or a more structured model if preferred
7
+ # new_filename: str
File without changes