Compare commits

...

71 Commits
v3.4 ... 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
archer
5f66f4523c perf: system model list 2023-05-13 14:38:43 +08:00
archer
4ec02c654b docs 2023-05-13 00:30:30 +08:00
archer
9a0c92629b perf: token params 2023-05-12 23:27:10 +08:00
archer
651eb1bf6b feat: fold sliderbar 2023-05-12 23:18:14 +08:00
archer
4e0c876154 perf: chat ui 2023-05-12 22:38:32 +08:00
archer
3e5118c4f7 fix: export 2023-05-12 18:30:52 +08:00
luiyezheng
bdd518fd3e 腾讯云函数代理方案 2023-05-12 11:25:57 +08:00
archer
340de071a9 fix: home;perf: phone adapt 2023-05-12 11:07:44 +08:00
archer
1226c3efb7 feat: chat ui 2023-05-12 00:09:10 +08:00
archer
39f9080eb2 perf: style 2023-05-11 21:19:43 +08:00
archer
3e4b165ed9 perf: ui 2023-05-11 17:29:46 +08:00
archer
451f234f68 perf: ssr and iphone daapt 2023-05-11 09:50:40 +08:00
archer
1ab45651e0 docs 2023-05-10 21:35:43 +08:00
archer
16f2ad7615 docs 2023-05-10 21:09:53 +08:00
archer
4ba4a99935 docs 2023-05-10 20:15:44 +08:00
archer
6f4471d2a0 merge 2023-05-10 19:59:19 +08:00
archer
250399a1aa docs 2023-05-10 18:28:20 +08:00
archer
1c8ce369b6 README 2023-05-10 15:29:17 +08:00
kense
875f78b42c fix: filter system prompt 字符串判空
修复 filterSystemPrompt为'\n'时, 判断有问题
2023-05-10 15:19:42 +08:00
archer
1e262a2198 docs 2023-05-10 15:18:16 +08:00
Archer
25067a14a6 Create LICENSE 2023-05-10 14:24:14 +08:00
archer
591cc21ff4 deploy docs 2023-05-10 14:14:36 +08:00
archer
5a21eb9bc1 perf: docs 2023-05-10 14:14:19 +08:00
archer
cdf4b9f324 perf: prompt and gpt4 2023-05-10 12:03:54 +08:00
archer
e3c9b8179e perf: message filter 2023-05-09 23:40:02 +08:00
archer
9b683884cc perf: overflow;load history 2023-05-09 22:54:39 +08:00
archer
4dc541e0a6 perf: search prompt 2023-05-09 21:54:07 +08:00
archer
ef2de489be perf: paging position 2023-05-09 21:30:51 +08:00
archer
cb3b9efc6e perf: filter prompt 2023-05-09 19:11:21 +08:00
archer
a745993829 perf: search prompt 2023-05-09 19:04:42 +08:00
archer
a837552b56 perf: search prompt 2023-05-09 16:32:02 +08:00
archer
f52f514f5f feat: callback training data 2023-05-09 13:20:52 +08:00
archer
fac53923dd perf: embedding price and img 2023-05-09 12:45:23 +08:00
archer
b200731d17 feat: cookie expired time 2023-05-09 11:26:11 +08:00
archer
de6ac0f589 perf: refresh page.img mode.collection filter 2023-05-09 11:13:33 +08:00
archer
18e0212d27 perf: ui show;fix: avatar 2023-05-08 18:44:10 +08:00
archer
f20a5fe9a6 perf: new chat modal 2023-05-08 14:19:28 +08:00
archer
d351084688 fix: openapi key unsave 2023-05-08 11:13:20 +08:00
archer
d807f9d097 deploy file 2023-05-08 10:46:33 +08:00
archer
3ef6d3fe63 deploy docs 2023-05-08 10:37:52 +08:00
217 changed files with 7281 additions and 3235 deletions

View File

@@ -1,8 +1,6 @@
# proxy # proxy
# AXIOS_PROXY_HOST=127.0.0.1 # AXIOS_PROXY_HOST=127.0.0.1
# AXIOS_PROXY_PORT=7890 # AXIOS_PROXY_PORT=7890
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1 # 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
queueTask=1 queueTask=1
parentUrl=https://hostname/api/openapi/startEvents parentUrl=https://hostname/api/openapi/startEvents
@@ -16,8 +14,18 @@ aliSignName=xxx
aliTemplateCode=SMS_xxx aliTemplateCode=SMS_xxx
# token # token
TOKEN_KEY=xxx TOKEN_KEY=xxx
# root key, 最高权限
ROOT_KEY=xxx
# 是否进行安全校验(1: 开启0: 关闭)
SENSITIVE_CHECK=1
# openai # openai
# OPENAI_BASE_URL=https://api.openai.com/v1
# OPENAI_BASE_URL_AUTH=可选的安全凭证(不需要的时候,记得去掉)
OPENAIKEY=sk-xxx OPENAIKEY=sk-xxx
GPT4KEY=sk-xxx
# claude
CLAUDE_BASE_URL=calude模型请求地址
CLAUDE_KEY=CLAUDE_KEY
# db # db
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
PG_HOST=0.0.0.0 PG_HOST=0.0.0.0

View File

@@ -37,12 +37,12 @@ jobs:
- name: build and publish image - name: build and publish image
env: env:
# fork friendly ^^ # fork friendly ^^
DOCKER_REPO: ghcr.io/${{ github.repository_owner }}/fast-gpt DOCKER_REPO: ghcr.io/${{ github.repository_owner }}/fastgpt
run: | run: |
docker buildx build \ docker buildx build \
--platform linux/amd64,linux/arm64 \ --platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fast-gpt image" \ --label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=MIT" \ --label "org.opencontainers.image.licenses=MIT" \
--push \ --push \
-t ${DOCKER_REPO}:latest \ -t ${DOCKER_REPO}:latest \

View File

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

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,4 +1,4 @@
SERVICE_NAME=fast-gpt SERVICE_NAME=fastgpt
# Image URL to use all building/pushing image targets # Image URL to use all building/pushing image targets
IMG ?= $(SERVICE_NAME):latest IMG ?= $(SERVICE_NAME):latest
@@ -34,13 +34,13 @@ run: ## Run a dev service from host.
.PHONY: docker-build .PHONY: docker-build
docker-build: ## Build docker image with the desktop-frontend. docker-build: ## Build docker image with the desktop-frontend.
docker build -t c121914yu/fast-gpt:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890 docker build -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890
##@ Deployment ##@ Deployment
.PHONY: docker-run .PHONY: docker-run
docker-run: ## Push docker image. docker-run: ## Push docker image.
docker run -d -p 8008:3000 --name fast-gpt -v /web_project/yjl/fast-gpt/logs:/app/.next/logs c121914yu/fast-gpt:latest docker run -d -p 8008:3000 --name fastgpt -v /web_project/yjl/fastgpt/logs:/app/.next/logs registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:latest
#TODO: add support of docker push #TODO: add support of docker push

293
README.md
View File

