Spaces:
Running
Running
"""Contains custom components used within a dashboard.""" | |
from typing import Literal | |
import black | |
import dash_bootstrap_components as dbc | |
import vizro.models as vm | |
from dash import dcc, get_asset_url, html | |
from pydantic import PrivateAttr | |
from vizro.models import Action | |
from vizro.models._action._actions_chain import _action_validator_factory | |
from vizro.models._models_utils import _log_call | |
from vizro.models.types import capture | |
class UserPromptTextArea(vm.VizroBaseModel): | |
"""Input component `UserPromptTextArea`. | |
Based on the underlying [`dcc.Input`](https://dash.plotly.com/dash-core-components/input). | |
Args: | |
type (Literal["user_input"]): Defaults to `"user_text_area"`. | |
title (str): Title to be displayed. Defaults to `""`. | |
placeholder (str): Default text to display in input field. Defaults to `""`. | |
actions (Optional[list[Action]]): Defaults to `[]`. | |
""" | |
type: Literal["user_text_area"] = "user_text_area" | |
actions: list[Action] = [] # noqa: RUF012 | |
_set_actions = _action_validator_factory("value") | |
def build(self): | |
"""Returns the text area component to display vizro-ai code output.""" | |
return html.Div( | |
children=[ | |
dcc.Textarea( | |
id=self.id, | |
placeholder="Describe the chart you want to create, e.g. " | |
"'Visualize the life expectancy per continent.'", | |
) | |
] | |
) | |
class UserUpload(vm.VizroBaseModel): | |
"""Component enabling data upload. | |
Args: | |
type (Literal["upload"]): Defaults to `"upload"`. | |
title (str): Title to be displayed. | |
actions (list[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. | |
""" | |
type: Literal["upload"] = "upload" | |
actions: list[Action] = [] # noqa: RUF012 | |
# 'contents' property is input to custom action callback | |
_input_property: str = PrivateAttr("contents") | |
# change in 'contents' property of Upload component triggers the actions | |
_set_actions = _action_validator_factory("contents") | |
def build(self): | |
"""Returns the upload component for data upload.""" | |
return html.Div( | |
[ | |
dcc.Upload( | |
id=self.id, | |
children=html.Div(["Drag and Drop or ", html.A("Select Files")], id="data-upload"), | |
), | |
] | |
) | |
class CodeClipboard(vm.VizroBaseModel): | |
"""Code snippet with a copy to clipboard button.""" | |
type: Literal["code_clipboard"] = "code_clipboard" | |
code: str = "" | |
language: str = "python" | |
def build(self): | |
"""Returns the code clipboard component inside a output text area.""" | |
code = black.format_str(self.code, mode=black.Mode(line_length=120)) | |
code = code.strip("'\"") | |
markdown_code = "\n".join(["```python", code, "```"]) | |
return html.Div( | |
[ | |
dcc.Clipboard(target_id=f"{self.id}-code-markdown", className="code-clipboard"), | |
dcc.Markdown(markdown_code, id=f"{self.id}-code-markdown"), | |
], | |
className="code-clipboard-container", | |
) | |
class MyDropdown(vm.Dropdown): | |
"""Custom dropdown component.""" | |
type: Literal["my_dropdown"] = "my_dropdown" | |
def build(self): | |
"""Returns custom dropdown component that cannot be cleared.""" | |
dropdown_build_obj = super().build() | |
dropdown_build_obj.id = f"{self.id}_outer_div" | |
dropdown_build_obj.children[1].clearable = False | |
return dropdown_build_obj | |
class Modal(vm.VizroBaseModel): | |
"""Modal to convey warning message.""" | |
type: Literal["modal"] = "modal" | |
def build(self): | |
"""Returns the modal component.""" | |
return dbc.Modal( | |
# id=self.id, | |
children=[ | |
dbc.ModalHeader(children=dcc.Markdown("""# Warning""")), | |
dbc.ModalBody( | |
children=dcc.Markdown( | |
"""### Do NOT upload any sensitive or personally identifying data. | |
#### Reasoning: | |
This space is hosted publicly running one server in a single container. Further this UI executes dynamically created | |
code on the server. It thus cannot guarantee the security of your data. In addition it sends the user query and the | |
data to the chosen LLM vendor API. This is not an exhaustive list. | |
#### Alternatives: | |
If sending your query and data to a LLM is acceptable, you can pull and run this image locally. This will avoid sharing | |
an instance with others. You can do so by clicking the three dots in the top right of the HuggingFace banner | |
and click `Run with Docker`. | |
Always exercise caution when sharing data online and understand your responsibilities regarding data privacy | |
and security. | |
""" | |
) | |
), | |
], | |
size="l", | |
is_open=True, | |
) | |
def create_provider_item(name, url, note=None): | |
"""Helper function to create a consistent ListGroupItem for each provider.""" | |
return dbc.ListGroupItem( | |
[ | |
html.Div( | |
[ | |
html.Span(name, style={"color": "#ffffff"}), | |
(html.Small(note, style={"color": "rgba(255, 255, 255, 0.5)"}) if note else None), | |
html.Span("→", className="float-end", style={"color": "#ffffff"}), | |
], | |
className="d-flex justify-content-between align-items-center", | |
) | |
], | |
href=url, | |
target="_blank", | |
action=True, | |
style={ | |
"background-color": "transparent", | |
"border": "1px solid rgba(255, 255, 255, 0.1)", | |
"margin-bottom": "8px", | |
"transition": "all 0.2s ease", | |
"cursor": "pointer", | |
}, | |
class_name="list-group-item-action hover-effect", | |
) | |
class OffCanvas(vm.VizroBaseModel): | |
"""OffCanvas component for settings.""" | |
type: Literal["offcanvas"] = "offcanvas" | |
options: list[str] | |
value: str | |
def build(self): | |
"""Returns the off canvas component for settings.""" | |
input_groups = html.Div( | |
[ | |
dbc.InputGroup( | |
[ | |
dbc.InputGroupText("API Key"), | |
dbc.Input(placeholder="API key", type="password", id=f"{self.id}-api-key"), | |
html.Div( | |
dbc.Checklist( | |
id=f"{self.id}-api-key-toggle", | |
options=[{"label": "", "value": False}], | |
switch=True, | |
inline=True, | |
), | |
id="toggle-div-api-key", | |
), | |
], | |
className="mb-3", | |
), | |
dbc.InputGroup( | |
[ | |
dbc.InputGroupText("API base"), | |
dbc.Input(placeholder="(optional) API base", type="password", id=f"{self.id}-api-base"), | |
html.Div( | |
dbc.Checklist( | |
id=f"{self.id}-api-base-toggle", | |
options=[{"label": "", "value": False}], | |
switch=True, | |
inline=True, | |
), | |
id="toggle-div-api-base", | |
), | |
], | |
className="mb-3", | |
), | |
dbc.InputGroup( | |
[ | |
dbc.InputGroupText("Choose your vendor"), | |
dbc.Select(options=self.options, value=self.value, id=f"{self.id}-dropdown"), | |
], | |
className="mb-3", | |
), | |
], | |
className="mb-3", | |
) | |
providers = [ | |
{"name": "OpenAI", "url": "https://openai.com/index/openai-api/"}, | |
{"name": "Anthropic", "url": "https://docs.anthropic.com/en/api/getting-started"}, | |
{"name": "Mistral", "url": "https://docs.mistral.ai/getting-started/quickstart/"}, | |
{"name": "xAI", "url": "https://x.ai/blog/api", "note": "(Free API credits available)"}, | |
] | |
api_instructions = html.Div( | |
[ | |
html.Hr( | |
style={ | |
"margin": "2rem 0", | |
"border-color": "rgba(255, 255, 255, 0.1)", | |
"border-style": "solid", | |
"border-width": "0 0 1px 0", | |
} | |
), | |
html.Div("Get API Keys", className="mb-3", style={"color": "#ffffff"}), | |
dbc.ListGroup( | |
[ | |
create_provider_item(name=provider["name"], url=provider["url"], note=provider.get("note")) | |
for provider in providers | |
], | |
flush=True, | |
className="border-0", | |
), | |
], | |
) | |
offcanvas = dbc.Offcanvas( | |
id=self.id, | |
children=[ | |
html.Div( | |
children=[ | |
input_groups, | |
api_instructions, | |
] | |
), | |
], | |
title="Settings", | |
is_open=True, | |
) | |
return offcanvas | |
class Icon(vm.VizroBaseModel): | |
"""Icon component for settings.""" | |
type: Literal["icon"] = "icon" | |
def build(self): | |
"""Returns the icon for api settings.""" | |
return html.Div( | |
children=[html.Span("settings", className="material-symbols-outlined", id=self.id)], | |
className="settings-div", | |
) | |
class CustomDashboard(vm.Dashboard): | |
"""Custom Dashboard model.""" | |
def build(self): | |
"""Returns custom dashboard.""" | |
dashboard_build_obj = super().build() | |
dashboard_build_obj.children.append(dcc.Store(id="data-store-id", storage_type="session")) | |
dashboard_build_obj.children.append(dcc.Store(id="code-output-store-id", storage_type="session")) | |
return dashboard_build_obj | |
class ToggleSwitch(vm.VizroBaseModel): | |
"""Custom toggle switch model.""" | |
type: Literal["toggle_switch"] = "toggle_switch" | |
def build(self): | |
"""Returns custom toggle switch component.""" | |
toggle_component = html.Div( | |
children=[ | |
html.P("Plotly"), | |
html.Div( | |
dbc.Switch(id="toggle-switch", value=True, style={"borderRadius": "8px"}), | |
style={"paddingRight": "4px"}, | |
), | |
html.P("Vizro"), | |
], | |
className="toggle-div", | |
) | |
return toggle_component | |
def custom_table(data_frame): | |
"""Custom table figure.""" | |
table = dbc.Table.from_dataframe(data_frame, striped=False, bordered=True, hover=True) | |
table_modal = html.Div( | |
[ | |
html.Span( | |
"table_view", | |
className="material-symbols-outlined", | |
id="modal-table-icon", | |
style={"color": "gray", "cursor": "default"}, | |
), | |
dbc.Tooltip( | |
"Click to view data!", | |
placement="top", | |
target="modal-table-icon", | |
style={"display": "none"}, | |
id="modal-table-tooltip", | |
), | |
html.P( | |
id="upload-message-id", children=["Upload your data file (csv or excel)"], style={"paddingTop": "10px"} | |
), | |
dbc.Modal( | |
id="data-modal", | |
children=[ | |
dbc.ModalHeader(dbc.ModalTitle(id="modal-title", children="No data uploaded!")), | |
dbc.ModalBody( | |
id="modal-table", | |
children=table, | |
), | |
], | |
size="xl", | |
modal_class_name="modal-class", | |
), | |
], | |
id="table-modal-div", | |
) | |
return table_modal | |
class DropdownMenu(vm.VizroBaseModel): | |
"""Custom dropdown menu component.""" | |
type: Literal["dropdown_menu"] = "dropdown_menu" | |
def build(self): | |
"""Returns custom dropdown menu.""" | |
dropdown_menu = dbc.DropdownMenu( | |
id="dropdown-menu-button", | |
label="Download ", | |
children=[ | |
dbc.DropdownMenuItem( | |
children=[ | |
dbc.Button(children="JSON", id=f"{self.id}-json", className="download-button"), | |
] | |
), | |
dbc.DropdownMenuItem( | |
children=[ | |
dbc.Button(children="HTML", id=f"{self.id}-html", className="download-button"), | |
] | |
), | |
dcc.Download(id="download-file"), | |
], | |
toggleClassName="dropdown-menu-toggle-class", | |
) | |
download_div = html.Div( | |
children=[ | |
html.Span("download", className="material-symbols-outlined", id=f"{self.id}-icon"), | |
dropdown_menu, | |
dbc.Tooltip( | |
"Download this plot to your device as a plotly JSON or interactive HTML file " | |
"for easy sharing or future use.", | |
target="dropdown-menu-icon", | |
), | |
], | |
id="dropdown-menu-id", | |
) | |
return download_div | |
class HeaderComponent(vm.VizroBaseModel): | |
"""Custom header component.""" | |
type: Literal["header"] = "header" | |
def build(self): | |
"""Returns custom header component.""" | |
title = html.Header("Vizro", id="custom-header-title") | |
header = html.Div( | |
children=[html.Img(src=get_asset_url("logo.svg"), alt="Vizro logo", className="header-logo"), title], | |
id="custom-header-div", | |
) | |
icon = html.Div( | |
children=[ | |
html.Span("settings", className="material-symbols-outlined", id="open-settings-id"), | |
dbc.Tooltip("Settings", target="open-settings-id"), | |
], | |
className="settings-div", | |
) | |
return html.Div(children=[header, icon], className="custom_header") | |