Adding DBQnA example in GenAIExamples (#894)
Signed-off-by: supriya-krishnamurthi <supriya.krishnamurthi@intel.com> Signed-off-by: Yogesh <yogeshpandey@intel.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: lvliang-intel <liang1.lv@intel.com> Co-authored-by: Yogesh <yogeshpandey@intel.com> Co-authored-by: Hoong Tee, Yeoh <hoong.tee.yeoh@intel.com> Co-authored-by: Yogesh Pandey <yogesh.pandey@intel.com>
17
DBQnA/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# DBQnA Application
|
||||
|
||||
Experience a revolutionary way to interact with your database using our DBQnA app! Harnessing the power of OPEA microservices, our application seamlessly translates natural language queries into SQL and delivers real-time database results, all designed to optimize workflows and enhance productivity for modern enterprises.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Key Features
|
||||
|
||||
### 💬 SQL Query Generation
|
||||
|
||||
The key feature of DBQnA app is that it converts a user's natural language query into an SQL query and automatically executes the generated SQL query on the database to return the relevant results. BAsically ask questions to database, receive corresponding SQL query and real-time query execution output, all without needing any SQL knowledge.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Setup Guide
|
||||
|
||||
- **[Xeon Guide](./docker_compose/intel/cpu/xeon/README.md)**: Instructions to build Docker images from source and run the application via Docker Compose.
|
||||
BIN
DBQnA/assets/img/dbQnA_ui_db_credentials.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_enter_question.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_failed_db_connection.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_failed_sql_output_generation.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_init.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_succesful_sql_output_generation.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
DBQnA/assets/img/dbQnA_ui_successful_db_connection.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
172
DBQnA/docker_compose/intel/cpu/xeon/README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Deploy on Intel Xeon Processor
|
||||
|
||||
This document outlines the deployment process for DBQnA application which helps generating a SQL query and its output given a NLP question, utilizing the [GenAIComps](https://github.com/opea-project/GenAIComps.git) microservice pipeline on an Intel Xeon server. The steps include Docker image creation, container deployment via Docker Compose, and service execution to integrate microservices. We will publish the Docker images to Docker Hub soon, which will simplify the deployment process for this service.
|
||||
|
||||
## 🚀 Build Docker Images
|
||||
|
||||
First of all, you need to build Docker Images locally. This step can be ignored once the Docker images are published to Docker hub.
|
||||
|
||||
### 1.1 Build Text to SQL service Image
|
||||
|
||||
```bash
|
||||
git clone https://github.com/opea-project/GenAIComps.git
|
||||
cd GenAIComps
|
||||
docker build --no-cache -t opea/texttosql:comps -f comps/texttosql/langchain/Dockerfile .
|
||||
|
||||
```
|
||||
|
||||
### 1.2 Build react UI Docker Image
|
||||
|
||||
Build the frontend Docker image based on react framework via below command:
|
||||
|
||||
```bash
|
||||
cd GenAIExamples/DBQnA/ui
|
||||
docker build --no-cache -t opea/texttosql-react-ui:latest -f docker/Dockerfile.react .
|
||||
|
||||
```
|
||||
|
||||
Then run the command `docker images`, you will have the following Docker Images:
|
||||
|
||||
1. `opea/texttosql:latest`
|
||||
2. `opea/dbqna-react-ui:latest`
|
||||
|
||||
## 🚀 Start Microservices
|
||||
|
||||
### Required Models
|
||||
|
||||
We set default model as "mistralai/Mistral-7B-Instruct-v0.3", change "LLM_MODEL_ID" in following Environment Variables setting if you want to use other models.
|
||||
|
||||
If use gated models, you also need to provide [huggingface token](https://huggingface.co/docs/hub/security-tokens) to "HUGGINGFACEHUB_API_TOKEN" environment variable.
|
||||
|
||||
### 2.1 Setup Environment Variables
|
||||
|
||||
Since the `compose.yaml` will consume some environment variables, you need to setup them in advance as below.
|
||||
|
||||
```bash
|
||||
# your_ip should be your external IP address, do not use localhost.
|
||||
export your_ip=$(hostname -I | awk '{print $1}')
|
||||
|
||||
# Example: no_proxy="localhost,127.0.0.1,192.168.1.1"
|
||||
export no_proxy=${your_no_proxy},${your_ip}
|
||||
|
||||
# If you are in a proxy environment, also set the proxy-related environment variables:
|
||||
export http_proxy=${your_http_proxy}
|
||||
export https_proxy=${your_http_proxy}
|
||||
|
||||
# Set other required variables
|
||||
|
||||
export TGI_PORT=8008
|
||||
export TGI_LLM_ENDPOINT=http://${your_ip}:${TGI_PORT}
|
||||
export HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN}
|
||||
export LLM_MODEL_ID="mistralai/Mistral-7B-Instruct-v0.3"
|
||||
export POSTGRES_USER=postgres
|
||||
export POSTGRES_PASSWORD=testpwd
|
||||
export POSTGRES_DB=chinook
|
||||
export texttosql_port=9090
|
||||
```
|
||||
|
||||
Note: Please replace with `your_ip` with your external IP address, do not use localhost.
|
||||
|
||||
### 2.2 Start Microservice Docker Containers
|
||||
|
||||
There are 2 options to start the microservice
|
||||
|
||||
#### 2.2.1 Start the microservice using docker compose
|
||||
|
||||
```bash
|
||||
cd GenAIExamples/DBQnA/docker_compose/intel/cpu/xeon
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### 2.2.2 Alternatively we can start the microservices by running individual docker services
|
||||
|
||||
**NOTE:** Make sure all the individual docker services are down before starting them.
|
||||
|
||||
Below are the commands to start each of the docker service individually
|
||||
|
||||
- Start PostgresDB Service
|
||||
|
||||
We will use [Chinook](https://github.com/lerocha/chinook-database) sample database as a default to test the Text-to-SQL microservice. Chinook database is a sample database ideal for demos and testing ORM tools targeting single and multiple database servers.
|
||||
|
||||
```bash
|
||||
|
||||
docker run --name test-texttosql-postgres --ipc=host -e POSTGRES_USER=${POSTGRES_USER} -e POSTGRES_HOST_AUTH_METHOD=trust -e POSTGRES_DB=${POSTGRES_DB} -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -p 5442:5432 -d -v $WORKPATH/comps/texttosql/langchain/chinook.sql:/docker-entrypoint-initdb.d/chinook.sql postgres:latest
|
||||
```
|
||||
|
||||
- Start TGI Service
|
||||
|
||||
```bash
|
||||
|
||||
docker run -d --name="test-texttosql-tgi-endpoint" --ipc=host -p $TGI_PORT:80 -v ./data:/data --shm-size 1g -e HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN} -e HF_TOKEN=${HF_TOKEN} -e model=${model} ghcr.io/huggingface/text-generation-inference:2.1.0 --model-id $model
|
||||
```
|
||||
|
||||
- Start Text-to-SQL Service
|
||||
|
||||
```bash
|
||||
unset http_proxy
|
||||
|
||||
docker run -d --name="test-texttosql-server" --ipc=host -p ${texttosql_port}:8090 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e TGI_LLM_ENDPOINT=$TGI_LLM_ENDPOINT opea/texttosql:latest
|
||||
```
|
||||
|
||||
- Start React UI service
|
||||
|
||||
```bash
|
||||
docker run -d --name="test-dbqna-react-ui-server" --ipc=host -p 5174:80 -e no_proxy=$no_proxy -e https_proxy=$https_proxy -e http_proxy=$http_proxy opea/dbqna-react-ui:latest
|
||||
```
|
||||
|
||||
## 🚀 Validate Microservices
|
||||
|
||||
### 3.1 TGI Service
|
||||
|
||||
```bash
|
||||
|
||||
curl http://${your_ip}:$TGI_PORT/generate \
|
||||
-X POST \
|
||||
-d '{"inputs":"What is Deep Learning?","parameters":{"max_new_tokens":17, "do_sample": true}}' \
|
||||
-H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
### 3.2 Postgres Microservice
|
||||
|
||||
Once Text-to-SQL microservice is started, user can use below command
|
||||
|
||||
#### 3.2.1 Test the Database connection
|
||||
|
||||
```bash
|
||||
curl --location http://${your_ip}:9090/v1/postgres/health \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"user": "'${POSTGRES_USER}'","password": "'${POSTGRES_PASSWORD}'","host": "'${your_ip}'", "port": "5442", "database": "'${POSTGRES_DB}'"}'
|
||||
```
|
||||
|
||||
#### 3.2.2 Invoke the microservice.
|
||||
|
||||
```bash
|
||||
curl http://${your_ip}:9090/v1/texttosql\
|
||||
-X POST \
|
||||
-d '{"input_text": "Find the total number of Albums.","conn_str": {"user": "'${POSTGRES_USER}'","password": "'${POSTGRES_PASSWORD}'","host": "'${your_ip}'", "port": "5442", "database": "'${POSTGRES_DB}'"}}' \
|
||||
-H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
### 3.3 Frontend validation
|
||||
|
||||
We test the API in frontend validation to check if API returns HTTP_STATUS: 200 and validates if API response returns SQL query and output
|
||||
|
||||
The test is present in App.test.tsx under react root folder ui/react/
|
||||
|
||||
Command to run the test
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
## 🚀 Launch the React UI
|
||||
|
||||
Open this URL `http://{your_ip}:5174` in your browser to access the frontend.
|
||||
|
||||

|
||||
|
||||
Test DB Connection
|
||||

|
||||
|
||||
Create SQL query and output for given NLP question
|
||||

|
||||
23856
DBQnA/docker_compose/intel/cpu/xeon/chinook.sql
Normal file
60
DBQnA/docker_compose/intel/cpu/xeon/compose.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright (C) 2024 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
tgi-service:
|
||||
image: ghcr.io/huggingface/text-generation-inference:2.1.0
|
||||
container_name: tgi-service
|
||||
ports:
|
||||
- "8008:80"
|
||||
volumes:
|
||||
- "./data:/data"
|
||||
environment:
|
||||
no_proxy: ${no_proxy}
|
||||
http_proxy: ${http_proxy}
|
||||
https_proxy: ${https_proxy}
|
||||
HF_TOKEN: ${HF_TOKEN}
|
||||
shm_size: 1g
|
||||
command: --model-id ${LLM_MODEL_ID}
|
||||
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
container_name: postgres-container
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- POSTGRES_DB=${POSTGRES_DB}
|
||||
ports:
|
||||
- '5442:5432'
|
||||
volumes:
|
||||
- ./chinook.sql:/docker-entrypoint-initdb.d/chinook.sql
|
||||
|
||||
texttosql-service:
|
||||
image: opea/texttosql:latest
|
||||
container_name: texttosql-service
|
||||
ports:
|
||||
- "9090:8090"
|
||||
environment:
|
||||
- TGI_LLM_ENDPOINT=${TGI_LLM_ENDPOINT}
|
||||
|
||||
dbqna-xeon-react-ui-server:
|
||||
image: opea/dbqna-react-ui:latest
|
||||
container_name: dbqna-xeon-react-ui-server
|
||||
depends_on:
|
||||
- texttosql-service
|
||||
ports:
|
||||
- "5174:80"
|
||||
environment:
|
||||
- no_proxy=${no_proxy}
|
||||
- https_proxy=${https_proxy}
|
||||
- http_proxy=${http_proxy}
|
||||
ipc: host
|
||||
restart: always
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
driver: bridge
|
||||
8
DBQnA/docker_compose/set_env.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (C) 2024 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export TGI_PORT=8008
|
||||
export TGI_LLM_ENDPOINT="http://${your_ip}:${TGI_PORT}"
|
||||
export LLM_MODEL_ID="mistralai/Mistral-7B-Instruct-v0.3"
|
||||
23
DBQnA/docker_image_build/build.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright (C) 2024 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
services:
|
||||
texttosql-service:
|
||||
build:
|
||||
context: GenAIComps
|
||||
dockerfile: comps/texttosql/langchain/Dockerfile
|
||||
args:
|
||||
http_proxy: ${http_proxy}
|
||||
https_proxy: ${https_proxy}
|
||||
no_proxy: ${no_proxy}
|
||||
image: ${REGISTRY:-opea}/texttosql:${TAG:-latest}
|
||||
|
||||
dbqna-xeon-react-ui:
|
||||
build:
|
||||
context: GenAIExamples/DBQnA/ui/docker
|
||||
dockerfile: Dockerfile.react
|
||||
args:
|
||||
http_proxy: ${http_proxy}
|
||||
https_proxy: ${https_proxy}
|
||||
no_proxy: ${no_proxy}
|
||||
image: ${REGISTRY:-opea}/texttosql-react-ui:${TAG:-latest}
|
||||
132
DBQnA/tests/test_compose_on_xeon.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2024 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -xe
|
||||
|
||||
WORKPATH=$(dirname "$PWD")
|
||||
LOG_PATH="$WORKPATH/tests"
|
||||
ip_address=$(hostname -I | awk '{print $1}')
|
||||
tgi_port=8008
|
||||
tgi_volume=$WORKPATH/data
|
||||
|
||||
export model="meta-llama/Meta-Llama-3-8B-Instruct"
|
||||
export HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN}
|
||||
export POSTGRES_USER=postgres
|
||||
export POSTGRES_PASSWORD=testpwd
|
||||
export POSTGRES_DB=chinook
|
||||
export TEXTTOSQL_PORT=9090
|
||||
export TGI_LLM_ENDPOINT="http://${ip_address}:${tgi_port}"
|
||||
|
||||
|
||||
function build_docker_images() {
|
||||
echo $WORKPATH
|
||||
OPEAPATH=$(realpath "$WORKPATH/../..")
|
||||
|
||||
echo "Building Text to Sql service..."
|
||||
cd $OPEAPATH
|
||||
rm -rf GenAIComps/
|
||||
git clone https://github.com/opea-project/GenAIComps.git
|
||||
cd $OPEAPATH/GenAIComps
|
||||
docker build --no-cache -t opea/texttosql:latest -f comps/texttosql/langchain/Dockerfile .
|
||||
|
||||
echo "Building React UI service..."
|
||||
cd $OPEAPATH/GenAIExamples/DBQnA/ui
|
||||
docker build --no-cache -t opea/dbqna-react-ui:latest -f docker/Dockerfile.react .
|
||||
|
||||
}
|
||||
|
||||
function start_service() {
|
||||
|
||||
docker run --name test-texttosql-postgres --ipc=host -e POSTGRES_USER=${POSTGRES_USER} -e POSTGRES_HOST_AUTH_METHOD=trust -e POSTGRES_DB=${POSTGRES_DB} -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} -p 5442:5432 -d -v $WORKPATH/docker_compose/intel/cpu/xeon/chinook.sql:/docker-entrypoint-initdb.d/chinook.sql postgres:latest
|
||||
|
||||
docker run -d --name="test-texttosql-tgi-endpoint" --ipc=host -p $tgi_port:80 -v ./data:/data --shm-size 1g -e HUGGINGFACEHUB_API_TOKEN=${HUGGINGFACEHUB_API_TOKEN} -e HF_TOKEN=${HUGGINGFACEHUB_API_TOKEN} -e model=${model} ghcr.io/huggingface/text-generation-inference:2.1.0 --model-id $model
|
||||
|
||||
|
||||
docker run -d --name="test-texttosql-server" --ipc=host -p $TEXTTOSQL_PORT:8090 --ipc=host -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e TGI_LLM_ENDPOINT=$TGI_LLM_ENDPOINT opea/texttosql:latest
|
||||
|
||||
# check whether tgi is fully ready.
|
||||
n=0
|
||||
until [[ "$n" -ge 100 ]] || [[ $ready == true ]]; do
|
||||
docker logs test-texttosql-tgi-endpoint > ${LOG_PATH}/tgi.log
|
||||
n=$((n+1))
|
||||
if grep -q Connected ${LOG_PATH}/tgi.log; then
|
||||
break
|
||||
fi
|
||||
sleep 5s
|
||||
done
|
||||
sleep 5s
|
||||
|
||||
# Run the UI container
|
||||
docker run -d --name="test-dbqna-react-ui-server" --ipc=host -p 5174:80 -e no_proxy=$no_proxy -e https_proxy=$https_proxy -e http_proxy=$http_proxy opea/dbqna-react-ui:latest
|
||||
|
||||
}
|
||||
|
||||
function validate_microservice() {
|
||||
result=$(http_proxy="" curl --connect-timeout 5 --max-time 120000 http://${ip_address}:$TEXTTOSQL_PORT/v1/texttosql\
|
||||
-X POST \
|
||||
-d '{"input_text": "Find the total number of Albums.","conn_str": {"user": "'${POSTGRES_USER}'","password": "'${POSTGRES_PASSWORD}'","host": "'${ip_address}'", "port": "5442", "database": "'${POSTGRES_DB}'" }}' \
|
||||
-H 'Content-Type: application/json')
|
||||
|
||||
if [[ $result == *"output"* ]]; then
|
||||
echo $result
|
||||
echo "Result correct."
|
||||
else
|
||||
echo "Result wrong. Received was $result"
|
||||
docker logs test-texttosql-server > ${LOG_PATH}/texttosql.log
|
||||
docker logs test-texttosql-tgi-endpoint > ${LOG_PATH}/tgi.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
function validate_frontend() {
|
||||
echo "[ TEST INFO ]: --------- frontend test started ---------"
|
||||
cd $WORKPATH/ui/react
|
||||
local conda_env_name="OPEA_e2e"
|
||||
export PATH=${HOME}/miniforge3/bin/:$PATH
|
||||
if conda info --envs | grep -q "$conda_env_name"; then
|
||||
echo "$conda_env_name exist!"
|
||||
else
|
||||
conda create -n ${conda_env_name} python=3.12 -y
|
||||
fi
|
||||
|
||||
source activate ${conda_env_name}
|
||||
echo "[ TEST INFO ]: --------- conda env activated ---------"
|
||||
|
||||
conda install -c conda-forge nodejs -y
|
||||
npm install && npm ci
|
||||
node -v && npm -v && pip list
|
||||
|
||||
exit_status=0
|
||||
npm run test || exit_status=$?
|
||||
|
||||
if [ $exit_status -ne 0 ]; then
|
||||
echo "[TEST INFO]: ---------frontend test failed---------"
|
||||
exit $exit_status
|
||||
else
|
||||
echo "[TEST INFO]: ---------frontend test passed---------"
|
||||
fi
|
||||
}
|
||||
|
||||
function stop_docker() {
|
||||
cid=$(docker ps -aq --filter "name=test-*")
|
||||
if [[ ! -z "$cid" ]]; then docker stop $cid && docker rm $cid && sleep 1s; fi
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
stop_docker
|
||||
|
||||
build_docker_images
|
||||
start_service
|
||||
|
||||
validate_microservice
|
||||
validate_frontend
|
||||
|
||||
stop_docker
|
||||
echo y | docker system prune
|
||||
|
||||
}
|
||||
|
||||
main
|
||||
24
DBQnA/ui/docker/Dockerfile.react
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright (C) 2024 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Stage 1: Build the React application using Node.js
|
||||
# Use Node 20.11.1 as the base image for the build step
|
||||
FROM node:20.11.1 AS vite-app
|
||||
|
||||
WORKDIR /usr/app/react
|
||||
|
||||
COPY react /usr/app/react
|
||||
WORKDIR /usr/app/react
|
||||
|
||||
|
||||
RUN ["npm", "install"]
|
||||
RUN ["npm", "run", "build"]
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
COPY --from=vite-app /usr/app/react/dist /usr/share/nginx/html
|
||||
COPY --from=vite-app /usr/app/react/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
1
DBQnA/ui/react/.env
Normal file
@@ -0,0 +1 @@
|
||||
VITE_TEXT_TO_SQL_URL=http://${HOSTNAME}:9090/v1
|
||||
23
DBQnA/ui/react/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
48
DBQnA/ui/react/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# DBQnA React Application
|
||||
|
||||
## 📸 Project Screenshots
|
||||
|
||||
Initial Page
|
||||

|
||||
|
||||
Enter DB credentials and connect to DB
|
||||

|
||||
|
||||
DB connection failed
|
||||

|
||||
|
||||
DB connection successful
|
||||

|
||||
|
||||
Enter user question
|
||||

|
||||
|
||||
SQL query generation failed
|
||||

|
||||
|
||||
SQL query generation successful
|
||||

|
||||
|
||||
## 🧐 Features
|
||||
|
||||
Here're some of the project's features:
|
||||
|
||||
- Database Connection Validation: The app allows users to input database credentials (host, user, database, password, and port). Upon clicking the "Connect" button, it verifies the connection status through an API, providing feedback if the database connection is successful or not.
|
||||
|
||||
- Natural Language to SQL Query: Once the database connection is established, the user can input an English-language question, which is then sent to an API for conversion into an SQL query. The generated SQL is displayed in the UI for user review.
|
||||
|
||||
- Scroll to Bottom: The output generated will automatically scroll to the bottom.
|
||||
|
||||
## 🛠️ Get it Running
|
||||
|
||||
1. Clone the repo.
|
||||
|
||||
2. cd command to the current folder.
|
||||
|
||||
3. Modify the required .env variables.
|
||||
```
|
||||
VITE_TEXT_TO_SQL_URL = ''
|
||||
```
|
||||
4. Execute `npm install` to install the corresponding dependencies.
|
||||
|
||||
5. Execute `npm run dev` in both environments
|
||||
18
DBQnA/ui/react/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright (C) 2024 Intel Corporation
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/opea-icon-color.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Opea DBQnA</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
DBQnA/ui/react/nginx.conf
Normal file
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
gzip on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types font/woff2 text/css application/javascript application/json application/font-woff application/font-tff image/gif image/png image/svg+xml application/octet-stream;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
|
||||
location ~* \.(gif|jpe?g|png|webp|ico|svg|css|js|mp4|woff2)$ {
|
||||
expires 1d;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
DBQnA/ui/react/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "db-connect",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@mantine/core": "^7.12.2",
|
||||
"@mantine/hooks": "^7.12.2",
|
||||
"@mantine/notifications": "^7.12.2",
|
||||
"@tabler/icons-react": "^3.17.0",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"axios": "^1.7.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.9.36",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/react": "^18.3.9",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"jsdom": "^25.0.1",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"sass": "^1.79.3",
|
||||
"vitest": "^2.1.1"
|
||||
}
|
||||
}
|
||||
14
DBQnA/ui/react/postcss.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-preset-mantine": {},
|
||||
"postcss-simple-vars": {
|
||||
variables: {
|
||||
"mantine-breakpoint-xs": "36em",
|
||||
"mantine-breakpoint-sm": "48em",
|
||||
"mantine-breakpoint-md": "62em",
|
||||
"mantine-breakpoint-lg": "75em",
|
||||
"mantine-breakpoint-xl": "88em",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
46
DBQnA/ui/react/src/App.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@import "./styles/styles";
|
||||
|
||||
.root {
|
||||
@include flex(row, nowrap, flex-start, flex-start);
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
@include absolutes;
|
||||
|
||||
display: grid;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
grid-template-columns: 80px auto;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
/* ===== Scrollbar CSS ===== */
|
||||
/* Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #d6d6d6 #ffffff;
|
||||
}
|
||||
|
||||
/* Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #d6d6d6;
|
||||
border-radius: 16px;
|
||||
border: 4px double #dedede;
|
||||
}
|
||||
51
DBQnA/ui/react/src/App.test.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import axios from 'axios';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const apiTimeOutInSeconds = 300;
|
||||
|
||||
// Helper function to get the host IP
|
||||
const getHostIP = () => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
exec('hostname -I | awk \'{print $1}\'', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`Error fetching IP address: ${error.message}`);
|
||||
} else if (stderr) {
|
||||
reject(`Stderr: ${stderr}`);
|
||||
} else {
|
||||
resolve(stdout.trim());
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
test('testing api with dynamic host', async () => {
|
||||
// Get the dynamic host IP
|
||||
const host = await getHostIP();
|
||||
const endpointUrl = `http://${host}:9090/v1/texttosql`;
|
||||
|
||||
const formData = {
|
||||
user: 'postgres',
|
||||
database: 'chinook',
|
||||
host: host, // Dynamic IP
|
||||
password: 'testpwd',
|
||||
port: '5442',
|
||||
};
|
||||
|
||||
const question = "Find the total number of invoices.";
|
||||
|
||||
const payload = {
|
||||
input_text: question,
|
||||
conn_str: formData,
|
||||
};
|
||||
|
||||
const response = await axios.post(endpointUrl, payload);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const result = response.data.result;
|
||||
expect(result.hasOwnProperty('sql')).toBe(true);
|
||||
expect(result.hasOwnProperty('output')).toBe(true);
|
||||
expect(result.hasOwnProperty('input')).toBe(true);
|
||||
expect(result.input).toBe(question);
|
||||
|
||||
}, apiTimeOutInSeconds * 1000);
|
||||
30
DBQnA/ui/react/src/App.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import "./App.scss";
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
import { SideNavbar, SidebarNavList } from "./components/sidebar/sidebar";
|
||||
import { IconFileText } from "@tabler/icons-react";
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
import DBConnect from "./components/DbConnect/DBConnect";
|
||||
|
||||
const title = "DBQnA";
|
||||
const navList: SidebarNavList = [
|
||||
{ icon: IconFileText, label: title },
|
||||
];
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<MantineProvider >
|
||||
<Notifications position="top-right" />
|
||||
<div className="layout-wrapper">
|
||||
<SideNavbar navList={navList} />
|
||||
<div className="content">
|
||||
<DBConnect />
|
||||
</div>
|
||||
</div>
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
39
DBQnA/ui/react/src/assets/opea-icon-black.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="800px" height="800px" viewBox="0 0 800 800" style="enable-background:new 0 0 800 800;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.Drop_x0020_Shadow{fill:none;}
|
||||
.Outer_x0020_Glow_x0020_5_x0020_pt{fill:none;}
|
||||
.Blue_x0020_Neon{fill:none;stroke:#8AACDA;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.Chrome_x0020_Highlight{fill:url(#SVGID_1_);stroke:#FFFFFF;stroke-width:0.3629;stroke-miterlimit:1;}
|
||||
.Jive_GS{fill:#FFDD00;}
|
||||
.Alyssa_GS{fill:#A6D0E4;}
|
||||
.st0{fill:#FF6900;}
|
||||
.st1{fill:#FFB500;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1640" y1="-1640" x2="-1640" y2="-1641">
|
||||
<stop offset="0" style="stop-color:#656565"/>
|
||||
<stop offset="0.618" style="stop-color:#1B1B1B"/>
|
||||
<stop offset="0.6292" style="stop-color:#545454"/>
|
||||
<stop offset="0.9831" style="stop-color:#3E3E3E"/>
|
||||
</linearGradient>
|
||||
<g>
|
||||
<polygon points="400,0 737.5,181.7 607.3,252.8 269.7,71.1 "/>
|
||||
<path d="M708.3,414.7l29.2,15.7l-130.3,71.1l-44.9-24.2v-31.7l40,21.5c1.5,0.8,3.2,1.2,4.9,1.2c1.7,0,3.4-0.4,5-1.3l0,0
|
||||
L708.3,414.7z"/>
|
||||
<path d="M557.3,532.1c-0.1-0.1-0.3-0.1-0.4-0.2l0,0l-34.2-18.4l31.2-17l42.9,23.1v169.9l-34.5-18.6V541
|
||||
C562.4,537.3,560.4,533.9,557.3,532.1z"/>
|
||||
<polygon points="410.4,381.3 541.6,309.7 541.6,479.5 410.4,551.2 "/>
|
||||
<path d="M258.4,88.5l338.6,182.3v169.9l-34.5-18.6V292.2c0-3.7-1.9-7-5.1-8.9c-0.1-0.1-0.3-0.1-0.4-0.2l0,0L258.4,122.3V88.5z"/>
|
||||
<polygon points="192.7,110.6 530.3,292.3 400,363.4 62.5,181.6 "/>
|
||||
<polygon points="51.1,369 51.1,199 389.6,381.3 389.6,551.3 96.6,393.5 "/>
|
||||
<path d="M91.7,414.4l303.4,163.3c1.5,0.8,3.2,1.2,4.9,1.2c1.7,0,3.4-0.4,5-1.3l0,0l96.1-52.4l29.2,15.7L400,612.1L62.5,430.4
|
||||
L91.7,414.4z"/>
|
||||
<polygon points="51.1,447.8 389.6,630.1 389.6,800 51.1,617.7 "/>
|
||||
<polygon points="541.6,728.3 410.4,799.9 410.4,630 541.6,558.4 "/>
|
||||
<polygon points="748.9,617.7 617.6,689.3 617.6,519.5 748.9,447.9 "/>
|
||||
<polygon points="748.9,369 617.6,440.6 617.6,270.7 748.9,199.1 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
40
DBQnA/ui/react/src/assets/opea-icon-color.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="800px" height="800px" viewBox="0 0 800 800" style="enable-background:new 0 0 800 800;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.Drop_x0020_Shadow{fill:none;}
|
||||
.Outer_x0020_Glow_x0020_5_x0020_pt{fill:none;}
|
||||
.Blue_x0020_Neon{fill:none;stroke:#8AACDA;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.Chrome_x0020_Highlight{fill:url(#SVGID_1_);stroke:#FFFFFF;stroke-width:0.3629;stroke-miterlimit:1;}
|
||||
.Jive_GS{fill:#FFDD00;}
|
||||
.Alyssa_GS{fill:#A6D0E4;}
|
||||
.st0{fill:#FF6900;}
|
||||
.st1{fill:#FFB500;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-820" y1="-1640" x2="-820" y2="-1641">
|
||||
<stop offset="0" style="stop-color:#656565"/>
|
||||
<stop offset="0.618" style="stop-color:#1B1B1B"/>
|
||||
<stop offset="0.6292" style="stop-color:#545454"/>
|
||||
<stop offset="0.9831" style="stop-color:#3E3E3E"/>
|
||||
</linearGradient>
|
||||
<g>
|
||||
<polygon class="st0" points="400,0 737.5,181.7 607.3,252.8 269.7,71.1 "/>
|
||||
<path class="st1" d="M708.3,414.7l29.2,15.7l-130.3,71.1l-44.9-24.2v-31.7l40,21.5c1.5,0.8,3.2,1.2,4.9,1.2c1.7,0,3.4-0.4,5-1.3
|
||||
l0,0L708.3,414.7z"/>
|
||||
<path class="st1" d="M557.3,532.1c-0.1-0.1-0.3-0.1-0.4-0.2l0,0l-34.2-18.4l31.2-17l42.9,23.1v169.9l-34.5-18.6V541
|
||||
C562.4,537.3,560.4,533.9,557.3,532.1z"/>
|
||||
<polygon class="st1" points="410.4,381.3 541.6,309.7 541.6,479.5 410.4,551.2 "/>
|
||||
<path class="st0" d="M258.4,88.5l338.6,182.3v169.9l-34.5-18.6V292.2c0-3.7-1.9-7-5.1-8.9c-0.1-0.1-0.3-0.1-0.4-0.2l0,0
|
||||
L258.4,122.3V88.5z"/>
|
||||
<polygon class="st1" points="192.7,110.6 530.3,292.3 400,363.4 62.5,181.6 "/>
|
||||
<polygon class="st1" points="51.1,369 51.1,199 389.6,381.3 389.6,551.3 96.6,393.5 "/>
|
||||
<path class="st0" d="M91.7,414.4l303.4,163.3c1.5,0.8,3.2,1.2,4.9,1.2c1.7,0,3.4-0.4,5-1.3l0,0l96.1-52.4l29.2,15.7L400,612.1
|
||||
L62.5,430.4L91.7,414.4z"/>
|
||||
<polygon class="st0" points="51.1,447.8 389.6,630.1 389.6,800 51.1,617.7 "/>
|
||||
<polygon class="st0" points="541.6,728.3 410.4,799.9 410.4,630 541.6,558.4 "/>
|
||||
<polygon class="st1" points="748.9,617.7 617.6,689.3 617.6,519.5 748.9,447.9 "/>
|
||||
<polygon class="st0" points="748.9,369 617.6,440.6 617.6,270.7 748.9,199.1 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
211
DBQnA/ui/react/src/components/DbConnect/DBConnect.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Button, Text, TextInput, Title, Textarea, Loader } from '@mantine/core';
|
||||
import { TEXT_TO_SQL_URL } from '../../config';
|
||||
// import { notifications } from '@mantine/notifications';
|
||||
import styleClasses from './dbconnect.module.scss'; // Importing the SCSS file
|
||||
|
||||
const DBConnect: React.FC = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
user: 'postgres',
|
||||
database: 'chinook',
|
||||
host: '10.223.24.113',
|
||||
password: 'testpwd',
|
||||
port: '5442',
|
||||
});
|
||||
|
||||
const [dbStatus, setDbStatus] = useState<string | null>(null);
|
||||
const [sqlStatus, setSqlStatus] = useState<string | null>(null);
|
||||
const [dberror, setDbError] = useState<string | null>(null);
|
||||
const [sqlerror, setSqlError] = useState<string | null>(null);
|
||||
const [question, setQuestion] = useState<string>('');
|
||||
const [sqlQuery, setSqlQuery] = useState<string | null>(null);
|
||||
const [queryOutput, setQueryOutput] = useState<string | null>(null);
|
||||
const [isConnected, setIsConnected] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Handle form input changes
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle question input changes
|
||||
const handleQuestionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setQuestion(e.target.value);
|
||||
};
|
||||
|
||||
// Handle form submission and API request
|
||||
const handleDBConnect = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
let api_response: Record<string, any>;
|
||||
api_response = await axios.post(`${TEXT_TO_SQL_URL}/postgres/health`, formData);
|
||||
|
||||
setSqlStatus(null);
|
||||
setSqlError(null);
|
||||
|
||||
if (api_response.data.status === 'success') {
|
||||
setDbStatus(api_response.data.message);
|
||||
setDbError(null);
|
||||
setIsConnected(true);
|
||||
setQuestion('');
|
||||
setSqlQuery(null);
|
||||
setQueryOutput(null);
|
||||
} else {
|
||||
setDbStatus(null);
|
||||
setIsConnected(false);
|
||||
setDbError(api_response.data.message); //response would contain error message in case of failure
|
||||
setSqlStatus(null);
|
||||
}
|
||||
} catch (err) {
|
||||
setDbError('Failed to connect to the database.');
|
||||
setIsConnected(false);
|
||||
setDbStatus(null);
|
||||
setSqlStatus(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle generating SQL query
|
||||
const handleGenerateSQL = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const payload = {
|
||||
input_text: question,
|
||||
conn_str: formData,
|
||||
};
|
||||
|
||||
let api_response: Record<string, any>;
|
||||
api_response = await axios.post(`${TEXT_TO_SQL_URL}/texttosql`, payload);
|
||||
|
||||
setSqlQuery(api_response.data.result.sql); // Assuming the API returns an SQL query
|
||||
setQueryOutput(api_response.data.result.output);
|
||||
setSqlError(null)
|
||||
setSqlStatus('SQL query output generated successfully')
|
||||
} catch (err) {
|
||||
setSqlError('Failed to generate SQL query output.');
|
||||
} finally {
|
||||
setIsLoading(false); // Stop loading
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styleClasses.dbconnectWrapper}>
|
||||
<div className={styleClasses.dbConnectSection}>
|
||||
<Title order={1}>DB Connection</Title>
|
||||
<form className={styleClasses.form} onSubmit={handleDBConnect}>
|
||||
<div className={styleClasses.inputField}>
|
||||
<TextInput
|
||||
label="Host"
|
||||
placeholder="Enter Host"
|
||||
name="host"
|
||||
value={formData.host}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styleClasses.inputField}>
|
||||
<TextInput
|
||||
label="User"
|
||||
placeholder="Enter User"
|
||||
name="user"
|
||||
value={formData.user}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styleClasses.inputField}>
|
||||
<TextInput
|
||||
label="Database Name"
|
||||
placeholder="Enter Database Name"
|
||||
name="database"
|
||||
value={formData.database}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styleClasses.inputField}>
|
||||
<TextInput
|
||||
label="Password"
|
||||
placeholder="Enter Password"
|
||||
name="password"
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styleClasses.inputField}>
|
||||
<TextInput
|
||||
label="Port"
|
||||
placeholder="Enter Port"
|
||||
name="port"
|
||||
value={formData.port}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className={styleClasses.submitButton} fullWidth>
|
||||
Connect
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{dbStatus && <Text className={styleClasses.status}>Status: {dbStatus}</Text>}
|
||||
{dberror && <Text className={styleClasses.error}>Error: {dberror}</Text>}
|
||||
</div>
|
||||
|
||||
{/* DBQnA Section */}
|
||||
<div className={styleClasses.textToSQLSection}>
|
||||
<Title order={1}>DBQnA</Title>
|
||||
{isConnected && (
|
||||
<form className={styleClasses.form} onSubmit={handleGenerateSQL}>
|
||||
<div className={styleClasses.sqlQuerySection}>
|
||||
<div className={styleClasses.inputField}>
|
||||
<label>Enter Your Question:</label>
|
||||
<Textarea
|
||||
placeholder="Type Your Question Here"
|
||||
value={question}
|
||||
onChange={handleQuestionChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className={styleClasses.submitButton} fullWidth disabled={isLoading}>
|
||||
{isLoading ? <div className="loader-container"><Loader size="sm" /></div> : 'Generate SQL Query Output'}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* Display SQL query response */}
|
||||
{isConnected && sqlQuery && queryOutput && !isLoading && (
|
||||
<div className={styleClasses.sqlQuerySection}>
|
||||
<form className={styleClasses.form}>
|
||||
<div className={styleClasses.inputField}>
|
||||
<label>Generated SQL Query:</label>
|
||||
<Textarea value={sqlQuery.replace('\n', '')} readOnly />
|
||||
<label>Generated SQL Query Output:</label>
|
||||
<Textarea value={queryOutput.replace('</s>', '')} readOnly />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Display SQL generation status, error if any */}
|
||||
{sqlStatus && !isLoading && <Text className={styleClasses.status}>Status: {sqlStatus}</Text>}
|
||||
{sqlerror && !isLoading && <Text className={styleClasses.error}>Error: {sqlerror}</Text>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DBConnect;
|
||||
133
DBQnA/ui/react/src/components/DbConnect/dbconnect.module.scss
Normal file
@@ -0,0 +1,133 @@
|
||||
@import "../../styles/styles"; // Import global styles if any
|
||||
|
||||
.dbconnectWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 20px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
max-width: 1200px; /* Set a max-width for the container */
|
||||
margin: 0 auto; /* Center the container */
|
||||
|
||||
h1 {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 20px;
|
||||
color: #343a40;
|
||||
}
|
||||
|
||||
.dbConnectSection,
|
||||
.textToSQLSection {
|
||||
flex: 1; /* Allow each section to take up equal space */
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin-right: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0; /* Remove the right margin for the last section */
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.inputField {
|
||||
margin-bottom: 15px;
|
||||
|
||||
label {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
resize: vertical;
|
||||
&:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 12px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #004494;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status,
|
||||
.error {
|
||||
font-size: 1.1rem;
|
||||
margin-top: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.sqlQuerySection {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
margin-right: 20px;
|
||||
|
||||
label {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
resize: vertical;
|
||||
min-height: 95px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
DBQnA/ui/react/src/components/sidebar/sidebar.module.scss
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
Copyright (c) 2024 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
**/
|
||||
|
||||
@import "../../styles/styles";
|
||||
|
||||
.navbar {
|
||||
width: 100%;
|
||||
@include flex(column, nowrap, center, flex-start);
|
||||
padding: var(--mantine-spacing-md);
|
||||
background-color: var(--mantine-color-blue-filled);
|
||||
// background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
|
||||
// border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
||||
}
|
||||
|
||||
.navbarMain {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.navbarLogo {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: var(--mantine-spacing-md);
|
||||
margin-bottom: var(--mantine-spacing-xl);
|
||||
}
|
||||
|
||||
.link {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--mantine-radius-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mantine-color-white);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--mantine-color-blue-7);
|
||||
}
|
||||
|
||||
&[data-active] {
|
||||
&,
|
||||
&:hover {
|
||||
box-shadow: var(--mantine-shadow-sm);
|
||||
background-color: var(--mantine-color-white);
|
||||
color: var(--mantine-color-blue-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aside {
|
||||
flex: 0 0 60px;
|
||||
background-color: var(--mantine-color-body);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
padding-top: var(--mantine-spacing-s);
|
||||
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
|
||||
margin-bottom: var(--mantine-spacing-xl);
|
||||
}
|
||||
.logoImg {
|
||||
width: 30px;
|
||||
}
|
||||
58
DBQnA/ui/react/src/components/sidebar/sidebar.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useState } from "react"
|
||||
import { Tooltip, UnstyledButton, Stack, rem } from "@mantine/core"
|
||||
import { IconHome2 } from "@tabler/icons-react"
|
||||
import classes from "./sidebar.module.scss"
|
||||
import OpeaLogo from "../../assets/opea-icon-color.svg"
|
||||
interface NavbarLinkProps {
|
||||
icon: typeof IconHome2
|
||||
label: string
|
||||
active?: boolean
|
||||
onClick?(): void
|
||||
}
|
||||
|
||||
function NavbarLink({ icon: Icon, label, active, onClick }: NavbarLinkProps) {
|
||||
return (
|
||||
<Tooltip label={label} position="right" transitionProps={{ duration: 0 }}>
|
||||
<UnstyledButton onClick={onClick} className={classes.link} data-active={active || undefined}>
|
||||
<Icon style={{ width: rem(20), height: rem(20) }} stroke={1.5} />
|
||||
</UnstyledButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export interface SidebarNavItem {
|
||||
icon: typeof IconHome2
|
||||
label: string
|
||||
}
|
||||
|
||||
export type SidebarNavList = SidebarNavItem[]
|
||||
|
||||
export interface SideNavbarProps {
|
||||
navList: SidebarNavList
|
||||
}
|
||||
|
||||
export function SideNavbar({ navList }: SideNavbarProps) {
|
||||
const [active, setActive] = useState(0)
|
||||
|
||||
|
||||
const links = navList.map((link, index) => (
|
||||
<NavbarLink {...link} key={link.label} active={index === active} onClick={() => setActive(index)} />
|
||||
))
|
||||
|
||||
return (
|
||||
<nav className={classes.navbar}>
|
||||
<div className={classes.navbarLogo}>
|
||||
<img className={classes.logoImg} src={OpeaLogo} alt="opea logo" />
|
||||
</div>
|
||||
|
||||
<div className={classes.navbarMain}>
|
||||
<Stack justify="center" gap={0}>
|
||||
{links}
|
||||
</Stack>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
4
DBQnA/ui/react/src/config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export const TEXT_TO_SQL_URL = import.meta.env.VITE_TEXT_TO_SQL_URL;
|
||||
20
DBQnA/ui/react/src/index.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@import "@mantine/core/styles.css";
|
||||
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
19
DBQnA/ui/react/src/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
// import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
// reportWebVitals();
|
||||
1
DBQnA/ui/react/src/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
13
DBQnA/ui/react/src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom/client"
|
||||
import App from './App';
|
||||
import "./index.scss"
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
7
DBQnA/ui/react/src/styles/layout/_basics.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@mixin absolutes {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
6
DBQnA/ui/react/src/styles/layout/_flex.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@mixin flex($direction: row, $wrap: nowrap, $alignItems: center, $justifyContent: center) {
|
||||
display: flex;
|
||||
flex-flow: $direction $wrap;
|
||||
align-items: $alignItems;
|
||||
justify-content: $justifyContent;
|
||||
}
|
||||
5
DBQnA/ui/react/src/styles/styles.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@import "layout/flex";
|
||||
@import "layout/basics";
|
||||
4
DBQnA/ui/react/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="vite/client" />
|
||||
25
DBQnA/ui/react/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"types": ["vitest/globals"],
|
||||
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
11
DBQnA/ui/react/tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
30
DBQnA/ui/react/vite.config.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2024 Intel Corporation
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import os from "os";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "./src/styles/styles.scss";`,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
},
|
||||
define: {
|
||||
// Dynamically set the hostname for the VITE_TEXT_TO_SQL_URL
|
||||
"import.meta.env.VITE_TEXT_TO_SQL_URL": JSON.stringify(`http://${os.hostname()}:9090/v1`),
|
||||
"import.meta.env": process.env,
|
||||
},
|
||||
});
|
||||
@@ -9,7 +9,7 @@ line-length = 120
|
||||
|
||||
|
||||
[tool.codespell]
|
||||
skip = '*.po,*.js,*.map,*.js.map,*.css.map,*.json'
|
||||
skip = '*.po,*.js,*.map,*.js.map,*.css.map,*.json,*.sql'
|
||||
count = ''
|
||||
quiet-level = 3
|
||||
ignore-words = ".github/code_spell_ignore.txt"
|
||||
|
||||