feat/ai-chat: Add core components for Database chat #5
140
app/app.py
140
app/app.py
@@ -1,8 +1,142 @@
|
|||||||
from dash import Dash, html
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
app = Dash()
|
from dash import (
|
||||||
|
Dash,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
State,
|
||||||
|
callback,
|
||||||
|
dcc,
|
||||||
|
get_asset_url,
|
||||||
|
html,
|
||||||
|
no_update,
|
||||||
|
)
|
||||||
|
from dash.exceptions import PreventUpdate
|
||||||
|
|
||||||
|
from .app_styles import header_style
|
||||||
|
from .data_chat import send_message
|
||||||
|
|
||||||
|
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
|
||||||
|
|
||||||
|
app = Dash(__name__, external_stylesheets=external_stylesheets)
|
||||||
|
app.index_string = header_style
|
||||||
|
|
||||||
|
err_style = {
|
||||||
|
"height": "0px",
|
||||||
|
"overflow": "hidden",
|
||||||
|
"transition": "height 0.5s ease-in-out",
|
||||||
|
"border-radius": "15px",
|
||||||
|
"background-color": "#FFCCCB",
|
||||||
|
"text-align": "center",
|
||||||
|
"color": "#FF6B6B",
|
||||||
|
"margin-top": "20px",
|
||||||
|
"margin-left": "20px",
|
||||||
|
"margin-right": "20px",
|
||||||
|
"font-weight": "bold",
|
||||||
|
"display": "flex",
|
||||||
|
"justify-content": "center",
|
||||||
|
"align-items": "center",
|
||||||
|
}
|
||||||
|
|
||||||
|
start_value = "Stelle deine Frage an die Datenbank..."
|
||||||
|
app.layout = html.Div(
|
||||||
|
[
|
||||||
|
html.Div(
|
||||||
|
[
|
||||||
|
html.H1("Datenbank-Chat", className="heading"),
|
||||||
|
html.Img(src=get_asset_url("logo.png"), className="logo"),
|
||||||
|
],
|
||||||
|
className="header-container",
|
||||||
|
), # Header
|
||||||
|
dcc.Store(
|
||||||
|
id="tmp-value", data=start_value, storage_type="session"
|
||||||
|
), # Store previous prompt
|
||||||
|
dcc.Textarea(
|
||||||
|
id="input-field",
|
||||||
|
value=start_value,
|
||||||
|
style={"width": "96%", "height": 200, "margin-left": "20px"},
|
||||||
|
), # Input field
|
||||||
|
html.Div([]), # Needed for keeping the layout clean
|
||||||
|
html.Button(
|
||||||
|
"Abschicken",
|
||||||
|
id="submit-button",
|
||||||
|
n_clicks=0,
|
||||||
|
disabled=False,
|
||||||
|
style={"margin-left": "20px"},
|
||||||
|
), # Submit button
|
||||||
|
html.Div(
|
||||||
|
[html.P("Bitte eine neue Anfrage eingeben.")], id="error", style=err_style
|
||||||
|
), # Error message (only visible if input is not updated but submit button is clicked)
|
||||||
|
dcc.Loading(
|
||||||
|
id="loading",
|
||||||
|
type="default",
|
||||||
|
children=[
|
||||||
|
html.Div(
|
||||||
|
"Hier erscheint die Antwort der Datenbank.",
|
||||||
|
id="text-output",
|
||||||
|
style={
|
||||||
|
"whiteSpace": "pre-line",
|
||||||
|
"margin-top": "30px",
|
||||||
|
"margin-left": "20px",
|
||||||
|
"margin-right": "20px",
|
||||||
|
"border": "2px solid #86bc25",
|
||||||
|
"border-radius": "15px",
|
||||||
|
"padding": "20px",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
], # Output field
|
||||||
|
className="container",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback(
|
||||||
|
Output("text-output", "children"),
|
||||||
|
Output("tmp-value", "data"),
|
||||||
|
Output("error", "style"),
|
||||||
|
Input("submit-button", "n_clicks"),
|
||||||
|
State("input-field", "value"),
|
||||||
|
State("tmp-value", "data"),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
running=[
|
||||||
|
(Output("submit-button", "disabled"), True, False),
|
||||||
|
(
|
||||||
|
Output("submit-button", "style"),
|
||||||
|
{"opacity": 0.5, "margin-left": "20px"},
|
||||||
|
{"opacity": 1.0, "margin-left": "20px"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def update_output(n_clicks: int, value: str, data: str) -> Tuple[str, str, Dict[str, Any]]:
|
||||||
|
"""Update the output based on user input and button clicks.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
n_clicks : int
|
||||||
|
Number of times the submit button has been clicked.
|
||||||
|
value : str
|
||||||
|
Current value of the input field.
|
||||||
|
data : str
|
||||||
|
Previously stored value.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Tuple[str, str, Dict[str, Any]]
|
||||||
|
Updated output text, new stored value, and error style.
|
||||||
|
"""
|
||||||
|
global err_style
|
||||||
|
if n_clicks > 0 and value != data:
|
||||||
|
result = send_message(value)
|
||||||
|
err_style["height"] = "0px"
|
||||||
|
return result, value, err_style
|
||||||
|
elif value == data:
|
||||||
|
|
||||||
|
err_style["height"] = "50px"
|
||||||
|
return no_update, no_update, err_style
|
||||||
|
|
||||||
|
raise PreventUpdate
|
||||||
|
|
||||||
app.layout = [html.Div(children="Hello World")]
|
|
||||||
|
|
||||||
server = app.server
|
server = app.server
|
||||||
|
|
||||||
|
|||||||
37
app/app_styles.py
Normal file
37
app/app_styles.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
header_style = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{%metas%}
|
||||||
|
<title>{%title%}</title>
|
||||||
|
{%favicon%}
|
||||||
|
{%css%}
|
||||||
|
<style>
|
||||||
|
.header-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
font-size: 2.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 30px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{%app_entry%}
|
||||||
|
<footer>
|
||||||
|
{%config%}
|
||||||
|
{%scripts%}
|
||||||
|
{%renderer%}
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
BIN
app/assets/logo.png
Normal file
BIN
app/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
36
app/data_chat.py
Normal file
36
app/data_chat.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from openai import AzureOpenAI
|
||||||
|
|
||||||
|
# Set up credentials
|
||||||
|
# NOTE: When running locally, these have to be set in the environment
|
||||||
|
client = AzureOpenAI(
|
||||||
|
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
|
||||||
|
api_key=os.getenv("AZURE_OPENAI_KEY"),
|
||||||
|
api_version="2024-02-01",
|
||||||
|
)
|
||||||
|
|
||||||
|
deployment_name = "sqlai"
|
||||||
|
|
||||||
|
|
||||||
|
def send_message(message: str) -> str:
|
||||||
|
"""Send a message to the openai chat completion API and return the response.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
message : str
|
||||||
|
The user's message to be sent to the chat completion API.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
The content of the assistant's response message.
|
||||||
|
"""
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model=deployment_name,
|
||||||
|
messages=[
|
||||||
|
{"role": "system", "content": "Du bist ein hilfreicher Assistent."},
|
||||||
|
{"role": "user", "content": message},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return response.choices[0].message.content
|
||||||
Reference in New Issue
Block a user