maxschulz-COL commited on
Commit
d65e306
Β·
1 Parent(s): 7b73298

Initial commit

Browse files
Files changed (11) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +18 -0
  3. README.md +2 -2
  4. _utils.py +8 -0
  5. actions.py +129 -0
  6. app.py +199 -0
  7. assets/custom_css.css +159 -0
  8. assets/logo.svg +3 -0
  9. components.py +244 -0
  10. requirements.in +4 -0
  11. requirements.txt +507 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
18
+ CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:7860", "app:server"]
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: Vizro Ai UI
3
- emoji: πŸ“‰
4
  colorFrom: blue
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
 
1
  ---
2
  title: Vizro Ai UI
3
+ emoji: πŸŒ–
4
  colorFrom: blue
5
+ colorTo: pink
6
  sdk: docker
7
  pinned: false
8
  license: apache-2.0
_utils.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ """Utils file."""
2
+
3
+
4
+ def check_file_extension(filename):
5
+ filename = filename.lower()
6
+
7
+ # Check if the filename ends with .csv or .xls
8
+ return filename.endswith(".csv") or filename.endswith(".xls") or filename.endswith(".xlsx")
actions.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Custom actions used within a dashboard."""
2
+
3
+ import base64
4
+ 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
11
+ from langchain_openai import ChatOpenAI
12
+ 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
+
31
+
32
+ @capture("action")
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
46
+
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.")
69
+ df = pd.DataFrame(data["data"])
70
+ ai_outputs = get_vizro_ai_plot(
71
+ user_prompt=user_prompt,
72
+ df=df,
73
+ model=model,
74
+ api_key=api_key,
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")
95
+ def data_upload_action(contents, filename):
96
+ """Custom data upload action."""
97
+ if not contents:
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
+
105
+ try:
106
+ decoded = base64.b64decode(content_string)
107
+ if filename.endswith(".csv"):
108
+ # Handle CSV file
109
+ df = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
110
+ else:
111
+ # Handle Excel file
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")
123
+ def display_filename(data):
124
+ """Custom action to display uploaded filename."""
125
+ if data is None:
126
+ raise PreventUpdate
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
app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """VizroAI UI dashboard configuration."""
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
+ MyDropdown,
15
+ MyPage,
16
+ OffCanvas,
17
+ UserPromptTextArea,
18
+ UserUpload,
19
+ Modal
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",
73
+ actions=[
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(),
99
+ inputs=[
100
+ "text-area-id.value",
101
+ "trigger-button-id.n_clicks",
102
+ "data-store-id.data",
103
+ "model-dropdown-id.value",
104
+ "settings-api-key.value",
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
+
121
+
122
+ 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(
151
+ Output("settings", "is_open"),
152
+ Input("open-settings-id", "n_clicks"),
153
+ [State("settings", "is_open")],
154
+ )
155
+ def open_settings(n_clicks, is_open):
156
+ """Callback for opening and closing offcanvas settings component."""
157
+ return not is_open if n_clicks else is_open
158
+
159
+
160
+ @callback(
161
+ Output("settings-api-key", "type"),
162
+ Input("settings-api-key-toggle", "value"),
163
+ )
164
+ def show_api_key(value):
165
+ """Callback to show api key."""
166
+ return "text" if value else "password"
167
+
168
+
169
+ @callback(
170
+ Output("settings-api-base", "type"),
171
+ Input("settings-api-base-toggle", "value"),
172
+ )
173
+ def show_api_base(value):
174
+ """Callback to show api base."""
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",
194
+ )
195
+ )
196
+
197
+
198
+ if __name__ == "__main__":
199
+ app.run()
assets/custom_css.css ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #page-header {
2
+ display: none;
3
+ }
4
+
5
+ .card-body {
6
+ color: var(--text-light-mode-secondary);
7
+ }
8
+
9
+ .textbox {
10
+ border-radius: 24px;
11
+ font-size: var(--text-size-02);
12
+ margin-bottom: 20px;
13
+ max-width: 60%;
14
+ padding: 4px 12px;
15
+ width: max-content;
16
+ }
17
+
18
+ .user_input:focus {
19
+ background: var(--field-enabled);
20
+ box-shadow: 0 0 0 2px var(--focus-focus) inset;
21
+ color: var(--text-primary);
22
+ outline-width: 0;
23
+ }
24
+
25
+ #text-area-id {
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
+ }
33
+
34
+ #code-clipboard {
35
+ padding: 8px;
36
+ }
37
+
38
+ .code-clipboard {
39
+ font-size: 20px;
40
+ position: absolute;
41
+ right: 14px;
42
+ top: 12px;
43
+ }
44
+
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;
53
+ }
54
+
55
+ .code-clipboard-container::-webkit-scrollbar-thumb {
56
+ border-color: var(--surfaces-bg-card);
57
+ }
58
+
59
+ #model-dropdown .Select--single .Select-value {
60
+ background-color: inherit;
61
+ font-size: 12px;
62
+ }
63
+
64
+ #model-dropdown .Select-control {
65
+ background-color: inherit;
66
+ font-size: 12px;
67
+ }
68
+
69
+ #model-dropdown-id .Select-menu-outer {
70
+ font-size: 12px;
71
+
72
+ /* top: 0; */
73
+
74
+ /* transform: translateY(3px) translateY(-100%); */
75
+ }
76
+
77
+ #model-dropdow-idn .dash-dropdown {
78
+ background-color: inherit;
79
+ font-size: 12px;
80
+ }
81
+
82
+ #trigger-button-id {
83
+ width: 100%;
84
+ }
85
+
86
+ #dashboard-container .dash-dropdown {
87
+ background-color: inherit;
88
+ }
89
+
90
+ #model-dropdown-id .Select-clear {
91
+ display: none;
92
+ }
93
+
94
+ #save-button-id {
95
+ width: 50%;
96
+ }
97
+
98
+ .card:has(#upload-message-id) {
99
+ background-color: inherit;
100
+ box-shadow: none;
101
+ font-size: 12px;
102
+ overflow: hidden;
103
+ padding-bottom: 0;
104
+ padding-left: 0;
105
+ padding-top: 8px;
106
+ }
107
+
108
+ .card:has(#settings-card-id) {
109
+ background-color: inherit;
110
+ box-shadow: none;
111
+ font-size: 12px;
112
+ overflow: hidden;
113
+ padding-bottom: 0;
114
+ padding-left: 0;
115
+ }
116
+
117
+ .settings-div {
118
+ display: flex;
119
+ justify-content: end;
120
+ padding-right: 2px;
121
+ width: 100%;
122
+ }
123
+
124
+ #data-upload-id {
125
+ border: 1px dashed var(--border-subtle-alpha-01);
126
+ border-radius: 5px;
127
+ color: var(--text-primary);
128
+ height: 46px;
129
+ line-height: 46px;
130
+ text-align: center;
131
+ }
132
+
133
+ #settings-api-key-toggle .form-check-input {
134
+ border-radius: 8px;
135
+ }
136
+
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;
144
+ display: flex;
145
+ gap: 4px;
146
+ justify-content: center;
147
+ }
148
+
149
+ .anchor-container {
150
+ background: #060a17;
151
+ bottom: 0;
152
+ display: flex;
153
+ font-weight: 600;
154
+ gap: 2rem;
155
+ padding: 4px;
156
+ place-content: baseline center;
157
+ position: fixed;
158
+ width: 100%;
159
+ }
assets/logo.svg ADDED
components.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):
16
+ """Input component `UserPromptTextArea`.
17
+
18
+ Based on the underlying [`dcc.Input`](https://dash.plotly.com/dash-core-components/input).
19
+
20
+ Args:
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
+
33
+ @_log_call
34
+ def build(self):
35
+ """Returns the text area component to display vizro-ai code output."""
36
+ return html.Div(
37
+ children=[
38
+ dcc.Textarea(
39
+ id=self.id,
40
+ placeholder="Describe the chart you want to create, e.g. "
41
+ "'Visualize the life expectancy per continent.'",
42
+ )
43
+ ]
44
+ )
45
+
46
+
47
+ class UserUpload(vm.VizroBaseModel):
48
+ """Component enabling data upload.
49
+
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")
62
+ # change in 'contents' property of Upload component triggers the actions
63
+ _set_actions = _action_validator_factory("contents")
64
+
65
+ def build(self):
66
+ """Returns the upload component for data upload."""
67
+ return html.Div(
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
+ )
77
+
78
+
79
+ class CodeClipboard(vm.VizroBaseModel):
80
+ """Code snippet with a copy to clipboard button."""
81
+
82
+ type: Literal["code_clipboard"] = "code_clipboard"
83
+ code: str = ""
84
+ language: str = "python"
85
+
86
+ def build(self):
87
+ """Returns the code clipboard component inside a output text area."""
88
+ code = black.format_str(self.code, mode=black.Mode(line_length=120))
89
+ code = code.strip("'\"")
90
+
91
+ markdown_code = "\n".join(["```python", code, "```"])
92
+
93
+ return html.Div(
94
+ [
95
+ dcc.Clipboard(target_id=f"{self.id}-code-markdown", className="code-clipboard"),
96
+ dcc.Markdown(markdown_code, id=f"{self.id}-code-markdown"),
97
+ ],
98
+ className="code-clipboard-container",
99
+ )
100
+
101
+
102
+ class MyDropdown(vm.Dropdown):
103
+ """Custom dropdown component."""
104
+
105
+ type: Literal["my_dropdown"] = "my_dropdown"
106
+
107
+ def build(self):
108
+ """Returns custom dropdown component that cannot be cleared."""
109
+ dropdown_build_obj = super().build()
110
+ dropdown_build_obj.id = f"{self.id}_outer_div"
111
+ dropdown_build_obj.children[1].clearable = False
112
+
113
+ return dropdown_build_obj
114
+
115
+
116
+ class Modal(vm.VizroBaseModel):
117
+ """Modal to convey warning message"""
118
+
119
+ type: Literal["modal"] = "modal"
120
+
121
+ def build(self):
122
+ """Returns the modal component."""
123
+ return dbc.Modal(
124
+ # id=self.id,
125
+ children=[
126
+ dbc.ModalHeader(children=dcc.Markdown("""# Warning""")),
127
+ dbc.ModalBody(children=dcc.Markdown("""### Do NOT upload any sensitive or personally identifying data.
128
+
129
+ #### Reasoning:
130
+ This space is hosted publicly running one server in a single container. Further this UI executes dynamically created code on the server. It thus
131
+ 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.
132
+
133
+ #### Alternatives:
134
+ If sending your query and data to a LLM is acceptable, you can pull and run this image locally. This will avoid sharing
135
+ an instance with others. You can do so by clicking the three dots in the top right of the HuggingFace banner und click `Run with Docker`.
136
+
137
+ In any case, please remain cautious and understand the responsibilities of sharing data.
138
+ """)),
139
+ ],
140
+ size="l",
141
+ is_open=True,
142
+ )
143
+
144
+ class OffCanvas(vm.VizroBaseModel):
145
+ """OffCanvas component for settings."""
146
+
147
+ type: Literal["offcanvas"] = "offcanvas"
148
+ options: List[str]
149
+ value: str
150
+
151
+ def build(self):
152
+ """Returns the off canvas component for settings."""
153
+ input_groups = html.Div(
154
+ [
155
+ dbc.InputGroup(
156
+ [
157
+ dbc.InputGroupText("API Key"),
158
+ dbc.Input(placeholder="API key", type="password", id=f"{self.id}-api-key"),
159
+ html.Div(
160
+ dbc.Checklist(
161
+ id=f"{self.id}-api-key-toggle",
162
+ options=[{"label": "", "value": False}],
163
+ switch=True,
164
+ inline=True,
165
+ ),
166
+ id="toggle-div-api-key",
167
+ ),
168
+ ],
169
+ className="mb-3",
170
+ ),
171
+ dbc.InputGroup(
172
+ [
173
+ dbc.InputGroupText("API base"),
174
+ dbc.Input(placeholder="(optional) API base", type="password", id=f"{self.id}-api-base"),
175
+ html.Div(
176
+ dbc.Checklist(
177
+ id=f"{self.id}-api-base-toggle",
178
+ options=[{"label": "", "value": False}],
179
+ switch=True,
180
+ inline=True,
181
+ ),
182
+ id="toggle-div-api-base",
183
+ ),
184
+ ],
185
+ className="mb-3",
186
+ ),
187
+ dbc.InputGroup(
188
+ [
189
+ dbc.InputGroupText("Choose your vendor"),
190
+ dbc.Select(options=self.options, value=self.value, id=f"{self.id}-dropdown"),
191
+ ],
192
+ className="mb-3",
193
+ ),
194
+ ],
195
+ className="mb-3",
196
+ )
197
+
198
+ offcanvas = dbc.Offcanvas(
199
+ id=self.id,
200
+ children=[
201
+ html.Div(
202
+ children=[
203
+ input_groups,
204
+ ]
205
+ )
206
+ ],
207
+ title="Settings",
208
+ is_open=True,
209
+ )
210
+ return offcanvas
211
+
212
+
213
+ class MyPage(vm.Page):
214
+ """Custom page."""
215
+
216
+ type: Literal["my_page"] = "my_page"
217
+
218
+ def pre_build(self):
219
+ """Overwriting pre_build."""
220
+ pass
221
+
222
+
223
+ class Icon(vm.VizroBaseModel):
224
+ """Icon component for settings."""
225
+
226
+ type: Literal["icon"] = "icon"
227
+
228
+ def build(self):
229
+ """Returns the icon for api settings."""
230
+ return html.Div(
231
+ children=[html.Span("settings", className="material-symbols-outlined", id=self.id)],
232
+ className="settings-div",
233
+ )
234
+
235
+
236
+ class CustomDashboard(vm.Dashboard):
237
+ """Custom Dashboard model."""
238
+
239
+ def build(self):
240
+ """Returns custom dashboard."""
241
+ dashboard_build_obj = super().build()
242
+ dashboard_build_obj.children.append(dcc.Store(id="data-store-id", storage_type="session"))
243
+ dashboard_build_obj.children.append(dcc.Store(id="outputs-store-id", storage_type="session"))
244
+ return dashboard_build_obj
requirements.in ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gunicorn
2
+ vizro-ai>=0.2.1
3
+ black
4
+ jupyter
requirements.txt ADDED
@@ -0,0 +1,507 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ anyio==4.4.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
44
+ # via flask-caching
45
+ certifi==2024.7.4
46
+ # via
47
+ # httpcore
48
+ # httpx
49
+ # requests
50
+ cffi==1.17.0
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.17.1
63
+ # via
64
+ # dash-ag-grid
65
+ # dash-bootstrap-components
66
+ # vizro
67
+ dash-ag-grid==31.2.0
68
+ # via vizro
69
+ dash-bootstrap-components==1.6.0
70
+ # via vizro
71
+ dash-core-components==2.0.0
72
+ # via dash
73
+ dash-html-components==2.0.0
74
+ # via dash
75
+ dash-mantine-components==0.12.1
76
+ # via vizro
77
+ dash-table==5.0.0
78
+ # via dash
79
+ debugpy==1.8.5
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
+ executing==2.0.1
88
+ # via stack-data
89
+ fastjsonschema==2.20.0
90
+ # via nbformat
91
+ flask==3.0.3
92
+ # via
93
+ # dash
94
+ # flask-caching
95
+ flask-caching==2.3.0
96
+ # via vizro
97
+ fqdn==1.5.1
98
+ # via jsonschema
99
+ frozenlist==1.4.1
100
+ # via
101
+ # aiohttp
102
+ # aiosignal
103
+ gunicorn==23.0.0
104
+ # via -r requirements.in
105
+ h11==0.14.0
106
+ # via httpcore
107
+ httpcore==1.0.5
108
+ # via httpx
109
+ httpx==0.27.2
110
+ # via
111
+ # jupyterlab
112
+ # langsmith
113
+ # openai
114
+ idna==3.8
115
+ # via
116
+ # anyio
117
+ # httpx
118
+ # jsonschema
119
+ # requests
120
+ # yarl
121
+ importlib-metadata==8.4.0
122
+ # via dash
123
+ ipykernel==6.29.5
124
+ # via
125
+ # jupyter
126
+ # jupyter-console
127
+ # jupyterlab
128
+ # qtconsole
129
+ ipython==8.26.0
130
+ # via
131
+ # ipykernel
132
+ # ipywidgets
133
+ # jupyter-console
134
+ ipywidgets==8.1.5
135
+ # via jupyter
136
+ isoduration==20.11.0
137
+ # via jsonschema
138
+ itsdangerous==2.2.0
139
+ # via flask
140
+ jedi==0.19.1
141
+ # via ipython
142
+ jinja2==3.1.4
143
+ # via
144
+ # flask
145
+ # jupyter-server
146
+ # jupyterlab
147
+ # jupyterlab-server
148
+ # nbconvert
149
+ jiter==0.5.0
150
+ # via openai
151
+ json5==0.9.25
152
+ # via jupyterlab-server
153
+ jsonpatch==1.33
154
+ # via langchain-core
155
+ jsonpointer==3.0.0
156
+ # via
157
+ # jsonpatch
158
+ # jsonschema
159
+ jsonschema==4.23.0
160
+ # via
161
+ # jupyter-events
162
+ # jupyterlab-server
163
+ # nbformat
164
+ jsonschema-specifications==2023.12.1
165
+ # via jsonschema
166
+ jupyter==1.0.0
167
+ # via -r requirements.in
168
+ jupyter-client==8.6.2
169
+ # via
170
+ # ipykernel
171
+ # jupyter-console
172
+ # jupyter-server
173
+ # nbclient
174
+ # qtconsole
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
+ # qtconsole
188
+ jupyter-events==0.10.0
189
+ # via jupyter-server
190
+ jupyter-lsp==2.2.5
191
+ # via jupyterlab
192
+ jupyter-server==2.14.2
193
+ # via
194
+ # jupyter-lsp
195
+ # jupyterlab
196
+ # jupyterlab-server
197
+ # notebook
198
+ # notebook-shim
199
+ jupyter-server-terminals==0.5.3
200
+ # via jupyter-server
201
+ jupyterlab==4.2.5
202
+ # via notebook
203
+ jupyterlab-pygments==0.3.0
204
+ # via nbconvert
205
+ jupyterlab-server==2.27.3
206
+ # via
207
+ # jupyterlab
208
+ # notebook
209
+ jupyterlab-widgets==3.0.13
210
+ # via ipywidgets
211
+ langchain==0.2.15
212
+ # via vizro-ai
213
+ langchain-core==0.2.36
214
+ # via
215
+ # langchain
216
+ # langchain-openai
217
+ # langchain-text-splitters
218
+ # langgraph
219
+ # langgraph-checkpoint
220
+ langchain-openai==0.1.23
221
+ # via vizro-ai
222
+ langchain-text-splitters==0.2.2
223
+ # via langchain
224
+ langgraph==0.2.14
225
+ # via vizro-ai
226
+ langgraph-checkpoint==1.0.6
227
+ # via langgraph
228
+ langsmith==0.1.106
229
+ # via
230
+ # langchain
231
+ # langchain-core
232
+ markupsafe==2.1.5
233
+ # via
234
+ # jinja2
235
+ # nbconvert
236
+ # werkzeug
237
+ matplotlib-inline==0.1.7
238
+ # via
239
+ # ipykernel
240
+ # ipython
241
+ mistune==3.0.2
242
+ # via nbconvert
243
+ multidict==6.0.5
244
+ # via
245
+ # aiohttp
246
+ # yarl
247
+ mypy-extensions==1.0.0
248
+ # via black
249
+ nbclient==0.10.0
250
+ # via nbconvert
251
+ nbconvert==7.16.4
252
+ # via
253
+ # jupyter
254
+ # jupyter-server
255
+ nbformat==5.10.4
256
+ # via
257
+ # jupyter-server
258
+ # nbclient
259
+ # nbconvert
260
+ nest-asyncio==1.6.0
261
+ # via
262
+ # dash
263
+ # ipykernel
264
+ notebook==7.2.2
265
+ # via jupyter
266
+ notebook-shim==0.2.4
267
+ # via
268
+ # jupyterlab
269
+ # notebook
270
+ numpy==1.26.4
271
+ # via
272
+ # langchain
273
+ # pandas
274
+ openai==1.42.0
275
+ # via
276
+ # langchain-openai
277
+ # vizro-ai
278
+ orjson==3.10.7
279
+ # via langsmith
280
+ overrides==7.7.0
281
+ # via jupyter-server
282
+ packaging==24.1
283
+ # via
284
+ # black
285
+ # gunicorn
286
+ # ipykernel
287
+ # jupyter-server
288
+ # jupyterlab
289
+ # jupyterlab-server
290
+ # langchain-core
291
+ # nbconvert
292
+ # plotly
293
+ # qtconsole
294
+ # qtpy
295
+ pandas==2.2.2
296
+ # via
297
+ # vizro
298
+ # vizro-ai
299
+ pandocfilters==1.5.1
300
+ # via nbconvert
301
+ parso==0.8.4
302
+ # via jedi
303
+ pathspec==0.12.1
304
+ # via black
305
+ pexpect==4.9.0
306
+ # via ipython
307
+ platformdirs==4.2.2
308
+ # via
309
+ # black
310
+ # jupyter-core
311
+ plotly==5.23.0
312
+ # via dash
313
+ prometheus-client==0.20.0
314
+ # via jupyter-server
315
+ prompt-toolkit==3.0.47
316
+ # via
317
+ # ipython
318
+ # jupyter-console
319
+ psutil==6.0.0
320
+ # via ipykernel
321
+ ptyprocess==0.7.0
322
+ # via
323
+ # pexpect
324
+ # terminado
325
+ pure-eval==0.2.3
326
+ # via stack-data
327
+ pycparser==2.22
328
+ # via cffi
329
+ pydantic==2.8.2
330
+ # via
331
+ # langchain
332
+ # langchain-core
333
+ # langsmith
334
+ # openai
335
+ # vizro
336
+ pydantic-core==2.20.1
337
+ # via pydantic
338
+ pygments==2.18.0
339
+ # via
340
+ # ipython
341
+ # jupyter-console
342
+ # nbconvert
343
+ # qtconsole
344
+ python-dateutil==2.9.0.post0
345
+ # via
346
+ # arrow
347
+ # jupyter-client
348
+ # pandas
349
+ python-dotenv==1.0.1
350
+ # via vizro-ai
351
+ python-json-logger==2.0.7
352
+ # via jupyter-events
353
+ pytz==2024.1
354
+ # via pandas
355
+ pyyaml==6.0.2
356
+ # via
357
+ # jupyter-events
358
+ # langchain
359
+ # langchain-core
360
+ pyzmq==26.2.0
361
+ # via
362
+ # ipykernel
363
+ # jupyter-client
364
+ # jupyter-console
365
+ # jupyter-server
366
+ qtconsole==5.6.0
367
+ # via jupyter
368
+ qtpy==2.4.1
369
+ # via qtconsole
370
+ referencing==0.35.1
371
+ # via
372
+ # jsonschema
373
+ # jsonschema-specifications
374
+ # jupyter-events
375
+ regex==2024.7.24
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.2
399
+ # via vizro
400
+ send2trash==1.8.3
401
+ # via jupyter-server
402
+ setuptools==74.0.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.32
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
427
+ # via
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
+ # qtconsole
466
+ types-python-dateutil==2.9.0.20240821
467
+ # via arrow
468
+ typing-extensions==4.12.2
469
+ # via
470
+ # dash
471
+ # langchain-core
472
+ # openai
473
+ # pydantic
474
+ # pydantic-core
475
+ # sqlalchemy
476
+ tzdata==2024.1
477
+ # via pandas
478
+ uri-template==1.3.0
479
+ # via jsonschema
480
+ urllib3==2.2.2
481
+ # via requests
482
+ vizro==0.1.21
483
+ # via vizro-ai
484
+ vizro-ai==0.2.1
485
+ # via -r requirements.in
486
+ wcwidth==0.2.13
487
+ # via prompt-toolkit
488
+ webcolors==24.8.0
489
+ # via jsonschema
490
+ webencodings==0.5.1
491
+ # via
492
+ # bleach
493
+ # tinycss2
494
+ websocket-client==1.8.0
495
+ # via jupyter-server
496
+ werkzeug==3.0.4
497
+ # via
498
+ # dash
499
+ # flask
500
+ widgetsnbextension==4.0.13
501
+ # via ipywidgets
502
+ wrapt==1.16.0
503
+ # via vizro
504
+ yarl==1.9.4
505
+ # via aiohttp
506
+ zipp==3.20.1
507
+ # via importlib-metadata