Refactor folder to support different vendors (#743)

Signed-off-by: Xinyao Wang <xinyao.wang@intel.com>
Signed-off-by: chensuyue <suyue.chen@intel.com>
This commit is contained in:
XinyaoWa
2024-09-10 23:27:19 +08:00
committed by GitHub
parent ba94e0130d
commit d73129cbf0
878 changed files with 915 additions and 1184 deletions

View File

@@ -0,0 +1,26 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
# Use node 20.11.1 as the base image
FROM node:20.11.1
# Update package manager and install Git
RUN apt-get update -y && apt-get install -y git
# Copy the front-end code repository
COPY svelte /home/user/svelte
# Set the working directory
WORKDIR /home/user/svelte
# Install front-end dependencies
RUN npm install
# Build the front-end application
RUN npm run build
# Expose the port of the front-end application
EXPOSE 5173
# Run the front-end application in preview mode
CMD ["npm", "run", "preview", "--", "--port", "5173", "--host", "0.0.0.0"]

View File

@@ -0,0 +1,21 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
# Use node 20.11.1 as the base image
FROM node:20.11.1 as vite-app
COPY react /usr/app/react
WORKDIR /usr/app/react
RUN ["npm", "install"]
RUN ["npm", "run", "build"]
FROM nginx:alpine
COPY --from=vite-app /usr/app/react/dist /usr/share/nginx/html
COPY ./react/env.sh /docker-entrypoint.d/env.sh
COPY ./react/nginx.conf /etc/nginx/conf.d/default.conf
RUN chmod +x /docker-entrypoint.d/env.sh

2
ChatQnA/ui/react/.env Normal file
View File

@@ -0,0 +1,2 @@
VITE_BACKEND_SERVICE_ENDPOINT=http://backend_address:8888/v1/chatqna
VITE_DATA_PREP_SERVICE_URL=http://backend_address:6007/v1/dataprep

View File

@@ -0,0 +1,2 @@
VITE_BACKEND_SERVICE_ENDPOINT=APP_BACKEND_SERVICE_ENDPOINT
VITE_DATA_PREP_SERVICE_URL=APP_DATA_PREP_SERVICE_URL

View File

@@ -0,0 +1,11 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
};

24
ChatQnA/ui/react/.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,32 @@
# ChatQnA Conversational UI
## 📸 Project Screenshots
![project-screenshot](../../assets/img/conversation_ui_init.png)
![project-screenshot](../../assets/img/conversation_ui_response.png)
![project-screenshot](../../assets/img/conversation_ui_upload.png)
## 🧐 Features
Here're some of the project's features:
- Start a Text ChatInitiate a text chat with the ability to input written conversations, where the dialogue content can also be customized based on uploaded files.
- Context Awareness: The AI assistant maintains the context of the conversation, understanding references to previous statements or questions. This allows for more natural and coherent exchanges.
- Upload File: The choice between uploading locally or copying a remote link. Chat according to uploaded knowledge base.
- Clear: Clear the record of the current dialog box without retaining the contents of the dialog box.
- Chat history: Historical chat records can still be retained after refreshing, making it easier for users to view the context.
- Conversational Chat : The application maintains a history of the conversation, allowing users to review previous messages and the AI to refer back to earlier points in the dialogue when necessary.
## 🛠️ Get it Running
1. Clone the repo.
2. cd command to the current folder.
3. Modify the required .env variables.
```
DOC_BASE_URL = ''
```
4. Execute `npm install` to install the corresponding dependencies.
5. Execute `npm run dev` in both environments

15
ChatQnA/ui/react/env.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
for i in $(env | grep APP_) #// Make sure to use the prefix MY_APP_ if you have any other prefix in env.production file variable name replace it with MY_APP_
do
key=$(echo $i | cut -d '=' -f 1)
value=$(echo $i | cut -d '=' -f 2-)
echo $key=$value
# sed All files
# find /usr/share/nginx/html -type f -exec sed -i "s|${key}|${value}|g" '{}' +
# sed JS and CSS only
find /usr/share/nginx/html -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|${key}|${value}|g" '{}' +
done

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>Conversations UI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

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,47 @@
{
"name": "ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@mantine/core": "^7.10.0",
"@mantine/hooks": "^7.10.0",
"@mantine/notifications": "^7.10.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@reduxjs/toolkit": "^2.2.5",
"@tabler/icons-react": "^3.5.0",
"axios": "^1.7.2",
"luxon": "^3.4.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.2"
},
"devDependencies": {
"@testing-library/react": "^16.0.0",
"@types/luxon": "^3.4.2",
"@types/node": "^20.12.12",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"jsdom": "^24.1.0",
"postcss": "^8.4.38",
"postcss-preset-mantine": "^1.15.0",
"postcss-simple-vars": "^7.0.1",
"sass": "1.64.2",
"typescript": "^5.2.2",
"vite": "^5.2.13",
"vitest": "^1.6.0"
}
}

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 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,42 @@
// 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;
}
/* ===== 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,34 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import "./App.scss"
import { MantineProvider } from "@mantine/core"
import '@mantine/notifications/styles.css';
import { SideNavbar, SidebarNavList } from "./components/sidebar/sidebar"
import { IconMessages } from "@tabler/icons-react"
import UserInfoModal from "./components/UserInfoModal/UserInfoModal"
import Conversation from "./components/Conversation/Conversation"
import { Notifications } from '@mantine/notifications';
const title = "Chat QnA"
const navList: SidebarNavList = [
{ icon: IconMessages, label: title }
]
function App() {
return (
<MantineProvider>
<Notifications position="top-right" />
<UserInfoModal />
<div className="layout-wrapper">
<SideNavbar navList={navList} />
<div className="content">
<Conversation title={title} />
</div>
</div>
</MantineProvider>
)
}
export default App

View File

@@ -0,0 +1,14 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { describe, expect, test } from "vitest";
import { getCurrentTimeStamp, uuidv4 } from "../common/util";
describe("unit tests", () => {
test("check UUID is of length 36", () => {
expect(uuidv4()).toHaveLength(36);
});
test("check TimeStamp generated is of unix", () => {
expect(getCurrentTimeStamp()).toBe(Math.floor(Date.now() / 1000));
});
});

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 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import axios from "axios";
//add iterceptors to add any request headers
export default axios;

View File

@@ -0,0 +1,12 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
export const getCurrentTimeStamp = () => {
return Math.floor(Date.now() / 1000);
};
export const uuidv4 = () => {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
(+c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (+c / 4)))).toString(16),
);
};

View File

@@ -0,0 +1,156 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { KeyboardEventHandler, SyntheticEvent, useEffect, useRef, useState } from 'react'
import styleClasses from "./conversation.module.scss"
import { ActionIcon, Group, Textarea, Title, rem } from '@mantine/core'
import { IconArrowRight, IconFilePlus, IconMessagePlus } from '@tabler/icons-react'
import { conversationSelector, doConversation, newConversation } from '../../redux/Conversation/ConversationSlice'
import { ConversationMessage } from '../Message/conversationMessage'
import { useAppDispatch, useAppSelector } from '../../redux/store'
import { Message, MessageRole } from '../../redux/Conversation/Conversation'
import { getCurrentTimeStamp } from '../../common/util'
import { useDisclosure } from '@mantine/hooks'
import DataSource from './DataSource'
import { ConversationSideBar } from './ConversationSideBar'
type ConversationProps = {
title:string
}
const Conversation = ({ title }: ConversationProps) => {
const [prompt, setPrompt] = useState<string>("")
const promptInputRef = useRef<HTMLTextAreaElement>(null)
const [fileUploadOpened, { open: openFileUpload, close: closeFileUpload }] = useDisclosure(false);
const { conversations, onGoingResult, selectedConversationId } = useAppSelector(conversationSelector)
const dispatch = useAppDispatch();
const selectedConversation = conversations.find(x=>x.conversationId===selectedConversationId)
const scrollViewport = useRef<HTMLDivElement>(null)
const toSend = "Enter"
const systemPrompt: Partial<Message> = {
role: MessageRole.System,
content: "You are helpful assistant",
};
const handleSubmit = () => {
const userPrompt: Message = {
role: MessageRole.User,
content: prompt,
time: getCurrentTimeStamp()
};
let messages: Partial<Message>[] = [];
if(selectedConversation){
messages = selectedConversation.Messages.map(message => {
return {role:message.role, content:message.content}
})
}
messages = [systemPrompt, ...messages]
doConversation({
conversationId: selectedConversationId,
userPrompt,
messages,
model: "Intel/neural-chat-7b-v3-3",
})
setPrompt("")
}
const scrollToBottom = () => {
scrollViewport.current!.scrollTo({ top: scrollViewport.current!.scrollHeight })
}
useEffect(() => {
scrollToBottom()
}, [onGoingResult, selectedConversation?.Messages])
const handleKeyDown: KeyboardEventHandler = (event) => {
if (!event.shiftKey && event.key === toSend) {
handleSubmit()
setTimeout(() => {
setPrompt("")
}, 1)
}
}
const handleNewConversation = () => {
dispatch(newConversation())
}
const handleChange = (event: SyntheticEvent) => {
event.preventDefault()
setPrompt((event.target as HTMLTextAreaElement).value)
}
return (
<div className={styleClasses.conversationWrapper}>
<ConversationSideBar title={title}/>
<div className={styleClasses.conversationContent}>
<div className={styleClasses.conversationContentMessages}>
<div className={styleClasses.conversationTitle}>
<Title order={3}>{selectedConversation?.title || ""} </Title>
<span className={styleClasses.spacer}></span>
<Group>
{selectedConversation && selectedConversation?.Messages.length > 0 && (
<ActionIcon onClick={handleNewConversation} disabled={onGoingResult != ""} size={32} variant="default">
<IconMessagePlus />
</ActionIcon>
)}
<ActionIcon onClick={openFileUpload} size={32} variant="default">
<IconFilePlus />
</ActionIcon>
</Group>
</div>
<div className={styleClasses.historyContainer} ref={scrollViewport}>
{!selectedConversation && (
<>
<div className="infoMessage">Start by asking a question</div>
<div className="infoMessage">You can also upload your Document by clicking on Document icon on top right corner</div>
</>
)}
{selectedConversation?.Messages.map((message) => {
return (<ConversationMessage key={`_ai`} date={message.time * 1000} human={message.role == MessageRole.User} message={message.content} />)
})
}
{onGoingResult && (
<ConversationMessage key={`_ai`} date={Date.now()} human={false} message={onGoingResult} />
)}
</div>
<div className={styleClasses.conversationActions}>
<Textarea
radius="xl"
size="md"
placeholder="Ask a question"
ref={promptInputRef}
onKeyDown={handleKeyDown}
onChange={handleChange}
value={prompt}
rightSectionWidth={42}
rightSection={
<ActionIcon onClick={handleSubmit} size={32} radius="xl" variant="filled">
<IconArrowRight style={{ width: rem(18), height: rem(18) }} stroke={1.5} />
</ActionIcon>
}
// {...props}
/>
</div>
</div>
</div>
<DataSource opened={fileUploadOpened} onClose={closeFileUpload} />
</div >
)
}
export default Conversation;

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { ScrollAreaAutosize, Title } from "@mantine/core"
import contextStyles from "../../styles/components/context.module.scss"
import { useAppDispatch, useAppSelector } from "../../redux/store"
import { conversationSelector, setSelectedConversationId } from "../../redux/Conversation/ConversationSlice"
// import { userSelector } from "../../redux/User/userSlice"
export interface ConversationContextProps {
title: string
}
export function ConversationSideBar({ title }: ConversationContextProps) {
const { conversations, selectedConversationId } = useAppSelector(conversationSelector)
// const user = useAppSelector(userSelector)
const dispatch = useAppDispatch()
const conversationList = conversations?.map((curr) => (
<div
className={contextStyles.contextListItem}
data-active={selectedConversationId === curr.conversationId || undefined}
onClick={(event) => {
event.preventDefault()
dispatch(setSelectedConversationId(curr.conversationId))
// dispatch(getConversationById({ user, conversationId: curr.conversationId }))
}}
key={curr.conversationId}
>
<div className={contextStyles.contextItemName} title={curr.title}>{curr.title}</div>
</div>
))
return (
<div className={contextStyles.contextWrapper}>
<Title order={3} className={contextStyles.contextTitle}>
{title}
</Title>
<ScrollAreaAutosize type="hover" scrollHideDelay={0}>
<div className={contextStyles.contextList}>{conversationList}</div>
</ScrollAreaAutosize>
</div>
)
}

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { Button, Container, Drawer, FileInput, Text, TextInput } from '@mantine/core'
import { SyntheticEvent, useState } from 'react'
import { useAppDispatch } from '../../redux/store'
import { submitDataSourceURL, uploadFile } from '../../redux/Conversation/ConversationSlice'
type Props = {
opened: boolean
onClose: () => void
}
export default function DataSource({ opened, onClose }: Props) {
const title = "Data Source"
const [file, setFile] = useState<File | null>();
const [isFile, setIsFile] = useState<boolean>(true);
const [url, setURL] = useState<string>("");
const dispatch = useAppDispatch()
const handleFileUpload = () => {
if (file)
dispatch(uploadFile({ file }))
}
const handleChange = (event: SyntheticEvent) => {
event.preventDefault()
setURL((event.target as HTMLTextAreaElement).value)
}
const handleSubmit = () => {
dispatch(submitDataSourceURL({ link_list: url.split(";") }))
}
return (
<Drawer title={title} position="right" opened={opened} onClose={onClose} withOverlay={false}>
<Text size="sm">
Please upload your local file or paste a remote file link, and Chat will respond based on the content of the uploaded file.
</Text>
<Container styles={{
root: { paddingTop: '40px', display:'flex', flexDirection:'column', alignItems:'center' }
}}>
<Button.Group styles={{ group:{alignSelf:'center'}}} >
<Button variant={isFile ? 'filled' : 'default'} onClick={() => setIsFile(true)}>Upload FIle</Button>
<Button variant={!isFile ? 'filled' : 'default'} onClick={() => setIsFile(false)}>Use Link</Button>
</Button.Group>
</Container>
<Container styles={{root:{paddingTop: '40px'}}}>
<div>
{isFile ? (
<>
<FileInput value={file} onChange={setFile} placeholder="Choose File" description={"choose a file to upload for RAG"}/>
<Button style={{marginTop:'5px'}} onClick={handleFileUpload} disabled={!file}>Upload</Button>
</>
) : (
<>
<TextInput value={url} onChange={handleChange} placeholder='URL' description={"Use semicolons (;) to separate multiple URLs."} />
<Button style={{ marginTop: '5px' }} onClick={handleSubmit} disabled={!url}>Upload</Button>
</>
)}
</div>
</Container>
</Drawer>
)
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
@import "../../styles/styles";
.spacer {
flex: 1 1 auto;
}
.conversationWrapper {
@include flex(row, nowrap, flex-start, flex-start);
flex: 1 1 auto;
height: 100%;
& > * {
height: 100%;
}
.conversationContent {
flex: 1 1 auto;
position: relative;
.conversationContentMessages {
@include absolutes;
// @include flex(column, nowrap, flex-start, flex-start);
display: grid;
grid-template-areas:
"header"
"messages"
"inputs";
grid-template-columns: auto;
grid-template-rows: 60px auto 100px;
.conversationTitle {
grid-area: header;
@include flex(row, nowrap, center, flex-start);
height: 60px;
padding: 8px 24px;
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
.historyContainer {
grid-area: messages;
overflow: auto;
width: 100%;
padding: 16px 32px;
& > * {
width: 100%;
}
}
.conversationActions {
// padding: --var()
grid-area: inputs;
padding: 18px;
border-top: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
}
.conversationSplash {
@include absolutes;
@include flex(column, nowrap, center, center);
font-size: 32px;
}
}
}

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
@import "../../styles/styles";
.conversationMessage {
@include flex(column, nowrap, flex-start, flex-start);
margin-top: 16px;
padding: 0 32px;
width: 100%;
& > * {
width: 100%;
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { IconAi, IconUser } from "@tabler/icons-react"
import style from "./conversationMessage.module.scss"
import { Group, Text } from "@mantine/core"
import { DateTime } from "luxon"
export interface ConversationMessageProps {
message: string
human: boolean
date: number
}
export function ConversationMessage({ human, message, date }: ConversationMessageProps) {
const dateFormat = () => {
// console.log(date)
// console.log(new Date(date))
return DateTime.fromJSDate(new Date(date)).toLocaleString(DateTime.DATETIME_MED)
}
return (
<div className={style.conversationMessage}>
<Group>
{/* <Avatar
src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-1.png"
alt="Jacob Warnhalter"
radius="xl"
/> */}
{human && <IconUser />}
{!human && <IconAi />}
<div>
<Text size="sm">
{human && "You"} {!human && "Assistant"}
</Text>
<Text size="xs" c="dimmed">
{dateFormat()}
</Text>
</div>
</Group>
<Text pl={54} pt="sm" size="sm">
{message}
</Text>
{/* <div className={style.header}>
{human && <IconUser />}
{!human && <IconAi />}
</div>
<div className={style.message}>{message}</div> */}
</div>
)
}

