Add Vision demo side by side UI (#70)

* Add Vision demo side by side UI

Signed-off-by: lvliang-intel <liang1.lv@intel.com>
This commit is contained in:
lvliang-intel
2024-04-11 16:28:53 +08:00
committed by GitHub
parent a2ea9217da
commit 71fd893ae4
120 changed files with 9313 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
// 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.
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
settings: { react: { version: "18.2" } },
plugins: ["react-refresh", "simple-import-sort"],
rules: {
"react/jsx-no-target-blank": "off",
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
};

24
ChatQnA/ui_side_by_side/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"endOfLine": "lf",
"printWidth": 100
}

View File

@@ -0,0 +1,68 @@
# LLM Chatbot GUI
## 📸 Project Screenshots
![project-screenshot](https://i.imgur.com/2Q1tkW3.png)
![project-screenshot](https://i.imgur.com/ERaSnYi.png)
## Requirements
- [Node.js](https://nodejs.org/) version v18.0.0 or higher
- [npm](https://www.npmjs.com/) version 9.6.5 or higher
To check if both were successfully installed run the following commands:
```bash
node --version
```
```bash
npm --version
```
_`--version` option can be replaced with `-v` shorthand_
## Setup
### Environment variables
Create `.env` file in this folder. It has to contain the following variables that represent corresponding endpoints
for communication with **without RAG** and **with RAG** backend:
```
VITE_WITH_RAG_BASE_URL = 'http://<ip-address>:<port>/v1/rag'
VITE_WITHOUT_RAG_BASE_URL = 'http://<ip-address>:<port>/v1/rag'
```
### Install dependencies
```bash
npm install
```
## Start GUI
Execute the following command to start GUI:
```bash
npm run dev
```
By default, UI will run on `http://localhost:5147`.
The port and IP address that the UI will be served on can be changed by modifying npm `dev` script
in `package.json`.
The following example presents how to change port and IP address by setting corresponding options: `--port`
and `--host`.
```json
"dev": "vite dev --port 9090 --host 0.0.0.0",
```
This also can be set via CLI by adding `-- --port <port> --host <ip>` :
```bash
npm run dev -- --port 9090 --host 0.0.0.0
```
In case of any configuring issues please refer to https://vitejs.dev/config/server-options.

View File

@@ -0,0 +1,29 @@
<!--
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.
-->
<!doctype html>
<html data-theme="dark" lang="en">
<head>
<meta charset="UTF-8" />
<link href="" rel="icon" type="image/svg+xml" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Enhancing Generative AI Business Relevant Results with RAG</title>
</head>
<body>
<div id="root"></div>
<script src="/src/main.jsx" type="module"></script>
</body>
</html>

6563
ChatQnA/ui_side_by_side/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
{
"name": "llm-chatbot",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.12",
"@mui/icons-material": "^5.15.14",
"@mui/material": "^5.15.14",
"@reduxjs/toolkit": "^2.2.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.3",
"remark-gfm": "^4.0.0",
"sse.js": "^2.4.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/react": "^18.2.64",
"@types/react-dom": "^18.2.21",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-simple-import-sort": "^12.0.0",
"prettier": "^3.2.5",
"sass": "^1.71.1",
"vite": "^5.1.6"
}
}

View File

@@ -0,0 +1,24 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
/* Uncomment the following line to disable whether the user can select and copy text */
/*user-select: none;*/
}
html {
font-size: 16px;
font-family: "IntelOneText", sans-serif !important;
@media screen and (min-width: 2500px) {
font-size: 24px;
}
@media screen and (max-width: 2500px) {
font-size: 18px;
}
@media screen and (max-width: 1366px) {
font-size: 12px;
}
}

View File

@@ -0,0 +1,25 @@
import { useEffect } from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import AppHeader from "./layout/app-header/AppHeader";
import ChatPage from "./pages/chat/ChatPage";
import TelemetryPage from "./pages/telemetry/TelemetryPage";
const App = () => {
useEffect(() => {
localStorage.clear();
}, []);
return (
<BrowserRouter>
<AppHeader />
<Routes>
<Route path="/" element={<Navigate to="/chat" />} />
<Route path="chat" element={<ChatPage />} />
<Route path="telemetry" element={<TelemetryPage />} />
</Routes>
</BrowserRouter>
);
};
export default App;

View File

@@ -0,0 +1,42 @@
/* Do not edit this file manually! This file is generated automatically from the @spark-design/tokens package.*/
@font-face {
font-family: IntelOneDisplay;
src:
url("intelone-display-font-family-medium.woff2") format("woff2"),
url("intelone-display-font-family-medium.woff") format("woff"),
url("intelone-display-font-family-medium.otf") format("opentype"),
url("intelone-display-font-family-medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: IntelOneDisplay;
src:
url("intelone-display-font-family-regular.woff2") format("woff2"),
url("intelone-display-font-family-regular.woff") format("woff"),
url("intelone-display-font-family-regular.otf") format("opentype"),
url("intelone-display-font-family-regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: IntelOneDisplay;
src:
url("intelone-display-font-family-light.woff2") format("woff2"),
url("intelone-display-font-family-light.woff") format("woff"),
url("intelone-display-font-family-light.otf") format("opentype"),
url("intelone-display-font-family-light.ttf") format("truetype");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: IntelOneDisplay;
src:
url("intelone-display-font-family-bold.woff2") format("woff2"),
url("intelone-display-font-family-bold.woff") format("woff"),
url("intelone-display-font-family-bold.otf") format("opentype"),
url("intelone-display-font-family-bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
}

View File

@@ -0,0 +1,82 @@
/* Do not edit this file manually! This file is generated automatically from the @spark-design/tokens package.*/
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-regular.woff2") format("woff2"),
url("intelone-mono-font-family-regular.woff") format("woff"),
url("intelone-mono-font-family-regular.otf") format("opentype"),
url("intelone-mono-font-family-regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-italic.woff2") format("woff2"),
url("intelone-mono-font-family-italic.woff") format("woff"),
url("intelone-mono-font-family-italic.otf") format("opentype"),
url("intelone-mono-font-family-italic.ttf") format("truetype");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-medium.woff2") format("woff2"),
url("intelone-mono-font-family-medium.woff") format("woff"),
url("intelone-mono-font-family-medium.otf") format("opentype"),
url("intelone-mono-font-family-medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-mediumitalic.woff2") format("woff2"),
url("intelone-mono-font-family-mediumitalic.woff") format("woff"),
url("intelone-mono-font-family-mediumitalic.otf") format("opentype"),
url("intelone-mono-font-family-mediumitalic.ttf") format("truetype");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-light.woff2") format("woff2"),
url("intelone-mono-font-family-light.woff") format("woff"),
url("intelone-mono-font-family-light.otf") format("opentype"),
url("intelone-mono-font-family-light.ttf") format("truetype");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-lightitalic.woff2") format("woff2"),
url("intelone-mono-font-family-lightitalic.woff") format("woff"),
url("intelone-mono-font-family-lightitalic.otf") format("opentype"),
url("intelone-mono-font-family-lightitalic.ttf") format("truetype");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-bold.woff2") format("woff2"),
url("intelone-mono-font-family-bold.woff") format("woff"),
url("intelone-mono-font-family-bold.otf") format("opentype"),
url("intelone-mono-font-family-bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: IntelOneMono;
src:
url("intelone-mono-font-family-bolditalic.woff2") format("woff2"),
url("intelone-mono-font-family-bolditalic.woff") format("woff"),
url("intelone-mono-font-family-bolditalic.otf") format("opentype"),
url("intelone-mono-font-family-bolditalic.ttf") format("truetype");
font-weight: bold;
font-style: italic;
}

View File

@@ -0,0 +1,82 @@
/* Do not edit this file manually! This file is generated automatically from the @spark-design/tokens package.*/
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-regular.woff2") format("woff2"),
url("intelone-bodytext-font-family-regular.woff") format("woff"),
url("intelone-bodytext-font-family-regular.otf") format("opentype"),
url("intelone-bodytext-font-family-regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-italic.woff2") format("woff2"),
url("intelone-bodytext-font-family-italic.woff") format("woff"),
url("intelone-bodytext-font-family-italic.otf") format("opentype"),
url("intelone-bodytext-font-family-italic.ttf") format("truetype");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-medium.woff2") format("woff2"),
url("intelone-bodytext-font-family-medium.woff") format("woff"),
url("intelone-bodytext-font-family-medium.otf") format("opentype"),
url("intelone-bodytext-font-family-medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-mediumitalic.woff2") format("woff2"),
url("intelone-bodytext-font-family-mediumitalic.woff") format("woff"),
url("intelone-bodytext-font-family-mediumitalic.otf") format("opentype"),
url("intelone-bodytext-font-family-mediumitalic.ttf") format("truetype");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-light.woff2") format("woff2"),
url("intelone-bodytext-font-family-light.woff") format("woff"),
url("intelone-bodytext-font-family-light.otf") format("opentype"),
url("intelone-bodytext-font-family-light.ttf") format("truetype");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-lightitalic.woff2") format("woff2"),
url("intelone-bodytext-font-family-lightitalic.woff") format("woff"),
url("intelone-bodytext-font-family-lightitalic.otf") format("opentype"),
url("intelone-bodytext-font-family-lightitalic.ttf") format("truetype");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-bold.woff2") format("woff2"),
url("intelone-bodytext-font-family-bold.woff") format("woff"),
url("intelone-bodytext-font-family-bold.otf") format("opentype"),
url("intelone-bodytext-font-family-bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: IntelOneText;
src:
url("intelone-bodytext-font-family-bolditalic.woff2") format("woff2"),
url("intelone-bodytext-font-family-bolditalic.woff") format("woff"),
url("intelone-bodytext-font-family-bolditalic.otf") format("opentype"),
url("intelone-bodytext-font-family-bolditalic.ttf") format("truetype");
font-weight: bold;
font-style: italic;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,3 @@
@import "./assets/fonts/intel-one-display/intel-one-display.css";
@import "./assets/fonts/intel-one-text/intel-one-text.css";
@import "./assets/fonts/intel-one-mono/intel-one-mono.css";

View File

@@ -0,0 +1,18 @@
import "./app-header.scss";
import { NavLink } from "react-router-dom";
const AppHeader = () => {
return (
<header className="app-header">
<img alt="Intel Logo" className="intel-logo" />
<h3 className="app-title">Enhancing Generative AI Business Relevant Results with RAG</h3>
<nav>
<NavLink to="/chat">Chat</NavLink>
<NavLink to="/telemetry">Telemetry</NavLink>
</nav>
</header>
);
};
export default AppHeader;

View File

@@ -0,0 +1,47 @@
.app-header {
display: inline-flex;
inline-size: 100%;
block-size: 3rem;
line-height: 3rem;
color: #ffffff;
background-color: #1f2029;
align-items: center;
padding-block: 0.5rem;
& .intel-logo {
height: 1rem;
padding-inline: 1.75rem;
content: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzIiIGhlaWdodD0iMjgiIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA3MiAyOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiAgPHRpdGxlPkludGVsPC90aXRsZT4NCiAgPHBhdGggZmlsbD0idmFyKC0tc3BhcmstY29sb3ItZW5lcmd5LWJsdWUsICMwMEM3RkQpIiBkPSJNNS4yMi4zOEgwdjUuMjRoNS4yMlYuMzhaIi8+DQogIDxwYXRoIGZpbGw9InZhcigtLXNwYXJrLWNvbG9yLXdoaXRlLCB2YXIoLS1zcGFyay1jb2xvci13aGl0ZSwgI2ZmZmZmZikpIiBkPSJNNS4wOCAyNy42MlY4Ljc4SC4xNHYxOC44NGg0Ljk0Wm0zMi44LjE5VjIzLjJjLS43MyAwLTEuMzMtLjA1LTEuOC0uMTItLjUtLjA4LS45LS4yNS0xLjE1LS41MmEyLjA2IDIuMDYgMCAwIDEtLjUyLTEuMTMgMTIuMSAxMi4xIDAgMCAxLS4xMi0xLjgxdi02LjZoMy41OFY4Ljc5SDM0LjNWMS40NmgtNC45NHYxOC4yYzAgMS41My4xMyAyLjg0LjQgMy44OS4yNiAxLjAyLjcgMS44NiAxLjMxIDIuNWE1LjI3IDUuMjcgMCAwIDAgMi40MSAxLjM2YzEgLjI3IDIuMjcuNDEgMy43Ny40MWguNjN2LS4wMVptMjguMzQtLjE5VjBoLTQuOTV2MjcuNjJoNC45NVpNMjQuNTggMTAuNjRjLTEuMzctMS40OS0zLjMtMi4yNC01Ljc2LTIuMjQtMS4xOCAwLTIuMjcuMjUtMy4yNC43M2E3LjEgNy4xIDAgMCAwLTIuNDUgMmwtLjI4LjM2di0yLjdINy45N3YxOC44M2g0LjkxVjE3LjU5di43LS4zNGMuMDYtMS43Ny40OS0zLjA3IDEuMzEtMy45YTQuMjcgNC4yNyAwIDAgMSAzLjE0LTEuMzVjMS40MyAwIDIuNTEuNDQgMy4yNCAxLjMuNy44NCAxLjA2IDIuMDYgMS4wNyAzLjYxdjEwLjAxaDVWMTYuOTNjMC0yLjY5LS43LTQuOC0yLjA2LTYuMjlabTM0LjEyIDcuNTNjMC0xLjM1LS4yNS0yLjYzLS43Mi0zLjgxYTEwLjAyIDEwLjAyIDAgMCAwLTItMy4xMiA4Ljk0IDguOTQgMCAwIDAtMy4wNS0yLjA5QTEwLjEgMTAuMSAwIDAgMCA0OSA4LjRhOS42IDkuNiAwIDAgMC0zLjgxLjc3IDkuOTQgOS45NCAwIDAgMC0zLjEgMi4xIDkuOCA5LjggMCAwIDAtMi4wOSAzLjEgOS41IDkuNSAwIDAgMC0uNzcgMy44MmMwIDEuMzUuMjQgMi42NC43NCAzLjgyYTkuMzMgOS4zMyAwIDAgMCAyLjAzIDMuMSA5LjQ1IDkuNDUgMCAwIDAgMy4xNCAyLjFjMS4yMi41MiAyLjU4Ljc3IDQuMDIuNzcgNC4xOSAwIDYuOC0xLjkxIDguMzYtMy43bC0zLjU2LTIuNzJhNi41IDYuNSAwIDAgMS00Ljc2IDIuMWMtMS40IDAtMi41NS0uMzMtMy40MS0uOTZBNC44MiA0LjgyIDAgMCAxIDQ0IDIwLjFsLS4wNS0uMThoMTQuNzR2LTEuNzRabS0xNC43MS0xLjczYzAtMS4zOCAxLjU4LTMuNzkgNC45OC0zLjc5IDMuNCAwIDQuOTggMi40IDQuOTggMy43OGwtOS45Ni4wMVoiLz4NCiAgPHBhdGggZmlsbD0idmFyKC0tc3BhcmstY29sb3Itd2hpdGUsIHZhcigtLXNwYXJrLWNvbG9yLXdoaXRlLCAjZmZmZmZmKSkiIGQ9Ik03MS44NSAyNS4yNGEyIDIgMCAwIDAtLjM5LS41OCAxLjgxIDEuODEgMCAwIDAtLjU4LS4zOSAxLjgxIDEuODEgMCAwIDAtMS40MyAwIDIgMiAwIDAgMC0uNTguNCAxLjgxIDEuODEgMCAwIDAtLjM5LjU3IDEuODIgMS44MiAwIDAgMCAwIDEuNDQgMS44IDEuOCAwIDAgMCAuOTcuOTcgMS44MSAxLjgxIDAgMCAwIDEuNDMgMCAxLjgzIDEuODMgMCAwIDAgLjk3LS45NyAxLjgyIDEuODIgMCAwIDAgMC0xLjQ0Wm0tLjMgMS4zMWExLjUyIDEuNTIgMCAwIDEtMS40LjkzYy0uMiAwLS40LS4wNC0uNTgtLjExYTEuNTEgMS41MSAwIDAgMS0uNDktLjMzIDEuNTcgMS41NyAwIDAgMS0uMzItLjQ5IDEuNTYgMS41NiAwIDAgMSAwLTEuMTggMS41MSAxLjUxIDAgMCAxIDEuNC0uOTMgMS41MyAxLjUzIDAgMCAxIDEuNC45MyAxLjU2IDEuNTYgMCAwIDEgMCAxLjE4Wm0tMS4wMy0uNDRhLjY2LjY2IDAgMCAwIC4zNS0uMTZjLjEtLjEuMTQtLjIzLjE0LS40MWEuNi42IDAgMCAwLS4xOC0uNDdjLS4xMS0uMS0uMy0uMTUtLjU2LS4xNWgtLjh2Mi4xaC4zN3YtLjg2aC4yOWwuNTIuODZoLjRsLS41My0uOTFabS0uMjEtLjNoLS40N3YtLjU5aC40N2EuMzUuMzUgMCAwIDEgLjI3LjEyLjI2LjI2IDAgMCAxIC4wNC4xNmMwIC4wNiAwIC4xMi0uMDQuMTYtLjAzLjA0LS4wNi4wNy0uMTIuMDgtLjA0LjA1LS4xLjA2LS4xNS4wNloiLz4NCjwvc3ZnPg0K);
}
& .app-title {
padding-inline: 1.5rem;
font-size: 1.125rem;
font-weight: 300;
line-height: 2.125rem;
height: 100%;
border-left: 0.25rem solid #00c7fd;
}
& > nav {
margin-left: auto;
padding-right: 6rem;
& a {
color: white;
text-decoration: none;
padding-inline: 1rem;
padding-block: 0.85rem;
font-weight: 500;
&:hover {
background-color: #242528;
cursor: pointer;
}
&.active {
box-shadow: inset 0 -0.125rem #00c7fd;
}
}
}
}

View File

@@ -0,0 +1,29 @@
import "./index.css";
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import "./App.css";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const darkTheme = createTheme({
palette: {
mode: "dark",
},
typography: {
fontFamily: ["IntelOneText", "IntelOneDisplay", "sans-serif"],
},
});
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ThemeProvider theme={darkTheme}>
<App />
</ThemeProvider>
</React.StrictMode>
);

View File

@@ -0,0 +1,323 @@
import "./chat-page.scss";
import { useEffect, useRef, useState } from "react";
import { SSE } from "sse.js";
import ResultsService from "../../service/ResultsService";
import PromptInput from "./components/prompt-input/PromptInput";
import ResultsCards from "./components/results-cards/ResultsCards";
import UploadFileDrawer from "./components/upload/UploadFileDrawer";
import UploadNotification from "./components/upload-notification/UploadNotification";
const { VITE_WITH_RAG_BASE_URL, VITE_WITHOUT_RAG_BASE_URL } = import.meta.env;
const RESULTS_DATA_LOCAL_STORAGE_KEY = "resultsData";
const resultsDataInitialState = {
withoutRAG: [],
withRAG: [],
};
const ChatPage = () => {
const [resultsData, setResultsData] = useState(resultsDataInitialState);
const [resultsLoading, setResultsLoading] = useState({
withoutRAG: false,
withRAG: false,
});
const withRAGAnswer = useRef("");
const withoutRAGAnswer = useRef("");
const [isPromptInputDisabled, setIsPromptInputDisabled] = useState(false);
const [showUploadFileDrawer, setShowUploadFileDrawer] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [notification, setNotification] = useState({
open: false,
message: <span></span>,
severity: "success",
});
const [isClearChatHistoryBtnDisabled, setIsClearChatHistoryBtnDisabled] = useState(true);
const isMounted = useRef(false);
useEffect(() => {
const storedResultsData = JSON.parse(localStorage.getItem(RESULTS_DATA_LOCAL_STORAGE_KEY));
if (storedResultsData) {
setResultsData(storedResultsData);
}
}, []);
useEffect(() => {
if (isMounted.current) {
window.localStorage.setItem(RESULTS_DATA_LOCAL_STORAGE_KEY, JSON.stringify(resultsData));
} else {
isMounted.current = true;
}
}, [resultsData]);
useEffect(() => {
setIsPromptInputDisabled(Object.values(resultsLoading).some((value) => value));
if (!resultsLoading.withRAG) {
withRAGAnswer.current = "";
}
if (!resultsLoading.withoutRAG) {
withoutRAGAnswer.current = "";
}
}, [resultsLoading]);
useEffect(() => {
setIsClearChatHistoryBtnDisabled(
Object.values(resultsLoading).some((value) => value) ||
Object.values(resultsData).every((arr) => arr.length === 0)
);
}, [resultsData, resultsLoading]);
const onConfirmPrompt = (prompt) => {
localStorage.setItem("latency_without_rag", null);
localStorage.setItem("llm_token_latency_without_rag", null);
localStorage.setItem("latency_with_rag", null);
localStorage.setItem("llm_token_latency_with_rag", null);
localStorage.setItem("retriver_latency", null);
localStorage.setItem("input_token_size_without_rag", null);
localStorage.setItem("output_token_size_without_rag", null);
localStorage.setItem("first_token_latency_without_rag", null);
localStorage.setItem("input_token_size_with_rag", null);
localStorage.setItem("output_token_size_with_rag", null);
localStorage.setItem("first_token_latency_with_rag", null);
setResultsLoading({
withoutRAG: true,
withRAG: true,
});
setIsPromptInputDisabled(true);
setResultsData((prevState) => ({
withoutRAG: [
...prevState.withoutRAG,
{
question: prompt,
answer: "",
sources: [],
},
],
withRAG: [
...prevState.withRAG,
{
question: prompt,
answer: "",
sources: [],
},
],
}));
const urlWithoutRAG = VITE_WITHOUT_RAG_BASE_URL + "/chat_stream";
const urlWithRAG = VITE_WITH_RAG_BASE_URL + "/chat_stream";
const payload = {
query: prompt,
knowledge_base_id: "default",
};
const eventSourceWithRAG = new SSE(urlWithRAG, {
headers: { "Content-Type": "application/json" },
payload: JSON.stringify(payload),
});
const eventSourceWithoutRAG = new SSE(urlWithoutRAG, {
headers: { "Content-Type": "application/json" },
payload: JSON.stringify(payload),
});
eventSourceWithRAG.addEventListener("readystatechange", function (e) {
if (e.readyState === 2) {
setResultsLoading((prevState) => ({
...prevState,
withRAG: false,
}));
}
});
eventSourceWithRAG.addEventListener("message", function (e) {
const currentMsgData = e.data;
if (currentMsgData !== "") {
const response = JSON.parse(e.data);
withRAGAnswer.current = response.streamed_text;
const sources = Object.values(response.document_metadata).filter((url) => url !== null);
setResultsData((prevState) => {
const currentPrompt = prevState.withRAG[prevState.withRAG.length - 1];
currentPrompt.answer = withRAGAnswer.current;
if (sources.length > 0) {
currentPrompt.sources = sources;
}
return {
...prevState,
withRAG: [...prevState.withRAG.slice(0, -1), currentPrompt],
};
});
// with rag metrics
const {
llm_e2e_latency,
llm_token_latency,
input_token_size,
output_token_size,
first_token_latency,
retriver_latency,
} = response;
localStorage.setItem("latency_with_rag", llm_e2e_latency);
localStorage.setItem("llm_token_latency_with_rag", llm_token_latency);
localStorage.setItem("input_token_size_with_rag", input_token_size);
localStorage.setItem("output_token_size_with_rag", output_token_size);
localStorage.setItem("first_token_latency_with_rag", first_token_latency);
localStorage.setItem("retriver_latency", retriver_latency);
}
});
eventSourceWithRAG.stream();
eventSourceWithoutRAG.addEventListener("readystatechange", function (e) {
if (e.readyState === 2) {
setResultsLoading((prevState) => ({
...prevState,
withoutRAG: false,
}));
}
});
eventSourceWithoutRAG.addEventListener("message", function (e) {
const currentMsgData = e.data;
if (currentMsgData !== "") {
const response = JSON.parse(e.data);
withoutRAGAnswer.current = response.streamed_text;
setResultsData((prevState) => {
const currentPrompt = prevState.withoutRAG[prevState.withoutRAG.length - 1];
currentPrompt.answer = withoutRAGAnswer.current;
return {
...prevState,
withoutRAG: [...prevState.withoutRAG.slice(0, -1), currentPrompt],
};
});
// without rag metrics
const {
llm_e2e_latency,
llm_token_latency,
input_token_size,
output_token_size,
first_token_latency,
} = response;
localStorage.setItem("latency_without_rag", llm_e2e_latency);
localStorage.setItem("llm_token_latency_without_rag", llm_token_latency);
localStorage.setItem("input_token_size_without_rag", input_token_size);
localStorage.setItem("output_token_size_without_rag", output_token_size);
localStorage.setItem("first_token_latency_without_rag", first_token_latency);
}
});
eventSourceWithoutRAG.stream();
};
const clearChatHistory = () => {
setResultsData(resultsDataInitialState);
};
const onUploadBtnClick = () => {
setShowUploadFileDrawer(true);
};
const onUploadFileDrawerClose = () => {
setShowUploadFileDrawer(false);
};
const onConfirmUpload = async (file) => {
onUploadFileDrawerClose();
if (file instanceof File) {
setIsUploading(true);
try {
const response = await ResultsService.uploadFile(file, file.name);
if (response.ok) {
setNotification({
severity: "success",
message: (
<span>
Your file <b>({file.name})</b> has been successfully uploaded
</span>
),
open: true,
});
} else {
throw response;
}
} catch (error) {
const { status, statusText } = error;
setNotification({
severity: "error",
message: (
<span>
{statusText} ({status}) when uploading your file <b>({file.name})</b>
</span>
),
open: true,
});
console.error(error);
} finally {
setIsUploading(false);
}
}
if (Array.isArray(file)) {
setIsUploading(true);
try {
const response = await ResultsService.uploadFileLink(file);
if (response.ok) {
setNotification({
severity: "success",
message: <span>Your {file.length} file links have been successfully uploaded</span>,
open: true,
});
} else {
throw response;
}
} catch (error) {
const { status, statusText } = error;
setNotification({
severity: "error",
message: (
<span>
{statusText} ({status}) when uploading your file link <b>({file})</b>
</span>
),
open: true,
});
console.error(error);
} finally {
setIsUploading(false);
}
}
};
const onNotificationClose = () => {
setNotification((prevState) => ({
...prevState,
open: false,
}));
};
return (
<main className="chat-page">
<PromptInput
isDisabled={isPromptInputDisabled}
isClearChatHistoryBtnDisabled={isClearChatHistoryBtnDisabled}
isUploading={isUploading}
onConfirmPrompt={onConfirmPrompt}
onUploadBtnClick={onUploadBtnClick}
onClearChatBtnClick={clearChatHistory}
/>
<ResultsCards data={resultsData} />
<UploadFileDrawer
onConfirmUpload={onConfirmUpload}
onUploadFileDrawerClose={onUploadFileDrawerClose}
show={showUploadFileDrawer}
/>
<UploadNotification notification={notification} onNotificationClose={onNotificationClose} />
</main>
);
};
export default ChatPage;

Some files were not shown because too many files have changed in this diff Show More