flock-core 0.1.1__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 +4 -0
- flock/agents/__init__.py +3 -0
- flock/agents/batch_agent.py +175 -0
- flock/agents/declarative_agent.py +166 -0
- flock/agents/loop_agent.py +178 -0
- flock/agents/trigger_agent.py +191 -0
- flock/agents/user_agent.py +230 -0
- flock/app/components/__init__.py +14 -0
- flock/app/components/charts/agent_workflow.py +14 -0
- flock/app/components/charts/core_architecture.py +14 -0
- flock/app/components/charts/tool_system.py +14 -0
- flock/app/components/history_grid.py +168 -0
- flock/app/components/history_grid_alt.py +189 -0
- flock/app/components/sidebar.py +19 -0
- flock/app/components/theme.py +9 -0
- flock/app/components/util.py +18 -0
- flock/app/hive_app.py +118 -0
- flock/app/html/d3.html +179 -0
- flock/app/modules/__init__.py +12 -0
- flock/app/modules/about.py +17 -0
- flock/app/modules/agent_detail.py +70 -0
- flock/app/modules/agent_list.py +59 -0
- flock/app/modules/playground.py +322 -0
- flock/app/modules/settings.py +96 -0
- flock/core/__init__.py +7 -0
- flock/core/agent.py +150 -0
- flock/core/agent_registry.py +162 -0
- flock/core/config/declarative_agent_config.py +0 -0
- flock/core/context.py +279 -0
- flock/core/context_vars.py +6 -0
- flock/core/flock.py +208 -0
- flock/core/handoff/handoff_base.py +12 -0
- flock/core/logging/__init__.py +18 -0
- flock/core/logging/error_handler.py +84 -0
- flock/core/logging/formatters.py +122 -0
- flock/core/logging/handlers.py +117 -0
- flock/core/logging/logger.py +107 -0
- flock/core/serializable.py +206 -0
- flock/core/tools/basic_tools.py +98 -0
- flock/workflow/activities.py +115 -0
- flock/workflow/agent_activities.py +26 -0
- flock/workflow/temporal_setup.py +37 -0
- flock/workflow/workflow.py +53 -0
- flock_core-0.1.1.dist-info/METADATA +449 -0
- flock_core-0.1.1.dist-info/RECORD +48 -0
- flock_core-0.1.1.dist-info/WHEEL +4 -0
- flock_core-0.1.1.dist-info/entry_points.txt +2 -0
- flock_core-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Agent History Explorer built with MonsterUI"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from fasthtml.common import *
|
|
7
|
+
from monsterui.all import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_history(page=1, per_page=10, search=None):
|
|
11
|
+
with open("data/history.json") as f:
|
|
12
|
+
data = json.load(f)
|
|
13
|
+
|
|
14
|
+
if search:
|
|
15
|
+
search = search.lower()
|
|
16
|
+
data = [
|
|
17
|
+
d
|
|
18
|
+
for d in data
|
|
19
|
+
if search in d["agent_name"].lower() or search in d["location"].lower() or search in d["date"].lower()
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
total = len(data)
|
|
23
|
+
pages = (total + per_page - 1) // per_page
|
|
24
|
+
start = (page - 1) * per_page
|
|
25
|
+
return {"data": data[start : start + per_page], "total": total, "pages": pages, "page": page}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_date(iso_date):
|
|
29
|
+
return datetime.fromisoformat(iso_date).strftime("%b %d, %Y %H:%M")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def json_preview(value):
|
|
33
|
+
return Pre(json.dumps(value, indent=2), cls="text-xs p-2 bg-muted rounded-md max-h-40 overflow-auto")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def header_render(col):
|
|
37
|
+
return Th(col, cls="p-2 text-left")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cell_render(col, val):
|
|
41
|
+
"""Modified cell renderer that only uses column value"""
|
|
42
|
+
match col:
|
|
43
|
+
case "Agent":
|
|
44
|
+
return Td(cls="p-2 font-medium")(Div(val))
|
|
45
|
+
case "Date":
|
|
46
|
+
return Td(cls="p-2 text-sm")(format_date(val))
|
|
47
|
+
case "Location":
|
|
48
|
+
return Td(cls="p-2 capitalize")(val) # val is already location string
|
|
49
|
+
case "Input" | "Output":
|
|
50
|
+
return Td(cls="p-2")(json_preview(val))
|
|
51
|
+
case "Details":
|
|
52
|
+
# val contains the full record here
|
|
53
|
+
return Td(cls="p-2")(Button("View", cls=ButtonT.primary, uk_toggle=f"target: #details-{val['agent_id']}"))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def details_modal(record):
|
|
57
|
+
return Modal(
|
|
58
|
+
Div(cls="p-6 space-y-4")(
|
|
59
|
+
ModalTitle(f"{record['agent_name']} - {format_date(record['date'])}"),
|
|
60
|
+
Grid(
|
|
61
|
+
Div(cls="space-y-2")(H4("Input Details", cls="text-sm font-medium"), json_preview(record["input"])),
|
|
62
|
+
Div(cls="space-y-2")(H4("Output Details", cls="text-sm font-medium"), json_preview(record["output"])),
|
|
63
|
+
),
|
|
64
|
+
DivRAligned(ModalCloseButton("Close", cls=ButtonT.ghost)),
|
|
65
|
+
),
|
|
66
|
+
id=f"details-{record['agent_id']}",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_history(page: int = 1, per_page: int = 5, search: str = None, reduced=False):
|
|
71
|
+
history = load_history(page, per_page, search)
|
|
72
|
+
|
|
73
|
+
controls = DivFullySpaced(cls="mt-8")(
|
|
74
|
+
Div(cls="flex gap-4 items-center")(
|
|
75
|
+
Input(
|
|
76
|
+
placeholder="Search agents...",
|
|
77
|
+
value=search,
|
|
78
|
+
name="search",
|
|
79
|
+
hx_get="/history",
|
|
80
|
+
hx_trigger="keyup changed delay:500ms",
|
|
81
|
+
hx_target="#history-content",
|
|
82
|
+
hx_include="[name='per_page']",
|
|
83
|
+
cls="w-64",
|
|
84
|
+
),
|
|
85
|
+
Select(
|
|
86
|
+
Option("5", value="5"),
|
|
87
|
+
Option("10", value="10"),
|
|
88
|
+
Option("20", value="20"),
|
|
89
|
+
name="per_page",
|
|
90
|
+
value=str(per_page),
|
|
91
|
+
hx_get="/history",
|
|
92
|
+
hx_trigger="change",
|
|
93
|
+
hx_target="#history-content",
|
|
94
|
+
cls="w-24",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if reduced:
|
|
100
|
+
header_data = ["Agent", "Date", "Details"]
|
|
101
|
+
bodydata = [
|
|
102
|
+
{
|
|
103
|
+
"Agent": d["agent_name"],
|
|
104
|
+
"Date": d["date"],
|
|
105
|
+
"Details": d, # Store full record here
|
|
106
|
+
}
|
|
107
|
+
for d in history["data"]
|
|
108
|
+
]
|
|
109
|
+
else:
|
|
110
|
+
header_data = ["Agent", "Date", "Input", "Output", "Details"]
|
|
111
|
+
bodydata = [
|
|
112
|
+
{
|
|
113
|
+
"Agent": d["agent_name"],
|
|
114
|
+
"Date": d["date"],
|
|
115
|
+
# "Location": d["input"]["location"], # Pre-extract location
|
|
116
|
+
"Input": d["input"],
|
|
117
|
+
"Output": d["output"],
|
|
118
|
+
"Details": d, # Store full record here
|
|
119
|
+
}
|
|
120
|
+
for d in history["data"]
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
table = TableFromDicts(
|
|
124
|
+
header_data=header_data,
|
|
125
|
+
body_data=bodydata,
|
|
126
|
+
body_cell_render=cell_render,
|
|
127
|
+
header_cell_render=header_render,
|
|
128
|
+
cls=TableT.responsive,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
footer = DivFullySpaced(cls="mt-4")(
|
|
132
|
+
Div(
|
|
133
|
+
f"Showing {min(history['total'], per_page)} of {history['total']} records",
|
|
134
|
+
cls="text-sm text-muted-foreground",
|
|
135
|
+
),
|
|
136
|
+
Div(cls="flex items-center gap-4")(
|
|
137
|
+
Button(
|
|
138
|
+
"< Prev",
|
|
139
|
+
hx_get=f"/history?page={history['page'] - 1}",
|
|
140
|
+
hx_target="#history-content",
|
|
141
|
+
disabled=history["page"] == 1,
|
|
142
|
+
cls=ButtonT.primary,
|
|
143
|
+
),
|
|
144
|
+
Span(f"Page {history['page']} of {history['pages']}"),
|
|
145
|
+
Button(
|
|
146
|
+
"Next >",
|
|
147
|
+
hx_get=f"/history?page={history['page'] + 1}",
|
|
148
|
+
hx_target="#history-content",
|
|
149
|
+
disabled=history["page"] == history["pages"],
|
|
150
|
+
cls=ButtonT.primary,
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return Div(id="history-content")(controls, table, footer, *[details_modal(d) for d in history["data"]])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def HistoryGrid(reduced=False):
|
|
159
|
+
if reduced:
|
|
160
|
+
return get_history(reduced)
|
|
161
|
+
|
|
162
|
+
return Div(cls="flex flex-col")(
|
|
163
|
+
Div(cls="px-4 py-2 ")(
|
|
164
|
+
H3("Agent History Explorer"),
|
|
165
|
+
P("Review historical agent executions and their outcomes", cls=TextFont.muted_sm),
|
|
166
|
+
),
|
|
167
|
+
get_history(reduced),
|
|
168
|
+
)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Agent History Explorer built with MonsterUI"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from fasthtml.common import *
|
|
7
|
+
from monsterui.all import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_date(iso_date):
|
|
11
|
+
return datetime.fromisoformat(iso_date).strftime("%b %d, %Y %H:%M")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def json_preview(value):
|
|
15
|
+
return Pre(json.dumps(value, indent=2), cls="text-xs p-2 bg-muted rounded-md max-h-40 overflow-auto")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def header_render(col):
|
|
19
|
+
return Th(col, cls="p-2 text-left")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cell_render(col, val):
|
|
23
|
+
"""Modified cell renderer that only uses column value"""
|
|
24
|
+
match col:
|
|
25
|
+
case "Agent":
|
|
26
|
+
return Td(cls="p-2 font-medium")(Div(val))
|
|
27
|
+
case "Date":
|
|
28
|
+
return Td(cls="p-2 text-sm")(format_date(val))
|
|
29
|
+
case "Location":
|
|
30
|
+
return Td(cls="p-2 capitalize")(val) # val is already location string
|
|
31
|
+
case "Input" | "Output":
|
|
32
|
+
return Td(cls="p-2")(json_preview(val))
|
|
33
|
+
case "Details":
|
|
34
|
+
# val contains the full record here
|
|
35
|
+
return Td(cls="p-2")(Button("View", cls=ButtonT.primary, uk_toggle=f"target: #details-{val['agent_id']}"))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_history(page=1, per_page=10, search=None):
|
|
39
|
+
with open("data/history.json") as f:
|
|
40
|
+
data = json.load(f)
|
|
41
|
+
|
|
42
|
+
# Full-text search across all fields
|
|
43
|
+
if search:
|
|
44
|
+
search = search.lower()
|
|
45
|
+
data = [
|
|
46
|
+
d
|
|
47
|
+
for d in data
|
|
48
|
+
if any(search in str(v).lower() for v in d.values())
|
|
49
|
+
or any(search in str(v).lower() for sub in [d["input"], d["output"]] for v in sub.values())
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
total = len(data)
|
|
53
|
+
pages = (total + per_page - 1) // per_page
|
|
54
|
+
start = (page - 1) * per_page
|
|
55
|
+
return {"data": data[start : start + per_page], "total": total, "pages": pages, "page": page}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def json_tree(value, depth=0):
|
|
59
|
+
if isinstance(value, dict):
|
|
60
|
+
return Div(cls=f"ml-{depth * 2} space-y-1")(
|
|
61
|
+
*[Details(Summary(k, cls="inline-flex items-center"), json_tree(v, depth + 1)) for k, v in value.items()]
|
|
62
|
+
)
|
|
63
|
+
elif isinstance(value, list):
|
|
64
|
+
return Div(cls=f"ml-{depth * 2} space-y-1")(
|
|
65
|
+
*[
|
|
66
|
+
Div(cls="flex items-center gap-2")(Span("•", cls="text-muted-foreground"), json_tree(item, depth + 1))
|
|
67
|
+
for item in value
|
|
68
|
+
]
|
|
69
|
+
)
|
|
70
|
+
return Span(str(value), cls="text-muted-foreground")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def render_cell(col, val):
|
|
74
|
+
match col:
|
|
75
|
+
case "date":
|
|
76
|
+
return datetime.fromisoformat(val).strftime("%b %d, %Y %H:%M")
|
|
77
|
+
case "input" | "output":
|
|
78
|
+
return Div(cls="max-w-[300px] overflow-auto p-2 bg-muted rounded-md")(json_tree(val))
|
|
79
|
+
case "_expand":
|
|
80
|
+
return Button("Expand", cls=ButtonT.primary, uk_toggle=f"target: #details-{val['agent_id']}")
|
|
81
|
+
case _:
|
|
82
|
+
return val if isinstance(val, str) else json.dumps(val, default=str)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def details_modal(record):
|
|
86
|
+
return Modal(
|
|
87
|
+
Div(cls="p-6 space-y-4")(
|
|
88
|
+
ModalTitle(f"Agent Execution Details - {record['agent_id']}"),
|
|
89
|
+
Div(cls="space-y-4")(
|
|
90
|
+
Div(cls="grid grid-cols-2 gap-4")(
|
|
91
|
+
Div(cls="space-y-2")(H4("Input", cls="text-sm font-medium"), json_tree(record["input"])),
|
|
92
|
+
Div(cls="space-y-2")(H4("Output", cls="text-sm font-medium"), json_tree(record["output"])),
|
|
93
|
+
),
|
|
94
|
+
Div(cls="space-y-2")(
|
|
95
|
+
H4("Metadata", cls="text-sm font-medium"),
|
|
96
|
+
json_tree({k: v for k, v in record.items() if k not in ["input", "output"]}),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
DivRAligned(ModalCloseButton("Close", cls=ButtonT.ghost)),
|
|
100
|
+
),
|
|
101
|
+
id=f"details-{record['agent_id']}",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def Pagination(current_page, total_pages, hx_get, hx_target):
|
|
106
|
+
return Div(cls="flex items-center gap-2")(
|
|
107
|
+
Button(
|
|
108
|
+
"Previous",
|
|
109
|
+
disabled=current_page == 1,
|
|
110
|
+
hx_get=f"{hx_get}?page={current_page - 1}",
|
|
111
|
+
hx_target=hx_target,
|
|
112
|
+
cls=ButtonT.primary,
|
|
113
|
+
),
|
|
114
|
+
Span(f"Page {current_page} of {total_pages}", cls="text-sm"),
|
|
115
|
+
Button(
|
|
116
|
+
"Next",
|
|
117
|
+
disabled=current_page >= total_pages,
|
|
118
|
+
hx_get=f"{hx_get}?page={current_page + 1}",
|
|
119
|
+
hx_target=hx_target,
|
|
120
|
+
cls=ButtonT.primary,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_history(page: int = 1, per_page: int = 10, search: str = None):
|
|
126
|
+
history = load_history(page, per_page, search)
|
|
127
|
+
|
|
128
|
+
# Dynamic columns from first record (if exists)
|
|
129
|
+
columns = []
|
|
130
|
+
if history["data"]:
|
|
131
|
+
sample = history["data"][0]
|
|
132
|
+
columns = [k for k in sample.keys() if k not in ["input", "output", "agent_id"]]
|
|
133
|
+
columns += ["input", "output", "_expand"]
|
|
134
|
+
|
|
135
|
+
controls = DivFullySpaced(cls="mt-8")(
|
|
136
|
+
Input(
|
|
137
|
+
placeholder="Search history...",
|
|
138
|
+
value=search,
|
|
139
|
+
name="search",
|
|
140
|
+
hx_get="/history",
|
|
141
|
+
hx_trigger="keyup changed delay:500ms",
|
|
142
|
+
hx_target="#history-content",
|
|
143
|
+
hx_include="[name='per_page']",
|
|
144
|
+
cls="w-64",
|
|
145
|
+
),
|
|
146
|
+
Select(
|
|
147
|
+
*[Option(str(n), value=str(n)) for n in [5, 10, 20, 50]],
|
|
148
|
+
name="per_page",
|
|
149
|
+
value=str(per_page),
|
|
150
|
+
hx_get="/history",
|
|
151
|
+
hx_trigger="change",
|
|
152
|
+
hx_target="#history-content",
|
|
153
|
+
cls="w-24",
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
table = TableFromDicts(
|
|
158
|
+
header_data=columns,
|
|
159
|
+
body_data=[{**d, "_expand": d} for d in history["data"]],
|
|
160
|
+
body_cell_render=lambda col, val: Td(render_cell(col, val)),
|
|
161
|
+
header_cell_render=lambda col: Th(col.replace("_", " ").title(), cls="p-2 text-left"),
|
|
162
|
+
cls=f"{TableT.responsive} {TableT.hover}",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
footer = DivFullySpaced(cls="mt-4")(
|
|
166
|
+
Div(
|
|
167
|
+
f"Showing {(page - 1) * per_page + 1}-{min(page * per_page, history['total'])} of {history['total']} records",
|
|
168
|
+
cls="text-sm text-muted-foreground",
|
|
169
|
+
),
|
|
170
|
+
Pagination(current_page=page, total_pages=history["pages"], hx_get="/history", hx_target="#history-content"),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return Div(id="history-content")(
|
|
174
|
+
controls,
|
|
175
|
+
table if history["data"] else P("No records found", cls=TextFont.muted),
|
|
176
|
+
footer,
|
|
177
|
+
*[details_modal(d) for d in history["data"]],
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def HistoryGrid(reduced=False):
|
|
182
|
+
return Container(
|
|
183
|
+
Div(cls="space-y-4")(
|
|
184
|
+
H1("Agent History Explorer"),
|
|
185
|
+
P("Inspect agent executions with dynamic JSON exploration", cls=TextFont.muted_sm),
|
|
186
|
+
),
|
|
187
|
+
get_history(),
|
|
188
|
+
cls="p-8",
|
|
189
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from fasthtml.common import *
|
|
2
|
+
from fasthtml.svg import *
|
|
3
|
+
from monsterui.all import *
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def SidebarLi(icon, title=None, href=None):
|
|
7
|
+
if icon == "---":
|
|
8
|
+
return Li(Hr())
|
|
9
|
+
return Li(
|
|
10
|
+
AX(DivLAligned(Span(UkIcon(icon)), Span(title)), hx_get=href, hx_target="#main-grid", hx_swap="outerHTML")
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def Sidebar(sidebar):
|
|
15
|
+
return NavContainer(
|
|
16
|
+
NavHeaderLi(H1("Flock UI", cls="text-xxl font-semibold")),
|
|
17
|
+
*[SidebarLi(icon, title, href) for icon, title, href in sidebar],
|
|
18
|
+
cls="space-y-6 mt-3",
|
|
19
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from fasthtml.common import *
|
|
4
|
+
from fasthtml.svg import *
|
|
5
|
+
from monsterui.all import *
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_date(date_str):
|
|
9
|
+
date_obj = datetime.fromisoformat(date_str)
|
|
10
|
+
return date_obj.strftime("%Y-%m-%d %I:%M %p")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def IconNavItem(*d):
|
|
14
|
+
return [Li(A(UkIcon(o[0], uk_tooltip=o[1]))) for o in d]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def IconNav(*c, cls=""):
|
|
18
|
+
return Ul(cls=f"uk-iconnav {cls}")(*c)
|
flock/app/hive_app.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from fasthtml.common import *
|
|
2
|
+
from fasthtml.svg import *
|
|
3
|
+
from monsterui.all import *
|
|
4
|
+
|
|
5
|
+
from flock.app.components import HistoryGrid, Sidebar
|
|
6
|
+
from flock.app.modules import AgentContent, AgentDetailView, Playground, Settings, agent_data
|
|
7
|
+
from flock.app.modules.about import AboutPage
|
|
8
|
+
|
|
9
|
+
hdrs = Theme.blue.headers()
|
|
10
|
+
|
|
11
|
+
app, rt = fast_app(hdrs=hdrs, live=True)
|
|
12
|
+
|
|
13
|
+
##############################
|
|
14
|
+
# Sidebar
|
|
15
|
+
##############################
|
|
16
|
+
|
|
17
|
+
sidebar = (
|
|
18
|
+
("layout-dashboard", "Dashboard", "/"),
|
|
19
|
+
("---", "", ""),
|
|
20
|
+
("bot", "Agents", "/agents"),
|
|
21
|
+
("server", "Agent Systems", "/systems"),
|
|
22
|
+
("scroll", "History", "/history"),
|
|
23
|
+
("---", "", ""),
|
|
24
|
+
("wrench", "Tools", "/tools"),
|
|
25
|
+
("test-tube", "Playground", "/playground"),
|
|
26
|
+
("---", "", ""),
|
|
27
|
+
("settings", "Settings", "/settings"),
|
|
28
|
+
("info", "About", "/about"),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
##############################
|
|
33
|
+
# MAIN
|
|
34
|
+
##############################
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@rt
|
|
38
|
+
def index():
|
|
39
|
+
return (
|
|
40
|
+
Title("Flock UI"),
|
|
41
|
+
Container(
|
|
42
|
+
Grid(
|
|
43
|
+
Div(Sidebar(sidebar), cls="col-span-1 w-48 flex"),
|
|
44
|
+
Grid(
|
|
45
|
+
Div(AgentContent(), cls="col-span-2"),
|
|
46
|
+
Div(AgentDetailView(agent_data[0]), cls="col-span-3"),
|
|
47
|
+
cols=5,
|
|
48
|
+
cls="col-span-5",
|
|
49
|
+
id="main-grid",
|
|
50
|
+
),
|
|
51
|
+
cols_sm=2,
|
|
52
|
+
cols_md=2,
|
|
53
|
+
cols_lg=6,
|
|
54
|
+
cols_xl=6,
|
|
55
|
+
gap=5,
|
|
56
|
+
cls="flex-1",
|
|
57
|
+
),
|
|
58
|
+
cls=("flex", ContainerT.xl),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@rt("/history")
|
|
64
|
+
def get():
|
|
65
|
+
return Grid(
|
|
66
|
+
Div(HistoryGrid(), cls="col-span-5"),
|
|
67
|
+
cols=5,
|
|
68
|
+
cls="col-span-5",
|
|
69
|
+
id="main-grid",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@rt("/playground")
|
|
74
|
+
def get():
|
|
75
|
+
return Grid(
|
|
76
|
+
Div(Playground(), cls="col-span-5"), # noqa: F405
|
|
77
|
+
cols=5,
|
|
78
|
+
cls="col-span-5",
|
|
79
|
+
id="main-grid",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@rt("/settings")
|
|
84
|
+
def get():
|
|
85
|
+
return Grid(
|
|
86
|
+
Div(Settings(), cls="col-span-5"),
|
|
87
|
+
cols=5,
|
|
88
|
+
cls="col-span-5",
|
|
89
|
+
id="main-grid",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@rt("/agents")
|
|
94
|
+
def get():
|
|
95
|
+
return Grid(
|
|
96
|
+
Div(AgentContent(), cls="col-span-2"),
|
|
97
|
+
Div(AgentDetailView(agent=agent_data[0]), cls="col-span-3"),
|
|
98
|
+
cols=5,
|
|
99
|
+
cls="col-span-5",
|
|
100
|
+
id="main-grid",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@rt("/about")
|
|
105
|
+
def get():
|
|
106
|
+
return Grid(
|
|
107
|
+
Div(AboutPage(), cls="col-span-5"),
|
|
108
|
+
cols=5,
|
|
109
|
+
cls="col-span-5",
|
|
110
|
+
id="main-grid",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
serve()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
serve()
|
flock/app/html/d3.html
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<title>D3.js Node Graph</title>
|
|
6
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
|
|
7
|
+
<style>
|
|
8
|
+
.node {
|
|
9
|
+
stroke: #fff;
|
|
10
|
+
stroke-width: 1.5px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.link {
|
|
14
|
+
stroke: #999;
|
|
15
|
+
stroke-opacity: 0.6;
|
|
16
|
+
stroke-width: 1px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.node text {
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
font: 10px sans-serif;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
|
|
26
|
+
<body>
|
|
27
|
+
<div>Double-click background to add node. Drag nodes to move them.</div>
|
|
28
|
+
<svg width="800" height="600"></svg>
|
|
29
|
+
<script>
|
|
30
|
+
// Initial data
|
|
31
|
+
let data = {
|
|
32
|
+
nodes: [
|
|
33
|
+
{ id: 1, name: "Node 1" },
|
|
34
|
+
{ id: 2, name: "Node 2" },
|
|
35
|
+
{ id: 3, name: "Node 3" }
|
|
36
|
+
],
|
|
37
|
+
links: [
|
|
38
|
+
{ source: 0, target: 1 },
|
|
39
|
+
{ source: 1, target: 2 }
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create SVG container
|
|
44
|
+
const svg = d3.select("svg");
|
|
45
|
+
const width = +svg.attr("width");
|
|
46
|
+
const height = +svg.attr("height");
|
|
47
|
+
|
|
48
|
+
// Create force simulation
|
|
49
|
+
const simulation = d3.forceSimulation(data.nodes)
|
|
50
|
+
.force("link", d3.forceLink(data.links).id(d => d.id))
|
|
51
|
+
.force("charge", d3.forceManyBody().strength(-300))
|
|
52
|
+
.force("center", d3.forceCenter(width / 2, height / 2));
|
|
53
|
+
|
|
54
|
+
// Create the links
|
|
55
|
+
const link = svg.append("g")
|
|
56
|
+
.selectAll("line")
|
|
57
|
+
.data(data.links)
|
|
58
|
+
.join("line")
|
|
59
|
+
.attr("class", "link");
|
|
60
|
+
|
|
61
|
+
// Create the nodes
|
|
62
|
+
const node = svg.append("g")
|
|
63
|
+
.selectAll("g")
|
|
64
|
+
.data(data.nodes)
|
|
65
|
+
.join("g")
|
|
66
|
+
.attr("class", "node");
|
|
67
|
+
|
|
68
|
+
// Add circles to nodes
|
|
69
|
+
node.append("circle")
|
|
70
|
+
.attr("r", 10)
|
|
71
|
+
.style("fill", (d, i) => d3.schemeCategory10[i % 10]);
|
|
72
|
+
|
|
73
|
+
// Add labels to nodes
|
|
74
|
+
node.append("text")
|
|
75
|
+
.attr("dx", 12)
|
|
76
|
+
.attr("dy", ".35em")
|
|
77
|
+
.text(d => d.name);
|
|
78
|
+
|
|
79
|
+
// Add drag behavior
|
|
80
|
+
node.call(d3.drag()
|
|
81
|
+
.on("start", dragstarted)
|
|
82
|
+
.on("drag", dragged)
|
|
83
|
+
.on("end", dragended));
|
|
84
|
+
|
|
85
|
+
// Double click on background to add node
|
|
86
|
+
svg.on("dblclick", function (event) {
|
|
87
|
+
const coords = d3.pointer(event);
|
|
88
|
+
const newNode = {
|
|
89
|
+
id: data.nodes.length + 1,
|
|
90
|
+
name: `Node ${data.nodes.length + 1}`,
|
|
91
|
+
x: coords[0],
|
|
92
|
+
y: coords[1]
|
|
93
|
+
};
|
|
94
|
+
data.nodes.push(newNode);
|
|
95
|
+
|
|
96
|
+
// Add link to nearest node
|
|
97
|
+
if (data.nodes.length > 1) {
|
|
98
|
+
const lastNode = data.nodes[data.nodes.length - 2];
|
|
99
|
+
data.links.push({
|
|
100
|
+
source: lastNode,
|
|
101
|
+
target: newNode
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
updateGraph();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Update function to refresh the graph
|
|
109
|
+
function updateGraph() {
|
|
110
|
+
// Update links
|
|
111
|
+
const link = svg.selectAll(".link")
|
|
112
|
+
.data(data.links)
|
|
113
|
+
.join("line")
|
|
114
|
+
.attr("class", "link");
|
|
115
|
+
|
|
116
|
+
// Update nodes
|
|
117
|
+
const node = svg.selectAll(".node")
|
|
118
|
+
.data(data.nodes)
|
|
119
|
+
.join(
|
|
120
|
+
enter => {
|
|
121
|
+
const nodeEnter = enter.append("g")
|
|
122
|
+
.attr("class", "node")
|
|
123
|
+
.call(d3.drag()
|
|
124
|
+
.on("start", dragstarted)
|
|
125
|
+
.on("drag", dragged)
|
|
126
|
+
.on("end", dragended));
|
|
127
|
+
|
|
128
|
+
nodeEnter.append("circle")
|
|
129
|
+
.attr("r", 10)
|
|
130
|
+
.style("fill", (d, i) => d3.schemeCategory10[i % 10]);
|
|
131
|
+
|
|
132
|
+
nodeEnter.append("text")
|
|
133
|
+
.attr("dx", 12)
|
|
134
|
+
.attr("dy", ".35em")
|
|
135
|
+
.text(d => d.name);
|
|
136
|
+
|
|
137
|
+
return nodeEnter;
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Update simulation
|
|
142
|
+
simulation.nodes(data.nodes);
|
|
143
|
+
simulation.force("link").links(data.links);
|
|
144
|
+
simulation.alpha(1).restart();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Simulation tick function
|
|
148
|
+
simulation.on("tick", () => {
|
|
149
|
+
link
|
|
150
|
+
.attr("x1", d => d.source.x)
|
|
151
|
+
.attr("y1", d => d.source.y)
|
|
152
|
+
.attr("x2", d => d.target.x)
|
|
153
|
+
.attr("y2", d => d.target.y);
|
|
154
|
+
|
|
155
|
+
node
|
|
156
|
+
.attr("transform", d => `translate(${d.x},${d.y})`);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Drag functions
|
|
160
|
+
function dragstarted(event, d) {
|
|
161
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
162
|
+
d.fx = d.x;
|
|
163
|
+
d.fy = d.y;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dragged(event, d) {
|
|
167
|
+
d.fx = event.x;
|
|
168
|
+
d.fy = event.y;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function dragended(event, d) {
|
|
172
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
173
|
+
d.fx = null;
|
|
174
|
+
d.fy = null;
|
|
175
|
+
}
|
|
176
|
+
</script>
|
|
177
|
+
</body>
|
|
178
|
+
|
|
179
|
+
</html>
|