Compare commits

..

94 Commits
v4.3 ... v4.4.6

Author SHA1 Message Date
Archer
98ce5103a0 v4.4.6 (#377) 2023-10-07 18:02:20 +08:00
Carson Yang
c65a36d3ab Docs: hide button for questionnaire on mobile device (#376)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-10-07 14:57:26 +08:00
Carson Yang
b6e49da288 Docs: update button for questionnaire (#375)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-10-06 23:52:45 +08:00
Archer
45998f9cf5 README (#372) 2023-10-06 21:19:44 +08:00
Carson Yang
4197f63751 Update README (#371)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-10-06 14:07:37 +08:00
Carson Yang
ace8134a16 Docs: add Dockerfile for docs (#369)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-10-06 08:01:16 +08:00
Carson Yang
7f1fecb84e Docs: update theme (#368)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-10-04 22:25:07 +08:00
Archer
bf172fab81 perf: markdown more wrap (#365) 2023-10-02 20:19:09 +08:00
Archer
36f5648cae perf: v4.4.6-1 (#364) 2023-09-28 17:30:05 +08:00
Archer
ab57bfcc4a perf: completions api.fix: new chat question guide (#361) 2023-09-27 12:05:13 +08:00
Archer
11848b8f44 v4.4.5-3 (#357) 2023-09-26 21:17:13 +08:00
epoh
a11e0bd9c3 Update chatglm2.md (#354) 2023-09-26 15:06:38 +08:00
Archer
f6552d0d4f v4.4.5-2 (#355) 2023-09-26 14:31:37 +08:00
epoh
38d4db5d5f Rename requirement.txt to requirements.txt (#352) 2023-09-26 09:38:14 +08:00
Archer
63cd379682 Add share link hook (#351) 2023-09-25 23:12:42 +08:00
Archer
9136c9306a Add OpenAPI docs;Correct the glm document (#346) 2023-09-25 14:28:44 +08:00
Byte Sound
c9db9f33ea Update intro.md (#348)
错别字,市区改为时区
2023-09-25 13:33:30 +08:00
Archer
3d7178d06f monorepo packages (#344) 2023-09-24 18:02:09 +08:00
Archer
a4ff5a3f73 perf: api key (#342) 2023-09-23 20:28:03 +08:00
Archer
814c5b3d3c Add bill of training and rate of file upload (#339) 2023-09-21 21:02:44 +08:00
Chen X
e7e0677291 Docs:add-workflow-case-全能助手 (#334) 2023-09-21 15:57:42 +08:00
Archer
823f4b7ad1 Optimize the structure and naming of projects (#335) 2023-09-21 14:49:56 +08:00
Carson Yang
a3c77480f7 Add action for translating Non-English issues content to English (#333)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-21 14:19:54 +08:00
Archer
e367265dbb feat: function call prompt version (#331) 2023-09-21 12:27:48 +08:00
Archer
7e0deb29e0 Add SSE controller; fix share page login failed (#330) 2023-09-20 16:34:32 +08:00
Archer
0d94db4331 fix: ts and default dataset (#329) 2023-09-20 11:43:49 +08:00
Carson Yang
177482b33a Docs: fix code block highlight (#328)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-20 11:43:35 +08:00
Archer
63b183a9fe fix: mark modal cannot select folder (#327) 2023-09-20 11:26:17 +08:00
Carson Yang
858117f8c0 Docs: update font to LXGW WenKai (#325)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2023-09-19 21:22:04 +08:00
Archer
ac4355d2e1 Add modal to show completion response data (#324) 2023-09-19 20:31:45 +08:00
Archer
ce7da2db66 Optimize chat reponse data (#322) 2023-09-19 16:10:30 +08:00
Archer
0a4a1def1e fix: connected error (#318) 2023-09-19 07:54:50 +08:00
Archer
35f4deca76 Revert "Feature: 高级编排自动布局 (#314)" (#319)
This reverts commit ba1451a0e9.
2023-09-18 23:44:44 +08:00
jaden
ba1451a0e9 Feature: 高级编排自动布局 (#314)
* feat: adFlow auto layout

* chore: delete file and build pnpm lock file
2023-09-18 23:39:19 +08:00
Archer
40d69e6e20 version (#317) 2023-09-18 21:56:38 +08:00
Sr
b8ba947ba8 feat: Added defaultOpen Attribute for iframe (#302)
* feat: Added defaultOpen Attribute for iframe

This commit introduces a new attribute `defaultOpen` for the iframe created in `iframe.js`. The `defaultOpen` attribute allows the iframe to be visible by default when the page loads. This new feature enhances the user experience by providing an option to display the chatbot window immediately after the page is loaded, without requiring user interaction.

* Update iframe.js

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

* perf: dataset file manage

* v441 description

* fix: qa csv update file

* feat: rename file

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

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

* perf: qa default value

* feat: dataset folder

* feat: kb folder delete and path

* fix: ts

* perf: script load

* feat: fileCard and dataCard

* feat: search file

* feat: max token

* feat: select dataset

* fix: preview chunk

* perf: source update

* export data limit file_id

* docs

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

* 更新镜像信息

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

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

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

* Update sse.ts

---------

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

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

View File

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

View File

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

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

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

15
.github/imgs/logo-left.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,19 @@
name: 'Github Rebot for issues-translator'
on:
issues:
types: [ opened ]
issue_comment:
types: [ created ]
jobs:
translate:
permissions:
issues: write
discussions: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
BOT_GITHUB_TOKEN: ${{ secrets.GH_PAT }}
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿

View File

@@ -55,8 +55,6 @@ jobs:
# 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
@@ -69,3 +67,4 @@ jobs:
github-comment: false
vercel-args: '--prod --local-config ../vercel.json' # Optional
working-directory: docSite/public

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

@@ -0,0 +1,94 @@
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
# 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"

85
.github/workflows/docs-image.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Build FastGPT docs images and copy image to docker hub
on:
workflow_dispatch:
push:
paths:
- 'docSite/**'
branches:
- 'main'
tags:
- 'v*.*.*'
jobs:
build-fastgpt-images:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
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:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-docs:latest" >> $GITHUB_ENV
else
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{ 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 }}
run: |
docker buildx build \
--build-arg name=app \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=Apache" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f docSite/Dockerfile \
.
push-to-docker-hub:
needs: build-fastgpt-images
runs-on: ubuntu-20.04
if: github.repository == 'labring/FastGPT'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt-docs:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}

View File

@@ -1,9 +1,10 @@
name: Build fastgpt images and copy image to docker hub
name: Build FastGPT images and copy image to docker hub
on:
workflow_dispatch:
push:
paths:
- 'client/**'
- 'projects/app/**'
- 'packages/**'
branches:
- 'main'
tags:
@@ -25,6 +26,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,24 +46,26 @@ 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 }}
run: |
cd client && \
docker buildx build \
--build-arg name=app \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
--label "org.opencontainers.image.licenses=MIT" \
--label "org.opencontainers.image.licenses=Apache" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.
push-to-docker-hub:
needs: build-fastgpt-images
runs-on: ubuntu-20.04
if: github.repository == 'labring/FastGPT'
steps:
- name: Checkout code
uses: actions/checkout@v3
@@ -79,6 +89,7 @@ jobs:
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
push-to-ali-hub:
needs: build-fastgpt-images
if: github.repository == 'labring/FastGPT'
runs-on: ubuntu-20.04
steps:
- name: Checkout code

52
.github/workflows/preview-image.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Preview FastGPT images
on:
pull_request_target:
paths:
- 'projects/app/**'
- 'packages/**'
branches:
- 'main'
workflow_dispatch:
jobs:
build-fastgpt-images:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Set up Docker Buildx
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:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- name: Build image for PR
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
--build-arg name=app \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr image" \
--label "org.opencontainers.image.licenses=Apache" \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.

4
.gitignore vendored
View File

@@ -33,4 +33,6 @@ dist/
# hugo
**/.hugo_build.lock
docSite/public/
docSite/public/
docSite/resources/_gen/
docSite/.vercel

View File

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

View File

@@ -1,10 +1,10 @@
{
"editor.formatOnSave": true,
"editor.mouseWheelZoom": true,
"typescript.tsdk": "client/node_modules/typescript/lib",
"prettier.prettierPath": "./node_modules/prettier",
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"i18n-ally.localesPaths": [
"client/public/locales"
"projects/app/public/locales"
],
"i18n-ally.enabledParsers": ["json"],
"i18n-ally.keystyle": "nested",

72
Dockerfile Normal file
View File

@@ -0,0 +1,72 @@
# Install dependencies only when needed
FROM node:current-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat && npm install -g pnpm
WORKDIR /app
ARG name
# copy packages and one project
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./packages ./packages
COPY ./projects/$name/package.json ./projects/$name/package.json
COPY ./projects/$name/pnpm-lock.yaml ./projects/$name/pnpm-lock.yaml
RUN \
[ -f pnpm-lock.yaml ] && pnpm install || \
(echo "Lockfile not found." && exit 1)
RUN pnpm prune
# Rebuild the source code only when needed
FROM node:current-alpine AS builder
WORKDIR /app
ARG name
# copy common node_modules and one project node_modules
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/packages ./packages
COPY ./projects/$name ./projects/$name
COPY --from=deps /app/projects/$name/node_modules ./projects/$name/node_modules
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./packages ./packages
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm install -g pnpm
RUN pnpm --filter=$name run build
FROM node:current-alpine AS runner
WORKDIR /app
ARG name
# create user and use it
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
# copy running files
COPY --from=builder /app/projects/$name/public ./projects/$name/public
COPY --from=builder /app/projects/$name/next.config.js ./projects/$name/next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static ./projects/$name/.next/static
# copy package.json to version file
COPY --from=builder /app/projects/$name/package.json ./package.json
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT=3000
EXPOSE 3000
USER nextjs
ENV serverPath=./projects/$name/server.js
ENTRYPOINT ["sh","-c","node ${serverPath}"]

View File

@@ -4,21 +4,36 @@
# FastGPT
<p align="center">
<a href="./README_en.md">English</a> |
<a href="./README.md">简体中文</a>
</p>
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!
</div>
<p align="center">
<a href="https://fastgpt.run/">线上体验</a>
·
<a href="https://doc.fastgpt.run/docs/intro">相关文档</a>
·
<a href="https://doc.fastgpt.run/docs/development">本地开发</a>
·
<a href="https://github.com/labring/FastGPT#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">相关项目</a>
<a href="https://fastgpt.run/">
<img height="21" src="https://img.shields.io/badge/在线使用-fff?style=flat-square&logo=spoj&logoColor=7d09f1" alt="cloud">
</a>
<a href="https://doc.fastgpt.run/docs/intro">
<img height="21" src="https://img.shields.io/badge/相关文档-7d09f1?style=flat-square" alt="document">
</a>
<a href="https://doc.fastgpt.run/docs/development">
<img height="21" src="https://img.shields.io/badge/本地开发-%23fff?style=flat-square&logo=xcode&logoColor=7d09f1" alt="development">
</a>
<a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">
<img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project">
</a>
<a href="https://github.com/labring/FastGPT/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=fff&color=7d09f1" alt="license">
</a>
</p>
## 🛸 在线体验
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4
## 🛸 在线使用
[fastgpt.run](https://fastgpt.run/)(服务器在新加坡,部分地区可能无法直连)
@@ -31,32 +46,29 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
1. 强大的可视化编排,轻松构建 AI 应用
- [x] 提供简易模式,无需操作编排
- [x] 用户对话前引导
- [x] 全局变量
- [x] 用户对话前引导, 全局字符串变量
- [x] 知识库搜索
- [x] 多 LLM 模型对话
- [x] 文本内容提取成结构化数据
- [x] HTTP 扩展
- [ ] 嵌入 Laf实现在线编写 HTTP 模块
- [ ] 连续对话引导
- [x] 对话下一步指引
- [ ] 对话多路线选择
- [x] 源文件引用追踪
- [ ] 自定义文件阅读器
2. 丰富的知识库预处理
- [x] 多库复用,混用
- [x] chunk 记录修改和删除
- [x] 支持直接分段导入
- [x] 支持 QA 拆分导入
- [x] 支持手动输入内容
- [x] 支持 url 读取导入
- [x] 支持 CSV 批量导入问答对
- [x] 支持 手动输入, 直接分段, QA 拆分导入
- [x] 支持 url 读取、 CSV 批量导入
- [x] 支持知识库单独设置向量模型
- [x] 源文件存储
- [ ] 文件学习 Agent
3. 多种效果测试渠道
- [x] 知识库单点搜索测试
- [x] 对话时反馈引用并可修改与删除
- [x] 完整上下文呈现
- [ ] 完整模块中间值呈现
- [x] 完整模块中间值呈现
4. OpenAPI
- [x] completions 接口(对齐 GPT 接口)
- [ ] 知识库 CRUD
@@ -81,8 +93,8 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
* [部署 FastGPT](https://doc.fastgpt.run/docs/installation)
* [系统配置文件说明](https://doc.fastgpt.run/docs/development/configuration/)
* [多模型配置](https://doc.fastgpt.run/docs/installation/one-api/)
* [版本升级](https://doc.fastgpt.run/docs/installation/upgrading)
* [API 文档](https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh?pre_pathname=%2Fdrive%2Fhome%2F)
* [版本更新/升级介绍](https://doc.fastgpt.run/docs/installation/upgrading)
* [API 文档](https://doc.fastgpt.run/docs/development/openapi/)
## 🏘️ 社区交流群
@@ -90,13 +102,6 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
![](https://otnvvf-imgs.oss.laf.run/wx300.jpg)
## 👀 其他
- [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/)
## 💪 相关项目
- [Laf: 3 分钟快速接入三方应用](https://github.com/labring/laf)
@@ -104,9 +109,15 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
- [One API: 多模型管理,支持 Azure、文心一言等](https://github.com/songquanpeng/one-api)
- [TuShan: 5 分钟搭建后台管理系统](https://github.com/msgbyte/tushan)
## 👀 其他
- [保姆级 FastGPT 教程](https://www.bilibili.com/video/BV1n34y1A7Bo/?spm_id_from=333.999.0.0)
- [接入飞书](https://www.bilibili.com/video/BV1Su4y1r7R3/?spm_id_from=333.999.0.0)
- [接入企微](https://www.bilibili.com/video/BV1Tp4y1n72T/?spm_id_from=333.999.0.0)
## 🤝 第三方生态
- [luolinAI: 企微机器人,开箱即用](https://github.com/luolin-ai/FastGPT-Enterprise-WeChatbot)
- [OnWeChat 个人微信/企微机器人](https://doc.fastgpt.run/docs/use-cases/onwechat/)
## 🌟 Star History
@@ -116,7 +127,7 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开
本仓库遵循 [FastGPT Open Source License](./LICENSE) 开源协议。
1. 允许作为后台服务直接商用,但不允许直接使用 saas 服务商用。
1. 允许作为后台服务直接商用,但不允许直接使用 SaaS 服务商用。
2. 需保留相关版权信息。
3. 完整请查看 [FastGPT Open Source License](./LICENSE)
4. 联系方式yujinlong@sealos.io, [点击查看定价策略](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf)
4. 联系方式yujinlong@sealos.io, [点击查看定价策略](https://doc.fastgpt.run/docs/commercial)

View File

@@ -1,25 +1,39 @@
<div align="center">
<a href="https://fastgpt.run/"><img src="/.github/imgs/logo.svg" width="120" height="120" alt="fastgpt logo"></a>
# FastGPT
FastGPT is a knowledge-based question answering system built on the LLM. It offers out-of-the-box data processing and model invocation capabilities. Moreover, it allows for workflow orchestration through Flow visualization, thereby enabling complex question and answer scenarios!
<p align="center">
<a href="./README_en.md">English</a> |
<a href="./README.md">简体中文</a>
</p>
FastGPT is a knowledge-based Q&A system built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization!
</div>
<p align="center">
<a href="https://fastgpt.run/">Online</a>
·
<a href="https://doc.fastgpt.run/docs/intro">Document</a>
·
<a href="https://doc.fastgpt.run/docs/development">Development</a>
·
<a href="https://doc.fastgpt.run/docs/installation">Deploy</a>
·
<a href="#powered-by">Power By</a>
<a href="https://fastgpt.run/">
<img height="21" src="https://img.shields.io/badge/Website-fff?style=flat-square&logo=spoj&logoColor=7d09f1" alt="cloud">
</a>
<a href="https://doc.fastgpt.run/docs/intro">
<img height="21" src="https://img.shields.io/badge/Docs-7d09f1?style=flat-square" alt="document">
</a>
<a href="https://doc.fastgpt.run/docs/development">
<img height="21" src="https://img.shields.io/badge/Development-%23fff?style=flat-square&logo=xcode&logoColor=7d09f1" alt="development">
</a>
<a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">
<img height="21" src="https://img.shields.io/badge/Related Projects-7d09f1?style=flat-square" alt="project">
</a>
<a href="https://github.com/labring/FastGPT/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=fff&color=7d09f1" alt="license">
</a>
</p>
## 🛸 Online
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4
## 🛸 Use Cloud Services
[fastgpt.run](https://fastgpt.run/)
| | |
@@ -29,35 +43,34 @@ FastGPT is a knowledge-based question answering system built on the LLM. It offe
## 💡 Features
1. Powerful visual orchestration for easy AI application building
1. Powerful visual workflows: Effortlessly craft AI applications
- [x] Provides a simple mode without the need for orchestration operations
- [x] Simple mode on deck - no need for manual arrangement
- [x] User dialogue pre-guidance
- [x] Global variables
- [x] Knowledge base search
- [x] Multi-LLM model dialogue
- [x] Extraction of text content into structured data
- [x] HTTP extension
- [ ] Sandbox JS runtime module
- [ ] Continuous dialogue guidance
- [ ] Dialogue multi-path selection
- [ ] Source file reference tracking
- [x] Dialogue via multiple LLM models
- [x] Text magic - convert to structured data
- [x] Extend with HTTP
- [ ] Embed Laf for on-the-fly HTTP module crafting
- [x] Directions for the next dialogue steps
- [ ] Multiple dialogue paths selection
- [x] Tracking source file references
- [ ] Custom file reader
2. Rich knowledge base preprocessing
2. Extensive knowledge base preprocessing
- [x] Multiple library reuse and mixing
- [x] Chunk record modification and deletion
- [x] Supports direct segment import
- [x] Supports QA split import
- [x] Supports manual input content
- [ ] Supports URL import reading
- [x] Supports batch import of Q&A pairs in CSV format
- [ ] Supports separate vector model settings for knowledge bases
- [ ] Source file storage
- [x] Reuse and mix multiple knowledge bases
- [x] Track chunk modifications and deletions
- [x] Supports manual entries, direct segmentation, and QA split imports
- [x] Supports URL fetching and batch CSV imports
- [x] Supports Set unique vector models for knowledge bases
- [x] Store original files
- [ ] File learning Agent
3. Multiple effect testing channels
- [x] Knowledge base single point search testing
- [x] Single-point knowledge base search test
- [x] Feedback references and ability to modify and delete during dialogue
- [x] Complete context presentation
- [ ] Complete module intermediate value presentation
@@ -77,11 +90,17 @@ FastGPT is a knowledge-based question answering system built on the LLM. It offe
Project tech stack: NextJs + TS + ChakraUI + Mongo + Postgres (Vector plugin)
- **⚡ Deployment**
[![](https://cdn.jsdelivr.us/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-fastdeploy%3FtemplateName%3Dfastgpt)
Give it a 2-4 minute wait after deployment as it sets up the database. Initially, it might be a tad slow since we're using the basic settings.
- [Getting Started with Local Development](https://doc.fastgpt.run/docs/development)
- [Deploying FastGPT](https://doc.fastgpt.run/docs/installation)
- [System Configuration File Explanation](https://doc.fastgpt.run/docs/installation/reference)
- [Multi-model Configuration](https://doc.fastgpt.run/docs/installation/reference/models)
- [V3 Upgrade V4 Initialization](https://doc.fastgpt.run/docs/installation/upgrading)
- [Guide on System Configs](https://doc.fastgpt.run/docs/installation/reference)
- [Configuring Multiple Models](https://doc.fastgpt.run/docs/installation/reference/models)
- [Version Updates & Upgrades](https://doc.fastgpt.run/docs/installation/upgrading)
<!-- ## :point_right: RoadMap
- [FastGPT RoadMap](https://kjqvjse66l.feishu.cn/docx/RVUxdqE2WolDYyxEKATcM0XXnte) -->

View File

@@ -1,65 +0,0 @@
# Install dependencies only when needed
FROM node:current-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat && npm install -g pnpm
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json ./
COPY pnpm-lock.yaml* ./
RUN \
[ -f pnpm-lock.yaml ] && pnpm fetch || \
(echo "Lockfile not found." && exit 1)
# Rebuild the source code only when needed
FROM node:current-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY pnpm-lock.yaml* ./
COPY package.json ./
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm install -g pnpm
RUN \
[ -f pnpm-lock.yaml ] && (pnpm --offline install && pnpm run build) || \
(echo "Lockfile not found." && exit 1)
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl \
&& apk add ca-certificates \
&& update-ca-certificates
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# COPY --from=builder /app/.env* .
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]

View File

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

View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

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

View File

@@ -1,16 +0,0 @@
import { GET, POST, DELETE } from './request';
import { UserOpenApiKey } from '@/types/openapi';
/**
* crete a api key
*/
export const createAOpenApiKey = () => POST<string>('/openapi/postKey');
/**
* get api keys
*/
export const getOpenApiKeys = () => GET<UserOpenApiKey[]>('/openapi/getKeys');
/**
* delete api by id
*/
export const delOpenApiById = (id: string) => DELETE(`/openapi/delKey?id=${id}`);

View File

@@ -1,97 +0,0 @@
import { GET, POST, PUT, DELETE } from '../request';
import type { DatasetItemType, KbItemType, KbListItemType } from '@/types/plugin';
import { RequestPaging } from '@/types/index';
import { TrainingModeEnum } from '@/constants/plugin';
import {
Props as PushDataProps,
Response as PushDateResponse
} from '@/pages/api/openapi/kb/pushData';
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 { QuoteItemType } from '@/types/chat';
/* knowledge base */
export const getKbList = () => GET<KbListItemType[]>(`/plugins/kb/list`);
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: CreateKbParams) => POST<string>(`/plugins/kb/create`, data);
export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, data);
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
/* kb data */
type GetKbDataListProps = RequestPaging & {
kbId: string;
searchText: string;
};
export const getKbDataList = (data: GetKbDataListProps) =>
POST(`/plugins/kb/data/getDataList`, data);
/**
* 获取导出数据(不分页)
*/
export const getExportDataList = (kbId: string) =>
GET<[string, string, string][]>(
`/plugins/kb/data/exportModelData`,
{ kbId },
{
timeout: 600000
}
);
/**
* 获取模型正在拆分数据的数量
*/
export const getTrainingData = (data: { kbId: string; init: boolean }) =>
POST<{
qaListLen: number;
vectorListLen: number;
}>(`/plugins/kb/data/getTrainingData`, data);
/* get length of system training queue */
export const getTrainingQueueLen = () => GET<number>(`/plugins/kb/data/getQueueLen`);
export const getKbDataItemById = (dataId: string) =>
GET<QuoteItemType>(`/plugins/kb/data/getDataById`, { dataId });
/**
* 直接push数据
*/
export const postKbDataFromList = (data: PushDataProps) =>
POST<PushDateResponse>(`/openapi/kb/pushData`, data);
/**
* insert one data to dataset
*/
export const insertData2Kb = (data: { kbId: string; data: DatasetItemType }) =>
POST<string>(`/plugins/kb/data/insertData`, data);
/**
* 更新一条数据
*/
export const putKbDataById = (data: UpdateDataProps) => PUT('/openapi/kb/updateData', data);
/**
* 删除一条知识库数据
*/
export const delOneKbDataByDataId = (dataId: string) =>
DELETE(`/openapi/kb/delDataById?dataId=${dataId}`);
/**
* 拆分数据
*/
export const postSplitData = (data: {
kbId: string;
chunks: string[];
prompt: string;
mode: `${TrainingModeEnum}`;
}) => POST(`/openapi/text/pushData`, data);
export const searchText = (data: SearchTestProps) =>
POST<SearchTestResponse>(`/openapi/kb/searchTest`, data);

View File

@@ -1,12 +0,0 @@
export type KbUpdateParams = {
id: string;
name: string;
tags: string;
avatar: string;
};
export type CreateKbParams = {
name: string;
tags: string[];
avatar: string;
vectorModel: string;
};

View File

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

View File

@@ -1,143 +0,0 @@
import React, { useState } from 'react';
import {
Box,
Button,
Flex,
ModalFooter,
ModalBody,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
IconButton
} from '@chakra-ui/react';
import { getOpenApiKeys, createAOpenApiKey, delOpenApiById } from '@/api/openapi';
import { useQuery, useMutation } from '@tanstack/react-query';
import { useLoading } from '@/hooks/useLoading';
import dayjs from 'dayjs';
import { AddIcon, DeleteIcon } from '@chakra-ui/icons';
import { getErrText, useCopyData } from '@/utils/tools';
import { useToast } from '@/hooks/useToast';
import MyIcon from '../Icon';
import MyModal from '../MyModal';
const APIKeyModal = ({ onClose }: { onClose: () => void }) => {
const { Loading } = useLoading();
const { toast } = useToast();
const {
data: apiKeys = [],
isLoading: isGetting,
refetch
} = useQuery(['getOpenApiKeys'], getOpenApiKeys);
const [apiKey, setApiKey] = useState('');
const { copyData } = useCopyData();
const { mutate: onclickCreateApiKey, isLoading: isCreating } = useMutation({
mutationFn: () => createAOpenApiKey(),
onSuccess(res) {
setApiKey(res);
refetch();
},
onError(err) {
toast({
status: 'warning',
title: getErrText(err)
});
}
});
const { mutate: onclickRemove, isLoading: isDeleting } = useMutation({
mutationFn: async (id: string) => delOpenApiById(id),
onSuccess() {
refetch();
}
});
return (
<MyModal isOpen onClose={onClose} w={'600px'}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
API 使~
</Box>
</Box>
<ModalBody minH={'300px'} maxH={['70vh', '500px']} overflow={'overlay'}>
<TableContainer mt={2} position={'relative'}>
<Table>
<Thead>
<Tr>
<Th>Api Key</Th>
<Th></Th>
<Th>使</Th>
<Th />
</Tr>
</Thead>
<Tbody fontSize={'sm'}>
{apiKeys.map(({ id, apiKey, createTime, lastUsedTime }) => (
<Tr key={id}>
<Td>{apiKey}</Td>
<Td>{dayjs(createTime).format('YYYY/MM/DD HH:mm:ss')}</Td>
<Td>
{lastUsedTime
? dayjs(lastUsedTime).format('YYYY/MM/DD HH:mm:ss')
: '没有使用过'}
</Td>
<Td>
<IconButton
icon={<DeleteIcon />}
size={'xs'}
aria-label={'delete'}
variant={'base'}
colorScheme={'gray'}
onClick={() => onclickRemove(id)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</ModalBody>
<ModalFooter>
<Button
variant="base"
leftIcon={<AddIcon color={'myGray.600'} fontSize={'sm'} />}
onClick={() => onclickCreateApiKey()}
>
</Button>
</ModalFooter>
<Loading loading={isGetting || isCreating || isDeleting} fixed={false} />
<MyModal isOpen={!!apiKey} w={'400px'} onClose={() => setApiKey('')}>
<Box py={3} px={5}>
<Box fontWeight={'bold'} fontSize={'2xl'}>
API
</Box>
<Box fontSize={'sm'} color={'myGray.600'}>
~
</Box>
</Box>
<ModalBody>
<Flex bg={'myGray.100'} px={3} py={2} cursor={'pointer'} onClick={() => copyData(apiKey)}>
<Box flex={1}>{apiKey}</Box>
<MyIcon name={'copy'} w={'16px'}></MyIcon>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant="base" onClick={() => setApiKey('')}>
</Button>
</ModalFooter>
</MyModal>
</MyModal>
);
};
export default APIKeyModal;

View File

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

View File

@@ -1,104 +0,0 @@
import React 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
};
export type IconName = keyof typeof map;
const MyIcon = (
{ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & IconProps,
ref: any
) => {
return map[name] ? (
<Icon
as={map[name]}
w={w}
h={h}
boxSizing={'content-box'}
verticalAlign={'top'}
fill={'currentcolor'}
{...props}
/>
) : null;
};
export default React.forwardRef(MyIcon);

View File

@@ -1,16 +0,0 @@
import type { KbItemType } from '@/types/plugin';
export const defaultKbDetail: KbItemType = {
_id: '',
userId: '',
avatar: '/icon/logo.svg',
name: '',
tags: '',
vectorModel: {
model: 'text-embedding-ada-002',
name: 'Embedding-2',
price: 0.2,
defaultToken: 500,
maxToken: 3000
}
};

View File

@@ -1,58 +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 { connectToDatabase, Chat } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 1000 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
chatId: { $exists: false }
});
let promise = Promise.resolve();
console.log(total);
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit, skipVal))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number, skip: number) {
// 遍历 app
const chats = await Chat.find(
{
chatId: { $exists: false }
},
'_id'
).limit(limit);
await Promise.all(
chats.map((chat) =>
Chat.findByIdAndUpdate(chat._id, {
chatId: String(chat._id),
source: 'online'
})
)
);
}

View File

@@ -1,98 +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 { connectToDatabase, Chat, ChatItem } from '@/service/mongo';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 100 } = req.body as { limit: number };
let skip = 0;
const total = await Chat.countDocuments({
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
});
const totalChat = await Chat.aggregate([
{
$project: {
contentLength: { $size: '$content' }
}
},
{
$group: {
_id: null,
totalLength: { $sum: '$contentLength' }
}
}
]);
console.log('chatLen:', total, totalChat);
let promise = Promise.resolve();
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number) {
// 遍历 app
const chats = await Chat.find(
{
content: { $exists: true, $not: { $size: 0 } },
isInit: { $ne: true }
},
'_id userId appId chatId content'
)
.sort({ updateTime: -1 })
.limit(limit);
await Promise.all(
chats.map(async (chat) => {
const inserts = chat.content
.map((item) => ({
dataId: nanoid(),
chatId: chat.chatId,
userId: chat.userId,
appId: chat.appId,
obj: item.obj,
value: item.value,
responseData: item.responseData
}))
.filter((item) => item.chatId && item.userId && item.appId && item.obj && item.value);
try {
await Promise.all(inserts.map((item) => ChatItem.create(item)));
await Chat.findByIdAndUpdate(chat._id, {
isInit: true
});
} catch (error) {
console.log(error);
await ChatItem.deleteMany({ chatId: chat.chatId });
}
})
);
}

View File

@@ -1,446 +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 { connectToDatabase, App } from '@/service/mongo';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowInputItemType } from '@/types/flow';
const chatModelInput = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
}): FlowInputItemType[] => [
{
key: 'model',
value: model,
type: 'custom',
label: '对话模型',
connected: true
},
{
key: 'temperature',
value: temperature,
label: '温度',
type: 'slider',
connected: true
},
{
key: 'maxToken',
value: maxToken,
type: 'custom',
label: '回复上限',
connected: true
},
{
key: 'systemPrompt',
value: systemPrompt,
type: 'textarea',
label: '系统提示词',
connected: true
},
{
key: 'limitPrompt',
label: '限定词',
type: 'textarea',
value: limitPrompt,
connected: true
},
{
key: 'switch',
type: 'target',
label: '触发器',
connected: kbList.length > 0
},
{
key: 'quoteQA',
type: 'target',
label: '引用内容',
connected: kbList.length > 0
},
{
key: 'history',
type: 'target',
label: '聊天记录',
connected: true
},
{
key: 'userChatInput',
type: 'target',
label: '用户问题',
connected: true
}
];
const chatTemplate = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
}) => {
return [
{
flowType: FlowModuleTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
flowType: FlowModuleTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 10,
connected: true
},
{
key: 'history',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList: []
}),
outputs: [
{
key: TaskResponseKeyEnum.answerText,
targets: []
}
],
position: {
x: 981.9682828103937,
y: 890.014595014464
},
moduleId: 'chatModule'
}
];
};
const kbTemplate = ({
model,
temperature,
maxToken,
systemPrompt,
limitPrompt,
kbList = [],
searchSimilarity,
searchLimit,
searchEmptyText
}: {
model: string;
temperature: number;
maxToken: number;
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
searchSimilarity: number;
searchLimit: number;
searchEmptyText: string;
}) => {
return [
{
flowType: FlowModuleTypeEnum.questionInput,
inputs: [
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'userChatInput',
targets: [
{
moduleId: 'chatModule',
key: 'userChatInput'
},
{
moduleId: 'kbSearch',
key: 'userChatInput'
}
]
}
],
position: {
x: 464.32198615344566,
y: 1602.2698463081606
},
moduleId: 'userChatInput'
},
{
flowType: FlowModuleTypeEnum.historyNode,
inputs: [
{
key: 'maxContext',
value: 10,
connected: true
},
{
key: 'history',
connected: true
}
],
outputs: [
{
key: 'history',
targets: [
{
moduleId: 'chatModule',
key: 'history'
}
]
}
],
position: {
x: 452.5466249541586,
y: 1276.3930310334215
},
moduleId: 'history'
},
{
flowType: FlowModuleTypeEnum.kbSearchNode,
inputs: [
{
key: 'kbList',
value: kbList,
connected: true
},
{
key: 'similarity',
value: searchSimilarity,
connected: true
},
{
key: 'limit',
value: searchLimit,
connected: true
},
{
key: 'switch',
connected: false
},
{
key: 'userChatInput',
connected: true
}
],
outputs: [
{
key: 'isEmpty',
targets: searchEmptyText
? [
{
moduleId: 'emptyText',
key: 'switch'
}
]
: [
{
moduleId: 'chatModule',
key: 'switch'
}
]
},
{
key: 'unEmpty',
targets: [
{
moduleId: 'chatModule',
key: 'switch'
}
]
},
{
key: 'quoteQA',
targets: [
{
moduleId: 'chatModule',
key: 'quoteQA'
}
]
}
],
position: {
x: 956.0838440206068,
y: 887.462827870246
},
moduleId: 'kbSearch'
},
...(searchEmptyText
? [
{
flowType: FlowModuleTypeEnum.answerNode,
inputs: [
{
key: 'switch',
connected: true
},
{
key: SpecialInputKeyEnum.answerText,
value: searchEmptyText,
connected: true
}
],
outputs: [],
position: {
x: 1553.5815811529146,
y: 637.8753731306779
},
moduleId: 'emptyText'
}
]
: []),
{
flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput({ model, temperature, maxToken, systemPrompt, limitPrompt, kbList }),
outputs: [
{
key: TaskResponseKeyEnum.answerText,
targets: []
}
],
position: {
x: 1551.71405495818,
y: 977.4911578918461
},
moduleId: 'chatModule'
}
];
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
await connectToDatabase();
const { limit = 1000 } = req.body as { limit: number };
let skip = 0;
const total = await App.countDocuments();
let promise = Promise.resolve();
console.log(total);
for (let i = 0; i < total; i += limit) {
const skipVal = skip;
skip += limit;
promise = promise
.then(() => init(limit, skipVal))
.then(() => {
console.log(skipVal);
});
}
await promise;
jsonRes(res, {});
} catch (error) {
jsonRes(res, {
code: 500,
error
});
}
}
async function init(limit: number, skip: number) {
// 遍历 app
const apps = await App.find(
{
chat: { $ne: null },
modules: { $exists: false }
// userId: '63f9a14228d2a688d8dc9e1b'
},
'_id chat'
).limit(limit);
return Promise.all(
apps.map(async (app) => {
if (!app.chat) return app;
const modules = (() => {
if (app.chat.relatedKbs.length === 0) {
return chatTemplate({
model: app.chat.chatModel,
temperature: app.chat.temperature,
maxToken: app.chat.maxToken,
systemPrompt: app.chat.systemPrompt,
limitPrompt: app.chat.limitPrompt
});
} else {
return kbTemplate({
model: app.chat.chatModel,
temperature: app.chat.temperature,
maxToken: app.chat.maxToken,
systemPrompt: app.chat.systemPrompt,
limitPrompt: app.chat.limitPrompt,
kbList: app.chat.relatedKbs.map((id) => ({ kbId: id })),
searchEmptyText: app.chat.searchEmptyText,
searchLimit: app.chat.searchLimit,
searchSimilarity: app.chat.searchSimilarity
});
}
})();
await App.findByIdAndUpdate(app.id, {
modules
});
return modules;
})
);
}

View File

@@ -1,37 +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 { connectToDatabase, OpenApi } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { UserOpenApiKey } from '@/types/openapi';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
const findResponse = await OpenApi.find({ userId }).sort({ _id: -1 });
// jus save four data
const apiKeys = findResponse.map<UserOpenApiKey>(
({ _id, apiKey, createTime, lastUsedTime }) => {
return {
id: _id,
apiKey: `******${apiKey.substring(apiKey.length - 4)}`,
createTime,
lastUsedTime
};
}
);
jsonRes(res, {
data: apiKeys
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

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

View File

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

View File

@@ -1,88 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
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 { 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';
export type Props = {
kbId: string;
data: DatasetItemType;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { kbId, data = { q: '', a: '' } } = req.body as Props;
if (!kbId || !data?.q) {
throw new Error('缺少参数');
}
// 凭证校验
const { userId } = await authUser({ req });
// auth kb
const kb = await authKb({ kbId, userId });
const q = data?.q?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
const a = data?.a?.replace(/\\n/g, '\n').trim().replace(/'/g, '"');
// token check
const token = modelToolMap.countTokens({
messages: [{ obj: 'System', value: q }]
});
if (token > getVectorModel(kb.vectorModel).maxToken) {
throw new Error('Over Tokens');
}
const { rows: existsRows } = await PgClient.query(`
SELECT COUNT(*) > 0 AS exists
FROM ${PgTrainingTableName}
WHERE md5(q)=md5('${q}') AND md5(a)=md5('${a}') AND user_id='${userId}' AND kb_id='${kbId}'
`);
const exists = existsRows[0]?.exists || false;
if (exists) {
throw new Error('已经存在完全一致的数据');
}
const { vectors } = await getVector({
model: kb.vectorModel,
input: [q],
userId
});
const response = await insertKbItem({
userId,
kbId,
data: [
{
q,
a,
source: data.source,
vector: vectors[0]
}
]
});
// @ts-ignore
const id = response?.rows?.[0]?.id || '';
jsonRes(res, {
data: id
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
});

View File

@@ -1,92 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Box, Divider, Flex, useTheme, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
ssr: false
});
const API = ({ appId }: { appId: string }) => {
const theme = useTheme();
const { copyData } = useCopyData();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi');
const {
isOpen: isOpenAPIModal,
onOpen: onOpenAPIModal,
onClose: onCloseAPIModal
} = useDisclosure();
const [isLoaded, setIsLoaded] = useState(false);
const { isPc } = useGlobalStore();
useEffect(() => {
setBaseUrl(`${location.origin}/api/openapi`);
}, []);
return (
<Flex flexDirection={'column'} pt={[0, 5]} h={'100%'}>
<Flex px={5} alignItems={'center'}>
<Box flex={1}>
AppId:
<Box
as={'span'}
ml={2}
fontWeight={'bold'}
cursor={'pointer'}
onClick={() => copyData(appId, '已复制 AppId')}
>
{appId}
</Box>
</Box>
{isPc && (
<>
<Flex
bg={'myWhite.600'}
py={2}
px={4}
borderRadius={'md'}
cursor={'pointer'}
onClick={() => copyData(baseUrl, '已复制 API 地址')}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
API服务器
</Box>
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
{baseUrl}
</Box>
</Flex>
<Button
ml={3}
leftIcon={<MyIcon name={'apikey'} w={'16px'} color={''} />}
variant={'base'}
onClick={onOpenAPIModal}
>
API
</Button>
</>
)}
</Flex>
<Divider mt={3} />
<Box flex={'1 0 0'} h={0}>
<Skeleton h="100%" isLoaded={isLoaded} fadeDuration={2}>
<iframe
style={{
width: '100%',
height: '100%'
}}
src="https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh"
frameBorder="0"
onLoad={() => setIsLoaded(true)}
onError={() => setIsLoaded(true)}
/>
</Skeleton>
</Box>
{isOpenAPIModal && <APIKeyModal onClose={onCloseAPIModal} />}
</Flex>
);
};
export default API;

View File

@@ -1,59 +0,0 @@
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app';
import MyIcon from '@/components/Icon';
import MyTooltip from '@/components/MyTooltip';
import { welcomeTextTip } from '@/constants/flow/ModuleTemplate';
const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
const { inputs, moduleId, onChangeNode } = data;
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
);
return (
<>
<NodeCard minW={'300px'} {...data}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
{welcomeText && (
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText.value}
bg={'myWhite.500'}
placeholder={welcomeTextTip}
onChange={(e) => {
onChangeNode({
moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: {
...welcomeText,
value: e.target.value
}
});
}}
/>
)}
</>
</Container>
</NodeCard>
</>
);
};
export default React.memo(NodeUserGuide);

View File

@@ -1,632 +0,0 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ReactFlow, {
Background,
Controls,
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
XYPosition,
Connection,
useViewport
} from 'reactflow';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import {
edgeOptions,
connectionLineStyle,
FlowModuleTypeEnum,
FlowInputItemTypeEnum,
FlowValueTypeEnum
} from '@/constants/flow';
import { appModule2FlowNode, appModule2FlowEdge } from '@/utils/adapt';
import {
FlowModuleItemType,
FlowModuleTemplateType,
FlowOutputTargetItemType,
type FlowModuleItemChangeProps
} from '@/types/flow';
import { AppModuleItemType } from '@/types/app';
import { customAlphabet } from 'nanoid';
import { useRequest } from '@/hooks/useRequest';
import type { AppSchema } from '@/types/mongoSchema';
import { useUserStore } from '@/store/user';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from 'next-i18next';
import { useCopyData } from '@/utils/tools';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import ButtonEdge from './components/modules/ButtonEdge';
import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
const ImportSettings = dynamic(() => import('./components/ImportSettings'), {
ssr: false
});
const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'), {
ssr: false
});
const NodeKbSearch = dynamic(() => import('./components/Nodes/NodeKbSearch'), {
ssr: false
});
const NodeHistory = dynamic(() => import('./components/Nodes/NodeHistory'), {
ssr: false
});
const NodeTFSwitch = dynamic(() => import('./components/Nodes/NodeTFSwitch'), {
ssr: false
});
const NodeAnswer = dynamic(() => import('./components/Nodes/NodeAnswer'), {
ssr: false
});
const NodeQuestionInput = dynamic(() => import('./components/Nodes/NodeQuestionInput'), {
ssr: false
});
const NodeCQNode = dynamic(() => import('./components/Nodes/NodeCQNode'), {
ssr: false
});
const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'), {
ssr: false
});
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
ssr: false
});
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
ssr: false
});
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
ssr: false
});
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
import { AppTypeEnum } from '@/constants/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide,
[FlowModuleTypeEnum.variable]: NodeVariable,
[FlowModuleTypeEnum.questionInput]: NodeQuestionInput,
[FlowModuleTypeEnum.historyNode]: NodeHistory,
[FlowModuleTypeEnum.chatNode]: NodeChat,
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
[FlowModuleTypeEnum.contentExtract]: NodeExtract,
[FlowModuleTypeEnum.httpRequest]: NodeHttp
// [FlowModuleTypeEnum.empty]: EmptyModule
};
const edgeTypes = {
buttonedge: ButtonEdge
};
type Props = { app: AppSchema; onCloseSettings: () => void };
const AppEdit = ({ app, onCloseSettings }: Props) => {
const theme = useTheme();
const { toast } = useToast();
const { t } = useTranslation();
const { copyData } = useCopyData();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const { updateAppDetail } = useUserStore();
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
const onFixView = useCallback(() => {
const btn = document.querySelector('.react-flow__controls-fitview') as HTMLButtonElement;
setTimeout(() => {
btn && btn.click();
}, 100);
}, []);
const onAddNode = useCallback(
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
if (!reactFlowWrapper.current) return;
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
setNodes((state) =>
state.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: mouseX, y: mouseY }
},
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
)
);
},
[x, zoom, y]
);
const onDelNode = useCallback(
(nodeId: string) => {
setNodes((state) => state.filter((item) => item.id !== nodeId));
setEdges((state) => state.filter((edge) => edge.source !== nodeId && edge.target !== nodeId));
},
[setEdges, setNodes]
);
const onDelEdge = useCallback(
({
moduleId,
sourceHandle,
targetHandle
}: {
moduleId: string;
sourceHandle?: string;
targetHandle?: string;
}) => {
if (!sourceHandle && !targetHandle) return;
setEdges((state) =>
state.filter((edge) => {
if (edge.source === moduleId && edge.sourceHandle === sourceHandle) return false;
if (edge.target === moduleId && edge.targetHandle === targetHandle) return false;
return true;
})
);
},
[setEdges]
);
const onCopyNode = useCallback(
(nodeId: string) => {
setNodes((nodes) => {
const node = nodes.find((node) => node.id === nodeId);
if (!node) return nodes;
const template = {
logo: node.data.logo,
name: node.data.name,
intro: node.data.intro,
description: node.data.description,
flowType: node.data.flowType,
inputs: node.data.inputs,
outputs: node.data.outputs,
showStatus: node.data.showStatus
};
return nodes.concat(
appModule2FlowNode({
item: {
...template,
moduleId: nanoid(),
position: { x: node.position.x + 200, y: node.position.y + 50 }
},
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
);
});
},
[setNodes]
);
const onCollectionNode = useCallback(
(nodeId: string) => {
console.log(nodes.find((node) => node.id === nodeId));
},
[nodes]
);
const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
name: item.data.name,
flowType: item.data.flowType,
showStatus: item.data.showStatus,
position: item.position,
inputs: item.data.inputs.map((item) => ({
...item,
connected: item.type !== FlowInputItemTypeEnum.target
})),
outputs: item.data.outputs.map((item) => ({
...item,
targets: [] as FlowOutputTargetItemType[]
}))
}));
// update inputs and outputs
modules.forEach((module) => {
module.inputs.forEach((input) => {
input.connected =
input.connected ||
!!edges.find(
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
);
});
module.outputs.forEach((output) => {
output.targets = edges
.filter(
(edge) =>
edge.source === module.moduleId &&
edge.sourceHandle === output.key &&
edge.targetHandle
)
.map((edge) => ({
moduleId: edge.target,
key: edge.targetHandle || ''
}));
});
});
return modules;
}, [edges, nodes]);
const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== moduleId) return node;
if (type === 'inputs') {
return {
...node,
data: {
...node.data,
inputs: node.data.inputs.map((item) => (item.key === key ? value : item))
}
};
}
if (type === 'addInput') {
const input = node.data.inputs.find((input) => input.key === value.key);
if (input) {
toast({
status: 'warning',
title: 'key 重复'
});
return {
...node,
data: {
...node.data,
inputs: node.data.inputs
}
};
}
return {
...node,
data: {
...node.data,
inputs: node.data.inputs.concat(value)
}
};
}
if (type === 'delInput') {
onDelEdge({ moduleId, targetHandle: key });
return {
...node,
data: {
...node.data,
inputs: node.data.inputs.filter((item) => item.key !== key)
}
};
}
// del output connect
const delOutputs = node.data.outputs.filter(
(item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key)
);
delOutputs.forEach((output) => {
onDelEdge({ moduleId, sourceHandle: output.key });
});
return {
...node,
data: {
...node.data,
outputs: value
}
};
})
);
},
[]
);
const onDelConnect = useCallback((id: string) => {
setEdges((state) => state.filter((item) => item.id !== id));
}, []);
const onConnect = useCallback(
({ connect }: { connect: Connection }) => {
const source = nodes.find((node) => node.id === connect.source)?.data;
const sourceType = (() => {
if (source?.flowType === FlowModuleTypeEnum.classifyQuestion) {
return FlowValueTypeEnum.boolean;
}
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
})();
const targetType = nodes
.find((node) => node.id === connect.target)
?.data?.inputs.find((input) => input.key === connect.targetHandle)?.valueType;
if (!sourceType || !targetType) {
return toast({
status: 'warning',
title: t('app.Connection is invalid')
});
}
if (
sourceType !== FlowValueTypeEnum.any &&
targetType !== FlowValueTypeEnum.any &&
sourceType !== targetType
) {
return toast({
status: 'warning',
title: t('app.Connection type is different')
});
}
setEdges((state) =>
addEdge(
{
...connect,
type: 'buttonedge',
animated: true,
data: {
onDelete: onDelConnect
}
},
state
)
);
},
[nodes]
);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: () => {
return updateAppDetail(app._id, {
modules: flow2AppModules(),
type: AppTypeEnum.advanced
});
},
successToast: '保存配置成功',
errorToast: '保存配置异常',
onSuccess() {
ChatTestRef.current?.resetChatTest();
}
});
const initData = useCallback(
(modules: AppModuleItemType[]) => {
const edges = appModule2FlowEdge({
modules,
onDelete: onDelConnect
});
setEdges(edges);
setNodes(
modules.map((item) =>
appModule2FlowNode({
item,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
})
)
);
onFixView();
},
[
onDelConnect,
setEdges,
setNodes,
onFixView,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
]
);
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
return (
<>
{/* header */}
<Flex
py={3}
px={[2, 5, 8]}
borderBottom={theme.borders.base}
alignItems={'center'}
userSelect={'none'}
>
<MyTooltip label={'返回'} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'back'} w={'14px'} />}
borderRadius={'md'}
borderColor={'myGray.300'}
variant={'base'}
aria-label={''}
onClick={() => {
onCloseSettings();
onFixView();
}}
/>
</MyTooltip>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
</Box>
<MyTooltip label={t('app.Import Configs')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'importLight'} w={['14px', '16px']} />}
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={onOpenImport}
/>
</MyTooltip>
<MyTooltip label={t('app.Export Configs')}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'export'} w={['14px', '16px']} />}
borderRadius={'lg'}
variant={'base'}
aria-label={'save'}
onClick={() =>
copyData(
JSON.stringify(flow2AppModules(), null, 2),
t('app.Export Config Successful')
)
}
/>
</MyTooltip>
{testModules ? (
<IconButton
mr={[3, 6]}
icon={<SmallCloseIcon fontSize={'25px'} />}
variant={'base'}
color={'myGray.600'}
borderRadius={'lg'}
aria-label={''}
onClick={() => setTestModules(undefined)}
/>
) : (
<MyTooltip label={'测试对话'}>
<IconButton
mr={[3, 6]}
icon={<MyIcon name={'chat'} w={['14px', '16px']} />}
borderRadius={'lg'}
aria-label={'save'}
variant={'base'}
onClick={() => {
setTestModules(flow2AppModules());
}}
/>
</MyTooltip>
)}
<MyTooltip label={'保存配置'}>
<IconButton
icon={<MyIcon name={'save'} w={['14px', '16px']} />}
borderRadius={'lg'}
isLoading={isLoading}
aria-label={'save'}
onClick={onclickSave}
/>
</MyTooltip>
</Flex>
<Box
minH={'400px'}
flex={'1 0 0'}
w={'100%'}
h={0}
position={'relative'}
onContextMenu={(e) => {
e.preventDefault();
return false;
}}
>
{/* open module template */}
<IconButton
position={'absolute'}
top={5}
left={5}
w={'38px'}
h={'38px'}
borderRadius={'50%'}
icon={<SmallCloseIcon fontSize={'26px'} />}
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
transition={'0.2s ease'}
aria-label={''}
zIndex={1}
boxShadow={'2px 2px 6px #85b1ff'}
onClick={() => {
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
}}
/>
<ReactFlow
ref={reactFlowWrapper}
className={styles.panel}
fitView
nodes={nodes}
edges={edges}
minZoom={0.4}
maxZoom={1.5}
defaultEdgeOptions={edgeOptions}
connectionLineStyle={connectionLineStyle}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={(connect) => {
connect.sourceHandle &&
connect.targetHandle &&
onConnect({
connect
});
}}
>
<Background />
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
</ReactFlow>
<TemplateList
isOpen={isOpenTemplate}
nodes={nodes}
onAddNode={onAddNode}
onClose={onCloseTemplate}
/>
<ChatTest
ref={ChatTestRef}
modules={testModules}
app={app}
onClose={() => setTestModules(undefined)}
/>
</Box>
{isOpenImport && (
<ImportSettings
onClose={onCloseImport}
onSuccess={(data) => {
setEdges([]);
setNodes([]);
setTimeout(() => {
initData(data);
}, 10);
}}
/>
)}
</>
);
};
const Flow = (data: Props) => (
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<ReactFlowProvider>
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{!!data.app._id && <AppEdit {...data} />}
</Flex>
</ReactFlowProvider>
</Box>
);
export default React.memo(Flow);

View File

@@ -1,242 +0,0 @@
import React, { useState } from 'react';
import {
Card,
Flex,
Box,
Button,
ModalBody,
ModalHeader,
ModalFooter,
useTheme,
Textarea
} from '@chakra-ui/react';
import Avatar from '@/components/Avatar';
import { KbListItemType } from '@/types/plugin';
import { useForm } from 'react-hook-form';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import type { SelectedKbType } from '@/types/plugin';
import { useGlobalStore } from '@/store/global';
import { useToast } from '@/hooks/useToast';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyIcon from '@/components/Icon';
export type KbParamsType = {
searchSimilarity: number;
searchLimit: number;
searchEmptyText: string;
};
export const KBSelectModal = ({
kbList,
activeKbs = [],
onChange,
onClose
}: {
kbList: KbListItemType[];
activeKbs: SelectedKbType;
onChange: (e: SelectedKbType) => void;
onClose: () => void;
}) => {
const theme = useTheme();
const [selectedKbList, setSelectedKbList] = useState<SelectedKbType>(activeKbs);
const { isPc } = useGlobalStore();
const { toast } = useToast();
return (
<MyModal
isOpen={true}
isCentered={!isPc}
maxW={['90vw', '800px']}
w={'800px'}
onClose={onClose}
>
<Flex flexDirection={'column'} h={['90vh', 'auto']}>
<ModalHeader>
<Box>({selectedKbList.length})</Box>
<Box fontSize={'sm'} color={'myGray.500'} fontWeight={'normal'}>
</Box>
</ModalHeader>
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
userSelect={'none'}
>
{kbList.map((item) =>
(() => {
const selected = !!selectedKbList.find((kb) => kb.kbId === item._id);
const active = !!activeKbs.find((kb) => kb.kbId === item._id);
return (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
order={active ? 0 : 1}
_hover={{
boxShadow: 'md'
}}
{...(selected
? {
bg: 'myBlue.300'
}
: {})}
onClick={() => {
if (selected) {
setSelectedKbList((state) => state.filter((kb) => kb.kbId !== item._id));
} else {
const vectorModel = selectedKbList[0]?.vectorModel?.model;
if (vectorModel && vectorModel !== item.vectorModel.model) {
return toast({
status: 'warning',
title: '仅能选择同一个索引模型的知识库'
});
}
setSelectedKbList((state) => [
...state,
{ kbId: item._id, vectorModel: item.vectorModel }
]);
}
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
{item.name}
</Box>
</Flex>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{item.vectorModel.name}</Box>
</Flex>
</Card>
);
})()
)}
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
onClose();
onChange(selectedKbList);
}}
>
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export const KbParamsModal = ({
searchEmptyText,
searchLimit,
searchSimilarity,
onClose,
onChange
}: KbParamsType & { onClose: () => void; onChange: (e: KbParamsType) => void }) => {
const [refresh, setRefresh] = useState(false);
const { register, setValue, getValues, handleSubmit } = useForm<KbParamsType>({
defaultValues: {
searchEmptyText,
searchLimit,
searchSimilarity
}
});
return (
<MyModal isOpen={true} onClose={onClose} title={'搜索参数调整'} minW={['90vw', '600px']}>
<Flex flexDirection={'column'}>
<ModalBody>
<Box display={['block', 'flex']} py={5} pt={[0, 5]}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
<MyTooltip
label={'不同索引模型的相似度有区别,请通过搜索测试来选择合适的数值'}
forceShow
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues('searchSimilarity')}
onChange={(val) => {
setValue('searchSimilarity', val);
setRefresh(!refresh);
}}
/>
</Box>
<Box display={['block', 'flex']} py={8}>
<Box flex={'0 0 100px'} mb={[8, 0]}>
</Box>
<Box flex={1}>
<MySlider
markList={[
{ label: '1', value: 1 },
{ label: '20', value: 20 }
]}
min={1}
max={20}
value={getValues('searchLimit')}
onChange={(val) => {
setValue('searchLimit', val);
setRefresh(!refresh);
}}
/>
</Box>
</Box>
<Box display={['block', 'flex']} pt={3}>
<Box flex={'0 0 100px'} mb={[2, 0]}>
</Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={
'若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文FastGPT 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button
onClick={() => {
onClose();
handleSubmit(onChange)();
}}
>
</Button>
</ModalFooter>
</Flex>
</MyModal>
);
};
export default KBSelectModal;

View File

@@ -1,263 +0,0 @@
import React, { useState } from 'react';
import {
Flex,
Box,
Button,
TableContainer,
Table,
Thead,
Tr,
Th,
Td,
Tbody,
useDisclosure,
ModalFooter,
ModalBody,
FormControl,
Input,
useTheme
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyIcon from '@/components/Icon';
import { useLoading } from '@/hooks/useLoading';
import { useQuery } from '@tanstack/react-query';
import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat';
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
import { useForm } from 'react-hook-form';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/app';
import { useRequest } from '@/hooks/useRequest';
import { formatPrice } from '@/utils/user';
import MyTooltip from '@/components/MyTooltip';
import MyModal from '@/components/MyModal';
import MyRadio from '@/components/Radio';
const Share = ({ appId }: { appId: string }) => {
const { Loading, setIsLoading } = useLoading();
const { copyData } = useCopyData();
const {
isOpen: isOpenCreateShareChat,
onOpen: onOpenCreateShareChat,
onClose: onCloseCreateShareChat
} = useDisclosure();
const {
register: registerShareChat,
getValues: getShareChatValues,
setValue: setShareChatValues,
handleSubmit: submitShareChat,
reset: resetShareChat
} = useForm({
defaultValues: defaultShareChat
});
const {
isFetching,
data: shareChatList = [],
refetch: refetchShareChatList
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
const { mutate: onclickCreateShareChat, isLoading: creating } = useRequest({
mutationFn: async (e: ShareChatEditType) =>
createShareChat({
...e,
appId
}),
errorToast: '创建分享链接异常',
onSuccess(id) {
onCloseCreateShareChat();
refetchShareChatList();
const url = `${location.origin}/chat/share?shareId=${id}`;
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
resetShareChat(defaultShareChat);
}
});
return (
<Box position={'relative'} pt={[3, 5, 8]} px={[5, 8]} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<MyTooltip
forceShow
label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。"
>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Box>
<Button
variant={'base'}
colorScheme={'myBlue'}
size={['sm', 'md']}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'}>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th>使</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>{formatPrice(item.total)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td display={'flex'} alignItems={'center'}>
<MyTooltip label={'嵌入网页'}>
<MyIcon
mr={4}
name="apiLight"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
const src = `${location.origin}/js/iframe.js`;
const script = `<script src="${src}" id="fastgpt-iframe" data-src="${url}" data-color="#4e83fd"></script>`;
copyData(script, '已复制嵌入 Script可在应用 HTML 底部嵌入', 3000);
}}
/>
</MyTooltip>
<MyTooltip label={'复制分享链接'}>
<MyIcon
mr={4}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
copyData(url, '已复制分享链接,可直接分享使用');
}}
/>
</MyTooltip>
<MyTooltip label={'删除链接'}>
<MyIcon
name="delete"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'red' }}
onClick={async () => {
setIsLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setIsLoading(false);
}}
/>
</MyTooltip>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{shareChatList.length === 0 && !isFetching && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
</Box>
</Flex>
)}
{/* create shareChat modal */}
<MyModal
isOpen={isOpenCreateShareChat}
onClose={onCloseCreateShareChat}
title={'创建免登录窗口'}
>
<ModalBody>
<FormControl>
<Flex alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
:
</Box>
<Input
placeholder="记录名字,仅用于展示"
maxLength={20}
{...registerShareChat('name', {
required: '记录名称不能为空'
})}
/>
</Flex>
</FormControl>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
</Button>
<Button
isLoading={creating}
onClick={submitShareChat((data) => onclickCreateShareChat(data))}
>
</Button>
</ModalFooter>
</MyModal>
<Loading loading={isFetching} fixed={false} />
</Box>
);
};
enum LinkTypeEnum {
share = 'share',
iframe = 'iframe'
}
const OutLink = ({ appId }: { appId: string }) => {
const theme = useTheme();
const [linkType, setLinkType] = useState<`${LinkTypeEnum}`>(LinkTypeEnum.share);
return (
<Box pt={[1, 5]}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
使
</Box>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 360px))']}
iconSize={'20px'}
list={[
{
icon: 'outlink_share',
title: '免登录窗口',
desc: '分享链接给其他用户,无需登录即可直接进行使用',
value: LinkTypeEnum.share
}
// {
// icon: 'outlink_iframe',
// title: '网页嵌入',
// desc: '嵌入到已有网页中,右下角会生成对话按键',
// value: LinkTypeEnum.iframe
// }
]}
value={linkType}
onChange={(e) => setLinkType(e as `${LinkTypeEnum}`)}
/>
</Box>
{linkType === LinkTypeEnum.share && <Share appId={appId} />}
</Box>
);
};
export default OutLink;

View File

@@ -1,53 +0,0 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import { feConfigs } from '@/store/static';
import { serviceSideProps } from '@/utils/i18n';
import { useRouter } from 'next/router';
import Navbar from './components/Navbar';
import Hero from './components/Hero';
import Ability from './components/Ability';
import Choice from './components/Choice';
import Footer from './components/Footer';
import Loading from '@/components/Loading';
const Home = ({ homeUrl = '/' }: { homeUrl: string }) => {
const router = useRouter();
if (homeUrl !== '/') {
router.replace(homeUrl);
}
return homeUrl === '/' ? (
<Box id="home" bg={'myWhite.600'} h={'100vh'} overflowY={'auto'} overflowX={'hidden'}>
<Box position={'fixed'} zIndex={10} top={0} left={0} right={0}>
<Navbar />
</Box>
<Box maxW={'1200px'} pt={'70px'} m={'auto'}>
<Hero />
<Ability />
<Box my={[4, 6]}>
<Choice />
</Box>
</Box>
{feConfigs?.show_git && (
<Box bg={'white'}>
<Footer />
</Box>
)}
</Box>
) : (
<Loading />
);
};
export async function getServerSideProps(content: any) {
return {
props: {
...(await serviceSideProps(content)),
homeUrl: process.env.HOME_URL || '/'
}
};
}
export default Home;

View File

@@ -1,194 +0,0 @@
import React, { useCallback, useRef } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { KbItemType } from '@/types/plugin';
import { getErrText } from '@/utils/tools';
import { useGlobalStore } from '@/store/global';
import { type ComponentRef } from './components/Info';
import Tabs from '@/components/Tabs';
import dynamic from 'next/dynamic';
import DataCard from './components/DataCard';
import MyIcon from '@/components/Icon';
import SideTabs from '@/components/SideTabs';
import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
import Info from './components/Info';
import { serviceSideProps } from '@/utils/i18n';
import { useTranslation } from 'react-i18next';
import { getTrainingQueueLen } from '@/api/plugins/kb';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { feConfigs } from '@/store/static';
const ImportData = dynamic(() => import('./components/Import'), {
ssr: false
});
const Test = dynamic(() => import('./components/Test'), {
ssr: false
});
enum TabEnum {
data = 'data',
import = 'import',
test = 'test',
info = 'info'
}
const Detail = ({ kbId, currentTab }: { kbId: string; currentTab: `${TabEnum}` }) => {
const InfoRef = useRef<ComponentRef>(null);
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { isPc } = useGlobalStore();
const { kbDetail, getKbDetail } = useUserStore();
const tabList = useRef([
{ label: '数据集', id: TabEnum.data, icon: 'overviewLight' },
{ label: '导入数据', id: TabEnum.import, icon: 'importLight' },
{ label: '搜索测试', id: TabEnum.test, icon: 'kbTest' },
{ label: '配置', id: TabEnum.info, icon: 'settingLight' }
]);
const setCurrentTab = useCallback(
(tab: `${TabEnum}`) => {
router.replace({
query: {
kbId,
currentTab: tab
}
});
},
[kbId, router]
);
const form = useForm<KbItemType>({
defaultValues: kbDetail
});
useQuery([kbId], () => getKbDetail(kbId), {
onSuccess(res) {
form.reset(res);
InfoRef.current?.initInput(res.tags);
},
onError(err: any) {
router.replace(`/kb/list`);
toast({
title: getErrText(err, '获取知识库异常'),
status: 'error'
});
}
});
const { data: trainingQueueLen = 0 } = useQuery(['getTrainingQueueLen'], getTrainingQueueLen, {
refetchInterval: 5000
});
return (
<PageContainer>
<Box display={['block', 'flex']} h={'100%'} pt={[4, 0]}>
{isPc ? (
<Flex
flexDirection={'column'}
p={4}
h={'100%'}
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={kbDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}>
{kbDetail.name}
</Box>
</Flex>
<SideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList.current}
activeId={currentTab}
onChange={(e: any) => {
setCurrentTab(e);
}}
/>
<Box textAlign={'center'}>
<Flex justifyContent={'center'} alignItems={'center'}>
<MyIcon mr={1} name="overviewLight" w={'16px'} color={'green.500'} />
<Box>{t('dataset.System Data Queue')}</Box>
<MyTooltip
label={t('dataset.Queue Desc', { title: feConfigs?.systemTitle })}
placement={'top'}
>
<QuestionOutlineIcon ml={1} w={'16px'} />
</MyTooltip>
</Flex>
<Box mt={1} fontWeight={'bold'}>
{trainingQueueLen}
</Box>
</Box>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.100' }}
onClick={() => router.replace('/kb/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
h={'28px'}
size={'sm'}
borderRadius={'50%'}
aria-label={''}
/>
</Flex>
</Flex>
) : (
<Box mb={3}>
<Tabs
m={'auto'}
w={'260px'}
size={isPc ? 'md' : 'sm'}
list={tabList.current.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
/>
</Box>
)}
{!!kbDetail._id && (
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
{currentTab === TabEnum.data && <DataCard kbId={kbId} />}
{currentTab === TabEnum.import && <ImportData kbId={kbId} />}
{currentTab === TabEnum.test && <Test kbId={kbId} />}
{currentTab === TabEnum.info && <Info ref={InfoRef} kbId={kbId} form={form} />}
</Box>
)}
</Box>
</PageContainer>
);
};
export async function getServerSideProps(context: any) {
const currentTab = context?.query?.currentTab || TabEnum.data;
const kbId = context?.query?.kbId;
return {
props: { currentTab, kbId, ...(await serviceSideProps(context)) }
};
}
export default React.memo(Detail);

View File

@@ -1,171 +0,0 @@
import React, { useCallback } from 'react';
import {
Box,
Card,
Flex,
Grid,
useTheme,
Button,
IconButton,
useDisclosure
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user';
import PageContainer from '@/components/PageContainer';
import { useConfirm } from '@/hooks/useConfirm';
import { AddIcon } from '@chakra-ui/icons';
import { useQuery } from '@tanstack/react-query';
import { useToast } from '@/hooks/useToast';
import { delKbById } from '@/api/plugins/kb';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import Tag from '@/components/Tag';
import { serviceSideProps } from '@/utils/i18n';
import dynamic from 'next/dynamic';
const CreateModal = dynamic(() => import('./component/CreateModal'), { ssr: false });
const Kb = () => {
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { openConfirm, ConfirmModal } = useConfirm({
title: '删除提示',
content: '确认删除该知识库?知识库相关的文件、记录将永久删除,无法恢复!'
});
const { myKbList, loadKbList, setKbList } = useUserStore();
const {
isOpen: isOpenCreateModal,
onOpen: onOpenCreateModal,
onClose: onCloseCreateModal
} = useDisclosure();
const { refetch } = useQuery(['loadKbList'], () => loadKbList());
/* 点击删除 */
const onclickDelKb = useCallback(
async (id: string) => {
try {
delKbById(id);
toast({
title: '删除成功',
status: 'success'
});
setKbList(myKbList.filter((item) => item._id !== id));
} catch (err: any) {
toast({
title: err?.message || '删除失败',
status: 'error'
});
}
},
[toast, setKbList, myKbList]
);
return (
<PageContainer>
<Flex pt={3} px={5} alignItems={'center'}>
<Box flex={1} className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
</Box>
<Button leftIcon={<AddIcon />} variant={'base'} onClick={onOpenCreateModal}>
</Button>
</Flex>
<Grid
p={5}
gridTemplateColumns={['1fr', 'repeat(3,1fr)', 'repeat(4,1fr)', 'repeat(5,1fr)']}
gridGap={5}
>
{myKbList.map((kb) => (
<Card
display={'flex'}
flexDirection={'column'}
key={kb._id}
py={4}
px={5}
cursor={'pointer'}
h={'140px'}
border={theme.borders.md}
boxShadow={'none'}
userSelect={'none'}
position={'relative'}
_hover={{
boxShadow: '1px 1px 10px rgba(0,0,0,0.2)',
borderColor: 'transparent',
'& .delete': {
display: 'block'
}
}}
onClick={() =>
router.push({
pathname: '/kb/detail',
query: {
kbId: kb._id
}
})
}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={kb.avatar} borderRadius={'lg'} w={'28px'} />
<Box ml={3}>{kb.name}</Box>
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
display={['', 'none']}
_hover={{
bg: 'red.100'
}}
onClick={(e) => {
e.stopPropagation();
openConfirm(() => onclickDelKb(kb._id))();
}}
/>
</Flex>
<Box flex={'1 0 0'} overflow={'hidden'} pt={2}>
<Flex>
{kb.tags.map((tag, i) => (
<Tag key={i} mr={2} mb={2}>
{tag}
</Tag>
))}
</Flex>
</Box>
<Flex justifyContent={'flex-end'} alignItems={'center'} fontSize={'sm'}>
<MyIcon mr={1} name="kbTest" w={'12px'} />
<Box color={'myGray.500'}>{kb.vectorModel.name}</Box>
</Flex>
</Card>
))}
</Grid>
{myKbList.length === 0 && (
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
</Box>
</Flex>
)}
<ConfirmModal />
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} />}
</PageContainer>
);
};
export async function getServerSideProps(content: any) {
return {
props: {
...(await serviceSideProps(content))
}
};
}
export default Kb;

View File

@@ -1,175 +0,0 @@
import { connectToDatabase, Bill, User, OutLink } from '../mongo';
import { BillSourceEnum } from '@/constants/user';
import { getModel } from '../utils/data';
import { ChatHistoryItemResType } from '@/types/chat';
import { formatPrice } from '@/utils/user';
import { addLog } from '../utils/tools';
export const pushTaskBill = async ({
appName,
appId,
userId,
source,
shareId,
response
}: {
appName: string;
appId: string;
userId: string;
source: `${BillSourceEnum}`;
shareId?: string;
response: ChatHistoryItemResType[];
}) => {
const total = response.reduce((sum, item) => sum + item.price, 0);
await Promise.allSettled([
Bill.create({
userId,
appName,
appId,
total,
source,
list: response.map((item) => ({
moduleName: item.moduleName,
amount: item.price || 0,
model: item.model,
tokenLen: item.tokens
}))
}),
User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
}),
...(shareId
? [
updateShareChatBill({
shareId,
total
})
]
: [])
]);
addLog.info(`finish completions`, {
source,
userId,
price: formatPrice(total)
});
};
export const updateShareChatBill = async ({
shareId,
total
}: {
shareId: string;
total: number;
}) => {
try {
await OutLink.findOneAndUpdate(
{ shareId },
{
$inc: { total },
lastTime: new Date()
}
);
} catch (err) {
addLog.error('update shareChat error', err);
}
};
export const pushQABill = async ({
userId,
totalTokens,
appName
}: {
userId: string;
totalTokens: number;
appName: string;
}) => {
addLog.info('splitData generate success', { totalTokens });
let billId;
try {
await connectToDatabase();
// 获取模型单价格, 都是用 gpt35 拆分
const unitPrice = global.qaModel.price || 3;
// 计算价格
const total = unitPrice * totalTokens;
// 插入 Bill 记录
const res = await Bill.create({
userId,
appName,
tokenLen: totalTokens,
total
});
billId = res._id;
// 账号扣费
await User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
});
} catch (err) {
addLog.error('Create completions bill error', err);
billId && Bill.findByIdAndDelete(billId);
}
};
export const pushGenerateVectorBill = async ({
userId,
tokenLen,
model
}: {
userId: string;
tokenLen: number;
model: string;
}) => {
let billId;
try {
await connectToDatabase();
try {
// 计算价格. 至少为1
const vectorModel =
global.vectorModels.find((item) => item.model === model) || global.vectorModels[0];
const unitPrice = vectorModel.price || 0.2;
let total = unitPrice * tokenLen;
total = total > 1 ? total : 1;
// 插入 Bill 记录
const res = await Bill.create({
userId,
model: vectorModel.model,
appName: '索引生成',
total,
list: [
{
moduleName: '索引生成',
amount: total,
model: vectorModel.model,
tokenLen
}
]
});
billId = res._id;
// 账号扣费
await User.findByIdAndUpdate(userId, {
$inc: { balance: -total }
});
} catch (err) {
addLog.error('Create generateVector bill error', err);
billId && Bill.findByIdAndDelete(billId);
}
} catch (error) {
console.log(error);
}
};
export const countModelPrice = ({ model, tokens }: { model: string; tokens: number }) => {
const modelData = getModel(model);
if (!modelData) return 0;
return modelData.price * tokens;
};

View File

@@ -1,23 +0,0 @@
import { Schema, model, models, Model } from 'mongoose';
import { OpenApiSchema } from '@/types/mongoSchema';
const OpenApiSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
apiKey: {
type: String,
required: true
},
createTime: {
type: Date,
default: () => new Date()
},
lastUsedTime: {
type: Date
}
});
export const OpenApi: Model<OpenApiSchema> = models['openapi'] || model('openapi', OpenApiSchema);

View File

@@ -1,107 +0,0 @@
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { countModelPrice } from '@/service/events/pushBill';
import { UserModelSchema } from '@/types/mongoSchema';
import { getModel } from '@/service/utils/data';
import { SystemInputEnum } from '@/constants/app';
import { SpecialInputKeyEnum } from '@/constants/flow';
export type CQProps = {
systemPrompt?: string;
history?: ChatItemType[];
[SystemInputEnum.userChatInput]: string;
userOpenaiAccount: UserModelSchema['openaiAccount'];
[SpecialInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
};
export type CQResponse = {
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
[key: string]: any;
};
const agentModel = 'gpt-3.5-turbo';
const agentFunName = 'agent_user_question';
const maxTokens = 3000;
/* request openai chat */
export const dispatchClassifyQuestion = async (props: Record<string, any>): Promise<CQResponse> => {
const { agents, systemPrompt, history = [], userChatInput, userOpenaiAccount } = props as CQProps;
if (!userChatInput) {
return Promise.reject('Input is empty');
}
const messages: ChatItemType[] = [
...(systemPrompt
? [
{
obj: ChatRoleEnum.System,
value: systemPrompt
}
]
: []),
...history,
{
obj: ChatRoleEnum.Human,
value: userChatInput
}
];
const filterMessages = ChatContextFilter({
model: agentModel,
prompts: messages,
maxTokens
});
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
// function body
const agentFunction = {
name: agentFunName,
description: '判断用户问题的类型属于哪方面,返回对应的枚举字段',
parameters: {
type: 'object',
properties: {
type: {
type: 'string',
description: agents.map((item) => `${item.value},返回:'${item.key}'`).join(''),
enum: agents.map((item) => item.key)
}
},
required: ['type']
}
};
const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion(
{
model: agentModel,
temperature: 0,
messages: [...adaptMessages],
function_call: { name: agentFunName },
functions: [agentFunction]
},
{
...axiosConfig(userOpenaiAccount)
}
);
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
const tokens = response.data.usage?.total_tokens || 0;
const result = agents.find((item) => item.key === arg?.type) || agents[0];
return {
[result.key]: 1,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.CQ,
price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
model: getModel(agentModel)?.name || agentModel,
tokens,
cqList: agents,
cqResult: result.value
}
};
};

View File

@@ -1,131 +0,0 @@
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getAIChatApi, axiosConfig } from '@/service/lib/openai';
import type { ContextExtractAgentItemType } from '@/types/app';
import { ContextExtractEnum } from '@/constants/flow/flowField';
import { countModelPrice } from '@/service/events/pushBill';
import { UserModelSchema } from '@/types/mongoSchema';
import { getModel } from '@/service/utils/data';
export type Props = {
userOpenaiAccount: UserModelSchema['openaiAccount'];
history?: ChatItemType[];
[ContextExtractEnum.content]: string;
[ContextExtractEnum.extractKeys]: ContextExtractAgentItemType[];
[ContextExtractEnum.description]: string;
};
export type Response = {
[ContextExtractEnum.success]?: boolean;
[ContextExtractEnum.failed]?: boolean;
[ContextExtractEnum.fields]: string;
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
};
const agentModel = 'gpt-3.5-turbo';
const agentFunName = 'agent_extract_data';
const maxTokens = 4000;
export async function dispatchContentExtract({
userOpenaiAccount,
content,
extractKeys,
history = [],
description
}: Props): Promise<Response> {
if (!content) {
return Promise.reject('Input is empty');
}
const messages: ChatItemType[] = [
...history,
{
obj: ChatRoleEnum.Human,
value: content
}
];
const filterMessages = ChatContextFilter({
// @ts-ignore
model: agentModel,
prompts: messages,
maxTokens
});
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
const properties: Record<
string,
{
type: string;
description: string;
}
> = {};
extractKeys.forEach((item) => {
properties[item.key] = {
type: 'string',
description: item.desc
};
});
// function body
const agentFunction = {
name: agentFunName,
description: `${description}\n如果内容不存在返回空字符串。`,
parameters: {
type: 'object',
properties,
required: extractKeys.filter((item) => item.required).map((item) => item.key)
}
};
const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion(
{
model: agentModel,
temperature: 0,
messages: [...adaptMessages],
function_call: { name: agentFunName },
functions: [agentFunction]
},
{
...axiosConfig(userOpenaiAccount)
}
);
const arg: Record<string, any> = (() => {
try {
return JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '{}');
} catch (error) {
return {};
}
})();
// auth fields
let success = !extractKeys.find((item) => !arg[item.key]);
// auth empty value
if (success) {
for (const key in arg) {
if (arg[key] === '') {
success = false;
break;
}
}
}
const tokens = response.data.usage?.total_tokens || 0;
return {
[ContextExtractEnum.success]: success ? true : undefined,
[ContextExtractEnum.failed]: success ? undefined : true,
[ContextExtractEnum.fields]: JSON.stringify(arg),
...arg,
[TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.Extract,
price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
model: getModel(agentModel)?.name || agentModel,
tokens,
extractDescription: description,
extractResult: arg
}
};
}

View File

@@ -1,12 +0,0 @@
import { SystemInputEnum } from '@/constants/app';
export type UserChatInputProps = {
[SystemInputEnum.userChatInput]: string;
};
export const dispatchChatInput = (props: Record<string, any>) => {
const { userChatInput } = props as UserChatInputProps;
return {
userChatInput
};
};

View File

@@ -1,42 +0,0 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { type KbTestItemType } from '@/types/plugin';
type State = {
kbTestList: KbTestItemType[];
pushKbTestItem: (data: KbTestItemType) => void;
delKbTestItemById: (id: string) => void;
updateKbItemById: (data: KbTestItemType) => void;
};
export const useKbStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
kbTestList: [],
pushKbTestItem(data) {
set((state) => {
state.kbTestList = [data, ...state.kbTestList].slice(0, 500);
});
},
delKbTestItemById(id) {
set((state) => {
state.kbTestList = state.kbTestList.filter((item) => item.id !== id);
});
},
updateKbItemById(data: KbTestItemType) {
set((state) => {
state.kbTestList = state.kbTestList.map((item) => (item.id === data.id ? data : item));
});
}
})),
{
name: 'kbStore',
partialize: (state) => ({
kbTestList: state.kbTestList
})
}
)
)
);

