maxschulz-COL commited on
Commit
1c98fd4
1 Parent(s): 8ded8cf

Update to new version of UI

Browse files
Files changed (7) hide show
  1. Dockerfile +26 -7
  2. actions.py +81 -24
  3. app.py +184 -64
  4. assets/custom_css.css +153 -5
  5. components.py +145 -28
  6. requirements.in +3 -3
  7. requirements.txt +92 -301
Dockerfile CHANGED
@@ -1,17 +1,36 @@
1
- # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
- # you will also find guides on how best to write your Dockerfile
3
 
4
- FROM python:3.12
 
 
5
 
6
  RUN useradd -m -u 1000 user
 
 
 
 
7
 
8
- WORKDIR /app
 
 
9
 
10
- COPY --chown=user ./requirements.txt requirements.txt
 
11
 
12
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
 
 
13
 
14
- COPY --chown=user . /app
 
 
 
 
 
 
 
 
 
15
 
16
  EXPOSE 7860
17
 
 
1
+ FROM python:3.12-slim AS builder
 
2
 
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ gcc \
5
+ && rm -rf /var/lib/apt/lists/*
6
 
7
  RUN useradd -m -u 1000 user
8
+ USER user
9
+ ENV HOME=/home/user \
10
+ PATH=/home/user/.local/bin:$PATH
11
+ WORKDIR $HOME/app
12
 
13
+ COPY --chown=user requirements.txt .
14
+ RUN pip install --user --no-cache-dir --upgrade pip && \
15
+ pip install --user --no-cache-dir -r requirements.txt
16
 
17
+ # Production stage
18
+ FROM python:3.12-slim
19
 
20
+ RUN apt-get update && apt-get upgrade -y && \
21
+ apt-get clean && \
22
+ rm -rf /var/lib/apt/lists/*
23
 
24
+ RUN useradd -m -u 1000 user
25
+ USER user
26
+ ENV HOME=/home/user \
27
+ PATH=/home/user/.local/bin:$PATH
28
+ WORKDIR $HOME/app
29
+
30
+ COPY --from=builder --chown=user $HOME/.local/lib/python3.12/site-packages $HOME/.local/lib/python3.12/site-packages
31
+ COPY --from=builder --chown=user $HOME/.local/bin $HOME/.local/bin
32
+
33
+ COPY --chown=user . $HOME/app
34
 
35
  EXPOSE 7860
36
 
actions.py CHANGED
@@ -5,6 +5,8 @@ import io
5
  import logging
6
 
7
  import black
 
 
8
  import pandas as pd
9
  from _utils import check_file_extension
10
  from dash.exceptions import PreventUpdate
@@ -13,18 +15,52 @@ from plotly import graph_objects as go
13
  from vizro.models.types import capture
14
  from vizro_ai import VizroAI
15
 
16
- logger = logging.getLogger(__name__)
17
- logger.setLevel(logging.INFO) # TODO: remove manual setting and make centrally controlled
 
 
18
 
19
- SUPPORTED_VENDORS = {"OpenAI": ChatOpenAI}
 
 
 
20
 
 
 
21
 
22
- def get_vizro_ai_plot(user_prompt, df, model, api_key, api_base, vendor_input): # noqa: PLR0913
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  """VizroAi plot configuration."""
24
  vendor = SUPPORTED_VENDORS[vendor_input]
25
- llm = vendor(model_name=model, openai_api_key=api_key, openai_api_base=api_base)
 
 
 
 
 
 
 
26
  vizro_ai = VizroAI(model=llm)
27
- ai_outputs = vizro_ai.plot(df, user_prompt, explain=False, return_elements=True)
28
 
29
  return ai_outputs
30
 
@@ -33,13 +69,8 @@ def get_vizro_ai_plot(user_prompt, df, model, api_key, api_base, vendor_input):
33
  def run_vizro_ai(user_prompt, n_clicks, data, model, api_key, api_base, vendor_input): # noqa: PLR0913
34
  """Gets the AI response and adds it to the text window."""
35
 
36
- def create_response(ai_response, figure, user_prompt, filename):
37
- plotly_fig = figure.to_json()
38
- return (
39
- ai_response,
40
- figure,
41
- {"ai_response": ai_response, "figure": plotly_fig, "prompt": user_prompt, "filename": filename},
42
- )
43
 
44
  if not n_clicks:
45
  raise PreventUpdate
@@ -47,22 +78,22 @@ def run_vizro_ai(user_prompt, n_clicks, data, model, api_key, api_base, vendor_i
47
  if not data:
48
  ai_response = "Please upload data to proceed!"
49
  figure = go.Figure()
50
- return create_response(ai_response, figure, user_prompt, None)
51
 
52
  if not api_key:
53
  ai_response = "API key not found. Make sure you enter your API key!"
54
  figure = go.Figure()
55
- return create_response(ai_response, figure, user_prompt, data["filename"])
56
 
57
  if api_key.startswith('"'):
58
  ai_response = "Make sure you enter your API key without quotes!"
59
  figure = go.Figure()
60
- return create_response(ai_response, figure, user_prompt, data["filename"])
61
 
62
  if api_base is not None and api_base.startswith('"'):
63
  ai_response = "Make sure you enter your API base without quotes!"
64
  figure = go.Figure()
65
- return create_response(ai_response, figure, user_prompt, data["filename"])
66
 
67
  try:
68
  logger.info("Attempting chart code.")
@@ -75,20 +106,25 @@ def run_vizro_ai(user_prompt, n_clicks, data, model, api_key, api_base, vendor_i
75
  api_base=api_base,
76
  vendor_input=vendor_input,
77
  )
78
- ai_code = ai_outputs.code
79
- figure = ai_outputs.figure
 
80
  formatted_code = black.format_str(ai_code, mode=black.Mode(line_length=100))
 
 
 
 
81
 
82
  ai_response = "\n".join(["```python", formatted_code, "```"])
83
  logger.info("Successful query produced.")
84
- return create_response(ai_response, figure, user_prompt, data["filename"])
85
 
86
  except Exception as exc:
87
  logger.debug(exc)
88
  logger.info("Chart creation failed.")
89
  ai_response = f"Sorry, I can't do that. Following Error occurred: {exc}"
90
  figure = go.Figure()
91
- return create_response(ai_response, figure, user_prompt, data["filename"])
92
 
93
 
94
  @capture("action")
@@ -98,7 +134,11 @@ def data_upload_action(contents, filename):
98
  raise PreventUpdate
99
 
100
  if not check_file_extension(filename=filename):
101
- return {"error_message": "Unsupported file extension.. Make sure to upload either csv or an excel file."}
 
 
 
 
102
 
103
  content_type, content_string = contents.split(",")
104
 
@@ -112,11 +152,15 @@ def data_upload_action(contents, filename):
112
  df = pd.read_excel(io.BytesIO(decoded))
113
 
114
  data = df.to_dict("records")
115
- return {"data": data, "filename": filename}
116
 
117
  except Exception as e:
118
  logger.debug(e)
119
- return {"error_message": "There was an error processing this file."}
 
 
 
 
120
 
121
 
122
  @capture("action")
@@ -127,3 +171,16 @@ def display_filename(data):
127
 
128
  display_message = data.get("filename") or data.get("error_message")
129
  return f"Uploaded file name: '{display_message}'" if "filename" in data else display_message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import logging
6
 
7
  import black
8
+ import dash
9
+ import dash_bootstrap_components as dbc
10
  import pandas as pd
11
  from _utils import check_file_extension
12
  from dash.exceptions import PreventUpdate
 
15
  from vizro.models.types import capture
16
  from vizro_ai import VizroAI
17
 
18
+ try:
19
+ from langchain_anthropic import ChatAnthropic
20
+ except ImportError:
21
+ ChatAnthropic = None
22
 
23
+ try:
24
+ from langchain_mistralai import ChatMistralAI
25
+ except ImportError:
26
+ ChatMistralAI = None
27
 
28
+ logger = logging.getLogger(__name__)
29
+ logger.setLevel(logging.INFO) # TODO: remove manual setting and make centrally controlled
30
 
31
+ SUPPORTED_VENDORS = {"OpenAI": ChatOpenAI, "Anthropic": ChatAnthropic, "Mistral": ChatMistralAI}
32
+
33
+ SUPPORTED_MODELS = {
34
+ "OpenAI": [
35
+ "gpt-4o-mini",
36
+ "gpt-4o",
37
+ "gpt-4",
38
+ "gpt-4-turbo",
39
+ "gpt-3.5-turbo",
40
+ ],
41
+ "Anthropic": [
42
+ "claude-3-opus-latest",
43
+ "claude-3-5-sonnet-latest",
44
+ "claude-3-sonnet-20240229",
45
+ "claude-3-haiku-20240307",
46
+ ],
47
+ "Mistral": ["mistral-large-latest", "open-mistral-nemo", "codestral-latest"],
48
+ }
49
+
50
+
51
+ def get_vizro_ai_plot(user_prompt, df, model, api_key, api_base, vendor_input):
52
  """VizroAi plot configuration."""
53
  vendor = SUPPORTED_VENDORS[vendor_input]
54
+
55
+ if vendor_input == "OpenAI":
56
+ llm = vendor(model_name=model, openai_api_key=api_key, openai_api_base=api_base)
57
+ if vendor_input == "Anthropic":
58
+ llm = vendor(model=model, anthropic_api_key=api_key, anthropic_api_url=api_base)
59
+ if vendor_input == "Mistral":
60
+ llm = vendor(model=model, mistral_api_key=api_key, mistral_api_url=api_base)
61
+
62
  vizro_ai = VizroAI(model=llm)
63
+ ai_outputs = vizro_ai.plot(df, user_prompt, return_elements=True)
64
 
65
  return ai_outputs
66
 
 
69
  def run_vizro_ai(user_prompt, n_clicks, data, model, api_key, api_base, vendor_input): # noqa: PLR0913
70
  """Gets the AI response and adds it to the text window."""
71
 
72
+ def create_response(ai_response, figure, ai_outputs):
73
+ return (ai_response, figure, {"ai_outputs": ai_outputs})
 
 
 
 
 
74
 
75
  if not n_clicks:
76
  raise PreventUpdate
 
78
  if not data:
79
  ai_response = "Please upload data to proceed!"
80
  figure = go.Figure()
81
+ return create_response(ai_response, figure, ai_outputs=None)
82
 
83
  if not api_key:
84
  ai_response = "API key not found. Make sure you enter your API key!"
85
  figure = go.Figure()
86
+ return create_response(ai_response, figure, ai_outputs=None)
87
 
88
  if api_key.startswith('"'):
89
  ai_response = "Make sure you enter your API key without quotes!"
90
  figure = go.Figure()
91
+ return create_response(ai_response, figure, ai_outputs=None)
92
 
93
  if api_base is not None and api_base.startswith('"'):
94
  ai_response = "Make sure you enter your API base without quotes!"
95
  figure = go.Figure()
96
+ return create_response(ai_response, figure, ai_outputs=None)
97
 
98
  try:
99
  logger.info("Attempting chart code.")
 
106
  api_base=api_base,
107
  vendor_input=vendor_input,
108
  )
109
+ ai_code = ai_outputs.code_vizro
110
+ figure_vizro = ai_outputs.get_fig_object(data_frame=df, vizro=True)
111
+ figure_plotly = ai_outputs.get_fig_object(data_frame=df, vizro=False)
112
  formatted_code = black.format_str(ai_code, mode=black.Mode(line_length=100))
113
+ ai_code_outputs = {
114
+ "vizro": {"code": ai_outputs.code_vizro, "fig": figure_vizro.to_json()},
115
+ "plotly": {"code": ai_outputs.code, "fig": figure_plotly.to_json()},
116
+ }
117
 
118
  ai_response = "\n".join(["```python", formatted_code, "```"])
119
  logger.info("Successful query produced.")
120
+ return create_response(ai_response, figure_vizro, ai_outputs=ai_code_outputs)
121
 
122
  except Exception as exc:
123
  logger.debug(exc)
124
  logger.info("Chart creation failed.")
125
  ai_response = f"Sorry, I can't do that. Following Error occurred: {exc}"
126
  figure = go.Figure()
127
+ return create_response(ai_response, figure, ai_outputs=None)
128
 
129
 
130
  @capture("action")
 
134
  raise PreventUpdate
135
 
136
  if not check_file_extension(filename=filename):
137
+ return (
138
+ {"error_message": "Unsupported file extension.. Make sure to upload either csv or an excel file."},
139
+ {"color": "gray"},
140
+ {"display": "none"},
141
+ )
142
 
143
  content_type, content_string = contents.split(",")
144
 
 
152
  df = pd.read_excel(io.BytesIO(decoded))
153
 
154
  data = df.to_dict("records")
155
+ return {"data": data, "filename": filename}, {"cursor": "pointer"}, {}
156
 
157
  except Exception as e:
158
  logger.debug(e)
159
+ return (
160
+ {"error_message": "There was an error processing this file."},
161
+ {"color": "gray", "cursor": "default"},
162
+ {"display": "none"},
163
+ )
164
 
165
 
166
  @capture("action")
 
171
 
172
  display_message = data.get("filename") or data.get("error_message")
173
  return f"Uploaded file name: '{display_message}'" if "filename" in data else display_message
174
+
175
+
176
+ @capture("action")
177
+ def update_table(data):
178
+ """Custom action for updating data."""
179
+ if not data:
180
+ return dash.no_update
181
+ df = pd.DataFrame(data["data"])
182
+ filename = data.get("filename") or data.get("error_message")
183
+ modal_title = f"Data sample preview for {filename} file"
184
+ df_sample = df.sample(5)
185
+ table = dbc.Table.from_dataframe(df_sample, striped=False, bordered=True, hover=True)
186
+ return table, modal_title
app.py CHANGED
@@ -2,71 +2,127 @@
2
 
3
  import json
4
 
 
 
5
  import dash_bootstrap_components as dbc
6
  import pandas as pd
 
 
7
  import vizro.models as vm
8
  import vizro.plotly.express as px
9
- from actions import data_upload_action, display_filename, run_vizro_ai
10
  from components import (
11
  CodeClipboard,
12
  CustomDashboard,
 
 
13
  Icon,
14
  Modal,
15
  MyDropdown,
16
- MyPage,
17
  OffCanvas,
 
18
  UserPromptTextArea,
19
  UserUpload,
 
20
  )
21
- from dash import Input, Output, State, callback, get_asset_url, html
22
- from dash.exceptions import PreventUpdate
23
  from vizro import Vizro
24
 
 
 
 
 
 
 
 
 
 
 
25
  vm.Container.add_type("components", UserUpload)
26
  vm.Container.add_type("components", MyDropdown)
27
  vm.Container.add_type("components", OffCanvas)
28
  vm.Container.add_type("components", CodeClipboard)
29
  vm.Container.add_type("components", Icon)
30
  vm.Container.add_type("components", Modal)
 
 
 
 
31
 
32
- MyPage.add_type("components", UserPromptTextArea)
33
- MyPage.add_type("components", UserUpload)
34
- MyPage.add_type("components", MyDropdown)
35
- MyPage.add_type("components", OffCanvas)
36
- MyPage.add_type("components", CodeClipboard)
37
- MyPage.add_type("components", Icon)
38
- MyPage.add_type("components", Modal)
39
 
40
 
41
- SUPPORTED_MODELS = [
42
- "gpt-4o-mini",
43
- "gpt-4",
44
- "gpt-4-turbo",
45
- "gpt-3.5-turbo",
46
- "gpt-4o",
47
- ]
 
 
 
 
 
 
 
 
 
48
 
49
 
50
- plot_page = MyPage(
51
  id="vizro_ai_plot_page",
52
- title="Vizro-AI - effortlessly create interactive charts with Plotly",
53
  layout=vm.Layout(
54
  grid=[
55
- [3, 3, -1, 5],
56
- [1, 1, 2, 2],
57
- [4, 4, 2, 2],
58
- *[[0, 0, 2, 2]] * 6,
 
 
 
59
  ]
60
  ),
61
  components=[
62
- vm.Container(title="", components=[CodeClipboard(id="plot")]),
63
- UserPromptTextArea(
64
- id="text-area-id",
 
 
 
 
 
65
  ),
66
- vm.Graph(id="graph-id", figure=px.scatter(pd.DataFrame())),
67
  vm.Container(
68
  title="",
69
- layout=vm.Layout(grid=[[1], [0]], row_gap="0px"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  components=[
71
  UserUpload(
72
  id="data-upload-id",
@@ -74,25 +130,39 @@ plot_page = MyPage(
74
  vm.Action(
75
  function=data_upload_action(),
76
  inputs=["data-upload-id.contents", "data-upload-id.filename"],
77
- outputs=["data-store-id.data"],
78
  ),
79
  vm.Action(
80
  function=display_filename(),
81
  inputs=["data-store-id.data"],
82
  outputs=["upload-message-id.children"],
83
  ),
 
 
 
 
 
84
  ],
85
  ),
86
- vm.Card(id="upload-message-id", text="Upload your data file (csv or excel)"),
87
  ],
88
  ),
89
  vm.Container(
90
  title="",
91
- layout=vm.Layout(grid=[[2, 3, -1, -1, -1, 1, 1, 0, 0]], row_gap="0px", col_gap="4px"),
 
 
 
 
 
 
 
 
 
92
  components=[
93
  vm.Button(
94
  id="trigger-button-id",
95
- text="Run VizroAI",
96
  actions=[
97
  vm.Action(
98
  function=run_vizro_ai(),
@@ -105,16 +175,22 @@ plot_page = MyPage(
105
  "settings-api-base.value",
106
  "settings-dropdown.value",
107
  ],
108
- outputs=["plot-code-markdown.children", "graph-id.figure", "outputs-store-id.data"],
109
  ),
110
  ],
111
  ),
112
- MyDropdown(options=SUPPORTED_MODELS, value="gpt-4o-mini", multi=False, id="model-dropdown-id"),
113
- OffCanvas(id="settings", options=["OpenAI"], value="OpenAI"),
114
- Modal(id="modal"),
 
 
 
115
  ],
116
  ),
117
- Icon(id="open-settings-id"),
 
 
 
118
  ],
119
  )
120
 
@@ -123,28 +199,6 @@ dashboard = CustomDashboard(pages=[plot_page])
123
 
124
 
125
  # pure dash callbacks
126
- @callback(
127
- [
128
- Output("plot-code-markdown", "children", allow_duplicate=True),
129
- Output("graph-id", "figure", allow_duplicate=True),
130
- Output("text-area-id", "value"),
131
- Output("upload-message-id", "children"),
132
- ],
133
- [Input("on_page_load_action_trigger_vizro_ai_plot_page", "data")],
134
- [State("outputs-store-id", "data")],
135
- prevent_initial_call="initial_duplicate",
136
- )
137
- def update_data(page_data, outputs_data):
138
- """Callback for retrieving latest vizro-ai output from dcc store."""
139
- if not outputs_data:
140
- raise PreventUpdate
141
-
142
- ai_response = outputs_data["ai_response"]
143
- fig = json.loads(outputs_data["figure"])
144
- filename = f"File uploaded: '{outputs_data['filename']}'"
145
- prompt = outputs_data["prompt"]
146
-
147
- return ai_response, fig, prompt, filename
148
 
149
 
150
  @callback(
@@ -175,19 +229,85 @@ def show_api_base(value):
175
  return "text" if value else "password"
176
 
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  app = Vizro().build(dashboard)
179
  app.dash.layout.children.append(
180
  html.Div(
181
  [
182
- dbc.NavLink("Contact Vizro", href="https://github.com/mckinsey/vizro/issues"),
183
- dbc.NavLink("GitHub", href="https://github.com/mckinsey/vizro"),
184
- dbc.NavLink("Docs", href="https://vizro.readthedocs.io/projects/vizro-ai/"),
185
  html.Div(
186
  [
187
  "Made using ",
188
  html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"),
189
- "vizro",
190
  ],
 
191
  ),
192
  ],
193
  className="anchor-container",
 
2
 
3
  import json
4
 
5
+ import black
6
+ import dash
7
  import dash_bootstrap_components as dbc
8
  import pandas as pd
9
+ import plotly.graph_objects as go
10
+ import plotly.io as pio
11
  import vizro.models as vm
12
  import vizro.plotly.express as px
13
+ from actions import data_upload_action, display_filename, run_vizro_ai, update_table
14
  from components import (
15
  CodeClipboard,
16
  CustomDashboard,
17
+ DropdownMenu,
18
+ HeaderComponent,
19
  Icon,
20
  Modal,
21
  MyDropdown,
 
22
  OffCanvas,
23
+ ToggleSwitch,
24
  UserPromptTextArea,
25
  UserUpload,
26
+ custom_table,
27
  )
28
+ from dash import Input, Output, State, callback, ctx, dcc, get_asset_url, html
 
29
  from vizro import Vizro
30
 
31
+ try:
32
+ from langchain_anthropic import ChatAnthropic
33
+ except ImportError:
34
+ ChatAnthropic = None
35
+
36
+ try:
37
+ from langchain_mistralai import ChatMistralAI
38
+ except ImportError:
39
+ ChatMistralAI = None
40
+
41
  vm.Container.add_type("components", UserUpload)
42
  vm.Container.add_type("components", MyDropdown)
43
  vm.Container.add_type("components", OffCanvas)
44
  vm.Container.add_type("components", CodeClipboard)
45
  vm.Container.add_type("components", Icon)
46
  vm.Container.add_type("components", Modal)
47
+ vm.Container.add_type("components", ToggleSwitch)
48
+ vm.Container.add_type("components", UserPromptTextArea)
49
+ vm.Container.add_type("components", DropdownMenu)
50
+ vm.Container.add_type("components", HeaderComponent)
51
 
52
+ vm.Page.add_type("components", UserUpload)
53
+ vm.Page.add_type("components", MyDropdown)
54
+ vm.Page.add_type("components", OffCanvas)
55
+ vm.Page.add_type("components", CodeClipboard)
56
+ vm.Page.add_type("components", Icon)
57
+ vm.Page.add_type("components", Modal)
 
58
 
59
 
60
+ SUPPORTED_MODELS = {
61
+ "OpenAI": [
62
+ "gpt-4o-mini",
63
+ "gpt-4o",
64
+ "gpt-4",
65
+ "gpt-4-turbo",
66
+ "gpt-3.5-turbo",
67
+ ],
68
+ "Anthropic": [
69
+ "claude-3-opus-latest",
70
+ "claude-3-5-sonnet-latest",
71
+ "claude-3-sonnet-20240229",
72
+ "claude-3-haiku-20240307",
73
+ ],
74
+ "Mistral": ["mistral-large-latest", "open-mistral-nemo", "codestral-latest"],
75
+ }
76
 
77
 
78
+ plot_page = vm.Page(
79
  id="vizro_ai_plot_page",
80
+ title="Vizro-AI - create interactive charts with Plotly and Vizro",
81
  layout=vm.Layout(
82
  grid=[
83
+ [4, 4, 4, 4],
84
+ [2, 2, 1, 1],
85
+ [2, 2, 1, 1],
86
+ [3, 3, 1, 1],
87
+ [3, 3, 1, 1],
88
+ [3, 3, 1, 1],
89
+ *[[0, 0, 1, 1]] * 8,
90
  ]
91
  ),
92
  components=[
93
+ vm.Container(
94
+ title="",
95
+ components=[CodeClipboard(id="plot"), ToggleSwitch(id="toggle-id")],
96
+ layout=vm.Layout(
97
+ grid=[*[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] * 7, [-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1]],
98
+ row_gap="12px",
99
+ col_gap="12px",
100
+ ),
101
  ),
 
102
  vm.Container(
103
  title="",
104
+ layout=vm.Layout(
105
+ grid=[
106
+ *[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] * 10,
107
+ [-1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1],
108
+ ]
109
+ ),
110
+ components=[
111
+ vm.Graph(id="graph-id", figure=px.scatter(pd.DataFrame())),
112
+ DropdownMenu(id="dropdown-menu"),
113
+ ],
114
+ ),
115
+ vm.Container(
116
+ id="upload-data-container",
117
+ title="Turn your data into visuals — just upload, describe, and see your chart in action",
118
+ layout=vm.Layout(
119
+ grid=[
120
+ [1],
121
+ [0],
122
+ ],
123
+ row_gap="0px",
124
+ # row_min_height="40px",
125
+ ),
126
  components=[
127
  UserUpload(
128
  id="data-upload-id",
 
130
  vm.Action(
131
  function=data_upload_action(),
132
  inputs=["data-upload-id.contents", "data-upload-id.filename"],
133
+ outputs=["data-store-id.data", "modal-table-icon.style", "modal-table-tooltip.style"],
134
  ),
135
  vm.Action(
136
  function=display_filename(),
137
  inputs=["data-store-id.data"],
138
  outputs=["upload-message-id.children"],
139
  ),
140
+ vm.Action(
141
+ function=update_table(),
142
+ inputs=["data-store-id.data"],
143
+ outputs=["modal-table.children", "modal-title.children"],
144
+ ),
145
  ],
146
  ),
147
+ vm.Figure(id="show-data-component", figure=custom_table(data_frame=pd.DataFrame())),
148
  ],
149
  ),
150
  vm.Container(
151
  title="",
152
+ layout=vm.Layout(
153
+ grid=[
154
+ [3, 3, 3, 3, 3, 3, 3, 3, 3],
155
+ [3, 3, 3, 3, 3, 3, 3, 3, 3],
156
+ [3, 3, 3, 3, 3, 3, 3, 3, 3],
157
+ [2, -1, -1, -1, -1, 1, 1, 0, 0],
158
+ ],
159
+ row_gap="10px",
160
+ col_gap="4px",
161
+ ),
162
  components=[
163
  vm.Button(
164
  id="trigger-button-id",
165
+ text="Run Vizro-AI",
166
  actions=[
167
  vm.Action(
168
  function=run_vizro_ai(),
 
175
  "settings-api-base.value",
176
  "settings-dropdown.value",
177
  ],
178
+ outputs=["plot-code-markdown.children", "graph-id.figure", "code-output-store-id.data"],
179
  ),
180
  ],
181
  ),
182
+ MyDropdown(
183
+ options=SUPPORTED_MODELS["OpenAI"], value="gpt-4o-mini", multi=False, id="model-dropdown-id"
184
+ ),
185
+ OffCanvas(id="settings", options=["OpenAI", "Anthropic", "Mistral"], value="OpenAI"),
186
+ UserPromptTextArea(id="text-area-id"),
187
+ # Modal(id="modal"),
188
  ],
189
  ),
190
+ vm.Container(
191
+ title="",
192
+ components=[HeaderComponent()],
193
+ ),
194
  ],
195
  )
196
 
 
199
 
200
 
201
  # pure dash callbacks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
 
204
  @callback(
 
229
  return "text" if value else "password"
230
 
231
 
232
+ @callback(
233
+ Output("plot-code-markdown", "children"),
234
+ Input("toggle-switch", "value"),
235
+ [State("code-output-store-id", "data")],
236
+ )
237
+ def toggle_code(value, data):
238
+ """Callback for switching between vizro and plotly code."""
239
+ if not data:
240
+ return dash.no_update
241
+
242
+ ai_code = data["ai_outputs"]["vizro"]["code"] if value else data["ai_outputs"]["plotly"]["code"]
243
+
244
+ formatted_code = black.format_str(ai_code, mode=black.Mode(line_length=100))
245
+ ai_response = "\n".join(["```python", formatted_code, "```"])
246
+ return ai_response
247
+
248
+
249
+ @callback(
250
+ Output("data-modal", "is_open"),
251
+ Input("modal-table-icon", "n_clicks"),
252
+ State("data-modal", "is_open"),
253
+ State("data-store-id", "data"),
254
+ )
255
+ def open_modal(n_clicks, is_open, data):
256
+ """Callback for opening modal component."""
257
+ if not data:
258
+ return dash.no_update
259
+ if n_clicks:
260
+ return not is_open
261
+ return is_open
262
+
263
+
264
+ @callback(
265
+ Output("download-file", "data"),
266
+ [Input("dropdown-menu-html", "n_clicks"), Input("dropdown-menu-json", "n_clicks")],
267
+ State("code-output-store-id", "data"),
268
+ prevent_initial_call=True,
269
+ )
270
+ def download_fig(n_clicks_html, n_clicks_json, data):
271
+ """Callback for downloading vizro fig."""
272
+ if not data:
273
+ return dash.no_update
274
+ if not (n_clicks_html or n_clicks_json):
275
+ return dash.no_update
276
+
277
+ button_clicked = ctx.triggered_id
278
+
279
+ if button_clicked == "dropdown-menu-html":
280
+ vizro_json = json.loads(data["ai_outputs"]["vizro"]["fig"])
281
+ fig = go.Figure(vizro_json)
282
+ graphs_html = pio.to_html(fig)
283
+ return dcc.send_string(graphs_html, filename="vizro_fig.html")
284
+
285
+ if button_clicked == "dropdown-menu-json":
286
+ plotly_json = data["ai_outputs"]["plotly"]["fig"]
287
+ return dcc.send_string(plotly_json, "plotly_fig.json")
288
+
289
+
290
+ @callback(
291
+ [Output("model-dropdown-id", "options"), Output("model-dropdown-id", "value")], Input("settings-dropdown", "value")
292
+ )
293
+ def update_model_dropdown(value):
294
+ """Callback for updating available models."""
295
+ available_models = SUPPORTED_MODELS[value]
296
+ default_model = available_models[0]
297
+ return available_models, default_model
298
+
299
+
300
  app = Vizro().build(dashboard)
301
  app.dash.layout.children.append(
302
  html.Div(
303
  [
 
 
 
304
  html.Div(
305
  [
306
  "Made using ",
307
  html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"),
308
+ dbc.NavLink("vizro", href="https://github.com/mckinsey/vizro", target="_blank", external_link=True),
309
  ],
310
+ style={"display": "flex", "flexDirection": "row"},
311
  ),
312
  ],
313
  className="anchor-container",
assets/custom_css.css CHANGED
@@ -26,7 +26,7 @@
26
  background-color: inherit;
27
  border: 1px solid var(--border-subtle-alpha-01);
28
  color: var(--text-primary);
29
- min-height: 90px;
30
  padding: 8px;
31
  width: 100%;
32
  }
@@ -45,8 +45,8 @@
45
  .code-clipboard-container {
46
  background: var(--surfaces-bg-card);
47
  font-family: monospace;
48
- height: 500px;
49
- max-height: 500px;
50
  overflow: auto;
51
  padding: 1rem;
52
  position: relative;
@@ -118,7 +118,7 @@
118
  display: flex;
119
  justify-content: end;
120
  padding-right: 2px;
121
- width: 100%;
122
  }
123
 
124
  #data-upload-id {
@@ -130,6 +130,7 @@
130
  text-align: center;
131
  }
132
 
 
133
  #settings-api-key-toggle .form-check-input {
134
  border-radius: 8px;
135
  }
@@ -137,7 +138,7 @@
137
  #settings-api-base-toggle .form-check-input {
138
  border-radius: 8px;
139
  }
140
-
141
  #toggle-div-api-base,
142
  #toggle-div-api-key {
143
  align-items: center;
@@ -158,3 +159,150 @@
158
  width: 100%;
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  background-color: inherit;
27
  border: 1px solid var(--border-subtle-alpha-01);
28
  color: var(--text-primary);
29
+ min-height: 13.5vh;
30
  padding: 8px;
31
  width: 100%;
32
  }
 
45
  .code-clipboard-container {
46
  background: var(--surfaces-bg-card);
47
  font-family: monospace;
48
+ height: 100%;
49
+ max-height: 470px;
50
  overflow: auto;
51
  padding: 1rem;
52
  position: relative;
 
118
  display: flex;
119
  justify-content: end;
120
  padding-right: 2px;
121
+ width: 20px;
122
  }
123
 
124
  #data-upload-id {
 
130
  text-align: center;
131
  }
132
 
133
+ /*
134
  #settings-api-key-toggle .form-check-input {
135
  border-radius: 8px;
136
  }
 
138
  #settings-api-base-toggle .form-check-input {
139
  border-radius: 8px;
140
  }
141
+ */
142
  #toggle-div-api-base,
