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>
This commit is contained in:
Supriya-Krishnamurthi
2024-10-14 11:06:00 +05:30
committed by GitHub
parent 088ab98f31
commit c0643b71e8
44 changed files with 25309 additions and 1 deletions

17
DBQnA/README.md Normal file
View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View 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.
![project-screenshot](../../../../assets/img/dbQnA_ui_init.png)
Test DB Connection
![project-screenshot](../../../../assets/img/dbQnA_ui_successful_db_connection.png)
Create SQL query and output for given NLP question
![project-screenshot](../../../../assets/img/dbQnA_ui_succesful_sql_output_generation.png)

File diff suppressed because it is too large Load Diff

View 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

View 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"

View 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}

View 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

View 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
View File

@@ -0,0 +1 @@
VITE_TEXT_TO_SQL_URL=http://${HOSTNAME}:9090/v1

23
DBQnA/ui/react/.gitignore vendored Normal file
View 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
View File

@@ -0,0 +1,48 @@
# DBQnA React Application
## 📸 Project Screenshots
Initial Page
![project-screenshot](../../assets/img/dbQnA_ui_init.png)
Enter DB credentials and connect to DB
![project-screenshot](../../assets/img/dbQnA_ui_db_credentials.png)
DB connection failed
![project-screenshot](../../assets/img/dbQnA_ui_failed_db_connection.png)
DB connection successful
![project-screenshot](../../assets/img/dbQnA_ui_successful_db_connection.png)
Enter user question
![project-screenshot](../../assets/img/dbQnA_ui_enter_question.png)
SQL query generation failed
![project-screenshot](../../assets/img/dbQnA_ui_failed_sql_output_generation.png)
SQL query generation successful
![project-screenshot](../../assets/img/dbQnA_ui_succesful_sql_output_generation.png)
## 🧐 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
View 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
View 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;
}
}
}

View 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"
}
}

View 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",
},
},
},
};

View 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;
}

View 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);

View 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;

View 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

View 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

View 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;

View 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;
}
}
}

View 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;
}

View 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>
)
}

View 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;

View 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;
}

View 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();

View 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

View 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>
)

View File

@@ -0,0 +1,7 @@
@mixin absolutes {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

View 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;
}

View 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
View File

@@ -0,0 +1,4 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
/// <reference types="vite/client" />

View 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" }]
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

View 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,
},
});

View File

@@ -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"