Compare commits

..

2 Commits
v4.4 ... v4.0

Author SHA1 Message Date
archer
5fed561471 fix: i18n next 2023-08-09 11:05:41 +08:00
archer
07dfce9b28 chat model config 2023-08-09 10:52:13 +08:00
564 changed files with 17727 additions and 38014 deletions

View File

@@ -1,30 +0,0 @@
---
name: 问题反馈
about: 详细清晰的描述你遇到的问题
title: ''
labels: bug
assignees: ''
---
**例行检查**
[//]: # '方框内填 x 表示打钩'
- [ ] 我已确认目前没有类似 issue
- [ ] 我已完整查看过项目 README以及[项目文档](https://doc.fastgpt.run/docs/intro/)
- [ ] 我使用了自己的 key并确认我的 key 是可正常使用的
- [ ] 我理解并愿意跟进此 issue协助测试和提供反馈
- [x] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
**你的版本**
- [ ] 公有云版本
- [ ] 私有部署版本
**问题描述**
**复现步骤**
**预期结果**
**相关截图**

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 微信交流群
url: https://doc.fastgpt.run/wechat-fastgpt.webp
about: FastGPT 全是问题群

View File

@@ -1,23 +0,0 @@
---
name: 功能请求
about: 详细描述你期望的功能
title: ''
labels: enhancement
assignees: ''
---
**例行检查**
[//]: # '方框内填 x 表示打钩'
- [ ] 我已确认目前没有类似 features
- [ ] 我已确认我已升级到最新版本
- [ ] 我已完整查看过项目 README已确定现有版本无法满足需求
- [ ] 我理解并愿意跟进此 features协助测试和提供反馈
- [x] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 features 可能会被无视或直接关闭**
**功能描述**
**应用场景**
**相关示例**

30
.github/gh-bot.yml vendored
View File

@@ -1,30 +0,0 @@
version: v1
debug: true
action:
printConfig: false
release:
retry: 15s
actionName: Release
allowOps:
- cuisongliu
bot:
prefix: /
spe: _
allowOps:
- sealos-ci-robot
- sealos-release-robot
email: sealos-ci-robot@sealos.io
username: sealos-ci-robot
repo:
org: false
message:
success: |
🤖 says: Hooray! The action {{.Body}} has been completed successfully. 🎉
format_error: |
🤖 says: ‼️ There is a formatting issue with the action, kindly verify the action's format.
permission_error: |
🤖 says: ‼️ The action doesn't have permission to trigger.
release_error: |
🤖 says: ‼️ Release action failed.
Error details: {{.Error}}

14
.github/imgs/logo.svg vendored
View File

@@ -1,14 +0,0 @@
<svg width="32" height="32" viewBox="0 0 1041 1348" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M340.837 0.33933L681.068 0.338989V0.455643C684.032 0.378397 686.999 0.339702 689.967 0.339702C735.961 0.3397 781.504 9.62899 823.997 27.6772C866.49 45.7254 905.099 72.1791 937.622 105.528C970.144 138.877 995.942 178.467 1013.54 222.04C1031.14 265.612 1040.2 312.312 1040.2 359.474L340.836 359.474L340.836 1347.84C296.157 1347.84 251.914 1338.55 210.636 1320.49C169.357 1302.43 131.85 1275.95 100.257 1242.58C68.6636 1209.21 43.6023 1169.59 26.5041 1125.99C11.3834 1087.43 2.75216 1046.42 0.957956 1004.81H0.605869L0.605897 368.098H0.70363C0.105752 341.831 2.23741 315.443 7.14306 289.411C20.2709 219.745 52.6748 155.754 100.257 105.528C147.839 55.3017 208.462 21.0975 274.461 7.24017C296.426 2.62833 318.657 0.339101 340.837 0.33933Z" fill="url(#paint0_linear_1172_228)"/>
<path d="M633.639 904.645H513.029V576.37H635.422V576.377C678.161 576.607 720.454 585.093 759.951 601.37C799.997 617.874 836.384 642.064 867.033 672.559C897.683 703.054 921.996 739.257 938.583 779.101C955.171 818.944 963.709 861.648 963.709 904.775H633.639V904.645Z" fill="url(#paint1_linear_1172_228)"/>
<defs>
<linearGradient id="paint0_linear_1172_228" x1="520.404" y1="0.338989" x2="520.404" y2="1347.84" gradientUnits="userSpaceOnUse">
<stop stop-color="#326DFF"/>
<stop offset="1" stop-color="#8EAEFF"/>
</linearGradient>
<linearGradient id="paint1_linear_1172_228" x1="738.369" y1="576.37" x2="738.369" y2="904.775" gradientUnits="userSpaceOnUse">
<stop stop-color="#326DFF"/>
<stop offset="1" stop-color="#8EAEFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,72 +0,0 @@
name: deploy-docs
on:
workflow_dispatch:
push:
paths:
- 'docSite/**'
branches:
- 'main'
tags:
- 'v*.*.*'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains jobs "deploy-production"
deploy-production:
# The environment this job references
environment:
name: Production
url: ${{ steps.vercel-action.outputs.preview-url }}
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Job outputs
outputs:
docs: ${{ steps.filter.outputs.docs }}
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Step 1 - Checks-out your repository under $GITHUB_WORKSPACE
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive # Fetch submodules
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
# Step 2 Detect changes to Docs Content
- name: Detect changes in doc content
uses: dorny/paths-filter@v2
id: filter
with:
filters: |
docs:
- 'docSite/content/docs/**'
base: main
# Step 3 - Install Hugo (specific version)
- name: Install Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.117.0'
extended: true
# Step 4 - Builds the site using Hugo
- name: Build
run: cd docSite && hugo mod get -u github.com/colinwilson/lotusdocs && hugo -v --minify
env:
HUGO_BASEURL: ${{ vars.BASE_URL }}
# Step 5 - Push our generated site to Vercel
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
id: vercel-action
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} #Required
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} #Required
github-comment: false
vercel-args: '--prod --local-config ../vercel.json' # Optional
working-directory: docSite/public

View File

@@ -1,92 +0,0 @@
name: deploy-docs-preview
on:
pull_request_target:
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains jobs "deploy-production"
deploy-preview:
# The environment this job references
environment:
name: Preview
url: ${{ steps.vercel-action.outputs.preview-url }}
# The type of runner that the job will run on
runs-on: ubuntu-22.04
# Job outputs
outputs:
url: ${{ steps.vercel-action.outputs.preview-url }}
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Step 1 - Checks-out your repository under $GITHUB_WORKSPACE
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
submodules: recursive # Fetch submodules
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
# Step 2 Detect changes to Docs Content
- name: Detect changes in doc content
uses: dorny/paths-filter@v2
id: filter
with:
filters: |
docs:
- 'docSite/content/docs/**'
base: main
# Step 3 - Install Hugo (specific version)
- name: Install Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.117.0'
extended: true
# Step 4 - Builds the site using Hugo
- name: Build
run: cd docSite && hugo mod get -u github.com/colinwilson/lotusdocs && hugo -v --minify
env:
HUGO_BASEURL: ${{ vars.BASE_URL }}
# Step 5 - Push our generated site to Vercel
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
id: vercel-action
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} #Required
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} #Required
github-comment: false
vercel-args: '--local-config ../vercel.json' # Optional
working-directory: docSite/public
alias-domains: | #Optional
fastgpt-staging.vercel.app
docsOutput:
needs: [ deploy-preview ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Write md
run: |
echo "# 🤖 Generated by deploy action" > report.md
echo "[👀 Visit Preview](${{ needs.deploy-preview.outputs.url }})" >> report.md
cat report.md
- name: Gh Rebot for Sealos
uses: labring/gh-rebot@v0.0.6
if: ${{ (github.event_name == 'pull_request_target') }}
with:
version: v0.0.6
env:
GH_TOKEN: "${{ secrets.GH_PAT }}"
SEALOS_TYPE: "pr_comment"
SEALOS_FILENAME: "report.md"
SEALOS_REPLACE_TAG: "DEFAULT_REPLACE_DEPLOY"

View File

@@ -77,28 +77,3 @@ jobs:
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
push-to-ali-hub:
needs: build-fastgpt-images
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Ali Hub
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_HUB_USERNAME }}
password: ${{ secrets.ALI_HUB_PASSWORD }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.ALI_IMAGE_NAME }}:${{env.IMAGE_TAG}}

6
.gitignore vendored
View File

@@ -29,8 +29,4 @@ next-env.d.ts
platform.json
testApi/
local/
dist/
# hugo
**/.hugo_build.lock
docSite/public/
dist/

View File

@@ -1,5 +1,4 @@
dist
.vscode
**/.DS_Store
node_modules
docSite/
node_modules

210
LICENSE
View File

@@ -1,35 +1,201 @@
# FastGPT Open Source License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
The FastGPT is licensed under the Apache License 2.0, with the following additional conditions:
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. FastGPT is permitted to be used for commercialization. You can use FastGPT as a "backend-as-a-service" for your other applications, or delivering it to enterprises as an application development platform. However, when the following conditions are met, you must contact the producer to obtain a commercial license:
1. Definitions.
a. Multi-tenant SaaS service: Unless explicitly authorized by FastGPT in writing, you may not use the FastGPT.AI source code to operate a multi-tenant SaaS service that is similar to the FastGPT.
b. LOGO and copyright information: In the process of using FastGPT, you may not remove or moFastGPT the LOGO or copyright information in the FastGPT console.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
Please contact yujinlong@sealos.io by email to inquire about licensing matters.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
2. As a contributor, you should agree that your contributed code:
"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.
a. The producer can adjust the open-source agreement to be more strict or relaxed.
b. Can be used for commercial purposes, such as FastGPT's cloud business.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
Apart from this, all other rights and restrictions follow the Apache License 2.0. If you need more detailed information, you can refer to the full version of Apache License 2.0.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
The interactive design of this product is protected by appearance patent.
"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.
© 2023 Sealos.
"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.
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
"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."
http://www.apache.org/licenses/LICENSE-2.0
"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.
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.
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.

102
README.md
View File