143
  #toggle-div-api-key {
144
  align-items: center;
 
159
  width: 100%;
160
  }
161
 
162
+ .toggle-div {
163
+ display: flex;
164
+ flex-direction: row;
165
+ gap: 4px;
166
+ justify-content: end;
167
+ padding-top: 6px;
168
+ width: 100%;
169
+ }
170
+
171
+ .form-check .form-check-input {
172
+ border-radius: 8px;
173
+ }
174
+
175
+ #settings-api-key-toggle .form-check-input {
176
+ border-radius: 8px;
177
+ }
178
+
179
+ #settings-api-base-toggle .form-check-input {
180
+ border-radius: 8px;
181
+ }
182
+
183
+ .form-check {
184
+ padding-top: 2px;
185
+ }
186
+
187
+ .modal-content {
188
+ width: fit-content;
189
+ }
190
+
191
+ .btn-primary:enabled:has(#dropdown-menu-icon) {
192
+ background-color: inherit;
193
+ color: var(--text-active);
194
+ }
195
+
196
+ .btn-primary:disabled:has(#dropdown-menu-icon) {
197
+ background-color: inherit;
198
+ color: var(--text-active);
199
+ }
200
+
201
+ .download-button {
202
+ background-color: inherit;
203
+ color: var(--text-active);
204
+ width: 100%;
205
+ }
206
+
207
+ #modal-table-icon {
208
+ cursor: pointer;
209
+ }
210
+
211
+ .dropdown-menu-toggle-class {
212
+ background-color: inherit;
213
+ color: var(--text-active);
214
+ height: 2rem;
215
+ scale: 90%;
216
+ }
217
+
218
+ .dropdown-menu-toggle-class:hover {
219
+ background-color: inherit;
220
+ color: var(--text-active);
221
+ cursor: pointer;
222
+ height: 2rem;
223
+ scale: 90%;
224
+ }
225
+
226
+ .dropdown-menu-toggle-class.btn-primary:focus:not(
227
+ :focus-visible,
228
+ :disabled,
229
+ .disabled
230
+ ) {
231
+ background-color: inherit;
232
+ color: var(--text-active);
233
+ }
234
+
235
+ .modal-class {
236
+ max-height: 600px;
237
+ max-width: 1000px;
238
+ overflow: auto;
239
+ scrollbar-width: thin;
240
+ }
241
+
242
+ #right-header {
243
+ display: none;
244
+ }
245
+
246
+ .custom_header {
247
+ align-items: center;
248
+ border-bottom: 1px solid var(--border-subtle-alpha-01);
249
+ display: flex;
250
+ flex-direction: row;
251
+ height: 60px;
252
+ justify-content: space-between;
253
+ min-height: 0;
254
+ width: 100%;
255
+ }
256
+
257
+ #right-side {
258
+ padding-top: 0;
259
+ }
260
+
261
+ .header-logo {
262
+ width: 48px;
263
+ }
264
+ /* stylelint-disable-next-line selector-id-pattern */
265
+ #upload-data-container_title {
266
+ font-size: 14px;
267
+ padding-top: 4px;
268
+ }
269
+
270
+ #dropdown-menu-id {
271
+ align-items: center;
272
+ border: 0.5px solid gray;
273
+ border-radius: 8px;
274
+ display: flex;
275
+ flex-direction: row;
276
+ justify-content: center;
277
+ width: 100%;
278
+ }
279
+
280
+ #custom-header-div {
281
+ display: flex;
282
+ flex-direction: row;
283
+ gap: 8px;
284
+ justify-content: center;
285
+ width: 100%;
286
+ }
287
+
288
+ #custom-header-title {
289
+ align-items: center;
290
+ display: flex;
291
+ font-size: 28px;
292
+ font-weight: 400;
293
+ }
294
+
295
+ #table-modal-div {
296
+ align-items: center;
297
+ display: flex;
298
+ flex-direction: row;
299
+ gap: 8px;
300
+ }
301
+
302
+ #data-upload {
303
+ color: var(--text-secondary);
304
+ }
305
+
306
+ #open-settings-id:hover {
307
+ cursor: pointer;
308
+ }
components.py CHANGED
@@ -1,15 +1,16 @@
1
  """Contains custom components used within a dashboard."""