View File

@@ -1,6 +0,0 @@
export interface UserOpenApiKey {
id: string;
apiKey: string;
createTime: Date;
lastUsedTime?: Date;
}

View File

@@ -1,44 +0,0 @@
import { VectorModelItemType } from './model';
import type { kbSchema } from './mongoSchema';
export type SelectedKbType = { kbId: string; vectorModel: VectorModelItemType }[];
export type KbListItemType = {
_id: string;
avatar: string;
name: string;
tags: string[];
vectorModel: VectorModelItemType;
};
/* kb type */
export interface KbItemType {
_id: string;
avatar: string;
name: string;
userId: string;
vectorModel: VectorModelItemType;
tags: string;
}
export type DatasetItemType = {
q: string; // 提问词
a: string; // 原文
source?: string;
file_id?: string;
};
export type KbDataItemType = DatasetItemType & {
id: string;
};
export type KbTestItemType = {
id: string;
kbId: string;
text: string;
time: Date;
results: (KbDataItemType & { score: number })[];
};
export type FetchResultItem = {
url: string;
content: string;
};

View File

@@ -1,25 +0,0 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Cookies from 'js-cookie';
export const LANG_KEY = 'NEXT_LOCALE_LANG';
export enum LangEnum {
'zh' = 'zh',
'en' = 'en'
}
export const setLangStore = (value: `${LangEnum}`) => {
return Cookies.set(LANG_KEY, value, { expires: 7, sameSite: 'None', secure: true });
};
export const getLangStore = () => {
return (Cookies.get(LANG_KEY) as `${LangEnum}`) || LangEnum.zh;
};
export const serviceSideProps = (content: any) => {
return serverSideTranslations(
content.req.cookies[LANG_KEY] || 'en',
undefined,
null,
content.locales
);
};

