yera 0.1.0__py3-none-any.whl → 0.2.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.
- infra_mvp/base_client.py +29 -0
- infra_mvp/base_server.py +68 -0
- infra_mvp/monitoring/__init__.py +15 -0
- infra_mvp/monitoring/metrics.py +185 -0
- infra_mvp/stream/README.md +56 -0
- infra_mvp/stream/__init__.py +14 -0
- infra_mvp/stream/__main__.py +101 -0
- infra_mvp/stream/agents/demos/financial/chart_additions_plan.md +170 -0
- infra_mvp/stream/agents/demos/financial/portfolio_assistant_stream.json +1571 -0
- infra_mvp/stream/agents/reference/blocks/action.json +170 -0
- infra_mvp/stream/agents/reference/blocks/button.json +66 -0
- infra_mvp/stream/agents/reference/blocks/date.json +65 -0
- infra_mvp/stream/agents/reference/blocks/input_prompt.json +94 -0
- infra_mvp/stream/agents/reference/blocks/layout.json +288 -0
- infra_mvp/stream/agents/reference/blocks/markdown.json +344 -0
- infra_mvp/stream/agents/reference/blocks/slider.json +67 -0
- infra_mvp/stream/agents/reference/blocks/spinner.json +110 -0
- infra_mvp/stream/agents/reference/blocks/table.json +56 -0
- infra_mvp/stream/agents/reference/chat_dynamics/branching_test_stream.json +145 -0
- infra_mvp/stream/app.py +49 -0
- infra_mvp/stream/container.py +112 -0
- infra_mvp/stream/schemas/__init__.py +16 -0
- infra_mvp/stream/schemas/agent.py +24 -0
- infra_mvp/stream/schemas/interaction.py +28 -0
- infra_mvp/stream/schemas/session.py +30 -0
- infra_mvp/stream/server.py +321 -0
- infra_mvp/stream/services/__init__.py +12 -0
- infra_mvp/stream/services/agent_service.py +40 -0
- infra_mvp/stream/services/event_converter.py +83 -0
- infra_mvp/stream/services/session_service.py +247 -0
- yera/__init__.py +50 -1
- yera/agents/__init__.py +2 -0
- yera/agents/context.py +41 -0
- yera/agents/dataclasses.py +69 -0
- yera/agents/decorator.py +207 -0
- yera/agents/discovery.py +124 -0
- yera/agents/typing/__init__.py +0 -0
- yera/agents/typing/coerce.py +408 -0
- yera/agents/typing/utils.py +19 -0
- yera/agents/typing/validate.py +206 -0
- yera/cli.py +377 -0
- yera/config/__init__.py +1 -0
- yera/config/config_utils.py +164 -0
- yera/config/function_config.py +55 -0
- yera/config/logging.py +18 -0
- yera/config/tool_config.py +8 -0
- yera/config2/__init__.py +8 -0
- yera/config2/dataclasses.py +534 -0
- yera/config2/keyring.py +270 -0
- yera/config2/paths.py +28 -0
- yera/config2/read.py +113 -0
- yera/config2/setup.py +109 -0
- yera/config2/setup_handlers/__init__.py +1 -0
- yera/config2/setup_handlers/anthropic.py +126 -0
- yera/config2/setup_handlers/azure.py +236 -0
- yera/config2/setup_handlers/base.py +125 -0
- yera/config2/setup_handlers/llama_cpp.py +205 -0
- yera/config2/setup_handlers/ollama.py +157 -0
- yera/config2/setup_handlers/openai.py +137 -0
- yera/config2/write.py +87 -0
- yera/dsl/__init__.py +0 -0
- yera/dsl/functions.py +94 -0
- yera/dsl/struct.py +20 -0
- yera/dsl/workspace.py +79 -0
- yera/events/__init__.py +57 -0
- yera/events/blocks/__init__.py +68 -0
- yera/events/blocks/action.py +57 -0
- yera/events/blocks/bar_chart.py +92 -0
- yera/events/blocks/base/__init__.py +20 -0
- yera/events/blocks/base/base.py +166 -0
- yera/events/blocks/base/chart.py +288 -0
- yera/events/blocks/base/layout.py +111 -0
- yera/events/blocks/buttons.py +37 -0
- yera/events/blocks/columns.py +26 -0
- yera/events/blocks/container.py +24 -0
- yera/events/blocks/date_picker.py +50 -0
- yera/events/blocks/exit.py +39 -0
- yera/events/blocks/form.py +24 -0
- yera/events/blocks/input_echo.py +22 -0
- yera/events/blocks/input_request.py +31 -0
- yera/events/blocks/line_chart.py +97 -0
- yera/events/blocks/markdown.py +67 -0
- yera/events/blocks/slider.py +54 -0
- yera/events/blocks/spinner.py +55 -0
- yera/events/blocks/system_prompt.py +22 -0
- yera/events/blocks/table.py +291 -0
- yera/events/models/__init__.py +39 -0
- yera/events/models/block_data.py +112 -0
- yera/events/models/in_event.py +7 -0
- yera/events/models/out_event.py +75 -0
- yera/events/runtime.py +187 -0
- yera/events/stream.py +91 -0
- yera/models/__init__.py +0 -0
- yera/models/data_classes.py +20 -0
- yera/models/llm_atlas_proxy.py +44 -0
- yera/models/llm_context.py +99 -0
- yera/models/llm_interfaces/__init__.py +0 -0
- yera/models/llm_interfaces/anthropic.py +153 -0
- yera/models/llm_interfaces/aws_bedrock.py +14 -0
- yera/models/llm_interfaces/azure_openai.py +143 -0
- yera/models/llm_interfaces/base.py +26 -0
- yera/models/llm_interfaces/interface_registry.py +74 -0
- yera/models/llm_interfaces/llama_cpp.py +136 -0
- yera/models/llm_interfaces/mock.py +29 -0
- yera/models/llm_interfaces/ollama_interface.py +118 -0
- yera/models/llm_interfaces/open_ai.py +150 -0
- yera/models/llm_workspace.py +19 -0
- yera/models/model_atlas.py +139 -0
- yera/models/model_definition.py +38 -0
- yera/models/model_factory.py +33 -0
- yera/opaque/__init__.py +9 -0
- yera/opaque/base.py +20 -0
- yera/opaque/decorator.py +8 -0
- yera/opaque/markdown.py +57 -0
- yera/opaque/opaque_function.py +25 -0
- yera/tools/__init__.py +29 -0
- yera/tools/atlas_tool.py +20 -0
- yera/tools/base.py +24 -0
- yera/tools/decorated_tool.py +18 -0
- yera/tools/decorator.py +35 -0
- yera/tools/tool_atlas.py +51 -0
- yera/tools/tool_utils.py +361 -0
- yera/ui/dist/404.html +1 -0
- yera/ui/dist/__next.__PAGE__.txt +10 -0
- yera/ui/dist/__next._full.txt +23 -0
- yera/ui/dist/__next._head.txt +6 -0
- yera/ui/dist/__next._index.txt +5 -0
- yera/ui/dist/__next._tree.txt +7 -0
- yera/ui/dist/_next/static/chunks/4c4688e1ff21ad98.js +1 -0
- yera/ui/dist/_next/static/chunks/652cd53c27924d50.js +4 -0
- yera/ui/dist/_next/static/chunks/786d2107b51e8499.css +1 -0
- yera/ui/dist/_next/static/chunks/7de9141b1af425c3.js +1 -0
- yera/ui/dist/_next/static/chunks/87ef65064d3524c1.js +2 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- yera/ui/dist/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- yera/ui/dist/_next/static/chunks/c4c79d5d0b280aeb.js +1 -0
- yera/ui/dist/_next/static/chunks/dc2d2a247505d66f.css +5 -0
- yera/ui/dist/_next/static/chunks/f773f714b55ec620.js +37 -0
- yera/ui/dist/_next/static/chunks/turbopack-98b3031e1b1dbc33.js +4 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_buildManifest.js +11 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_clientMiddlewareManifest.json +1 -0
- yera/ui/dist/_next/static/lnhYLzJ1-a5EfNbW1uFF6/_ssgManifest.js +1 -0
- yera/ui/dist/_next/static/media/14e23f9b59180572-s.9c448f3c.woff2 +0 -0
- yera/ui/dist/_next/static/media/2a65768255d6b625-s.p.d19752fb.woff2 +0 -0
- yera/ui/dist/_next/static/media/2b2eb4836d2dad95-s.f36de3af.woff2 +0 -0
- yera/ui/dist/_next/static/media/31183d9fd602dc89-s.c4ff9b73.woff2 +0 -0
- yera/ui/dist/_next/static/media/3fcb63a1ac6a562e-s.2f77a576.woff2 +0 -0
- yera/ui/dist/_next/static/media/45ec8de98929b0f6-s.81056204.woff2 +0 -0
- yera/ui/dist/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- yera/ui/dist/_next/static/media/65c558afe41e89d6-s.e2c8389a.woff2 +0 -0
- yera/ui/dist/_next/static/media/67add6cc0f54b8cf-s.8ce53448.woff2 +0 -0
- yera/ui/dist/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- yera/ui/dist/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- yera/ui/dist/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- yera/ui/dist/_next/static/media/a8ff2d5d0ccb0d12-s.fc5b72a7.woff2 +0 -0
- yera/ui/dist/_next/static/media/aae5f0be330e13db-s.p.853e26d6.woff2 +0 -0
- yera/ui/dist/_next/static/media/b11a6ccf4a3edec7-s.2113d282.woff2 +0 -0
- yera/ui/dist/_next/static/media/b49b0d9b851e4899-s.4f3fa681.woff2 +0 -0
- yera/ui/dist/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- yera/ui/dist/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- yera/ui/dist/_next/static/media/favicon.0b3bf435.ico +0 -0
- yera/ui/dist/_not-found/__next._full.txt +14 -0
- yera/ui/dist/_not-found/__next._head.txt +6 -0
- yera/ui/dist/_not-found/__next._index.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.__PAGE__.txt +5 -0
- yera/ui/dist/_not-found/__next._not-found.txt +4 -0
- yera/ui/dist/_not-found/__next._tree.txt +2 -0
- yera/ui/dist/_not-found.html +1 -0
- yera/ui/dist/_not-found.txt +14 -0
- yera/ui/dist/agent-icon.svg +3 -0
- yera/ui/dist/favicon.ico +0 -0
- yera/ui/dist/file.svg +1 -0
- yera/ui/dist/globe.svg +1 -0
- yera/ui/dist/index.html +1 -0
- yera/ui/dist/index.txt +23 -0
- yera/ui/dist/logo/full_logo.png +0 -0
- yera/ui/dist/logo/rune_logo.png +0 -0
- yera/ui/dist/logo/rune_logo_borderless.png +0 -0
- yera/ui/dist/logo/text_logo.png +0 -0
- yera/ui/dist/next.svg +1 -0
- yera/ui/dist/send.png +0 -0
- yera/ui/dist/send_single.png +0 -0
- yera/ui/dist/vercel.svg +1 -0
- yera/ui/dist/window.svg +1 -0
- yera/utils/__init__.py +1 -0
- yera/utils/path_utils.py +38 -0
- yera-0.2.0.dist-info/METADATA +65 -0
- yera-0.2.0.dist-info/RECORD +190 -0
- {yera-0.1.0.dist-info → yera-0.2.0.dist-info}/WHEEL +1 -1
- yera-0.2.0.dist-info/entry_points.txt +2 -0
- yera-0.1.0.dist-info/METADATA +0 -11
- yera-0.1.0.dist-info/RECORD +0 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Chat-specific block construction helpers for the Yera library.
|
|
2
|
+
|
|
3
|
+
This package provides block construction functions that can be used either via
|
|
4
|
+
the events namespace:
|
|
5
|
+
|
|
6
|
+
import yera
|
|
7
|
+
|
|
8
|
+
yera.events.markdown(...)
|
|
9
|
+
yera.events.buttons(...)
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
or via the top-level re-exports:
|
|
13
|
+
|
|
14
|
+
import yera as yr
|
|
15
|
+
|
|
16
|
+
yr.markdown(...)
|
|
17
|
+
yr.buttons(...)
|
|
18
|
+
...
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from yera.events.blocks.action import action
|
|
24
|
+
from yera.events.blocks.bar_chart import bar_chart
|
|
25
|
+
from yera.events.blocks.base.base import _BlockFactory
|
|
26
|
+
from yera.events.blocks.buttons import request_input_buttons as request_input_buttons
|
|
27
|
+
from yera.events.blocks.columns import columns
|
|
28
|
+
from yera.events.blocks.container import container
|
|
29
|
+
from yera.events.blocks.date_picker import (
|
|
30
|
+
request_input_date_picker as request_input_date_picker,
|
|
31
|
+
)
|
|
32
|
+
from yera.events.blocks.exit import exit_event, quit_event
|
|
33
|
+
from yera.events.blocks.form import form
|
|
34
|
+
from yera.events.blocks.input_echo import input_echo as input_echo
|
|
35
|
+
from yera.events.blocks.input_request import request_input_text as request_input_text
|
|
36
|
+
from yera.events.blocks.line_chart import line_chart
|
|
37
|
+
from yera.events.blocks.markdown import markdown
|
|
38
|
+
from yera.events.blocks.slider import request_input_slider as request_input_slider
|
|
39
|
+
from yera.events.blocks.spinner import spinner
|
|
40
|
+
from yera.events.blocks.system_prompt import system_prompt as system_prompt
|
|
41
|
+
from yera.events.blocks.table import table
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def reset_all_factories() -> None:
|
|
45
|
+
"""Reset all block factory counters to 0."""
|
|
46
|
+
_BlockFactory.reset_all()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"action",
|
|
51
|
+
"bar_chart",
|
|
52
|
+
"columns",
|
|
53
|
+
"container",
|
|
54
|
+
"exit_event",
|
|
55
|
+
"form",
|
|
56
|
+
"input_echo",
|
|
57
|
+
"line_chart",
|
|
58
|
+
"markdown",
|
|
59
|
+
"quit_event",
|
|
60
|
+
"request_input_buttons",
|
|
61
|
+
"request_input_date_picker",
|
|
62
|
+
"request_input_slider",
|
|
63
|
+
"request_input_text",
|
|
64
|
+
"reset_all_factories",
|
|
65
|
+
"spinner",
|
|
66
|
+
"system_prompt",
|
|
67
|
+
"table",
|
|
68
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Action block implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from yera.events.blocks.base import _BlockFactory, _StreamHandle
|
|
6
|
+
from yera.events.models import ActionData
|
|
7
|
+
from yera.events.stream import push_output
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ActionStream(_StreamHandle):
|
|
11
|
+
"""Stream handle for creating multiple chunks of the same action block."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, block_id: str, initial_message: str):
|
|
14
|
+
super().__init__(block_id, "action")
|
|
15
|
+
self.initial_message = initial_message
|
|
16
|
+
|
|
17
|
+
def __enter__(self):
|
|
18
|
+
event = self._create_chunk_event(
|
|
19
|
+
data=ActionData(status="active", message=self.initial_message),
|
|
20
|
+
)
|
|
21
|
+
push_output(event)
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
25
|
+
event = self._create_chunk_event(
|
|
26
|
+
data=ActionData(status="complete"),
|
|
27
|
+
)
|
|
28
|
+
push_output(event)
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
def update(self, message: str) -> None:
|
|
32
|
+
"""Create an update chunk with status 'active'."""
|
|
33
|
+
event = self._create_chunk_event(
|
|
34
|
+
data=ActionData(status="active", message=message),
|
|
35
|
+
)
|
|
36
|
+
push_output(event)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _ActionBlockFactory(_BlockFactory):
|
|
40
|
+
"""Factory for action blocks. Returns a context manager when called with message."""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
super().__init__("action")
|
|
44
|
+
|
|
45
|
+
def __call__(
|
|
46
|
+
self,
|
|
47
|
+
message: str,
|
|
48
|
+
) -> ActionStream:
|
|
49
|
+
"""Return a context manager for creating multiple action chunks."""
|
|
50
|
+
block_id = self._generate_block_id()
|
|
51
|
+
return ActionStream(block_id, message)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Create the action callable/context manager
|
|
55
|
+
action = _ActionBlockFactory()
|
|
56
|
+
|
|
57
|
+
__all__ = ["action"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Bar chart block implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from yera.events.blocks.base import _ChartBlockFactory
|
|
12
|
+
from yera.events.blocks.base.chart import _ChartConversionData
|
|
13
|
+
from yera.events.models import BarChartData
|
|
14
|
+
from yera.events.stream import push_output
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _BarChartBlockFactory(_ChartBlockFactory):
|
|
18
|
+
"""Factory for creating bar chart blocks, matching Streamlit's st.bar_chart API."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__("bar_chart")
|
|
22
|
+
|
|
23
|
+
def _create_chart_data_from_conversion(
|
|
24
|
+
self,
|
|
25
|
+
conversion_data: _ChartConversionData,
|
|
26
|
+
chart_specific_params: dict,
|
|
27
|
+
) -> BarChartData:
|
|
28
|
+
"""Convert intermediate data to BarChartData."""
|
|
29
|
+
return BarChartData(
|
|
30
|
+
x_label=conversion_data.x_label,
|
|
31
|
+
x_categories=conversion_data.x_categories or [],
|
|
32
|
+
y_label=conversion_data.y_label,
|
|
33
|
+
y_values=conversion_data.y_values,
|
|
34
|
+
colour_label=conversion_data.colour_label,
|
|
35
|
+
colour_categories=conversion_data.colour_categories,
|
|
36
|
+
colour_values=conversion_data.colour_values,
|
|
37
|
+
horizontal=chart_specific_params.get("horizontal", False),
|
|
38
|
+
stack=chart_specific_params.get("stack", True),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def __call__(
|
|
42
|
+
self,
|
|
43
|
+
data: pd.DataFrame,
|
|
44
|
+
*,
|
|
45
|
+
x: str | None = None,
|
|
46
|
+
y: str | Sequence[str] | None = None,
|
|
47
|
+
colour: str | Sequence[str] | None = None,
|
|
48
|
+
horizontal: bool = False,
|
|
49
|
+
stack: bool = True,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Create a bar chart block, matching Streamlit's st.bar_chart() signature.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data: DataFrame to plot
|
|
55
|
+
x: Column name for x-axis (None = use index)
|
|
56
|
+
y: Column name(s) for y-axis (None = use all columns except x)
|
|
57
|
+
colour: Column name for colour grouping, or list of colors
|
|
58
|
+
horizontal: Whether bars are horizontal
|
|
59
|
+
stack: Whether bars are stacked
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
None (informational block)
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
chart_specific_params = {"horizontal": horizontal, "stack": stack}
|
|
66
|
+
|
|
67
|
+
if x is None and y is None:
|
|
68
|
+
conversion_data = self._convert_default_format(data)
|
|
69
|
+
elif x is not None and isinstance(y, str):
|
|
70
|
+
conversion_data = self._convert_long_format(data, x, y, colour)
|
|
71
|
+
elif x is not None and isinstance(y, Sequence) and not isinstance(y, str):
|
|
72
|
+
conversion_data = self._convert_wide_format(data, x, y, colour)
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"Unsupported bar_chart parameter combination: x={x}, y={y}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
chart_data = self._create_chart_data_from_conversion(
|
|
79
|
+
conversion_data, chart_specific_params
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
event = self._create_block_output_event(
|
|
83
|
+
chart_data, message_type="informational"
|
|
84
|
+
)
|
|
85
|
+
push_output(event)
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Create the bar_chart callable
|
|
90
|
+
bar_chart = _BarChartBlockFactory()
|
|
91
|
+
|
|
92
|
+
__all__ = ["bar_chart"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Base classes for block construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from yera.events.blocks.base.base import _BlockFactory, _StreamHandle
|
|
6
|
+
from yera.events.blocks.base.chart import _ChartBlockFactory
|
|
7
|
+
from yera.events.blocks.base.layout import (
|
|
8
|
+
LayoutContext,
|
|
9
|
+
LayoutHandle,
|
|
10
|
+
_current_layout,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"LayoutContext",
|
|
15
|
+
"LayoutHandle",
|
|
16
|
+
"_BlockFactory",
|
|
17
|
+
"_ChartBlockFactory",
|
|
18
|
+
"_StreamHandle",
|
|
19
|
+
"_current_layout",
|
|
20
|
+
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Base classes for block construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import ClassVar
|
|
6
|
+
|
|
7
|
+
from yera.agents import get_agent_context
|
|
8
|
+
from yera.events.models import (
|
|
9
|
+
ActionData,
|
|
10
|
+
BarChartData,
|
|
11
|
+
ButtonsData,
|
|
12
|
+
ColumnsData,
|
|
13
|
+
ContainerData,
|
|
14
|
+
DatePickerData,
|
|
15
|
+
ExitEventData,
|
|
16
|
+
InputRequestData,
|
|
17
|
+
LayoutEndData,
|
|
18
|
+
LineChartData,
|
|
19
|
+
MarkdownData,
|
|
20
|
+
OutputEvent,
|
|
21
|
+
SliderData,
|
|
22
|
+
SpinnerData,
|
|
23
|
+
TableData,
|
|
24
|
+
)
|
|
25
|
+
from yera.events.models.in_event import InputEvent
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_parent_block_id(exclude_block_id: str | None = None) -> str | None:
|
|
29
|
+
"""Get parent block ID from layout context if available."""
|
|
30
|
+
# Import here to avoid circular import
|
|
31
|
+
from yera.events.blocks.base.layout import _current_layout
|
|
32
|
+
|
|
33
|
+
layout_context = _current_layout.get()
|
|
34
|
+
if layout_context and layout_context.block_id != exclude_block_id:
|
|
35
|
+
return layout_context.block_id
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_event(
|
|
40
|
+
block_type: str,
|
|
41
|
+
block_id: str,
|
|
42
|
+
data: (
|
|
43
|
+
MarkdownData
|
|
44
|
+
| ButtonsData
|
|
45
|
+
| ActionData
|
|
46
|
+
| SpinnerData
|
|
47
|
+
| DatePickerData
|
|
48
|
+
| SliderData
|
|
49
|
+
| TableData
|
|
50
|
+
| ContainerData
|
|
51
|
+
| ColumnsData
|
|
52
|
+
| BarChartData
|
|
53
|
+
| LineChartData
|
|
54
|
+
| LayoutEndData
|
|
55
|
+
),
|
|
56
|
+
message_type: str,
|
|
57
|
+
chunk_id: int,
|
|
58
|
+
parent_block_id: str | None = None,
|
|
59
|
+
) -> OutputEvent:
|
|
60
|
+
"""Create an event with automatic layout context detection."""
|
|
61
|
+
agent_ctx = get_agent_context()
|
|
62
|
+
|
|
63
|
+
if parent_block_id is None:
|
|
64
|
+
parent_block_id = get_parent_block_id(exclude_block_id=block_id)
|
|
65
|
+
return OutputEvent(
|
|
66
|
+
message_type=message_type,
|
|
67
|
+
block_type=block_type,
|
|
68
|
+
block_id=block_id,
|
|
69
|
+
data=data,
|
|
70
|
+
chunk_id=chunk_id,
|
|
71
|
+
agent_instance=agent_ctx.metadata.make_instance_id(0),
|
|
72
|
+
parent_block_id=parent_block_id,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _StreamHandle:
|
|
77
|
+
"""Base class for streamable block handles."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, block_id: str, block_type: str):
|
|
80
|
+
self.block_id = block_id
|
|
81
|
+
self.block_type = block_type
|
|
82
|
+
self.chunk_counter = 0
|
|
83
|
+
|
|
84
|
+
def _create_chunk_event(
|
|
85
|
+
self,
|
|
86
|
+
data: (
|
|
87
|
+
MarkdownData
|
|
88
|
+
| ActionData
|
|
89
|
+
| SpinnerData
|
|
90
|
+
| TableData
|
|
91
|
+
| ContainerData
|
|
92
|
+
| ColumnsData
|
|
93
|
+
),
|
|
94
|
+
message_type: str = "informational",
|
|
95
|
+
parent_block_id: str | None = None,
|
|
96
|
+
) -> OutputEvent:
|
|
97
|
+
"""Create a chunk event (for stream handles)."""
|
|
98
|
+
self.chunk_counter += 1
|
|
99
|
+
return create_event(
|
|
100
|
+
block_type=self.block_type,
|
|
101
|
+
block_id=self.block_id,
|
|
102
|
+
data=data,
|
|
103
|
+
message_type=message_type,
|
|
104
|
+
chunk_id=self.chunk_counter,
|
|
105
|
+
parent_block_id=parent_block_id,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _BlockFactory:
|
|
110
|
+
"""Base class for block construction factories."""
|
|
111
|
+
|
|
112
|
+
_registry: ClassVar[list[_BlockFactory]] = [] # Track all factory instances
|
|
113
|
+
|
|
114
|
+
def __init__(self, block_type: str):
|
|
115
|
+
self.block_type = block_type
|
|
116
|
+
self._block_counter = 0
|
|
117
|
+
_BlockFactory._registry.append(self) # Auto-register instance
|
|
118
|
+
|
|
119
|
+
def reset(self) -> None:
|
|
120
|
+
"""Reset the block counter to 0."""
|
|
121
|
+
self._block_counter = 0
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def reset_all(cls) -> None:
|
|
125
|
+
"""Reset all registered factory instances."""
|
|
126
|
+
for factory in cls._registry:
|
|
127
|
+
factory.reset()
|
|
128
|
+
|
|
129
|
+
def _generate_block_id(self) -> str:
|
|
130
|
+
"""Generate a block_id."""
|
|
131
|
+
self._block_counter += 1
|
|
132
|
+
return f"{self.block_type}-{self._block_counter}"
|
|
133
|
+
|
|
134
|
+
def _create_block_input_event(self) -> InputEvent:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def _create_block_output_event(
|
|
138
|
+
self,
|
|
139
|
+
data: (
|
|
140
|
+
MarkdownData
|
|
141
|
+
| ButtonsData
|
|
142
|
+
| ActionData
|
|
143
|
+
| SpinnerData
|
|
144
|
+
| DatePickerData
|
|
145
|
+
| SliderData
|
|
146
|
+
| TableData
|
|
147
|
+
| ContainerData
|
|
148
|
+
| ColumnsData
|
|
149
|
+
| BarChartData
|
|
150
|
+
| LineChartData
|
|
151
|
+
| InputRequestData
|
|
152
|
+
| ExitEventData
|
|
153
|
+
),
|
|
154
|
+
message_type: str,
|
|
155
|
+
) -> OutputEvent:
|
|
156
|
+
"""Create a block event (for factory methods)."""
|
|
157
|
+
return create_event(
|
|
158
|
+
block_type=self.block_type,
|
|
159
|
+
block_id=self._generate_block_id(),
|
|
160
|
+
data=data,
|
|
161
|
+
message_type=message_type,
|
|
162
|
+
chunk_id=1, # Block events are always single chunks
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
__all__ = ["_BlockFactory", "_StreamHandle"]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Base chart block factory for shared chart functionality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from yera.events.blocks.base.base import _BlockFactory
|
|
14
|
+
from yera.events.models import BarChartData, LineChartData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_hex_color(value: str) -> bool:
|
|
18
|
+
"""Check if a string is a valid hex color (starts with # and has valid hex digits).
|
|
19
|
+
|
|
20
|
+
Raises ValueError if the value is not a valid hex color format.
|
|
21
|
+
"""
|
|
22
|
+
if not value.startswith("#"):
|
|
23
|
+
raise ValueError(f"Hex color must start with '#', got: {value!r}")
|
|
24
|
+
if len(value) not in (4, 7): # #rgb or #rrggbb format
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"Hex color must be 4 or 7 characters (#rgb or #rrggbb), got {len(value)} characters: {value!r}"
|
|
27
|
+
)
|
|
28
|
+
# Check that all characters after # are valid hex digits (0-9, a-f, A-F)
|
|
29
|
+
hex_digits = set("0123456789abcdefABCDEF")
|
|
30
|
+
invalid_chars = [c for c in value[1:] if c not in hex_digits]
|
|
31
|
+
if invalid_chars:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Hex color contains invalid characters: {invalid_chars}. "
|
|
34
|
+
f"Only 0-9, a-f, A-F are allowed after '#'. Got: {value!r}"
|
|
35
|
+
)
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_default_colors(count: int) -> list[str]:
|
|
40
|
+
"""Get default colors for the given number of series."""
|
|
41
|
+
default_palette = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF"]
|
|
42
|
+
return [default_palette[i % len(default_palette)] for i in range(count)]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _check_if_colour_column_contains_hex_colors(colour_values: pd.Series) -> bool:
|
|
46
|
+
"""Check if all values in a colour column are valid hex colors."""
|
|
47
|
+
try:
|
|
48
|
+
all(_is_hex_color(str(val)) for val in colour_values)
|
|
49
|
+
return True
|
|
50
|
+
except ValueError:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _build_x_colour_to_y_value_map(
|
|
55
|
+
df: pd.DataFrame, x_column: str, colour_column: str, y_column: str
|
|
56
|
+
) -> dict[tuple[str, str], float | None]:
|
|
57
|
+
"""Build a mapping from (x_category, colour) to y_value."""
|
|
58
|
+
value_map = {}
|
|
59
|
+
for _, row in df.iterrows():
|
|
60
|
+
x_val = str(row[x_column])
|
|
61
|
+
colour_val = str(row[colour_column])
|
|
62
|
+
y_val = row[y_column]
|
|
63
|
+
value_map[(x_val, colour_val)] = float(y_val) if y_val is not None else None
|
|
64
|
+
return value_map
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _extract_y_values_for_x_and_colour_categories(
|
|
68
|
+
x_categories: list[str],
|
|
69
|
+
colour_categories: list[str],
|
|
70
|
+
value_map: dict[tuple[str, str], float | None],
|
|
71
|
+
) -> list[list[float | None]]:
|
|
72
|
+
"""Extract y_values matrix from value map for given x and colour categories."""
|
|
73
|
+
y_values = []
|
|
74
|
+
for x_cat in x_categories:
|
|
75
|
+
row_values = [
|
|
76
|
+
value_map.get((x_cat, colour_cat)) for colour_cat in colour_categories
|
|
77
|
+
]
|
|
78
|
+
y_values.append(row_values)
|
|
79
|
+
return y_values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _simplify_to_single_value_per_x_category(
|
|
83
|
+
y_values: list[list[float | None]],
|
|
84
|
+
) -> list[list[float | None]]:
|
|
85
|
+
"""Simplify y_values when each x-category has only one non-None value."""
|
|
86
|
+
has_single_value_per_x = all(
|
|
87
|
+
sum(1 for v in row if v is not None) == 1 for row in y_values
|
|
88
|
+
)
|
|
89
|
+
if has_single_value_per_x:
|
|
90
|
+
return [[next(v for v in row if v is not None)] for row in y_values]
|
|
91
|
+
return y_values
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _get_y_value_for_x_category(
|
|
95
|
+
df: pd.DataFrame, x_column: str, x_category: str, y_column: str
|
|
96
|
+
) -> float | None:
|
|
97
|
+
"""Get the y value for a specific x category (assumes single match)."""
|
|
98
|
+
mask = df[x_column] == x_category
|
|
99
|
+
matching_rows = df[mask]
|
|
100
|
+
if len(matching_rows) > 0:
|
|
101
|
+
val = matching_rows[y_column].iloc[0]
|
|
102
|
+
return float(val) if val is not None else None
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_y_values_for_x_categories_with_colour_grouping(
|
|
107
|
+
df: pd.DataFrame,
|
|
108
|
+
x_column: str,
|
|
109
|
+
y_column: str,
|
|
110
|
+
colour_column: str,
|
|
111
|
+
x_categories: list[str],
|
|
112
|
+
colour_categories: list[str],
|
|
113
|
+
) -> list[list[float | None]]:
|
|
114
|
+
"""Get y_values matrix grouped by x and colour categories."""
|
|
115
|
+
y_values = []
|
|
116
|
+
for x_cat in x_categories:
|
|
117
|
+
row_values = []
|
|
118
|
+
for colour_cat in colour_categories:
|
|
119
|
+
mask = (df[x_column] == x_cat) & (df[colour_column] == colour_cat)
|
|
120
|
+
matching_rows = df[mask]
|
|
121
|
+
if len(matching_rows) > 0:
|
|
122
|
+
val = matching_rows[y_column].iloc[0]
|
|
123
|
+
row_values.append(float(val) if val is not None else None)
|
|
124
|
+
else:
|
|
125
|
+
row_values.append(None)
|
|
126
|
+
y_values.append(row_values)
|
|
127
|
+
return y_values
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class _ChartConversionData:
|
|
132
|
+
"""Intermediate data structure for chart conversion."""
|
|
133
|
+
|
|
134
|
+
x_label: str | None
|
|
135
|
+
x_categories: list[str] | None
|
|
136
|
+
y_label: str | None
|
|
137
|
+
y_values: list[list[float | None]]
|
|
138
|
+
colour_label: str | None
|
|
139
|
+
colour_categories: list[str]
|
|
140
|
+
colour_values: list[str]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class _ChartBlockFactory(_BlockFactory, ABC):
|
|
144
|
+
"""Base factory for chart blocks that process DataFrames."""
|
|
145
|
+
|
|
146
|
+
def _convert_default_format(
|
|
147
|
+
self,
|
|
148
|
+
df: pd.DataFrame,
|
|
149
|
+
) -> _ChartConversionData:
|
|
150
|
+
"""Convert DataFrame using default format: index as x, all columns as y series."""
|
|
151
|
+
x_categories = [str(idx) for idx in df.index]
|
|
152
|
+
y_columns = list(df.columns)
|
|
153
|
+
y_values = [
|
|
154
|
+
[
|
|
155
|
+
float(df.iloc[i][col]) if df.iloc[i][col] is not None else None
|
|
156
|
+
for col in y_columns
|
|
157
|
+
]
|
|
158
|
+
for i in range(len(df))
|
|
159
|
+
]
|
|
160
|
+
return _ChartConversionData(
|
|
161
|
+
x_label=None,
|
|
162
|
+
x_categories=x_categories,
|
|
163
|
+
y_label=None,
|
|
164
|
+
y_values=y_values,
|
|
165
|
+
colour_label=None,
|
|
166
|
+
colour_categories=y_columns,
|
|
167
|
+
colour_values=_get_default_colors(len(y_columns)),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _convert_long_format(
|
|
171
|
+
self,
|
|
172
|
+
df: pd.DataFrame,
|
|
173
|
+
x_column: str,
|
|
174
|
+
y_column: str,
|
|
175
|
+
colour: str | Sequence[str] | None,
|
|
176
|
+
) -> _ChartConversionData:
|
|
177
|
+
"""Convert DataFrame in long format: x and y columns specified, optional colour grouping."""
|
|
178
|
+
x_categories = [str(val) for val in df[x_column].unique()]
|
|
179
|
+
|
|
180
|
+
if isinstance(colour, str):
|
|
181
|
+
colour_label = colour
|
|
182
|
+
unique_colour_values = df[colour].unique()
|
|
183
|
+
|
|
184
|
+
if _check_if_colour_column_contains_hex_colors(unique_colour_values):
|
|
185
|
+
colour_categories = [str(val) for val in unique_colour_values]
|
|
186
|
+
colour_values = colour_categories.copy()
|
|
187
|
+
|
|
188
|
+
value_map = _build_x_colour_to_y_value_map(
|
|
189
|
+
df, x_column, colour, y_column
|
|
190
|
+
)
|
|
191
|
+
y_values = _extract_y_values_for_x_and_colour_categories(
|
|
192
|
+
x_categories, colour_categories, value_map
|
|
193
|
+
)
|
|
194
|
+
y_values = _simplify_to_single_value_per_x_category(y_values)
|
|
195
|
+
else:
|
|
196
|
+
colour_categories = sorted([str(val) for val in unique_colour_values])
|
|
197
|
+
colour_values = _get_default_colors(len(colour_categories))
|
|
198
|
+
y_values = _get_y_values_for_x_categories_with_colour_grouping(
|
|
199
|
+
df, x_column, y_column, colour, x_categories, colour_categories
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
colour_label = None
|
|
203
|
+
colour_categories = [y_column]
|
|
204
|
+
colour_values = _get_default_colors(1)
|
|
205
|
+
y_values = [
|
|
206
|
+
[_get_y_value_for_x_category(df, x_column, x_cat, y_column)]
|
|
207
|
+
for x_cat in x_categories
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
return _ChartConversionData(
|
|
211
|
+
x_label=x_column,
|
|
212
|
+
x_categories=x_categories,
|
|
213
|
+
y_label=y_column,
|
|
214
|
+
y_values=y_values,
|
|
215
|
+
colour_label=colour_label,
|
|
216
|
+
colour_categories=colour_categories,
|
|
217
|
+
colour_values=colour_values,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _convert_wide_format(
|
|
221
|
+
self,
|
|
222
|
+
df: pd.DataFrame,
|
|
223
|
+
x_column: str | None,
|
|
224
|
+
y_columns: Sequence[str],
|
|
225
|
+
colour: str | Sequence[str] | None,
|
|
226
|
+
) -> _ChartConversionData:
|
|
227
|
+
"""Convert DataFrame in wide format: x column specified (or None), multiple y columns."""
|
|
228
|
+
if x_column is not None:
|
|
229
|
+
x_categories = [str(val) for val in df[x_column].unique()]
|
|
230
|
+
x_label = x_column
|
|
231
|
+
else:
|
|
232
|
+
# When x is None but y is specified, use index
|
|
233
|
+
x_categories = [str(idx) for idx in df.index]
|
|
234
|
+
x_label = None
|
|
235
|
+
|
|
236
|
+
if isinstance(colour, Sequence) and not isinstance(colour, str):
|
|
237
|
+
colour_values = list(colour)
|
|
238
|
+
colour_categories = list(y_columns)
|
|
239
|
+
else:
|
|
240
|
+
colour_values = _get_default_colors(len(y_columns))
|
|
241
|
+
colour_categories = list(y_columns)
|
|
242
|
+
|
|
243
|
+
y_values = []
|
|
244
|
+
if x_column is not None:
|
|
245
|
+
for x_cat in x_categories:
|
|
246
|
+
mask = df[x_column] == x_cat
|
|
247
|
+
matching_rows = df[mask]
|
|
248
|
+
if len(matching_rows) > 0:
|
|
249
|
+
row = matching_rows.iloc[0]
|
|
250
|
+
y_values.append(
|
|
251
|
+
[
|
|
252
|
+
float(row[col]) if row[col] is not None else None
|
|
253
|
+
for col in y_columns
|
|
254
|
+
]
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
y_values.append([None] * len(y_columns))
|
|
258
|
+
else:
|
|
259
|
+
# Use index for x
|
|
260
|
+
for i in range(len(df)):
|
|
261
|
+
row = df.iloc[i]
|
|
262
|
+
y_values.append(
|
|
263
|
+
[
|
|
264
|
+
float(row[col]) if row[col] is not None else None
|
|
265
|
+
for col in y_columns
|
|
266
|
+
]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return _ChartConversionData(
|
|
270
|
+
x_label=x_label,
|
|
271
|
+
x_categories=x_categories,
|
|
272
|
+
y_label=None,
|
|
273
|
+
y_values=y_values,
|
|
274
|
+
colour_label=None,
|
|
275
|
+
colour_categories=colour_categories,
|
|
276
|
+
colour_values=colour_values,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
@abstractmethod
|
|
280
|
+
def _create_chart_data_from_conversion(
|
|
281
|
+
self,
|
|
282
|
+
conversion_data: _ChartConversionData,
|
|
283
|
+
chart_specific_params: dict,
|
|
284
|
+
) -> BarChartData | LineChartData:
|
|
285
|
+
"""Convert intermediate data to specific chart data type. Override in subclasses."""
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
__all__ = ["_ChartBlockFactory"]
|