2
 
3
- from typing import List, Literal
4
 
5
  import black
6
  import dash_bootstrap_components as dbc
7
  import vizro.models as vm
8
- from dash import dcc, html
9
  from pydantic import PrivateAttr
10
  from vizro.models import Action
11
  from vizro.models._action._actions_chain import _action_validator_factory
12
  from vizro.models._models_utils import _log_call
 
13
 
14
 
15
  class UserPromptTextArea(vm.VizroBaseModel):
@@ -21,12 +22,12 @@ class UserPromptTextArea(vm.VizroBaseModel):
21
  type (Literal["user_input"]): Defaults to `"user_text_area"`.
22
  title (str): Title to be displayed. Defaults to `""`.
23
  placeholder (str): Default text to display in input field. Defaults to `""`.
24
- actions (Optional[List[Action]]): Defaults to `[]`.
25
 
26
  """
27
 
28
  type: Literal["user_text_area"] = "user_text_area"
29
- actions: List[Action] = [] # noqa: RUF012
30
 
31
  _set_actions = _action_validator_factory("value")
32
 
@@ -50,12 +51,12 @@ class UserUpload(vm.VizroBaseModel):
50
  Args:
51
  type (Literal["upload"]): Defaults to `"upload"`.
52
  title (str): Title to be displayed.
53
- actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.
54
 
55
  """