View File

@@ -1,8 +0,0 @@
import { countOpenAIToken, openAiSliceTextByToken } from './openai';
import { gpt_chatItemTokenSlice } from '@/pages/api/openapi/text/gptMessagesSlice';
export const modelToolMap = {
countTokens: countOpenAIToken,
sliceText: openAiSliceTextByToken,
tokenSlice: gpt_chatItemTokenSlice
};

View File

@@ -1,86 +0,0 @@
import { encoding_for_model } from '@dqbd/tiktoken';
import type { ChatItemType } from '@/types/chat';
import { ChatRoleEnum } from '@/constants/chat';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import axios from 'axios';
import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
export const getOpenAiEncMap = () => {
if (typeof window !== 'undefined' && window.OpenAiEncMap) {
return window.OpenAiEncMap;
}
if (typeof global !== 'undefined' && global.OpenAiEncMap) {
return global.OpenAiEncMap;
}
const enc = encoding_for_model('gpt-3.5-turbo', {
'<|im_start|>': 100264,
'<|im_end|>': 100265,
'<|im_sep|>': 100266
});
if (typeof window !== 'undefined') {
window.OpenAiEncMap = enc;
}
if (typeof global !== 'undefined') {
global.OpenAiEncMap = enc;
}
return enc;
};
export const adaptChatItem_openAI = ({
messages,
reserveId
}: {
messages: ChatItemType[];
reserveId: boolean;
}): MessageItemType[] => {
const map = {
[ChatRoleEnum.AI]: ChatCompletionRequestMessageRoleEnum.Assistant,
[ChatRoleEnum.Human]: ChatCompletionRequestMessageRoleEnum.User,
[ChatRoleEnum.System]: ChatCompletionRequestMessageRoleEnum.System
};
return messages.map((item) => ({
...(reserveId && { dataId: item.dataId }),
role: map[item.obj] || ChatCompletionRequestMessageRoleEnum.System,
content: item.value || ''
}));
};
export function countOpenAIToken({ messages }: { messages: ChatItemType[] }) {
const adaptMessages = adaptChatItem_openAI({ messages, reserveId: true });
const token = adaptMessages.reduce((sum, item) => {
const text = `${item.role}\n${item.content}`;
const enc = getOpenAiEncMap();
const encodeText = enc.encode(text);
const tokens = encodeText.length + 3; // 补充估算值
return sum + tokens;
}, 0);
return token;
}
export const openAiSliceTextByToken = ({ text, length }: { text: string; length: number }) => {
const enc = getOpenAiEncMap();
const encodeText = enc.encode(text);
const decoder = new TextDecoder();
return decoder.decode(enc.decode(encodeText.slice(0, length)));
};
export const authOpenAiKey = async (key: string) => {
return axios
.get('https://ccdbwscohpmu.cloud.sealos.io/openai/v1/dashboard/billing/subscription', {
headers: {
Authorization: `Bearer ${key}`
}
})
.then((res) => {
if (!res.data.access_until) {
return Promise.resolve('OpenAI Key 可能无效');
}
})
.catch((err) => {
console.log(err);
return Promise.reject(err?.response?.data?.error?.message || 'OpenAI Key 可能无效');
});
};

