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.

Files changed (63) hide show
  1. flock/__init__.py +53 -8
  2. flock/cli/loaded_flock_cli.py +38 -18
  3. flock/core/api/main.py +138 -39
  4. flock/core/flock.py +48 -3
  5. flock/themes/alabaster.toml +43 -43
  6. flock/themes/guezwhoz.toml +43 -43
  7. flock/themes/wildcherry.toml +43 -43
  8. flock/themes/wombat.toml +43 -43
  9. flock/themes/zenburn.toml +43 -43
  10. flock/webapp/__init__.py +1 -0
  11. flock/webapp/app/__init__.py +0 -0
  12. flock/webapp/app/api/__init__.py +0 -0
  13. flock/webapp/app/api/agent_management.py +270 -0
  14. flock/webapp/app/api/execution.py +173 -0
  15. flock/webapp/app/api/flock_management.py +102 -0
  16. flock/webapp/app/api/registry_viewer.py +30 -0
  17. flock/webapp/app/config.py +87 -0
  18. flock/webapp/app/main.py +1074 -0
  19. flock/webapp/app/models_ui.py +7 -0
  20. flock/webapp/app/services/__init__.py +0 -0
  21. flock/webapp/app/services/flock_service.py +291 -0
  22. flock/webapp/app/templates/theme_mapper.html +326 -0
  23. flock/webapp/app/theme_mapper.py +812 -0
  24. flock/webapp/app/utils.py +85 -0
  25. flock/webapp/run.py +132 -0
  26. flock/webapp/static/css/custom.css +612 -0
  27. flock/webapp/templates/base.html +105 -0
  28. flock/webapp/templates/flock_editor.html +17 -0
  29. flock/webapp/templates/index.html +12 -0
  30. flock/webapp/templates/partials/_agent_detail_form.html +98 -0
  31. flock/webapp/templates/partials/_agent_list.html +19 -0
  32. flock/webapp/templates/partials/_agent_manager_view.html +53 -0
  33. flock/webapp/templates/partials/_agent_manager_view_old.html +19 -0
  34. flock/webapp/templates/partials/_agent_tools_checklist.html +14 -0
  35. flock/webapp/templates/partials/_create_flock_form.html +52 -0
  36. flock/webapp/templates/partials/_dashboard_flock_detail.html +18 -0
  37. flock/webapp/templates/partials/_dashboard_flock_file_list.html +17 -0
  38. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +29 -0
  39. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +17 -0
  40. flock/webapp/templates/partials/_dynamic_input_form_content.html +22 -0
  41. flock/webapp/templates/partials/_env_vars_table.html +25 -0
  42. flock/webapp/templates/partials/_execution_form.html +48 -0
  43. flock/webapp/templates/partials/_execution_view_container.html +20 -0
  44. flock/webapp/templates/partials/_flock_file_list.html +24 -0
  45. flock/webapp/templates/partials/_flock_properties_form.html +52 -0
  46. flock/webapp/templates/partials/_flock_upload_form.html +17 -0
  47. flock/webapp/templates/partials/_header_flock_status.html +5 -0
  48. flock/webapp/templates/partials/_load_manager_view.html +50 -0
  49. flock/webapp/templates/partials/_registry_table.html +25 -0
  50. flock/webapp/templates/partials/_registry_viewer_content.html +47 -0
  51. flock/webapp/templates/partials/_results_display.html +35 -0
  52. flock/webapp/templates/partials/_settings_env_content.html +10 -0
  53. flock/webapp/templates/partials/_settings_theme_content.html +15 -0
  54. flock/webapp/templates/partials/_settings_view.html +36 -0
  55. flock/webapp/templates/partials/_sidebar.html +70 -0
  56. flock/webapp/templates/partials/_structured_data_view.html +40 -0
  57. flock/webapp/templates/partials/_theme_preview.html +23 -0
  58. flock/webapp/templates/registry_viewer.html +84 -0
  59. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/METADATA +1 -1
  60. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/RECORD +63 -14
  61. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/WHEEL +0 -0
  62. {flock_core-0.4.0b34.dist-info → flock_core-0.4.0b36.dist-info}/entry_points.txt +0 -0
  63. {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
+