56
 
57
  type: Literal["upload"] = "upload"
58
- actions: List[Action] = [] # noqa: RUF012
59
 
60
  # 'contents' property is input to custom action callback
61
  _input_property: str = PrivateAttr("contents")
@@ -68,9 +69,7 @@ class UserUpload(vm.VizroBaseModel):
68
  [
69
  dcc.Upload(
70
  id=self.id,
71
- children=html.Div(
72
- ["Drag and Drop or ", html.A("Select Files")], style={"fontColor": "rgba(255, 255, 255, 0.6)"}
73
- ),
74
  ),
75
  ]
76
  )
@@ -114,7 +113,7 @@ class MyDropdown(vm.Dropdown):
114
 
115
 
116
  class Modal(vm.VizroBaseModel):
117
- """Modal to convey warning message"""
118
 
119
  type: Literal["modal"] = "modal"
120
 
@@ -129,15 +128,17 @@ class Modal(vm.VizroBaseModel):
129
  """### Do NOT upload any sensitive or personally identifying data.
130
 
131
  #### Reasoning:
132
- This space is hosted publicly running one server in a single container. Further this UI executes dynamically created code on the server. It thus
133
- 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.
 
134
 
135
  #### Alternatives:
 
 
 
136
 
137
- 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.
138
- To do so by, click the three dots in the top right of the HuggingFace banner and then select "Run with Docker".
139
-
140
- Always exercise caution when sharing data online and understand your responsibilities regarding data privacy and security.
141
  """
