flock-core 0.4.0b34__py3-none-any.whl → 0.4.0b36__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 (63) hide show
  1. flock/__init__.py +53 -8
  2. flock/cli/loaded_flock_cli.py +38 -18
  3. flock/core/api/main.py +138 -39
  4. flock/core/flock.py +48 -3
  5. flock/themes/alabaster.toml +43 -43
  6. flock/themes/guezwhoz.toml +43 -43
  7. flock/themes/wildcherry.toml +43 -43
  8. flock/themes/wombat.toml +43 -43
  9. flock/themes/zenburn.toml +43 -43
  10. flock/webapp/__init__.py +1 -0
  11. flock/webapp/app/__init__.py +0 -0
  12. flock/webapp/app/api/__init__.py +0 -0
  13. flock/webapp/app/api/agent_management.py +270 -0
  14. flock/webapp/app/api/execution.py +173 -0
  15. flock/webapp/app/api/flock_management.py +102 -0
  16. flock/webapp/app/api/registry_viewer.py +30 -0
  17. flock/webapp/app/config.py +87 -0
  18. flock/webapp/app/main.py +1074 -0
  19. flock/webapp/app/models_ui.py +7 -0
  20. flock/webapp/app/services/__init__.py +0 -0
  21. flock/webapp/app/services/flock_service.py +291 -0
  22. flock/webapp/app/templates/theme_mapper.html +326 -0
  23. flock/webapp/app/theme_mapper.py +812 -0
  24. flock/webapp/app/utils.py +85 -0
  25. flock/webapp/run.py +132 -0
  26. flock/webapp/static/css/custom.css +612 -0
  27. flock/webapp/templates/base.html +105 -0
  28. flock/webapp/templates/flock_editor.html +17 -0
  29. flock/webapp/templates/index.html +12 -0
  30. flock/webapp/templates/partials/_agent_detail_form.html +98 -0
  31. flock/webapp/templates/partials/_agent_list.html +19 -0
  32. flock/webapp/templates/partials/_agent_manager_view.html +53 -0
  33. flock/webapp/templates/partials/_agent_manager_view_old.html +19 -0
  34. flock/webapp/templates/partials/_agent_tools_checklist.html +14 -0
  35. flock/webapp/templates/partials/_create_flock_form.html +52 -0
  36. flock/webapp/templates/partials/_dashboard_flock_detail.html +18 -0
  37. flock/webapp/templates/partials/_dashboard_flock_file_list.html +17 -0
  38. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +29 -0
  39. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +17 -0
  40. flock/webapp/templates/partials/_dynamic_input_form_content.html +22 -0
  41. flock/webapp/templates/partials/_env_vars_table.html +25 -0
  42. flock/webapp/templates/partials/_execution_form.html +48 -0
  43. flock/webapp/templates/partials/_execution_view_container.html +20 -0
  44. flock/webapp/templates/partials/_flock_file_list.html +24 -0
  45. flock/webapp/templates/partials/_flock_properties_form.html +52 -0
  46. flock/webapp/templates/partials/_flock_upload_form.html +17 -0
  47. flock/webapp/templates/partials/_header_flock_status.html +5 -0
  48. flock/webapp/templates/partials/_load_manager_view.html +50 -0
  49. flock/webapp/templates/partials/_registry_table.html +25 -0
  50. flock/webapp/templates/partials/_registry_viewer_content.html +47 -0
  51. flock/webapp/templates/partials/_results_display.html +35 -0
  52. flock/webapp/templates/partials/_settings_env_content.html +10 -0
  53. flock/webapp/templates/partials/_settings_theme_content.html +15 -0
  54. flock/webapp/templates/partials/_settings_view.html +36 -0
  55. flock/webapp/templates/partials/_sidebar.html +70 -0
  56. flock/webapp/templates/partials/_structured_data_view.html +40 -0
  57. flock/webapp/templates/partials/_theme_preview.html +23 -0
  58. flock/webapp/templates/registry_viewer.html +84 -0
  59. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/METADATA +1 -1
  60. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/RECORD +63 -14
  61. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/WHEEL +0 -0
  62. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/entry_points.txt +0 -0
  63. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/licenses/LICENSE +0 -0
