flock-core 0.4.506__py3-none-any.whl → 0.4.509__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.

@@ -1,271 +0,0 @@
1
- # src/flock/core/api/ui/routes.py
2
- """FastHTML UI routes for the Flock API."""
3
-
4
- from typing import TYPE_CHECKING
5
-
6
- # --- Conditional FastHTML Imports ---
7
- try:
8
- import httpx
9
- from fasthtml.common import *
10
-
11
- # Import Form explicitly with an alias to avoid collisions
12
- from fasthtml.common import Form as FHForm
13
-
14
- FASTHTML_AVAILABLE = True
15
- except ImportError:
16
- FASTHTML_AVAILABLE = False
17
-
18
- # Define necessary dummies if not available
19
- class Request:
20
- pass
21
-
22
- class Titled:
23
- pass
24
-
25
- class Div:
26
- pass
27
-
28
- class H1:
29
- pass
30
-
31
- class P:
32
- pass
33
-
34
- class H2:
35
- pass
36
-
37
- class Pre:
38
- pass
39
-
40
- class Code:
41
- pass
42
-
43
- class Label:
44
- pass
45
-
46
- class Select:
47
- pass
48
-
49
- class Option:
50
- pass
51
-
52
- class FHForm:
53
- pass # Dummy alias if not available
54
-
55
- class Button:
56
- pass
57
-
58
- class Span:
59
- pass
60
-
61
- class Script:
62
- pass
63
-
64
- class Style:
65
- pass
66
-
67
- class Hidden:
68
- pass
69
-
70
- class Textarea:
71
- pass
72
-
73
- class Input:
74
- pass
75
-
76
- def fast_app():
77
- return None, None
78
-
79
- def picolink():
80
- return None
81
- # ------------------------------------
82
-
83
- # Use TYPE_CHECKING to avoid circular import errors for type hints
84
- if TYPE_CHECKING:
85
- from flock.core.api.main import FlockAPI
86
-
87
- # Import logger and utils needed by UI routes
88
- from flock.core.logging.logging import get_logger
89
-
90
- logger = get_logger("api.ui")
91
-
92
-
93
- def create_ui_app(
94
- flock_api_instance: "FlockAPI",
95
- api_host: str,
96
- api_port: int,
97
- server_name: str,
98
- ) -> Any:
99
- """Creates and configures the FastHTML application and its routes."""
100
- if not FASTHTML_AVAILABLE:
101
- raise ImportError("FastHTML is not installed. Cannot create UI.")
102
- logger.debug("Creating FastHTML application instance for UI")
103
-
104
- # Use the passed FlockAPI instance to access necessary data/methods
105
- flock_instance = flock_api_instance.flock
106
- parse_input_spec_func = (
107
- flock_api_instance._parse_input_spec
108
- ) # Get reference to parser
109
-
110
- fh_app, fh_rt = fast_app(
111
- hdrs=(
112
- Script(src="https://unpkg.com/htmx.org@1.9.10/dist/htmx.min.js"),
113
- picolink, # Pass directly
114
- Style("""
115
- body { padding: 20px; max-width: 800px; margin: auto; font-family: sans-serif; }
116
- label { display: block; margin-top: 1rem; font-weight: bold;}
117
- input, select, textarea { width: 100%; margin-top: 0.25rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
118
- input[type=checkbox] { width: auto; margin-right: 0.5rem; vertical-align: middle; }
119
- label[for^=input_] { font-weight: normal; display: inline; margin-top: 0;} /* Style for checkbox labels */
120
- button[type=submit] { margin-top: 1.5rem; padding: 0.75rem 1.5rem; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem;}
121
- button[type=submit]:hover { background-color: #0056b3; }
122
- #result-area { margin-top: 2rem; background-color: #f8f9fa; padding: 15px; border: 1px solid #dee2e6; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; font-family: monospace; }
123
- .htmx-indicator { display: none; margin-left: 10px; font-style: italic; color: #6c757d; }
124
- .htmx-request .htmx-indicator { display: inline; }
125
- .htmx-request.htmx-indicator { display: inline; }
126
- .error-message { color: #721c24; margin-top: 10px; font-weight: bold; background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 10px; border-radius: 5px;}
127
- """),
128
- )
129
- )
130
-
131
- @fh_rt("/get-agent-inputs")
132
- def get_agent_inputs(request: Request):
133
- """Endpoint called by HTMX to get agent input fields."""
134
- agent_name = request.query_params.get("agent_name")
135
- logger.debug(f"UI requesting inputs for agent: {agent_name}")
136
- if not agent_name:
137
- return Div("Please select an agent.", cls="error-message")
138
-
139
- # Access agents via the passed FlockAPI instance
140
- agent_def = flock_instance.agents.get(agent_name)
141
- if not agent_def:
142
- logger.warning(f"Agent '{agent_name}' not found for UI.")
143
- return Div(f"Agent '{agent_name}' not found.", cls="error-message")
144
-
145
- # Use the parsing function from the FlockAPI instance
146
- input_fields = parse_input_spec_func(agent_def.input or "")
147
- logger.debug(f"Parsed input fields for {agent_name}: {input_fields}")
148
-
149
- inputs_html = []
150
- for field in input_fields:
151
- field_id = f"input_{field['name']}"
152
- label_text = f"{field['name']}"
153
- if field["type"] != "bool":
154
- label_text += f" ({field['type']})"
155
- label = Label(label_text, fr=field_id)
156
- input_attrs = dict(
157
- id=field_id,
158
- name=f"inputs.{field['name']}",
159
- type=field["html_type"],
160
- )
161
- if field.get("step"):
162
- input_attrs["step"] = field["step"]
163
- if field.get("desc"):
164
- input_attrs["placeholder"] = field["desc"]
165
- if field.get("rows"):
166
- input_attrs["rows"] = field["rows"]
167
-
168
- if field["html_type"] == "textarea":
169
- input_el = Textarea(**input_attrs)
170
- elif field["html_type"] == "checkbox":
171
- input_el = Div(
172
- Input(**input_attrs, value="true"),
173
- Label(f" Enable?", fr=field_id),
174
- )
175
- else:
176
- input_el = Input(**input_attrs)
177
-
178
- inputs_html.append(
179
- Div(label, input_el, style="margin-bottom: 1rem;")
180
- )
181
-
182
- inputs_html.append(
183
- Hidden(
184
- id="selected_agent_name", name="agent_name", value=agent_name
185
- )
186
- )
187
- return (
188
- Div(*inputs_html)
189
- if inputs_html
190
- else P("This agent requires no input.")
191
- )
192
-
193
- @fh_rt("/")
194
- async def ui_root(request: Request):
195
- """Serves the main UI page."""
196
- logger.info("Serving main UI page /ui/")
197
- agents_list = []
198
- error_msg = None
199
- api_url = f"http://{api_host}:{api_port}/agents"
200
- try:
201
- async with httpx.AsyncClient() as client:
202
- logger.debug(f"UI fetching agents from {api_url}")
203
- response = await client.get(api_url)
204
- response.raise_for_status()
205
- agent_data = response.json()
206
- agents_list = agent_data.get("agents", [])
207
- logger.debug(f"Fetched {len(agents_list)} agents for UI")
208
- except Exception as e:
209
- error_msg = f"UI Error: Could not fetch agent list from API at {api_url}. Details: {e}"
210
- logger.error(error_msg, exc_info=True)
211
-
212
- options = [
213
- Option("-- Select Agent --", value="", selected=True, disabled=True)
214
- ] + [
215
- Option(
216
- f"{agent['name']}: {agent['description']}", value=agent["name"]
217
- )
218
- for agent in agents_list
219
- ]
220
-
221
- # Use FHForm alias here
222
- content = Div(
223
- H2(f"Agent Runner"),
224
- P(
225
- "Select an agent, provide the required inputs, and click 'Run Flock'."
226
- ),
227
- Label("Select Starting Agent:", fr="agent_select"),
228
- Select(
229
- *options,
230
- id="agent_select",
231
- name="agent_name",
232
- hx_get="/ui/get-agent-inputs",
233
- hx_trigger="change",
234
- hx_target="#agent-inputs-container",
235
- hx_indicator="#loading-indicator",
236
- ),
237
- FHForm(
238
- Div(id="agent-inputs-container", style="margin-top: 1rem;"),
239
- Button("Run Flock", type="submit"),
240
- Span(
241
- " Processing...",
242
- id="loading-indicator",
243
- cls="htmx-indicator",
244
- ),
245
- hx_post="/ui/run-agent-form", # Target the dedicated form endpoint
246
- hx_target="#result-area",
247
- hx_swap="innerHTML",
248
- hx_indicator="#loading-indicator",
249
- ),
250
- H2("Result"),
251
- Div(
252
- Pre(
253
- Code(
254
- "Result will appear here...",
255
- id="result-content",
256
- class_="language-json",
257
- )
258
- ),
259
- id="result-area",
260
- style="min-height: 100px;",
261
- ),
262
- )
263
-
264
- if error_msg:
265
- content = Div(
266
- H1("Flock UI - Error"), P(error_msg, cls="error-message")
267
- )
268
-
269
- return Titled(f"{server_name}", content)
270
-
271
- return fh_app
@@ -1,119 +0,0 @@
1
- # src/flock/core/api/ui/utils.py
2
- """Utility functions for the Flock FastHTML UI."""
3
-
4
- import html
5
- from typing import Any
6
-
7
- from flock.core.logging.logging import get_logger
8
- from flock.core.util.input_resolver import (
9
- split_top_level, # Assuming this is the correct location
10
- )
11
-
12
- logger = get_logger("api.ui.utils")
13
-
14
-
15
- def parse_input_spec(input_spec: str) -> list[dict[str, str]]:
16
- """Parses an agent input string into a list of field definitions."""
17
- fields = []
18
- if not input_spec:
19
- return fields
20
- try:
21
- parts = split_top_level(input_spec)
22
- except NameError:
23
- logger.error("split_top_level utility function not found!")
24
- return fields # Or raise?
25
-
26
- for part in parts:
27
- part = part.strip()
28
- if not part:
29
- continue
30
- field_info = {
31
- "name": "",
32
- "type": "str",
33
- "desc": "",
34
- "html_type": "text",
35
- }
36
- name_type_part, *desc_part = part.split("|", 1)
37
- if desc_part:
38
- field_info["desc"] = desc_part[0].strip()
39
- name_part, *type_part = name_type_part.split(":", 1)
40
- field_info["name"] = name_part.strip()
41
- if type_part:
42
- field_info["type"] = type_part[0].strip().lower()
43
-
44
- step = None
45
- field_type_norm = field_info["type"]
46
- if field_type_norm.startswith("int"):
47
- field_info["html_type"] = "number"
48
- elif field_type_norm.startswith("float"):
49
- field_info["html_type"] = "number"
50
- step = "any"
51
- elif field_type_norm.startswith("bool"):
52
- field_info["html_type"] = "checkbox"
53
- elif "list" in field_type_norm or "dict" in field_type_norm:
54
- field_info["html_type"] = "textarea"
55
- field_info["rows"] = 3
56
-
57
- if step:
58
- field_info["step"] = step
59
- if field_info["name"]:
60
- fields.append(field_info)
61
- else:
62
- logger.warning(
63
- f"Could not parse field name from input spec part: '{part}'"
64
- )
65
- return fields
66
-
67
-
68
- def format_result_to_html(
69
- data: Any, level: int = 0, max_level: int = 5, max_str_len: int = 999999
70
- ) -> str:
71
- """Recursively formats a Python object (dict, list, Box, etc.) into an HTML string."""
72
- if hasattr(data, "to_dict") and callable(data.to_dict):
73
- data = data.to_dict()
74
- if level > max_level:
75
- return html.escape(f"[Max recursion depth {max_level} reached]")
76
-
77
- if isinstance(data, dict):
78
- if not data:
79
- return "<i>(empty dictionary)</i>"
80
- table_html = '<table style="width: 100%; border-collapse: collapse; margin-bottom: 10px; border: 1px solid #dee2e6;">'
81
- table_html += '<thead style="background-color: #e9ecef;"><tr><th style="text-align: left; padding: 8px; border-bottom: 2px solid #dee2e6;">Key</th><th style="text-align: left; padding: 8px; border-bottom: 2px solid #dee2e6;">Value</th></tr></thead>'
82
- table_html += "<tbody>"
83
- for key, value in data.items():
84
- escaped_key = html.escape(str(key))
85
- formatted_value = format_result_to_html(
86
- value, level + 1, max_level, max_str_len
87
- ) # Recursive call
88
- table_html += f'<tr><td style="vertical-align: top; padding: 8px; border-top: 1px solid #dee2e6;"><strong>{escaped_key}</strong></td><td style="padding: 8px; border-top: 1px solid #dee2e6;">{formatted_value}</td></tr>'
89
- table_html += "</tbody></table>"
90
- return table_html
91
- elif isinstance(data, (list, tuple)):
92
- if not data:
93
- return "<i>(empty list)</i>"
94
- list_html = '<dl style="margin-left: 20px; padding-left: 0; margin-bottom: 10px;">'
95
- for i, item in enumerate(data):
96
- formatted_item = format_result_to_html(
97
- item, level + 1, max_level, max_str_len
98
- ) # Recursive call
99
- list_html += f'<dt style="font-weight: bold; margin-top: 5px;">Item {i + 1}:</dt><dd style="margin-left: 20px; margin-bottom: 5px;">{formatted_item}</dd>'
100
- list_html += "</dl>"
101
- return list_html
102
- else:
103
- str_value = str(data)
104
- escaped_value = html.escape(str_value)
105
- if len(str_value) > max_str_len:
106
- escaped_value = (
107
- html.escape(str_value[:max_str_len])
108
- + f"... <i style='color: #6c757d;'>({len(str_value) - max_str_len} more chars)</i>"
109
- )
110
-
111
- style = ""
112
- if isinstance(data, bool):
113
- style = "color: #d63384; font-weight: bold;"
114
- elif isinstance(data, (int, float)):
115
- style = "color: #0d6efd;"
116
- elif data is None:
117
- style = "color: #6c757d; font-style: italic;"
118
- escaped_value = "None"
119
- return f'<code style="{style}">{escaped_value}</code>'