Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46eb96c72e | ||
|
|
0540c2e46a | ||
|
|
d13b823065 | ||
|
|
71f58b791f | ||
|
|
4b1cc6878c | ||
|
|
c6a5f16336 | ||
|
|
7ed3c91ac6 | ||
|
|
1f801d1464 | ||
|
|
d0e65431d0 | ||
|
|
a21b2ccdd0 | ||
|
|
8767c576be | ||
|
|
fb08f61eb5 | ||
|
|
ce68791c3c | ||
|
|
3294be5e7f | ||
|
|
ec86847280 | ||
|
|
dd2d93c953 | ||
|
|
e60c36b423 | ||
|
|
1f112f7715 | ||
|
|
adbaa8b37b | ||
|
|
29c95d24ae | ||
|
|
e0b1a78344 | ||
|
|
2774940851 | ||
|
|
c2c73ed23c | ||
|
|
9682c82713 | ||
|
|
e8d4933dc4 | ||
|
|
0b6020a9cd | ||
|
|
894beee266 | ||
|
|
405e453ed3 | ||
|
|
79d289e25b | ||
|
|
51054f5829 | ||
|
|
317fba1855 | ||
|
|
f61e467d04 | ||
|
|
27de1cad47 | ||
|
|
3ea2cf1dcb | ||
|
|
4397a0ad6b | ||
|
|
4f51839026 | ||
|
|
3c0fa30aaf | ||
|
|
02abe42afe | ||
|
|
088a90de10 | ||
|
|
a98c56f968 | ||
|
|
1e5714da1b | ||
|
|
867d69659f | ||
|
|
d44203bff1 | ||
|
|
629a147741 | ||
|
|
9e951fbc15 | ||
|
|
a540ee944a | ||
|
|
b064e704f3 | ||
|
|
7e54421190 | ||
|
|
647f701692 | ||
|
|
0db413ab52 | ||
|
|
426eceac22 | ||
|
|
1ee527ceb8 | ||
|
|
03f1ab1a2f | ||
|
|
faf722fa15 | ||
|
|
36dad6df33 | ||
|
|
6ff5db7b41 | ||
|
|
56a0b48b97 | ||
|
|
ff24042df5 | ||
|
|
c31d247f07 | ||
|
|
e903eb5b94 | ||
|
|
c605964fa8 | ||
|
|
1fe5cd751a | ||
|
|
488e2f476e | ||
|
|
915b104b8a | ||
|
|
aaa350a13e | ||
|
|
6a2b34cb92 | ||
|
|
7f26b31f53 | ||
|
|
2a597964a2 | ||
|
|
c1d3a46dc7 | ||
|
|
0c55beb72d | ||
|
|
9b1c0e1a3c | ||
|
|
a7988c164e | ||
|
|
99e5fbd0f5 | ||
|
|
5e4c4dd79b | ||
|
|
70584783a5 | ||
|
|
705ac1c27e | ||
|
|
52d00d0562 | ||
|
|
9a145f223f | ||
|
|
b7cd4dec89 | ||
|
|
33154a9c19 | ||
|
|
e1c7503611 |
@@ -8,3 +8,4 @@ README.md
|
||||
|
||||
.yalc/
|
||||
yalc.lock
|
||||
testApi/
|
||||
@@ -1,8 +1,27 @@
|
||||
AXIOS_PROXY_HOST=127.0.0.1
|
||||
AXIOS_PROXY_PORT=33210
|
||||
MONGODB_URI=
|
||||
MY_MAIL=
|
||||
MAILE_CODE=
|
||||
TOKEN_KEY=
|
||||
OPENAIKEY=
|
||||
REDIS_URL=
|
||||
# proxy
|
||||
# AXIOS_PROXY_HOST=127.0.0.1
|
||||
# AXIOS_PROXY_PORT=7890
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# OPENAI_BASE_URL_AUTH=可选的安全凭证
|
||||
# 是否开启队列任务。 1-开启,0-关闭(请求parentUrl去执行任务,单机时直接填1)
|
||||
queueTask=1
|
||||
parentUrl=https://hostname/api/openapi/startEvents
|
||||
# email
|
||||
MY_MAIL=xxx@qq.com
|
||||
MAILE_CODE=xxx
|
||||
# ali ems
|
||||
aliAccessKeyId=xxx
|
||||
aliAccessKeySecret=xxx
|
||||
aliSignName=xxx
|
||||
aliTemplateCode=SMS_xxx
|
||||
# token
|
||||
TOKEN_KEY=xxx
|
||||
# openai
|
||||
OPENAIKEY=sk-xxx
|
||||
# db
|
||||
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
|
||||
PG_HOST=0.0.0.0
|
||||
PG_PORT=8100
|
||||
PG_USER=xxx
|
||||
PG_PASSWORD=xxx
|
||||
PG_DB_NAME=xxx
|
||||
50
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y nodejs npm
|
||||
- # Add support for more platforms with QEMU (optional)
|
||||
# https://github.com/docker/setup-qemu-action
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Login to gitbub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
- name: build and publish image
|
||||
env:
|
||||
# fork friendly ^^
|
||||
DOCKER_REPO: ghcr.io/${{ github.repository_owner }}/fast-gpt
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
|
||||
--label "org.opencontainers.image.description=fast-gpt image" \
|
||||
--label "org.opencontainers.image.licenses=MIT" \
|
||||
--push \
|
||||
-t ${DOCKER_REPO}:latest \
|
||||
-f Dockerfile \
|
||||
.
|
||||
1
.gitignore
vendored
@@ -34,7 +34,6 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/public/trainData/
|
||||
/.vscode/
|
||||
platform.json
|
||||
testApi/
|
||||
@@ -54,13 +54,4 @@ USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV MAX_USER ''
|
||||
ENV AXIOS_PROXY_HOST ''
|
||||
ENV AXIOS_PROXY_PORT ''
|
||||
ENV MONGODB_URI ''
|
||||
ENV MY_MAIL ''
|
||||
ENV MAILE_CODE ''
|
||||
ENV TOKEN_KEY ''
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
2
Makefile
@@ -34,7 +34,7 @@ run: ## Run a dev service from host.
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build: ## Build docker image with the desktop-frontend.
|
||||
docker build -t c121914yu/fast-gpt:latest .
|
||||
docker build -t c121914yu/fast-gpt:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890
|
||||
|
||||
##@ Deployment
|
||||
|
||||
|
||||
325
README.md
@@ -1,60 +1,227 @@
|
||||
# Fast GPT
|
||||
|
||||
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,包括 GPT3 及其微调方法,以及最新的 gpt3.5 接口。
|
||||
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 gpt35 和 embedding. 可构建自己的知识库。
|
||||
|
||||
## 初始化
|
||||
复制 .env.template 成 .env.local ,填写核心参数
|
||||
|
||||
```
|
||||
AXIOS_PROXY_HOST=axios代理地址,目前 openai 接口都需要走代理,本机的话就填 127.0.0.1
|
||||
AXIOS_PROXY_PORT=代理端口
|
||||
MONGODB_URI=mongo数据库地址
|
||||
MY_MAIL=发送验证码邮箱
|
||||
MAILE_CODE=邮箱秘钥(代理里设置的是QQ邮箱,不知道怎么找这个 code 的,可以百度搜"nodemailer发送邮件")
|
||||
TOKEN_KEY=随便填一个,用于生成和校验 token
|
||||
OPENAIKEY=openai的key
|
||||
REDIS_URL=redis的地址
|
||||
```
|
||||
## 知识库原理
|
||||

