dataeval-flow 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.
Files changed (94) hide show
  1. dataeval_flow/__init__.py +93 -0
  2. dataeval_flow/__main__.py +149 -0
  3. dataeval_flow/_app/__init__.py +5 -0
  4. dataeval_flow/_app/_model/__init__.py +5 -0
  5. dataeval_flow/_app/_model/_coerce.py +126 -0
  6. dataeval_flow/_app/_model/_discover.py +171 -0
  7. dataeval_flow/_app/_model/_execution.py +108 -0
  8. dataeval_flow/_app/_model/_introspect.py +280 -0
  9. dataeval_flow/_app/_model/_item.py +213 -0
  10. dataeval_flow/_app/_model/_registry.py +255 -0
  11. dataeval_flow/_app/_model/_state.py +322 -0
  12. dataeval_flow/_app/_model/_undo.py +61 -0
  13. dataeval_flow/_app/_panes/__init__.py +35 -0
  14. dataeval_flow/_app/_panes/_config_pane.py +173 -0
  15. dataeval_flow/_app/_panes/_result_pane.py +125 -0
  16. dataeval_flow/_app/_panes/_task_pane.py +91 -0
  17. dataeval_flow/_app/_panes/_widgets.py +111 -0
  18. dataeval_flow/_app/_screens/__init__.py +25 -0
  19. dataeval_flow/_app/_screens/_base.py +242 -0
  20. dataeval_flow/_app/_screens/_detail.py +333 -0
  21. dataeval_flow/_app/_screens/_model.py +102 -0
  22. dataeval_flow/_app/_screens/_params.py +80 -0
  23. dataeval_flow/_app/_screens/_pathpicker.py +68 -0
  24. dataeval_flow/_app/_screens/_section.py +621 -0
  25. dataeval_flow/_app/_screens/_settings.py +183 -0
  26. dataeval_flow/_app/_viewmodel/__init__.py +15 -0
  27. dataeval_flow/_app/_viewmodel/_builder_vm.py +272 -0
  28. dataeval_flow/_app/_viewmodel/_model_vm.py +70 -0
  29. dataeval_flow/_app/_viewmodel/_rendering.py +189 -0
  30. dataeval_flow/_app/_viewmodel/_result_vm.py +210 -0
  31. dataeval_flow/_app/_viewmodel/_section_vm.py +224 -0
  32. dataeval_flow/_app/app.py +742 -0
  33. dataeval_flow/_app/cli.py +592 -0
  34. dataeval_flow/_logging.py +102 -0
  35. dataeval_flow/cache.py +1355 -0
  36. dataeval_flow/config/__init__.py +80 -0
  37. dataeval_flow/config/_loader.py +79 -0
  38. dataeval_flow/config/_merge.py +92 -0
  39. dataeval_flow/config/_models.py +115 -0
  40. dataeval_flow/config/_paths.py +85 -0
  41. dataeval_flow/config/schemas/__init__.py +112 -0
  42. dataeval_flow/config/schemas/_dataset.py +111 -0
  43. dataeval_flow/config/schemas/_extractor.py +119 -0
  44. dataeval_flow/config/schemas/_metadata.py +28 -0
  45. dataeval_flow/config/schemas/_preprocessor.py +18 -0
  46. dataeval_flow/config/schemas/_selection.py +100 -0
  47. dataeval_flow/config/schemas/_task.py +89 -0
  48. dataeval_flow/config/schemas/_workflow.py +135 -0
  49. dataeval_flow/dataset.py +635 -0
  50. dataeval_flow/embeddings.py +135 -0
  51. dataeval_flow/metadata.py +48 -0
  52. dataeval_flow/preprocessing.py +141 -0
  53. dataeval_flow/py.typed +0 -0
  54. dataeval_flow/runner.py +118 -0
  55. dataeval_flow/selection.py +50 -0
  56. dataeval_flow/workflow/__init__.py +328 -0
  57. dataeval_flow/workflow/_text_report.py +511 -0
  58. dataeval_flow/workflow/base.py +69 -0
  59. dataeval_flow/workflow/orchestrator.py +454 -0
  60. dataeval_flow/workflows/__init__.py +1 -0
  61. dataeval_flow/workflows/analysis/__init__.py +38 -0
  62. dataeval_flow/workflows/analysis/outputs.py +202 -0
  63. dataeval_flow/workflows/analysis/params.py +114 -0
  64. dataeval_flow/workflows/analysis/workflow.py +1313 -0
  65. dataeval_flow/workflows/cleaning/__init__.py +23 -0
  66. dataeval_flow/workflows/cleaning/outputs.py +200 -0
  67. dataeval_flow/workflows/cleaning/params.py +160 -0
  68. dataeval_flow/workflows/cleaning/report.py +304 -0
  69. dataeval_flow/workflows/cleaning/workflow.py +794 -0
  70. dataeval_flow/workflows/drift/__init__.py +1 -0
  71. dataeval_flow/workflows/drift/outputs.py +144 -0
  72. dataeval_flow/workflows/drift/params.py +332 -0
  73. dataeval_flow/workflows/drift/report.py +201 -0
  74. dataeval_flow/workflows/drift/workflow.py +647 -0
  75. dataeval_flow/workflows/ood/__init__.py +1 -0
  76. dataeval_flow/workflows/ood/outputs.py +134 -0
  77. dataeval_flow/workflows/ood/params.py +161 -0
  78. dataeval_flow/workflows/ood/report.py +311 -0
  79. dataeval_flow/workflows/ood/workflow.py +728 -0
  80. dataeval_flow/workflows/prioritization/__init__.py +1 -0
  81. dataeval_flow/workflows/prioritization/outputs.py +122 -0
  82. dataeval_flow/workflows/prioritization/params.py +124 -0
  83. dataeval_flow/workflows/prioritization/report.py +117 -0
  84. dataeval_flow/workflows/prioritization/workflow.py +587 -0
  85. dataeval_flow/workflows/splitting/__init__.py +25 -0
  86. dataeval_flow/workflows/splitting/outputs.py +101 -0
  87. dataeval_flow/workflows/splitting/params.py +61 -0
  88. dataeval_flow/workflows/splitting/report.py +485 -0
  89. dataeval_flow/workflows/splitting/workflow.py +371 -0
  90. dataeval_flow-0.1.0.dist-info/METADATA +305 -0
  91. dataeval_flow-0.1.0.dist-info/RECORD +94 -0
  92. dataeval_flow-0.1.0.dist-info/WHEEL +4 -0
  93. dataeval_flow-0.1.0.dist-info/entry_points.txt +2 -0
  94. dataeval_flow-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,173 @@
