Skip to content

Commit 1dded99

Browse files
Updated source code with genie room as added resource (#574)
[updated source code with genie room as added resource](912fd4f) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent deb061b commit 1dded99

18 files changed

+189
-201
lines changed

conversational-agent-app/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ date: 2025-06-09
1515
This repository demonstrates how to integrate Databricks' AI/BI Genie Conversation APIs into custom Databricks Apps applications, allowing users to interact with their structured data using natural language.
1616

1717
You can also click the Generate insights button and generate deep analysis and trends of your data.
18-
![](./assets/insights1.png)
19-
![](./assets/insights2.png)
20-
21-
18+
![](./assets/genie2.png)
2219

2320
## Overview
2421

@@ -29,6 +26,7 @@ The Databricks Genie Conversation APIs (in Public Preview) enable you to embed A
2926
- Get SQL-powered insights without writing code
3027
- Follow up with contextual questions in a conversation thread
3128

29+
3230
## Key Features
3331

3432
- **Powered by Databricks Apps**: Deploy and run directly from your Databricks workspace with built-in security and scaling
@@ -58,6 +56,9 @@ The app can be installed through Databricks Marketplace. If you prefer to clone
5856
- Go to the Apps tab and click the **Create app** button. Fill in the necessary fields and click **Next: Configuration**
5957
- To reuse an existing app, click the link to your app in the **Name** column to go to the detail page of the app, then click **Edit**
6058
- In the App resources section, click **+ Add resource** and select **Serving endpoint**. Choose a chat endpoint, grant **CAN_QUERY** permission and name it 'serving_endpoint'.
59+
select **Genie Space**. Choose a genie space, grant **CAN_RUN** permission and name it 'genie_space'.
60+
![](./assets/genie1.png)
61+
6162

6263
For detailed instructions on configuring resources, refer to the [official Databricks documentation](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/resources#configure-resources-for-your-app).
6364

@@ -70,7 +71,7 @@ For detailed instructions on configuring resources, refer to the [official Datab
7071
For more details, refer to the [official Databricks documentation](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/deploy).
7172

7273
## Troubleshooting
73-
1. After you install the app from Markeplace, check the Authorization page for API scope details.
74+
1. After you install the app from Marketplace, check the Authorization page for API scope details.
7475
![](./assets/genie-space12.png)
7576

7677
Then you open the url link the first time, ensure that you see this OBO scope authorization page, which has all four scopes:

conversational-agent-app/app.py

Lines changed: 40 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
logging.basicConfig(level=logging.INFO)
2323
logger = logging.getLogger(__name__)
2424

25+
# Get the Genie space ID from environment variable
26+
GENIE_SPACE_ID = os.environ.get("GENIE_SPACE")
27+
2528
# Create Dash app
2629
app = dash.Dash(
2730
__name__,
@@ -58,21 +61,7 @@
5861
# Define the layout
5962
app.layout = html.Div([
6063
html.Div([
61-
dcc.Store(id="selected-space-id", data=None, storage_type="local"),
62-
dcc.Store(id="spaces-list", data=[]),
6364
dcc.Store(id="user-info", data={"initial": "Y", "username": "You"}),
64-
# Space selection overlay
65-
html.Div([
66-
html.Div([
67-
html.Div([
68-
html.Span(className="space-select-spinner"),
69-
"Loading Genie Spaces..."
70-
], id="space-select-title", className="space-select-title"),
71-
dcc.Dropdown(id="space-dropdown", options=[], placeholder="Choose a Genie Space", className="space-select-dropdown", optionHeight=60, searchable=True),
72-
html.Button("Select", id="select-space-button", className="space-select-button"),
73-
html.Div(id="space-select-error", className="space-select-error")
74-
], className="space-select-card")
75-
], id="space-select-container", className="space-select-container"),
7665
# Top navigation bar
7766
html.Div([
7867
# Left component containing both nav-left and sidebar
@@ -191,16 +180,16 @@
191180
html.Div("Always review the accuracy of responses.", className="disclaimer-fixed")
192181
], id="fixed-input-wrapper", className="fixed-input-wrapper"),
193182
], id="chat-container", className="chat-container"),
194-
], id="main-content", className="main-content", style={"display": "none"}),
183+
], id="main-content", className="main-content"),
195184

196185
html.Div(id='dummy-output'),
186+
html.Div(id='dummy-insight-scroll'),
197187
dcc.Store(id="chat-trigger", data={"trigger": False, "message": ""}),
198188
dcc.Store(id="chat-history-store", data=[]),
199189
dcc.Store(id="query-running-store", data=False),
200190
dcc.Store(id="session-store", data={"current_session": None}),
201-
html.Div(id='dummy-insight-scroll')
202-
], id="app-inner-layout"),
203-
], id="root-container")
191+
], id="root-container", className="root-container")
192+
], id="app-container", className="app-container")
204193

