flock-core 0.4.0b34__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 (47) hide show
  1. flock/__init__.py +27 -5
  2. flock/core/api/main.py +138 -39
  3. flock/webapp/__init__.py +1 -0
  4. flock/webapp/app/__init__.py +0 -0
  5. flock/webapp/app/api/__init__.py +0 -0
  6. flock/webapp/app/api/agent_management.py +270 -0
  7. flock/webapp/app/api/execution.py +173 -0
  8. flock/webapp/app/api/flock_management.py +102 -0
  9. flock/webapp/app/api/registry_viewer.py +30 -0
  10. flock/webapp/app/config.py +9 -0
  11. flock/webapp/app/main.py +571 -0
  12. flock/webapp/app/models_ui.py +7 -0
  13. flock/webapp/app/services/__init__.py +0 -0
  14. flock/webapp/app/services/flock_service.py +291 -0
  15. flock/webapp/app/utils.py +85 -0
  16. flock/webapp/run.py +30 -0
  17. flock/webapp/static/css/custom.css +527 -0
  18. flock/webapp/templates/base.html +98 -0
  19. flock/webapp/templates/flock_editor.html +17 -0
  20. flock/webapp/templates/index.html +12 -0
  21. flock/webapp/templates/partials/_agent_detail_form.html +97 -0
  22. flock/webapp/templates/partials/_agent_list.html +24 -0
  23. flock/webapp/templates/partials/_agent_manager_view.html +15 -0
  24. flock/webapp/templates/partials/_agent_tools_checklist.html +14 -0
  25. flock/webapp/templates/partials/_create_flock_form.html +52 -0
  26. flock/webapp/templates/partials/_dashboard_flock_detail.html +18 -0
  27. flock/webapp/templates/partials/_dashboard_flock_file_list.html +17 -0
  28. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +29 -0
  29. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +17 -0
  30. flock/webapp/templates/partials/_dynamic_input_form_content.html +22 -0
  31. flock/webapp/templates/partials/_execution_form.html +48 -0
  32. flock/webapp/templates/partials/_execution_view_container.html +19 -0
  33. flock/webapp/templates/partials/_flock_file_list.html +24 -0
  34. flock/webapp/templates/partials/_flock_properties_form.html +51 -0
  35. flock/webapp/templates/partials/_flock_upload_form.html +17 -0
  36. flock/webapp/templates/partials/_load_manage_view.html +88 -0
  37. flock/webapp/templates/partials/_registry_table.html +25 -0
  38. flock/webapp/templates/partials/_registry_viewer_content.html +47 -0
  39. flock/webapp/templates/partials/_results_display.html +35 -0
  40. flock/webapp/templates/partials/_sidebar.html +63 -0
  41. flock/webapp/templates/partials/_structured_data_view.html +40 -0
  42. flock/webapp/templates/registry_viewer.html +84 -0
  43. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b35.dist-info}/METADATA +1 -1
  44. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b35.dist-info}/RECORD +47 -7
  45. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b35.dist-info}/WHEEL +0 -0
  46. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b35.dist-info}/entry_points.txt +0 -0
  47. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b35.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,291 @@