1
+ """Config sidebar pane — rebuild, section update, and focus management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, cast
6
+
7
+ from textual.containers import VerticalScroll
8
+ from textual.css.query import NoMatches
9
+ from textual.widgets import Static
10
+
11
+ from dataeval_flow._app._panes._widgets import (
12
+ _CONFIG_SECTIONS,
13
+ SECTION_TITLES,
14
+ CfgItem,
15
+ CfgSectionHeader,
16
+ PaneWidget,
17
+ uid,
18
+ )
19
+ from dataeval_flow._app._viewmodel._rendering import snippet_config_item
20
+
21
+ if TYPE_CHECKING:
22
+ from textual.app import App
23
+ from textual.widget import Widget
24
+
25
+ from dataeval_flow._app._viewmodel._builder_vm import BuilderViewModel
26
+ else:
27
+ App = object
28
+
29
+
30
+ class ConfigPaneMixin(App):
31
+ """Mixin providing config sidebar rebuild logic for FlowApp.
32
+
33
+ Expects the host class to also inherit from ``App`` and to provide
34
+ ``_vm`` and ``_get_pane_widgets`` (both supplied by ``FlowApp``).
35
+ """
36
+
37
+ if TYPE_CHECKING:
38
+ _vm: BuilderViewModel
39
+
40
+ def _get_pane_widgets(self, pane_id: str) -> list[PaneWidget]: ...
41
+
42
+ def _rebuild_config_pane(self) -> None:
43
+ """Full rebuild of the config sidebar."""
44
+ try:
45
+ container = cast(VerticalScroll, self.query_one("#config-pane", VerticalScroll))
46
+ except NoMatches:
47
+ return
48
+
49
+ focused = self.focused
50
+ restore = self._save_config_focus(focused)
51
+
52
+ if restore[0] is not None:
53
+ self.set_focus(None)
54
+
55
+ container.remove_children()
56
+ for category, _ in _CONFIG_SECTIONS:
57
+ self._mount_config_section(container, category)
58
+
59
+ if restore[0] is not None:
60
+ self.call_after_refresh(self._restore_config_focus, *restore)
61
+
62
+ def _rebuild_config_section(self, category: str) -> None:
63
+ """Rebuild a single config section in place."""
64
+ try:
65
+ container = cast(VerticalScroll, self.query_one("#config-pane", VerticalScroll))
66
+ except NoMatches:
67
+ return
68
+
69
+ focused = self.focused
70
+ restore = self._save_config_focus(focused)
71
+
72
+ if restore[0] == category:
73
+ self.set_focus(None)
74
+
75
+ insert_before = self._remove_section_widgets(container, category)
76
+ self._mount_config_section(container, category, before=insert_before)
77
+
78
+ if restore[0] == category:
79
+ self.call_after_refresh(self._restore_config_focus, *restore)
80
+
81
+ def _remove_section_widgets(self, container: VerticalScroll, category: str) -> PaneWidget | None:
82
+ """Remove all widgets for *category* and return the next section's header (or None)."""
83
+ in_section = False
84
+ insert_before: PaneWidget | None = None
85
+ to_remove: list[Widget] = []
86
+ for child in list(container.children):
87
+ if isinstance(child, CfgSectionHeader) and child.fc_category == category:
88
+ in_section = True
89
+ to_remove.append(child)
90
+ elif in_section:
91
+ if isinstance(child, CfgSectionHeader):
92
+ insert_before = child
93
+ break
94
+ to_remove.append(child)
95
+ for w in to_remove:
96
+ w.remove()
97
+ return insert_before
98
+
99
+ def _mount_config_section(self, container: VerticalScroll, category: str, before: PaneWidget | None = None) -> None:
100
+ """Mount header + items for one config section."""
101
+ title = SECTION_TITLES.get(category, category)
102
+ items = self._vm.items(category)
103
+
104
+ widgets: list[Static] = []
105
+ widgets.append(
106
+ CfgSectionHeader(
107
+ f"[bold]{title}[/bold] [dim]({len(items)})[/dim]",
108
+ category=category,
109
+ classes="cfg-section-header",
110
+ id=uid("ch"),
111
+ markup=True,
112
+ )
113
+ )
114
+ if not items:
115
+ widgets.append(Static("[dim] (empty)[/dim]"))
116
+ else:
117
+ for idx, item in enumerate(items):
118
+ snippet = snippet_config_item(category, item)
119
+ widgets.append(
120
+ CfgItem(
121
+ snippet,
122
+ category=category,
123
+ index=idx,
124
+ classes="cfg-item",
125
+ id=uid("ci"),
126
+ markup=True,
127
+ )
128
+ )
129
+
130
+ if before is not None:
131
+ for w in widgets:
132
+ container.mount(w, before=before)
133
+ else:
134
+ for w in widgets:
135
+ container.mount(w)
136
+
137
+ # -- Config focus save/restore -----------------------------------------
138
+
139
+ def _save_config_focus(self, focused: Any) -> tuple[str | None, int | None, bool]:
140
+ """Extract focus context from the currently focused widget."""
141
+ if isinstance(focused, CfgItem) and focused.pane == "config-pane":
142
+ return focused.fc_category, focused.fc_index, False
143
+ if isinstance(focused, CfgSectionHeader) and focused.pane == "config-pane":
144
+ return focused.fc_category, None, True
145
+ return None, None, False
146
+
147
+ def _restore_config_focus(self, category: str | None, index: int | None, is_header: bool) -> None:
148
+ """Restore focus by finding the widget with matching attributes."""
149
+ if category is None:
150
+ return
151
+ widgets = self._get_pane_widgets("config-pane")
152
+ if is_header or index is None:
153
+ target = next((w for w in widgets if isinstance(w, CfgSectionHeader) and w.fc_category == category), None)
154
+ else:
155
+ target = next(
156
+ (w for w in widgets if isinstance(w, CfgItem) and w.fc_category == category and w.fc_index == index),
157
+ None,
158
+ )
159
+ if target is None and index > 0:
160
+ target = next(
161
+ (
162
+ w
163
+ for w in reversed(widgets)
164
+ if isinstance(w, CfgItem) and w.fc_category == category and w.fc_index < index
165
+ ),
166
+ None,
167
+ )
168
+ if target is None:
169
+ target = next(
170
+ (w for w in widgets if isinstance(w, CfgSectionHeader) and w.fc_category == category), None
171
+ )
172
+ if target is not None:
173
+ self.set_focus(target, scroll_visible=False)
@@ -0,0 +1,125 @@
1
+ """Result pane — rebuild, incremental append/update."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from textual.containers import VerticalScroll
8
+ from textual.css.query import NoMatches
9
+ from textual.widgets import Static
10
+
11
+ from dataeval_flow._app._panes._widgets import PaneWidget, ResultCard, uid
12
+
13
+ if TYPE_CHECKING:
14
+ from textual.app import App
15
+
16
+ from dataeval_flow._app._viewmodel._builder_vm import BuilderViewModel
17
+ else:
18
+ App = object
19
+
20
+
21
+ class ResultPaneMixin(App):
22
+ """Mixin providing result pane rebuild logic for FlowApp.
23
+
24
+ Expects the host class to also inherit from ``App`` and to provide ``_vm``.
25
+ """
26
+
27
+ if TYPE_CHECKING:
28
+ _vm: BuilderViewModel
29
+
30
+ def _rebuild_result_pane(self) -> None:
31
+ """Full rebuild of the result pane (used on undo/redo/load/new)."""
32
+ try:
33
+ container = self.query_one("#result-pane", VerticalScroll)
34
+ except NoMatches:
35
+ return
36
+
37
+ container.remove_children()
38
+ executions = [e for e in self._vm.all_executions() if e.status in ("completed", "failed")]
39
+ count = len(executions)
40
+ header_text = f"[bold]Results[/bold] [dim]({count})[/dim]" if count else "[bold]Results[/bold]"
41
+ container.mount(Static(header_text, classes="result-pane-header", id=uid("rh"), markup=True))
42
+
43
+ if not executions:
44
+ container.mount(Static("[dim] (no results yet)[/dim]"))
45
+ return
46
+
47
+ for entry in executions:
48
+ snippet = self._render_result_snippet(entry)
49
+ card = ResultCard(
50
+ snippet,
51
+ task_name=entry.task_name,
52
+ classes="result-card",
53
+ id=uid("rc"),
54
+ markup=True,
55
+ )
56
+ container.mount(card)
57
+
58
+ def _render_result_snippet(self, entry: Any) -> str:
59
+ """Render a result card snippet for a single execution entry."""
60
+ from dataeval_flow._app._screens._detail import _colorize_marker
61
+ from dataeval_flow._app._viewmodel._result_vm import ResultViewModel
62
+
63
+ if entry.status == "completed" and entry.result is not None:
64
+ rvm = ResultViewModel(entry.result)
65
+ summary = rvm.summary_line()
66
+ warnings = rvm.warning_count()
67
+ severity_tag = " [bold red][!!][/bold red]" if warnings else " [green][ok][/green]"
68
+ lines = [f"[bold]{entry.task_name}[/bold] — {summary}{severity_tag}"]
69
+ for fi in range(rvm.finding_count()):
70
+ fline = _colorize_marker(rvm.finding_summary_markup(fi))
71
+ lines.append(f" {fline}")
72
+ return "\n".join(lines)
73
+ error_brief = (entry.error or "unknown error")[:60]
74
+ return f"[bold]{entry.task_name}[/bold] — [bold red]FAILED[/bold red]: {error_brief}"
75
+
76
+ def _append_or_update_result(self, task_name: str) -> None:
77
+ """Add or update a single result card without full rebuild."""
78
+ entry = self._vm.task_execution(task_name)
79
+ if entry is None or entry.status not in ("completed", "failed"):
80
+ return
81
+
82
+ snippet = self._render_result_snippet(entry)
83
+
84
+ # Try to update existing card (re-run of same task)
85
+ try:
86
+ container = self.query_one("#result-pane", VerticalScroll)
87
+ except NoMatches:
88
+ return
89
+ for child in container.children:
90
+ if isinstance(child, ResultCard) and child.task_name == task_name:
91
+ child.update(snippet)
92
+ self._update_result_header()
93
+ return
94
+
95
+ # Remove "no results yet" placeholder
96
+ for child in list(container.children):
97
+ is_placeholder = isinstance(child, Static) and not isinstance(child, PaneWidget)
98
+ if is_placeholder and "no results" in str(getattr(child, "content", "")):
99
+ child.remove()
100
+ break
101
+
102
+ # Append new card
103
+ card = ResultCard(
104
+ snippet,
105
+ task_name=task_name,
106
+ classes="result-card",
107
+ id=uid("rc"),
108
+ markup=True,
109
+ )
110
+ container.mount(card)
111
+ self._update_result_header()
112
+
113
+ def _update_result_header(self) -> None:
114
+ """Update the result pane header count without rebuilding."""
115
+ executions = [e for e in self._vm.all_executions() if e.status in ("completed", "failed")]
116
+ count = len(executions)
117
+ header_text = f"[bold]Results[/bold] [dim]({count})[/dim]" if count else "[bold]Results[/bold]"
118
+ try:
119
+ container = self.query_one("#result-pane", VerticalScroll)
120
+ except NoMatches:
121
+ return
122
+ for child in container.children:
123
+ if isinstance(child, Static) and "result-pane-header" in (child.classes or set()):
124
+ child.update(header_text)
125
+ break
@@ -0,0 +1,91 @@
1
+ """Task pane — rebuild and targeted card updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from textual.containers import VerticalScroll
8
+ from textual.css.query import NoMatches
9
+ from textual.widgets import Static
10
+
11
+ from dataeval_flow._app._panes._widgets import TaskCard, TaskPaneHeader, uid
12
+ from dataeval_flow._app._viewmodel._rendering import snippet_task_with_execution
13
+
14
+ if TYPE_CHECKING:
15
+ from textual.app import App
16
+
17
+ from dataeval_flow._app._viewmodel._builder_vm import BuilderViewModel
18
+ else:
19
+ App = object
20
+
21
+
22
+ class TaskPaneMixin(App):
23
+ """Mixin providing task pane rebuild logic for FlowApp.
24
+
25
+ Expects the host class to also inherit from ``App`` and to provide ``_vm``.
26
+ """
27
+
28
+ if TYPE_CHECKING:
29
+ _vm: BuilderViewModel
30
+
31
+ def _rebuild_task_pane(self) -> None:
32
+ """Full rebuild of the task pane (structural changes only)."""
33
+ try:
34
+ container = self.query_one("#task-pane", VerticalScroll)
35
+ except NoMatches:
36
+ return
37
+
38
+ focused = self.focused
39
+ restore_index: int | None = None
40
+ if isinstance(focused, TaskCard):
41
+ restore_index = focused.fc_index
42
+
43
+ container.remove_children()
44
+ tasks = self._vm.items("tasks")
45
+ header = TaskPaneHeader(
46
+ f"[bold]Tasks[/bold] [dim]({len(tasks)})[/dim]",
47
+ classes="task-pane-header",
48
+ id=uid("th"),
49
+ markup=True,
50
+ )
51
+ container.mount(header)
52
+
53
+ if not tasks:
54
+ container.mount(Static("[dim] (no tasks defined)[/dim]"))
55
+ else:
56
+ for idx, task in enumerate(tasks):
57
+ task_name = task.get("name", "")
58
+ execution = self._vm.task_execution(task_name)
59
+ snippet = snippet_task_with_execution(task, execution)
60
+ card = TaskCard(
61
+ snippet,
62
+ index=idx,
63
+ task_name=task_name,
64
+ classes="task-card",
65
+ id=uid("tc"),
66
+ markup=True,
67
+ )
68
+ container.mount(card)
69
+
70
+ if restore_index is not None:
71
+ target = next(
72
+ (w for w in container.children if isinstance(w, TaskCard) and w.fc_index == restore_index), None
73
+ )
74
+ if target is not None:
75
+ self.set_focus(target)
76
+
77
+ def _update_task_card_by_name(self, task_name: str) -> None:
78
+ """Update a single task card's content without rebuilding the pane."""
79
+ try:
80
+ container = self.query_one("#task-pane", VerticalScroll)
81
+ except NoMatches:
82
+ return
83
+ for child in container.children:
84
+ if isinstance(child, TaskCard) and child.task_name == task_name:
85
+ execution = self._vm.task_execution(task_name)
86
+ task = next((t for t in self._vm.items("tasks") if t.get("name") == task_name), None)
87
+ if task is not None:
88
+ child.update(snippet_task_with_execution(task, execution))
89
+ return
90
+ # Card not found — structural change happened, full rebuild
91
+ self._rebuild_task_pane()
@@ -0,0 +1,111 @@
1
+ """Focusable widget classes shared across panes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from textual.widgets import Static
8
+
9
+ # Pane IDs used for Tab-cycling between panes
10
+ PANE_IDS = ("config-pane", "task-pane", "result-pane")
11
+
12
+ _CONFIG_SECTIONS = [
13
+ ("datasets", "Datasets"),
14
+ ("selections", "Selections"),
15
+ ("sources", "Sources"),
16
+ ("preprocessors", "Preprocessors"),
17
+ ("extractors", "Extractors"),
18
+ ("workflows", "Workflows"),
19
+ ]
20
+
21
+ SECTION_TITLES: dict[str, str] = dict(_CONFIG_SECTIONS)
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Global unique-ID factory — avoids Textual DuplicateIds on rebuild
25
+ # ---------------------------------------------------------------------------
26
+
27
+ _next_uid: int = 0
28
+
29
+
30
+ def uid(prefix: str) -> str:
31
+ """Return a globally unique widget ID like ``prefix-42``."""
32
+ global _next_uid
33
+ _next_uid += 1
34
+ return f"{prefix}-{_next_uid}"
35
+
36
+
37
+ # ---------------------------------------------------------------------------
38
+ # Focusable pane widgets
39
+ # ---------------------------------------------------------------------------
40
+
41
+
42
+ class PaneWidget(Static):
43
+ """Base for focusable widgets that know which pane they live in."""
44
+
45
+ can_focus = True
46
+ pane: str = ""
47
+
48
+ def key_down(self) -> None:
49
+ self.app._focus_within_pane(self.pane, 1) # type: ignore[attr-defined] # noqa: SLF001
50
+
51
+ def key_up(self) -> None:
52
+ self.app._focus_within_pane(self.pane, -1) # type: ignore[attr-defined] # noqa: SLF001
53
+
54
+ def key_tab(self) -> None:
55
+ self.app._cycle_pane(1) # type: ignore[attr-defined] # noqa: SLF001
56
+
57
+ def key_shift_tab(self) -> None:
58
+ self.app._cycle_pane(-1) # type: ignore[attr-defined] # noqa: SLF001
59
+
60
+ async def _on_key(self, event: Any) -> None:
61
+ if event.key in ("tab", "shift+tab", "down", "up"):
62
+ event.stop()
63
+ event.prevent_default()
64
+ await super()._on_key(event)
65
+
66
+
67
+ class CfgSectionHeader(PaneWidget):
68
+ pane = "config-pane"
69
+
70
+ def __init__(self, content: str, category: str, **kw: Any) -> None:
71
+ super().__init__(content, **kw)
72
+ self.fc_category = category
73
+
74
+
75
+ class CfgItem(PaneWidget):
76
+ pane = "config-pane"
77
+
78
+ def __init__(self, content: str, category: str, index: int, **kw: Any) -> None:
79
+ super().__init__(content, **kw)
80
+ self.fc_category = category
81
+ self.fc_index = index
82
+
83
+
84
+ class TaskPaneHeader(PaneWidget):
85
+ pane = "task-pane"
86
+
87
+ def __init__(self, content: str, **kw: Any) -> None:
88
+ super().__init__(content, **kw)
89
+ self.fc_category = "tasks"
90
+
91
+
92
+ class TaskCard(PaneWidget):
93
+ pane = "task-pane"
94
+
95
+ def __init__(self, content: str, index: int, task_name: str = "", **kw: Any) -> None:
96
+ super().__init__(content, **kw)
97
+ self.fc_category = "tasks"
98
+ self.fc_index = index
99
+ self.task_name = task_name
100
+
101
+
102
+ class ResultPaneHeader(PaneWidget):
103
+ pane = "result-pane"
104
+
105
+
106
+ class ResultCard(PaneWidget):
107
+ pane = "result-pane"
108
+
109
+ def __init__(self, content: str, task_name: str, **kw: Any) -> None:
110
+ super().__init__(content, **kw)
111
+ self.task_name = task_name
@@ -0,0 +1,25 @@
1
+ """Screen and modal components for the TUI app."""
2
+
3
+ from dataeval_flow._app._screens._base import _MODAL_CSS, ComponentModal, _select_value
4
+ from dataeval_flow._app._screens._detail import ErrorDetailModal, ResultDetailModal
5
+ from dataeval_flow._app._screens._model import ModelModal
6
+ from dataeval_flow._app._screens._params import build_param_form, collect_param_form, validate_param_form
7
+ from dataeval_flow._app._screens._pathpicker import PathPickerScreen
8
+ from dataeval_flow._app._screens._section import SectionModal
9
+ from dataeval_flow._app._screens._settings import ExecutionSettings, SettingsModal
10
+
11
+ __all__ = [
12
+ "ComponentModal",
13
+ "ErrorDetailModal",
14
+ "ExecutionSettings",
15
+ "ModelModal",
16
+ "PathPickerScreen",
17
+ "ResultDetailModal",
18
+ "SectionModal",
19
+ "SettingsModal",
20
+ "_MODAL_CSS",
21
+ "_select_value",
22
+ "build_param_form",
23
+ "collect_param_form",
24
+ "validate_param_form",
25
+ ]