mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: support openai chat on web
This commit is contained in:
parent
86696b271e
commit
0c1a9959f2
@ -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.).
|
||||
|
||||
- **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.
|
||||
|
||||
Let's dive in and get the project up and running! 🚀
|
||||
|
||||
## 🛠 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, make sure you have the following installed on your system:
|
||||
|
||||
- [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) 🧪
|
||||
|
||||
### Clone the Repository
|
||||
|
||||
First, clone the repository to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/AppFlowy-IO/AppFlowy
|
||||
git clone https://github.com/AppFlowy-IO/AppFlowy.git
|
||||
cd frontend/appflowy_web_app
|
||||
```
|
||||
|
||||
#### 🌐 Install the frontend dependencies:
|
||||
### Install Dependencies
|
||||
|
||||
Install the required dependencies using pnpm:
|
||||
|
||||
```bash
|
||||
cd frontend/appflowy_web_app
|
||||
## ensure you have pnpm installed, if not run the following command
|
||||
# npm install -g pnpm@8.5.0
|
||||
|
||||
pnpm install
|
||||
```
|
||||
|
||||
#### 🖥️ Desktop Application (Tauri) (Optional)
|
||||
### Start the Development Server
|
||||
|
||||
> **Note**: if you want to run the web app in the browser, skip this step
|
||||
|
||||
- Follow the instructions [here](https://tauri.app/v1/guides/getting-started/prerequisites/) to install Tauri
|
||||
|
||||
##### Windows and Linux Prerequisites
|
||||
|
||||
###### Windows only
|
||||
|
||||
- Install the Duckscript CLI and vcpkg
|
||||
|
||||
```bash
|
||||
cargo install --force duckscript_cli
|
||||
vcpkg integrate install
|
||||
```
|
||||
|
||||
###### Linux only
|
||||
|
||||
- Install the required dependencies
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
```
|
||||
|
||||
- **Get error**: failed to run custom build command for librocksdb-sys v6.11.4
|
||||
|
||||
```bash
|
||||
sudo apt install clang
|
||||
```
|
||||
|
||||
##### Install Tauri Dependencies
|
||||
|
||||
- Install cargo-make
|
||||
|
||||
```bash
|
||||
cargo install --force cargo-make
|
||||
```
|
||||
|
||||
|
||||
- Install AppFlowy dev tools
|
||||
|
||||
```bash
|
||||
# install development tools
|
||||
# make sure you are in the root directory of the project
|
||||
cd frontend
|
||||
cargo make appflowy-tauri-deps-tools
|
||||
```
|
||||
|
||||
- Build the service/dependency
|
||||
|
||||
```bash
|
||||
# make sure you are in the root directory of the project
|
||||
cd frontend/appflowy_web_app
|
||||
mkdir dist
|
||||
cd src-tauri
|
||||
cargo build
|
||||
```
|
||||
|
||||
### 🚀 Running the Application
|
||||
|
||||
#### 🌐 Web Application
|
||||
|
||||
- Run the web application
|
||||
To start the development server, run the following command:
|
||||
|
||||
```bash
|
||||
pnpm run dev
|
||||
```
|
||||
- Open your browser and navigate to `http://localhost:3000`, You can now interact with the AppFlowy web application
|
||||
|
||||
#### 🖥️ Desktop Application (Tauri)
|
||||
### 🚀 Building for Production(Optional)
|
||||
|
||||
**Ensure close web application before running the desktop application**
|
||||
|
||||
- Run the desktop application
|
||||
if you want to run the production build, use the following commands
|
||||
|
||||
```bash
|
||||
pnpm run tauri:dev
|
||||
pnpm run build
|
||||
pnpm run start
|
||||
```
|
||||
- The AppFlowy desktop application will open, and you can interact with it
|
||||
|
||||
### 🛠️ Development
|
||||
This will start the application in development mode. Open http://localhost:3000 to view it in the browser.
|
||||
|
||||
#### How to add or modify i18n keys
|
||||
## 🧪 Running Tests
|
||||
|
||||
- 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
|
||||
### Unit Tests
|
||||
|
||||
We use **Jest** for running unit tests. To run the tests, use the following command:
|
||||
|
||||
```bash
|
||||
pnpm run sync:i18n
|
||||
pnpm run test:unit
|
||||
```
|
||||
|
||||
#### How to modify the theme
|
||||
This will execute all the unit tests in the project and provide a summary of the results. ✅
|
||||
|
||||
Don't modify the theme file in `frontend/appflowy_web_app/src/styles/variables` directly)
|
||||
### Components Tests
|
||||
|
||||
- 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
|
||||
We use **Cypress** for end-to-end testing. To run the Cypress tests, use the following command:
|
||||
|
||||
```bash
|
||||
pnpm run css:variables
|
||||
pnpm run cypress:open
|
||||
```
|
||||
|
||||
#### How to add or modify the environment variables
|
||||
This will open the Cypress Test Runner where you can run your end-to-end tests. 🧪
|
||||
|
||||
- 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/).
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
30
frontend/appflowy_web_app/src/components/chat/Answer.tsx
Normal file
30
frontend/appflowy_web_app/src/components/chat/Answer.tsx
Normal 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>
|
||||
);
|
||||
};
|
114
frontend/appflowy_web_app/src/components/chat/Chat.tsx
Normal file
114
frontend/appflowy_web_app/src/components/chat/Chat.tsx
Normal 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;
|
81
frontend/appflowy_web_app/src/components/chat/Search.tsx
Normal file
81
frontend/appflowy_web_app/src/components/chat/Search.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
97
frontend/appflowy_web_app/src/components/chat/fetch.ts
Normal file
97
frontend/appflowy_web_app/src/components/chat/fetch.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
frontend/appflowy_web_app/src/components/chat/index.ts
Normal file
3
frontend/appflowy_web_app/src/components/chat/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const Chat = lazy(() => import('./Chat'));
|
62
frontend/appflowy_web_app/src/components/chat/markdown.scss
Normal file
62
frontend/appflowy_web_app/src/components/chat/markdown.scss
Normal 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;
|
||||
}
|
||||
}
|
12
frontend/appflowy_web_app/src/components/chat/types.ts
Normal file
12
frontend/appflowy_web_app/src/components/chat/types.ts
Normal 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;
|
||||
};
|
8
frontend/appflowy_web_app/src/pages/AFChatPage.tsx
Normal file
8
frontend/appflowy_web_app/src/pages/AFChatPage.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { Chat } from '@/components/chat';
|
||||
import React from 'react';
|
||||
|
||||
function AfChatPage() {
|
||||
return <Chat />;
|
||||
}
|
||||
|
||||
export default AfChatPage;
|
1
frontend/appflowy_web_app/src/vite-env.d.ts
vendored
1
frontend/appflowy_web_app/src/vite-env.d.ts
vendored
@ -9,4 +9,5 @@ interface Window {
|
||||
WebFont?: {
|
||||
load: (options: { google: { families: string[] } }) => void;
|
||||
};
|
||||
markdownit: () => any;
|
||||
}
|
||||
|
@ -16,5 +16,4 @@ module.exports = {
|
||||
boxShadow,
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user