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.
- flock/__init__.py +53 -8
- flock/cli/loaded_flock_cli.py +38 -18
- flock/core/api/main.py +138 -39
- flock/core/flock.py +48 -3
- flock/themes/alabaster.toml +43 -43
- flock/themes/guezwhoz.toml +43 -43
- flock/themes/wildcherry.toml +43 -43
- flock/themes/wombat.toml +43 -43
- flock/themes/zenburn.toml +43 -43
- 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 +87 -0
- flock/webapp/app/main.py +1074 -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/templates/theme_mapper.html +326 -0
- flock/webapp/app/theme_mapper.py +812 -0
- flock/webapp/app/utils.py +85 -0
- flock/webapp/run.py +132 -0
- flock/webapp/static/css/custom.css +612 -0
- flock/webapp/templates/base.html +105 -0
- flock/webapp/templates/flock_editor.html +17 -0
- flock/webapp/templates/index.html +12 -0
- flock/webapp/templates/partials/_agent_detail_form.html +98 -0
- flock/webapp/templates/partials/_agent_list.html +19 -0
- flock/webapp/templates/partials/_agent_manager_view.html +53 -0
- flock/webapp/templates/partials/_agent_manager_view_old.html +19 -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/_env_vars_table.html +25 -0
- flock/webapp/templates/partials/_execution_form.html +48 -0
- flock/webapp/templates/partials/_execution_view_container.html +20 -0
- flock/webapp/templates/partials/_flock_file_list.html +24 -0
- flock/webapp/templates/partials/_flock_properties_form.html +52 -0
- flock/webapp/templates/partials/_flock_upload_form.html +17 -0
- flock/webapp/templates/partials/_header_flock_status.html +5 -0
- flock/webapp/templates/partials/_load_manager_view.html +50 -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/_settings_env_content.html +10 -0
- flock/webapp/templates/partials/_settings_theme_content.html +15 -0
- flock/webapp/templates/partials/_settings_view.html +36 -0
- flock/webapp/templates/partials/_sidebar.html +70 -0
- flock/webapp/templates/partials/_structured_data_view.html +40 -0
- flock/webapp/templates/partials/_theme_preview.html +23 -0
- flock/webapp/templates/registry_viewer.html +84 -0
- {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/METADATA +1 -1
- {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/RECORD +63 -14
- {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Pydantic models specific to UI interactions, if needed.
|
|
2
|
+
# For MVP, we might not need many here, as we'll primarily pass basic dicts to flock_service.
|
|
3
|
+
# Example:
|
|
4
|
+
# from pydantic import BaseModel
|
|
5
|
+
# class SaveFlockRequest(BaseModel):
|
|
6
|
+
# current_flock_json: str # Or a more structured model if preferred
|
|
7
|
+
# new_filename: str
|
|
File without changes
|
|
@@ -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,326 @@
|
|
|
1
|
+
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en" data-theme="light">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Theme Mapper</title>
|
|
8
|
+
<!-- Use Pico CSS only -->
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
|
|
10
|
+
<style>
|
|
11
|
+
{{ css_vars | safe }}
|
|
12
|
+
|
|
13
|
+
/* Only use Pico CSS classes and variables */
|
|
14
|
+
|
|
15
|
+
.color-sample {
|
|
16
|
+
height: 20px;
|
|
17
|
+
width: 100%;
|
|
18
|
+
border-radius: 4px;
|
|
19
|
+
margin-bottom: 5px;
|
|
20
|
+
}
|
|
21
|
+
.theme-selector {
|
|
22
|
+
position: fixed;
|
|
23
|
+
top: 10px;
|
|
24
|
+
right: 10px;
|
|
25
|
+
z-index: 100;
|
|
26
|
+
background-color: var(--pico-card-background-color);
|
|
27
|
+
padding: 10px;
|
|
28
|
+
border-radius: 8px;
|
|
29
|
+
border: 1px solid var(--pico-border-color);
|
|
30
|
+
}
|
|
31
|
+
article {
|
|
32
|
+
margin-bottom: 1rem;
|
|
33
|
+
}
|
|
34
|
+
.contrast-table td {
|
|
35
|
+
padding: 5px;
|
|
36
|
+
text-align: center;
|
|
37
|
+
}
|
|
38
|
+
.good-contrast {
|
|
39
|
+
background-color: var(--pico-ins-color);
|
|
40
|
+
color: var(--pico-background-color);
|
|
41
|
+
}
|
|
42
|
+
.bad-contrast {
|
|
43
|
+
background-color: var(--pico-del-color);
|
|
44
|
+
color: var(--pico-background-color);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* New: give demo content cards a themed background so text always contrasts */
|
|
48
|
+
article {
|
|
49
|
+
background-color: var(--pico-card-sectioning-background-color);
|
|
50
|
+
border: 1px solid var(--pico-card-border-color);
|
|
51
|
+
padding: 1rem;
|
|
52
|
+
border-radius: 8px;
|
|
53
|
+
}
|
|
54
|
+
/* New: background for the two grid columns */
|
|
55
|
+
.grid > div {
|
|
56
|
+
background-color: var(--pico-card-background-color);
|
|
57
|
+
padding: 1rem;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Override any non-pico CSS variables */
|
|
62
|
+
body {
|
|
63
|
+
background-color: var(--pico-background-color);
|
|
64
|
+
color: var(--pico-color);
|
|
65
|
+
}
|
|
66
|
+
a {
|
|
67
|
+
color: var(--pico-primary);
|
|
68
|
+
}
|
|
69
|
+
a:hover {
|
|
70
|
+
color: var(--pico-primary-hover);
|
|
71
|
+
}
|
|
72
|
+
h1 {
|
|
73
|
+
color: var(--pico-h1-color);
|
|
74
|
+
}
|
|
75
|
+
h2 {
|
|
76
|
+
color: var(--pico-h2-color);
|
|
77
|
+
}
|
|
78
|
+
h3 {
|
|
79
|
+
color: var(--pico-h3-color);
|
|
80
|
+
}
|
|
81
|
+
mark {
|
|
82
|
+
background-color: var(--pico-mark-background-color);
|
|
83
|
+
color: var(--pico-mark-color);
|
|
84
|
+
}
|
|
85
|
+
ins {
|
|
86
|
+
color: var(--pico-ins-color);
|
|
87
|
+
}
|
|
88
|
+
del {
|
|
89
|
+
color: var(--pico-del-color);
|
|
90
|
+
}
|
|
91
|
+
code {
|
|
92
|
+
background-color: var(--pico-code-background-color);
|
|
93
|
+
color: var(--pico-code-color);
|
|
94
|
+
}
|
|
95
|
+
button, input[type="submit"], input[type="button"] {
|
|
96
|
+
background-color: var(--pico-button-base-background-color);
|
|
97
|
+
color: var(--pico-button-base-color);
|
|
98
|
+
border-color: var(--pico-button-base-background-color);
|
|
99
|
+
}
|
|
100
|
+
button:hover, input[type="submit"]:hover, input[type="button"]:hover {
|
|
101
|
+
background-color: var(--pico-button-hover-background-color);
|
|
102
|
+
color: var(--pico-button-hover-color);
|
|
103
|
+
border-color: var(--pico-button-hover-background-color);
|
|
104
|
+
}
|
|
105
|
+
button.secondary, input[type="submit"].secondary, input[type="button"].secondary {
|
|
106
|
+
background-color: var(--pico-secondary);
|
|
107
|
+
color: var(--pico-secondary-inverse);
|
|
108
|
+
border-color: var(--pico-secondary);
|
|
109
|
+
}
|
|
110
|
+
button.secondary:hover, input[type="submit"].secondary:hover, input[type="button"].secondary:hover {
|
|
111
|
+
background-color: var(--pico-secondary-hover);
|
|
112
|
+
color: var(--pico-secondary-inverse);
|
|
113
|
+
border-color: var(--pico-secondary-hover);
|
|
114
|
+
}
|
|
115
|
+
button.contrast, input[type="submit"].contrast, input[type="button"].contrast {
|
|
116
|
+
background-color: var(--pico-contrast);
|
|
117
|
+
color: var(--pico-contrast-inverse);
|
|
118
|
+
border-color: var(--pico-contrast);
|
|
119
|
+
}
|
|
120
|
+
/* Improve grid columns on wider screens */
|
|
121
|
+
@media (min-width: 768px) {
|
|
122
|
+
.grid {
|
|
123
|
+
display: grid;
|
|
124
|
+
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
|
|
125
|
+
gap: 2rem;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/* Ensure container can grow a little wider than Pico default */
|
|
129
|
+
.container {
|
|
130
|
+
max-width: 90rem; /* ~1440px */
|
|
131
|
+
}
|
|
132
|
+
/* Ensure tables use full-strength text colour */
|
|
133
|
+
table th,
|
|
134
|
+
table td {
|
|
135
|
+
color: var(--pico-color);
|
|
136
|
+
opacity: 1; /* override Pico's default fade */
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
139
|
+
</head>
|
|
140
|
+
<body>
|
|
141
|
+
<div class="container">
|
|
142
|
+
<div class="theme-selector">
|
|
143
|
+
<label for="theme-select">Select Theme:</label>
|
|
144
|
+
<select id="theme-select" onchange="window.location.href='/?theme=' + this.value">
|
|
145
|
+
{% for theme_name in themes %}
|
|
146
|
+
<option value="{{ theme_name }}" {% if theme_name == current_theme %}selected{% endif %}>{{ theme_name }}</option>
|
|
147
|
+
{% endfor %}
|
|
148
|
+
</select>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<h1>Theme Mapper: {{ current_theme }}</h1>
|
|
152
|
+
|
|
153
|
+
<div class="grid">
|
|
154
|
+
<div>
|
|
155
|
+
<h2>UI Elements</h2>
|
|
156
|
+
<article>
|
|
157
|
+
<h3>Headings and Text</h3>
|
|
158
|
+
<h1>Heading 1</h1>
|
|
159
|
+
<h2>Heading 2</h2>
|
|
160
|
+
<h3>Heading 3</h3>
|
|
161
|
+
<p>Normal paragraph text. <a href="#">This is a link</a>. <mark>This is marked text</mark>.</p>
|
|
162
|
+
<p><small>This is small text</small></p>
|
|
163
|
+
<p><ins>This is inserted text</ins> and <del>this is deleted text</del>.</p>
|
|
164
|
+
<blockquote>
|
|
165
|
+
This is a blockquote with <cite>a citation</cite>.
|
|
166
|
+
</blockquote>
|
|
167
|
+
<code>This is inline code</code>
|
|
168
|
+
<pre><code>// This is a code block
|
|
169
|
+
function example() {
|
|
170
|
+
return "Hello World";
|
|
171
|
+
}</code></pre>
|
|
172
|
+
</article>
|
|
173
|
+
|
|
174
|
+
<article>
|
|
175
|
+
<h3>Buttons</h3>
|
|
176
|
+
<button>Default Button</button>
|
|
177
|
+
<button class="secondary">Secondary Button</button>
|
|
178
|
+
<button class="contrast">Contrast Button</button>
|
|
179
|
+
</article>
|
|
180
|
+
|
|
181
|
+
<article>
|
|
182
|
+
<h3>Form Elements</h3>
|
|
183
|
+
<form>
|
|
184
|
+
<label for="text">Text Input</label>
|
|
185
|
+
<input type="text" id="text" placeholder="Text input">
|
|
186
|
+
|
|
187
|
+
<label for="select">Select</label>
|
|
188
|
+
<select id="select">
|
|
189
|
+
<option>Option 1</option>
|
|
190
|
+
<option>Option 2</option>
|
|
191
|
+
</select>
|
|
192
|
+
|
|
193
|
+
<label for="textarea">Textarea</label>
|
|
194
|
+
<textarea id="textarea" placeholder="Textarea"></textarea>
|
|
195
|
+
|
|
196
|
+
<label for="invalid" aria-invalid="true">Invalid Input</label>
|
|
197
|
+
<input type="text" id="invalid" aria-invalid="true" placeholder="Invalid input">
|
|
198
|
+
|
|
199
|
+
<fieldset>
|
|
200
|
+
<legend>Checkboxes</legend>
|
|
201
|
+
<label>
|
|
202
|
+
<input type="checkbox" checked>
|
|
203
|
+
Checkbox 1
|
|
204
|
+
</label>
|
|
205
|
+
<label>
|
|
206
|
+
<input type="checkbox">
|
|
207
|
+
Checkbox 2
|
|
208
|
+
</label>
|
|
209
|
+
</fieldset>
|
|
210
|
+
|
|
211
|
+
<fieldset>
|
|
212
|
+
<legend>Radio Buttons</legend>
|
|
213
|
+
<label>
|
|
214
|
+
<input type="radio" name="radio" checked>
|
|
215
|
+
Radio 1
|
|
216
|
+
</label>
|
|
217
|
+
<label>
|
|
218
|
+
<input type="radio" name="radio">
|
|
219
|
+
Radio 2
|
|
220
|
+
</label>
|
|
221
|
+
</fieldset>
|
|
222
|
+
</form>
|
|
223
|
+
</article>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div>
|
|
227
|
+
<h2>Theme Color Mapping</h2>
|
|
228
|
+
<article>
|
|
229
|
+
<h3>Main Colors</h3>
|
|
230
|
+
<div class="grid">
|
|
231
|
+
{% for color_name, color_value in main_colors %}
|
|
232
|
+
<div>
|
|
233
|
+
<div class="color-sample" style="background-color: {{ color_value }};"></div>
|
|
234
|
+
<small>{{ color_name }}<br>{{ color_value }}</small>
|
|
235
|
+
</div>
|
|
236
|
+
{% endfor %}
|
|
237
|
+
</div>
|
|
238
|
+
</article>
|
|
239
|
+
|
|
240
|
+
<article>
|
|
241
|
+
<h3>All Pico CSS Variables</h3>
|
|
242
|
+
<div style="max-height: 300px; overflow-y: auto;">
|
|
243
|
+
<table>
|
|
244
|
+
<thead>
|
|
245
|
+
<tr>
|
|
246
|
+
<th>Variable</th>
|
|
247
|
+
<th>Value</th>
|
|
248
|
+
<th>Sample</th>
|
|
249
|
+
</tr>
|
|
250
|
+
</thead>
|
|
251
|
+
<tbody>
|
|
252
|
+
{% for var_name, var_value in all_vars %}
|
|
253
|
+
<tr>
|
|
254
|
+
<td>{{ var_name }}</td>
|
|
255
|
+
<td>{{ var_value }}</td>
|
|
256
|
+
<td>
|
|
257
|
+
{% if var_value.startswith('#') or var_value.startswith('rgb') %}
|
|
258
|
+
<div class="color-sample" style="background-color: {{ var_value }};"></div>
|
|
259
|
+
{% endif %}
|
|
260
|
+
</td>
|
|
261
|
+
</tr>
|
|
262
|
+
{% endfor %}
|
|
263
|
+
</tbody>
|
|
264
|
+
</table>
|
|
265
|
+
</div>
|
|
266
|
+
</article>
|
|
267
|
+
|
|
268
|
+
<article>
|
|
269
|
+
<h3>Color Contrast Checks</h3>
|
|
270
|
+
<table class="contrast-table">
|
|
271
|
+
<thead>
|
|
272
|
+
<tr>
|
|
273
|
+
<th>Foreground</th>
|
|
274
|
+
<th>Background</th>
|
|
275
|
+
<th>Contrast</th>
|
|
276
|
+
<th>WCAG AA</th>
|
|
277
|
+
</tr>
|
|
278
|
+
</thead>
|
|
279
|
+
<tbody>
|
|
280
|
+
{% for check in contrast_checks %}
|
|
281
|
+
<tr>
|
|
282
|
+
<td>{{ check.fg_name }}</td>
|
|
283
|
+
<td>{{ check.bg_name }}</td>
|
|
284
|
+
<td>{{ check.contrast }}</td>
|
|
285
|
+
<td class="{% if check.passes %}good-contrast{% else %}bad-contrast{% endif %}">
|
|
286
|
+
{{ "Pass" if check.passes else "Fail" }}
|
|
287
|
+
</td>
|
|
288
|
+
</tr>
|
|
289
|
+
{% endfor %}
|
|
290
|
+
</tbody>
|
|
291
|
+
</table>
|
|
292
|
+
</article>
|
|
293
|
+
|
|
294
|
+
<article>
|
|
295
|
+
<h3>Original Theme Colors</h3>
|
|
296
|
+
<div style="max-height: 300px; overflow-y: auto;">
|
|
297
|
+
<table>
|
|
298
|
+
<thead>
|
|
299
|
+
<tr>
|
|
300
|
+
<th>Group</th>
|
|
301
|
+
<th>Name</th>
|
|
302
|
+
<th>Value</th>
|
|
303
|
+
<th>Sample</th>
|
|
304
|
+
</tr>
|
|
305
|
+
</thead>
|
|
306
|
+
<tbody>
|
|
307
|
+
{% for group, names in original_colors.items() %}
|
|
308
|
+
{% for name, value in names.items() %}
|
|
309
|
+
<tr>
|
|
310
|
+
<td>{{ group }}</td>
|
|
311
|
+
<td>{{ name }}</td>
|
|
312
|
+
<td>{{ value }}</td>
|
|
313
|
+
<td><div class="color-sample" style="background-color: {{ value }};"></div></td>
|
|
314
|
+
</tr>
|
|
315
|
+
{% endfor %}
|
|
316
|
+
{% endfor %}
|
|
317
|
+
</tbody>
|
|
318
|
+
</table>
|
|
319
|
+
</div>
|
|
320
|
+
</article>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
326
|
+
|