@@ -1,119 +1,51 @@
<div align="center">
<a href="https://fastgpt.run/"><img src="/.github/imgs/logo.svg" width="120" height="120" alt="fastgpt logo"></a>
# FastGPT
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!
</div>
<p align="center">
<a href="https://fastgpt.run/">线上体验</a>
·
<a href="https://doc.fastgpt.run/docs/intro">相关文档</a>
·
<a href="https://doc.fastgpt.run/docs/development">本地开发</a>
·
<a href="https://github.com/labring/FastGPT#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">相关项目</a>
</p>
## 🛸 在线体验
[fastgpt.run](https://fastgpt.run/)(服务器在新加坡,部分地区可能无法直连)
🎉 [fastgpt.run](https://fastgpt.run/)(服务器在新加坡,部分地区可能无法直连)
| | |
| ---------------------------------- | ---------------------------------- |
| ![Demo](./.github/imgs/intro1.png) | ![Demo](./.github/imgs/intro2.png) |
| ![Demo](./.github/imgs/intro3.png) | ![Demo](./.github/imgs/intro4.png) |
## 💡 功能
1. 强大的可视化编排,轻松构建 AI 应用
- [x] 提供简易模式,无需操作编排
- [x] 用户对话前引导, 全局字符串变量
- [x] 知识库搜索
- [x] 多 LLM 模型对话
- [x] 文本内容提取成结构化数据
- [x] HTTP 扩展
- [ ] 嵌入 Laf实现在线编写 HTTP 模块
- [ ] 连续对话引导
- [ ] 对话多路线选择
- [x] 源文件引用追踪
- [ ] 自定义文件阅读器
2. 丰富的知识库预处理
- [x] 多库复用,混用
- [x] chunk 记录修改和删除
- [x] 支持 手动输入, 直接分段, QA 拆分导入
- [x] 支持 url 读取、 CSV 批量导入
- [x] 支持知识库单独设置向量模型
- [x] 源文件存储
- [ ] 文件学习 Agent
3. 多种效果测试渠道
- [x] 知识库单点搜索测试
- [x] 对话时反馈引用并可修改与删除
- [x] 完整上下文呈现
- [ ] 完整模块中间值呈现
4. OpenAPI
- [x] completions 接口(对齐 GPT 接口)
- [ ] 知识库 CRUD
5. 运营功能
- [x] 免登录分享窗口
- [x] Iframe 一键嵌入
- [x] 统一查阅对话记录,并对数据进行标注
## 👨‍💻 开发
项目技术栈: NextJs + TS + ChakraUI + Mongo + PostgresVector 插件)
- **⚡ 快速部署**
- [快开始本地开发](https://doc.fastgpt.run/docs/develop/dev)
- [部署 FastGPT](https://doc.fastgpt.run/docs/category/deploy)
> Sealos 的服务器在国外,不需要额外处理网络问题,无需服务器、无需魔法、无需域名,支持高并发 & 动态伸缩。点击以下按钮即可一键部署 👇
## :point_right: RoadMap
[![](https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
由于需要部署数据库,部署完后需要等待 2~4 分钟才能正常访问。默认用了最低配置,首次访问时会有些慢。
* [快开始本地开发](https://doc.fastgpt.run/docs/development/intro/)
* [部署 FastGPT](https://doc.fastgpt.run/docs/installation)
* [系统配置文件说明](https://doc.fastgpt.run/docs/development/configuration/)
* [多模型配置](https://doc.fastgpt.run/docs/installation/one-api/)
* [版本升级](https://doc.fastgpt.run/docs/installation/upgrading)
* [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
- [FastGpt RoadMap](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte)
## 🏘️ 社区交流群
添加 wx 小助手加入:
| 交流群 | 小助手 |
| ------------------------------------------------- | ---------------------------------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) |
![](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
## Powered by
- [TuShan: 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
- [Laf: 3 分钟快速接入三方应用](https://github.com/labring/laf)
- [Sealos: 快速部署集群应用](https://github.com/labring/sealos)
- [One API: 令牌管理 & 二次分发,支持 Azure](https://github.com/songquanpeng/one-api)
## 👀 其他
- [FastGPT 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [FastGpt 常见问题](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [docker 部署教程视频](https://www.bilibili.com/video/BV1jo4y147fT/)
- [公众号接入视频教程](https://www.bilibili.com/video/BV1xh4y1t7fy/)
- [FastGPT 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
- [FastGpt 知识库演示](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
## 💪 相关项目
- [Laf: 3 分钟快速接入三方应用](https://github.com/labring/laf)
- [Sealos: 快速部署集群应用](https://github.com/labring/sealos)
- [One API: 多模型管理,支持 Azure、文心一言等](https://github.com/songquanpeng/one-api)
- [TuShan: 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
## 🤝 第三方生态
## 第三方生态
- [luolinAI: 企微机器人,开箱即用](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
## 🌟 Star History
[![Star History Chart](https://api.star-history.com/svg?repos=labring/FastGPT&type=Date)](https://star-history.com/#labring/FastGPT&Date)
## 使用协议
本仓库遵循 [FastGPT Open Source License](./LICENSE) 开源协议。
1. 允许作为后台服务直接商用,但不允许直接使用 saas 服务商用。
2. 需保留相关版权信息。
3. 完整请查看 [FastGPT Open Source License](./LICENSE)
4. 联系方式yujinlong@sealos.io, [点击查看定价策略](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)

View File

@@ -1,115 +0,0 @@
<div align="center">
<a href="https://fastgpt.run/"><img src="/.github/imgs/logo.svg" width="120" height="120" alt="fastgpt logo"></a>
# FastGPT
FastGPT is a knowledge-based question answering system built on the LLM. It offers out-of-the-box data processing and model invocation capabilities. Moreover, it allows for workflow orchestration through Flow visualization, thereby enabling complex question and answer scenarios!
</div>
<p align="center">
<a href="https://fastgpt.run/">Online</a>
·
<a href="https://doc.fastgpt.run/docs/intro">Document</a>
·
<a href="https://doc.fastgpt.run/docs/development">Development</a>
·
<a href="https://doc.fastgpt.run/docs/installation">Deploy</a>
·
<a href="#powered-by">Power By</a>
</p>
## 🛸 Online
[fastgpt.run](https://fastgpt.run/)
| | |
| ---------------------------------- | ---------------------------------- |
| ![Demo](./.github/imgs/intro1.png) | ![Demo](./.github/imgs/intro2.png) |
| ![Demo](./.github/imgs/intro3.png) | ![Demo](./.github/imgs/intro4.png) |
## 💡 Features
1. Powerful visual orchestration for easy AI application building
- [x] Provides a simple mode without the need for orchestration operations
- [x] User dialogue pre-guidance
- [x] Global variables
- [x] Knowledge base search
- [x] Multi-LLM model dialogue
- [x] Extraction of text content into structured data
- [x] HTTP extension
- [ ] Sandbox JS runtime module
- [ ] Continuous dialogue guidance
- [ ] Dialogue multi-path selection
- [ ] Source file reference tracking
2. Rich knowledge base preprocessing
- [x] Multiple library reuse and mixing
- [x] Chunk record modification and deletion
- [x] Supports direct segment import
- [x] Supports QA split import
- [x] Supports manual input content
- [ ] Supports URL import reading
- [x] Supports batch import of Q&A pairs in CSV format
- [ ] Supports separate vector model settings for knowledge bases
- [ ] Source file storage
3. Multiple effect testing channels
- [x] Knowledge base single point search testing
- [x] Feedback references and ability to modify and delete during dialogue
- [x] Complete context presentation
- [ ] Complete module intermediate value presentation
4. OpenAPI
- [x] completions interface (aligned with GPT interface)
- [ ] Knowledge base CRUD
5. Operational functions
- [x] Login-free sharing window
- [x] One-click embedding with Iframe
- [ ] Unified access to dialogue records
## 👨‍💻 Development
Project tech stack: NextJs + TS + ChakraUI + Mongo + Postgres (Vector plugin)
- [Getting Started with Local Development](https://doc.fastgpt.run/docs/development)
- [Deploying FastGPT](https://doc.fastgpt.run/docs/installation)
- [System Configuration File Explanation](https://doc.fastgpt.run/docs/installation/reference)
- [Multi-model Configuration](https://doc.fastgpt.run/docs/installation/reference/models)
- [V3 Upgrade V4 Initialization](https://doc.fastgpt.run/docs/installation/upgrading)
<!-- ## :point_right: RoadMap
- [FastGPT RoadMap](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte) -->
<!-- ## 🏘️ Community
| Community Group | Assistant |
| ------------------------------------------------- | ---------------------------------------------- |
| ![](https://otnvvf-imgs.oss.laf.run/wxqun300.jpg) | ![](https://otnvvf-imgs.oss.laf.run/wx300.jpg) | -->
## 👀 Others
- [FastGPT FAQ](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [Docker Deployment Tutorial Video](https://www.bilibili.com/video/BV1jo4y147fT/)
- [Official Account Integration Video Tutorial](https://www.bilibili.com/video/BV1xh4y1t7fy/)
- [FastGPT Knowledge Base Demo](https://www.bilibili.com/video/BV1Wo4y1p7i1/)
## 💪 Related Projects
- [Laf: 3-minute quick access to third-party applications](https://github.com/labring/laf)
- [Sealos: Rapid deployment of cluster applications](https://github.com/labring/sealos)
- [One API: Multi-model management, supports Azure, Wenxin Yiyuan, etc.](https://github.com/songquanpeng/one-api)
- [TuShan: Build a backend management system in 5 minutes](https://github.com/msgbyte/tushan)
## 🤝 Third-party Ecosystem
- [luolinAI: Enterprise WeChat bot, ready to use](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
## 🌟 Star History
[![Star History Chart](https://api.star-history.com/svg?repos=labring/FastGPT&type=Date)](https://star-history.com/#labring/FastGPT&Date)

View File

@@ -1,22 +1,26 @@
# 默认用户密码,用户名为 root每次重启时会自动更新。
DEFAULT_ROOT_PSW=123456
# 代理
# AXIOS_PROXY_HOST=127.0.0.1
# AXIOS_PROXY_PORT=7890
# 数据库最大连接数
DB_MAX_LINK=5
# token
TOKEN_KEY=dfdasfdas
# 文件阅读时的秘钥
FILE_TOKEN_KEY=filetokenkey
# root key, 最高权限
ROOT_KEY=fdafasd
# openai 基本地址,可用作中转。
OPENAI_BASE_URL=https://api.openai.com/v1
# oneapi 地址,可以使用 oneapi 来实现多模型接入
# ONEAPI_URL=https://xxxx.cloud.sealos.io/openai/v1
ONEAPI_URL=https://xxxx.cloud.sealos.io/openai/v1
# 通用key。可以是 openai 的也可以是 oneapi 的。
# 此处逻辑:优先走 ONEAPI_URL如果填写了 ONEAPI_URLkey 也需要是 ONEAPI 的 key
CHAT_API_KEY=sk-xxxx
# db
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin
PG_URL=postgresql://username:password@host:port/postgres
# 首页路径
HOME_URL=/
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_USER=root
PG_PASSWORD=psw
PG_DB_NAME=dbname

View File

@@ -4,61 +4,60 @@
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_contact": true,
"show_git": true,
"show_doc": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"exportLimitMinutes": 0,
"scripts": []
"systemTitle": "FastAI",
"authorText": "Made by FastAI Team.",
"gitLoginKey": ""
},
"SystemParams": {
"gitLoginSecret": "",
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20
},
"plugins": {},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
"name": "GPT35-4k",
"name": "FastAI-4k",
"contextMaxToken": 4000,
"quoteMaxToken": 2000,
"maxTemperature": 1.2,
"price": 0,
"price": 1.5,
"defaultSystem": ""
},
{
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"name": "FastAI-16k",
"contextMaxToken": 16000,
"quoteMaxToken": 8000,
"maxTemperature": 1.2,
"price": 0,
"price": 3,
"defaultSystem": ""
},
{
"model": "gpt-4",
"name": "GPT4-8k",
"name": "FastAI-Plus",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"price": 45,
"defaultSystem": ""
}
],
"QAModels": [
{
"model": "gpt-3.5-turbo-16k",
"name": "FastAI-16k",
"maxToken": 16000,
"price": 3
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",
"name": "Embedding-2",
"price": 0,
"defaultToken": 500,
"maxToken": 3000
"price": 0.2
}
],
"QAModel": {
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
}
]
}

View File

@@ -5,8 +5,8 @@
module.exports = {
i18n: {
defaultLocale: 'zh',
locales: ['en', 'zh', 'zh-Hans', 'zh-CN'],
defaultLocale: 'en',
locales: ['en', 'zh', 'zh-Hans'],
localeDetection: false
}
};

View File

@@ -15,7 +15,6 @@
"@dqbd/tiktoken": "^1.0.7",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@mozilla/readability": "^0.4.4",
"@next/font": "13.1.6",
"@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0",
@@ -25,21 +24,17 @@
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"echarts": "^5.4.1",
"echarts-gl": "^2.0.9",
"formidable": "^2.1.1",
"framer-motion": "^9.0.6",
"hyperdown": "^2.4.29",
"i18next": "^22.5.1",
"immer": "^9.0.19",
"js-cookie": "^3.0.5",
"jschardet": "^3.0.0",
"jsdom": "^22.1.0",
"jsonwebtoken": "^9.0.0",
"lodash": "^4.17.21",
"mammoth": "^1.5.1",
"mermaid": "^10.2.3",
"mongoose": "^6.10.0",
"multer": "1.4.5-lts.1",
"nanoid": "^4.0.1",
"next": "13.1.6",
"next-i18next": "^13.3.0",
@@ -62,10 +57,7 @@
"remark-math": "^5.1.1",
"request-ip": "^3.3.0",
"sass": "^1.58.3",
"timezones-list": "^3.0.2",
"tunnel": "^0.0.6",
"winston": "^3.10.0",
"winston-mongodb": "^5.1.1",
"zustand": "^4.3.5"
},
"devDependencies": {
@@ -73,10 +65,8 @@
"@types/cookie": "^0.5.1",
"@types/formidable": "^2.0.5",
"@types/js-cookie": "^3.0.3",
"@types/jsdom": "^21.1.1",
"@types/jsonwebtoken": "^9.0.1",
"@types/lodash": "^4.14.191",
"@types/multer": "^1.4.7",
"@types/node": "18.14.0",
"@types/papaparse": "^5.3.7",
"@types/pg": "^8.6.6",

840
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
### 常见问题
- [**Git 地址**,点击查看项目地址](https://github.com/labring/FastGPT)
- [本地部署 FastGPT](https://doc.fastgpt.run/docs/installation)
- [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
- **反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#)
- **问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
- [点击查看商业版文档](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)
**一键部署**V4 版本未正式开源,目前仅提供一键部署方案:
[![](https://raw.githubusercontent.com/labring-actions/templates/main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
**Git 地址**: [项目地址。V4-beta 暂未开源,在正式版发布后会开源。](https://github.com/labring/FastGPT)
**反馈问卷**: 如果你遇到任何使用问题或有期望的功能,可以[填写该问卷](https://www.wjx.cn/vm/rLIw1uD.aspx#)
**问题文档**: [先看文档,再提问](https://kjqvjse66l.feishu.cn/docx/HtrgdT0pkonP4kxGx8qcu6XDnGh)
**价格表**
| 计费项 | 价格: 元/ 1K tokens包含上下文|
| --- | --- |
@@ -14,7 +14,7 @@
| FastAI4k - 对话 | 0.015 |
| FastAI16k - 对话 | 0.03 |
| FastAI-Plus - 对话 | 0.45 |
| 文件 QA 拆分 | 0.03 |
| 文件拆分 | 0.03 |
**其他问题**
| 交流群 | 小助手 |

View File

@@ -1,6 +1,8 @@
### Fast GPT V4.4
### Fast GPT V4.0-beta
1. 新增 - 知识库目录结构
2. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/)
3. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
4. [点击查看商业版](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)
1. 全新交互,增加采用模块组合的方式构建知识库,同时保留表单的简易模式。
2. 问题分类 - 可以对用户的问题进行分类,再执行不同的操作。
3. 对话引导 - 增加开场引导和变量,提高对话可玩性。
4. 新增 - 每轮对话均可展示完整的上下文状态。
5. 新增 - 网页嵌入(简单版),可直接接入现有网页。在 【应用详情-外部使用-创建新链接-嵌入网页】 中获取嵌入脚本。
6. 新增 - 个人 openai 账号可以使用网页上 OpenAI 对话模型。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
client/public/icon/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,14 +0,0 @@
<svg width="1041" height="1348" viewBox="0 0 1041 1348" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M340.837 0.33933L681.068 0.338989V0.455643C684.032 0.378397 686.999 0.339702 689.967 0.339702C735.961 0.3397 781.504 9.62899 823.997 27.6772C866.49 45.7254 905.099 72.1791 937.622 105.528C970.144 138.877 995.942 178.467 1013.54 222.04C1031.14 265.612 1040.2 312.312 1040.2 359.474L340.836 359.474L340.836 1347.84C296.157 1347.84 251.914 1338.55 210.636 1320.49C169.357 1302.43 131.85 1275.95 100.257 1242.58C68.6636 1209.21 43.6023 1169.59 26.5041 1125.99C11.3834 1087.43 2.75216 1046.42 0.957956 1004.81H0.605869L0.605897 368.098H0.70363C0.105752 341.831 2.23741 315.443 7.14306 289.411C20.2709 219.745 52.6748 155.754 100.257 105.528C147.839 55.3017 208.462 21.0975 274.461 7.24017C296.426 2.62833 318.657 0.339101 340.837 0.33933Z" fill="url(#paint0_linear_1172_228)"/>
<path d="M633.639 904.645H513.029V576.37H635.422V576.377C678.161 576.607 720.454 585.093 759.951 601.37C799.997 617.874 836.384 642.064 867.033 672.559C897.683 703.054 921.996 739.257 938.583 779.101C955.171 818.944 963.709 861.648 963.709 904.775H633.639V904.645Z" fill="url(#paint1_linear_1172_228)"/>
<defs>
<linearGradient id="paint0_linear_1172_228" x1="520.404" y1="0.338989" x2="520.404" y2="1347.84" gradientUnits="userSpaceOnUse">
<stop stop-color="#326DFF"/>
<stop offset="1" stop-color="#8EAEFF"/>
</linearGradient>
<linearGradient id="paint1_linear_1172_228" x1="738.369" y1="576.37" x2="738.369" y2="904.775" gradientUnits="userSpaceOnUse">
<stop stop-color="#326DFF"/>
<stop offset="1" stop-color="#8EAEFF"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694327751771" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4992" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M0 0h1024v1024H0V0z" fill="#202425" opacity=".01" p-id="4993"></path><path d="M136.533333 68.266667a68.266667 68.266667 0 0 0-68.266666 68.266666v428.305067a17.066667 17.066667 0 0 0 28.842666 12.356267l237.738667-226.440534a34.133333 34.133333 0 0 1 42.496-3.6864l268.288 178.858667a17.066667 17.066667 0 0 0 22.766933-3.447467L951.978667 171.4176A17.066667 17.066667 0 0 0 955.733333 160.699733V136.533333a68.266667 68.266667 0 0 0-68.266666-68.266666H136.533333z m819.2 255.3856a17.066667 17.066667 0 0 0-30.344533-10.717867l-221.866667 274.705067a17.066667 17.066667 0 0 0-3.7888 10.717866v340.309334a17.066667 17.066667 0 0 0 17.066667 17.066666h170.666667a68.266667 68.266667 0 0 0 68.266666-68.266666V323.652267zM614.4 955.733333a17.066667 17.066667 0 0 0 17.066667-17.066666v-330.990934a17.066667 17.066667 0 0 0-7.611734-14.199466l-204.8-136.533334a17.066667 17.066667 0 0 0-26.5216 14.199467V938.666667a17.066667 17.066667 0 0 0 17.066667 17.066666h204.8z m-307.2 0a17.066667 17.066667 0 0 0 17.066667-17.066666v-443.733334a17.066667 17.066667 0 0 0-28.842667-12.356266l-221.866667 211.285333a17.066667 17.066667 0 0 0-5.290666 12.3904V887.466667a68.266667 68.266667 0 0 0 68.266666 68.266666h170.666667z" fill="#FFAA44" p-id="4994"></path><path d="M73.557333 693.8624a17.066667 17.066667 0 0 0-5.290666 12.3904V887.466667a68.266667 68.266667 0 0 0 68.266666 68.266666h170.666667a17.066667 17.066667 0 0 0 17.066667-17.066666v-443.733334a17.066667 17.066667 0 0 0-28.842667-12.356266l-221.866667 211.285333zM392.533333 938.666667a17.066667 17.066667 0 0 0 17.066667 17.066666h204.8a17.066667 17.066667 0 0 0 17.066667-17.066666v-330.990934a17.066667 17.066667 0 0 0-7.611734-14.199466l-204.8-136.533334a17.066667 17.066667 0 0 0-26.5216 14.199467V938.666667z m307.2 0a17.066667 17.066667 0 0 0 17.066667 17.066666h170.666667a68.266667 68.266667 0 0 0 68.266666-68.266666V323.6864a17.066667 17.066667 0 0 0-30.344533-10.752l-221.866667 274.705067a17.066667 17.066667 0 0 0-3.7888 10.717866v340.309334z" fill="#11AA66" p-id="4995"></path></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694141197423" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4891" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M855.04 385.024q19.456 2.048 38.912 10.24t33.792 23.04 21.504 37.376 2.048 54.272q-2.048 8.192-8.192 40.448t-14.336 74.24-18.432 86.528-19.456 76.288q-5.12 18.432-14.848 37.888t-25.088 35.328-36.864 26.112-51.2 10.24l-567.296 0q-21.504 0-44.544-9.216t-42.496-26.112-31.744-40.96-12.288-53.76l0-439.296q0-62.464 33.792-97.792t95.232-35.328l503.808 0q22.528 0 46.592 8.704t43.52 24.064 31.744 35.84 12.288 44.032l0 11.264-53.248 0q-40.96 0-95.744-0.512t-116.736-0.512-115.712-0.512-92.672-0.512l-47.104 0q-26.624 0-41.472 16.896t-23.04 44.544q-8.192 29.696-18.432 62.976t-18.432 61.952q-10.24 33.792-20.48 65.536-2.048 8.192-2.048 13.312 0 17.408 11.776 29.184t29.184 11.776q31.744 0 43.008-39.936l54.272-198.656q133.12 1.024 243.712 1.024l286.72 0z" fill="#FFCC66" p-id="4892"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1692418843591" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4084" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M511.5 82c-236.6 0-429 192.4-429 429 0 236.5 192.5 429 429 429 236.6 0 429-192.4 429-429 0-236.5-192.4-429-429-429z m377.6 403.8H734.3c-4-139.9-41.4-259.9-97.5-331.9C776.5 203 879 332 889.1 485.8z m-402.8-349v349h-147c5.5-175.5 68.6-322.6 147-349z m0 399.4v349c-78.4-26.4-141.4-173.5-147-349h147z m50.5 349v-349h147c-5.6 175.5-68.6 322.6-147 349z m0-399.4v-349c78.4 26.4 141.4 173.5 147 349h-147zM386.3 153.9c-56.1 72-93.5 192-97.5 331.9H133.9C144.1 332 246.5 203 386.3 153.9zM133.9 536.2h154.8c4 139.9 41.4 259.9 97.5 331.9C246.5 819 144.1 690 133.9 536.2z m502.8 331.9c56.1-72 93.5-192 97.5-331.9H889C879 690 776.5 819 636.7 868.1z" fill="#5F9BEB" p-id="4085"></path></svg>

Before

Width:  |  Height:  |  Size: 1006 B

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693533362715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4021" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M166.32 808.98l-99.28 99.28c-15.31 15.32-39.71 15.32-55.03 0-15.32-15.31-15.32-39.71 0-55.03l98.71-99.28c15.32-15.31 39.71-15.31 55.03 0 15.32 15.32 15.32 39.72 0.57 55.03z m48.22 48.79l-99.28 99.27c-15.31 15.32-15.31 39.71 0 55.03 15.32 15.32 39.71 15.32 55.03 0l99.28-99.28c15.32-15.32 15.32-39.71 0-55.03-15.32-15.31-39.72-15.31-55.03 0.01z m625.14-545.16c0 71.47-57.86 129.34-129.34 129.34S581 384.08 581 312.61c0-71.48 57.86-129.34 129.34-129.34 72.05 0 129.91 57.86 129.34 129.34z m-77.71 0.57c0-28.36-23.26-51.63-51.62-51.63-28.36 0-51.62 23.26-51.62 51.63 0 28.36 23.26 51.62 51.62 51.62 28.93 0 52.19-23.26 51.62-51.62z m236.56 68.07c-27.8 104.39-83.39 203.65-164.51 285.35h-3.97l-5.11 5.67c10.21 38.57 11.35 78.28 2.84 116.86-9.07 46.52-32.33 90.2-66.94 124.8l-2.27 2.27c-38.57 36.87-85.09 59.57-134.45 67.51-51.62 7.94-105.51 0-152.6-23.26-17.58-10.21-25.53-32.9-16.45-51.63 1.7-2.84 3.41-5.67 5.67-8.5 14.18-15.32 24.96-32.9 32.9-51.63l3.4-7.94c-10.21 1.7-20.42 4.53-30.63 5.1-29.5 4.54-60.14 6.24-90.2 5.67-10.21 0-19.86-3.97-26.1-11.35L181.63 671.14c-8.51-7.95-11.35-18.72-10.78-29.5 0-28.37 2.27-58.43 5.67-86.79 1.7-10.78 2.84-20.99 5.1-31.2l-8.51 3.4c-17.58 7.95-35.17 18.72-51.06 33.47-14.75 14.18-39.14 12.48-53.32-2.27-2.27-2.84-3.97-7.37-6.24-10.21-23.26-47.09-30.63-99.28-22.69-150.9 8.22-51.8 32.65-99.66 69.78-136.71 34.61-34.61 78.85-57.86 123.67-66.94 39.14-8.51 79.42-7.94 116.86 2.27l7.95-7.95C439.75 106.69 538.45 51.66 642.83 23.3c108.35-28.36 222.38-28.36 329.59 0 13.62 4.54 23.82 15.32 27.23 27.23l0.57 0.57v0.56c28.37 107.78 28.37 221.81-1.69 329.59zM936.12 92.51c-89.06-19.86-182.67-18.16-270.03 5.1-91.34 24.96-178.7 73.18-250.74 145.22l-10.78 10.78-10.21 11.35c-10.21 11.35-26.66 16.45-42.55 10.78-31.2-12.48-65.81-14.75-98.71-7.38-31.2 6.81-61.27 22.13-86.23 46.52-26.66 27.8-43.12 60.7-48.79 95.3-3.4 20.99-3.4 42.55 0.57 62.97 9.64-5.67 18.72-10.78 28.93-14.18 26.66-11.34 55.6-18.15 84.53-18.72 3.97-0.57 7.94 0 11.91 0.56 19.86 5.67 31.77 26.66 26.1 46.52-7.95 26.1-13.05 52.2-16.45 78.29-1.7 22.12-3.4 43.12-3.97 64.67l146.36 146.93c22.12-1.14 43.11-2.27 64.67-5.11 27.23-3.97 52.76-8.51 78.29-15.89 3.97-0.56 7.94-1.13 11.91-1.13 20.42 0.57 36.31 18.72 36.31 39.14-1.7 29.5-7.38 58.43-19.29 85.66-3.97 9.08-9.07 19.29-14.18 28.36 20.42 3.97 41.98 3.97 62.4 1.13 33.47-5.1 66.94-20.99 93.03-47.09l1.13-1.13c24.39-24.96 39.71-55.6 47.09-86.79 6.81-32.34 3.97-66.94-7.94-99.28-4.54-14.18-1.7-30.64 10.21-41.41l11.91-11.35 8.51-8.51 2.27-1.13c72.04-72.61 119.7-159.41 145.79-250.17 23.27-89.05 25.54-180.95 7.95-270.01z" p-id="4022" fill="#515151"></path></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -20,14 +20,16 @@ async function embedChatbot() {
const ChatBtn = document.createElement('div');
ChatBtn.id = chatBtnId;
ChatBtn.style.cssText =
'position: fixed; bottom: 1rem; right: 1rem; width: 40px; height: 40px; cursor: pointer; z-index: 2147483647; transition: 0;';
'position: fixed; bottom: 1rem; right: 1rem; width: 40px; height: 40px; cursor: pointer; z-index: 2147483647; ';
const ChatBtnDiv = document.createElement('div');
ChatBtnDiv.style.cssText =
'transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);} display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 9999;';
ChatBtnDiv.innerHTML = MessageIcon;
const iframe = document.createElement('iframe');
iframe.allow = 'fullscreen;microphone';
iframe.title = 'FastGPT Chat Window';
iframe.title = 'FastGpt Chat Window';
iframe.id = chatWindowId;
iframe.src = botSrc;
@@ -36,15 +38,7 @@ async function embedChatbot() {
document.body.appendChild(iframe);
let chatBtnDragged = false;
let chatBtnDown = false;
let chatBtnMouseX;
let chatBtnMouseY;
ChatBtn.addEventListener('click', function () {
if (chatBtnDragged) {
chatBtnDragged = false;
return;
}
const chatWindow = document.getElementById(chatWindowId);
if (!chatWindow) return;
@@ -58,31 +52,6 @@ async function embedChatbot() {
}
});
ChatBtn.addEventListener('mousedown', (e) => {
if (!chatBtnMouseX && !chatBtnMouseY) {
chatBtnMouseX = e.clientX;
chatBtnMouseY = e.clientY;
}
chatBtnDown = true;
});
ChatBtn.addEventListener('mousemove', (e) => {
if (!chatBtnDown) return;
chatBtnDragged = true;
const transformX = e.clientX - chatBtnMouseX;
const transformY = e.clientY - chatBtnMouseY;
ChatBtn.style.transform = `translate3d(${transformX}px, ${transformY}px, 0)`;
e.stopPropagation();
});
ChatBtn.addEventListener('mouseup', (e) => {
chatBtnDown = false;
});
ChatBtn.addEventListener('mouseleave', (e) => {
chatBtnDown = false;
});
ChatBtn.appendChild(ChatBtnDiv);
document.body.appendChild(ChatBtn);
}

View File

@@ -2,116 +2,43 @@
"App": "App",
"Cancel": "No",
"Confirm": "Yes",
"Create New": "Create",
"Dataset": "Dataset",
"Folder": "Folder",
"Name": "Name",
"Running": "Running",
"Select value is empty": "Select value is empty",
"UnKnow": "UnKnow",
"Warning": "Warning",
"app": {
"Advance App TestTip": "The current application is advanced editing mode \n. If you need to switch to [simple mode], please click the save button on the left",
"App Detail": "App Detail",
"Chat Logs Tips": "Logs record the app's online, shared, and API(chatId is existing) conversations",
"Chat logs": "Chat Logs",
"Confirm Del App Tip": "Confirm to delete the app and all its chats",
"Confirm Save App Tip": "The application may be in advanced orchestration mode, and the advanced orchestration configuration will be overwritten after saving, please confirm!",
"Connection is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different",
"Copy Module Config": "Copy config",
"Export Config Successful": "The configuration has been copied. Please check for important data",
"Export Configs": "Export Configs",
"Feedback Count": "User Feedback",
"Import Config": "Import Config",
"Import Config Failed": "Failed to import the configuration, please ensure that the configuration is normal!",
"Import Configs": "Import Configs",
"Input Field Settings": "Input Field Settings",
"Logs Empty": "Logs is empty",
"Logs Message Total": "Message Count",
"Logs Source": "Source",
"Logs Time": "Time",
"Logs Title": "Title",
"Mark Count": "Mark Count",
"My Apps": "My Apps",
"Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config",
"Variable Key Repeat Tip": "Variable Key Repeat"
"Output Field Settings": "Output Field Settings"
},
"chat": {
"Admin Mark Content": "Corrected response",
"Complete Response": "Complete Response",
"Confirm to clear history": "Confirm to clear history?",
"Confirm to clear share chat histroy": " Are you sure to delete all chats?",
"Exit Chat": "Exit",
"Feedback Close": "Close Feedback",
"Feedback Failed": "Feedback Failed",
"Feedback Mark": "Mark",
"Feedback Modal": "Feedback",
"Feedback Modal Tip": "Enter what you find unsatisfactory",
"Feedback Submit": "Submit",
"Feedback Success": "Feedback Success",
"Feedback Update Failed": "Feedback Update Failed",
"History": "History",
"Mark": "Mark",
"Mark Description": "The annotation feature is currently in beta. \n\n After clicking Add annotation, you need to select a knowledge base in order to store annotation data. You can use this feature to quickly annotate questions and expected answers to guide the model to the next answer. At present, the annotation function, like other data in the knowledge base, is affected by the model, which does not mean that the annotation meets 100% expectations. The \n\n annotation data is only unidirectional synchronization with the knowledge base. If the knowledge base modifies the annotation data, the annotation data displayed in the log cannot be synchronized",
"Mark Description Title": "Mark Description",
"New Chat": "New Chat",
"Read Mark Description": "Read mark description",
"Read User Feedback": "Read user feedback",
"Select Mark Kb": "Select Dataset",
"Select Mark Kb Desc": "Select a dataset to store the expected answers",
"You need to a chat app": "You need to a chat app",
"logs": {
"api": "API",
"online": "Online Chat",
"share": "Share",
"test": "Test Chat "
},
"retry": "Retry"
"You need to a chat app": "You need to a chat app"
},
"common": {
"Add": "Add",
"Cancel": "Cancel",
"Collect": "Collect",
"Copy": "Copy",
"Copy Successful": "Copy Successful",
"Course": "",
"Delete": "Delete",
"Delete Failed": "Delete Failed",
"Delete Success": "Delete Successful",
"Delete Warning": "Warning",
"Filed is repeat": "Filed is repeated",
"Filed is repeated": "",
"Input": "Input",
"Output": "Output",
"Password inconsistency": "Password inconsistency",
"Rename": "Rename",
"Search": "Search",
"Status": "Status",
"export": ""
},
"dataset": {
"Confirm to delete the data": "Confirm to delete the data?",
"Export": "Export",
"Queue Desc": "This data refers to the current amount of training for the entire system. FastGPT uses queued training, and if you have too much data to train, you may need to wait for a while",
"System Data Queue": "Data Queue"
"Output": "Output"
},
"file": {
"Click to download CSV template": "Click to download CSV template",
"Click to view file": "Click to view file",
"Create File": "Create File",
"Create file": "Create file",
"Drag and drop": "Drag and drop files here",
"Embedding": "Embedding",
"Fetch Url": "Fetch Url",
"Drag and drop": "Drag and drop files here, or click",
"If the imported file is garbled, please convert CSV to UTF-8 encoding format": "If the imported file is garbled, please convert CSV to UTF-8 encoding format",
"Parse": "{{name}} Parsing...",
"Ready": "Ready",
"Release the mouse to upload the file": "Release the mouse to upload the file",
"Select a maximum of 10 files": "Select a maximum of 10 files",
"Uploading": "Uploading: {{name}}, Progress: {{percent}}%",
"max 10": "Max 10 files",
"select a document": "select a document",
"support": "support {{fileExtension}} file",
"upload error description": "Only upload multiple files or one folder at a time"
@@ -126,12 +53,10 @@
"Choice Desc": "FastGPT follows the Apache License 2.0 open source protocol",
"Choice Extension": "Infinite Extension",
"Choice Extension Desc": "HTTP based extension, easy to achieve custom functions",
"Choice Fast": "Fast",
"Choice Fast Desc": "{{title}} provides out-of-the-box visual actions to build AI applications point-by-point",
"Choice Models": "Multiple Models",
"Choice Models Desc": "Supports multiple models such as GPT, Claude, Spark, and ChatGLM",
"Choice Models Desc": "",
"Choice Open": "Open",
"Choice Open Desc": "{{title}} follows the Apache License 2.0 open source protocol",
"Choice Open Desc": "",
"Choice QA": "QA Struceture",
"Choice QA Desc": "The index is constructed with the structure of QA pairs, and ADAPTS to various scenarios such as Q&A and reading",
"Choice Visual": "Visual workflow",
@@ -140,12 +65,12 @@
"Dateset": "",
"Dateset Desc": "",
"Docs": "Docs",
"FastGPT Ability": "{{title}} Ability",
"FastGPT Desc": "{{title}} is a knowledgebase question answering system based on LLM large language model, which provides out-of-the-box data processing, model invocation and other capabilities. At the same time, workflow orchestration can be performed through Flow visualization to achieve complex Q&A scenarios!",
"FastGPT Ability": "FastGPT Ability",
"FastGPT Desc": "FastGPT is a knowledgebase question answering system based on LLM large language model, which provides out-of-the-box data processing, model invocation and other capabilities. At the same time, workflow orchestration can be performed through Flow visualization to achieve complex Q&A scenarios!",
"Features": "Features",
"Footer Developer": "Developer",
"Footer Docs": "Docs",
"Footer FastGPT Cloud": "{{title}} Cloud",
"Footer FastGPT Cloud": "FastGPT Cloud",
"Footer Feedback": "Feedback",
"Footer Git": "Code",
"Footer Product": "Product",
@@ -157,27 +82,9 @@
"Quickly build AI question and answer library": "Quickly build AI question and answer library",
"Start Now": "Start Now",
"Visual AI orchestration": "Visual AI orchestration",
"Why FastGPT": "Why {{title}}",
"desc": "AI knowledge base question and answer platform based on LLM large model",
"slogan": "Let the AI know more about you"
},
"kb": {
"Chunk Length": "Chunk Length",
"Confirm to delete the file": "Are you sure to delete the file and all its data?",
"Create Folder": "Create Folder",
"Delete Dataset Error": "Delete dataset failed",
"Edit Folder": "Edit Folder",
"File Size": "File Size",
"Filename": "Filename",
"Files": "{{total}} Files",
"Folder Name": "Input folder name",
"My Dataset": "My Dataset",
"Other Data": "Other Data",
"Select Dataset": "Select Dataset",
"Select Folder": "Enter folder",
"Upload Time": "Upload Time",
"deleteDatasetTips": "Are you sure to delete the knowledge base? Data cannot be recovered after deletion, please confirm!",
"deleteFolderTips": "Are you sure to delete this folder and all the knowledge bases it contains? Data cannot be recovered after deletion, please confirm!"
"Why FastGPT": "",
"desc": "",
"slogan": ""
},
"navbar": {
"Account": "Account",
@@ -189,41 +96,27 @@
},
"user": {
"Account": "Account",
"Amount of earnings": "Earnings",
"Amount of inviter": "Inviter",
"Application Name": "Application Name",
"Avatar": "Avatar",
"Balance": "Balance",
"Bill Detail": "Bill Detail",
"Change": "Change",
"Copy invite url": "Copy invitation link",
"Invite Url": "Invite Url",
"Invite url tip": "Friends who register through this link will be permanently bound to you, and you will get a certain balance reward when they recharge. In addition, when friends register with their mobile phone number, you will get 5 yuan reward immediately.",
"Notice": "Notice",
"Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting",
"Password": "Password",
"Pay": "Pay",
"Personal Information": "Personal",
"Promotion": "Promotion",
"Promotion Rate": "Promotion Rate",
"Promotion Record": "Promotion",
"Promotion rate tip": "You will be rewarded with a percentage of the balance when your friends top up",
"Recharge Record": "Recharge",
"Replace": "Replace",
"Set OpenAI Account Failed": "Set OpenAI account failed",
"Sign Out": "Sign Out",
"Source": "Source",
"Time": "Time",
"Timezone": "Timezone",
"Total Amount": "Total Amount",
"Update Password": "Update Password",
"Update password failed": "Update password failed",
"Update password succseful": "Update password succseful",
"Usage Record": "Usage",
"promotion": {
"pay": "",
"register": ""
}
"Usage Record": "Usage"
}
}

View File

@@ -2,116 +2,42 @@
"App": "应用",
"Cancel": "取消",
"Confirm": "确认",
"Create New": "新建",
"Dataset": "知识库",
"Folder": "文件夹",
"Name": "名称",
"Running": "运行中",
"Select value is empty": "选择的内容为空",
"UnKnow": "未知",
"Warning": "提示",
"app": {
"Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情",
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
"Chat logs": "对话日志",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
"Confirm Save App Tip": "该应用可能为高级编排模式,保存后将会覆盖高级编排配置,请确认!",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"Copy Module Config": "复制配置",
"Export Config Successful": "已复制配置,请注意检查是否有重要数据",
"Export Configs": "导出配置",
"Feedback Count": "用户反馈",
"Import Config": "导入配置",
"Import Config Failed": "导入配置失败,请确保配置正常!",
"Import Configs": "导入配置",
"Input Field Settings": "输入字段编辑",
"Logs Empty": "还没有日志噢~",
"Logs Message Total": "消息总数",
"Logs Source": "来源",
"Logs Time": "时间",
"Logs Title": "标题",
"Mark Count": "标注答案数量",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置",
"Variable Key Repeat Tip": "变量 key 重复"
"Output Field Settings": "输出字段编辑"
},
"chat": {
"Admin Mark Content": "纠正后的回复",
"Complete Response": "完整响应",
"Confirm to clear history": "确认清空该应用的在线聊天记录?分享和 API 调用的记录不会被清空。",
"Confirm to clear share chat histroy": "确认删除所有聊天记录?",
"Confirm to clear history": "确认清空该应用的聊天记录?",
"Exit Chat": "退出聊天",
"Feedback Close": "关闭反馈",
"Feedback Failed": "提交反馈异常",
"Feedback Mark": "标注",
"Feedback Modal": "结果反馈",
"Feedback Modal Tip": "输入你觉得回答不满意的地方",
"Feedback Submit": "提交反馈",
"Feedback Success": "反馈成功!",
"Feedback Update Failed": "更新反馈状态失败",
"History": "记录",
"Mark": "标注预期回答",
"Mark Description": "当前标注功能为测试版。\n\n点击添加标注后需要选择一个知识库以便存储标注数据。你可以通过该功能快速的标注问题和预期回答以便引导模型下次的回答。\n\n目前标注功能同知识库其他数据一样受模型的影响不代表标注后 100% 符合预期。\n\n标注数据仅单向与知识库同步如果知识库修改了该标注数据日志展示的标注数据无法同步",
"Mark Description Title": "标注功能介绍",
"New Chat": "新对话",
"Read Mark Description": "查看标注功能介绍",
"Read User Feedback": "查看用户反馈",
"Select Mark Kb": "选择知识库",
"Select Mark Kb Desc": "选择一个知识库存储预期答案",
"You need to a chat app": "你需要创建一个应用",
"logs": {
"api": "API 调用",
"online": "在线使用",
"share": "外部链接调用",
"test": "测试"
},
"retry": "重新生成"
"You need to a chat app": "你需要创建一个应用"
},
"common": {
"Add": "添加",
"Cancel": "取消",
"Collect": "收藏",
"Copy": "复制",
"Copy Successful": "复制成功",
"Course": "",
"Delete": "删除",
"Delete Failed": "删除失败",
"Delete Success": "删除成功",
"Delete Warning": "删除警告",
"Filed is repeat": "",
"Filed is repeated": "字段重复了",
"Input": "输入",
"Output": "输出",
"Password inconsistency": "两次密码不一致",
"Rename": "重命名",
"Search": "搜索",
"Status": "状态",
"export": ""
},
"dataset": {
"Confirm to delete the data": "确认删除该数据?",
"Export": "导出",
"Queue Desc": "该数据是指整个系统当前待训练的数量。{{title}} 采用排队训练的方式,如果待训练的数据过多,可能需要等待一段时间",
"System Data Queue": "排队长度"
"Output": "输出"
},
"file": {
"Click to download CSV template": "点击下载 CSV 模板",
"Click to view file": "点击查看原始文件",
"Create File": "创建新文件",
"Create file": "创建文件",
"Drag and drop": "拖拽文件至此",
"Embedding": "索引中",
"Fetch Url": "链接读取",
"Drag and drop": "拖拽文件至此,或点击",
"If the imported file is garbled, please convert CSV to UTF-8 encoding format": "如果导入文件乱码,请将 CSV 转成 UTF-8 编码格式",
"Parse": "{{name}} 解析中...",
"Ready": "可用",
"Release the mouse to upload the file": "松开鼠标上传文件",
"Select a maximum of 10 files": "最多选择10个文件",
"Uploading": "正在上传 {{name}},进度: {{percent}}%",
"max 10": "最多选择 10 个文件",
"select a document": "选择文件",
"support": "支持 {{fileExtension}} 文件",
"upload error description": "单次只支持上传多个文件或者一个文件夹"
@@ -126,12 +52,10 @@
"Choice Desc": "",
"Choice Extension": "无限扩展",
"Choice Extension Desc": "基于 HTTP 实现扩展,轻松实现定制功能",
"Choice Fast": "开箱即用",
"Choice Fast Desc": "{{title}} 提供开箱即用的可视化操作,点点点即可构建 AI 应用",
"Choice Models": "支持多种模型",
"Choice Models Desc": "支持 GPT、Claude、Spark、ChatGLM等多模型",
"Choice Models Desc": "支持 GPT、Claude、文心一言等多模型",
"Choice Open": "更开放",
"Choice Open Desc": "{{title}} 遵循 Apache License 2.0 开源协议",
"Choice Open Desc": "FastGPT 遵循 Apache License 2.0 开源协议",
"Choice QA": "独特的 QA 结构",
"Choice QA Desc": "采用 QA 对的结构构建索引,适应问答、阅读等多种场景",
"Choice Visual": "可视化工作流",
@@ -140,12 +64,12 @@
"Dateset": "自动数据预处理",
"Dateset Desc": "提供手动输入、直接分段、LLM 自动处理和 CSV 等多种数据导入途径",
"Docs": "文档",
"FastGPT Ability": "{{title}} 能力",
"FastGPT Desc": "{{title}} 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!",
"FastGPT Ability": "FastGPT 能力",
"FastGPT Desc": "FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!",
"Features": "特点",
"Footer Developer": "开发者",
"Footer Docs": "文档",
"Footer FastGPT Cloud": "{{title}} 线上服务",
"Footer FastGPT Cloud": "FastGPT 线上服务",
"Footer Feedback": "反馈",
"Footer Git": "源码",
"Footer Product": "产品",
@@ -157,28 +81,10 @@
"Quickly build AI question and answer library": "快速搭建 AI 问答系统",
"Start Now": "立即开始",
"Visual AI orchestration": "可视化 AI 编排",
"Why FastGPT": "为什么选择 {{title}}",
"Why FastGPT": "为什么选择 FastGPT",
"desc": "基于 LLM 大模型的 AI 知识库问答平台",
"slogan": "让 AI 更懂你的知识"
},
"kb": {
"Chunk Length": "数据总量",
"Confirm to delete the file": "确认删除该文件及其所有数据?",
"Create Folder": "创建文件夹",
"Delete Dataset Error": "删除知识库异常",
"Edit Folder": "编辑文件夹",
"File Size": "文件大小",
"Filename": "文件名",
"Files": "文件: {{total}}个",
"Folder Name": "输入文件夹名称",
"My Dataset": "我的知识库",
"Other Data": "其他数据",
"Select Dataset": "选择该知识库",
"Select Folder": "进入文件夹",
"Upload Time": "上传时间",
"deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!",
"deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!"
},
"navbar": {
"Account": "账号",
"Apps": "应用",
@@ -189,41 +95,27 @@
},
"user": {
"Account": "账号",
"Amount of earnings": "收益(¥)",
"Amount of inviter": "累计邀请人数",
"Application Name": "应用名",
"Avatar": "头像",
"Balance": "余额",
"Bill Detail": "账单详情",
"Change": "变更",
"Copy invite url": "复制邀请链接",
"Invite Url": "邀请链接",
"Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外好友使用手机号注册时你将立即获得 5 元奖励。",
"Notice": "通知",
"Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置",
"Password": "密码",
"Pay": "充值",
"Personal Information": "个人信息",
"Promotion": "",
"Promotion Rate": "返现比例",
"Promotion Record": "推广记录",
"Promotion rate tip": "好友充值时你将获得一定比例的余额奖励",
"Recharge Record": "充值记录",
"Replace": "更换",
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
"Sign Out": "登出",
"Source": "来源",
"Time": "时间",
"Timezone": "时区",
"Total Amount": "总金额",
"Update Password": "修改密码",
"Update password failed": "修改密码异常",
"Update password succseful": "修改密码成功",
"Usage Record": "使用记录",
"promotion": {
"pay": "好友充值",
"register": "好友注册"
}
"Usage Record": "使用记录"
}
}

View File

@@ -4,7 +4,6 @@ import type { AppListItemType, AppUpdateParams } from '@/types/app';
import { RequestPaging } from '../types/index';
import type { Props as CreateAppProps } from '@/pages/api/app/create';
import { addDays } from 'date-fns';
import { GetAppChatLogsParams } from './request/app';
/**
* 获取模型列表
@@ -52,5 +51,3 @@ export const getAppTotalUsage = (data: { appId: string }) =>
start: addDays(new Date(), -13),
end: addDays(new Date(), 1)
}).then((res) => (res.length === 0 ? [{ date: new Date(), total: 0 }] : res));
export const getAppChatLogs = (data: GetAppChatLogsParams) => POST(`/app/getChatLogs`, data);

View File

@@ -5,7 +5,6 @@ import { RequestPaging } from '../types/index';
import type { OutLinkSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/app';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
import { AdminUpdateFeedbackParams } from './request/chat';
/**
* 获取初始化聊天内容
@@ -28,10 +27,20 @@ export const delChatHistoryById = (chatId: string) => DELETE(`/chat/removeHistor
*/
export const clearChatHistoryByAppId = (appId: string) => DELETE(`/chat/removeHistory`, { appId });
/**
* update history quote status
*/
export const updateHistoryQuote = (params: {
chatId: string;
contentId: string;
quoteId: string;
sourceText: string;
}) => PUT(`/chat/history/updateHistoryQuote`, params);
/**
* 删除一句对话
*/
export const delChatRecordById = (data: { chatId: string; contentId: string }) =>
export const delChatRecordByIndex = (data: { chatId: string; contentId: string }) =>
DELETE(`/chat/delChatRecordByContentId`, data);
/**
@@ -65,9 +74,3 @@ export const getShareChatList = (appId: string) =>
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
POST('/chat/feedback/userUpdate', data);
export const adminUpdateChatFeedback = (data: AdminUpdateFeedbackParams) =>
POST('/chat/feedback/adminUpdate', data);

View File

@@ -74,7 +74,7 @@ export const streamFetch = ({
if (!eventName || !data) return;
if (eventName === sseResponseEventEnum.answer && data !== '[DONE]') {
const answer: string = data?.choices?.[0]?.delta?.content || '';
const answer: string = data?.choices?.[0].delta.content || '';
onMessage({ text: answer });
responseText += answer;
} else if (

View File

@@ -1,6 +0,0 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { FetchResultItem } from '@/types/plugin';
export const fetchUrls = (urlList: string[]) =>
POST<FetchResultItem[]>(`/plugins/urlFetch`, { urlList });

View File

@@ -1,12 +1,6 @@
import { GET, POST, PUT, DELETE } from '../request';
import type {
DatasetItemType,
FileInfo,
KbFileItemType,
KbItemType,
KbListItemType,
KbPathItemType
} from '@/types/plugin';
import type { KbItemType, KbListItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin';
import {
Props as PushDataProps,
@@ -16,47 +10,46 @@ import {
Props as SearchTestProps,
Response as SearchTestResponse
} from '@/pages/api/openapi/kb/searchTest';
import { Response as KbDataItemType } from '@/pages/api/plugins/kb/data/getDataById';
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb';
import { QuoteItemType } from '@/types/chat';
export type KbUpdateParams = {
id: string;
name: string;
tags: string;
avatar: string;
};
/* knowledge base */
export const getKbList = (parentId?: string) =>
GET<KbListItemType[]>(`/plugins/kb/list`, { parentId });
export const getAllDataset = () => GET<KbListItemType[]>(`/plugins/kb/allDataset`);
export const getKbPaths = (parentId?: string) =>
GET<KbPathItemType[]>('/plugins/kb/paths', { parentId });
export const getKbList = () => GET<KbListItemType[]>(`/plugins/kb/list`);
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: CreateKbParams) => POST<string>(`/plugins/kb/create`, data);
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 file */
export const getKbFiles = (data: { kbId: string; searchText: string }) =>
GET<KbFileItemType[]>(`/plugins/kb/file/list`, data);
export const deleteKbFileById = (params: { fileId: string; kbId: string }) =>
DELETE(`/plugins/kb/file/delFileByFileId`, params);
export const getFileInfoById = (fileId: string) =>
GET<FileInfo>(`/plugins/kb/file/getFileInfo`, { fileId });
export const delEmptyFiles = (kbId: string) =>
DELETE(`/plugins/kb/file/deleteEmptyFiles`, { kbId });
/* kb data */
type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* 获取导出数据(不分页)
*/
export const getExportDataList = (data: { kbId: string; fileId: string }) =>
GET<[string, string, string][]>(`/plugins/kb/data/exportModelData`, data, {
timeout: 600000
});
export const getExportDataList = (kbId: string) =>
GET<[string, string, string][]>(
`/plugins/kb/data/exportModelData`,
{ kbId },
{
timeout: 600000
}
);
/**
* 获取模型正在拆分数据的数量
@@ -67,11 +60,8 @@ export const getTrainingData = (data: { kbId: string; init: boolean }) =>
vectorListLen: number;
}>(`/plugins/kb/data/getTrainingData`, data);
/* get length of system training queue */
export const getTrainingQueueLen = () => GET<number>(`/plugins/kb/data/getQueueLen`);
export const getKbDataItemById = (dataId: string) =>
GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
GET<KbDataItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
@@ -79,12 +69,6 @@ export const getKbDataItemById = (dataId: string) =>
export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/**
* insert one data to dataset
*/
export const insertData2Kb = (data: { kbId: string; data: DatasetItemType }) =>
POST<string>(`/plugins/kb/data/insertData`, data);
/**
* 更新一条数据
*/

View File

@@ -1,9 +1,4 @@
import axios, {
Method,
InternalAxiosRequestConfig,
AxiosResponse,
AxiosProgressEvent
} from 'axios';
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { clearToken, getToken } from '@/utils/user';
import { TOKEN_ERROR_CODE } from '@/service/errorCode';
@@ -11,7 +6,6 @@ interface ConfigType {
headers?: { [key: string]: string };
hold?: boolean;
timeout?: number;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
}
interface ResponseDataType {
code: number;

View File

@@ -1,7 +0,0 @@
import { RequestPaging } from '@/types';
export type GetAppChatLogsParams = RequestPaging & {
appId: string;
dateStart: Date;
dateEnd: Date;
};

View File

@@ -1,6 +0,0 @@
export type AdminUpdateFeedbackParams = {
chatItemId: string;
kbId: string;
dataId: string;
content: string;
};

View File

@@ -1,23 +0,0 @@
import { KbTypeEnum } from '@/constants/kb';
import type { RequestPaging } from '@/types';
export type KbUpdateParams = {
id: string;
tags?: string;
name?: string;
avatar?: string;
};
export type CreateKbParams = {
parentId?: string;
name: string;
tags: string[];
avatar: string;
vectorModel?: string;
type: `${KbTypeEnum}`;
};
export type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
fileId: string;
};

View File

@@ -1,8 +0,0 @@
import { GET, POST } from './request';
export const textCensor = (data: { text: string }) =>
POST<{ code?: number; message: string }>('/plugins/censor/text_baidu', data).then((res) => {
if (res?.code === 5000) {
return Promise.reject(res.message);
}
});

View File

@@ -1,20 +1,6 @@
import { GET, POST, PUT } from './request';
import type { InitDateResponse } from '@/pages/api/system/getInitData';
import { AxiosProgressEvent } from 'axios';
export const getInitData = () => GET<InitDateResponse>('/system/getInitData');
export const uploadImg = (base64Img: string) => POST<string>('/system/uploadImage', { base64Img });
export const postUploadFiles = (
data: FormData,
onUploadProgress: (progressEvent: AxiosProgressEvent) => void
) =>
POST<string[]>('/plugins/file/upload', data, {
onUploadProgress,
headers: {
'Content-Type': 'multipart/form-data; charset=utf-8'
}
});
export const getFileViewUrl = (fileId: string) => GET<string>('/plugins/file/readUrl', { fileId });

View File

@@ -1,6 +1,6 @@
import { GET, POST, PUT } from './request';
import { createHashPassword } from '@/utils/tools';
import type { ResLogin, PromotionRecordType } from './response/user';
import { ResLogin } from './response/user';
import { UserAuthTypeEnum } from '@/constants/common';
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types';
@@ -10,11 +10,10 @@ export const sendAuthCode = (data: {
username: string;
type: `${UserAuthTypeEnum}`;
googleToken: string;
}) => POST(`/plusApi/user/account/sendCode`, data);
}) => POST('/user/sendAuthCode', data);
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
export const gitLogin = (params: { code: string; inviterId?: string }) =>
GET<ResLogin>('/plusApi/user/account/gitLogin', params);
export const gitLogin = (code: string) => GET<ResLogin>('/user/account/gitLogin', { code });
export const postRegister = ({
username,
@@ -25,9 +24,9 @@ export const postRegister = ({
username: string;
code: string;
password: string;
inviterId?: string;
inviterId: string;
}) =>
POST<ResLogin>(`/plusApi/user/account/register`, {
POST<ResLogin>('/user/account/register', {
username,
code,
inviterId,
@@ -43,7 +42,7 @@ export const postFindPassword = ({
code: string;
password: string;
}) =>
POST<ResLogin>(`/plusApi/user/account/updatePasswordByCode`, {
POST<ResLogin>('/user/account/updatePasswordByCode', {
username,
code,
password: createHashPassword(password)
@@ -74,29 +73,12 @@ export const getPayCode = (amount: number) =>
GET<{
codeUrl: string;
payId: string;
}>(`/plusApi/user/pay/getPayCode`, { amount });
}>(`/user/getPayCode?amount=${amount}`);
export const checkPayResult = (payId: string) =>
GET<number>(`/plusApi/user/pay/checkPayResult`, { payId }).then(() => {
try {
GET('/user/account/paySuccess');
} catch (error) {}
return 'success';
});
export const checkPayResult = (payId: string) => GET<number>(`/user/checkPayResult?payId=${payId}`);
export const getInforms = (data: RequestPaging) =>
POST<PagingData<informSchema>>(`/user/inform/list`, data);
export const getUnreadCount = () => GET<number>(`/user/inform/countUnread`);
export const readInform = (id: string) => GET(`/user/inform/read`, { id });
/* get promotion init data */
export const getPromotionInitData = () =>
GET<{
invitedAmount: number;
earningsAmount: number;
}>('/user/promotion/getPromotionData');
/* promotion records */
export const getPromotionRecords = (data: RequestPaging) =>
POST<PromotionRecordType>(`/user/promotion/getPromotions`, data);

View File

@@ -8,8 +8,8 @@ const Avatar = ({ w = '30px', ...props }: ImageProps) => {
<Image
fallbackSrc={LOGO_ICON}
fallbackStrategy={'onError'}
borderRadius={'md'}
objectFit={'contain'}
borderRadius={'50%'}
objectFit={'cover'}
alt=""
w={w}
h={w}

View File

@@ -21,13 +21,7 @@ const ContextModal = ({
minW={['90vw', '600px']}
isCentered
>
<ModalBody
pt={0}
whiteSpace={'pre-wrap'}
textAlign={'justify'}
wordBreak={'break-all'}
fontSize={'sm'}
>
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
{context.map((item, i) => (
<Box
key={i}

View File

@@ -1,56 +0,0 @@
import React, { useRef } from 'react';
import { ModalBody, Textarea, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useRequest } from '@/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { userUpdateChatFeedback } from '@/api/chat';
const FeedbackModal = ({
chatItemId,
onSuccess,
onClose
}: {
chatItemId: string;
onSuccess: (e: string) => void;
onClose: () => void;
}) => {
const ref = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
const { mutate, isLoading } = useRequest({
mutationFn: async () => {
const val = ref.current?.value || 'N/A';
return userUpdateChatFeedback({
chatItemId,
userFeedback: val
});
},
onSuccess() {
onSuccess(ref.current?.value || 'N/A');
},
successToast: t('chat.Feedback Success'),
errorToast: t('chat.Feedback Failed')
});
return (
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
<ModalBody>
<Textarea
ref={ref}
rows={10}
placeholder={t('chat.Feedback Modal Tip') || 'chat.Feedback Modal Tip'}
/>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={2} onClick={onClose}>
{t('Cancel')}
</Button>
<Button isLoading={isLoading} onClick={mutate}>
{t('chat.Feedback Submit')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default FeedbackModal;

View File

@@ -1,49 +1,53 @@
import React, { useCallback, useState } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import { ModalBody, ModalCloseButton, ModalHeader, Box, useTheme } from '@chakra-ui/react';
import { getKbDataItemById } from '@/api/plugins/kb';
import { useLoading } from '@/hooks/useLoading';
import { useToast } from '@/hooks/useToast';
import { getErrText } from '@/utils/tools';
import { QuoteItemType } from '@/types/chat';
import MyIcon from '@/components/Icon';
import InputDataModal, { RawFileText } from '@/pages/kb/detail/components/InputDataModal';
import InputDataModal from '@/pages/kb/detail/components/InputDataModal';
import MyModal from '../MyModal';
import { KbDataItemType } from '@/types/plugin';
type SearchType = KbDataItemType & {
kb_id?: string;
};
const QuoteModal = ({
onUpdateQuote,
rawSearch = [],
onClose
}: {
onUpdateQuote: (quoteId: string, sourceText?: string) => Promise<void>;
rawSearch: SearchType[];
onUpdateQuote: (quoteId: string, sourceText: string) => Promise<void>;
rawSearch: QuoteItemType[];
onClose: () => void;
}) => {
const theme = useTheme();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editDataItem, setEditDataItem] = useState<QuoteItemType>();
const [editDataItem, setEditDataItem] = useState<{
kbId: string;
dataId: string;
a: string;
q: string;
}>();
/**
* click edit, get new kbDataItem
*/
const onclickEdit = useCallback(
async (item: SearchType) => {
if (!item.id) return;
async (item: QuoteItemType) => {
try {
setIsLoading(true);
const data = await getKbDataItemById(item.id);
const data = (await getKbDataItemById(item.id)) as QuoteItemType;
if (!data) {
onUpdateQuote(item.id, '已删除');
throw new Error('该数据已被删除');
}
setEditDataItem(data);
setEditDataItem({
kbId: data.kb_id,
dataId: data.id,
q: data.q,
a: data.a
});
} catch (err) {
toast({
status: 'warning',
@@ -72,16 +76,10 @@ const QuoteModal = ({
</>
}
>
<ModalBody
pt={0}
whiteSpace={'pre-wrap'}
textAlign={'justify'}
wordBreak={'break-all'}
fontSize={'sm'}
>
{rawSearch.map((item, i) => (
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
{rawSearch.map((item) => (
<Box
key={i}
key={item.id}
flex={'1 0 0'}
p={2}
borderRadius={'lg'}
@@ -89,38 +87,35 @@ const QuoteModal = ({
_notLast={{ mb: 2 }}
position={'relative'}
_hover={{ '& .edit': { display: 'flex' } }}
overflow={'hidden'}
>
{item.source && <RawFileText filename={item.source} fileId={item.file_id} />}
{item.source && <Box color={'myGray.600'}>({item.source})</Box>}
<Box>{item.q}</Box>
<Box>{item.a}</Box>
{item.id && (
<Box
className="edit"
display={'none'}
position={'absolute'}
right={0}
top={0}
bottom={0}
w={'40px'}
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={'18px'}
h={'18px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'myBlue.700'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
)}
<Box
className="edit"
display={'none'}
position={'absolute'}
right={0}
top={0}
bottom={0}
w={'40px'}
bg={'rgba(255,255,255,0.9)'}
alignItems={'center'}
justifyContent={'center'}
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
>
<MyIcon
name={'edit'}
w={'18px'}
h={'18px'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{
color: 'myBlue.700'
}}
onClick={() => onclickEdit(item)}
/>
</Box>
</Box>
))}
</ModalBody>
@@ -129,13 +124,10 @@ const QuoteModal = ({
{editDataItem && (
<InputDataModal
onClose={() => setEditDataItem(undefined)}
onSuccess={() => onUpdateQuote(editDataItem.id)}
onDelete={() => onUpdateQuote(editDataItem.id, '已删除')}
kbId={editDataItem.kb_id}
defaultValues={{
...editDataItem,
dataId: editDataItem.id
}}
onSuccess={() => onUpdateQuote(editDataItem.dataId, '手动修改')}
onDelete={() => onUpdateQuote(editDataItem.dataId, '已删除')}
kbId={editDataItem.kbId}
defaultValues={editDataItem}
/>
)}
</>

View File

@@ -1,55 +0,0 @@
import React from 'react';
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useRequest } from '@/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { userUpdateChatFeedback } from '@/api/chat';
const ReadFeedbackModal = ({
chatItemId,
content,
isMarked,
onMark,
onSuccess,
onClose
}: {
chatItemId: string;
content: string;
isMarked: boolean;
onMark: () => void;
onSuccess: () => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { mutate, isLoading } = useRequest({
mutationFn: async () => {
return userUpdateChatFeedback({
chatItemId,
userFeedback: undefined
});
},
onSuccess() {
onSuccess();
},
errorToast: t('chat.Feedback Update Failed')
});
return (
<MyModal isOpen={true} onClose={onClose} title={t('chat.Feedback Modal')}>
<ModalBody>{content}</ModalBody>
<ModalFooter>
{!isMarked && (
<Button variant={'base'} mr={2} onClick={onMark}>
{t('chat.Feedback Mark')}
</Button>
)}
<Button isLoading={isLoading} onClick={mutate}>
{t('chat.Feedback Close')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(ReadFeedbackModal);

View File

@@ -1,17 +1,14 @@
import React, { useCallback, useMemo, useState } from 'react';
import { ChatModuleEnum } from '@/constants/chat';
import { ChatHistoryItemResType, ChatItemType, QuoteItemType } from '@/types/chat';
import { Flex, BoxProps, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useGlobalStore } from '@/store/global';
import { Flex, BoxProps } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import Tag from '../Tag';
import MyTooltip from '../MyTooltip';
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr: false });
const ResponseTags = ({
const ResponseDetailModal = ({
chatId,
contentId,
responseData = []
@@ -20,38 +17,36 @@ const ResponseTags = ({
contentId?: string;
responseData?: ChatHistoryItemResType[];
}) => {
const { isPc } = useGlobalStore();
const { t } = useTranslation();
const [quoteModalData, setQuoteModalData] = useState<QuoteItemType[]>();
const [contextModalData, setContextModalData] = useState<ChatItemType[]>();
const {
isOpen: isOpenWholeModal,
onOpen: onOpenWholeModal,
onClose: onCloseWholeModal
} = useDisclosure();
const {
tokens = 0,
quoteList = [],
completeMessages = [],
tokens = 0
completeMessages = []
} = useMemo(() => {
const chatData = responseData.find((item) => item.moduleName === ChatModuleEnum.AIChat);
if (!chatData) return {};
return {
tokens: chatData.tokens,
quoteList: chatData.quoteList,
completeMessages: chatData.completeMessages,
tokens: responseData.reduce((sum, item) => sum + (item.tokens || 0), 0)
completeMessages: chatData.completeMessages
};
}, [responseData]);
const updateQuote = useCallback(async (quoteId: string, sourceText?: string) => {}, []);
const isEmpty = useMemo(
() => quoteList.length === 0 && completeMessages.length === 0 && tokens === 0,
[completeMessages.length, quoteList.length, tokens]
);
const updateQuote = useCallback(async (quoteId: string, sourceText: string) => {}, []);
const TagStyles: BoxProps = {
mr: 2,
bg: 'transparent'
};
return responseData.length === 0 ? null : (
return isEmpty ? null : (
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
{quoteList.length > 0 && (
<MyTooltip label="查看引用">
@@ -77,17 +72,11 @@ const ResponseTags = ({
</Tag>
</MyTooltip>
)}
{isPc && tokens > 0 && (
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
{tokens}Tokens
{tokens > 0 && (
<Tag colorSchema="gray" cursor={'default'} {...TagStyles}>
{tokens}tokens
</Tag>
)}
<MyTooltip label={'点击查看完整响应值'}>
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
{t('chat.Complete Response')}
</Tag>
</MyTooltip>
{!!quoteModalData && (
<QuoteModal
rawSearch={quoteModalData}
@@ -98,11 +87,8 @@ const ResponseTags = ({
{!!contextModalData && (
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
)}
{isOpenWholeModal && (
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
)}
</Flex>
);
};
export default ResponseTags;
export default ResponseDetailModal;

View File

@@ -1,117 +0,0 @@
import React, { useRef, useState } from 'react';
import {
ModalBody,
useTheme,
ModalFooter,
Button,
ModalHeader,
Box,
Card,
Flex
} from '@chakra-ui/react';
import MyModal from '../MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useDatasetStore } from '@/store/dataset';
import { useToast } from '@/hooks/useToast';
import Avatar from '../Avatar';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
const SelectDataset = ({
onSuccess,
onClose
}: {
onSuccess: (kbId: string) => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const { isPc } = useGlobalStore();
const { toast } = useToast();
const { myKbList, loadKbList } = useDatasetStore();
const [selectedId, setSelectedId] = useState<string>();
useQuery(['loadKbList'], () => loadKbList());
return (
<MyModal isOpen={true} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered={!isPc}>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
<Box>{t('chat.Select Mark Kb')}</Box>
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
{t('chat.Select Mark Kb Desc')}
</Box>
</ModalHeader>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
userSelect={'none'}
>
{myKbList.map((item) =>
(() => {
const selected = selectedId === item._id;
return (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
_hover={{
boxShadow: 'md'
}}
{...(selected
? {
bg: 'myBlue.300'
}
: {})}
onClick={() => {
setSelectedId(item._id);
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
{item.name}
</Box>
</Flex>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{item.vectorModel.name}</Box>
</Flex>
</Card>
);
})()
)}
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={2} onClick={onClose}>
{t('Cancel')}
</Button>
<Button
onClick={() => {
if (!selectedId) {
return toast({
status: 'warning',
title: t('Select value is empty')
});
}
onSuccess(selectedId);
}}
>
{t('Confirm')}
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default SelectDataset;

View File

@@ -1,71 +0,0 @@
import React, { useMemo } from 'react';
import { Box, ModalBody, useTheme, Flex } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@/types/chat';
import { useTranslation } from 'react-i18next';
import MyModal from '../MyModal';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
const ResponseModal = ({
response,
onClose
}: {
response: ChatHistoryItemResType[];
onClose: () => void;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const formatResponse = useMemo(
() =>
response.map((item) => {
const copy = { ...item };
delete copy.completeMessages;
delete copy.quoteList;
return copy;
}),
[response]
);
return (
<MyModal
isOpen={true}
onClose={onClose}
h={['90vh', '80vh']}
minW={['90vw', '600px']}
title={
<Flex alignItems={'center'}>
{t('chat.Complete Response')}
<MyTooltip
label={
'moduleName: 模型名\nprice: 价格倍率100000\nmodel?: 模型名\ntokens?: token 消耗\n\nanswer?: 回答内容\nquestion?: 问题\ntemperature?: 温度\nmaxToken?: 最大 tokens\n\nsimilarity?: 相似度\nlimit?: 单次搜索结果\n\ncqList?: 问题分类列表\ncqResult?: 分类结果\n\nextractDescription?: 内容提取描述\nextractResult?: 提取结果'
}
>
<QuestionOutlineIcon ml={2} />
</MyTooltip>
</Flex>
}
isCentered
>
<ModalBody>
{formatResponse.map((item, i) => (
<Box
key={i}
p={2}
pt={[0, 2]}
borderRadius={'lg'}
border={theme.borders.base}
_notLast={{ mb: 2 }}
position={'relative'}
whiteSpace={'pre-wrap'}
>
{JSON.stringify(item, null, 2)}
</Box>
))}
</ModalBody>
</MyModal>
);
};
export default ResponseModal;

View File

@@ -25,7 +25,8 @@ import {
} from '@/utils/tools';
import { Box, Card, Flex, Input, Textarea, Button, useTheme, BoxProps } from '@chakra-ui/react';
import { feConfigs } from '@/store/static';
import { event } from '@/utils/plugin/eventbus';
import { Types } from 'mongoose';
import { EventNameEnum } from '../Markdown/constant';
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { useMarkdown } from '@/hooks/useMarkdown';
@@ -37,10 +38,8 @@ import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat';
import { useTranslation } from 'react-i18next';
import { customAlphabet } from 'nanoid';
import { userUpdateChatFeedback, adminUpdateChatFeedback } from '@/api/chat';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
@@ -48,21 +47,13 @@ import Markdown from '@/components/Markdown';
import MySelect from '@/components/Select';
import MyTooltip from '../MyTooltip';
import dynamic from 'next/dynamic';
const ResponseTags = dynamic(() => import('./ResponseTags'));
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
const ReadFeedbackModal = dynamic(() => import('./ReadFeedbackModal'));
const SelectDataset = dynamic(() => import('./SelectDataset'));
const InputDataModal = dynamic(() => import('@/pages/kb/detail/components/InputDataModal'));
const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
import styles from './index.module.scss';
import Script from 'next/script';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
const textareaMinH = '22px';
type generatingMessageProps = { text?: string; name?: string; status?: 'running' | 'finish' };
export type StartChatFnProps = {
chatList: ChatSiteItemType[];
messages: MessageItemType[];
controller: AbortController;
variables: Record<string, any>;
@@ -76,12 +67,6 @@ export type ComponentRef = {
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
};
enum FeedbackTypeEnum {
user = 'user',
admin = 'admin',
hidden = 'hidden'
}
const VariableLabel = ({
required = false,
children
@@ -135,9 +120,6 @@ const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => {
const ChatBox = (
{
feedbackType = FeedbackTypeEnum.hidden,
showMarkIcon = false,
showVoiceIcon = true,
showEmptyIntro = false,
chatId,
appAvatar,
@@ -148,9 +130,6 @@ const ChatBox = (
onStartChat,
onDelMessage
}: {
feedbackType?: `${FeedbackTypeEnum}`;
showMarkIcon?: boolean;
showVoiceIcon?: boolean;
showEmptyIntro?: boolean;
chatId?: string;
appAvatar?: string;
@@ -158,7 +137,7 @@ const ChatBox = (
variableModules?: VariableItemType[];
welcomeText?: string;
onUpdateVariable?: (e: Record<string, any>) => void;
onStartChat?: (e: StartChatFnProps) => Promise<{
onStartChat: (e: StartChatFnProps) => Promise<{
responseText: string;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType[];
}>;
@@ -179,19 +158,6 @@ const ChatBox = (
const [refresh, setRefresh] = useState(false);
const [variables, setVariables] = useState<Record<string, any>>({});
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
const [feedbackId, setFeedbackId] = useState<string>();
const [readFeedbackData, setReadFeedbackData] = useState<{
chatItemId: string;
content: string;
isMarked: boolean;
}>();
const [adminMarkData, setAdminMarkData] = useState<{
kbId?: string;
chatItemId: string;
dataId?: string;
q: string;
a: string;
}>();
const isChatting = useMemo(
() =>
@@ -199,10 +165,8 @@ const ChatBox = (
chatHistory[chatHistory.length - 1]?.status !== 'finish',
[chatHistory]
);
// compute variable input is finish.
const [variableInputFinish, setVariableInputFinish] = useState(false);
const variableIsFinish = useMemo(() => {
if (!variableModules || variableModules.length === 0 || chatHistory.length > 0) return true;
if (!variableModules || chatHistory.length > 0) return true;
for (let i = 0; i < variableModules.length; i++) {
const item = variableModules[i];
@@ -211,8 +175,8 @@ const ChatBox = (
}
}
return variableInputFinish;
}, [chatHistory.length, variableInputFinish, variableModules, variables]);
return true;
}, [chatHistory.length, variableModules, variables]);
const { register, reset, getValues, setValue, handleSubmit } = useForm<Record<string, any>>({
defaultValues: variables
@@ -271,7 +235,8 @@ const ChatBox = (
// 复制内容
const onclickCopy = useCallback(
(value: string) => {
copyData(value);
const val = value.replace(/\n+/g, '\n');
copyData(val);
},
[copyData]
);
@@ -294,8 +259,7 @@ const ChatBox = (
* user confirm send prompt
*/
const sendPrompt = useCallback(
async (variables: Record<string, any> = {}, inputVal = '', history = chatHistory) => {
if (!onStartChat) return;
async (variables: Record<string, any> = {}, inputVal = '') => {
if (isChatting) {
toast({
title: '正在聊天中...请等待结束',
@@ -304,7 +268,7 @@ const ChatBox = (
return;
}
// get input value
const val = inputVal.trim();
const val = inputVal.trim().replace(/\n\s*/g, '\n');
if (!val) {
toast({
@@ -315,15 +279,15 @@ const ChatBox = (
}
const newChatList: ChatSiteItemType[] = [
...history,
...chatHistory,
{
dataId: nanoid(),
_id: String(new Types.ObjectId()),
obj: 'Human',
value: val,
status: 'finish'
},
{
dataId: nanoid(),
_id: String(new Types.ObjectId()),
obj: 'AI',
value: '',
status: 'loading'
@@ -347,11 +311,13 @@ const ChatBox = (
const messages = adaptChatItem_openAI({ messages: newChatList, reserveId: true });
const { responseData } = await onStartChat({
chatList: newChatList,
messages,
controller: abortSignal,
generatingMessage,
variables
variables: {
...getDefaultChatVariables(),
...variables
}
});
// set finish status
@@ -408,23 +374,6 @@ const ChatBox = (
]
);
// retry input
const retryInput = useCallback(
async (index: number) => {
if (!onDelMessage) return;
const delHistory = chatHistory.slice(index);
setChatHistory((state) => (index === 0 ? [] : state.slice(0, index)));
await Promise.all(
delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i }))
);
sendPrompt(variables, delHistory[0].value, chatHistory.slice(0, index));
},
[chatHistory, onDelMessage, sendPrompt, variables]
);
// output data
useImperativeHandle(ref, () => ({
getChatHistory: () => chatHistory,
resetVariables(e) {
@@ -437,7 +386,6 @@ const ChatBox = (
setVariables(e || defaultVal);
},
resetHistory(e) {
setVariableInputFinish(!!e.length);
setChatHistory(e);
},
scrollToBottom
@@ -453,20 +401,13 @@ const ChatBox = (
border: theme.borders.base,
mr: 3
};
const controlContainerStyle = useCallback((status: ChatSiteItemType['status']) => {
return {
className: 'control',
color: 'myGray.400',
display:
status === 'finish'
? feedbackType === FeedbackTypeEnum.admin
? 'flex'
: ['flex', 'none']
: 'none',
pl: 1,
mt: 2
};
}, []);
const controlContainerStyle = {
className: 'control',
color: 'myGray.400',
display: ['flex', 'none'],
pl: 1,
mt: 2
};
const MessageCardStyle: BoxProps = {
px: 4,
py: 3,
@@ -487,7 +428,7 @@ const ChatBox = (
);
const statusBoxData = useMemo(() => {
const colorMap = {
loading: 'myGray.700',
loading: '#67c13b',
running: '#67c13b',
finish: 'myBlue.600'
};
@@ -501,7 +442,6 @@ const ChatBox = (
};
}, [chatHistory, isChatting, t]);
// page change and abort request
useEffect(() => {
return () => {
controller.current?.abort('leave');
@@ -510,7 +450,6 @@ const ChatBox = (
};
}, [router.query]);
// page destroy and abort request
useEffect(() => {
const listen = () => {
cancelBroadcast();
@@ -522,24 +461,10 @@ const ChatBox = (
};
}, []);
// add guide text listener
useEffect(() => {
event.on('guideClick', ({ text }: { text: string }) => {
if (!text) return;
handleSubmit((data) => sendPrompt(data, text))();
});
return () => {
event.off('guideClick');
};
}, [handleSubmit, sendPrompt]);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
<Box maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
{showEmpty && <Empty />}
{!!welcomeText && (
@@ -548,7 +473,15 @@ const ChatBox = (
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Card order={2} mt={2} {...MessageCardStyle} bg={'white'} maxW={messageCardMaxW}>
<Markdown source={`~~~guide \n${welcomeText}`} isChatting={false} />
<Markdown
source={`~~~guide \n${welcomeText}`}
isChatting={false}
onClick={(e) => {
const val = e?.data;
if (e?.event !== EventNameEnum.guideClick || !val) return;
handleSubmit((data) => sendPrompt(data, val))();
}}
/>
</Card>
</Flex>
)}
@@ -585,9 +518,6 @@ const ChatBox = (
label: item.value,
value: item.value
}))}
{...register(item.key, {
required: item.required
})}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
@@ -606,7 +536,6 @@ const ChatBox = (
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
setVariableInputFinish(true);
})}
>
{'开始对话'}
@@ -621,7 +550,7 @@ const ChatBox = (
{chatHistory.map((item, index) => (
<Flex
position={'relative'}
key={item.dataId}
key={item._id}
flexDirection={'column'}
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
py={5}
@@ -634,12 +563,8 @@ const ChatBox = (
{item.obj === 'Human' && (
<>
<Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
<Flex
{...controlContainerStyle(item.status)}
justifyContent={'flex-end'}
mr={3}
>
<MyTooltip label={t('common.Copy')}>
<Flex {...controlContainerStyle} justifyContent={'flex-end'} mr={3}>
<MyTooltip label={'复制'}>
<MyIcon
{...controlIconStyle}
name={'copy'}
@@ -647,18 +572,8 @@ const ChatBox = (
onClick={() => onclickCopy(item.value)}
/>
</MyTooltip>
{!!onDelMessage && (
<MyTooltip label={t('chat.retry')}>
<MyIcon
{...controlIconStyle}
name={'retryLight'}
_hover={{ color: 'green.500' }}
onClick={() => retryInput(index)}
/>
</MyTooltip>
)}
{onDelMessage && (
<MyTooltip label={t('common.Delete')}>
<MyTooltip label={'删除'}>
<MyIcon
{...controlIconStyle}
mr={0}
@@ -666,10 +581,10 @@ const ChatBox = (
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat.dataId !== item.dataId)
state.filter((chat) => chat._id !== item._id)
);
onDelMessage({
contentId: item.dataId,
contentId: item._id,
index
});
}}
@@ -696,7 +611,7 @@ const ChatBox = (
<>
<Flex w={'100%'} alignItems={'flex-end'}>
<ChatAvatar src={appAvatar} type={'AI'} />
<Flex {...controlContainerStyle(item.status)} ml={3}>
<Flex {...controlContainerStyle} ml={3}>
<MyTooltip label={'复制'}>
<MyIcon
{...controlIconStyle}
@@ -713,17 +628,17 @@ const ChatBox = (
_hover={{ color: 'red.600' }}
onClick={() => {
setChatHistory((state) =>
state.filter((chat) => chat.dataId !== item.dataId)
state.filter((chat) => chat._id !== item._id)
);
onDelMessage({
contentId: item.dataId,
contentId: item._id,
index
});
}}
/>
</MyTooltip>
)}
{showVoiceIcon && hasVoiceApi && (
{hasVoiceApi && (
<MyTooltip label={'语音播报'}>
<MyIcon
{...controlIconStyle}
@@ -733,92 +648,7 @@ const ChatBox = (
/>
</MyTooltip>
)}
{/* admin mark icon */}
{showMarkIcon && (
<MyTooltip label={t('chat.Mark')}>
<MyIcon
{...controlIconStyle}
name={'markLight'}
_hover={{ color: '#67c13b' }}
onClick={() => {
if (!item.dataId) return;
if (item.adminFeedback) {
setAdminMarkData({
chatItemId: item.dataId,
kbId: item.adminFeedback.kbId,
dataId: item.adminFeedback.dataId,
q: chatHistory[index - 1]?.value || '',
a: item.adminFeedback.content
});
} else {
setAdminMarkData({
chatItemId: item.dataId,
q: chatHistory[index - 1]?.value || '',
a: item.value
});
}
}}
/>
</MyTooltip>
)}
{feedbackType === FeedbackTypeEnum.admin && (
<MyTooltip label={t('chat.Read User Feedback')}>
<MyIcon
display={item.userFeedback ? 'block' : 'none'}
{...controlIconStyle}
color={'white'}
bg={'#FC9663'}
fontWeight={'bold'}
name={'badLight'}
onClick={() =>
setReadFeedbackData({
chatItemId: item.dataId || '',
content: item.userFeedback || '',
isMarked: !!item.adminFeedback
})
}
/>
</MyTooltip>
)}
{feedbackType === FeedbackTypeEnum.user && (
<MyTooltip
label={
item.userFeedback
? `取消反馈。\n您当前反馈内容为:\n${item.userFeedback}`
: '反馈'
}
>
<MyIcon
{...controlIconStyle}
{...(!!item.userFeedback
? {
color: 'white',
bg: '#FC9663',
fontWeight: 'bold',
onClick: () => {
if (!item.dataId) return;
setChatHistory((state) =>
state.map((chatItem) =>
chatItem.dataId === item.dataId
? { ...chatItem, userFeedback: undefined }
: chatItem
)
);
try {
userUpdateChatFeedback({ chatItemId: item.dataId });
} catch (error) {}
}
}
: {
_hover: { color: '#FB7C3C' },
onClick: () => setFeedbackId(item.dataId)
})}
name={'badLight'}
/>
</MyTooltip>
)}
</Flex>
{/* chatting status */}
{statusBoxData && index === chatHistory.length - 1 && (
<Flex
ml={3}
@@ -848,24 +678,11 @@ const ChatBox = (
source={item.value}
isChatting={index === chatHistory.length - 1 && isChatting}
/>
<ResponseTags
<ResponseDetailModal
chatId={chatId}
contentId={item.dataId}
contentId={item._id}
responseData={item.responseData}
/>
{/* admin mark content */}
{showMarkIcon && item.adminFeedback && (
<Box>
<Flex alignItems={'center'} py={2}>
<MyIcon name={'markLight'} w={'14px'} color={'myGray.900'} />
<Box ml={2} color={'myGray.500'}>
{t('chat.Admin Mark Content')}
</Box>
<Box h={'1px'} bg={'myGray.300'} flex={'1'} />
</Flex>
<Box>{item.adminFeedback.content}</Box>
</Box>
)}
</Card>
</Box>
</>
@@ -876,7 +693,7 @@ const ChatBox = (
</Box>
</Box>
{/* input */}
{onStartChat && variableIsFinish ? (
{variableIsFinish ? (
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
<Box
py={'18px'}
@@ -960,115 +777,6 @@ const ChatBox = (
</Box>
</Box>
) : null}
{/* user feedback modal */}
{!!feedbackId && (
<FeedbackModal
chatItemId={feedbackId}
onClose={() => setFeedbackId(undefined)}
onSuccess={(content: string) => {
setChatHistory((state) =>
state.map((item) =>
item.dataId === feedbackId ? { ...item, userFeedback: content } : item
)
);
setFeedbackId(undefined);
}}
/>
)}
{/* admin read feedback modal */}
{!!readFeedbackData && (
<ReadFeedbackModal
{...readFeedbackData}
onClose={() => setReadFeedbackData(undefined)}
onMark={() => {
const index = chatHistory.findIndex(
(item) => item.dataId === readFeedbackData.chatItemId
);
if (index === -1) return setReadFeedbackData(undefined);
setAdminMarkData({
chatItemId: readFeedbackData.chatItemId,
q: chatHistory[index - 1]?.value || '',
a: chatHistory[index]?.value || ''
});
}}
onSuccess={() => {
setChatHistory((state) =>
state.map((chatItem) =>
chatItem.dataId === readFeedbackData.chatItemId
? { ...chatItem, userFeedback: undefined }
: chatItem
)
);
setReadFeedbackData(undefined);
}}
/>
)}
{/* select one dataset to insert markData */}
{adminMarkData && !adminMarkData.kbId && (
<SelectDataset
onClose={() => setAdminMarkData(undefined)}
// @ts-ignore
onSuccess={(kbId) => setAdminMarkData((state) => ({ ...state, kbId }))}
/>
)}
{/* edit markData modal */}
{adminMarkData && adminMarkData.kbId && (
<InputDataModal
onClose={() => setAdminMarkData(undefined)}
onSuccess={async (data) => {
if (!adminMarkData.kbId || !data.dataId) {
return setAdminMarkData(undefined);
}
const adminFeedback = {
kbId: adminMarkData.kbId,
dataId: data.dataId,
content: data.a
};
// update dom
setChatHistory((state) =>
state.map((chatItem) =>
chatItem.dataId === adminMarkData.chatItemId
? {
...chatItem,
adminFeedback
}
: chatItem
)
);
// request to update adminFeedback
try {
adminUpdateChatFeedback({
chatItemId: adminMarkData.chatItemId,
...adminFeedback
});
if (readFeedbackData) {
userUpdateChatFeedback({
chatItemId: readFeedbackData.chatItemId,
userFeedback: undefined
});
setChatHistory((state) =>
state.map((chatItem) =>
chatItem.dataId === readFeedbackData.chatItemId
? { ...chatItem, userFeedback: undefined }
: chatItem
)
);
setReadFeedbackData(undefined);
}
} catch (error) {}
setAdminMarkData(undefined);
}}
kbId={adminMarkData.kbId}
defaultValues={{
dataId: adminMarkData.dataId,
q: adminMarkData.q,
a: adminMarkData.a
}}
/>
)}
</Flex>
);
};

View File

@@ -1,8 +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="1694331723034"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5978"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M512 70.283C267.486 70.283 69.268 268.046 69.268 512S267.486 953.717 512 953.717 954.732 755.954 954.732 512 756.514 70.283 512 70.283m223.045 488.321H558.603v176.442c0 25.738-20.866 46.604-46.604 46.604s-46.604-20.866-46.604-46.604V558.604H288.953c-25.738 0-46.604-20.866-46.604-46.604s20.866-46.604 46.604-46.604h176.442V288.954c0-25.738 20.866-46.604 46.604-46.604s46.604 20.866 46.604 46.604v176.442h176.442c25.738 0 46.604 20.866 46.604 46.604s-20.866 46.604-46.604 46.604z"
p-id="5979"></path>
</svg>

Before

Width:  |  Height:  |  Size: 867 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -1,14 +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="1692867122828"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4021"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M269.844659 81.4308h44.821057v166.626082h-44.821057zM677.140966 491.719232c52.335426 0 102.092273 19.937769 140.105639 56.13883 38.126482 36.31053 60.461599 85.284073 62.891788 137.900467 2.5056 54.276658-16.27424 106.280032-52.881549 146.431672-36.60731 40.15164-86.65972 63.643469-140.936379 66.150285-3.180653 0.147174-6.401444 0.221369-9.576016 0.221369-52.341508 0-102.102004-19.936552-140.114153-56.136398-38.126482-36.309314-60.461599-85.284073-62.891789-137.902899-2.5056-54.276658 16.27424-106.280032 52.88155-146.431672 36.60731-40.15164 86.65972-63.643469 140.936379-66.149069a208.122961 208.122961 0 0 1 9.576016-0.221369h0.008514m-0.00973-44.822274c-3.859355 0-7.746684 0.088791-11.642528 0.268805-136.951744 6.3236-242.847422 122.470346-236.525038 259.422091 6.143586 133.0559 115.942406 236.793842 247.779562 236.793842 3.859355 0 7.747901-0.088791 11.642529-0.268804 136.951744-6.322384 242.847422-122.470346 236.525037-259.422091-6.143586-133.057117-115.942406-236.798708-247.779562-236.793843z"
p-id="4022"></path>
<path
d="M490.264524 891.110734a272.361206 272.361206 0 0 1-32.682275-37.369937H180.453104c-20.912034 0-37.927007-17.013757-37.927007-37.92579v-590.263526c0-20.912034 17.013757-37.927007 37.927007-37.927007H732.799354c20.912034 0 37.925791 17.013757 37.925791 37.927007V441.15597a268.605238 268.605238 0 0 1 44.821057 21.463023V225.551481c0-45.70045-37.047614-82.746848-82.746848-82.746849H180.453104c-45.70045 0-82.746848 37.047614-82.746848 82.746849v590.263526c0 45.70045 37.047614 82.746848 82.746848 82.746848h317.980164a273.587248 273.587248 0 0 1-8.168744-7.451121z"
p-id="4023"></path>
<path
d="M770.725145 489.61623a225.243754 225.243754 0 0 1 44.821057 27.231985v-0.21407a225.182938 225.182938 0 0 0-44.821057-27.114003v0.096088zM812.590566 778.530212H646.820768V576.105667h44.821057v157.604704h120.948741zM209.55091 380.121489h498.255687v44.821057H209.55091zM600.682445 81.4308h44.821058v166.626082h-44.821058zM406.842623 712.17437H209.55091v44.821057h203.864657a272.351476 272.351476 0 0 1-6.572944-44.821057zM450.941192 546.147929H209.55091v44.821057h217.435038a268.707408 268.707408 0 0 1 23.955244-44.821057z"
p-id="4024"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693123058703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16328" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M875.65 912h-380a36 36 0 0 1 0-72h380a36 36 0 1 1 0 72zM812.26 284.82L285.39 811.69l-88.11 15.1L212 738l526.72-526.72 73.54 73.54m90.51-11.31L750 120.77a16 16 0 0 0-22.62 0L152.5 695.68a34.11 34.11 0 0 0-9.5 18.56l-25.95 156.23a32 32 0 0 0 37 36.78l155.38-26.62a34.2 34.2 0 0 0 18.38-9.52l575-575a16 16 0 0 0 0-22.63z" p-id="16329"></path></svg>

Before

Width:  |  Height:  |  Size: 677 B

View File

@@ -1,8 +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="1692522401431"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4063"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M855 789.1V127.4L633 274.9H169V641h81l59.5 255.7H508L448.4 641H633l222 148.1z m-46-86l-146-97.5V310.2l146-97v489.9zM450 850.6H346L297.1 641h104L450 850.6zM239.2 595H215V320.9h274V595H239.2z m377.8 0h-82V320.9h82V595z"
p-id="4064"></path>
</svg>

Before

Width:  |  Height:  |  Size: 605 B

View File

@@ -1,8 +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="1694067364830"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5118"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path
d="M727.950222 274.773333l-55.296-9.329777a38.741333 38.741333 0 0 0-12.856889 76.344888l193.308445 32.597334c1.991111 0.113778 1.991111 0.113778 2.844444 0 2.275556 0.227556 4.266667 0.113778 7.850667-0.284445l0.682667-0.056889a28.216889 28.216889 0 0 0 5.632-0.967111c1.080889 0 1.080889 0 3.185777-0.568889a15.530667 15.530667 0 0 0 4.039111-2.332444l1.137778-0.796444 0.796445-0.398223a28.444444 28.444444 0 0 0 4.152889-2.730666 37.091556 37.091556 0 0 0 6.542222-6.826667l0.796444-0.967111c1.080889-1.422222 1.080889-1.422222 2.161778-3.128889a37.432889 37.432889 0 0 0 3.697778-9.557333c0.568889-1.194667 0.568889-1.194667 1.137778-3.128889 0.113778-1.763556 0.113778-1.763556 0-2.503111v0.910222a36.579556 36.579556 0 0 0-0.341334-10.24l-0.113778-0.967111a22.755556 22.755556 0 0 0-0.682666-3.982222c0-1.080889 0-1.080889-0.568889-3.128889l-68.494222-183.751111a38.798222 38.798222 0 0 0-49.777778-22.755556 38.798222 38.798222 0 0 0-22.755556 49.777778l16.270223 43.804444A397.880889 397.880889 0 0 0 512 113.777778C292.408889 113.777778 113.777778 292.408889 113.777778 512s178.631111 398.222222 398.222222 398.222222 398.222222-178.631111 398.222222-398.222222a38.684444 38.684444 0 1 0-77.368889 0c0 176.924444-143.928889 320.853333-320.853333 320.853333S191.146667 688.924444 191.146667 512 335.075556 191.146667 512 191.146667c80.099556 0 157.070222 29.980444 215.950222 83.626666z"
p-id="5119"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24">
<path d="M8.3 5.7a1 1 0 011.4-1.4l7.71 7.7-7.7 7.7a1 1 0 11-1.42-1.4l6.3-6.3-6.3-6.3z" fill-rule="nonzero"></path>
</svg>

Before

Width:  |  Height:  |  Size: 174 B

View File

@@ -1,8 +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="1694224177076"
class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3984"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64.125" height="64">
<path
d="M989.365124 873.455175c21.85764 24.973422 33.760499 46.831061 35.714294 65.567202 1.948078 18.730424-5.271103 37.076377-21.661831 55.026425-18.736141 21.075836-39.416072 31.030616-62.055515 29.858625-22.633727-1.171991-44.491366-10.344968-65.567202-27.513213L679.093265 806.715982c-35.128298 22.633727-72.786383 40.197876-112.989976 52.68673-40.197876 12.488855-82.545355 18.730424-127.036721 18.730424-60.882095 0-117.863745-11.512672-170.940663-34.536586-53.078347-23.029631-99.523509-54.446147-139.331197-94.252406-39.811976-39.811976-71.228492-86.25285-94.252406-139.329768C11.512672 556.93603 0 499.950092 0 439.066568c0-60.883524 11.512672-117.863745 34.542303-170.940663 23.023914-53.078347 54.44043-99.523509 94.252406-139.331197 39.807688-39.811976 86.25285-71.228492 139.331197-94.252406 53.076918-23.029631 110.058568-34.542303 170.940663-34.542303 60.883524 0 117.869462 11.512672 170.94638 34.542303 53.078347 23.023914 99.517792 54.44043 139.329768 94.252406 39.807688 39.807688 71.222775 86.25285 94.252406 139.331197 23.023914 53.076918 34.536586 110.057139 34.536586 170.940663 0 46.054974-6.633185 89.764536-19.903844 131.134403s-32.002511 79.619664-56.198417 114.742246l38.639985 38.639985c18.730424 18.730424 38.439889 38.249797 59.124108 58.543829s39.61188 39.416072 56.784413 57.371837C973.754771 857.448917 984.680017 868.771497 989.365124 873.455175L989.365124 873.455175zM443.751675 731.779995c40.588063 0 78.83786-7.609369 114.742246-22.829535 35.904385-15.224454 67.13081-36.105911 93.66641-62.641511 26.541317-26.541317 47.422774-57.762025 62.641511-93.66641 15.218737-35.910102 22.835252-74.154183 22.835252-114.747963 0-40.589492-7.615086-78.832143-22.835252-114.742246-15.218737-35.905815-36.100194-67.125093-62.641511-93.667839-26.5356-26.5356-57.762025-47.415628-93.66641-62.641511-35.904385-15.218737-74.154183-22.828106-114.742246-22.828106-40.589492 0-78.83929 7.609369-114.743675 22.828106-35.904385 15.225883-67.129381 36.105911-93.66641 62.641511-26.541317 26.542747-47.422774 57.762025-62.641511 93.667839-15.218737 35.910102-22.829535 74.152753-22.829535 114.742246 0 40.59378 7.610798 78.83786 22.829535 114.747963 15.218737 35.904385 36.100194 67.125093 62.641511 93.66641 26.53703 26.5356 57.762025 47.417057 93.66641 62.641511C364.912385 724.170627 403.162183 731.779995 443.751675 731.779995L443.751675 731.779995zM443.751675 731.779995"
p-id="3985"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -75,15 +75,7 @@ const map = {
outlink_iframe: require('./icons/outlink/iframe.svg').default,
addCircle: require('./icons/circle/add.svg').default,
playFill: require('./icons/fill/play.svg').default,
courseLight: require('./icons/light/course.svg').default,
promotionLight: require('./icons/light/promotion.svg').default,
logsLight: require('./icons/light/logs.svg').default,
badLight: require('./icons/light/bad.svg').default,
markLight: require('./icons/light/mark.svg').default,
retryLight: require('./icons/light/retry.svg').default,
rightArrowLight: require('./icons/light/rightArrow.svg').default,
searchLight: require('./icons/light/search.svg').default,
plusFill: require('./icons/fill/plus.svg').default
courseLight: require('./icons/light/course.svg').default
};
export type IconName = keyof typeof map;

View File

@@ -1,12 +1,21 @@
import React, { useState } from 'react';
import { Menu, MenuButton, MenuItem, MenuList, MenuButtonProps } from '@chakra-ui/react';
import { getLangStore, LangEnum, setLangStore, langMap } from '@/utils/i18n';
import { getLangStore, LangEnum, setLangStore } from '@/utils/i18n';
import MyIcon from '@/components/Icon';
import { useTranslation } from 'react-i18next';
import { useRouter } from 'next/router';
const langMap = {
[LangEnum.en]: {
label: 'English',
icon: 'language_en'
},
[LangEnum.zh]: {
label: '简体中文',
icon: 'language_zh'
}
};
const Language = (props: MenuButtonProps) => {
const router = useRouter();
const { i18n } = useTranslation();
const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore());
@@ -34,7 +43,6 @@ const Language = (props: MenuButtonProps) => {
setLangStore(lang);
setLanguage(lang);
i18n?.changeLanguage?.(lang);
router.reload();
}}
>
{lang.label}

View File

@@ -106,13 +106,7 @@ const Navbar = ({ unread }: { unread: number }) => {
cursor={'pointer'}
onClick={() => router.push('/account')}
>
<Avatar
w={'36px'}
h={'36px'}
borderRadius={'none'}
src={userInfo?.avatar}
fallbackSrc={HUMAN_ICON}
/>
<Avatar w={'36px'} h={'36px'} src={userInfo?.avatar} fallbackSrc={HUMAN_ICON} />
</Box>
{/* 导航列表 */}
<Box flex={1}>
@@ -167,20 +161,20 @@ const Navbar = ({ unread }: { unread: number }) => {
</Link>
</Box>
)}
{feConfigs?.show_doc && (
<MyTooltip label={t('home.Docs')} placement={'right-end'}>
<Box
{...itemStyles}
mb={0}
color={'#9096a5'}
onClick={() => {
window.open(`https://doc.fastgpt.run/docs/intro`);
}}
>
<MyTooltip label={t('home.Docs')} placement={'right-end'}>
<Box
{...itemStyles}
mb={0}
color={'#9096a5'}
onClick={() => {
window.open(`https://doc.fastgpt.run/docs/intro`);
}}
>
<Badge count={unread}>
<MyIcon name={'courseLight'} width={'26px'} height={'26px'} />
</Box>
</MyTooltip>
)}
</Badge>
</Box>
</MyTooltip>
<Language {...itemStyles} />
{feConfigs?.show_git && (
<MyTooltip label={`Git Star: ${gitStar}`} placement={'right-end'}>

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Spinner, Flex, Box } from '@chakra-ui/react';
import { Spinner, Flex } from '@chakra-ui/react';
const Loading = ({ fixed = true, text = '' }: { fixed?: boolean; text?: string }) => {
const Loading = ({ fixed = true }: { fixed?: boolean }) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
@@ -13,14 +13,8 @@ const Loading = ({ fixed = true, text = '' }: { fixed?: boolean; text?: string }
bottom={0}
alignItems={'center'}
justifyContent={'center'}
flexDirection={'column'}
>
<Spinner thickness="4px" speed="0.65s" emptyColor="myGray.100" color="myBlue.600" size="xl" />
{text && (
<Box mt={2} color="myBlue.700" fontWeight={'bold'}>
{text}
</Box>
)}
</Flex>
);
};

View File

@@ -0,0 +1,32 @@
import React, { useMemo } from 'react';
import { Box } from '@chakra-ui/react';
import Image from './img/Image';
const regex = /((http|https|ftp):\/\/[^\s\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]+)/gi;
const Link = (props: { href?: string; children?: any }) => {
const Html = useMemo(() => {
const decText = decodeURIComponent(props.href || '');
return decText.replace(regex, (match, p1) => {
let text = decText === props.children?.[0] ? p1 : props.children?.[0];
const isInternal = /^\/#/i.test(p1);
const target = isInternal ? '_self' : '_blank';
if (props?.children?.[0]?.props?.node?.tagName === 'img') {
// eslint-disable-next-line @next/next/no-img-element
text = `<img src="${props?.children?.[0]?.props?.src}" />`;
}
return `<a href="${p1}" target=${target}>${text}</a>`;
});
}, [props.children, props.href]);
return typeof Html === 'string' ? (
<Box as={'span'} dangerouslySetInnerHTML={{ __html: Html }} />
) : (
Html
);
};
export default React.memo(Link);

View File

@@ -4,37 +4,13 @@ import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm';
import RemarkMath from 'remark-math';
import RehypeKatex from 'rehype-katex';
import { event } from '@/utils/plugin/eventbus';
import 'katex/dist/katex.min.css';
import styles from '../index.module.scss';
import Image from '../img/Image';
import { EventNameEnum } from '../constant';
function Link(e: any) {
const href = e.href;
const text = String(e.children);
return (
<Box as={'li'} py={1} m={0}>
<Box
as={'span'}
color={'blue.600'}
textDecoration={'underline'}
cursor={'pointer'}
onClick={() => {
if (href) {
return window.open(href, '_blank');
}
event.emit('guideClick', { text });
}}
>
{text}
</Box>
</Box>
);
}
const Guide = ({ text }: { text: string }) => {
const formatText = useMemo(() => text.replace(/\[(.*?)\]($|\n)/g, '[$1]()\n'), [text]);
const Guide = ({ text, onClick }: { text: string; onClick?: (e: any) => void }) => {
const formatText = useMemo(() => text.replace(/\[(.*?)\]/g, '[$1]()'), [text]);
return (
<ReactMarkdown
@@ -42,8 +18,27 @@ const Guide = ({ text }: { text: string }) => {
remarkPlugins={[RemarkGfm, RemarkMath]}
rehypePlugins={[RehypeKatex]}
components={{
a: Link,
img: Image
a({ children }: any) {
return (
<Box as={'li'} py={1} m={0}>
<Box
as={'span'}
color={'blue.600'}
textDecoration={'underline'}
cursor={'pointer'}
onClick={() => {
if (!onClick) return;
onClick({
event: EventNameEnum.guideClick,
data: String(children)
});
}}
>
{String(children)}
</Box>
</Box>
);
}
}}
>
{formatText}

View File

@@ -0,0 +1,3 @@
export enum EventNameEnum {
guideClick = 'guideClick'
}

View File

@@ -1,64 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import * as echarts from 'echarts';
import type { ECharts } from 'echarts';
import { Box, Skeleton } from '@chakra-ui/react';
const EChartsCodeBlock = ({ code }: { code: string }) => {
const chartRef = useRef<HTMLDivElement>(null);
const eChart = useRef<ECharts>();
const [option, setOption] = useState<any>();
const [width, setWidth] = useState(400);
useEffect(() => {
const clientWidth = document.getElementById('chat-container')?.clientWidth || 500;
setWidth(clientWidth * 0.9);
setTimeout(() => {
eChart.current?.resize();
}, 100);
}, []);
useEffect(() => {
let option;
try {
option = JSON.parse(code.trim());
option = {
...option,
toolbox: {
show: true,
feature: {
saveAsImage: {}
}
}
};
setOption(option);
} catch (error) {}
if (!option) return;
(async () => {
// @ts-ignore
await import('echarts-gl');
})();
if (chartRef.current) {
eChart.current = echarts.init(chartRef.current);
eChart.current.setOption(option);
eChart.current?.resize();
}
return () => {
if (eChart.current) {
eChart.current.dispose();
}
};
}, [code]);
return (
<Box overflowX={'auto'}>
<Box h={'400px'} minW={'400px'} w={`${width}px`} ref={chartRef} />
{!option && <Skeleton isLoaded={true} fadeDuration={2} h={'400px'} w={`400px`}></Skeleton>}
</Box>
);
};
export default EChartsCodeBlock;

View File

@@ -22,7 +22,6 @@ const MdImage = ({ src }: { src?: string }) => {
fallbackStrategy={'onError'}
cursor={succeed ? 'pointer' : 'default'}
loading="eager"
objectFit={'contain'}
onLoad={() => {
setIsLoading(false);
setSucceed(true);

View File

@@ -80,7 +80,7 @@ const MermaidBlock = ({ code }: { code: string }) => {
ctx.fillRect(0, 0, w, h);
const img = new Image();
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(ref.current?.innerHTML)}`;
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(ref.current.innerHTML)}`;
img.onload = () => {
ctx.drawImage(img, 0, 0, w, h);

View File

@@ -334,9 +334,6 @@
background-color: transparent;
border: medium none;
}
.markdown hr {
margin: 10px 0;
}
.markdown {
text-align: justify;
tab-size: 4;

View File

@@ -9,14 +9,14 @@ import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
import dynamic from 'next/dynamic';
import Link from './Link';
import CodeLight from './CodeLight';
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'));
const MdImage = dynamic(() => import('./img/Image'));
const ChatGuide = dynamic(() => import('./chat/Guide'));
const EChartsCodeBlock = dynamic(() => import('./img/EChartsCodeBlock'));
function Code({ inline, className, children }: any) {
function Code({ inline, className, children, onClick }: any) {
const match = /language-(\w+)/.exec(className || '');
const codeType = match?.[1];
@@ -25,30 +25,38 @@ function Code({ inline, className, children }: any) {
}
if (codeType === 'guide') {
return <ChatGuide text={String(children)} />;
}
if (codeType === 'echarts') {
return <EChartsCodeBlock code={String(children)} />;
return <ChatGuide text={String(children)} onClick={onClick} />;
}
return (
<CodeLight className={className} inline={inline} match={match}>
{children}
</CodeLight>
);
}
function Image({ src }: { src?: string }) {
return <MdImage src={src} />;
}
const Markdown = ({ source, isChatting = false }: { source: string; isChatting?: boolean }) => {
const Markdown = ({
source,
isChatting = false,
onClick
}: {
source: string;
isChatting?: boolean;
onClick?: (e: any) => void;
}) => {
const components = useMemo(
() => ({
a: Link,
img: Image,
pre: 'div',
p: 'div',
code: Code
code: (props: any) => <Code {...props} onClick={onClick} />
}),
[]
[onClick]
);
return (
@@ -60,7 +68,6 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
rehypePlugins={[RehypeKatex]}
// @ts-ignore
components={components}
linkTarget={'_blank'}
>
{source}
</ReactMarkdown>

View File

@@ -1,28 +0,0 @@
import React from 'react';
import { Flex, Input, InputProps } from '@chakra-ui/react';
interface Props extends InputProps {
leftIcon?: React.ReactNode;
}
const MyInput = ({ leftIcon, ...props }: Props) => {
return (
<Flex position={'relative'} alignItems={'center'}>
<Input w={'100%'} pl={leftIcon ? '30px' : 3} {...props} />
{leftIcon && (
<Flex
alignItems={'center'}
position={'absolute'}
left={3}
w={'20px'}
zIndex={10}
transform={'translateY(1.5px)'}
>
{leftIcon}
</Flex>
)}
</Flex>
);
};
export default MyInput;

View File

@@ -1,51 +0,0 @@
import React from 'react';
import { Menu, MenuList, MenuItem } from '@chakra-ui/react';
interface Props {
width: number;
offset?: [number, number];
Button: React.ReactNode;
menuList: {
isActive?: boolean;
child: React.ReactNode;
onClick: () => void;
}[];
}
const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
const menuItemStyles = {
borderRadius: 'sm',
py: 3,
display: 'flex',
alignItems: 'center',
_hover: {
backgroundColor: 'myWhite.600',
color: 'hover.blue'
}
};
return (
<Menu offset={offset} autoSelect={false} isLazy>
{Button}
<MenuList
minW={`${width}px !important`}
p={'6px'}
border={'1px solid #fff'}
boxShadow={'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'}
>
{menuList.map((item, i) => (
<MenuItem
key={i}
{...menuItemStyles}
onClick={item.onClick}
color={item.isActive ? 'hover.blue' : ''}
>
{item.child}
</MenuItem>
))}
</MenuList>
</Menu>
);
};
export default MyMenu;

View File

@@ -21,7 +21,7 @@ interface Props extends ButtonProps {
}
const MySelect = (
{ placeholder, value, width = '100%', list, onchange, ...props }: Props,
{ placeholder, value, width = 'auto', list, onchange, ...props }: Props,
selectRef: any
) => {
const ref = useRef<HTMLButtonElement>(null);
@@ -94,8 +94,6 @@ const MySelect = (
}
zIndex={99}
transform={'translateY(35px) !important'}
maxH={'40vh'}
overflowY={'auto'}
>
{list.map((item) => (
<MenuItem

View File

@@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import type { GridProps } from '@chakra-ui/react';
import MyIcon, { type IconName } from '../Icon';
import { useTranslation } from 'next-i18next';
// @ts-ignore
export interface Props extends GridProps {
@@ -12,6 +13,7 @@ export interface Props extends GridProps {
}
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
const { t } = useTranslation();
const sizeMap = useMemo(() => {
switch (size) {
case 'sm':
@@ -61,7 +63,7 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
}}
>
<MyIcon mr={2} name={item.icon as IconName} w={'16px'} />
{item.label}
{t(item.label)}
</Flex>
))}
</Box>

View File

@@ -42,7 +42,6 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
p={sizeMap.outP}
borderRadius={'sm'}
fontSize={sizeMap.fontSize}
overflowX={'auto'}
{...props}
>
{list.map((item) => (
@@ -51,8 +50,6 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
py={sizeMap.inlineP}
textAlign={'center'}
borderBottom={'2px solid transparent'}
px={3}
whiteSpace={'nowrap'}
{...(activeId === item.id
? {
color: 'myBlue.700',

View File

@@ -3,7 +3,7 @@ import { Flex, type FlexProps } from '@chakra-ui/react';
interface Props extends FlexProps {
children: React.ReactNode | React.ReactNode[];
colorSchema?: 'blue' | 'green' | 'gray' | 'purple';
colorSchema?: 'blue' | 'green' | 'gray';
}
const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
@@ -19,11 +19,6 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
bg: '#f8fff8',
color: '#67c13b'
},
purple: {
borderColor: '#A558C9',
bg: '#F6EEFA',
color: '#A558C9'
},
gray: {
borderColor: '#979797',
bg: '#F7F7F7',

View File

@@ -31,7 +31,7 @@ export const ChatRoleMap = {
};
export enum ChatSourceEnum {
test = 'test',
'test' = 'test',
online = 'online',
share = 'share',
api = 'api'
@@ -39,16 +39,16 @@ export enum ChatSourceEnum {
export const ChatSourceMap = {
[ChatSourceEnum.test]: {
name: 'chat.logs.test'
name: '调试测试'
},
[ChatSourceEnum.online]: {
name: 'chat.logs.online'
name: '在线使用'
},
[ChatSourceEnum.share]: {
name: 'chat.logs.share'
name: '链接分享'
},
[ChatSourceEnum.api]: {
name: 'chat.logs.api'
name: 'API调用'
}
};
@@ -56,8 +56,7 @@ export enum ChatModuleEnum {
'AIChat' = 'AI Chat',
'KBSearch' = 'KB Search',
'CQ' = 'Classify Question',
'Extract' = 'Content Extract',
'Http' = 'Http'
'Extract' = 'Content Extract'
}
export enum OutLinkTypeEnum {
@@ -65,5 +64,9 @@ export enum OutLinkTypeEnum {
'iframe' = 'iframe'
}
export const HUMAN_ICON = `/icon/human.png`;
export const LOGO_ICON = `/icon/logo.svg`;
export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`;
export const LOGO_ICON = `https://fastgpt.run/icon/logo.png`;
export const getDefaultChatVariables = () => ({
cTime: dayjs().format('YYYY/MM/DD HH:mm:ss')
});

View File

@@ -6,27 +6,20 @@ export enum UserAuthTypeEnum {
export const PRICE_SCALE = 100000;
export const fileImgs = [
{ suffix: 'pdf', src: '/imgs/files/pdf.svg' },
{ suffix: 'csv', src: '/imgs/files/csv.svg' },
{ suffix: '(doc|docs)', src: '/imgs/files/doc.svg' },
{ suffix: 'txt', src: '/imgs/files/txt.svg' },
{ suffix: 'md', src: '/imgs/files/markdown.svg' },
{ suffix: '.', src: '/imgs/files/file.svg' }
{ reg: /pdf/gi, src: '/imgs/files/pdf.svg' },
{ reg: /csv/gi, src: '/imgs/files/csv.svg' },
{ reg: /(doc|docs)/gi, src: '/imgs/files/doc.svg' },
{ reg: /txt/gi, src: '/imgs/files/txt.svg' },
{ reg: /md/gi, src: '/imgs/files/markdown.svg' }
];
export enum TrackEventName {
windowError = 'windowError',
pageError = 'pageError',
wordReadError = 'wordReadError'
}
export const htmlTemplate = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width initial-scale=1.0" />
<title>FastGPT</title>
<title>FastGpt</title>
</head>
<style>
.markdown > :first-child {

View File

@@ -20,7 +20,7 @@ import { ContextExtractEnum, HttpPropsEnum } from './flowField';
export const ChatModelSystemTip =
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
export const ChatModelLimitTip =
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。不建议内容太长,会影响上下文,可使用变量,例如 {{language}}。可在文档中找到对应的限定例子';
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"';
export const userGuideTip = '可以添加特殊的对话前后引导模块,更好的让用户进行对话';
export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
@@ -156,7 +156,6 @@ export const ChatModule: FlowModuleTemplateType = {
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,
label: '系统提示词',
max: 300,
valueType: FlowValueTypeEnum.string,
description: ChatModelSystemTip,
placeholder: ChatModelSystemTip,
@@ -167,7 +166,6 @@ export const ChatModule: FlowModuleTemplateType = {
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
label: '限定词',
max: 500,
description: ChatModelLimitTip,
placeholder: ChatModelLimitTip,
value: ''
@@ -177,7 +175,6 @@ export const ChatModule: FlowModuleTemplateType = {
key: 'quoteQA',
type: FlowInputItemTypeEnum.target,
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: FlowValueTypeEnum.kbQuote
},
Input_Template_History,
@@ -187,9 +184,8 @@ export const ChatModule: FlowModuleTemplateType = {
{
key: TaskResponseKeyEnum.answerText,
label: '模型回复',
description: '将在 stream 回复完毕后触发',
valueType: FlowValueTypeEnum.string,
type: FlowOutputItemTypeEnum.source,
description: '直接响应,无需配置',
type: FlowOutputItemTypeEnum.hidden,
targets: []
},
{
@@ -221,7 +217,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
key: 'similarity',
type: FlowInputItemTypeEnum.slider,
label: '相似度',
value: 0.4,
value: 0.8,
min: 0,
max: 1,
step: 0.01,
@@ -284,24 +280,15 @@ export const AnswerModule: FlowModuleTemplateType = {
Input_Template_TFSwitch,
{
key: SpecialInputKeyEnum.answerText,
value: '',
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '回复的内容',
description:
'可以使用 \\n 来实现换行。也可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容'
}
],
outputs: [
{
key: 'finish',
label: '回复结束',
description: '回复完成后触发',
valueType: FlowValueTypeEnum.boolean,
type: FlowOutputItemTypeEnum.source,
targets: []
}
]
outputs: []
};
export const TFSwitchModule: FlowModuleTemplateType = {
logo: '',
@@ -343,11 +330,11 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '系统提示词',
description:
'你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。',
placeholder: '例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统'
placeholder: '例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统',
value: ''
},
Input_Template_History,
Input_Template_UserChatInput,
@@ -405,11 +392,12 @@ export const ContextExtractModule: FlowModuleTemplateType = {
key: ContextExtractEnum.description,
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
value: '',
label: '提取要求描述',
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
required: true,
placeholder: '例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间'
placeholder:
'例如: \n1. 你是一个实验室预约助手。根据用户问题,提取出姓名、实验室号和预约时间',
value: ''
},
Input_Template_History,
{
@@ -669,7 +657,6 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
type: 'textarea',
valueType: 'string',
label: '限定词',
max: 500,
description:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
placeholder:
@@ -849,7 +836,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'similarity',
type: 'slider',
label: '相似度',
value: 0.4,
value: 0.8,
min: 0,
max: 1,
step: 0.01,

View File

@@ -3,35 +3,10 @@ import type { KbItemType } from '@/types/plugin';
export const defaultKbDetail: KbItemType = {
_id: '',
userId: '',
avatar: '/icon/logo.svg',
updateTime: new Date(),
avatar: '/icon/logo.png',
name: '',
tags: '',
vectorModel: {
model: 'text-embedding-ada-002',
name: 'Embedding-2',
price: 0.2,
defaultToken: 500,
maxToken: 3000
}
totalData: 0,
model: 'text-embedding-ada-002'
};
export enum KbTypeEnum {
folder = 'folder',
dataset = 'dataset'
}
export enum FileStatusEnum {
embedding = 'embedding',
ready = 'ready'
}
export const KbTypeMap = {
[KbTypeEnum.folder]: {
name: 'folder'
},
[KbTypeEnum.dataset]: {
name: 'dataset'
}
};
export const FolderAvatarSrc = '/imgs/files/folder.svg';
export const OtherFileId = 'other';

View File

@@ -1,12 +1,19 @@
import type { ShareChatEditType } from '@/types/app';
import type { AppSchema } from '@/types/mongoSchema';
export enum OpenAiChatEnum {
'GPT35' = 'gpt-3.5-turbo',
'GPT3516k' = 'gpt-3.5-turbo-16k',
'FastAI-Plus' = 'gpt-4',
'FastAI-Plus32k' = 'gpt-4-32k'
}
export const defaultApp: AppSchema = {
_id: '',
userId: 'userId',
name: '模型加载中',
type: 'basic',
avatar: '/icon/logo.svg',
avatar: '/icon/logo.png',
intro: '',
updateTime: Date.now(),
share: {

View File

@@ -6,5 +6,3 @@ export const TrainingTypeMap = {
[TrainingModeEnum.qa]: 'qa',
[TrainingModeEnum.index]: 'index'
};
export const PgTrainingTableName = 'modeldata';

View File

@@ -10,16 +10,23 @@ export enum PageTypeEnum {
}
export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
[BillSourceEnum.fastgpt]: 'FastGPT 平台',
[BillSourceEnum.fastgpt]: 'FastGpt 平台',
[BillSourceEnum.api]: 'Api',
[BillSourceEnum.shareLink]: '免登录链接'
};
export enum PromotionEnum {
register = 'register',
pay = 'pay'
invite = 'invite',
shareModel = 'shareModel',
withdraw = 'withdraw'
}
export const PromotionTypeMap = {
[PromotionEnum.invite]: '好友充值',
[PromotionEnum.shareModel]: '应用分享',
[PromotionEnum.withdraw]: '提现'
};
export enum InformTypeEnum {
system = 'system'
}

View File

@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from 'react';
import { useCallback, useRef } from 'react';
import {
AlertDialog,
AlertDialogBody,
@@ -11,25 +11,21 @@ import {
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
export const useConfirm = (props: { title?: string | null; content?: string | null }) => {
export const useConfirm = (props: { title?: string; content: string }) => {
const { t } = useTranslation();
const { title = t('Warning'), content } = props;
const [customContent, setCustomContent] = useState(content);
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef(null);
const confirmCb = useRef<any>();
const cancelCb = useRef<any>();
return {
openConfirm: useCallback(
(confirm?: any, cancel?: any, customContent?: string) => {
(confirm?: any, cancel?: any) => {
confirmCb.current = confirm;
cancelCb.current = cancel;
customContent && setCustomContent(customContent);
return onOpen;
},
[onOpen]
@@ -48,7 +44,7 @@ export const useConfirm = (props: { title?: string | null; content?: string | nu
{title}
</AlertDialogHeader>
<AlertDialogBody>{customContent}</AlertDialogBody>
<AlertDialogBody>{content}</AlertDialogBody>
<AlertDialogFooter>
<Button
@@ -74,7 +70,7 @@ export const useConfirm = (props: { title?: string | null; content?: string | nu
</AlertDialogOverlay>
</AlertDialog>
),
[customContent, isOpen, onClose, t, title]
[content, isOpen, onClose, title]
)
};
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useCallback, useRef } from 'react';
import { ModalFooter, ModalBody, Input, useDisclosure, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';

View File

@@ -5,16 +5,8 @@ export const useLoading = (props?: { defaultLoading: boolean }) => {
const [isLoading, setIsLoading] = useState(props?.defaultLoading || false);
const Loading = useCallback(
({
loading,
fixed = true,
text = ''
}: {
loading?: boolean;
fixed?: boolean;
text?: string;
}): JSX.Element | null => {
return isLoading || loading ? <LoadingComponent fixed={fixed} text={text} /> : null;
({ loading, fixed = true }: { loading?: boolean; fixed?: boolean }): JSX.Element | null => {
return isLoading || loading ? <LoadingComponent fixed={fixed} /> : null;
},
[isLoading]
);

View File

@@ -1,12 +1,8 @@
import React, { useRef, useCallback } from 'react';
import { Box } from '@chakra-ui/react';
import { useToast } from './useToast';
import { useTranslation } from 'react-i18next';
export const useSelectFile = (props?: { fileType?: string; multiple?: boolean }) => {
const { t } = useTranslation();
const { fileType = '*', multiple = false } = props || {};
const { toast } = useToast();
const SelectFileDom = useRef<HTMLInputElement>(null);
const File = useCallback(
@@ -19,18 +15,12 @@ export const useSelectFile = (props?: { fileType?: string; multiple?: boolean })
multiple={multiple}
onChange={(e) => {
if (!e.target.files || e.target.files?.length === 0) return;
if (e.target.files.length > 10) {
return toast({
status: 'warning',
title: t('file.Select a maximum of 10 files')
});
}
onSelect(Array.from(e.target.files));
}}
/>
</Box>
),
[fileType, multiple, t, toast]
[fileType, multiple]
);
const onOpen = useCallback(() => {

View File

@@ -1,11 +1,10 @@
import { useState, useMemo, useCallback } from 'react';
import { sendAuthCode } from '@/api/user';
import { UserAuthTypeEnum } from '@/constants/common';
import { useToast } from './useToast';
import { feConfigs } from '@/store/static';
import { getErrText } from '@/utils/tools';
let timer: any;
import { useToast } from './useToast';
import { getClientToken } from '@/utils/plugin/google';
import { feConfigs } from '@/store/static';
export const useSendCode = () => {
const { toast } = useToast();
@@ -46,7 +45,7 @@ export const useSendCode = () => {
});
} catch (error: any) {
toast({
title: getErrText(error, '验证码发送异常'),
title: error.message || '发送验证码异常',
status: 'error'
});
}
@@ -62,20 +61,3 @@ export const useSendCode = () => {
codeCountDown
};
};
export function getClientToken(googleClientVerKey?: string) {
if (!googleClientVerKey || typeof window.grecaptcha === 'undefined' || !window.grecaptcha?.ready)
return '';
return new Promise<string>((resolve, reject) => {
window.grecaptcha.ready(async () => {
try {
const token = await window.grecaptcha.execute(googleClientVerKey, {
action: 'submit'
});
resolve(token);
} catch (error) {
reject(error);
}
});
});
}

View File

@@ -10,13 +10,11 @@ import NProgress from 'nprogress'; //nprogress module
import Router from 'next/router';
import { clientInitData, feConfigs } from '@/store/static';
import { appWithTranslation, useTranslation } from 'next-i18next';
import { getLangStore, setLangStore } from '@/utils/i18n';
import { getLangStore } from '@/utils/i18n';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import 'nprogress/nprogress.css';
import '@/styles/reset.scss';
import { FeConfigsType } from '@/types';
//Binding events.
Router.events.on('routeChangeStart', () => NProgress.start());
@@ -29,62 +27,37 @@ const queryClient = new QueryClient({
queries: {
refetchOnWindowFocus: false,
retry: false,
cacheTime: 10
cacheTime: 0
}
}
});
function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const { hiId } = router.query as { hiId?: string };
const { i18n } = useTranslation();
const { setLastRoute } = useGlobalStore();
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
const [googleClientVerKey, setGoogleVerKey] = useState<string>();
const [baiduTongjiUrl, setBaiduTongjiUrl] = useState<string>();
useEffect(() => {
// get init data
(async () => {
const {
feConfigs: { scripts }
feConfigs: { googleClientVerKey, baiduTongjiUrl }
} = await clientInitData();
setScripts(scripts || []);
setGoogleVerKey(googleClientVerKey);
setBaiduTongjiUrl(baiduTongjiUrl);
})();
// add window error track
window.onerror = function (msg, url) {
window.umami?.track('windowError', {
device: {
userAgent: navigator.userAgent,
platform: navigator.platform,
appName: navigator.appName
},
msg,
url
});
};
return () => {
window.onerror = null;
};
}, []);
useEffect(() => {
hiId && localStorage.setItem('inviterId', hiId);
}, [hiId]);
useEffect(() => {
const lang = getLangStore() || 'zh';
i18n?.changeLanguage?.(lang);
setLangStore(lang);
return () => {
setLastRoute(router.asPath);
};
}, [router.asPath]);
return (
<>
<Head>
<title>{feConfigs?.systemTitle || 'AI'}</title>
<title>{feConfigs?.systemTitle || 'FastAI'}</title>
<meta name="description" content="Embedding + LLM, Build AI knowledge base" />
<meta
name="viewport"
@@ -92,10 +65,18 @@ function App({ Component, pageProps }: AppProps) {
/>
<link rel="icon" href="/favicon.ico" />
</Head>
{scripts?.map((item, i) => (
<Script key={i} strategy="lazyOnload" {...item}></Script>
))}
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
<Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>
{baiduTongjiUrl && <Script src={baiduTongjiUrl} strategy="lazyOnload"></Script>}
{googleClientVerKey && (
<>
<Script
src={`https://www.recaptcha.net/recaptcha/api.js?render=${googleClientVerKey}`}
strategy="afterInteractive"
></Script>
</>
)}
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />

View File

@@ -1,27 +1,10 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { serviceSideProps } from '@/utils/i18n';
import { useGlobalStore } from '@/store/global';
import { addLog } from '@/service/utils/tools';
import { getErrText } from '@/utils/tools';
function Error() {
const router = useRouter();
const { lastRoute } = useGlobalStore();
useEffect(() => {
setTimeout(() => {
window.umami?.track('pageError', {
userAgent: navigator.userAgent,
platform: navigator.platform,
appName: navigator.appName,
lastRoute,
route: router.asPath
});
}, 1000);
setTimeout(() => {
router.back();
router.replace('/app/list');
}, 2000);
}, []);
@@ -33,14 +16,4 @@ function Error() {
);
}
export async function getServerSideProps(context: any) {
console.log('[render error]: ', context);
addLog.error(getErrText(context?.res));
return {
props: { ...(await serviceSideProps(context)) }
};
}
export default Error;

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react';
import { Box, Flex, Button, useDisclosure, useTheme, Divider, Select } from '@chakra-ui/react';
import React, { useCallback } from 'react';
import { Box, Flex, Button, useDisclosure, useTheme, Divider } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { useToast } from '@/hooks/useToast';
@@ -11,7 +11,6 @@ import { useSelectFile } from '@/hooks/useSelectFile';
import { compressImg } from '@/utils/file';
import { feConfigs } from '@/store/static';
import { useTranslation } from 'next-i18next';
import { timezoneList } from '@/utils/user';
import Loading from '@/components/Loading';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
@@ -34,7 +33,6 @@ const UserInfo = () => {
const theme = useTheme();
const { t } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const timezones = useRef(timezoneList());
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
@@ -61,7 +59,6 @@ const UserInfo = () => {
async (data: UserType) => {
await updateUserInfo({
avatar: data.avatar,
timezone: data.timezone,
openaiAccount: data.openaiAccount
});
reset(data);
@@ -105,13 +102,7 @@ const UserInfo = () => {
});
return (
<Box
display={['block', 'flex']}
py={[2, 10]}
justifyContent={'center'}
alignItems={'flex-start'}
fontSize={['lg', 'xl']}
>
<Box display={['block', 'flex']} py={[2, 10]} justifyContent={'center'} fontSize={['lg', 'xl']}>
<Flex
flexDirection={'column'}
alignItems={'center'}
@@ -144,27 +135,11 @@ const UserInfo = () => {
mt={[6, 0]}
>
<Flex alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={'0 0 50px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Timezone')}:&nbsp;</Box>
<Select
value={userInfo?.timezone}
onChange={(e) => {
if (!userInfo) return;
onclickSave({ ...userInfo, timezone: e.target.value });
}}
>
{timezones.current.map((item) => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Select>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 80px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={'0 0 50px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
<Button size={['sm', 'md']} variant={'base'} ml={5} onClick={onOpenUpdatePsw}>
{t('user.Change')}
@@ -174,7 +149,7 @@ const UserInfo = () => {
<>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>{t('user.Balance')}:&nbsp;</Box>
<Box flex={'0 0 50px'}>{t('user.Balance')}:&nbsp;</Box>
<Box flex={1}>
<strong>{userInfo?.balance.toFixed(3)}</strong>
</Box>

View File

@@ -32,7 +32,8 @@ const OpenAIAccountModal = ({
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
<ModalBody>
<Box fontSize={'sm'} color={'myGray.500'}>
线使 OpenAI Chat
线使 OpenAI Chat
API
</Box>
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 65px'}>API Key:</Box>
@@ -43,7 +44,7 @@ const OpenAIAccountModal = ({
<Input
flex={1}
{...register('baseUrl')}
placeholder={'请求地址,默认为 openai 官方。可填中转地址,未自动补全 "v1"'}
placeholder={'中转地址,未自动补全 "v1"'}
></Input>
</Flex>
</ModalBody>

View File

@@ -104,7 +104,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
| FastAI4k - 对话 | 0.015 |
| FastAI16k - 对话 | 0.03 |
| FastAI-Plus - 对话 | 0.45 |
| 文件QA拆分 | 0.03 |`}
| 文件拆分 | 0.03 |`}
/>
</>
)}

View File

@@ -26,12 +26,6 @@ const PayRecordTable = () => {
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
const { toast } = useToast();
const { isInitialLoading, refetch } = useQuery(['initPayOrder'], getPayOrders, {
onSuccess(res) {
setPayOrders(res);
}
});
const handleRefreshPayOrder = useCallback(
async (payId: string) => {
setIsLoading(true);
@@ -42,6 +36,8 @@ const PayRecordTable = () => {
title: data,
status: 'success'
});
const res = await getPayOrders();
setPayOrders(res);
} catch (error: any) {
toast({
title: error?.message,
@@ -49,15 +45,18 @@ const PayRecordTable = () => {
});
console.log(error);
}
try {
refetch();
} catch (error) {}
setIsLoading(false);
},
[refetch, setIsLoading, toast]
[setIsLoading, toast]
);
const { isInitialLoading } = useQuery(['initPayOrder'], getPayOrders, {
onSuccess(res) {
setPayOrders(res);
}
});
return (
<Box position={'relative'} h={'100%'}>
{!isInitialLoading && payOrders.length === 0 ? (

View File

@@ -1,148 +0,0 @@
import React from 'react';
import {
Grid,
Box,
Flex,
BoxProps,
useTheme,
Button,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer
} from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { getPromotionInitData, getPromotionRecords } from '@/api/user';
import { useUserStore } from '@/store/user';
import { useLoading } from '@/hooks/useLoading';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useCopyData } from '@/utils/tools';
import { usePagination } from '@/hooks/usePagination';
import { PromotionRecordType } from '@/api/response/user';
import MyIcon from '@/components/Icon';
import dayjs from 'dayjs';
const Promotion = () => {
const { t } = useTranslation();
const theme = useTheme();
const { copyData } = useCopyData();
const { userInfo } = useUserStore();
const { Loading } = useLoading();
const {
data: promotionRecords,
isLoading,
total,
pageSize,
Pagination
} = usePagination<PromotionRecordType>({
api: getPromotionRecords
});
const { data: { invitedAmount = 0, earningsAmount = 0 } = {} } = useQuery(
['getPromotionInitData'],
getPromotionInitData
);
const statisticsStyles: BoxProps = {
p: [4, 5],
border: theme.borders.base,
textAlign: 'center',
fontSize: ['md', 'xl'],
borderRadius: 'md'
};
const titleStyles: BoxProps = {
mt: 2,
fontSize: ['lg', '28px'],
fontWeight: 'bold'
};
return (
<Flex flexDirection={'column'} py={[0, 5]} px={5} h={'100%'} position={'relative'}>
<Grid gridTemplateColumns={['1fr 1fr', 'repeat(2,1fr)', 'repeat(4,1fr)']} gridGap={5}>
<Box {...statisticsStyles}>
<Box>{t('user.Amount of inviter')}</Box>
<Box {...titleStyles}>{invitedAmount}</Box>
</Box>
<Box {...statisticsStyles}>
<Box>{t('user.Amount of earnings')}</Box>
<Box {...titleStyles}>{earningsAmount}</Box>
</Box>
<Box {...statisticsStyles}>
<Flex alignItems={'center'} justifyContent={'center'}>
<Box>{t('user.Promotion Rate')}</Box>
<MyTooltip label={t('user.Promotion rate tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Box {...titleStyles}>{userInfo?.promotionRate || 15}%</Box>
</Box>
<Box {...statisticsStyles}>
<Flex alignItems={'center'} justifyContent={'center'}>
<Box>{t('user.Invite Url')}</Box>
<MyTooltip label={t('user.Invite url tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Button
mt={4}
variant={'base'}
fontSize={'sm'}
onClick={() => {
copyData(`${location.origin}/?hiId=${userInfo?._id}`);
}}
>
{t('user.Copy invite url')}
</Button>
</Box>
</Grid>
<Box mt={5}>
<TableContainer position={'relative'} overflow={'hidden'} minH={'100px'}>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th>()</Th>
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{promotionRecords.map((item) => (
<Tr key={item._id}>
<Td>
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
</Td>
<Td>{t(`user.promotion.${item.type}`)}</Td>
<Td>{item.amount}</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{!isLoading && promotionRecords.length === 0 && (
<Flex mt={'10vh'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
~
</Box>
</Flex>
)}
{total > pageSize && (
<Flex mt={4} justifyContent={'flex-end'}>
<Pagination />
</Flex>
)}
<Loading loading={isLoading} fixed={false} />
</Box>
</Flex>
);
};
export default Promotion;

View File

@@ -6,15 +6,9 @@ import { useForm } from 'react-hook-form';
import { useRequest } from '@/hooks/useRequest';
import { updatePasswordByOld } from '@/api/user';
type FormType = {
oldPsw: string;
newPsw: string;
confirmPsw: string;
};
const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { register, handleSubmit } = useForm<FormType>({
const { register, handleSubmit } = useForm({
defaultValues: {
oldPsw: '',
newPsw: '',
@@ -23,10 +17,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
});
const { mutate: onSubmit, isLoading } = useRequest({
mutationFn: (data: FormType) => {
if (data.newPsw !== data.confirmPsw) {
return Promise.reject(t('common.Password inconsistency'));
}
mutationFn: (data) => {
return updatePasswordByOld(data);
},
onSuccess() {
@@ -45,31 +36,11 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 70px'}>:</Box>
<Input
flex={1}
type={'password'}
{...register('newPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
}
})}
></Input>
<Input flex={1} type={'password'} {...register('newPsw', { required: true })}></Input>
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 70px'}>:</Box>
<Input
flex={1}
type={'password'}
{...register('confirmPsw', {
required: true,
maxLength: {
value: 20,
message: '密码最少 4 位最多 20 位'
}
})}
></Input>
<Input flex={1} type={'password'} {...register('confirmPsw', { required: true })}></Input>
</Flex>
</ModalBody>
<ModalFooter>

View File

@@ -11,18 +11,19 @@ import SideTabs from '@/components/SideTabs';
import Tabs from '@/components/Tabs';
import UserInfo from './components/Info';
import { serviceSideProps } from '@/utils/i18n';
import { feConfigs } from '@/store/static';
import { useTranslation } from 'react-i18next';
import Script from 'next/script';
const Promotion = dynamic(() => import('./components/Promotion'));
const BillTable = dynamic(() => import('./components/BillTable'));
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'));
const InformTable = dynamic(() => import('./components/InformTable'));
const BillTable = dynamic(() => import('./components/BillTable'), {
ssr: false
});
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), {
ssr: false
});
const InformTable = dynamic(() => import('./components/InformTable'), {
ssr: false
});
enum TabEnum {
'info' = 'info',
'promotion' = 'promotion',
'bill' = 'bill',
'pay' = 'pay',
'inform' = 'inform',
@@ -30,42 +31,36 @@ enum TabEnum {
}
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation();
const tabList = useRef([
{
icon: 'meLight',
label: t('user.Personal Information'),
id: TabEnum.info
label: 'user.Personal Information',
id: TabEnum.info,
Component: <BillTable />
},
{
icon: 'billRecordLight',
label: t('user.Usage Record'),
id: TabEnum.bill
label: 'user.Usage Record',
id: TabEnum.bill,
Component: <BillTable />
},
{
icon: 'payRecordLight',
label: 'user.Recharge Record',
id: TabEnum.pay,
Component: <PayRecordTable />
},
...(feConfigs?.show_userDetail
? [
{
icon: 'promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
},
{
icon: 'payRecordLight',
label: t('user.Recharge Record'),
id: TabEnum.pay
}
]
: []),
{
icon: 'informLight',
label: t('user.Notice'),
id: TabEnum.inform
label: 'user.Notice',
id: TabEnum.inform,
Component: <InformTable />
},
{
icon: 'loginoutLight',
label: t('user.Sign Out'),
id: TabEnum.loginout
label: 'user.Sign Out',
id: TabEnum.loginout,
Component: () => <></>
}
]);
@@ -98,54 +93,51 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
);
return (
<>
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<PageContainer>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList.current}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Flex>
) : (
<Box mb={3}>
<Tabs
m={'auto'}
size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Box>
)}
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.info && <UserInfo />}
{currentTab === TabEnum.promotion && <Promotion />}
{currentTab === TabEnum.bill && <BillTable />}
{currentTab === TabEnum.pay && <PayRecordTable />}
{currentTab === TabEnum.inform && <InformTable />}
<PageContainer>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList.current}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Flex>
) : (
<Box mb={3}>
<Tabs
m={'auto'}
w={'90%'}
size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
</>
)}
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.info && <UserInfo />}
{currentTab === TabEnum.bill && <BillTable />}
{currentTab === TabEnum.pay && <PayRecordTable />}
{currentTab === TabEnum.inform && <InformTable />}
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
);
};

View File

@@ -45,7 +45,9 @@ async function init(limit: number, skip: number) {
chatId: { $exists: false }
},
'_id'
).limit(limit);
)
.limit(limit)
.skip(skip);
await Promise.all(
chats.map((chat) =>

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