Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02caa57304 | ||
|
|
6014a56e54 | ||
|
|
b8f08eb33e | ||
|
|
944e876aaa | ||
|
|
ee2c259c3d | ||
|
|
1c8db69a5a | ||
|
|
5128bbcce4 | ||
|
|
51a5d450b7 | ||
|
|
98444fd04b | ||
|
|
e45c1eb1e0 | ||
|
|
bd9d83e630 | ||
|
|
b66952ad98 | ||
|
|
242b21263a | ||
|
|
2843178ede | ||
|
|
bb312441c6 | ||
|
|
d07e5b8501 | ||
|
|
246ee973ec | ||
|
|
a62a9c4067 | ||
|
|
7408db9cf6 | ||
|
|
5d4dd4a18c | ||
|
|
5bf95bd846 | ||
|
|
a79429fdcd | ||
|
|
021add2af4 | ||
|
|
371e0e36c6 | ||
|
|
e7d3a8e2e1 | ||
|
|
32a8d68c6c | ||
|
|
06ab718e6e | ||
|
|
1d74095739 | ||
|
|
ca99837dab | ||
|
|
d31bdf0ee0 | ||
|
|
d3e7923040 | ||
|
|
5f66f4523c | ||
|
|
4ec02c654b | ||
|
|
9a0c92629b | ||
|
|
651eb1bf6b | ||
|
|
4e0c876154 | ||
|
|
3e5118c4f7 | ||
|
|
bdd518fd3e | ||
|
|
340de071a9 | ||
|
|
1226c3efb7 | ||
|
|
39f9080eb2 | ||
|
|
3e4b165ed9 | ||
|
|
451f234f68 | ||
|
|
1ab45651e0 | ||
|
|
16f2ad7615 | ||
|
|
4ba4a99935 | ||
|
|
6f4471d2a0 | ||
|
|
250399a1aa | ||
|
|
1c8ce369b6 | ||
|
|
875f78b42c | ||
|
|
1e262a2198 | ||
|
|
25067a14a6 | ||
|
|
591cc21ff4 | ||
|
|
5a21eb9bc1 | ||
|
|
cdf4b9f324 | ||
|
|
e3c9b8179e | ||
|
|
9b683884cc | ||
|
|
4dc541e0a6 | ||
|
|
ef2de489be | ||
|
|
cb3b9efc6e | ||
|
|
a745993829 | ||
|
|
a837552b56 | ||
|
|
f52f514f5f | ||
|
|
fac53923dd | ||
|
|
b200731d17 | ||
|
|
de6ac0f589 | ||
|
|
18e0212d27 | ||
|
|
f20a5fe9a6 | ||
|
|
d351084688 | ||
|
|
d807f9d097 | ||
|
|
3ef6d3fe63 |
@@ -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
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
@@ -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 \
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
6
Makefile
@@ -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
@@ -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/) (海外版)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 知识库原理图
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 开发
|
## 👨💻 开发
|
||||||
|
|
||||||
**配置环境变量**
|
项目技术栈: NextJs + TS + ChakraUI + Mongo + Postgres(Vector 插件)
|
||||||
复制.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) - 需要一台国外服务器
|

