chore: support openai chat on web

This commit is contained in:
Kilu 2024-06-10 16:27:45 +08:00
parent 86696b271e
commit 0c1a9959f2
14 changed files with 617 additions and 282 deletions

View File

@ -1,284 +1,151 @@
<h1 align="center" style="margin:0"> AppFlowy Web </h1>
<div align="center">
<h1><code>AppFlowy Web Project</code></h1>
<div>Welcome to the AppFlowy Web Project, a robust and versatile platform designed to bring the innovative features of
AppFlowy to the web. This project uniquely supports running as a desktop application via Tauri, and offers web
interfaces powered by WebAssembly (WASM). Dive into an exceptional development experience with high performance and
extensive capabilities.</div>
<img src="https://img.shields.io/badge/React-v18.2.0-blue"/>
<img src="https://img.shields.io/badge/TypeScript-v4.9.5-blue"/>
<img src="https://img.shields.io/badge/Nginx-v1.21.6-brightgreen"/>
<img src="https://img.shields.io/badge/Bun-latest-black"/>
<img src="https://img.shields.io/badge/Docker-v20.10.12-blue"/>
</div>
## 🐑 Features
## 🌟 Introduction
- **Cross-Platform Compatibility**: Seamlessly run on desktop environments with Tauri, and on any web browser through
WASM.
- **High Performance**: Leverage the speed and efficiency of WebAssembly for your web interfaces.
- **Tauri Integration**: Build lightweight, secure, and efficient desktop applications.
- **Flexible Development**: Utilize a wide range of AppFlowy's functionalities in your web or desktop projects.
Welcome to the AppFlowy Web project! This project aims to bring the powerful features of AppFlowy to the web. Whether
you're a developer looking to contribute or a user eager to try out the latest features, this guide will help you get
started.
## 🚀 Getting Started
AppFlowy Web is built with the following technologies:
### 🛠️ Prerequisites
- **React**: A JavaScript library for building user interfaces.
- **TypeScript**: A typed superset of JavaScript that compiles to plain JavaScript.
- **Bun**: A fast all-in-one JavaScript runtime.
- **Nginx**: A high-performance web server.
- **Docker**: A platform to develop, ship, and run applications in containers.
Before you begin, ensure you have the following installed:
### Resource Sharing
- Node.js (v14 or later)
- Rust (latest stable version)
- Tauri prerequisites for your operating system
- PNPM (8.5.0)
To maintain consistency across different platforms, the Web project shares i18n translation files and Icons with the
Flutter project. This ensures a unified user experience and reduces duplication of effort in maintaining these
resources.
### 🏗️ Installation
- **i18n Translation Files**: The translation files are shared to provide a consistent localization experience across
both Web and Flutter applications. The path to the translation files is `frontend/resources/translations/`.
#### Clone the Repository
> The translation files are stored in JSON format and contain translations for different languages. The files are
named according to the language code (e.g., `en.json` for English, `es.json` for Spanish, etc.).
```bash
git clone https://github.com/AppFlowy-IO/AppFlowy
```
- **Icons**: The icon set used in the Web project is the same as the one used in the Flutter project, ensuring visual
consistency. The icons are stored in the `frontend/resources/flowy_icons/` directory.
#### 🌐 Install the frontend dependencies:
Let's dive in and get the project up and running! 🚀
```bash
cd frontend/appflowy_web_app
pnpm install
```
## 🛠 Getting Started
#### 🖥️ Desktop Application (Tauri) (Optional)
### Prerequisites
> **Note**: if you want to run the web app in the browser, skip this step
Before you begin, make sure you have the following installed on your system:
- Follow the instructions [here](https://tauri.app/v1/guides/getting-started/prerequisites/) to install Tauri
- [Node.js](https://nodejs.org/) (v18.6.0) 🌳
- [pnpm](https://pnpm.io/) (package manager) 📦
- [Jest](https://jestjs.io/) (testing framework) 🃏
- [Cypress](https://www.cypress.io/) (end-to-end testing) 🧪
##### Windows and Linux Prerequisites
### Clone the Repository
###### Windows only
First, clone the repository to your local machine:
- Install the Duckscript CLI and vcpkg
```bash
git clone https://github.com/AppFlowy-IO/AppFlowy.git
cd frontend/appflowy_web_app
```
```bash
cargo install --force duckscript_cli
vcpkg integrate install
```
### Install Dependencies
###### Linux only
Install the required dependencies using pnpm:
- Install the required dependencies
```bash
## ensure you have pnpm installed, if not run the following command
# npm install -g pnpm@8.5.0
```bash
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
```
pnpm install
```
- **Get error**: failed to run custom build command for librocksdb-sys v6.11.4
### Start the Development Server
```bash
sudo apt install clang
```
To start the development server, run the following command:
##### Install Tauri Dependencies
```bash
pnpm run dev
```
- Install cargo-make
### 🚀 Building for Production(Optional)
```bash
cargo install --force cargo-make
```
if you want to run the production build, use the following commands
```bash
pnpm run build
pnpm run start
```
- Install AppFlowy dev tools
This will start the application in development mode. Open http://localhost:3000 to view it in the browser.
```bash
# install development tools
# make sure you are in the root directory of the project
cd frontend
cargo make appflowy-tauri-deps-tools
```
## 🧪 Running Tests
- Build the service/dependency
### Unit Tests
```bash
# make sure you are in the root directory of the project
cd frontend/appflowy_web_app
mkdir dist
cd src-tauri
cargo build
```
We use **Jest** for running unit tests. To run the tests, use the following command:
### 🚀 Running the Application
```bash
pnpm run test:unit
```
#### 🌐 Web Application
This will execute all the unit tests in the project and provide a summary of the results. ✅
- Run the web application
### Components Tests
```bash
pnpm run dev
```
- Open your browser and navigate to `http://localhost:3000`, You can now interact with the AppFlowy web application
We use **Cypress** for end-to-end testing. To run the Cypress tests, use the following command:
#### 🖥️ Desktop Application (Tauri)
```bash
pnpm run cypress:open
```
**Ensure close web application before running the desktop application**
This will open the Cypress Test Runner where you can run your end-to-end tests. 🧪
- Run the desktop application
```bash
pnpm run tauri:dev
```
- The AppFlowy desktop application will open, and you can interact with it
### 🛠️ Development
#### How to add or modify i18n keys
- Modify the i18n files in `frontend/resources/translations/en.json` to add or modify i18n keys
- Run the following command to update the i18n keys in the application
```bash
pnpm run sync:i18n
```
#### How to modify the theme
Don't modify the theme file in `frontend/appflowy_web_app/src/styles/variables` directly)
- Modify the theme file in `frontend/appflowy_web_app/style-dictionary/tokens/base.json( or dark.json or light.json)` to
add or modify theme keys
- Run the following command to update the theme in the application
```bash
pnpm run css:variables
```
#### How to add or modify the environment variables
- Modify the environment file in `frontend/appflowy_web_app/.env` to add or modify environment variables
#### How to create symlink for the @appflowyinc/client-api-wasm in local development
- Run the following command to create a symlink for the @appflowyinc/client-api-wasm
```bash
# ensure you are in the frontend/appflowy_web_app directory
pnpm run link:client-api $source_path $target_path
# Example
# pnpm run link:client-api ../../../AppFlowy-Cloud/libs/client-api-wasm/pkg ./node_modules/@appflowyinc/client-api-wasm
```
### 📝 About the Project
#### 📁 Directory Structure
- `frontend/appflowy_web_app`: Contains the web application source code
- `frontend/appflowy_web_app/src`: Contains the app entry point and the source code
- `frontend/appflowy_web_app/src/components`: Contains the react components
- `frontend/appflowy_web_app/src/styles`: Contains the styles for the application
- `frontend/appflowy_web_app/src/utils`: Contains the utility functions
- `frontend/appflowy_web_app/src/i18n`: Contains the i18n files
- `frontend/appflowy_web_app/src/assets`: Contains the assets for the application
- `frontend/appflowy_web_app/src/store`: Contains the redux store
- `frontend/appflowy_web_app/src/@types`: Contains the typescript types
- `frontend/appflowy_web_app/src/applications/services`: Contains the services for the application. In vite.config.ts,
we have defined the alias for the services directory for different environments(Tauri/Web)
```typescript
resolve: {
alias: [
// ...
{
find: '$client-services',
replacement: !!process.env.TAURI_PLATFORM
? `${__dirname}/src/application/services/tauri-services`
: `${__dirname}/src/application/services/js-services`,
},
]
}
```
### 📦 Deployment
Use the AppFlowy CI/CD pipeline to deploy the application to the test and production environments.
- Push the changes to the main branch
- Deploy Test Environment
- Automatically, the test environment will be deployed if merged to the main branch or build/test branch
- Deploy Production Environment
- Navigate to the Actions tab
- Click on the workflow and select the Run workflow
- Enter the options
- Click on the Run workflow button
#### 📦 Deployment (Self-Hosted EC2)
##### Pre-requisites
Please ensure you have learned about:
- [Deploy Web application on AWS Cloud using EC2 Instance](https://www.youtube.com/watch?v=gWVIIU1ev0Y)
- [How to Install and Use Rsync Command](https://operavps.com/docs/install-rsync-command-in-linux/)
- [How to Use ssh-keygen to Generate a New SSH Key?](https://www.ssh.com/academy/ssh/keygen)
- [Linux post-installation steps for Docker Engine](https://docs.docker.com/engine/install/linux-postinstall/)
- [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
And then follow the steps below:
1. Ensure you have the following installed on your server:
- Docker: [Install Docker](https://docs.docker.com/engine/install/)
- Rsync: [Install Rsync](https://operavps.com/docs/install-rsync-command-in-linux/)
2. Create a new user for deploy, and generate an SSH key for the user
```bash
sudo adduser appflowy(or any name)
sudo su - appflowy
mkdir ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t rsa
chmod 600 ~/.ssh/authorized_keys
# add the user to the docker group, to run docker commands without sudo
sudo usermod -aG docker ${USER}
```
- visit the `~/.ssh/id_rsa` and `~/.ssh/id_rsa.pub` to get the private and public key respectively
- add the public key to the `~/.ssh/authorized_keys` file
- ensure the private key is kept safe
- exit and login back to the server with the new
user: `ssh -i your-existing-key.pem ec2-user@your-instance-public-dns`
3. Clone the AppFlowy repository
4. Set the following secrets in your
repository, have to
know [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)
> Note: Test Environment: prefix the secret with `WEB_TEST_` and Production Environment: prefix the secret with `WEB_`
> for example, `WEB_TEST_SSH_PRIVATE_KEY` and `WEB_SSH_PRIVATE_KEY`
- `SSH_PRIVATE_KEY`: The private key generated in step 2: cat ~/.ssh/id_rsa
- `REMOTE_HOST`: The host of the server: `your-instance-public-dns` or `your-instance-ip`
- `REMOTE_USER`: The user created in step 2: `appflowy`
- `SSL_CERTIFICATE`: The SSL certificate for the
server - [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
- `SSL_CERTIFICATE_KEY`: The SSL certificate key for the
server - [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html)
5. Run the deployment workflow to deploy the application(production or test environment)
> Note: the test server will **automatically** deploy if merged to the main branch or build/test branch
### 🧪 Testing
> We use Cypress for end-to-end testing and component testing - [Cypress](https://www.cypress.io/)
#### 🧪 End-to-End Testing
> to be continued
#### 🧪 Component Testing
Run the following command to run the component tests
Alternatively, to run Cypress tests in the headless mode, use:
```bash
pnpm run test:components
```
Both commands will provide detailed test results and generate a code coverage report.
## 🔄 Development Workflow
### Linting
To maintain code quality, we use **ESLint**. To run the linter and fix any linting errors, use the following command:
```bash
pnpm run lint
```
## 🚀 Production Deployment
Our production deployment process is automated using GitHub Actions. The process involves:
1. **Setting up an AWS EC2 instance**: We use an EC2 instance to host the application.
2. **Installing Docker and Docker Compose**: Docker is installed on the AWS instance.
3. **Configuring SSH Access**: SSH access is set up with a user and password.
4. **Preparing Project Configuration**: We configure `Dockerfile`, `nginx.conf`, and `server.cjs` in the web project.
5. **Using GitHub Actions**: We use the easingthemes/ssh-deploy@main action to deploy the project to the remote server.
The deployment steps include building the Docker image and running the Docker container with the necessary port
mappings:
```bash
docker build -t appflowy-web-app .
docker rm -f appflowy-web-app || true
docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app
```
The Web server runs on Bun. For more details about Bun, please refer to the [Bun documentation](https://bun.sh/).

View File

@ -36,6 +36,7 @@
"@mui/x-date-pickers-pro": "^6.18.2",
"@reduxjs/toolkit": "2.0.0",
"@slate-yjs/core": "^1.0.2",
"@tailwindcss/typography": "^0.5.13",
"@tauri-apps/api": "^1.5.3",
"@types/react-swipeable-views": "^0.13.4",
"async-retry": "^1.3.3",
@ -43,9 +44,11 @@
"colorthief": "^2.4.0",
"dayjs": "^1.11.9",
"decimal.js": "^10.4.3",
"dompurify": "^3.1.5",
"emoji-mart": "^5.5.2",
"emoji-regex": "^10.2.1",
"events": "^3.3.0",
"eventsource-parser": "^1.1.2",
"google-protobuf": "^3.15.12",
"i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1",
@ -55,6 +58,7 @@
"js-base64": "^3.7.5",
"katex": "^0.16.7",
"lodash-es": "^4.17.21",
"markdown-it": "^14.1.0",
"nanoid": "^4.0.0",
"numeral": "^2.0.6",
"prismjs": "^1.29.0",
@ -107,6 +111,7 @@
"@svgr/plugin-svgo": "^8.0.1",
"@tauri-apps/cli": "^1.5.11",
"@testing-library/react": "^16.0.0",
"@types/dompurify": "^3.0.5",
"@types/google-protobuf": "^3.15.12",
"@types/is-hotkey": "^0.1.7",
"@types/jest": "^29.5.3",

View File

@ -1,5 +1,9 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@appflowyinc/client-api-wasm':
specifier: 0.0.3
@ -37,6 +41,9 @@ dependencies:
'@slate-yjs/core':
specifier: ^1.0.2
version: 1.0.2(slate@0.101.5)(yjs@13.6.15)
'@tailwindcss/typography':
specifier: ^0.5.13
version: 0.5.13(tailwindcss@3.2.7)
'@tauri-apps/api':
specifier: ^1.5.3
version: 1.5.6
@ -58,6 +65,9 @@ dependencies:
decimal.js:
specifier: ^10.4.3
version: 10.4.3
dompurify:
specifier: ^3.1.5
version: 3.1.5
emoji-mart:
specifier: ^5.5.2
version: 5.6.0
@ -67,6 +77,9 @@ dependencies:
events:
specifier: ^3.3.0
version: 3.3.0
eventsource-parser:
specifier: ^1.1.2
version: 1.1.2
google-protobuf:
specifier: ^3.15.12
version: 3.21.2
@ -94,6 +107,9 @@ dependencies:
lodash-es:
specifier: ^4.17.21
version: 4.17.21
markdown-it:
specifier: ^14.1.0
version: 14.1.0
nanoid:
specifier: ^4.0.0
version: 4.0.2
@ -246,6 +262,9 @@ devDependencies:
'@testing-library/react':
specifier: ^16.0.0
version: 16.0.0(@testing-library/dom@10.1.0)(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
'@types/dompurify':
specifier: ^3.0.5
version: 3.0.5
'@types/google-protobuf':
specifier: ^3.15.12
version: 3.15.12
@ -3363,12 +3382,10 @@ packages:
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
dev: true
/@nodelib/fs.stat@2.0.5:
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
dev: true
/@nodelib/fs.walk@1.2.8:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
@ -3376,7 +3393,6 @@ packages:
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
dev: true
/@pkgjs/parseargs@0.11.0:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
@ -3821,6 +3837,18 @@ packages:
- typescript
dev: true
/@tailwindcss/typography@0.5.13(tailwindcss@3.2.7):
resolution: {integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.2.7(postcss@8.4.21)
dev: false
/@tauri-apps/api@1.5.6:
resolution: {integrity: sha512-LH5ToovAHnDVe5Qa9f/+jW28I6DeMhos8bNDtBOmmnaDpPmJmYLyHdeDblAWWWYc7KKRDg9/66vMuKyq0WIeFA==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
@ -4029,6 +4057,12 @@ packages:
resolution: {integrity: sha512-p9eZ2X9B80iKiTW4ukVj8B4K6q9/+xFtQ5MGYA5HWToY9nL4EkhV9+6ftT2VHpVMEZb5Tv00Iel516bVdO+yRw==}
dev: true
/@types/dompurify@3.0.5:
resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
dependencies:
'@types/trusted-types': 2.0.7
dev: true
/@types/eslint-scope@3.7.7:
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
dependencies:
@ -4273,6 +4307,10 @@ packages:
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
dev: true
/@types/trusted-types@2.0.7:
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
dev: true
/@types/use-sync-external-store@0.0.3:
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
dev: false
@ -4609,12 +4647,10 @@ packages:
acorn: 7.4.1
acorn-walk: 7.2.0
xtend: 4.0.2
dev: true
/acorn-walk@7.2.0:
resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
engines: {node: '>=0.4.0'}
dev: true
/acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
@ -4625,7 +4661,6 @@ packages:
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
@ -4768,7 +4803,6 @@ packages:
/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
dev: true
/argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@ -4777,7 +4811,6 @@ packages:
/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
@ -5219,7 +5252,6 @@ packages:
/camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
dev: true
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
@ -5600,7 +5632,6 @@ packages:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
dev: true
/csso@5.0.5:
resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
@ -5836,7 +5867,6 @@ packages:
/defined@1.0.1:
resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
@ -5858,11 +5888,9 @@ packages:
acorn-node: 1.8.2
defined: 1.0.1
minimist: 1.2.8
dev: true
/didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true
/diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
@ -5887,7 +5915,6 @@ packages:
/dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
dev: true
/doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
@ -5949,6 +5976,10 @@ packages:
domelementtype: 2.3.0
dev: true
/dompurify@3.1.5:
resolution: {integrity: sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==}
dev: false
/domutils@3.1.0:
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
dependencies:
@ -6036,7 +6067,6 @@ packages:
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: true
/error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@ -6384,6 +6414,11 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
/eventsource-parser@1.1.2:
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
engines: {node: '>=14.18'}
dev: false
/execa@4.1.0:
resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
engines: {node: '>=10'}
@ -6483,7 +6518,6 @@ packages:
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
dev: true
/fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
@ -6496,7 +6530,6 @@ packages:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
dependencies:
reusify: 1.0.4
dev: true
/fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
@ -6790,7 +6823,6 @@ packages:
engines: {node: '>=10.13.0'}
dependencies:
is-glob: 4.0.3
dev: true
/glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
@ -8089,11 +8121,16 @@ packages:
/lilconfig@2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
dev: true
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
/linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
dependencies:
uc.micro: 2.1.0
dev: false
/listr2@3.14.0(enquirer@2.4.1):
resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==}
engines: {node: '>=10.0.0'}
@ -8151,6 +8188,10 @@ packages:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
dev: false
/lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
@ -8165,13 +8206,16 @@ packages:
/lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
/lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
dev: false
/lodash.memoize@4.1.2:
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
dev: true
/lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
@ -8273,6 +8317,18 @@ packages:
dependencies:
tmpl: 1.0.5
/markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
dev: false
/material-colors@1.2.6:
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
dev: false
@ -8285,6 +8341,10 @@ packages:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
dev: false
/memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
dev: false
@ -8299,7 +8359,6 @@ packages:
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
dev: true
/micromatch@4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
@ -8343,7 +8402,6 @@ packages:
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/minipass@7.0.4:
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
@ -8507,7 +8565,6 @@ packages:
/object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
dev: true
/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
@ -8803,7 +8860,6 @@ packages:
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
dev: true
/pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
@ -8854,7 +8910,6 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.8
dev: true
/postcss-js@4.0.1(postcss@8.4.21):
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
@ -8864,7 +8919,6 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.21
dev: true
/postcss-load-config@3.1.4(postcss@8.4.21):
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@ -8881,7 +8935,6 @@ packages:
lilconfig: 2.1.0
postcss: 8.4.21
yaml: 1.10.2
dev: true
/postcss-nested@6.0.0(postcss@8.4.21):
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
@ -8891,7 +8944,14 @@ packages:
dependencies:
postcss: 8.4.21
postcss-selector-parser: 6.0.16
dev: true
/postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: false
/postcss-selector-parser@6.0.16:
resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
@ -8899,11 +8959,9 @@ packages:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
/postcss@8.4.21:
resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
@ -8912,7 +8970,6 @@ packages:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.2.0
dev: true
/postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
@ -9084,6 +9141,11 @@ packages:
pump: 2.0.1
dev: true
/punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
dev: false
/punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@ -9109,7 +9171,6 @@ packages:
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true
/queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
@ -9118,7 +9179,6 @@ packages:
/quick-lru@5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
dev: true
/quill-delta@3.6.3:
resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==}
@ -9621,7 +9681,6 @@ packages:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
dependencies:
pify: 2.3.0
dev: true
/readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
@ -9827,7 +9886,6 @@ packages:
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/rfdc@1.3.1:
resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==}
@ -9891,7 +9949,6 @@ packages:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
queue-microtask: 1.2.3
dev: true
/rxjs@7.8.0:
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
@ -10437,7 +10494,6 @@ packages:
resolve: 1.22.8
transitivePeerDependencies:
- ts-node
dev: true
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
@ -10809,6 +10865,10 @@ packages:
hasBin: true
dev: true
/uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
dev: false
/ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
dev: true
@ -10940,7 +11000,6 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid@3.4.0:
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
@ -11306,7 +11365,6 @@ packages:
/xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
dev: true
/y-indexeddb@9.0.12(yjs@13.6.15):
resolution: {integrity: sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==}
@ -11414,7 +11472,3 @@ packages:
resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
engines: {node: '>=12.20'}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -1,3 +1,4 @@
import AfChatPage from '@/pages/AFChatPage';
import FolderPage from '@/pages/FolderPage';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import ProtectedRoutes from '@/components/auth/ProtectedRoutes';
@ -13,6 +14,7 @@ const AppMain = withAppWrapper(() => {
<Route path={'/view/:workspaceId/:objectId'} element={<ProductPage />} />
</Route>
<Route path={'/login'} element={<LoginPage />} />
<Route path={'/chat'} element={<AfChatPage />} />
</Routes>
);
});

View File

@ -0,0 +1,30 @@
import { FC, useMemo } from 'react';
import DOMPurify from 'dompurify';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
import markdownit from 'markdown-it';
interface AnswerProps {
query: string;
answer: string;
done: boolean;
}
export const Answer: FC<AnswerProps> = ({ query, answer }) => {
const md = useMemo(() => markdownit(), []);
return (
<div className='max-w-[800px] px-8 pb-8'>
<div className='text-2xl font-bold text-text-caption'>{`Q: ${query} `}</div>
<div className='border-b border-line-border pb-8'>
<div
className='markdown-content mt-2 h-fit'
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(md.render(answer)),
}}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,114 @@
import { AFScroller } from '@/components/_shared/scroller';
import { SearchQuery } from '@/components/chat/types';
import { Tooltip } from '@mui/material';
import React, { useEffect, useRef, useState } from 'react';
import { Answer } from './Answer';
import { Search } from './Search';
import { ReactComponent as ReloadICON } from '$icons/16x/reload.svg';
import './markdown.scss';
export function Chat() {
const [searchQuery, setSearchQuery] = useState<SearchQuery>({ query: '' });
const [answer, setAnswer] = useState<string>('');
const [apiKeyValue, setApiKeyValue] = useState<string>('');
const [apiKey, setApiKey] = useState<string>(() => {
const key = localStorage.getItem('openai-api-key');
return key ? key : '';
});
const conversationRef = useRef<
{
query: string;
answer: string;
}[]
>([]);
const [done, setDone] = useState<boolean>(false);
useEffect(() => {
if (!done) {
return;
}
if (!answer) {
return;
}
if (conversationRef.current.length === 0) {
conversationRef.current = [
{
query: searchQuery.query,
answer,
},
];
} else {
conversationRef.current.push({
query: searchQuery.query,
answer,
});
}
setSearchQuery({ query: '' });
setAnswer('');
}, [answer, done, searchQuery.query]);
return (
<div className={'flex h-screen w-screen flex-col items-center justify-center gap-8 py-10'}>
<div className={'flex h-[48px] w-full items-center justify-end gap-2 px-10'}>
<Tooltip title={'Clear all conversation'}>
<button>
<ReloadICON
className={'h-6 w-6 cursor-pointer hover:text-content-blue-400'}
onClick={() => {
conversationRef.current = [];
setAnswer('');
setSearchQuery({ query: '' });
setDone(false);
setApiKey(localStorage.getItem('openai-api-key') || '');
}}
/>
</button>
</Tooltip>
</div>
<AFScroller className={'flex flex-1 flex-col items-center gap-4'}>
{conversationRef.current?.map((item, index) => (
<Answer key={index} query={item.query} answer={item.answer} done={done} />
))}
{answer && <Answer query={searchQuery.query} answer={answer} done={done} />}
</AFScroller>
<div className={'h-[64px] w-[80%]'}>
{apiKey ? (
<Search
apiKey={apiKey}
onSearch={setSearchQuery}
onAnswerUpdate={(value) => {
setAnswer((prev) => prev + value);
}}
done={done}
onDone={setDone}
/>
) : (
<div className={'flex items-center justify-center gap-4'}>
<input
placeholder={'Enter your API Key'}
value={apiKeyValue}
onChange={(e) => setApiKeyValue(e.target.value)}
className={'h-12 w-full rounded-md border border-gray-300 px-4'}
/>
<button
onClick={() => {
setApiKey(apiKeyValue);
localStorage.setItem('openai-api-key', apiKeyValue);
setApiKeyValue('');
}}
className={'h-12 w-[100px] rounded-md bg-blue-400 text-white'}
>
Save
</button>
</div>
)}
</div>
</div>
);
}
export default Chat;

View File

@ -0,0 +1,81 @@
import { fetchData } from '@/components/chat/fetch';
import CircularProgress from '@mui/material/CircularProgress';
import { SearchQuery } from './types';
import { FC, useRef, useState } from 'react';
import { ReactComponent as IconArrowRight } from '$icons/16x/arrow_right.svg';
import { ReactComponent as IconSearch } from '$icons/16x/search.svg';
interface SearchProps {
onSearch: (searchResult: SearchQuery) => void;
onAnswerUpdate: (answer: string) => void;
onDone: (done: boolean) => void;
done: boolean;
apiKey: string;
}
export const Search: FC<SearchProps> = ({ apiKey, onSearch, onAnswerUpdate, onDone }) => {
const inputRef = useRef<HTMLInputElement>(null);
const [query, setQuery] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const handleSearch = async () => {
if (!query) {
alert('Please enter a query');
return;
}
onDone(false);
onSearch({ query });
setLoading(true);
await fetchData(apiKey, query, {
onAnswerUpdate,
onDone: (done) => {
console.log('done', done);
onDone(done);
setLoading(false);
setQuery('');
},
});
};
return (
<>
<div className='mx-auto flex h-full w-full flex-col items-center'>
<div className='relative w-full'>
<IconSearch className='text=[#D4D4D8] absolute top-3 left-1 h-6 w-10 rounded-full opacity-50 sm:left-3 sm:top-4 sm:h-8' />
<input
ref={inputRef}
readOnly={loading}
style={{
cursor: loading ? 'not-allowed' : 'text',
color: loading ? '#D4D4D8' : '#000',
}}
className='bg-body h-12 w-full rounded-full border border-line-border pr-12 pl-11 focus:border-zinc-800 focus:bg-content-blue-50 focus:outline-none focus:ring-2 focus:ring-zinc-800 sm:h-16 sm:py-2 sm:pr-16 sm:pl-16 sm:text-lg'
type='text'
placeholder='Ask anything...'
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
void handleSearch();
}
}}
/>
<button
className='absolute flex items-center justify-center hover:cursor-pointer sm:right-3 sm:top-3 sm:h-10 sm:w-10'
onClick={handleSearch}
disabled={loading}
>
{loading ? (
<CircularProgress size={24} />
) : (
<IconArrowRight className='h-7 w-7 rounded-full bg-content-blue-400 p-1 text-content-on-fill hover:bg-content-blue-600' />
)}
</button>
</div>
</div>
</>
);
};

View File

@ -0,0 +1,97 @@
import { OpenAIModel } from '@/components/chat/types';
const url = 'https://api.openai.com/v1/chat/completions';
// Create an AbortController to control and cancel the fetch request when the user hits the stop button
const controller = new AbortController();
export function stopFetch() {
controller.abort();
}
export async function fetchData(
apiKey: string,
searchContent: string,
{
onAnswerUpdate,
onDone,
}: {
onAnswerUpdate: (line: string) => void;
onDone: (done: boolean) => void;
}
) {
const prompt = `Generate a comprehensive summary in Markdown format on the topic of ${searchContent}. Include sections for definition, importance, current technologies, challenges, and future perspectives. Use headers, bullet points, and links where appropriate.`;
// Make a POST request to the OpenAI API to get chat completions
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
messages: [
{
role: 'user',
content: prompt,
},
],
temperature: 0.6,
model: OpenAIModel.DAVINCI_TURBO,
// Limiting the tokens during development
max_tokens: 150,
stream: true,
}),
// Use the AbortController's signal to allow aborting the request
// This is a `fetch()` API thing, not an OpenAI thing
signal: controller.signal,
});
if (!response.body) {
return;
}
const data = response.body;
if (!data) {
return;
}
// Create a TextDecoder to decode the response body stream
const decoder = new TextDecoder();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for await (const chunk of response.body as any) {
const decodedChunk = decoder.decode(chunk);
const isDone = decodedChunk.includes('[DONE]');
if (isDone) {
onDone(true);
break;
}
// Clean up the data
const lines = decodedChunk
.split('\n')
.map((line) => line.replace('data: ', ''))
.filter((line) => line.length > 0)
.filter((line) => line !== '[DONE]')
.map((line) => JSON.parse(line));
// Destructuring!
for (const line of lines) {
const {
choices: [
{
delta: { content },
},
],
} = line;
if (content) {
onAnswerUpdate(content);
}
}
}
}

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const Chat = lazy(() => import('./Chat'));

View File

@ -0,0 +1,62 @@
.markdown-content {
& h1, & h2, & h3, & h4, & h5, & h6 {
@apply text-gray-800 font-semibold;
}
& h1 {
@apply text-3xl my-6;
}
& h2 {
@apply text-2xl my-4;
}
& h3 {
@apply text-xl my-3;
}
& p, & ul, & ol {
@apply text-base leading-relaxed text-gray-700;
}
& a {
@apply text-blue-500 hover:text-blue-700;
}
& strong {
@apply font-bold;
}
& em {
@apply italic;
}
& blockquote {
@apply border-l-4 border-gray-200 pl-4 italic text-gray-500;
}
& ul, & ol {
@apply pl-5 list-decimal;
}
& ul {
@apply list-disc;
}
& code {
@apply bg-gray-100 text-gray-900 p-2 rounded;
}
& pre {
@apply bg-gray-200 text-gray-800 p-3 overflow-x-auto;
}
& img {
@apply my-4 mx-auto max-w-full h-auto;
}
& hr {
@apply my-4 border-t border-gray-300;
}
}

View File

@ -0,0 +1,12 @@
export enum OpenAIModel {
DAVINCI_TURBO = 'gpt-3.5-turbo',
}
export type Source = {
url: string;
text: string;
};
export type SearchQuery = {
query: string;
};

View File

@ -0,0 +1,8 @@
import { Chat } from '@/components/chat';
import React from 'react';
function AfChatPage() {
return <Chat />;
}
export default AfChatPage;

View File

@ -9,4 +9,5 @@ interface Window {
WebFont?: {
load: (options: { google: { families: string[] } }) => void;
};
markdownit: () => any;
}

View File

@ -16,5 +16,4 @@ module.exports = {
boxShadow,
},
},
plugins: [],
};