Compare commits

...

31 Commits
v3.6 ... delete

Author SHA1 Message Date
archer
02caa57304 docs 2023-05-23 19:18:52 +08:00
archer
6014a56e54 feat: system prompt 2023-05-23 19:13:01 +08:00
archer
b8f08eb33e feat: quote change 2023-05-23 18:35:45 +08:00
archer
944e876aaa feat: chat quote 2023-05-23 15:09:57 +08:00
archer
ee2c259c3d perf: text and avatar 2023-05-22 16:47:41 +08:00
archer
1c8db69a5a feat: limit export kb 2023-05-22 14:14:06 +08:00
archer
5128bbcce4 perf: insert kb data 2023-05-22 13:16:34 +08:00
archer
51a5d450b7 feat: content check 2023-05-21 22:12:02 +08:00
archer
98444fd04b perf: bill framwork 2023-05-21 18:19:42 +08:00
archer
e45c1eb1e0 uniform authuser 2023-05-21 11:00:05 +08:00
archer
bd9d83e630 img and push data size 2023-05-21 11:00:00 +08:00
stakeswky
b66952ad98 Create useSearch.ts (#35)
联网功能
2023-05-20 17:04:12 +08:00
archer
242b21263a imgs 2023-05-19 14:11:48 +08:00
archer
2843178ede feat: openapi cors 2023-05-19 12:01:59 +08:00
archer
bb312441c6 fix: share prompts 2023-05-19 11:17:20 +08:00
archer
d07e5b8501 price 2023-05-19 10:31:25 +08:00
archer
246ee973ec feat: share unlogin.perf: link format and model ui 2023-05-19 10:26:30 +08:00
archer
a62a9c4067 perf: stream response 2023-05-19 00:00:56 +08:00
archer
7408db9cf6 docs 2023-05-18 22:31:18 +08:00
archer
5d4dd4a18c fix: ui bug 2023-05-18 20:54:01 +08:00
archer
5bf95bd846 feat: model related kb 2023-05-17 22:24:36 +08:00
archer
a79429fdcd feat: kb crud 2023-05-17 19:30:43 +08:00
archer
021add2af4 fix: safari reg error 2023-05-16 14:27:10 +08:00
archer
371e0e36c6 perf: error show 2023-05-15 22:33:37 +08:00
archer
e7d3a8e2e1 perf: http recognition and input textarea 2023-05-15 22:33:37 +08:00
archer
32a8d68c6c feat: docs and git 2023-05-15 22:33:36 +08:00
archer
06ab718e6e fix: share login. 2023-05-15 22:33:35 +08:00
archer
1d74095739 temp 2023-05-15 22:33:35 +08:00
archer
ca99837dab fix: ui;perf: docs 2023-05-15 22:33:34 +08:00
archer
d31bdf0ee0 feat: share chat page 2023-05-15 22:33:33 +08:00
ShengYan, Zhang
d3e7923040 fix: correct sql script. 2023-05-15 20:31:28 +08:00
175 changed files with 5910 additions and 2749 deletions

View File

@@ -14,9 +14,13 @@ aliSignName=xxx
aliTemplateCode=SMS_xxx
# token
TOKEN_KEY=xxx
# root key, 最高权限
ROOT_KEY=xxx
# 是否进行安全校验(1: 开启0: 关闭)
SENSITIVE_CHECK=1
# openai
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证
# OPENAI_BASE_URL_AUTH=可选的安全凭证(不需要的时候,记得去掉)
OPENAIKEY=sk-xxx
GPT4KEY=sk-xxx
# claude

View File

@@ -52,6 +52,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]

View File