|
||||||
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
|
[](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)
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
sudo chmod +x /usr/local/bin/docker-compose
|
|
||||||
84
docs/deploy/docker.md
Normal 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)
|
||||||
@@ -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 ./ &
|
||||||
87
docs/deploy/fastgpt/docker-compose.yml
Normal 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
|
||||||
2
docs/deploy/fastgpt/docker-compose/init.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
cp ./docker-compose /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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、需要创建一个放置pg和mongo数据的文件夹,这里创建在`~/fastgpt`目录中,里面有`pg` 和`mongo `两个文件夹
|
6、需要创建一个放置 pg 和 mongo 数据的文件夹,这里创建在`~/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 看项目是否跑起来了**
|
|
||||||
BIN
docs/deploy/proxy/imgs/sealos1.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
docs/deploy/proxy/imgs/sealos2.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
docs/deploy/proxy/imgs/sealos3.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
docs/deploy/proxy/imgs/sealos4.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/deploy/proxy/imgs/sealos5.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
@@ -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
@@ -0,0 +1,100 @@
|
|||||||
|
# sealos 部署 openai 中转
|
||||||
|
|
||||||
|
## 登录 sealos cloud
|
||||||
|
|
||||||
|
[sealos cloud](https://cloud.sealos.io/)
|
||||||
|
|
||||||
|
## 创建应用
|
||||||
|
|
||||||
|
打开 App Launchpad -> 新建应用
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
### 开启外网访问
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 添加 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. 点击确认
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 部署应用
|
||||||
|
|
||||||
|
填写完毕后,点击右上角的 `部署应用`,即可完成。
|
||||||
|
|
||||||
|
## 修改 FastGpt 环境变量
|
||||||
|
|
||||||
|
1. 进入刚刚部署应用的详情,复制外网地址
|
||||||
|

|
||||||
|
|
||||||
|
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
@@ -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
|
After Width: | Height: | Size: 456 KiB |
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||

|
| 交流群 | 小助手 |
|
||||||
|
| ----------------------- | -------------------- |
|
||||||
|
|  |  |
|
||||||
|
|||||||
@@ -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 为内核的云操作系统发行版,可以…… |
|
||||||
|
|||||||
@@ -1,43 +1,34 @@
|
|||||||
## 欢迎使用 Fast GPT
|
## 欢迎使用 Fast GPT
|
||||||
|
|
||||||
[Git 仓库](https://github.com/c121914yu/FastGPT)
|
### 项目开源
|
||||||
|
|
||||||
### 交流群/问题反馈
|
FastGpt 项目完全开源,可随意私有化部署,去除平台风险忧虑。项目地址:[Git 仓库](https://github.com/c121914yu/FastGPT)
|
||||||
|
|
||||||
扫码满了,加个小号,定时拉
|
### 开始使用知识库
|
||||||
wx 号: fastgpt123
|
|
||||||

|
|
||||||
|
|
||||||
### 快速开始
|
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. 创建模型时选择【知识库】
|
|  |  |
|
||||||
2. 进入模型编辑页
|
|
||||||
3. 导入数据,可以选择手动导入,或者选择文件导入。文件导入会自动调用 chatGPT 理解文件内容,并生成知识库。
|
|
||||||
4. 使用该模型对话。
|
|
||||||
|
|
||||||
注意:使用知识库模型对话时,tokens 消耗会加快。
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
### Fast GPT V3.4
|
### Fast GPT V3.8
|
||||||
|
|
||||||
- 新增 - 全新的交互 UI
|
- 新增 - 知识库引用反馈。
|
||||||
- 新增 - 用户自定义头像
|
- 新增 - 知识库与 AI 助手对多对关系,一个知识库可以被多个 AI 助手关联,一个 AI 助手可以关联多个知识库。
|
||||||
- 优化 - 知识库搜索,会将上一个问题并入搜索范围。
|
|
||||||
- 优化 - 模型结构设计,不再区分知识库和对话模型,而是通过开关的形式,手动选择手否需要进行知识库搜索。
|
|
||||||
- 新增 - 模型共享市场,可以使用其他用户分享的模型。
|
|
||||||
- 新增 - 邀请好友注册功能。
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 50 KiB |
BIN
public/imgs/wxqun300.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
9
public/js/particles.js
Normal 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)}`);
|
||||||
|
|||||||
@@ -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 || '请求异常');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/api/response/chat.d.ts
vendored
@@ -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
@@ -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');
|
||||||
@@ -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`);
|
||||||
|
|
||||||
|
|||||||
19
src/components/Avatar/index.tsx
Normal 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;
|
||||||
1
src/components/Icon/icons/appStore.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
src/components/Icon/icons/edit.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
src/components/Icon/icons/git.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
src/components/Icon/icons/kb.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
src/components/Icon/icons/menu.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
22
src/components/Loading/index.tsx
Normal 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;
|
||||||
@@ -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%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
67
src/components/SideBar/index.tsx
Normal 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;
|
||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
};
|
||||||
@@ -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
@@ -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'
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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]: '提现'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
49
src/pages/api/chat/getHistoryQuote.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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', []] } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
137
src/pages/api/chat/shareChat/chat.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/pages/api/chat/shareChat/create.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/pages/api/chat/shareChat/delete.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/pages/api/chat/shareChat/init.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/pages/api/chat/shareChat/list.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/pages/api/chat/updateHistoryQuote.ts
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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,
|
||||||
|
|||||||