1
+ import yaml # For parsing issues, if any, during load
2
+
3
+ from flock.core import Flock, FlockFactory
4
+ from flock.core.flock_registry import get_registry
5
+ from flock.webapp.app.config import (
6
+ CURRENT_FLOCK_FILENAME,
7
+ CURRENT_FLOCK_INSTANCE,
8
+ FLOCK_FILES_DIR,
9
+ )
10
+
11
+
12
+ def get_available_flock_files() -> list[str]:
13
+ if not FLOCK_FILES_DIR.exists():
14
+ return []
15
+ return sorted(
16
+ [
17
+ f.name
18
+ for f in FLOCK_FILES_DIR.iterdir()
19
+ if f.is_file() and (f.suffix in [".yaml", ".yml", ".flock"])
20
+ ]
21
+ )
22
+
23
+
24
+ def load_flock_from_file_service(filename: str) -> Flock | None:
25
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
26
+ file_path = FLOCK_FILES_DIR / filename
27
+ if not file_path.exists():
28
+ print(f"Error: File not found {file_path}")
29
+ CURRENT_FLOCK_INSTANCE = None
30
+ CURRENT_FLOCK_FILENAME = None
31
+ return None
32
+ try:
33
+ # Temporarily clear registry parts that might be file-specific if needed,
34
+ # or ensure load_from_file handles re-registration gracefully.
35
+ # For MVP, assume load_from_file is robust enough.
36
+ CURRENT_FLOCK_INSTANCE = Flock.load_from_file(str(file_path))
37
+ CURRENT_FLOCK_FILENAME = filename
38
+ print(
39
+ f"Successfully loaded flock: {CURRENT_FLOCK_INSTANCE.name if CURRENT_FLOCK_INSTANCE else 'None'}"
40
+ )
41
+ return CURRENT_FLOCK_INSTANCE
42
+ except Exception as e:
43
+ print(f"Error loading flock from {file_path}: {e}")
44
+ CURRENT_FLOCK_INSTANCE = None
45
+ CURRENT_FLOCK_FILENAME = None
46
+ return None
47
+
48
+
49
+ def create_new_flock_service(
50
+ name: str, model: str | None, description: str | None
51
+ ) -> Flock:
52
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
53
+ effective_model = model.strip() if model and model.strip() else None
54
+ CURRENT_FLOCK_INSTANCE = Flock(
55
+ name=name,
56
+ model=effective_model,
57
+ description=description,
58
+ show_flock_banner=False,
59
+ enable_logging=False,
60
+ )
61
+ CURRENT_FLOCK_FILENAME = f"{name.replace(' ', '_').lower()}.flock.yaml"
62
+ print(f"Created new flock: {name}")
63
+ return CURRENT_FLOCK_INSTANCE
64
+
65
+
66
+ def get_current_flock_instance() -> Flock | None:
67
+ return CURRENT_FLOCK_INSTANCE
68
+
69
+
70
+ def get_current_flock_filename() -> str | None:
71
+ return CURRENT_FLOCK_FILENAME
72
+
73
+
74
+ def set_current_flock_instance_programmatically(flock: Flock, filename: str):
75
+ """Sets the current flock instance and filename programmatically.
76
+ Used when launching the UI with a pre-loaded flock from an external source (e.g., API server).
77
+ """
78
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
79
+ CURRENT_FLOCK_INSTANCE = flock
80
+ CURRENT_FLOCK_FILENAME = filename
81
+ print(
82
+ f"Programmatically set flock: {filename} (Name: {flock.name if flock else 'None'})"
83
+ )
84
+
85
+
86
+ def set_current_flock_filename(filename: str | None):
87
+ global CURRENT_FLOCK_FILENAME
88
+ CURRENT_FLOCK_FILENAME = filename
89
+
90
+
91
+ def clear_current_flock():
92
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
93
+ CURRENT_FLOCK_INSTANCE = None
94
+ CURRENT_FLOCK_FILENAME = None
95
+ print("Current flock cleared.")
96
+
97
+
98
+ def save_current_flock_to_file_service(new_filename: str) -> tuple[bool, str]:
99
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
100
+ if not CURRENT_FLOCK_INSTANCE:
101
+ return False, "No flock loaded to save."
102
+ if not new_filename.strip():
103
+ return False, "Filename cannot be empty."
104
+ save_path = FLOCK_FILES_DIR / new_filename
105
+ try:
106
+ CURRENT_FLOCK_INSTANCE.to_yaml_file(str(save_path))
107
+ CURRENT_FLOCK_FILENAME = new_filename
108
+ return True, f"Flock saved to {new_filename}."
109
+ except Exception as e:
110
+ return False, f"Error saving flock: {e}"
111
+
112
+
113
+ def update_flock_properties_service(
114
+ name: str, model: str | None, description: str | None
115
+ ) -> bool:
116
+ # ... (same as before)
117
+ global CURRENT_FLOCK_INSTANCE, CURRENT_FLOCK_FILENAME
118
+ if not CURRENT_FLOCK_INSTANCE:
119
+ return False
120
+ old_name_default_filename = (
121
+ f"{CURRENT_FLOCK_INSTANCE.name.replace(' ', '_').lower()}.flock.yaml"
122
+ )
123
+ if (
124
+ old_name_default_filename == CURRENT_FLOCK_FILENAME
125
+ and CURRENT_FLOCK_INSTANCE.name != name
126
+ ):
127
+ CURRENT_FLOCK_FILENAME = f"{name.replace(' ', '_').lower()}.flock.yaml"
128
+
129
+ CURRENT_FLOCK_INSTANCE.name = name
130
+ CURRENT_FLOCK_INSTANCE.model = (
131
+ model.strip() if model and model.strip() else None
132
+ )
133
+ CURRENT_FLOCK_INSTANCE.description = description
134
+ return True
135
+
136
+
137
+ def add_agent_to_current_flock_service(agent_config: dict) -> bool:
138
+ # ... (same as before) ...
139
+ global CURRENT_FLOCK_INSTANCE
140
+ if not CURRENT_FLOCK_INSTANCE:
141
+ return False
142
+ registry = get_registry()
143
+ tools_instances = []
144
+ if agent_config.get("tools_names"):
145
+ for tool_name in agent_config["tools_names"]:
146
+ try:
147
+ tools_instances.append(registry.get_callable(tool_name))
148
+ except KeyError:
149
+ print(f"Warning: Tool '{tool_name}' not found. Skipping.")
150
+ try:
151
+ agent = FlockFactory.create_default_agent(
152
+ name=agent_config["name"],
153
+ description=agent_config.get("description"),
154
+ model=agent_config.get("model"),
155
+ input=agent_config["input"],
156
+ output=agent_config["output"],
157
+ tools=tools_instances or None,
158
+ )
159
+ handoff_target = agent_config.get("default_router_handoff")
160
+ if handoff_target:
161
+ from flock.routers.default.default_router import DefaultRouterConfig
162
+
163
+ agent.add_component(DefaultRouterConfig(hand_off=handoff_target))
164
+ CURRENT_FLOCK_INSTANCE.add_agent(agent)
165
+ return True
166
+ except Exception as e:
167
+ print(f"Error adding agent: {e}")
168
+ return False
169
+
170
+
171
+ def update_agent_in_current_flock_service(
172
+ original_agent_name: str, agent_config: dict
173
+ ) -> bool:
174
+ # ... (same as before) ...
175
+ global CURRENT_FLOCK_INSTANCE
176
+ if not CURRENT_FLOCK_INSTANCE:
177
+ return False
178
+ agent_to_update = CURRENT_FLOCK_INSTANCE.agents.get(original_agent_name)
179
+ if not agent_to_update:
180
+ return False
181
+ registry = get_registry()
182
+ tools_instances = []
183
+ if agent_config.get("tools_names"):
184
+ for tool_name in agent_config["tools_names"]:
185
+ try:
186
+ tools_instances.append(registry.get_callable(tool_name))
187
+ except KeyError:
188
+ print(f"Warning: Tool '{tool_name}' not found. Skipping.")
189
+ try:
190
+ new_name = agent_config["name"]
191
+ agent_to_update.description = agent_config.get("description")
192
+ agent_to_update.model = agent_config.get("model")
193
+ agent_to_update.input = agent_config["input"]
194
+ agent_to_update.output = agent_config["output"]
195
+ agent_to_update.tools = tools_instances or None
196
+ handoff_target = agent_config.get("default_router_handoff")
197
+ if handoff_target:
198
+ from flock.routers.default.default_router import DefaultRouterConfig
199
+
200
+ agent_to_update.add_component(
201
+ DefaultRouterConfig(hand_off=handoff_target)
202
+ )
203
+ elif agent_to_update.handoff_router:
204
+ agent_to_update.handoff_router = None
205
+ if original_agent_name != new_name:
206
+ CURRENT_FLOCK_INSTANCE._agents[new_name] = (
207
+ CURRENT_FLOCK_INSTANCE._agents.pop(original_agent_name)
208
+ )
209
+ agent_to_update.name = new_name
210
+ return True
211
+ except Exception as e:
212
+ print(f"Error updating agent: {e}")
213
+ return False
214
+
215
+
216
+ def remove_agent_from_current_flock_service(agent_name: str) -> bool:
217
+ # ... (same as before) ...
218
+ global CURRENT_FLOCK_INSTANCE
219
+ if (
220
+ not CURRENT_FLOCK_INSTANCE
221
+ or agent_name not in CURRENT_FLOCK_INSTANCE.agents
222
+ ):
223
+ return False
224
+ del CURRENT_FLOCK_INSTANCE._agents[agent_name]
225
+ return True
226
+
227
+
228
+ async def run_current_flock_service(
229
+ start_agent_name: str, inputs: dict
230
+ ) -> dict | str:
231
+ global CURRENT_FLOCK_INSTANCE
232
+ if not CURRENT_FLOCK_INSTANCE:
233
+ return "Error: No flock loaded."
234
+ if (
235
+ not start_agent_name
236
+ or start_agent_name not in CURRENT_FLOCK_INSTANCE.agents
237
+ ):
238
+ return f"Error: Start agent '{start_agent_name}' not found."
239
+ try:
240
+ result = await CURRENT_FLOCK_INSTANCE.run_async(
241
+ start_agent=start_agent_name, input=inputs, box_result=False
242
+ )
243
+ # Don't convert here - let the API route handle it to avoid double conversion
244
+ return result
245
+ except Exception as e:
246
+ print(f"Error during flock execution: {e}")
247
+ return f"Error: {e!s}"
248
+
249
+
250
+ def get_registered_items_service(item_type: str) -> list:
251
+ # ... (same as before) ...
252
+ registry = get_registry()
253
+ items, items_dict = [], None
254
+ if item_type == "type":
255
+ items_dict = registry._types
256
+ elif item_type == "tool":
257
+ items_dict = registry._callables
258
+ elif item_type == "component":
259
+ items_dict = registry._components
260
+ else:
261
+ return []
262
+ for name, item_obj in items_dict.items():
263
+ module_path = "N/A"
264
+ try:
265
+ module_path = item_obj.__module__
266
+ except AttributeError:
267
+ pass
268
+ items.append({"name": name, "module": module_path})
269
+ return sorted(items, key=lambda x: x["name"])
270
+
271
+
272
+ def get_flock_preview_service(filename: str) -> dict | None:
273
+ """Loads only basic properties of a flock file for preview without full deserialization."""
274
+ file_path = FLOCK_FILES_DIR / filename
275
+ if not file_path.exists():
276
+ return None
277
+ try:
278
+ with file_path.open("r", encoding="utf-8") as f:
279
+ # Load YAML just to get top-level keys
280
+ data = yaml.safe_load(f)
281
+ if isinstance(data, dict):
282
+ return {
283
+ "name": data.get("name", filename),
284
+ "model": data.get("model"),
285
+ "description": data.get("description"),
286
+ "agents_count": len(data.get("agents", {})),
287
+ }
288
+ return {"name": filename, "error": "Not a valid Flock YAML structure"}
289
+ except Exception as e:
290
+ print(f"Error getting flock preview for {filename}: {e}")
291
+ return {"name": filename, "error": str(e)}
@@ -0,0 +1,85 @@
1
+ import inspect
2
+ import json
3
+ from typing import Any
4
+
5
+ def is_pydantic_model(obj: Any) -> bool:
6
+ """
7
+ Check if an object is a Pydantic model instance.
8
+ More robust detection for both v1 and v2 Pydantic models.
9
+ """
10
+ # Check for Pydantic v2 model
11
+ if hasattr(obj, "__class__") and hasattr(obj.__class__, "model_dump"):
12
+ return True
13
+
14
+ # Check for Pydantic v1 model
15
+ if hasattr(obj, "__class__") and hasattr(obj.__class__, "schema") and hasattr(obj, "dict"):
16
+ return True
17
+
18
+ # Check if it has a __pydantic_core__ attribute (v2 models)
19
+ if hasattr(obj, "__pydantic_core__"):
20
+ return True
21
+
22
+ # Final check: class name check and module check
23
+ if hasattr(obj, "__class__"):
24
+ cls = obj.__class__
25
+ cls_name = cls.__name__
26
+ module_name = getattr(cls, "__module__", "")
27
+ if "pydantic" in module_name.lower() or "basemodel" in cls_name.lower():
28
+ return True
29
+
30
+ return False
31
+
32
+ def is_json_serializable(obj: Any) -> bool:
33
+ """Check if an object can be serialized to JSON."""
34
+ try:
35
+ json.dumps(obj)
36
+ return True
37
+ except (TypeError, ValueError):
38
+ return False
39
+
40
+ def pydantic_to_dict(obj: Any) -> Any:
41
+ """
42
+ Recursively convert Pydantic models to dictionaries.
43
+ Works with nested models, lists, and dictionaries containing models.
44
+ Falls back to string representation if object can't be serialized.
45
+ """
46
+ if obj is None:
47
+ return None
48
+
49
+ if is_pydantic_model(obj):
50
+ # Handle Pydantic v2 models
51
+ if hasattr(obj, "model_dump"):
52
+ return obj.model_dump()
53
+ # Handle Pydantic v1 models
54
+ elif hasattr(obj, "dict"):
55
+ return obj.dict()
56
+ # Last resort - try __dict__ if it exists
57
+ elif hasattr(obj, "__dict__"):
58
+ return {k: pydantic_to_dict(v) for k, v in obj.__dict__.items()
59
+ if not k.startswith("_")}
60
+
61
+ elif isinstance(obj, dict):
62
+ # Handle dictionaries that might contain Pydantic models
63
+ return {k: pydantic_to_dict(v) for k, v in obj.items()}
64
+
65
+ elif isinstance(obj, list):
66
+ # Handle lists that might contain Pydantic models
67
+ return [pydantic_to_dict(item) for item in obj]
68
+
69
+ elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
70
+ # Handle namedtuples
71
+ return dict(zip(obj._fields, (pydantic_to_dict(item) for item in obj)))
72
+
73
+ elif isinstance(obj, (list, tuple)):
74
+ # Handle regular lists and tuples
75
+ return [pydantic_to_dict(item) for item in obj]
76
+
77
+ # Final check - if object is not JSON serializable, convert to string
78
+ if not is_json_serializable(obj):
79
+ try:
80
+ return str(obj)
81
+ except Exception:
82
+ return f"<Unserializable object of type {type(obj).__name__}>"
83
+
84
+ # Return other types unchanged - these should be JSON serializable by default
85
+ return obj
flock/webapp/run.py ADDED
@@ -0,0 +1,30 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ import uvicorn
5
+
6
+
7
+ def main():
8
+ """Run the Flock web application."""
9
+ # Ensure the webapp directory is in the Python path
10
+ webapp_dir = Path(__file__).resolve().parent
11
+ if str(webapp_dir) not in sys.path:
12
+ sys.path.insert(0, str(webapp_dir.parent.parent)) # Add 'src' to path
13
+
14
+ try:
15
+ uvicorn.run(
16
+ "flock.webapp.app.main:app",
17
+ host="127.0.0.1",
18
+ port=8344,
19
+ reload=True,
20
+ )
21
+ except ModuleNotFoundError as e:
22
+ print(f"Error loading webapp modules: {e}")
23
+ print(
24
+ "Make sure all required packages are installed and module structure is correct."
25
+ )
26
+ sys.exit(1)
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()