diff --git a/vizro-ai/examples/dashboard_ui/_utils.py b/vizro-ai/examples/dashboard_ui/_utils.py index d889aa0de..06b459fd0 100644 --- a/vizro-ai/examples/dashboard_ui/_utils.py +++ b/vizro-ai/examples/dashboard_ui/_utils.py @@ -87,7 +87,7 @@ def format_output(generated_code): def check_available_port(port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sk: - return sk.connect_ex(('127.0.0.1', port)) != 0 + return sk.connect_ex(("127.0.0.1", port)) != 0 def find_available_port(base_port=8051): diff --git a/vizro-ai/examples/dashboard_ui/app.py b/vizro-ai/examples/dashboard_ui/app.py index 8db4482e3..7a938aec8 100644 --- a/vizro-ai/examples/dashboard_ui/app.py +++ b/vizro-ai/examples/dashboard_ui/app.py @@ -5,12 +5,13 @@ import dash_bootstrap_components as dbc import vizro.models as vm -from _utils import format_output, find_available_port +from _utils import find_available_port, format_output from actions import data_upload_action, display_filename, save_files from components import ( CodeClipboard, CustomButton, CustomDashboard, + HeaderComponent, Icon, Modal, MyDropdown, @@ -37,6 +38,7 @@ vm.Container.add_type("components", Icon) vm.Container.add_type("components", Modal) vm.Container.add_type("components", CustomButton) +vm.Container.add_type("components", HeaderComponent) MyPage.add_type("components", UserPromptTextArea) MyPage.add_type("components", UserUpload) @@ -50,58 +52,48 @@ id="vizro_ai_dashboard_page", title="Vizro AI - Dashboard", layout=vm.Layout( - grid=[[2, 2, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [3, 3, 0, 0, 0]] + grid=[ + [4, 4, 4, 4, 4], + [2, 2, 0, 0, 0], + [2, 2, 0, 0, 0], + [3, 3, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 0, 0, 0], + ] ), components=[ - # vm.Container( - # id="clipboard-container", - # title="", - # components=[ - # CodeClipboard(id="dashboard"), - # CustomButton(text="Run dashboard", id="run-dashboard-button"), - # ], - # layout=vm.Layout( - # grid=[ - # *[[0, 0, 0, 0, 0, 0]] * 11, - # [-1, -1, -1, -1, -1, 1] - # ], - # col_gap="20px" - # ) - # ), - vm.Tabs( - tabs=[ + vm.Container( + title="Code", + components=[ vm.Container( - title="Code", + title="", components=[ vm.Container( + id="clipboard-container", title="", components=[ - vm.Container( - id="clipboard-container", - title="", - components=[ - CodeClipboard(id="dashboard"), - CustomButton(text="Run dashboard", id="run-dashboard-button"), - ], - layout=vm.Layout( - grid=[*[[0, 0, 0, 0, 0, 0]] * 10, [-1, -1, -1, -1, -1, 1]], col_gap="20px" - ), - ) + CodeClipboard(id="dashboard"), + CustomButton(id="run-dashboard"), ], - id="clipboard-tab", - ), + layout=vm.Layout( + grid=[*[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] * 11, [-1, -1, -1, -1, -1, -1, -1, -1, 1, 1]], + col_gap="20px", + ), + ) ], - ), - vm.Container( - id="embedded-dashboard", - title="Dashboard", - components=[vm.Card(text="VizroAI generated dashboard placeholder")], + id="clipboard-tab", ), ], ), UserPromptTextArea(id="dashboard-text-area", placeholder="Describe the dashboard you want to create."), vm.Container( - title="", + id="upload-container", + title="Turn your data into visuals — just upload, describe, and see your dashboard in action", layout=vm.Layout(grid=[[0], [1]], row_gap="0px"), components=[ vm.Card(id="dashboard-upload-message-id", text="Upload your data files (csv or excel)"), @@ -134,7 +126,9 @@ vm.Container( title="", layout=vm.Layout( - grid=[[3, -1, -1, -1, -1, -1, 1, 1, 0, 0], [-1, -1, -1, -1, -1, -1, -1, -1, 2, 2]], + grid=[ + [2, -1, -1, -1, -1, -1, 1, 1, 0, 0], + ], row_gap="0px", col_gap="4px", ), @@ -144,11 +138,14 @@ text="Run VizroAI", ), MyDropdown(options=SUPPORTED_MODELS, value="gpt-4o-mini", multi=False, id="dashboard-model-dropdown"), - Icon(id="open-settings-id"), OffCanvas(id="dashboard-settings", options=["OpenAI"], value="OpenAI"), # Modal(id="modal"), ], ), + vm.Container( + title="", + components=[HeaderComponent()], + ), ], ) @@ -249,28 +246,32 @@ def save_to_file(generated_code): @callback( - Output("run-dashboard-button", "style"), + Output("run-dashboard-navlink", "style"), Input("dashboard-code-markdown", "children"), ) def show_button(ai_response): """Displays a button to launch the dashboard in a subprocess.""" - if ai_response: - return {"minWidth": "100%"} + if not ai_response: + raise PreventUpdate + port = find_available_port() + subprocess.Popen(["python", "output_files/run_vizro_ai_output.py", str(port)]) + return {} -@callback( - [Output("run-dashboard-button", "disabled"), Output("embedded-dashboard", "children")], - Input("run-dashboard-button", "n_clicks"), -) -def run_generated_dashboard(n_clicks): - """Runs vizro-ai generated dashboard in an iframe window.""" - port = find_available_port() - if not n_clicks: - raise PreventUpdate - else: - subprocess.Popen(["python", "output_files/run_vizro_ai_output.py", str(port)]) - iframe = html.Iframe(src="http://localhost:8051/", height="600px") - return True, iframe +# +# @callback( +# [Output("run-dashboard-button", "disabled"), Output("embedded-dashboard", "children")], +# Input("run-dashboard-button", "n_clicks"), +# ) +# def run_generated_dashboard(n_clicks): +# """Runs vizro-ai generated dashboard in an iframe window.""" +# port = find_available_port() +# if not n_clicks: +# raise PreventUpdate +# else: +# subprocess.Popen(["python", "output_files/run_vizro_ai_output.py", str(port)]) +# iframe = html.Iframe(src="http://localhost:8051/", height="600px") +# return True, iframe app = Vizro().build(dashboard) diff --git a/vizro-ai/examples/dashboard_ui/assets/custom_css.css b/vizro-ai/examples/dashboard_ui/assets/custom_css.css index 33e17a9dc..a3fcce0ab 100644 --- a/vizro-ai/examples/dashboard_ui/assets/custom_css.css +++ b/vizro-ai/examples/dashboard_ui/assets/custom_css.css @@ -130,7 +130,6 @@ display: flex; justify-content: end; padding-right: 2px; - width: 100%; } #dashboard-data-upload { @@ -181,3 +180,69 @@ #clipboard-container { overflow: hidden; } + +#right-header { + display: none; +} + +.custom_header { + align-items: center; + border-bottom: 1px solid var(--border-subtle-alpha-01); + display: flex; + flex-direction: row; + height: 60px; + justify-content: space-between; + min-height: 0; + width: 100%; +} + +#right-side { + padding-top: 0; +} + +.header-logo { + width: 48px; +} + +#custom-header-div { + display: flex; + flex-direction: row; + gap: 8px; + justify-content: center; + width: 100%; +} + +#custom-header-title { + align-items: center; + display: flex; + font-size: 28px; + font-weight: 400; +} + +#data-upload { + color: var(--text-secondary); +} + +#open-settings-id:hover { + cursor: pointer; +} + +.container__title { + font-size: 14px; + padding-top: 4px; +} + +.navlink-button { + background: var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-0); + color: var(--text-primary-inverted); + font-size: 14px; + font-stretch: normal; + font-style: normal; + font-weight: 600; + letter-spacing: -0.056px; + line-height: 16px; + text-decoration: none; + transition: box-shadow 0.2s; +} diff --git a/vizro-ai/examples/dashboard_ui/components.py b/vizro-ai/examples/dashboard_ui/components.py index 7638699d7..e442e17a7 100644 --- a/vizro-ai/examples/dashboard_ui/components.py +++ b/vizro-ai/examples/dashboard_ui/components.py @@ -5,7 +5,7 @@ import black import dash_bootstrap_components as dbc import vizro.models as vm -from dash import dcc, html +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 @@ -272,7 +272,41 @@ class CustomButton(vm.Button): def build(self): """Returns custom button.""" - button_build_obj = super().build() - # button_build_obj.disabled = True - button_build_obj.style = {"minWidth": "100%", "display": "none"} - return button_build_obj + button = dbc.Button( + id=self.id, + children=[ + dbc.NavLink( + "View your dashboard", + href="http://localhost:8051/", + target="_blank", + external_link=True, + id=f"{self.id}-navlink", + ), + ], + style={"width": "12rem"}, + className="navlink-button", + ) + return button + + +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")