142
  )
143
  ),
@@ -151,7 +152,7 @@ class OffCanvas(vm.VizroBaseModel):
151
  """OffCanvas component for settings."""
152
 
153
  type: Literal["offcanvas"] = "offcanvas"
154
- options: List[str]
155
  value: str
156
 
157
  def build(self):
@@ -216,16 +217,6 @@ class OffCanvas(vm.VizroBaseModel):
216
  return offcanvas
217
 
218
 
219
- class MyPage(vm.Page):
220
- """Custom page."""
221
-
222
- type: Literal["my_page"] = "my_page"
223
-
224
- def pre_build(self):
225
- """Overwriting pre_build."""
226
- pass
227
-
228
-
229
  class Icon(vm.VizroBaseModel):
230
  """Icon component for settings."""
231
 
@@ -246,5 +237,131 @@ class CustomDashboard(vm.Dashboard):
246
  """Returns custom dashboard."""
247
  dashboard_build_obj = super().build()
248
  dashboard_build_obj.children.append(dcc.Store(id="data-store-id", storage_type="session"))
249
- dashboard_build_obj.children.append(dcc.Store(id="outputs-store-id", storage_type="session"))
250
  return dashboard_build_obj
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """Contains custom components used within a dashboard."""
2
 
3
+ from typing import Literal
4
 
5
  import black
6
  import dash_bootstrap_components as dbc
7
  import vizro.models as vm
8
+ from dash import dcc, get_asset_url, html
9
  from pydantic import PrivateAttr
10
  from vizro.models import Action
11
  from vizro.models._action._actions_chain import _action_validator_factory
12
  from vizro.models._models_utils import _log_call
13
+ from vizro.models.types import capture
14
 
15
 
16
  class UserPromptTextArea(vm.VizroBaseModel):
 
22
  type (Literal["user_input"]): Defaults to `"user_text_area"`.
23
  title (str): Title to be displayed. Defaults to `""`.
24
  placeholder (str): Default text to display in input field. Defaults to `""`.
25
+ actions (Optional[list[Action]]): Defaults to `[]`.
26
 
27
  """
28
 
29
  type: Literal["user_text_area"] = "user_text_area"
30
+ actions: list[Action] = [] # noqa: RUF012
31
 
32
  _set_actions = _action_validator_factory("value")
33
 
 
51
  Args:
52
  type (Literal["upload"]): Defaults to `"upload"`.
53
  title (str): Title to be displayed.
54
+ actions (list[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.
55
 
56
  """
57
 
58
  type: Literal["upload"] = "upload"
59
+ actions: list[Action] = [] # noqa: RUF012
60
 
61
  # 'contents' property is input to custom action callback
62
  _input_property: str = PrivateAttr("contents")
 
69
  [
70
  dcc.Upload(
71
  id=self.id,
72
+ children=html.Div(["Drag and Drop or ", html.A("Select Files")], id="data-upload"),
 
 
73
  ),
74
  ]
75
  )
 
113
 
114
 
115
  class Modal(vm.VizroBaseModel):
116
+ """Modal to convey warning message."""
117
 
118
  type: Literal["modal"] = "modal"
119
 
 
128
  """### Do NOT upload any sensitive or personally identifying data.
129
 
130
  #### Reasoning:
131
+ This space is hosted publicly running one server in a single container. Further this UI executes dynamically created
132
+ code on the server. It thus cannot guarantee the security of your data. In addition it sends the user query and the
133
+ data to the chosen LLM vendor API. This is not an exhaustive list.
134
 
135
  #### Alternatives:
136
+ If sending your query and data to a LLM is acceptable, you can pull and run this image locally. This will avoid sharing
137
+ an instance with others. You can do so by clicking the three dots in the top right of the HuggingFace banner
138
+ and click `Run with Docker`.
139
 
140
+ Always exercise caution when sharing data online and understand your responsibilities regarding data privacy
141
+ and security.
 
 
142
  """
143
  )
144
  ),
 
152
  """OffCanvas component for settings."""
153
 
154
  type: Literal["offcanvas"] = "offcanvas"
155
+ options: list[str]
156
  value: str
157
 
158
  def build(self):
 
217
  return offcanvas
218
 
219
 
 
 
 
 
 
 
 
 
 
 
220
  class Icon(vm.VizroBaseModel):
221
  """Icon component for settings."""
222
 
 
237
  """Returns custom dashboard."""
238
  dashboard_build_obj = super().build()
239
  dashboard_build_obj.children.append(dcc.Store(id="data-store-id", storage_type="session"))
240
+ dashboard_build_obj.children.append(dcc.Store(id="code-output-store-id", storage_type="session"))
241
  return dashboard_build_obj