|
||||
|
||||
## 开发
|
||||
**配置环境变量**
|
||||
```bash
|
||||
# proxy(可选)
|
||||
AXIOS_PROXY_HOST=127.0.0.1
|
||||
AXIOS_PROXY_PORT=7890
|
||||
# openai 中转连接(可选)
|
||||
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
OPENAI_BASE_URL_AUTH=可选的安全凭证
|
||||
# 是否开启队列任务。 1-开启,0-关闭(请求 parentUrl 去执行任务,单机时直接填1)
|
||||
queueTask=1
|
||||
parentUrl=https://hostname/api/openapi/startEvents
|
||||
# 发送邮箱验证码配置。参考 nodeMail 获取参数,自行百度。
|
||||
MY_MAIL=xxx@qq.com
|
||||
MAILE_CODE=xxx
|
||||
# 阿里短信服务(邮箱和短信至少二选一)
|
||||
aliAccessKeyId=xxx
|
||||
aliAccessKeySecret=xxx
|
||||
aliSignName=xxx
|
||||
aliTemplateCode=SMS_xxx
|
||||
# token(随便填,作为登录凭证)
|
||||
TOKEN_KEY=xxx
|
||||
# openai key
|
||||
OPENAIKEY=sk-xxx
|
||||
# mongo连接地址
|
||||
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
|
||||
# mongo数据库名称
|
||||
MONGODB_NAME=xxx
|
||||
# pg 数据库相关内容,和 docker-compose 对上
|
||||
PG_HOST=0.0.0.0
|
||||
PG_PORT=8102
|
||||
PG_USER=xxx
|
||||
PG_PASSWORD=xxx
|
||||
PG_DB_NAME=xxx
|
||||
```
|
||||
**运行**
|
||||
```
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
### docker 模式
|
||||
请准备好 docker, mongo,代理, 和 nginx。 镜像走本机的代理,所以用 network=host,port 改成代理的端口,clash 一般都是 7890。
|
||||
### 代理环境(国外服务器可忽略)
|
||||
1. [clash 方案](./docs/proxy/clash.md) - 仅需一台服务器(需要有 clash)
|
||||
2. [nginx 方案](./docs/proxy/nginx.md) - 需要一台国外服务器
|
||||
3. [cloudflare 方案](./docs/proxy/cloudflare.md) - 需要有域名(每日免费 10w 次代理请求)
|
||||
|
||||
#### docker 打包
|
||||
### docker 部署
|
||||
#### 1. 安装 docker 和 docker-compose
|
||||
这个不同系统略有区别,百度安装下。验证安装成功后进行下一步。下面给出一个例子:
|
||||
```bash
|
||||
docker build -t imageName:tag .
|
||||
docker push imageName:tag
|
||||
# 或者直接拉镜像,见下方
|
||||
# 安装docker
|
||||
curl -L https://get.daocloud.io/docker | sh
|
||||
sudo systemctl start docker
|
||||
# 安装 docker-compose
|
||||
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
# 验证安装
|
||||
docker -v
|
||||
docker-compose -v
|
||||
```
|
||||
|
||||
#### 服务器拉取镜像和运行
|
||||
|
||||
#### 2. 创建3个初始化文件
|
||||
手动创建或者直接把 deploy 里内容复制过去
|
||||
|
||||
**/root/fast-gpt/pg/init.sql**
|
||||
```sql
|
||||
set -e
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
|
||||
CREATE EXTENSION vector;
|
||||
-- init table
|
||||
CREATE TABLE modelData (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
vector VECTOR(1536),
|
||||
status VARCHAR(50) NOT NULL,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
model_id VARCHAR(50) NOT NULL,
|
||||
q TEXT NOT NULL,
|
||||
a TEXT NOT NULL
|
||||
);
|
||||
-- create index
|
||||
CREATE INDEX modelData_status_index ON modelData USING HASH (status);
|
||||
CREATE INDEX modelData_userId_index ON modelData USING HASH (user_id);
|
||||
CREATE INDEX modelData_modelId_index ON modelData USING HASH (model_id);
|
||||
EOSQL
|
||||
```
|
||||
|
||||
**/root/fast-gpt/nginx/nginx.conf**
|
||||
```conf
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 51200;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log off;
|
||||
server_names_hash_bucket_size 512;
|
||||
client_header_buffer_size 32k;
|
||||
large_client_header_buffers 4 32k;
|
||||
client_max_body_size 50M;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 6;
|
||||
gzip_vary on;
|
||||
gzip_types text/plain application/x-javascript text/css application/javascript application/json application/xml;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
open_file_cache max=1000 inactive=1d;
|
||||
open_file_cache_valid 30s;
|
||||
open_file_cache_min_uses 8;
|
||||
open_file_cache_errors off;
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name docgpt.ahapocket.cn;
|
||||
ssl_certificate /ssl/docgpt.pem;
|
||||
ssl_certificate_key /ssl/docgpt.key;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name docgpt.ahapocket.cn;
|
||||
rewrite ^(.*) https://$server_name$1 permanent;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**/root/fast-gpt/docker-compose.yml**
|
||||
```yml
|
||||
# docker-compose
|
||||
version: "3.3"
|
||||
services:
|
||||
fast-gpt:
|
||||
image: c121914yu/fast-gpt:latest
|
||||
environment:
|
||||
AXIOS_PROXY_HOST: 127.0.0.1
|
||||
AXIOS_PROXY_PORT: 7890
|
||||
MY_MAIL:
|
||||
MAILE_CODE:
|
||||
TOKEN_KEY:
|
||||
MONGODB_URI:
|
||||
OPENAIKEY:
|
||||
REDIS_URL:
|
||||
network_mode: host
|
||||
restart: always
|
||||
container_name: fast-gpt
|
||||
environment:
|
||||
# - AXIOS_PROXY_HOST=127.0.0.1
|
||||
# - AXIOS_PROXY_PORT=7890
|
||||
# - OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# - OPENAI_BASE_URL_AUTH=可选的安全凭证
|
||||
- MY_MAIL=xxxx@qq.com
|
||||
- MAILE_CODE=xxxx
|
||||
- aliAccessKeyId=xxxx
|
||||
- aliAccessKeySecret=xxxx
|
||||
- aliSignName=xxxxx
|
||||
- aliTemplateCode=SMS_xxxx
|
||||
- TOKEN_KEY=xxxx
|
||||
- queueTask=1
|
||||
- parentUrl=https://hostname/api/openapi/startEvents
|
||||
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
|
||||
- MONGODB_NAME=xxx
|
||||
- PG_HOST=0.0.0.0
|
||||
- PG_PORT=8100
|
||||
- PG_USER=xxx
|
||||
- PG_PASSWORD=xxx
|
||||
- PG_DB_NAME=xxx
|
||||
- OPENAIKEY=sk-xxxxx
|
||||
nginx:
|
||||
image: nginx:alpine3.17
|
||||
container_name: nginx
|
||||
restart: always
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /root/fast-gpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- /root/fast-gpt/nginx/logs:/var/log/nginx
|
||||
- /root/fast-gpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
|
||||
- /root/fast-gpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
|
||||
pg:
|
||||
image: ankane/pgvector
|
||||
container_name: pg
|
||||
restart: always
|
||||
ports:
|
||||
- 8100:5432
|
||||
environment:
|
||||
- POSTGRES_USER=xxx
|
||||
- POSTGRES_PASSWORD=xxx
|
||||
- POSTGRES_DB=xxx
|
||||
volumes:
|
||||
- /root/fast-gpt/pg/data:/var/lib/postgresql/data
|
||||
- /root/fast-gpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
mongodb:
|
||||
image: mongo:4.0.1
|
||||
container_name: mongo
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=username
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
volumes:
|
||||
- /root/fast-gpt/mongo/data:/data/db
|
||||
- /root/fast-gpt/mongo/logs:/var/log/mongodb
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
```
|
||||
|
||||
#### 3. 运行 docker-compose
|
||||
下面是一个辅助脚本,也可以直接 docker-compose up -d
|
||||
**run.sh 运行文件**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 拉取最新镜像
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
|
||||
@@ -75,91 +242,15 @@ do
|
||||
done
|
||||
```
|
||||
|
||||
#### 软件教程:docker 安装
|
||||
```bash
|
||||
# 安装docker
|
||||
curl -sSL https://get.daocloud.io/docker | sh
|
||||
sudo systemctl start docker
|
||||
```
|
||||
|
||||
#### 软件教程:mongo 安装
|
||||
```bash
|
||||
docker pull mongo:6.0.4
|
||||
docker stop mongo
|
||||
docker rm mongo
|
||||
docker run -d --name mongo \
|
||||
-e MONGO_INITDB_ROOT_USERNAME= \
|
||||
-e MONGO_INITDB_ROOT_PASSWORD= \
|
||||
-v /root/service/mongo:/data/db \
|
||||
mongo:6.0.4
|
||||
## 其他优化点
|
||||
### Git Action 自动打包镜像
|
||||
.github里拥有一个 git 提交到 main 分支时自动打包 amd64 和 arm64 镜像的 actions。你仅需要提前在 git 配置好 session。
|
||||
|
||||
# 检查 mongo 运行情况, 有成功的 logs 代表访问成功
|
||||
docker logs mongo
|
||||
```
|
||||
#### 软件教程: clash 代理
|
||||
```bash
|
||||
# 下载包
|
||||
curl https://glados.rocks/tools/clash-linux.zip -o clash.zip
|
||||
# 解压
|
||||
unzip clash.zip
|
||||
# 下载终端配置⽂件(改成自己配置文件路径)
|
||||
curl https://update.glados-config.com/clash/98980/8f30944/70870/glados-terminal.yaml > config.yaml
|
||||
# 赋予运行权限
|
||||
chmod +x ./clash-linux-amd64-v1.10.0
|
||||
# 记得配置端口变量:
|
||||
export ALL_PROXY=socks5://127.0.0.1:7891
|
||||
export http_proxy=http://127.0.0.1:7890
|
||||
export https_proxy=http://127.0.0.1:7890
|
||||
export HTTP_PROXY=http://127.0.0.1:7890
|
||||
export HTTPS_PROXY=http://127.0.0.1:7890
|
||||
1. 创建账号 session: 头像 -> settings -> 最底部 Developer settings -> Personal access tokens -> tokens(classic) -> 创建新 session,把一些看起来需要的权限勾上。
|
||||
2. 添加 session 到仓库: 仓库 -> settings -> Secrets and variables -> Actions -> 创建secret
|
||||
3. 填写 secret: Name-GH_PAT, Secret-第一步的tokens
|
||||
|
||||
# 运行脚本: 删除clash - 到 clash 目录 - 删除缓存 - 执行运行
|
||||
# 会生成一个 nohup.out 文件,可以看到 clash 的 logs
|
||||
OLD_PROCESS=$(pgrep clash)
|
||||
if [ ! -z "$OLD_PROCESS" ]; then
|
||||
echo "Killing old process: $OLD_PROCESS"
|
||||
kill $OLD_PROCESS
|
||||
fi
|
||||
sleep 2
|
||||
cd **/clash
|
||||
rm -f ./nohup.out || true
|
||||
rm -f ./cache.db || true
|
||||
nohup ./clash-linux-amd64-v1.10.0 -d ./ &
|
||||
echo "Restart clash"
|
||||
```
|
||||
|
||||
#### 软件教程:Nginx
|
||||
...没写,这个百度吧。
|
||||
|
||||
#### redis
|
||||
|
||||
安装
|
||||
```bash
|
||||
#!/bin/bash
|
||||
docker pull redis/redis-stack:6.2.6-v6
|
||||
docker stop fast-gpt-redis-stack
|
||||
docker rm fast-gpt-redis-stack
|
||||
|
||||
docker run -d --name fast-gpt-redis-stack \
|
||||
-v /redis/data:/data \
|
||||
-v /etc/localtime:/etc/localtime:ro \
|
||||
-v /redis.conf:/redis-stack.conf \
|
||||
-e REDIS_ARGS="--requirepass 1111111"\
|
||||
-p 8102:6379 \
|
||||
-p 8103:8001 \
|
||||
--restart unless-stopped \
|
||||
redis/redis-stack:6.2.6-v6
|
||||
```
|
||||
```bash
|
||||
# /redis.conf
|
||||
# 开启aop持久化
|
||||
appendonly yes
|
||||
#default: 持久化文件
|
||||
appendfilename "appendonly.aof"
|
||||
#default: 每秒同步一次
|
||||
appendfsync everysec
|
||||
```
|
||||
```bash
|
||||
# 添加索引
|
||||
FT.CREATE idx:model:data:hash ON HASH PREFIX 1 model:data: SCHEMA modelId TAG userId TAG status TAG q TEXT text TEXT vector VECTOR FLAT 6 DIM 1536 DISTANCE_METRIC COSINE TYPE FLOAT32
|
||||
```
|
||||
## 其他问题
|
||||
### Mac 可能的问题
|
||||
> 因为教程有部分镜像不兼容arm64,所以写个文档指导新手如何快速在mac上面搭建fast-gpt[如何在mac上面部署fastgpt](./docs/mac.md)
|
||||
|
||||
66
deploy/docker-compose.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
fast-gpt:
|
||||
image: c121914yu/fast-gpt:latest
|
||||
network_mode: host
|
||||
restart: always
|
||||
container_name: fast-gpt
|
||||
environment:
|
||||
# - AXIOS_PROXY_HOST=127.0.0.1
|
||||
# - AXIOS_PROXY_PORT=7890
|
||||
# - OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
# - OPENAI_BASE_URL_AUTH=可选的安全凭证
|
||||
- MY_MAIL=xxxx@qq.com
|
||||
- MAILE_CODE=xxxx
|
||||
- aliAccessKeyId=xxxx
|
||||
- aliAccessKeySecret=xxxx
|
||||
- aliSignName=xxxxx
|
||||
- aliTemplateCode=SMS_xxxx
|
||||
- TOKEN_KEY=xxxx
|
||||
- queueTask=1
|
||||
- parentUrl=https://hostname/api/openapi/startEvents
|
||||
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
|
||||
- MONGODB_NAME=xxx
|
||||
- PG_HOST=0.0.0.0
|
||||
- PG_PORT=8100
|
||||
- PG_USER=xxx
|
||||
- PG_PASSWORD=xxx
|
||||
- PG_DB_NAME=xxx
|
||||
- OPENAIKEY=sk-xxxxx
|
||||
nginx:
|
||||
image: nginx:alpine3.17
|
||||
container_name: nginx
|
||||
restart: always
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /root/fast-gpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- /root/fast-gpt/nginx/logs:/var/log/nginx
|
||||
- /root/fast-gpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
|
||||
- /root/fast-gpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
|
||||
pg:
|
||||
image: ankane/pgvector
|
||||
container_name: pg
|
||||
restart: always
|
||||
ports:
|
||||
- 8100:5432
|
||||
environment:
|
||||
- POSTGRES_USER=xxx
|
||||
- POSTGRES_PASSWORD=xxx
|
||||
- POSTGRES_DB=xxx
|
||||
volumes:
|
||||
- /root/fast-gpt/pg/data:/var/lib/postgresql/data
|
||||
- /root/fast-gpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
mongodb:
|
||||
image: mongo:4.0.1
|
||||
container_name: mongo
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=username
|
||||
- MONGO_INITDB_ROOT_PASSWORD=password
|
||||
volumes:
|
||||
- /root/fast-gpt/mongo/data:/data/db
|
||||
- /root/fast-gpt/mongo/logs:/var/log/mongodb
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
49
deploy/nginx/nginx.conf
Normal file
@@ -0,0 +1,49 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 51200;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log off;
|
||||
server_names_hash_bucket_size 512;
|
||||
client_header_buffer_size 32k;
|
||||
large_client_header_buffers 4 32k;
|
||||
client_max_body_size 50M;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 6;
|
||||
gzip_vary on;
|
||||
gzip_types text/plain application/x-javascript text/css application/javascript application/json application/xml;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
open_file_cache max=1000 inactive=1d;
|
||||
open_file_cache_valid 30s;
|
||||
open_file_cache_min_uses 8;
|
||||
open_file_cache_errors off;
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name docgpt.ahapocket.cn;
|
||||
ssl_certificate /ssl/docgpt.pem;
|
||||
ssl_certificate_key /ssl/docgpt.key;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name docgpt.ahapocket.cn;
|
||||
rewrite ^(.*) https://$server_name$1 permanent;
|
||||
}
|
||||
}
|
||||
19
deploy/pg/init.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
set -e
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
|
||||
CREATE EXTENSION vector;
|
||||
-- init table
|
||||
CREATE TABLE modelData (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
vector VECTOR(1536),
|
||||
status VARCHAR(50) NOT NULL,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
model_id VARCHAR(50) NOT NULL,
|
||||
q TEXT NOT NULL,
|
||||
a TEXT NOT NULL
|
||||
);
|
||||
-- create index
|
||||
CREATE INDEX modelData_status_index ON modelData USING HASH (status);
|
||||
CREATE INDEX modelData_userId_index ON modelData USING HASH (user_id);
|
||||
CREATE INDEX modelData_modelId_index ON modelData USING HASH (model_id);
|
||||
EOSQL
|
||||
19
deploy/run.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
|
||||
echo "Docker Compose 重新拉取镜像完成!"
|
||||
|
||||
# 删除本地旧镜像
|
||||
images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fast-gpt)
|
||||
|
||||
# 将镜像 ID 和名称放入数组中
|
||||
IFS=$'\n' read -rd '' -a image_array <<<"$images"
|
||||
|
||||
# 遍历数组并删除所有旧的镜像
|
||||
for ((i=1; i<${#image_array[@]}; i++))
|
||||
do
|
||||
image=${image_array[$i]}
|
||||
image_id=${image%% *}
|
||||
docker rmi $image_id
|
||||
done
|
||||
BIN
docs/imgs/KBProcess.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
100
docs/mac.md
Normal file
@@ -0,0 +1,100 @@
|
||||
## 怎么在mac上面部署fastgpt
|
||||
|
||||
### 前置条件
|
||||
|
||||
1、可以 curl api.openai.com
|
||||
|
||||
2、有openai key
|
||||
|
||||
3、有邮箱MAILE_CODE
|
||||
|
||||
4、有docker
|
||||
|
||||
```
|
||||
docker -v
|
||||
```
|
||||
|
||||
5、有pnpm ,可以使用`brew install pnpm`安装
|
||||
|
||||
6、需要创建一个放置pg和mongo数据的文件夹,这里创建在`~/fastgpt`目录中,里面有`pg` 和`mongo `两个文件夹
|
||||
|
||||
```
|
||||
➜ fast-gpt pwd
|
||||
/Users/jie/fast-gpt
|
||||
➜ fast-gpt ls
|
||||
mongo pg
|
||||
```
|
||||
|
||||
|
||||
|
||||
### docker部署方式
|
||||
|
||||
这种方式主要是为了方便调试,可以使用`pnpm dev ` 运行fast-gpt项目
|
||||
|
||||
**1、.env.local 文件**
|
||||
|
||||
```
|
||||
# proxy
|
||||
AXIOS_PROXY_HOST=127.0.0.1
|
||||
AXIOS_PROXY_PORT_FAST=7890
|
||||
AXIOS_PROXY_PORT_NORMAL=7890
|
||||
queueTask=1
|
||||
# email
|
||||
MY_MAIL= {Your Mail}
|
||||
MAILE_CODE={Yoir Mail code}
|
||||
# ali ems
|
||||
aliAccessKeyId=xxx
|
||||
aliAccessKeySecret=xxx
|
||||
aliSignName=xxx
|
||||
aliTemplateCode=SMS_xxx
|
||||
# token
|
||||
TOKEN_KEY=sswada
|
||||
# openai
|
||||
OPENAIKEY={Your openapi key}
|
||||
# db
|
||||
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
|
||||
PG_HOST=0.0.0.0
|
||||
PG_PORT=8100
|
||||
PG_USER=xxx
|
||||
PG_PASSWORD=xxx
|
||||
PG_DB_NAME=xxx
|
||||
```
|
||||
|
||||
**2、部署mongo**
|
||||
|
||||
```
|
||||
docker run --name mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=username -e MONGO_INITDB_ROOT_PASSWORD=password -v ~/fast-gpt/mongo/data:/data/db -d mongo:4.0.1
|
||||
```
|
||||
|
||||
**3、部署pgsql**
|
||||
|
||||
```
|
||||
docker run -it --name pg -e "POSTGRES_PASSWORD=xxx" -e POSTGRES_USER=xxx -p 8100:5432 -v ~/fast-gpt/pg/data:/var/lib/postgresql/data -d octoberlan/pgvector:v0.4.1
|
||||
```
|
||||
|
||||
进pgsql容器运行
|
||||
|
||||
```
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
|
||||
CREATE EXTENSION vector;
|
||||
-- init table
|
||||
CREATE TABLE modelData (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
vector VECTOR(1536),
|
||||
status VARCHAR(50) NOT NULL,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
model_id VARCHAR(50) NOT NULL,
|
||||
q TEXT NOT NULL,
|
||||
a TEXT NOT NULL
|
||||
);
|
||||
-- create index
|
||||
CREATE INDEX modelData_status_index ON modelData (status);
|
||||
CREATE INDEX modelData_modelId_index ON modelData (modelId);
|
||||
CREATE INDEX modelData_userId_index ON modelData (userId);
|
||||
EOSQL
|
||||
```
|
||||
|
||||
|
||||
|
||||
4、**最后在FASTGPT项目里面运行pnpm dev 运行项目,然后进入localhost:3000 看项目是否跑起来了**
|
||||
68
docs/proxy/clash.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 安装 clash
|
||||
|
||||
clash 会在本机启动代理。对应的,你需要配置项目的两个环境变量:
|
||||
|
||||
```
|
||||
AXIOS_PROXY_HOST=127.0.0.1
|
||||
AXIOS_PROXY_PORT=7890
|
||||
```
|
||||
|
||||
需要注的是,在你的 config.yaml 文件中,最好仅指定 api.openai.com 走代理,其他请求都直连。
|
||||
|
||||
**安装clash**
|
||||
```bash
|
||||
# 下载包
|
||||
curl https://glados.rocks/tools/clash-linux.zip -o clash.zip
|
||||
# 解压
|
||||
unzip clash.zip
|
||||
# 下载终端配置⽂件(改成自己配置文件路径)
|
||||
curl https://update.glados-config.com/clash/98980/8f30944/70870/glados-terminal.yaml > config.yaml
|
||||
# 赋予运行权限
|
||||
chmod +x ./clash-linux-amd64-v1.10.0
|
||||
```
|
||||
|
||||
**runClash.sh**
|
||||
```sh
|
||||
# 记得配置端口变量:
|
||||
export ALL_PROXY=socks5://127.0.0.1:7891
|
||||
export http_proxy=http://127.0.0.1:7890
|
||||
export https_proxy=http://127.0.0.1:7890
|
||||
export HTTP_PROXY=http://127.0.0.1:7890
|
||||
export HTTPS_PROXY=http://127.0.0.1:7890
|
||||
|
||||
# 运行脚本: 删除clash - 到 clash 目录 - 删除缓存 - 执行运行. 会生成一个 nohup.out 文件,可以看到 clash 的 logs
|
||||
OLD_PROCESS=$(pgrep clash)
|
||||
if [ ! -z "$OLD_PROCESS" ]; then
|
||||
echo "Killing old process: $OLD_PROCESS"
|
||||
kill $OLD_PROCESS
|
||||
fi
|
||||
sleep 2
|
||||
cd **/clash
|
||||
rm -f ./nohup.out || true
|
||||
rm -f ./cache.db || true
|
||||
nohup ./clash-linux-amd64-v1.10.0 -d ./ &
|
||||
echo "Restart clash"
|
||||
```
|
||||
|
||||
**config.yaml配置例子**
|
||||
```yaml
|
||||
mixed-port: 7890
|
||||
allow-lan: false
|
||||
bind-address: '*'
|
||||
mode: rule
|
||||
log-level: warning
|
||||
dns:
|
||||
enable: true
|
||||
ipv6: false
|
||||
nameserver:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
||||
cache-size: 400
|
||||
proxies:
|
||||
-
|
||||
proxy-groups:
|
||||
- { name: '♻️ 自动选择', type: url-test, proxies: [香港V01×1.5], url: 'https://api.openai.com', interval: 3600}
|
||||
rules:
|
||||
- 'DOMAIN-SUFFIX,api.openai.com,♻️ 自动选择'
|
||||
- 'MATCH,DIRECT'
|
||||
```
|
||||
46
docs/proxy/cloudflare.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# cloudflare 代理配置
|
||||
|
||||
[来自 "不做了睡觉" 教程](https://gravel-twister-d32.notion.site/FastGPT-API-ba7bb261d5fd4fd9bbb2f0607dacdc9e)
|
||||
|
||||
**workers 配置文件**
|
||||
|
||||
```js
|
||||
const TELEGRAPH_URL = 'https://api.openai.com';
|
||||
|
||||
addEventListener('fetch', (event) => {
|
||||
event.respondWith(handleRequest(event.request));
|
||||
});
|
||||
|
||||
async function handleRequest(request) {
|
||||
// 安全校验
|
||||
if (request.headers.get('auth') !== 'auth_code') {
|
||||
return new Response('UnAuthorization', { status: 403 });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
url.host = TELEGRAPH_URL.replace(/^https?:\/\//, '');
|
||||
|
||||
const modifiedRequest = new Request(url.toString(), {
|
||||
headers: request.headers,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
redirect: 'follow'
|
||||
});
|
||||
|
||||
const response = await fetch(modifiedRequest);
|
||||
const modifiedResponse = new Response(response.body, response);
|
||||
|
||||
// 添加允许跨域访问的响应头
|
||||
modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');
|
||||
|
||||
return modifiedResponse;
|
||||
}
|
||||
```
|
||||
|
||||
**对应的环境变量**
|
||||
务必别忘了填 v1
|
||||
|
||||
```
|
||||
OPENAI_BASE_URL=https://xxxxxx/v1
|
||||
OPENAI_BASE_URL_AUTH=auth_code
|
||||
```
|
||||
72
docs/proxy/nginx.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# nginx 反向代理 openai 接口
|
||||
如果你有国外的服务器,可以通过配置 nginx 反向代理,转发 openai 相关的请求,从而让国内的服务器可以通过访问该 nginx 去访问 openai 接口。
|
||||
|
||||
```conf
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
worker_rlimit_nofile 51200;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
resolver 8.8.8.8;
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
access_log off;
|
||||
server_names_hash_bucket_size 512;
|
||||
client_header_buffer_size 32k;
|
||||
large_client_header_buffers 4 32k;
|
||||
client_max_body_size 50M;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_comp_level 6;
|
||||
gzip_vary on;
|
||||
gzip_types text/plain application/x-javascript text/css application/javascript application/json application/xml;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
open_file_cache max=1000 inactive=1d;
|
||||
open_file_cache_valid 30s;
|
||||
open_file_cache_min_uses 8;
|
||||
open_file_cache_errors off;
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name your_host;
|
||||
ssl_certificate /ssl/your_host.pem;
|
||||
ssl_certificate_key /ssl/your_host.key;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
location ~ /openai/(.*) {
|
||||
# auth check
|
||||
if ($http_authkey != "xxxxxx") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
proxy_pass https://api.openai.com/$1$is_args$args;
|
||||
proxy_set_header Host api.openai.com;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# 流式响应
|
||||
proxy_set_header Connection '';
|
||||
proxy_http_version 1.1;
|
||||
chunked_transfer_encoding off;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
# 一般响应
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
proxy_busy_buffers_size 256k;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name ai.fastgpt.run;
|
||||
rewrite ^(.*) https://$server_name$1 permanent;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,13 +1,15 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const path = require('path');
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
reactStrictMode: false,
|
||||
compress: true,
|
||||
|
||||
webpack(config) {
|
||||
config.experiments = {
|
||||
asyncWebAssembly: true,
|
||||
layers: true
|
||||
};
|
||||
config.module.rules = config.module.rules.concat([
|
||||
{
|
||||
test: /\.svg$/i,
|
||||
|
||||
12
package.json
@@ -11,8 +11,13 @@
|
||||
"format": "prettier --config \"./.prettierrc.js\" --write \"./src/**/*.{ts,tsx,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/dysmsapi20170525": "^2.0.23",
|
||||
"@alicloud/openapi-client": "^0.4.5",
|
||||
"@alicloud/tea-util": "^1.4.5",
|
||||
"@chakra-ui/icons": "^2.0.17",
|
||||
"@chakra-ui/react": "^2.5.1",
|
||||
"@chakra-ui/system": "^2.5.5",
|
||||
"@dqbd/tiktoken": "^1.0.6",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@next/font": "13.1.6",
|
||||
@@ -24,7 +29,7 @@
|
||||
"eventsource-parser": "^0.1.0",
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^9.0.6",
|
||||
"gpt-token-utils": "^1.2.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"hyperdown": "^2.4.29",
|
||||
"immer": "^9.0.19",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
@@ -36,6 +41,8 @@
|
||||
"nodemailer": "^6.9.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"openai": "^3.2.1",
|
||||
"papaparse": "^5.4.1",
|
||||
"pg": "^8.10.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.1",
|
||||
@@ -58,11 +65,12 @@
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "18.14.0",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@types/tunnel": "^0.0.3",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"husky": "^8.0.3",
|
||||
|
||||
471
pnpm-lock.yaml
generated
@@ -1,8 +1,13 @@
|
||||
lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@alicloud/dysmsapi20170525': ^2.0.23
|
||||
'@alicloud/openapi-client': ^0.4.5
|
||||
'@alicloud/tea-util': ^1.4.5
|
||||
'@chakra-ui/icons': ^2.0.17
|
||||
'@chakra-ui/react': ^2.5.1
|
||||
'@chakra-ui/system': ^2.5.5
|
||||
'@dqbd/tiktoken': ^1.0.6
|
||||
'@emotion/react': ^11.10.6
|
||||
'@emotion/styled': ^11.10.6
|
||||
'@next/font': 13.1.6
|
||||
@@ -14,11 +19,12 @@ specifiers:
|
||||
'@types/node': 18.14.0
|
||||
'@types/nodemailer': ^6.4.7
|
||||
'@types/nprogress': ^0.2.0
|
||||
'@types/papaparse': ^5.3.7
|
||||
'@types/pg': ^8.6.6
|
||||
'@types/react': 18.0.28
|
||||
'@types/react-dom': 18.0.11
|
||||
'@types/react-syntax-highlighter': ^15.5.6
|
||||
'@types/tunnel': ^0.0.3
|
||||
'@types/uuid': ^9.0.1
|
||||
axios: ^1.3.3
|
||||
crypto: ^1.0.1
|
||||
dayjs: ^1.11.7
|
||||
@@ -27,7 +33,7 @@ specifiers:
|
||||
eventsource-parser: ^0.1.0
|
||||
formidable: ^2.1.1
|
||||
framer-motion: ^9.0.6
|
||||
gpt-token-utils: ^1.2.0
|
||||
graphemer: ^1.4.0
|
||||
husky: ^8.0.3
|
||||
hyperdown: ^2.4.29
|
||||
immer: ^9.0.19
|
||||
@@ -41,6 +47,8 @@ specifiers:
|
||||
nodemailer: ^6.9.1
|
||||
nprogress: ^0.2.0
|
||||
openai: ^3.2.1
|
||||
papaparse: ^5.4.1
|
||||
pg: ^8.10.0
|
||||
prettier: ^2.8.4
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0
|
||||
@@ -59,8 +67,13 @@ specifiers:
|
||||
zustand: ^4.3.5
|
||||
|
||||
dependencies:
|
||||
'@chakra-ui/icons': registry.npmmirror.com/@chakra-ui/icons/2.0.17_react@18.2.0
|
||||
'@alicloud/dysmsapi20170525': registry.npmmirror.com/@alicloud/dysmsapi20170525/2.0.23
|
||||
'@alicloud/openapi-client': registry.npmmirror.com/@alicloud/openapi-client/0.4.5
|
||||
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
|
||||
'@chakra-ui/icons': registry.npmmirror.com/@chakra-ui/icons/2.0.17_lze4h7kxffpjhokvtqbtrlfkmq
|
||||
'@chakra-ui/react': registry.npmmirror.com/@chakra-ui/react/2.5.1_e6pzu3hsaqmql4fl7jx73ckiym
|
||||
'@chakra-ui/system': registry.npmmirror.com/@chakra-ui/system/2.5.5_xqp3pgpqjlfxxa3zxu4zoc4fba
|
||||
'@dqbd/tiktoken': registry.npmmirror.com/@dqbd/tiktoken/1.0.6
|
||||
'@emotion/react': registry.npmmirror.com/@emotion/react/11.10.6_pmekkgnqduwlme35zpnqhenc34
|
||||
'@emotion/styled': registry.npmmirror.com/@emotion/styled/11.10.6_oouaibmszuch5k64ms7uxp2aia
|
||||
'@next/font': registry.npmmirror.com/@next/font/13.1.6
|
||||
@@ -72,7 +85,7 @@ dependencies:
|
||||
eventsource-parser: registry.npmmirror.com/eventsource-parser/0.1.0
|
||||
formidable: registry.npmmirror.com/formidable/2.1.1
|
||||
framer-motion: registry.npmmirror.com/framer-motion/9.0.6_biqbaboplfbrettd7655fr4n2y
|
||||
gpt-token-utils: registry.npmmirror.com/gpt-token-utils/1.2.0
|
||||
graphemer: registry.npmmirror.com/graphemer/1.4.0
|
||||
hyperdown: registry.npmmirror.com/hyperdown/2.4.29
|
||||
immer: registry.npmmirror.com/immer/9.0.19
|
||||
jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0
|
||||
@@ -84,6 +97,8 @@ dependencies:
|
||||
nodemailer: registry.npmmirror.com/nodemailer/6.9.1
|
||||
nprogress: registry.npmmirror.com/nprogress/0.2.0
|
||||
openai: registry.npmmirror.com/openai/3.2.1
|
||||
papaparse: registry.npmmirror.com/papaparse/5.4.1
|
||||
pg: registry.npmmirror.com/pg/8.10.0
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
react-dom: registry.npmmirror.com/react-dom/18.2.0_react@18.2.0
|
||||
react-hook-form: registry.npmmirror.com/react-hook-form/7.43.1_react@18.2.0
|
||||
@@ -106,11 +121,12 @@ devDependencies:
|
||||
'@types/lodash': registry.npmmirror.com/@types/lodash/4.14.191
|
||||
'@types/node': registry.npmmirror.com/@types/node/18.14.0
|
||||
'@types/nodemailer': registry.npmmirror.com/@types/nodemailer/6.4.7
|
||||
'@types/papaparse': registry.npmmirror.com/@types/papaparse/5.3.7
|
||||
'@types/pg': registry.npmmirror.com/@types/pg/8.6.6
|
||||
'@types/react': registry.npmmirror.com/@types/react/18.0.28
|
||||
'@types/react-dom': registry.npmmirror.com/@types/react-dom/18.0.11
|
||||
'@types/react-syntax-highlighter': registry.npmmirror.com/@types/react-syntax-highlighter/15.5.6
|
||||
'@types/tunnel': registry.npmmirror.com/@types/tunnel/0.0.3
|
||||
'@types/uuid': registry.npmmirror.com/@types/uuid/9.0.1
|
||||
eslint: registry.npmmirror.com/eslint/8.34.0
|
||||
eslint-config-next: registry.npmmirror.com/eslint-config-next/13.1.6_7kw3g6rralp5ps6mg3uyzz6azm
|
||||
husky: registry.npmmirror.com/husky/8.0.3
|
||||
@@ -120,6 +136,117 @@ devDependencies:
|
||||
|
||||
packages:
|
||||
|
||||
registry.npmmirror.com/@alicloud/credentials/2.2.6:
|
||||
resolution: {integrity: sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz}
|
||||
name: '@alicloud/credentials'
|
||||
version: 2.2.6
|
||||
dependencies:
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
httpx: registry.npmmirror.com/httpx/2.2.7
|
||||
ini: registry.npmmirror.com/ini/1.3.8
|
||||
kitx: registry.npmmirror.com/kitx/2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/dysmsapi20170525/2.0.23:
|
||||
resolution: {integrity: sha512-C02xj9S2ZPL13SciChlIY3s5+PiOM13jEGZSn+L92aiWYCBqTlpx9UMwNKBNWImMSOlG71IOSYfsQggaoIY+4Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/dysmsapi20170525/-/dysmsapi20170525-2.0.23.tgz}
|
||||
name: '@alicloud/dysmsapi20170525'
|
||||
version: 2.0.23
|
||||
dependencies:
|
||||
'@alicloud/endpoint-util': registry.npmmirror.com/@alicloud/endpoint-util/0.0.1
|
||||
'@alicloud/openapi-client': registry.npmmirror.com/@alicloud/openapi-client/0.4.5
|
||||
'@alicloud/openapi-util': registry.npmmirror.com/@alicloud/openapi-util/0.3.1
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/endpoint-util/0.0.1:
|
||||
resolution: {integrity: sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz}
|
||||
name: '@alicloud/endpoint-util'
|
||||
version: 0.0.1
|
||||
dependencies:
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
kitx: registry.npmmirror.com/kitx/2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/gateway-spi/0.0.8:
|
||||
resolution: {integrity: sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz}
|
||||
name: '@alicloud/gateway-spi'
|
||||
version: 0.0.8
|
||||
dependencies:
|
||||
'@alicloud/credentials': registry.npmmirror.com/@alicloud/credentials/2.2.6
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/openapi-client/0.4.5:
|
||||
resolution: {integrity: sha512-x1blwhfPOVkH/JCLWFssFRWDL0C75RToun9AwhNV+84gqJB2/GUipm3quHGLon8JiQ0DQ9YBUho2rukSoAvhJQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/openapi-client/-/openapi-client-0.4.5.tgz}
|
||||
name: '@alicloud/openapi-client'
|
||||
version: 0.4.5
|
||||
dependencies:
|
||||
'@alicloud/credentials': registry.npmmirror.com/@alicloud/credentials/2.2.6
|
||||
'@alicloud/gateway-spi': registry.npmmirror.com/@alicloud/gateway-spi/0.0.8
|
||||
'@alicloud/openapi-util': registry.npmmirror.com/@alicloud/openapi-util/0.3.1
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
|
||||
'@alicloud/tea-xml': registry.npmmirror.com/@alicloud/tea-xml/0.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/openapi-util/0.3.1:
|
||||
resolution: {integrity: sha512-6mGT+hs+SXismZi/CEkjPhhbn2U3qTT/Qv/RXAYFA1DC3Jk4/YaX3N7RtpgdzOhdD7uI8XtNkaULKHZY3BrtxQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/openapi-util/-/openapi-util-0.3.1.tgz}
|
||||
name: '@alicloud/openapi-util'
|
||||
version: 0.3.1
|
||||
dependencies:
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
'@alicloud/tea-util': registry.npmmirror.com/@alicloud/tea-util/1.4.5
|
||||
kitx: registry.npmmirror.com/kitx/2.1.0
|
||||
sm3: registry.npmmirror.com/sm3/1.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/tea-typescript/1.8.0:
|
||||
resolution: {integrity: sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz}
|
||||
name: '@alicloud/tea-typescript'
|
||||
version: 1.8.0
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/12.20.55
|
||||
httpx: registry.npmmirror.com/httpx/2.2.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/tea-util/1.4.5:
|
||||
resolution: {integrity: sha512-7NuThYUi90/ivT/ORKusm0NVKlc1khPTtlzTR77xEqSBt7d24Ee/Lo70hx9PWP28nHpIZ1gM0NKYBtpq7HUDlg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-util/-/tea-util-1.4.5.tgz}
|
||||
name: '@alicloud/tea-util'
|
||||
version: 1.4.5
|
||||
dependencies:
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
kitx: registry.npmmirror.com/kitx/2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@alicloud/tea-xml/0.0.2:
|
||||
resolution: {integrity: sha512-Xs7v5y7YSNSDDYmiDWAC0/013VWPjS3dQU4KezSLva9VGiTVPaL3S7Nk4NrTmAYCG6MKcrRj/nGEDIWL5KRoPg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/tea-xml/-/tea-xml-0.0.2.tgz}
|
||||
name: '@alicloud/tea-xml'
|
||||
version: 0.0.2
|
||||
dependencies:
|
||||
'@alicloud/tea-typescript': registry.npmmirror.com/@alicloud/tea-typescript/1.8.0
|
||||
'@types/xml2js': registry.npmmirror.com/@types/xml2js/0.4.11
|
||||
xml2js: registry.npmmirror.com/xml2js/0.4.23
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@ampproject/remapping/2.2.0:
|
||||
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.0.tgz}
|
||||
name: '@ampproject/remapping'
|
||||
@@ -2962,6 +3089,20 @@ packages:
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/icon/3.0.16_lze4h7kxffpjhokvtqbtrlfkmq:
|
||||
resolution: {integrity: sha512-RpA1X5Ptz8Mt39HSyEIW1wxAz2AXyf9H0JJ5HVx/dBdMZaGMDJ0HyyPBVci0m4RCoJuyG1HHG/DXJaVfUTVAeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/icon/-/icon-3.0.16.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/icon/3.0.16
|
||||
name: '@chakra-ui/icon'
|
||||
version: 3.0.16
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
'@chakra-ui/system': registry.npmmirror.com/@chakra-ui/system/2.5.5_xqp3pgpqjlfxxa3zxu4zoc4fba
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/icon/3.0.16_n3dxrjldmk5gnycgnw7noyo5tu:
|
||||
resolution: {integrity: sha512-RpA1X5Ptz8Mt39HSyEIW1wxAz2AXyf9H0JJ5HVx/dBdMZaGMDJ0HyyPBVci0m4RCoJuyG1HHG/DXJaVfUTVAeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/icon/-/icon-3.0.16.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/icon/3.0.16
|
||||
@@ -2976,20 +3117,7 @@ packages:
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/icon/3.0.16_react@18.2.0:
|
||||
resolution: {integrity: sha512-RpA1X5Ptz8Mt39HSyEIW1wxAz2AXyf9H0JJ5HVx/dBdMZaGMDJ0HyyPBVci0m4RCoJuyG1HHG/DXJaVfUTVAeg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/icon/-/icon-3.0.16.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/icon/3.0.16
|
||||
name: '@chakra-ui/icon'
|
||||
version: 3.0.16
|
||||
peerDependencies:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/icons/2.0.17_react@18.2.0:
|
||||
registry.npmmirror.com/@chakra-ui/icons/2.0.17_lze4h7kxffpjhokvtqbtrlfkmq:
|
||||
resolution: {integrity: sha512-HMJP0WrJgAmFR9+Xh/CBH0nVnGMsJ4ZC8MK6tMgxPKd9/muvn0I4hsicHqdPlLpmB0TlxlhkBAKaVMtOdz6F0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/icons/-/icons-2.0.17.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/icons/2.0.17
|
||||
name: '@chakra-ui/icons'
|
||||
@@ -2998,7 +3126,8 @@ packages:
|
||||
'@chakra-ui/system': '>=2.0.0'
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/icon': registry.npmmirror.com/@chakra-ui/icon/3.0.16_react@18.2.0
|
||||
'@chakra-ui/icon': registry.npmmirror.com/@chakra-ui/icon/3.0.16_lze4h7kxffpjhokvtqbtrlfkmq
|
||||
'@chakra-ui/system': registry.npmmirror.com/@chakra-ui/system/2.5.5_xqp3pgpqjlfxxa3zxu4zoc4fba
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
@@ -3745,6 +3874,16 @@ packages:
|
||||
lodash.mergewith: registry.npmmirror.com/lodash.mergewith/4.6.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/styled-system/2.8.0:
|
||||
resolution: {integrity: sha512-bmRv/8ACJGGKGx84U1npiUddwdNifJ+/ETklGwooS5APM0ymwUtBYZpFxjYNJrqvVYpg3mVY6HhMyBVptLS7iA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/styled-system/-/styled-system-2.8.0.tgz}
|
||||
name: '@chakra-ui/styled-system'
|
||||
version: 2.8.0
|
||||
dependencies:
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
csstype: registry.npmmirror.com/csstype/3.1.1
|
||||
lodash.mergewith: registry.npmmirror.com/lodash.mergewith/4.6.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/switch/2.0.22_6k64q2ggygf5zznlgufl3vff54:
|
||||
resolution: {integrity: sha512-+/Yy6y7VFD91uSPruF8ZvePi3tl5D8UNVATtWEQ+QBI92DLSM+PtgJ2F0Y9GMZ9NzMxpZ80DqwY7/kqcPCfLvw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/switch/-/switch-2.0.22.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/switch/2.0.22
|
||||
@@ -3784,6 +3923,28 @@ packages:
|
||||
react-fast-compare: registry.npmmirror.com/react-fast-compare/3.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/system/2.5.5_xqp3pgpqjlfxxa3zxu4zoc4fba:
|
||||
resolution: {integrity: sha512-52BIp/Zyvefgxn5RTByfkTeG4J+y81LWEjWm8jCaRFsLVm8IFgqIrngtcq4I7gD5n/UKbneHlb4eLHo4uc5yDQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/system/-/system-2.5.5.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/system/2.5.5
|
||||
name: '@chakra-ui/system'
|
||||
version: 2.5.5
|
||||
peerDependencies:
|
||||
'@emotion/react': ^11.0.0
|
||||
'@emotion/styled': ^11.0.0
|
||||
react: '>=18'
|
||||
dependencies:
|
||||
'@chakra-ui/color-mode': registry.npmmirror.com/@chakra-ui/color-mode/2.1.12_react@18.2.0
|
||||
'@chakra-ui/object-utils': registry.npmmirror.com/@chakra-ui/object-utils/2.0.8
|
||||
'@chakra-ui/react-utils': registry.npmmirror.com/@chakra-ui/react-utils/2.0.12_react@18.2.0
|
||||
'@chakra-ui/styled-system': registry.npmmirror.com/@chakra-ui/styled-system/2.8.0
|
||||
'@chakra-ui/theme-utils': registry.npmmirror.com/@chakra-ui/theme-utils/2.0.15
|
||||
'@chakra-ui/utils': registry.npmmirror.com/@chakra-ui/utils/2.0.15
|
||||
'@emotion/react': registry.npmmirror.com/@emotion/react/11.10.6_pmekkgnqduwlme35zpnqhenc34
|
||||
'@emotion/styled': registry.npmmirror.com/@emotion/styled/11.10.6_oouaibmszuch5k64ms7uxp2aia
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
react-fast-compare: registry.npmmirror.com/react-fast-compare/3.2.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/table/2.0.16_n3dxrjldmk5gnycgnw7noyo5tu:
|
||||
resolution: {integrity: sha512-vWDXZ6Ad3Aj66curp1tZBHvCfQHX2FJ4ijLiqGgQszWFIchfhJ5vMgEBJaFMZ+BN1draAjuRTZqaQefOApzvRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/table/-/table-2.0.16.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/table/2.0.16
|
||||
@@ -3865,6 +4026,20 @@ packages:
|
||||
color2k: registry.npmmirror.com/color2k/2.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/theme-tools/2.0.17_wv7sq5bj4kx5i3evdevscgumbi:
|
||||
resolution: {integrity: sha512-Auu38hnihlJZQcPok6itRDBbwof3TpXGYtDPnOvrq4Xp7jnab36HLt7KEXSDPXbtOk3ZqU99pvI1en5LbDrdjg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/theme-tools/-/theme-tools-2.0.17.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/theme-tools/2.0.17
|
||||
name: '@chakra-ui/theme-tools'
|
||||
version: 2.0.17
|
||||
peerDependencies:
|
||||
'@chakra-ui/styled-system': '>=2.0.0'
|
||||
dependencies:
|
||||
'@chakra-ui/anatomy': registry.npmmirror.com/@chakra-ui/anatomy/2.1.2
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
'@chakra-ui/styled-system': registry.npmmirror.com/@chakra-ui/styled-system/2.8.0
|
||||
color2k: registry.npmmirror.com/color2k/2.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/theme-utils/2.0.11:
|
||||
resolution: {integrity: sha512-lBAay6Sq3/fl7exd3mFxWAbzgdQowytor0fnlHrpNStn1HgFjXukwsf6356XQOie2Vd8qaMM7qZtMh4AiC0dcg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/theme-utils/-/theme-utils-2.0.11.tgz}
|
||||
name: '@chakra-ui/theme-utils'
|
||||
@@ -3876,6 +4051,17 @@ packages:
|
||||
lodash.mergewith: registry.npmmirror.com/lodash.mergewith/4.6.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/theme-utils/2.0.15:
|
||||
resolution: {integrity: sha512-UuxtEgE7gwMTGDXtUpTOI7F5X0iHB9ekEOG5PWPn2wWBL7rlk2JtPI7UP5Um5Yg6vvBfXYGK1ySahxqsgf+87g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/theme-utils/-/theme-utils-2.0.15.tgz}
|
||||
name: '@chakra-ui/theme-utils'
|
||||
version: 2.0.15
|
||||
dependencies:
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
'@chakra-ui/styled-system': registry.npmmirror.com/@chakra-ui/styled-system/2.8.0
|
||||
'@chakra-ui/theme': registry.npmmirror.com/@chakra-ui/theme/3.0.1_wv7sq5bj4kx5i3evdevscgumbi
|
||||
lodash.mergewith: registry.npmmirror.com/lodash.mergewith/4.6.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/theme/2.2.5_es2flcfvdj7o2v4vs237ptvmhy:
|
||||
resolution: {integrity: sha512-hYASZMwu0NqEv6PPydu+F3I+kMNd44yR4TwjR/lXBz/LEh64L6UPY6kQjebCfgdVtsGdl3HKg+eLlfa7SvfRgw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/theme/-/theme-2.2.5.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/theme/2.2.5
|
||||
@@ -3890,6 +4076,20 @@ packages:
|
||||
'@chakra-ui/theme-tools': registry.npmmirror.com/@chakra-ui/theme-tools/2.0.17_es2flcfvdj7o2v4vs237ptvmhy
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/theme/3.0.1_wv7sq5bj4kx5i3evdevscgumbi:
|
||||
resolution: {integrity: sha512-92kDm/Ux/51uJqhRKevQo/O/rdwucDYcpHg2QuwzdAxISCeYvgtl2TtgOOl5EnqEP0j3IEAvZHZUlv8TTbawaw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/theme/-/theme-3.0.1.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/theme/3.0.1
|
||||
name: '@chakra-ui/theme'
|
||||
version: 3.0.1
|
||||
peerDependencies:
|
||||
'@chakra-ui/styled-system': '>=2.0.0'
|
||||
dependencies:
|
||||
'@chakra-ui/anatomy': registry.npmmirror.com/@chakra-ui/anatomy/2.1.2
|
||||
'@chakra-ui/shared-utils': registry.npmmirror.com/@chakra-ui/shared-utils/2.0.5
|
||||
'@chakra-ui/styled-system': registry.npmmirror.com/@chakra-ui/styled-system/2.8.0
|
||||
'@chakra-ui/theme-tools': registry.npmmirror.com/@chakra-ui/theme-tools/2.0.17_wv7sq5bj4kx5i3evdevscgumbi
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@chakra-ui/toast/6.0.1_jgj3ekl54faqnu3nlobnfmds2q:
|
||||
resolution: {integrity: sha512-ej2kJXvu/d2h6qnXU5D8XTyw0qpsfmbiU7hUffo/sPxkz89AUOQ08RUuUmB1ssW/FZcQvNMJ5WgzCTKHGBxtxw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@chakra-ui/toast/-/toast-6.0.1.tgz}
|
||||
id: registry.npmmirror.com/@chakra-ui/toast/6.0.1
|
||||
@@ -3978,6 +4178,12 @@ packages:
|
||||
react: registry.npmmirror.com/react/18.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@dqbd/tiktoken/1.0.6:
|
||||
resolution: {integrity: sha512-umSdeZTy/SbPPKVuZKV/XKyFPmXSN145CcM3iHjBbmhlohBJg7vaDp4cPCW+xNlWL6L2U1sp7T2BD+di2sUKdA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@dqbd/tiktoken/-/tiktoken-1.0.6.tgz}
|
||||
name: '@dqbd/tiktoken'
|
||||
version: 1.0.6
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@emotion/babel-plugin/11.10.6:
|
||||
resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz}
|
||||
name: '@emotion/babel-plugin'
|
||||
@@ -5027,6 +5233,18 @@ packages:
|
||||
version: 0.7.31
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/node/12.20.55:
|
||||
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz}
|
||||
name: '@types/node'
|
||||
version: 12.20.55
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/node/14.18.42:
|
||||
resolution: {integrity: sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-14.18.42.tgz}
|
||||
name: '@types/node'
|
||||
version: 14.18.42
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/node/18.14.0:
|
||||
resolution: {integrity: sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/node/-/node-18.14.0.tgz}
|
||||
name: '@types/node'
|
||||
@@ -5046,11 +5264,29 @@ packages:
|
||||
version: 0.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/papaparse/5.3.7:
|
||||
resolution: {integrity: sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/papaparse/-/papaparse-5.3.7.tgz}
|
||||
name: '@types/papaparse'
|
||||
version: 5.3.7
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/18.14.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@types/parse-json/4.0.0:
|
||||
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz}
|
||||
name: '@types/parse-json'
|
||||
version: 4.0.0
|
||||
|
||||
registry.npmmirror.com/@types/pg/8.6.6:
|
||||
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/pg/-/pg-8.6.6.tgz}
|
||||
name: '@types/pg'
|
||||
version: 8.6.6
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/18.14.0
|
||||
pg-protocol: registry.npmmirror.com/pg-protocol/1.6.0
|
||||
pg-types: registry.npmmirror.com/pg-types/2.2.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@types/prop-types/15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.5.tgz}
|
||||
name: '@types/prop-types'
|
||||
@@ -5100,12 +5336,6 @@ packages:
|
||||
version: 2.0.6
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/uuid/9.0.1:
|
||||
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/uuid/-/uuid-9.0.1.tgz}
|
||||
name: '@types/uuid'
|
||||
version: 9.0.1
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/@types/webidl-conversions/7.0.0:
|
||||
resolution: {integrity: sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz}
|
||||
name: '@types/webidl-conversions'
|
||||
@@ -5121,6 +5351,14 @@ packages:
|
||||
'@types/webidl-conversions': registry.npmmirror.com/@types/webidl-conversions/7.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@types/xml2js/0.4.11:
|
||||
resolution: {integrity: sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.11.tgz}
|
||||
name: '@types/xml2js'
|
||||
version: 0.4.11
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/18.14.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/@typescript-eslint/parser/5.52.0_7kw3g6rralp5ps6mg3uyzz6azm:
|
||||
resolution: {integrity: sha512-e2KiLQOZRo4Y0D/b+3y08i3jsekoSkOYStROYmPUnGMEoA0h+k2qOH5H6tcjIc68WDvGwH+PaOrP1XRzLJ6QlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.52.0.tgz}
|
||||
id: registry.npmmirror.com/@typescript-eslint/parser/5.52.0
|
||||
@@ -5667,6 +5905,13 @@ packages:
|
||||
version: 1.0.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/buffer-writer/2.0.0:
|
||||
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer-writer/-/buffer-writer-2.0.0.tgz}
|
||||
name: buffer-writer
|
||||
version: 2.0.0
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/buffer/5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz}
|
||||
name: buffer
|
||||
@@ -7421,12 +7666,6 @@ packages:
|
||||
get-intrinsic: registry.npmmirror.com/get-intrinsic/1.2.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/gpt-token-utils/1.2.0:
|
||||
resolution: {integrity: sha512-s8twaU38UE2Vp65JhQEjz8qvWhWY8KZYvmvYHapxlPT03Ok35Clq+gm9eE27wQILdFisseMVRSiC5lJR9GBklA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/gpt-token-utils/-/gpt-token-utils-1.2.0.tgz}
|
||||
name: gpt-token-utils
|
||||
version: 1.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/graceful-fs/4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz}
|
||||
name: graceful-fs
|
||||
@@ -7438,6 +7677,12 @@ packages:
|
||||
version: 1.0.4
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/graphemer/1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz}
|
||||
name: graphemer
|
||||
version: 1.4.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/has-bigints/1.0.2:
|
||||
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/has-bigints/-/has-bigints-1.0.2.tgz}
|
||||
name: has-bigints
|
||||
@@ -7638,6 +7883,17 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/httpx/2.2.7:
|
||||
resolution: {integrity: sha512-Wjh2JOAah0pdczfqL8NC5378G7jMt0Zcpn8U+yyxAiejjlagzSTQgJHuVvka2VNPQlKfoGehYRc79WKq9E4gDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/httpx/-/httpx-2.2.7.tgz}
|
||||
name: httpx
|
||||
version: 2.2.7
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/14.18.42
|
||||
debug: registry.npmmirror.com/debug/4.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/human-signals/3.0.1:
|
||||
resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/human-signals/-/human-signals-3.0.1.tgz}
|
||||
name: human-signals
|
||||
@@ -8272,6 +8528,14 @@ packages:
|
||||
commander: registry.npmmirror.com/commander/8.3.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/kitx/2.1.0:
|
||||
resolution: {integrity: sha512-C/5v9MtIX7aHGOjwn5BmrrbNkJSf7i0R5mRzmh13GSAdRqQ7bYQo/Su2pTYNylFicqKNTVX3HML9k1u8k51+pQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/kitx/-/kitx-2.1.0.tgz}
|
||||
name: kitx
|
||||
version: 2.1.0
|
||||
dependencies:
|
||||
'@types/node': registry.npmmirror.com/@types/node/12.20.55
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/kleur/4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz}
|
||||
name: kleur
|
||||
@@ -9565,12 +9829,24 @@ packages:
|
||||
netmask: registry.npmmirror.com/netmask/2.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/packet-reader/1.0.0:
|
||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/packet-reader/-/packet-reader-1.0.0.tgz}
|
||||
name: packet-reader
|
||||
version: 1.0.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/pako/1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz}
|
||||
name: pako
|
||||
version: 1.0.11
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/papaparse/5.4.1:
|
||||
resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz}
|
||||
name: papaparse
|
||||
version: 5.4.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/parent-module/1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz}
|
||||
name: parent-module
|
||||
@@ -9655,6 +9931,74 @@ packages:
|
||||
through: registry.npmmirror.com/through/2.3.8
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/pg-connection-string/2.5.0:
|
||||
resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz}
|
||||
name: pg-connection-string
|
||||
version: 2.5.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/pg-int8/1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz}
|
||||
name: pg-int8
|
||||
version: 1.0.1
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
registry.npmmirror.com/pg-pool/3.6.0_pg@8.10.0:
|
||||
resolution: {integrity: sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-pool/-/pg-pool-3.6.0.tgz}
|
||||
id: registry.npmmirror.com/pg-pool/3.6.0
|
||||
name: pg-pool
|
||||
version: 3.6.0
|
||||
peerDependencies:
|
||||
pg: '>=8.0'
|
||||
dependencies:
|
||||
pg: registry.npmmirror.com/pg/8.10.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/pg-protocol/1.6.0:
|
||||
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.6.0.tgz}
|
||||
name: pg-protocol
|
||||
version: 1.6.0
|
||||
|
||||
registry.npmmirror.com/pg-types/2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz}
|
||||
name: pg-types
|
||||
version: 2.2.0
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
pg-int8: registry.npmmirror.com/pg-int8/1.0.1
|
||||
postgres-array: registry.npmmirror.com/postgres-array/2.0.0
|
||||
postgres-bytea: registry.npmmirror.com/postgres-bytea/1.0.0
|
||||
postgres-date: registry.npmmirror.com/postgres-date/1.0.7
|
||||
postgres-interval: registry.npmmirror.com/postgres-interval/1.2.0
|
||||
|
||||
registry.npmmirror.com/pg/8.10.0:
|
||||
resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg/-/pg-8.10.0.tgz}
|
||||
name: pg
|
||||
version: 8.10.0
|
||||
engines: {node: '>= 8.0.0'}
|
||||
peerDependencies:
|
||||
pg-native: '>=3.0.1'
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
buffer-writer: registry.npmmirror.com/buffer-writer/2.0.0
|
||||
packet-reader: registry.npmmirror.com/packet-reader/1.0.0
|
||||
pg-connection-string: registry.npmmirror.com/pg-connection-string/2.5.0
|
||||
pg-pool: registry.npmmirror.com/pg-pool/3.6.0_pg@8.10.0
|
||||
pg-protocol: registry.npmmirror.com/pg-protocol/1.6.0
|
||||
pg-types: registry.npmmirror.com/pg-types/2.2.0
|
||||
pgpass: registry.npmmirror.com/pgpass/1.0.5
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/pgpass/1.0.5:
|
||||
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz}
|
||||
name: pgpass
|
||||
version: 1.0.5
|
||||
dependencies:
|
||||
split2: registry.npmmirror.com/split2/4.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/picocolors/1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz}
|
||||
name: picocolors
|
||||
@@ -9685,6 +10029,32 @@ packages:
|
||||
source-map-js: registry.npmmirror.com/source-map-js/1.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/postgres-array/2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz}
|
||||
name: postgres-array
|
||||
version: 2.0.0
|
||||
engines: {node: '>=4'}
|
||||
|
||||
registry.npmmirror.com/postgres-bytea/1.0.0:
|
||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz}
|
||||
name: postgres-bytea
|
||||
version: 1.0.0
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
registry.npmmirror.com/postgres-date/1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz}
|
||||
name: postgres-date
|
||||
version: 1.0.7
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
registry.npmmirror.com/postgres-interval/1.2.0:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz}
|
||||
name: postgres-interval
|
||||
version: 1.2.0
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
xtend: registry.npmmirror.com/xtend/4.0.2
|
||||
|
||||
registry.npmmirror.com/prebuild-install/7.1.1:
|
||||
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.1.tgz}
|
||||
name: prebuild-install
|
||||
@@ -9895,6 +10265,12 @@ packages:
|
||||
version: 3.2.0
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/react-fast-compare/3.2.1:
|
||||
resolution: {integrity: sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz}
|
||||
name: react-fast-compare
|
||||
version: 3.2.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/react-focus-lock/2.9.4_pmekkgnqduwlme35zpnqhenc34:
|
||||
resolution: {integrity: sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/react-focus-lock/-/react-focus-lock-2.9.4.tgz}
|
||||
id: registry.npmmirror.com/react-focus-lock/2.9.4
|
||||
@@ -10580,6 +10956,12 @@ packages:
|
||||
is-fullwidth-code-point: registry.npmmirror.com/is-fullwidth-code-point/4.0.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/sm3/1.0.3:
|
||||
resolution: {integrity: sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sm3/-/sm3-1.0.3.tgz}
|
||||
name: sm3
|
||||
version: 1.0.3
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/smart-buffer/4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz}
|
||||
name: smart-buffer
|
||||
@@ -10651,6 +11033,13 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
registry.npmmirror.com/split2/4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz}
|
||||
name: split2
|
||||
version: 4.2.0
|
||||
engines: {node: '>= 10.x'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/sprintf-js/1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz}
|
||||
name: sprintf-js
|
||||
@@ -11600,6 +11989,16 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xml2js/0.4.23:
|
||||
resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xml2js/-/xml2js-0.4.23.tgz}
|
||||
name: xml2js
|
||||
version: 0.4.23
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: registry.npmmirror.com/sax/1.1.6
|
||||
xmlbuilder: registry.npmmirror.com/xmlbuilder/11.0.1
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xmlbuilder/10.1.1:
|
||||
resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz}
|
||||
name: xmlbuilder
|
||||
@@ -11607,6 +12006,13 @@ packages:
|
||||
engines: {node: '>=4.0'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xmlbuilder/11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz}
|
||||
name: xmlbuilder
|
||||
version: 11.0.1
|
||||
engines: {node: '>=4.0'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/xregexp/2.0.0:
|
||||
resolution: {integrity: sha512-xl/50/Cf32VsGq/1R8jJE5ajH1yMCQkpmoS10QbFZWl2Oor4H0Me64Pu2yxvsRWK3m6soJbmGfzSR7BYmDcWAA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xregexp/-/xregexp-2.0.0.tgz}
|
||||
name: xregexp
|
||||
@@ -11618,7 +12024,6 @@ packages:
|
||||
name: xtend
|
||||
version: 4.0.2
|
||||
engines: {node: '>=0.4'}
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/yallist/3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz}
|
||||
|
||||
10
public/docs/chatProblem.md
Normal file
@@ -0,0 +1,10 @@
|
||||
### 常见问题
|
||||
**请求次数太多了**
|
||||
一般是因为自己的 openai 账号异常。请先检查自己的账号是否正常使用。
|
||||
**内容长度**
|
||||
chatgpt 上下文最长 4096 tokens, 上下文超长时会报错。
|
||||
**删除和复制**
|
||||
电脑端:聊天内容右侧有复制和删除的图标。
|
||||
移动端:点击对话头像,可以选择复制或删除该条内容。
|
||||
**代理出错**
|
||||
服务器代理不稳定,可以过一会儿再尝试。 或者可以访问国外服务器: [FastGpt](https://fastgpt.run/)
|
||||
6
public/docs/csvSelect.md
Normal file
@@ -0,0 +1,6 @@
|
||||
接受一个csv文件,表格头包含 question 和 answer。question 代表问题,answer 代表答案。
|
||||
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
|
||||
| question | answer |
|
||||
| --- | --- |
|
||||
| 什么是 laf | laf 是一个云函数开发平台…… |
|
||||
| 什么是 sealos | Sealos 是以 kubernetes 为内核的云操作系统发行版,可以…… |
|
||||
41
public/docs/intro.md
Normal file
@@ -0,0 +1,41 @@
|
||||
## 欢迎使用 Fast GPT
|
||||
|
||||
[Git 仓库](https://github.com/c121914yu/FastGPT)
|
||||
|
||||
### 交流群/问题反馈
|
||||
扫码满了,加个小号,定时拉
|
||||
wx号: fastgpt123
|
||||

|
||||
|
||||
|
||||
### 快速开始
|
||||
1. 使用手机号注册账号。
|
||||
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
|
||||
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
|
||||
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。
|
||||
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
|
||||
|
||||
### 价格表
|
||||
如果使用了自己的 Api Key,不会计费。可以在账号页,看到详细账单。单纯使用 chatGPT 模型进行对话,只有一个计费项目。使用知识库时,包含**对话**和**索引**生成两个计费项。
|
||||
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
|
||||
| --- | --- |
|
||||
| chatgpt - 对话 | 0.03 |
|
||||
| 知识库 - 对话 | 0.03 |
|
||||
| 知识库 - 索引 | 0.004 |
|
||||
| 文件拆分 | 0.03 |
|
||||
|
||||
|
||||
### 定制 prompt
|
||||
|
||||
1. 进入模型编辑页
|
||||
2. 调整温度和提示词
|
||||
3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。
|
||||
|
||||
### 知识库
|
||||
|
||||
1. 创建模型时选择【知识库】
|
||||
2. 进入模型编辑页
|
||||
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
|
||||
4. 使用该模型对话。
|
||||
|
||||
注意:使用知识库模型对话时,tokens 消耗会加快。
|
||||
3
public/docs/shareHint.md
Normal file
@@ -0,0 +1,3 @@
|
||||
你正准备分享对话,请确保分享链接不会滥用,因为它是使用的是你的 API key。
|
||||
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
|
||||
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
|
||||
6
public/docs/versionIntro.md
Normal file
@@ -0,0 +1,6 @@
|
||||
### Fast GPT V2.8.1
|
||||
* 新增 - 暂停聊天。
|
||||
* 优化 - 知识库升级,内容条数不上限!
|
||||
* 优化 - 导入去重效果,可防止导出后的 csv 重复导入。
|
||||
* 优化 - 聊天框,电脑端复制删除图标。
|
||||
* 优化 - 聊天框,生成内容时,如果滚动条触底,则会自动向下滚动,不需要手动下滑。
|
||||
BIN
public/imgs/wx300-2.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/imgs/wx300.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,42 +1,32 @@
|
||||
import { GET, POST, DELETE } from './request';
|
||||
import type { ChatItemType, ChatSiteItemType } from '@/types/chat';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import type { InitChatResponse } from './response/chat';
|
||||
|
||||
/**
|
||||
* 获取一个聊天框的ID
|
||||
*/
|
||||
export const getChatSiteId = (modelId: string, isShare = false) =>
|
||||
GET<string>(`/chat/generate?modelId=${modelId}&isShare=${isShare ? 'true' : 'false'}`);
|
||||
|
||||
/**
|
||||
* 获取初始化聊天内容
|
||||
*/
|
||||
export const getInitChatSiteInfo = (chatId: string) =>
|
||||
GET<InitChatResponse>(`/chat/init?chatId=${chatId}`);
|
||||
export const getInitChatSiteInfo = (modelId: string, chatId: '' | string) =>
|
||||
GET<InitChatResponse>(`/chat/init?modelId=${modelId}&chatId=${chatId}`);
|
||||
|
||||
/**
|
||||
* 发送 GPT3 prompt
|
||||
* 获取历史记录
|
||||
*/
|
||||
export const postGPT3SendPrompt = ({
|
||||
chatId,
|
||||
prompt
|
||||
}: {
|
||||
prompt: ChatSiteItemType[];
|
||||
chatId: string;
|
||||
}) =>
|
||||
POST<string>(`/chat/gpt3`, {
|
||||
chatId,
|
||||
prompt: prompt.map((item) => ({
|
||||
obj: item.obj,
|
||||
value: item.value
|
||||
}))
|
||||
});
|
||||
export const getChatHistory = () =>
|
||||
GET<{ _id: string; title: string; modelId: string }[]>('/chat/getHistory');
|
||||
|
||||
/**
|
||||
* 删除一条历史记录
|
||||
*/
|
||||
export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${id}`);
|
||||
|
||||
/**
|
||||
* 存储一轮对话
|
||||
*/
|
||||
export const postSaveChat = (data: { chatId: string; prompts: ChatItemType[] }) =>
|
||||
POST('/chat/saveChat', data);
|
||||
export const postSaveChat = (data: {
|
||||
modelId: string;
|
||||
chatId: '' | string;
|
||||
prompts: ChatItemType[];
|
||||
}) => POST<string>('/chat/saveChat', data);
|
||||
|
||||
/**
|
||||
* 删除一句对话
|
||||
|
||||
1
src/api/common.ts
Normal file
@@ -0,0 +1 @@
|
||||
import { GET, POST, DELETE } from './request';
|
||||
@@ -1,25 +0,0 @@
|
||||
import { GET, POST, DELETE, PUT } from './request';
|
||||
import { RequestPaging } from '../types/index';
|
||||
import { Obj2Query } from '@/utils/tools';
|
||||
import type { DataListItem } from '@/types/data';
|
||||
import type { PagingData } from '../types/index';
|
||||
import type { DataItemSchema } from '@/types/mongoSchema';
|
||||
import type { CreateDataProps } from '@/pages/data/components/CreateDataModal';
|
||||
|
||||
export const getDataList = () => GET<DataListItem[]>(`/data/getDataList`);
|
||||
|
||||
export const postData = (data: CreateDataProps) => POST<string>(`/data/postData`, data);
|
||||
|
||||
export const postSplitData = (dataId: string, text: string) =>
|
||||
POST(`/data/splitData`, { dataId, text });
|
||||
|
||||
export const updateDataName = (dataId: string, name: string) =>
|
||||
PUT(`/data/putDataName?dataId=${dataId}&name=${name}`);
|
||||
|
||||
export const delData = (dataId: string) => DELETE(`/data/delData?dataId=${dataId}`);
|
||||
|
||||
type GetDataItemsProps = RequestPaging & {
|
||||
dataId: string;
|
||||
};
|
||||
export const getDataItems = (data: GetDataItemsProps) =>
|
||||
GET<PagingData<DataItemSchema>>(`/data/getDataItems?${Obj2Query(data)}`);
|
||||
@@ -6,7 +6,7 @@ interface StreamFetchProps {
|
||||
abortSignal: AbortController;
|
||||
}
|
||||
export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchProps) =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
new Promise<string>(async (resolve, reject) => {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
@@ -23,26 +23,30 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
|
||||
let responseText = '';
|
||||
|
||||
const read = async () => {
|
||||
const { done, value } = await reader?.read();
|
||||
if (done) {
|
||||
if (res.status === 200) {
|
||||
resolve(responseText);
|
||||
} else {
|
||||
try {
|
||||
try {
|
||||
const { done, value } = await reader?.read();
|
||||
if (done) {
|
||||
if (res.status === 200) {
|
||||
resolve(responseText);
|
||||
} else {
|
||||
const parseError = JSON.parse(responseText);
|
||||
reject(parseError?.message || '请求异常');
|
||||
} catch (err) {
|
||||
reject('请求异常');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
const text = decoder.decode(value).replace(/<br\/>/g, '\n');
|
||||
res.status === 200 && onMessage(text);
|
||||
responseText += text;
|
||||
read();
|
||||
} catch (err: any) {
|
||||
if (err?.message === 'The user aborted a request.') {
|
||||
return resolve(responseText);
|
||||
}
|
||||
reject(typeof err === 'string' ? err : err?.message || '请求异常');
|
||||
}
|
||||
const text = decoder.decode(value).replace(/<br\/>/g, '\n');
|
||||
res.status === 200 && onMessage(text);
|
||||
responseText += text;
|
||||
read();
|
||||
};
|
||||
|
||||
read();
|
||||
} catch (err: any) {
|
||||
console.log(err, '====');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { GET, POST, DELETE, PUT } from './request';
|
||||
import type { ModelSchema, ModelDataSchema, ModelSplitDataSchema } from '@/types/mongoSchema';
|
||||
import type { ModelSchema, ModelDataSchema } from '@/types/mongoSchema';
|
||||
import { ModelUpdateParams } from '@/types/model';
|
||||
import { TrainingItemType } from '../types/training';
|
||||
import { RequestPaging } from '../types/index';
|
||||
import { Obj2Query } from '@/utils/tools';
|
||||
|
||||
@@ -32,23 +31,10 @@ export const getModelById = (id: string) => GET<ModelSchema>(`/model/detail?mode
|
||||
export const putModelById = (id: string, data: ModelUpdateParams) =>
|
||||
PUT(`/model/update?modelId=${id}`, data);
|
||||
|
||||
export const postTrainModel = (id: string, form: FormData) =>
|
||||
POST(`/model/train/train?modelId=${id}`, form, {
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
export const putModelTrainingStatus = (id: string) =>
|
||||
PUT(`/model/train/putTrainStatus?modelId=${id}`);
|
||||
|
||||
export const getModelTrainings = (id: string) =>
|
||||
GET<TrainingItemType[]>(`/model/train/getTrainings?modelId=${id}`);
|
||||
|
||||
/* 模型 data */
|
||||
|
||||
type GetModelDataListProps = RequestPaging & {
|
||||
modelId: string;
|
||||
searchText: string;
|
||||
};
|
||||
/**
|
||||
* 获取模型的知识库数据
|
||||
@@ -60,7 +46,7 @@ export const getModelDataList = (props: GetModelDataListProps) =>
|
||||
* 获取导出数据(不分页)
|
||||
*/
|
||||
export const getExportDataList = (modelId: string) =>
|
||||
GET<string>(`/model/data/exportModelData?modelId=${modelId}`);
|
||||
GET<[string, string][]>(`/model/data/exportModelData?modelId=${modelId}`);
|
||||
|
||||
/**
|
||||
* 获取模型正在拆分数据的数量
|
||||
@@ -78,27 +64,29 @@ export const getWebContent = (url: string) => POST<string>(`/model/data/fetching
|
||||
*/
|
||||
export const postModelDataInput = (data: {
|
||||
modelId: string;
|
||||
data: { text: ModelDataSchema['text']; q: ModelDataSchema['q'] }[];
|
||||
data: { a: ModelDataSchema['a']; q: ModelDataSchema['q'] }[];
|
||||
}) => POST<number>(`/model/data/pushModelDataInput`, data);
|
||||
|
||||
/**
|
||||
* 拆分数据
|
||||
*/
|
||||
export const postModelDataSplitData = (data: { modelId: string; text: string; prompt: string }) =>
|
||||
POST(`/model/data/splitData`, data);
|
||||
export const postModelDataSplitData = (data: {
|
||||
modelId: string;
|
||||
chunks: string[];
|
||||
prompt: string;
|
||||
mode: 'qa' | 'subsection';
|
||||
}) => POST(`/model/data/splitData`, data);
|
||||
|
||||
/**
|
||||
* json导入数据
|
||||
*/
|
||||
export const postModelDataJsonData = (
|
||||
modelId: string,
|
||||
jsonData: { prompt: string; completion: string; vector?: number[] }[]
|
||||
) => POST(`/model/data/pushModelDataJson`, { modelId, data: jsonData });
|
||||
export const postModelDataCsvData = (modelId: string, data: string[][]) =>
|
||||
POST<number>(`/model/data/pushModelDataCsv`, { modelId, data: data });
|
||||
|
||||
/**
|
||||
* 更新模型数据
|
||||
*/
|
||||
export const putModelDataById = (data: { dataId: string; text: string; q?: string }) =>
|
||||
export const putModelDataById = (data: { dataId: string; a: string; q?: string }) =>
|
||||
PUT('/model/data/putModelData', data);
|
||||
/**
|
||||
* 删除一条模型数据
|
||||
|
||||
@@ -13,4 +13,4 @@ export const getOpenApiKeys = () => GET<UserOpenApiKey[]>('/openapi/getKeys');
|
||||
/**
|
||||
* delete api by id
|
||||
*/
|
||||
export const delOpenApiById = (id: string) => DELETE(`/openapi/delKet?id=${id}`);
|
||||
export const delOpenApiById = (id: string) => DELETE(`/openapi/delKey?id=${id}`);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { getToken, clearToken } from '@/utils/user';
|
||||
import { TOKEN_ERROR_CODE } from '@/constants/responseCode';
|
||||
import { TOKEN_ERROR_CODE } from '@/service/errorCode';
|
||||
|
||||
interface ConfigType {
|
||||
headers?: { [key: string]: string };
|
||||
|
||||
1
src/api/response/chat.d.ts
vendored
@@ -6,7 +6,6 @@ export type InitChatResponse = {
|
||||
modelId: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
chatModel: ModelSchema.service.chatModel; // 对话模型名
|
||||
modelName: ModelSchema.service.modelName; // 底层模型
|
||||
history: ChatItemType[];
|
||||
|
||||
8
src/api/response/user.d.ts
vendored
@@ -1,5 +1,13 @@
|
||||
import type { UserType } from '@/types/user';
|
||||
import type { PromotionRecordSchema } from '@/types/mongoSchema';
|
||||
export interface ResLogin {
|
||||
token: string;
|
||||
user: UserType;
|
||||
}
|
||||
|
||||
export interface PromotionRecordType {
|
||||
_id: PromotionRecordSchema['_id'];
|
||||
type: PromotionRecordSchema['type'];
|
||||
createTime: PromotionRecordSchema['createTime'];
|
||||
amount: PromotionRecordSchema['amount'];
|
||||
}
|
||||
|
||||
@@ -1,50 +1,66 @@
|
||||
import { GET, POST, PUT } from './request';
|
||||
import { createHashPassword, Obj2Query } from '@/utils/tools';
|
||||
import { ResLogin } from './response/user';
|
||||
import { EmailTypeEnum } from '@/constants/common';
|
||||
import { ResLogin, PromotionRecordType } from './response/user';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
import { UserType, UserUpdateParams } from '@/types/user';
|
||||
import type { PagingData, RequestPaging } from '@/types';
|
||||
import { BillSchema, PaySchema } from '@/types/mongoSchema';
|
||||
import { adaptBill } from '@/utils/adapt';
|
||||
|
||||
export const sendCodeToEmail = ({ email, type }: { email: string; type: `${EmailTypeEnum}` }) =>
|
||||
GET('/user/sendEmail', { email, type });
|
||||
export const sendAuthCode = ({
|
||||
username,
|
||||
type
|
||||
}: {
|
||||
username: string;
|
||||
type: `${UserAuthTypeEnum}`;
|
||||
}) => GET('/user/sendAuthCode', { username, type });
|
||||
|
||||
export const getTokenLogin = () => GET<UserType>('/user/tokenLogin');
|
||||
|
||||
/* get promotion init data */
|
||||
export const getPromotionInitData = () =>
|
||||
GET<{
|
||||
invitedAmount: number;
|
||||
historyAmount: number;
|
||||
residueAmount: number;
|
||||
}>('/user/promotion/getPromotionData');
|
||||
|
||||
export const postRegister = ({
|
||||
email,
|
||||
username,
|
||||
password,
|
||||
code
|
||||
code,
|
||||
inviterId
|
||||
}: {
|
||||
email: string;
|
||||
username: string;
|
||||
code: string;
|
||||
password: string;
|
||||
inviterId: string;
|
||||
}) =>
|
||||
POST<ResLogin>('/user/register', {
|
||||
email,
|
||||
username,
|
||||
code,
|
||||
inviterId,
|
||||
password: createHashPassword(password)
|
||||
});
|
||||
|
||||
export const postFindPassword = ({
|
||||
email,
|
||||
username,
|
||||
code,
|
||||
password
|
||||
}: {
|
||||
email: string;
|
||||
username: string;
|
||||
code: string;
|
||||
password: string;
|
||||
}) =>
|
||||
POST<ResLogin>('/user/updatePasswordByCode', {
|
||||
email,
|
||||
username,
|
||||
code,
|
||||
password: createHashPassword(password)
|
||||
});
|
||||
|
||||
export const postLogin = ({ email, password }: { email: string; password: string }) =>
|
||||
export const postLogin = ({ username, password }: { username: string; password: string }) =>
|
||||
POST<ResLogin>('/user/loginByPassword', {
|
||||
email,
|
||||
username,
|
||||
password: createHashPassword(password)
|
||||
});
|
||||
|
||||
@@ -65,3 +81,7 @@ export const getPayCode = (amount: number) =>
|
||||
}>(`/user/getPayCode?amount=${amount}`);
|
||||
|
||||
export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResult?payId=${payId}`);
|
||||
|
||||
/* promotion records */
|
||||
export const getPromotionRecords = (data: RequestPaging) =>
|
||||
GET<PromotionRecordType>(`/user/promotion/getPromotions?${Obj2Query(data)}`);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="204px" height="204px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="84" cy="50" r="10" fill="#e15b64">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="0.5681818181818182s" calcMode="spline" keyTimes="0;1" values="15;0" keySplines="0 0.5 0.5 1" begin="0s"></animate>
|
||||
<animate attributeName="fill" repeatCount="indefinite" dur="2.272727272727273s" calcMode="discrete" keyTimes="0;0.25;0.5;0.75;1" values="#e15b64;#abbd81;#f8b26a;#f47e60;#e15b64" begin="0s"></animate>
|
||||
</circle><circle cx="16" cy="50" r="10" fill="#e15b64">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;15;15;15" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="0s"></animate>
|
||||
</circle><circle cx="50" cy="50" r="10" fill="#f47e60">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;15;15;15" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.5681818181818182s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-0.5681818181818182s"></animate>
|
||||
</circle><circle cx="84" cy="50" r="10" fill="#f8b26a">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;15;15;15" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.1363636363636365s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.1363636363636365s"></animate>
|
||||
</circle><circle cx="16" cy="50" r="10" fill="#abbd81">
|
||||
<animate attributeName="r" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="0;0;15;15;15" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.7045454545454546s"></animate>
|
||||
<animate attributeName="cx" repeatCount="indefinite" dur="2.272727272727273s" calcMode="spline" keyTimes="0;0.25;0.5;0.75;1" values="16;16;16;50;84" keySplines="0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1;0 0.5 0.5 1" begin="-1.7045454545454546s"></animate>
|
||||
</circle>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
1
src/components/Icon/icons/dbModel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682232349111" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7070" xmlns:xlink="http://www.w3.org/1999/xlink" width="28" height="28"><path d="M512 102.6c110.7 0 215 12.3 293.9 34.7 35.8 10.2 65 22.1 84.5 34.7 18.6 12 21.3 19.7 21.6 20.6-0.2 0.9-3 8.6-21.6 20.6-19.5 12.5-48.7 24.5-84.5 34.7-78.9 22.3-183.2 34.7-293.9 34.7s-215-12.3-293.9-34.7c-35.8-10.2-65-22.1-84.5-34.7-18.6-12-21.3-19.7-21.6-20.6 0.2-0.9 3-8.6 21.6-20.6 19.5-12.5 48.7-24.5 84.5-34.7 78.9-22.4 183.2-34.7 293.9-34.7m0-40c-243 0-440 58.2-440 130s197 130 440 130 440-58.2 440-130-197-130-440-130zM112 190.4H72v641h40v-641z m840-0.3h-40v641h40v-641zM912 831v0.5c-0.2 0.9-3 8.6-21.6 20.6-19.5 12.5-48.7 24.5-84.5 34.7-78.9 22.3-183.2 34.6-293.9 34.6s-215-12.3-293.9-34.7c-35.8-10.2-65-22.1-84.5-34.7-18.6-12-21.3-19.7-21.6-20.6v-0.3l-40 0.3v0.1c0 71.8 197 130 440 130s440-58.2 440-130v-0.4l-40-0.1z m0-210.5v0.5c-0.2 0.9-3 8.6-21.6 20.6-19.5 12.5-48.7 24.5-84.5 34.7C727 698.6 622.7 711 512 711s-215-12.3-293.9-34.7c-35.8-10.2-65-22.1-84.5-34.7-18.6-12-21.3-19.7-21.6-20.6v-0.3l-40 0.3v0.1c0 71.8 197 130 440 130s440-58.2 440-130v-0.4l-40-0.2z m0-221.5v0.5c-0.2 0.9-3 8.6-21.6 20.6-19.5 12.5-48.7 24.5-84.5 34.7-78.9 22.3-183.2 34.7-293.9 34.7s-215-12.3-293.9-34.7c-35.8-10.2-65-22.1-84.5-34.7-18.6-12-21.3-19.7-21.6-20.6v-0.3l-40 0.3v0.1c0 71.8 197 130 440 130s440-58.2 440-130v-0.4l-40-0.2z" fill="" p-id="7071"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
src/components/Icon/icons/delete.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1681997838051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4520" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M898 178.7H665.3c4.3-9.8 6.7-20.6 6.7-32 0-44-36-80-80-80H432c-44 0-80 36-80 80 0 11.4 2.4 22.2 6.7 32H126c-13.2 0-24 10.8-24 24s10.8 24 24 24h772c13.2 0 24-10.8 24-24s-10.8-24-24-24z m-466 0c-8.5 0-16.5-3.4-22.6-9.4-6.1-6.1-9.4-14.1-9.4-22.6s3.4-16.5 9.4-22.6c6.1-6.1 14.1-9.4 22.6-9.4h160c8.5 0 16.5 3.4 22.6 9.4 6.1 6.1 9.4 14.1 9.4 22.6 0 8.5-3.4 16.5-9.4 22.6-6.1 6.1-14.1 9.4-22.6 9.4H432zM513 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2zM363 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2zM663 774.7c18.1 0 33-14.8 33-33v-334c0-18.1-14.9-33-33-33h-2c-18.1 0-33 14.8-33 33v334c0 18.2 14.8 33 33 33h2z" p-id="4521"></path><path d="M812 280.7c-13.3 0-24 10.7-24 24v530c0 41.9-34.1 76-76 76H312c-41.9 0-76-34.1-76-76v-530c0-13.3-10.7-24-24-24s-24 10.7-24 24v530c0 68.4 55.6 124 124 124h400c68.4 0 124-55.6 124-124v-530c0-13.2-10.7-24-24-24z" p-id="4522"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/components/Icon/icons/history.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682232686576" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8959" xmlns:xlink="http://www.w3.org/1999/xlink" width="28" height="28"><path d="M762.805186 140.938939c-14.335497-9.66922-33.725102-5.887081-43.373857 8.398274-9.648754 14.295588-5.897314 33.714869 8.398274 43.373857 106.369609 71.852468 169.864736 191.267185 169.864736 319.445496 0 212.414831-172.802648 385.217479-385.217479 385.217479S127.259382 724.571397 127.259382 512.156566c0-128.178311 63.494103-247.593028 169.864736-319.445496 14.295588-9.658987 18.047028-29.078269 8.398274-43.373857-9.658987-14.285355-29.088502-18.067494-43.373857-8.398274C138.575102 224.432539 64.791655 363.206162 64.791655 512.156566c0 246.851131 200.834074 447.685205 447.685205 447.685205S960.162066 759.007697 960.162066 512.156566C960.162066 363.206162 886.377596 224.432539 762.805186 140.938939z" p-id="8960"></path><path d="M401.003 64.47136c-17.253966 0-31.234375 13.980409-31.234375 31.233352l0 30.470989c0 17.253966 13.980409 31.234375 31.234375 31.234375s31.234375-13.980409 31.234375-31.234375L432.237375 95.704712C432.236352 78.450746 418.256966 64.47136 401.003 64.47136z" p-id="8961"></path><path d="M623.950721 64.47136c-17.253966 0-31.233352 13.980409-31.233352 31.233352l0 30.470989c0 17.253966 13.980409 31.234375 31.233352 31.234375s31.234375-13.980409 31.234375-31.234375L655.185097 95.704712C655.184073 78.450746 641.204687 64.47136 623.950721 64.47136z" p-id="8962"></path><path d="M426.012603 227.493248c11.214413 18.047028 41.970904 48.589648 86.157265 48.589648 43.963281 0 75.105558-30.318516 86.574774-48.223305 9.222035-14.396895 5.03262-33.358759-9.242502-42.763966-14.304797-9.405207-33.593096-5.398964-43.159986 8.764618-0.132006 0.193405-13.614066 19.754926-34.172287 19.754926-19.989263 0-32.423457-18.098193-33.267685-19.36914-9.160637-14.427594-28.264741-18.799158-42.834574-9.770528C421.416935 193.584973 416.912341 212.841549 426.012603 227.493248z" p-id="8963"></path><path d="M510.781242 335.164502c-17.253966 0-31.233352 13.980409-31.233352 31.233352l0 208.225415c0 0.63445 0.149403 1.227967 0.187265 1.853208 0.067538 1.115404 0.148379 2.217505 0.333598 3.314489 0.168846 1.00898 0.416486 1.978051 0.679475 2.951215 0.258896 0.954745 0.529049 1.895163 0.87595 2.821255 0.36839 0.981351 0.801249 1.916653 1.26276 2.847861 0.431835 0.876973 0.880043 1.734504 1.393743 2.569522 0.532119 0.860601 1.115404 1.670036 1.727341 2.472308 0.610914 0.805342 1.235131 1.588171 1.926886 2.336208 0.688685 0.74292 1.424442 1.420349 2.181689 2.093684 0.741897 0.659009 1.484817 1.303692 2.298346 1.89721 0.899486 0.657986 1.850138 1.222851 2.819209 1.783623 0.544399 0.314155 1.00898 0.714268 1.577938 0.998747l208.225415 104.113219c4.484128 2.236947 9.252735 3.304256 13.94971 3.304256 11.44875 0 22.479991-6.334265 27.959795-17.274432 7.706519-15.433504 1.454118-34.192753-13.970176-41.909505l-190.961216-95.480608L542.015617 366.397854C542.015617 349.143888 528.035208 335.164502 510.781242 335.164502z" p-id="8964"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
1
src/components/Icon/icons/promotion.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682078370900" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3577" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M941.312 888.704H628.032a32 32 0 0 1 0-64h313.28a32 32 0 0 1 0 64zM519.808 576.768c-158.976 0-288.384-129.344-288.384-288.384S360.832 0 519.808 0s288.384 129.344 288.384 288.384-129.408 288.384-288.384 288.384z m0-512.768C396.096 64 295.424 164.672 295.424 288.384s100.672 224.384 224.384 224.384c123.776 0 224.384-100.672 224.384-224.384S643.584 64 519.808 64z" p-id="3578"></path><path d="M763.264 606.528a31.552 31.552 0 0 1-16.96-4.864 427.2 427.2 0 0 0-100.544-45.952 32 32 0 0 1-21.184-40 31.744 31.744 0 0 1 39.936-21.184 492.16 492.16 0 0 1 115.712 52.864 32 32 0 0 1-16.96 59.136zM59.776 996.928a32 32 0 0 1-32-32 489.6 489.6 0 0 1 347.328-470.464 32 32 0 1 1 18.816 61.184 425.856 425.856 0 0 0-302.144 409.28 32 32 0 0 1-32 32zM964.224 879.68a32.128 32.128 0 0 1-24.32-11.2l-108.224-126.336a32 32 0 1 1 48.64-41.6l108.224 126.336a32 32 0 0 1-24.32 52.8z" p-id="3579"></path><path d="M856 1024a32 32 0 0 1-25.664-51.2l108.224-144.32a32.064 32.064 0 0 1 51.264 38.336L881.6 1011.2a32 32 0 0 1-25.6 12.8z" p-id="3580"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/components/Icon/icons/stop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682424901088" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3662" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M885.333333 85.333333H138.666667a53.393333 53.393333 0 0 0-53.333334 53.333334v746.666666a53.393333 53.393333 0 0 0 53.333334 53.333334h746.666666a53.393333 53.393333 0 0 0 53.333334-53.333334V138.666667a53.393333 53.393333 0 0 0-53.333334-53.333334z m-160 602.666667a37.373333 37.373333 0 0 1-37.333333 37.333333H336a37.373333 37.373333 0 0 1-37.333333-37.333333V336a37.373333 37.373333 0 0 1 37.333333-37.333333h352a37.373333 37.373333 0 0 1 37.333333 37.333333z" p-id="3663"></path></svg>
|
||||
|
After Width: | Height: | Size: 823 B |
1
src/components/Icon/icons/withdraw.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682079057126" class="icon" viewBox="0 0 1322 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2677" xmlns:xlink="http://www.w3.org/1999/xlink" width="36.1484375" height="28"><path d="M952.04654459 837.88839531H336.95615443A113.52706888 113.52706888 0 0 1 223.61160468 724.54384556v-419.79462838h728.43493991a113.52706888 113.52706888 0 0 1 113.34454973 113.34454975V724.54384556a113.52706888 113.52706888 0 0 1-113.34454973 113.34454975zM278.36742569 359.13999928v365.03880736a58.77124787 58.77124787 0 0 0 58.58872873 58.58872874h615.09039016a58.77124787 58.77124787 0 0 0 58.58872874-58.58872874V417.72872802a58.77124787 58.77124787 0 0 0-58.58872874-58.58872874z" p-id="2678"></path><path d="M278.36742569 350.37906772H223.61160468V297.44844068A111.51935577 111.51935577 0 0 1 334.94844068 186.11160469h334.01050924a111.51935577 111.51935577 0 0 1 111.33683598 111.33683599v49.09771996h-54.75582101V297.44844068A56.76353475 56.76353475 0 0 0 668.95894991 240.8674257H334.94844068A56.76353475 56.76353475 0 0 0 278.36742569 297.44844068zM1038.19570329 704.83175018H825.92563707A131.59649008 131.59649008 0 0 1 825.92563707 441.63877003h208.43715913v54.75582103H825.92563707a76.84066906 76.84066906 0 0 0 0 153.86385725h212.27006621z" p-id="2679"></path><path d="M889.80742792 600.43065117h-65.34194654a27.37791082 27.37791082 0 0 1 0-54.75582103h65.34194654a27.37791082 27.37791082 0 0 1-1e-8 54.75582103z" p-id="2680"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -13,14 +13,27 @@ const map = {
|
||||
board: require('./icons/board.svg').default,
|
||||
develop: require('./icons/develop.svg').default,
|
||||
user: require('./icons/user.svg').default,
|
||||
chatting: require('./icons/chatting.svg').default
|
||||
promotion: require('./icons/promotion.svg').default,
|
||||
delete: require('./icons/delete.svg').default,
|
||||
withdraw: require('./icons/withdraw.svg').default,
|
||||
dbModel: require('./icons/dbModel.svg').default,
|
||||
history: require('./icons/history.svg').default,
|
||||
stop: require('./icons/stop.svg').default
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof map;
|
||||
|
||||
const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps) => {
|
||||
return map[name] ? (
|
||||
<Icon as={map[name]} w={w} h={h} boxSizing={'content-box'} verticalAlign={'top'} {...props} />
|
||||
<Icon
|
||||
as={map[name]}
|
||||
w={w}
|
||||
h={h}
|
||||
boxSizing={'content-box'}
|
||||
verticalAlign={'top'}
|
||||
fill={'currentcolor'}
|
||||
{...props}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const Auth = ({ children }: { children: JSX.Element }) => {
|
||||
{
|
||||
onError(error) {
|
||||
console.log('error->', error);
|
||||
router.push('/login');
|
||||
router.replace('/login');
|
||||
toast();
|
||||
},
|
||||
onSettled() {
|
||||
|
||||
@@ -32,6 +32,12 @@ const navbarList = [
|
||||
link: '/number/setting',
|
||||
activeLink: ['/number/setting']
|
||||
},
|
||||
{
|
||||
label: '邀请',
|
||||
icon: 'promotion',
|
||||
link: '/promotion',
|
||||
activeLink: ['/promotion']
|
||||
},
|
||||
{
|
||||
label: '开发',
|
||||
icon: 'develop',
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
}
|
||||
.markdown ul,
|
||||
.markdown ol {
|
||||
padding-left: 1em;
|
||||
padding-left: 2em;
|
||||
}
|
||||
.markdown ul.no-list,
|
||||
.markdown ol.no-list {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
|
||||
@@ -13,7 +13,6 @@ import styles from './index.module.scss';
|
||||
import { codeLight } from './codeLight';
|
||||
|
||||
const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
|
||||
const formatSource = useMemo(() => source, [source]);
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
return (
|
||||
@@ -63,7 +62,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
|
||||
}}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{formatSource}
|
||||
{source}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
};
|
||||
|
||||
52
src/components/Radio/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { Stack, Box, Flex, useTheme } from '@chakra-ui/react';
|
||||
import type { StackProps } from '@chakra-ui/react';
|
||||
|
||||
// @ts-ignore
|
||||
interface Props extends StackProps {
|
||||
list: { label: string; value: string | number }[];
|
||||
value: string | number;
|
||||
onChange: (e: string | number) => void;
|
||||
}
|
||||
|
||||
const Radio = ({ list, value, onChange, ...props }: Props) => {
|
||||
return (
|
||||
<Stack {...props} spacing={5} direction={'row'}>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.value}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
_before={{
|
||||
content: '""',
|
||||
w: '16px',
|
||||
h: '16px',
|
||||
mr: 1,
|
||||
borderRadius: '16px',
|
||||
transition: '0.2s',
|
||||
...(value === item.value
|
||||
? {
|
||||
border: '5px solid',
|
||||
borderColor: 'blue.500'
|
||||
}
|
||||
: {
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.200'
|
||||
})
|
||||
}}
|
||||
_hover={{
|
||||
_before: {
|
||||
borderColor: 'blue.400'
|
||||
}
|
||||
}}
|
||||
onClick={() => onChange(item.value)}
|
||||
>
|
||||
{item.label}
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Radio;
|
||||
@@ -23,7 +23,7 @@ const WxConcat = ({ onClose }: { onClose: () => void }) => {
|
||||
<ModalBody textAlign={'center'}>
|
||||
<Image
|
||||
style={{ margin: 'auto' }}
|
||||
src={'/imgs/wxerweima300.jpg'}
|
||||
src={'/imgs/wx300.jpg'}
|
||||
width={200}
|
||||
height={200}
|
||||
alt=""
|
||||
@@ -31,7 +31,7 @@ const WxConcat = ({ onClose }: { onClose: () => void }) => {
|
||||
<Box mt={2}>
|
||||
微信号:
|
||||
<Box as={'span'} userSelect={'all'}>
|
||||
YNyiqi
|
||||
fastgpt123
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
@@ -1,72 +1,6 @@
|
||||
export enum EmailTypeEnum {
|
||||
export enum UserAuthTypeEnum {
|
||||
register = 'register',
|
||||
findPassword = 'findPassword'
|
||||
}
|
||||
|
||||
export const PRICE_SCALE = 100000;
|
||||
|
||||
export const introPage = `
|
||||
## 欢迎使用 Fast GPT
|
||||
|
||||
[Git 仓库](https://github.com/c121914yu/FastGPT)
|
||||
|
||||
### 交流群/问题反馈
|
||||
wx号: YNyiqi
|
||||

|
||||
|
||||
|
||||
### 快速开始
|
||||
1. 使用邮箱注册账号。
|
||||
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
|
||||
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
|
||||
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。
|
||||
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
|
||||
|
||||
### 定制 prompt
|
||||
|
||||
1. 进入模型编辑页
|
||||
2. 调整温度和提示词
|
||||
3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。
|
||||
|
||||
### 知识库
|
||||
|
||||
1. 创建模型时选择【知识库】
|
||||
2. 进入模型编辑页
|
||||
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
|
||||
4. 使用该模型对话。
|
||||
|
||||
注意:使用知识库模型对话时,tokens 消耗会加快。
|
||||
|
||||
### 价格表
|
||||
如果使用了自己的 Api Key,不会计费。可以在账号页,看到详细账单。单纯使用 chatGPT 模型进行对话,只有一个计费项目。使用知识库时,包含**对话**和**索引**生成两个计费项。
|
||||
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
|
||||
| --- | --- |
|
||||
| chatgpt - 对话 | 0.03 |
|
||||
| 知识库 - 对话 | 0.03 |
|
||||
| 知识库 - 索引 | 0.004 |
|
||||
| 文件拆分 | 0.03 |
|
||||
`;
|
||||
|
||||
export const chatProblem = `
|
||||
## 常见问题
|
||||
**内容长度**
|
||||
单次最长 4000 tokens, 上下文最长 8000 tokens, 上下文超长时会被截断。
|
||||
|
||||
**删除和复制**
|
||||
点击对话头像,可以选择复制或删除该条内容。
|
||||
|
||||
**代理出错**
|
||||
服务器代理不稳定,可以过一会儿再尝试。
|
||||
`;
|
||||
|
||||
export const versionIntro = `
|
||||
## Fast GPT V2.5
|
||||
* 内容压缩,替换中文标点符号和多余符号,减少一些上下文tokens。
|
||||
* 优化 QA 拆分记账。
|
||||
`;
|
||||
|
||||
export const shareHint = `
|
||||
你正准备分享对话,请确保分享链接不会滥用,因为它是使用的是你的 API key。
|
||||
* 分享空白对话:为该模型创建一个空白的聊天分享出去。
|
||||
* 分享当前对话:会把当前聊天的内容也分享出去,但是要注意不要多个人同时用一个聊天内容。
|
||||
`;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { DataType } from '@/types/data';
|
||||
|
||||
export const DataTypeTextMap: Record<DataType, string> = {
|
||||
QA: '问答拆分',
|
||||
abstract: '摘要总结'
|
||||
};
|
||||
@@ -1,26 +1,27 @@
|
||||
import type { ServiceName, ModelDataType, ModelSchema } from '@/types/mongoSchema';
|
||||
import type { RedisModelDataItemType } from '@/types/redis';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
|
||||
export enum ChatModelNameEnum {
|
||||
GPT35 = 'gpt-3.5-turbo',
|
||||
VECTOR_GPT = 'VECTOR_GPT',
|
||||
GPT3 = 'text-davinci-003',
|
||||
VECTOR = 'text-embedding-ada-002'
|
||||
export const embeddingModel = 'text-embedding-ada-002';
|
||||
export enum ChatModelEnum {
|
||||
'GPT35' = 'gpt-3.5-turbo',
|
||||
'GPT4' = 'gpt-4',
|
||||
'GPT432k' = 'gpt-4-32k'
|
||||
}
|
||||
|
||||
export const ChatModelNameMap = {
|
||||
[ChatModelNameEnum.GPT35]: 'gpt-3.5-turbo',
|
||||
[ChatModelNameEnum.VECTOR_GPT]: 'gpt-3.5-turbo',
|
||||
[ChatModelNameEnum.GPT3]: 'text-davinci-003',
|
||||
[ChatModelNameEnum.VECTOR]: 'text-embedding-ada-002'
|
||||
export enum ModelNameEnum {
|
||||
GPT35 = 'gpt-3.5-turbo',
|
||||
VECTOR_GPT = 'VECTOR_GPT'
|
||||
}
|
||||
|
||||
export const Model2ChatModelMap: Record<`${ModelNameEnum}`, `${ChatModelEnum}`> = {
|
||||
[ModelNameEnum.GPT35]: 'gpt-3.5-turbo',
|
||||
[ModelNameEnum.VECTOR_GPT]: 'gpt-3.5-turbo'
|
||||
};
|
||||
|
||||
export type ModelConstantsData = {
|
||||
serviceCompany: `${ServiceName}`;
|
||||
icon: 'model' | 'dbModel';
|
||||
name: string;
|
||||
model: `${ChatModelNameEnum}`;
|
||||
model: `${ModelNameEnum}`;
|
||||
trainName: string; // 空字符串代表不能训练
|
||||
maxToken: number;
|
||||
contextMaxToken: number;
|
||||
maxTemperature: number;
|
||||
price: number; // 多少钱 / 1token,单位: 0.00001元
|
||||
@@ -28,44 +29,25 @@ export type ModelConstantsData = {
|
||||
|
||||
export const modelList: ModelConstantsData[] = [
|
||||
{
|
||||
serviceCompany: 'openai',
|
||||
icon: 'model',
|
||||
name: 'chatGPT',
|
||||
model: ChatModelNameEnum.GPT35,
|
||||
model: ModelNameEnum.GPT35,
|
||||
trainName: '',
|
||||
maxToken: 4000,
|
||||
contextMaxToken: 7500,
|
||||
maxTemperature: 2,
|
||||
contextMaxToken: 4096,
|
||||
maxTemperature: 1.5,
|
||||
price: 3
|
||||
},
|
||||
{
|
||||
serviceCompany: 'openai',
|
||||
icon: 'dbModel',
|
||||
name: '知识库',
|
||||
model: ChatModelNameEnum.VECTOR_GPT,
|
||||
model: ModelNameEnum.VECTOR_GPT,
|
||||
trainName: 'vector',
|
||||
maxToken: 4000,
|
||||
contextMaxToken: 7000,
|
||||
contextMaxToken: 4096,
|
||||
maxTemperature: 1,
|
||||
price: 3
|
||||
}
|
||||
// {
|
||||
// serviceCompany: 'openai',
|
||||
// name: 'GPT3',
|
||||
// model: ChatModelNameEnum.GPT3,
|
||||
// trainName: 'davinci',
|
||||
// maxToken: 4000,
|
||||
// contextMaxToken: 7500,
|
||||
// maxTemperature: 2,
|
||||
// price: 30
|
||||
// }
|
||||
];
|
||||
|
||||
export enum TrainingStatusEnum {
|
||||
pending = 'pending',
|
||||
succeed = 'succeed',
|
||||
errored = 'errored',
|
||||
canceled = 'canceled'
|
||||
}
|
||||
|
||||
export enum ModelStatusEnum {
|
||||
running = 'running',
|
||||
training = 'training',
|
||||
@@ -92,27 +74,59 @@ export const formatModelStatus = {
|
||||
}
|
||||
};
|
||||
|
||||
export const ModelDataStatusMap: Record<RedisModelDataItemType['status'], string> = {
|
||||
export enum ModelDataStatusEnum {
|
||||
ready = 'ready',
|
||||
waiting = 'waiting'
|
||||
}
|
||||
|
||||
export const ModelDataStatusMap: Record<`${ModelDataStatusEnum}`, string> = {
|
||||
ready: '训练完成',
|
||||
waiting: '训练中'
|
||||
};
|
||||
|
||||
/* 知识库搜索时的配置 */
|
||||
// 搜索方式
|
||||
export enum ModelVectorSearchModeEnum {
|
||||
hightSimilarity = 'hightSimilarity', // 高相似度+禁止回复
|
||||
lowSimilarity = 'lowSimilarity', // 低相似度
|
||||
noContext = 'noContex' // 高相似度+无上下文回复
|
||||
}
|
||||
export const ModelVectorSearchModeMap: Record<
|
||||
`${ModelVectorSearchModeEnum}`,
|
||||
{
|
||||
text: string;
|
||||
similarity: number;
|
||||
}
|
||||
> = {
|
||||
[ModelVectorSearchModeEnum.hightSimilarity]: {
|
||||
text: '高相似度, 无匹配时拒绝回复',
|
||||
similarity: 0.2
|
||||
},
|
||||
[ModelVectorSearchModeEnum.noContext]: {
|
||||
text: '高相似度,无匹配时直接回复',
|
||||
similarity: 0.2
|
||||
},
|
||||
[ModelVectorSearchModeEnum.lowSimilarity]: {
|
||||
text: '低相似度匹配',
|
||||
similarity: 0.8
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultModel: ModelSchema = {
|
||||
_id: '',
|
||||
userId: '',
|
||||
name: '',
|
||||
name: 'modelName',
|
||||
avatar: '',
|
||||
status: ModelStatusEnum.pending,
|
||||
updateTime: Date.now(),
|
||||
trainingTimes: 0,
|
||||
systemPrompt: '',
|
||||
intro: '',
|
||||
temperature: 5,
|
||||
search: {
|
||||
mode: ModelVectorSearchModeEnum.hightSimilarity
|
||||
},
|
||||
service: {
|
||||
company: 'openai',
|
||||
trainId: '',
|
||||
chatModel: ChatModelNameEnum.GPT35,
|
||||
modelName: ChatModelNameEnum.GPT35
|
||||
chatModel: ModelNameEnum.GPT35,
|
||||
modelName: ModelNameEnum.GPT35
|
||||
},
|
||||
security: {
|
||||
domain: ['*'],
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export const VecModelDataPrefix = 'model:data';
|
||||
export const VecModelDataIdx = `idx:${VecModelDataPrefix}:hash`;
|
||||
export enum ModelDataStatusEnum {
|
||||
ready = 'ready',
|
||||
waiting = 'waiting'
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export const ERROR_CODE: { [key: number]: string } = {
|
||||
400: '请求失败',
|
||||
401: '无权访问',
|
||||
403: '紧张访问',
|
||||
404: '请求不存在',
|
||||
405: '请求方法错误',
|
||||
406: '请求的格式错误',
|
||||
410: '资源已删除',
|
||||
422: '验证错误',
|
||||
500: '服务器发生错误',
|
||||
502: '网关错误',
|
||||
503: '服务器暂时过载或维护',
|
||||
504: '网关超时'
|
||||
};
|
||||
|
||||
export const TOKEN_ERROR_CODE: { [key: number]: string } = {
|
||||
506: '请先登录',
|
||||
507: '请重新登录',
|
||||
508: '登录已过期'
|
||||
};
|
||||
@@ -20,3 +20,15 @@ export const BillTypeMap: Record<`${BillTypeEnum}`, string> = {
|
||||
[BillTypeEnum.vector]: '索引生成',
|
||||
[BillTypeEnum.return]: '退款'
|
||||
};
|
||||
|
||||
export enum PromotionEnum {
|
||||
invite = 'invite',
|
||||
shareModel = 'shareModel',
|
||||
withdraw = 'withdraw'
|
||||
}
|
||||
|
||||
export const PromotionTypeMap = {
|
||||
[PromotionEnum.invite]: '好友充值',
|
||||
[PromotionEnum.shareModel]: '模型分享',
|
||||
[PromotionEnum.withdraw]: '提现'
|
||||
};
|
||||
|
||||
15
src/hooks/useMarkdown.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
export const getMd = async (url: string) => {
|
||||
const response = await fetch(`/docs/${url}`);
|
||||
const textContent = await response.text();
|
||||
return textContent;
|
||||
};
|
||||
|
||||
export const useMarkdown = ({ url }: { url: string }) => {
|
||||
const { data = '' } = useQuery([url], () => getMd(url));
|
||||
|
||||
return {
|
||||
data
|
||||
};
|
||||
};
|
||||
@@ -18,7 +18,7 @@ export const usePagination = <T = any,>({
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const maxPage = useMemo(() => Math.ceil(total / pageSize), [pageSize, total]);
|
||||
const maxPage = useMemo(() => Math.ceil(total / pageSize) || 1, [pageSize, total]);
|
||||
|
||||
const { mutate, isLoading } = useMutation({
|
||||
mutationFn: async (num: number = pageNum) => {
|
||||
@@ -41,10 +41,6 @@ export const usePagination = <T = any,>({
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
mutate(1);
|
||||
}, []);
|
||||
|
||||
const Pagination = useCallback(() => {
|
||||
return (
|
||||
<Flex alignItems={'center'} justifyContent={'end'}>
|
||||
@@ -93,6 +89,10 @@ export const usePagination = <T = any,>({
|
||||
);
|
||||
}, [maxPage, mutate, pageNum]);
|
||||
|
||||
useEffect(() => {
|
||||
mutate(1);
|
||||
}, [mutate]);
|
||||
|
||||
return {
|
||||
pageNum,
|
||||
pageSize,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { sendCodeToEmail } from '@/api/user';
|
||||
import { EmailTypeEnum } from '@/constants/common';
|
||||
import { sendAuthCode } from '@/api/user';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
let timer: any;
|
||||
import { useToast } from './useToast';
|
||||
|
||||
@@ -19,11 +19,11 @@ export const useSendCode = () => {
|
||||
}, [codeCountDown]);
|
||||
|
||||
const sendCode = useCallback(
|
||||
async ({ email, type }: { email: string; type: `${EmailTypeEnum}` }) => {
|
||||
async ({ username, type }: { username: string; type: `${UserAuthTypeEnum}` }) => {
|
||||
setCodeSending(true);
|
||||
try {
|
||||
await sendCodeToEmail({
|
||||
email,
|
||||
await sendAuthCode({
|
||||
username,
|
||||
type
|
||||
});
|
||||
setCodeCountDown(60);
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
<meta name="description" content="Generated by Fast GPT" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
15
src/pages/_error.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
function Error({ statusCode }: { statusCode: number }) {
|
||||
return (
|
||||
<p>
|
||||
{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
Error.getInitialProps = ({ res, err }: { res: any; err: any }) => {
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
||||
console.log(err);
|
||||
return { statusCode };
|
||||
};
|
||||
|
||||
export default Error;
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getOpenAIApi, authChat } from '@/service/utils/chat';
|
||||
import { httpsAgent, openaiChatFilter } from '@/service/utils/tools';
|
||||
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import { getOpenAIApi, authChat } from '@/service/utils/auth';
|
||||
import { axiosConfig, openaiChatFilter } from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { PassThrough } from 'stream';
|
||||
import { modelList } from '@/constants/model';
|
||||
import { pushChatBill } from '@/service/events/pushBill';
|
||||
@@ -28,29 +26,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
try {
|
||||
const { chatId, prompt } = req.body as {
|
||||
const { chatId, prompt, modelId } = req.body as {
|
||||
prompt: ChatItemType;
|
||||
chatId: string;
|
||||
modelId: string;
|
||||
chatId: '' | string;
|
||||
};
|
||||
|
||||
const { authorization } = req.headers;
|
||||
if (!chatId || !prompt) {
|
||||
if (!modelId || !prompt) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
let startTime = Date.now();
|
||||
|
||||
const { chat, userApiKey, systemKey, userId } = await authChat(chatId, authorization);
|
||||
const { model, content, userApiKey, systemKey, userId } = await authChat({
|
||||
modelId,
|
||||
chatId,
|
||||
authorization
|
||||
});
|
||||
|
||||
const model: ModelSchema = chat.modelId;
|
||||
const modelConstantsData = modelList.find((item) => item.model === model.service.modelName);
|
||||
if (!modelConstantsData) {
|
||||
throw new Error('模型加载异常');
|
||||
}
|
||||
|
||||
// 读取对话内容
|
||||
const prompts = [...chat.content, prompt];
|
||||
const prompts = [...content, prompt];
|
||||
|
||||
// 如果有系统提示词,自动插入
|
||||
if (model.systemPrompt) {
|
||||
@@ -61,33 +63,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter(prompts, modelConstantsData.contextMaxToken);
|
||||
const filterPrompts = openaiChatFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts,
|
||||
maxTokens: modelConstantsData.contextMaxToken - 500
|
||||
});
|
||||
|
||||
// 格式化文本内容成 chatgpt 格式
|
||||
const map = {
|
||||
Human: ChatCompletionRequestMessageRoleEnum.User,
|
||||
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
|
||||
(item: ChatItemType) => ({
|
||||
role: map[item.obj],
|
||||
content: item.value
|
||||
})
|
||||
);
|
||||
// console.log(formatPrompts);
|
||||
// 计算温度
|
||||
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
|
||||
|
||||
// console.log(filterPrompts);
|
||||
// 获取 chatAPI
|
||||
const chatAPI = getOpenAIApi(userApiKey || systemKey);
|
||||
// 发出请求
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature: temperature,
|
||||
// max_tokens: modelConstantsData.maxToken,
|
||||
messages: formatPrompts,
|
||||
temperature,
|
||||
messages: filterPrompts,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream: true,
|
||||
@@ -96,7 +88,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
{
|
||||
timeout: 40000,
|
||||
responseType: 'stream',
|
||||
httpsAgent
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
@@ -109,7 +101,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
stream,
|
||||
chatResponse
|
||||
});
|
||||
const promptsContent = formatPrompts.map((item) => item.content).join('');
|
||||
|
||||
// 只有使用平台的 key 才计费
|
||||
pushChatBill({
|
||||
@@ -117,7 +108,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
modelName: model.service.modelName,
|
||||
userId,
|
||||
chatId,
|
||||
text: promptsContent + responseContent
|
||||
messages: filterPrompts.concat({ role: 'assistant', content: responseContent })
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (step === 1) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model, Chat } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId, isShare = 'false' } = req.query as {
|
||||
modelId: string;
|
||||
isShare?: 'true' | 'false';
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权生成对话');
|
||||
}
|
||||
|
||||
if (!modelId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取模型配置
|
||||
const model = await Model.findOne<ModelSchema>({
|
||||
_id: modelId,
|
||||
userId
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('模型不存在');
|
||||
}
|
||||
|
||||
// 创建 chat 数据
|
||||
const response = await Chat.create({
|
||||
userId,
|
||||
modelId,
|
||||
expiredTime: Date.now() + model.security.expiredTime,
|
||||
loadAmount: model.security.maxLoadAmount,
|
||||
isShare: isShare === 'true',
|
||||
content: []
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: response._id // 即聊天框的 ID
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
31
src/pages/api/chat/getHistory.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
|
||||
/* 获取历史记录 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const userId = await authToken(req.headers.authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const data = await Chat.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id title modelId'
|
||||
)
|
||||
.sort({ updateTime: -1 })
|
||||
.limit(20);
|
||||
|
||||
jsonRes(res, {
|
||||
data
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||
import type { ChatPopulate } from '@/types/mongoSchema';
|
||||
import type { InitChatResponse } from '@/api/response/chat';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { authModel } from '@/service/utils/auth';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -11,43 +13,49 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const { authorization } = req.headers;
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const { chatId } = req.query as { chatId: string };
|
||||
const { modelId, chatId } = req.query as { modelId: string; chatId: '' | string };
|
||||
|
||||
if (!chatId) {
|
||||
if (!modelId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取 chat 数据
|
||||
const chat = await Chat.findOne<ChatPopulate>({
|
||||
_id: chatId,
|
||||
userId
|
||||
}).populate({
|
||||
path: 'modelId',
|
||||
options: {
|
||||
strictPopulate: false
|
||||
}
|
||||
});
|
||||
// 获取 model 数据
|
||||
const { model } = await authModel(modelId, userId);
|
||||
|
||||
if (!chat) {
|
||||
throw new Error('聊天框不存在');
|
||||
// 历史记录
|
||||
let history: ChatItemType[] = [];
|
||||
|
||||
if (chatId) {
|
||||
// 获取 chat.content 数据
|
||||
history = await Chat.aggregate([
|
||||
{ $match: { _id: new mongoose.Types.ObjectId(chatId) } },
|
||||
{ $unwind: '$content' },
|
||||
{ $match: { 'content.deleted': false } },
|
||||
{ $sort: { 'content._id': -1 } },
|
||||
{ $limit: 50 },
|
||||
{
|
||||
$project: {
|
||||
id: '$content._id',
|
||||
obj: '$content.obj',
|
||||
value: '$content.value'
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
history.reverse();
|
||||
}
|
||||
|
||||
// filter 掉被 deleted 的内容
|
||||
chat.content = chat.content.filter((item) => item.deleted !== true);
|
||||
|
||||
const model = chat.modelId;
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId: chat._id,
|
||||
modelId: model._id,
|
||||
chatId: chatId || '',
|
||||
modelId: modelId,
|
||||
name: model.name,
|
||||
avatar: model.avatar,
|
||||
intro: model.intro,
|
||||
modelName: model.service.modelName,
|
||||
chatModel: model.service.chatModel,
|
||||
history: chat.content
|
||||
history
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
27
src/pages/api/chat/removeHistory.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
|
||||
/* 获取历史记录 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { id } = req.query;
|
||||
const userId = await authToken(req.headers.authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
await Chat.findOneAndRemove({
|
||||
_id: id,
|
||||
userId
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,54 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||
import { authModel } from '@/service/utils/auth';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
|
||||
/* 聊天内容存存储 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { chatId, prompts } = req.body as {
|
||||
chatId: string;
|
||||
const { chatId, modelId, prompts } = req.body as {
|
||||
chatId: '' | string;
|
||||
modelId: string;
|
||||
prompts: ChatItemType[];
|
||||
};
|
||||
|
||||
if (!chatId || !prompts) {
|
||||
if (!prompts) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
const userId = await authToken(req.headers.authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 存入库
|
||||
await Chat.findByIdAndUpdate(chatId, {
|
||||
$push: {
|
||||
content: {
|
||||
$each: prompts.map((item) => ({
|
||||
obj: item.obj,
|
||||
value: item.value
|
||||
}))
|
||||
}
|
||||
},
|
||||
updateTime: new Date()
|
||||
});
|
||||
const content = prompts.map((item) => ({
|
||||
obj: item.obj,
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
// 没有 chatId, 创建一个对话
|
||||
if (!chatId) {
|
||||
await authModel(modelId, userId);
|
||||
const { _id } = await Chat.create({
|
||||
userId,
|
||||
modelId,
|
||||
content,
|
||||
title: content[0].value.slice(0, 20)
|
||||
});
|
||||
return jsonRes(res, {
|
||||
data: _id
|
||||
});
|
||||
} else {
|
||||
// 已经有记录,追加入库
|
||||
await Chat.findByIdAndUpdate(chatId, {
|
||||
$push: {
|
||||
content: {
|
||||
$each: content
|
||||
}
|
||||
},
|
||||
updateTime: new Date()
|
||||
});
|
||||
}
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authChat } from '@/service/utils/chat';
|
||||
import { httpsAgent, openaiChatFilter, systemPromptFilter } from '@/service/utils/tools';
|
||||
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import { authChat } from '@/service/utils/auth';
|
||||
import { axiosConfig, systemPromptFilter, openaiChatFilter } from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { PassThrough } from 'stream';
|
||||
import { modelList } from '@/constants/model';
|
||||
import {
|
||||
modelList,
|
||||
ModelVectorSearchModeMap,
|
||||
ModelVectorSearchModeEnum,
|
||||
ModelDataStatusEnum
|
||||
} from '@/constants/model';
|
||||
import { pushChatBill } from '@/service/events/pushBill';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataPrefix } from '@/constants/redis';
|
||||
import { vectorToBuffer } from '@/utils/tools';
|
||||
import { openaiCreateEmbedding, gpt35StreamResponse } from '@/service/utils/openai';
|
||||
import dayjs from 'dayjs';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -31,30 +33,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
try {
|
||||
const { chatId, prompt } = req.body as {
|
||||
const { modelId, chatId, prompt } = req.body as {
|
||||
modelId: string;
|
||||
chatId: '' | string;
|
||||
prompt: ChatItemType;
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
const { authorization } = req.headers;
|
||||
if (!chatId || !prompt) {
|
||||
if (!modelId || !prompt) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
let startTime = Date.now();
|
||||
|
||||
const { chat, userApiKey, systemKey, userId } = await authChat(chatId, authorization);
|
||||
const { model, content, userApiKey, systemKey, userId } = await authChat({
|
||||
modelId,
|
||||
chatId,
|
||||
authorization
|
||||
});
|
||||
|
||||
const model: ModelSchema = chat.modelId;
|
||||
const modelConstantsData = modelList.find((item) => item.model === model.service.modelName);
|
||||
if (!modelConstantsData) {
|
||||
throw new Error('模型加载异常');
|
||||
}
|
||||
|
||||
// 读取对话内容
|
||||
const prompts = [...chat.content, prompt];
|
||||
const prompts = [...content, prompt];
|
||||
|
||||
// 获取提示词的向量
|
||||
const { vector: promptVector, chatAPI } = await openaiCreateEmbedding({
|
||||
@@ -64,66 +69,70 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
text: prompt.value
|
||||
});
|
||||
|
||||
// 搜索系统提示词, 按相似度从 redis 中搜出相关的 q 和 text
|
||||
const redisData: any[] = await redis.sendCommand([
|
||||
'FT.SEARCH',
|
||||
`idx:${VecModelDataPrefix}:hash`,
|
||||
`@modelId:{${String(
|
||||
chat.modelId._id
|
||||
)}} @vector:[VECTOR_RANGE 0.24 $blob]=>{$YIELD_DISTANCE_AS: score}`,
|
||||
'RETURN',
|
||||
'1',
|
||||
'text',
|
||||
'SORTBY',
|
||||
'score',
|
||||
'PARAMS',
|
||||
'2',
|
||||
'blob',
|
||||
vectorToBuffer(promptVector),
|
||||
'LIMIT',
|
||||
'0',
|
||||
'20',
|
||||
'DIALECT',
|
||||
'2'
|
||||
]);
|
||||
|
||||
const formatRedisPrompt: string[] = [];
|
||||
// 格式化响应值,获取 qa
|
||||
for (let i = 2; i < 42; i += 2) {
|
||||
const text = redisData[i]?.[1];
|
||||
if (text) {
|
||||
formatRedisPrompt.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
if (formatRedisPrompt.length === 0) {
|
||||
throw new Error('对不起,我没有找到你的问题');
|
||||
}
|
||||
|
||||
// textArr 筛选,最多 2800 tokens
|
||||
const systemPrompt = systemPromptFilter(formatRedisPrompt, 2800);
|
||||
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: `${model.systemPrompt} 知识库内容是最新的,知识库内容为: "${systemPrompt}"`
|
||||
// 相似度搜素
|
||||
const similarity = ModelVectorSearchModeMap[model.search.mode]?.similarity || 0.22;
|
||||
const vectorSearch = await PgClient.select<{ id: string; q: string; a: string }>('modelData', {
|
||||
fields: ['id', 'q', 'a'],
|
||||
where: [
|
||||
['status', ModelDataStatusEnum.ready],
|
||||
'AND',
|
||||
['model_id', model._id],
|
||||
'AND',
|
||||
`vector <=> '[${promptVector}]' < ${similarity}`
|
||||
],
|
||||
order: [{ field: 'vector', mode: `<=> '[${promptVector}]'` }],
|
||||
limit: 20
|
||||
});
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter(prompts, modelConstantsData.contextMaxToken);
|
||||
const formatRedisPrompt: string[] = vectorSearch.rows.map((item) => `${item.q}\n${item.a}`);
|
||||
|
||||
// 格式化文本内容成 chatgpt 格式
|
||||
const map = {
|
||||
Human: ChatCompletionRequestMessageRoleEnum.User,
|
||||
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
|
||||
(item: ChatItemType) => ({
|
||||
role: map[item.obj],
|
||||
content: item.value
|
||||
})
|
||||
);
|
||||
// console.log(formatPrompts);
|
||||
/* 高相似度+退出,无法匹配时直接退出 */
|
||||
if (
|
||||
formatRedisPrompt.length === 0 &&
|
||||
model.search.mode === ModelVectorSearchModeEnum.hightSimilarity
|
||||
) {
|
||||
return res.send('对不起,你的问题不在知识库中。');
|
||||
}
|
||||
/* 高相似度+无上下文,不添加额外知识 */
|
||||
if (
|
||||
formatRedisPrompt.length === 0 &&
|
||||
model.search.mode === ModelVectorSearchModeEnum.noContext
|
||||
) {
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: model.systemPrompt
|
||||
});
|
||||
} else {
|
||||
// 有匹配情况下,system 添加知识库内容。
|
||||
// 系统提示词过滤,最多 2500 tokens
|
||||
const systemPrompt = systemPromptFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts: formatRedisPrompt,
|
||||
maxTokens: 2500
|
||||
});
|
||||
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: `
|
||||
${model.systemPrompt}
|
||||
${
|
||||
model.search.mode === ModelVectorSearchModeEnum.hightSimilarity
|
||||
? `你只能从知识库选择内容回答.不在知识库内容拒绝回复`
|
||||
: ''
|
||||
}
|
||||
知识库内容为: 当前时间为${dayjs().format('YYYY/MM/DD HH:mm:ss')}\n${systemPrompt}'
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts,
|
||||
maxTokens: modelConstantsData.contextMaxToken - 500
|
||||
});
|
||||
|
||||
// console.log(filterPrompts);
|
||||
// 计算温度
|
||||
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
|
||||
|
||||
@@ -131,17 +140,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature: temperature,
|
||||
// max_tokens: modelConstantsData.maxToken,
|
||||
messages: formatPrompts,
|
||||
temperature,
|
||||
messages: filterPrompts,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream: true
|
||||
stream: true,
|
||||
stop: ['.!?。']
|
||||
},
|
||||
{
|
||||
timeout: 40000,
|
||||
responseType: 'stream',
|
||||
httpsAgent
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
@@ -155,14 +164,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
chatResponse
|
||||
});
|
||||
|
||||
const promptsContent = formatPrompts.map((item) => item.content).join('');
|
||||
// 只有使用平台的 key 才计费
|
||||
pushChatBill({
|
||||
isPay: !userApiKey,
|
||||
modelName: model.service.modelName,
|
||||
userId,
|
||||
chatId,
|
||||
text: promptsContent + responseContent
|
||||
messages: filterPrompts.concat({ role: 'assistant', content: responseContent })
|
||||
});
|
||||
// jsonRes(res);
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Data, DataItem } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import type { DataListItem } from '@/types/data';
|
||||
import type { PagingData } from '@/types';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('缺少登录凭证');
|
||||
}
|
||||
|
||||
await authToken(authorization);
|
||||
|
||||
const { dataId } = req.query as { dataId: string };
|
||||
if (!dataId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
await Data.findByIdAndUpdate(dataId, {
|
||||
isDeleted: true
|
||||
});
|
||||
|
||||
// 改变 dataItem 状态为 0
|
||||
await DataItem.updateMany(
|
||||
{
|
||||
dataId
|
||||
},
|
||||
{
|
||||
status: 0
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes<PagingData<DataListItem>>(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Data, DataItem } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import type { DataListItem } from '@/types/data';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('缺少登录凭证');
|
||||
}
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const datalist = await Data.aggregate<DataListItem>([
|
||||
{
|
||||
$match: {
|
||||
userId: new mongoose.Types.ObjectId(userId),
|
||||
isDeleted: false
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { createTime: -1 } // 按照创建时间倒序排列
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'dataitems',
|
||||
localField: '_id',
|
||||
foreignField: 'dataId',
|
||||
as: 'items'
|
||||
}
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
totalData: {
|
||||
$size: '$items' // 统计dataItem的总数
|
||||
},
|
||||
trainingData: {
|
||||
$size: {
|
||||
$filter: {
|
||||
input: '$items',
|
||||
as: 'item',
|
||||
cond: { $ne: ['$$item.status', 0] } // 统计 status 不为0的数量
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
items: 0 // 不返回 items 字段
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
data: datalist
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Data } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import type { DataType } from '@/types/data';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let { name, type } = req.body as { name: string; type: DataType };
|
||||
if (!name || !type) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
await connectToDatabase();
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
// 生成 data 集合
|
||||
const data = await Data.create({
|
||||
userId,
|
||||
name,
|
||||
type
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: data._id
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Data } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import type { DataListItem } from '@/types/data';
|
||||
import type { PagingData } from '@/types';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('缺少登录凭证');
|
||||
}
|
||||
|
||||
await authToken(authorization);
|
||||
|
||||
const { dataId, name } = req.query as { dataId: string; name: string };
|
||||
if (!dataId || !name) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
await Data.findByIdAndUpdate(dataId, {
|
||||
name
|
||||
});
|
||||
|
||||
jsonRes<PagingData<DataListItem>>(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, DataItem, Data } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { generateQA } from '@/service/events/generateQA';
|
||||
import { generateAbstract } from '@/service/events/generateAbstract';
|
||||
import { encode } from 'gpt-token-utils';
|
||||
|
||||
/* 拆分数据成QA */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { text, dataId } = req.body as { text: string; dataId: string };
|
||||
if (!text || !dataId) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
await connectToDatabase();
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const DataRecord = await Data.findById(dataId);
|
||||
|
||||
if (!DataRecord) {
|
||||
throw new Error('找不到数据集');
|
||||
}
|
||||
const replaceText = text.replace(/[\\n]+/g, ' ');
|
||||
|
||||
// 文本拆分成 chunk
|
||||
let chunks = replaceText.match(/[^!?.。]+[!?.。]/g) || [];
|
||||
|
||||
const dataItems: any[] = [];
|
||||
let splitText = '';
|
||||
|
||||
chunks.forEach((chunk) => {
|
||||
splitText += chunk;
|
||||
const tokens = encode(splitText).length;
|
||||
if (tokens >= 780) {
|
||||
dataItems.push({
|
||||
userId,
|
||||
dataId,
|
||||
type: DataRecord.type,
|
||||
text: splitText,
|
||||
status: 1
|
||||
});
|
||||
splitText = '';
|
||||
}
|
||||
});
|
||||
|
||||
// 批量插入数据
|
||||
await DataItem.insertMany(dataItems);
|
||||
|
||||
try {
|
||||
generateQA();
|
||||
generateAbstract();
|
||||
} catch (error) {
|
||||
error;
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
data: { chunks, replaceText }
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { ModelStatusEnum, modelList, ChatModelNameEnum, ChatModelNameMap } from '@/constants/model';
|
||||
import { ModelStatusEnum, modelList, ModelNameEnum, Model2ChatModelMap } from '@/constants/model';
|
||||
import { Model } from '@/service/models/model';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { name, serviceModelName } = req.body as {
|
||||
name: string;
|
||||
serviceModelName: `${ChatModelNameEnum}`;
|
||||
serviceModelName: `${ModelNameEnum}`;
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
@@ -47,9 +47,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
userId,
|
||||
status: ModelStatusEnum.running,
|
||||
service: {
|
||||
company: modelItem.serviceCompany,
|
||||
trainId: '',
|
||||
chatModel: ChatModelNameMap[modelItem.model], // 聊天时用的模型
|
||||
chatModel: Model2ChatModelMap[modelItem.model], // 聊天时用的模型
|
||||
modelName: modelItem.model // 最底层的模型,不会变,用于计费等核心操作
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -21,15 +21,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const redis = await connectRedis();
|
||||
await PgClient.delete('modelData', {
|
||||
where: [['user_id', userId], 'AND', ['id', dataId]]
|
||||
});
|
||||
|
||||
// 校验是否为该用户的数据
|
||||
const dataItemUserId = await redis.hGet(dataId, 'userId');
|
||||
if (dataItemUserId !== userId) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
// 删除
|
||||
await redis.del(dataId);
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
@@ -2,8 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataIdx } from '@/constants/redis';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -25,28 +24,26 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
|
||||
// 从 redis 中获取数据
|
||||
const searchRes = await redis.ft.search(
|
||||
VecModelDataIdx,
|
||||
`@modelId:{${modelId}} @userId:{${userId}}`,
|
||||
{
|
||||
RETURN: ['q', 'text'],
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: 10000
|
||||
}
|
||||
}
|
||||
);
|
||||
// 统计数据
|
||||
const count = await PgClient.count('modelData', {
|
||||
where: [['model_id', modelId], 'AND', ['user_id', userId]]
|
||||
});
|
||||
// 从 pg 中获取所有数据
|
||||
const pgData = await PgClient.select<{ q: string; a: string }>('modelData', {
|
||||
where: [['model_id', modelId], 'AND', ['user_id', userId]],
|
||||
fields: ['q', 'a'],
|
||||
order: [{ field: 'id', mode: 'DESC' }],
|
||||
limit: count
|
||||
});
|
||||
|
||||
const data = searchRes.documents.map((item: any) => ({
|
||||
prompt: item.value.q,
|
||||
completion: item.value.text
|
||||
}));
|
||||
const data: [string, string][] = pgData.rows.map((item) => [
|
||||
item.q.replace(/\n/g, '\\n'),
|
||||
item.a.replace(/\n/g, '\\n')
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
data: JSON.stringify(data)
|
||||
data
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import axios from 'axios';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
import { axiosConfig } from '@/service/utils/tools';
|
||||
|
||||
/**
|
||||
* 读取网站的内容
|
||||
@@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const data = await axios
|
||||
.get(url, {
|
||||
httpsAgent
|
||||
httpsAgent: axiosConfig.httpsAgent
|
||||
})
|
||||
.then((res) => res.data as string);
|
||||
|
||||
|
||||
@@ -2,22 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataIdx } from '@/constants/redis';
|
||||
import { SearchOptions } from 'redis';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import type { PgModelDataItemType } from '@/types/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
let {
|
||||
modelId,
|
||||
pageNum = 1,
|
||||
pageSize = 10
|
||||
pageSize = 10,
|
||||
searchText = ''
|
||||
} = req.query as {
|
||||
modelId: string;
|
||||
pageNum: string;
|
||||
pageSize: string;
|
||||
searchText: string;
|
||||
};
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
pageNum = +pageNum;
|
||||
@@ -35,34 +35,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
|
||||
// 从 redis 中获取数据
|
||||
const searchRes = await redis.ft.search(
|
||||
VecModelDataIdx,
|
||||
`@modelId:{${modelId}} @userId:{${userId}}`,
|
||||
{
|
||||
RETURN: ['q', 'text', 'status'],
|
||||
LIMIT: {
|
||||
from: (pageNum - 1) * pageSize,
|
||||
size: pageSize
|
||||
},
|
||||
SORTBY: {
|
||||
BY: 'modelId',
|
||||
DIRECTION: 'DESC'
|
||||
}
|
||||
}
|
||||
);
|
||||
const where: any = [
|
||||
['user_id', userId],
|
||||
'AND',
|
||||
['model_id', modelId],
|
||||
...(searchText ? ['AND', `(q LIKE '%${searchText}%' OR a LIKE '%${searchText}%')`] : [])
|
||||
];
|
||||
|
||||
const searchRes = await PgClient.select<PgModelDataItemType>('modelData', {
|
||||
fields: ['id', 'q', 'a', 'status'],
|
||||
where,
|
||||
order: [{ field: 'id', mode: 'DESC' }],
|
||||
limit: pageSize,
|
||||
offset: pageSize * (pageNum - 1)
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data: searchRes.documents.map((item) => ({
|
||||
id: item.id,
|
||||
...item.value
|
||||
})),
|
||||
total: searchRes.total
|
||||
data: searchRes.rows,
|
||||
total: await PgClient.count('modelData', {
|
||||
where
|
||||
})
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
99
src/pages/api/model/data/pushModelDataCsv.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
import { ModelDataStatusEnum } from '@/constants/model';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId, data } = req.body as {
|
||||
modelId: string;
|
||||
data: string[][];
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
|
||||
if (!modelId || !Array.isArray(data)) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 验证是否是该用户的 model
|
||||
const model = await Model.findOne({
|
||||
_id: modelId,
|
||||
userId
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('无权操作该模型');
|
||||
}
|
||||
|
||||
// 去重
|
||||
const searchRes = await Promise.allSettled(
|
||||
data.map(async ([q, a]) => {
|
||||
if (!q || !a) {
|
||||
return Promise.reject('q/a为空');
|
||||
}
|
||||
try {
|
||||
q = q.replace(/\\n/g, '\n');
|
||||
a = a.replace(/\\n/g, '\n');
|
||||
const count = await PgClient.count('modelData', {
|
||||
where: [
|
||||
['user_id', userId],
|
||||
'AND',
|
||||
['model_id', modelId],
|
||||
'AND',
|
||||
['q', q],
|
||||
'AND',
|
||||
['a', a]
|
||||
]
|
||||
});
|
||||
if (count > 0) {
|
||||
return Promise.reject('已经存在');
|
||||
}
|
||||
} catch (error) {
|
||||
error;
|
||||
}
|
||||
return Promise.resolve({
|
||||
q,
|
||||
a
|
||||
});
|
||||
})
|
||||
);
|
||||
// 过滤重复的内容
|
||||
const filterData = searchRes
|
||||
.filter((item) => item.status === 'fulfilled')
|
||||
.map<{ q: string; a: string }>((item: any) => item.value);
|
||||
|
||||
// 插入 pg
|
||||
const insertRes = await PgClient.insert('modelData', {
|
||||
values: filterData.map((item) => [
|
||||
{ key: 'user_id', value: userId },
|
||||
{ key: 'model_id', value: modelId },
|
||||
{ key: 'q', value: item.q },
|
||||
{ key: 'a', value: item.a },
|
||||
{ key: 'status', value: ModelDataStatusEnum.waiting }
|
||||
])
|
||||
});
|
||||
|
||||
generateVector();
|
||||
|
||||
jsonRes(res, {
|
||||
data: insertRes.rowCount
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,13 @@ import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { ModelDataSchema } from '@/types/mongoSchema';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataPrefix, ModelDataStatusEnum } from '@/constants/redis';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId, data } = req.body as {
|
||||
modelId: string;
|
||||
data: { text: ModelDataSchema['text']; q: ModelDataSchema['q'] }[];
|
||||
data: { a: ModelDataSchema['a']; q: ModelDataSchema['q'] }[];
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
@@ -27,7 +26,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
|
||||
// 验证是否是该用户的 model
|
||||
const model = await Model.findOne({
|
||||
@@ -39,29 +37,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('无权操作该模型');
|
||||
}
|
||||
|
||||
const insertRes = await Promise.allSettled(
|
||||
data.map((item) => {
|
||||
return redis.sendCommand([
|
||||
'HMSET',
|
||||
`${VecModelDataPrefix}:${item.q.id}`,
|
||||
'userId',
|
||||
userId,
|
||||
'modelId',
|
||||
modelId,
|
||||
'q',
|
||||
item.q.text,
|
||||
'text',
|
||||
item.text,
|
||||
'status',
|
||||
ModelDataStatusEnum.waiting
|
||||
]);
|
||||
})
|
||||
);
|
||||
// 插入记录
|
||||
await PgClient.insert('modelData', {
|
||||
values: data.map((item) => [
|
||||
{ key: 'user_id', value: userId },
|
||||
{ key: 'model_id', value: modelId },
|
||||
{ key: 'q', value: item.q },
|
||||
{ key: 'a', value: item.a },
|
||||
{ key: 'status', value: 'waiting' }
|
||||
])
|
||||
});
|
||||
|
||||
generateVector();
|
||||
|
||||
jsonRes(res, {
|
||||
data: insertRes.filter((item) => item.status === 'rejected').length
|
||||
data: 0
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
import { vectorToBuffer, formatVector } from '@/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataPrefix, ModelDataStatusEnum } from '@/constants/redis';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId, data } = req.body as {
|
||||
modelId: string;
|
||||
data: { prompt: string; completion: string; vector?: number[] }[];
|
||||
};
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
|
||||
if (!modelId || !Array.isArray(data)) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
|
||||
// 验证是否是该用户的 model
|
||||
const model = await Model.findOne({
|
||||
_id: modelId,
|
||||
userId
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('无权操作该模型');
|
||||
}
|
||||
|
||||
// 插入 redis
|
||||
const insertRedisRes = await Promise.allSettled(
|
||||
data.map((item) => {
|
||||
const vector = item.vector;
|
||||
|
||||
return redis.sendCommand([
|
||||
'HMSET',
|
||||
`${VecModelDataPrefix}:${nanoid()}`,
|
||||
'userId',
|
||||
userId,
|
||||
'modelId',
|
||||
String(modelId),
|
||||
...(vector ? ['vector', vectorToBuffer(formatVector(vector))] : []),
|
||||
'q',
|
||||
item.prompt,
|
||||
'text',
|
||||
item.completion,
|
||||
'status',
|
||||
vector ? ModelDataStatusEnum.ready : ModelDataStatusEnum.waiting
|
||||
]);
|
||||
})
|
||||
);
|
||||
|
||||
generateVector();
|
||||
|
||||
jsonRes(res, {
|
||||
data: insertRedisRes.filter((item) => item.status === 'rejected').length
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { ModelDataStatusEnum } from '@/constants/redis';
|
||||
import { ModelDataStatusEnum } from '@/constants/model';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { dataId, text, q } = req.body as { dataId: string; text: string; q?: string };
|
||||
const { dataId, a, q } = req.body as { dataId: string; a: string; q?: string };
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
@@ -21,26 +21,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 凭证校验
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
const redis = await connectRedis();
|
||||
// 更新 pg 内容
|
||||
await PgClient.update('modelData', {
|
||||
where: [['id', dataId], 'AND', ['user_id', userId]],
|
||||
values: [
|
||||
{ key: 'a', value: a },
|
||||
...(q
|
||||
? [
|
||||
{ key: 'q', value: q },
|
||||
{ key: 'status', value: ModelDataStatusEnum.waiting }
|
||||
]
|
||||
: [])
|
||||
]
|
||||
});
|
||||
|
||||
// 校验是否为该用户的数据
|
||||
const dataItemUserId = await redis.hGet(dataId, 'userId');
|
||||
if (dataItemUserId !== userId) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
|
||||
// 更新
|
||||
await redis.sendCommand([
|
||||
'HMSET',
|
||||
dataId,
|
||||
...(q ? ['q', q, 'status', ModelDataStatusEnum.waiting] : []),
|
||||
'text',
|
||||
text
|
||||
]);
|
||||
|
||||
if (q) {
|
||||
generateVector();
|
||||
}
|
||||
q && generateVector();
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
|
||||
@@ -2,14 +2,20 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, SplitData, Model } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
import { generateQA } from '@/service/events/generateQA';
|
||||
import { encode } from 'gpt-token-utils';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
/* 拆分数据成QA */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { text, modelId, prompt } = req.body as { text: string; modelId: string; prompt: string };
|
||||
if (!text || !modelId || !prompt) {
|
||||
const { chunks, modelId, prompt, mode } = req.body as {
|
||||
modelId: string;
|
||||
chunks: string[];
|
||||
prompt: string;
|
||||
mode: 'qa' | 'subsection';
|
||||
};
|
||||
if (!chunks || !modelId || !prompt) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
await connectToDatabase();
|
||||
@@ -28,46 +34,31 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('无权操作该模型');
|
||||
}
|
||||
|
||||
const replaceText = text.replace(/(\\n|\n)+/g, ' ');
|
||||
if (mode === 'qa') {
|
||||
// 批量QA拆分插入数据
|
||||
await SplitData.create({
|
||||
userId,
|
||||
modelId,
|
||||
textList: chunks,
|
||||
prompt
|
||||
});
|
||||
|
||||
// 文本拆分成 chunk
|
||||
const chunks = replaceText.match(/[^!?.。]+[!?.。]/g) || [];
|
||||
generateQA();
|
||||
} else if (mode === 'subsection') {
|
||||
// 插入记录
|
||||
await PgClient.insert('modelData', {
|
||||
values: chunks.map((item) => [
|
||||
{ key: 'user_id', value: userId },
|
||||
{ key: 'model_id', value: modelId },
|
||||
{ key: 'q', value: item },
|
||||
{ key: 'a', value: '' },
|
||||
{ key: 'status', value: 'waiting' }
|
||||
])
|
||||
});
|
||||
|
||||
const textList: string[] = [];
|
||||
let splitText = '';
|
||||
|
||||
/* 取 3k ~ 4K tokens 内容 */
|
||||
chunks.forEach((chunk) => {
|
||||
const tokens = encode(splitText + chunk).length;
|
||||
if (tokens >= 4000) {
|
||||
// 超过 4000,不要这块内容
|
||||
textList.push(splitText);
|
||||
splitText = chunk;
|
||||
} else if (tokens >= 3000) {
|
||||
// 超过 3000,取内容
|
||||
textList.push(splitText + chunk);
|
||||
splitText = '';
|
||||
} else {
|
||||
//没超过 3000,继续添加
|
||||
splitText += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
if (splitText) {
|
||||
textList.push(splitText);
|
||||
generateVector();
|
||||
}
|
||||
|
||||
// 批量插入数据
|
||||
await SplitData.create({
|
||||
userId,
|
||||
modelId,
|
||||
rawText: text,
|
||||
textList,
|
||||
prompt
|
||||
});
|
||||
|
||||
generateQA();
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Chat, Model, Training, connectToDatabase } from '@/service/mongo';
|
||||
import { Chat, Model, connectToDatabase } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { getUserApiOpenai } from '@/service/utils/openai';
|
||||
import { TrainingStatusEnum } from '@/constants/model';
|
||||
import { TrainingItemType } from '@/types/training';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataIdx } from '@/constants/redis';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { modelId } = req.query;
|
||||
const { modelId } = req.query as { modelId: string };
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
@@ -37,50 +32,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
|
||||
// 获取 redis 中模型关联的所有数据
|
||||
const searchRes = await redis.ft.search(
|
||||
VecModelDataIdx,
|
||||
`@modelId:{${modelId}} @userId:{${userId}}`,
|
||||
{
|
||||
LIMIT: {
|
||||
from: 0,
|
||||
size: 10000
|
||||
}
|
||||
}
|
||||
);
|
||||
// 删除 redis 内容
|
||||
await Promise.all(searchRes.documents.map((item) => redis.del(item.id)));
|
||||
// 删除 pg 中所有该模型的数据
|
||||
await PgClient.delete('modelData', {
|
||||
where: [['user_id', userId], 'AND', ['model_id', modelId]]
|
||||
});
|
||||
|
||||
// 删除对应的聊天
|
||||
await Chat.deleteMany({
|
||||
modelId
|
||||
});
|
||||
|
||||
// 查看是否正在训练
|
||||
const training: TrainingItemType | null = await Training.findOne({
|
||||
modelId,
|
||||
status: TrainingStatusEnum.pending
|
||||
});
|
||||
|
||||
// 如果正在训练,需要删除openai上的相关信息
|
||||
if (training) {
|
||||
const { openai } = await getUserApiOpenai(userId);
|
||||
// 获取训练记录
|
||||
const tuneRecord = await openai.retrieveFineTune(training.tuneId, { httpsAgent });
|
||||
|
||||
// 删除训练文件
|
||||
openai.deleteFile(tuneRecord.data.training_files[0].id, { httpsAgent });
|
||||
// 取消训练
|
||||
openai.cancelFineTune(training.tuneId, { httpsAgent });
|
||||
}
|
||||
|
||||
// 删除对应训练记录
|
||||
await Training.deleteMany({
|
||||
modelId
|
||||
});
|
||||
|
||||
// 删除模型
|
||||
await Model.deleteOne({
|
||||
_id: modelId,
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Training } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
|
||||
// 关闭next默认的bodyParser处理方式
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false
|
||||
}
|
||||
};
|
||||
|
||||
/* 获取模型训练记录 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
const { modelId } = req.query;
|
||||
if (!modelId) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
/* 获取 modelId 下的 training 记录 */
|
||||
const records = await Training.find({
|
||||
modelId
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: records
|
||||
});
|
||||
} catch (err: any) {
|
||||
/* 清除上传的文件,关闭训练记录 */
|
||||
// @ts-ignore
|
||||
if (openai) {
|
||||
// @ts-ignore
|
||||
uploadFileId && openai.deleteFile(uploadFileId);
|
||||
// @ts-ignore
|
||||
trainId && openai.cancelFineTune(trainId);
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model, Training } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { getUserApiOpenai } from '@/service/utils/openai';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import { TrainingItemType } from '@/types/training';
|
||||
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
|
||||
import { OpenAiTuneStatusEnum } from '@/service/constants/training';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
|
||||
/* 更新训练状态 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
const { modelId } = req.query as { modelId: string };
|
||||
if (!modelId) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取模型
|
||||
const model = await Model.findById<ModelSchema>(modelId);
|
||||
|
||||
if (!model || model.status !== 'training') {
|
||||
throw new Error('模型不在训练中');
|
||||
}
|
||||
|
||||
// 查询正在训练中的训练记录
|
||||
const training: TrainingItemType | null = await Training.findOne({
|
||||
modelId,
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
if (!training) {
|
||||
throw new Error('找不到训练记录');
|
||||
}
|
||||
|
||||
// 用户的 openai 实例
|
||||
const { openai } = await getUserApiOpenai(userId);
|
||||
|
||||
// 获取 openai 的训练情况
|
||||
const { data } = await openai.retrieveFineTune(training.tuneId, { httpsAgent });
|
||||
// console.log(data);
|
||||
if (data.status === OpenAiTuneStatusEnum.succeeded) {
|
||||
// 删除训练文件
|
||||
openai.deleteFile(data.training_files[0].id, { httpsAgent });
|
||||
|
||||
// 更新模型状态和模型内容
|
||||
await Model.findByIdAndUpdate(modelId, {
|
||||
status: ModelStatusEnum.running,
|
||||
updateTime: new Date(),
|
||||
service: {
|
||||
...model.service,
|
||||
trainId: data.fine_tuned_model, // 训练完后,再次训练和对话使用的 model 是一样的
|
||||
chatModel: data.fine_tuned_model
|
||||
}
|
||||
});
|
||||
// 更新训练数据
|
||||
await Training.findByIdAndUpdate(training._id, {
|
||||
status: TrainingStatusEnum.succeed
|
||||
});
|
||||
|
||||
return jsonRes(res, {
|
||||
data: '模型微调完成'
|
||||
});
|
||||
}
|
||||
|
||||
/* 取消微调 */
|
||||
if (data.status === OpenAiTuneStatusEnum.cancelled) {
|
||||
// 删除训练文件
|
||||
openai.deleteFile(data.training_files[0].id, { httpsAgent });
|
||||
|
||||
// 更新模型
|
||||
await Model.findByIdAndUpdate(modelId, {
|
||||
status: ModelStatusEnum.running,
|
||||
updateTime: new Date()
|
||||
});
|
||||
// 更新训练数据
|
||||
await Training.findByIdAndUpdate(training._id, {
|
||||
status: TrainingStatusEnum.canceled
|
||||
});
|
||||
|
||||
return jsonRes(res, {
|
||||
data: '模型微调已取消'
|
||||
});
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
data: '模型还在训练中'
|
||||
});
|
||||
} catch (err: any) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Model, Training } from '@/service/mongo';
|
||||
import formidable from 'formidable';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { getUserApiOpenai } from '@/service/utils/openai';
|
||||
import { join } from 'path';
|
||||
import fs from 'fs';
|
||||
import type { ModelSchema } from '@/types/mongoSchema';
|
||||
import type { OpenAIApi } from 'openai';
|
||||
import { ModelStatusEnum, TrainingStatusEnum } from '@/constants/model';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
|
||||
// 关闭next默认的bodyParser处理方式
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false
|
||||
}
|
||||
};
|
||||
|
||||
/* 上传文件,开始微调 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let openai: OpenAIApi, trainId: string, uploadFileId: string;
|
||||
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('无权操作');
|
||||
}
|
||||
const { modelId } = req.query;
|
||||
|
||||
if (!modelId) {
|
||||
throw new Error('参数错误');
|
||||
}
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 获取模型的状态
|
||||
const model = await Model.findById<ModelSchema>(modelId);
|
||||
|
||||
if (!model || model.status !== 'running') {
|
||||
throw new Error('模型正忙');
|
||||
}
|
||||
|
||||
// const trainingType = model.service.modelType
|
||||
const trainingType = model.service.trainId; // 目前都默认是 openai text-davinci-03
|
||||
|
||||
// 获取用户的 API Key 实例化后的对象
|
||||
const user = await getUserApiOpenai(userId);
|
||||
openai = user.openai;
|
||||
|
||||
// 接收文件并保存
|
||||
const form = formidable({
|
||||
uploadDir: join(process.cwd(), 'public/trainData'),
|
||||
keepExtensions: true
|
||||
});
|
||||
|
||||
const { files } = await new Promise<{
|
||||
fields: formidable.Fields;
|
||||
files: formidable.Files;
|
||||
}>((resolve, reject) => {
|
||||
form.parse(req, (err, fields, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve({ fields, files });
|
||||
});
|
||||
});
|
||||
const file = files.file;
|
||||
|
||||
// 上传文件到 openai
|
||||
// @ts-ignore
|
||||
const uploadRes = await openai.createFile(
|
||||
// @ts-ignore
|
||||
fs.createReadStream(file.filepath),
|
||||
'fine-tune',
|
||||
{ httpsAgent }
|
||||
);
|
||||
uploadFileId = uploadRes.data.id; // 记录上传文件的 ID
|
||||
|
||||
// 开始训练
|
||||
const trainRes = await openai.createFineTune(
|
||||
{
|
||||
training_file: uploadFileId,
|
||||
model: trainingType,
|
||||
suffix: model.name,
|
||||
n_epochs: 4
|
||||
},
|
||||
{ httpsAgent }
|
||||
);
|
||||
|
||||
trainId = trainRes.data.id; // 记录训练 ID
|
||||
|
||||
// 创建训练记录
|
||||
await Training.create({
|
||||
serviceName: 'openai',
|
||||
tuneId: trainId,
|
||||
status: TrainingStatusEnum.pending,
|
||||
modelId
|
||||
});
|
||||
|
||||
// 修改模型状态
|
||||
await Model.findByIdAndUpdate(modelId, {
|
||||
$inc: {
|
||||
trainingTimes: +1
|
||||
},
|
||||
updateTime: new Date(),
|
||||
status: ModelStatusEnum.training
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: 'start training'
|
||||
});
|
||||
} catch (err: any) {
|
||||
/* 清除上传的文件,关闭训练记录 */
|
||||
// @ts-ignore
|
||||
if (openai) {
|
||||
// @ts-ignore
|
||||
uploadFileId && openai.deleteFile(uploadFileId, { httpsAgent });
|
||||
// @ts-ignore
|
||||
trainId && openai.cancelFineTune(trainId, { httpsAgent });
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import type { ModelUpdateParams } from '@/types/model';
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { name, service, security, systemPrompt, intro, temperature } =
|
||||
const { name, search, service, security, systemPrompt, temperature } =
|
||||
req.body as ModelUpdateParams;
|
||||
const { modelId } = req.query as { modelId: string };
|
||||
const { authorization } = req.headers;
|
||||
@@ -35,9 +35,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
{
|
||||
name,
|
||||
systemPrompt,
|
||||
intro,
|
||||
temperature,
|
||||
// service,
|
||||
search,
|
||||
security
|
||||
}
|
||||
);
|
||||
|
||||
146
src/pages/api/openapi/chat/chatGpt.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import { getOpenAIApi } from '@/service/utils/auth';
|
||||
import { axiosConfig, openaiChatFilter, authOpenApiKey } from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { PassThrough } from 'stream';
|
||||
import { modelList } from '@/constants/model';
|
||||
import { pushChatBill } from '@/service/events/pushBill';
|
||||
import { gpt35StreamResponse } from '@/service/utils/openai';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let step = 0; // step=1时,表示开始了流响应
|
||||
const stream = new PassThrough();
|
||||
stream.on('error', () => {
|
||||
console.log('error: ', 'stream error');
|
||||
stream.destroy();
|
||||
});
|
||||
res.on('close', () => {
|
||||
stream.destroy();
|
||||
});
|
||||
res.on('error', () => {
|
||||
console.log('error: ', 'request error');
|
||||
stream.destroy();
|
||||
});
|
||||
|
||||
try {
|
||||
const {
|
||||
prompts,
|
||||
modelId,
|
||||
isStream = true
|
||||
} = req.body as {
|
||||
prompts: ChatItemType[];
|
||||
modelId: string;
|
||||
isStream: boolean;
|
||||
};
|
||||
|
||||
if (!prompts || !modelId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
if (!Array.isArray(prompts)) {
|
||||
throw new Error('prompts is not array');
|
||||
}
|
||||
if (prompts.length > 30 || prompts.length === 0) {
|
||||
throw new Error('prompts length range 1-30');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
let startTime = Date.now();
|
||||
|
||||
const { apiKey, userId } = await authOpenApiKey(req);
|
||||
|
||||
const model = await Model.findOne({
|
||||
_id: modelId,
|
||||
userId
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('无权使用该模型');
|
||||
}
|
||||
|
||||
const modelConstantsData = modelList.find((item) => item.model === model.service.modelName);
|
||||
if (!modelConstantsData) {
|
||||
throw new Error('模型加载异常');
|
||||
}
|
||||
|
||||
// 如果有系统提示词,自动插入
|
||||
if (model.systemPrompt) {
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: model.systemPrompt
|
||||
});
|
||||
}
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts,
|
||||
maxTokens: modelConstantsData.contextMaxToken - 500
|
||||
});
|
||||
|
||||
// console.log(filterPrompts);
|
||||
// 计算温度
|
||||
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
|
||||
|
||||
// 获取 chatAPI
|
||||
const chatAPI = getOpenAIApi(apiKey);
|
||||
// 发出请求
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature,
|
||||
messages: filterPrompts,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream: isStream,
|
||||
stop: ['.!?。']
|
||||
},
|
||||
{
|
||||
timeout: 40000,
|
||||
responseType: isStream ? 'stream' : 'json',
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
|
||||
|
||||
step = 1;
|
||||
let responseContent = '';
|
||||
|
||||
if (isStream) {
|
||||
const streamResponse = await gpt35StreamResponse({
|
||||
res,
|
||||
stream,
|
||||
chatResponse
|
||||
});
|
||||
responseContent = streamResponse.responseContent;
|
||||
} else {
|
||||
responseContent = chatResponse.data.choices?.[0]?.message?.content || '';
|
||||
jsonRes(res, {
|
||||
data: responseContent
|
||||
});
|
||||
}
|
||||
|
||||
// 只有使用平台的 key 才计费
|
||||
pushChatBill({
|
||||
isPay: true,
|
||||
modelName: model.service.modelName,
|
||||
userId,
|
||||
messages: filterPrompts.concat({ role: 'assistant', content: responseContent })
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (step === 1) {
|
||||
// 直接结束流
|
||||
console.log('error,结束');
|
||||
stream.destroy();
|
||||
} else {
|
||||
res.status(500);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import { getOpenAIApi } from '@/service/utils/chat';
|
||||
import { getOpenAIApi } from '@/service/utils/auth';
|
||||
import { authOpenApiKey } from '@/service/utils/tools';
|
||||
import { httpsAgent, openaiChatFilter, systemPromptFilter } from '@/service/utils/tools';
|
||||
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import { axiosConfig, openaiChatFilter, systemPromptFilter } from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { PassThrough } from 'stream';
|
||||
import { ChatModelNameEnum, modelList, ChatModelNameMap } from '@/constants/model';
|
||||
import {
|
||||
ModelNameEnum,
|
||||
modelList,
|
||||
ModelVectorSearchModeMap,
|
||||
ChatModelEnum
|
||||
} from '@/constants/model';
|
||||
import { pushChatBill } from '@/service/events/pushBill';
|
||||
import { connectRedis } from '@/service/redis';
|
||||
import { VecModelDataPrefix } from '@/constants/redis';
|
||||
import { vectorToBuffer } from '@/utils/tools';
|
||||
import { openaiCreateEmbedding, gpt35StreamResponse } from '@/service/utils/openai';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -46,7 +48,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
const redis = await connectRedis();
|
||||
let startTime = Date.now();
|
||||
|
||||
/* 凭证校验 */
|
||||
@@ -58,9 +59,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('找不到模型');
|
||||
}
|
||||
|
||||
const modelConstantsData = modelList.find(
|
||||
(item) => item.model === ChatModelNameEnum.VECTOR_GPT
|
||||
);
|
||||
const modelConstantsData = modelList.find((item) => item.model === ModelNameEnum.VECTOR_GPT);
|
||||
if (!modelConstantsData) {
|
||||
throw new Error('模型已下架');
|
||||
}
|
||||
@@ -72,36 +71,46 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
// 请求一次 chatgpt 拆解需求
|
||||
const promptResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: ChatModelNameMap[ChatModelNameEnum.GPT35],
|
||||
model: ChatModelEnum.GPT35,
|
||||
temperature: 0,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `服务端逻辑生成器.根据用户输入的需求,拆解成代码实现的步骤,并按格式返回: 1.\n2.\n3.\n ......
|
||||
content: `服务端逻辑生成器.根据用户输入的需求,拆解成 laf 云函数实现的步骤,只返回步骤,按格式返回步骤: 1.\n2.\n3.\n ......
|
||||
下面是一些例子:
|
||||
一个 hello world 例子
|
||||
1. 返回字符串: "hello world"
|
||||
|
||||
计算圆的面积
|
||||
1. 从 body 中获取半径 radius.
|
||||
2. 校验 radius 是否为有效的数字.
|
||||
3. 计算圆的面积.
|
||||
4. 返回圆的面积: {area}
|
||||
|
||||
实现一个手机号发生注册验证码方法.
|
||||
1. 从 query 中获取 phone.
|
||||
2. 校验手机号格式是否正确,不正确返回{error: "手机号格式错误"}.
|
||||
2. 校验手机号格式是否正确,不正确则返回错误原因:手机号格式错误.
|
||||
3. 给 phone 发送一个短信验证码,验证码长度为6位字符串,内容为:你正在注册laf,验证码为:code.
|
||||
4. 数据库添加数据,表为"codes",内容为 {phone, code}.
|
||||
|
||||
实现根据手机号注册账号,需要验证手机验证码.
|
||||
实现一个云函数,使用手机号注册账号,需要验证手机验证码.
|
||||
1. 从 body 中获取 phone 和 code.
|
||||
2. 校验手机号格式是否正确,不正确返回{error: "手机号格式错误"}.
|
||||
2. 获取数据库数据,表为"codes",查找是否有符合 phone, code 等于body参数的记录,没有的话返回 {error:"验证码不正确"}.
|
||||
2. 校验手机号格式是否正确,不正确则返回错误原因:手机号格式错误.
|
||||
2. 获取数据库数据,表为"codes",查找是否有符合 phone, code 等于body参数的记录,没有的话返回错误原因:验证码不正确.
|
||||
4. 添加数据库数据,表为"users" ,内容为{phone, code, createTime}.
|
||||
5. 删除数据库数据,删除 code 记录.
|
||||
6. 返回新建用户的Id: return {userId}
|
||||
|
||||
更新博客记录。传入blogId,blogText,tags,还需要记录更新的时间.
|
||||
1. 从 body 中获取 blogId,blogText 和 tags.
|
||||
2. 校验 blogId 是否为空,为空则返回 {error: "博客ID不能为空"}.
|
||||
3. 校验 blogText 是否为空,为空则返回 {error: "博客内容不能为空"}.
|
||||
4. 校验 tags 是否为数组,不是则返回 {error: "标签必须为数组"}.
|
||||
2. 校验 blogId 是否为空,为空则返回错误原因:博客ID不能为空.
|
||||
3. 校验 blogText 是否为空,为空则返回错误原因:博客内容不能为空.
|
||||
4. 校验 tags 是否为数组,不是则返回错误原因:标签必须为数组.
|
||||
5. 获取当前时间,记录为 updateTime.
|
||||
6. 更新数据库数据,表为"blogs",更新符合 blogId 的记录的内容为{blogText, tags, updateTime}.
|
||||
7. 返回结果 {message: "更新博客记录成功"}.`
|
||||
7. 返回结果 "更新博客记录成功"`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
@@ -110,8 +119,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]
|
||||
},
|
||||
{
|
||||
timeout: 120000,
|
||||
httpsAgent
|
||||
timeout: 180000,
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
@@ -126,7 +135,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
// 获取提示词的向量
|
||||
const { vector: promptVector } = await openaiCreateEmbedding({
|
||||
isPay: true,
|
||||
apiKey: apiKey,
|
||||
apiKey,
|
||||
userId,
|
||||
text: prompt.value
|
||||
});
|
||||
@@ -134,57 +143,43 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
// 读取对话内容
|
||||
const prompts = [prompt];
|
||||
|
||||
// 搜索系统提示词, 按相似度从 redis 中搜出相关的 q 和 text
|
||||
const redisData: any[] = await redis.sendCommand([
|
||||
'FT.SEARCH',
|
||||
`idx:${VecModelDataPrefix}:hash`,
|
||||
`@modelId:{${String(model._id)}}=>[KNN 20 @vector $blob AS score]`,
|
||||
'RETURN',
|
||||
'1',
|
||||
'text',
|
||||
'SORTBY',
|
||||
'score',
|
||||
'PARAMS',
|
||||
'2',
|
||||
'blob',
|
||||
vectorToBuffer(promptVector),
|
||||
'DIALECT',
|
||||
'2'
|
||||
]);
|
||||
// 相似度搜索
|
||||
const similarity = ModelVectorSearchModeMap[model.search.mode]?.similarity || 0.22;
|
||||
const vectorSearch = await PgClient.select<{ id: string; q: string; a: string }>('modelData', {
|
||||
fields: ['id', 'q', 'a'],
|
||||
order: [{ field: 'vector', mode: `<=> '[${promptVector}]'` }],
|
||||
where: [
|
||||
['model_id', model._id],
|
||||
'AND',
|
||||
['user_id', userId],
|
||||
'AND',
|
||||
`vector <=> '[${promptVector}]' < ${similarity}`
|
||||
],
|
||||
limit: 30
|
||||
});
|
||||
|
||||
// 格式化响应值,获取 qa
|
||||
const formatRedisPrompt: string[] = [];
|
||||
for (let i = 2; i < 42; i += 2) {
|
||||
const text = redisData[i]?.[1];
|
||||
if (text) {
|
||||
formatRedisPrompt.push(text);
|
||||
}
|
||||
}
|
||||
const formatRedisPrompt: string[] = vectorSearch.rows.map((item) => `${item.q}\n${item.a}`);
|
||||
|
||||
// textArr 筛选,最多 3200 tokens
|
||||
const systemPrompt = systemPromptFilter(formatRedisPrompt, 3200);
|
||||
// system 筛选,最多 2500 tokens
|
||||
const systemPrompt = systemPromptFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts: formatRedisPrompt,
|
||||
maxTokens: 2500
|
||||
});
|
||||
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: `${model.systemPrompt} 知识库内容是最新的,知识库内容为: "${systemPrompt}"`
|
||||
value: `${model.systemPrompt} 知识库是最新的,下面是知识库内容:${systemPrompt}`
|
||||
});
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter(prompts, modelConstantsData.contextMaxToken);
|
||||
// 控制上下文 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts,
|
||||
maxTokens: modelConstantsData.contextMaxToken - 500
|
||||
});
|
||||
|
||||
// 格式化文本内容成 chatgpt 格式
|
||||
const map = {
|
||||
Human: ChatCompletionRequestMessageRoleEnum.User,
|
||||
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
|
||||
(item: ChatItemType) => ({
|
||||
role: map[item.obj],
|
||||
content: item.value
|
||||
})
|
||||
);
|
||||
// console.log(formatPrompts);
|
||||
// console.log(filterPrompts);
|
||||
// 计算温度
|
||||
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
|
||||
|
||||
@@ -193,15 +188,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature,
|
||||
messages: formatPrompts,
|
||||
messages: filterPrompts,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream: isStream
|
||||
},
|
||||
{
|
||||
timeout: 120000,
|
||||
timeout: 180000,
|
||||
responseType: isStream ? 'stream' : 'json',
|
||||
httpsAgent
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
@@ -226,13 +221,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
console.log('laf gpt done. time:', `${(Date.now() - startTime) / 1000}s`);
|
||||
|
||||
const promptsContent = formatPrompts.map((item) => item.content).join('');
|
||||
|
||||
pushChatBill({
|
||||
isPay: true,
|
||||
modelName: model.service.modelName,
|
||||
userId,
|
||||
text: promptsContent + responseContent
|
||||
messages: filterPrompts.concat({ role: 'assistant', content: responseContent })
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (step === 1) {
|
||||
|
||||
217
src/pages/api/openapi/chat/vectorGpt.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase, Model } from '@/service/mongo';
|
||||
import {
|
||||
axiosConfig,
|
||||
systemPromptFilter,
|
||||
authOpenApiKey,
|
||||
openaiChatFilter
|
||||
} from '@/service/utils/tools';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { PassThrough } from 'stream';
|
||||
import {
|
||||
modelList,
|
||||
ModelVectorSearchModeMap,
|
||||
ModelVectorSearchModeEnum,
|
||||
ModelDataStatusEnum
|
||||
} from '@/constants/model';
|
||||
import { pushChatBill } from '@/service/events/pushBill';
|
||||
import { openaiCreateEmbedding, gpt35StreamResponse } from '@/service/utils/openai';
|
||||
import dayjs from 'dayjs';
|
||||
import { PgClient } from '@/service/pg';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let step = 0; // step=1时,表示开始了流响应
|
||||
const stream = new PassThrough();
|
||||
stream.on('error', () => {
|
||||
console.log('error: ', 'stream error');
|
||||
stream.destroy();
|
||||
});
|
||||
res.on('close', () => {
|
||||
stream.destroy();
|
||||
});
|
||||
res.on('error', () => {
|
||||
console.log('error: ', 'request error');
|
||||
stream.destroy();
|
||||
});
|
||||
|
||||
try {
|
||||
const {
|
||||
prompts,
|
||||
modelId,
|
||||
isStream = true
|
||||
} = req.body as {
|
||||
prompts: ChatItemType[];
|
||||
modelId: string;
|
||||
isStream: boolean;
|
||||
};
|
||||
|
||||
if (!prompts || !modelId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
if (!Array.isArray(prompts)) {
|
||||
throw new Error('prompts is not array');
|
||||
}
|
||||
if (prompts.length > 30 || prompts.length === 0) {
|
||||
throw new Error('prompts length range 1-30');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
let startTime = Date.now();
|
||||
|
||||
/* 凭证校验 */
|
||||
const { apiKey, userId } = await authOpenApiKey(req);
|
||||
|
||||
const model = await Model.findOne({
|
||||
_id: modelId,
|
||||
userId
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
throw new Error('无权使用该模型');
|
||||
}
|
||||
|
||||
const modelConstantsData = modelList.find((item) => item.model === model?.service?.modelName);
|
||||
if (!modelConstantsData) {
|
||||
throw new Error('模型初始化异常');
|
||||
}
|
||||
|
||||
// 获取提示词的向量
|
||||
const { vector: promptVector, chatAPI } = await openaiCreateEmbedding({
|
||||
isPay: true,
|
||||
apiKey,
|
||||
userId,
|
||||
text: prompts[prompts.length - 1].value // 取最后一个
|
||||
});
|
||||
|
||||
// 相似度搜素
|
||||
const similarity = ModelVectorSearchModeMap[model.search.mode]?.similarity || 0.22;
|
||||
const vectorSearch = await PgClient.select<{ id: string; q: string; a: string }>('modelData', {
|
||||
fields: ['id', 'q', 'a'],
|
||||
where: [
|
||||
['status', ModelDataStatusEnum.ready],
|
||||
'AND',
|
||||
['model_id', model._id],
|
||||
'AND',
|
||||
`vector <=> '[${promptVector}]' < ${similarity}`
|
||||
],
|
||||
order: [{ field: 'vector', mode: `<=> '[${promptVector}]'` }],
|
||||
limit: 20
|
||||
});
|
||||
|
||||
const formatRedisPrompt: string[] = vectorSearch.rows.map((item) => `${item.q}\n${item.a}`);
|
||||
|
||||
// system 合并
|
||||
if (prompts[0].obj === 'SYSTEM') {
|
||||
formatRedisPrompt.unshift(prompts.shift()?.value || '');
|
||||
}
|
||||
|
||||
/* 高相似度+退出,无法匹配时直接退出 */
|
||||
if (
|
||||
formatRedisPrompt.length === 0 &&
|
||||
model.search.mode === ModelVectorSearchModeEnum.hightSimilarity
|
||||
) {
|
||||
return res.send('对不起,你的问题不在知识库中。');
|
||||
}
|
||||
/* 高相似度+无上下文,不添加额外知识 */
|
||||
if (
|
||||
formatRedisPrompt.length === 0 &&
|
||||
model.search.mode === ModelVectorSearchModeEnum.noContext
|
||||
) {
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: model.systemPrompt
|
||||
});
|
||||
} else {
|
||||
// 有匹配或者低匹配度模式情况下,添加知识库内容。
|
||||
// 系统提示词过滤,最多 2500 tokens
|
||||
const systemPrompt = systemPromptFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts: formatRedisPrompt,
|
||||
maxTokens: 2500
|
||||
});
|
||||
|
||||
prompts.unshift({
|
||||
obj: 'SYSTEM',
|
||||
value: `
|
||||
${model.systemPrompt}
|
||||
${
|
||||
model.search.mode === ModelVectorSearchModeEnum.hightSimilarity
|
||||
? `你只能从知识库选择内容回答.不在知识库内容拒绝回复`
|
||||
: ''
|
||||
}
|
||||
知识库内容为: 当前时间为${dayjs().format('YYYY/MM/DD HH:mm:ss')}\n${systemPrompt}'
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
// 控制在 tokens 数量,防止超出
|
||||
const filterPrompts = openaiChatFilter({
|
||||
model: model.service.chatModel,
|
||||
prompts,
|
||||
maxTokens: modelConstantsData.contextMaxToken - 500
|
||||
});
|
||||
|
||||
// console.log(filterPrompts);
|
||||
// 计算温度
|
||||
const temperature = modelConstantsData.maxTemperature * (model.temperature / 10);
|
||||
|
||||
// 发出请求
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
temperature,
|
||||
messages: filterPrompts,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream: isStream,
|
||||
stop: ['.!?。']
|
||||
},
|
||||
{
|
||||
timeout: 180000,
|
||||
responseType: isStream ? 'stream' : 'json',
|
||||
...axiosConfig
|
||||
}
|
||||
);
|
||||
|
||||
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
|
||||
|
||||
step = 1;
|
||||
let responseContent = '';
|
||||
|
||||
if (isStream) {
|
||||
const streamResponse = await gpt35StreamResponse({
|
||||
res,
|
||||
stream,
|
||||
chatResponse
|
||||
});
|
||||
responseContent = streamResponse.responseContent;
|
||||
} else {
|
||||
responseContent = chatResponse.data.choices?.[0]?.message?.content || '';
|
||||
jsonRes(res, {
|
||||
data: responseContent
|
||||
});
|
||||
}
|
||||
|
||||
pushChatBill({
|
||||
isPay: true,
|
||||
modelName: model.service.modelName,
|
||||
userId,
|
||||
messages: filterPrompts.concat({ role: 'assistant', content: responseContent })
|
||||
});
|
||||
// jsonRes(res);
|
||||
} catch (err: any) {
|
||||
if (step === 1) {
|
||||
// 直接结束流
|
||||
console.log('error,结束');
|
||||
stream.destroy();
|
||||
} else {
|
||||
res.status(500);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import axios from 'axios';
|
||||
import { connectToDatabase, User, Pay } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
import dayjs from 'dayjs';
|
||||
import { generateQA } from '@/service/events/generateQA';
|
||||
import { generateVector } from '@/service/events/generateVector';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
res.send('');
|
||||
generateQA();
|
||||
generateVector();
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
@@ -2,9 +2,11 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, User, Pay } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
import { PaySchema, UserModelSchema } from '@/types/mongoSchema';
|
||||
import dayjs from 'dayjs';
|
||||
import { getPayResult } from '@/service/utils/wxpay';
|
||||
import { pushPromotionRecord } from '@/service/utils/promotion';
|
||||
import { PRICE_SCALE } from '@/constants/common';
|
||||
|
||||
/* 校验支付结果 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -26,6 +28,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
throw new Error('订单已结算');
|
||||
}
|
||||
|
||||
// 获取当前用户
|
||||
const user = await User.findById(userId);
|
||||
if (!user) {
|
||||
throw new Error('找不到用户');
|
||||
}
|
||||
// 获取邀请者
|
||||
let inviter: UserModelSchema | null = null;
|
||||
if (user.inviterId) {
|
||||
inviter = await User.findById(user.inviterId);
|
||||
}
|
||||
|
||||
const payRes = await getPayResult(payOrder.orderId);
|
||||
|
||||
// 校验下是否超过一天
|
||||
@@ -50,6 +63,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await User.findByIdAndUpdate(userId, {
|
||||
$inc: { balance: payOrder.price }
|
||||
});
|
||||
// 推广佣金发放
|
||||
if (inviter) {
|
||||
pushPromotionRecord({
|
||||
userId: inviter._id,
|
||||
objUId: userId,
|
||||
type: 'invite',
|
||||
// amount 单位为元,需要除以缩放比例,最后乘比例
|
||||
amount: (payOrder.price / PRICE_SCALE) * inviter.promotion.rate * 0.01
|
||||
});
|
||||
}
|
||||
jsonRes(res, {
|
||||
data: '支付成功'
|
||||
});
|
||||
|
||||
@@ -15,7 +15,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await connectToDatabase();
|
||||
|
||||
const records = await Pay.find({
|
||||
userId
|
||||
userId,
|
||||
status: { $ne: 'CLOSED' }
|
||||
}).sort({ createTime: -1 });
|
||||
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -7,24 +7,24 @@ import { generateToken } from '@/service/utils/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
if (!username || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
// 检测邮箱是否存在
|
||||
const authEmail = await User.findOne({
|
||||
email
|
||||
// 检测用户是否存在
|
||||
const authUser = await User.findOne({
|
||||
username
|
||||
});
|
||||
if (!authEmail) {
|
||||
throw new Error('邮箱未注册');
|
||||
if (!authUser) {
|
||||
throw new Error('用户未注册');
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
|
||||
70
src/pages/api/user/promotion/getPromotionData.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, User, promotionRecord } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error('缺少登录凭证');
|
||||
}
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const invitedAmount = await User.countDocuments({
|
||||
inviterId: userId
|
||||
});
|
||||
|
||||
// 计算累计合
|
||||
const countHistory: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||
{ $match: { userId: new mongoose.Types.ObjectId(userId), amount: { $gt: 0 } } },
|
||||
{
|
||||
$group: {
|
||||
_id: null, // 分组条件,这里使用 null 表示不分组
|
||||
totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: false, // 排除 _id 字段
|
||||
totalAmount: true // 只返回 totalAmount 字段
|
||||
}
|
||||
}
|
||||
]);
|
||||
// 计算剩余金额
|
||||
const countResidue: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||
{ $match: { userId: new mongoose.Types.ObjectId(userId) } },
|
||||
{
|
||||
$group: {
|
||||
_id: null, // 分组条件,这里使用 null 表示不分组
|
||||
totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: false, // 排除 _id 字段
|
||||
totalAmount: true // 只返回 totalAmount 字段
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
invitedAmount,
|
||||
historyAmount: countHistory[0]?.totalAmount || 0,
|
||||
residueAmount: countResidue[0]?.totalAmount || 0
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, DataItem } from '@/service/mongo';
|
||||
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||
import { authToken } from '@/service/utils/tools';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let {
|
||||
dataId,
|
||||
pageNum = 1,
|
||||
pageSize = 10
|
||||
} = req.query as { dataId: string; pageNum: string; pageSize: string };
|
||||
const { authorization } = req.headers;
|
||||
let { pageNum = 1, pageSize = 10 } = req.query as { pageNum: string; pageSize: string };
|
||||
pageNum = +pageNum;
|
||||
pageSize = +pageSize;
|
||||
|
||||
if (!dataId) {
|
||||
throw new Error('参数错误');
|
||||
if (!authorization) {
|
||||
throw new Error('缺少登录凭证');
|
||||
}
|
||||
|
||||
const userId = await authToken(authorization);
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const { authorization } = req.headers;
|
||||
|
||||
await authToken(authorization);
|
||||
|
||||
const dataItems = await DataItem.find({
|
||||
dataId
|
||||
})
|
||||
.sort({ _id: -1 }) // 按照创建时间倒序排列
|
||||
const data = await promotionRecord
|
||||
.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id createTime type amount'
|
||||
)
|
||||
.sort({ _id: -1 })
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize);
|
||||
|
||||
@@ -33,9 +33,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data: dataItems,
|
||||
total: await DataItem.countDocuments({
|
||||
dataId
|
||||
data,
|
||||
total: await promotionRecord.countDocuments({
|
||||
userId
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -5,13 +5,13 @@ import { User } from '@/service/models/user';
|
||||
import { AuthCode } from '@/service/models/authCode';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { generateToken } from '@/service/utils/tools';
|
||||
import { EmailTypeEnum } from '@/constants/common';
|
||||
import { UserAuthTypeEnum } from '@/constants/common';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { email, code, password } = req.body;
|
||||
const { username, code, password, inviterId } = req.body;
|
||||
|
||||
if (!email || !code || !password) {
|
||||
if (!username || !code || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
// 验证码校验
|
||||
const authCode = await AuthCode.findOne({
|
||||
email,
|
||||
username,
|
||||
code,
|
||||
type: EmailTypeEnum.register,
|
||||
type: UserAuthTypeEnum.register,
|
||||
expiredTime: { $gte: Date.now() }
|
||||
});
|
||||
|
||||
@@ -31,16 +31,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
// 重名校验
|
||||
const authRepeat = await User.findOne({
|
||||
email
|
||||
username
|
||||
});
|
||||
|
||||
if (authRepeat) {
|
||||
throw new Error('邮箱已被注册');
|
||||
throw new Error('该用户已被注册');
|
||||
}
|
||||
|
||||
const response = await User.create({
|
||||
email,
|
||||
password
|
||||
username,
|
||||
password,
|
||||
inviterId: inviterId ? inviterId : undefined
|
||||
});
|
||||
|
||||
// 根据 id 获取用户信息
|
||||
@@ -50,6 +51,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('获取用户信息异常');
|
||||
}
|
||||
|
||||
// 删除验证码记录
|
||||
await AuthCode.deleteMany({
|
||||
username
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
token: generateToken(user._id),
|
||||
|
||||