View File

@@ -1,26 +0,0 @@
import { PRICE_SCALE } from '@/constants/common';
import { loginOut } from '@/api/user';
const tokenKey = 'token';
export const clearToken = () => {
try {
loginOut();
localStorage.removeItem(tokenKey);
} catch (error) {
error;
}
};
export const setToken = (token: string) => {
localStorage.setItem(tokenKey, token);
};
export const getToken = () => {
return localStorage.getItem(tokenKey) || '';
};
/**
* 把数据库读取到的price转化成元
*/
export const formatPrice = (val = 0, multiple = 1) => {
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
};

12
docSite/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM hugomods/hugo:0.117.0 AS builder
WORKDIR /app
ADD ./docSite hugo
RUN cd /app/hugo && hugo mod get -u github.com/colinwilson/lotusdocs && hugo -v --minify
FROM fholzer/nginx-brotli:latest
LABEL org.opencontainers.image.source https://github.com/labring/FastGPT
COPY --from=builder /app/hugo/public /usr/share/nginx/html

View File

@@ -0,0 +1,145 @@
.docs-content .main-content img, .docs-content .main-content svg:not(.gitinfo svg):not(a svg) {
max-width: 80% !important;
height: auto;
display: block !important;
margin: 0 auto !important;
border-radius: .25rem;
}
div.code-toolbar {
padding-top: 1.95rem !important;
}
.docs-content .main-content pre code::before {
background: #fc625d;
border-radius: 50%;
box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
content: ' ';
height: 12px;
left: 12px;
margin-top: -21px;
position: absolute;
width: 12px;
z-index: 1;
}
li p {
margin-top: 1rem !important;
margin-bottom: 1rem;
}
footer {
height: 118px !important;
}
/*
footer a:hover {
text-decoration: none !important;
}
*/
.medium-zoom-overlay,
.medium-zoom-image--opened {
z-index: 1999;
}
/* 徽章样式 */
.github-badge {
display: inline-block;
border-radius: 4px;
text-shadow: none;
font-size: 12px;
color: #fff;
line-height: 15px;
margin-bottom: 5px;
margin-top: 5px;
}
.github-badge .badge-subject {
display: inline-block;
background-color: #4D4D4D;
padding: 4px 4px 4px 6px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.github-badge .badge-value {
display: inline-block;
padding: 4px 6px 4px 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.github-badge .bg-brightgreen {
background-color: #4DC820 !important;
}
.github-badge .bg-orange {
background-color: #FFA500 !important;
}
.github-badge .bg-yellow {
background-color: #D8B024 !important;
}
.github-badge .bg-blueviolet {
background-color: #8833D7 !important;
}
.github-badge .bg-pink {
background-color: #F26BAE !important;
}
.github-badge .bg-red {
background-color: #e05d44 !important;
}
.github-badge .bg-blue {
background-color: #007EC6 !important;
}
.github-badge .bg-lightgrey {
background-color: #9F9F9F !important;
}
.github-badge .bg-grey, .github-badge .bg-gray {
background-color: #555 !important;
}
.github-badge .bg-lightgrey, .github-badge .bg-lightgray {
background-color: #9f9f9f !important;
}
#fixed-box {
position: fixed;
z-index: 9999;
}
@media (max-width: 600px) {
#fixed-box {
display: none
}
}
.feedback-btn-wrapper {
position: fixed;
z-index: 1000;
bottom: 0;
left: 0;
margin: 2rem;
}
#feedback-btn {
height: 30px;
display: flex;
align-items: center;
padding: 1.2rem 0.7rem;
border-radius: 0.4rem;
cursor: pointer;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
user-select: none;
border: 0;
outline: 0;
color: #fff;
background-color: #4d698e;
transition: filter 0.4s ease;
}
#feedback-btn svg {
width: 1.25rem;
height: 1.25rem;
}
#feedback-btn span {
font-weight: 700;
font-size: 1rem;
margin-left: 0.5rem;
}

