Compare commits

..

56 Commits
v4.3 ... v4.4.2

Author SHA1 Message Date
Archer
b8ea546b3f v4.2.2 (#312) 2023-09-18 13:37:25 +08:00
Carson Yang
0bb31b985d Docs: update style (#310)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-17 15:06:25 +08:00
Carson Yang
453824260f Docs: fix typo (#307)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-15 22:44:04 +08:00
hehan
a8fdffc3e9 Docs: intergate feishu (#305) 2023-09-15 14:32:43 +08:00
Carson Yang
24164d9454 Update deploy-docs-preview workflow (#304)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-15 13:43:36 +08:00
Archer
4365a94ea9 System optimize (#303) 2023-09-15 10:21:46 +08:00
Carson Yang
7c1ec04380 Docs: add github badge (#301)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-14 17:36:51 +08:00
Archer
09b6365321 perf: action cache (#300) 2023-09-13 22:17:55 +08:00
Archer
eb2e383cc7 perf: document icon and language select (#299) 2023-09-13 19:54:29 +08:00
Archer
ae4c479f37 file name (#297) 2023-09-13 18:23:55 +08:00
Archer
6a996272da fix: share link quote (#296) 2023-09-13 18:15:22 +08:00
Carson Yang
1bf76ebe7a Docs: add limiting responsibility (#295)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-13 17:00:46 +08:00
Archer
a19afca148 v4.4.1 (#294)
* move file

* perf: dataset file manage

* v441 description

* fix: qa csv update file

* feat: rename file

* frontend show system-version
2023-09-13 17:00:17 +08:00
Carson Yang
be3b680bc6 Docs: add community (#293)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-13 13:55:50 +08:00
Carson Yang
31dbcfde9f Docs: update cdn (#291)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-13 09:29:17 +08:00
Archer
6d438aafdf google login and power share link (#292) 2023-09-13 08:49:22 +08:00
Carson Yang
1aaafcf631 Docs: update weight (#290)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-12 22:36:34 +08:00
Carson Yang
7521bce77e Docs: update cdn (#289)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-12 21:33:12 +08:00
Carson Yang
c8dee29dc4 Docs: add pricing doc (#287)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-12 20:06:09 +08:00
Carson Yang
8f953d1fc4 Update README.md (#283)
Add demo video

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-12 12:46:29 +08:00
Carson Yang
970b62be25 Docs: enable ‘Edit this page’ (#280)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-11 23:59:14 +08:00
Carson Yang
b2b3aa651d Docs: add details shortcode (#279)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-11 20:48:14 +08:00
Archer
b0e7d25464 docs weight (#278) 2023-09-11 18:36:43 +08:00
Archer
b46048609c feat: move dataset (#277) 2023-09-11 18:23:51 +08:00
Archer
ae2887e956 fix: file_id undefined bug (#275) 2023-09-11 10:15:52 +08:00
Archer
7917766024 Dataset folder manager (#274)
* feat: retry send

* perf: qa default value

* feat: dataset folder

* feat: kb folder delete and path

* fix: ts

* perf: script load

* feat: fileCard and dataCard

* feat: search file

* feat: max token

* feat: select dataset

* fix: preview chunk

* perf: source update

* export data limit file_id

* docs

* fix: export limit
2023-09-10 16:37:32 +08:00
不做了睡大觉
a1a63260dd 更新镜像通道 (#272)
* 更新镜像

* 更新镜像信息

* 更新镜像信息
2023-09-08 18:13:37 +08:00
Archer
6f2d556a87 demo (#266) 2023-09-06 18:56:59 +08:00
Carson Yang
565f9c8113 Docs: fix typo (#265)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-06 14:29:49 +08:00
Carson Yang
975e011e03 Docs: update table style (#264)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-06 13:54:23 +08:00
Carson Yang
19ce6f66ca Docs: fix typo (#263)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-06 13:39:47 +08:00
cuisongliu
da6e26f95c build(main): add docs for ci (#261)
Signed-off-by: cuisongliu <cuisongliu@qq.com>
2023-09-06 12:06:51 +08:00
archer
71abe08f05 fix: onwechat yml 2023-09-06 10:46:55 +08:00
Carson Yang
45ba5e1e01 Docs: optimize codeblock (#259)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-06 09:53:37 +08:00
Carson Yang
139d0be52b Docs: fix favicon (#258)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-05 21:28:13 +08:00
Carson Yang
1ba3d72a8a Update docs: change image width (#256)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-05 20:51:50 +08:00
archer
cd455b2a79 prompt docs 2023-09-05 20:22:22 +08:00
archer
fa3f3e6264 limit prompt template 2023-09-05 18:29:18 +08:00
archer
9bf5a3ec76 m3e doc 2023-09-05 18:29:15 +08:00
qing hua
95389e31f7 修改了一些错误 (#254)
* Update sse.ts

解决chatglm2控制台输出一半不输出的问题

* 解决知识库相似度搜索结果超过1

* Update sse.ts

---------

Co-authored-by: Archer <545436317@qq.com>
2023-09-05 18:28:59 +08:00
archer
ea65d9b34b onwechat demo 2023-09-05 14:31:04 +08:00
archer
2dd2976efa perf: dev doc 2023-09-05 11:47:04 +08:00
archer
64fde42c87 perf: default lang 2023-09-05 11:44:31 +08:00
archer
7a926b7086 user timezone 2023-09-05 11:30:52 +08:00
archer
562fd2692d issue template 2023-09-04 23:44:08 +08:00
archer
935287a95a doc favicon 2023-09-04 19:45:24 +08:00
archer
bd419a22f4 adapt echarts 2023-09-04 19:25:28 +08:00
不做了睡大觉
32f482b232 增加对echarts图表的支持 (#249)
* 增加对echarts图表的支持

* 增加对echarts的支持
2023-09-04 18:17:39 +08:00
archer
5d596bd3d5 favicon html 2023-09-04 18:16:29 +08:00
archer
ae88d79d6f update bash 2023-09-04 18:03:20 +08:00
archer
1207e3e566 update bash 2023-09-04 17:51:56 +08:00
archer
3449024678 feat: labBot demo 2023-09-04 17:02:21 +08:00
archer
8dba2c39e1 fix: empty kb 2023-09-04 15:47:01 +08:00
archer
94c53804ce fix: quick question and variable 2023-09-04 14:45:01 +08:00
archer
a1bcd798e1 image name 2023-09-04 14:31:03 +08:00
archer
6d51b3babe README 2023-09-04 11:37:46 +08:00
280 changed files with 7957 additions and 2120 deletions

View File

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

View File

@@ -8,13 +8,13 @@ assignees: ''
**例行检查**
[//]: # '方框内删除已有的空格,填 x 号'
[//]: # '方框内填 x 表示打钩'
- [ ] 我已确认目前没有类似 features
- [ ] 我已确认我已升级到最新版本
- [ ] 我已完整查看过项目 README已确定现有版本无法满足需求
- [ ] 我理解并愿意跟进此 features协助测试和提供反馈
- [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 features 可能会被无视或直接关闭**
- [x] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 features 可能会被无视或直接关闭**
**功能描述**

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

@@ -0,0 +1,30 @@
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}}

View File

@@ -69,3 +69,4 @@ jobs:
github-comment: false
vercel-args: '--prod --local-config ../vercel.json' # Optional
working-directory: docSite/public

96
.github/workflows/deploy-preview.yml vendored Normal file
View File

@@ -0,0 +1,96 @@
name: deploy-docs-preview
on:
pull_request_target:
paths:
- 'docSite/**'
branches:
- 'main'
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

@@ -25,6 +25,13 @@ jobs:
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
@@ -38,7 +45,6 @@ jobs:
else
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
@@ -50,6 +56,8 @@ jobs:
--label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=MIT" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.

View File

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

View File

@@ -18,6 +18,8 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
<a href="https://github.com/labring/FastGPT#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">相关项目</a>
</p>
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4
## 🛸 在线体验
[fastgpt.run](https://fastgpt.run/)(服务器在新加坡,部分地区可能无法直连)
@@ -31,8 +33,7 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
1. 强大的可视化编排,轻松构建 AI 应用
- [x] 提供简易模式,无需操作编排
- [x] 用户对话前引导
- [x] 全局变量
- [x] 用户对话前引导, 全局字符串变量
- [x] 知识库搜索
- [x] 多 LLM 模型对话
- [x] 文本内容提取成结构化数据
@@ -45,13 +46,11 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
2. 丰富的知识库预处理
- [x] 多库复用,混用
- [x] chunk 记录修改和删除
- [x] 支持直接分段导入
- [x] 支持 QA 拆分导入
- [x] 支持手动输入内容
- [x] 支持 url 读取导入
- [x] 支持 CSV 批量导入问答对
- [x] 支持 手动输入, 直接分段, QA 拆分导入
- [x] 支持 url 读取、 CSV 批量导入
- [x] 支持知识库单独设置向量模型
- [x] 源文件存储
- [ ] 文件学习 Agent
3. 多种效果测试渠道
- [x] 知识库单点搜索测试
- [x] 对话时反馈引用并可修改与删除
@@ -94,7 +93,6 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
- [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/)
## 💪 相关项目
@@ -106,6 +104,7 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
## 🤝 第三方生态
- [OnWeChat 个人微信/企微机器人](https://doc.fastgpt.run/docs/use-cases/onwechat/)
- [luolinAI: 企微机器人,开箱即用](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
## 🌟 Star History

View File

@@ -9,7 +9,9 @@
"show_doc": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"gitLoginKey": "",
"limit": {
"exportLimitMinutes": 0
},
"scripts": []
},
"SystemParams": {

View File

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

View File

@@ -17,10 +17,6 @@ const nextConfig = {
}
};
}
config.experiments = {
asyncWebAssembly: true,
layers: true
};
config.module = {
...config.module,
rules: config.module.rules.concat([

View File

@@ -1,7 +1,7 @@
{
"name": "fastgpt",
"version": "3.7",
"private": true,
"version": "4.2.1",
"private": false,
"scripts": {
"dev": "next dev",
"build": "next build",
@@ -12,7 +12,6 @@
"@chakra-ui/icons": "^2.0.17",
"@chakra-ui/react": "^2.7.0",
"@chakra-ui/system": "^2.5.8",
"@dqbd/tiktoken": "^1.0.7",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@mozilla/readability": "^0.4.4",
@@ -24,13 +23,16 @@
"crypto": "^1.0.1",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"downloadjs": "^1.4.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",
"js-tiktoken": "^1.0.7",
"jschardet": "^3.0.0",
"jsdom": "^22.1.0",
"jsonwebtoken": "^9.0.0",
@@ -47,6 +49,7 @@
"openai": "^3.3.0",
"papaparse": "^5.4.1",
"pg": "^8.10.0",
"pg-query-stream": "^4.5.3",
"react": "18.2.0",
"react-day-picker": "^8.7.1",
"react-dom": "18.2.0",
@@ -61,6 +64,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",
@@ -69,6 +73,7 @@
"devDependencies": {
"@svgr/webpack": "^6.5.1",
"@types/cookie": "^0.5.1",
"@types/downloadjs": "^1.4.3",
"@types/formidable": "^2.0.5",
"@types/js-cookie": "^3.0.3",
"@types/jsdom": "^21.1.1",

95
client/pnpm-lock.yaml generated
View File

@@ -14,9 +14,6 @@ dependencies:
'@chakra-ui/system':
specifier: ^2.5.8
version: registry.npmmirror.com/@chakra-ui/system@2.5.8(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0)
'@dqbd/tiktoken':
specifier: ^1.0.7
version: registry.npmmirror.com/@dqbd/tiktoken@1.0.7
'@emotion/react':
specifier: ^11.10.6
version: registry.npmmirror.com/@emotion/react@11.10.6(@types/react@18.0.28)(react@18.2.0)
@@ -50,9 +47,15 @@ dependencies:
dayjs:
specifier: ^1.11.7
version: registry.npmmirror.com/dayjs@1.11.7
downloadjs:
specifier: ^1.4.7
version: registry.npmmirror.com/downloadjs@1.4.7
echarts:
specifier: ^5.4.1
version: registry.npmmirror.com/echarts@5.4.1
echarts-gl:
specifier: ^2.0.9
version: registry.npmmirror.com/echarts-gl@2.0.9(echarts@5.4.1)
formidable:
specifier: ^2.1.1
version: registry.npmmirror.com/formidable@2.1.1
@@ -71,6 +74,9 @@ dependencies:
js-cookie:
specifier: ^3.0.5
version: registry.npmmirror.com/js-cookie@3.0.5
js-tiktoken:
specifier: ^1.0.7
version: registry.npmmirror.com/js-tiktoken@1.0.7
jschardet:
specifier: ^3.0.0
version: registry.npmmirror.com/jschardet@3.0.0
@@ -119,6 +125,9 @@ dependencies:
pg:
specifier: ^8.10.0
version: registry.npmmirror.com/pg@8.10.0
pg-query-stream:
specifier: ^4.5.3
version: registry.npmmirror.com/pg-query-stream@4.5.3(pg@8.10.0)
react:
specifier: 18.2.0
version: registry.npmmirror.com/react@18.2.0
@@ -161,6 +170,9 @@ dependencies:
sass:
specifier: ^1.58.3
version: registry.npmmirror.com/sass@1.58.3
timezones-list:
specifier: ^3.0.2
version: registry.npmmirror.com/timezones-list@3.0.2
tunnel:
specifier: ^0.0.6
version: registry.npmmirror.com/tunnel@0.0.6
@@ -181,6 +193,9 @@ devDependencies:
'@types/cookie':
specifier: ^0.5.1
version: registry.npmmirror.com/@types/cookie@0.5.1
'@types/downloadjs':
specifier: ^1.4.3
version: registry.npmmirror.com/@types/downloadjs@1.4.3
'@types/formidable':
specifier: ^2.0.5
version: registry.npmmirror.com/@types/formidable@2.0.5
@@ -4196,12 +4211,6 @@ packages:
kuler: registry.npmmirror.com/kuler@2.0.0
dev: false
registry.npmmirror.com/@dqbd/tiktoken@1.0.7:
resolution: {integrity: sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@dqbd/tiktoken/-/tiktoken-1.0.7.tgz}
name: '@dqbd/tiktoken'
version: 1.0.7
dev: false
registry.npmmirror.com/@emotion/babel-plugin@11.11.0:
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz}
name: '@emotion/babel-plugin'
@@ -5389,6 +5398,12 @@ packages:
'@types/ms': registry.npmmirror.com/@types/ms@0.7.31
dev: false
registry.npmmirror.com/@types/downloadjs@1.4.3:
resolution: {integrity: sha512-MjJepFle/tLtT2/jmDNth6ZnwWzEhm40L+olE5HKR70ISUCfgT55eqreeHldAzFLY2HDUGsn8zgyto8KygN0CA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/downloadjs/-/downloadjs-1.4.3.tgz}
name: '@types/downloadjs'
version: 1.4.3
dev: true
registry.npmmirror.com/@types/express-serve-static-core@4.17.36:
resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz}
name: '@types/express-serve-static-core'
@@ -6338,6 +6353,12 @@ packages:
version: 5.0.4
dev: false
registry.npmmirror.com/claygl@1.3.0:
resolution: {integrity: sha512-+gGtJjT6SSHD2l2yC3MCubW/sCV40tZuSs5opdtn79vFSGUgp/lH139RNEQ6Jy078/L0aV8odCw8RSrUcMfLaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/claygl/-/claygl-1.3.0.tgz}
name: claygl
version: 1.3.0
dev: false
registry.npmmirror.com/client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz}
name: client-only
@@ -7255,6 +7276,12 @@ packages:
domhandler: registry.npmmirror.com/domhandler@4.3.1
dev: true
registry.npmmirror.com/downloadjs@1.4.7:
resolution: {integrity: sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/downloadjs/-/downloadjs-1.4.7.tgz}
name: downloadjs
version: 1.4.7
dev: false
registry.npmmirror.com/duck@0.1.12:
resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/duck/-/duck-0.1.12.tgz}
name: duck
@@ -7271,6 +7298,19 @@ packages:
safe-buffer: registry.npmmirror.com/safe-buffer@5.2.1
dev: false
registry.npmmirror.com/echarts-gl@2.0.9(echarts@5.4.1):
resolution: {integrity: sha512-oKeMdkkkpJGWOzjgZUsF41DOh6cMsyrGGXimbjK2l6Xeq/dBQu4ShG2w2Dzrs/1bD27b2pLTGSaUzouY191gzA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/echarts-gl/-/echarts-gl-2.0.9.tgz}
id: registry.npmmirror.com/echarts-gl/2.0.9
name: echarts-gl
version: 2.0.9
peerDependencies:
echarts: ^5.1.2
dependencies:
claygl: registry.npmmirror.com/claygl@1.3.0
echarts: registry.npmmirror.com/echarts@5.4.1
zrender: registry.npmmirror.com/zrender@5.4.1
dev: false
registry.npmmirror.com/echarts@5.4.1:
resolution: {integrity: sha512-9ltS3M2JB0w2EhcYjCdmtrJ+6haZcW6acBolMGIuf01Hql1yrIV01L1aRj7jsaaIULJslEP9Z3vKlEmnJaWJVQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/echarts/-/echarts-5.4.1.tgz}
name: echarts
@@ -8907,6 +8947,14 @@ packages:
version: 4.4.1
dev: true
registry.npmmirror.com/js-tiktoken@1.0.7:
resolution: {integrity: sha512-biba8u/clw7iesNEWLOLwrNGoBP2lA+hTaBLs/D45pJdUPFXyxD6nhcDVtADChghv4GgyAiMKYMiRx7x6h7Biw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-tiktoken/-/js-tiktoken-1.0.7.tgz}
name: js-tiktoken
version: 1.0.7
dependencies:
base64-js: registry.npmmirror.com/base64-js@1.5.1
dev: false
registry.npmmirror.com/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz}
name: js-tokens
@@ -10478,6 +10526,17 @@ packages:
version: 2.6.0
dev: false
registry.npmmirror.com/pg-cursor@2.10.3(pg@8.10.0):
resolution: {integrity: sha512-rDyBVoqPVnx/PTmnwQAYgusSeAKlTL++gmpf5klVK+mYMFEqsOc6VHHZnPKc/4lOvr4r6fiMuoxSFuBF1dx4FQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-cursor/-/pg-cursor-2.10.3.tgz}
id: registry.npmmirror.com/pg-cursor/2.10.3
name: pg-cursor
version: 2.10.3
peerDependencies:
pg: ^8
dependencies:
pg: registry.npmmirror.com/pg@8.10.0
dev: false
registry.npmmirror.com/pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz}
name: pg-int8
@@ -10500,6 +10559,18 @@ packages:
name: pg-protocol
version: 1.6.0
registry.npmmirror.com/pg-query-stream@4.5.3(pg@8.10.0):
resolution: {integrity: sha512-ufa94r/lHJdjAm3+zPZEO0gXAmCb4tZPaOt7O76mjcxdL/HxwTuryy76km+u0odBBgtfdKFYq/9XGfiYeQF0yA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-query-stream/-/pg-query-stream-4.5.3.tgz}
id: registry.npmmirror.com/pg-query-stream/4.5.3
name: pg-query-stream
version: 4.5.3
peerDependencies:
pg: ^8
dependencies:
pg: registry.npmmirror.com/pg@8.10.0
pg-cursor: registry.npmmirror.com/pg-cursor@2.10.3(pg@8.10.0)
dev: false
registry.npmmirror.com/pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz}
name: pg-types
@@ -11710,6 +11781,12 @@ packages:
version: 0.2.0
dev: true
registry.npmmirror.com/timezones-list@3.0.2:
resolution: {integrity: sha512-I698hm6Jp/xxkwyTSOr39pZkYKETL8LDJeSIhjxXBfPUAHM5oZNuQ4o9UK3PSkDBOkjATecSOBb3pR1IkIBUsg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/timezones-list/-/timezones-list-3.0.2.tgz}
name: timezones-list
version: 3.0.2
dev: false
registry.npmmirror.com/tiny-invariant@1.3.1:
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz}
name: tiny-invariant

View File

@@ -14,7 +14,7 @@
| FastAI4k - 对话 | 0.015 |
| FastAI16k - 对话 | 0.03 |
| FastAI-Plus - 对话 | 0.45 |
| 文件拆分 | 0.03 |
| 文件 QA 拆分 | 0.03 |
**其他问题**
| 交流群 | 小助手 |

View File

@@ -1,7 +1,7 @@
### Fast GPT V4.3
### Fast GPT V4.4.1
1. 新增 - 知识库源文件存储,可以从引用窗口点击文件名,查看源文件。
2. 新增 - 用户反馈和管理员标注预期答案,以不断提高模型回复准确率。 该功能为测试版,未来交互可能会有变化,欢迎大家提出宝贵意见。
1. 新增 - 知识库目录结构
2. 新增 - 分享链接支持配置 IP 限流、过期时间、最大额度等
3. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/)
4. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
5. [点击查看商业版](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683254592786" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M450.09164971 42.7357605a123.86965959 123.86965959 0 0 1 123.76374272 0L889.06369251 222.84722403a123.92261859 123.92261859 0 0 1 62.06722618 107.2407279v360.38180181c0 44.22025102-23.6194395 85.05116445-61.9613093 107.13480989l-0.10591688 0.10591687-315.20830008 180.11146353a123.86965959 123.86965959 0 0 1-123.76374272 0L134.93630749 797.7104805a123.92261859 123.92261859 0 0 1-62.06722618-107.24072676V330.08795193c0-44.22025102 23.67239737-85.05116445 61.9613093-107.13481102l0.10591688-0.10591688z m462.16781482 223.59029646a33.78744889 33.78744889 0 0 0-46.17971029-12.28634453l-353.81496263 204.57823687L158.44982898 254.09267029a33.78744889 33.78744889 0 0 0-33.89336463 58.46605597l353.6031289 204.47232v430.02207687c0 18.00585102 15.14609778 32.62236445 33.84040675 32.62236444a33.20490667 33.20490667 0 0 0 33.73449102-32.62236444V517.29583787l354.18567111-204.79006948a33.78744889 33.78744889 0 0 0 14.66947129-41.20162304z" p-id="1353"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,13 @@
"App": "App",
"Cancel": "No",
"Confirm": "Yes",
"Create New": "Create",
"Dataset": "Dataset",
"Export": "Export",
"Folder": "Folder",
"Move": "Move",
"Name": "Name",
"Rename": "Rename",
"Running": "Running",
"Select value is empty": "Select value is empty",
"UnKnow": "UnKnow",
@@ -38,6 +45,7 @@
"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",
@@ -62,10 +70,8 @@
"online": "Online Chat",
"share": "Share",
"test": "Test Chat "
}
},
"commom": {
"Password inconsistency": "Password inconsistency"
},
"retry": "Retry"
},
"common": {
"Add": "Add",
@@ -75,14 +81,26 @@
"Copy Successful": "Copy Successful",
"Course": "",
"Delete": "Delete",
"Delete Failed": "Delete Failed",
"Delete Success": "Delete Successful",
"Delete Warning": "Warning",
"Edit": "Edit",
"Expired Time": "Expired",
"Filed is repeat": "Filed is repeated",
"Filed is repeated": "",
"Input": "Input",
"Name is empty": "Name is empty",
"Output": "Output",
"Password inconsistency": "Password inconsistency",
"Rename": "Rename",
"Search": "Search",
"Status": "Status",
"Update Successful": "Update Successful",
"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"
},
@@ -92,9 +110,11 @@
"Create File": "Create File",
"Create file": "Create file",
"Drag and drop": "Drag and drop files here",
"Embedding": "Embedding",
"Fetch Url": "Fetch Url",
"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}}%",
@@ -148,6 +168,27 @@
"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 move the folder": "Confirm Move",
"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",
"Move Failed": "Move Failed",
"My Dataset": "My Dataset",
"No Folder": "No Folder",
"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!"
},
"navbar": {
"Account": "Account",
"Apps": "Apps",
@@ -156,6 +197,28 @@
"Store": "Store",
"Tools": "Tools"
},
"outlink": {
"Copy Iframe": "Copy Iframe",
"Copy Link": "Copy",
"Create Ifrme Window": "Create Iframe Link",
"Create Share Window": "Create Share Window",
"Delete Link": "Delete",
"Edit Ifrme Link": "Edit Iframe Link",
"Edit Link": "Edit",
"Edit Share Window": "Edit Share Window",
"Link Name": "Link Name",
"Link is empty": "",
"Max credit": "Credit",
"Max credit tips": "What is the maximum amount of money that can be consumed by the link? If the link is exceeded, it will be banned. -1 indicates no limit.",
"QPM": "QPM",
"QPM Tips": "The maximum number of queries per IP address per minute",
"QPM is empty": "QPM is empty",
"Response Detail": "Detail",
"Response Detail tips": "Whether detailed data such as references and full context need to be returned"
},
"system": {
"Help Document": "Document"
},
"user": {
"Account": "Account",
"Amount of earnings": "Earnings",
@@ -168,6 +231,7 @@
"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.",
"Language": "Language",
"Notice": "Notice",
"Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting",
@@ -184,6 +248,7 @@
"Sign Out": "Sign Out",
"Source": "Source",
"Time": "Time",
"Timezone": "Timezone",
"Total Amount": "Total Amount",
"Update Password": "Update Password",
"Update password failed": "Update password failed",

View File

@@ -2,6 +2,13 @@
"App": "应用",
"Cancel": "取消",
"Confirm": "确认",
"Create New": "新建",
"Dataset": "知识库",
"Export": "导出",
"Folder": "文件夹",
"Move": "移动",
"Name": "名称",
"Rename": "重命名",
"Running": "运行中",
"Select value is empty": "选择的内容为空",
"UnKnow": "未知",
@@ -38,6 +45,7 @@
"Admin Mark Content": "纠正后的回复",
"Complete Response": "完整响应",
"Confirm to clear history": "确认清空该应用的在线聊天记录?分享和 API 调用的记录不会被清空。",
"Confirm to clear share chat histroy": "确认删除所有聊天记录?",
"Exit Chat": "退出聊天",
"Feedback Close": "关闭反馈",
"Feedback Failed": "提交反馈异常",
@@ -62,10 +70,8 @@
"online": "在线使用",
"share": "外部链接调用",
"test": "测试"
}
},
"commom": {
"Password inconsistency": "两次密码不一致"
},
"retry": "重新生成"
},
"common": {
"Add": "添加",
@@ -75,14 +81,26 @@
"Copy Successful": "复制成功",
"Course": "",
"Delete": "删除",
"Delete Failed": "删除失败",
"Delete Success": "删除成功",
"Delete Warning": "删除警告",
"Edit": "编辑",
"Expired Time": "过期时间",
"Filed is repeat": "",
"Filed is repeated": "字段重复了",
"Input": "输入",
"Name is empty": "名称不能为空",
"Output": "输出",
"Password inconsistency": "两次密码不一致",
"Rename": "重命名",
"Search": "搜索",
"Status": "状态",
"Update Successful": "更新成功",
"export": ""
},
"dataset": {
"Confirm to delete the data": "确认删除该数据?",
"Export": "导出",
"Queue Desc": "该数据是指整个系统当前待训练的数量。{{title}} 采用排队训练的方式,如果待训练的数据过多,可能需要等待一段时间",
"System Data Queue": "排队长度"
},
@@ -92,9 +110,11 @@
"Create File": "创建新文件",
"Create file": "创建文件",
"Drag and drop": "拖拽文件至此",
"Embedding": "索引中",
"Fetch Url": "链接读取",
"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}}%",
@@ -148,6 +168,27 @@
"desc": "基于 LLM 大模型的 AI 知识库问答平台",
"slogan": "让 AI 更懂你的知识"
},
"kb": {
"Chunk Length": "数据总量",
"Confirm move the folder": "确认移动到该目录",
"Confirm to delete the file": "确认删除该文件及其所有数据?",
"Create Folder": "创建文件夹",
"Delete Dataset Error": "删除知识库异常",
"Edit Folder": "编辑文件夹",
"File Size": "文件大小",
"Filename": "文件名",
"Files": "文件: {{total}}个",
"Folder Name": "输入文件夹名称",
"Move Failed": "移动出现错误~",
"My Dataset": "我的知识库",
"No Folder": "没有子目录了~",
"Other Data": "其他数据",
"Select Dataset": "选择该知识库",
"Select Folder": "进入文件夹",
"Upload Time": "上传时间",
"deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!",
"deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!"
},
"navbar": {
"Account": "账号",
"Apps": "应用",
@@ -156,6 +197,28 @@
"Store": "应用市场",
"Tools": "工具"
},
"outlink": {
"Copy Iframe": "复制嵌入",
"Copy Link": "复制",
"Create Ifrme Window": "创建嵌入链接",
"Create Share Window": "创建免登录窗口",
"Delete Link": "删除链接",
"Edit Ifrme Link": "更新嵌入链接",
"Edit Link": "编辑",
"Edit Share Window": "更新分享窗口",
"Link Name": "分享链接的名字",
"Link is empty": "",
"Max credit": "最大金额",
"Max credit tips": "该链接最大可消耗多少金额,超出后链接将被禁止使用。-1 代表无限制。",
"QPM": "",
"QPM Tips": "每个 IP 每分钟最多提问多少次",
"QPM is empty": "QPM 不能为空",
"Response Detail": "返回详情",
"Response Detail tips": "是否需要返回引用、完整上下文等详细数据"
},
"system": {
"Help Document": "帮助文档"
},
"user": {
"Account": "账号",
"Amount of earnings": "收益(¥)",
@@ -168,6 +231,7 @@
"Copy invite url": "复制邀请链接",
"Invite Url": "邀请链接",
"Invite url tip": "通过该链接注册的好友将永久与你绑定,其充值时你会获得一定余额奖励。\n此外好友使用手机号注册时你将立即获得 5 元奖励。",
"Language": "语言",
"Notice": "通知",
"Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置",
@@ -184,6 +248,7 @@
"Sign Out": "登出",
"Source": "来源",
"Time": "时间",
"Timezone": "时区",
"Total Amount": "总金额",
"Update Password": "修改密码",
"Update password failed": "修改密码异常",

View File

@@ -1,9 +1,7 @@
import { GET, POST, DELETE, PUT } from './request';
import type { ChatHistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import type { InitChatResponse } from './response/chat';
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';
@@ -40,32 +38,6 @@ export const delChatRecordById = (data: { chatId: string; contentId: string }) =
export const putChatHistory = (data: UpdateHistoryProps) =>
PUT('/chat/history/updateChatHistory', data);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init`, data);
/**
* create a shareChat
*/
export const createShareChat = (
data: ShareChatEditType & {
appId: string;
}
) => POST<string>(`/chat/shareChat/create`, data);
/**
* get shareChat
*/
export const getShareChatList = (appId: string) =>
GET<OutLinkSchema[]>(`/chat/shareChat/list`, { appId });
/**
* 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);

8
client/src/api/core/dataset/file.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
import { RequestPaging } from '../../../types/index';
export type GetFileListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export type UpdateFileProps = { id: string; name?: string; datasetUsed?: boolean };

View File

@@ -0,0 +1,15 @@
import { GET, POST, PUT, DELETE } from '@/api/request';
import type { FileInfo, KbFileItemType } from '@/types/plugin';
import type { GetFileListProps, UpdateFileProps } from './file.d';
export const getDatasetFiles = (data: GetFileListProps) =>
POST<KbFileItemType[]>(`/core/dataset/file/list`, data);
export const delDatasetFileById = (params: { fileId: string; kbId: string }) =>
DELETE(`/core/dataset/file/delById`, params);
export const getFileInfoById = (fileId: string) =>
GET<FileInfo>(`/core/dataset/file/detail`, { fileId });
export const delDatasetEmptyFiles = (kbId: string) =>
DELETE(`/core/dataset/file/delEmptyFiles`, { kbId });
export const updateDatasetFile = (data: UpdateFileProps) => PUT(`/core/dataset/file/update`, data);

View File

@@ -1,6 +1,5 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { DatasetItemType, KbItemType, KbListItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import type { DatasetItemType, KbItemType, KbListItemType, KbPathItemType } from '@/types/plugin';
import { TrainingModeEnum } from '@/constants/plugin';
import {
Props as PushDataProps,
@@ -10,13 +9,20 @@ 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 } from '../request/kb';
import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb';
import { QuoteItemType } from '@/types/chat';
import { KbTypeEnum } from '@/constants/kb';
import { getToken } from '@/utils/user';
import download from 'downloadjs';
/* knowledge base */
export const getKbList = () => GET<KbListItemType[]>(`/plugins/kb/list`);
export const getKbList = (data: { parentId?: string; type?: `${KbTypeEnum}` }) =>
GET<KbListItemType[]>(`/plugins/kb/list`, data);
export const getAllDataset = () => GET<KbListItemType[]>(`/plugins/kb/allDataset`);
export const getKbPaths = (parentId?: string) =>
GET<KbPathItemType[]>('/plugins/kb/paths', { parentId });
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
@@ -27,24 +33,27 @@ export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, dat
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
/* kb data */
type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* 获取导出数据(不分页)
* export and download data
*/
export const getExportDataList = (kbId: string) =>
GET<[string, string, string][]>(
`/plugins/kb/data/exportModelData`,
{ kbId },
{
timeout: 600000
export const exportDataset = (data: { kbId: string }) =>
fetch(`/api/plugins/kb/data/exportAll?kbId=${data.kbId}`, {
method: 'GET',
headers: {
token: getToken()
}
);
})
.then(async (res) => {
if (!res.ok) {
const data = await res.json();
throw new Error(data?.message || 'Export failed');
}
return res.blob();
})
.then((blob) => download(blob, 'dataset.csv', 'text/csv'));
/**
* 获取模型正在拆分数据的数量

View File

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

View File

@@ -1,5 +1,4 @@
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { baseUrl } from '../../service/lib/openai';
interface ConfigType {
headers?: { [key: string]: string };

View File

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

View File

@@ -0,0 +1,34 @@
import { GET, POST, DELETE } from '../request';
import type { InitShareChatResponse } from '../response/chat';
import type { OutLinkEditType } from '@/types/support/outLink';
import type { OutLinkSchema } from '@/types/support/outLink';
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/support/outLink/init`, data);
/**
* create a shareChat
*/
export const createShareChat = (
data: OutLinkEditType & {
appId: string;
type: OutLinkSchema['type'];
}
) => POST<string>(`/support/outLink/create`, data);
export const putShareChat = (data: OutLinkEditType) =>
POST<string>(`/support/outLink/update`, data);
/**
* get shareChat
*/
export const getShareChatList = (appId: string) =>
GET<OutLinkSchema[]>(`/support/outLink/list`, { appId });
/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/support/outLink/delete?id=${id}`);

View File

@@ -1,20 +1,4 @@
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

@@ -5,16 +5,21 @@ import { UserAuthTypeEnum } from '@/constants/common';
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
import type { PagingData, RequestPaging } from '@/types';
import { informSchema, PaySchema } from '@/types/mongoSchema';
import { OAuthEnum } from '@/constants/user';
export const sendAuthCode = (data: {
username: string;
type: `${UserAuthTypeEnum}`;
googleToken: string;
}) => POST(`/plusApi/user/account/sendCode`, data);
}) => POST(`/plusApi/user/inform/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 oauthLogin = (params: {
type: `${OAuthEnum}`;
code: string;
callbackUrl: string;
inviterId?: string;
}) => POST<ResLogin>('/plusApi/user/account/login/oauth', params);
export const postRegister = ({
username,
@@ -27,7 +32,7 @@ export const postRegister = ({
password: string;
inviterId?: string;
}) =>
POST<ResLogin>(`/plusApi/user/account/register`, {
POST<ResLogin>(`/plusApi/user/account/register/emailAndPhone`, {
username,
code,
inviterId,
@@ -43,7 +48,7 @@ export const postFindPassword = ({
code: string;
password: string;
}) =>
POST<ResLogin>(`/plusApi/user/account/updatePasswordByCode`, {
POST<ResLogin>(`/plusApi/user/account/password/updateByCode`, {
username,
code,
password: createHashPassword(password)

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import { getKbDataItemById } from '@/api/plugins/kb';
import { useLoading } from '@/hooks/useLoading';
@@ -9,6 +9,7 @@ import MyIcon from '@/components/Icon';
import InputDataModal, { RawFileText } from '@/pages/kb/detail/components/InputDataModal';
import MyModal from '../MyModal';
import { KbDataItemType } from '@/types/plugin';
import { useRouter } from 'next/router';
type SearchType = KbDataItemType & {
kb_id?: string;
@@ -19,15 +20,18 @@ const QuoteModal = ({
rawSearch = [],
onClose
}: {
onUpdateQuote: (quoteId: string, sourceText: string) => Promise<void>;
onUpdateQuote: (quoteId: string, sourceText?: string) => Promise<void>;
rawSearch: SearchType[];
onClose: () => void;
}) => {
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editDataItem, setEditDataItem] = useState<QuoteItemType>();
const isShare = useMemo(() => router.pathname === '/chat/share', [router.pathname]);
/**
* click edit, get new kbDataItem
*/
@@ -91,10 +95,12 @@ const QuoteModal = ({
_hover={{ '& .edit': { display: 'flex' } }}
overflow={'hidden'}
>
{item.source && <RawFileText filename={item.source} fileId={item.file_id} />}
{item.source && !isShare && (
<RawFileText filename={item.source} fileId={item.file_id} />
)}
<Box>{item.q}</Box>
<Box>{item.a}</Box>
{item.id && (
{item.id && !isShare && (
<Box
className="edit"
display={'none'}
@@ -129,7 +135,7 @@ const QuoteModal = ({
{editDataItem && (
<InputDataModal
onClose={() => setEditDataItem(undefined)}
onSuccess={() => onUpdateQuote(editDataItem.id, '手动修改')}
onSuccess={() => onUpdateQuote(editDataItem.id)}
onDelete={() => onUpdateQuote(editDataItem.id, '已删除')}
kbId={editDataItem.kb_id}
defaultValues={{

View File

@@ -44,7 +44,7 @@ const ResponseTags = ({
};
}, [responseData]);
const updateQuote = useCallback(async (quoteId: string, sourceText: string) => {}, []);
const updateQuote = useCallback(async (quoteId: string, sourceText?: string) => {}, []);
const TagStyles: BoxProps = {
mr: 2,

View File

@@ -12,7 +12,7 @@ import {
import MyModal from '../MyModal';
import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { useDatasetStore } from '@/store/dataset';
import { useToast } from '@/hooks/useToast';
import Avatar from '../Avatar';
import MyIcon from '@/components/Icon';
@@ -29,10 +29,10 @@ const SelectDataset = ({
const theme = useTheme();
const { isPc } = useGlobalStore();
const { toast } = useToast();
const { myKbList, loadKbList } = useUserStore();
const { myKbList, loadKbList } = useDatasetStore();
const [selectedId, setSelectedId] = useState<string>();
useQuery(['loadKbList'], loadKbList);
useQuery(['loadKbList'], () => loadKbList());
return (
<MyModal isOpen={true} onClose={onClose} w={'100%'} maxW={['90vw', '900px']} isCentered={!isPc}>

View File

@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Box, ModalBody, useTheme, ModalHeader, Flex } from '@chakra-ui/react';
import { Box, ModalBody, useTheme, Flex } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@/types/chat';
import { useTranslation } from 'react-i18next';

View File

@@ -26,8 +26,7 @@ import {
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 { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
import { useMarkdown } from '@/hooks/useMarkdown';
import { VariableItemType } from '@/types/app';
import { VariableInputEnum } from '@/constants/app';
@@ -37,7 +36,7 @@ import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global';
import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { useTranslation } from 'react-i18next';
import { customAlphabet } from 'nanoid';
import { userUpdateChatFeedback, adminUpdateChatFeedback } from '@/api/chat';
@@ -55,6 +54,7 @@ const SelectDataset = dynamic(() => import('./SelectDataset'));
const InputDataModal = dynamic(() => import('@/pages/kb/detail/components/InputDataModal'));
import styles from './index.module.scss';
import Script from 'next/script';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
@@ -293,7 +293,7 @@ const ChatBox = (
* user confirm send prompt
*/
const sendPrompt = useCallback(
async (variables: Record<string, any> = {}, inputVal = '') => {
async (variables: Record<string, any> = {}, inputVal = '', history = chatHistory) => {
if (!onStartChat) return;
if (isChatting) {
toast({
@@ -314,7 +314,7 @@ const ChatBox = (
}
const newChatList: ChatSiteItemType[] = [
...chatHistory,
...history,
{
dataId: nanoid(),
obj: 'Human',
@@ -343,17 +343,14 @@ const ChatBox = (
const abortSignal = new AbortController();
controller.current = abortSignal;
const messages = adaptChatItem_openAI({ messages: newChatList, reserveId: true });
const messages = adaptChat2GptMessages({ messages: newChatList, reserveId: true });
const { responseData } = await onStartChat({
chatList: newChatList,
messages,
controller: abortSignal,
generatingMessage,
variables: {
...getDefaultChatVariables(),
...variables
}
variables
});
// set finish status
@@ -410,6 +407,22 @@ 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,
@@ -439,29 +452,22 @@ 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',
pl: 1,
mt: 2
};
const MessageCardStyle: BoxProps = {
px: 4,
py: 3,
borderRadius: '0 8px 8px 8px',
boxShadow: '0 0 8px rgba(0,0,0,0.15)'
boxShadow: '0 0 8px rgba(0,0,0,0.15)',
display: 'inline-block',
maxW: ['calc(100% - 25px)', 'calc(100% - 40px)']
};
const messageCardMaxW = ['calc(100% - 25px)', 'calc(100% - 40px)'];
const showEmpty = useMemo(
() =>
feConfigs?.show_emptyChat &&
@@ -473,7 +479,7 @@ const ChatBox = (
);
const statusBoxData = useMemo(() => {
const colorMap = {
loading: '#67c13b',
loading: 'myGray.700',
running: '#67c13b',
finish: 'myBlue.600'
};
@@ -487,6 +493,7 @@ const ChatBox = (
};
}, [chatHistory, isChatting, t]);
// page change and abort request
useEffect(() => {
return () => {
controller.current?.abort('leave');
@@ -495,16 +502,7 @@ const ChatBox = (
};
}, [router.query]);
useEffect(() => {
event.on('guideClick', ({ text }: { text: string }) => {
if (!text) return;
handleSubmit((data) => sendPrompt(data, text))();
});
return () => {
event.off('guideClick');
};
}, [handleSubmit, sendPrompt]);
// page destroy and abort request
useEffect(() => {
const listen = () => {
cancelBroadcast();
@@ -516,88 +514,101 @@ 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 maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
{showEmpty && <Empty />}
{!!welcomeText && (
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
<Box py={3}>
{/* avatar */}
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Card order={2} mt={2} {...MessageCardStyle} bg={'white'} maxW={messageCardMaxW}>
<Markdown source={`~~~guide \n${welcomeText}`} isChatting={false} />
</Card>
</Flex>
<Box textAlign={'left'}>
<Card order={2} mt={2} {...MessageCardStyle} bg={'white'}>
<Markdown source={`~~~guide \n${welcomeText}`} isChatting={false} />
</Card>
</Box>
</Box>
)}
{/* variable input */}
{!!variableModules?.length && (
<Flex flexDirection={'column'} alignItems={'flex-start'} py={2}>
<Box py={3}>
{/* avatar */}
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Card
order={2}
mt={2}
bg={'white'}
w={'400px'}
maxW={messageCardMaxW}
{...MessageCardStyle}
>
{variableModules.map((item) => (
<Box key={item.id} mb={4}>
<VariableLabel required={item.required}>{item.label}</VariableLabel>
{item.type === VariableInputEnum.input && (
<Input
isDisabled={variableIsFinish}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.select && (
<MySelect
width={'100%'}
isDisabled={variableIsFinish}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
setRefresh(!refresh);
}}
/>
)}
</Box>
))}
{!variableIsFinish && (
<Button
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
setVariableInputFinish(true);
})}
>
{'开始对话'}
</Button>
)}
</Card>
</Flex>
<Box textAlign={'left'}>
<Card order={2} mt={2} bg={'white'} w={'400px'} {...MessageCardStyle}>
{variableModules.map((item) => (
<Box key={item.id} mb={4}>
<VariableLabel required={item.required}>{item.label}</VariableLabel>
{item.type === VariableInputEnum.input && (
<Input
isDisabled={variableIsFinish}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.select && (
<MySelect
width={'100%'}
isDisabled={variableIsFinish}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
{...register(item.key, {
required: item.required
})}
value={getValues(item.key)}
onchange={(e) => {
setValue(item.key, e);
setRefresh(!refresh);
}}
/>
)}
</Box>
))}
{!variableIsFinish && (
<Button
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
borderRadius={'lg'}
onClick={handleSubmit((data) => {
onUpdateVariable?.(data);
setVariables(data);
setVariableInputFinish(true);
})}
>
{'开始对话'}
</Button>
)}
</Card>
</Box>
</Box>
)}
{/* chat history */}
<Box id={'history'}>
{chatHistory.map((item, index) => (
<Flex
position={'relative'}
<Box
key={item.dataId}
flexDirection={'column'}
alignItems={item.obj === 'Human' ? 'flex-end' : 'flex-start'}
@@ -611,12 +622,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={'复制'}>
<Flex {...controlContainerStyle} justifyContent={'flex-end'} mr={3}>
<MyTooltip label={t('common.Copy')}>
<MyIcon
{...controlIconStyle}
name={'copy'}
@@ -624,8 +631,18 @@ 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={'删除'}>
<MyTooltip label={t('common.Delete')}>
<MyIcon
{...controlIconStyle}
mr={0}
@@ -646,7 +663,7 @@ const ChatBox = (
</Flex>
<ChatAvatar src={userAvatar} type={'Human'} />
</Flex>
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
<Box mt={['6px', 2]} textAlign={'right'}>
<Card
className="markdown"
whiteSpace={'pre-wrap'}
@@ -663,7 +680,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}
@@ -809,7 +826,7 @@ const ChatBox = (
</Flex>
)}
</Flex>
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
<Box textAlign={'left'} mt={['6px', 2]}>
<Card bg={'white'} {...MessageCardStyle}>
<Markdown
source={item.value}
@@ -837,7 +854,7 @@ const ChatBox = (
</Box>
</>
)}
</Flex>
</Box>
))}
</Box>
</Box>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694437679570" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7334" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="7335"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.962667L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="7336"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="7337"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.648-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="7338"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,8 @@
<?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>

After

Width:  |  Height:  |  Size: 867 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694403033666" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4053" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M1016.32 494.08l-143.872-143.36c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84L936.96 486.4h-399.36V87.04l99.84 100.352c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84l-143.36-143.872c-9.728-9.728-26.112-9.728-35.84 0l-143.36 143.872c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0L486.4 87.04v399.36H87.04l100.352-99.84c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0l-143.872 143.36c-9.728 9.728-9.728 26.112 0 35.84l143.872 143.36c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84L87.04 537.6h399.36v399.36l-99.84-100.352c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84l143.36 143.872c9.728 9.728 26.112 9.728 35.84 0l143.36-143.872c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0L537.6 936.96v-399.36h399.36l-100.352 99.84c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0l143.872-143.36c10.24-9.728 10.24-26.112 0-35.84z" p-id="4054"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,8 @@
<?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>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,3 @@
<svg 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>

After

Width:  |  Height:  |  Size: 151 B

View File

@@ -0,0 +1,8 @@
<?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>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,96 +1,109 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import type { IconProps } from '@chakra-ui/react';
import { Icon } from '@chakra-ui/react';
const map = {
appFill: require('./icons/fill/app.svg').default,
appLight: require('./icons/light/app.svg').default,
copy: require('./icons/copy.svg').default,
chatSend: require('./icons/chatSend.svg').default,
delete: require('./icons/delete.svg').default,
stop: require('./icons/stop.svg').default,
collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default,
empty: require('./icons/empty.svg').default,
back: require('./icons/back.svg').default,
backFill: require('./icons/fill/back.svg').default,
more: require('./icons/more.svg').default,
tabbarChat: require('./icons/phoneTabbar/chat.svg').default,
tabbarModel: require('./icons/phoneTabbar/app.svg').default,
tabbarMore: require('./icons/phoneTabbar/more.svg').default,
tabbarMe: require('./icons/phoneTabbar/me.svg').default,
closeSolid: require('./icons/closeSolid.svg').default,
wx: require('./icons/wx.svg').default,
out: require('./icons/out.svg').default,
git: require('./icons/git.svg').default,
gitFill: require('./icons/fill/git.svg').default,
menu: require('./icons/menu.svg').default,
edit: require('./icons/edit.svg').default,
inform: require('./icons/inform.svg').default,
export: require('./icons/export.svg').default,
text: require('./icons/text.svg').default,
history: require('./icons/history.svg').default,
kbTest: require('./icons/kbTest.svg').default,
date: require('./icons/date.svg').default,
apikey: require('./icons/apikey.svg').default,
save: require('./icons/save.svg').default,
minus: require('./icons/minus.svg').default,
chat: require('./icons/light/chat.svg').default,
chatFill: require('./icons/fill/chat.svg').default,
clear: require('./icons/light/clear.svg').default,
apiLight: require('./icons/light/appApi.svg').default,
overviewLight: require('./icons/light/overview.svg').default,
settingLight: require('./icons/light/setting.svg').default,
shareLight: require('./icons/light/share.svg').default,
dbLight: require('./icons/light/db.svg').default,
dbFill: require('./icons/fill/db.svg').default,
appStoreLight: require('./icons/light/appStore.svg').default,
appStoreFill: require('./icons/fill/appStore.svg').default,
meLight: require('./icons/light/me.svg').default,
meFill: require('./icons/fill/me.svg').default,
welcomeText: require('./icons/modules/welcomeText.svg').default,
variable: require('./icons/modules/variable.svg').default,
setTop: require('./icons/light/setTop.svg').default,
fullScreenLight: require('./icons/light/fullScreen.svg').default,
voice: require('./icons/voice.svg').default,
html: require('./icons/file/html.svg').default,
pdf: require('./icons/file/pdf.svg').default,
markdown: require('./icons/file/markdown.svg').default,
importLight: require('./icons/light/import.svg').default,
manualImport: require('./icons/file/manualImport.svg').default,
indexImport: require('./icons/file/indexImport.svg').default,
csvImport: require('./icons/file/csv.svg').default,
qaImport: require('./icons/file/qaImport.svg').default,
uploadFile: require('./icons/file/uploadFile.svg').default,
closeLight: require('./icons/light/close.svg').default,
customTitle: require('./icons/light/customTitle.svg').default,
billRecordLight: require('./icons/light/billRecord.svg').default,
informLight: require('./icons/light/inform.svg').default,
payRecordLight: require('./icons/light/payRecord.svg').default,
loginoutLight: require('./icons/light/loginout.svg').default,
chatModelTag: require('./icons/light/chatModelTag.svg').default,
language_en: require('./icons/language/en.svg').default,
language_zh: require('./icons/language/zh.svg').default,
outlink_share: require('./icons/outlink/share.svg').default,
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
const iconPaths = {
appFill: () => import('./icons/fill/app.svg'),
appLight: () => import('./icons/light/app.svg'),
copy: () => import('./icons/copy.svg'),
chatSend: () => import('./icons/chatSend.svg'),
delete: () => import('./icons/delete.svg'),
stop: () => import('./icons/stop.svg'),
collectionLight: () => import('./icons/collectionLight.svg'),
collectionSolid: () => import('./icons/collectionSolid.svg'),
empty: () => import('./icons/empty.svg'),
back: () => import('./icons/back.svg'),
backFill: () => import('./icons/fill/back.svg'),
more: () => import('./icons/more.svg'),
tabbarChat: () => import('./icons/phoneTabbar/chat.svg'),
tabbarModel: () => import('./icons/phoneTabbar/app.svg'),
tabbarMore: () => import('./icons/phoneTabbar/more.svg'),
tabbarMe: () => import('./icons/phoneTabbar/me.svg'),
closeSolid: () => import('./icons/closeSolid.svg'),
wx: () => import('./icons/wx.svg'),
out: () => import('./icons/out.svg'),
git: () => import('./icons/git.svg'),
gitFill: () => import('./icons/fill/git.svg'),
googleFill: () => import('./icons/fill/google.svg'),
menu: () => import('./icons/menu.svg'),
edit: () => import('./icons/edit.svg'),
inform: () => import('./icons/inform.svg'),
export: () => import('./icons/export.svg'),
text: () => import('./icons/text.svg'),
history: () => import('./icons/history.svg'),
kbTest: () => import('./icons/kbTest.svg'),
date: () => import('./icons/date.svg'),
apikey: () => import('./icons/apikey.svg'),
save: () => import('./icons/save.svg'),
minus: () => import('./icons/minus.svg'),
chat: () => import('./icons/light/chat.svg'),
chatFill: () => import('./icons/fill/chat.svg'),
clear: () => import('./icons/light/clear.svg'),
apiLight: () => import('./icons/light/appApi.svg'),
overviewLight: () => import('./icons/light/overview.svg'),
settingLight: () => import('./icons/light/setting.svg'),
shareLight: () => import('./icons/light/share.svg'),
dbLight: () => import('./icons/light/db.svg'),
dbFill: () => import('./icons/fill/db.svg'),
appStoreLight: () => import('./icons/light/appStore.svg'),
appStoreFill: () => import('./icons/fill/appStore.svg'),
meLight: () => import('./icons/light/me.svg'),
meFill: () => import('./icons/fill/me.svg'),
welcomeText: () => import('./icons/modules/welcomeText.svg'),
variable: () => import('./icons/modules/variable.svg'),
setTop: () => import('./icons/light/setTop.svg'),
fullScreenLight: () => import('./icons/light/fullScreen.svg'),
voice: () => import('./icons/voice.svg'),
html: () => import('./icons/file/html.svg'),
pdf: () => import('./icons/file/pdf.svg'),
markdown: () => import('./icons/file/markdown.svg'),
importLight: () => import('./icons/light/import.svg'),
manualImport: () => import('./icons/file/manualImport.svg'),
indexImport: () => import('./icons/file/indexImport.svg'),
csvImport: () => import('./icons/file/csv.svg'),
qaImport: () => import('./icons/file/qaImport.svg'),
uploadFile: () => import('./icons/file/uploadFile.svg'),
closeLight: () => import('./icons/light/close.svg'),
customTitle: () => import('./icons/light/customTitle.svg'),
billRecordLight: () => import('./icons/light/billRecord.svg'),
informLight: () => import('./icons/light/inform.svg'),
payRecordLight: () => import('./icons/light/payRecord.svg'),
loginoutLight: () => import('./icons/light/loginout.svg'),
chatModelTag: () => import('./icons/light/chatModelTag.svg'),
language_en: () => import('./icons/language/en.svg'),
language_zh: () => import('./icons/language/zh.svg'),
outlink_share: () => import('./icons/outlink/share.svg'),
outlink_iframe: () => import('./icons/outlink/iframe.svg'),
addCircle: () => import('./icons/circle/add.svg'),
playFill: () => import('./icons/fill/play.svg'),
courseLight: () => import('./icons/light/course.svg'),
promotionLight: () => import('./icons/light/promotion.svg'),
logsLight: () => import('./icons/light/logs.svg'),
badLight: () => import('./icons/light/bad.svg'),
markLight: () => import('./icons/light/mark.svg'),
retryLight: () => import('./icons/light/retry.svg'),
rightArrowLight: () => import('./icons/light/rightArrow.svg'),
searchLight: () => import('./icons/light/search.svg'),
plusFill: () => import('./icons/fill/plus.svg'),
moveLight: () => import('./icons/light/move.svg')
};
export type IconName = keyof typeof map;
export type IconName = keyof typeof iconPaths;
const MyIcon = (
{ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps,
ref: any
) => {
return map[name] ? (
const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps) => {
const [IconComponent, setIconComponent] = useState<any>(null);
useEffect(() => {
iconPaths[name]()
.then((icon) => {
setIconComponent({ as: icon.default });
})
.catch((error) => console.log(error));
}, [name]);
return name ? (
<Icon
as={map[name]}
{...IconComponent}
w={w}
h={h}
boxSizing={'content-box'}
@@ -101,4 +114,4 @@ const MyIcon = (
) : null;
};
export default React.forwardRef(MyIcon);
export default MyIcon;

View File

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

View File

@@ -9,7 +9,6 @@ import NextLink from 'next/link';
import Badge from '../Badge';
import Avatar from '../Avatar';
import MyIcon from '../Icon';
import Language from '../Language';
import { useTranslation } from 'next-i18next';
import { useGlobalStore } from '@/store/global';
import MyTooltip from '../MyTooltip';
@@ -109,7 +108,7 @@ const Navbar = ({ unread }: { unread: number }) => {
<Avatar
w={'36px'}
h={'36px'}
borderRadius={'none'}
borderRadius={'50%'}
src={userInfo?.avatar}
fallbackSrc={HUMAN_ICON}
/>
@@ -157,7 +156,8 @@ const Navbar = ({ unread }: { unread: number }) => {
<Link
as={NextLink}
{...itemStyles}
href={`/account?type=inform`}
prefetch
href={`/account?currentTab=inform`}
mb={0}
color={'#9096a5'}
>
@@ -181,7 +181,6 @@ const Navbar = ({ unread }: { unread: number }) => {
</Box>
</MyTooltip>
)}
<Language {...itemStyles} />
{feConfigs?.show_git && (
<MyTooltip label={`Git Star: ${gitStar}`} placement={'right-end'}>
<Link

View File

@@ -0,0 +1,64 @@
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

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

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm';
import RemarkMath from 'remark-math';
@@ -14,6 +14,7 @@ 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) {
const match = /language-(\w+)/.exec(className || '');
@@ -26,7 +27,9 @@ function Code({ inline, className, children }: any) {
if (codeType === 'guide') {
return <ChatGuide text={String(children)} />;
}
if (codeType === 'echarts') {
return <EChartsCodeBlock code={String(children)} />;
}
return (
<CodeLight className={className} inline={inline} match={match}>
{children}

View File

@@ -0,0 +1,40 @@
import React, { useState } from 'react';
import { Image, Skeleton, ImageProps } from '@chakra-ui/react';
export const MyImage = (props: ImageProps) => {
const [isLoading, setIsLoading] = useState(true);
const [succeed, setSucceed] = useState(false);
return (
<Skeleton
minH="100px"
isLoaded={!isLoading}
fadeDuration={2}
display={'flex'}
justifyContent={'center'}
my={1}
>
<Image
display={'inline-block'}
borderRadius={'md'}
alt={''}
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
cursor={succeed ? 'pointer' : 'default'}
objectFit={'contain'}
loading={'lazy'}
onLoad={() => {
setIsLoading(false);
setSucceed(true);
}}
onError={() => setIsLoading(false)}
onClick={() => {
if (!succeed) return;
window.open(props.src, '_blank');
}}
{...props}
/>
</Skeleton>
);
};
export default React.memo(MyImage);

View File

@@ -0,0 +1,28 @@
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 !important' : 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

@@ -0,0 +1,54 @@
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: () => any;
}[];
}
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={(e) => {
e.stopPropagation();
item.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 = 'auto', list, onchange, ...props }: Props,
{ placeholder, value, width = '100%', list, onchange, ...props }: Props,
selectRef: any
) => {
const ref = useRef<HTMLButtonElement>(null);
@@ -94,6 +94,8 @@ const MySelect = (
}
zIndex={99}
transform={'translateY(35px) !important'}
maxH={'40vh'}
overflowY={'auto'}
>
{list.map((item) => (
<MenuItem

View File

@@ -67,7 +67,3 @@ export enum OutLinkTypeEnum {
export const HUMAN_ICON = `/icon/human.png`;
export const LOGO_ICON = `/icon/logo.svg`;
export const getDefaultChatVariables = () => ({
cTime: dayjs().format('YYYY/MM/DD HH:mm:ss')
});

View File

@@ -10,7 +10,8 @@ export const fileImgs = [
{ 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: 'md', src: '/imgs/files/markdown.svg' },
{ suffix: '.', src: '/imgs/files/file.svg' }
];
export enum TrackEventName {

View File

@@ -20,7 +20,7 @@ import { ContextExtractEnum, HttpPropsEnum } from './flowField';
export const ChatModelSystemTip =
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
export const ChatModelLimitTip =
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"';
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。不建议内容太长,会影响上下文,可使用变量,例如 {{language}}。可在文档中找到对应的限定例子';
export const userGuideTip = '可以添加特殊的对话前后引导模块,更好的让用户进行对话';
export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
@@ -156,6 +156,7 @@ export const ChatModule: FlowModuleTemplateType = {
key: 'systemPrompt',
type: FlowInputItemTypeEnum.textarea,
label: '系统提示词',
max: 300,
valueType: FlowValueTypeEnum.string,
description: ChatModelSystemTip,
placeholder: ChatModelSystemTip,
@@ -166,6 +167,7 @@ export const ChatModule: FlowModuleTemplateType = {
type: FlowInputItemTypeEnum.textarea,
valueType: FlowValueTypeEnum.string,
label: '限定词',
max: 500,
description: ChatModelLimitTip,
placeholder: ChatModelLimitTip,
value: ''
@@ -175,6 +177,7 @@ export const ChatModule: FlowModuleTemplateType = {
key: 'quoteQA',
type: FlowInputItemTypeEnum.target,
label: '引用内容',
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
valueType: FlowValueTypeEnum.kbQuote
},
Input_Template_History,
@@ -666,6 +669,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
type: 'textarea',
valueType: 'string',
label: '限定词',
max: 500,
description:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。可使用变量,例如 {{language}}。引导例子:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
placeholder:

View File

@@ -14,3 +14,24 @@ export const defaultKbDetail: KbItemType = {
maxToken: 3000
}
};
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,5 +1,5 @@
import type { ShareChatEditType } from '@/types/app';
import type { AppSchema } from '@/types/mongoSchema';
import type { OutLinkEditType } from '@/types/support/outLink';
export const defaultApp: AppSchema = {
_id: '',
@@ -17,6 +17,11 @@ export const defaultApp: AppSchema = {
modules: []
};
export const defaultShareChat: ShareChatEditType = {
name: ''
export const defaultOutLinkForm: OutLinkEditType = {
name: '',
responseDetail: false,
limit: {
QPM: 100,
credit: -1
}
};

View File

@@ -7,4 +7,4 @@ export const TrainingTypeMap = {
[TrainingModeEnum.index]: 'index'
};
export const PgTrainingTableName = 'modeldata';
export const PgDatasetTableName = 'modeldata';

View File

@@ -1,3 +1,7 @@
export enum OAuthEnum {
github = 'github',
google = 'google'
}
export enum BillSourceEnum {
fastgpt = 'fastgpt',
api = 'api',
@@ -10,7 +14,7 @@ export enum PageTypeEnum {
}
export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
[BillSourceEnum.fastgpt]: 'FastGPT 平台',
[BillSourceEnum.fastgpt]: '在线使用',
[BillSourceEnum.api]: 'Api',
[BillSourceEnum.shareLink]: '免登录链接'
};

View File

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

View File

@@ -2,7 +2,7 @@ import React, { useCallback, useRef } from 'react';
import { ModalFooter, ModalBody, Input, useDisclosure, Button } from '@chakra-ui/react';
import MyModal from '@/components/MyModal';
export const useEditInfo = ({
export const useEditTitle = ({
title,
placeholder = ''
}: {
@@ -39,6 +39,7 @@ export const useEditInfo = ({
try {
const val = inputRef.current.value;
await onSuccessCb.current?.(val);
onClose();
} catch (err) {
onErrorCb.current?.(err);

View File

@@ -41,16 +41,14 @@ function App({ Component, pageProps }: AppProps) {
const { setLastRoute } = useGlobalStore();
const [scripts, setScripts] = useState<FeConfigsType['scripts']>([]);
const [googleClientVerKey, setGoogleVerKey] = useState<string>();
useEffect(() => {
// get init data
(async () => {
const {
feConfigs: { scripts, googleClientVerKey }
feConfigs: { scripts }
} = await clientInitData();
setScripts(scripts || []);
setGoogleVerKey(googleClientVerKey);
})();
// add window error track
window.onerror = function (msg, url) {
@@ -94,20 +92,10 @@ function App({ Component, pageProps }: AppProps) {
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<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>
{scripts?.map((item, i) => (
<Script key={i} strategy="lazyOnload" {...item}></Script>
))}
{googleClientVerKey && (
<>
<Script
src={`https://www.recaptcha.net/recaptcha/api.js?render=${googleClientVerKey}`}
strategy="lazyOnload"
></Script>
</>
)}
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={theme}>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />

View File

@@ -1,5 +1,17 @@
import React, { useCallback } from 'react';
import { Box, Flex, Button, useDisclosure, useTheme, Divider } from '@chakra-ui/react';
import React, { useCallback, useRef, useState } from 'react';
import {
Box,
Flex,
Button,
useDisclosure,
useTheme,
Divider,
Select,
Menu,
MenuButton,
MenuList,
MenuItem
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { useToast } from '@/hooks/useToast';
@@ -9,12 +21,17 @@ import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useSelectFile } from '@/hooks/useSelectFile';
import { compressImg } from '@/utils/file';
import { feConfigs } from '@/store/static';
import { feConfigs, systemVersion } 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';
import MyTooltip from '@/components/MyTooltip';
import { getLangStore, LangEnum, langMap, setLangStore } from '@/utils/i18n';
import { useRouter } from 'next/router';
import MyMenu from '@/components/MyMenu';
import MySelect from '@/components/Select';
const PayModal = dynamic(() => import('./PayModal'), {
loading: () => <Loading fixed={false} />,
@@ -31,8 +48,10 @@ const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'), {
const UserInfo = () => {
const theme = useTheme();
const { t } = useTranslation();
const router = useRouter();
const { t, i18n } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const timezones = useRef(timezoneList());
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
});
@@ -55,10 +74,13 @@ const UserInfo = () => {
multiple: false
});
const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore());
const onclickSave = useCallback(
async (data: UserType) => {
await updateUserInfo({
avatar: data.avatar,
timezone: data.timezone,
openaiAccount: data.openaiAccount
});
reset(data);
@@ -102,7 +124,13 @@ const UserInfo = () => {
});
return (
<Box display={['block', 'flex']} py={[2, 10]} justifyContent={'center'} fontSize={['lg', 'xl']}>
<Box
display={['block', 'flex']}
py={[2, 10]}
justifyContent={'center'}
alignItems={'flex-start'}
fontSize={['lg', 'xl']}
>
<Flex
flexDirection={'column'}
alignItems={'center'}
@@ -115,10 +143,12 @@ const UserInfo = () => {
h={['44px', '54px']}
borderRadius={'50%'}
border={theme.borders.base}
overflow={'hidden'}
p={'2px'}
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
mb={2}
>
<Avatar src={userInfo?.avatar} w={'100%'} h={'100%'} />
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
</Box>
</MyTooltip>
@@ -135,30 +165,94 @@ const UserInfo = () => {
mt={[6, 0]}
>
<Flex alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 50px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={'0 0 80px'}>{t('user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
<Box flex={'0 0 50px'}>{t('user.Password')}:&nbsp;</Box>
<Box flex={'0 0 80px'}>{t('user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
<MySelect
value={language}
list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label,
value: key
}))}
onchange={(val: any) => {
const lang = val;
setLangStore(lang);
setLanguage(lang);
i18n?.changeLanguage?.(lang);
router.reload();
}}
/>
</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={1}>*****</Box>
<Button size={['sm', 'md']} variant={'base'} ml={5} onClick={onOpenUpdatePsw}>
{t('user.Change')}
</Button>
</Flex>
{feConfigs?.show_userDetail && (
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'}>{t('user.Balance')}:&nbsp;</Box>
<Box flex={1}>
<strong>{userInfo?.balance.toFixed(3)}</strong>
</Box>
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>
</Flex>
</Box>
)}
{feConfigs?.show_doc && (
<>
<Flex
mt={4}
w={['85%', '300px']}
py={3}
px={6}
border={theme.borders.sm}
borderWidth={'1.5px'}
borderRadius={'md'}
alignItems={'center'}
cursor={'pointer'}
userSelect={'none'}
onClick={() => {
window.open(`https://doc.fastgpt.run/docs/intro`);
}}
>
<MyIcon name={'courseLight'} w={'18px'} />
<Box ml={2} flex={1}>
{t('system.Help Document')}
</Box>
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
<Box fontSize={'md'} ml={2}>
V{systemVersion}
</Box>
</Flex>
</>
)}
{feConfigs?.show_openai_account && (
<>
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
<Flex alignItems={'center'}>
<Box flex={'0 0 50px'}>{t('user.Balance')}:&nbsp;</Box>
<Box flex={1}>
<strong>{userInfo?.balance.toFixed(3)}</strong>
</Box>
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
{t('user.Pay')}
</Button>
</Flex>
</Box>
<Divider my={3} />
<MyTooltip label={'点击配置账号'}>

View File

@@ -6,8 +6,10 @@ import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { getErrText } from '@/utils/tools';
import { useTranslation } from 'react-i18next';
import { formatPrice } from '@/utils/user';
import Markdown from '@/components/Markdown';
import MyModal from '@/components/MyModal';
import { vectorModelList, chatModelList, qaModel } from '@/store/static';
const PayModal = ({ onClose }: { onClose: () => void }) => {
const router = useRouter();
@@ -69,6 +71,7 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
onClose();
}}
title={t('user.Pay')}
isCentered
showCloseBtn={!payId}
>
<ModalBody py={0}>
@@ -100,11 +103,13 @@ const PayModal = ({ onClose }: { onClose: () => void }) => {
source={`
| 计费项 | 价格: 元/ 1K tokens(包含上下文)|
| --- | --- |
| 知识库 - 索引 | 0.002 |
| FastAI4k - 对话 | 0.015 |
| FastAI16k - 对话 | 0.03 |
| FastAI-Plus - 对话 | 0.45 |
| 文件拆分 | 0.03 |`}
${vectorModelList
.map((item) => `| 索引-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
${chatModelList
.map((item) => `| 对话-${item.name} | ${formatPrice(item.price, 1000)} |`)
.join('\n')}
| 文件QA拆分 | ${formatPrice(qaModel.price, 1000)} |`}
/>
</>
)}

View File

@@ -25,7 +25,7 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
const { mutate: onSubmit, isLoading } = useRequest({
mutationFn: (data: FormType) => {
if (data.newPsw !== data.confirmPsw) {
return Promise.reject(t('commom.Password inconsistency'));
return Promise.reject(t('common.Password inconsistency'));
}
return updatePasswordByOld(data);
},

View File

@@ -13,6 +13,7 @@ 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'));
@@ -30,7 +31,7 @@ enum TabEnum {
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation();
const tabList = useRef([
const tabList = [
{
icon: 'meLight',
label: t('user.Personal Information'),
@@ -66,7 +67,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
label: t('user.Sign Out'),
id: TabEnum.loginout
}
]);
];
const { openConfirm, ConfirmModal } = useConfirm({
content: '确认退出登录?'
@@ -97,51 +98,54 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
);
return (
<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>
)}
<>
<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}
activeId={currentTab}
onChange={setCurrentTab}
/>
</Flex>
) : (
<Box mb={3}>
<Tabs
m={'auto'}
size={isPc ? 'md' : 'sm'}
list={tabList.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 />}
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
<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 />}
</Box>
</Flex>
<ConfirmModal />
</PageContainer>
</>
);
};

View File

@@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { rowCount } = await PgClient.query(`SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = '${PgTrainingTableName}'
AND table_name = '${PgDatasetTableName}'
AND column_name = 'file_id'`);
if (rowCount > 0) {
@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
jsonRes(res, {
data: await PgClient.query(
`ALTER TABLE ${PgTrainingTableName} ADD COLUMN file_id VARCHAR(100)`
`ALTER TABLE ${PgDatasetTableName} ADD COLUMN file_id VARCHAR(100)`
)
});
} catch (error) {

View File

@@ -0,0 +1,41 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, KB } from '@/service/mongo';
import { KbTypeEnum } from '@/constants/kb';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
await KB.updateMany(
{
type: { $exists: false }
},
{
$set: {
type: KbTypeEnum.dataset,
parentId: null
}
}
);
const response = await PgClient.update(PgDatasetTableName, {
where: [['file_id', 'undefined']],
values: [{ key: 'file_id', value: '' }]
});
jsonRes(res, {
data: response.rowCount
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -0,0 +1,35 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase } from '@/service/mongo';
import mongoose from 'mongoose';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
const data = await mongoose.connection.db
.collection('dataset.files')
.updateMany({}, { $set: { 'metadata.datasetUsed': true } });
// update pg data
const pg = await PgClient.query(`UPDATE ${PgDatasetTableName}
SET file_id = ''
WHERE (file_id = 'undefined' OR LENGTH(file_id) < 20) AND file_id != '';`);
jsonRes(res, {
data: {
data,
pg
}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -0,0 +1,27 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, Bill } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authUser({ req, authRoot: true });
try {
await Bill.collection.dropIndex('time_1');
} catch (error) {}
try {
await Bill.collection.createIndex({ time: 1 }, { expireAfterSeconds: 90 * 24 * 60 * 60 });
} catch (error) {}
jsonRes(res, {
data: {}
});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -42,6 +42,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/* user auth */
const { userId, user } = await authUser({ req, authBalance: true });
if (!user) {
throw new Error('user not found');
}
/* start process */
const { responseData } = await dispatchModules({
res,

View File

@@ -0,0 +1,62 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, TrainingData } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
import { Types } from 'mongoose';
import { OtherFileId } from '@/constants/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { fileId, kbId } = req.query as { fileId: string; kbId: string };
if (!fileId || !kbId) {
throw new Error('fileId and kbId is required');
}
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
// other data. Delete only vector data
if (fileId === OtherFileId) {
await PgClient.delete(PgDatasetTableName, {
where: [
['user_id', userId],
'AND',
['kb_id', kbId],
"AND (file_id IS NULL OR file_id = '')"
]
});
} else {
// auth file
const gridFs = new GridFSStorage('dataset', userId);
const bucket = gridFs.GridFSBucket();
await gridFs.findAndAuthFile(fileId);
// delete all pg data
await PgClient.delete(PgDatasetTableName, {
where: [['user_id', userId], 'AND', ['kb_id', kbId], 'AND', ['file_id', fileId]]
});
// delete all training data
await TrainingData.deleteMany({
userId,
file_id: fileId
});
// delete file
await bucket.delete(new Types.ObjectId(fileId));
}
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,31 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { kbId } = req.query as { kbId: string };
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const gridFs = new GridFSStorage('dataset', userId);
const collection = gridFs.Collection();
const files = await collection.deleteMany({
uploadDate: { $lte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
['metadata.kbId']: kbId,
['metadata.userId']: userId,
['metadata.datasetUsed']: { $ne: true }
});
jsonRes(res, {
data: files
});
} catch (err) {
jsonRes(res);
}
}

View File

@@ -0,0 +1,43 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
import { OtherFileId } from '@/constants/kb';
import type { FileInfo } from '@/types/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { fileId } = req.query as { kbId: string; fileId: string };
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
if (fileId === OtherFileId) {
return jsonRes<FileInfo>(res, {
data: {
id: OtherFileId,
size: 0,
filename: 'kb.Other Data',
uploadDate: new Date(),
encoding: '',
contentType: ''
}
});
}
const gridFs = new GridFSStorage('dataset', userId);
const file = await gridFs.findAndAuthFile(fileId);
jsonRes<FileInfo>(res, {
data: file
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,112 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, TrainingData } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
import { FileStatusEnum, OtherFileId } from '@/constants/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
let {
pageNum = 1,
pageSize = 10,
kbId,
searchText = ''
} = req.body as { pageNum: number; pageSize: number; kbId: string; searchText: string };
searchText = searchText?.replace(/'/g, '');
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
// find files
const gridFs = new GridFSStorage('dataset', userId);
const collection = gridFs.Collection();
const mongoWhere = {
['metadata.kbId']: kbId,
['metadata.userId']: userId,
['metadata.datasetUsed']: true,
...(searchText && { filename: { $regex: searchText } })
};
const [files, total] = await Promise.all([
collection
.find(mongoWhere, {
projection: {
_id: 1,
filename: 1,
uploadDate: 1,
length: 1
}
})
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.sort({ uploadDate: -1 })
.toArray(),
collection.countDocuments(mongoWhere)
]);
async function GetOtherData() {
return {
id: OtherFileId,
size: 0,
filename: 'kb.Other Data',
uploadTime: new Date(),
status: (await TrainingData.findOne({ userId, kbId, file_id: '' }))
? FileStatusEnum.embedding
: FileStatusEnum.ready,
chunkLength: await PgClient.count(PgDatasetTableName, {
fields: ['id'],
where: [
['user_id', userId],
'AND',
['kb_id', kbId],
"AND (file_id IS NULL OR file_id = '')"
]
})
};
}
const data = await Promise.all([
GetOtherData(),
...files.map(async (file) => {
return {
id: String(file._id),
size: file.length,
filename: file.filename,
uploadTime: file.uploadDate,
status: (await TrainingData.findOne({ userId, kbId, file_id: file._id }))
? FileStatusEnum.embedding
: FileStatusEnum.ready,
chunkLength: await PgClient.count(PgDatasetTableName, {
fields: ['id'],
where: [
['user_id', userId],
'AND',
['kb_id', kbId],
'AND',
['file_id', String(file._id)]
]
})
};
})
]);
jsonRes(res, {
data: {
pageNum,
pageSize,
data: data.flat(),
total
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -0,0 +1,66 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { GridFSStorage } from '@/service/lib/gridfs';
import { UpdateFileProps } from '@/api/core/dataset/file.d';
import { Types } from 'mongoose';
import { PgClient } from '@/service/pg';
import { PgDatasetTableName } from '@/constants/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id, name, datasetUsed } = req.body as UpdateFileProps;
const { userId } = await authUser({ req, authToken: true });
const gridFs = new GridFSStorage('dataset', userId);
const collection = gridFs.Collection();
await collection.findOneAndUpdate(
{
_id: new Types.ObjectId(id)
},
{
$set: {
...(name && { filename: name }),
...(datasetUsed && { ['metadata.datasetUsed']: datasetUsed })
}
}
);
// data source
updateDatasetSource({
fileId: id,
userId,
name
});
jsonRes(res, {});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
async function updateDatasetSource(data: { fileId: string; userId: string; name?: string }) {
const { fileId, userId, name } = data;
if (!fileId || !name || !userId) return;
try {
await PgClient.update(PgDatasetTableName, {
where: [['user_id', userId], 'AND', ['file_id', fileId]],
values: [
{
key: 'source',
value: name
}
]
});
} catch (error) {
setTimeout(() => {
updateDatasetSource(data);
}, 2000);
}
}

View File

@@ -3,7 +3,7 @@ import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -18,7 +18,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// 凭证校验
const { userId } = await authUser({ req });
await PgClient.delete(PgTrainingTableName, {
await PgClient.delete(PgDatasetTableName, {
where: [['user_id', userId], 'AND', ['id', dataId]]
});

View File

@@ -4,12 +4,12 @@ import { connectToDatabase, TrainingData, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { authKb } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
import { PgTrainingTableName, TrainingModeEnum } from '@/constants/plugin';
import { PgDatasetTableName, TrainingModeEnum } from '@/constants/plugin';
import { startQueue } from '@/service/utils/tools';
import { PgClient } from '@/service/pg';
import { modelToolMap } from '@/utils/plugin';
import { getVectorModel } from '@/service/utils/data';
import { DatasetItemType } from '@/types/plugin';
import { countPromptTokens } from '@/utils/common/tiktoken';
export type Props = {
kbId: string;
@@ -88,7 +88,7 @@ export async function pushDataToKb({
]);
const modeMaxToken = {
[TrainingModeEnum.index]: vectorModel.maxToken,
[TrainingModeEnum.index]: vectorModel.maxToken * 1.5,
[TrainingModeEnum.qa]: global.qaModel.maxToken * 0.8
};
@@ -102,9 +102,7 @@ export async function pushDataToKb({
const text = item.q + item.a;
// count q token
const token = modelToolMap.countTokens({
messages: [{ obj: 'System', value: item.q }]
});
const token = countPromptTokens(item.q, 'system');
if (token > modeMaxToken[mode]) {
return;
@@ -136,7 +134,7 @@ export async function pushDataToKb({
try {
const { rows } = await PgClient.query(`
SELECT COUNT(*) > 0 AS exists
FROM ${PgTrainingTableName}
FROM ${PgDatasetTableName}
WHERE md5(q)=md5('${q}') AND md5(a)=md5('${a}') AND user_id='${userId}' AND kb_id='${kbId}'
`);
const exists = rows[0]?.exists || false;
@@ -146,7 +144,6 @@ export async function pushDataToKb({
}
} catch (error) {
console.log(error);
error;
}
return Promise.resolve(data);
})

View File

@@ -5,7 +5,7 @@ import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import { getVector } from '../plugin/vector';
import type { KbTestItemType } from '@/types/plugin';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
import { KB } from '@/service/mongo';
export type Props = {
@@ -43,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
select id, q, a, source, file_id, (vector <#> '[${
vectors[0]
}]') * -1 AS score from ${PgTrainingTableName} where kb_id='${kbId}' AND user_id='${userId}' order by vector <#> '[${
}]') * -1 AS score from ${PgDatasetTableName} where kb_id='${kbId}' AND user_id='${userId}' order by vector <#> '[${
vectors[0]
}]' limit 12;
COMMIT;`

View File

@@ -5,7 +5,7 @@ import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import { KB, connectToDatabase } from '@/service/mongo';
import { getVector } from '../plugin/vector';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
export type Props = {
dataId: string;
@@ -47,10 +47,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})();
// 更新 pg 内容.仅修改a不需要更新向量。
await PgClient.update(PgTrainingTableName, {
await PgClient.update(PgDatasetTableName, {
where: [['id', dataId], 'AND', ['user_id', userId]],
values: [
{ key: 'source', value: '手动修改' },
{ key: 'a', value: a.replace(/'/g, '"') },
...(q
? [

View File

@@ -68,8 +68,9 @@ export async function getVector({
)
.then(async (res) => {
if (!res.data?.data?.[0]?.embedding) {
console.log(res.data);
// @ts-ignore
return Promise.reject(res.data?.error?.message || 'Embedding API Error');
return Promise.reject(res.data?.err?.message || 'Embedding API Error');
}
return {
tokenLen: res.data.usage.total_tokens || 0,

View File

@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error('最多 10 组 API 秘钥');
}
const apiKey = `fastgpt-${nanoid()}`;
const apiKey = `${global.systemEnv?.openapiPrefix || 'fastgpt'}-${nanoid()}`;
await OpenApi.create({
userId,

View File

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

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser, authApp, authShareChat, AuthUserTypeEnum } from '@/service/utils/auth';
import { authUser, authApp } from '@/service/utils/auth';
import { sseErrRes, jsonRes } from '@/service/response';
import { addLog, withNextCors } from '@/service/utils/tools';
import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat';
@@ -28,6 +28,9 @@ import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat';
import { UserModelSchema } from '@/types/mongoSchema';
import { SystemInputEnum } from '@/constants/app';
import { getSystemTime } from '@/utils/user';
import { authOutLinkChat } from '@/service/support/outLink/auth';
import requestIp from 'request-ip';
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
type FastGptWebChatProps = {
@@ -76,28 +79,31 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (!Array.isArray(messages)) {
throw new Error('messages is not array');
}
if (messages.length === 0) {
throw new Error('messages is empty');
}
await connectToDatabase();
let startTime = Date.now();
/* user auth */
const {
let {
// @ts-ignore
responseDetail,
user,
userId,
appId: authAppid,
authType
} = await (shareId
? authShareChat({
shareId
? authOutLinkChat({
shareId,
ip: requestIp.getClientIp(req)
})
: authUser({ req, authBalance: true }));
if (!user) {
throw new Error('Account is error');
}
// if (authType === AuthUserTypeEnum.apikey || shareId) {
// user.openaiAccount = undefined;
// }
appId = appId ? appId : authAppid;
if (!appId) {
@@ -110,13 +116,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
appId,
userId
}),
getChatHistory({ chatId, userId })
getChatHistory({ chatId, appId, userId })
]);
const isOwner = !shareId && userId === String(app.userId);
responseDetail = isOwner || responseDetail;
const prompts = history.concat(gptMessage2ChatType(messages));
if (prompts[prompts.length - 1].obj === 'AI') {
if (prompts[prompts.length - 1]?.obj === 'AI') {
prompts.pop();
}
// user question
@@ -159,7 +166,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
appId,
userId,
variables,
isOwner,
isOwner, // owner update use time
shareId,
source: (() => {
if (shareId) {
@@ -199,7 +206,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
data: '[DONE]'
});
if (isOwner && detail) {
if (responseDetail && detail) {
sseResponse({
res,
event: sseResponseEventEnum.appStreamResponse,
@@ -249,6 +256,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
});
/* running */
export async function dispatchModules({
res,
modules,
@@ -260,12 +268,16 @@ export async function dispatchModules({
}: {
res: NextApiResponse;
modules: AppModuleItemType[];
user?: UserModelSchema;
user: UserModelSchema;
params?: Record<string, any>;
variables?: Record<string, any>;
stream?: boolean;
detail?: boolean;
}) {
variables = {
...getSystemVariable({ timezone: user.timezone }),
...variables
};
const runningModules = loadModules(modules, variables);
// let storeData: Record<string, any> = {}; // after module used
@@ -390,6 +402,7 @@ export async function dispatchModules({
};
}
/* init store modules to running modules */
function loadModules(
modules: AppModuleItemType[],
variables: Record<string, any>
@@ -431,6 +444,7 @@ function loadModules(
});
}
/* sse response modules staus */
export function responseStatus({
res,
status,
@@ -451,6 +465,13 @@ export function responseStatus({
});
}
/* get system variable */
export function getSystemVariable({ timezone }: { timezone: string }) {
return {
cTime: getSystemTime(timezone)
};
}
export const config = {
api: {
bodyParser: {

View File

@@ -7,6 +7,7 @@ import { Types } from 'mongoose';
import type { ChatItemType } from '@/types/chat';
export type Props = {
appId?: string;
chatId?: string;
limit?: number;
};
@@ -36,9 +37,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
export async function getChatHistory({
chatId,
userId,
appId,
limit = 30
}: Props & { userId: string }): Promise<Response> {
if (!chatId) {
if (!chatId || !appId) {
return { history: [] };
}
@@ -46,6 +48,7 @@ export async function getChatHistory({
{
$match: {
chatId,
appId: new Types.ObjectId(appId),
userId: new Types.ObjectId(userId)
}
},

View File

@@ -0,0 +1,34 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { getVectorModel } from '@/service/utils/data';
import { KbListItemType } from '@/types/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const kbList = await KB.find({
userId,
type: 'dataset'
});
const data = kbList.map((item) => ({
...item.toJSON(),
vectorModel: getVectorModel(item.vectorModel)
}));
jsonRes<KbListItemType[]>(res, {
data
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@@ -6,11 +6,7 @@ import type { CreateKbParams } from '@/api/request/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { name, tags, avatar, vectorModel } = req.body as CreateKbParams;
if (!name || !vectorModel) {
throw new Error('缺少参数');
}
const { name, tags, avatar, vectorModel, parentId, type } = req.body as CreateKbParams;
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
@@ -22,7 +18,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
userId,
tags,
vectorModel,
avatar
avatar,
parentId: parentId || null,
type
});
jsonRes(res, { data: _id });

View File

@@ -0,0 +1,105 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, User } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgDatasetTableName } from '@/constants/plugin';
import { findAllChildrenIds } from '../delete';
import QueryStream from 'pg-query-stream';
import Papa from 'papaparse';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { kbId } = req.query as {
kbId: string;
};
if (!kbId || !global.pgClient) {
throw new Error('缺少参数');
}
await connectToDatabase();
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
const exportIds = [kbId, ...(await findAllChildrenIds(kbId))];
const thirtyMinutesAgo = new Date(
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
);
// auth export times
const authTimes = await User.findOne(
{
_id: userId,
$or: [
{ 'limit.exportKbTime': { $exists: false } },
{ 'limit.exportKbTime': { $lte: thirtyMinutesAgo } }
]
},
'_id limit'
);
if (!authTimes) {
const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`;
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
}
// connect pg
global.pgClient.connect((err, client, done) => {
if (err) {
console.error(err);
res.end('Error connecting to database');
return;
}
// create pg select stream
const query = new QueryStream(
`SELECT q, a, source FROM ${PgDatasetTableName} where user_id='${userId}' AND kb_id IN (${exportIds
.map((id) => `'${id}'`)
.join(',')})`
);
const stream = client.query(query);
res.setHeader('Content-Disposition', 'attachment; filename=dataset.csv');
res.setHeader('Content-Type', 'text/csv');
res.write('index,content,source');
// parse data every row
stream.on('data', (row: { q: string; a: string; source?: string }) => {
const csv = Papa.unparse([row], { header: false });
res.write(`\n${csv}`);
});
stream.on('end', async () => {
try {
// update export time
await User.findByIdAndUpdate(userId, {
'limit.exportKbTime': new Date()
});
} catch (error) {}
// close response
done();
res.end();
});
stream.on('error', (err) => {
done(err);
res.end('Error exporting data');
});
});
} catch (err) {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '200mb'
}
}
};

View File

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

View File

@@ -4,7 +4,7 @@ import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import type { KbDataItemType } from '@/types/plugin';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
export type Response = {
id: string;
@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const where: any = [['user_id', userId], 'AND', ['id', dataId]];
const searchRes = await PgClient.select<KbDataItemType>(PgTrainingTableName, {
const searchRes = await PgClient.select<KbDataItemType>(PgDatasetTableName, {
fields: ['kb_id', 'id', 'q', 'a', 'source', 'file_id'],
where,
limit: 1

View File

@@ -4,7 +4,8 @@ import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import type { KbDataItemType } from '@/types/plugin';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
import { OtherFileId } from '@/constants/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -12,12 +13,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
kbId,
pageNum = 1,
pageSize = 10,
searchText = ''
searchText = '',
fileId = ''
} = req.body as {
kbId: string;
pageNum: number;
pageSize: number;
searchText: string;
fileId: string;
};
if (!kbId) {
throw new Error('缺少参数');
@@ -33,6 +36,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
['user_id', userId],
'AND',
['kb_id', kbId],
...(fileId
? fileId === OtherFileId
? ["AND (file_id IS NULL OR file_id = '')"]
: ['AND', ['file_id', fileId]]
: []),
...(searchText
? [
'AND',
@@ -42,14 +50,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
];
const [searchRes, total] = await Promise.all([
PgClient.select<KbDataItemType>(PgTrainingTableName, {
PgClient.select<KbDataItemType>(PgDatasetTableName, {
fields: ['id', 'q', 'a', 'source', 'file_id'],
where,
order: [{ field: 'id', mode: 'DESC' }],
limit: pageSize,
offset: pageSize * (pageNum - 1)
}),
PgClient.count(PgTrainingTableName, {
PgClient.count(PgDatasetTableName, {
fields: ['id'],
where
})

View File

@@ -3,12 +3,12 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authKb, authUser } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
import { insertKbItem, PgClient } from '@/service/pg';
import { modelToolMap } from '@/utils/plugin';
import { getVectorModel } from '@/service/utils/data';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { DatasetItemType } from '@/types/plugin';
import { countPromptTokens } from '@/utils/common/tiktoken';
export type Props = {
kbId: string;
@@ -35,9 +35,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const a = data?.a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
// token check
const token = modelToolMap.countTokens({
messages: [{ obj: 'System', value: q }]
});
const token = countPromptTokens(q, 'system');
if (token > getVectorModel(kb.vectorModel).maxToken) {
throw new Error('Over Tokens');
@@ -45,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { rows: existsRows } = await PgClient.query(`
SELECT COUNT(*) > 0 AS exists
FROM ${PgTrainingTableName}
FROM ${PgDatasetTableName}
WHERE md5(q)=md5('${q}') AND md5(a)=md5('${a}') AND user_id='${userId}' AND kb_id='${kbId}'
`);
const exists = existsRows[0]?.exists || false;

View File

@@ -3,12 +3,12 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, KB, App, TrainingData } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { Types } from 'mongoose';
import { PgTrainingTableName } from '@/constants/plugin';
import { PgDatasetTableName } from '@/constants/plugin';
import { GridFSStorage } from '@/service/lib/gridfs';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { id } = req.query as {
id: string;
};
@@ -20,26 +20,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const deletedIds = [id, ...(await findAllChildrenIds(id))];
// delete training data
await TrainingData.deleteMany({
userId,
kbId: id
kbId: { $in: deletedIds }
});
// delete all pg data
await PgClient.delete(PgTrainingTableName, {
where: [['user_id', userId], 'AND', ['kb_id', id]]
await PgClient.delete(PgDatasetTableName, {
where: [
['user_id', userId],
'AND',
`kb_id IN (${deletedIds.map((id) => `'${id}'`).join(',')})`
]
});
// delete related files
const gridFs = new GridFSStorage('dataset', userId);
await gridFs.deleteFilesByKbId(id);
await Promise.all(deletedIds.map((id) => gridFs.deleteFilesByKbId(id)));
// delete kb data
await KB.findOneAndDelete({
_id: id,
await KB.deleteMany({
_id: { $in: deletedIds },
userId
});
@@ -51,3 +55,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}
export async function findAllChildrenIds(id: string) {
// find children
const children = await KB.find({ parentId: id });
let allChildrenIds = children.map((child) => String(child._id));
for (const child of children) {
const grandChildrenIds = await findAllChildrenIds(child._id);
allChildrenIds = allChildrenIds.concat(grandChildrenIds);
}
return allChildrenIds;
}

View File

@@ -2,29 +2,27 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { KbListItemType } from '@/types/plugin';
import { getVectorModel } from '@/service/utils/data';
import { KbListItemType } from '@/types/plugin';
import { KbTypeEnum } from '@/constants/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { parentId, type } = req.query as { parentId?: string; type?: `${KbTypeEnum}` };
// 凭证校验
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const kbList = await KB.find(
{
userId
},
'_id avatar name tags vectorModel'
).sort({ updateTime: -1 });
const kbList = await KB.find({
userId,
...(parentId !== undefined && { parentId: parentId || null }),
...(type && { type })
}).sort({ updateTime: -1 });
const data = await Promise.all(
kbList.map(async (item) => ({
_id: item._id,
avatar: item.avatar,
name: item.name,
tags: item.tags,
...item.toJSON(),
vectorModel: getVectorModel(item.vectorModel)
}))
);

View File

@@ -0,0 +1,36 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { KbPathItemType } from '@/types/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { parentId } = req.query as { parentId: string };
jsonRes<KbPathItemType[]>(res, {
data: await getParents(parentId)
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
async function getParents(parentId?: string): Promise<KbPathItemType[]> {
if (!parentId) {
return [];
}
const parent = await KB.findById(parentId, 'name parentId');
if (!parent) return [];
const paths = await getParents(parent.parentId);
paths.push({ parentId, parentName: parent.name });
return paths;
}

View File

@@ -6,9 +6,9 @@ import type { KbUpdateParams } from '@/api/request/kb';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id, name, tags, avatar } = req.body as KbUpdateParams;
const { id, parentId, name, avatar, tags } = req.body as KbUpdateParams;
if (!id || !name) {
if (!id) {
throw new Error('缺少参数');
}
@@ -23,9 +23,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
userId
},
{
avatar,
name,
tags: tags.split(' ').filter((item) => item)
...(parentId !== undefined && { parentId: parentId || null }),
...(name && { name }),
...(avatar && { avatar }),
...(typeof tags === 'string' && {
tags: tags.split(' ').filter((item) => item)
})
}
);

View File

@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
jsonRes(res, {
data: `/api/plugins/file/read?token=${token}`
data: `/api/support/file/read?token=${token}`
});
} catch (error) {
jsonRes(res, {

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