Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8ea546b3f | ||
|
|
0bb31b985d | ||
|
|
453824260f | ||
|
|
a8fdffc3e9 | ||
|
|
24164d9454 | ||
|
|
4365a94ea9 | ||
|
|
7c1ec04380 | ||
|
|
09b6365321 | ||
|
|
eb2e383cc7 | ||
|
|
ae4c479f37 | ||
|
|
6a996272da | ||
|
|
1bf76ebe7a | ||
|
|
a19afca148 | ||
|
|
be3b680bc6 | ||
|
|
31dbcfde9f | ||
|
|
6d438aafdf | ||
|
|
1aaafcf631 | ||
|
|
7521bce77e | ||
|
|
c8dee29dc4 | ||
|
|
8f953d1fc4 | ||
|
|
970b62be25 | ||
|
|
b2b3aa651d | ||
|
|
b0e7d25464 | ||
|
|
b46048609c | ||
|
|
ae2887e956 | ||
|
|
7917766024 | ||
|
|
a1a63260dd | ||
|
|
6f2d556a87 | ||
|
|
565f9c8113 | ||
|
|
975e011e03 | ||
|
|
19ce6f66ca | ||
|
|
da6e26f95c | ||
|
|
71abe08f05 | ||
|
|
45ba5e1e01 | ||
|
|
139d0be52b | ||
|
|
1ba3d72a8a | ||
|
|
cd455b2a79 | ||
|
|
fa3f3e6264 | ||
|
|
9bf5a3ec76 | ||
|
|
95389e31f7 | ||
|
|
ea65d9b34b | ||
|
|
2dd2976efa | ||
|
|
64fde42c87 | ||
|
|
7a926b7086 | ||
|
|
562fd2692d | ||
|
|
935287a95a | ||
|
|
bd419a22f4 | ||
|
|
32f482b232 | ||
|
|
5d596bd3d5 | ||
|
|
ae88d79d6f | ||
|
|
1207e3e566 | ||
|
|
3449024678 | ||
|
|
8dba2c39e1 | ||
|
|
94c53804ce | ||
|
|
a1bcd798e1 | ||
|
|
6d51b3babe |
19
.github/ISSUE_TEMPLATE/bugs.md
vendored
@@ -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 可能会被无视或直接关闭**
|
||||
|
||||
**你的版本**
|
||||
+ [ ] 公有云版本
|
||||
+ [ ] 私有部署版本
|
||||
|
||||
- [ ] 公有云版本
|
||||
- [ ] 私有部署版本
|
||||
|
||||
**问题描述**
|
||||
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/features.md
vendored
@@ -8,13 +8,13 @@ assignees: ''
|
||||
|
||||
**例行检查**
|
||||
|
||||
[//]: # '方框内删除已有的空格,填 x 号'
|
||||
[//]: # '方框内填 x 表示打钩'
|
||||
|
||||
- [ ] 我已确认目前没有类似 features
|
||||
- [ ] 我已确认我已升级到最新版本
|
||||
- [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求
|
||||
- [ ] 我理解并愿意跟进此 features,协助测试和提供反馈
|
||||
- [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 features 可能会被无视或直接关闭**
|
||||
- [x] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 features 可能会被无视或直接关闭**
|
||||
|
||||
**功能描述**
|
||||
|
||||
|
||||
30
.github/gh-bot.yml
vendored
Normal 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}}
|
||||
1
.github/workflows/deploy-docs.yml
vendored
@@ -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
@@ -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"
|
||||
10
.github/workflows/fastgpt-image.yml
vendored
@@ -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 \
|
||||
.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
dist
|
||||
.vscode
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
node_modules
|
||||
docSite/
|
||||
15
README.md
@@ -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
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
"show_doc": true,
|
||||
"systemTitle": "FastGPT",
|
||||
"authorText": "Made by FastGPT Team.",
|
||||
"gitLoginKey": "",
|
||||
"limit": {
|
||||
"exportLimitMinutes": 0
|
||||
},
|
||||
"scripts": []
|
||||
},
|
||||
"SystemParams": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,10 +17,6 @@ const nextConfig = {
|
||||
}
|
||||
};
|
||||
}
|
||||
config.experiments = {
|
||||
asyncWebAssembly: true,
|
||||
layers: true
|
||||
};
|
||||
config.module = {
|
||||
...config.module,
|
||||
rules: config.module.rules.concat([
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
| FastAI4k - 对话 | 0.015 |
|
||||
| FastAI16k - 对话 | 0.03 |
|
||||
| FastAI-Plus - 对话 | 0.45 |
|
||||
| 文件拆分 | 0.03 |
|
||||
| 文件 QA 拆分 | 0.03 |
|
||||
|
||||
**其他问题**
|
||||
| 交流群 | 小助手 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
1
client/public/icon/fill/app.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
client/public/imgs/files/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
client/public/imgs/files/folder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
1
client/public/js/cl100k_base.json
Normal 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",
|
||||
|
||||
@@ -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": "修改密码异常",
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
15
client/src/api/core/dataset/file.ts
Normal 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);
|
||||
@@ -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'));
|
||||
|
||||
/**
|
||||
* 获取模型正在拆分数据的数量
|
||||
|
||||
20
client/src/api/request/kb.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { baseUrl } from '../../service/lib/openai';
|
||||
|
||||
interface ConfigType {
|
||||
headers?: { [key: string]: string };
|
||||
|
||||
18
client/src/api/support/file.ts
Normal 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 });
|
||||
34
client/src/api/support/outLink.ts
Normal 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}`);
|
||||
@@ -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 });
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
1
client/src/components/Icon/icons/fill/google.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
8
client/src/components/Icon/icons/fill/plus.svg
Normal 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 |
1
client/src/components/Icon/icons/light/move.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="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 |
8
client/src/components/Icon/icons/light/retry.svg
Normal 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 |
3
client/src/components/Icon/icons/light/rightArrow.svg
Normal 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 |
8
client/src/components/Icon/icons/light/search.svg
Normal 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 |
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
64
client/src/components/Markdown/img/EChartsCodeBlock.tsx
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
@@ -334,6 +334,9 @@
|
||||
background-color: transparent;
|
||||
border: medium none;
|
||||
}
|
||||
.markdown hr {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.markdown {
|
||||
text-align: justify;
|
||||
tab-size: 4;
|
||||
|
||||
@@ -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}
|
||||
|
||||
40
client/src/components/MyImage/index.tsx
Normal 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);
|
||||
28
client/src/components/MyInput/index.tsx
Normal 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;
|
||||
54
client/src/components/MyMenu/index.tsx
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,4 +7,4 @@ export const TrainingTypeMap = {
|
||||
[TrainingModeEnum.index]: 'index'
|
||||
};
|
||||
|
||||
export const PgTrainingTableName = 'modeldata';
|
||||
export const PgDatasetTableName = 'modeldata';
|
||||
|
||||
@@ -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]: '免登录链接'
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -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} />
|
||||
|
||||
@@ -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')}: </Box>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 50px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </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')}: </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')}: </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')}: </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')}: </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={'点击配置账号'}>
|
||||
|
||||
@@ -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)} |`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
41
client/src/pages/api/admin/initv44.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
35
client/src/pages/api/admin/initv441.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
27
client/src/pages/api/admin/initv442.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
62
client/src/pages/api/core/dataset/file/delById.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
31
client/src/pages/api/core/dataset/file/delEmptyFiles.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
43
client/src/pages/api/core/dataset/file/detail.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
112
client/src/pages/api/core/dataset/file/list.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
66
client/src/pages/api/core/dataset/file/update.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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]]
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
|
||||
@@ -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;`
|
||||
|
||||
@@ -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
|
||||
? [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
34
client/src/pages/api/plugins/kb/allDataset.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
105
client/src/pages/api/plugins/kb/data/exportAll.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
);
|
||||
|
||||
36
client/src/pages/api/plugins/kb/paths.ts
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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, {
|
||||