flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__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 (359) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
@@ -1,812 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
-
5
- ###########################
6
- # Low–level colour utils #
7
- ###########################
8
-
9
- _HEX_RE = re.compile(r"^#?([0-9a-f]{6})$", re.I)
10
-
11
- def _hex_to_rgb(hex_str: str) -> tuple[int, int, int] | None:
12
- if hex_str is None:
13
- return None
14
- # Clean the string - remove any leading/trailing whitespace
15
- hex_str = hex_str.strip()
16
- m = _HEX_RE.match(hex_str)
17
- if not m:
18
- return None
19
- h = m.group(1)
20
- return tuple(int(h[i : i + 2], 16) for i in (0, 2, 4)) # type: ignore[misc]
21
-
22
- def _rgb_to_hex(rgb: tuple[int, int, int]) -> str:
23
- return f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
24
-
25
- def _rel_lum(rgb: tuple[int, int, int]) -> float:
26
- def channel(c: int) -> float:
27
- c = c / 255.0
28
- return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
29
- r, g, b = map(channel, rgb)
30
- return 0.2126 * r + 0.7152 * g + 0.0722 * b
31
-
32
- def _contrast(a: tuple[int, int, int], b: tuple[int, int, int]) -> float:
33
- if a is None or b is None:
34
- return 1.0 # Default value if invalid colors provided
35
- try:
36
- la, lb = _rel_lum(a), _rel_lum(b)
37
- darker, lighter = sorted((la, lb))
38
- return (lighter + 0.05) / (darker + 0.05)
39
- except (TypeError, ValueError, ZeroDivisionError) as e:
40
- print(f"Error calculating contrast: {e}")
41
- return 1.0 # Default value on error
42
-
43
- def _mix(rgb: tuple[int, int, int], other: tuple[int, int, int], pct: float) -> tuple[int, int, int]:
44
- if rgb is None or other is None:
45
- # Return a fallback color if either input is None
46
- return (128, 128, 128) # Default to gray
47
- try:
48
- return tuple(int(rgb[i] * (1 - pct) + other[i] * pct) for i in range(3))
49
- except (TypeError, IndexError) as e:
50
- print(f"Error mixing colors: {e}")
51
- return (128, 128, 128) # Default to gray
52
-
53
- def _rgba(rgb: tuple[int, int, int], alpha: float) -> str:
54
- if rgb is None:
55
- rgb = (128, 128, 128) # Default gray if rgb is None
56
- return f"rgba({rgb[0]}, {rgb[1]}, {rgb[2]}, {alpha})"
57
-
58
- ########################################
59
- # Theme → Pico-CSS conversion engine #
60
- ########################################
61
-
62
- AccentOrder = ["blue", "cyan", "magenta", "green", "yellow", "red"]
63
-
64
- def _theme_color(theme: dict, group: str, name: str) -> tuple[int, int, int] | None:
65
- try:
66
- raw = (
67
- theme.get("colors", {})
68
- .get(group, {},)
69
- .get(name)
70
- )
71
- return _hex_to_rgb(raw) if raw else None
72
- except (TypeError, AttributeError) as e:
73
- print(f"Error extracting color {group}.{name}: {e}")
74
- return None
75
-
76
- def _order_colors(theme: dict, color_to_compare_to: tuple[int, int, int]) -> list[tuple[int, int, int]]:
77
- colors = []
78
-
79
- # Extract all colors from the theme recursively
80
- def extract_colors(data):
81
- if isinstance(data, dict):
82
- for key, value in data.items():
83
- if isinstance(value, str) and value.startswith('#'):
84
- rgb = _hex_to_rgb(value)
85
- if rgb:
86
- colors.append(rgb)
87
- else:
88
- extract_colors(value)
89
- elif isinstance(data, list):
90
- for item in data:
91
- extract_colors(item)
92
-
93
- # Start extraction from the root of the theme
94
- extract_colors(theme)
95
-
96
- # Sort colors by contrast with color_to_compare_to, highest contrast first
97
- colors.sort(key=lambda c: _contrast(c, color_to_compare_to), reverse=True)
98
-
99
- return colors
100
-
101
- def _best_contrast(
102
- candidates: list[tuple[int, int, int]],
103
- bg: tuple[int, int, int],
104
- min_ratio: float = 4.5,
105
- ) -> tuple[int, int, int]:
106
- if not candidates:
107
- # Instead of raising an exception, return a fallback color
108
- # Use white or black depending on the background
109
- return (255, 255, 255) if sum(bg) < 384 else (0, 0, 0)
110
- best = max(candidates, key=lambda c: _contrast(c, bg))
111
- if _contrast(best, bg) >= min_ratio:
112
- return best
113
- return best # return highest available even if below threshold
114
-
115
- def alacritty_to_pico(theme: dict) -> dict[str, str]:
116
- css: dict[str, str] = {}
117
-
118
- # 1 Main surface colours
119
- fixed_background = _theme_color(theme, "primary", "background") or (0, 0, 0)
120
- fixed_foreground = _theme_color(theme, "primary", "foreground") or (255, 255, 255)
121
-
122
- fixed_background2 = _theme_color(theme, "selection", "background") or (0, 0, 0)
123
- # Try theme's own foreground first, then fall back to palette extremes
124
- # fg_candidates = [
125
- # _theme_color(theme, "primary", "foreground"),
126
- # _theme_color(theme, "normal", "white"),
127
- # _theme_color(theme, "bright", "white"),
128
- # _theme_color(theme, "normal", "black"),
129
- # _theme_color(theme, "bright", "black"),
130
- # ]
131
- # fg = _best_contrast([c for c in fg_candidates if c], bg)
132
-
133
- css["--pico-background-color"] = _rgb_to_hex(fixed_background)
134
- css["--pico-color"] = _rgb_to_hex(fixed_foreground)
135
-
136
- # 2 Pick an accent colour that stands out
137
- accent = None
138
- for shade in ("normal", "bright"):
139
- for name in AccentOrder:
140
- c = _theme_color(theme, shade, name)
141
- if c and _contrast(c, fixed_background) >= 3:
142
- accent = c
143
- break
144
- if accent:
145
- break
146
- accent = accent or fixed_foreground # worst case
147
-
148
- accent_hover = _mix(accent, (255, 255, 255), 0.15) # lighten 15 %
149
- accent_active = _mix(accent, (0, 0, 0), 0.15) # darken 15 %
150
- accent_focus = _rgba(accent, 0.25) # 25 % overlay
151
- accent_inv = _best_contrast([fixed_foreground, fixed_background, (255, 255, 255), (0, 0, 0)], accent, 4.5)
152
-
153
- css["--pico-primary"] = _rgb_to_hex(accent)
154
- css["--pico-primary-hover"] = _rgb_to_hex(accent_hover)
155
- css["--pico-primary-active"] = _rgb_to_hex(accent_active)
156
- css["--pico-primary-focus"] = accent_focus
157
- css["--pico-primary-inverse"] = _rgb_to_hex(accent_inv)
158
-
159
- # 3 Secondary accent = next colour in queue with sufficient contrast
160
- sec = None
161
- for name in AccentOrder:
162
- if sec is None and _hex_to_rgb(css["--pico-primary"]) != _theme_color(theme, "normal", name):
163
- for shade in ("normal", "bright"):
164
- c = _theme_color(theme, shade, name)
165
- if c and _contrast(c, fixed_background) >= 3:
166
- sec = c
167
- break
168
- sec = sec or _mix(accent, (255, 255, 255), 0.25)
169
-
170
- css["--pico-secondary"] = _rgb_to_hex(sec)
171
- css["--pico-secondary-hover"] = _rgb_to_hex(_mix(sec, (255, 255, 255), 0.15))
172
- css["--pico-secondary-focus"] = _rgba(sec, 0.25)
173
- css["--pico-secondary-active"] = _rgb_to_hex(sec)
174
- css["--pico-secondary-inverse"] = _rgb_to_hex(_best_contrast([fixed_foreground, fixed_background], sec, 4.5))
175
-
176
- # 4 Headings inherit the accent spectrum
177
- css["--pico-h1-color"] = css["--pico-primary"]
178
- css["--pico-h2-color"] = css["--pico-secondary"]
179
- css["--pico-h3-color"] = css["--pico-color"] # body colour
180
-
181
- # 5 Muted text & borders use the least-contrasty greys we can still read
182
- grey_candidates = [
183
- _theme_color(theme, "bright", "black"),
184
- _theme_color(theme, "normal", "black"),
185
- _theme_color(theme, "bright", "white"),
186
- _theme_color(theme, "normal", "white"),
187
- ]
188
- muted = _best_contrast([c for c in grey_candidates if c], fixed_background, 3)
189
- css["--pico-muted-color"] = _rgb_to_hex(muted)
190
- css["--pico-border-color"] = _rgb_to_hex(_mix(muted, fixed_background, 0.5))
191
- css["--pico-muted-border-color"] = css["--pico-border-color"]
192
-
193
- # 6 Cards, selections, cursor, code — pick safe defaults
194
- css["--pico-card-background-color"] = css["--pico-background-color"]
195
- css["--pico-card-sectioning-background-color"] = _rgb_to_hex(fixed_background2)
196
- css["--pico-card-border-color"] = css["--pico-border-color"]
197
-
198
- sel_bg = _theme_color(theme, "selection", "background") or _mix(fixed_background, fixed_foreground, 0.20)
199
- sel_fg = _best_contrast([fixed_foreground, muted, accent, sec], sel_bg, 4.5)
200
- css["--pico-selection-background-color"] = _rgb_to_hex(sel_bg)
201
- css["--pico-selection-color"] = _rgb_to_hex(sel_fg)
202
-
203
- cur_bg = _theme_color(theme, "cursor", "cursor") or sel_bg
204
-
205
- # Try theme's own foreground first, then fall back to palette extremes
206
- fg_cur_candidates = [
207
- _theme_color(theme, "primary", "foreground"),
208
- _theme_color(theme, "normal", "white"),
209
- _theme_color(theme, "bright", "white"),
210
- _theme_color(theme, "normal", "blue"),
211
- _theme_color(theme, "bright", "blue"),
212
- _theme_color(theme, "normal", "cyan"),
213
- _theme_color(theme, "bright", "cyan"),
214
- _theme_color(theme, "normal", "magenta"),
215
- _theme_color(theme, "bright", "magenta"),
216
- _theme_color(theme, "normal", "green"),
217
- _theme_color(theme, "bright", "green"),
218
- _theme_color(theme, "normal", "yellow"),
219
- _theme_color(theme, "bright", "yellow"),
220
- _theme_color(theme, "normal", "red"),
221
- _theme_color(theme, "bright", "red"),
222
- _theme_color(theme, "normal", "black"),
223
- _theme_color(theme, "bright", "black"),
224
- ]
225
- cur_fg = _best_contrast([c for c in fg_cur_candidates if c], cur_bg)
226
-
227
- css["--pico-code-background-color"] = _rgb_to_hex(cur_bg)
228
- css["--pico-code-color"] = _rgb_to_hex(cur_fg)
229
-
230
- # 7 Form elements and buttons reuse the existing tokens
231
- css["--pico-form-element-background-color"] = css["--pico-background-color"]
232
- css["--pico-form-element-border-color"] = css["--pico-border-color"]
233
- css["--pico-form-element-color"] = css["--pico-color"]
234
- css["--pico-form-element-focus-color"] = css["--pico-primary-hover"]
235
- css["--pico-form-element-placeholder-color"] = css["--pico-muted-color"]
236
- css["--pico-form-element-active-border-color"] = css["--pico-primary"]
237
- css["--pico-form-element-active-background-color"] = css["--pico-selection-background-color"]
238
- css["--pico-form-element-disabled-background-color"] = _rgb_to_hex(_mix(fixed_background, fixed_foreground, 0.1))
239
- css["--pico-form-element-disabled-border-color"] = css["--pico-border-color"]
240
- css["--pico-form-element-invalid-border-color"] = _rgb_to_hex(_theme_color(theme, "normal", "red") or accent_active)
241
- css["--pico-form-element-invalid-focus-color"] = _rgb_to_hex(_theme_color(theme, "bright", "red") or accent_hover)
242
-
243
- # 8 Buttons follow primary palette by default
244
- css["--pico-button-base-background-color"] = css["--pico-primary"]
245
- css["--pico-button-base-color"] = css["--pico-primary-inverse"]
246
- css["--pico-button-hover-background-color"] = css["--pico-primary-hover"]
247
- css["--pico-button-hover-color"] = css["--pico-primary-inverse"]
248
-
249
- # 9 Semantic markup helpers
250
- yellow = _theme_color(theme, "normal", "yellow") or _mix(accent, (255, 255, 0), 0.5)
251
- css["--pico-mark-background-color"] = _rgba(yellow, 0.2)
252
- css["--pico-mark-color"] = css["--pico-color"]
253
- css["--pico-ins-color"] = _rgb_to_hex(_theme_color(theme, "normal", "green") or accent)
254
- css["--pico-del-color"] = _rgb_to_hex(_theme_color(theme, "normal", "red") or accent_active)
255
-
256
- # 10 Contrast helpers
257
- css["--pico-contrast"] = css["--pico-color"]
258
- css["--pico-contrast-inverse"] = css["--pico-primary-inverse"]
259
-
260
- return css
261
-
262
- if __name__ == "__main__":
263
- import pathlib
264
- import sys
265
-
266
- try:
267
- import toml
268
- import uvicorn
269
- from fastapi import FastAPI, Request
270
- from fastapi.responses import HTMLResponse
271
- from fastapi.templating import Jinja2Templates
272
- except ImportError as e:
273
- print(f"Error: Required module not found: {e}")
274
- print("Please install required packages: pip install toml fastapi uvicorn jinja2")
275
- sys.exit(1)
276
-
277
- # Find themes directory
278
- current_dir = pathlib.Path(__file__).parent
279
- themes_dir = current_dir.parent.parent / 'themes'
280
-
281
- # Check if themes directory exists
282
- if not themes_dir.exists():
283
- print(f"Error: Themes directory not found at {themes_dir}")
284
- print(f"Current directory is {current_dir}")
285
- sys.exit(1)
286
-
287
- # Get list of all theme files
288
- theme_files = list(themes_dir.glob('*.toml'))
289
-
290
- if not theme_files:
291
- print(f"Error: No theme files found in {themes_dir}")
292
- sys.exit(1)
293
-
294
- # Make sure alabaster is included
295
- alabaster_path = themes_dir / 'alabaster.toml'
296
- if alabaster_path not in theme_files and alabaster_path.exists():
297
- theme_files.append(alabaster_path)
298
-
299
- # Load a few random themes plus alabaster
300
- selected_themes = theme_files
301
- if alabaster_path.exists() and alabaster_path not in selected_themes:
302
- selected_themes.append(alabaster_path)
303
-
304
- # Dictionary to store theme data
305
- themes = {}
306
-
307
- # Load each theme
308
- for theme_path in selected_themes:
309
- try:
310
- # Read the file as text to handle whitespace issues
311
- with open(theme_path) as f:
312
- content = f.read()
313
-
314
- # Remove leading/trailing whitespace from each line
315
- cleaned_content = '\n'.join(line.rstrip() for line in content.splitlines())
316
-
317
- # Parse using StringIO
318
- from io import StringIO
319
- theme_data = toml.load(StringIO(cleaned_content))
320
- themes[theme_path.stem] = theme_data
321
- except Exception as e:
322
- print(f"Error loading {theme_path.name}: {e}")
323
-
324
- # Check if any themes were successfully loaded
325
- if not themes:
326
- print("Error: No themes could be loaded successfully.")
327
- sys.exit(1)
328
-
329
- # Create a FastAPI application
330
- app = FastAPI(title="Theme Mapper")
331
-
332
- # Create template directory
333
- template_dir = current_dir / "templates"
334
- try:
335
- template_dir.mkdir(exist_ok=True)
336
- except Exception as e:
337
- print(f"Error creating template directory: {e}")
338
- sys.exit(1)
339
-
340
- # Create and write template file
341
- template_path = template_dir / "theme_mapper.html"
342
- template_content = '''
343
- <!DOCTYPE html>
344
- <html lang="en" data-theme="light">
345
- <head>
346
- <meta charset="UTF-8">
347
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
348
- <title>Theme Mapper</title>
349
- <!-- Use Pico CSS only -->
350
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
351
- <style>
352
- {{ css_vars | safe }}
353
-
354
- /* Only use Pico CSS classes and variables */
355
-
356
- .color-sample {
357
- height: 20px;
358
- width: 100%;
359
- border-radius: 4px;
360
- margin-bottom: 5px;
361
- }
362
- .theme-selector {
363
- position: fixed;
364
- top: 10px;
365
- right: 10px;
366
- z-index: 100;
367
- background-color: var(--pico-card-background-color);
368
- padding: 10px;
369
- border-radius: 8px;
370
- border: 1px solid var(--pico-border-color);
371
- }
372
- article {
373
- margin-bottom: 1rem;
374
- }
375
- .contrast-table td {
376
- padding: 5px;
377
- text-align: center;
378
- }
379
- .good-contrast {
380
- background-color: var(--pico-ins-color);
381
- color: var(--pico-background-color);
382
- }
383
- .bad-contrast {
384
- background-color: var(--pico-del-color);
385
- color: var(--pico-background-color);
386
- }
387
-
388
- /* New: give demo content cards a themed background so text always contrasts */
389
- article {
390
- background-color: var(--pico-card-sectioning-background-color);
391
- border: 1px solid var(--pico-card-border-color);
392
- padding: 1rem;
393
- border-radius: 8px;
394
- }
395
- /* New: background for the two grid columns */
396
- .grid > div {
397
- background-color: var(--pico-card-background-color);
398
- padding: 1rem;
399
- border-radius: 8px;
400
- }
401
-
402
- /* Override any non-pico CSS variables */
403
- body {
404
- background-color: var(--pico-background-color);
405
- color: var(--pico-color);
406
- }
407
- a {
408
- color: var(--pico-primary);
409
- }
410
- a:hover {
411
- color: var(--pico-primary-hover);
412
- }
413
- h1 {
414
- color: var(--pico-h1-color);
415
- }
416
- h2 {
417
- color: var(--pico-h2-color);
418
- }
419
- h3 {
420
- color: var(--pico-h3-color);
421
- }
422
- mark {
423
- background-color: var(--pico-mark-background-color);
424
- color: var(--pico-mark-color);
425
- }
426
- ins {
427
- color: var(--pico-ins-color);
428
- }
429
- del {
430
- color: var(--pico-del-color);
431
- }
432
- code {
433
- background-color: var(--pico-code-background-color);
434
- color: var(--pico-code-color);
435
- }
436
- button, input[type="submit"], input[type="button"] {
437
- background-color: var(--pico-button-base-background-color);
438
- color: var(--pico-button-base-color);
439
- border-color: var(--pico-button-base-background-color);
440
- }
441
- button:hover, input[type="submit"]:hover, input[type="button"]:hover {
442
- background-color: var(--pico-button-hover-background-color);
443
- color: var(--pico-button-hover-color);
444
- border-color: var(--pico-button-hover-background-color);
445
- }
446
- button.secondary, input[type="submit"].secondary, input[type="button"].secondary {
447
- background-color: var(--pico-secondary);
448
- color: var(--pico-secondary-inverse);
449
- border-color: var(--pico-secondary);
450
- }
451
- button.secondary:hover, input[type="submit"].secondary:hover, input[type="button"].secondary:hover {
452
- background-color: var(--pico-secondary-hover);
453
- color: var(--pico-secondary-inverse);
454
- border-color: var(--pico-secondary-hover);
455
- }
456
- button.contrast, input[type="submit"].contrast, input[type="button"].contrast {
457
- background-color: var(--pico-contrast);
458
- color: var(--pico-contrast-inverse);
459
- border-color: var(--pico-contrast);
460
- }
461
- /* Improve grid columns on wider screens */
462
- @media (min-width: 768px) {
463
- .grid {
464
- display: grid;
465
- grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
466
- gap: 2rem;
467
- }
468
- }
469
- /* Ensure container can grow a little wider than Pico default */
470
- .container {
471
- max-width: 90rem; /* ~1440px */
472
- }
473
- /* Ensure tables use full-strength text colour */
474
- table th,
475
- table td {
476
- color: var(--pico-color);
477
- opacity: 1; /* override Pico's default fade */
478
- }
479
- </style>
480
- </head>
481
- <body>
482
- <div class="container">
483
- <div class="theme-selector">
484
- <label for="theme-select">Select Theme:</label>
485
- <select id="theme-select" onchange="window.location.href='/?theme=' + this.value">
486
- {% for theme_name in themes %}
487
- <option value="{{ theme_name }}" {% if theme_name == current_theme %}selected{% endif %}>{{ theme_name }}</option>
488
- {% endfor %}
489
- </select>
490
- </div>
491
-
492
- <h1>Theme Mapper: {{ current_theme }}</h1>
493
-
494
- <div class="grid">
495
- <div>
496
- <h2>UI Elements</h2>
497
- <article>
498
- <h3>Headings and Text</h3>
499
- <h1>Heading 1</h1>
500
- <h2>Heading 2</h2>
501
- <h3>Heading 3</h3>
502
- <p>Normal paragraph text. <a href="#">This is a link</a>. <mark>This is marked text</mark>.</p>
503
- <p><small>This is small text</small></p>
504
- <p><ins>This is inserted text</ins> and <del>this is deleted text</del>.</p>
505
- <blockquote>
506
- This is a blockquote with <cite>a citation</cite>.
507
- </blockquote>
508
- <code>This is inline code</code>
509
- <pre><code>// This is a code block
510
- function example() {
511
- return "Hello World";
512
- }</code></pre>
513
- </article>
514
-
515
- <article>
516
- <h3>Buttons</h3>
517
- <button>Default Button</button>
518
- <button class="secondary">Secondary Button</button>
519
- <button class="contrast">Contrast Button</button>
520
- </article>
521
-
522
- <article>
523
- <h3>Form Elements</h3>
524
- <form>
525
- <label for="text">Text Input</label>
526
- <input type="text" id="text" placeholder="Text input">
527
-
528
- <label for="select">Select</label>
529
- <select id="select">
530
- <option>Option 1</option>
531
- <option>Option 2</option>
532
- </select>
533
-
534
- <label for="textarea">Textarea</label>
535
- <textarea id="textarea" placeholder="Textarea"></textarea>
536
-
537
- <label for="invalid" aria-invalid="true">Invalid Input</label>
538
- <input type="text" id="invalid" aria-invalid="true" placeholder="Invalid input">
539
-
540
- <fieldset>
541
- <legend>Checkboxes</legend>
542
- <label>
543
- <input type="checkbox" checked>
544
- Checkbox 1
545
- </label>
546
- <label>
547
- <input type="checkbox">
548
- Checkbox 2
549
- </label>
550
- </fieldset>
551
-
552
- <fieldset>
553
- <legend>Radio Buttons</legend>
554
- <label>
555
- <input type="radio" name="radio" checked>
556
- Radio 1
557
- </label>
558
- <label>
559
- <input type="radio" name="radio">
560
- Radio 2
561
- </label>
562
- </fieldset>
563
- </form>
564
- </article>
565
- </div>
566
-
567
- <div>
568
- <h2>Theme Color Mapping</h2>
569
- <article>
570
- <h3>Main Colors</h3>
571
- <div class="grid">
572
- {% for color_name, color_value in main_colors %}
573
- <div>
574
- <div class="color-sample" style="background-color: {{ color_value }};"></div>
575
- <small>{{ color_name }}<br>{{ color_value }}</small>
576
- </div>
577
- {% endfor %}
578
- </div>
579
- </article>
580
-
581
- <article>
582
- <h3>All Pico CSS Variables</h3>
583
- <div style="max-height: 300px; overflow-y: auto;">
584
- <table>
585
- <thead>
586
- <tr>
587
- <th>Variable</th>
588
- <th>Value</th>
589
- <th>Sample</th>
590
- </tr>
591
- </thead>
592
- <tbody>
593
- {% for var_name, var_value in all_vars %}
594
- <tr>
595
- <td>{{ var_name }}</td>
596
- <td>{{ var_value }}</td>
597
- <td>
598
- {% if var_value.startswith('#') or var_value.startswith('rgb') %}
599
- <div class="color-sample" style="background-color: {{ var_value }};"></div>
600
- {% endif %}
601
- </td>
602
- </tr>
603
- {% endfor %}
604
- </tbody>
605
- </table>
606
- </div>
607
- </article>
608
-
609
- <article>
610
- <h3>Color Contrast Checks</h3>
611
- <table class="contrast-table">
612
- <thead>
613
- <tr>
614
- <th>Foreground</th>
615
- <th>Background</th>
616
- <th>Contrast</th>
617
- <th>WCAG AA</th>
618
- </tr>
619
- </thead>
620
- <tbody>
621
- {% for check in contrast_checks %}
622
- <tr>
623
- <td>{{ check.fg_name }}</td>
624
- <td>{{ check.bg_name }}</td>
625
- <td>{{ check.contrast }}</td>
626
- <td class="{% if check.passes %}good-contrast{% else %}bad-contrast{% endif %}">
627
- {{ "Pass" if check.passes else "Fail" }}
628
- </td>
629
- </tr>
630
- {% endfor %}
631
- </tbody>
632
- </table>
633
- </article>
634
-
635
- <article>
636
- <h3>Original Theme Colors</h3>
637
- <div style="max-height: 300px; overflow-y: auto;">
638
- <table>
639
- <thead>
640
- <tr>
641
- <th>Group</th>
642
- <th>Name</th>
643
- <th>Value</th>
644
- <th>Sample</th>
645
- </tr>
646
- </thead>
647
- <tbody>
648
- {% for group, names in original_colors.items() %}
649
- {% for name, value in names.items() %}
650
- <tr>
651
- <td>{{ group }}</td>
652
- <td>{{ name }}</td>
653
- <td>{{ value }}</td>
654
- <td><div class="color-sample" style="background-color: {{ value }};"></div></td>
655
- </tr>
656
- {% endfor %}
657
- {% endfor %}
658
- </tbody>
659
- </table>
660
- </div>
661
- </article>
662
- </div>
663
- </div>
664
- </div>
665
- </body>
666
- </html>
667
- '''
668
-
669
- try:
670
- with open(template_path, 'w') as f:
671
- f.write(template_content)
672
- except Exception as e:
673
- print(f"Error writing template file: {e}")
674
- sys.exit(1)
675
-
676
- # Setup Jinja2 templates
677
- try:
678
- templates = Jinja2Templates(directory=str(template_dir))
679
- except Exception as e:
680
- print(f"Error setting up Jinja2 templates: {e}")
681
- sys.exit(1)
682
-
683
- @app.get("/", response_class=HTMLResponse)
684
- async def index(request: Request, theme: str | None = None):
685
- try:
686
- # Get requested theme name from query parameter
687
- if theme is None or theme not in themes:
688
- # If no theme is provided or theme is invalid, default to alabaster or first theme
689
- theme_name = 'alabaster' if 'alabaster' in themes else next(iter(themes))
690
- else:
691
- # Use the requested theme directly
692
- theme_name = theme
693
-
694
- # Get the theme data
695
- theme_data = themes[theme_name]
696
-
697
- # Convert theme to pico css variables
698
- css_vars = alacritty_to_pico(theme_data)
699
-
700
- # Format css variables for the style tag - this is important for proper application of the theme
701
- css_vars_str = ":root {\n" + "\n".join([f" {k}: {v};" for k, v in css_vars.items()]) + "\n}"
702
-
703
- # Prepare main colors for display
704
- main_colors = [
705
- ("Background", css_vars["--pico-background-color"]),
706
- ("Text", css_vars["--pico-color"]),
707
- ("Primary", css_vars["--pico-primary"]),
708
- ("Secondary", css_vars["--pico-secondary"]),
709
- ("Muted", css_vars["--pico-muted-color"]),
710
- ]
711
-
712
- # Extract original colors from theme
713
- original_colors = {}
714
- if "colors" in theme_data:
715
- for group, colors in theme_data["colors"].items():
716
- if isinstance(colors, dict):
717
- group_colors = {}
718
- for name, value in colors.items():
719
- if isinstance(value, str):
720
- group_colors[name] = value.strip()
721
- if group_colors:
722
- original_colors[group] = group_colors
723
-
724
- # Check contrasts
725
- contrast_checks = []
726
-
727
- # Define relevant foreground/background variable pairs to evaluate
728
- contrast_pairs = [
729
- ("Text", "--pico-color", "Background", "--pico-background-color"),
730
- ("Primary", "--pico-primary", "Background", "--pico-background-color"),
731
- ("Secondary", "--pico-secondary", "Background", "--pico-background-color"),
732
- ("Muted", "--pico-muted-color", "Background", "--pico-background-color"),
733
- (
734
- "Button Text",
735
- "--pico-button-base-color",
736
- "Button Background",
737
- "--pico-button-base-background-color",
738
- ),
739
- (
740
- "Secondary Btn Text",
741
- "--pico-secondary-inverse",
742
- "Secondary",
743
- "--pico-secondary",
744
- ),
745
- (
746
- "Contrast Text",
747
- "--pico-contrast-inverse",
748
- "Contrast Background",
749
- "--pico-contrast",
750
- ),
751
- (
752
- "Code",
753
- "--pico-code-color",
754
- "Code Background",
755
- "--pico-code-background-color",
756
- ),
757
- ]
758
-
759
- for fg_name, fg_var, bg_name, bg_var in contrast_pairs:
760
- fg_hex = css_vars.get(fg_var)
761
- bg_hex = css_vars.get(bg_var)
762
- if not fg_hex or not bg_hex:
763
- continue
764
- fg_rgb = _hex_to_rgb(fg_hex)
765
- bg_rgb = _hex_to_rgb(bg_hex)
766
- if fg_rgb is None or bg_rgb is None:
767
- continue
768
- ratio = _contrast(fg_rgb, bg_rgb)
769
- contrast_checks.append({
770
- "fg_name": fg_name,
771
- "bg_name": bg_name,
772
- "contrast": f"{ratio:.2f}",
773
- "passes": ratio >= 4.5,
774
- })
775
-
776
- return templates.TemplateResponse(
777
- "theme_mapper.html",
778
- {
779
- "request": request,
780
- "css_vars": css_vars_str,
781
- "themes": themes.keys(),
782
- "current_theme": theme_name,
783
- "main_colors": main_colors,
784
- "all_vars": list(css_vars.items()),
785
- "original_colors": original_colors,
786
- "contrast_checks": contrast_checks
787
- }
788
- )
789
- except Exception as e:
790
- print(f"Error handling request: {e}")
791
- from fastapi.responses import PlainTextResponse
792
- return PlainTextResponse(f"Error processing theme: {e!s}", status_code=500)
793
-
794
- # Add a more robust error handler for the application
795
- @app.exception_handler(Exception)
796
- async def global_exception_handler(request: Request, exc: Exception):
797
- print(f"Global exception handler caught: {exc}")
798
- from fastapi.responses import PlainTextResponse
799
- return PlainTextResponse(f"Internal server error: {exc!s}", status_code=500)
800
-
801
- # Run the app
802
- print("Starting Theme Mapper web app...")
803
- print(f"Current directory: {current_dir}")
804
- print(f"Template directory: {template_dir} (exists: {template_dir.exists()})")
805
- print(f"Themes directory: {themes_dir} (exists: {themes_dir.exists()})")
806
- print(f"Loaded {len(themes)} themes: {', '.join(themes.keys())}")
807
-
808
- try:
809
- uvicorn.run(app, host="127.0.0.1", port=5050)
810
- except Exception as e:
811
- print(f"Error starting the web server: {e}")
812
- sys.exit(1)