205194
# Store chat history
206195
chat_history = []
@@ -411,11 +400,10 @@ def handle_all_inputs(s1_clicks, s2_clicks, s3_clicks, s4_clicks, send_clicks, s
411400
Output("query-running-store", "data", allow_duplicate=True)],
412401
[Input("chat-trigger", "data")],
413402
[State("chat-messages", "children"),
414-
State("chat-history-store", "data"),
415-
State("selected-space-id", "data")],
403+
State("chat-history-store", "data")],
416404
prevent_initial_call=True
417405
)
418-
def get_model_response(trigger_data, current_messages, chat_history, selected_space_id):
406+
def get_model_response(trigger_data, current_messages, chat_history):
419407
if not trigger_data or not trigger_data.get("trigger"):
420408
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
421409

@@ -425,9 +413,8 @@ def get_model_response(trigger_data, current_messages, chat_history, selected_sp
425413

426414
try:
427415
headers = request.headers
428-
# user_token = os.environ.get("DATABRICKS_TOKEN")
429416
user_token = headers.get('X-Forwarded-Access-Token')
430-
response, query_text = genie_query(user_input, user_token, selected_space_id)
417+
response, query_text = genie_query(user_input, user_token, GENIE_SPACE_ID)
431418

432419
if isinstance(response, str):
433420
# Escape square brackets to prevent markdown auto-linking
@@ -747,84 +734,42 @@ def generate_insights(n_clicks, btn_id, chat_history):
747734
if df is None:
748735
return html.Div("No data available for insights.", style={"color": "red"})
749736
insights = call_llm_for_insights(df)
750-
return html.Div(
751-
dcc.Markdown(insights),
752-
style={"marginTop": "32px", "background": "#f4f4f4", "padding": "16px", "borderRadius": "4px"},
753-
className="insight-output"
754-
)
737+
return html.Div([
738+
html.Div([
739+
dcc.Markdown(insights, className="insight-content")
740+
], className="insight-body")
741+
], className="insight-wrapper")
742+
755743

756744
# Callback to fetch spaces on load
745+
# Initialize welcome title and description from space info
757746
@app.callback(
758-
Output("spaces-list", "data"),
759-
Input("space-select-container", "id"),
747+
[Output("welcome-title", "children"),
748+
Output("welcome-description", "children")],
749+
Input("user-info", "id"),
760750
prevent_initial_call=False
761751
)
762-
def fetch_spaces(_):
752+
def initialize_welcome_info(_):
753+
if not GENIE_SPACE_ID:
754+
return DEFAULT_WELCOME_TITLE, DEFAULT_WELCOME_DESCRIPTION
755+
763756
try:
757+
# Try to fetch space information to get title and description
764758
headers = request.headers
765-
# token = os.environ.get("DATABRICKS_TOKEN")
766759
token = headers.get('X-Forwarded-Access-Token')
767760
host = os.environ.get("DATABRICKS_HOST")
768761
client = GenieClient(host=host, space_id="", token=token)
769-
spaces = client.list_spaces()
770-
return spaces
762+
763+
# Get the specific space details
764+
space_details = client.get_space(GENIE_SPACE_ID)
765+
766+
title = space_details.get("title", DEFAULT_WELCOME_TITLE)
767+
description = space_details.get("description", DEFAULT_WELCOME_DESCRIPTION)
768+
return title, description
769+
771770
except Exception as e:
772-
return []
773-
774-
# Populate dropdown options
775-
@app.callback(
776-
Output("space-dropdown", "options"),
777-
Input("spaces-list", "data"),
778-
prevent_initial_call=False
779-
)
780-
def update_space_dropdown(spaces):
781-
if not spaces:
782-
return []
783-
options = []
784-
for s in spaces:
785-
title = s.get('title', '')
786-
space_id = s.get('space_id', '')
787-
label_lines = [title]
788-
label_lines.append(space_id)
789-
label = " | ".join(label_lines) # or use '\\n'.join(label_lines) for multi-line (but most browsers will show as a single line)
790-
options.append({"label": label, "value": space_id})
791-
return options
792-
793-
# Handle space selection
794-
@app.callback(
795-
[Output("selected-space-id", "data", allow_duplicate=True),
796-
Output("space-select-container", "style"),
797-
Output("main-content", "style"),
798-
Output("space-select-error", "children"),
799-
Output("welcome-title", "children"),
800-
Output("welcome-description", "children")],
801-
Input("select-space-button", "n_clicks"),
802-
State("space-dropdown", "value"),
803-
State("spaces-list", "data"),
804-
prevent_initial_call=True
805-
)
806-
def select_space(n_clicks, space_id, spaces):
807-
if not n_clicks:
808-
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
809-
if not space_id:
810-
return dash.no_update, {"display": "flex", "flexDirection": "column", "alignItems": "center", "justifyContent": "center", "height": "100vh"}, {"display": "none"}, "Please select a Genie space.", dash.no_update, dash.no_update
811-
# Find the selected space's title and description
812-
selected = next((s for s in spaces if s["space_id"] == space_id), None)
813-
title = selected["title"] if selected and selected.get("title") else DEFAULT_WELCOME_TITLE
814-
description = selected["description"] if selected and selected.get("description") else DEFAULT_WELCOME_DESCRIPTION
815-
return space_id, {"display": "none"}, {"display": "block"}, "", title, description
816-
817-
# Add a callback to control visibility of main-content and space-select-container
818-
@app.callback(
819-
[Output("main-content", "style", allow_duplicate=True), Output("space-select-container", "style", allow_duplicate=True)],
820-
Input("selected-space-id", "data"),
821-
prevent_initial_call=True
822-
)
823-
def toggle_main_ui(selected_space_id):
824-
if selected_space_id:
825-
return {"display": "block"}, {"display": "none"}
826-
else:
827-
return {"display": "none"}, {"display": "flex", "flexDirection": "column", "alignItems": "center", "justifyContent": "center", "height": "100vh"}
771+
logger.warning(f"Could not fetch space information: {e}")
772+
return DEFAULT_WELCOME_TITLE, DEFAULT_WELCOME_DESCRIPTION
828773

829774
# Add clientside callback for scrolling to bottom of chat when insight is generated
830775
app.clientside_callback(
@@ -847,39 +792,20 @@ def toggle_main_ui(selected_space_id):
847792
prevent_initial_call=True
848793
)
849794

850-
@app.callback(
851-
Output("selected-space-id", "data", allow_duplicate=True),
852-
Input("logout-button", "n_clicks"),
853-
prevent_initial_call=True
854-
)
855-
def logout_and_clear_space(n_clicks):
856-
if n_clicks:
857-
return None
858-
return dash.no_update
859795

860-
# Add a callback to control the root-container style to prevent scrolling when overlay is visible
796+
797+
# Add a callback to control the root-container style
861798
@app.callback(
862799
Output("root-container", "style"),
863-
Input("selected-space-id", "data"),
800+
Input("user-info", "id"),
864801
prevent_initial_call=False
865802
)
866-
def set_root_style(selected_space_id):
867-
if selected_space_id:
803+
def set_root_style(_):
804+
if GENIE_SPACE_ID:
868805
return {"height": "auto", "overflow": "auto"}
869806
else:
870807
return {"height": "100vh", "overflow": "hidden"}
871808

872-
# Add a callback to update the title based on spaces-list
873-
@app.callback(
874-
Output("space-select-title", "children"),
875-
Input("spaces-list", "data"),
876-
prevent_initial_call=False
877-
)
878-
def update_space_select_title(spaces):
879-
if not spaces:
880-
return [html.Span(className="space-select-spinner"), "Loading Genie Spaces..."]
881-
return "Select a Genie Space"
882-
883809
@app.callback(
884810
Output("query-tooltip", "className"),
885811
Input("query-running-store", "data"),

conversational-agent-app/app.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ command:
44

55
env:
66
- name: "SERVING_ENDPOINT_NAME"
7-
valueFrom: "serving_endpoint"
7+
valueFrom: "serving-endpoint"
8+
- name: "GENIE_SPACE"
9+
valueFrom: "genie-space"
-410 KB
Binary file not shown.
-387 KB
Binary file not shown.
-303 KB
Binary file not shown.
-354 KB
Binary file not shown.
-223 KB
Binary file not shown.
-561 KB
Binary file not shown.
168 KB
Loading

0 commit comments

Comments
 (0)