@@ -2,282 +2,45 @@
Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 gpt35 和 embedding. 可构建自己的知识库。 Fast GPT 允许你使用自己的 openai API KEY 来快速的调用 openai 接口,目前集成了 gpt35 和 embedding. 可构建自己的知识库。
## 知识库原理 ## 🛸 在线体验
🎉 [fastgpt.run](https://fastgpt.run/) (国内版)
🎉 [ai.fastgpt.run](https://ai.fastgpt.run/) (海外版)
![Demo](docs/imgs/demo.png?raw=true 'demo')
#### 知识库原理图
![KBProcess](docs/imgs/KBProcess.jpg?raw=true 'KBProcess') ![KBProcess](docs/imgs/KBProcess.jpg?raw=true 'KBProcess')
## 开发 ## 👨‍💻 开发
**配置环境变量** 项目技术栈: NextJs + TS + ChakraUI + Mongo + PostgresVector 插件)
复制.env.template 文件,生成一个.env.local 环境变量文件夹,修改.env.local 内容,参考下方: 这是一个平台项目,非单机项目,除了模型调用外还涉及非常多用户的内容。
[本地开发 Quick Start](docs/dev/README.md)
```bash ## 🚀 私有化部署
# proxy可选
AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT=7890
# openai 中转连接(可选)
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_BASE_URL_AUTH=可选的安全凭证
# 是否开启队列任务。 1-开启0-关闭(请求 parentUrl 去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# 发送邮箱验证码配置。参考 nodeMail 获取参数,自行百度。
MY_MAIL=xxx@qq.com
MAILE_CODE=xxx
# 阿里短信服务(邮箱和短信至少二选一)
aliAccessKeyId=xxx
aliAccessKeySecret=xxx
aliSignName=xxx
aliTemplateCode=SMS_xxx
# token随便填作为登录凭证
TOKEN_KEY=xxx
# openai key
OPENAIKEY=sk-xxx
# mongo连接地址
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/test?authSource=admin
# mongo数据库名称
MONGODB_NAME=xxx
# pg 数据库相关内容,和 docker-compose pg 部分对上
PG_HOST=0.0.0.0
PG_PORT=8102
PG_USER=fastgpt
PG_PASSWORD=1234
PG_DB_NAME=fastgpt
```
**运行** - [docker-compose 部署教程](docs/deploy/docker.md)
- [由社区贡献的宝塔部署和本地运行教程](https://space.bilibili.com/431177525/channel/collectiondetail?sid=1370663)
``` ## :point_right: RoadMap
pnpm dev
```
## 部署 - [FastGpt RoadMap](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte)
### 代理环境(国外服务器可忽略) ## 🏘️ 交流群
1. [clash 方案](./docs/proxy/clash.md) - 仅需一台服务器(需要有 clash 添加 wx 进入:
2. [nginx 方案](./docs/proxy/nginx.md) - 需要一台国外服务器 ![Demo](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
3. [cloudflare 方案](./docs/proxy/cloudflare.md) - 需要有域名(每日免费 10w 次代理请求)
### docker 部署 ## 👀 其他
#### 1. 安装 docker 和 docker-compose - [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [公众号接入](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/)
这个不同系统略有区别,百度安装下。验证安装成功后进行下一步。下面给出一个例子: ## 🌟 Star History
```bash [![Star History Chart](https://api.star-history.com/svg?repos=c121914yu/FastGPT&type=Date)](https://star-history.com/#c121914yu/FastGPT&Date)
# 安装docker
curl -L https://get.daocloud.io/docker | sh
sudo systemctl start docker
# 安装 docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker -v
docker-compose -v
```
#### 2. 创建 3 个初始化文件
手动创建或者直接把 deploy 里内容复制过去
**/root/fast-gpt/pg/init.sql**
```sql
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE EXTENSION vector;
-- init table
CREATE TABLE modelData (
id BIGSERIAL PRIMARY KEY,
vector VECTOR(1536),
status VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
model_id VARCHAR(50) NOT NULL,
q TEXT NOT NULL,
a TEXT NOT NULL
);
-- create index
CREATE INDEX modelData_status_index ON modelData USING HASH (status);
CREATE INDEX modelData_userId_index ON modelData USING HASH (user_id);
CREATE INDEX modelData_modelId_index ON modelData USING HASH (model_id);
EOSQL
```
**/root/fast-gpt/nginx/nginx.conf**
```conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 51200;
events {
worker_connections 1024;
}
http {
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/fast-gpt/docker-compose.yml**
```yml
version: '3.3'
services:
fast-gpt:
image: c121914yu/fast-gpt:latest
network_mode: host
restart: always
container_name: fast-gpt
environment:
# - AXIOS_PROXY_HOST=127.0.0.1
# - AXIOS_PROXY_PORT=7890
# - OPENAI_BASE_URL=https://api.openai.com/v1
# - OPENAI_BASE_URL_AUTH=可选的安全凭证
- MY_MAIL=xxxx@qq.com
- MAILE_CODE=xxxx
- aliAccessKeyId=xxxx
- aliAccessKeySecret=xxxx
- aliSignName=xxxxx
- aliTemplateCode=SMS_xxxx
- TOKEN_KEY=xxxx
- queueTask=1
- parentUrl=https://hostname/api/openapi/startEvents
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=xxx
- PG_HOST=0.0.0.0
- PG_PORT=8100
- PG_USER=fastgpt
- PG_PASSWORD=1234
- PG_DB_NAME=fastgpt
- OPENAIKEY=sk-xxxxx
nginx:
image: nginx:alpine3.17
container_name: nginx
restart: always
network_mode: host
volumes:
- /root/fast-gpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/fast-gpt/nginx/logs:/var/log/nginx
- /root/fast-gpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
- /root/fast-gpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
pg:
image: ankane/pgvector
container_name: pg
restart: always
ports:
- 8100:5432
environment:
- POSTGRES_USER=fastgpt
- POSTGRES_PASSWORD=1234
- POSTGRES_DB=fastgpt
volumes:
- /root/fast-gpt/pg/data:/var/lib/postgresql/data
- /root/fast-gpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
- /etc/localtime:/etc/localtime:ro
mongodb:
image: mongo:6.0.4
container_name: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=username
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- /root/fast-gpt/mongo/data:/data/db
- /root/fast-gpt/mongo/logs:/var/log/mongodb
- /etc/localtime:/etc/localtime:ro
```
#### 3. 运行 docker-compose
下面是一个辅助脚本,也可以直接 docker-compose up -d
**run.sh 运行文件**
```bash
#!/bin/bash
docker-compose pull
docker-compose up -d
echo "Docker Compose 重新拉取镜像完成!"
# 删除本地旧镜像
images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fast-gpt)
# 将镜像 ID 和名称放入数组中
IFS=$'\n' read -rd '' -a image_array <<<"$images"
# 遍历数组并删除所有旧的镜像
for ((i=1; i<${#image_array[@]}; i++))
do
image=${image_array[$i]}
image_id=${image%% *}
docker rmi $image_id
done
```
## 其他优化点
### Git Action 自动打包镜像
.github 里拥有一个 git 提交到 main 分支时自动打包 amd64 和 arm64 镜像的 actions。你仅需要提前在 git 配置好 session。
1. 创建账号 session: 头像 -> settings -> 最底部 Developer settings -> Personal access tokens -> tokens(classic) -> 创建新 session把一些看起来需要的权限勾上。
2. 添加 session 到仓库: 仓库 -> settings -> Secrets and variables -> Actions -> 创建 secret
3. 填写 secret: Name-GH_PAT, Secret-第一步的 tokens
## 其他问题
### Mac 可能的问题
> 因为教程有部分镜像不兼容 arm64所以写个文档指导新手如何快速在 mac 上面搭建 fast-gpt[如何在 mac 上面部署 fastgpt](./docs/mac.md)

View File

@@ -1,66 +0,0 @@
version: '3.3'
services:
fast-gpt:
image: c121914yu/fast-gpt:latest
network_mode: host
restart: always
container_name: fast-gpt
environment:
# - AXIOS_PROXY_HOST=127.0.0.1
# - AXIOS_PROXY_PORT=7890
# - OPENAI_BASE_URL=https://api.openai.com/v1
# - OPENAI_BASE_URL_AUTH=可选的安全凭证
- MY_MAIL=xxxx@qq.com
- MAILE_CODE=xxxx
- aliAccessKeyId=xxxx
- aliAccessKeySecret=xxxx
- aliSignName=xxxxx
- aliTemplateCode=SMS_xxxx
- TOKEN_KEY=xxxx
- queueTask=1
- parentUrl=https://hostname/api/openapi/startEvents
- MONGODB_URI=mongodb://username:passsword@0.0.0.0:27017/?authSource=admin
- MONGODB_NAME=xxx
- PG_HOST=0.0.0.0
- PG_PORT=8100
- PG_USER=xxx
- PG_PASSWORD=xxx
- PG_DB_NAME=xxx
- OPENAIKEY=sk-xxxxx
nginx:
image: nginx:alpine3.17
container_name: nginx
restart: always
network_mode: host
volumes:
- /root/fast-gpt/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- /root/fast-gpt/nginx/logs:/var/log/nginx
- /root/fast-gpt/nginx/ssl/docgpt.key:/ssl/docgpt.key
- /root/fast-gpt/nginx/ssl/docgpt.pem:/ssl/docgpt.pem
pg:
image: ankane/pgvector
container_name: pg
restart: always
ports:
- 8100:5432
environment:
- POSTGRES_USER=xxx
- POSTGRES_PASSWORD=xxx
- POSTGRES_DB=xxx
volumes:
- /root/fast-gpt/pg/data:/var/lib/postgresql/data
- /root/fast-gpt/pg/init.sql:/docker-entrypoint-initdb.d/init.sh
- /etc/localtime:/etc/localtime:ro
mongodb:
image: mongo:6.0.4
container_name: mongo
restart: always
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=username
- MONGO_INITDB_ROOT_PASSWORD=password
volumes:
- /root/fast-gpt/mongo/data:/data/db
- /root/fast-gpt/mongo/logs:/var/log/mongodb
- /etc/localtime:/etc/localtime:ro

View File

@@ -1 +0,0 @@
sudo chmod +x /usr/local/bin/docker-compose

84
docs/deploy/docker.md Normal file
View File

@@ -0,0 +1,84 @@
# Docker 部署 FastGpt
## 代理环境(国外服务器可忽略)
选择一个即可。这只是代理!!!不是项目。
1. [sealos nginx 方案](./proxy/sealos.md) - 推荐。约等于不用钱,不需要额外准备任何东西。
2. [clash 方案](./proxy/clash.md) - 仅需一台服务器(需要有 clash
3. [nginx 方案](./proxy/nginx.md) - 需要一台国外服务器
4. [cloudflare 方案](./proxy/cloudflare.md) - 需要有域名(每日免费 10w 次代理请求)
5. [腾讯云函数代理方案](https://github.com/easychen/openai-api-proxy/blob/master/FUNC.md) - 仅需一台服务器
### 1. 准备一些内容
> 1. 服务器开通 80 端口。用代理的话,对应的代理端口也需要打开。
> 2. QQ 邮箱 Code进入 QQ 邮箱 -> 账号 -> 申请 SMTP 账号
> 3. 有域名的准备好 SSL 证书
### 2. 安装 docker 和 docker-compose
这个不同系统略有区别,百度安装下。验证安装成功后进行下一步。下面给出一个例子:
```bash
# 安装docker
curl -L https://get.daocloud.io/docker | sh
sudo systemctl start docker
# 安装 docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker -v
docker-compose -v
# 如果docker-compose运行不了可以把 deploy/fastgpt/docker-compose 文件复制到服务器,然后在 docker-compose 文件夹里执行 sh init.sh。会把docker-compose文件复制到对应目录。
```
### 2. 创建 3 个初始化文件
fastgpt 文件夹。分别为fastgpt/docker-compose.yaml, fastgpt/pg/init.sql, fastgpt/nginx/nginx.conf
手动创建或者直接把 fastgpt 文件夹复制过去。
### 3. 运行 docker-compose
下面是一个辅助脚本,也可以直接 docker-compose up -d
**run.sh 运行文件**
```bash
#!/bin/bash
docker-compose pull
docker-compose up -d
echo "Docker Compose 重新拉取镜像完成!"
# 删除本地旧镜像
images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fastgpt)
# 将镜像 ID 和名称放入数组中
IFS=$'\n' read -rd '' -a image_array <<<"$images"
# 遍历数组并删除所有旧的镜像
for ((i=1; i<${#image_array[@]}; i++))
do
image=${image_array[$i]}
image_id=${image%% *}
docker rmi $image_id
done
```
## 其他优化点
# Git Action 自动打包镜像
.github 里拥有一个 git 提交到 main 分支时自动打包 amd64 和 arm64 镜像的 actions。你仅需要提前在 git 配置好 session。
1. 创建账号 session: 头像 -> settings -> 最底部 Developer settings -> Personal access tokens -> tokens(classic) -> 创建新 session把一些看起来需要的权限勾上。
2. 添加 session 到仓库: 仓库 -> settings -> Secrets and variables -> Actions -> 创建 secret
3. 填写 secret: Name-GH_PAT, Secret-第一步的 tokens
## 其他问题
### Mac 可能的问题
> 因为教程有部分镜像不兼容 arm64所以写个文档指导新手如何快速在 mac 上面搭建 fast-gpt[在 mac 上面部署 fastgpt 可能存在的问题](./mac.md)

View File

@@ -11,7 +11,7 @@ if [ ! -z "$OLD_PROCESS" ]; then
fi fi
sleep 2 sleep 2
cd /root/fast-gpt/clash/fast cd /root/fastgpt/clash/fast
rm -f ./nohup.out || true rm -f ./nohup.out || true
rm -f ./cache.db || true rm -f ./cache.db || true
nohup ./clash-linux-amd64-v3 -d ./ & nohup ./clash-linux-amd64-v3 -d ./ &

View File

@@ -0,0 +1,87 @@
version: '3.3'
services:
pg:
image: ankane/pgvector:v0.4.2
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
# 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
- 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

View File

@@ -0,0 +1,2 @@
cp ./docker-compose /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

View File

@@ -54,4 +54,16 @@ http {
server_name docgpt.ahapocket.cn; server_name docgpt.ahapocket.cn;
rewrite ^(.*) https://$server_name$1 permanent; rewrite ^(.*) https://$server_name$1 permanent;
} }
server {
listen 3000;
server_name 120.0.0.0;
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;
}
}
} }

View File

@@ -8,12 +8,14 @@ CREATE TABLE modelData (
vector VECTOR(1536), vector VECTOR(1536),
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
user_id 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, q TEXT NOT NULL,
a TEXT NOT NULL a TEXT NOT NULL
); );
-- create index -- create index
CREATE INDEX modelData_status_index ON modelData USING HASH (status); CREATE INDEX modelData_status_index ON modelData USING HASH (status);
CREATE INDEX modelData_userId_index ON modelData USING HASH (user_id); 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 EOSQL

View File

@@ -5,7 +5,7 @@ docker-compose up -d
echo "Docker Compose 重新拉取镜像完成!" echo "Docker Compose 重新拉取镜像完成!"
# 删除本地旧镜像 # 删除本地旧镜像
images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fast-gpt) images=$(docker images --format "{{.ID}} {{.Repository}}" | grep fastgpt)
# 将镜像 ID 和名称放入数组中 # 将镜像 ID 和名称放入数组中
IFS=$'\n' read -rd '' -a image_array <<<"$images" IFS=$'\n' read -rd '' -a image_array <<<"$images"

View File

@@ -1,35 +1,33 @@
## 怎么在mac上面部署fastgpt ## Mac 上部署可能遇到的问题
### 前置条件 ### 前置条件
1、可以 curl api.openai.com 1、可以 curl api.openai.com
2、有openai key 2、有 openai key
3、有邮箱MAILE_CODE 3、有邮箱 MAILE_CODE
4、有docker 4、有 docker
``` ```
docker -v docker -v
``` ```
5、有pnpm ,可以使用`brew install pnpm`安装 5、有 pnpm ,可以使用`brew install pnpm`安装
6、需要创建一个放置pgmongo数据的文件夹这里创建在`~/fastgpt`目录中,里面有`pg``mongo `两个文件夹 6、需要创建一个放置 pgmongo 数据的文件夹,这里创建在`~/fastgpt`目录中,里面有`pg``mongo `两个文件夹
``` ```
➜ fast-gpt pwd ➜ fastgpt pwd
/Users/jie/fast-gpt /Users/jie/fastgpt
➜ fast-gpt ls ➜ fastgpt ls
mongo pg mongo pg
``` ```
### docker 部署方式
这种方式主要是为了方便调试,可以使用`pnpm dev ` 运行 fastgpt 项目
### docker部署方式
这种方式主要是为了方便调试,可以使用`pnpm dev ` 运行fast-gpt项目
**1、.env.local 文件** **1、.env.local 文件**
@@ -60,19 +58,19 @@ PG_PASSWORD=xxx
PG_DB_NAME=xxx PG_DB_NAME=xxx
``` ```
**2、部署mongo** **2、部署 mongo**
``` ```
docker run --name mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=username -e MONGO_INITDB_ROOT_PASSWORD=password -v ~/fast-gpt/mongo/data:/data/db -d mongo:4.0.1 docker run --name mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=username -e MONGO_INITDB_ROOT_PASSWORD=password -v ~/fastgpt/mongo/data:/data/db -d mongo:4.0.1
``` ```
**3、部署pgsql** **3、部署 pgsql**
``` ```
docker run -it --name pg -e "POSTGRES_PASSWORD=xxx" -e POSTGRES_USER=xxx -p 8100:5432 -v ~/fast-gpt/pg/data:/var/lib/postgresql/data -d octoberlan/pgvector:v0.4.1 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容器运行 pgsql 容器运行
``` ```
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
@@ -90,11 +88,9 @@ CREATE TABLE modelData (
); );
-- create index -- create index
CREATE INDEX modelData_status_index ON modelData (status); CREATE INDEX modelData_status_index ON modelData (status);
CREATE INDEX modelData_modelId_index ON modelData (modelId); CREATE INDEX modelData_modelId_index ON modelData (model_id);
CREATE INDEX modelData_userId_index ON modelData (userId); CREATE INDEX modelData_userId_index ON modelData (user_id);
EOSQL EOSQL
``` ```
4、**最后在 FASTGPT 项目里面运行 pnpm dev 运行项目,然后进入 localhost:3000 看项目是否跑起来了**
4、**最后在FASTGPT项目里面运行pnpm dev 运行项目然后进入localhost:3000 看项目是否跑起来了**

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -1,4 +1,5 @@
# nginx 反向代理 openai 接口 # nginx 反向代理 openai 接口
如果你有国外的服务器,可以通过配置 nginx 反向代理,转发 openai 相关的请求,从而让国内的服务器可以通过访问该 nginx 去访问 openai 接口。 如果你有国外的服务器,可以通过配置 nginx 反向代理,转发 openai 相关的请求,从而让国内的服务器可以通过访问该 nginx 去访问 openai 接口。
```conf ```conf
@@ -19,7 +20,7 @@ http {
client_header_buffer_size 32k; client_header_buffer_size 32k;
large_client_header_buffers 4 32k; large_client_header_buffers 4 32k;
client_max_body_size 50M; client_max_body_size 50M;
gzip on; gzip on;
gzip_min_length 1k; gzip_min_length 1k;
gzip_buffers 4 8k; gzip_buffers 4 8k;
@@ -43,10 +44,10 @@ http {
location ~ /openai/(.*) { location ~ /openai/(.*) {
# auth check # auth check
if ($http_authkey != "xxxxxx") { if ($auth != "xxxxxx") {
return 403; return 403;
} }
proxy_pass https://api.openai.com/$1$is_args$args; proxy_pass https://api.openai.com/$1$is_args$args;
proxy_set_header Host api.openai.com; proxy_set_header Host api.openai.com;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@@ -69,4 +70,4 @@ http {
rewrite ^(.*) https://$server_name$1 permanent; rewrite ^(.*) https://$server_name$1 permanent;
} }
} }
``` ```

100
docs/deploy/proxy/sealos.md Normal file
View File

@@ -0,0 +1,100 @@
# sealos 部署 openai 中转
## 登录 sealos cloud
[sealos cloud](https://cloud.sealos.io/)
## 创建应用
打开 App Launchpad -> 新建应用
![step1](./imgs//sealos1.png)
![step2](./imgs//sealos2.png)
### 开启外网访问
![step3](./imgs//sealos3.png)
### 添加 configmap 文件
1. 复制下面这段代码,注意 `server_name` 后面的内容替换成上图的地址。
```
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;
server {
listen 80;
server_name tgohwtdlrmer.cloud.sealos.io; # 这个地方替换成 sealos 提供的内容
location ~ /openai/(.*) {
# auth check
if ($http_auth != "auth") { # 安全凭证
return 403;
}
proxy_pass https://api.openai.com/$1$is_args$args;
proxy_set_header Host api.openai.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 如果响应是流式的
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
# 如果响应是一般的
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
}
```
2. 点开高级配置
3. 点击新增 configmap
4. 文件名写: `/etc/nginx/nginx.conf`
5. 文件值为刚刚复制的那段代码
6. 点击确认
![step4](./imgs//sealos4.png)
### 部署应用
填写完毕后,点击右上角的 `部署应用`,即可完成。
## 修改 FastGpt 环境变量
1. 进入刚刚部署应用的详情,复制外网地址
![step5](./imgs//sealos5.png)
2. 修改环境变量(是 FastGpt 的环境变量,不是 sealos 的):
```
OPENAI_BASE_URL=https://tgohwtdlrmer.cloud.sealos.io/openai/v1
OPENAI_BASE_URL_AUTH=auth
```
**Done!**

51
docs/dev/README.md Normal file
View File

@@ -0,0 +1,51 @@
# FastGpt 本地开发
第一次开发,请先[部署教程](../deploy/docker.md),需要部署数据库.
## 环境变量配置
复制.env.template 文件,生成一个.env.local 环境变量文件夹,修改.env.local 里内容。
```bash
# proxy可选
AXIOS_PROXY_HOST=127.0.0.1
AXIOS_PROXY_PORT=7890
# 是否开启队列任务。 1-开启0-关闭请求parentUrl去执行任务,单机时直接填1
queueTask=1
parentUrl=https://hostname/api/openapi/startEvents
# email
MY_MAIL=xxx@qq.com
MAILE_CODE=xxx
# ali ems
aliAccessKeyId=xxx
aliAccessKeySecret=xxx
aliSignName=xxx
aliTemplateCode=SMS_xxx
# token
TOKEN_KEY=xxx
# 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_USER=xxx
PG_PASSWORD=xxx
PG_DB_NAME=xxx
```
## 运行
```
pnpm dev
```

BIN
docs/imgs/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

View File

@@ -1,6 +1,6 @@
{ {
"name": "fastgpt", "name": "fastgpt",
"version": "0.1.0", "version": "3.7",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -39,6 +39,7 @@
"mongoose": "^6.10.0", "mongoose": "^6.10.0",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",
"next": "13.1.6", "next": "13.1.6",
"nextjs-cors": "^2.1.2",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"openai": "^3.2.1", "openai": "^3.2.1",
@@ -82,5 +83,8 @@
}, },
"lint-staged": { "lint-staged": {
"./src/**/*.{ts,tsx,scss}": "npm run format" "./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 mongoose: ^6.10.0
nanoid: ^4.0.1 nanoid: ^4.0.1
next: 13.1.6 next: 13.1.6
nextjs-cors: ^2.1.2
nodemailer: ^6.9.1 nodemailer: ^6.9.1
nprogress: ^0.2.0 nprogress: ^0.2.0
openai: ^3.2.1 openai: ^3.2.1
@@ -97,6 +98,7 @@ dependencies:
mongoose: registry.npmmirror.com/mongoose/6.10.0 mongoose: registry.npmmirror.com/mongoose/6.10.0
nanoid: registry.npmmirror.com/nanoid/4.0.1 nanoid: registry.npmmirror.com/nanoid/4.0.1
next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4 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 nodemailer: registry.npmmirror.com/nodemailer/6.9.1
nprogress: registry.npmmirror.com/nprogress/0.2.0 nprogress: registry.npmmirror.com/nprogress/0.2.0
openai: registry.npmmirror.com/openai/3.2.1 openai: registry.npmmirror.com/openai/3.2.1
@@ -294,11 +296,29 @@ packages:
resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==}
dev: true 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: /cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false 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: /fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -309,9 +329,24 @@ packages:
/graceful-fs/4.2.10: /graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
requiresBuild: true
dev: false dev: false
optional: true 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: /saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -324,9 +359,15 @@ packages:
/source-map/0.6.1: /source-map/0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: false dev: false
optional: true 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: 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} 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' name: '@alicloud/credentials'
@@ -7742,7 +7783,7 @@ packages:
name: hast-util-from-parse5 name: hast-util-from-parse5
version: 7.1.2 version: 7.1.2
dependencies: 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 '@types/unist': registry.npmmirror.com/@types/unist/2.0.6
hastscript: registry.npmmirror.com/hastscript/7.2.0 hastscript: registry.npmmirror.com/hastscript/7.2.0
property-information: registry.npmmirror.com/property-information/6.2.0 property-information: registry.npmmirror.com/property-information/6.2.0
@@ -7756,7 +7797,7 @@ packages:
name: hast-util-is-element name: hast-util-is-element
version: 2.1.3 version: 2.1.3
dependencies: 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 '@types/unist': registry.npmmirror.com/@types/unist/2.0.6
dev: false dev: false
@@ -7771,7 +7812,7 @@ packages:
name: hast-util-parse-selector name: hast-util-parse-selector
version: 3.1.1 version: 3.1.1
dependencies: dependencies:
'@types/hast': registry.npmmirror.com/@types/hast/2.3.4 '@types/hast': 2.3.4
dev: false dev: false
registry.npmmirror.com/hast-util-to-text/3.1.2: registry.npmmirror.com/hast-util-to-text/3.1.2:
@@ -7796,7 +7837,7 @@ packages:
name: hastscript name: hastscript
version: 6.0.0 version: 6.0.0
dependencies: 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 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 hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/2.2.5
property-information: registry.npmmirror.com/property-information/5.6.0 property-information: registry.npmmirror.com/property-information/5.6.0
@@ -7808,7 +7849,7 @@ packages:
name: hastscript name: hastscript
version: 7.2.0 version: 7.2.0
dependencies: 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 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 hast-util-parse-selector: registry.npmmirror.com/hast-util-parse-selector/3.1.1
property-information: registry.npmmirror.com/property-information/6.2.0 property-information: registry.npmmirror.com/property-information/6.2.0
@@ -8762,7 +8803,7 @@ packages:
version: 5.1.2 version: 5.1.2
dependencies: dependencies:
'@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10 '@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 unist-util-visit: registry.npmmirror.com/unist-util-visit/4.1.2
dev: false dev: false
@@ -8890,7 +8931,7 @@ packages:
name: mdast-util-to-hast name: mdast-util-to-hast
version: 12.3.0 version: 12.3.0
dependencies: 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 '@types/mdast': registry.npmmirror.com/@types/mdast/3.0.10
mdast-util-definitions: registry.npmmirror.com/mdast-util-definitions/5.1.2 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 micromark-util-sanitize-uri: registry.npmmirror.com/micromark-util-sanitize-uri/1.1.0
@@ -9441,7 +9482,7 @@ packages:
version: 2.7.0 version: 2.7.0
dependencies: dependencies:
any-promise: registry.npmmirror.com/any-promise/1.3.0 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 thenify-all: registry.npmmirror.com/thenify-all/1.6.0
dev: false dev: false
@@ -11624,7 +11665,7 @@ packages:
name: unist-util-position name: unist-util-position
version: 4.0.4 version: 4.0.4
dependencies: dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6 '@types/unist': 2.0.6
dev: false dev: false
registry.npmmirror.com/unist-util-remove-position/4.0.2: registry.npmmirror.com/unist-util-remove-position/4.0.2:
@@ -11817,7 +11858,7 @@ packages:
name: vfile-location name: vfile-location
version: 4.1.0 version: 4.1.0
dependencies: dependencies:
'@types/unist': registry.npmmirror.com/@types/unist/2.0.6 '@types/unist': 2.0.6
vfile: registry.npmmirror.com/vfile/5.3.7 vfile: registry.npmmirror.com/vfile/5.3.7
dev: false dev: false

View File

@@ -1,14 +1,27 @@
### 常见问题 ### 常见问题
**请求次数太多了** **Git 地址**
一般是因为自己的 openai 账号异常。请先检查自己的账号是否正常使用。 [项目地址,完全开源,随便用。](https://github.com/c121914yu/FastGPT)
**内容长度**
chatgpt 上下文最长 4096 tokens, 会自动截取上下文,超过 4096 部分会被遗忘。 **问题文档**
[先看文档,再提问](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 请 WX 联系: fastgpt123
![FastGpt](/imgs/wx300.jpg) | 交流群 | 小助手 |
| ----------------------- | -------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |

View File

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

View File

@@ -1,43 +1,34 @@
## 欢迎使用 Fast GPT ## 欢迎使用 Fast GPT
[Git 仓库](https://github.com/c121914yu/FastGPT) ### 项目开源
### 交流群/问题反馈 FastGpt 项目完全开源,可随意私有化部署,去除平台风险忧虑。项目地址:[Git 仓库](https://github.com/c121914yu/FastGPT)
扫码满了,加个小号,定时拉 ### 开始使用知识库
wx 号: fastgpt123
![](/imgs/wx300.jpg)
### 快速开始 1. AI 助手详情里,有一个模型效果。打开知识库搜索开关即可使用知识库搜索功能。
2. 导入知识库数据。可以手动输入或文件导入。
3. 开始对话。
4. 对话结束后,会看到聊天下方有一个“查看提示词”,可以看到搜索到了哪些内容。
1. 使用手机号注册账号 注意使用知识库模型对话时tokens 消耗会加快
2. 进入账号页面,添加关联账号,目前只有 openai 的账号可以添加,直接去 openai 官网,把 API Key 粘贴过来。
3. 如果填写了自己的 openai 账号,使用时会直接用你的账号。如果没有填写,需要付费使用平台的账号。
4. 进入模型页,创建一个模型,建议直接用 ChatGPT。
5. 在模型列表点击【对话】,即可使用 API 进行聊天。
### 价格表 ### 价格表
如果使用了自己的 Api Key不会计费。可以在账号页看到详细账单。单纯使用 chatGPT 模型进行对话,只有一个计费项目。使用知识库时,包含**对话**和**索引**生成两个计费项。 如果使用了自己的 Api Key不会计费。可以在账号页看到详细账单。
| 计费项 | 价格: 元/ 1K tokens包含上下文| | 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- | | --- | --- |
| claude - 对话 | 免费 | | claude - 对话 | 免费 |
| chatgpt - 对话 | 0.03 | | 知识库 - 索引 | 免费 |
| 知识库 - 对话 | 0.03 | | chatgpt - 对话 | 0.025 |
| 知识库 - 索引 | 0.004 | | gpt4 - 对话 | 0.5 |
| 文件拆分 | 0.03 | | 文件拆分 | 0.025 |
### 定制 prompt ### 交流群/问题反馈
1. 进入模型编辑页 如果群满了,可加个小助手,定时拉
2. 调整温度和提示词 wx 号: fastgpt123
3. 使用该模型对话。每次对话时,提示词和温度都会自动注入,方便管理个人的模型。建议把自己日常经常需要使用的 5~10 个方向预设好。
### 知识库 | 交流群 | 小助手 |
| ------------------------------------------------- | ---------------------------------------------- |
1. 创建模型时选择【知识库】 | ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |
2. 进入模型编辑页
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
4. 使用该模型对话。
注意使用知识库模型对话时tokens 消耗会加快。

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: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

BIN
public/imgs/wxqun300.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

9
public/js/particles.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,11 @@
import { GET, POST, DELETE } from './request'; import { GET, POST, DELETE } from './request';
import type { ChatItemType, HistoryItemType } from '@/types/chat'; import type { HistoryItemType } from '@/types/chat';
import type { InitChatResponse } from './response/chat'; import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index'; 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}`); export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${id}`);
/** /**
* 存储一轮对话 * get history quotes
*/ */
export const postSaveChat = (data: { export const getHistoryQuote = (params: { chatId: string; historyId: string }) =>
modelId: string; GET<(QuoteItemType & { _id: string })[]>(`/chat/getHistoryQuote`, params);
newChatId: '' | string;
chatId: '' | string; /**
prompts: ChatItemType[]; * update history quote status
}) => POST<string>('/chat/saveChat', data); */
export const updateHistoryQuote = (params: {
chatId: string;
historyId: string;
quoteId: string;
}) => GET(`/chat/updateHistoryQuote`, params);
/** /**
* 删除一句对话 * 删除一句对话
*/ */
export const delChatRecordByIndex = (chatId: string, contentId: string) => export const delChatRecordByIndex = (chatId: string, contentId: string) =>
DELETE(`/chat/delChatRecordByContentId?chatId=${chatId}&contentId=${contentId}`); 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 { interface StreamFetchProps {
url: string; url: string;
@@ -7,7 +7,7 @@ interface StreamFetchProps {
abortSignal: AbortController; abortSignal: AbortController;
} }
export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchProps) => 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) => { async (resolve, reject) => {
try { try {
const res = await fetch(url, { const res = await fetch(url, {
@@ -23,8 +23,11 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
const decoder = new TextDecoder(); const decoder = new TextDecoder();
const systemPrompt = decodeURIComponent(res.headers.get(SYSTEM_PROMPT_HEADER) || '').trim();
const newChatId = decodeURIComponent(res.headers.get(NEW_CHATID_HEADER) || ''); 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 = ''; let responseText = '';
@@ -33,7 +36,7 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
const { done, value } = await reader?.read(); const { done, value } = await reader?.read();
if (done) { if (done) {
if (res.status === 200) { if (res.status === 200) {
resolve({ responseText, systemPrompt, newChatId }); resolve({ responseText, newChatId, quoteLen, systemPrompt });
} else { } else {
const parseError = JSON.parse(responseText); const parseError = JSON.parse(responseText);
reject(parseError?.message || '请求异常'); reject(parseError?.message || '请求异常');
@@ -41,13 +44,13 @@ export const streamFetch = ({ url, data, onMessage, abortSignal }: StreamFetchPr
return; return;
} }
const text = decoder.decode(value).replace(/<br\/>/g, '\n'); const text = decoder.decode(value);
responseText += text; responseText += text;
onMessage(text); onMessage(text);
read(); read();
} catch (err: any) { } catch (err: any) {
if (err?.message === 'The user aborted a request.') { 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 || '请求异常'); reject(typeof err === 'string' ? err : err?.message || '请求异常');
} }

View File

@@ -1,8 +1,7 @@
import { GET, POST, DELETE, PUT } from './request'; import { GET, POST, DELETE, PUT } from './request';
import type { ModelSchema, ModelDataSchema } from '@/types/mongoSchema'; import type { ModelSchema } from '@/types/mongoSchema';
import type { ModelUpdateParams, ShareModelItem } from '@/types/model'; import type { ModelUpdateParams } from '@/types/model';
import { RequestPaging } from '../types/index'; import { RequestPaging } from '../types/index';
import { Obj2Query } from '@/utils/tools';
import type { ModelListResponse } from './response/model'; import type { ModelListResponse } from './response/model';
/** /**
@@ -31,79 +30,13 @@ export const getModelById = (id: string) => GET<ModelSchema>(`/model/detail?mode
export const putModelById = (id: string, data: ModelUpdateParams) => export const putModelById = (id: string, data: ModelUpdateParams) =>
PUT(`/model/update?modelId=${id}`, data); 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<number>(`/model/data/getSplitData?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) => export const getShareModelList = (data: { searchText?: string } & RequestPaging) =>
POST(`/model/share/getModels`, data); 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

@@ -1,5 +1,5 @@
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { clearToken } from '@/utils/user'; import { clearCookie } from '@/utils/user';
import { TOKEN_ERROR_CODE } from '@/service/errorCode'; import { TOKEN_ERROR_CODE } from '@/service/errorCode';
interface ConfigType { interface ConfigType {
@@ -54,13 +54,13 @@ function responseError(err: any) {
if (typeof err === 'string') { if (typeof err === 'string') {
return Promise.reject({ message: err }); return Promise.reject({ message: err });
} }
if (err.response) { // 有报错响应
// 有报错响应 if (err?.code in TOKEN_ERROR_CODE) {
const res = err.response; clearCookie();
if (res.data.code in TOKEN_ERROR_CODE) { window.location.replace(
clearToken(); `/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`
return Promise.reject({ message: 'token过期重新登录' }); );
} return Promise.reject({ message: 'token过期重新登录' });
} }
return Promise.reject(err); return Promise.reject(err);
} }

View File

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

6
src/api/system.ts Normal file
View File

@@ -0,0 +1,6 @@
import { GET, POST, PUT } from './request';
import type { ChatModelItemType } from '@/constants/model';
export const getFilling = () => GET<{ beianText: string }>('/system/getFiling');
export const getSystemModelList = () => GET<ChatModelItemType[]>('/system/getModels');

View File

@@ -2,10 +2,9 @@ import { GET, POST, PUT } from './request';
import { createHashPassword, Obj2Query } from '@/utils/tools'; import { createHashPassword, Obj2Query } from '@/utils/tools';
import { ResLogin, PromotionRecordType } from './response/user'; import { ResLogin, PromotionRecordType } from './response/user';
import { UserAuthTypeEnum } from '@/constants/common'; 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 type { PagingData, RequestPaging } from '@/types';
import { BillSchema, PaySchema } from '@/types/mongoSchema'; import { PaySchema } from '@/types/mongoSchema';
import { adaptBill } from '@/utils/adapt';
export const sendAuthCode = ({ export const sendAuthCode = ({
username, username,
@@ -64,13 +63,12 @@ export const postLogin = ({ username, password }: { username: string; password:
password: createHashPassword(password) password: createHashPassword(password)
}); });
export const loginOut = () => GET('/user/loginout');
export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data); export const putUserInfo = (data: UserUpdateParams) => PUT('/user/update', data);
export const getUserBills = (data: RequestPaging) => export const getUserBills = (data: RequestPaging) =>
GET<PagingData<BillSchema>>(`/user/getBill?${Obj2Query(data)}`).then((res) => ({ GET<PagingData<UserBillType>>(`/user/getBill?${Obj2Query(data)}`);
...res,
data: res.data.map((bill) => adaptBill(bill))
}));
export const getPayOrders = () => GET<PaySchema[]>(`/user/getPayOrders`); 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, delete: require('./icons/delete.svg').default,
withdraw: require('./icons/withdraw.svg').default, withdraw: require('./icons/withdraw.svg').default,
stop: require('./icons/stop.svg').default, stop: require('./icons/stop.svg').default,
shareMarket: require('./icons/shareMarket.svg').default,
collectionLight: require('./icons/collectionLight.svg').default, collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default, collectionSolid: require('./icons/collectionSolid.svg').default,
chat: require('./icons/chat.svg').default, chat: require('./icons/chat.svg').default,
@@ -25,7 +24,12 @@ const map = {
tabbarMe: require('./icons/phoneTabbar/me.svg').default, tabbarMe: require('./icons/phoneTabbar/me.svg').default,
closeSolid: require('./icons/closeSolid.svg').default, closeSolid: require('./icons/closeSolid.svg').default,
wx: require('./icons/wx.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; export type IconName = keyof typeof map;

View File

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

View File

@@ -1,23 +1,34 @@
import React, { useCallback, useEffect } from 'react'; import React, { useEffect, useMemo } from 'react';
import { Box, useColorMode, Flex } from '@chakra-ui/react'; import { Box, useColorMode, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useLoading } from '@/hooks/useLoading';
import { useGlobalStore } from '@/store/global';
import { throttle } from 'lodash';
import Auth from './auth';
import Navbar from './navbar'; import Navbar from './navbar';
import NavbarPhone from './navbarPhone'; import NavbarPhone from './navbarPhone';
import { useRouter } from 'next/router';
import { useScreen } from '@/hooks/useScreen';
import { useLoading } from '@/hooks/useLoading';
import Auth from './auth';
import { useGlobalStore } from '@/store/global';
const pcUnShowLayoutRoute: Record<string, boolean> = { const pcUnShowLayoutRoute: Record<string, boolean> = {
'/login': true '/': true,
'/login': true,
'/chat/share': true
};
const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true,
'/chat/share': true
}; };
const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: boolean }) => { const Layout = ({ children }: { children: JSX.Element }) => {
const { isPc } = useScreen({ defaultIsPc: isPcDevice });
const router = useRouter(); const router = useRouter();
const { colorMode, setColorMode } = useColorMode(); const { colorMode, setColorMode } = useColorMode();
const { Loading } = useLoading({ defaultLoading: true }); const { Loading } = useLoading();
const { loading } = useGlobalStore(); const { loading, setScreenWidth, isPc } = useGlobalStore();
const isChatPage = useMemo(
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
[router.pathname, router.query]
);
useEffect(() => { useEffect(() => {
if (colorMode === 'dark' && router.pathname !== '/chat') { if (colorMode === 'dark' && router.pathname !== '/chat') {
@@ -25,60 +36,54 @@ const Layout = ({ children, isPcDevice }: { children: JSX.Element; isPcDevice: b
} }
}, [colorMode, router.pathname, setColorMode]); }, [colorMode, router.pathname, setColorMode]);
const RenderPc = useCallback( useEffect(() => {
() => const resize = throttle(() => {
pcUnShowLayoutRoute[router.pathname] ? ( setScreenWidth(document.documentElement.clientWidth);
<Auth>{children}</Auth> }, 300);
) : ( resize();
<>
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'60px'}>
<Navbar />
</Box>
<Box h={'100%'} ml={'60px'} overflow={'overlay'}>
<Auth>{children}</Auth>
</Box>
</>
),
[children, router.pathname]
);
const RenderPhone = useCallback(() => { window.addEventListener('resize', resize);
const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/login': true return () => {
window.removeEventListener('resize', resize);
}; };
}, [setScreenWidth]);
const isChatPage =
router.pathname === '/chat' && Object.values(router.query).join('').length !== 0;
if (phoneUnShowLayoutRoute[router.pathname] || isChatPage) {
return <Auth>{children}</Auth>;
}
return (
<Flex h={'100%'} flexDirection={'column'}>
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
<Auth>{children}</Auth>
</Box>
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
<NavbarPhone />
</Box>
</Flex>
);
}, [children, router]);
return ( return (
<> <>
<Box h={'100%'} overflow={'overlay'} bg={'gray.100'}> <Box
{isPc ? <RenderPc /> : <RenderPhone />} h={'100%'}
bgGradient={'linear(to-t,rgba(173, 206, 255, 0.05) 0%, rgba(173, 206, 255, 0.12) 100%)'}
>
{isPc ? (
pcUnShowLayoutRoute[router.pathname] ? (
<Auth>{children}</Auth>
) : (
<>
<Box h={'100%'} position={'fixed'} left={0} top={0} w={'60px'}>
<Navbar />
</Box>
<Box h={'100%'} ml={'60px'} overflow={'overlay'}>
<Auth>{children}</Auth>
</Box>
</>
)
) : phoneUnShowLayoutRoute[router.pathname] || isChatPage ? (
<Auth>{children}</Auth>
) : (
<Flex h={'100%'} flexDirection={'column'}>
<Box flex={'1 0 0'} h={0} overflow={'overlay'}>
<Auth>{children}</Auth>
</Box>
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
<NavbarPhone />
</Box>
</Flex>
)}
</Box> </Box>
{loading && <Loading />} <Loading loading={loading} />
</> </>
); );
}; };
export default Layout; 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 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 { useRouter } from 'next/router';
import MyIcon from '../Icon'; import MyIcon from '../Icon';
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import { useChatStore } from '@/store/chat'; import { useChatStore } from '@/store/chat';
import Avatar from '../Avatar';
export enum NavbarTypeEnum { export enum NavbarTypeEnum {
normal = 'normal', normal = 'normal',
@@ -16,22 +17,27 @@ const Navbar = () => {
const { lastChatModelId, lastChatId } = useChatStore(); const { lastChatModelId, lastChatId } = useChatStore();
const navbarList = useMemo( const navbarList = useMemo(
() => [ () => [
{
label: 'AI助手',
icon: 'model',
link: `/model?modelId=${lastModelId}`,
activeLink: ['/model']
},
{ {
label: '聊天', label: '聊天',
icon: 'chat', icon: 'chat',
link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`, link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`,
activeLink: ['/chat'] activeLink: ['/chat']
}, },
{ {
label: '共享', label: '我的应用',
icon: 'shareMarket', icon: 'model',
link: `/model?modelId=${lastModelId}`,
activeLink: ['/model']
},
{
label: '知识库',
icon: 'kb',
link: `/kb`,
activeLink: ['/kb']
},
{
label: '应用市场',
icon: 'appStore',
link: '/model/share', link: '/model/share',
activeLink: ['/model/share'] activeLink: ['/model/share']
}, },
@@ -77,13 +83,7 @@ const Navbar = () => {
cursor={'pointer'} cursor={'pointer'}
onClick={() => router.push('/number')} onClick={() => router.push('/number')}
> >
<Image <Avatar w={'36px'} h={'36px'} src={userInfo?.avatar} fallbackSrc={'/icon/human.png'} />
src={userInfo?.avatar || '/icon/human.png'}
objectFit={'contain'}
w={'36px'}
h={'36px'}
alt=""
/>
</Box> </Box>
{/* 导航列表 */} {/* 导航列表 */}
<Box flex={1}> <Box flex={1}>
@@ -102,9 +102,7 @@ const Navbar = () => {
justifyContent={'center'} justifyContent={'center'}
onClick={() => { onClick={() => {
if (item.link === router.asPath) return; if (item.link === router.asPath) return;
router.push(item.link, undefined, { router.push(item.link);
shallow: true
});
}} }}
cursor={'pointer'} cursor={'pointer'}
w={'60px'} w={'60px'}
@@ -127,6 +125,24 @@ const Navbar = () => {
</Tooltip> </Tooltip>
))} ))}
</Box> </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> </Flex>
); );
}; };

View File

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

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { Spinner, Flex } from '@chakra-ui/react';
const Loading = ({ fixed = true }: { fixed?: boolean }) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={1000}
backgroundColor={'rgba(255,255,255,0.5)'}
top={0}
left={0}
right={0}
bottom={0}
alignItems={'center'}
justifyContent={'center'}
>
<Spinner thickness="4px" speed="0.65s" emptyColor="gray.200" color="myBlue.500" size="xl" />
</Flex>
);
};
export default Loading;

View File

@@ -337,10 +337,13 @@
} }
.markdown { .markdown {
text-align: justify; text-align: justify;
overflow-y: hidden;
tab-size: 4; tab-size: 4;
word-spacing: normal; word-spacing: normal;
word-break: break-all; width: 100%;
* {
word-break: break-all;
}
p { p {
white-space: pre-line; white-space: pre-line;
@@ -353,13 +356,13 @@
margin: 0; margin: 0;
border: none; border: none;
border-radius: 0; border-radius: 0;
background-color: #222 !important; background-color: #292b33 !important;
overflow-x: auto; overflow-x: auto;
color: #fff; color: #fff;
} }
pre code { pre code {
background-color: #222 !important; background-color: #292b33 !important;
width: 100%; width: 100%;
} }

View File

@@ -1,8 +1,8 @@
import React, { memo } from 'react'; import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Box, Flex, useColorModeValue } from '@chakra-ui/react'; 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 Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
@@ -12,9 +12,21 @@ import 'katex/dist/katex.min.css';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { codeLight } from './codeLight'; 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 { copyData } = useCopyData();
const formatSource = useMemo(() => {
return formatLink ? formatLinkText(source) : source;
}, [source, formatLink]);
return ( return (
<ReactMarkdown <ReactMarkdown
className={`markdown ${styles.markdown} ${ className={`markdown ${styles.markdown} ${
@@ -29,7 +41,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
const code = String(children); const code = String(children);
return !inline || match ? ( return !inline || match ? (
<Box my={3} borderRadius={'md'} overflow={'hidden'} backgroundColor={'#222'}> <Box my={3} borderRadius={'md'} overflow={'overlay'} backgroundColor={'#222'}>
<Flex <Flex
className="code-header" className="code-header"
py={2} py={2}
@@ -63,7 +75,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
}} }}
linkTarget="_blank" linkTarget="_blank"
> >
{source} {formatSource}
</ReactMarkdown> </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'}> <ModalBody textAlign={'center'}>
<Image <Image
style={{ margin: 'auto' }} style={{ margin: 'auto' }}
src={'/imgs/wx300.jpg'} src={'https://otnvvf-imgs.oss.laf.run/wx300.jpg'}
width={'200px'} width={'200px'}
height={'200px'} height={'200px'}
alt="" alt=""

View File

@@ -1,5 +1,6 @@
export const SYSTEM_PROMPT_HEADER = 'System-Prompt-Header'; export const NEW_CHATID_HEADER = 'response-new-chat-id';
export const NEW_CHATID_HEADER = 'Chat-Id-Header'; export const QUOTE_LEN_HEADER = 'response-quote-len';
export const GUIDE_PROMPT_HEADER = 'response-guide-prompt';
export enum ChatRoleEnum { export enum ChatRoleEnum {
System = 'System', 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,3 +1,5 @@
import { getSystemModelList } from '@/api/system';
import type { ShareChatEditType } from '@/types/model';
import type { ModelSchema } from '@/types/mongoSchema'; import type { ModelSchema } from '@/types/mongoSchema';
export const embeddingModel = 'text-embedding-ada-002'; export const embeddingModel = 'text-embedding-ada-002';
@@ -28,40 +30,45 @@ export const ChatModelMap = {
chatModel: OpenAiChatEnum.GPT35, chatModel: OpenAiChatEnum.GPT35,
name: 'ChatGpt', name: 'ChatGpt',
contextMaxToken: 4096, contextMaxToken: 4096,
systemMaxToken: 2500, systemMaxToken: 2400,
maxTemperature: 1.5, maxTemperature: 1.2,
price: 3 price: 2.5
}, },
[OpenAiChatEnum.GPT4]: { [OpenAiChatEnum.GPT4]: {
chatModel: OpenAiChatEnum.GPT4, chatModel: OpenAiChatEnum.GPT4,
name: 'Gpt4', name: 'Gpt4',
contextMaxToken: 8000, contextMaxToken: 8000,
systemMaxToken: 3500, systemMaxToken: 3000,
maxTemperature: 1.5, maxTemperature: 1.2,
price: 30 price: 50
}, },
[OpenAiChatEnum.GPT432k]: { [OpenAiChatEnum.GPT432k]: {
chatModel: OpenAiChatEnum.GPT432k, chatModel: OpenAiChatEnum.GPT432k,
name: 'Gpt4-32k', name: 'Gpt4-32k',
contextMaxToken: 32000, contextMaxToken: 32000,
systemMaxToken: 6000, systemMaxToken: 3000,
maxTemperature: 1.5, maxTemperature: 1.2,
price: 30 price: 90
}, },
[ClaudeEnum.Claude]: { [ClaudeEnum.Claude]: {
chatModel: ClaudeEnum.Claude, chatModel: ClaudeEnum.Claude,
name: 'Claude(免费体验)', name: 'Claude(免费体验)',
contextMaxToken: 9000, contextMaxToken: 9000,
systemMaxToken: 2500, systemMaxToken: 2400,
maxTemperature: 1, maxTemperature: 1,
price: 0 price: 0
} }
}; };
export const chatModelList: ChatModelItemType[] = [ let chatModelList: ChatModelItemType[] = [];
ChatModelMap[OpenAiChatEnum.GPT35], export const getChatModelList = async () => {
ChatModelMap[ClaudeEnum.Claude] if (chatModelList.length > 0) {
]; return chatModelList;
}
const list = await getSystemModelList();
chatModelList = list;
return list;
};
export enum ModelStatusEnum { export enum ModelStatusEnum {
running = 'running', running = 'running',
@@ -115,15 +122,15 @@ export const ModelVectorSearchModeMap: Record<
> = { > = {
[ModelVectorSearchModeEnum.hightSimilarity]: { [ModelVectorSearchModeEnum.hightSimilarity]: {
text: '高相似度, 无匹配时拒绝回复', text: '高相似度, 无匹配时拒绝回复',
similarity: 0.2 similarity: 0.18
}, },
[ModelVectorSearchModeEnum.noContext]: { [ModelVectorSearchModeEnum.noContext]: {
text: '高相似度,无匹配时直接回复', text: '高相似度,无匹配时直接回复',
similarity: 0.2 similarity: 0.18
}, },
[ModelVectorSearchModeEnum.lowSimilarity]: { [ModelVectorSearchModeEnum.lowSimilarity]: {
text: '低相似度匹配', text: '低相似度匹配',
similarity: 0.8 similarity: 0.7
} }
}; };
@@ -135,7 +142,7 @@ export const defaultModel: ModelSchema = {
status: ModelStatusEnum.pending, status: ModelStatusEnum.pending,
updateTime: Date.now(), updateTime: Date.now(),
chat: { chat: {
useKb: false, relatedKbs: [],
searchMode: ModelVectorSearchModeEnum.hightSimilarity, searchMode: ModelVectorSearchModeEnum.hightSimilarity,
systemPrompt: '', systemPrompt: '',
temperature: 0, temperature: 0,
@@ -146,12 +153,11 @@ export const defaultModel: ModelSchema = {
isShareDetail: false, isShareDetail: false,
intro: '', intro: '',
collection: 0 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'; import { extendTheme, defineStyleConfig, ComponentStyleConfig } from '@chakra-ui/react';
// @ts-ignore // @ts-ignore
import { modalAnatomy, switchAnatomy, selectAnatomy } from '@chakra-ui/anatomy'; import { modalAnatomy, switchAnatomy, selectAnatomy, checkboxAnatomy } from '@chakra-ui/anatomy';
// @ts-ignore // @ts-ignore
import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system'; import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
@@ -11,6 +11,8 @@ const { definePartsStyle: switchPart, defineMultiStyleConfig: switchMultiStyle }
createMultiStyleConfigHelpers(switchAnatomy.keys); createMultiStyleConfigHelpers(switchAnatomy.keys);
const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle } = const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle } =
createMultiStyleConfigHelpers(selectAnatomy.keys); createMultiStyleConfigHelpers(selectAnatomy.keys);
const { definePartsStyle: checkboxPart, defineMultiStyleConfig: checkboxMultiStyle } =
createMultiStyleConfigHelpers(checkboxAnatomy.keys);
// modal 弹窗 // modal 弹窗
const ModalTheme = defineMultiStyleConfig({ const ModalTheme = defineMultiStyleConfig({
@@ -171,6 +173,9 @@ export const theme = extendTheme({
fontWeight: 400, fontWeight: 400,
height: '100%', height: '100%',
overflow: 'hidden' overflow: 'hidden'
},
a: {
color: 'myBlue.700'
} }
} }
}, },
@@ -221,8 +226,8 @@ export const theme = extendTheme({
sm: '900px', sm: '900px',
md: '1200px', md: '1200px',
lg: '1500px', lg: '1500px',
xl: '1800', xl: '1800px',
'2xl': '2100' '2xl': '2100px'
}, },
components: { components: {
Modal: ModalTheme, Modal: ModalTheme,

View File

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

View File

@@ -1,32 +1,12 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Spinner, Flex } from '@chakra-ui/react'; import LoadingComponent from '@/components/Loading';
export const useLoading = (props?: { defaultLoading: boolean }) => { export const useLoading = (props?: { defaultLoading: boolean }) => {
const [isLoading, setIsLoading] = useState(props?.defaultLoading || false); const [isLoading, setIsLoading] = useState(props?.defaultLoading || false);
const Loading = useCallback( const Loading = useCallback(
({ loading, fixed = true }: { loading?: boolean; fixed?: boolean }): JSX.Element | null => { ({ loading, fixed = true }: { loading?: boolean; fixed?: boolean }): JSX.Element | null => {
return isLoading || loading ? ( return isLoading || loading ? <LoadingComponent fixed={fixed} /> : null;
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={100}
backgroundColor={'rgba(255,255,255,0.5)'}
top={0}
left={0}
right={0}
bottom={0}
alignItems={'center'}
justifyContent={'center'}
>
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="gray.200"
color="myBlue.500"
size="xl"
/>
</Flex>
) : null;
}, },
[isLoading] [isLoading]
); );

View File

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

@@ -45,15 +45,17 @@ export default function App({ Component, pageProps }: AppProps) {
<Head> <Head>
<title>Fast GPT</title> <title>Fast GPT</title>
<meta name="description" content="Generated by Fast GPT" /> <meta name="description" content="Generated by Fast GPT" />
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no, viewport-fit=cover"
/> />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script> <Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Script src="/js/pdf.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/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
<Script src="/js/particles.js"></Script>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<ColorModeScript initialColorMode={theme.config.initialColorMode} /> <ColorModeScript initialColorMode={theme.config.initialColorMode} />

View File

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

View File

@@ -2,36 +2,34 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authChat } from '@/service/utils/auth'; import { authChat } from '@/service/utils/auth';
import { modelServiceToolMap } from '@/service/utils/chat'; import { modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat'; import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { PassThrough } from 'stream';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model'; import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill'; import { pushChatBill } from '@/service/events/pushBill';
import { resStreamResponse } from '@/service/utils/chat'; import { resStreamResponse } from '@/service/utils/chat';
import { searchKb } from '@/service/plugins/searchKb'; import { appKbSearch } from '../openapi/kb/appKbSearch';
import { ChatRoleEnum } from '@/constants/chat'; 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) { 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', () => { res.on('close', () => {
stream.destroy(); res.end();
}); });
res.on('error', () => { res.on('error', () => {
console.log('error: ', 'request error'); console.log('error: ', 'request error');
stream.destroy(); res.end();
}); });
try { try {
const { chatId, prompt, modelId } = req.body as { const { chatId, prompt, modelId } = req.body as {
prompt: ChatItemSimpleType; prompt: [ChatItemType, ChatItemType];
modelId: string; modelId: string;
chatId: '' | string; chatId?: string;
}; };
if (!modelId || !prompt) { if (!modelId || !prompt) {
@@ -51,82 +49,143 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const modelConstantsData = ChatModelMap[model.chat.chatModel]; 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
});
// 使用了知识库搜索 return {
if (model.chat.useKb) { code,
const { code, searchPrompt } = await searchKb({ quote: rawSearch,
userOpenAiKey, systemPrompts: searchPrompts,
prompts, guidePrompt
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity, };
model, }
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 userId
}); });
return res.end(response);
// search result is empty
if (code === 201) {
return res.send(searchPrompt?.value);
}
searchPrompt && prompts.unshift(searchPrompt);
} else {
// 没有用知识库搜索,仅用系统提示词
model.chat.systemPrompt &&
prompts.unshift({
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
});
} }
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( const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2 2
); );
// 发出请求 // 发出 chat 请求
const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({ const { streamResponse } = await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey: userOpenAiKey || systemAuthKey, apiKey: userOpenAiKey || systemAuthKey,
temperature: +temperature, temperature: +temperature,
messages: prompts, messages: prompts,
stream: true, stream: true,
res, res,
chatId chatId: conversationId
}); });
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`); console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
step = 1; if (res.closed) return res.end();
const { totalTokens, finishMessages } = await resStreamResponse({ try {
model: model.chat.chatModel, const { totalTokens, finishMessages, responseContent } = await resStreamResponse({
res, model: model.chat.chatModel,
stream, res,
chatResponse: streamResponse, chatResponse: streamResponse,
prompts, prompts
systemPrompt:
showModelDetail && prompts[0].obj === ChatRoleEnum.System ? prompts[0].value : ''
});
// 只有使用平台的 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
}); });
// 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 type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
// 凭证校验 // 凭证校验
const userId = await authToken(req); const { userId } = await authUser({ req, authToken: true });
const chatRecord = await Chat.findById(chatId); const chatRecord = await Chat.findById(chatId);

View File

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

View File

@@ -3,62 +3,38 @@ import { jsonRes } from '@/service/response';
import { ChatItemType } from '@/types/chat'; import { ChatItemType } from '@/types/chat';
import { connectToDatabase, Chat } from '@/service/mongo'; import { connectToDatabase, Chat } from '@/service/mongo';
import { authModel } from '@/service/utils/auth'; import { authModel } from '@/service/utils/auth';
import { authToken } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
type Props = {
newChatId?: string;
chatId?: string;
modelId: string;
prompts: [ChatItemType, ChatItemType];
};
/* 聊天内容存存储 */ /* 聊天内容存存储 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const { chatId, modelId, prompts, newChatId } = req.body as { const { chatId, modelId, prompts, newChatId } = req.body as Props;
newChatId: '' | string;
chatId: '' | string;
modelId: string;
prompts: ChatItemType[];
};
if (!prompts) { if (!prompts) {
throw new Error('缺少参数'); 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) => ({ jsonRes(res, {
_id: new mongoose.Types.ObjectId(item._id), data: nId
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[content.length - 1].value
});
return jsonRes(res, {
data: _id
});
} else {
// 已经有记录,追加入库
await Chat.findByIdAndUpdate(chatId, {
$push: {
content: {
$each: content
}
},
updateTime: new Date(),
latestChat: content[content.length - 1].value
});
}
jsonRes(res);
} catch (err) { } catch (err) {
jsonRes(res, { jsonRes(res, {
code: 500, code: 500,
@@ -66,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 type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authToken } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import { ModelStatusEnum } from '@/constants/model'; import { ModelStatusEnum } from '@/constants/model';
import { Model } from '@/service/models/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(); 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,33 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, SplitData, Model } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { modelId } = req.query as { modelId: string };
if (!modelId) {
throw new Error('参数错误');
}
await connectToDatabase();
const userId = await authToken(req);
// 找到长度大于0的数据
const data = await SplitData.find({
userId,
modelId,
textList: { $exists: true, $not: { $size: 0 } }
});
jsonRes(res, {
data: data.map((item) => item.textList).flat().length
});
} 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,75 +0,0 @@
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 { generateVector } from '@/service/events/generateVector';
import { generateQA } from '@/service/events/generateQA';
import { PgClient } from '@/service/pg';
/* 拆分数据成QA */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chunks, modelId, prompt, mode } = req.body as {
modelId: string;
chunks: string[];
prompt: string;
mode: 'qa' | 'subsection';
};
if (!chunks || !modelId || !prompt) {
throw new Error('参数错误');
}
await connectToDatabase();
const userId = await authToken(req);
// 验证是否是该用户的 model
const model = await Model.findOne({
_id: modelId,
userId
});
if (!model) {
throw new Error('无权操作该模型');
}
if (mode === 'qa') {
// 批量QA拆分插入数据
await SplitData.create({
userId,
modelId,
textList: chunks,
prompt
});
generateQA();
} else if (mode === 'subsection') {
// 插入记录
await PgClient.insert('modelData', {
values: chunks.map((item) => [
{ key: 'user_id', value: userId },
{ key: 'model_id', value: modelId },
{ key: 'q', value: item },
{ key: 'a', value: '' },
{ key: 'status', value: 'waiting' }
])
});
generateVector();
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb'
}
}
};

View File

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

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