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,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()
|