242
+
243
+
244
+ class ToggleSwitch(vm.VizroBaseModel):
245
+ """Custom toggle switch model."""
246
+
247
+ type: Literal["toggle_switch"] = "toggle_switch"
248
+
249
+ def build(self):
250
+ """Returns custom toggle switch component."""
251
+ toggle_component = html.Div(
252
+ children=[
253
+ html.P("Plotly"),
254
+ html.Div(
255
+ dbc.Switch(id="toggle-switch", value=True, style={"borderRadius": "8px"}),
256
+ style={"paddingRight": "4px"},
257
+ ),
258
+ html.P("Vizro"),
259
+ ],
260
+ className="toggle-div",
261
+ )
262
+ return toggle_component
263
+
264
+
265
+ @capture("figure")
266
+ def custom_table(data_frame):
267
+ """Custom table figure."""
268
+ table = dbc.Table.from_dataframe(data_frame, striped=False, bordered=True, hover=True)
269
+
270
+ table_modal = html.Div(
271
+ [
272
+ html.Span(
273
+ "table_view",
274
+ className="material-symbols-outlined",
275
+ id="modal-table-icon",
276
+ style={"color": "gray", "cursor": "default"},
277
+ ),
278
+ dbc.Tooltip(
279
+ "Click to view data!",
280
+ placement="top",
281
+ target="modal-table-icon",
282
+ style={"display": "none"},
283
+ id="modal-table-tooltip",
284
+ ),
285
+ html.P(
286
+ id="upload-message-id", children=["Upload your data file (csv or excel)"], style={"paddingTop": "10px"}
287
+ ),
288
+ dbc.Modal(
289
+ id="data-modal",
290
+ children=[
291
+ dbc.ModalHeader(dbc.ModalTitle(id="modal-title", children="No data uploaded!")),
292
+ dbc.ModalBody(
293
+ id="modal-table",
294
+ children=table,
295
+ ),
296
+ ],
297
+ size="xl",
298
+ modal_class_name="modal-class",
299
+ ),
300
+ ],
301
+ id="table-modal-div",
302
+ )
303
+ return table_modal
304
+
305
+
306
+ class DropdownMenu(vm.VizroBaseModel):
307
+ """Custom dropdown menu component."""
308
+
309
+ type: Literal["dropdown_menu"] = "dropdown_menu"
310
+
311
+ def build(self):
312
+ """Returns custom dropdown menu."""
313
+ dropdown_menu = dbc.DropdownMenu(
314
+ id="dropdown-menu-button",
315
+ label="Download ",
316
+ children=[
317
+ dbc.DropdownMenuItem(
318
+ children=[
319
+ dbc.Button(children="JSON", id=f"{self.id}-json", className="download-button"),
320
+ ]
321
+ ),
322
+ dbc.DropdownMenuItem(
323
+ children=[
324
+ dbc.Button(children="HTML", id=f"{self.id}-html", className="download-button"),
325
+ ]
326
+ ),
327
+ dcc.Download(id="download-file"),
328
+ ],
329
+ toggleClassName="dropdown-menu-toggle-class",
330
+ )
331
+ download_div = html.Div(
332
+ children=[
333
+ html.Span("download", className="material-symbols-outlined", id=f"{self.id}-icon"),
334
+ dropdown_menu,
335
+ dbc.Tooltip(
336
+ "Download this plot to your device as a plotly JSON or interactive HTML file "
337
+ "for easy sharing or future use.",
338
+ target="dropdown-menu-icon",
339
+ ),
340
+ ],
341
+ id="dropdown-menu-id",
342
+ )
343
+
344
+ return download_div
345
+
346
+
347
+ class HeaderComponent(vm.VizroBaseModel):
348
+ """Custom header component."""
349
+
350
+ type: Literal["header"] = "header"
351
+
352
+ def build(self):
353
+ """Returns custom header component."""
354
+ title = html.Header("Vizro", id="custom-header-title")
355
+ header = html.Div(
356
+ children=[html.Img(src=get_asset_url("logo.svg"), alt="Vizro logo", className="header-logo"), title],
357
+ id="custom-header-div",
358
+ )
359
+ icon = html.Div(
360
+ children=[
361
+ html.Span("settings", className="material-symbols-outlined", id="open-settings-id"),
362
+ dbc.Tooltip("Settings", target="open-settings-id"),
363
+ ],
364
+ className="settings-div",
365
+ )
366
+
367
+ return html.Div(children=[header, icon], className="custom_header")
requirements.in CHANGED
@@ -1,6 +1,6 @@
1
  gunicorn
2
- vizro-ai>=0.2.1
3
  black
4
- jupyter
5
  openpyxl
6
- langgraph-checkpoint<=1.0.12
 
 
1
  gunicorn
2
+ vizro-ai>=0.3.0
3
  black
 
4
  openpyxl
5
+ langchain_anthropic
6
+ langchain_mistralai
requirements.txt CHANGED
@@ -1,43 +1,32 @@
1
  # This file was autogenerated by uv via the following command:
2
  # uv pip compile requirements.in -o requirements.txt
3
- aiohappyeyeballs==2.4.3
4
  # via aiohttp
5
- aiohttp==3.10.8
6
  # via langchain
7
  aiosignal==1.3.1
8
  # via aiohttp
9
  annotated-types==0.7.0
10
  # via pydantic
11
- anyio==4.6.0
 
 
12
  # via
 
13
  # httpx
14
- # jupyter-server
15
  # openai
16
- appnope==0.1.4
17
- # via ipykernel
18
- argon2-cffi==23.1.0
19
- # via jupyter-server
20
- argon2-cffi-bindings==21.2.0
21
- # via argon2-cffi
22
- arrow==1.3.0
23
- # via isoduration
24
- asttokens==2.4.1
25
- # via stack-data
26
- async-lru==2.0.4
27
- # via jupyterlab
28
- attrs==24.2.0
29
  # via
30
  # aiohttp
31
- # jsonschema
32
- # referencing
33
- babel==2.16.0
34
- # via jupyterlab-server
35
- beautifulsoup4==4.12.3
36
- # via nbconvert
37
  black==24.8.0
38
- # via -r requirements.in
39
- bleach==6.1.0
40
- # via nbconvert
41
  blinker==1.8.2
42
  # via flask
43
  cachelib==0.9.0
@@ -47,18 +36,12 @@ certifi==2024.8.30
47
  # httpcore
48
  # httpx
49
  # requests
50
- cffi==1.17.1
51
- # via argon2-cffi-bindings
52
  charset-normalizer==3.3.2
53
  # via requests
54
  click==8.1.7
55
  # via
56
  # black
57
  # flask
58
- comm==0.2.2
59
- # via
60
- # ipykernel
61
- # ipywidgets
62
  dash==2.18.1
63
  # via
64
  # dash-ag-grid
@@ -76,32 +59,32 @@ dash-mantine-components==0.12.1
76
  # via vizro
77
  dash-table==5.0.0
78
  # via dash
79
- debugpy==1.8.6
80
- # via ipykernel
81
- decorator==5.1.1
82
- # via ipython
83
  defusedxml==0.7.1
84
- # via nbconvert
85
  distro==1.9.0
86
- # via openai
 
 
87
  et-xmlfile==1.1.0
88
  # via openpyxl
89
- executing==2.1.0
90
- # via stack-data
91
- fastjsonschema==2.20.0
92
- # via nbformat
93
  flask==3.0.3
94
  # via
95
  # dash
96
  # flask-caching
97
  flask-caching==2.3.0
98
  # via vizro
99
- fqdn==1.5.1
100
- # via jsonschema
101
  frozenlist==1.4.1
102
  # via
103
  # aiohttp
104
  # aiosignal
 
 
 
 
105
  gunicorn==23.0.0
106
  # via -r requirements.in
107
  h11==0.14.0
@@ -110,173 +93,82 @@ httpcore==1.0.5
110
  # via httpx
111
  httpx==0.27.2
112
  # via
113
- # jupyterlab
 
114
  # langsmith
115
  # openai
116
- idna==3.10
 
 
 
 
117
  # via
118
  # anyio
119
  # httpx
120
- # jsonschema
121
  # requests
122
  # yarl
123
  importlib-metadata==8.5.0
124
- # via dash
125
- ipykernel==6.29.5
126
- # via
127
- # jupyter
128
- # jupyter-console
129
- # jupyterlab
130
- ipython==8.27.0
131
- # via
132
- # ipykernel
133
- # ipywidgets
134
- # jupyter-console
135
- ipywidgets==8.1.5
136
- # via jupyter
137
- isoduration==20.11.0
138
- # via jsonschema
139
  itsdangerous==2.2.0
140
  # via flask
141
- jedi==0.19.1
142
- # via ipython
143
  jinja2==3.1.4
144
- # via
145
- # flask
146
- # jupyter-server
147
- # jupyterlab
148
- # jupyterlab-server
149
- # nbconvert
150
  jiter==0.5.0
151
- # via openai
152
- json5==0.9.25
153
- # via jupyterlab-server
154
  jsonpatch==1.33
155
  # via langchain-core
156
  jsonpointer==3.0.0
