bead 0.1.0__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.
- bead/__init__.py +11 -0
- bead/__main__.py +11 -0
- bead/active_learning/__init__.py +15 -0
- bead/active_learning/config.py +231 -0
- bead/active_learning/loop.py +566 -0
- bead/active_learning/models/__init__.py +24 -0
- bead/active_learning/models/base.py +852 -0
- bead/active_learning/models/binary.py +910 -0
- bead/active_learning/models/categorical.py +943 -0
- bead/active_learning/models/cloze.py +862 -0
- bead/active_learning/models/forced_choice.py +956 -0
- bead/active_learning/models/free_text.py +773 -0
- bead/active_learning/models/lora.py +365 -0
- bead/active_learning/models/magnitude.py +835 -0
- bead/active_learning/models/multi_select.py +795 -0
- bead/active_learning/models/ordinal_scale.py +811 -0
- bead/active_learning/models/peft_adapter.py +155 -0
- bead/active_learning/models/random_effects.py +639 -0
- bead/active_learning/selection.py +354 -0
- bead/active_learning/strategies.py +391 -0
- bead/active_learning/trainers/__init__.py +26 -0
- bead/active_learning/trainers/base.py +210 -0
- bead/active_learning/trainers/data_collator.py +172 -0
- bead/active_learning/trainers/dataset_utils.py +261 -0
- bead/active_learning/trainers/huggingface.py +304 -0
- bead/active_learning/trainers/lightning.py +324 -0
- bead/active_learning/trainers/metrics.py +424 -0
- bead/active_learning/trainers/mixed_effects.py +551 -0
- bead/active_learning/trainers/model_wrapper.py +509 -0
- bead/active_learning/trainers/registry.py +104 -0
- bead/adapters/__init__.py +11 -0
- bead/adapters/huggingface.py +61 -0
- bead/behavioral/__init__.py +116 -0
- bead/behavioral/analytics.py +646 -0
- bead/behavioral/extraction.py +343 -0
- bead/behavioral/merging.py +343 -0
- bead/cli/__init__.py +11 -0
- bead/cli/active_learning.py +513 -0
- bead/cli/active_learning_commands.py +779 -0
- bead/cli/completion.py +359 -0
- bead/cli/config.py +624 -0
- bead/cli/constraint_builders.py +286 -0
- bead/cli/deployment.py +859 -0
- bead/cli/deployment_trials.py +493 -0
- bead/cli/deployment_ui.py +332 -0
- bead/cli/display.py +378 -0
- bead/cli/items.py +960 -0
- bead/cli/items_factories.py +776 -0
- bead/cli/list_constraints.py +714 -0
- bead/cli/lists.py +490 -0
- bead/cli/main.py +430 -0
- bead/cli/models.py +877 -0
- bead/cli/resource_loaders.py +621 -0
- bead/cli/resources.py +1036 -0
- bead/cli/shell.py +356 -0
- bead/cli/simulate.py +840 -0
- bead/cli/templates.py +1158 -0
- bead/cli/training.py +1080 -0
- bead/cli/utils.py +614 -0
- bead/cli/workflow.py +1273 -0
- bead/config/__init__.py +68 -0
- bead/config/active_learning.py +1009 -0
- bead/config/config.py +192 -0
- bead/config/defaults.py +118 -0
- bead/config/deployment.py +217 -0
- bead/config/env.py +147 -0
- bead/config/item.py +45 -0
- bead/config/list.py +193 -0
- bead/config/loader.py +149 -0
- bead/config/logging.py +42 -0
- bead/config/model.py +49 -0
- bead/config/paths.py +46 -0
- bead/config/profiles.py +320 -0
- bead/config/resources.py +47 -0
- bead/config/serialization.py +210 -0
- bead/config/simulation.py +206 -0
- bead/config/template.py +238 -0
- bead/config/validation.py +267 -0
- bead/data/__init__.py +65 -0
- bead/data/base.py +87 -0
- bead/data/identifiers.py +97 -0
- bead/data/language_codes.py +61 -0
- bead/data/metadata.py +270 -0
- bead/data/range.py +123 -0
- bead/data/repository.py +358 -0
- bead/data/serialization.py +249 -0
- bead/data/timestamps.py +89 -0
- bead/data/validation.py +349 -0
- bead/data_collection/__init__.py +11 -0
- bead/data_collection/jatos.py +223 -0
- bead/data_collection/merger.py +154 -0
- bead/data_collection/prolific.py +198 -0
- bead/deployment/__init__.py +5 -0
- bead/deployment/distribution.py +402 -0
- bead/deployment/jatos/__init__.py +1 -0
- bead/deployment/jatos/api.py +200 -0
- bead/deployment/jatos/exporter.py +210 -0
- bead/deployment/jspsych/__init__.py +9 -0
- bead/deployment/jspsych/biome.json +44 -0
- bead/deployment/jspsych/config.py +411 -0
- bead/deployment/jspsych/generator.py +598 -0
- bead/deployment/jspsych/package.json +51 -0
- bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
- bead/deployment/jspsych/randomizer.py +299 -0
- bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
- bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
- bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
- bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
- bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
- bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
- bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
- bead/deployment/jspsych/src/plugins/rating.ts +248 -0
- bead/deployment/jspsych/src/slopit/index.ts +9 -0
- bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
- bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
- bead/deployment/jspsych/templates/experiment.css +1 -0
- bead/deployment/jspsych/templates/experiment.js.template +289 -0
- bead/deployment/jspsych/templates/index.html +51 -0
- bead/deployment/jspsych/templates/randomizer.js +241 -0
- bead/deployment/jspsych/templates/randomizer.js.template +313 -0
- bead/deployment/jspsych/trials.py +723 -0
- bead/deployment/jspsych/tsconfig.json +23 -0
- bead/deployment/jspsych/tsup.config.ts +30 -0
- bead/deployment/jspsych/ui/__init__.py +1 -0
- bead/deployment/jspsych/ui/components.py +383 -0
- bead/deployment/jspsych/ui/styles.py +411 -0
- bead/dsl/__init__.py +80 -0
- bead/dsl/ast.py +168 -0
- bead/dsl/context.py +178 -0
- bead/dsl/errors.py +71 -0
- bead/dsl/evaluator.py +570 -0
- bead/dsl/grammar.lark +81 -0
- bead/dsl/parser.py +231 -0
- bead/dsl/stdlib.py +929 -0
- bead/evaluation/__init__.py +13 -0
- bead/evaluation/convergence.py +485 -0
- bead/evaluation/interannotator.py +398 -0
- bead/items/__init__.py +40 -0
- bead/items/adapters/__init__.py +70 -0
- bead/items/adapters/anthropic.py +224 -0
- bead/items/adapters/api_utils.py +167 -0
- bead/items/adapters/base.py +216 -0
- bead/items/adapters/google.py +259 -0
- bead/items/adapters/huggingface.py +1074 -0
- bead/items/adapters/openai.py +323 -0
- bead/items/adapters/registry.py +202 -0
- bead/items/adapters/sentence_transformers.py +224 -0
- bead/items/adapters/togetherai.py +309 -0
- bead/items/binary.py +515 -0
- bead/items/cache.py +558 -0
- bead/items/categorical.py +593 -0
- bead/items/cloze.py +757 -0
- bead/items/constructor.py +784 -0
- bead/items/forced_choice.py +413 -0
- bead/items/free_text.py +681 -0
- bead/items/generation.py +432 -0
- bead/items/item.py +396 -0
- bead/items/item_template.py +787 -0
- bead/items/magnitude.py +573 -0
- bead/items/multi_select.py +621 -0
- bead/items/ordinal_scale.py +569 -0
- bead/items/scoring.py +448 -0
- bead/items/validation.py +723 -0
- bead/lists/__init__.py +30 -0
- bead/lists/balancer.py +263 -0
- bead/lists/constraints.py +1067 -0
- bead/lists/experiment_list.py +286 -0
- bead/lists/list_collection.py +378 -0
- bead/lists/partitioner.py +1141 -0
- bead/lists/stratification.py +254 -0
- bead/participants/__init__.py +73 -0
- bead/participants/collection.py +699 -0
- bead/participants/merging.py +312 -0
- bead/participants/metadata_spec.py +491 -0
- bead/participants/models.py +276 -0
- bead/resources/__init__.py +29 -0
- bead/resources/adapters/__init__.py +19 -0
- bead/resources/adapters/base.py +104 -0
- bead/resources/adapters/cache.py +128 -0
- bead/resources/adapters/glazing.py +508 -0
- bead/resources/adapters/registry.py +117 -0
- bead/resources/adapters/unimorph.py +796 -0
- bead/resources/classification.py +856 -0
- bead/resources/constraint_builders.py +329 -0
- bead/resources/constraints.py +165 -0
- bead/resources/lexical_item.py +223 -0
- bead/resources/lexicon.py +744 -0
- bead/resources/loaders.py +209 -0
- bead/resources/template.py +441 -0
- bead/resources/template_collection.py +707 -0
- bead/resources/template_generation.py +349 -0
- bead/simulation/__init__.py +29 -0
- bead/simulation/annotators/__init__.py +15 -0
- bead/simulation/annotators/base.py +175 -0
- bead/simulation/annotators/distance_based.py +135 -0
- bead/simulation/annotators/lm_based.py +114 -0
- bead/simulation/annotators/oracle.py +182 -0
- bead/simulation/annotators/random.py +181 -0
- bead/simulation/dsl_extension/__init__.py +3 -0
- bead/simulation/noise_models/__init__.py +13 -0
- bead/simulation/noise_models/base.py +42 -0
- bead/simulation/noise_models/random_noise.py +82 -0
- bead/simulation/noise_models/systematic.py +132 -0
- bead/simulation/noise_models/temperature.py +86 -0
- bead/simulation/runner.py +144 -0
- bead/simulation/strategies/__init__.py +23 -0
- bead/simulation/strategies/base.py +123 -0
- bead/simulation/strategies/binary.py +103 -0
- bead/simulation/strategies/categorical.py +123 -0
- bead/simulation/strategies/cloze.py +224 -0
- bead/simulation/strategies/forced_choice.py +127 -0
- bead/simulation/strategies/free_text.py +105 -0
- bead/simulation/strategies/magnitude.py +116 -0
- bead/simulation/strategies/multi_select.py +129 -0
- bead/simulation/strategies/ordinal_scale.py +131 -0
- bead/templates/__init__.py +27 -0
- bead/templates/adapters/__init__.py +17 -0
- bead/templates/adapters/base.py +128 -0
- bead/templates/adapters/cache.py +178 -0
- bead/templates/adapters/huggingface.py +312 -0
- bead/templates/combinatorics.py +103 -0
- bead/templates/filler.py +605 -0
- bead/templates/renderers.py +177 -0
- bead/templates/resolver.py +178 -0
- bead/templates/strategies.py +1806 -0
- bead/templates/streaming.py +195 -0
- bead-0.1.0.dist-info/METADATA +212 -0
- bead-0.1.0.dist-info/RECORD +231 -0
- bead-0.1.0.dist-info/WHEEL +4 -0
- bead-0.1.0.dist-info/entry_points.txt +2 -0
- bead-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""UI customization commands for jsPsych deployment.
|
|
2
|
+
|
|
3
|
+
This module provides commands for customizing the appearance of jsPsych
|
|
4
|
+
experiments using Material Design themes and custom CSS.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
|
|
16
|
+
from bead.cli.utils import print_error, print_info, print_success
|
|
17
|
+
from bead.deployment.jspsych.ui.styles import MaterialDesignStylesheet
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group(name="ui")
|
|
23
|
+
def deployment_ui() -> None:
|
|
24
|
+
r"""UI customization commands for jsPsych experiments.
|
|
25
|
+
|
|
26
|
+
Commands for applying themes, generating CSS, and customizing the
|
|
27
|
+
appearance of jsPsych experiments.
|
|
28
|
+
|
|
29
|
+
\b
|
|
30
|
+
Examples:
|
|
31
|
+
$ bead deployment ui generate-css experiment/css/custom.css \\
|
|
32
|
+
--theme dark --primary-color "#1976D2"
|
|
33
|
+
$ bead deployment ui customize experiment/ \\
|
|
34
|
+
--theme dark --primary-color "#1976D2"
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.command()
|
|
39
|
+
@click.argument("output_file", type=click.Path(path_type=Path))
|
|
40
|
+
@click.option(
|
|
41
|
+
"--theme",
|
|
42
|
+
type=click.Choice(["light", "dark", "auto"], case_sensitive=False),
|
|
43
|
+
default="light",
|
|
44
|
+
help="Color theme (light, dark, or auto for system preference)",
|
|
45
|
+
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--primary-color",
|
|
48
|
+
default="#6200EE",
|
|
49
|
+
help="Primary color as hex code (default: Material Purple)",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--secondary-color",
|
|
53
|
+
default="#03DAC6",
|
|
54
|
+
help="Secondary color as hex code (default: Material Teal)",
|
|
55
|
+
)
|
|
56
|
+
@click.pass_context
|
|
57
|
+
def generate_css(
|
|
58
|
+
ctx: click.Context,
|
|
59
|
+
output_file: Path,
|
|
60
|
+
theme: Literal["light", "dark", "auto"],
|
|
61
|
+
primary_color: str,
|
|
62
|
+
secondary_color: str,
|
|
63
|
+
) -> None:
|
|
64
|
+
r"""Generate Material Design CSS for jsPsych experiment.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
ctx : click.Context
|
|
69
|
+
Click context object.
|
|
70
|
+
output_file : Path
|
|
71
|
+
Output path for generated CSS file.
|
|
72
|
+
theme : Literal["light", "dark", "auto"]
|
|
73
|
+
Color theme.
|
|
74
|
+
primary_color : str
|
|
75
|
+
Primary color as hex code.
|
|
76
|
+
secondary_color : str
|
|
77
|
+
Secondary color as hex code.
|
|
78
|
+
|
|
79
|
+
Examples
|
|
80
|
+
--------
|
|
81
|
+
$ bead deployment ui generate-css experiment/css/custom.css
|
|
82
|
+
|
|
83
|
+
$ bead deployment ui generate-css experiment/css/dark.css \\
|
|
84
|
+
--theme dark --primary-color "#1976D2"
|
|
85
|
+
|
|
86
|
+
$ bead deployment ui generate-css experiment/css/material.css \\
|
|
87
|
+
--theme auto --primary-color "#6200EE" --secondary-color "#03DAC6"
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
print_info(f"Generating Material Design CSS (theme: {theme})")
|
|
91
|
+
|
|
92
|
+
# Validate color codes
|
|
93
|
+
if not _is_valid_hex_color(primary_color):
|
|
94
|
+
print_error(f"Invalid primary color: {primary_color}")
|
|
95
|
+
print_info("Color must be a valid hex code (e.g., #1976D2)")
|
|
96
|
+
ctx.exit(1)
|
|
97
|
+
|
|
98
|
+
if not _is_valid_hex_color(secondary_color):
|
|
99
|
+
print_error(f"Invalid secondary color: {secondary_color}")
|
|
100
|
+
print_info("Color must be a valid hex code (e.g., #03DAC6)")
|
|
101
|
+
ctx.exit(1)
|
|
102
|
+
|
|
103
|
+
# Create output directory if needed
|
|
104
|
+
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
# Generate CSS
|
|
107
|
+
stylesheet = MaterialDesignStylesheet()
|
|
108
|
+
css = stylesheet.generate_css(
|
|
109
|
+
theme=theme,
|
|
110
|
+
primary_color=primary_color,
|
|
111
|
+
secondary_color=secondary_color,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Write to file
|
|
115
|
+
output_file.write_text(css, encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
print_success(f"Generated CSS: {output_file}")
|
|
118
|
+
|
|
119
|
+
# Show preview
|
|
120
|
+
preview_panel = Panel(
|
|
121
|
+
f"[cyan]Theme:[/cyan] {theme}\n"
|
|
122
|
+
f"[cyan]Primary:[/cyan] {primary_color}\n"
|
|
123
|
+
f"[cyan]Secondary:[/cyan] {secondary_color}\n"
|
|
124
|
+
f"[cyan]Lines:[/cyan] {len(css.splitlines())}",
|
|
125
|
+
title="[bold]CSS Preview[/bold]",
|
|
126
|
+
border_style="green",
|
|
127
|
+
)
|
|
128
|
+
console.print(preview_panel)
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print_error(f"Failed to generate CSS: {e}")
|
|
132
|
+
ctx.exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@click.command()
|
|
136
|
+
@click.argument("experiment_dir", type=click.Path(exists=True, path_type=Path))
|
|
137
|
+
@click.option(
|
|
138
|
+
"--theme",
|
|
139
|
+
type=click.Choice(["light", "dark", "auto"], case_sensitive=False),
|
|
140
|
+
default="light",
|
|
141
|
+
help="Color theme (light, dark, or auto for system preference)",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--primary-color",
|
|
145
|
+
default="#6200EE",
|
|
146
|
+
help="Primary color as hex code (default: Material Purple)",
|
|
147
|
+
)
|
|
148
|
+
@click.option(
|
|
149
|
+
"--secondary-color",
|
|
150
|
+
default="#03DAC6",
|
|
151
|
+
help="Secondary color as hex code (default: Material Teal)",
|
|
152
|
+
)
|
|
153
|
+
@click.option(
|
|
154
|
+
"--css-file",
|
|
155
|
+
type=click.Path(exists=True, path_type=Path),
|
|
156
|
+
help="Path to custom CSS file to include (optional)",
|
|
157
|
+
)
|
|
158
|
+
@click.option(
|
|
159
|
+
"--output-name",
|
|
160
|
+
default="experiment.css",
|
|
161
|
+
help="Output CSS filename (default: experiment.css)",
|
|
162
|
+
)
|
|
163
|
+
@click.pass_context
|
|
164
|
+
def customize(
|
|
165
|
+
ctx: click.Context,
|
|
166
|
+
experiment_dir: Path,
|
|
167
|
+
theme: Literal["light", "dark", "auto"],
|
|
168
|
+
primary_color: str,
|
|
169
|
+
secondary_color: str,
|
|
170
|
+
css_file: Path | None,
|
|
171
|
+
output_name: str,
|
|
172
|
+
) -> None:
|
|
173
|
+
r"""Apply UI customization to jsPsych experiment directory.
|
|
174
|
+
|
|
175
|
+
Generates Material Design CSS and optionally merges with custom CSS.
|
|
176
|
+
Writes the final CSS to the experiment's css/ directory.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
ctx : click.Context
|
|
181
|
+
Click context object.
|
|
182
|
+
experiment_dir : Path
|
|
183
|
+
Path to experiment directory.
|
|
184
|
+
theme : Literal["light", "dark", "auto"]
|
|
185
|
+
Color theme.
|
|
186
|
+
primary_color : str
|
|
187
|
+
Primary color as hex code.
|
|
188
|
+
secondary_color : str
|
|
189
|
+
Secondary color as hex code.
|
|
190
|
+
css_file : Path | None
|
|
191
|
+
Optional path to custom CSS file.
|
|
192
|
+
output_name : str
|
|
193
|
+
Output CSS filename.
|
|
194
|
+
|
|
195
|
+
Examples
|
|
196
|
+
--------
|
|
197
|
+
$ bead deployment ui customize experiment/
|
|
198
|
+
|
|
199
|
+
$ bead deployment ui customize experiment/ \\
|
|
200
|
+
--theme dark --primary-color "#1976D2"
|
|
201
|
+
|
|
202
|
+
$ bead deployment ui customize experiment/ \\
|
|
203
|
+
--theme auto --css-file custom.css --output-name styles.css
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
print_info(f"Customizing UI for experiment: {experiment_dir}")
|
|
207
|
+
|
|
208
|
+
# Validate color codes
|
|
209
|
+
if not _is_valid_hex_color(primary_color):
|
|
210
|
+
print_error(f"Invalid primary color: {primary_color}")
|
|
211
|
+
print_info("Color must be a valid hex code (e.g., #1976D2)")
|
|
212
|
+
ctx.exit(1)
|
|
213
|
+
|
|
214
|
+
if not _is_valid_hex_color(secondary_color):
|
|
215
|
+
print_error(f"Invalid secondary color: {secondary_color}")
|
|
216
|
+
print_info("Color must be a valid hex code (e.g., #03DAC6)")
|
|
217
|
+
ctx.exit(1)
|
|
218
|
+
|
|
219
|
+
# Create css directory if needed
|
|
220
|
+
css_dir = experiment_dir / "css"
|
|
221
|
+
css_dir.mkdir(parents=True, exist_ok=True)
|
|
222
|
+
|
|
223
|
+
# Generate Material Design CSS
|
|
224
|
+
stylesheet = MaterialDesignStylesheet()
|
|
225
|
+
material_css = stylesheet.generate_css(
|
|
226
|
+
theme=theme,
|
|
227
|
+
primary_color=primary_color,
|
|
228
|
+
secondary_color=secondary_color,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Merge with custom CSS if provided
|
|
232
|
+
final_css = material_css
|
|
233
|
+
if css_file:
|
|
234
|
+
print_info(f"Merging with custom CSS: {css_file}")
|
|
235
|
+
custom_css = css_file.read_text(encoding="utf-8")
|
|
236
|
+
final_css = material_css + "\n\n/* Custom CSS */\n\n" + custom_css
|
|
237
|
+
|
|
238
|
+
# Write to output file
|
|
239
|
+
output_path = css_dir / output_name
|
|
240
|
+
output_path.write_text(final_css, encoding="utf-8")
|
|
241
|
+
|
|
242
|
+
print_success(f"CSS written to: {output_path}")
|
|
243
|
+
|
|
244
|
+
# Show summary
|
|
245
|
+
summary_panel = Panel(
|
|
246
|
+
f"[cyan]Theme:[/cyan] {theme}\n"
|
|
247
|
+
f"[cyan]Primary:[/cyan] {primary_color}\n"
|
|
248
|
+
f"[cyan]Secondary:[/cyan] {secondary_color}\n"
|
|
249
|
+
f"[cyan]Custom CSS:[/cyan] {'Yes' if css_file else 'No'}\n"
|
|
250
|
+
f"[cyan]Output:[/cyan] {output_path.relative_to(experiment_dir)}\n"
|
|
251
|
+
f"[cyan]Lines:[/cyan] {len(final_css.splitlines())}",
|
|
252
|
+
title="[bold]UI Customization Summary[/bold]",
|
|
253
|
+
border_style="green",
|
|
254
|
+
)
|
|
255
|
+
console.print(summary_panel)
|
|
256
|
+
|
|
257
|
+
# Update index.html to reference the CSS file
|
|
258
|
+
index_html = experiment_dir / "index.html"
|
|
259
|
+
if index_html.exists():
|
|
260
|
+
_update_index_html_css_reference(index_html, output_name)
|
|
261
|
+
print_success("Updated index.html CSS reference")
|
|
262
|
+
else:
|
|
263
|
+
print_info("index.html not found - skipping CSS reference update")
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print_error(f"Failed to customize UI: {e}")
|
|
267
|
+
ctx.exit(1)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _is_valid_hex_color(color: str) -> bool:
|
|
271
|
+
"""Validate hex color code.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
color : str
|
|
276
|
+
Color string to validate.
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
bool
|
|
281
|
+
True if valid hex color, False otherwise.
|
|
282
|
+
|
|
283
|
+
Examples
|
|
284
|
+
--------
|
|
285
|
+
>>> _is_valid_hex_color("#1976D2")
|
|
286
|
+
True
|
|
287
|
+
>>> _is_valid_hex_color("1976D2")
|
|
288
|
+
False
|
|
289
|
+
>>> _is_valid_hex_color("#GGG")
|
|
290
|
+
False
|
|
291
|
+
"""
|
|
292
|
+
if not color.startswith("#"):
|
|
293
|
+
return False
|
|
294
|
+
hex_part = color[1:]
|
|
295
|
+
if len(hex_part) not in (3, 6):
|
|
296
|
+
return False
|
|
297
|
+
try:
|
|
298
|
+
int(hex_part, 16)
|
|
299
|
+
return True
|
|
300
|
+
except ValueError:
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _update_index_html_css_reference(index_html: Path, css_filename: str) -> None:
|
|
305
|
+
"""Update index.html to reference the CSS file.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
index_html : Path
|
|
310
|
+
Path to index.html file.
|
|
311
|
+
css_filename : str
|
|
312
|
+
CSS filename to reference.
|
|
313
|
+
"""
|
|
314
|
+
html_content = index_html.read_text(encoding="utf-8")
|
|
315
|
+
|
|
316
|
+
# Check if CSS link already exists
|
|
317
|
+
css_link = f'<link rel="stylesheet" href="css/{css_filename}">'
|
|
318
|
+
if css_link in html_content:
|
|
319
|
+
return # Already has the correct reference
|
|
320
|
+
|
|
321
|
+
# Find </head> tag and insert CSS link before it
|
|
322
|
+
if "</head>" in html_content:
|
|
323
|
+
html_content = html_content.replace(
|
|
324
|
+
"</head>",
|
|
325
|
+
f" {css_link}\n</head>",
|
|
326
|
+
)
|
|
327
|
+
index_html.write_text(html_content, encoding="utf-8")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# Register commands
|
|
331
|
+
deployment_ui.add_command(generate_css)
|
|
332
|
+
deployment_ui.add_command(customize)
|
bead/cli/display.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""Rich display utilities for CLI commands.
|
|
2
|
+
|
|
3
|
+
This module provides centralized Rich display utilities for beautiful terminal output
|
|
4
|
+
across all bead CLI commands. All CLI modules should import from this module for
|
|
5
|
+
consistent formatting.
|
|
6
|
+
|
|
7
|
+
Examples
|
|
8
|
+
--------
|
|
9
|
+
>>> from bead.cli.display import print_header, print_success, create_summary_table
|
|
10
|
+
>>> print_header("Stage 1: Resources")
|
|
11
|
+
>>> print_success("Loaded 1,234 items")
|
|
12
|
+
>>> table = create_summary_table({"Items": "1,234", "Time": "2.3s"})
|
|
13
|
+
>>> console.print(table)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
from rich.live import Live
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
from rich.progress import (
|
|
25
|
+
BarColumn,
|
|
26
|
+
Progress,
|
|
27
|
+
SpinnerColumn,
|
|
28
|
+
TextColumn,
|
|
29
|
+
TimeElapsedColumn,
|
|
30
|
+
)
|
|
31
|
+
from rich.spinner import Spinner
|
|
32
|
+
from rich.table import Table
|
|
33
|
+
from rich.traceback import install
|
|
34
|
+
|
|
35
|
+
# Install rich traceback globally for CLI
|
|
36
|
+
install(show_locals=True, width=100, word_wrap=True)
|
|
37
|
+
|
|
38
|
+
# Shared console instance
|
|
39
|
+
console = Console()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def print_header(text: str) -> None:
|
|
43
|
+
"""Print command header with horizontal rule.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
text : str
|
|
48
|
+
Header text to display.
|
|
49
|
+
|
|
50
|
+
Examples
|
|
51
|
+
--------
|
|
52
|
+
>>> print_header("Stage 1: Resources")
|
|
53
|
+
═══════════════════════════════════════
|
|
54
|
+
Stage 1: Resources
|
|
55
|
+
═══════════════════════════════════════
|
|
56
|
+
"""
|
|
57
|
+
console.rule(f"[bold]{text}[/bold]")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def print_success(text: str) -> None:
|
|
61
|
+
"""Print success message with green checkmark.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
text : str
|
|
66
|
+
Success message to display.
|
|
67
|
+
|
|
68
|
+
Examples
|
|
69
|
+
--------
|
|
70
|
+
>>> print_success("Loaded 1,234 items")
|
|
71
|
+
✓ Loaded 1,234 items
|
|
72
|
+
"""
|
|
73
|
+
console.print(f"[green]✓[/green] {text}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def print_error(text: str) -> None:
|
|
77
|
+
"""Print error message with red X.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
text : str
|
|
82
|
+
Error message to display.
|
|
83
|
+
|
|
84
|
+
Examples
|
|
85
|
+
--------
|
|
86
|
+
>>> print_error("Failed to load config")
|
|
87
|
+
✗ Failed to load config
|
|
88
|
+
"""
|
|
89
|
+
console.print(f"[red]✗[/red] {text}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def print_warning(text: str) -> None:
|
|
93
|
+
"""Print warning message with yellow warning sign.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
text : str
|
|
98
|
+
Warning message to display.
|
|
99
|
+
|
|
100
|
+
Examples
|
|
101
|
+
--------
|
|
102
|
+
>>> print_warning("Using default strategy")
|
|
103
|
+
⚠ Warning: Using default strategy
|
|
104
|
+
"""
|
|
105
|
+
console.print(f"[yellow]⚠[/yellow] {text}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def print_info(text: str) -> None:
|
|
109
|
+
"""Print info message with blue info icon.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
text : str
|
|
114
|
+
Info message to display.
|
|
115
|
+
|
|
116
|
+
Examples
|
|
117
|
+
--------
|
|
118
|
+
>>> print_info("Next step: Run partition command")
|
|
119
|
+
ℹ Next step: Run partition command
|
|
120
|
+
"""
|
|
121
|
+
console.print(f"[blue]ℹ[/blue] {text}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_summary_table(
|
|
125
|
+
data: dict[str, str],
|
|
126
|
+
title: str | None = None,
|
|
127
|
+
show_header: bool = False,
|
|
128
|
+
) -> Table:
|
|
129
|
+
"""Create formatted summary table.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
data : dict[str, str]
|
|
134
|
+
Dictionary mapping metric names to values.
|
|
135
|
+
title : str | None, optional
|
|
136
|
+
Table title. Default is None.
|
|
137
|
+
show_header : bool, optional
|
|
138
|
+
Whether to show table header. Default is False.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
Table
|
|
143
|
+
Formatted Rich Table object.
|
|
144
|
+
|
|
145
|
+
Examples
|
|
146
|
+
--------
|
|
147
|
+
>>> table = create_summary_table({
|
|
148
|
+
... "Items processed": "1,234",
|
|
149
|
+
... "Success rate": "98.5%",
|
|
150
|
+
... "Time": "45.2s"
|
|
151
|
+
... }, title="Summary")
|
|
152
|
+
>>> console.print(table)
|
|
153
|
+
"""
|
|
154
|
+
table = Table(show_header=show_header, title=title)
|
|
155
|
+
table.add_column("Metric", style="cyan", no_wrap=True)
|
|
156
|
+
table.add_column("Value", justify="right", style="green")
|
|
157
|
+
|
|
158
|
+
for key, value in data.items():
|
|
159
|
+
table.add_row(key, value)
|
|
160
|
+
|
|
161
|
+
return table
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def create_progress() -> Progress:
|
|
165
|
+
"""Create standard progress bar for CLI operations.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
Progress
|
|
170
|
+
Configured Rich Progress instance.
|
|
171
|
+
|
|
172
|
+
Examples
|
|
173
|
+
--------
|
|
174
|
+
>>> with create_progress() as progress:
|
|
175
|
+
... task = progress.add_task("Loading items...", total=1000)
|
|
176
|
+
... for i in range(1000):
|
|
177
|
+
... progress.advance(task)
|
|
178
|
+
"""
|
|
179
|
+
return Progress(
|
|
180
|
+
SpinnerColumn(),
|
|
181
|
+
TextColumn("[progress.description]{task.description}"),
|
|
182
|
+
BarColumn(),
|
|
183
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
184
|
+
TimeElapsedColumn(),
|
|
185
|
+
console=console,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def create_spinner_progress() -> Progress:
|
|
190
|
+
"""Create spinner-only progress (for indeterminate operations).
|
|
191
|
+
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
Progress
|
|
195
|
+
Configured Rich Progress instance with spinner only.
|
|
196
|
+
|
|
197
|
+
Examples
|
|
198
|
+
--------
|
|
199
|
+
>>> with create_spinner_progress() as progress:
|
|
200
|
+
... progress.add_task("Loading model weights...", total=None)
|
|
201
|
+
"""
|
|
202
|
+
return Progress(
|
|
203
|
+
SpinnerColumn(),
|
|
204
|
+
TextColumn("[progress.description]{task.description}"),
|
|
205
|
+
console=console,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def create_live_status(message: str) -> Live:
|
|
210
|
+
"""Create live status display with spinner.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
message : str
|
|
215
|
+
Status message to display.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
Live
|
|
220
|
+
Rich Live instance for status updates.
|
|
221
|
+
|
|
222
|
+
Examples
|
|
223
|
+
--------
|
|
224
|
+
>>> with create_live_status("Training model..."):
|
|
225
|
+
... model.train()
|
|
226
|
+
"""
|
|
227
|
+
return Live(Spinner("dots", text=message), console=console)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def create_panel(
|
|
231
|
+
content: str,
|
|
232
|
+
title: str | None = None,
|
|
233
|
+
style: str = "cyan",
|
|
234
|
+
) -> Panel:
|
|
235
|
+
"""Create formatted panel for important messages.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
content : str
|
|
240
|
+
Panel content.
|
|
241
|
+
title : str | None, optional
|
|
242
|
+
Panel title. Default is None.
|
|
243
|
+
style : str, optional
|
|
244
|
+
Panel border style color. Default is "cyan".
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
Panel
|
|
249
|
+
Rich Panel object.
|
|
250
|
+
|
|
251
|
+
Examples
|
|
252
|
+
--------
|
|
253
|
+
>>> panel = create_panel(
|
|
254
|
+
... "Generating lexicons from VerbNet...",
|
|
255
|
+
... title="In Progress"
|
|
256
|
+
... )
|
|
257
|
+
>>> console.print(panel)
|
|
258
|
+
"""
|
|
259
|
+
return Panel(content, title=f"[{style}]{title}[/{style}]" if title else None)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def display_file_stats(file_path: Path, count: int, item_type: str = "items") -> None:
|
|
263
|
+
"""Display statistics for a saved file.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
file_path : Path
|
|
268
|
+
Path to saved file.
|
|
269
|
+
count : int
|
|
270
|
+
Number of items in file.
|
|
271
|
+
item_type : str, optional
|
|
272
|
+
Type of items (for display). Default is "items".
|
|
273
|
+
|
|
274
|
+
Examples
|
|
275
|
+
--------
|
|
276
|
+
>>> display_file_stats(Path("items.jsonl"), 1234, "items")
|
|
277
|
+
✓ Saved 1,234 items to items.jsonl
|
|
278
|
+
"""
|
|
279
|
+
print_success(f"Saved {count:,} {item_type} to {file_path}")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def display_validation_errors(
|
|
283
|
+
errors: list[str],
|
|
284
|
+
max_display: int = 10,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Display validation errors with truncation.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
errors : list[str]
|
|
291
|
+
List of error messages.
|
|
292
|
+
max_display : int, optional
|
|
293
|
+
Maximum number of errors to display. Default is 10.
|
|
294
|
+
|
|
295
|
+
Examples
|
|
296
|
+
--------
|
|
297
|
+
>>> errors = ["Line 1: Invalid JSON", "Line 5: Missing field"]
|
|
298
|
+
>>> display_validation_errors(errors)
|
|
299
|
+
"""
|
|
300
|
+
print_error(f"Validation failed with {len(errors)} error(s):")
|
|
301
|
+
for error in errors[:max_display]:
|
|
302
|
+
console.print(f" [red]✗[/red] {error}")
|
|
303
|
+
if len(errors) > max_display:
|
|
304
|
+
console.print(f" ... and {len(errors) - max_display} more error(s)")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def confirm(
|
|
308
|
+
message: str,
|
|
309
|
+
default: bool = False,
|
|
310
|
+
) -> bool:
|
|
311
|
+
"""Prompt user for yes/no confirmation.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
message : str
|
|
316
|
+
Confirmation message.
|
|
317
|
+
default : bool, optional
|
|
318
|
+
Default value if user presses Enter. Default is False.
|
|
319
|
+
|
|
320
|
+
Returns
|
|
321
|
+
-------
|
|
322
|
+
bool
|
|
323
|
+
True if user confirmed, False otherwise.
|
|
324
|
+
|
|
325
|
+
Examples
|
|
326
|
+
--------
|
|
327
|
+
>>> if confirm("Delete all files?", default=False):
|
|
328
|
+
... delete_files()
|
|
329
|
+
"""
|
|
330
|
+
suffix = " [Y/n]: " if default else " [y/N]: "
|
|
331
|
+
response = console.input(f"[yellow]?[/yellow] {message}{suffix}")
|
|
332
|
+
|
|
333
|
+
if not response:
|
|
334
|
+
return default
|
|
335
|
+
|
|
336
|
+
return response.lower() in ("y", "yes")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def display_dry_run_summary(data: dict[str, Any]) -> None:
|
|
340
|
+
"""Display dry run summary.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
data : dict[str, Any]
|
|
345
|
+
Dictionary of dry run information.
|
|
346
|
+
|
|
347
|
+
Examples
|
|
348
|
+
--------
|
|
349
|
+
>>> display_dry_run_summary({
|
|
350
|
+
... "Templates": 26,
|
|
351
|
+
... "Filled Templates": 1234,
|
|
352
|
+
... "Output": "items.jsonl"
|
|
353
|
+
... })
|
|
354
|
+
"""
|
|
355
|
+
print_info("[DRY RUN] Preview of operation:")
|
|
356
|
+
for key, value in data.items():
|
|
357
|
+
console.print(f" {key}: {value}")
|
|
358
|
+
print_warning("[DRY RUN] No changes will be made")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# Export commonly used items
|
|
362
|
+
__all__ = [
|
|
363
|
+
"console",
|
|
364
|
+
"print_header",
|
|
365
|
+
"print_success",
|
|
366
|
+
"print_error",
|
|
367
|
+
"print_warning",
|
|
368
|
+
"print_info",
|
|
369
|
+
"create_summary_table",
|
|
370
|
+
"create_progress",
|
|
371
|
+
"create_spinner_progress",
|
|
372
|
+
"create_live_status",
|
|
373
|
+
"create_panel",
|
|
374
|
+
"display_file_stats",
|
|
375
|
+
"display_validation_errors",
|
|
376
|
+
"confirm",
|
|
377
|
+
"display_dry_run_summary",
|
|
378
|
+
]
|