@@ -21,7 +21,8 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
## 🚀 私有化部署
[docker-compose 部署教程](docs/deploy/docker.md)
- [docker-compose 部署教程](docs/deploy/docker.md)
- [由社区贡献的宝塔部署和本地运行教程](https://space.bilibili.com/431177525/channel/collectiondetail?sid=1370663)
## :point_right: RoadMap
@@ -29,13 +30,14 @@ Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接
## 🏘️ 交流群
wx: fastgpt123
![Demo](docs/imgs/wx300.jpg?raw=true 'wx')
添加 wx 进入:
![Demo](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
## 👀 其他
- [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [FastGpt + Laf 最佳实践,将知识库装入公众号,点击去 Laf 公众号体验效果](https://hnvacz-laf-upload-ai.oss.laf.run/3ffd528ee2f9ae1dcd3508fe9994dd9.png)
- [公众号接入](https://www.bilibili.com/video/BV1xh4y1t7fy/)
- [FastGpt + Laf 最佳实践,将知识库装入公众号,点击去 Laf 公众号体验效果](https://b4jky7-fastgpt.oss.laf.run/lafercode.png)
- [FastGpt V3.4 更新集合](https://www.bilibili.com/video/BV1Lo4y147Qh/?vd_source=92041a1a395f852f9d89158eaa3f61b4)
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)

View File

@@ -2,7 +2,7 @@
## 代理环境(国外服务器可忽略)
选择一个即可。
选择一个即可。这只是代理!!!不是项目。
1. [sealos nginx 方案](./proxy/sealos.md) - 推荐。约等于不用钱,不需要额外准备任何东西。
2. [clash 方案](./proxy/clash.md) - 仅需一台服务器(需要有 clash
@@ -35,185 +35,10 @@ docker-compose -v
### 2. 创建 3 个初始化文件
fastgpt 文件夹。分别为fastgpt/docker-compose.yaml, fastgpt/pg/init.sql, fastgpt/nginx/nginx.conf
手动创建或者直接把 fastgpt 文件夹复制过去。
**/root/fastgpt/pg/init.sql PG 数据库初始化**
```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/fastgpt/nginx/nginx.conf Nginx 配置**
```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 64k;
large_client_header_buffers 4 64k;
client_max_body_size 50M;
proxy_connect_timeout 240s;
proxy_read_timeout 240s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
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/fastgpt/docker-compose.yml 核心部署文件**
环境变量内容和开发时的环境变量基本相同,除了数据库的地址。
```yml
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.4.1
container_name: pg
restart: always
ports:
- 8100:5432
environment:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
- POSTGRES_USER=fastgpt
- POSTGRES_PASSWORD=1234
- POSTGRES_DB=fastgpt
volumes:
# 刚创建的文件
- /root/fastgpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
- /root/fastgpt/pg/data:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
mongodb:
image: mongo:6.0.4
container_name: mongo
restart: always
ports:
- 27017:27017
environment:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
- MONGO_INITDB_ROOT_USERNAME=username
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- /root/fastgpt/mongo/data:/data/db
- /root/fastgpt/mongo/logs:/var/log/mongodb
- /etc/localtime:/etc/localtime:ro
fastgpt:
image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest
network_mode: host
restart: always
container_name: fastgpt
environment:
# proxy可选
- AXIOS_PROXY_HOST=127.0.0.1
- AXIOS_PROXY_PORT=7890
# 是否开启队列任务。 1-开启0-关闭(请求 parentUrl 去执行任务,单机时直接填1
- queueTask=1
- parentUrl=https://hostname/api/openapi/startEvents
# 发送邮箱验证码配置。用的是QQ邮箱。参考 nodeMail 获取MAILE_CODE自行百度。
- MY_MAIL=xxxx@qq.com
- MAILE_CODE=xxxx
# 阿里短信服务(邮箱和短信至少二选一)
- aliAccessKeyId=xxxx
- aliAccessKeySecret=xxxx
- aliSignName=xxxxx
- aliTemplateCode=SMS_xxxx
# token加密凭证随便填作为登录凭证
- TOKEN_KEY=xxxx
# 和上方mongo镜像的username,password对应
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=fastgpt
- PG_HOST=0.0.0.0
- PG_PORT=8100
# 和上方PG镜像对应.
- PG_USER=fastgpt # POSTGRES_USER
- PG_PASSWORD=1234 # POSTGRES_PASSWORD
- PG_DB_NAME=fastgpt # POSTGRES_DB
# openai
- OPENAIKEY=sk-xxxxx
- GPT4KEY=sk-xxx
- OPENAI_BASE_URL=https://api.openai.com/v1
- OPENAI_BASE_URL_AUTH=可选的安全凭证
# claude
- CLAUDE_BASE_URL=calude模型请求地址
- CLAUDE_KEY=CLAUDE_KEY
nginx:
image: nginx:alpine3.17
container_name: nginx
restart: always
network_mode: host
volumes:
# 刚创建的文件
- /root/fastgpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/fastgpt/nginx/logs:/var/log/nginx
# https证书没有的话不填对应的nginx.conf也要修改
- /root/fastgpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
- /root/fastgpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
```
### 3. 运行 docker-compose
下面是一个辅助脚本,也可以直接 docker-compose up -d

View File

@@ -1,7 +1,7 @@
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.4.1
image: ankane/pgvector:v0.4.2
container_name: pg
restart: always
ports:
@@ -52,6 +52,10 @@ services:
- aliTemplateCode=SMS_xxxx
# token加密凭证随便填作为登录凭证
- TOKEN_KEY=xxxx
# root key, 最高权限,可以内部接口互相调用
- ROOT_KEY=xxx
# 是否进行安全校验(1: 开启0: 关闭)
- SENSITIVE_CHECK=1
# 和上方mongo镜像的username,password对应
- MONGODB_URI=mongodb://username:password@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=fastgpt

View File

@@ -8,12 +8,14 @@ CREATE TABLE modelData (
vector VECTOR(1536),
status VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
model_id VARCHAR(50) NOT NULL,
model_id VARCHAR(50),
kb_id VARCHAR(50),
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);
CREATE INDEX modelData_userId_index ON modelData USING HASH (model_id);
CREATE INDEX modelData_kbId_index ON modelData USING HASH (kb_id);
EOSQL

View File

@@ -67,7 +67,7 @@ docker run --name mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=username -e
**3、部署 pgsql**
```
docker run -it --name pg -e "POSTGRES_PASSWORD=xxx" -e POSTGRES_USER=xxx -p 8100:5432 -v ~/fastgpt/pg/data:/var/lib/postgresql/data -d octoberlan/pgvector:v0.4.1
docker run -it --name pg -e "POSTGRES_DB=fastgpt" -e "POSTGRES_PASSWORD=xxx" -e POSTGRES_USER=xxx -p 8100:5432 -v ~/fastgpt/pg/data:/var/lib/postgresql/data -d octoberlan/pgvector:v0.4.1
```
进 pgsql 容器运行
@@ -88,8 +88,8 @@ CREATE TABLE modelData (
);
-- 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);
CREATE INDEX modelData_modelId_index ON modelData (model_id);
CREATE INDEX modelData_userId_index ON modelData (user_id);
EOSQL
```

View File

@@ -90,7 +90,7 @@ http {
1. 进入刚刚部署应用的详情,复制外网地址
![step5](./imgs//sealos5.png)
2. 修改环境变量:
2. 修改环境变量(是 FastGpt 的环境变量,不是 sealos 的):
```
OPENAI_BASE_URL=https://tgohwtdlrmer.cloud.sealos.io/openai/v1

View File

@@ -10,34 +10,38 @@
# 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
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# 发送邮箱验证码配置。用的是 QQ 邮箱。参考 nodeMail 获取MAILE_CODE自行百度。
MY_MAIL=xxxx@qq.com
MAILE_CODE=xxxx
# 阿里短信服务(邮箱和短信至少二选一)
aliAccessKeyId=xxxx
aliAccessKeySecret=xxxx
aliSignName=xxxxx
aliTemplateCode=SMS_xxxx
# token加密凭证(随便填,作为登录凭证)
TOKEN_KEY=xxxx
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# 和mongo镜像的username,password对应
MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
MONGODB_NAME=xxx
# email
MY_MAIL=xxx@qq.com
MAILE_CODE=xxx
# ali ems
aliAccessKeyId=xxx
aliAccessKeySecret=xxx
aliSignName=xxx
aliTemplateCode=SMS_xxx
# token
TOKEN_KEY=xxx
# root key, 最高权限
ROOT_KEY=xxx
# 是否进行安全校验(1: 开启0: 关闭)
SENSITIVE_CHECK=1
# openai
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证(不需要的时候,记得去掉)
OPENAIKEY=sk-xxx
GPT4KEY=sk-xxx
# claude
CLAUDE_BASE_URL=calude模型请求地址
CLAUDE_KEY=CLAUDE_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镜像对应.
PG_USER=fastgpt # POSTGRES_USER
PG_PASSWORD=1234 # POSTGRES_PASSWORD
PG_DB_NAME=fastgpt # POSTGRES_DB
OPENAIKEY=sk-xxxxx
PG_USER=xxx
PG_PASSWORD=xxx
PG_DB_NAME=xxx
```
## 运行

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "fastgpt",
"version": "0.1.0",
"version": "3.7",
"private": true,
"scripts": {
"dev": "next dev",
@@ -39,6 +39,7 @@
"mongoose": "^6.10.0",
"nanoid": "^4.0.1",
"next": "13.1.6",
"nextjs-cors": "^2.1.2",
"nodemailer": "^6.9.1",
"nprogress": "^0.2.0",
"openai": "^3.2.1",
@@ -82,5 +83,8 @@
},
"lint-staged": {
"./src/**/*.{ts,tsx,scss}": "npm run format"
},
"engines": {
"node": ">=18.0.0"
}
}

61
pnpm-lock.yaml generated
View File

@@ -46,6 +46,7 @@ specifiers:
mongoose: ^6.10.0
nanoid: ^4.0.1
next: 13.1.6
nextjs-cors: ^2.1.2
nodemailer: ^6.9.1
nprogress: ^0.2.0
openai: ^3.2.1
@@ -97,6 +98,7 @@ dependencies:
mongoose: registry.npmmirror.com/mongoose/6.10.0
nanoid: registry.npmmirror.com/nanoid/4.0.1
next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4
nextjs-cors: 2.1.2_next@13.1.6
nodemailer: registry.npmmirror.com/nodemailer/6.9.1
nprogress: registry.npmmirror.com/nprogress/0.2.0
openai: registry.npmmirror.com/openai/3.2.1
@@ -294,11 +296,29 @@ packages:
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
dev: true
/@types/hast/2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: false
/cors/2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -309,9 +329,24 @@ packages:
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
requiresBuild: true
dev: false
optional: true
/nextjs-cors/2.1.2_next@13.1.6:
resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==}
peerDependencies:
next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0
dependencies:
cors: 2.8.5
next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4
dev: false
/object-assign/4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
/saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
engines: {node: '>=6'}
@@ -324,9 +359,15 @@ packages:
/source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dev: false
optional: true
/vary/1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
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'
@@ -7742,7 +7783,7 @@ packages:
name: hast-util-from-parse5
version: 7.1.2
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
hastscript: registry.npmmirror.com/hastscript/7.2.0
property-information: registry.npmmirror.com/property-information/6.2.0
@@ -7756,7 +7797,7 @@ packages:
name: hast-util-is-element
version: 2.1.3
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
dev: false
@@ -7771,7 +7812,7 @@ packages:
name: hast-util-parse-selector
version: 3.1.1
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
dev: false
registry.npmmirror.com/hast-util-to-text/3.1.2:
@@ -7796,7 +7837,7 @@ packages:
name: hastscript
version: 6.0.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
comma-separated-tokens: registry.npmmirror.com/comma-separated-tokens/1.0.8
hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/2.2.5
property-information: registry.npmmirror.com/property-information/5.6.0
@@ -7808,7 +7849,7 @@ packages:
name: hastscript
version: 7.2.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
comma-separated-tokens: registry.npmmirror.com/comma-separated-tokens/2.0.3
hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/3.1.1
property-information: registry.npmmirror.com/property-information/6.2.0
@@ -8762,7 +8803,7 @@ packages:
version: 5.1.2
dependencies:
'@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
unist-util-visit: registry.npmmirror.com/unist-util-visit/4.1.2
dev: false
@@ -8890,7 +8931,7 @@ packages:
name: mdast-util-to-hast
version: 12.3.0
dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4
'@types/hast': 2.3.4
'@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10
mdast-util-definitions: registry.npmmirror.com/mdast-util-definitions/5.1.2
micromark-util-sanitize-uri: registry.npmmirror.com/micromark-util-sanitize-uri/1.1.0
@@ -9441,7 +9482,7 @@ packages:
version: 2.7.0
dependencies:
any-promise: registry.npmmirror.com/any-promise/1.3.0
object-assign: registry.npmmirror.com/object-assign/4.1.1
object-assign: 4.1.1
thenify-all: registry.npmmirror.com/thenify-all/1.6.0
dev: false
@@ -11624,7 +11665,7 @@ packages:
name: unist-util-position
version: 4.0.4
dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
dev: false
registry.npmmirror.com/unist-util-remove-position/4.0.2:
@@ -11817,7 +11858,7 @@ packages:
name: vfile-location
version: 4.1.0
dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6
'@types/unist': 2.0.6
vfile: registry.npmmirror.com/vfile/5.3.7
dev: false

View File

@@ -1,16 +1,27 @@
### 常见问题
**请求次数太多了**
一般是因为自己的 openai 账号异常。请先检查自己的账号是否正常使用。
**内容长度**
chatgpt 上下文最长 4096 tokens, 会自动截取上下文,超过 4096 部分会被遗忘。
**Git 地址**
[项目地址,完全开源,随便用。](https://github.com/c121914yu/FastGPT)
**问题文档**
[先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
**删除和复制**
电脑端:聊天内容右侧有复制和删除的图标。
移动端:点击对话头像,可以选择复制或删除该条内容。
**代理出错**
服务器代理不稳定,可以过一会儿再尝试。 或者可以访问国外服务器: [FastGpt](https://fastgpt.run/)
**价格表**
如果使用了自己的 Api Key不会计费。可以在账号页看到详细账单。
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
| claude - 对话 | 免费 |
| 知识库 - 索引 | 免费 |
| chatgpt - 对话 | 0.025 |
| gpt4 - 对话 | 0.5 |
| 文件拆分 | 0.025 |
**其他问题**
请 WX 联系: fastgpt123
| 交流群 | 小助手 |
| ----------------------- | -------------------- |
| ![](/imgs/wxqun300.jpg) | ![](/imgs/wx300.jpg) |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

@@ -1,7 +1,9 @@
接受一个 csv 文件,表格头包含 question 和 answer。question 代表问题answer 代表答案。
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
**请保证 csv 文件为 utf-8 编码**
| question | answer |
| --- | --- |
| 什么是 laf | laf 是一个云函数开发平台…… |
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
### 请保证 csv 文件为 utf-8 编码
| question | answer |
| ------------- | ------------------------------------------------------ |
| 什么是 laf | laf 是一个云函数开发平台…… |
| 什么是 sealos | Sealos 是以 kubernetes 为内核的云操作系统发行版,可以…… |

View File

@@ -19,16 +19,16 @@ FastGpt 项目完全开源,可随意私有化部署,去除平台风险忧虑
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
| claude - 对话 | 免费 |
| chatgpt - 对话 | 0.03 |
| gpt4 - 对话 | 0.5 |
| 知识库 - 索引 | 免费 |
| 文件拆分 | 0.03 |
| chatgpt - 对话 | 0.025 |
| gpt4 - 对话 | 0.5 |
| 文件拆分 | 0.025 |
### 交流群/问题反馈
如果群满了,可加个小助手,定时拉
wx 号: fastgpt123
| 交流群 | 小助手 |
| ----------------------- | -------------------- |
| ![](/imgs/wxqun300.jpg) | ![](/imgs/wx300.jpg) |
| 交流群 | 小助手 |
| ------------------------------------------------- | ---------------------------------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

@@ -1,8 +1,4 @@
### Fast GPT V3.4
### Fast GPT V3.8
- 新增 - 全新的交互 UI
- 新增 - 用户自定义头像
- 优化 - 知识库搜索,会将上一个问题并入搜索范围。
- 优化 - 模型结构设计,不再区分知识库和对话模型,而是通过开关的形式,手动选择手否需要进行知识库搜索。
- 新增 - 模型共享市场,可以使用其他用户分享的模型。
- 新增 - 邀请好友注册功能。
- 新增 - 知识库引用反馈。
- 新增 - 知识库与 AI 助手对多对关系,一个知识库可以被多个 AI 助手关联,一个 AI 助手可以关联多个知识库。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,7 +1,11 @@
import { GET, POST, DELETE } from './request';
import type { ChatItemType, HistoryItemType } from '@/types/chat';
import type { InitChatResponse } from './response/chat';
import type { HistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/model';
import { Obj2Query } from '@/utils/tools';
import { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
/**
* 获取初始化聊天内容
@@ -21,17 +25,48 @@ export const getChatHistory = (data: RequestPaging) =>
export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${id}`);
/**
* 存储一轮对话
* get history quotes
*/
export const postSaveChat = (data: {
modelId: string;
newChatId: '' | string;
chatId: '' | string;
prompts: [ChatItemType, ChatItemType];
}) => POST<string>('/chat/saveChat', data);
export const getHistoryQuote = (params: { chatId: string; historyId: string }) =>
GET<(QuoteItemType & { _id: string })[]>(`/chat/getHistoryQuote`, params);
/**
* update history quote status
*/
export const updateHistoryQuote = (params: {
chatId: string;
historyId: string;
quoteId: string;
}) => GET(`/chat/updateHistoryQuote`, params);
/**
* 删除一句对话
*/
export const delChatRecordByIndex = (chatId: string, contentId: string) =>
DELETE(`/chat/delChatRecordByContentId?chatId=${chatId}&contentId=${contentId}`);
/**
* create a shareChat
*/
export const createShareChat = (
data: ShareChatEditType & {
modelId: string;
}
) => POST<string>(`/chat/shareChat/create`, data);
/**
* get shareChat
*/
export const getShareChatList = (modelId: string) =>
GET<ShareChatSchema[]>(`/chat/shareChat/list?modelId=${modelId}`);
/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string; password: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init?${Obj2Query(data)}`);

View File

@@ -1,4 +1,4 @@
import { SYSTEM_PROMPT_HEADER, NEW_CHATID_HEADER } from '@/constants/chat';
import { GUIDE_PROMPT_HEADER, NEW_CHATID_HEADER, QUOTE_LEN_HEADER } from '@/constants/chat';
interface StreamFetchProps {
url: string;
@@ -7,7 +7,7 @@ interface StreamFetchProps {
abortSignal: AbortController;
}
export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchProps) =>
new Promise<{ responseText: string; systemPrompt: string; newChatId: string }>(
new Promise<{ responseText: string; newChatId: string; systemPrompt: string; quoteLen: number }>(
async (resolve, reject) => {
try {
const res = await fetch(url, {
@@ -23,8 +23,11 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
const decoder = new TextDecoder();
const systemPrompt = decodeURIComponent(res.headers.get(SYSTEM_PROMPT_HEADER) || '').trim();
const newChatId = decodeURIComponent(res.headers.get(NEW_CHATID_HEADER) || '');
const systemPrompt = decodeURIComponent(res.headers.get(GUIDE_PROMPT_HEADER) || '').trim();
const quoteLen = res.headers.get(QUOTE_LEN_HEADER)
? Number(res.headers.get(QUOTE_LEN_HEADER))
: 0;
let responseText = '';
@@ -33,7 +36,7 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
const { done, value } = await reader?.read();
if (done) {
if (res.status === 200) {
resolve({ responseText, systemPrompt, newChatId });
resolve({ responseText, newChatId, quoteLen, systemPrompt });
} else {
const parseError = JSON.parse(responseText);
reject(parseError?.message || '请求异常');
@@ -41,13 +44,13 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
return;
}
const text = decoder.decode(value).replace(/<br\/>/g, '\n');
const text = decoder.decode(value);
responseText += text;
onMessage(text);
read();
} catch (err: any) {
if (err?.message === 'The user aborted a request.') {
return resolve({ responseText, systemPrompt, newChatId });
return resolve({ responseText, newChatId, quoteLen: 0, systemPrompt: '' });
}
reject(typeof err === 'string' ? err : err?.message || '请求异常');
}

View File

@@ -1,8 +1,7 @@
import { GET, POST, DELETE, PUT } from './request';
import type { ModelSchema, ModelDataSchema } from '@/types/mongoSchema';
import type { ModelUpdateParams, ShareModelItem } from '@/types/model';
import type { ModelSchema } from '@/types/mongoSchema';
import type { ModelUpdateParams } from '@/types/model';
import { RequestPaging } from '../types/index';
import { Obj2Query } from '@/utils/tools';
import type { ModelListResponse } from './response/model';
/**
@@ -31,82 +30,13 @@ export const getModelById = (id: string) => GET<ModelSchema>(`/model/detail?mode
export const putModelById = (id: string, data: ModelUpdateParams) =>
PUT(`/model/update?modelId=${id}`, data);
/* 模型 data */
type GetModelDataListProps = RequestPaging & {
modelId: string;
searchText: string;
};
/**
* 获取模型的知识库数据
*/
export const getModelDataList = (props: GetModelDataListProps) =>
GET(`/model/data/getModelData?${Obj2Query(props)}`);
/**
* 获取导出数据(不分页)
*/
export const getExportDataList = (modelId: string) =>
GET<[string, string][]>(`/model/data/exportModelData?modelId=${modelId}`);
/**
* 获取模型正在拆分数据的数量
*/
export const getModelSplitDataListLen = (modelId: string) =>
GET<{
splitDataQueue: number;
embeddingQueue: number;
}>(`/model/data/getTrainingData?modelId=${modelId}`);
/**
* 获取 web 页面内容
*/
export const getWebContent = (url: string) => POST<string>(`/model/data/fetchingUrlData`, { url });
/**
* 手动输入数据
*/
export const postModelDataInput = (data: {
modelId: string;
data: { a: ModelDataSchema['a']; q: ModelDataSchema['q'] }[];
}) => POST<number>(`/model/data/pushModelDataInput`, data);
/**
* 拆分数据
*/
export const postModelDataSplitData = (data: {
modelId: string;
chunks: string[];
prompt: string;
mode: 'qa' | 'subsection';
}) => POST(`/model/data/splitData`, data);
/**
* json导入数据
*/
export const postModelDataCsvData = (modelId: string, data: string[][]) =>
POST<number>(`/model/data/pushModelDataCsv`, { modelId, data: data });
/**
* 更新模型数据
*/
export const putModelDataById = (data: { dataId: string; a: string; q?: string }) =>
PUT('/model/data/putModelData', data);
/**
* 删除一条模型数据
*/
export const delOneModelData = (dataId: string) =>
DELETE(`/model/data/delModelDataById?dataId=${dataId}`);
/* 共享市场 */
/**
* 获取共享市场模型
*/
export const getShareModelList = (data: { searchText?: string } & RequestPaging) =>
POST(`/model/share/getModels`, data);
/**
* 获取我收藏的模型
*/
export const getCollectionModels = () => GET<ShareModelItem[]>(`/model/share/getCollection`);
/**
* 收藏/取消收藏模型
*/

73
src/api/plugins/kb.ts Normal file
View File

@@ -0,0 +1,73 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { KbItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import { SplitTextTypEnum } from '@/constants/plugin';
import { KbDataItemType } from '@/types/plugin';
export type KbUpdateParams = { id: string; name: string; tags: string; avatar: string };
/* knowledge base */
export const getKbList = () => GET<KbItemType[]>(`/plugins/kb/list`);
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: { name: string }) => POST<string>(`/plugins/kb/create`, data);
export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, data);
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
/* kb data */
type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* 获取导出数据(不分页)
*/
export const getExportDataList = (kbId: string) =>
GET<[string, string][]>(`/plugins/kb/data/exportModelData?kbId=${kbId}`);
/**
* 获取模型正在拆分数据的数量
*/
export const getTrainingData = (kbId: string) =>
GET<{
splitDataQueue: number;
embeddingQueue: number;
}>(`/plugins/kb/data/getTrainingData?kbId=${kbId}`);
export const getKbDataItemById = (dataId: string) =>
GET(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
*/
export const postKbDataFromList = (data: {
kbId: string;
data: { a: KbDataItemType['a']; q: KbDataItemType['q'] }[];
}) => POST(`/openapi/kb/pushData`, data);
/**
* 更新一条数据
*/
export const putKbDataById = (data: { dataId: string; a: string; q?: string }) =>
PUT('/openapi/kb/updateData', data);
/**
* 删除一条知识库数据
*/
export const delOneKbDataByDataId = (dataId: string) =>
DELETE(`/openapi/kb/delDataById?dataId=${dataId}`);
/**
* 拆分数据
*/
export const postSplitData = (data: {
kbId: string;
chunks: string[];
prompt: string;
mode: `${SplitTextTypEnum}`;
}) => POST(`/openapi/text/splitText`, data);

View File

@@ -54,13 +54,13 @@ function responseError(err: any) {
if (typeof err === 'string') {
return Promise.reject({ message: err });
}
if (err.response) {
// 有报错响应
const res = err.response;
if (res.data.code in TOKEN_ERROR_CODE) {
clearCookie();
return Promise.reject({ message: 'token过期重新登录' });
}
// 有报错响应
if (err?.code in TOKEN_ERROR_CODE) {
clearCookie();
window.location.replace(
`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`
);
return Promise.reject({ message: 'token过期重新登录' });
}
return Promise.reject(err);
}

View File

@@ -13,3 +13,13 @@ export interface InitChatResponse {
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
history: ChatItemType[];
}
export interface InitShareChatResponse {
maxContext: number;
model: {
name: string;
avatar: string;
intro: string;
};
chatModel: ModelSchema['chat']['chatModel']; // 对话模型名
}

View File

@@ -2,10 +2,9 @@ import { GET, POST, PUT } from './request';
import { createHashPassword, Obj2Query } from '@/utils/tools';
import { ResLogin, PromotionRecordType } from './response/user';
import { UserAuthTypeEnum } from '@/constants/common';
import { UserType, UserUpdateParams } from '@/types/user';
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types';
import { BillSchema, PaySchema } from '@/types/mongoSchema';
import { adaptBill } from '@/utils/adapt';
import { PaySchema } from '@/types/mongoSchema';
export const sendAuthCode = ({
username,
@@ -69,10 +68,7 @@ export const loginOut = () => GET('/user/loginout');
export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data);
export const getUserBills = (data: RequestPaging) =>
GET<PagingData<BillSchema>>(`/user/getBill?${Obj2Query(data)}`).then((res) => ({
...res,
data: res.data.map((bill) => adaptBill(bill))
}));
GET<PagingData<UserBillType>>(`/user/getBill?${Obj2Query(data)}`);
export const getPayOrders = () => GET<PaySchema[]>(`/user/getPayOrders`);

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { Image } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react';
const Avatar = ({ w = '30px', ...props }: ImageProps) => {
return (
<Image
fallbackSrc="/icon/logo.png"
borderRadius={'50%'}
objectFit={'contain'}
alt=""
w={w}
h={w}
{...props}
/>
);
};
export default Avatar;

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684739031957" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4988" xmlns:xlink="http://www.w3.org/1999/xlink" width="64.125" height="64"><path d="M371.732817 94.172314q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 247.821878q0 25.773475-18.338819 44.112294t-44.112294 18.338819l-247.821878 0q-25.773475 0-43.616651-18.338819t-17.843175-44.112294l0-247.821878q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM371.732817 589.81607q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 248.813166q0 25.773475-18.338819 43.616651t-44.112294 17.843175l-247.821878 0q-25.773475 0-43.616651-17.843175t-17.843175-43.616651l0-248.813166q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM868.367861 589.81607q25.773475 0 43.616651 17.843175t17.843175 43.616651l0 248.813166q0 25.773475-17.843175 43.616651t-43.616651 17.843175l-247.821878 0q-25.773475 0-44.112294-17.843175t-18.338819-43.616651l0-248.813166q0-25.773475 18.338819-43.616651t44.112294-17.843175l247.821878 0zM1006.156825 203.21394q19.82575 19.82575 19.82575 46.590513t-19.82575 45.599226l-184.379477 184.379477q-19.82575 19.82575-46.094869 19.82575t-46.094869-19.82575l-184.379477-184.379477q-18.834463-18.834463-18.834463-45.599226t18.834463-46.590513l184.379477-184.379477q19.82575-18.834463 46.094869-18.834463t46.094869 18.834463z" p-id="4989"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684826302600" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2244" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M904 512h-56c-4.4 0-8 3.6-8 8v320H184V184h320c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V520c0-4.4-3.6-8-8-8z" p-id="2245"></path><path d="M355.9 534.9L354 653.8c-0.1 8.9 7.1 16.2 16 16.2h0.4l118-2.9c2-0.1 4-0.9 5.4-2.3l415.9-415c3.1-3.1 3.1-8.2 0-11.3L785.4 114.3c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1 0.8-5.7 2.3l-415.8 415c-1.4 1.5-2.3 3.5-2.3 5.6z m63.5 23.6L779.7 199l45.2 45.1-360.5 359.7-45.7 1.1 0.7-46.4z" p-id="2246"></path></svg>

After

Width:  |  Height:  |  Size: 810 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684122143852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2364" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" p-id="2365"></path></svg>

After

Width:  |  Height:  |  Size: 1013 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684163814302" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3451" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 384c-229.8 0-416-57.3-416-128v256c0 70.7 186.2 128 416 128s416-57.3 416-128V256c0 70.7-186.2 128-416 128z" p-id="3452"></path><path d="M512 704c-229.8 0-416-57.3-416-128v256c0 70.7 186.2 128 416 128s416-57.3 416-128V576c0 70.7-186.2 128-416 128zM512 320c229.8 0 416-57.3 416-128S741.8 64 512 64 96 121.3 96 192s186.2 128 416 128z" p-id="3453"></path></svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684745011703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1481" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M110.025 252.249c0 13.741 5.456 27.374 14.899 37.112s22.663 15.364 35.987 15.364 26.544-5.626 35.987-15.364c9.441-9.738 14.899-23.371 14.899-37.112s-5.456-27.375-14.899-37.111c-9.442-9.738-22.663-15.364-35.987-15.364s-26.544 5.626-35.987 15.364c-9.338 9.736-14.899 23.37-14.899 37.111m0 0zM103.625 512.575c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.371 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.738-22.662-15.364-36.091-15.364-13.324 0-26.649 5.626-36.091 15.364-9.444 9.737-14.899 23.37-14.899 37.22m0 0zM103.625 774.089c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.37 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.737-22.662-15.364-36.091-15.364-13.324 0-26.649 5.627-36.091 15.364-9.444 9.847-14.899 23.479-14.899 37.22m0 0zM919.041 249.869c0 27.699-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.397-44.59-50.095 0-27.699 19.935-50.095 44.59-50.095h528.571c24.656-0.001 44.59 22.396 44.59 50.095m0 0zM919.041 510.195c0 27.59-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.398-44.59-50.096 0-27.699 19.935-50.096 44.59-50.096h528.571c24.656-0.109 44.59 22.397 44.59 50.097m0 0zM919.041 771.601c0 27.699-19.935 50.096-44.59 50.096H345.88c-24.655 0-44.59-22.397-44.59-50.096 0-27.591 19.935-49.988 44.59-49.988h528.571c24.656-0.108 44.59 22.397 44.59 49.988m0 0z" p-id="1482"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +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="1683450447995" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2005" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M728.99015111 121.90378667h107.92732444V40.96h-107.92732444v80.94378667z m175.38161778 67.45429333v107.92732445H985.31555555v-107.92732445h-80.94378666z m-67.45429334 175.38161778h-107.92732444v80.94378667h107.92732444v-80.94378667z m-175.38161777-67.45429333v-107.92732445h-80.94378667v107.92732445h80.94378667z m67.45429333 67.45429333c-37.23491555 0-67.45429333-30.21937778-67.45429333-67.45429333h-80.94378667c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378667z m175.38161778-67.45429333c0 37.23491555-30.21937778 67.45429333-67.45429334 67.45429333v80.94378667c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378666zM836.91747555 121.90151111c37.23491555 0 67.45429333 30.21937778 67.45429334 67.45429334H985.31555555C985.31555555 107.38801778 918.88753778 40.96 836.91747555 40.96v80.94378667zM728.99015111 40.96c-81.97006222 0-148.39808 66.42801778-148.39808 148.39808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333V40.96zM189.35808 661.53585778h107.92732445v-80.94378667h-107.92732445v80.94378667z m175.38161778 67.45429333v107.92732444h80.94378667v-107.92732444h-80.94378667z m-67.45429333 175.38161778h-107.92732445V985.31555555h107.92732445v-80.94378666zM121.90151111 836.91747555v-107.92732444H40.96v107.92732444h80.94378667z m67.45429334 67.45429334c-37.23491555 0-67.45429333-30.21937778-67.45429334-67.45429334H40.96C40.96 918.88753778 107.38801778 985.31555555 189.35808 985.31555555v-80.94378666z m175.38161777-67.45429334c0 37.23491555-30.21937778 67.45429333-67.45429333 67.45429334V985.31555555c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378667z m-67.45429333-175.38161777c37.23491555 0 67.45429333 30.21937778 67.45429333 67.45429333h80.94378667c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378667z m-107.92732444-80.94378667C107.38801778 580.59207111 40.96 647.02008889 40.96 728.99015111h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333v-80.94378667z m0-458.68828444h107.92732444V40.96h-107.92732444v80.94378667z m175.38161777 67.45429333v107.92732445h80.94378667v-107.92732445h-80.94378667z m-67.45429333 175.38161778h-107.92732444v80.94378667h107.92732444v-80.94378667zM121.90151111 297.28540445v-107.92732445H40.96v107.92732445h80.94378667z m67.45429334 67.45429333c-37.23491555 0-67.45429333-30.21937778-67.45429334-67.45429333H40.96c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378667z m175.38161777-67.45429333c0 37.23491555-30.21937778 67.45429333-67.45429333 67.45429333v80.94378667c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378667zM297.28540445 121.90151111c37.23491555 0 67.45429333 30.21937778 67.45429333 67.45429334h80.94378667c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378666zM189.35808 40.96C107.38801778 40.96 40.96 107.38801778 40.96 189.35808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333V40.96z m539.63207111 620.57585778h107.92732444v-80.94378667h-107.92732444v80.94378667z m175.38161778 67.45429333v107.92732444H985.31555555v-107.92732444h-80.94378666z m-67.45429334 175.38161778h-107.92732444V985.31555555h107.92732444v-80.94378666z m-175.38161777-67.45429334v-107.92732444h-80.94378667v107.92732444h80.94378667z m67.45429333 67.45429334c-37.23491555 0-67.45429333-30.21937778-67.45429333-67.45429334h-80.94378667c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378666z m175.38161778-67.45429334c0 37.23491555-30.21937778 67.45429333-67.45429334 67.45429334V985.31555555C918.88753778 985.31555555 985.31555555 918.88753778 985.31555555 836.91747555h-80.94378666z m-67.45429334-175.38161777c37.23491555 0 67.45429333 30.21937778 67.45429334 67.45429333H985.31555555c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378667z m-107.92732444-80.94378667c-81.97006222 0-148.39808 66.42801778-148.39808 148.39808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333v-80.94378667z" p-id="2006"></path></svg>
<?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="1684739068105" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7879" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M817.87 556.31h-63.58v-66.24A42.27 42.27 0 0 0 712 447.8h-84.81a42.27 42.27 0 0 0-42.27 42.27v66.24H436.57v-66.24a42.27 42.27 0 0 0-42.27-42.27h-84.83a42.27 42.27 0 0 0-42.27 42.27v66.24h-61.83A22.39 22.39 0 0 0 183 578.7a22.39 22.39 0 0 0 22.39 22.39h61.81v65.55a42.27 42.27 0 0 0 42.27 42.27h84.83a42.27 42.27 0 0 0 42.27-42.27v-65.55h148.36v65.55a42.27 42.27 0 0 0 42.27 42.27H712a42.27 42.27 0 0 0 42.27-42.27v-65.55h63.58a22.39 22.39 0 0 0 22.39-22.39 22.39 22.39 0 0 0-22.37-22.39z m-438.64 95.26h-54.69V505.14h54.69z m317.72 0h-54.69V505.14H697z" p-id="7880"></path><path d="M823 202.58h-90.81v-63.09a71.88 71.88 0 0 0-71.88-71.88H363.19a71.88 71.88 0 0 0-71.88 71.88v63.08h-90.12A137.17 137.17 0 0 0 64 339.75v479a137.17 137.17 0 0 0 137.19 137.14H823a137.17 137.17 0 0 0 137.19-137.17v-479A137.17 137.17 0 0 0 823 202.58z m-474.36-54.1A23.52 23.52 0 0 1 372.17 125h279.16a23.52 23.52 0 0 1 23.52 23.52v54.1h-326.2z m554.23 673.31a76.76 76.76 0 0 1-76.76 76.76h-628a76.76 76.76 0 0 1-76.76-76.76V336.67a76.76 76.76 0 0 1 76.76-76.76h628a76.76 76.76 0 0 1 76.76 76.76z" p-id="7881"></path></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<?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="1683254591061" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M389.5296 650.78613333a204.8 204.8 0 1 1-10.82026667-288.49493333L557.43146667 245.76a153.6 153.6 0 1 1 39.25333333 55.9104l-176.46933333 115.02933333c15.01866667 28.50133333 23.48373333 60.928 23.48373333 95.3344a204.11733333 204.11733333 0 0 1-16.86186667 81.47626667l257.1264 144.62293333a153.6 153.6 0 1 1-30.9248 60.928l-263.54346666-148.24106666z" p-id="1214"></path></svg>

Before

Width:  |  Height:  |  Size: 710 B

View File

@@ -12,7 +12,6 @@ const map = {
delete: require('./icons/delete.svg').default,
withdraw: require('./icons/withdraw.svg').default,
stop: require('./icons/stop.svg').default,
shareMarket: require('./icons/shareMarket.svg').default,
collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default,
chat: require('./icons/chat.svg').default,
@@ -25,7 +24,12 @@ const map = {
tabbarMe: require('./icons/phoneTabbar/me.svg').default,
closeSolid: require('./icons/closeSolid.svg').default,
wx: require('./icons/wx.svg').default,
out: require('./icons/out.svg').default
out: require('./icons/out.svg').default,
git: require('./icons/git.svg').default,
kb: require('./icons/kb.svg').default,
appStore: require('./icons/appStore.svg').default,
menu: require('./icons/menu.svg').default,
edit: require('./icons/edit.svg').default
};
export type IconName = keyof typeof map;

View File

@@ -7,7 +7,8 @@ import { useQuery } from '@tanstack/react-query';
const unAuthPage: { [key: string]: boolean } = {
'/': true,
'/login': true,
'/model/share': true
'/model/share': true,
'/chat/share': true
};
const Auth = ({ children }: { children: JSX.Element }) => {

View File

@@ -1,28 +1,29 @@
import React, { useEffect, useMemo } from 'react';
import { Box, useColorMode, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useScreen } from '@/hooks/useScreen';
import { useLoading } from '@/hooks/useLoading';
import { useGlobalStore } from '@/store/global';
import { throttle } from 'lodash';
import Auth from './auth';
import Navbar from './navbar';
import NavbarPhone from './navbarPhone';
const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true
'/login': true,
'/chat/share': true
};
const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true
'/login': true,
'/chat/share': true
};
const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: boolean }) => {
const { isPc } = useScreen({ defaultIsPc: isPcDevice });
const Layout = ({ children }: { children: JSX.Element }) => {
const router = useRouter();
const { colorMode, setColorMode } = useColorMode();
const { Loading } = useLoading();
const { loading } = useGlobalStore();
const { loading, setScreenWidth, isPc } = useGlobalStore();
const isChatPage = useMemo(
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
@@ -35,6 +36,19 @@ const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: b
}
}, [colorMode, router.pathname, setColorMode]);
useEffect(() => {
const resize = throttle(() => {
setScreenWidth(document.documentElement.clientWidth);
}, 300);
resize();
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
};
}, [setScreenWidth]);
return (
<>
<Box
@@ -67,15 +81,9 @@ const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: b
</Flex>
)}
</Box>
{loading && <Loading />}
<Loading loading={loading} />
</>
);
};
export default Layout;
Layout.getInitialProps = ({ req }: any) => {
return {
isPcDevice: !/Mobile/.test(req ? req.headers['user-agent'] : navigator.userAgent)
};
};

View File

@@ -1,9 +1,10 @@
import React, { useMemo } from 'react';
import { Box, Flex, Image, Tooltip } from '@chakra-ui/react';
import { Box, Flex, Tooltip } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import MyIcon from '../Icon';
import { useUserStore } from '@/store/user';
import { useChatStore } from '@/store/chat';
import Avatar from '../Avatar';
export enum NavbarTypeEnum {
normal = 'normal',
@@ -22,16 +23,21 @@ const Navbar = () => {
link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`,
activeLink: ['/chat']
},
{
label: 'AI助手',
label: '我的应用',
icon: 'model',
link: `/model?modelId=${lastModelId}`,
activeLink: ['/model']
},
{
label: '共享',
icon: 'shareMarket',
label: '知识库',
icon: 'kb',
link: `/kb`,
activeLink: ['/kb']
},
{
label: '应用市场',
icon: 'appStore',
link: '/model/share',
activeLink: ['/model/share']
},
@@ -77,13 +83,7 @@ const Navbar = () => {
cursor={'pointer'}
onClick={() => router.push('/number')}
>
<Image
src={userInfo?.avatar || '/icon/human.png'}
objectFit={'contain'}
w={'36px'}
h={'36px'}
alt=""
/>
<Avatar w={'36px'} h={'36px'} src={userInfo?.avatar} fallbackSrc={'/icon/human.png'} />
</Box>
{/* 导航列表 */}
<Box flex={1}>
@@ -125,6 +125,24 @@ const Navbar = () => {
</Tooltip>
))}
</Box>
<Box>
<Flex
mb={3}
flexDirection={'column'}
alignItems={'center'}
justifyContent={'center'}
cursor={'pointer'}
w={'60px'}
h={'45px'}
color={'#9096a5'}
_hover={{
color: '#ffffff'
}}
onClick={() => window.open('https://github.com/c121914yu/FastGPT')}
>
<MyIcon name={'git'} width={'22px'} height={'22px'} />
</Flex>
</Box>
</Flex>
);
};

View File

@@ -10,25 +10,21 @@ const NavbarPhone = () => {
const navbarList = useMemo(
() => [
{
label: '聊天',
icon: 'tabbarChat',
link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`,
activeLink: ['/chat']
},
{
label: 'AI助手',
icon: 'tabbarModel',
link: `/model`,
activeLink: ['/model']
},
{
label: '发现',
icon: 'tabbarMore',
link: '/tools',
activeLink: ['/tools']
},
{
label: '我',
icon: 'tabbarMe',
link: '/number',
activeLink: ['/number']

View File

@@ -5,7 +5,7 @@ const Loading = ({ fixed = true }: { fixed?: boolean }) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={100}
zIndex={1000}
backgroundColor={'rgba(255,255,255,0.5)'}
top={0}
left={0}

View File

@@ -339,9 +339,12 @@
text-align: justify;
tab-size: 4;
word-spacing: normal;
word-break: break-all;
width: 100%;
* {
word-break: break-all;
}
p {
white-space: pre-line;
}

View File

@@ -1,8 +1,8 @@
import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import { useCopyData, formatLinkText } from '@/utils/tools';
import Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
@@ -12,9 +12,21 @@ import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
import { codeLight } from './codeLight';
const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
const Markdown = ({
source,
isChatting = false,
formatLink
}: {
source: string;
formatLink?: boolean;
isChatting?: boolean;
}) => {
const { copyData } = useCopyData();
const formatSource = useMemo(() => {
return formatLink ? formatLinkText(source) : source;
}, [source, formatLink]);
return (
<ReactMarkdown
className={`markdown ${styles.markdown} ${
@@ -63,7 +75,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
}}
linkTarget="_blank"
>
{source}
{formatSource}
</ReactMarkdown>
);
};

View File

@@ -0,0 +1,67 @@
import React, { useState } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import type { BoxProps } from '@chakra-ui/react';
import MyIcon from '../Icon';
interface Props extends BoxProps {}
const SideBar = (e?: Props) => {
const {
w = ['100%', '0 0 250px', '0 0 280px', '0 0 310px', '0 0 340px'],
children,
...props
} = e || {};
const [foldSideBar, setFoldSideBar] = useState(false);
return (
<Box
position={'relative'}
flex={foldSideBar ? '0 0 0' : w}
w={['100%', 0]}
h={'100%'}
zIndex={1}
transition={'0.2s'}
_hover={{
'& > div': { visibility: 'visible', opacity: 1 }
}}
{...props}
>
<Flex
position={'absolute'}
right={0}
top={'50%'}
transform={'translate(50%,-50%)'}
alignItems={'center'}
justifyContent={'flex-end'}
pr={1}
w={'36px'}
h={'50px'}
borderRadius={'10px'}
bg={'rgba(0,0,0,0.5)'}
cursor={'pointer'}
transition={'0.2s'}
{...(foldSideBar
? {
opacity: 0.6
}
: {
visibility: 'hidden',
opacity: 0
})}
onClick={() => setFoldSideBar(!foldSideBar)}
>
<MyIcon
name={'back'}
transform={foldSideBar ? 'rotate(180deg)' : ''}
w={'14px'}
color={'white'}
/>
</Flex>
<Box position={'relative'} h={'100%'} overflow={foldSideBar ? 'hidden' : 'visible'}>
{children}
</Box>
</Box>
);
};
export default SideBar;

View File

@@ -23,7 +23,7 @@ const WxConcat = ({ onClose }: { onClose: () => void }) => {
<ModalBody textAlign={'center'}>
<Image
style={{ margin: 'auto' }}
src={'/imgs/wx300.jpg'}
src={'https://otnvvf-imgs.oss.laf.run/wx300.jpg'}
width={'200px'}
height={'200px'}
alt=""

View File

@@ -1,5 +1,6 @@
export const SYSTEM_PROMPT_HEADER = 'System-Prompt-Header';
export const NEW_CHATID_HEADER = 'Chat-Id-Header';
export const NEW_CHATID_HEADER = 'response-new-chat-id';
export const QUOTE_LEN_HEADER = 'response-quote-len';
export const GUIDE_PROMPT_HEADER = 'response-guide-prompt';
export enum ChatRoleEnum {
System = 'System',

11
src/constants/kb.ts Normal file
View File

@@ -0,0 +1,11 @@
import type { KbItemType } from '@/types/plugin';
export const defaultKbDetail: KbItemType = {
_id: '',
userId: '',
updateTime: new Date(),
avatar: '/icon/logo.png',
name: '',
tags: '',
totalData: 0
};

View File

@@ -1,4 +1,5 @@
import { getSystemModelList } from '@/api/system';
import type { ShareChatEditType } from '@/types/model';
import type { ModelSchema } from '@/types/mongoSchema';
export const embeddingModel = 'text-embedding-ada-002';
@@ -31,7 +32,7 @@ export const ChatModelMap = {
contextMaxToken: 4096,
systemMaxToken: 2400,
maxTemperature: 1.2,
price: 3
price: 2.5
},
[OpenAiChatEnum.GPT4]: {
chatModel: OpenAiChatEnum.GPT4,
@@ -121,15 +122,15 @@ export const ModelVectorSearchModeMap: Record<
> = {
[ModelVectorSearchModeEnum.hightSimilarity]: {
text: '高相似度, 无匹配时拒绝回复',
similarity: 0.2
similarity: 0.18
},
[ModelVectorSearchModeEnum.noContext]: {
text: '高相似度,无匹配时直接回复',
similarity: 0.2
similarity: 0.18
},
[ModelVectorSearchModeEnum.lowSimilarity]: {
text: '低相似度匹配',
similarity: 0.8
similarity: 0.7
}
};
@@ -141,7 +142,7 @@ export const defaultModel: ModelSchema = {
status: ModelStatusEnum.pending,
updateTime: Date.now(),
chat: {
useKb: false,
relatedKbs: [],
searchMode: ModelVectorSearchModeEnum.hightSimilarity,
systemPrompt: '',
temperature: 0,
@@ -152,12 +153,11 @@ export const defaultModel: ModelSchema = {
isShareDetail: false,
intro: '',
collection: 0
},
security: {
domain: ['*'],
contextMaxLen: 1,
contentMaxLen: 1,
expiredTime: 9999,
maxLoadAmount: 1
}
};
export const defaultShareChat: ShareChatEditType = {
name: '',
password: '',
maxContext: 5
};

14
src/constants/plugin.ts Normal file
View File

@@ -0,0 +1,14 @@
export enum SplitTextTypEnum {
'qa' = 'qa',
'subsection' = 'subsection'
}
export enum PluginTypeEnum {
LLM = 'LLM',
Text = 'Text',
Function = 'Function'
}
export enum PluginParamsTypeEnum {
'Text' = 'text'
}

View File

@@ -1,6 +1,6 @@
import { extendTheme, defineStyleConfig, ComponentStyleConfig } from '@chakra-ui/react';
// @ts-ignore
import { modalAnatomy, switchAnatomy, selectAnatomy } from '@chakra-ui/anatomy';
import { modalAnatomy, switchAnatomy, selectAnatomy, checkboxAnatomy } from '@chakra-ui/anatomy';
// @ts-ignore
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
@@ -11,6 +11,8 @@ const { definePartsStyle: switchPart, defineMultiStyleConfig: switchMultiStyle }
createMultiStyleConfigHelpers(switchAnatomy.keys);
const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle } =
createMultiStyleConfigHelpers(selectAnatomy.keys);
const { definePartsStyle: checkboxPart, defineMultiStyleConfig: checkboxMultiStyle } =
createMultiStyleConfigHelpers(checkboxAnatomy.keys);
// modal 弹窗
const ModalTheme = defineMultiStyleConfig({
@@ -171,6 +173,9 @@ export const theme = extendTheme({
fontWeight: 400,
height: '100%',
overflow: 'hidden'
},
a: {
color: 'myBlue.700'
}
}
},

View File

@@ -1,8 +1,7 @@
export enum BillTypeEnum {
chat = 'chat',
splitData = 'splitData',
openapiChat = 'openapiChat',
QA = 'QA',
abstract = 'abstract',
vector = 'vector',
return = 'return'
}
@@ -14,9 +13,8 @@ export enum PageTypeEnum {
export const BillTypeMap: Record<`${BillTypeEnum}`, string> = {
[BillTypeEnum.chat]: '对话',
[BillTypeEnum.splitData]: 'QA拆分',
[BillTypeEnum.openapiChat]: 'api 对话',
[BillTypeEnum.QA]: 'QA拆分',
[BillTypeEnum.abstract]: '摘要总结',
[BillTypeEnum.vector]: '索引生成',
[BillTypeEnum.return]: '退款'
};
@@ -29,6 +27,6 @@ export enum PromotionEnum {
export const PromotionTypeMap = {
[PromotionEnum.invite]: '好友充值',
[PromotionEnum.shareModel]: 'AI助手分享',
[PromotionEnum.shareModel]: '应用分享',
[PromotionEnum.withdraw]: '提现'
};

View File

@@ -4,7 +4,6 @@ import { IconButton, Flex, Box, Input } from '@chakra-ui/react';
import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
import { useMutation } from '@tanstack/react-query';
import { useToast } from './useToast';
import { useQuery } from '@tanstack/react-query';
export const usePagination = <T = any,>({
api,
@@ -41,6 +40,7 @@ export const usePagination = <T = any,>({
});
console.log(error);
}
return null;
}
});

View File

@@ -1,18 +0,0 @@
import React, { useState, useCallback, useRef } from 'react';
export const useTabs = ({
tabs = []
}: {
tabs: {
id: string;
label: string;
}[];
}) => {
const [activeTab, setActiveTab] = useState(tabs[0].id);
return {
tabs,
activeTab,
setActiveTab
};
};

View File

@@ -55,7 +55,7 @@ export default function App({ Component, pageProps }: AppProps) {
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
<Script src="/js/particles.js" strategy="lazyOnload"></Script>
<Script src="/js/particles.js"></Script>
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />

View File

@@ -1,15 +1,12 @@
function Error({ statusCode }: { statusCode: number }) {
return (
<p>
{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}
</p>
);
function Error({ errStr }: { errStr: string }) {
return <p>{errStr}</p>;
}
Error.getInitialProps = ({ res, err }: { res: any; err: any }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
console.log(err);
return { statusCode };
return {
errStr: `部分系统不兼容,导致页面崩溃。如果可以,请联系作者,反馈下具体操作和页面。大部分是 苹果 的 safari 浏览器导致,可以尝试更换 chrome 浏览器。`
};
};
export default Error;

View File

@@ -2,36 +2,34 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
import { appKbSearch } from '../openapi/kb/appKbSearch';
import { ChatRoleEnum, QUOTE_LEN_HEADER, GUIDE_PROMPT_HEADER } from '@/constants/chat';
import { BillTypeEnum } from '@/constants/user';
import { sensitiveCheck } from '@/service/api/text';
import { NEW_CHATID_HEADER } from '@/constants/chat';
import { saveChat } from './saveChat';
import { Types } from 'mongoose';
/* 发送提示词 */
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.end();
});
res.on('error', () => {
console.log('error: ', 'request error');
stream.destroy();
res.end();
});
try {
const { chatId, prompt, modelId } = req.body as {
prompt: ChatItemSimpleType;
prompt: [ChatItemType, ChatItemType];
modelId: string;
chatId: '' | string;
chatId?: string;
};
if (!modelId || !prompt) {
@@ -51,86 +49,143 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const modelConstantsData = ChatModelMap[model.chat.chatModel];
// 读取对话内容
const prompts = [...content, prompt];
const prompts = [...content, prompt[0]];
const {
code = 200,
systemPrompts = [],
quote = [],
guidePrompt = ''
} = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs.length > 0) {
const { code, searchPrompts, rawSearch, guidePrompt } = await appKbSearch({
model,
userId,
prompts,
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
});
// 使用了知识库搜索
if (model.chat.useKb) {
const { code, searchPrompts } = await searchKb({
userOpenAiKey,
prompts,
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
model,
return {
code,
quote: rawSearch,
systemPrompts: searchPrompts,
guidePrompt
};
}
if (model.chat.systemPrompt) {
return {
guidePrompt: model.chat.systemPrompt,
systemPrompts: [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
}
]
};
}
return {};
})();
// get conversationId. create a newId if it is null
const conversationId = chatId || String(new Types.ObjectId());
!chatId && res.setHeader(NEW_CHATID_HEADER, conversationId);
if (showModelDetail) {
guidePrompt && res.setHeader(GUIDE_PROMPT_HEADER, encodeURIComponent(guidePrompt));
res.setHeader(QUOTE_LEN_HEADER, quote.length);
}
// search result is empty
if (code === 201) {
const response = systemPrompts[0]?.value;
await saveChat({
chatId,
newChatId: conversationId,
modelId,
prompts: [
prompt[0],
{
...prompt[1],
quote: [],
value: response
}
],
userId
});
// search result is empty
if (code === 201) {
return res.send(searchPrompts[0]?.value);
}
prompts.splice(prompts.length - 3, 0, ...searchPrompts);
} else {
// 没有用知识库搜索,仅用系统提示词
model.chat.systemPrompt &&
prompts.splice(prompts.length - 3, 0, {
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
});
return res.end(response);
}
prompts.splice(prompts.length - 3, 0, ...systemPrompts);
// content check
await sensitiveCheck({
input: [...systemPrompts, prompt[0]].map((item) => item.value).join('')
});
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// 发出请求
// 发出 chat 请求
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey: userOpenAiKey || systemAuthKey,
temperature: +temperature,
messages: prompts,
stream: true,
res,
chatId
chatId: conversationId
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
step = 1;
if (res.closed) return res.end();
const { totalTokens, finishMessages } = await resStreamResponse({
model: model.chat.chatModel,
res,
stream,
chatResponse: streamResponse,
prompts,
systemPrompt: showModelDetail
? prompts
.filter((item) => item.obj === ChatRoleEnum.System)
.map((item) => item.value)
.join('\n')
: ''
});
// 只有使用平台的 key 才计费
pushChatBill({
isPay: !userOpenAiKey,
chatModel: model.chat.chatModel,
userId,
chatId,
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens
});
} catch (err: any) {
if (step === 1) {
// 直接结束流
console.log('error结束');
stream.destroy();
} else {
res.status(500);
jsonRes(res, {
code: 500,
error: err
try {
const { totalTokens, finishMessages, responseContent } = await resStreamResponse({
model: model.chat.chatModel,
res,
chatResponse: streamResponse,
prompts
});
// save chat
await saveChat({
chatId,
newChatId: conversationId,
modelId,
prompts: [
prompt[0],
{
...prompt[1],
value: responseContent,
quote: showModelDetail ? quote : [],
systemPrompt: showModelDetail ? guidePrompt : ''
}
],
userId
});
res.end();
// 只有使用平台的 key 才计费
pushChatBill({
isPay: !userOpenAiKey,
chatModel: model.chat.chatModel,
userId,
chatId: conversationId,
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens,
type: BillTypeEnum.chat
});
} catch (error) {
res.end();
console.log('error结束', error);
}
} catch (err: any) {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
const chatRecord = await Chat.findById(chatId);

View File

@@ -1,12 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
/* 获取历史记录 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -0,0 +1,49 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chatId, historyId } = req.query as { chatId: string; historyId: string };
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
if (!chatId || !historyId) {
throw new Error('params is error');
}
const history = await Chat.aggregate([
{
$match: {
_id: new Types.ObjectId(chatId),
userId: new Types.ObjectId(userId)
}
},
{
$unwind: '$content'
},
{
$match: {
'content._id': new Types.ObjectId(historyId)
}
},
{
$project: {
quote: '$content.quote'
}
}
]);
jsonRes(res, {
data: history[0]?.quote || []
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat, Model } from '@/service/mongo';
import type { InitChatResponse } from '@/api/response/chat';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authModel } from '@/service/utils/auth';
import mongoose from 'mongoose';
@@ -12,7 +12,7 @@ import type { ModelSchema } from '@/types/mongoSchema';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
let { modelId, chatId } = req.query as { modelId: '' | string; chatId: '' | string };
@@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const myModel = await Model.findOne({ userId });
if (!myModel) {
const { _id } = await Model.create({
name: 'AI助手1',
name: '应用1',
userId,
status: ModelStatusEnum.running
});
@@ -73,7 +73,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
_id: '$content._id',
obj: '$content.obj',
value: '$content.value',
systemPrompt: '$content.systemPrompt'
systemPrompt: '$content.systemPrompt',
quoteLen: { $size: { $ifNull: ['$content.quote', []] } }
}
}
]);

View File

@@ -1,13 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
/* 获取历史记录 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { id } = req.query;
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -3,63 +3,38 @@ 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/auth';
import { authUser } from '@/service/utils/auth';
import mongoose from 'mongoose';
type Props = {
newChatId?: string;
chatId?: string;
modelId: string;
prompts: [ChatItemType, ChatItemType];
};
/* 聊天内容存存储 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chatId, modelId, prompts, newChatId } = req.body as {
newChatId: '' | string;
chatId: '' | string;
modelId: string;
prompts: [ChatItemType, ChatItemType];
};
const { chatId, modelId, prompts, newChatId } = req.body as Props;
if (!prompts) {
throw new Error('缺少参数');
}
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const nId = await saveChat({
chatId,
modelId,
prompts,
newChatId,
userId
});
const content = prompts.map((item) => ({
_id: new mongoose.Types.ObjectId(item._id),
obj: item.obj,
value: item.value,
systemPrompt: item.systemPrompt
}));
await authModel({ modelId, userId, authOwner: false });
// 没有 chatId, 创建一个对话
if (!chatId) {
const { _id } = await Chat.create({
_id: newChatId ? new mongoose.Types.ObjectId(newChatId) : undefined,
userId,
modelId,
content,
title: content[0].value.slice(0, 20),
latestChat: content[1].value
});
return jsonRes(res, {
data: _id
});
} else {
// 已经有记录,追加入库
await Chat.findByIdAndUpdate(chatId, {
$push: {
content: {
$each: content
}
},
title: content[0].value.slice(0, 20),
latestChat: content[1].value,
updateTime: new Date()
});
}
jsonRes(res);
jsonRes(res, {
data: nId
});
} catch (err) {
jsonRes(res, {
code: 500,
@@ -67,3 +42,47 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
export async function saveChat({
chatId,
newChatId,
modelId,
prompts,
userId
}: Props & { userId: string }) {
await connectToDatabase();
await authModel({ modelId, userId, authOwner: false });
const content = prompts.map((item) => ({
_id: item._id ? new mongoose.Types.ObjectId(item._id) : undefined,
obj: item.obj,
value: item.value,
systemPrompt: item.systemPrompt,
quote: item.quote || []
}));
// 没有 chatId, 创建一个对话
if (!chatId) {
const { _id } = await Chat.create({
_id: newChatId ? new mongoose.Types.ObjectId(newChatId) : undefined,
userId,
modelId,
content,
title: content[0].value.slice(0, 20),
latestChat: content[1].value
});
return _id;
} else {
// 已经有记录,追加入库
await Chat.findByIdAndUpdate(chatId, {
$push: {
content: {
$each: content
}
},
title: content[0].value.slice(0, 20),
latestChat: content[1].value,
updateTime: new Date()
});
}
}

View File

@@ -0,0 +1,137 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authShareChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill, updateShareChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat';
import { ChatRoleEnum } from '@/constants/chat';
import { BillTypeEnum } from '@/constants/user';
import { sensitiveCheck } from '@/service/api/text';
import { appKbSearch } from '../../openapi/kb/appKbSearch';
/* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('error', () => {
console.log('error: ', 'request error');
res.end();
});
try {
const { shareId, password, historyId, prompts } = req.body as {
prompts: ChatItemSimpleType[];
password: string;
shareId: string;
historyId: string;
};
if (!historyId || !prompts) {
throw new Error('分享链接无效');
}
await connectToDatabase();
let startTime = Date.now();
const { model, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
shareId,
password
});
const modelConstantsData = ChatModelMap[model.chat.chatModel];
const { code = 200, systemPrompts = [] } = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs.length > 0) {
const { code, searchPrompts } = await appKbSearch({
model,
userId,
prompts,
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity
});
return {
code,
systemPrompts: searchPrompts
};
}
if (model.chat.systemPrompt) {
return {
systemPrompts: [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
}
]
};
}
return {};
})();
// search result is empty
if (code === 201) {
return res.send(systemPrompts[0]?.value);
}
prompts.splice(prompts.length - 3, 0, ...systemPrompts);
// content check
await sensitiveCheck({
input: [...systemPrompts, prompts[prompts.length - 1]].map((item) => item.value).join('')
});
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// 发出请求
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey: userOpenAiKey || systemAuthKey,
temperature: +temperature,
messages: prompts,
stream: true,
res,
chatId: historyId
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
if (res.closed) return res.end();
try {
const { totalTokens, finishMessages } = await resStreamResponse({
model: model.chat.chatModel,
res,
chatResponse: streamResponse,
prompts
});
res.end();
/* bill */
pushChatBill({
isPay: !userOpenAiKey,
chatModel: model.chat.chatModel,
userId,
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens,
type: BillTypeEnum.chat
});
updateShareChatBill({
shareId,
tokens: totalTokens
});
} catch (error) {
res.end();
console.log('error结束', error);
}
} catch (err: any) {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,40 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authModel, authUser } from '@/service/utils/auth';
import type { ShareChatEditType } from '@/types/model';
/* create a shareChat */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId, name, maxContext, password } = req.body as ShareChatEditType & {
modelId: string;
};
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
await authModel({
modelId,
userId,
authOwner: false
});
const { _id } = await ShareChat.create({
userId,
modelId,
name,
password,
maxContext
});
jsonRes(res, {
data: _id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,29 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
/* delete a shareChat by shareChatId */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { id } = req.query as {
id: string;
};
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
await ShareChat.findOneAndRemove({
_id: id,
userId
});
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,60 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import type { InitShareChatResponse } from '@/api/response/chat';
import { authModel } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let { shareId, password = '' } = req.query as {
shareId: string;
password: string;
};
if (!shareId) {
throw new Error('params is error');
}
await connectToDatabase();
// get shareChat
const shareChat = await ShareChat.findById(shareId);
if (!shareChat) {
throw new Error('分享链接已失效');
}
if (shareChat.password !== hashPassword(password)) {
return jsonRes(res, {
code: 501,
message: '密码不正确'
});
}
// 校验使用权限
const { model } = await authModel({
modelId: shareChat.modelId,
userId: String(shareChat.userId),
authOwner: false
});
jsonRes<InitShareChatResponse>(res, {
data: {
maxContext: shareChat.maxContext,
model: {
name: model.name,
avatar: model.avatar,
intro: model.share.intro
},
chatModel: model.chat.chatModel
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,43 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools';
/* get shareChat list by modelId */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId } = req.query as {
modelId: string;
};
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
const data = await ShareChat.find({
modelId,
userId
}).sort({
_id: -1
});
const blankPassword = hashPassword('');
jsonRes(res, {
data: data.map((item) => ({
_id: item._id,
name: item.name,
password: item.password === blankPassword ? '' : '1',
tokens: item.tokens,
maxContext: item.maxContext,
lastTime: item.lastTime
}))
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,51 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let { chatId, historyId, quoteId } = req.query as {
chatId: string;
historyId: string;
quoteId: string;
};
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
if (!chatId || !historyId || !quoteId) {
throw new Error('params is error');
}
await Chat.updateOne(
{
_id: new Types.ObjectId(chatId),
userId: new Types.ObjectId(userId),
'content._id': new Types.ObjectId(historyId)
},
{
$set: {
'content.$.quote.$[quoteElem].isEdit': true
}
},
{
arrayFilters: [
{
'quoteElem.id': quoteId
}
]
}
);
jsonRes(res, {
data: ''
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -2,7 +2,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { ModelStatusEnum } from '@/constants/model';
import { Model } from '@/service/models/model';
@@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -1,48 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { modelId } = req.query as {
modelId: string;
};
if (!modelId) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
// 统计数据
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: [string, string][] = pgData.rows.map((item) => [
item.q.replace(/\n/g, '\\n'),
item.a.replace(/\n/g, '\\n')
]);
jsonRes(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,34 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import axios from 'axios';
import { axiosConfig } from '@/service/utils/tools';
/**
* 读取网站的内容
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { url } = req.body as { url: string };
if (!url) {
throw new Error('缺少 url');
}
await connectToDatabase();
await authToken(req);
const data = await axios
.get(url, {
httpsAgent: axiosConfig().httpsAgent
})
.then((res) => res.data as string);
jsonRes(res, { data });
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,54 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { ModelDataSchema } from '@/types/mongoSchema';
import { generateVector } from '@/service/events/generateVector';
import { PgClient } from '@/service/pg';
import { authModel } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { modelId, data } = req.body as {
modelId: string;
data: { a: ModelDataSchema['a']; q: ModelDataSchema['q'] }[];
};
if (!modelId || !Array.isArray(data)) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
// 验证是否是该用户的 model
await authModel({
userId,
modelId
});
// 插入记录
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: 0
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,8 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, Model, connectToDatabase, Collection } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { Chat, Model, connectToDatabase, Collection, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { authModel } from '@/service/utils/auth';
/* 获取我的模型 */
@@ -15,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
@@ -25,11 +24,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
userId
});
// 删除 pg 中所有该模型的数据
await PgClient.delete('modelData', {
where: [['user_id', userId], 'AND', ['model_id', modelId]]
});
// 删除对应的聊天
await Chat.deleteMany({
modelId
@@ -40,6 +34,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
modelId
});
// 删除分享链接
await ShareChat.deleteMany({
modelId
});
// 删除模型
await Model.deleteOne({
_id: modelId,

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { authModel } from '@/service/utils/auth';
/* 获取我的模型 */
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -1,14 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection, Model } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import type { ModelListResponse } from '@/api/response/model';
/* 获取模型列表 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection, Model } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
/* 模型收藏切换 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -1,37 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import type { ShareModelItem } from '@/types/model';
/* 获取模型列表 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
// get my collections
const collections = await Collection.find({
userId
}).populate('modelId', '_id avatar name userId share');
jsonRes<ShareModelItem[]>(res, {
data: collections
.map((item: any) => ({
_id: item.modelId?._id,
avatar: item.modelId?.avatar || '/icon/logo.png',
name: item.modelId?.name || '',
userId: item.modelId?.userId || '',
share: item.modelId?.share || {},
isCollection: true
}))
.filter((item) => item.share.isShare)
});
} catch (err) {
jsonRes(res, {
data: []
});
}
}

View File

@@ -3,6 +3,8 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection, Model } from '@/service/mongo';
import type { PagingData } from '@/types';
import type { ShareModelItem } from '@/types/model';
import { parseCookie } from '@/service/utils/auth';
import { Types } from 'mongoose';
/* 获取模型列表 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -15,6 +17,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
let userId = '';
try {
userId = await parseCookie(req.headers.cookie);
} catch (error) {
error;
}
const regex = new RegExp(searchText, 'i');
const where = {
@@ -23,15 +33,58 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
{ $or: [{ name: { $regex: regex } }, { 'share.intro': { $regex: regex } }] }
]
};
const pipeline = [
{
$match: where
},
{
$lookup: {
from: 'collections',
let: { modelId: '$_id' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$modelId', '$$modelId'] },
{
$eq: ['$userId', userId ? new Types.ObjectId(userId) : new Types.ObjectId()]
}
]
}
}
}
],
as: 'collections'
}
},
{
$project: {
_id: 1,
avatar: { $ifNull: ['$avatar', '/icon/logo.png'] },
name: 1,
userId: 1,
share: 1,
isCollection: {
$cond: { if: { $gt: [{ $size: '$collections' }, 0] }, then: true, else: false }
}
}
},
{
$sort: { 'share.collection': -1 }
},
{
$skip: (pageNum - 1) * pageSize
},
{
$limit: pageSize
}
];
// 获取被分享的模型
const [models, total] = await Promise.all([
Model.find(where, '_id avatar name userId share')
.sort({
'share.collection': -1
})
.limit(pageSize)
.skip((pageNum - 1) * pageSize),
// @ts-ignore
Model.aggregate(pipeline),
Model.countDocuments(where)
]);
@@ -39,14 +92,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
data: {
pageNum,
pageSize,
data: models.map((item) => ({
_id: item._id,
avatar: item.avatar || '/icon/logo.png',
name: item.name,
userId: item.userId,
share: item.share,
isCollection: false
})),
data: models,
total
}
});

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { Model } from '@/service/models/model';
import type { ModelUpdateParams } from '@/types/model';
import { authModel } from '@/service/utils/auth';
@@ -9,15 +9,15 @@ import { authModel } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { name, avatar, chat, share, security } = req.body as ModelUpdateParams;
const { name, avatar, chat, share } = req.body as ModelUpdateParams;
const { modelId } = req.query as { modelId: string };
if (!name || !chat || !security || !modelId) {
if (!name || !chat || !modelId) {
throw new Error('参数错误');
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
@@ -38,8 +38,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
chat,
'share.isShare': share.isShare,
'share.isShareDetail': share.isShareDetail,
'share.intro': share.intro,
security
'share.intro': share.intro
}
);

View File

@@ -1,29 +1,27 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authOpenApiKey, authModel, getApiKey } from '@/service/utils/auth';
import { authUser, authModel, getApiKey } from '@/service/utils/auth';
import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools';
import { BillTypeEnum } from '@/constants/user';
import { sensitiveCheck } from '@/service/api/text';
import { NEW_CHATID_HEADER } from '@/constants/chat';
import { Types } from 'mongoose';
import { appKbSearch } from '../kb/appKbSearch';
/* 发送提示词 */
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();
});
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
res.on('close', () => {
stream.destroy();
res.end();
});
res.on('error', () => {
console.log('error: ', 'request error');
stream.destroy();
res.end();
});
try {
@@ -53,7 +51,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
let startTime = Date.now();
/* 凭证校验 */
const { userId } = await authOpenApiKey(req);
const { userId } = await authUser({ req });
const { model } = await authModel({
userId,
@@ -69,36 +67,56 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const modelConstantsData = ChatModelMap[model.chat.chatModel];
// 使用了知识库搜索
if (model.chat.useKb) {
const similarity = ModelVectorSearchModeMap[model.chat.searchMode]?.similarity || 0.22;
let systemPrompts: {
obj: ChatRoleEnum;
value: string;
}[] = [];
const { code, searchPrompts } = await searchKb({
// 使用了知识库搜索
if (model.chat.relatedKbs.length > 0) {
const { code, searchPrompts } = await appKbSearch({
prompts,
similarity,
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
model,
userId
});
// search result is empty
if (code === 201) {
return res.send(searchPrompts[0]?.value);
return isStream
? res.send(searchPrompts[0]?.value)
: jsonRes(res, {
data: searchPrompts[0]?.value,
message: searchPrompts[0]?.value
});
}
prompts.splice(prompts.length - 1, 0, ...searchPrompts);
} else {
// 没有用知识库搜索,仅用系统提示词
model.chat.systemPrompt &&
prompts.splice(prompts.length - 1, 0, {
systemPrompts = searchPrompts;
} else if (model.chat.systemPrompt) {
systemPrompts = [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
});
}
];
}
prompts.splice(prompts.length - 3, 0, ...systemPrompts);
// content check
await sensitiveCheck({
input: [...systemPrompts, prompts[prompts.length - 1]].map((item) => item.value).join('')
});
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// get conversationId. create a newId if it is null
const conversationId = chatId || String(new Types.ObjectId());
!chatId && res?.setHeader(NEW_CHATID_HEADER, conversationId);
// 发出请求
const { streamResponse, responseMessages, responseText, totalTokens } =
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
@@ -107,50 +125,55 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
messages: prompts,
stream: isStream,
res,
chatId
chatId: conversationId
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
let textLen = 0;
let tokens = totalTokens;
if (res.closed) return res.end();
if (isStream) {
step = 1;
const { finishMessages, totalTokens } = await resStreamResponse({
model: model.chat.chatModel,
res,
stream,
chatResponse: streamResponse,
prompts
});
textLen = finishMessages.map((item) => item.value).join('').length;
tokens = totalTokens;
} else {
textLen = responseMessages.map((item) => item.value).join('').length;
jsonRes(res, {
data: responseText
});
}
const { textLen = 0, tokens = totalTokens } = await (async () => {
if (isStream) {
try {
const { finishMessages, totalTokens } = await resStreamResponse({
model: model.chat.chatModel,
res,
chatResponse: streamResponse,
prompts
});
res.end();
return {
textLen: finishMessages.map((item) => item.value).join('').length,
tokens: totalTokens
};
} catch (error) {
res.end();
console.log('error结束', error);
}
} else {
jsonRes(res, {
data: responseText
});
return {
textLen: responseMessages.map((item) => item.value).join('').length
};
}
return {};
})();
pushChatBill({
isPay: true,
chatModel: model.chat.chatModel,
userId,
textLen,
tokens
tokens,
type: BillTypeEnum.openapiChat
});
} catch (err: any) {
if (step === 1) {
// 直接结束流
console.log('error结束');
stream.destroy();
} else {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}
});

View File

@@ -1,194 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authOpenApiKey, authModel, getApiKey } from '@/service/utils/auth';
import { resStreamResponse, modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
/* 发送提示词 */
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 {
prompt,
modelId,
isStream = true
} = req.body as {
prompt: ChatItemSimpleType;
modelId: string;
isStream: boolean;
};
if (!prompt || !modelId) {
throw new Error('缺少参数');
}
await connectToDatabase();
let startTime = Date.now();
/* 凭证校验 */
const { userId } = await authOpenApiKey(req);
/* 查找数据库里的模型信息 */
const { model } = await authModel({
userId,
modelId
});
/* get api key */
const { systemAuthKey: apiKey } = await getApiKey({
model: model.chat.chatModel,
userId,
mustPay: true
});
const modelConstantsData = ChatModelMap[model.chat.chatModel];
console.log('laf gpt start');
// 请求一次 chatgpt 拆解需求
const { responseText: resolveText, totalTokens: resolveTokens } = await modelServiceToolMap[
model.chat.chatModel
].chatCompletion({
apiKey,
temperature: 0,
messages: [
{
obj: ChatRoleEnum.System,
value: `服务端逻辑生成器.根据用户输入的需求,拆解成 laf 云函数实现的步骤,只返回步骤,按格式返回步骤: 1.\n2.\n3.\n ......
下面是一些例子:
一个 hello world 例子
1. 返回字符串: "hello world"
计算圆的面积
1. 从 body 中获取半径 radius.
2. 校验 radius 是否为有效的数字.
3. 计算圆的面积.
4. 返回圆的面积: {area}
实现一个手机号发生注册验证码方法.
1. 从 query 中获取 phone.
2. 校验手机号格式是否正确,不正确则返回错误原因:手机号格式错误.
3. 给 phone 发送一个短信验证码,验证码长度为6位字符串,内容为:你正在注册laf,验证码为:code.
4. 数据库添加数据,表为"codes",内容为 {phone, code}.
实现一个云函数,使用手机号注册账号,需要验证手机验证码.
1. 从 body 中获取 phone 和 code.
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 是否为空,为空则返回错误原因:博客ID不能为空.
3. 校验 blogText 是否为空,为空则返回错误原因:博客内容不能为空.
4. 校验 tags 是否为数组,不是则返回错误原因:标签必须为数组.
5. 获取当前时间,记录为 updateTime.
6. 更新数据库数据,表为"blogs",更新符合 blogId 的记录的内容为{blogText, tags, updateTime}.
7. 返回结果 "更新博客记录成功"`
},
{
obj: ChatRoleEnum.Human,
value: prompt.value
}
],
stream: false
});
prompt.value += ` ${resolveText}`;
console.log('prompt resolve success, time:', `${(Date.now() - startTime) / 1000}s`);
// 读取对话内容
const prompts = [prompt];
// 获取向量匹配到的提示词
const { searchPrompts } = await searchKb({
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
prompts,
model,
userId
});
prompts.splice(prompts.length - 1, 0, ...searchPrompts);
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// 发出请求
const { streamResponse, responseMessages, responseText, totalTokens } =
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey,
temperature: +temperature,
messages: prompts,
stream: isStream
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
let textLen = resolveText.length;
let tokens = resolveTokens;
if (isStream) {
step = 1;
const { finishMessages, totalTokens } = await resStreamResponse({
model: model.chat.chatModel,
res,
stream,
chatResponse: streamResponse,
prompts
});
textLen += finishMessages.map((item) => item.value).join('').length;
tokens += totalTokens;
} else {
textLen += responseMessages.map((item) => item.value).join('').length;
tokens += totalTokens;
jsonRes(res, {
data: responseText
});
}
console.log('laf gpt done. time:', `${(Date.now() - startTime) / 1000}s`);
pushChatBill({
isPay: true,
chatModel: model.chat.chatModel,
userId,
textLen,
tokens
});
} catch (err: any) {
if (step === 1) {
// 直接结束流
console.log('error结束');
stream.destroy();
} else {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}
}

View File

@@ -2,7 +2,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, OpenApi } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error('缺少参数');
}
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -2,12 +2,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, OpenApi } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { UserOpenApiKey } from '@/types/openapi';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -0,0 +1,221 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import type { ChatItemSimpleType } from '@/types/chat';
import type { ModelSchema } from '@/types/mongoSchema';
import { ModelVectorSearchModeEnum } from '@/constants/model';
import { authModel } from '@/service/utils/auth';
import { ChatModelMap } from '@/constants/model';
import { ChatRoleEnum } from '@/constants/chat';
import { openaiEmbedding } from '../plugin/openaiEmbedding';
import { ModelDataStatusEnum } from '@/constants/model';
import { modelToolMap } from '@/utils/plugin';
export type QuoteItemType = { id: string; q: string; a: string; isEdit: boolean };
type Props = {
prompts: ChatItemSimpleType[];
similarity: number;
appId: string;
};
type Response = {
code: 200 | 201;
rawSearch: QuoteItemType[];
guidePrompt: string;
searchPrompts: {
obj: ChatRoleEnum;
value: string;
}[];
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { userId } = await authUser({ req });
if (!userId) {
throw new Error('userId is empty');
}
const { prompts, similarity, appId } = req.body as Props;
if (!similarity || !Array.isArray(prompts) || !appId) {
throw new Error('params is error');
}
// auth model
const { model } = await authModel({
modelId: appId,
userId
});
const result = await appKbSearch({
userId,
prompts,
similarity,
model
});
jsonRes<Response>(res, {
data: result
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
}
});
export async function appKbSearch({
model,
userId,
prompts,
similarity
}: {
userId: string;
prompts: ChatItemSimpleType[];
similarity: number;
model: ModelSchema;
}): Promise<Response> {
const modelConstantsData = ChatModelMap[model.chat.chatModel];
// search two times.
const userPrompts = prompts.filter((item) => item.obj === 'Human');
const input: string[] = [
userPrompts[userPrompts.length - 1].value,
userPrompts[userPrompts.length - 2]?.value
].filter((item) => item);
// get vector
const promptVectors = await openaiEmbedding({
userId,
input
});
// search kb
const searchRes = await Promise.all(
promptVectors.map((promptVector) =>
PgClient.select<QuoteItemType>('modelData', {
fields: ['id', 'q', 'a'],
where: [
['status', ModelDataStatusEnum.ready],
'AND',
`kb_id IN (${model.chat.relatedKbs.map((item) => `'${item}'`).join(',')})`,
'AND',
`vector <=> '[${promptVector}]' < ${similarity}`
],
order: [{ field: 'vector', mode: `<=> '[${promptVector}]'` }],
limit: promptVectors.length === 1 ? 15 : 10
}).then((res) => res.rows)
)
);
// filter same search result
const idSet = new Set<string>();
const filterSearch = searchRes.map((search) =>
search.filter((item) => {
if (idSet.has(item.id)) {
return false;
}
idSet.add(item.id);
return true;
})
);
// slice search result by rate.
const sliceRateMap: Record<number, number[]> = {
1: [1],
2: [0.7, 0.3]
};
const sliceRate = sliceRateMap[searchRes.length] || sliceRateMap[0];
// 计算固定提示词的 token 数量
const guidePrompt = model.chat.systemPrompt // user system prompt
? {
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
}
: model.chat.searchMode === ModelVectorSearchModeEnum.noContext
? {
obj: ChatRoleEnum.System,
value: `知识库是关于"${model.name}"的内容,根据知识库内容回答问题.`
}
: {
obj: ChatRoleEnum.System,
value: `玩一个问答游戏,规则为:
1.你完全忘记你已有的知识
2.你只回答关于"${model.name}"的问题
3.你只从知识库中选择内容进行回答
4.如果问题不在知识库中,你会回答:"我不知道。"
请务必遵守规则`
};
const fixedSystemTokens = modelToolMap[model.chat.chatModel].countTokens({
messages: [guidePrompt]
});
const maxTokens = modelConstantsData.systemMaxToken - fixedSystemTokens;
const sliceResult = sliceRate.map((rate, i) =>
modelToolMap[model.chat.chatModel]
.tokenSlice({
maxToken: Math.round(maxTokens * rate),
messages: filterSearch[i].map((item) => ({
obj: ChatRoleEnum.System,
value: `${item.q}\n${item.a}`
}))
})
.map((item) => item.value)
);
// slice filterSearch
const sliceSearch = filterSearch.map((item, i) => item.slice(0, sliceResult[i].length)).flat();
// system prompt
const systemPrompt = sliceResult.flat().join('\n').trim();
/* 高相似度+不回复 */
if (!systemPrompt && model.chat.searchMode === ModelVectorSearchModeEnum.hightSimilarity) {
return {
code: 201,
rawSearch: [],
guidePrompt: '',
searchPrompts: [
{
obj: ChatRoleEnum.System,
value: '对不起,你的问题不在知识库中。'
}
]
};
}
/* 高相似度+无上下文,不添加额外知识,仅用系统提示词 */
if (!systemPrompt && model.chat.searchMode === ModelVectorSearchModeEnum.noContext) {
return {
code: 200,
rawSearch: [],
guidePrompt: model.chat.systemPrompt || '',
searchPrompts: model.chat.systemPrompt
? [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
}
]
: []
};
}
return {
code: 200,
rawSearch: sliceSearch,
guidePrompt: guidePrompt.value || '',
searchPrompts: [
{
obj: ChatRoleEnum.System,
value: `知识库:${systemPrompt}`
},
guidePrompt
]
};
}

View File

@@ -1,9 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { dataId } = req.query as {
dataId: string;
@@ -14,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req });
await PgClient.delete('modelData', {
where: [['user_id', userId], 'AND', ['id', dataId]]
@@ -28,4 +29,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});

View File

@@ -1,53 +1,55 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { KbDataItemType } from '@/types/plugin';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { generateVector } from '@/service/events/generateVector';
import { ModelDataStatusEnum } from '@/constants/model';
import { PgClient } from '@/service/pg';
import { authModel } from '@/service/utils/auth';
import { PgClient, insertKbItem } from '@/service/pg';
import { authKb } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { modelId, data } = req.body as {
modelId: string;
data: string[][];
const {
kbId,
data,
formatLineBreak = true
} = req.body as {
kbId: string;
formatLineBreak?: boolean;
data: { a: KbDataItemType['a']; q: KbDataItemType['q'] }[];
};
if (!modelId || !Array.isArray(data)) {
if (!kbId || !Array.isArray(data)) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
// 验证是否是该用户的 model
await authModel({
// 凭证校
const { userId } = await authUser({ req });
await authKb({
userId,
modelId
kbId
});
// 去重
// 过滤重复的内容
const searchRes = await Promise.allSettled(
data.map(async ([q, a = '']) => {
data.map(async ({ q, a = '' }) => {
if (!q) {
return Promise.reject('q为空');
}
try {
if (formatLineBreak) {
q = q.replace(/\\n/g, '\n');
a = a.replace(/\\n/g, '\n');
}
// Exactly the same data, not push
try {
const count = await PgClient.count('modelData', {
where: [
['user_id', userId],
'AND',
['model_id', modelId],
'AND',
['q', q],
'AND',
['a', a]
]
where: [['user_id', userId], 'AND', ['kb_id', kbId], 'AND', ['q', q], 'AND', ['a', a]]
});
if (count > 0) {
return Promise.reject('已经存在');
@@ -61,25 +63,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
})
);
// 过滤重复的内容
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 }
])
// 插入记录
const insertRes = await insertKbItem({
userId,
kbId,
data: filterData
});
generateVector();
jsonRes(res, {
message: `共插入 ${insertRes.rowCount} 条数据`,
data: insertRes.rowCount
});
} catch (err) {
@@ -88,4 +86,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});
export const config = {
api: {
bodyParser: {
sizeLimit: '100mb'
}
}
};

View File

@@ -1,11 +1,12 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { ModelDataStatusEnum } from '@/constants/model';
import { generateVector } from '@/service/events/generateVector';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { dataId, a, q } = req.body as { dataId: string; a: string; q?: string };
@@ -14,9 +15,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req });
// 更新 pg 内容
// 更新 pg 内容.仅修改a不需要更新向量。
await PgClient.update('modelData', {
where: [['id', dataId], 'AND', ['user_id', userId]],
values: [
@@ -39,4 +40,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});

View File

@@ -0,0 +1,77 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import { getApiKey } from '@/service/utils/auth';
import { getOpenAIApi } from '@/service/utils/chat/openai';
import { embeddingModel } from '@/constants/model';
import { axiosConfig } from '@/service/utils/tools';
import { pushGenerateVectorBill } from '@/service/events/pushBill';
type Props = {
input: string[];
};
type Response = number[][];
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { userId } = await authUser({ req });
let { input } = req.query as Props;
if (!Array.isArray(input)) {
throw new Error('缺少参数');
}
jsonRes<Response>(res, {
data: await openaiEmbedding({ userId, input, mustPay: true })
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
}
});
export async function openaiEmbedding({
userId,
input,
mustPay = false
}: { userId: string; mustPay?: boolean } & Props) {
const { userOpenAiKey, systemAuthKey } = await getApiKey({
model: 'gpt-3.5-turbo',
userId,
mustPay
});
// 获取 chatAPI
const chatAPI = getOpenAIApi();
// 把输入的内容转成向量
const result = await chatAPI
.createEmbedding(
{
model: embeddingModel,
input
},
{
timeout: 60000,
...axiosConfig(userOpenAiKey || systemAuthKey)
}
)
.then((res) => ({
tokenLen: res.data.usage.total_tokens || 0,
vectors: res.data.data.map((item) => item.embedding)
}));
pushGenerateVectorBill({
isPay: !userOpenAiKey,
userId,
text: input.join(''),
tokenLen: result.tokenLen
});
return result.vectors;
}

View File

@@ -2,13 +2,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, OpenApi } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890');
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -0,0 +1,66 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import type { ChatItemSimpleType } from '@/types/chat';
import { countOpenAIToken } from '@/utils/plugin/openai';
type ModelType = 'gpt-3.5-turbo' | 'gpt-4' | 'gpt-4-32k';
type Props = {
messages: ChatItemSimpleType[];
model: ModelType;
maxLen: number;
};
type Response = ChatItemSimpleType[];
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req });
const { messages, model, maxLen } = req.body as Props;
if (!Array.isArray(messages) || !model || !maxLen) {
throw new Error('params is error');
}
return jsonRes<Response>(res, {
data: gpt_chatItemTokenSlice({
messages,
model,
maxToken: maxLen
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export function gpt_chatItemTokenSlice({
messages,
model,
maxToken
}: {
messages: ChatItemSimpleType[];
model: ModelType;
maxToken: number;
}) {
let result: ChatItemSimpleType[] = [];
for (let i = 0; i < messages.length; i++) {
const msgs = [...result, messages[i]];
const tokens = countOpenAIToken({ messages: msgs, model });
if (tokens < maxToken) {
result = msgs;
} else {
break;
}
}
return result;
}

View File

@@ -0,0 +1,48 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser, getSystemOpenAiKey } from '@/service/utils/auth';
import type { TextPluginRequestParams } from '@/types/plugin';
import axios from 'axios';
import { axiosConfig } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (process.env.SENSITIVE_CHECK !== '1') {
return jsonRes(res);
}
await authUser({ req });
const { input } = req.body as TextPluginRequestParams;
const response = await axios({
...axiosConfig(getSystemOpenAiKey()),
method: 'POST',
url: `/moderations`,
data: {
input
}
});
const data = (response.data.results?.[0]?.category_scores as Record<string, number>) || {};
const values = Object.values(data);
for (const val of values) {
if (val > 0.2) {
return jsonRes(res, {
code: 500,
message: '您的内容不合规'
});
}
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,53 +1,55 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, SplitData } from '@/service/mongo';
import { authModel, authToken } from '@/service/utils/auth';
import { authKb, authUser } from '@/service/utils/auth';
import { generateVector } from '@/service/events/generateVector';
import { generateQA } from '@/service/events/generateQA';
import { PgClient } from '@/service/pg';
import { insertKbItem } from '@/service/pg';
import { SplitTextTypEnum } from '@/constants/plugin';
import { withNextCors } from '@/service/utils/tools';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
/* split text */
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chunks, modelId, prompt, mode } = req.body as {
modelId: string;
const { chunks, kbId, prompt, mode } = req.body as {
kbId: string;
chunks: string[];
prompt: string;
mode: 'qa' | 'subsection';
mode: `${SplitTextTypEnum}`;
};
if (!chunks || !modelId || !prompt) {
if (!chunks || !kbId || !prompt) {
throw new Error('参数错误');
}
await connectToDatabase();
const userId = await authToken(req);
const { userId } = await authUser({ req });
// 验证是否是该用户的 model
await authModel({
modelId,
await authKb({
kbId,
userId
});
if (mode === 'qa') {
if (mode === SplitTextTypEnum.qa) {
// 批量QA拆分插入数据
await SplitData.create({
userId,
modelId,
kbId,
textList: chunks,
prompt
});
generateQA();
} else if (mode === 'subsection') {
} else if (mode === SplitTextTypEnum.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' }
])
await insertKbItem({
userId,
kbId,
data: chunks.map((item) => ({
q: item,
a: ''
}))
});
generateVector();
@@ -60,7 +62,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
error: err
});
}
}
});
export const config = {
api: {

View File

@@ -0,0 +1,35 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { name, tags } = req.body as {
name: string;
tags: string[];
};
if (!name) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const { _id } = await KB.create({
name,
userId,
tags
});
jsonRes(res, { data: _id });
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,71 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, User } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { kbId } = req.query as {
kbId: string;
};
if (!kbId) {
throw new Error('缺少参数');
}
await connectToDatabase();
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000);
// auth export times
const authTimes = await User.findOne(
{
_id: userId,
$or: [
{ 'limit.exportKbTime': { $exists: false } },
{ 'limit.exportKbTime': { $lte: thirtyMinutesAgo } }
]
},
'_id limit'
);
if (!authTimes) {
throw new Error('上次导出未到半小时,每半小时仅可导出一次。');
}
// 统计数据
const count = await PgClient.count('modelData', {
where: [['kb_id', kbId], 'AND', ['user_id', userId]]
});
// 从 pg 中获取所有数据
const pgData = await PgClient.select<{ q: string; a: string }>('modelData', {
where: [['kb_id', kbId], 'AND', ['user_id', userId]],
fields: ['q', 'a'],
order: [{ field: 'id', mode: 'DESC' }],
limit: count
});
const data: [string, string][] = pgData.rows.map((item) => [
item.q.replace(/\n/g, '\\n'),
item.a.replace(/\n/g, '\\n')
]);
// update export time
await User.findByIdAndUpdate(userId, {
'limit.exportKbTime': new Date()
});
jsonRes(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,39 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import type { PgKBDataItemType } from '@/types/pg';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { dataId } = req.query as {
dataId: string;
};
if (!dataId) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const where: any = [['user_id', userId], 'AND', ['id', dataId]];
const searchRes = await PgClient.select<PgKBDataItemType>('modelData', {
fields: ['id', 'q', 'a', 'status'],
where,
limit: 1
});
jsonRes(res, {
data: searchRes.rows[0]
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,50 +1,40 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import type { PgModelDataItemType } from '@/types/pg';
import { authModel } from '@/service/utils/auth';
import type { PgKBDataItemType } from '@/types/pg';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let {
modelId,
kbId,
pageNum = 1,
pageSize = 10,
searchText = ''
} = req.query as {
modelId: string;
pageNum: string;
pageSize: string;
} = req.body as {
kbId: string;
pageNum: number;
pageSize: number;
searchText: string;
};
pageNum = +pageNum;
pageSize = +pageSize;
if (!modelId) {
if (!kbId) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const { model } = await authModel({
userId,
modelId,
authOwner: false
});
const where: any = [
...(model.share.isShareDetail ? [] : [['user_id', userId], 'AND']),
['model_id', modelId],
['user_id', userId],
'AND',
['kb_id', kbId],
...(searchText ? ['AND', `(q LIKE '%${searchText}%' OR a LIKE '%${searchText}%')`] : [])
];
const searchRes = await PgClient.select<PgModelDataItemType>('modelData', {
const searchRes = await PgClient.select<PgKBDataItemType>('modelData', {
fields: ['id', 'q', 'a', 'status'],
where,
order: [{ field: 'id', mode: 'DESC' }],

View File

@@ -1,43 +1,43 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, SplitData, Model } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { ModelDataStatusEnum } from '@/constants/model';
import { PgClient } from '@/service/pg';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId } = req.query as { modelId: string };
if (!modelId) {
const { kbId } = req.query as { kbId: string };
if (!kbId) {
throw new Error('参数错误');
}
await connectToDatabase();
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
// split queue data
const data = await SplitData.find({
userId,
modelId,
kbId,
textList: { $exists: true, $not: { $size: 0 } }
});
// embedding queue data
const where: any = [
['user_id', userId],
'AND',
['model_id', modelId],
'AND',
['status', ModelDataStatusEnum.waiting]
];
const embeddingData = await PgClient.count('modelData', {
where: [
['user_id', userId],
'AND',
['kb_id', kbId],
'AND',
['status', ModelDataStatusEnum.waiting]
]
});
jsonRes(res, {
data: {
splitDataQueue: data.map((item) => item.textList).flat().length,
embeddingQueue: await PgClient.count('modelData', {
where
})
embeddingQueue: embeddingData
}
});
} catch (err) {

View File

@@ -0,0 +1,43 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as {
id: string;
};
if (!id) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
// delete mongo data
await KB.findOneAndDelete({
_id: id,
userId
});
// delete all pg data
// 删除 pg 中所有该模型的数据
await PgClient.delete('modelData', {
where: [['user_id', userId], 'AND', ['kb_id', id]]
});
// delete related model
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as {
id: string;
};
if (!id) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const data = await KB.findOne({
_id: id,
userId
});
if (!data) {
throw new Error('kb is not exist');
}
jsonRes(res, {
data: {
_id: data._id,
avatar: data.avatar,
name: data.name,
userId: data.userId,
updateTime: data.updateTime,
tags: data.tags.join(' ')
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { KbItemType } from '@/types/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const kbList = await KB.find({
userId
}).sort({ updateTime: -1 });
const data = await Promise.all(
kbList.map(async (item) => ({
_id: item._id,
avatar: item.avatar,
name: item.name,
userId: item.userId,
updateTime: item.updateTime,
tags: item.tags.join(' '),
totalData: await PgClient.count('modelData', {
where: [['user_id', userId], 'AND', ['kb_id', item._id]]
})
}))
);
jsonRes<KbItemType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,39 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import type { KbUpdateParams } from '@/api/plugins/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id, name, tags, avatar } = req.body as KbUpdateParams;
if (!id || !name) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
await KB.findOneAndUpdate(
{
_id: id,
userId
},
{
avatar,
name,
tags: tags.split(' ').filter((item) => item)
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, User, Pay } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { PaySchema, UserModelSchema } from '@/types/mongoSchema';
import dayjs from 'dayjs';
import { getPayResult } from '@/service/utils/wxpay';
@@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try {
let { payId } = req.query as { payId: string };
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();

View File

@@ -2,8 +2,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Bill } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
import type { BillSchema } from '@/types/mongoSchema';
import { authUser } from '@/service/utils/auth';
import { adaptBill } from '@/utils/adapt';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -12,12 +12,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
pageNum = +pageNum;
pageSize = +pageSize;
const userId = await authToken(req);
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
// 根据 id 获取用户账单
const bills = await Bill.find<BillSchema>({
const bills = await Bill.find({
userId
})
.sort({ _id: -1 }) // 按照创建时间倒序排列
@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
data: {
pageNum,
pageSize,
data: bills,
data: bills.map(adaptBill),
total
}
});

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