157
- # via
158
- # jsonpatch
159
- # jsonschema
160
- jsonschema==4.23.0
161
- # via
162
- # jupyter-events
163
- # jupyterlab-server
164
- # nbformat
165
- jsonschema-specifications==2023.12.1
166
- # via jsonschema
167
- jupyter==1.1.1
168
- # via -r requirements.in
169
- jupyter-client==8.6.3
170
- # via
171
- # ipykernel
172
- # jupyter-console
173
- # jupyter-server
174
- # nbclient
175
- jupyter-console==6.6.3
176
- # via jupyter
177
- jupyter-core==5.7.2
178
- # via
179
- # ipykernel
180
- # jupyter-client
181
- # jupyter-console
182
- # jupyter-server
183
- # jupyterlab
184
- # nbclient
185
- # nbconvert
186
- # nbformat
187
- jupyter-events==0.10.0
188
- # via jupyter-server
189
- jupyter-lsp==2.2.5
190
- # via jupyterlab
191
- jupyter-server==2.14.2
192
- # via
193
- # jupyter-lsp
194
- # jupyterlab
195
- # jupyterlab-server
196
- # notebook
197
- # notebook-shim
198
- jupyter-server-terminals==0.5.3
199
- # via jupyter-server
200
- jupyterlab==4.2.5
201
- # via
202
- # jupyter
203
- # notebook
204
- jupyterlab-pygments==0.3.0
205
- # via nbconvert
206
- jupyterlab-server==2.27.3
207
- # via
208
- # jupyterlab
209
- # notebook
210
- jupyterlab-widgets==3.0.13
211
- # via ipywidgets
212
  langchain==0.2.16
213
  # via vizro-ai
214
- langchain-core==0.2.41
 
 
215
  # via
216
  # langchain
 
 
217
  # langchain-openai
218
  # langchain-text-splitters
219
  # langgraph
220
  # langgraph-checkpoint
221
- langchain-openai==0.1.25
 
 
222
  # via vizro-ai
223
  langchain-text-splitters==0.2.4
224
  # via langchain
225
  langgraph==0.2.16
226
  # via vizro-ai
227
- langgraph-checkpoint==1.0.12
228
  # via
229
- # -r requirements.in
230
  # langgraph
231
- langsmith==0.1.129
 
232
  # via
233
  # langchain
234
  # langchain-core
235
  markupsafe==2.1.5
236
  # via
237
  # jinja2
238
- # nbconvert
239
  # werkzeug
240
- matplotlib-inline==0.1.7
241
- # via
242
- # ipykernel
243
- # ipython
244
- mistune==3.0.2
245
- # via nbconvert
246
- msgpack==1.1.0
247
- # via langgraph-checkpoint
248
  multidict==6.1.0
249
  # via
250
  # aiohttp
251
  # yarl
252
  mypy-extensions==1.0.0
253
  # via black
254
- nbclient==0.10.0
255
- # via nbconvert
256
- nbconvert==7.16.4
257
- # via
258
- # jupyter
259
- # jupyter-server
260
- nbformat==5.10.4
261
- # via
262
- # jupyter-server
263
- # nbclient
264
- # nbconvert
265
  nest-asyncio==1.6.0
266
- # via
267
- # dash
268
- # ipykernel
269
- notebook==7.2.2
270
- # via jupyter
271
- notebook-shim==0.2.4
272
- # via
273
- # jupyterlab
274
- # notebook
275
  numpy==1.26.4
276
  # via
277
  # langchain
278
  # pandas
279
- openai==1.50.2
280
  # via
281
  # langchain-openai
282
  # vizro-ai
@@ -284,143 +176,73 @@ openpyxl==3.1.5
284
  # via -r requirements.in
285
  orjson==3.10.7
286
  # via langsmith
287
- overrides==7.7.0
288
- # via jupyter-server
289
  packaging==24.1
290
  # via
291
  # black
292
  # gunicorn
293
- # ipykernel
294
- # jupyter-server
295
- # jupyterlab
296
- # jupyterlab-server
297
  # langchain-core
298
- # nbconvert
299
  # plotly
300
- pandas==2.2.3
301
  # via
302
  # vizro
303
  # vizro-ai
304
- pandocfilters==1.5.1
305
- # via nbconvert
306
- parso==0.8.4
307
- # via jedi
308
  pathspec==0.12.1
309
  # via black
310
- pexpect==4.9.0
311
- # via ipython
312
- platformdirs==4.3.6
313
- # via
314
- # black
315
- # jupyter-core
316
  plotly==5.24.1
317
  # via dash
318
- prometheus-client==0.21.0
319
- # via jupyter-server
320
- prompt-toolkit==3.0.48
321
- # via
322
- # ipython
323
- # jupyter-console
324
- psutil==6.0.0
325
- # via ipykernel
326
- ptyprocess==0.7.0
327
- # via
328
- # pexpect
329
- # terminado
330
- pure-eval==0.2.3
331
- # via stack-data
332
- pycparser==2.22
333
- # via cffi
334
- pydantic==2.9.2
335
  # via
 
336
  # langchain
337
  # langchain-core
338
  # langsmith
339
  # openai
340
  # vizro
341
- pydantic-core==2.23.4
342
  # via pydantic
343
- pygments==2.18.0
344
- # via
345
- # ipython
346
- # jupyter-console
347
- # nbconvert
348
  python-dateutil==2.9.0.post0
349
- # via
350
- # arrow
351
- # jupyter-client
352
- # pandas
353
  python-dotenv==1.0.1
354
  # via vizro-ai
355
- python-json-logger==2.0.7
356
- # via jupyter-events
357
  pytz==2024.2
358
  # via pandas
359
  pyyaml==6.0.2
360
  # via
361
- # jupyter-events
362
  # langchain
363
  # langchain-core
364
- pyzmq==26.2.0
365
- # via
366
- # ipykernel
367
- # jupyter-client
368
- # jupyter-console
369
- # jupyter-server
370
- referencing==0.35.1
371
- # via
372
- # jsonschema
373
- # jsonschema-specifications
374
- # jupyter-events
375
  regex==2024.9.11
376
  # via tiktoken
377
  requests==2.32.3
378
  # via
379
  # dash
380
- # jupyterlab-server
381
  # langchain
382
  # langsmith
383
  # tiktoken
384
  retrying==1.3.4
385
  # via dash
386
- rfc3339-validator==0.1.4
387
- # via
388
- # jsonschema
389
- # jupyter-events
390
- rfc3986-validator==0.1.1
391
- # via
392
- # jsonschema
393
- # jupyter-events
394
- rpds-py==0.20.0
395
- # via
396
- # jsonschema
397
- # referencing
398
- ruff==0.6.8
399
  # via vizro
400
- send2trash==1.8.3
401
- # via jupyter-server
402
- setuptools==75.1.0
403
- # via
404
- # dash
405
- # jupyterlab
406
  six==1.16.0
407
  # via
408
- # asttokens
409
- # bleach
410
  # python-dateutil
411
  # retrying
412
- # rfc3339-validator
413
  sniffio==1.3.1
414
  # via
 
415
  # anyio
416
  # httpx
417
  # openai
418
- soupsieve==2.6
419
- # via beautifulsoup4
420
- sqlalchemy==2.0.35
421
  # via langchain
422
- stack-data==0.6.3
423
- # via ipython
424
  tabulate==0.9.0
425
  # via vizro-ai
426
  tenacity==8.5.0
@@ -428,79 +250,48 @@ tenacity==8.5.0
428
  # langchain
429
  # langchain-core
430
  # plotly
431
- terminado==0.18.1
432
- # via
433
- # jupyter-server
434
- # jupyter-server-terminals
435
  tiktoken==0.7.0
436
  # via langchain-openai
437
- tinycss2==1.3.0
438
- # via nbconvert
439
- tornado==6.4.1
440
- # via
441
- # ipykernel
442
- # jupyter-client
443
- # jupyter-server
444
- # jupyterlab
445
- # notebook
446
- # terminado
447
  tqdm==4.66.5
448
- # via openai
449
- traitlets==5.14.3
450
- # via
451
- # comm
452
- # ipykernel
453
- # ipython
454
- # ipywidgets
455
- # jupyter-client
456
- # jupyter-console
457
- # jupyter-core
458
- # jupyter-events
459
- # jupyter-server
460
- # jupyterlab
461
- # matplotlib-inline
462
- # nbclient
463
- # nbconvert
464
- # nbformat
465
- types-python-dateutil==2.9.0.20240906
466
- # via arrow
467
  typing-extensions==4.12.2
468
  # via
 
 
 
469
  # dash
 
470
  # langchain-core
 
471
  # openai
472
  # pydantic
473
  # pydantic-core
474
  # sqlalchemy
475
- tzdata==2024.2
476
  # via pandas
477
- uri-template==1.3.0
478
- # via jsonschema
479
  urllib3==2.2.3
480
  # via requests
481
  vizro==0.1.23
482
  # via vizro-ai
483
- vizro-ai==0.2.3
484
  # via -r requirements.in