@@ -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,87 @@
1
+ import os
2
+ import random # Added for random theme selection
3
+ from pathlib import Path
4
+
5
+ from flock.core import Flock # Add type hint
6
+ from flock.core.logging.formatters.themes import OutputTheme
7
+
8
+ FLOCK_FILES_DIR = Path(os.getenv("FLOCK_FILES_DIR", "./.flock_ui_projects"))
9
+ FLOCK_FILES_DIR.mkdir(parents=True, exist_ok=True)
10
+
11
+ # --- Theme Configuration ---
12
+ # Calculate themes directory relative to this config file's location, assuming structure:
13
+ # src/flock/webapp/app/config.py
14
+ # src/flock/themes/
15
+ CONFIG_FILE_PATH = Path(__file__).resolve() # src/flock/webapp/app/config.py
16
+ FLOCK_WEBAPP_DIR = CONFIG_FILE_PATH.parent.parent # src/flock/webapp/
17
+ FLOCK_BASE_DIR = FLOCK_WEBAPP_DIR.parent # src/flock/
18
+ THEMES_DIR = FLOCK_BASE_DIR / "themes"
19
+
20
+ # Global state for MVP - NOT SUITABLE FOR PRODUCTION/MULTI-USER
21
+ CURRENT_FLOCK_INSTANCE: Flock | None = None
22
+ CURRENT_FLOCK_FILENAME: str | None = None
23
+
24
+ DEFAULT_THEME_NAME = OutputTheme.crayonponyfish.value # Default if random fails or invalid theme specified
25
+
26
+ def list_available_themes() -> list[str]:
27
+ """Scans the THEMES_DIR for .toml files and returns their names (without .toml)."""
28
+ if not THEMES_DIR.exists() or not THEMES_DIR.is_dir():
29
+ return []
30
+ return sorted([p.stem for p in THEMES_DIR.glob("*.toml") if p.is_file()])
31
+
32
+ # Initialize CURRENT_THEME_NAME
33
+ _initial_theme_from_env = os.environ.get("FLOCK_WEB_THEME")
34
+ _resolved_initial_theme = DEFAULT_THEME_NAME
35
+
36
+ if _initial_theme_from_env:
37
+ if _initial_theme_from_env.lower() == "random":
38
+ available_themes = list_available_themes()
39
+ if available_themes:
40
+ _resolved_initial_theme = random.choice(available_themes)
41
+ print(f"Config: Initial theme from FLOCK_WEB_THEME='random' resolved to: {_resolved_initial_theme}")
42
+ else:
43
+ print(f"Warning: FLOCK_WEB_THEME='random' specified, but no themes found in {THEMES_DIR}. Using default: {DEFAULT_THEME_NAME}")
44
+ _resolved_initial_theme = DEFAULT_THEME_NAME
45
+ elif _initial_theme_from_env in [t.value for t in OutputTheme] or _initial_theme_from_env in list_available_themes():
46
+ _resolved_initial_theme = _initial_theme_from_env
47
+ print(f"Config: Initial theme set from FLOCK_WEB_THEME env var: {_resolved_initial_theme}")
48
+ else:
49
+ print(f"Warning: Invalid theme name '{_initial_theme_from_env}' in FLOCK_WEB_THEME. Using default: {DEFAULT_THEME_NAME}")
50
+ _resolved_initial_theme = DEFAULT_THEME_NAME
51
+
52
+ CURRENT_THEME_NAME: str = _resolved_initial_theme
53
+
54
+ def set_current_theme_name(theme_name: str | None):
55
+ """Sets the globally accessible current theme name.
56
+ If 'random' is passed, a random theme is chosen.
57
+ """
58
+ global CURRENT_THEME_NAME
59
+ resolved_theme = DEFAULT_THEME_NAME # Default to start
60
+
61
+ if theme_name:
62
+ if theme_name.lower() == "random":
63
+ available = list_available_themes()
64
+ if available:
65
+ resolved_theme = random.choice(available)
66
+ print(f"Config: Theme 'random' resolved to: {resolved_theme}")
67
+ else:
68
+ print(f"Warning: Theme 'random' specified, but no themes found in {THEMES_DIR}. Using default: {DEFAULT_THEME_NAME}")
69
+ # resolved_theme remains DEFAULT_THEME_NAME
70
+ elif theme_name in [t.value for t in OutputTheme] or theme_name in list_available_themes():
71
+ resolved_theme = theme_name
72
+ else:
73
+ print(f"Warning: Invalid theme name provided ('{theme_name}'). Using default: {DEFAULT_THEME_NAME}")
74
+ # resolved_theme remains DEFAULT_THEME_NAME
75
+ else: # theme_name is None
76
+ # resolved_theme remains DEFAULT_THEME_NAME (set at the beginning of function)
77
+ pass
78
+
79
+ CURRENT_THEME_NAME = resolved_theme
80
+ # Ensure the theme name is set in the environment if we want other processes to see it,
81
+ # though this might be better handled by the calling process (e.g. CLI)
82
+ # os.environ["FLOCK_WEB_THEME"] = CURRENT_THEME_NAME
83
+ print(f"Config: Current theme explicitly set to: {CURRENT_THEME_NAME}")
84
+
85
+ def get_current_theme_name() -> str:
86
+ """Gets the globally accessible current theme name."""
87
+ return CURRENT_THEME_NAME