View File

@@ -0,0 +1,77 @@
/* Template Name: Lotus Docs
Author: Colin Wilson
E-mail: colin@aigis.uk
Created: October 2022
Version: 1.2.0
File Description: Main CSS file for Lotus Docs
*/
// Custom Font Variables
$font-family-secondary: {{ .Site.Params.secondary_font | default "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Ubuntu'" }};
$font-family-sans-serif: {{ .Site.Params.sans_serif_font | default "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Ubuntu'" }};
$font-family-monospace: {{ .Site.Params.mono_font | default "SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" }};
// Code Padding Variables
$code-block-padding-top: {{ if eq .Site.Params.docs.prism true -}}0{{ else }}1.25rem 0 0 0{{ end }};
// Icon Fonts
@import "custom/plugins/icons/google-material";
// Core files
@import "../../scss/bootstrap/functions";
@import "../../scss/bootstrap/variables";
@import {{ printf "'%s%s'" "custom/colors/" (.Site.Params.docs.themeColor | default "blue") }}; // current theme color
@import "../../scss/bootstrap/mixins";
@import "../../scss/bootstrap/bootstrap";
@import "variables";
{{ if and (.Site.Params.docsearch.appID) (.Site.Params.docsearch.apiKey) -}}
@import "custom/plugins/docsearch/style";
{{ end }}
// Structure
@import "custom/structure/general";
@import "custom/structure/content";
@import "custom/structure/sidebar";
@import "custom/structure/doc-nav";
@import "custom/structure/toc";
@import "custom/structure/footer";
// Components
@import "custom/components/buttons";
@import "custom/components/modal";
@import "custom/components/breadcrumb";
@import "custom/components/badge";
@import "custom/components/backgrounds";
@import "custom/components/alerts";
@import "custom/components/card";
@import "custom/components/forms";
@import "custom/components/table";
@import "custom/components/tabs";
@import "custom/components/tooltip";
// Pages
@import "custom/pages/features";
@import "custom/pages/helper";
// Plugins
// Prism / Chroma
{{- if eq .Site.Params.docs.prism true }}
@import {{ printf "'%s%s'" "custom/plugins/prism/themes/" (.Site.Params.docs.prismTheme | default "lotusdocs") }}; // current prism theme
@import "custom/plugins/prism/prism";
{{- else }}
@import "custom/plugins/chroma/default";
{{- end -}}
// FlexSearch
{{ if or (not (isset .Site.Params.flexsearch "enabled")) (eq .Site.Params.flexsearch.enabled true) -}}@import "custom/plugins/flexsearch/flexsearch";{{ end }}
// Feedback Widget
{{ if .Site.Params.feedback.enabled | default false -}}@import "custom/plugins/feedback/feedback";{{ end}}
// Mermaid
@import "custom/plugins/mermaid/mermaid";
// change
@import "custom/pages/custom";