View File

@@ -0,0 +1,48 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { SyntheticEvent, useEffect, useState } from 'react'
import { useDisclosure } from '@mantine/hooks';
import { TextInput, Button, Modal } from '@mantine/core';
import { useDispatch, useSelector } from 'react-redux';
import { userSelector, setUser } from '../../redux/User/userSlice';
const UserInfoModal = () => {
const [opened, { open, close }] = useDisclosure(false);
const { name } = useSelector(userSelector);
const [username, setUsername] = useState(name || "");
const dispatch = useDispatch();
const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault()
if(username){
close();
dispatch(setUser(username));
setUsername("")
}
}
useEffect(() => {
if (!name) {
open();
}
}, [name])
return (
<>
<Modal opened={opened} withCloseButton={false} onClose={()=>handleSubmit} title="Tell us who you are ?" centered>
<>
<form onSubmit={handleSubmit} >
<TextInput label="Username" placeholder="Username" onChange={(event)=> setUsername(event?.currentTarget.value)} value={username} data-autofocus />
<Button fullWidth onClick={handleSubmit} mt="md">
Submit
</Button>
</form>
</>
</Modal>
</>
)
}
export default UserInfoModal

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,70 @@
// 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, IconLogout } from "@tabler/icons-react"
import classes from "./sidebar.module.scss"
import OpeaLogo from "../../assets/opea-icon-black.svg"
import { useAppDispatch } from "../../redux/store"
import { removeUser } from "../../redux/User/userSlice"
import { logout } from "../../redux/Conversation/ConversationSlice"
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 dispatch =useAppDispatch()
const [active, setActive] = useState(0)
const handleLogout = () => {
dispatch(logout())
dispatch(removeUser())
}
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>
<Stack justify="center" gap={0}>
<NavbarLink icon={IconLogout} label="Logout" onClick={handleLogout} />
</Stack>
</nav>
)
}

View File

@@ -0,0 +1,5 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
export const DATA_PREP_URL = import.meta.env.VITE_DATA_PREP_SERVICE_URL;
export const CHAT_QNA_URL = import.meta.env.VITE_BACKEND_SERVICE_ENDPOINT;

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,17 @@
// 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.tsx"
import "./index.scss"
import { Provider } from 'react-redux'
import { store } from "./redux/store.ts"
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Provider store={store} >
<App />
</Provider>
</React.StrictMode>
)

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
export type ConversationRequest = {
conversationId: string;
userPrompt: Message;
messages: Partial<Message>[];
model: string;
};
export enum MessageRole {
Assistant = "assistant",
User = "user",
System = "system",
}
export interface Message {
role: MessageRole;
content: string;
time: number;
}
export interface Conversation {
conversationId: string;
title?: string;
Messages: Message[];
}
export interface ConversationReducer {
selectedConversationId: string;
conversations: Conversation[];
onGoingResult: string;
}

View File

@@ -0,0 +1,221 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { RootState, store } from "../store";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import { Message, MessageRole, ConversationReducer, ConversationRequest } from "./Conversation";
import { getCurrentTimeStamp, uuidv4 } from "../../common/util";
import { createAsyncThunkWrapper } from "../thunkUtil";
import client from "../../common/client";
import { notifications } from "@mantine/notifications";
import { CHAT_QNA_URL, DATA_PREP_URL } from "../../config";
const initialState: ConversationReducer = {
conversations: [],
selectedConversationId: "",
onGoingResult: "",
};
export const ConversationSlice = createSlice({
name: "Conversation",
initialState,
reducers: {
logout: (state) => {
state.conversations = [];
state.selectedConversationId = "";
state.onGoingResult = "";
},
setOnGoingResult: (state, action: PayloadAction<string>) => {
state.onGoingResult = action.payload;
},
addMessageToMessages: (state, action: PayloadAction<Message>) => {
const selectedConversation = state.conversations.find((x) => x.conversationId === state.selectedConversationId);
selectedConversation?.Messages?.push(action.payload);
},
newConversation: (state) => {
(state.selectedConversationId = ""), (state.onGoingResult = "");
},
createNewConversation: (state, action: PayloadAction<{ title: string; id: string; message: Message }>) => {
state.conversations.push({
title: action.payload.title,
conversationId: action.payload.id,
Messages: [action.payload.message],
});
},
setSelectedConversationId: (state, action: PayloadAction<string>) => {
state.selectedConversationId = action.payload;
},
},
extraReducers(builder) {
builder.addCase(uploadFile.fulfilled, () => {
notifications.update({
id: "upload-file",
message: "File Uploaded Successfully",
loading: false,
autoClose: 3000,
});
}),
builder.addCase(uploadFile.rejected, () => {
notifications.update({
color: "red",
id: "upload-file",
message: "Failed to Upload file",
loading: false,
});
});
builder.addCase(submitDataSourceURL.fulfilled, () => {
notifications.show({
message: "Submitted Successfully",
});
});
builder.addCase(submitDataSourceURL.rejected, () => {
notifications.show({
color: "red",
message: "Submit Failed",
});
});
},
});
export const submitDataSourceURL = createAsyncThunkWrapper(
"conversation/submitDataSourceURL",
async ({ link_list }: { link_list: string[] }, {}) => {
const body = new FormData();
body.append("link_list", JSON.stringify(link_list));
const response = await client.post(DATA_PREP_URL, body);
return response.data;
},
);
export const uploadFile = createAsyncThunkWrapper("conversation/uploadFile", async ({ file }: { file: File }, {}) => {
const body = new FormData();
body.append("files", file);
notifications.show({
id: "upload-file",
message: "uploading File",
loading: true,
});
const response = await client.post(DATA_PREP_URL, body);
return response.data;
});
export const {
logout,
setOnGoingResult,
newConversation,
addMessageToMessages,
setSelectedConversationId,
createNewConversation,
} = ConversationSlice.actions;
export const conversationSelector = (state: RootState) => state.conversationReducer;
export default ConversationSlice.reducer;
export const doConversation = (conversationRequest: ConversationRequest) => {
const { conversationId, userPrompt, messages, model } = conversationRequest;
if (!conversationId) {
//newConversation
const id = uuidv4();
store.dispatch(
createNewConversation({
title: userPrompt.content,
id,
message: userPrompt,
}),
);
store.dispatch(setSelectedConversationId(id));
} else {
store.dispatch(addMessageToMessages(userPrompt));
}
const userPromptWithoutTime = {
role: userPrompt.role,
content: userPrompt.content,
};
const body = {
messages: [...messages, userPromptWithoutTime],
model,
};
// let conversation: Conversation;
let result = "";
try {
fetchEventSource(CHAT_QNA_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
openWhenHidden: true,
async onopen(response) {
if (response.ok) {
return;
} else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
const e = await response.json();
console.log(e);
throw Error(e.error.message);
} else {
console.log("error", response);
}
},
onmessage(msg) {
if (msg?.data != "[DONE]") {
try {
const match = msg.data.match(/b'([^']*)'/);
if (match && match[1] != "</s>") {
const extractedText = match[1];
// Check for the presence of \x hexadecimal
if (extractedText.includes("\\x")) {
// Decode Chinese (or other non-ASCII characters)
const decodedText = decodeEscapedBytes(extractedText);
result += decodedText;
} else {
result += extractedText;
}
} else if (!match) {
// Return data without pattern
result += msg?.data;
}
// Store back result if it is not null
if (result) {
store.dispatch(setOnGoingResult(result));
}
} catch (e) {
console.log("something wrong in msg", e);
throw e;
}
}
},
onerror(err) {
console.log("error", err);
store.dispatch(setOnGoingResult(""));
//notify here
throw err;
//handle error
},
onclose() {
//handle close
store.dispatch(setOnGoingResult(""));
store.dispatch(
addMessageToMessages({
role: MessageRole.Assistant,
content: result,
time: getCurrentTimeStamp(),
}),
);
},
});
} catch (err) {
console.log(err);
}
};
// decode \x hexadecimal encoding
function decodeEscapedBytes(str: string): string {
// Convert the byte portion separated by \x into a byte array and decode it into a UTF-8 string
const byteArray: number[] = str
.split("\\x")
.slice(1)
.map((byte: string) => parseInt(byte, 16));
return new TextDecoder("utf-8").decode(new Uint8Array(byteArray));
}