485
- wcwidth==0.2.13
486
- # via prompt-toolkit
487
- webcolors==24.8.0
488
- # via jsonschema
489
- webencodings==0.5.1
490
- # via
491
- # bleach
492
- # tinycss2
493
- websocket-client==1.8.0
494
- # via jupyter-server
495
  werkzeug==3.0.4
496
  # via
497
  # dash
498
  # flask
499
- widgetsnbextension==4.0.13
500
- # via ipywidgets
501
  wrapt==1.16.0
502
  # via vizro
503
- yarl==1.13.1
504
  # via aiohttp
505
- zipp==3.20.2
506
  # via importlib-metadata
 
1
  # This file was autogenerated by uv via the following command:
2
  # uv pip compile requirements.in -o requirements.txt
3
+ aiohappyeyeballs==2.4.0
4
  # via aiohttp
5
+ aiohttp==3.10.5
6
  # via langchain
7
  aiosignal==1.3.1
8
  # via aiohttp
9
  annotated-types==0.7.0
10
  # via pydantic
11
+ anthropic==0.38.0
12
+ # via langchain-anthropic
13
+ anyio==4.4.0
14
  # via
15
+ # anthropic
16
  # httpx
 
17
  # openai
18
+ async-timeout==4.0.3
 
 
 
 
 
 
 
 
 
 
 
 
19
  # via
20
  # aiohttp
21
+ # langchain
22
+ attrs==24.2.0
23
+ # via aiohttp
24
+ autoflake==2.3.1
25
+ # via vizro-ai
 
26
  black==24.8.0
27
+ # via
28
+ # -r requirements.in
29
+ # vizro-ai
30
  blinker==1.8.2
31
  # via flask
32
  cachelib==0.9.0
 
36
  # httpcore
37
  # httpx
38
  # requests
 
 
39
  charset-normalizer==3.3.2
40
  # via requests
41
  click==8.1.7
42
  # via
43
  # black
44
  # flask
 
 
 
 
45
  dash==2.18.1
46
  # via
47
  # dash-ag-grid
 
59
  # via vizro
60
  dash-table==5.0.0
61
  # via dash
 
 
 
 
62
  defusedxml==0.7.1
63
+ # via langchain-anthropic
64
  distro==1.9.0
65
+ # via
66
+ # anthropic
67
+ # openai
68
  et-xmlfile==1.1.0
69
  # via openpyxl
70
+ exceptiongroup==1.2.2
71
+ # via anyio
72
+ filelock==3.16.1
73
+ # via huggingface-hub
74
  flask==3.0.3
75
  # via
76
  # dash
77
  # flask-caching
78
  flask-caching==2.3.0
79
  # via vizro
 
 
80
  frozenlist==1.4.1
81
  # via
82
  # aiohttp
83
  # aiosignal
84
+ fsspec==2024.10.0
85
+ # via huggingface-hub
86
+ greenlet==3.1.0
87
+ # via sqlalchemy
88
  gunicorn==23.0.0
89
  # via -r requirements.in
90
  h11==0.14.0
 
93
  # via httpx
94
  httpx==0.27.2
95
  # via
96
+ # anthropic
97
+ # langchain-mistralai
98
  # langsmith
99
  # openai
100
+ httpx-sse==0.4.0
101
+ # via langchain-mistralai
102
+ huggingface-hub==0.26.2
103
+ # via tokenizers
104
+ idna==3.8
105
  # via
106
  # anyio
107
  # httpx
 
108
  # requests
109
  # yarl
110
  importlib-metadata==8.5.0
111
+ # via
112
+ # dash
113
+ # flask
 
 
 
 
 
 
 
 
 
 
 
 
114
  itsdangerous==2.2.0
115
  # via flask
 
 
116
  jinja2==3.1.4
117
+ # via flask
 
 
 
 
 
118
  jiter==0.5.0
119
+ # via
120
+ # anthropic
121
+ # openai
122
  jsonpatch==1.33
123
  # via langchain-core
124
  jsonpointer==3.0.0
125
+ # via jsonpatch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  langchain==0.2.16
127
  # via vizro-ai
128
+ langchain-anthropic==0.1.23
129
+ # via -r requirements.in
130
+ langchain-core==0.2.39
131
  # via
132
  # langchain
133
+ # langchain-anthropic
134
+ # langchain-mistralai
135
  # langchain-openai
136
  # langchain-text-splitters
137
  # langgraph
138
  # langgraph-checkpoint
139
+ langchain-mistralai==0.1.13
140
+ # via -r requirements.in
141
+ langchain-openai==0.1.23
142
  # via vizro-ai
143
  langchain-text-splitters==0.2.4
144
  # via langchain
145
  langgraph==0.2.16
146
  # via vizro-ai
147
+ langgraph-checkpoint==1.0.9
148
  # via
 
149
  # langgraph
150
+ # vizro-ai
151
+ langsmith==0.1.119
152
  # via
153
  # langchain
154
  # langchain-core
155
  markupsafe==2.1.5
156
  # via
157
  # jinja2
 
158
  # werkzeug
 
 
 
 
 
 
 
 
159
  multidict==6.1.0
160
  # via
161
  # aiohttp
162
  # yarl
163
  mypy-extensions==1.0.0
164
  # via black
 
 
 
 
 
 
 
 
 
 
 
165
  nest-asyncio==1.6.0
166
+ # via dash
 
 
 
 
 
 
 
 
167
  numpy==1.26.4
168
  # via
169
  # langchain
170
  # pandas
171
+ openai==1.45.0
172
  # via
173
  # langchain-openai
174
  # vizro-ai
 
176
  # via -r requirements.in
177
  orjson==3.10.7
178
  # via langsmith
 
 
179
  packaging==24.1
180
  # via
181
  # black
182
  # gunicorn
183
+ # huggingface-hub
 
 
 
184
  # langchain-core
 
185
  # plotly
186
+ pandas==2.2.2
187
  # via
188
  # vizro
189
  # vizro-ai
 
 
 
 
190
  pathspec==0.12.1
191
  # via black
192
+ platformdirs==4.3.2
193
+ # via black
 
 
 
 
194
  plotly==5.24.1
195
  # via dash
196
+ pydantic==2.9.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  # via
198
+ # anthropic
199
  # langchain
200
  # langchain-core
201
  # langsmith
202
  # openai
203
  # vizro
204
+ pydantic-core==2.23.3
205
  # via pydantic
206
+ pyflakes==3.2.0
207
+ # via autoflake
 
 
 
208
  python-dateutil==2.9.0.post0
209
+ # via pandas
 
 
 
210
  python-dotenv==1.0.1
211
  # via vizro-ai
 
 
212
  pytz==2024.2
213
  # via pandas
214
  pyyaml==6.0.2
215
  # via
216
+ # huggingface-hub
217
  # langchain
218
  # langchain-core
 
 
 
 
 
 
 
 
 
 
 
219
  regex==2024.9.11
220
  # via tiktoken
221
  requests==2.32.3
222
  # via
223
  # dash
224
+ # huggingface-hub
225
  # langchain
226
  # langsmith
227
  # tiktoken
228
  retrying==1.3.4
229
  # via dash
230
+ ruff==0.6.4
 
 
 
 
 
 
 
 
 
 
 
 
231
  # via vizro
232
+ setuptools==74.1.2
233
+ # via dash
 
 
 
 
234
  six==1.16.0
235
  # via
 
 
236
  # python-dateutil
237
  # retrying
 
238
  sniffio==1.3.1
239
  # via
240
+ # anthropic
241
  # anyio
242
  # httpx
243
  # openai
244
+ sqlalchemy==2.0.34
 
 
245
  # via langchain
 
 
246
  tabulate==0.9.0
247
  # via vizro-ai
248
  tenacity==8.5.0
 
250
  # langchain
251
  # langchain-core
252
  # plotly
 
 
 
 
253
  tiktoken==0.7.0
254
  # via langchain-openai
255
+ tokenizers==0.20.1
256
+ # via
257
+ # anthropic
258
+ # langchain-mistralai
259
+ tomli==2.0.1
260
+ # via
261
+ # autoflake
262
+ # black
 
 
263
  tqdm==4.66.5
264
+ # via
265
+ # huggingface-hub
266
+ # openai
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  typing-extensions==4.12.2
268
  # via
269
+ # anthropic
270
+ # anyio
271
+ # black
272
  # dash
273
+ # huggingface-hub
274
  # langchain-core
275
+ # multidict
276
  # openai
277
  # pydantic
278
  # pydantic-core
279
  # sqlalchemy
280
+ tzdata==2024.1
281
  # via pandas
 
 
282
  urllib3==2.2.3
283
  # via requests
284
  vizro==0.1.23
285
  # via vizro-ai
286
+ vizro-ai==0.3.0
287
  # via -r requirements.in
 
 
 
 
 
 
 
 
 
 
288
  werkzeug==3.0.4
289
  # via
290
  # dash
291
  # flask
 
 
292
  wrapt==1.16.0
293
  # via vizro
294
+ yarl==1.11.1
295
  # via aiohttp
296
+ zipp==3.20.1
297
  # via importlib-metadata