View File

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

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,11 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2@v2.21100.20000/package/dist/cjs/popper.js/*",
"../../../../../.cache/hugo_cache/modules/filecache/modules/pkg/mod/github.com/twbs/bootstrap@v5.3.0+incompatible/js/*"
]
}
}
}

View File

@@ -0,0 +1,88 @@
---
title: '商业版'
description: 'FastGPT 商业版相关说明'
icon: 'shopping_cart'
draft: false
toc: true
weight: 20
---
## FastGPT 线上服务
[按线上标准计费](/docs/pricing)即可。 地址: https://fastgpt.run
## 商业版
商业版最终交付版本功能与 https://fastgpt.run 完全一致,品牌内容可自定义。
{{% alert icon="🤖" context="warning" %}}
商业版与开源版功能差异:(目前计划)
1. 自定义 title 和 logo
2. 用户注册,支付 (已有微信扫码支付,后续会补充支付方式)
3. API 访问限制,可配置:额度、过期时间
4. 团队空间 (计划)
5. 完善的 OpenAPI计划
6. 高级编排额外插件(计划)
7. 后台管理系统
a. 查询:用户、支付、应用、知识库
b. 变更:用户
c. 新增:用户
{{% /alert %}}
### 商业版定价
#### 交付费用
+ 使用 [Sealos 公有云](https://sealos.io)部署1万元/年/套 (无部署费用。赠送 8000 sealos 公有云额度,可用于 FastGPT 或其他云资源)。
+ 渠道商使用 Sealos 部署:返现 20% 成交额。
+ 私有服务器部署2万元/年/套(如需部署支持,按技术服务费计算)
+ 渠道商私有服务器部署1.3万元/年/套(渠道商合同单独约谈,累计 5 套以上可签)
#### 用户注册数量费用(按注册量算,不计量分享和 API
{{< table "table-hover table-striped-columns" >}}
| 最大用户数量 | 费用 元/年 |
| ------------ | ---------- |
| 100 | 0 |
| 5000 | 5000 |
| 2万 | 18000 |
| 5万 | 35000 |
| 20万 | 100000 |
{{< /table >}}
#### 总费用
总费用 = 商业版交付费用 + 用户数量费用
## 技术支持
### 应用定制
根据需求,定制实现某个需求的编排功能,最终会交付一个应用编排。可根据实际情况商讨。
### 技术服务费(定开、部署、维护等)
2000元/人/天
### 更新费用
大部分更新,重新拉镜像就可以了,不需要执行额外操作。
复杂更新可参考文档自行更新;或付费支持,标准与技术服务费一致。
## 联系方式
请填写[咨询问卷](https://fael3z0zfze.feishu.cn/share/base/form/shrcnRxj3utrzjywsom96Px4sud),我们会尽快与您联系。
## QA
1. 如何交付?
完整版应用 = 开源版镜像 + 商业版镜像
我们会提供一个商业版镜像给你使用,该镜像需要一个 license 启动license 有效期为 1 年。此外,还会提供一个简单的后台管理系统(目前只设置了简单的查询功能)
2. 二次开发如何操作?
可自行修改开源版代码进行二次开发,不支持修改商业版镜像。

View File

@@ -0,0 +1,8 @@
---
weight: 1100
title: '社区'
description: '社区相关内容'
icon: 'forum'
draft: false
images: []
---

View File

@@ -0,0 +1,24 @@
---
title: '开源协议'
description: ' FastGPT 开源许可证'
icon: 'verified_user'
draft: false
toc: true
weight: 1120
---
FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件:
+ FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可:
+ 多租户 SaaS 服务:除非获得 FastGPT 的明确书面授权,否则不得使用 fastgpt.run 的源码来运营与 fastgpt.run 服务版类似的多租户 SaaS 服务。
+ LOGO 及版权信息:在使用 FastGPT 的过程中,不得移除或修改 FastGPT 控制台内的 LOGO 或版权信息。
请通过电子邮件 yujinlong@sealos.io 联系我们咨询许可事宜。
+ 作为贡献者,你必须同意将你贡献的代码用于以下用途:
+ 生产者有权将开源协议调整为更严格或更宽松的形式。
+ 可用于商业目的,例如 FastGPT 的云服务。
除此之外,所有其他权利和限制均遵循 Apache License 2.0。如果你需要更多详细信息,可以参考 Apache License 2.0 的完整版本。本产品的交互设计受到外观专利保护。© 2023 Sealos.

View File

@@ -0,0 +1,16 @@
---
title: '加入社区'
description: ' 加入 FastGPT 开发者社区和我们一起成长'
icon: 'forum'
draft: false
toc: true
weight: 1110
---
FastGPT 是一个由用户和贡献者参与推动的开源项目,如果您对产品使用存在疑问和建议,可尝试以下方式寻求支持。我们的团队与社区会竭尽所能为您提供帮助。
+ 📱 扫码加入社区微信交流群👇
<img width="400px" src="/wechat-fastgpt.webp" />
+ 🐞 请将任何 FastGPT 的 Bug、问题和需求提交到 [GitHub Issue](https://github.com/labring/fastgpt/issues/new/choose)。

View File

@@ -1,5 +1,5 @@
---
weight: 800
weight: 900
title: '本地模型使用'
description: 'FastGPT 对接本地模型'
icon: 'model_training'

View File

@@ -4,21 +4,26 @@ description: ' 将 FastGPT 接入私有化模型 ChatGLM2和m3e-large'
icon: 'model_training'
draft: false
toc: true
weight: 200
weight: 930
---
## 前言
FastGPT 默认使用了 openai 的 LLM 模型和向量模型,如果想要私有化部署的话,可以使用 ChatGLM2 和 m3e-large 模型。以下是由用户@不做了睡大觉 提供的接入方法。该镜像直接集成了 M3E-Large 和 ChatGLM2-6B 模型,可以直接使用。
FastGPT 默认使用了 OpenAI 的 LLM 模型和向量模型,如果想要私有化部署的话,可以使用 ChatGLM2 和 m3e-large 模型。以下是由用户@不做了睡大觉 提供的接入方法。该镜像直接集成了 M3E-Large 和 ChatGLM2-6B 模型,可以直接使用。
## 部署镜像
镜像名: `stawky/chatglm2-m3e:latest`
国内镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt/chatglm2-m3e:latest`
端口号: 6006
镜像默认 sk-key: `sk-aaabbbcccdddeeefffggghhhiiijjjkkk`
+ 镜像名: `stawky/chatglm2-m3e:latest`
+ 国内镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt_docker/chatglm2-m3e:latest`
+ 端口号: 6006
## 接入 OneAPI
```
# 设置安全凭证即oneapi中的渠道密钥
默认值sk-aaabbbcccdddeeefffggghhhiiijjjkkk
也可以通过环境变量引入sk-key。有关docker环境变量引入的方法请自寻教程此处不再赘述。
```
## 接入 [One API](/docs/installation/one-api/)
为 chatglm2 和 m3e-large 各添加一个渠道,参数如下:
@@ -50,26 +55,25 @@ curl --location --request POST 'https://domain/v1/chat/completions' \
}'
```
Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 OneAPI 填写的自定义模型。
Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One API 填写的自定义模型。
## 接入 FastGPT
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 和 M3E 模型:
```json
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"VectorModels": [
{
"model": "text-embedding-ada-002",
@@ -94,20 +98,20 @@ M3E 模型的使用方法如下:
1. 创建知识库时候选择 M3E 模型。
注意,一旦选择后,知识库将无法修改向量模型。
![](/imgs/model-m3e2.png)
注意,一旦选择后,知识库将无法修改向量模型。
![](/imgs/model-m3e2.png)
2. 导入数据
3. 搜索测试
![](/imgs/model-m3e3.png)
![](/imgs/model-m3e3.png)
4. 应用绑定知识库
注意,应用只能绑定同一个向量模型的知识库,不能跨模型绑定。并且,需要注意调整相似度,不同向量模型的相似度(距离)会有所区别,需要自行测试实验。
![](/imgs/model-m3e4.png)
注意,应用只能绑定同一个向量模型的知识库,不能跨模型绑定。并且,需要注意调整相似度,不同向量模型的相似度(距离)会有所区别,需要自行测试实验。
![](/imgs/model-m3e4.png)
chatglm2 模型的使用方法如下:
模型选择 chatglm2 即可

View File

@@ -4,7 +4,7 @@ description: ' 将 FastGPT 接入私有化模型 ChatGLM2-6B'
icon: 'model_training'
draft: false
toc: true
weight: 100
weight: 910
---
## 前言
@@ -27,7 +27,7 @@ ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,
因此推荐配置如下:
{{< table "table-hover table-striped" >}}
{{< table "table-hover table-striped-columns" >}}
| 类型 | 内存 | 显存 | 硬盘空间 | 启动命令 |
|------|---------|---------|----------|--------------------------|
| fp16 | >=16GB | >=16GB | >=25GB | python openai_api.py 16 |
@@ -48,8 +48,8 @@ ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,
1. 根据上面的环境配置配置好环境,具体教程自行 GPT
2. 下载 [python 文件](https://github.com/labring/FastGPT/blob/main/files/models/ChatGLM2/openai_api.py)
3. 在命令行输入命令 `pip install -r requirments.txt`
4. 打开你需要启动的 py 文件,在代码的第 76 行配置 token这里的 token 只是加一层验证,防止接口被人盗用;
5. 执行命令 `python openai_api.py 16`。这里的数字根据上面的配置进行选择。
4. 打开你需要启动的 py 文件,在代码的 `verify_token` 方法中配置 token这里的 token 只是加一层验证,防止接口被人盗用;
5. 执行命令 `python openai_api.py --model_name 16`。这里的数字根据上面的配置进行选择。
然后等待模型下载,直到模型加载完毕为止。如果出现报错先问 GPT。
@@ -63,12 +63,17 @@ ChatGLM2-6B 是开源中英双语对话模型 ChatGLM-6B 的第二代版本,
**镜像和端口**
镜像名: `stawky/chatglm2:latest`
国内镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt/chatglm2:latest`
端口号: 6006
镜像默认 sk-key: `sk-aaabbbcccdddeeefffggghhhiiijjjkkk`
+ 镜像名: `stawky/chatglm2:latest`
+ 国内镜像名: `registry.cn-hangzhou.aliyuncs.com/fastgpt_docker/chatglm2:latest`
+ 端口号: 6006
## 接入 OneAPI
```
# 设置安全凭证即oneapi中的渠道密钥
默认值sk-aaabbbcccdddeeefffggghhhiiijjjkkk
也可以通过环境变量引入sk-key。有关docker环境变量引入的方法请自寻教程此处不再赘述。
```
## 接入 One API
为 chatglm2 添加一个渠道,参数如下:
@@ -90,28 +95,29 @@ curl --location --request POST 'https://domain/v1/chat/completions' \
}'
```
Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 OneAPI 填写的自定义模型。
Authorization 为 sk-aaabbbcccdddeeefffggghhhiiijjjkkk。model 为刚刚在 One API 填写的自定义模型。
## 接入 FastGPT
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 和 M3E 模型:
修改 config.json 配置文件,在 VectorModels 中加入 chatglm2 模型:
```json
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
],
"ChatModels": [
//已有模型
{
"model": "chatglm2",
"name": "chatglm2",
"contextMaxToken": 8000,
"quoteMaxToken": 4000,
"maxTemperature": 1.2,
"price": 0,
"defaultSystem": ""
}
]
```
## 测试使用
chatglm2 模型的使用方法如下:
模型选择 chatglm2 即可

View File

@@ -4,7 +4,7 @@ description: ' 将 FastGPT 接入私有化模型 M3E'
icon: 'model_training'
draft: false
toc: true
weight: 100
weight: 920
---
## 前言
@@ -14,10 +14,17 @@ FastGPT 默认使用了 openai 的 embedding 向量模型,如果你想私有
## 部署镜像
镜像名: `stawky/m3e-large-api:latest`
国内镜像: `registry.cn-guangzhou.aliyuncs.com/kbgpt/m3e-large-api:latest`
国内镜像: `registry.cn-hangzhou.aliyuncs.com/fastgpt_docker/m3e-large-api:latest`
端口号: 6008
环境变量:
## 接入 OneAPI
```
# 设置安全凭证即oneapi中的渠道密钥
默认值sk-aaabbbcccdddeeefffggghhhiiijjjkkk
也可以通过环境变量引入sk-key。有关docker环境变量引入的方法请自寻教程此处不再赘述。
```
## 接入 One API
添加一个渠道,参数如下:
@@ -29,7 +36,7 @@ curl 例子:
```bash
curl --location --request POST 'https://domain/v1/embeddings' \
--header 'Authorization: Bearer sk-key' \
--header 'Authorization: Bearer xxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "m3e",
@@ -37,7 +44,7 @@ curl --location --request POST 'https://domain/v1/embeddings' \
}'
```
Authorization 为 sk-key。model 为刚刚在 OneAPI 填写的自定义模型。
Authorization 为 sk-key。model 为刚刚在 One API 填写的自定义模型。
## 接入 FastGPT
@@ -59,24 +66,24 @@ Authorization 为 sk-key。model 为刚刚在 OneAPI 填写的自定义模型。
"defaultToken": 500,
"maxToken": 1800
}
],
]
```
## 测试使用
1. 创建知识库时候选择 M3E 模型。
注意,一旦选择后,知识库将无法修改向量模型。
注意,一旦选择后,知识库将无法修改向量模型。
![](/imgs/model-m3e2.png)
![](/imgs/model-m3e2.png)
2. 导入数据
3. 搜索测试
![](/imgs/model-m3e3.png)
![](/imgs/model-m3e3.png)
4. 应用绑定知识库
注意,应用只能绑定同一个向量模型的知识库,不能跨模型绑定。并且,需要注意调整相似度,不同向量模型的相似度(距离)会有所区别,需要自行测试实验。
注意,应用只能绑定同一个向量模型的知识库,不能跨模型绑定。并且,需要注意调整相似度,不同向量模型的相似度(距离)会有所区别,需要自行测试实验。
![](/imgs/model-m3e4.png)
![](/imgs/model-m3e4.png)

View File

@@ -1,5 +1,5 @@
---
weight: 200
weight: 500
title: '开发指南'
description: '本地开发 FastGPT 必看'
icon: 'code_blocks'

View File

@@ -4,67 +4,27 @@ description: 'FastGPT 配置参数介绍'
icon: 'settings'
draft: false
toc: true
weight: 100
weight: 520
---
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `client/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/installation/docker/) 来挂载配置文件。
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `projects/app/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/installation/docker/) 来挂载配置文件。
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
这个配置文件中包含了前端页面定制、系统级参数、AI 对话的模型等……
这个配置文件中包含了系统级参数、AI 对话的模型、function 模型等……
{{% alert context="warning" %}}
注意:下面的配置介绍仅是局部介绍,你需要完整挂载整个 `config.json`,不能仅挂载一部分。你可以直接在默认的 config.json 基础上根据下面的介绍进行修改。挂载上去的配置文件不能包含注释。
{{% /alert %}}
## 基础字段粗略说明
这里介绍一些基础的配置字段:
```json
// 这个配置会控制前端的一些样式
"FeConfig": {
"show_emptyChat": true, // 对话页面,空内容时,是否展示介绍页
"show_register": false, // 是否展示注册按键(包括忘记密码,注册账号和三方登录)
"show_appStore": false, // 是否展示应用市场(不过目前权限还没做好,放开也没用)
"show_userDetail": false, // 是否展示用户详情账号余额、OpenAI 绑定)
"show_git": true, // 是否展示 Git
"systemTitle": "FastGPT", // 系统的 title
"authorText": "Made by FastGPT Team.", // 签名
"gitLoginKey": "" // Git 登录凭证
},
...
...
// 这个配置文件是系统级参数
"SystemParams": {
"vectorMaxProcess": 15, // 向量生成最大进程,结合数据库性能和 key 来设置
"qaMaxProcess": 15, // QA 生成最大进程,结合数据库性能和 key 来设置
"pgIvfflatProbe": 20 // pg vector 搜索探针。没有设置索引前可忽略,通常 50w 组以上才需要设置。
},
...
```
## 完整配置参数
**使用时,请务必去除注释!**
```json
{
"FeConfig": {
"show_emptyChat": true,
"show_register": false,
"show_appStore": false,
"show_userDetail": false,
"show_git": true,
"systemTitle": "FastGPT",
"authorText": "Made by FastGPT Team.",
"gitLoginKey": "",
"scripts": []
},
"SystemParams": {
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgIvfflatProbe": 20
"vectorMaxProcess": 15, // 向量生成最大进程,结合数据库性能和 key 来设置
"qaMaxProcess": 15, // QA 生成最大进程,结合数据库性能和 key 来设置
"pgIvfflatProbe": 20 // pg vector 搜索探针。没有设置索引前可忽略,通常 50w 组以上才需要设置。
},
"plugins": {},
"ChatModels": [
{
"model": "gpt-3.5-turbo",
@@ -94,12 +54,6 @@ weight: 100
"defaultSystem": ""
}
],
"QAModel": {
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
},
"VectorModels": [
{
"model": "text-embedding-ada-002",
@@ -108,6 +62,28 @@ weight: 100
"defaultToken": 500,
"maxToken": 3000
}
]
],
"QAModel": { // QA 拆分模型
"model": "gpt-3.5-turbo-16k",
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0
},
"ExtractModel": { // 内容提取模型
"model": "gpt-3.5-turbo-16k",
"functionCall": true, // 是否使用 functionCall
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0,
"prompt": ""
},
"CQModel": { // 问题分类模型
"model": "gpt-3.5-turbo-16k",
"functionCall": true,
"name": "GPT35-16k",
"maxToken": 16000,
"price": 0,
"prompt": ""
}
}
```

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