View File

@@ -0,0 +1,6 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
export interface User {
name: string | null;
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "../store";
import { User } from "./user";
const initialState: User = {
name: localStorage.getItem("user"),
};
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUser: (state, action: PayloadAction<string>) => {
state.name = action.payload;
},
removeUser: (state) => {
state.name = null;
},
},
});
export const { setUser, removeUser } = userSlice.actions;
export const userSelector = (state: RootState) => state.userReducer;
export default userSlice.reducer;

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import userReducer from "./User/userSlice";
import conversationReducer from "./Conversation/ConversationSlice";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
export const store = configureStore({
reducer: combineReducers({
userReducer,
conversationReducer,
}),
devTools: import.meta.env.PROD || true,
preloadedState: loadFromLocalStorage(),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
function saveToLocalStorage(state: ReturnType<typeof store.getState>) {
try {
const serialState = JSON.stringify(state);
localStorage.setItem("reduxStore", serialState);
} catch (e) {
console.warn(e);
}
}
function loadFromLocalStorage() {
try {
const serialisedState = localStorage.getItem("reduxStore");
if (serialisedState === null) return undefined;
return JSON.parse(serialisedState);
} catch (e) {
console.warn(e);
return undefined;
}
}
store.subscribe(() => saveToLocalStorage(store.getState()));
console.log(store);
export default store;
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { createAsyncThunk, AsyncThunkPayloadCreator, AsyncThunk } from "@reduxjs/toolkit";
interface ThunkAPIConfig {}
export const createAsyncThunkWrapper = <Returned, ThunkArg = any>(
type: string,
thunk: AsyncThunkPayloadCreator<Returned, ThunkArg>, // <-- very unsure of this - have tried many things here
): AsyncThunk<Returned, ThunkArg, ThunkAPIConfig> => {
return createAsyncThunk<Returned, ThunkArg, ThunkAPIConfig>(
type,
// @ts-ignore
async (arg, thunkAPI) => {
try {
// do some stuff here that happens on every action
return await thunk(arg, thunkAPI);
} catch (err) {
// do some stuff here that happens on every error
return thunkAPI.rejectWithValue(err);
}
},
);
};

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
@import "../layout/flex";
@mixin sidebar {
@include flex(column, nowrap, flex-start, flex-start);
}

View File

@@ -0,0 +1,5 @@
@mixin textWrapEllipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}

View File

@@ -0,0 +1,67 @@
@import "../layout/flex";
@import "../components/content.scss";
.contextWrapper {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
width: 180px;
overflow-y: hidden;
overflow-x: hidden;
// overflow-y: auto;
.contextTitle {
position: sticky;
top: 0;
font-family:
Greycliff CF,
var(--mantine-font-family);
margin-bottom: var(--mantine-spacing-xl);
background-color: var(--mantine-color-body);
padding: var(--mantine-spacing-md);
padding-top: 18px;
width: 100%;
height: 60px;
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.contextList {
height: 90vh;
// display: flex();
.contextListItem {
display: block;
text-decoration: none;
border-top-right-radius: var(--mantine-radius-md);
border-bottom-right-radius: var(--mantine-radius-md);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
padding: 0 var(--mantine-spacing-md);
font-size: var(--mantine-font-size-sm);
margin-right: var(--mantine-spacing-md);
font-weight: 500;
height: 44px;
width: 100%;
line-height: 44px;
cursor: pointer;
.contextItemName {
flex: 1 1 auto;
width: 130px;
@include textWrapEllipsis;
}
&:hover {
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
color: light-dark(var(--mantine-color-dark), var(--mantine-color-light));
}
&[data-active] {
&,
&:hover {
border-left-color: var(--mantine-color-blue-filled);
background-color: var(--mantine-color-blue-filled);
color: var(--mantine-color-white);
}
}
}
}
}

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
ChatQnA/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,23 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": 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,27 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/styles/styles.scss";`,
},
},
},
plugins: [react()],
server: {
port: 80,
},
test: {
globals: true,
environment: "jsdom",
},
define: {
"import.meta.env": process.env,
},
});

View File

@@ -0,0 +1,10 @@
[*]
indent_style = tab
[package.json]
indent_style = space
indent_size = 2
[*.md]
indent_style = space
indent_size = 2

7
ChatQnA/ui/svelte/.env Normal file
View File

@@ -0,0 +1,7 @@
CHAT_BASE_URL = '/v1/chatqna'
UPLOAD_FILE_BASE_URL = '/v1/dataprep'
GET_FILE = '/v1/dataprep/get_file'
DELETE_FILE = '/v1/dataprep/delete_file'

View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

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,
parser: "@typescript-eslint/parser",
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
plugins: ["svelte3", "@typescript-eslint", "neverthrow"],
ignorePatterns: ["*.cjs"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
env: {
browser: true,
es2017: true,
node: true,
},
};

View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -0,0 +1 @@
{"pluginSearchDirs": ["."], "overrides": [{"files": "*.svelte", "options": {"parser": "svelte"}}]}

View File

@@ -0,0 +1,42 @@
# ChatQnA Customized UI
## 📸 Project Screenshots
![project-screenshot](../../assets/img/chat_ui_init.png)
![project-screenshot](../../assets/img/chat_ui_response.png)
![project-screenshot](../../assets/img/chat_ui_upload.png)
## 🧐 Features
Here're some of the project's features:
- Start a Text ChatInitiate a text chat with the ability to input written conversations, where the dialogue content can also be customized based on uploaded files.
- Clear: Clear the record of the current dialog box without retaining the contents of the dialog box.
- Chat history: Historical chat records can still be retained after refreshing, making it easier for users to view the context.
- Scroll to Bottom / Top: The chat automatically slides to the bottom. Users can also click the top icon to slide to the top of the chat record.
- End to End Time: Shows the time spent on the current conversation.
- Upload File: The choice between uploading locally or copying a remote link. Chat according to uploaded knowledge base.
- Delete File: Delete a certain uploaded file.
## 🛠️ Get it Running
1. Clone the repo.
2. cd command to the current folder.
3. Modify the required .env variables.
```
CHAT_BASE_URL = ''
UPLOAD_FILE_BASE_URL = ''
GET_FILE = ''
DELETE_FILE = ''
```
4. Execute `npm install` to install the corresponding dependencies.
5. Execute `npm run dev` in both environments

View File

@@ -0,0 +1,61 @@
{
"name": "sveltekit-auth-example",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@playwright/test": "^1.33.0",
"@sveltejs/adapter-auto": "1.0.0-next.75",
"@sveltejs/kit": "^1.30.4",
"@tailwindcss/typography": "0.5.7",
"@types/debug": "4.1.7",
"@types/node": "^20.12.13",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"autoprefixer": "^10.4.7",
"daisyui": "3.5.1",
"date-picker-svelte": "^2.6.0",
"debug": "4.3.4",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-neverthrow": "1.1.4",
"eslint-plugin-svelte3": "^4.0.0",
"postcss": "^8.4.31",
"postcss-load-config": "^4.0.1",
"postcss-preset-env": "^8.3.2",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.7.0",
"prettier-plugin-tailwindcss": "^0.3.0",
"svelte": "^3.59.1",
"svelte-check": "^2.7.1",
"svelte-fa": "3.0.3",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.1.5",
"tslib": "^2.3.1",
"typescript": "^4.7.4",
"vite": "^4.5.2"
},
"type": "module",
"dependencies": {
"date-fns": "^2.30.0",
"driver.js": "^1.3.0",
"flowbite-svelte": "^0.38.5",
"flowbite-svelte-icons": "^1.4.0",
"fuse.js": "^6.6.2",
"lodash": "^4.17.21",
"playwright": "^1.44.0",
"ramda": "^0.29.0",
"sse.js": "^0.6.1",
"svelte-notifications": "^0.9.98",
"svrollbar": "^0.12.0"
}
}

View File

@@ -0,0 +1,87 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
import { defineConfig, devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [["html", { open: "never" }]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:5173",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
// {
// name: "chromium",
// use: { ...devices["Desktop Chrome"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { channel: 'msedge' },
// },
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
// {
// name: 'Google Chrome',
// use: { channel: 'chrome' },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
});

View File

@@ -0,0 +1,27 @@
// 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.
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer,
],
};
module.exports = config;

19
ChatQnA/ui/svelte/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
// 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.
// See: https://kit.svelte.dev/docs/types#app
// import { Result} from "neverthrow";
interface Window {
deviceType: string;
}

View File

@@ -0,0 +1,28 @@
<!--
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 lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div class="h-full w-full">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,86 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
height: 100%;
}
.btn {
@apply flex-nowrap;
}
a.btn {
@apply no-underline;
}
.input {
@apply text-base;
}
.bg-dark-blue {
background-color: #004a86;
}
.bg-light-blue {
background-color: #0068b5;
}
.bg-turquoise {
background-color: #00a3f6;
}
.bg-header {
background-color: #ffffff;
}
.bg-button {
background-color: #0068b5;
}
.bg-title {
background-color: #f7f7f7;
}
.text-header {
color: #0068b5;
}
.text-button {
color: #252e47;
}
.text-title-color {
color: rgb(38,38,38);
}
.font-intel {
font-family: "intel-clear","tahoma",Helvetica,"helvetica",Arial,sans-serif;
}
.font-title-intel {
font-family: "intel-one","intel-clear",Helvetica,Arial,sans-serif;
}
.bg-footer {
background-color: #e7e7e7;
}
.bg-light-green {
background-color: #d7f3a1;
}
.bg-purple {
background-color: #653171;
}
.bg-dark-blue {
background-color: #224678;
}
.border-input-color {
border-color: #605e5c;
}
.w-12\/12 {
width: 100%
}

View File

@@ -0,0 +1,36 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script>
export let className = "w-16 h-16";
</script>
<svg
t="1713775351763"
class="icon {className}"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="12834"
width="32"
height="32"
><path
d="M192 160h253.728a64 64 0 0 1 53.312 28.576l25.824 38.848A64 64 0 0 0 578.176 256H832a64 64 0 0 1 64 64v480a64 64 0 0 1-64 64H192a64 64 0 0 1-64-64V224a64 64 0 0 1 64-64z"
fill="#1989FA"
p-id="12835"
/><path
d="M192 352h640a64 64 0 0 1 64 64v384a64 64 0 0 1-64 64H192a64 64 0 0 1-64-64V416a64 64 0 0 1 64-64z"
fill="#8BC4FC"
p-id="12836"
/><path
d="M422.624 768a70.656 70.656 0 0 1-49.888-120.672l30.112-30.112a7.488 7.488 0 0 1 5.28-2.208c5.152 1.28 7.104 3.616 7.552 6.4a93.76 93.76 0 0 0 5.472 22.144 7.68 7.68 0 0 1-1.696 8.032l-21.312 21.312a34.912 34.912 0 0 0 0 48.928 34.24 34.24 0 0 0 24.352 10.08 34.944 34.944 0 0 0 24.544-10.08l89.312-89.376a34.688 34.688 0 0 0 0-48.896 7.488 7.488 0 0 1 0-10.56l15.008-15.04a7.488 7.488 0 0 1 5.344-2.208 7.616 7.616 0 0 1 5.312 2.144 70.688 70.688 0 0 1 0 100.032l-89.312 89.28a70.4 70.4 0 0 1-49.76 20.736z"
fill="#FFFFFF"
p-id="12837"
/><path
d="M467.168 660.128a7.456 7.456 0 0 1-5.12-2.112 70.72 70.72 0 0 1 0-100l89.312-89.312a70.656 70.656 0 1 1 99.904 99.968l-30.112 30.112a7.488 7.488 0 0 1-5.248 2.208c-5.184-1.28-7.136-3.616-7.552-6.4a97.504 97.504 0 0 0-5.504-22.176 7.648 7.648 0 0 1 1.696-8l21.312-21.312a34.848 34.848 0 0 0 0-48.928 34.24 34.24 0 0 0-24.352-10.08 34.944 34.944 0 0 0-24.544 10.08l-89.312 89.344a34.752 34.752 0 0 0 0 48.896 7.584 7.584 0 0 1 0 10.688l-14.848 14.912a7.52 7.52 0 0 1-5.248 2.176z"
fill="#FFFFFF"
p-id="12838"
/></svg
>

View File

@@ -0,0 +1,30 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script>
export let className = "w-16 h-16";
</script>
<svg
t="1711440565760"
class="icon {className}"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="23643"
><path
d="M913.29536 941.04064c0.0256 24.82688-16.54784 44.96384-37.0176 44.98432l-708.23936 0.6912c-20.46464 0.02048-37.07904-20.08576-37.10464-44.91264l-0.83968-859.02848c-0.0256-24.82688 16.54784-44.96384 37.0176-44.98432l521.10848-0.50688 224.39424 210.50368 0.68096 693.25312z"
fill="#E6E4E2"
p-id="23644"
/><path
d="M913.29536 253.26592l-189.11744 0.18432c-20.46464 0.02048-37.07904-20.08576-37.10464-44.91264l-0.16384-165.77024 226.38592 210.49856z"
fill="#C4BCB1"
p-id="23645"
/><path
d="M720.72192 396.84096a22.54848 22.54848 0 0 1-22.54848 22.54848H326.13376a22.54848 22.54848 0 0 1 0-45.09696h372.0448a22.54848 22.54848 0 0 1 22.54336 22.54848zM720.72192 565.95456a22.54848 22.54848 0 0 1-22.54848 22.54848H326.13376a22.54848 22.54848 0 0 1 0-45.09696h372.0448a22.54848 22.54848 0 0 1 22.54336 22.54848zM720.72192 746.33728a22.54848 22.54848 0 0 1-22.54848 22.54848H326.13376a22.54848 22.54848 0 0 1 0-45.09696h372.0448a22.54848 22.54848 0 0 1 22.54336 22.54848z"
fill="#C4BCB1"
p-id="23646"
/></svg
>

View File

@@ -0,0 +1,30 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script>
export let className = "w-16 h-16";
</script>
<svg
t="1711440048470"
class="icon {className}"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7455"
><path
d="M848.8576 199.1936H415.7568c0-26.5728-21.5424-48.128-48.128-48.128H175.1424c-26.5728 0-48.128 21.5424-48.128 48.128V343.5648c0 26.5984 21.5424 48.1408 48.128 48.1408h673.728c26.5728 0 48.128-21.5424 48.128-48.1408v-96.2432c-0.0128-26.5856-21.5552-48.128-48.1408-48.128z"
fill="#CCA352"
p-id="7456"
/><path
d="M800.7424 247.3088H223.2576c-26.5728 0-48.128 21.5424-48.128 48.128v48.128c0 26.5984 21.5424 48.1408 48.128 48.1408h577.472c26.5728 0 48.128-21.5424 48.128-48.1408v-48.128c0-26.5728-21.5424-48.128-48.1152-48.128z"
fill="#FFFFFF"
p-id="7457"
/><path
d="M848.8576 295.4368H175.1424c-26.5728 0-48.128 21.5424-48.128 48.128v481.2544c0 26.5472 21.5424 48.128 48.128 48.128h673.728c26.5728 0 48.128-21.568 48.128-48.128V343.552c-0.0128-26.5728-21.5552-48.1152-48.1408-48.1152z"
fill="#FFCC66"
p-id="7458"
/></svg
>

View File

@@ -0,0 +1,30 @@
<!--
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.
-->
<script lang="ts">
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<svg
class="absolute top-0 right-0 hover:opacity-70"
on:click={() => {
dispatch('DeleteAvatar') }}
viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="20">
<path d="M512 832c-176.448 0-320-143.552-320-320S335.552 192 512 192s320 143.552 320 320-143.552 320-320 320m0-704C300.256 128 128 300.256 128 512s172.256 384 384 384 384-172.256 384-384S723.744 128 512 128" fill="#bbbbbb"></path><path d="M649.824 361.376a31.968 31.968 0 0 0-45.248 0L505.6 460.352l-98.976-98.976a31.968 31.968 0 1 0-45.248 45.248l98.976 98.976-98.976 98.976a32 32 0 0 0 45.248 45.248l98.976-98.976 98.976 98.976a31.904 31.904 0 0 0 45.248 0 31.968 31.968 0 0 0 0-45.248L550.848 505.6l98.976-98.976a31.968 31.968 0 0 0 0-45.248" fill="#bbbbbb"></path>
</svg>

View File

@@ -0,0 +1,44 @@
<!--
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.
-->
<!-- <svg
width="35"
height="35"
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_16_93)">
<rect x="0.5" y="0.238312" width="47" height="47" fill="#0068B5" />
<path
d="M39.51 0.238312H8.49C4.0955 0.238312 0.5 3.83381 0.5 8.22831V39.2483C0.5 43.6428 4.0955 47.2383 8.49 47.2383H39.51C43.9045 47.2383 47.5 43.6428 47.5 39.2483V8.22831C47.5 3.83381 43.9045 0.238312 39.51 0.238312ZM44.915 39.2483C44.915 42.2328 42.4945 44.6533 39.51 44.6533H8.49C5.5055 44.6533 3.085 42.2328 3.085 39.2483V8.22831C3.085 5.24381 5.5055 2.82331 8.49 2.82331H39.51C42.4945 2.82331 44.915 5.24381 44.915 8.22831V39.2483Z"
fill="#0068B5"
/>
<path
d="M9.52393 21.3178H11.7094L11.7094 29.3548H9.52393V21.3178ZM20.3574 22.2108C20.1694 21.9523 19.8874 21.7408 19.4879 21.5763C19.1119 21.4118 18.6889 21.3178 18.2424 21.3178C17.2084 21.3178 16.3389 21.7643 15.6574 22.6338V21.4823H13.7304V29.3078H15.7984V25.7593C15.7984 24.8898 15.8454 24.2788 15.9629 23.9498C16.0569 23.6208 16.2684 23.3623 16.5504 23.1743C16.8324 22.9863 17.1614 22.8688 17.5139 22.8688C17.7959 22.8688 18.0309 22.9393 18.2424 23.0803C18.4304 23.2213 18.5949 23.4093 18.6654 23.6678C18.7594 23.9263 18.8064 24.4668 18.8064 25.3128V29.3078H20.8744V24.4433C20.8744 23.8323 20.8274 23.3858 20.7569 23.0568C20.6864 22.7513 20.5689 22.4693 20.3574 22.2108ZM25.7389 27.8038C25.5979 27.8038 25.4804 27.7803 25.3864 27.7098C25.2924 27.6393 25.2219 27.5453 25.1984 27.4513C25.1749 27.3573 25.1514 26.9813 25.1514 26.3233V23.1508H26.5614V21.5058H25.1514V18.7563L23.0834 19.9548V21.5058V23.1508V26.5583C23.0834 27.2868 23.1069 27.7803 23.1539 28.0153C23.2009 28.3443 23.2949 28.6263 23.4359 28.8143C23.5769 29.0023 23.7884 29.1668 24.0939 29.3078C24.3994 29.4253 24.7284 29.4958 25.1044 29.4958C25.7154 29.4958 26.2559 29.4018 26.7494 29.1903L26.5614 27.5923C26.2089 27.7333 25.9269 27.8038 25.7389 27.8038ZM33.7524 22.4928C33.0709 21.7173 32.1544 21.3413 31.0029 21.3413C29.9689 21.3413 29.0994 21.7173 28.4414 22.4458C27.7599 23.1743 27.4309 24.1848 27.4309 25.5008C27.4309 26.5818 27.6894 27.4748 28.2064 28.2033C28.8644 29.0963 29.8749 29.5428 31.2379 29.5428C32.1074 29.5428 32.8124 29.3548 33.3764 28.9553C33.9404 28.5558 34.3634 27.9918 34.6219 27.2163L32.5539 26.8638C32.4364 27.2633 32.2719 27.5453 32.0604 27.7098C31.8489 27.8743 31.5669 27.9683 31.2379 27.9683C30.7679 27.9683 30.3684 27.8038 30.0394 27.4513C29.7104 27.0988 29.5459 26.6288 29.5459 26.0178H34.7394C34.7394 24.4433 34.4339 23.2448 33.7524 22.4928ZM29.5694 24.7488C29.5694 24.1848 29.7104 23.7383 29.9924 23.4093C30.2979 23.0803 30.6504 22.9158 31.1204 22.9158C31.5434 22.9158 31.8959 23.0803 32.2014 23.3858C32.5069 23.6913 32.6479 24.1613 32.6714 24.7488H29.5694ZM36.4079 18.5448H38.4759V29.3548H36.4079V18.5448Z"
fill="white"
/>
<path
d="M9.52393 18.5448H11.7094L11.7094 20.5654H9.52393V18.5448ZM39.2058 53.1889C59.7131 70.5741 37.9465 53.1367 37.547 52.9722C60.5267 71.228 41.5876 53.1889 41.1411 53.1889C40.1071 53.1889 54.2638 57.2959 53.5823 58.1654L44.3775 54.0099L42.8 56.0803L44.9335 56.0763L43.617 55.1029L49.2888 57.4321C49.2888 56.5626 69.0838 68.5409 41.665 52.9722C67.9574 69.2353 48.7539 58.3534 49.0359 58.1654C49.3179 57.9774 72.2331 77.3305 48.0529 59.0448C73.8431 77.373 40.6532 52.2185 40.8647 52.3595C64.5928 69.3279 66.2469 69.734 44.0477 53.3531C68.4587 70.8049 45.1808 54.42 45.1808 55.266L49.6436 57.6191L50.8176 56.2254L46.645 54.7317C46.645 54.1207 47.0599 55.184 46.9894 54.855C46.9189 54.5495 63.0924 72.6928 39.2058 53.1889ZM45.3834 56.0442C45.2424 56.0442 60.49 64.1373 43.0764 53.1889C59.6606 67.1938 58.0346 62.1756 40.8647 50.7007C58.8678 64.6804 43.7296 53.3942 43.7296 52.7362L43.617 55.1029L43.3529 52.3595L44.7353 53.7418L43.0764 53.1889L44.244 54.855L46.1176 55.6771L42.8 57.336L45.5647 53.1889L41.9705 49.5948L46.1176 55.1029L46.3941 55.6771C46.3941 56.4056 44.3403 54.3363 44.3873 54.5713C65.2775 66.4664 68.0297 70.4029 45.348 56.6803C69.965 73.7705 43.9793 55.5361 44.2848 55.6771C44.5903 55.7946 60.4832 66.2088 41.9705 53.7418C42.5815 53.7418 44.8545 53.1837 45.348 52.9722L43.7511 52.3595C43.3986 52.5005 45.5714 56.0442 45.3834 56.0442ZM44.0342 56.5108C43.3527 55.7353 45.3338 56.783 44.1823 56.783C43.1483 56.783 44.9043 55.6048 44.2463 56.3333C43.5648 57.0618 43.7511 51.0435 43.7511 52.3595C43.7511 53.4405 43.6653 53.0133 44.1823 53.7418C44.8403 54.6348 41.7134 54.2598 43.0764 54.2598C43.9459 54.2598 43.4702 56.9103 44.0342 56.5108C44.5982 56.1113 44.1288 57.5428 44.3873 56.7673L43.7511 56.2254C55.3795 71.8986 44.3938 54.9384 44.1823 55.1029C43.9708 55.2674 44.0801 54.2598 43.7511 54.2598C56.2643 69.3767 58.4567 71.4935 44.1823 55.1029C57.894 68.7712 44.3873 57.3783 44.3873 56.7673L44.1823 56.945C44.1823 55.3705 44.7157 57.2628 44.0342 56.5108ZM44.3873 54.5713C44.3873 54.0073 43.7522 56.8398 44.0342 56.5108C44.3397 56.1818 43.495 56.2254 43.965 56.2254C44.388 56.2254 55.4258 75.7185 43.7511 56.2254C44.0566 56.5309 44.1588 56.1955 44.1823 56.783L44.3873 54.5713Z"
fill="#00C7FD"
/>
</g>
<defs>
<clipPath id="clip0_16_93">
<rect x="0.5" y="0.238312" width="47" height="47" fill="white" />
</clipPath>
</defs>
</svg> -->

View File

@@ -0,0 +1,68 @@
<!--
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.
-->
<script lang="ts">
export let overrideClasses = "";
const classes = overrideClasses ? overrideClasses : `w-5 h-5 text-gray-400`;
</script>
<!-- <svg
class={classes}
width="10"
height="10"
fill="none"
viewBox="0 0 18 18"
style="min-width: 18px; min-height: 18px;"
><g
><path
fill="#3369FF"
d="M15.71 8.019 3.835 1.368a1.125 1.125 0 0 0-1.61 1.36l2.04 5.71h5.298a.562.562 0 1 1 0 1.125H4.264l-2.04 5.71a1.128 1.128 0 0 0 1.058 1.506c.194 0 .384-.05.552-.146l11.877-6.65a1.125 1.125 0 0 0 0-1.964Z"
/></g
></svg
> -->
<!--
<svg
class={classes}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
/>
</svg> -->
<svg
t="1708926517502"
class={classes}
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4586"
id="mx_n_1708926517503"
width="200"
height="200"
><path
d="M0 1024l106.496-474.112 588.8-36.864-588.8-39.936-106.496-473.088 1024 512z"
p-id="4587"
fill="#0068b5"
/></svg
>

View File

@@ -0,0 +1,26 @@
<!--
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.
-->
<!-- <svg
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
>
<path d="M512 512c93.866667 0 170.666667-76.8 170.666667-170.666667 0-93.866667-76.8-170.666667-170.666667-170.666667C418.133333 170.666667 341.333333 247.466667 341.333333 341.333333 341.333333 435.2 418.133333 512 512 512zM512 597.333333c-115.2 0-341.333333 55.466667-341.333333 170.666667l0 85.333333 682.666667 0 0-85.333333C853.333333 652.8 627.2 597.333333 512 597.333333z" p-id="4050" fill="#ffffff"></path></svg> -->
<svg t="1708914168912" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1581" width="200" height="200"><path d="M447.13 46.545h101.818v930.91H447.13V46.545z" fill="#0068b5" p-id="1582" data-spm-anchor-id="a313x.search_index.0.i0.12a13a81x9rPe6" class="selected"></path></svg>

View File

@@ -0,0 +1,94 @@
.driverjs-theme {
background: transparent;
color: #fff;
box-shadow: none;
padding: 0;
}
.driver-popover-arrow {
border: 10px solid transparent;
animation: blink 1s 3 steps(1);
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.2;
}
100% {
opacity: 1;
}
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: #174ed1;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: #174ed1;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: #174ed1;
}
.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: #174ed1;
}
.driver-popover-footer {
background: transparent;
color: #fff;
}
.driver-popover-title {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.driver-popover-title,
.driver-popover-description {
display: block;
padding: 15px 15px 7px 15px;
background: #174ed1;
border: none;
}
.driver-popover-close-btn {
color: #fff;
}
.driver-popover-footer button:hover,
.driver-popover-footer button:focus {
background: #174ed1;
color: #fff;
}
.driver-popover-description {
padding: 5px 15px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.driver-popover-title[style*="block"] + .driver-popover-description {
margin: 0;
}
.driver-popover-progress-text {
color: #fff;
}
.driver-popover-footer button {
background: #174ed1;
border: 2px #174ed1 dashed;
color: #fff;
border-radius: 50%;
text-shadow: none;
}
.driver-popover-close-btn:hover,
.driver-popover-close-btn:focus {
color: #fff;
}
.driver-popover-navigation-btns button + button {
margin-left: 10px;
}

View File

@@ -0,0 +1,22 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<svg
t="1711440930029"
class="icon w-5 h-5"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4255"
><path
d="M668.8 896h-320c-48.64 0-88.32-37.76-92.8-87.68L211.2 403.2c-1.92-17.28 10.88-33.28 28.16-35.2 17.28-1.92 33.28 10.88 35.2 28.16l44.16 405.76c1.28 17.28 14.08 30.08 28.8 30.08h320c14.72 0 27.52-12.8 28.8-29.44l44.16-406.4c1.92-17.28 17.92-30.08 35.2-28.16 17.28 1.92 30.08 17.92 28.16 35.2l-44.16 405.76c-2.56 49.28-42.88 87.04-90.88 87.04zM826.24 321.28H190.72c-17.92 0-32-14.08-32-32s14.08-32 32-32h636.16c17.92 0 32 14.08 32 32s-14.72 32-32.64 32z"
fill="#a6adbb"
p-id="4256"
/><path
d="M424.96 789.12c-16.64 0-30.72-12.8-32-29.44l-27.52-347.52c-1.28-17.92 11.52-33.28 29.44-34.56 17.92-1.28 33.28 11.52 34.56 29.44l27.52 347.52c1.28 17.92-11.52 33.28-29.44 34.56h-2.56zM580.48 789.12h-2.56c-17.92-1.28-30.72-16.64-29.44-34.56L576 407.04c1.28-17.92 16.64-30.72 34.56-29.44 17.92 1.28 30.72 16.64 29.44 34.56l-27.52 347.52c-1.92 16.64-15.36 29.44-32 29.44zM581.76 244.48c-17.92 0-32-14.08-32-32 0-23.68-19.2-43.52-43.52-43.52s-43.52 19.2-43.52 43.52c0 17.92-14.08 32-32 32s-32-14.08-32-32c0-59.52 48-107.52 107.52-107.52s107.52 48 107.52 107.52c0 17.28-14.08 32-32 32z"
fill="#a6adbb"
p-id="4257"
/></svg
>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<svg
class="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="#0597ff"
stroke-width="4"
/>
<path
class="opacity-75"
fill="#0597ff"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>

After

Width:  |  Height:  |  Size: 520 B

View File

@@ -0,0 +1,31 @@
<!--
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.
-->
<svg
class="h-4 w-4 text-white rtl:rotate-180 dark:text-white-800"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 9 4-4-4-4"
/>
</svg>

After

Width:  |  Height:  |  Size: 881 B

View File

@@ -0,0 +1,37 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<svg
t="1697618332186"
class="w-16 h-16"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="7984"
width="16"
height="16"
><path
d="M146.285714 146.285714a97.52381 97.52381 0 0 1 97.52381-97.523809h341.333333l292.571429 292.571428v536.380953a97.52381 97.52381 0 0 1-97.52381 97.523809H243.809524a97.52381 97.52381 0 0 1-97.52381-97.523809V146.285714z"
fill="#D1DDE5"
p-id="7985"
/><path
d="M585.142857 48.761905l292.571429 292.571428h-195.047619a97.52381 97.52381 0 0 1-97.52381-97.523809V48.761905z"
fill="#ABBBC7"
p-id="7986"
/><path
d="M73.142857 609.52381m48.761905 0l365.714286 0q48.761905 0 48.761904 48.761904l0 121.904762q0 48.761905-48.761904 48.761905l-365.714286 0q-48.761905 0-48.761905-48.761905l0-121.904762q0-48.761905 48.761905-48.761904Z"
fill="#ABBBC7"
p-id="7987"
/><path
d="M162.06019 674.133333v34.572191h51.321905v22.186666H162.06019v55.637334H136.533333v-134.582857h85.430857v22.186666H162.06019zM238.640762 651.946667h25.502476v134.582857H238.665143v-134.582857zM288.353524 651.946667h25.502476v112.39619h58.953143v22.186667h-84.455619v-134.582857zM414.427429 674.133333v33.426286h51.151238v22.186667h-51.151238v34.57219h59.928381v22.186667h-85.430858V651.946667h85.430858v22.186666h-59.904z"
fill="#FFFFFF"
p-id="7988"
/><path
d="M329.142857 231.619048m-60.952381 0a60.952381 60.952381 0 1 0 121.904762 0 60.952381 60.952381 0 1 0-121.904762 0Z"
fill="#FFFFFF"
opacity=".6"
p-id="7989"
/></svg
>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,31 @@
<!--
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.
-->
<svg
class="h-4 w-4 text-white rtl:rotate-180 dark:text-white-800"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>

After

Width:  |  Height:  |  Size: 881 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699596229588" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20460" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M576 128a96 96 0 0 1 96 96v128h-224a96 96 0 0 0-95.84 90.368L352 448v224H224a96 96 0 0 1-96-96V224a96 96 0 0 1 96-96h352z" fill="#CCD9FF" p-id="20461"></path><path d="M576 96a128 128 0 0 1 128 128v128h-64V224a64 64 0 0 0-59.2-63.84L576 160H224a64 64 0 0 0-64 64v352a64 64 0 0 0 64 64h128v64H224a128 128 0 0 1-128-128V224a128 128 0 0 1 128-128z" fill="#3671FD" p-id="20462"></path><path d="M800 320H448a128 128 0 0 0-128 128v352a128 128 0 0 0 128 128h352a128 128 0 0 0 128-128V448a128 128 0 0 0-128-128z m-352 64h352a64 64 0 0 1 64 64v352a64 64 0 0 1-64 64H448a64 64 0 0 1-64-64V448a64 64 0 0 1 64-64z" fill="#3671FD" p-id="20463"></path><path d="M128 736a32 32 0 0 1 32 32 96 96 0 0 0 90.368 95.84L256 864a32 32 0 0 1 0 64 160 160 0 0 1-160-160 32 32 0 0 1 32-32z" fill="#FE9C23" p-id="20464"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,70 @@
<!--
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.
-->
<script lang="ts">
import MessageAvatar from "$lib/modules/chat/MessageAvatar.svelte";
import type { Message } from "$lib/shared/constant/Interface";
import MessageTimer from "./MessageTimer.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
export let msg: Message;
export let time: string = "";
</script>
<div
class={msg.role === 0
? "flex w-full gap-3"
: "flex w-full items-center gap-3"}
data-testid={msg.role === 0
? "display-answer"
: "display-question"}
>
<div
class={msg.role === 0
? "flex aspect-square w-[3px] items-center justify-center rounded bg-[#0597ff] max-sm:hidden"
: "flex aspect-square h-10 w-[3px] items-center justify-center rounded bg-[#000] max-sm:hidden"}
>
<MessageAvatar role={msg.role} />
</div>
<div class="group relative items-center">
<div>
<p
class=" max-w-[60vw] items-center whitespace-pre-line break-keep text-[0.8rem] leading-5 sm:max-w-[60rem]"
>
{@html msg.content}
</p>
</div>
</div>
</div>
{#if time}
<div>
<MessageTimer
{time}
on:handleTop={() => {
dispatch("scrollTop");
}}
/>
</div>
{/if}
<style>
.wrap-style {
word-wrap: break-word;
word-break: break-all;
}
</style>

View File

@@ -0,0 +1,30 @@
<!--
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.
-->
<script lang="ts">
import AssistantIcon from "$lib/assets/chat/svelte/Assistant.svelte";
import PersonOutlined from "$lib/assets/chat/svelte/PersonOutlined.svelte";
import { MessageRole } from "$lib/shared/constant/Interface";
export let role: MessageRole;
</script>
{#if role === MessageRole.User}
<PersonOutlined />
{:else}
<AssistantIcon />
{/if}

View File

@@ -0,0 +1,67 @@
<!--
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.
-->
<script lang="ts">
export let time: string;
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
</script>
<div class="ml-2 flex flex-col">
<div class="my-4 flex items-center justify-end gap-2 space-x-2">
<div class="ml-2 w-min cursor-pointer" data-state="closed">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<svg
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
viewBox="0 0 21.6 21.6"
width="24"
height="24"
class="w-5 fill-[#0597ff] hover:fill-[#0597ff]"
on:click={() => {
dispatch("handleTop");
}}
><path
d="M2.2 3.6V.8h17.2v2.8zm7.2 17.2V10.4L5.8 14l-1.9-1.9 6.9-6.9 6.9 6.9-1.9 1.9-3.6-3.6v10.4z"
/></svg
>
</div>
<div
class="inline-block w-0.5 self-stretch bg-gray-300 opacity-100 dark:opacity-50"
/>
<div class="w-min cursor-pointer" data-state="closed">
<svg
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
viewBox="0 0 21.6 21.6"
width="24"
height="24"
class="w-5 fill-[#0597ff] hover:fill-[#0597ff]"
><path d="M12.3 17.1V7.6H7.6v2.8h1.9v6.7H6.4v2.7h8.8v-2.7z" /><circle
cx="10.8"
cy="3.6"
r="1.9"
/></svg
>
</div>
<div class="flex items-center space-x-1 text-base text-gray-800" data-testid='msg-time'>
<strong>End to End Time: </strong>
<p>{time}s</p>
</div>
</div>
<div class="ml-2 flex flex-col" />
</div>

View File

@@ -0,0 +1,48 @@
<!--
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.
-->
<script lang="ts">
import { onMount } from "svelte";
import { page } from "$app/stores";
import { browser } from "$app/environment";
import { open } from "$lib/shared/stores/common/Store";
import Scrollbar from "$lib/shared/components/scrollbar/Scrollbar.svelte";
let root: HTMLElement
onMount(() => {
document.getElementsByTagName("body").item(0)!.removeAttribute("tabindex");
// root.style.height = document.documentElement.clientHeight + 'px'
});
if (browser) {
page.subscribe(() => {
// close side navigation when route changes
if (window.innerWidth > 768) {
$open = true;
}
});
}
</script>
<div bind:this={root} class='h-full overflow-hidden relative'>
<div class="h-full flex items-start">
<div class='relative flex flex-col h-full pl-0 w-full bg-white'>
<Scrollbar className="h-0 grow " classLayout="h-full" alwaysVisible={false}>
<slot />
</Scrollbar>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
// 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 { env } from "$env/dynamic/public";
import { SSE } from "sse.js";
const CHAT_BASE_URL = env.CHAT_BASE_URL;
export async function fetchTextStream(query: string) {
let payload = {};
let url = "";
payload = {
model: "Intel/neural-chat-7b-v3-3",
messages: query,
};
url = `${CHAT_BASE_URL}`;
console.log("fetchTextStream", url);
return new SSE(url, {
headers: { "Content-Type": "application/json" },
payload: JSON.stringify(payload),
});
}

View File

@@ -0,0 +1,82 @@
// 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 { env } from "$env/dynamic/public";
const UPLOAD_FILE_BASE_URL = env.UPLOAD_FILE_BASE_URL;
const GET_FILE = env.GET_FILE;
const DELETE_FILE = env.DELETE_FILE;
async function fetchFunc(url, init) {
try {
const response = await fetch(url, init);
if (!response.ok) throw response.status;
return await response.json();
} catch (error) {
console.error("network error: ", error);
return undefined;
}
}
export async function fetchKnowledgeBaseId(file: Blob, fileName: string) {
const url = `${UPLOAD_FILE_BASE_URL}`;
const formData = new FormData();
formData.append("files", file, fileName);
const init: RequestInit = {
method: "POST",
body: formData,
};
return fetchFunc(url, init);
}
export async function fetchKnowledgeBaseIdByPaste(pasteUrlList: any) {
const url = `${UPLOAD_FILE_BASE_URL}`;
const formData = new FormData();
formData.append("link_list", JSON.stringify(pasteUrlList));
const init: RequestInit = {
method: "POST",
body: formData,
};
return fetchFunc(url, init);
}
export async function fetchAllFile() {
const url = `${GET_FILE}`;
const init: RequestInit = {
method: "POST",
headers: { "Content-Type": "application/json" },
};
return fetchFunc(url, init);
}
export async function deleteFiles(path) {
const UploadKnowledge_URL = DELETE_FILE;
const data = {
file_path: path,
};
const init: RequestInit = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
};
return fetchFunc(UploadKnowledge_URL, init);
}

View File

@@ -0,0 +1,54 @@
// 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.
export function scrollToBottom(scrollToDiv: HTMLElement) {
if (scrollToDiv) {
setTimeout(
() =>
scrollToDiv.scroll({
behavior: "auto",
top: scrollToDiv.scrollHeight,
}),
100,
);
}
}
export function scrollToTop(scrollToDiv: HTMLElement) {
if (scrollToDiv) {
setTimeout(
() =>
scrollToDiv.scroll({
behavior: "auto",
top: 0,
}),
100,
);
}
}
export function getCurrentTimeStamp() {
return Math.floor(new Date().getTime());
}
export function fromTimeStampToTime(timeStamp: number) {
return new Date(timeStamp * 1000).toTimeString().slice(0, 8);
}
export function formatTime(seconds) {
const hours = String(Math.floor(seconds / 3600)).padStart(2, "0");
const minutes = String(Math.floor((seconds % 3600) / 60)).padStart(2, "0");
const remainingSeconds = String(seconds % 60).padStart(2, "0");
return `${hours}:${minutes}:${remainingSeconds}`;
}

View File

@@ -0,0 +1,156 @@
<!--
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.
-->
<script lang="ts">
import Scrollbar from "$lib/shared/components/scrollbar/Scrollbar.svelte";
import ChatMessage from "$lib/modules/chat/ChatMessage.svelte";
import "driver.js/dist/driver.css";
import "$lib/assets/layout/css/driver.css";
import Previous from "$lib/assets/upload/previous.svelte";
import Next from "$lib/assets/upload/next.svelte";
import { scrollToBottom } from "$lib/shared/Utils";
import { onMount } from "svelte";
let scrollToDiv: HTMLDivElement;
export let items;
export let label: string;
export let scrollName: string;
onMount(async () => {
scrollToDiv = document
.querySelector(scrollName)
?.querySelector(".svlr-viewport")!;
console.log(
"scrollToDiv",
scrollName,
document,
document.querySelector("chat-scrollbar1")
);
});
// gallery
let currentIndex = 0;
function nextItem() {
currentIndex = (currentIndex + 1) % items.length;
console.log("nextItem", currentIndex);
}
function prevItem() {
currentIndex = (currentIndex - 1 + items.length) % items.length;
console.log("prevItem", currentIndex);
}
$: currentItem = items[currentIndex];
$: {
if (items) {
scrollToBottom(scrollToDiv);
}
}
// gallery
</script>
<div
id="custom-controls-gallery"
class="relative mb-8 h-0 w-full w-full grow px-2 {scrollName}"
data-carousel="slide"
>
<!-- Carousel wrapper -->
<!-- Display current item -->
{#if currentItem}
<Scrollbar
classLayout="flex flex-col gap-5"
className=" h-0 w-full grow px-2 mt-3 ml-10"
>
{#each currentItem.content as message, i}
<ChatMessage msg={message} />
{/each}
</Scrollbar>
<!-- Loading text -->
{/if}
<div class="radius absolute left-0 p-2">
<!-- Display end to end time -->
<label for="" class="mr-2 text-xs font-bold text-blue-700">{label} </label>
</div>
{#if currentItem.time !== "0s"}
<div class="radius absolute right-0 p-2">
<!-- Display end to end time -->
<label for="" class="mr-2 text-xs font-bold text-blue-700"
>End to End Time:
</label>
<label for="" class="text-xs">{currentItem.time}</label>
</div>
{/if}
<div class="flex items-center justify-between">
<div class="justify-left ml-2 flex items-center">
<!-- Previous button -->
<button
type="button"
class="group absolute start-0 top-0 z-30 flex h-full
cursor-pointer items-center justify-center
focus:outline-none"
on:click={prevItem}
>
<span
class="group-focus:ring-gray dark:group-hover:bg-[#000]-800/60 dark:group-focus:ring-[#000]-800/70 inline-flex h-7
w-7 items-center justify-center
rounded-full bg-[#000]/10
group-hover:bg-[#000]/50 group-focus:bg-[#000]/50
group-focus:outline-none
group-focus:ring-4 dark:bg-gray-800/30"
>
<Previous />
<span class="sr-only">Previous</span>
</span>
</button>
<!-- Next button -->
<button
type="button"
class="group absolute end-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center focus:outline-none"
on:click={nextItem}
>
<span
class="group-focus:ring-gray dark:group-hover:bg-[#000]-800/60 dark:group-focus:ring-[#000]-800/70 inline-flex h-7
w-7 items-center justify-center
rounded-full bg-[#000]/10
group-hover:bg-[#000]/50 group-focus:bg-[#000]/50
group-focus:outline-none
group-focus:ring-4 dark:bg-gray-800/30"
>
<Next />
<span class="sr-only">Next</span>
</span>
</button>
</div>
</div>
</div>
<style>
.row::-webkit-scrollbar {
display: none;
}
.row {
scrollbar-width: none;
}
.row {
-ms-overflow-style: none;
}
</style>

View File

@@ -0,0 +1,150 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script lang="ts">
import FolderIcon from "$lib/assets/DocManagement/folderIcon.svelte";
import LinkfolderIcon from "$lib/assets/DocManagement/LinkfolderIcon.svelte";
import { Button, Modal } from "flowbite-svelte";
import SvelteTree from "$lib/shared/components/doc_management/treeView/svelte-tree.svelte";
import FileIcon from "$lib/assets/DocManagement/fileIcon.svelte";
import { createEventDispatcher } from "svelte";
import DeleteIcon from "$lib/assets/upload/deleteIcon.svelte";
import { deleteFiles } from "$lib/network/upload/Network";
import { storageFiles } from "$lib/shared/stores/common/Store";
import { getNotificationsContext } from "svelte-notifications";
const { addNotification } = getNotificationsContext();
let dispatch = createEventDispatcher();
let showDirectory = false;
let chooseDir = undefined;
let currentIdx = 0;
let deleteModal = false;
/**
* @type {any}
*/
let currentFile;
/**
* @type {number}
*/
let currentFileIdx;
export let files = [];
console.log("files", files);
function handleDirClick(file, index) {
chooseDir = file;
showDirectory = true;
currentIdx = index;
console.log("chooseDir", chooseDir);
}
async function deleteCurrentFolder() {
const res = await deleteFiles(currentFile);
// succeed
if (res.status) {
$storageFiles = $storageFiles.filter((_, index) => index !== currentFileIdx);
files = $storageFiles;
showNotification("Deleted successfully", "success");
} else {
showNotification("Deletion failed", "success");
}
}
function showNotification(text: string, type: string) {
addNotification({
text: text,
position: "top-left",
type: type,
removeAfter: 3000,
});
}
function deleteFileIdx(file, index) {
currentFile = file;
currentFileIdx = index;
deleteModal = true;
}
</script>
<Modal
bind:open={showDirectory}
size="xs"
autoclose={true}
class="z-50 w-full"
outsideclose
>
<hr class="my-8 h-px border-0 bg-gray-200 dark:bg-gray-700" />
<SvelteTree data={chooseDir.children} {currentIdx} />
</Modal>
<Modal bind:open={deleteModal} size="xs" autoclose>
<div class="text-center">
<h3 class="mb-5 text-lg font-normal text-gray-500">Confirm file deletion?</h3>
<Button
color="red"
class="mr-2"
on:click={() => { deleteCurrentFolder() }}>Yes, I'm sure</Button
>
<Button color="alternative"
on:click={() => { deleteModal = false; }}
>No, cancel</Button>
</div>
</Modal>
<div class="grid max-h-[35rem] grid-cols-2 gap-5 overflow-auto mt-6">
{#each files as file, index}
<div
class="group relative flex w-full flex-col items-center justify-center p-2 px-12 text-center hover:bg-[#d9eeff] focus:bg-[#d9eeff]"
>
{#if file.type === "File"}
<div class="flex-shrink-0">
<FileIcon />
</div>
<p class="w-[6rem] truncate">
{file.name}
</p>
{:else if file.type === "Directory" && file.id === "uploaded_links"}
<button
class="flex flex-col items-center"
on:click={() => handleDirClick(file, index)}
>
<div class="flex-shrink-0">
<LinkfolderIcon />
</div>
<p class="truncate">
{file.name}
</p>
</button>
{:else}
<button
class="flex flex-col items-center"
on:click={() => handleDirClick(file, index)}
>
<div class="flex-shrink-0">
<FolderIcon />
</div>
<p class="truncate">
{file.name}
</p>
</button>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="absolute right-0 top-0 hidden group-hover:block"
on:click={() => { deleteFileIdx(file.id, index) }}
>
<DeleteIcon />
</div>
</div>
{/each}
</div>

View File

@@ -0,0 +1,35 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script lang="ts">
import TreeBranch from "./tree-branch.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
type IData = {
name: string;
id: string;
type: string;
children: never[];
};
export let currentIdx;
export let collapse = false,
data: IData[] = [],
onClick = "";
console.log("data", data);
</script>
<div>
{#if data && data.length > 0}
<ul>
<TreeBranch {data} {collapse} {onClick} {currentIdx} />
</ul>
{:else}
<p>Folder is empty. Please upload a file.</p>
{/if}
</div>

View File

@@ -0,0 +1,46 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script lang="ts">
import { storageFiles } from "$lib/shared/stores/common/Store";
import TreeNode from "./tree-node.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
type IData = {
name: string;
id: string;
type: string;
children: never[];
};
export let data: IData[] = [],
collapse = false,
onClick = "";
export let currentIdx;
function changeData() {
console.log('change', $storageFiles);
data = $storageFiles[currentIdx].children;
}
$: $storageFiles ? changeData() : console.log('No change', $storageFiles);
console.log(data);
</script>
{#if data && data.length > 0}
{#each data as item}
<TreeNode
bind:node={item}
{collapse}
{onClick}
{currentIdx}
/>
{/each}
{:else}
<p>Folder is empty. Please upload a file.</p>
{/if}

View File

@@ -0,0 +1,111 @@
<!--
Copyright (C) 2024 Intel Corporation
SPDX-License-Identifier: Apache-2.0
-->
<script lang="ts">
import FileIcon from "$lib/assets/DocManagement/fileIcon.svelte";
import FolderIcon from "$lib/assets/DocManagement/folderIcon.svelte";
import LinkfolderIcon from "$lib/assets/DocManagement/LinkfolderIcon.svelte";
import { createEventDispatcher } from "svelte";
import { onMount } from "svelte";
type IData = {
name: string;
id: string;
type: string;
children: never[];
parent: IData;
currentIdx: number;
};
export let node: IData,
collapse = false,
onClick = "",
parent = "";
export let currentIdx;
let open = collapse;
function toggleOpen() {
open = !open;
}
function handleClickOpen() {
toggleOpen();
}
onMount(() => {
if (node) {
node.parent = parent;
}
});
</script>
<li class="relative ml-5">
<div
class="my-2 flex items-center gap-4 {node.type === 'File' ? 'ml-5' : ''}"
>
<!-- link -->
{#if node.type === "Directory"}
{#if open}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<svg
on:click={handleClickOpen}
data-testid="caret-down-node"
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 cursor-pointer"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<svg
on:click={handleClickOpen}
data-testid="caret-up-node"
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 cursor-pointer"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
/>
</svg>
{/if}
{#if node.id === "uploaded_links"}
<LinkfolderIcon className={"w-12 h-12"} />
{:else}
<FolderIcon className={"w-12 h-12"} />
{/if}
{:else}
<FileIcon className={"w-10 h-10"} />
{/if}
<!-- link -->
<span>{node?.name}</span>
</div>
{#if open && node.type === "Directory"}
<ul>
{#each node.children as child}
<svelte:self
bind:node={child}
bind:parent={node}
{collapse}
{onClick}
/>
{/each}
</ul>
{/if}
</li>

View File

@@ -0,0 +1,48 @@
<!--
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.
-->
<div
class="mb-6 flex items-center justify-center self-center bg-black text-sm text-gray-500"
/>
<div class="flex items-center justify-center gap-3">
<div class="relative inline-flex">
<div class="h-2 w-2 rounded-full bg-blue-600" />
<div
class="absolute left-0 top-0 h-2 w-2 animate-[ping_1s_infinite_100ms] rounded-full bg-blue-600"
/>
<div
class="duration-800 absolute left-0 top-0 h-2 w-2 animate-pulse rounded-full bg-blue-600"
/>
</div>
<div class="relative inline-flex">
<div class="h-2 w-2 rounded-full bg-blue-600" />
<div
class="absolute left-0 top-0 h-2 w-2 animate-[ping_1s_infinite_300ms] rounded-full bg-blue-600"
/>
<div
class="absolute left-0 top-0 h-2 w-2 animate-pulse rounded-full bg-blue-600"
/>
</div>
<div class="relative inline-flex">
<div class="h-2 w-2 rounded-full bg-blue-600" />
<div
class="absolute left-0 top-0 h-2 w-2 animate-[ping_1s_infinite_500ms] rounded-full bg-blue-600"
/>
<div
class="absolute left-0 top-0 h-2 w-2 animate-pulse rounded-full bg-blue-600"
/>
</div>
</div>

View File

@@ -0,0 +1,48 @@
<!--
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.
-->
<script lang="ts">
import { Svroller } from "svrollbar";
export let className: string = "";
export let classLayout: string = "";
export let alwaysVisible = true;
</script>
<div class={className}>
<Svroller height="100%" width="100%" {alwaysVisible}>
<div class={classLayout}>
<slot></slot>
</div>
</Svroller>
</div>
<style>
:global(.svlr-contents) {
height: 100%;
}
.row::-webkit-scrollbar {
display: none;
}
.row {
scrollbar-width: none;
}
.row {
-ms-overflow-style: none;
}
</style>

View File

@@ -0,0 +1,52 @@
<!--
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.
-->
<script lang="ts">
import { Button, Helper, Input, Label, Modal } from "flowbite-svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let formModal = false;
let urlValue = "";
function handelPasteURL() {
const pasteUrlList = urlValue.split(";").map((url) => url.trim());
dispatch("paste", { pasteUrlList });
formModal = false;
}
</script>
<Label class="space-y-1">
<div class="grid grid-cols-3">
<Input
class="col-span-2 rounded-none rounded-l-lg focus:border-blue-700 focus:ring-blue-700"
type="text"
name="text"
placeholder="URL"
bind:value={urlValue}
data-testid="paste-link"
/>
<Button
type="submit"
class="w-full rounded-none rounded-r-lg bg-blue-700"
data-testid="paste-click"
on:click={() => handelPasteURL()}>Confirm</Button
>
</div>
<Helper>Use semicolons (;) to separate multiple URLs.</Helper>
</Label>

View File

@@ -0,0 +1,49 @@
<!--
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.
-->
<script lang="ts">
import { Fileupload, Label } from "flowbite-svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
let value;
function handleInput(event: Event) {
const file = (event.target as HTMLInputElement).files![0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = () => {
if (!reader.result) return;
const src = reader.result.toString();
dispatch("upload", { src: src, fileName: file.name });
};
reader.readAsDataURL(file);
}
</script>
<div>
<Label class="space-y-2 mb-2">
<Fileupload
bind:value
on:change={handleInput}
class="focus:border-blue-700 focus:ring-0"
data-testid="file-upload"
accept=".txt,.pdf,.json,.md"
/>
</Label>

View File

@@ -0,0 +1,184 @@
<!--
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.
-->
<script lang="ts">
import { Drawer, Button, CloseButton, Tabs, TabItem } from "flowbite-svelte";
import { InfoCircleSolid } from "flowbite-svelte-icons";
import { sineIn } from "svelte/easing";
import UploadFile from "./upload-knowledge.svelte";
import PasteURL from "./PasteKnowledge.svelte";
import {
knowledge1,
knowledgeName,
storageFiles,
} from "$lib/shared/stores/common/Store";
import { getNotificationsContext } from "svelte-notifications";
import {
fetchAllFile,
fetchKnowledgeBaseId,
fetchKnowledgeBaseIdByPaste,
} from "$lib/network/upload/Network";
import DocCard from "../doc_management/docCard.svelte";
import NoFile from "$lib/assets/upload/no-file.svelte";
import LoadingButton from "$lib/assets/upload/loading-button.svelte";
const { addNotification } = getNotificationsContext();
$: files = $storageFiles ? $storageFiles : [];
let hidden6 = true;
let uploading = false;
let transitionParamsRight = {
x: 320,
duration: 200,
easing: sineIn,
};
async function handleKnowledgePaste(
e: CustomEvent<{ pasteUrlList: string[] }>
) {
uploading = true;
try {
const pasteUrlList = e.detail.pasteUrlList;
const res = await fetchKnowledgeBaseIdByPaste(pasteUrlList);
handleUploadResult(res, "knowledge_base");
} catch {
handleUploadError();
}
}
async function handleKnowledgeUpload(e: CustomEvent<any>) {
uploading = true;
try {
const blob = await fetch(e.detail.src).then((r) => r.blob());
const fileName = e.detail.fileName;
const res = await fetchKnowledgeBaseId(blob, fileName);
handleUploadResult(res, fileName);
} catch {
handleUploadError();
}
}
async function handleUploadResult(res: Response, fileName: string) {
if (res.status === 200) {
knowledge1.set({ id: "default" });
knowledgeName.set(fileName);
showNotification("Uploaded successfully", "success");
// update fileStructure
const res = await fetchAllFile();
uploading = false;
console.log('handleUploadResult', res);
if (res) {
storageFiles.set(res);
files = $storageFiles;
}
} else {
showNotification("Uploaded failed", "error");
}
}
function handleUploadError() {
showNotification("Uploaded failed", "error");
}
function showNotification(text: string, type: string) {
addNotification({
text: text,
position: "top-left",
type: type,
removeAfter: 3000,
});
}
</script>
<div class="text-center">
<Button
on:click={() => (hidden6 = false)}
class="bg-transparent focus-within:ring-gray-300 hover:bg-transparent focus:ring-0"
data-testid="open-upload"
>
<svg
aria-hidden="true"
class="h-7 w-7 text-blue-700"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
/></svg
>
</Button>
</div>
<Drawer
backdrop={false}
placement="right"
transitionType="fly"
transitionParams={transitionParamsRight}
bind:hidden={hidden6}
class=" border-2 border-b-0 border-r-0 shadow"
id="sidebar6"
>
<div class="flex items-center">
<h5
id="drawer-label"
class="mb-4 inline-flex items-center text-base font-semibold text-gray-500 dark:text-gray-400"
>
<InfoCircleSolid class="me-2.5 h-4 w-4" />Data Source
</h5>
<CloseButton
on:click={() => (hidden6 = true)}
class="mb-4 dark:text-white"
/>
</div>
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400">
Please upload your local file or paste a remote file link, and Chat will
respond based on the content of the uploaded file.
</p>
<Tabs
style="full"
defaultClass="flex rounded-lg divide-x rtl:divide-x-reverse divide-gray-200 shadow dark:divide-gray-700 focus:ring-0"
>
<TabItem class="w-full" open>
<span slot="title">Upload File</span>
<UploadFile on:upload={handleKnowledgeUpload} />
</TabItem>
<TabItem class="w-full" data-testid="exchange-paste">
<span slot="title">Paste Link</span>
<PasteURL on:paste={handleKnowledgePaste} />
</TabItem>
</Tabs>
{#if uploading}
<div class="flex flex-col items-center justify-center my-6">
<LoadingButton />
</div>
{/if}
{#if files.length > 0}
<DocCard {files} />
{:else}
<div class="flex flex-col items-center justify-center mt-6">
<NoFile />
<p class=" text-sm opacity-70">No files uploaded</p>
</div>
{/if}
</Drawer>

View File

@@ -0,0 +1,47 @@
// 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.
export enum MessageRole {
Assistant,
User,
}
export enum MessageType {
Text,
SingleAudio,
AudioList,
SingleImage,
ImageList,
singleVideo,
}
type Map<T> = T extends MessageType.Text | MessageType.SingleAudio
? string
: T extends MessageType.AudioList
? string[]
: T extends MessageType.SingleImage
? { imgSrc: string; imgId: string }
: { imgSrc: string; imgId: string }[];
export interface Message {
role: MessageRole;
type: MessageType;
content: Map<Message["type"]>;
time: number;
}
export enum LOCAL_STORAGE_KEY {
STORAGE_CHAT_KEY = "chatMessages",
STORAGE_TIME_KEY = "initTime",
}

View File

@@ -0,0 +1,41 @@
// 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 { writable } from "svelte/store";
export let open = writable(true);
export let knowledgeAccess = writable(true);
export let showTemplate = writable(false);
export let showSidePage = writable(false);
export let droppedObj = writable({});
export let isLoading = writable(false);
export let newUploadNum = writable(0);
export let ifStoreMsg = writable(true);
export const resetControl = writable(false);
export const knowledge1 = writable<{
id: string;
}>();
export const knowledgeName = writable("");
export const storageFiles = writable([]);

View File

@@ -0,0 +1,48 @@
<!--
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.
-->
<script>
import "tailwindcss/tailwind.css";
import "../app.postcss";
import Notifications from "svelte-notifications";
import Layout from "$lib/modules/frame/Layout.svelte";
import { onMount } from "svelte";
onMount(() => {
window.deviceType = window.innerWidth > 640 ? "pc" : "mobile";
window.onresize = () => {
window.deviceType = window.innerWidth > 640 ? "pc" : "mobile";
};
window.addEventListener("load", function () {
setTimeout(function () {
// This hides the address bar:
window.scrollTo(0, 1);
}, 0);
});
});
</script>
<Notifications>
<Layout>
<div class="flex h-full flex-col">
<div class="h-0 grow bg-white lg:rounded-tl-3xl">
<slot />
</div>
</div>
</Layout>
</Notifications>

View File

@@ -0,0 +1,311 @@
<!--
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.
-->
<script lang="ts">
export let data;
import { knowledge1, storageFiles } from "$lib/shared/stores/common/Store";
import { onMount } from "svelte";
import {
LOCAL_STORAGE_KEY,
MessageRole,
MessageType,
type Message,
} from "$lib/shared/constant/Interface";
import {
getCurrentTimeStamp,
scrollToBottom,
scrollToTop,
} from "$lib/shared/Utils";
import { fetchTextStream } from "$lib/network/chat/Network";
import LoadingAnimation from "$lib/shared/components/loading/Loading.svelte";
import "driver.js/dist/driver.css";
import "$lib/assets/layout/css/driver.css";
import UploadFile from "$lib/shared/components/upload/uploadFile.svelte";
import PaperAirplane from "$lib/assets/chat/svelte/PaperAirplane.svelte";
import Scrollbar from "$lib/shared/components/scrollbar/Scrollbar.svelte";
import ChatMessage from "$lib/modules/chat/ChatMessage.svelte";
import { fetchAllFile } from "$lib/network/upload/Network.js";
import { getNotificationsContext } from "svelte-notifications";
let query: string = "";
let loading: boolean = false;
let scrollToDiv: HTMLDivElement;
// ·········
let chatMessages: Message[] = data.chatMsg ? data.chatMsg : [];
const { addNotification } = getNotificationsContext();
// ··············
$: knowledge_1 = $knowledge1?.id ? $knowledge1.id : "default";
onMount(async () => {
scrollToDiv = document
.querySelector(".chat-scrollbar")
?.querySelector(".svlr-viewport")!;
const res = await fetchAllFile();
if (res) {
storageFiles.set(res);
}
});
function showNotification(text: string, type: string) {
addNotification({
text: text,
position: "top-left",
type: type,
removeAfter: 3000,
});
}
function handleTop() {
scrollToTop(scrollToDiv);
}
function storeMessages() {
localStorage.setItem(
LOCAL_STORAGE_KEY.STORAGE_CHAT_KEY,
JSON.stringify(chatMessages)
);
}
function decodeEscapedBytes(str: string): string {
const byteArray = str
.split("\\x")
.slice(1)
.map((byte) => parseInt(byte, 16));
const decoded = new TextDecoder("utf-8").decode(new Uint8Array(byteArray));
return decoded;
}
function decodeUnicode(str: string): string {
const decoded = str.replace(/\\u[\dA-Fa-f]{4}/g, (match) => {
return String.fromCharCode(parseInt(match.replace(/\\u/g, ""), 16));
});
return decoded;
}
const callTextStream = async (query: string, startSendTime: number) => {
try {
const eventSource = await fetchTextStream(query);
eventSource.addEventListener("error", (e: any) => {
if (e.type === "error") {
showNotification("Failed to load chat content.", "error");
loading = false;
}
});
eventSource.addEventListener("message", (e: any) => {
let msg = e.data;
console.log("msg", msg);
const handleDecodedMessage = (decodedMsg: string) => {
if (decodedMsg !== "</s>") {
decodedMsg = decodedMsg.replace(/\\n/g, "\n");
}
if (chatMessages[chatMessages.length - 1].role === MessageRole.User) {
chatMessages.push({
role: MessageRole.Assistant,
type: MessageType.Text,
content: decodedMsg,
time: startSendTime,
});
} else {
chatMessages[chatMessages.length - 1].content += decodedMsg;
}
scrollToBottom(scrollToDiv);
};
if (msg.startsWith("b")) {
let currentMsg = msg.slice(2, -1);
if (/\\x[\dA-Fa-f]{2}/.test(currentMsg)) {
currentMsg = decodeEscapedBytes(currentMsg);
} else if (/\\u[\dA-Fa-f]{4}/.test(currentMsg)) {
currentMsg = decodeUnicode(currentMsg);
}
handleDecodedMessage(currentMsg);
} else if (msg === "[DONE]") {
console.log("Done");
let startTime = chatMessages[chatMessages.length - 1].time;
loading = false;
let totalTime = parseFloat(
((getCurrentTimeStamp() - startTime) / 1000).toFixed(2)
);
if (chatMessages.length - 1 !== -1) {
chatMessages[chatMessages.length - 1].time = totalTime;
}
storeMessages();
} else {
if (/\\x[\dA-Fa-f]{2}/.test(msg)) {
msg = decodeEscapedBytes(msg);
} else if (/\\u[\dA-Fa-f]{4}/.test(msg)) {
msg = decodeUnicode(msg);
}
let currentMsg = msg.replace(/"/g, "").replace(/\\n/g, "\n");
handleDecodedMessage(currentMsg);
}
});
eventSource.stream();
} catch (error: any) {
showNotification("Failed to load chat content.", "error");
loading = false;
}
};
const handleTextSubmit = async () => {
loading = true;
const newMessage = {
role: MessageRole.User,
type: MessageType.Text,
content: query,
time: 0,
};
chatMessages = [...chatMessages, newMessage];
scrollToBottom(scrollToDiv);
storeMessages();
query = "";
await callTextStream(newMessage.content, getCurrentTimeStamp());
scrollToBottom(scrollToDiv);
storeMessages();
};
function handelClearHistory() {
localStorage.removeItem(LOCAL_STORAGE_KEY.STORAGE_CHAT_KEY);
chatMessages = [];
}
</script>
<!-- <DropZone on:drop={handleImageSubmit}> -->
<div
class="h-full items-center gap-5 bg-white sm:flex sm:pb-2 lg:rounded-tl-3xl"
>
<div class="mx-auto flex h-full w-full flex-col sm:mt-0 sm:w-[72%]">
<div class="flex justify-between p-2">
<p class="text-[1.7rem] font-bold tracking-tight">ChatQnA</p>
<UploadFile />
</div>
<div
class="fixed relative flex w-full flex-col items-center justify-between bg-white p-2 pb-0"
>
<div class="relative my-4 flex w-full flex-row justify-center">
<div class="relative w-full focus:border-none">
<input
class="text-md block w-full border-0 border-b-2 border-gray-300 px-1 py-4
text-gray-900 focus:border-gray-300 focus:ring-0 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
type="text"
data-testid="chat-input"
placeholder="Enter prompt here"
disabled={loading}
maxlength="1200"
bind:value={query}
on:keydown={(event) => {
if (event.key === "Enter" && !event.shiftKey && query) {
event.preventDefault();
handleTextSubmit();
}
}}
/>
<button
on:click={() => {
if (query) {
handleTextSubmit();
}
}}
type="submit"
id="send"
class="absolute bottom-2.5 end-2.5 px-4 py-2 text-sm font-medium text-white dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
><PaperAirplane /></button
>
</div>
</div>
</div>
<!-- clear -->
{#if Array.isArray(chatMessages) && chatMessages.length > 0 && !loading}
<div class="flex w-full justify-between pr-5">
<div class="flex items-center">
<button
class="bg-primary text-primary-foreground hover:bg-primary/90 group flex items-center justify-center space-x-2 p-2"
type="button"
data-testid="clear-chat"
on:click={() => handelClearHistory()}
><svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
width="24"
height="24"
class="fill-[#0597ff] group-hover:fill-[#0597ff]"
><path
d="M12.6 12 10 9.4 7.4 12 6 10.6 8.6 8 6 5.4 7.4 4 10 6.6 12.6 4 14 5.4 11.4 8l2.6 2.6zm7.4 8V2q0-.824-.587-1.412A1.93 1.93 0 0 0 18 0H2Q1.176 0 .588.588A1.93 1.93 0 0 0 0 2v12q0 .825.588 1.412Q1.175 16 2 16h14zm-3.15-6H2V2h16v13.125z"
/></svg
><span class="font-medium text-[#0597ff]">CLEAR</span></button
>
</div>
</div>
{/if}
<!-- clear -->
<div class="mx-auto flex h-full w-full flex-col" data-testid="chat-message">
<Scrollbar
classLayout="flex flex-col gap-1 mr-4"
className="chat-scrollbar h-0 w-full grow px-2 pt-2 mt-3 mr-5"
>
{#each chatMessages as message, i}
<ChatMessage
on:scrollTop={() => handleTop()}
msg={message}
time={i === 0 || (message.time > 0 && message.time < 100)
? message.time
: ""}
/>
{/each}
</Scrollbar>
<!-- Loading text -->
{#if loading}
<LoadingAnimation />
{/if}
</div>
<!-- gallery -->
</div>
</div>
<style>
.row::-webkit-scrollbar {
display: none;
}
.row {
scrollbar-width: none;
}
.row {
-ms-overflow-style: none;
}
</style>

View File

@@ -0,0 +1,26 @@
// 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 { browser } from "$app/environment";
import { LOCAL_STORAGE_KEY } from "$lib/shared/constant/Interface";
export const load = async () => {
if (browser) {
const chat = localStorage.getItem(LOCAL_STORAGE_KEY.STORAGE_CHAT_KEY);
return {
chatMsg: JSON.parse(chat || "[]"),
};
}
};

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