Compare commits

...

38 Commits

Author SHA1 Message Date
Archer
e6efd3318d perf: long org name ui (#4347)
* sync collection

* remove lock

* perf: long org name ui
2025-03-26 22:22:20 +08:00
Finley Ge
95ffd710aa pref: member list (#4344)
* chore: search member new api

* chore: permission

* fix: ts error

* fix: member modal
2025-03-26 22:10:03 +08:00
Archer
097bb97417 perf: intro wrap (#4346)
* sync collection

* remove lock

* perf: intro wrap
2025-03-26 21:38:52 +08:00
Finley Ge
4faea8d2b8 fix: group (#4330) 2025-03-26 11:48:58 +08:00
Archer
7ecadb33d1 fix: ts (#4325)
* sync collection

* remove lock

* fix: ts
2025-03-26 10:48:33 +08:00
Archer
ce61bda223 perf: member group (#4324)
* sync collection

* remove lock

* perf: member group
2025-03-26 10:48:32 +08:00
Finley Ge
8dba01da73 fix: member list, login button (#4322) 2025-03-26 10:48:32 +08:00
Finley Ge
dcdad6fa39 pref: member/group/org (#4316)
* feat: change group owner api

* pref: member/org/group

* fix: member modal select clb

* fix: search member when change owner
2025-03-26 10:48:32 +08:00
Archer
11d080d521 update search filter code (#4317)
* sync collection

* remove lock

* update search filter code
2025-03-26 10:48:32 +08:00
Archer
2f954d2f3f perf: text splitter (#4313)
* sync collection

* remove lock

* perf: text splitter

* update comment
2025-03-26 10:48:31 +08:00
Archer
a956fbca73 tmp org api rewrite (#4304)
* sync collection

* remove lock

* tmp org api rewrite
2025-03-26 10:48:31 +08:00
Finley Ge
db7510c5eb pref: member/org/gourp list (#4295)
* refactor: org api

* refactor: org api

* pref: member/org/group list

* feat: change group owner api

* fix: manage org member

* pref: member search
2025-03-26 10:48:31 +08:00
Archer
b87cc353da perf: ai proxy log remove retry log;perf: workflow type auto parse;add chunk spliter test (#4296)
* sync collection

* remove lock

* perf: workflow type auto parse

* add chunk spliter test

* perf: ai proxy log remove retry log

* udpate ai proxy field
2025-03-26 10:48:31 +08:00
Archer
ff85121546 add model test log (#4272)
* sync collection

* remove lock

* add model test log

* update ui

* update log

* fix: channel test

* preview chunk ui

* test model ux

* test model log

* perf: dataset selector

* fix: system plugin auth

* update nextjs
2025-03-26 10:48:30 +08:00
heheer
79f9d83349 fix input form label overflow (#4266) 2025-03-26 10:48:30 +08:00
Archer
159bf17369 feat: chunk index independent config (#4271)
* sync collection

* remove lock

* feat: chunk index independent config

* feat: add max chunksize to split chunk function

* remove log

* update doc

* remove

* remove log
2025-03-26 10:48:29 +08:00
Finley Ge
4512b23d4d fix: member count (#4269) 2025-03-26 10:48:29 +08:00
Archer
5300ddf654 perf: ai proxy (#4265)
* sync collection

* remove lock

* perf: ai proxy
2025-03-26 10:48:29 +08:00
Archer
f1f0dfc691 update prompt version (#4242)
* sync collection

* remove lock

* update prompt version
2025-03-26 10:48:28 +08:00
heheer
e5acec8dc7 feat: node prompt version (#4141)
* feat: node prompt version

* fix

* delete unused code

* fix

* fix code
2025-03-26 10:48:28 +08:00
heheer
cb832b6305 fix variable sync & popover button height (#4227)
* fix variable sync & popover button height

* required
2025-03-26 10:48:28 +08:00
Finley Ge
ae9b8a2b8e Sso (#4235)
* feat: redirect url can be inner url (#4138)

* fix: update new user sync api (#4145)

* feat: post all params to backend (#4151)

* pref: sso getauthurl api (#4172)

* pref: sso getauthurl api

* pref: sso

* solve the rootorglist (#4234)

---------

Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com>
2025-03-26 10:48:27 +08:00
Archer
d209255015 fix ts (#4239)
* sync collection

* remove lock

* fix ts

* fix: ts
2025-03-26 10:48:27 +08:00
Archer
6eae841e4a feat: sync api collection will refresh title;perf: invite link ux (#4237)
* update queue

* feat: sync api collection will refresh title

* sync collection

* remove lock

* perf: invite link ux
2025-03-26 10:48:26 +08:00
Finley Ge
75c1631670 fix: invite link (#4229)
* fix: invite link

* feat: create invite link and copy it directly
2025-03-26 10:48:26 +08:00
Archer
97a182c7fd perf: custom varialbe (#4225) 2025-03-26 10:48:26 +08:00
heheer
a0ad450032 add external variable debug (#4204)
* add external variable debug

* fix ui

* plugin variables
2025-03-26 10:48:25 +08:00
Archer
74b36219e1 feat: custom dataset split sign (#4221)
* feat: custom dataset split sign

* feat: custom dataset split sign
2025-03-26 10:48:23 +08:00
LGiki
fc23db745c fixed: permission error when viewing database search node responses (#4308)
Fix issue where API-called workflows with database search nodes would show
"无权操作该数据集" when trying to view full responses in conversation logs.
2025-03-26 10:47:39 +08:00
ROKY
8a68de6471 add pdf-mineru (#4276)
* add pdf-mineru

添加了基于MinerU的PDF转Markdown接口服务,调用方式与pdf-marker一致,开箱即用。

* Rename Readme.md to README.md

* Rename pdf_parser_mineru.py to main.py
2025-03-24 17:17:08 +08:00
dreamer6680
1c4e0c66d5 Ollama接入文档 (#4294)
* Add files via upload

* Add files via upload

* Update ollama.md

* Update ollama.md

* Add files via upload
2025-03-24 15:05:56 +08:00
dependabot[bot]
6dcdd540b9 chore(deps): bump python-multipart in /plugins/model/pdf-mistral (#4290)
Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.17 to 0.0.18.
- [Release notes](https://github.com/Kludex/python-multipart/releases)
- [Changelog](https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Kludex/python-multipart/compare/0.0.17...0.0.18)

---
updated-dependencies:
- dependency-name: python-multipart
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 13:44:18 +08:00
lzs2000131
48233c7d55 add mistral-ocr support as a plugin like pdf-marker (#4284)
Co-authored-by: zhengshuai.li <zhengshuai.li@cloudpense.com>
2025-03-24 10:32:06 +08:00
dependabot[bot]
f3ef56998d chore(deps): bump transformers in /plugins/model/llm-ChatGLM2 (#4288)
Bumps [transformers](https://github.com/huggingface/transformers) from 4.31.0 to 4.48.0.
- [Release notes](https://github.com/huggingface/transformers/releases)
- [Commits](https://github.com/huggingface/transformers/compare/v4.31.0...v4.48.0)

---
updated-dependencies:
- dependency-name: transformers
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 10:28:58 +08:00
dependabot[bot]
7e7269b2ba chore(deps): bump transformers in /plugins/model/llm-Baichuan2 (#4281)
Bumps [transformers](https://github.com/huggingface/transformers) from 4.30.2 to 4.48.0.
- [Release notes](https://github.com/huggingface/transformers/releases)
- [Commits](https://github.com/huggingface/transformers/compare/v4.30.2...v4.48.0)

---
updated-dependencies:
- dependency-name: transformers
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 10:26:54 +08:00
dependabot[bot]
606e9505c0 chore(deps): bump torch in /plugins/model/llm-ChatGLM2 (#4282)
Bumps [torch](https://github.com/pytorch/pytorch) from 2.0.1 to 2.4.0.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.0.1...v2.4.0)

---
updated-dependencies:
- dependency-name: torch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 10:26:46 +08:00
Archer
1db39e8907 4.9.1 fix dataset (#4279) (#4280)
* fix dataset search node reference save

* rewrite

---------

Co-authored-by: heheer <1239331448@qq.com>
2025-03-22 00:26:31 +08:00
Archer
7f13eb4642 update doc (#4256) 2025-03-21 09:58:29 +08:00
210 changed files with 5511 additions and 2373 deletions

View File

@@ -114,15 +114,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1-fix2 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1-fix2 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -72,15 +72,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1-fix2 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1-fix2 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -53,15 +53,15 @@ services:
wait $$!
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.1-fix2 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.1-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.1-fix2 # 阿里云
ports:
- 3000:3000
networks:

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,184 @@
---
title: '使用 Ollama 接入本地模型 '
description: ' 采用 Ollama 部署自己的模型'
icon: 'api'
draft: false
toc: true
weight: 950
---
[Ollama](https://ollama.com/)是一个开源的AI大模型部署工具专注于简化大语言模型的部署和使用支持一键下载和运行各种大模型。
## 安装 Ollama
Ollama 本身支持多种安装方式,但是推荐使用 Docker 拉取镜像部署。如果是个人设备上安装了 Ollama 后续需要解决如何让 Docker 中 FastGPT 容器访问宿主机 Ollama的问题较为麻烦。
### Docker 安装(推荐)
你可以使用 Ollama 官方的 Docker 镜像来一键安装和启动 Ollama 服务(确保你的机器上已经安装了 Docker命令如下
```bash
docker pull ollama/ollama
docker run --rm -d --name ollama -p 11434:11434 ollama/ollama
```
如果你的 FastGPT 是在 Docker 中进行部署的,建议在拉取 Ollama 镜像时保证和 FastGPT 镜像处于同一网络,否则可能出现 FastGPT 无法访问的问题,命令如下:
```bash
docker run --rm -d --name ollama --network (你的 Fastgpt 容器所在网络) -p 11434:11434 ollama/ollama
```
### 主机安装
如果你不想使用 Docker ,也可以采用主机安装,以下是主机安装的一些方式。
#### MacOS
如果你使用的是 macOS且系统中已经安装了 Homebrew 包管理器,可通过以下命令来安装 Ollama
```bash
brew install ollama
ollama serve #安装完成后,使用该命令启动服务
```
#### Linux
在 Linux 系统上,你可以借助包管理器来安装 Ollama。以 Ubuntu 为例,在终端执行以下命令:
```bash
curl https://ollama.com/install.sh | sh #此命令会从官方网站下载并执行安装脚本。
ollama serve #安装完成后,同样启动服务
```
#### Windows
在 Windows 系统中,你可以从 Ollama 官方网站 下载 Windows 版本的安装程序。下载完成后,运行安装程序,按照安装向导的提示完成安装。安装完成后,在命令提示符或 PowerShell 中启动服务:
```bash
ollama serve #安装完成并启动服务后,你可以在浏览器中访问 http://localhost:11434 来验证 Ollama 是否安装成功。
```
#### 补充说明
如果你是采用的主机应用 Ollama 而不是镜像,需要确保你的 Ollama 可以监听0.0.0.0。
##### 1. Linxu 系统
如果 Ollama 作为 systemd 服务运行,打开终端,编辑 Ollama 的 systemd 服务文件使用命令sudo systemctl edit ollama.service在[Service]部分添加Environment="OLLAMA_HOST=0.0.0.0"。保存并退出编辑器然后执行sudo systemctl daemon - reload和sudo systemctl restart ollama使配置生效。
##### 2. MacOS 系统
打开终端使用launchctl setenv ollama_host "0.0.0.0"命令设置环境变量,然后重启 Ollama 应用程序以使更改生效。
##### 3. Windows 系统
通过 “开始” 菜单或搜索栏打开 “编辑系统环境变量”,在 “系统属性” 窗口中点击 “环境变量”,在 “系统变量” 部分点击 “新建”创建一个名为OLLAMA_HOST的变量变量值设置为0.0.0.0,点击 “确定” 保存更改,最后从 “开始” 菜单重启 Ollama 应用程序。
### Ollama 拉取模型镜像
在安装后 Ollama 后,本地是没有模型镜像的,需要自己去拉取 Ollama 中的模型镜像。命令如下:
```bash
# Docker 部署需要先进容器,命令为: docker exec -it < Ollama 容器名 > /bin/sh
ollama pull <模型名>
```
![](/imgs/Ollama-pull.png)
### 测试通信
在安装完成后,需要进行检测测试,首先进入 FastGPT 所在的容器,尝试访问自己的 Ollama ,命令如下:
```bash
docker exec -it < FastGPT 所在的容器名 > /bin/sh
curl http://XXX.XXX.XXX.XXX:11434 #容器部署地址为“http://<容器名>:<端口>”,主机安装地址为"http://<主机IP>:<端口>"主机IP不可为localhost
```
看到访问显示自己的 Ollama 服务以及启动,说明可以正常通信。
## 将 Ollama 接入 FastGPT
### 1. 查看 Ollama 所拥有的模型
首先采用下述命令查看 Ollama 中所拥有的模型,
```bash
# Docker 部署 Ollama需要此命令 docker exec -it < Ollama 容器名 > /bin/sh
ollama ls
```
![](/imgs/Ollama-models1.png)
### 2. AI Proxy 接入
如果你采用的是 FastGPT 中的默认配置文件部署[这里](/docs/development/docker.md),即默认采用 AI Proxy 进行启动。
![](/imgs/Ollama-aiproxy1.png)
以及在确保你的 FastGPT 可以直接访问 Ollama 容器的情况下,无法访问,参考上文[点此跳转](#安装-ollama)的安装过程检测是不是主机不能监测0.0.0.0,或者容器不在同一个网络。
![](/imgs/Ollama-aiproxy2.png)
在 FastGPT 中点击账号->模型提供商->模型配置->新增模型添加自己的模型即可添加模型时需要保证模型ID和 OneAPI 中的模型名称一致。详细参考[这里](/docs/development/modelConfig/intro.md)
![](/imgs/Ollama-models2.png)
![](/imgs/Ollama-models3.png)
运行 FastGPT ,在页面中选择账号->模型提供商->模型渠道->新增渠道。之后,在渠道选择中选择 Ollama ,然后加入自己拉取的模型,填入代理地址,如果是容器中安装 Ollama 代理地址为http://地址:端口补充容器部署地址为“http://<容器名>:<端口>”,主机安装地址为"http://<主机IP>:<端口>"主机IP不可为localhost
![](/imgs/Ollama-aiproxy3.png)
在工作台中创建一个应用,选择自己之前添加的模型,此处模型名称为自己当时设置的别名。注:同一个模型无法多次添加,系统会采取最新添加时设置的别名。
![](/imgs/Ollama-models4.png)
### 3. OneAPI 接入
如果你想使用 OneAPI ,首先需要拉取 OneAPI 镜像,然后将其在 FastGPT 容器的网络中运行。具体命令如下:
```bash
# 拉取 oneAPI 镜像
docker pull intel/oneapi-hpckit
# 运行容器并指定自定义网络和容器名
docker run -it --network < FastGPT 网络 > --name 容器名 intel/oneapi-hpckit /bin/bash
```
进入 OneAPI 页面,添加新的渠道,类型选择 Ollama ,在模型中填入自己 Ollama 中的模型,需要保证添加的模型名称和 Ollama 中一致,再在下方填入自己的 Ollama 代理地址默认http://地址:端口,不需要填写/v1。添加成功后在 OneAPI 进行渠道测试,测试成功则说明添加成功。此处演示采用的是 Docker 部署 Ollama 的效果,主机 Ollama需要修改代理地址为http://<主机IP>:<端口>
![](/imgs/Ollama-oneapi1.png)
渠道添加成功后,点击令牌,点击添加令牌,填写名称,修改配置。
![](/imgs/Ollama-oneapi2.png)
修改部署 FastGPT 的 docker-compose.yml 文件,在其中将 AI Proxy 的使用注释,在 OPENAI_BASE_URL 中加入自己的 OneAPI 开放地址默认是http://地址:端口/v1v1必须填写。KEY 中填写自己在 OneAPI 的令牌。
![](/imgs/Ollama-oneapi3.png)
[直接跳转5](#5-模型添加和使用)添加模型,并使用。
### 4. 直接接入
如果你既不想使用 AI Proxy也不想使用 OneAPI也可以选择直接接入修改部署 FastGPT 的 docker-compose.yml 文件,在其中将 AI Proxy 的使用注释,采用和 OneAPI 的类似配置。注释掉 AIProxy 相关代码在OPENAI_BASE_URL中加入自己的 Ollama 开放地址默认是http://地址:端口/v1强调:v1必须填写。在KEY中随便填入因为 Ollama 默认没有鉴权,如果开启鉴权,请自行填写。其他操作和在 OneAPI 中加入 Ollama 一致,只需在 FastGPT 中加入自己的模型即可使用。此处演示采用的是 Docker 部署 Ollama 的效果,主机 Ollama需要修改代理地址为http://<主机IP>:<端口>
![](/imgs/Ollama-direct1.png)
完成后[点击这里](#5-模型添加和使用)进行模型添加并使用。
### 5. 模型添加和使用
在 FastGPT 中点击账号->模型提供商->模型配置->新增模型添加自己的模型即可添加模型时需要保证模型ID和 OneAPI 中的模型名称一致。
![](/imgs/Ollama-models2.png)
![](/imgs/Ollama-models3.png)
在工作台中创建一个应用,选择自己之前添加的模型,此处模型名称为自己当时设置的别名。注:同一个模型无法多次添加,系统会采取最新添加时设置的别名。
![](/imgs/Ollama-models4.png)
### 6. 补充
上述接入 Ollama 的代理地址中,主机安装 Ollama 的地址为“http://<主机IP>:<端口>”,容器部署 Ollama 地址为“http://<容器名>:<端口>”

View File

@@ -11,8 +11,6 @@ weight: 853
| --------------------- | --------------------- |
| ![](/imgs/getDatasetId.jpg) | ![](/imgs/getfile_id.webp) |
## 创建训练订单
{{< tabs tabTotal="2" >}}
@@ -289,7 +287,7 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/delete?
## 集合
### 通用创建参数说明
### 通用创建参数说明(必看)
**入参**
@@ -300,8 +298,11 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/delete?
| trainingType | 数据处理方式。chunk: 按文本长度进行分割;qa: 问答对提取 | ✅ |
| autoIndexes | 是否自动生成索引(仅商业版支持) | |
| imageIndex | 是否自动生成图片索引(仅商业版支持) | |
| chunkSize | 预估块大小 | |
| chunkSplitter | 自定义最高优先分割符号 | |
| chunkSettingMode | 分块参数模式。auto: 系统默认参数; custom: 手动指定参数 | |
| chunkSplitMode | 分块拆分模式。size: 按长度拆分; char: 按字符拆分。chunkSettingMode=auto时不生效。 | |
| chunkSize | 分块大小,默认 1500。chunkSettingMode=auto时不生效。 | |
| indexSize | 索引大小,默认 512必须小于索引模型最大token。chunkSettingMode=auto时不生效。 | |
| chunkSplitter | 自定义最高优先分割符号除非超出文件处理最大上下文否则不会进行进一步拆分。chunkSettingMode=auto时不生效。 | |
| qaPrompt | qa拆分提示词 | |
| tags | 集合标签(字符串数组) | |
| createTime | 文件创建时间Date / String | |
@@ -389,9 +390,8 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
"name":"测试训练",
"trainingType": "qa",
"chunkSize":8000,
"chunkSplitter":"",
"qaPrompt":"11",
"chunkSettingMode": "auto",
"qaPrompt":"",
"metadata":{}
}'
@@ -409,10 +409,6 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
- parentId 父级ID不填则默认为根目录
- name: 集合名称(必填)
- metadata 元数据(暂时没啥用)
- trainingType: 训练模式(必填)
- chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token16k模型通常建议不超过10000
- chunkSplitter: 自定义最高优先分割符号(可选)
- qaPrompt: qa拆分自定义提示词可选
{{% /alert %}}
{{< /markdownify >}}
@@ -462,8 +458,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
"parentId": null,
"trainingType": "chunk",
"chunkSize":512,
"chunkSplitter":"",
"chunkSettingMode": "auto",
"qaPrompt":"",
"metadata":{
@@ -483,10 +478,6 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
- datasetId: 知识库的ID(必填)
- parentId 父级ID不填则默认为根目录
- metadata.webPageSelector: 网页选择器,用于指定网页中的哪个元素作为文本(可选)
- trainingType:训练模式(必填)
- chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token16k模型通常建议不超过10000
- chunkSplitter: 自定义最高优先分割符号(可选)
- qaPrompt: qa拆分自定义提示词可选
{{% /alert %}}
{{< /markdownify >}}
@@ -545,13 +536,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
{{% alert icon=" " context="success" %}}
- file: 文件
- data: 知识库相关信息json序列化后传入
- datasetId: 知识库的ID(必填)
- parentId 父级ID不填则默认为根目录
- trainingType:训练模式(必填)
- chunkSize: 每个 chunk 的长度(可选). chunk模式:100~3000; qa模式: 4000~模型最大token16k模型通常建议不超过10000
- chunkSplitter: 自定义最高优先分割符号(可选)
- qaPrompt: qa拆分自定义提示词可选
- data: 知识库相关信息json序列化后传入,参数说明见上方“通用创建参数说明”
{{% /alert %}}
{{< /markdownify >}}

View File

@@ -13,8 +13,8 @@ weight: 799
### 2. 更新镜像
- 更新 FastGPT 镜像 tag: v4.9.1
- 更新 FastGPT 商业版镜像 tag: v4.9.1
- 更新 FastGPT 镜像 tag: v4.9.1-fix2
- 更新 FastGPT 商业版镜像 tag: v4.9.1-fix2
- Sandbox 镜像,可以不更新
- AIProxy 镜像修改为: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.1.3

View File

@@ -0,0 +1,44 @@
---
title: 'V4.9.2(进行中)'
description: 'FastGPT V4.9.2 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 799
---
## 重要提示
- 知识库导入数据 API 变更,增加`chunkSettingMode`,`chunkSplitMode`,`indexSize`可选参数,具体可参考 [知识库导入数据 API](/docs/development/openapi/dataset) 文档。
## 🚀 新增内容
1. 知识库分块优化:支持单独配置分块大小和索引大小,允许进行超大分块,以更大的输入 Tokens 换取完整分块。
2. 知识库分块增加自定义分隔符预设值,同时支持自定义换行符分割。
3. 外部变量改名:自定义变量。 并且支持在测试时调试,在分享链接中,该变量直接隐藏。
4. 集合同步时,支持同步修改标题。
5. 团队成员管理重构,抽离主流 IM SSO企微、飞书、钉钉并支持通过自定义 SSO 接入 FastGPT。同时完善与外部系统的成员同步。
## ⚙️ 优化
1. 导出对话日志时,支持导出成员名。
2. 邀请链接交互。
3. 无 SSL 证书时复制失败,会提示弹窗用于手动复制。
4. FastGPT 未内置 ai proxy 渠道时,也能正常展示其名称。
5. 升级 nextjs 版本至 14.2.25。
6. 工作流节点数组字符串类型,自动适配 string 输入。
7. 工作流节点数组类型,自动进行 JSON parse 解析 string 输入。
8. AI proxy 日志优化,去除重试失败的日志,仅保留最后一份错误日志。
9. 分块算法小调整:
* 跨处理符号之间连续性更强。
* 代码块分割时,用 LLM 模型上下文作为分块大小,尽可能保证代码块完整性。
* 表格分割时,用 LLM 模型上下文作为分块大小,尽可能保证表格完整性。
## 🐛 修复
1. 飞书和语雀知识库无法同步。
2. 渠道测试时,如果配置了模型自定义请求地址,会走自定义请求地址,而不是渠道请求地址。
3. 语音识别模型测试未启用的模型时,无法正常测试。
4. 管理员配置系统插件时,如果插件包含其他系统应用,无法正常鉴权。
5. 移除 TTS 自定义请求地址时,必须需要填 requestAuth 字段。

View File

@@ -124,6 +124,7 @@ curl --location --request GET '{{baseURL}}/v1/file/content?id=xx' \
"success": true,
"message": "",
"data": {
"title": "文档标题",
"content": "FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!\n",
"previewUrl": "xxxx"
}
@@ -131,10 +132,13 @@ curl --location --request GET '{{baseURL}}/v1/file/content?id=xx' \
```
{{% alert icon=" " context="success" %}}
二选一返回,如果同时返回则 content 优先级更高。
- title - 文件标题。
- content - 文件内容,直接拿来用。
- previewUrl - 文件链接,系统会请求该地址获取文件内容。
`content``previewUrl`二选一返回,如果同时返回则 `content` 优先级更高,返回 `previewUrl`时,则会访问该链接进行文档内容读取。
{{% /alert %}}
{{< /markdownify >}}

View File

@@ -1,16 +1,17 @@
import { defaultMaxChunkSize } from '../../core/dataset/training/utils';
import { getErrText } from '../error/utils';
import { replaceRegChars } from './tools';
export const CUSTOM_SPLIT_SIGN = '-----CUSTOM_SPLIT_SIGN-----';
type SplitProps = {
text: string;
chunkLen: number;
chunkSize: number;
maxSize?: number;
overlapRatio?: number;
customReg?: string[];
};
export type TextSplitProps = Omit<SplitProps, 'text' | 'chunkLen'> & {
chunkLen?: number;
export type TextSplitProps = Omit<SplitProps, 'text' | 'chunkSize'> & {
chunkSize?: number;
};
type SplitResponse = {
@@ -56,7 +57,7 @@ const strIsMdTable = (str: string) => {
return true;
};
const markdownTableSplit = (props: SplitProps): SplitResponse => {
let { text = '', chunkLen } = props;
let { text = '', chunkSize } = props;
const splitText2Lines = text.split('\n');
const header = splitText2Lines[0];
const headerSize = header.split('|').length - 2;
@@ -72,7 +73,7 @@ ${mdSplitString}
`;
for (let i = 2; i < splitText2Lines.length; i++) {
if (chunk.length + splitText2Lines[i].length > chunkLen * 1.2) {
if (chunk.length + splitText2Lines[i].length > chunkSize * 1.2) {
chunks.push(chunk);
chunk = `${header}
${mdSplitString}
@@ -99,11 +100,17 @@ ${mdSplitString}
5. 标点分割:重叠
*/
const commonSplit = (props: SplitProps): SplitResponse => {
let { text = '', chunkLen, overlapRatio = 0.15, customReg = [] } = props;
let {
text = '',
chunkSize,
maxSize = defaultMaxChunkSize,
overlapRatio = 0.15,
customReg = []
} = props;
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
const overlapLen = Math.round(chunkLen * overlapRatio);
const overlapLen = Math.round(chunkSize * overlapRatio);
// replace code block all \n to codeBlockMarker
text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) {
@@ -115,34 +122,38 @@ const commonSplit = (props: SplitProps): SplitResponse => {
// The larger maxLen is, the next sentence is less likely to trigger splitting
const markdownIndex = 4;
const forbidOverlapIndex = 8;
const stepReges: { reg: RegExp; maxLen: number }[] = [
...customReg.map((text) => ({
reg: new RegExp(`(${replaceRegChars(text)})`, 'g'),
maxLen: chunkLen * 1.4
})),
{ reg: /^(#\s[^\n]+\n)/gm, maxLen: chunkLen * 1.2 },
{ reg: /^(##\s[^\n]+\n)/gm, maxLen: chunkLen * 1.4 },
{ reg: /^(###\s[^\n]+\n)/gm, maxLen: chunkLen * 1.6 },
{ reg: /^(####\s[^\n]+\n)/gm, maxLen: chunkLen * 1.8 },
{ reg: /^(#####\s[^\n]+\n)/gm, maxLen: chunkLen * 1.8 },
{ reg: /([\n]([`~]))/g, maxLen: chunkLen * 4 }, // code block
{ reg: /([\n](?=\s*[0-9]+\.))/g, maxLen: chunkLen * 2 }, // 增大块,尽可能保证它是一个完整的段落。 (?![\*\-|>`0-9]): markdown special char
{ reg: /(\n{2,})/g, maxLen: chunkLen * 1.6 },
{ reg: /([\n])/g, maxLen: chunkLen * 1.2 },
const stepReges: { reg: RegExp | string; maxLen: number }[] = [
...customReg.map((text) => ({
reg: text.replaceAll('\\n', '\n'),
maxLen: chunkSize
})),
{ reg: /^(#\s[^\n]+\n)/gm, maxLen: chunkSize },
{ reg: /^(##\s[^\n]+\n)/gm, maxLen: chunkSize },
{ reg: /^(###\s[^\n]+\n)/gm, maxLen: chunkSize },
{ reg: /^(####\s[^\n]+\n)/gm, maxLen: chunkSize },
{ reg: /^(#####\s[^\n]+\n)/gm, maxLen: chunkSize },
{ reg: /([\n](```[\s\S]*?```|~~~[\s\S]*?~~~))/g, maxLen: maxSize }, // code block
{
reg: /(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n)*)/g,
maxLen: maxSize
}, // Table 尽可能保证完整性
{ reg: /(\n{2,})/g, maxLen: chunkSize },
{ reg: /([\n])/g, maxLen: chunkSize },
// ------ There's no overlap on the top
{ reg: /([。]|([a-zA-Z])\.\s)/g, maxLen: chunkLen * 1.2 },
{ reg: /([]|!\s)/g, maxLen: chunkLen * 1.2 },
{ reg: /([]|\?\s)/g, maxLen: chunkLen * 1.4 },
{ reg: /([]|;\s)/g, maxLen: chunkLen * 1.6 },
{ reg: /([]|,\s)/g, maxLen: chunkLen * 2 }
{ reg: /([。]|([a-zA-Z])\.\s)/g, maxLen: chunkSize },
{ reg: /([]|!\s)/g, maxLen: chunkSize },
{ reg: /([]|\?\s)/g, maxLen: chunkSize },
{ reg: /([]|;\s)/g, maxLen: chunkSize },
{ reg: /([]|,\s)/g, maxLen: chunkSize }
];
const customRegLen = customReg.length;
const checkIsCustomStep = (step: number) => step < customRegLen;
const checkIsMarkdownSplit = (step: number) =>
step >= customRegLen && step <= markdownIndex + customRegLen;
+customReg.length;
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex + customRegLen;
// if use markdown title split, Separate record title
@@ -151,7 +162,8 @@ const commonSplit = (props: SplitProps): SplitResponse => {
return [
{
text,
title: ''
title: '',
chunkMaxSize: chunkSize
}
];
}
@@ -159,27 +171,46 @@ const commonSplit = (props: SplitProps): SplitResponse => {
const isCustomStep = checkIsCustomStep(step);
const isMarkdownSplit = checkIsMarkdownSplit(step);
const { reg } = stepReges[step];
const { reg, maxLen } = stepReges[step];
const splitTexts = text
.replace(
const replaceText = (() => {
if (typeof reg === 'string') {
let tmpText = text;
reg.split('|').forEach((itemReg) => {
tmpText = tmpText.replaceAll(
itemReg,
(() => {
if (isCustomStep) return splitMarker;
if (isMarkdownSplit) return `${splitMarker}$1`;
return `$1${splitMarker}`;
})()
);
});
return tmpText;
}
return text.replace(
reg,
(() => {
if (isCustomStep) return splitMarker;
if (isMarkdownSplit) return `${splitMarker}$1`;
return `$1${splitMarker}`;
})()
)
.split(`${splitMarker}`)
.filter((part) => part.trim());
);
})();
const splitTexts = replaceText.split(splitMarker).filter((part) => part.trim());
return splitTexts
.map((text) => {
const matchTitle = isMarkdownSplit ? text.match(reg)?.[0] || '' : '';
// 如果一个分块没有匹配到,则使用默认块大小,否则使用最大块大小
const chunkMaxSize = text.match(reg) === null ? chunkSize : maxLen;
return {
text: isMarkdownSplit ? text.replace(matchTitle, '') : text,
title: matchTitle
title: matchTitle,
chunkMaxSize
};
})
.filter((item) => !!item.title || !!item.text?.trim());
@@ -188,7 +219,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
/* Gets the overlap at the end of a text as the beginning of the next block */
const getOneTextOverlapText = ({ text, step }: { text: string; step: number }): string => {
const forbidOverlap = checkForbidOverlap(step);
const maxOverlapLen = chunkLen * 0.4;
const maxOverlapLen = chunkSize * 0.4;
// step >= stepReges.length: Do not overlap incomplete sentences
if (forbidOverlap || overlapLen === 0 || step >= stepReges.length) return '';
@@ -229,15 +260,15 @@ const commonSplit = (props: SplitProps): SplitResponse => {
const isCustomStep = checkIsCustomStep(step);
const forbidConcat = isCustomStep; // forbid=true时候lastText肯定为空
// oversize
// Over step
if (step >= stepReges.length) {
if (text.length < chunkLen * 3) {
if (text.length < maxSize) {
return [text];
}
// use slice-chunkLen to split text
// use slice-chunkSize to split text
const chunks: string[] = [];
for (let i = 0; i < text.length; i += chunkLen - overlapLen) {
chunks.push(text.slice(i, i + chunkLen));
for (let i = 0; i < text.length; i += chunkSize - overlapLen) {
chunks.push(text.slice(i, i + chunkSize));
}
return chunks;
}
@@ -245,19 +276,18 @@ const commonSplit = (props: SplitProps): SplitResponse => {
// split text by special char
const splitTexts = getSplitTexts({ text, step });
const maxLen = splitTexts.length > 1 ? stepReges[step].maxLen : chunkLen;
const minChunkLen = chunkLen * 0.7;
const chunks: string[] = [];
for (let i = 0; i < splitTexts.length; i++) {
const item = splitTexts[i];
const maxLen = item.chunkMaxSize; // 当前块最大长度
const lastTextLen = lastText.length;
const currentText = item.text;
const newText = lastText + currentText;
const newTextLen = newText.length;
// Markdown 模式下,会强制向下拆分最小块,并再最后一个标题时候,给小块都补充上所有标题(包含父级标题)
// Markdown 模式下,会强制向下拆分最小块,并再最后一个标题深度,给小块都补充上所有标题(包含父级标题)
if (isMarkdownStep) {
// split new Text, split chunks must will greater 1 (small lastText)
const innerChunks = splitTextRecursively({
@@ -267,11 +297,13 @@ const commonSplit = (props: SplitProps): SplitResponse => {
parentTitle: parentTitle + item.title
});
// 只有标题,没有内容。
if (innerChunks.length === 0) {
chunks.push(`${parentTitle}${item.title}`);
continue;
}
// 在合并最深级标题时,需要补充标题
chunks.push(
...innerChunks.map(
(chunk) =>
@@ -282,9 +314,18 @@ const commonSplit = (props: SplitProps): SplitResponse => {
continue;
}
// newText is too large(now, The lastText must be smaller than chunkLen)
// newText is too large(now, The lastText must be smaller than chunkSize)
if (newTextLen > maxLen) {
// lastText greater minChunkLen, direct push it to chunks, not add to next chunk. (large lastText)
const minChunkLen = maxLen * 0.8; // 当前块最小长度
const maxChunkLen = maxLen * 1.2; // 当前块最大长度
// 新文本没有非常大,直接认为它是一个新的块
if (newTextLen < maxChunkLen) {
chunks.push(newText);
lastText = getOneTextOverlapText({ text: newText, step }); // next chunk will start with overlayText
continue;
}
// 上一个文本块已经挺大的,单独做一个块
if (lastTextLen > minChunkLen) {
chunks.push(lastText);
@@ -294,13 +335,13 @@ const commonSplit = (props: SplitProps): SplitResponse => {
continue;
}
// 说明是新的文本比较大,需要进一步拆分
// 说明是当前文本比较大,需要进一步拆分
// split new Text, split chunks must will greater 1 (small lastText)
// 把新的文本块进行一个拆分,并追加到 latestText
const innerChunks = splitTextRecursively({
text: newText,
text: currentText,
step: step + 1,
lastText: '',
lastText,
parentTitle: parentTitle + item.title
});
const lastChunk = innerChunks[innerChunks.length - 1];
@@ -328,16 +369,16 @@ const commonSplit = (props: SplitProps): SplitResponse => {
// Not overlap
if (forbidConcat) {
chunks.push(item.text);
chunks.push(currentText);
continue;
}
lastText += item.text;
lastText = newText;
}
/* If the last chunk is independent, it needs to be push chunks. */
if (lastText && chunks[chunks.length - 1] && !chunks[chunks.length - 1].endsWith(lastText)) {
if (lastText.length < chunkLen * 0.4) {
if (lastText.length < chunkSize * 0.4) {
chunks[chunks.length - 1] = chunks[chunks.length - 1] + lastText;
} else {
chunks.push(lastText);
@@ -371,9 +412,9 @@ const commonSplit = (props: SplitProps): SplitResponse => {
/**
* text split into chunks
* chunkLen - one chunk len. max: 3500
* chunkSize - one chunk len. max: 3500
* overlapLen - The size of the before and after Text
* chunkLen > overlapLen
* chunkSize > overlapLen
* markdown
*/
export const splitText2Chunks = (props: SplitProps): SplitResponse => {

View File

@@ -56,7 +56,7 @@ export const replaceSensitiveText = (text: string) => {
};
/* Make sure the first letter is definitely lowercase */
export const getNanoid = (size = 12) => {
export const getNanoid = (size = 16) => {
const firstChar = customAlphabet('abcdefghijklmnopqrstuvwxyz', 1)();
if (size === 1) return firstChar;

View File

@@ -84,11 +84,6 @@ export type FastGPTFeConfigsType = {
github?: string;
google?: string;
wechat?: string;
dingtalk?: string;
wecom?: {
corpid?: string;
agentid?: string;
};
microsoft?: {
clientId?: string;
tenantId?: string;

View File

@@ -1,54 +1,70 @@
import { PromptTemplateItem } from '../type.d';
import { i18nT } from '../../../../web/i18n/utils';
import { getPromptByVersion } from './utils';
export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: i18nT('app:template.standard_template_des'),
value: `{
value: {
['4.9.2']: `{
"sourceName": "{{source}}",
"updateTime": "{{updateTime}}",
"content": "{{q}}\n{{a}}"
}
`
}
},
{
title: i18nT('app:template.qa_template'),
desc: i18nT('app:template.qa_template_des'),
value: `<Question>
value: {
['4.9.2']: `<Question>
{{q}}
</Question>
<Answer>
{{a}}
</Answer>`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: i18nT('app:template.standard_strict_des'),
value: `{
value: {
['4.9.2']: `{
"sourceName": "{{source}}",
"updateTime": "{{updateTime}}",
"content": "{{q}}\n{{a}}"
}
`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: i18nT('app:template.hard_strict_des'),
value: `<Question>
value: {
['4.9.2']: `<Question>
{{q}}
</Question>
<Answer>
{{a}}
</Answer>`
}
}
];
export const getQuoteTemplate = (version?: string) => {
const defaultTemplate = Prompt_QuoteTemplateList[0].value;
return getPromptByVersion(version, defaultTemplate);
};
export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
value: {
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
@@ -62,11 +78,13 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: `使用 <QA></QA> 标记中的问答对进行回答。
value: {
['4.9.2']: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
@@ -79,11 +97,13 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
- 避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
@@ -101,11 +121,13 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
@@ -126,6 +148,7 @@ export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
}
];
@@ -133,7 +156,8 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
value: {
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
@@ -145,11 +169,13 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: `使用 <QA></QA> 标记中的问答对进行回答。
value: {
['4.9.2']: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
@@ -160,11 +186,13 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
- 回答的内容应尽可能与 <答案></答案> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 QA 获取的知识,只需要回复答案。`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
@@ -180,11 +208,13 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
@@ -203,12 +233,28 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
- 避免提及你是从 QA 获取的知识,只需要回复答案。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
}
];
export const getQuotePrompt = (version?: string, role: 'user' | 'system' = 'user') => {
const quotePromptTemplates =
role === 'user' ? Prompt_userQuotePromptList : Prompt_systemQuotePromptList;
const defaultTemplate = quotePromptTemplates[0].value;
return getPromptByVersion(version, defaultTemplate);
};
// Document quote prompt
export const Prompt_DocumentQuote = `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
<FilesContent>
{{quote}}
</FilesContent>
`;
export const getDocumentQuotePrompt = (version: string) => {
const promptMap = {
['4.9.2']: `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
<FilesContent>
{{quote}}
</FilesContent>
`
};
return getPromptByVersion(version, promptMap);
};

View File

@@ -1,3 +1,5 @@
import { getPromptByVersion } from './utils';
export const Prompt_AgentQA = {
description: `<Context></Context> 标记中是一段文本,学习和分析它,并整理学习成果:
- 提出问题并给出每个问题的答案。
@@ -25,7 +27,9 @@ A2:
`
};
export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录> 中提取指定 Json 信息,你仅需返回 Json 字符串,无需回答问题。
export const getExtractJsonPrompt = (version?: string) => {
const promptMap: Record<string, string> = {
['4.9.2']: `你可以从 <对话记录></对话记录> 中提取指定 Json 信息,你仅需返回 Json 字符串,无需回答问题。
<提取要求>
{{description}}
</提取要求>
@@ -44,9 +48,31 @@ export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录>
{{text}}
</对话记录>
提取的 json 字符串:`;
提取的 json 字符串:`
};
export const Prompt_CQJson = `请帮我执行一个“问题分类”任务,将问题分类为以下几种类型之一:
return getPromptByVersion(version, promptMap);
};
export const getExtractJsonToolPrompt = (version?: string) => {
const promptMap: Record<string, string> = {
['4.9.2']: `我正在执行一个函数,需要你提供一些参数,请以 JSON 字符串格式返回这些参数,要求:
"""
- {{description}}
- 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。
- 需要结合前面的对话内容,一起生成合适的参数。
"""
本次输入内容: """{{content}}"""
`
};
return getPromptByVersion(version, promptMap);
};
export const getCQPrompt = (version?: string) => {
const promptMap: Record<string, string> = {
['4.9.2']: `请帮我执行一个"问题分类"任务,将问题分类为以下几种类型之一:
"""
{{typeList}}
@@ -64,9 +90,13 @@ export const Prompt_CQJson = `请帮我执行一个“问题分类”任务,
问题:"{{question}}"
类型ID=
`;
`
};
export const PROMPT_QUESTION_GUIDE = `You are an AI assistant tasked with predicting the user's next question based on the conversation history. Your goal is to generate 3 potential questions that will guide the user to continue the conversation. When generating these questions, adhere to the following rules:
return getPromptByVersion(version, promptMap);
};
export const QuestionGuidePrompt = `You are an AI assistant tasked with predicting the user's next question based on the conversation history. Your goal is to generate 3 potential questions that will guide the user to continue the conversation. When generating these questions, adhere to the following rules:
1. Use the same language as the user's last question in the conversation history.
2. Keep each question under 20 characters in length.
@@ -74,4 +104,5 @@ export const PROMPT_QUESTION_GUIDE = `You are an AI assistant tasked with predic
Analyze the conversation history provided to you and use it as context to generate relevant and engaging follow-up questions. Your predictions should be logical extensions of the current topic or related areas that the user might be interested in exploring further.
Remember to maintain consistency in tone and style with the existing conversation while providing diverse options for the user to choose from. Your goal is to keep the conversation flowing naturally and help the user delve deeper into the subject matter or explore related topics.`;
export const PROMPT_QUESTION_GUIDE_FOOTER = `Please strictly follow the format rules: \nReturn questions in JSON format: ['Question 1', 'Question 2', 'Question 3']. Your output: `;
export const QuestionGuideFooterPrompt = `Please strictly follow the format rules: \nReturn questions in JSON format: ['Question 1', 'Question 2', 'Question 3']. Your output: `;

View File

@@ -0,0 +1,19 @@
export const getPromptByVersion = (version?: string, promptMap: Record<string, string> = {}) => {
const versions = Object.keys(promptMap).sort((a, b) => {
const [majorA, minorA, patchA] = a.split('.').map(Number);
const [majorB, minorB, patchB] = b.split('.').map(Number);
if (majorA !== majorB) return majorB - majorA;
if (minorA !== minorB) return minorB - minorA;
return patchB - patchA;
});
if (!version) {
return promptMap[versions[0]];
}
if (version in promptMap) {
return promptMap[version];
}
return promptMap[versions[versions.length - 1]];
};

View File

@@ -80,5 +80,5 @@ export * from 'openai';
export type PromptTemplateItem = {
title: string;
desc: string;
value: string;
value: Record<string, string>;
};

View File

@@ -1,4 +1,3 @@
import { PROMPT_QUESTION_GUIDE } from '../ai/prompt/agent';
import {
AppTTSConfigType,
AppFileSelectConfigType,

View File

@@ -1,5 +1,10 @@
import { DatasetDataIndexItemType, DatasetSchemaType } from './type';
import { DatasetCollectionTypeEnum, DatasetCollectionDataProcessModeEnum } from './constants';
import {
DatasetCollectionTypeEnum,
DatasetCollectionDataProcessModeEnum,
ChunkSettingModeEnum,
DataChunkSplitModeEnum
} from './constants';
import type { LLMModelItemType } from '../ai/model.d';
import { ParentIdType } from 'common/parentFolder/type';
@@ -33,7 +38,13 @@ export type DatasetCollectionChunkMetadataType = {
trainingType?: DatasetCollectionDataProcessModeEnum;
imageIndex?: boolean;
autoIndexes?: boolean;
chunkSettingMode?: ChunkSettingModeEnum;
chunkSplitMode?: DataChunkSplitModeEnum;
chunkSize?: number;
indexSize?: number;
chunkSplitter?: string;
qaPrompt?: string;
metadata?: Record<string, any>;

View File

@@ -1,3 +1,5 @@
import { RequireOnlyOne } from '../../common/type/utils';
export type APIFileItem = {
id: string;
parentId: string | null;
@@ -15,9 +17,9 @@ export type APIFileServer = {
export type APIFileListResponse = APIFileItem[];
export type APIFileContentResponse = {
content?: string;
previewUrl?: string;
export type ApiFileReadContentResponse = {
title?: string;
rawText: string;
};
export type APIFileReadResponse = {

View File

@@ -16,3 +16,7 @@ export const getCollectionSourceData = (collection?: DatasetCollectionSchemaType
export const checkCollectionIsFolder = (type: DatasetCollectionTypeEnum) => {
return type === DatasetCollectionTypeEnum.folder || type === DatasetCollectionTypeEnum.virtual;
};
export const collectionCanSync = (type: DatasetCollectionTypeEnum) => {
return [DatasetCollectionTypeEnum.link, DatasetCollectionTypeEnum.apiFile].includes(type);
};

View File

@@ -13,38 +13,38 @@ export enum DatasetTypeEnum {
export const DatasetTypeMap = {
[DatasetTypeEnum.folder]: {
icon: 'common/folderFill',
label: 'folder_dataset',
collectionLabel: 'common.Folder'
label: i18nT('dataset:folder_dataset'),
collectionLabel: i18nT('common:Folder')
},
[DatasetTypeEnum.dataset]: {
icon: 'core/dataset/commonDatasetOutline',
label: 'common_dataset',
collectionLabel: 'common.File'
label: i18nT('dataset:common_dataset'),
collectionLabel: i18nT('common:common.File')
},
[DatasetTypeEnum.websiteDataset]: {
icon: 'core/dataset/websiteDatasetOutline',
label: 'website_dataset',
collectionLabel: 'common.Website'
label: i18nT('dataset:website_dataset'),
collectionLabel: i18nT('common:common.Website')
},
[DatasetTypeEnum.externalFile]: {
icon: 'core/dataset/externalDatasetOutline',
label: 'external_file',
collectionLabel: 'common.File'
label: i18nT('dataset:external_file'),
collectionLabel: i18nT('common:common.File')
},
[DatasetTypeEnum.apiDataset]: {
icon: 'core/dataset/externalDatasetOutline',
label: 'api_file',
collectionLabel: 'common.File'
label: i18nT('dataset:api_file'),
collectionLabel: i18nT('common:common.File')
},
[DatasetTypeEnum.feishu]: {
icon: 'core/dataset/feishuDatasetOutline',
label: 'feishu_dataset',
collectionLabel: 'common.File'
label: i18nT('dataset:feishu_dataset'),
collectionLabel: i18nT('common:common.File')
},
[DatasetTypeEnum.yuque]: {
icon: 'core/dataset/yuqueDatasetOutline',
label: 'yuque_dataset',
collectionLabel: 'common.File'
label: i18nT('dataset:yuque_dataset'),
collectionLabel: i18nT('common:common.File')
}
};
@@ -129,6 +129,16 @@ export const DatasetCollectionDataProcessModeMap = {
}
};
export enum ChunkSettingModeEnum {
auto = 'auto',
custom = 'custom'
}
export enum DataChunkSplitModeEnum {
size = 'size',
char = 'char'
}
/* ------------ data -------------- */
/* ------------ training -------------- */

View File

@@ -13,6 +13,7 @@ export type CreateDatasetDataProps = {
export type UpdateDatasetDataProps = {
dataId: string;
q?: string;
a?: string;
indexes?: (Omit<DatasetDataIndexItemType, 'dataId'> & {

View File

@@ -15,6 +15,8 @@ export type PushDataToTrainingQueueProps = {
vectorModel: string;
vlmModel?: string;
indexSize?: number;
billId?: string;
session?: ClientSession;
};

View File

@@ -0,0 +1,136 @@
import { EmbeddingModelItemType, LLMModelItemType } from '../../../core/ai/model.d';
import {
ChunkSettingModeEnum,
DataChunkSplitModeEnum,
DatasetCollectionDataProcessModeEnum
} from '../constants';
export const minChunkSize = 64; // min index and chunk size
// Chunk size
export const chunkAutoChunkSize = 1500;
export const getMaxChunkSize = (model: LLMModelItemType) => {
return Math.max(model.maxContext - model.maxResponse, 2000);
};
// QA
export const defaultMaxChunkSize = 8000;
export const getLLMDefaultChunkSize = (model?: LLMModelItemType) => {
if (!model) return defaultMaxChunkSize;
return Math.max(Math.min(model.maxContext - model.maxResponse, defaultMaxChunkSize), 2000);
};
export const getLLMMaxChunkSize = (model?: LLMModelItemType) => {
if (!model) return 8000;
return Math.max(model.maxContext - model.maxResponse, 2000);
};
// Index size
export const getMaxIndexSize = (model?: EmbeddingModelItemType) => {
return model?.maxToken || 512;
};
export const getAutoIndexSize = (model?: EmbeddingModelItemType) => {
return model?.defaultToken || 512;
};
const indexSizeSelectList = [
{
label: '64',
value: 64
},
{
label: '128',
value: 128
},
{
label: '256',
value: 256
},
{
label: '512',
value: 512
},
{
label: '768',
value: 768
},
{
label: '1024',
value: 1024
},
{
label: '1536',
value: 1536
},
{
label: '2048',
value: 2048
},
{
label: '3072',
value: 3072
},
{
label: '4096',
value: 4096
},
{
label: '5120',
value: 5120
},
{
label: '6144',
value: 6144
},
{
label: '7168',
value: 7168
},
{
label: '8192',
value: 8192
}
];
export const getIndexSizeSelectList = (max = 512) => {
return indexSizeSelectList.filter((item) => item.value <= max);
};
// Compute
export const computeChunkSize = (params: {
trainingType: DatasetCollectionDataProcessModeEnum;
chunkSettingMode?: ChunkSettingModeEnum;
chunkSplitMode?: DataChunkSplitModeEnum;
llmModel?: LLMModelItemType;
chunkSize?: number;
}) => {
if (params.trainingType === DatasetCollectionDataProcessModeEnum.qa) {
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
return getLLMDefaultChunkSize(params.llmModel);
}
} else {
// chunk
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
return chunkAutoChunkSize;
}
}
if (params.chunkSplitMode === DataChunkSplitModeEnum.char) {
return getLLMMaxChunkSize(params.llmModel);
}
return Math.min(params.chunkSize || chunkAutoChunkSize, getLLMMaxChunkSize(params.llmModel));
};
export const computeChunkSplitter = (params: {
chunkSettingMode?: ChunkSettingModeEnum;
chunkSplitMode?: DataChunkSplitModeEnum;
chunkSplitter?: string;
}) => {
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
return undefined;
}
if (params.chunkSplitMode === DataChunkSplitModeEnum.size) {
return undefined;
}
return params.chunkSplitter;
};

View File

@@ -2,6 +2,7 @@ import type { LLMModelItemType, EmbeddingModelItemType } from '../../core/ai/mod
import { PermissionTypeEnum } from '../../support/permission/constant';
import { PushDatasetDataChunkProps } from './api';
import {
DataChunkSplitModeEnum,
DatasetCollectionDataProcessModeEnum,
DatasetCollectionTypeEnum,
DatasetStatusEnum,
@@ -14,6 +15,7 @@ import { Permission } from '../../support/permission/controller';
import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset';
import { SourceMemberType } from 'support/user/type';
import { DatasetDataIndexTypeEnum } from './data/constants';
import { ChunkSettingModeEnum } from './constants';
export type DatasetSchemaType = {
_id: string;
@@ -88,7 +90,12 @@ export type DatasetCollectionSchemaType = {
autoIndexes?: boolean;
imageIndex?: boolean;
trainingType: DatasetCollectionDataProcessModeEnum;
chunkSize: number;
chunkSettingMode?: ChunkSettingModeEnum;
chunkSplitMode?: DataChunkSplitModeEnum;
chunkSize?: number;
indexSize?: number;
chunkSplitter?: string;
qaPrompt?: string;
};

View File

@@ -1,7 +1,6 @@
import { TrainingModeEnum, DatasetCollectionTypeEnum } from './constants';
import { getFileIcon } from '../../common/file/icon';
import { strIsLink } from '../../common/string/tools';
import { DatasetDataIndexTypeEnum } from './data/constants';
export function getCollectionIcon(
type: DatasetCollectionTypeEnum = DatasetCollectionTypeEnum.file,
@@ -38,26 +37,6 @@ export function getSourceNameIcon({
return 'file/fill/file';
}
/* get dataset data default index */
export function getDefaultIndex(props?: { q?: string; a?: string }) {
const { q = '', a } = props || {};
return [
{
text: q,
type: DatasetDataIndexTypeEnum.default
},
...(a
? [
{
text: a,
type: DatasetDataIndexTypeEnum.default
}
]
: [])
];
}
export const predictDataLimitLength = (mode: TrainingModeEnum, data: any[]) => {
if (mode === TrainingModeEnum.qa) return data.length * 20;
if (mode === TrainingModeEnum.auto) return data.length * 5;

View File

@@ -41,6 +41,8 @@ export type PluginTemplateType = PluginRuntimeType & {
export type PluginRuntimeType = {
id: string;
teamId?: string;
tmbId?: string;
name: string;
avatar: string;
showStatus?: boolean;

View File

@@ -20,6 +20,7 @@ export enum WorkflowIOValueTypeEnum {
number = 'number',
boolean = 'boolean',
object = 'object',
arrayString = 'arrayString',
arrayNumber = 'arrayNumber',
arrayBoolean = 'arrayBoolean',

View File

@@ -76,16 +76,9 @@ export const Input_Template_Text_Quote: FlowNodeInputItemType = {
valueType: WorkflowIOValueTypeEnum.string
};
export const Input_Template_File_Link_Prompt: FlowNodeInputItemType = {
key: NodeInputKeyEnum.fileUrlList,
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.input],
label: i18nT('app:file_quote_link'),
debugLabel: i18nT('app:file_quote_link'),
valueType: WorkflowIOValueTypeEnum.arrayString
};
export const Input_Template_File_Link: FlowNodeInputItemType = {
key: NodeInputKeyEnum.fileUrlList,
renderTypeList: [FlowNodeInputTypeEnum.reference],
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.input],
label: i18nT('app:workflow.user_file_input'),
debugLabel: i18nT('app:workflow.user_file_input'),
description: i18nT('app:workflow.user_file_input_desc'),

View File

@@ -17,7 +17,7 @@ import {
Input_Template_History,
Input_Template_System_Prompt,
Input_Template_UserChatInput,
Input_Template_File_Link_Prompt
Input_Template_File_Link
} from '../../input';
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
import { getHandleConfig } from '../../utils';
@@ -55,7 +55,7 @@ export const AiChatModule: FlowNodeTemplateType = {
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/ai_chat/',
version: '490',
version: '4.9.0',
inputs: [
Input_Template_SettingAiModel,
// --- settings modal
@@ -129,7 +129,7 @@ export const AiChatModule: FlowNodeTemplateType = {
},
Input_Template_History,
Input_Template_Dataset_Quote,
Input_Template_File_Link_Prompt,
Input_Template_File_Link,
{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }
],
outputs: [

View File

@@ -30,7 +30,7 @@ export const ClassifyQuestionModule: FlowNodeTemplateType = {
name: i18nT('workflow:question_classification'),
intro: i18nT('workflow:intro_question_classification'),
showStatus: true,
version: '481',
version: '4.9.2',
courseUrl: '/docs/guide/workbench/workflow/question_classify/',
inputs: [
{

View File

@@ -27,7 +27,7 @@ export const ContextExtractModule: FlowNodeTemplateType = {
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/content_extract/',
version: '481',
version: '4.9.2',
inputs: [
{
...Input_Template_SelectAIModel,

View File

@@ -31,7 +31,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/dataset_search/',
version: '481',
version: '4.9.2',
inputs: [
{
key: NodeInputKeyEnum.datasetSelectList,

View File

@@ -23,7 +23,7 @@ export const ReadFilesNode: FlowNodeTemplateType = {
name: i18nT('app:workflow.read_files'),
intro: i18nT('app:workflow.read_files_tip'),
showStatus: true,
version: '4812',
version: '4.9.2',
isTool: false,
courseUrl: '/docs/guide/course/fileinput/',
inputs: [

View File

@@ -20,7 +20,7 @@ import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
import { LLMModelTypeEnum } from '../../../ai/constants';
import { getHandleConfig } from '../utils';
import { i18nT } from '../../../../../web/i18n/utils';
import { Input_Template_File_Link_Prompt } from '../input';
import { Input_Template_File_Link } from '../input';
export const ToolModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.tools,
@@ -33,7 +33,7 @@ export const ToolModule: FlowNodeTemplateType = {
intro: i18nT('workflow:template.tool_call_intro'),
showStatus: true,
courseUrl: '/docs/guide/workbench/workflow/tool/',
version: '4813',
version: '4.9.2',
inputs: [
{
...Input_Template_SettingAiModel,
@@ -97,7 +97,7 @@ export const ToolModule: FlowNodeTemplateType = {
placeholder: chatNodeSystemPromptTip
},
Input_Template_History,
Input_Template_File_Link_Prompt,
Input_Template_File_Link,
Input_Template_UserChatInput
],
outputs: [

View File

@@ -10,7 +10,7 @@
"js-yaml": "^4.1.0",
"jschardet": "3.1.1",
"nanoid": "^5.1.3",
"next": "14.2.24",
"next": "14.2.25",
"openai": "4.61.0",
"openapi-types": "^12.1.3",
"json5": "^2.2.3",

View File

@@ -0,0 +1,4 @@
export type GetGroupListBody = {
searchKey?: string;
withMembers?: boolean;
};

View File

@@ -1,6 +1,7 @@
import { TeamMemberItemType } from 'support/user/team/type';
import { TeamPermission } from '../user/controller';
import { GroupMemberRole } from './constant';
import { Permission } from '../controller';
type MemberGroupSchemaType = {
_id: string;
@@ -16,12 +17,28 @@ type GroupMemberSchemaType = {
role: `${GroupMemberRole}`;
};
type MemberGroupType = MemberGroupSchemaType & {
members: {
tmbId: string;
role: `${GroupMemberRole}`;
}[]; // we can get tmb's info from other api. there is no need but only need to get tmb's id
permission: TeamPermission;
type MemberGroupListItemType<T extends boolean | undefined> = MemberGroupSchemaType & {
members: T extends true
? {
tmbId: string;
name: string;
avatar: string;
}[]
: undefined;
count: T extends true ? number : undefined;
owner?: T extends true
? {
tmbId: string;
name: string;
avatar: string;
}
: undefined;
permission: T extends true ? Permission : undefined;
};
type MemberGroupListType = MemberGroupType[];
type GroupMemberItemType = {
tmbId: string;
name: string;
avatar: string;
role: `${GroupMemberRole}`;
};

View File

@@ -1,4 +1,7 @@
import { MemberGroupSchemaType, MemberGroupType } from 'support/permission/memberGroup/type';
import {
MemberGroupSchemaType,
MemberGroupListItemType
} from 'support/permission/memberGroup/type';
import { OAuthEnum } from './constant';
import { TrackRegisterParams } from './login/api';
import { TeamMemberStatusEnum } from './team/constant';
@@ -12,8 +15,8 @@ export type PostLoginProps = {
export type OauthLoginProps = {
type: `${OAuthEnum}`;
code: string;
callbackUrl: string;
props: Record<string, string>;
} & TrackRegisterParams;
export type WxLoginProps = {

View File

@@ -16,7 +16,5 @@ export enum OAuthEnum {
google = 'google',
wechat = 'wechat',
microsoft = 'microsoft',
dingtalk = 'dingtalk',
wecom = 'wecom',
sso = 'sso'
}

View File

@@ -1,12 +1,12 @@
export type postCreateOrgData = {
name: string;
parentId: string;
description?: string;
avatar?: string;
orgId?: string;
};
export type putUpdateOrgMembersData = {
orgId: string;
orgId?: string;
members: {
tmbId: string;
// role: `${OrgMemberRole}`;
@@ -14,7 +14,7 @@ export type putUpdateOrgMembersData = {
};
export type putUpdateOrgData = {
orgId: string;
orgId: string; // can not be undefined because can not uppdate root org
name?: string;
avatar?: string;
description?: string;
@@ -22,7 +22,7 @@ export type putUpdateOrgData = {
export type putMoveOrgType = {
orgId: string;
targetOrgId: string;
targetOrgId?: string; // '' ===> move to root org
};
// type putChnageOrgOwnerData = {

View File

@@ -3,7 +3,10 @@ import { OrgSchemaType } from './type';
export const OrgCollectionName = 'team_orgs';
export const OrgMemberCollectionName = 'team_org_members';
export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`;
export const getOrgChildrenPath = (org: OrgSchemaType) => {
if (org.path === '' && org.pathId === '') return '';
return `${org.path ?? ''}/${org.pathId}`;
};
export enum SyncOrgSourceEnum {
wecom = 'wecom'

View File

@@ -1,5 +1,6 @@
import type { TeamPermission } from 'support/permission/user/controller';
import type { TeamPermission } from '../../../permission/user/controller';
import { ResourcePermissionType } from '../type';
import { SourceMemberType } from '../../type';
type OrgSchemaType = {
_id: string;
@@ -7,7 +8,7 @@ type OrgSchemaType = {
pathId: string;
path: string;
name: string;
avatar?: string;
avatar: string;
description?: string;
updateTime: Date;
};
@@ -19,8 +20,14 @@ type OrgMemberSchemaType = {
tmbId: string;
};
type OrgType = Omit<OrgSchemaType, 'avatar'> & {
export type OrgListItemType = OrgSchemaType & {
permission?: TeamPermission;
total: number; // members + children orgs
};
export type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string;
permission: TeamPermission;
members: OrgMemberSchemaType[];
total: number; // members + children orgs
};

View File

@@ -70,7 +70,13 @@ export type TeamTmbItemType = {
permission: TeamPermission;
} & ThirdPartyAccountType;
export type TeamMemberItemType = {
export type TeamMemberItemType<
Options extends {
withPermission?: boolean;
withOrgs?: boolean;
withGroupRole?: boolean;
} = { withPermission: true; withOrgs: true; withGroupRole: false }
> = {
userId: string;
tmbId: string;
teamId: string;
@@ -78,11 +84,24 @@ export type TeamMemberItemType = {
avatar: string;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
permission: TeamPermission;
contact?: string;
createTime: Date;
updateTime?: Date;
};
} & (Options extends { withPermission: true }
? {
permission: TeamPermission;
}
: {}) &
(Options extends { withOrgs: true }
? {
orgs?: string[]; // full path name, pattern: /teamName/orgname1/orgname2
}
: {}) &
(Options extends { withGroupRole: true }
? {
groupRole?: `${GroupMemberRole}`;
}
: {});
export type TeamTagItemType = {
label: string;

View File

@@ -30,11 +30,11 @@ export async function text2Speech({
response_format: 'mp3',
speed
},
modelData.requestUrl && modelData.requestAuth
modelData.requestUrl
? {
path: modelData.requestUrl,
headers: {
Authorization: `Bearer ${modelData.requestAuth}`
...(modelData.requestAuth ? { Authorization: `Bearer ${modelData.requestAuth}` } : {})
}
}
: {}

View File

@@ -3,21 +3,25 @@ import { getAxiosConfig } from '../config';
import axios from 'axios';
import FormData from 'form-data';
import { getSTTModel } from '../model';
import { STTModelType } from '@fastgpt/global/core/ai/model.d';
export const aiTranscriptions = async ({
model,
model: modelData,
fileStream,
headers
}: {
model: string;
model: STTModelType;
fileStream: fs.ReadStream;
headers?: Record<string, string>;
}) => {
if (!modelData) {
return Promise.reject('no model');
}
const data = new FormData();
data.append('model', model);
data.append('model', modelData.model);
data.append('file', fileStream);
const modelData = getSTTModel(model);
const aiAxiosConfig = getAxiosConfig();
const { data: result } = await axios<{ text: string }>({

View File

@@ -4,8 +4,8 @@ import { countGptMessagesTokens, countPromptTokens } from '../../../common/strin
import { loadRequestMessages } from '../../chat/utils';
import { llmCompletionsBodyFormat } from '../utils';
import {
PROMPT_QUESTION_GUIDE,
PROMPT_QUESTION_GUIDE_FOOTER
QuestionGuidePrompt,
QuestionGuideFooterPrompt
} from '@fastgpt/global/core/ai/prompt/agent';
import { addLog } from '../../../common/system/log';
import json5 from 'json5';
@@ -27,7 +27,7 @@ export async function createQuestionGuide({
...messages,
{
role: 'user',
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
content: `${customPrompt || QuestionGuidePrompt}\n${QuestionGuideFooterPrompt}`
}
];
const requestMessages = await loadRequestMessages({

View File

@@ -65,6 +65,7 @@ export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
const requestBody: T = {
...body,
model: modelData.model,
temperature:
typeof body.temperature === 'number'
? computedTemperature({

View File

@@ -37,11 +37,12 @@ export async function splitCombinePluginId(id: string) {
return { source, pluginId: id };
}
type ChildAppType = SystemPluginTemplateItemType & { teamId?: string };
type ChildAppType = SystemPluginTemplateItemType & { teamId?: string; tmbId?: string };
const getSystemPluginTemplateById = async (
pluginId: string,
versionId?: string
): Promise<SystemPluginTemplateItemType> => {
): Promise<ChildAppType> => {
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject(PluginErrEnum.unAuth);
@@ -67,12 +68,17 @@ const getSystemPluginTemplateById = async (
: await getAppLatestVersion(plugin.associatedPluginId, app);
if (!version.versionId) return Promise.reject('App version not found');
plugin.workflow = {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
return {
...plugin,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
version: versionId || String(version.versionId),
teamId: String(app.teamId),
tmbId: String(app.tmbId)
};
plugin.version = versionId || String(version.versionId);
}
return plugin;
};
@@ -168,6 +174,7 @@ export async function getChildAppRuntimeById(
return {
id: String(item._id),
teamId: String(item.teamId),
tmbId: String(item.tmbId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
@@ -187,6 +194,7 @@ export async function getChildAppRuntimeById(
pluginOrder: 0
};
} else {
// System
return getSystemPluginTemplateById(pluginId, versionId);
}
})();
@@ -194,6 +202,7 @@ export async function getChildAppRuntimeById(
return {
id: app.id,
teamId: app.teamId,
tmbId: app.tmbId,
name: app.name,
avatar: app.avatar,
showStatus: app.showStatus,

View File

@@ -131,6 +131,10 @@ export async function rewriteAppWorkflowToSimple(formatNodes: StoreNodeItemType[
if (!val) {
input.value = [];
} else if (Array.isArray(val)) {
// Not rewrite reference value
if (val.length === 2 && val.every((item) => typeof item === 'string')) {
return;
}
input.value = val
.map((dataset: { datasetId: string }) => ({
datasetId: dataset.datasetId

View File

@@ -1,6 +1,6 @@
import type {
APIFileContentResponse,
APIFileListResponse,
ApiFileReadContentResponse,
APIFileReadResponse,
APIFileServer
} from '@fastgpt/global/core/dataset/apiDataset';
@@ -8,6 +8,7 @@ import axios, { Method } from 'axios';
import { addLog } from '../../../common/system/log';
import { readFileRawTextByUrl } from '../read';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
type ResponseDataType = {
success: boolean;
@@ -118,17 +119,24 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
tmbId: string;
apiFileId: string;
customPdfParse?: boolean;
}) => {
const data = await request<APIFileContentResponse>(
`/v1/file/content`,
{ id: apiFileId },
'GET'
);
}): Promise<ApiFileReadContentResponse> => {
const data = await request<
{
title?: string;
} & RequireOnlyOne<{
content: string;
previewUrl: string;
}>
>(`/v1/file/content`, { id: apiFileId }, 'GET');
const title = data.title;
const content = data.content;
const previewUrl = data.previewUrl;
if (content) {
return content;
return {
title,
rawText: content
};
}
if (previewUrl) {
const rawText = await readFileRawTextByUrl({
@@ -138,7 +146,10 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
relatedId: apiFileId,
customPdfParse
});
return rawText;
return {
title,
rawText
};
}
return Promise.reject('Invalid content type: content or previewUrl is required');
};

View File

@@ -27,6 +27,11 @@ import { addDays } from 'date-fns';
import { MongoDatasetDataText } from '../data/dataTextSchema';
import { retryFn } from '@fastgpt/global/common/system/utils';
import { getTrainingModeByCollection } from './utils';
import {
computeChunkSize,
computeChunkSplitter,
getLLMMaxChunkSize
} from '@fastgpt/global/core/dataset/training/utils';
export const createCollectionAndInsertData = async ({
dataset,
@@ -54,18 +59,22 @@ export const createCollectionAndInsertData = async ({
const teamId = createCollectionParams.teamId;
const tmbId = createCollectionParams.tmbId;
// Chunk split params
// Set default params
const trainingType =
createCollectionParams.trainingType || DatasetCollectionDataProcessModeEnum.chunk;
const chunkSize = createCollectionParams.chunkSize || 512;
const chunkSplitter = createCollectionParams.chunkSplitter;
const qaPrompt = createCollectionParams.qaPrompt;
const usageName = createCollectionParams.name;
const chunkSize = computeChunkSize({
...createCollectionParams,
trainingType,
llmModel: getLLMModel(dataset.agentModel)
});
const chunkSplitter = computeChunkSplitter(createCollectionParams);
// 1. split chunks
const chunks = rawText2Chunks({
rawText,
chunkLen: chunkSize,
chunkSize,
maxSize: getLLMMaxChunkSize(getLLMModel(dataset.agentModel)),
overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0,
customReg: chunkSplitter ? [chunkSplitter] : [],
isQAImport
@@ -76,7 +85,7 @@ export const createCollectionAndInsertData = async ({
teamId,
insertLen: predictDataLimitLength(
getTrainingModeByCollection({
trainingType,
trainingType: trainingType,
autoIndexes: createCollectionParams.autoIndexes,
imageIndex: createCollectionParams.imageIndex
}),
@@ -88,6 +97,9 @@ export const createCollectionAndInsertData = async ({
// 3. create collection
const { _id: collectionId } = await createOneCollection({
...createCollectionParams,
trainingType,
chunkSize,
chunkSplitter,
hashRawText: hashStr(rawText),
rawTextLength: rawText.length,
@@ -111,7 +123,7 @@ export const createCollectionAndInsertData = async ({
const { billId: newBillId } = await createTrainingUsage({
teamId,
tmbId,
appName: usageName,
appName: createCollectionParams.name,
billSource: UsageSourceEnum.training,
vectorModel: getEmbeddingModel(dataset.vectorModel)?.name,
agentModel: getLLMModel(dataset.agentModel)?.name,
@@ -130,12 +142,13 @@ export const createCollectionAndInsertData = async ({
agentModel: dataset.agentModel,
vectorModel: dataset.vectorModel,
vlmModel: dataset.vlmModel,
indexSize: createCollectionParams.indexSize,
mode: getTrainingModeByCollection({
trainingType,
trainingType: trainingType,
autoIndexes: createCollectionParams.autoIndexes,
imageIndex: createCollectionParams.imageIndex
}),
prompt: qaPrompt,
prompt: createCollectionParams.qaPrompt,
billId: traingBillId,
data: chunks.map((item, index) => ({
...item,
@@ -207,11 +220,14 @@ export async function createOneCollection({
// Parse settings
customPdfParse,
imageIndex,
autoIndexes,
// Chunk settings
trainingType = DatasetCollectionDataProcessModeEnum.chunk,
autoIndexes,
chunkSize = 512,
trainingType,
chunkSettingMode,
chunkSplitMode,
chunkSize,
indexSize,
chunkSplitter,
qaPrompt,
@@ -249,11 +265,14 @@ export async function createOneCollection({
// Parse settings
customPdfParse,
imageIndex,
autoIndexes,
// Chunk settings
trainingType,
autoIndexes,
chunkSettingMode,
chunkSplitMode,
chunkSize,
indexSize,
chunkSplitter,
qaPrompt
}

View File

@@ -3,7 +3,9 @@ const { Schema, model, models } = connectionMongo;
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d';
import {
DatasetCollectionTypeMap,
DatasetCollectionDataProcessModeEnum
DatasetCollectionDataProcessModeEnum,
ChunkSettingModeEnum,
DataChunkSplitModeEnum
} from '@fastgpt/global/core/dataset/constants';
import { DatasetCollectionName } from '../schema';
import {
@@ -94,11 +96,18 @@ const DatasetCollectionSchema = new Schema({
type: String,
enum: Object.values(DatasetCollectionDataProcessModeEnum)
},
chunkSize: {
type: Number,
required: true
chunkSettingMode: {
type: String,
enum: Object.values(ChunkSettingModeEnum)
},
chunkSplitMode: {
type: String,
enum: Object.values(DataChunkSplitModeEnum)
},
chunkSize: Number,
chunkSplitter: String,
indexSize: Number,
qaPrompt: String
});

View File

@@ -11,7 +11,6 @@ import {
DatasetCollectionSyncResultEnum,
DatasetCollectionTypeEnum,
DatasetSourceReadTypeEnum,
DatasetTypeEnum,
TrainingModeEnum
} from '@fastgpt/global/core/dataset/constants';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
@@ -19,6 +18,7 @@ import { readDatasetSourceRawText } from '../read';
import { hashStr } from '@fastgpt/global/common/string/tools';
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { createCollectionAndInsertData, delCollection } from './controller';
import { collectionCanSync } from '@fastgpt/global/core/dataset/collection/utils';
/**
* get all collection by top collectionId
@@ -137,10 +137,7 @@ export const collectionTagsToTagLabel = async ({
export const syncCollection = async (collection: CollectionWithDatasetType) => {
const dataset = collection.dataset;
if (
collection.type !== DatasetCollectionTypeEnum.link &&
dataset.type !== DatasetTypeEnum.apiDataset
) {
if (!collectionCanSync(collection.type)) {
return Promise.reject(DatasetErrEnum.notSupportSync);
}
@@ -155,15 +152,20 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
};
}
if (!collection.apiFileId) return Promise.reject('apiFileId is missing');
if (!dataset.apiServer) return Promise.reject('apiServer not found');
const sourceId = collection.apiFileId;
if (!sourceId) return Promise.reject('apiFileId is missing');
return {
type: DatasetSourceReadTypeEnum.apiFile,
sourceId: collection.apiFileId,
apiServer: dataset.apiServer
sourceId,
apiServer: dataset.apiServer,
feishuServer: dataset.feishuServer,
yuqueServer: dataset.yuqueServer
};
})();
const rawText = await readDatasetSourceRawText({
const { title, rawText } = await readDatasetSourceRawText({
teamId: collection.teamId,
tmbId: collection.tmbId,
...sourceReadType
@@ -196,7 +198,7 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
createCollectionParams: {
teamId: collection.teamId,
tmbId: collection.tmbId,
name: collection.name,
name: title || collection.name,
datasetId: collection.datasetId,
parentId: collection.parentId,
type: collection.type,

View File

@@ -79,9 +79,12 @@ export const readDatasetSourceRawText = async ({
apiServer?: APIFileServer; // api dataset
feishuServer?: FeishuServer; // feishu dataset
yuqueServer?: YuqueServer; // yuque dataset
}): Promise<string> => {
}): Promise<{
title?: string;
rawText: string;
}> => {
if (type === DatasetSourceReadTypeEnum.fileLocal) {
const { rawText } = await readFileContentFromMongo({
const { filename, rawText } = await readFileContentFromMongo({
teamId,
tmbId,
bucketName: BucketNameEnum.dataset,
@@ -89,14 +92,20 @@ export const readDatasetSourceRawText = async ({
isQAImport,
customPdfParse
});
return rawText;
return {
title: filename,
rawText
};
} else if (type === DatasetSourceReadTypeEnum.link) {
const result = await urlsFetch({
urlList: [sourceId],
selector
});
return result[0]?.content || '';
return {
title: result[0]?.title,
rawText: result[0]?.content || ''
};
} else if (type === DatasetSourceReadTypeEnum.externalFile) {
if (!externalFileId) return Promise.reject('FileId not found');
const rawText = await readFileRawTextByUrl({
@@ -106,9 +115,11 @@ export const readDatasetSourceRawText = async ({
relatedId: externalFileId,
customPdfParse
});
return rawText;
return {
rawText
};
} else if (type === DatasetSourceReadTypeEnum.apiFile) {
const rawText = await readApiServerFileContent({
const { title, rawText } = await readApiServerFileContent({
apiServer,
feishuServer,
yuqueServer,
@@ -116,9 +127,15 @@ export const readDatasetSourceRawText = async ({
teamId,
tmbId
});
return rawText;
return {
title,
rawText
};
}
return '';
return {
title: '',
rawText: ''
};
};
export const readApiServerFileContent = async ({
@@ -137,7 +154,10 @@ export const readApiServerFileContent = async ({
teamId: string;
tmbId: string;
customPdfParse?: boolean;
}) => {
}): Promise<{
title?: string;
rawText: string;
}> => {
if (apiServer) {
return useApiDatasetRequest({ apiServer }).getFileContent({
teamId,
@@ -148,7 +168,10 @@ export const readApiServerFileContent = async ({
}
if (feishuServer || yuqueServer) {
return POST<string>(`/core/dataset/systemApiDataset`, {
return POST<{
title?: string;
rawText: string;
}>(`/core/dataset/systemApiDataset`, {
type: 'content',
feishuServer,
yuqueServer,
@@ -162,7 +185,7 @@ export const readApiServerFileContent = async ({
export const rawText2Chunks = ({
rawText,
isQAImport,
chunkLen = 512,
chunkSize = 512,
...splitProps
}: {
rawText: string;
@@ -175,7 +198,7 @@ export const rawText2Chunks = ({
const { chunks } = splitText2Chunks({
text: rawText,
chunkLen,
chunkSize,
...splitProps
});

View File

@@ -134,12 +134,10 @@ export const filterDatasetDataByMaxTokens = async (
let totalTokens = 0;
for await (const item of tokensScoreFilter) {
results.push(item);
totalTokens += item.tokens;
if (totalTokens > maxTokens + 500) {
break;
}
results.push(item);
if (totalTokens > maxTokens) {
break;
}

View File

@@ -12,6 +12,10 @@ import { getCollectionWithDataset } from '../controller';
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { PushDataToTrainingQueueProps } from '@fastgpt/global/core/dataset/training/type';
import { i18nT } from '../../../../web/i18n/utils';
import {
getLLMDefaultChunkSize,
getLLMMaxChunkSize
} from '../../../../global/core/dataset/training/utils';
export const lockTrainingDataByTeamId = async (teamId: string): Promise<any> => {
try {
@@ -55,6 +59,7 @@ export async function pushDataListToTrainingQueue({
prompt,
billId,
mode = TrainingModeEnum.chunk,
indexSize,
session
}: PushDataToTrainingQueueProps): Promise<PushDatasetDataResponse> {
const getImageChunkMode = (data: PushDatasetDataChunkProps, mode: TrainingModeEnum) => {
@@ -68,38 +73,41 @@ export async function pushDataListToTrainingQueue({
}
return mode;
};
const vectorModelData = getEmbeddingModel(vectorModel);
if (!vectorModelData) {
return Promise.reject(i18nT('common:error_embedding_not_config'));
}
const agentModelData = getLLMModel(agentModel);
if (!agentModelData) {
return Promise.reject(i18nT('common:error_llm_not_config'));
}
if (mode === TrainingModeEnum.chunk || mode === TrainingModeEnum.auto) {
prompt = undefined;
}
const { model, maxToken, weight } = await (async () => {
if (mode === TrainingModeEnum.chunk) {
const vectorModelData = getEmbeddingModel(vectorModel);
if (!vectorModelData) {
return Promise.reject(i18nT('common:error_embedding_not_config'));
}
return {
maxToken: vectorModelData.maxToken * 1.5,
maxToken: getLLMMaxChunkSize(agentModelData),
model: vectorModelData.model,
weight: vectorModelData.weight
};
}
if (mode === TrainingModeEnum.qa || mode === TrainingModeEnum.auto) {
const agentModelData = getLLMModel(agentModel);
if (!agentModelData) {
return Promise.reject(i18nT('common:error_llm_not_config'));
}
return {
maxToken: agentModelData.maxContext * 0.8,
maxToken: getLLMMaxChunkSize(agentModelData),
model: agentModelData.model,
weight: 0
};
}
if (mode === TrainingModeEnum.image) {
const vllmModelData = getVlmModel(vlmModel);
if (!vllmModelData) {
return Promise.reject(i18nT('common:error_vlm_not_config'));
}
return {
maxToken: vllmModelData.maxContext * 0.8,
maxToken: getLLMMaxChunkSize(vllmModelData),
model: vllmModelData.model,
weight: 0
};
@@ -107,10 +115,6 @@ export async function pushDataListToTrainingQueue({
return Promise.reject(`Training mode "${mode}" is inValid`);
})();
// Filter redundant params
if (mode === TrainingModeEnum.chunk || mode === TrainingModeEnum.auto) {
prompt = undefined;
}
// filter repeat or equal content
const set = new Set();
@@ -143,13 +147,13 @@ export async function pushDataListToTrainingQueue({
const text = item.q + item.a;
// Oversize llm tokens
if (text.length > maxToken) {
filterResult.overToken.push(item);
return;
}
if (set.has(text)) {
console.log('repeat', item);
filterResult.repeat.push(item);
} else {
filterResult.success.push(item);
@@ -182,6 +186,7 @@ export async function pushDataListToTrainingQueue({
q: item.q,
a: item.a,
chunkIndex: item.chunkIndex ?? 0,
indexSize,
weight: weight ?? 0,
indexes: item.indexes,
retryCount: 5

View File

@@ -76,6 +76,7 @@ const TrainingDataSchema = new Schema({
type: Number,
default: 0
},
indexSize: Number,
weight: {
type: Number,
default: 0

View File

@@ -10,8 +10,7 @@ import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflo
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { Prompt_CQJson } from '@fastgpt/global/core/ai/prompt/agent';
import { getCQPrompt } from '@fastgpt/global/core/ai/prompt/agent';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getLLMModel } from '../../../ai/model';
import { getHistories } from '../utils';
@@ -23,6 +22,7 @@ import { loadRequestMessages } from '../../../chat/utils';
import { llmCompletionsBodyFormat } from '../../../ai/utils';
import { addLog } from '../../../../common/system/log';
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.aiModel]: string;
@@ -99,7 +99,8 @@ const completions = async ({
cqModel,
externalProvider,
histories,
params: { agents, systemPrompt = '', userChatInput }
params: { agents, systemPrompt = '', userChatInput },
node: { version }
}: ActionProps) => {
const messages: ChatItemType[] = [
{
@@ -108,7 +109,7 @@ const completions = async ({
{
type: ChatItemValueTypeEnum.text,
text: {
content: replaceVariable(cqModel.customCQPrompt || Prompt_CQJson, {
content: replaceVariable(cqModel.customCQPrompt || getCQPrompt(version), {
systemPrompt: systemPrompt || 'null',
typeList: agents
.map((item) => `{"类型ID":"${item.key}", "问题类型":"${item.value}"}`)

View File

@@ -16,7 +16,6 @@ import {
} from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { Prompt_ExtractJson } from '@fastgpt/global/core/ai/prompt/agent';
import { replaceVariable, sliceJsonStr } from '@fastgpt/global/common/string/tools';
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { getHistories } from '../utils';
@@ -33,6 +32,10 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { llmCompletionsBodyFormat } from '../../../ai/utils';
import { ModelTypeEnum } from '../../../../../global/core/ai/model';
import {
getExtractJsonPrompt,
getExtractJsonToolPrompt
} from '@fastgpt/global/core/ai/prompt/agent';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.history]?: ChatItemType[];
@@ -154,7 +157,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const getFunctionCallSchema = async ({
extractModel,
histories,
params: { content, extractKeys, description }
params: { content, extractKeys, description },
node: { version }
}: ActionProps) => {
const messages: ChatItemType[] = [
...histories,
@@ -164,15 +168,10 @@ const getFunctionCallSchema = async ({
{
type: ChatItemValueTypeEnum.text,
text: {
content: `我正在执行一个函数,需要你提供一些参数,请以 JSON 字符串格式返回这些参数,要求:
"""
${description ? `- ${description}` : ''}
- 不是每个参数都是必须生成的,如果没有合适的参数值,不要生成该参数,或返回空字符串。
- 需要结合前面的对话内容,一起生成合适的参数。
"""
本次输入内容: """${content}"""
`
content: replaceVariable(getExtractJsonToolPrompt(version), {
description,
content
})
}
}
]
@@ -334,7 +333,8 @@ const completions = async ({
extractModel,
externalProvider,
histories,
params: { content, extractKeys, description = 'No special requirements' }
params: { content, extractKeys, description = 'No special requirements' },
node: { version }
}: ActionProps) => {
const messages: ChatItemType[] = [
{
@@ -343,23 +343,26 @@ const completions = async ({
{
type: ChatItemValueTypeEnum.text,
text: {
content: replaceVariable(extractModel.customExtractPrompt || Prompt_ExtractJson, {
description,
json: extractKeys
.map((item) => {
const valueType = item.valueType || 'string';
if (valueType !== 'string' && valueType !== 'number') {
item.enum = undefined;
}
content: replaceVariable(
extractModel.customExtractPrompt || getExtractJsonPrompt(version),
{
description,
json: extractKeys
.map((item) => {
const valueType = item.valueType || 'string';
if (valueType !== 'string' && valueType !== 'number') {
item.enum = undefined;
}
return `{"type":${item.valueType || 'string'}, "key":"${item.key}", "description":"${item.desc}" ${
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
}}`;
})
.join('\n'),
text: `${histories.map((item) => `${item.obj}:${chatValue2RuntimePrompt(item.value).text}`).join('\n')}
return `{"type":${item.valueType || 'string'}, "key":"${item.key}", "description":"${item.desc}" ${
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
}}`;
})
.join('\n'),
text: `${histories.map((item) => `${item.obj}:${chatValue2RuntimePrompt(item.value).text}`).join('\n')}
Human: ${content}`
})
}
)
}
}
]

View File

@@ -28,10 +28,10 @@ import { filterToolResponseToPreview } from './utils';
import { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { getFileContentFromLinks, getHistoryFileLinks } from '../../tools/readFiles';
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { postTextCensor } from '../../../../../common/api/requestPlusApi';
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
import { getDocumentQuotePrompt } from '@fastgpt/global/core/ai/prompt/AIChat';
type Response = DispatchNodeResultType<{
[NodeOutputKeyEnum.answerText]: string;
@@ -40,7 +40,7 @@ type Response = DispatchNodeResultType<{
export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<Response> => {
const {
node: { nodeId, name, isEntry },
node: { nodeId, name, isEntry, version },
runtimeNodes,
runtimeEdges,
histories,
@@ -118,7 +118,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
toolModel.defaultSystemChatPrompt,
systemPrompt,
documentQuoteText
? replaceVariable(Prompt_DocumentQuote, {
? replaceVariable(getDocumentQuotePrompt(version), {
quote: documentQuoteText
})
: ''

View File

@@ -24,10 +24,9 @@ import {
runtimePrompt2ChatsValue
} from '@fastgpt/global/core/chat/adapt';
import {
Prompt_DocumentQuote,
Prompt_userQuotePromptList,
Prompt_QuoteTemplateList,
Prompt_systemQuotePromptList
getQuoteTemplate,
getQuotePrompt,
getDocumentQuotePrompt
} from '@fastgpt/global/core/ai/prompt/AIChat';
import type { AIChatNodeProps } from '@fastgpt/global/core/workflow/runtime/type.d';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
@@ -70,7 +69,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
stream = false,
externalProvider,
histories,
node: { name },
node: { name, version },
query,
runningUserInfo,
workflowStreamResponse,
@@ -115,7 +114,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
filterDatasetQuote({
quoteQA,
model: modelConstantsData,
quoteTemplate
quoteTemplate: quoteTemplate || getQuoteTemplate(version)
}),
getMultiInput({
histories: chatHistories,
@@ -147,6 +146,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
datasetQuoteText,
aiChatQuoteRole,
datasetQuotePrompt: quotePrompt,
version,
userChatInput,
systemPrompt,
userFiles,
@@ -326,10 +326,10 @@ async function filterDatasetQuote({
}: {
quoteQA: ChatProps['params']['quoteQA'];
model: LLMModelItemType;
quoteTemplate?: string;
quoteTemplate: string;
}) {
function getValue(item: SearchDataResponseItemType, index: number) {
return replaceVariable(quoteTemplate || Prompt_QuoteTemplateList[0].value, {
return replaceVariable(quoteTemplate, {
id: item.id,
q: item.q,
a: item.a,
@@ -425,6 +425,7 @@ async function getChatMessages({
datasetQuotePrompt = '',
datasetQuoteText,
useDatasetQuote,
version,
histories = [],
systemPrompt,
userChatInput,
@@ -437,6 +438,7 @@ async function getChatMessages({
aiChatQuoteRole: AiChatQuoteRoleType; // user: replace user prompt; system: replace system prompt
datasetQuotePrompt?: string;
datasetQuoteText: string;
version: string;
useDatasetQuote: boolean;
histories: ChatItemType[];
@@ -451,11 +453,7 @@ async function getChatMessages({
const quoteRole =
aiChatQuoteRole === 'user' || datasetQuotePrompt.includes('{{question}}') ? 'user' : 'system';
const datasetQuotePromptTemplate = datasetQuotePrompt
? datasetQuotePrompt
: quoteRole === 'user'
? Prompt_userQuotePromptList[0].value
: Prompt_systemQuotePromptList[0].value;
const datasetQuotePromptTemplate = datasetQuotePrompt || getQuotePrompt(version, quoteRole);
// Reset user input, add dataset quote to user input
const replaceInputValue =
@@ -477,7 +475,7 @@ async function getChatMessages({
})
: '',
documentQuoteText
? replaceVariable(Prompt_DocumentQuote, {
? replaceVariable(getDocumentQuotePrompt(version), {
quote: documentQuoteText
})
: ''

View File

@@ -88,9 +88,9 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
: {}),
runningAppInfo: {
id: String(plugin.id),
// 如果系统插件,则使用当前团队的 teamId 和 tmbId
// 如果系统插件有 teamId 和 tmbId则使用系统插件的 teamId 和 tmbId管理员指定了插件作为系统插件
teamId: plugin.teamId || runningAppInfo.teamId,
tmbId: pluginData?.tmbId || runningAppInfo.tmbId
tmbId: plugin.tmbId || runningAppInfo.tmbId
},
variables: runtimeVariables,
query: getPluginRunUserQuery({

View File

@@ -117,6 +117,9 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
return Boolean(value);
}
try {
if (WorkflowIOValueTypeEnum.arrayString && typeof value === 'string') {
return [value];
}
if (
type &&
[
@@ -124,7 +127,12 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
WorkflowIOValueTypeEnum.chatHistory,
WorkflowIOValueTypeEnum.datasetQuote,
WorkflowIOValueTypeEnum.selectApp,
WorkflowIOValueTypeEnum.selectDataset
WorkflowIOValueTypeEnum.selectDataset,
WorkflowIOValueTypeEnum.arrayString,
WorkflowIOValueTypeEnum.arrayNumber,
WorkflowIOValueTypeEnum.arrayBoolean,
WorkflowIOValueTypeEnum.arrayObject,
WorkflowIOValueTypeEnum.arrayAny
].includes(type) &&
typeof value !== 'object'
) {

View File

@@ -26,7 +26,7 @@
"mammoth": "^1.6.0",
"mongoose": "^8.10.1",
"multer": "1.4.5-lts.1",
"next": "14.2.24",
"next": "14.2.25",
"nextjs-cors": "^2.2.0",
"node-cron": "^3.0.3",
"node-xlsx": "^0.24.0",

View File

@@ -4,14 +4,14 @@ import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { authUserPer } from '../user/auth';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
/*
/*
Team manager can control org
*/
export const authOrgMember = async ({
orgIds,
...props
}: {
orgIds: string | string[];
orgIds?: string | string[];
} & AuthModeType): Promise<AuthResponseType> => {
const result = await authUserPer({
...props,

View File

@@ -90,6 +90,6 @@ export async function createRootOrg({
path: ''
}
],
{ session }
{ session, ordered: true }
);
}

View File

@@ -55,6 +55,14 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
};
}
export const getTeamOwner = async (teamId: string) => {
const tmb = await MongoTeamMember.findOne({
teamId,
role: TeamMemberRoleEnum.owner
}).lean();
return tmb;
};
export async function getTmbInfoByTmbId({ tmbId }: { tmbId: string }) {
if (!tmbId) {
return Promise.reject('tmbId or userId is required');

View File

@@ -1,49 +0,0 @@
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel } from '../../../../common/mongo';
import { InvitationSchemaType } from './type';
import addDays from 'date-fns/esm/fp/addDays/index.js';
const { Schema } = connectionMongo;
export const InvitationCollectionName = 'team_invitation_links';
const InvitationSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
usedTimesLimit: {
type: Number,
default: 1,
enum: [1, -1]
},
forbidden: Boolean,
expires: Date,
description: String,
members: {
type: [String],
default: []
}
});
InvitationSchema.virtual('team', {
ref: TeamCollectionName,
localField: 'teamId',
foreignField: '_id',
justOne: true
});
try {
InvitationSchema.index({ teamId: 1 });
InvitationSchema.index({ expires: 1 }, { expireAfterSeconds: 30 * 24 * 60 * 60 });
} catch (error) {
console.log(error);
}
export const MongoInvitationLink = getMongoModel<InvitationSchemaType>(
InvitationCollectionName,
InvitationSchema
);

View File

@@ -2,6 +2,7 @@ import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
export type InvitationSchemaType = {
_id: string;
linkId: string;
teamId: string;
usedTimesLimit?: number;
forbidden?: boolean;
@@ -25,11 +26,10 @@ export type InvitationLinkCreateType = {
expires: InvitationLinkExpiresType;
usedTimesLimit: 1 | -1;
};
export type InvitationLinkUpdateType = Partial<
Omit<InvitationSchemaType, 'members' | 'teamId' | '_id'>
> & {
linkId: string;
};
// export type InvitationLinkUpdateType = Partial<
// Omit<InvitationSchemaType, 'members' | 'teamId' | '_id'>
// >;
export type InvitationInfoType = InvitationSchemaType & {
teamAvatar: string;

View File

@@ -0,0 +1,8 @@
import zustandNpm from 'zustand';
export * from 'zustand';
export * from 'zustand/middleware';
export * from 'zustand/middleware/immer';
export * from 'zustand';
export default zustandNpm;

View File

@@ -10,7 +10,16 @@ import { Box, Flex } from '@chakra-ui/react';
* @param [groupId] - group id to make the key unique
* @returns
*/
function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] }) {
function AvatarGroup({
avatars,
max = 3,
total
}: {
max?: number;
avatars: string[];
total?: number;
}) {
const remain = (total ?? avatars.length) - max;
return (
<Flex position="relative">
{avatars.slice(0, max).map((avatar, index) => (
@@ -24,10 +33,10 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] })
borderRadius={'50%'}
/>
))}
{avatars.length > max && (
{remain > 0 && (
<Box
position="relative"
left={`${(max - 1) * 15}px`}
left={`${(max - 1) * 15 + 15}px`}
w={'24px'}
h={'24px'}
borderRadius="50%"
@@ -37,7 +46,7 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] })
fontSize="sm"
color="myGray.500"
>
+{avatars.length - max}
+{String(remain)}
</Box>
)}
</Flex>

View File

@@ -72,7 +72,7 @@ const EditFolderModal = ({
{...register('name', { required: true })}
bg={'myGray.50'}
autoFocus
maxLength={20}
maxLength={100}
/>
</Box>
<Box mt={4}>

View File

@@ -0,0 +1,67 @@
import React from 'react';
import { Box, Flex, Grid, type GridProps, HStack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import QuestionTip from '../MyTooltip/QuestionTip';
type Props<T> = Omit<GridProps, 'onChange'> & {
list: {
title: string;
value: T;
tooltip?: string;
}[];
value: T;
defaultBg?: string;
activeBg?: string;
onChange: (e: T) => void;
};
const RadioGroup = <T = any,>({ list, value, onChange, ...props }: Props<T>) => {
const { t } = useTranslation();
return (
<Flex gap={[3, 5]} fontSize={['sm', 'md']} alignItems={'center'} {...props}>
{list.map((item) => (
<Flex
alignItems={'center'}
key={item.value as any}
cursor={'pointer'}
userSelect={'none'}
gap={1}
onClick={() => onChange(item.value)}
>
<Box
w={'18px'}
h={'18px'}
borderWidth={'2.4px'}
borderColor={value === item.value ? 'primary.015' : 'transparent'}
borderRadius={'50%'}
>
<Flex
w={'100%'}
h={'100%'}
borderWidth={'1px'}
borderColor={value === item.value ? 'primary.600' : 'borderColor.high'}
bg={value === item.value ? 'primary.1' : 'transparent'}
borderRadius={'50%'}
alignItems={'center'}
justifyContent={'center'}
>
<Box
w={'5px'}
h={'5px'}
borderRadius={'50%'}
bg={value === item.value ? 'primary.600' : 'transparent'}
/>
</Flex>
</Box>
<HStack spacing={1} color={'myGray.900'} whiteSpace={'nowrap'} fontSize={'sm'}>
<Box>{typeof item.title === 'string' ? t(item.title as any) : item.title}</Box>
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
</HStack>
</Flex>
))}
</Flex>
);
};
export default RadioGroup;

View File

@@ -3,6 +3,11 @@ import { useToast } from './useToast';
import { useCallback } from 'react';
import { hasHttps } from '../common/system/utils';
import { isProduction } from '@fastgpt/global/common/system/constants';
import MyModal from '../components/common/MyModal';
import React from 'react';
import { Box, ModalBody } from '@chakra-ui/react';
import Tag from '../components/common/Tag';
import { useCommonStore } from '../store/useCommonStore';
/**
* copy text data
@@ -10,11 +15,12 @@ import { isProduction } from '@fastgpt/global/common/system/constants';
export const useCopyData = () => {
const { t } = useTranslation();
const { toast } = useToast();
const { setCopyContent } = useCommonStore();
const copyData = useCallback(
async (
data: string,
title: string | null = t('common:common.Copy Successful'),
title: string | null | undefined = t('common:common.Copy Successful'),
duration = 1000
) => {
data = data.trim();
@@ -22,37 +28,18 @@ export const useCopyData = () => {
try {
if ((hasHttps() || !isProduction) && navigator.clipboard) {
await navigator.clipboard.writeText(data);
if (title) {
toast({
title,
status: 'success',
duration
});
}
} else {
throw new Error('');
}
} catch (error) {
// console.log(error);
const textarea = document.createElement('textarea');
textarea.value = data;
textarea.style.position = 'absolute';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
const res = document.execCommand('copy');
document.body.removeChild(textarea);
if (!res) {
return toast({
title: t('common:common.Copy_failed'),
status: 'error',
duration
});
}
}
if (title) {
toast({
title,
status: 'success',
duration
});
setCopyContent(data);
}
},
[t, toast]
@@ -62,3 +49,29 @@ export const useCopyData = () => {
copyData
};
};
export const ManualCopyModal = () => {
const { t } = useTranslation();
const { copyContent, setCopyContent } = useCommonStore();
return (
<MyModal
isOpen={!!copyContent}
iconSrc="copy"
iconColor="primary.600"
title={t('common:common.Copy')}
maxW={['90vw', '500px']}
w={'100%'}
onClose={() => setCopyContent(undefined)}
>
<ModalBody>
<Tag w={'100%'} colorSchema="blue">
{t('common:can_copy_content_tip')}
</Tag>
<Box mt={3} borderRadius={'md'} p={3} border={'base'} userSelect={'all'}>
{copyContent}
</Box>
</ModalBody>
</MyModal>
);
};

View File

@@ -12,6 +12,7 @@
"channel_status_unknown": "unknown",
"channel_type": "Manufacturer",
"clear_model": "Clear the model",
"confirm_delete_channel": "Confirm the deletion of the [{{name}}] channel?",
"copy_model_id_success": "Copyed model id",
"create_channel": "Added channels",
"default_url": "Default address",
@@ -36,6 +37,7 @@
"model_tokens": "Input/Output tokens",
"request_at": "Request time",
"request_duration": "Request duration: {{duration}}s",
"retry_times": "Number of retry times",
"running_test": "In testing",
"search_model": "Search for models",
"select_channel": "Select a channel name",

View File

@@ -19,7 +19,7 @@
"delete_org": "Delete organization",
"edit_info": "Edit information",
"edit_org_info": "Edit organization information",
"expires": "Expiration",
"expires": "Expiration time",
"forbid_hint": "After forbidden, this invitation link will become invalid. This action is irreversible. Are you sure you want to deactivate?",
"forbid_success": "Forbid success",
"forbidden": "Forbidden",
@@ -29,6 +29,7 @@
"has_forbidden": "Forbidden",
"has_invited": "Invited",
"ignore": "Ignore",
"invitation_copy_link": "[{{systemName}}] {{userName}} invites you to join the {{teamName}} team, link: {{url}}",
"invitation_link_auto_clean_hint": "Expired links will be automatically cleaned up after 30 days",
"invitation_link_description": "Link description",
"invitation_link_list": "Invitation link list",
@@ -58,6 +59,5 @@
"user_team_invite_member": "Invite members",
"user_team_leave_team": "Leave the team",
"user_team_leave_team_failed": "Failure to leave the team",
"waiting": "To be accepted",
"invitation_copy_link": "[{{systemName}}] {{userName}} invites you to join the {{teamName}} team, link: {{url}}"
"waiting": "To be accepted"
}

View File

@@ -59,6 +59,7 @@
"to_dataset": "Go to the Knowledge Base",
"unsupported_file_type": "Unsupported file types",
"upload": "Upload",
"variable_invisable_in_share": "Custom variables are not visible in login-free links",
"view_citations": "View References",
"web_site_sync": "Web Site Sync"
}

View File

@@ -37,6 +37,7 @@
"add_new_param": "Add new param",
"app.templateMarket.templateTags.Writing": "Writing",
"back": "Back",
"can_copy_content_tip": "It is not possible to copy automatically using the browser, please manually copy the following content",
"chose_condition": "Choose Condition",
"chosen": "Chosen",
"classification": "Classification",
@@ -128,7 +129,6 @@
"common.Continue_Adding": "Continue adding",
"common.Copy": "Copy",
"common.Copy Successful": "Copied Successfully",
"common.Copy_failed": "Copy Failed, Please Copy Manually",
"common.Create Failed": "Creation Failed",
"common.Create Success": "Created Successfully",
"common.Create Time": "Creation Time",
@@ -569,8 +569,6 @@
"core.dataset.import.Custom process": "Custom Rules",
"core.dataset.import.Custom process desc": "Customize segmentation and preprocessing rules",
"core.dataset.import.Custom prompt": "Custom Prompt",
"core.dataset.import.Custom split char": "Custom Separator",
"core.dataset.import.Custom split char Tips": "Allows you to segment based on custom separators. Usually used for pre-processed data, using specific separators for precise segmentation.",
"core.dataset.import.Custom text": "Custom Text",
"core.dataset.import.Custom text desc": "Manually enter a piece of text as a dataset",
"core.dataset.import.Data process params": "Data Processing Parameters",
@@ -646,11 +644,11 @@
"core.dataset.training.Auto mode": "Auto index",
"core.dataset.training.Auto mode Tip": "Increase the semantic richness of data blocks by generating related questions and summaries through sub-indexes and calling models, making it more conducive to retrieval. Requires more storage space and increases AI call times.",
"core.dataset.training.Chunk mode": "Chunk",
"core.dataset.training.Full": "Estimated Over 5 Minutes",
"core.dataset.training.Full": "It is expected to be more than 20 minutes",
"core.dataset.training.Leisure": "Idle",
"core.dataset.training.QA mode": "QA",
"core.dataset.training.Vector queue": "Index Queue",
"core.dataset.training.Waiting": "Estimated 5 Minutes",
"core.dataset.training.Waiting": "Estimated 20 minutes",
"core.dataset.training.Website Sync": "Website Sync",
"core.dataset.training.tag": "Queue Status",
"core.dataset.website.Base Url": "Base URL",

View File

@@ -5,6 +5,7 @@
"api_url": "API Url",
"auto_indexes": "Automatically generate supplementary indexes",
"auto_indexes_tips": "Additional index generation is performed through large models to improve semantic richness and improve retrieval accuracy.",
"auto_training_queue": "Enhanced index queueing",
"chunk_max_tokens": "max_tokens",
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
"collection.Create update time": "Creation/Update Time",
@@ -25,7 +26,7 @@
"core.dataset.import.Adjust parameters": "Adjust parameters",
"custom_data_process_params": "Custom",
"custom_data_process_params_desc": "Customize data processing rules",
"data.ideal_chunk_length": "ideal block length",
"custom_split_sign_tip": "Allows you to chunk according to custom delimiters. \nUsually used for processed data, using specific separators for precise chunking. \nYou can use the | symbol to represent multiple splitters, such as: \".|.\" to represent a period in Chinese and English.\n\nTry to avoid using special symbols related to regular, such as: * () [] {}, etc.",
"data_amount": "{{dataAmount}} Datas, {{indexAmount}} Indexes",
"data_index_num": "Index {{index}}",
"data_process_params": "Params",
@@ -51,10 +52,9 @@
"file_model_function_tip": "Enhances indexing and QA generation",
"filename": "Filename",
"folder_dataset": "Folder",
"ideal_chunk_length": "ideal block length",
"ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.",
"image_auto_parse": "Automatic image indexing",
"image_auto_parse_tips": "Call VLM to automatically label the pictures in the document and generate additional search indexes",
"image_training_queue": "Queue of image processing",
"import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens",
"import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens",
"import_confirm": "Confirm upload",
@@ -65,6 +65,8 @@
"import_param_setting": "Parameter settings",
"import_select_file": "Select a file",
"import_select_link": "Enter link",
"index_size": "Index size",
"index_size_tips": "When vectorized, the system will automatically further segment the blocks according to this size.",
"is_open_schedule": "Enable scheduled synchronization",
"keep_image": "Keep the picture",
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
@@ -78,7 +80,7 @@
"permission.des.write": "Ability to add and change knowledge base content",
"preview_chunk": "Preview chunks",
"preview_chunk_empty": "Unable to read the contents of the file",
"preview_chunk_intro": "Display up to 10 pieces",
"preview_chunk_intro": "A total of {{total}} blocks, up to 10",
"preview_chunk_not_selected": "Click on the file on the left to preview",
"rebuild_embedding_start_tip": "Index model switching task has started",
"rebuilding_index_count": "Number of indexes being rebuilt: {{count}}",
@@ -86,6 +88,16 @@
"retain_collection": "Adjust Training Parameters",
"retrain_task_submitted": "The retraining task has been submitted",
"same_api_collection": "The same API set exists",
"split_chunk_char": "Block by specified splitter",
"split_chunk_size": "Block by length",
"split_sign_break": "1 newline character",
"split_sign_break2": "2 newline characters",
"split_sign_custom": "Customize",
"split_sign_exclamatiob": "exclamation mark",
"split_sign_null": "Not set",
"split_sign_period": "period",
"split_sign_question": "question mark",
"split_sign_semicolon": "semicolon",
"start_sync_website_tip": "Confirm to start synchronizing data? \nThe old data will be deleted and retrieved again, please confirm!",
"sync_collection_failed": "Synchronization collection error, please check whether the source file can be accessed normally",
"sync_schedule": "Timing synchronization",

View File

@@ -12,6 +12,7 @@
"channel_status_unknown": "未知",
"channel_type": "厂商",
"clear_model": "清空模型",
"confirm_delete_channel": "确认删除 【{{name}}】渠道?",
"copy_model_id_success": "已复制模型id",
"create_channel": "新增渠道",
"default_url": "默认地址",
@@ -36,6 +37,7 @@
"model_tokens": "输入/输出 Tokens",
"request_at": "请求时间",
"request_duration": "请求时长: {{duration}}s",
"retry_times": "重试次数",
"running_test": "测试中",
"search_model": "搜索模型",
"select_channel": "选择渠道名",

View File

@@ -22,7 +22,7 @@
"delete_org": "删除部门",
"edit_info": "编辑信息",
"edit_org_info": "编辑部门信息",
"expires": "有效期",
"expires": "过期时间",
"export_members": "导出成员",
"forbid_hint": "停用后,该邀请链接将失效。 该操作不可撤销,是否确认停用?",
"forbid_success": "停用成功",
@@ -33,6 +33,7 @@
"has_forbidden": "已失效",
"has_invited": "已邀请",
"ignore": "忽略",
"invitation_copy_link": "【{{systemName}}】 {{userName}} 邀请您加入{{teamName}}团队,链接:{{url}}",
"invitation_link_auto_clean_hint": "已失效链接将在30天后自动清理",
"invitation_link_description": "链接描述",
"invitation_link_list": "链接列表",
@@ -53,6 +54,7 @@
"org_name": "部门名称",
"owner": "所有者",
"permission": "权限",
"please_bind_contact": "请绑定联系方式",
"remark": "备注",
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
@@ -73,7 +75,5 @@
"user_team_invite_member": "邀请成员",
"user_team_leave_team": "离开团队",
"user_team_leave_team_failed": "离开团队失败",
"waiting": "待接受",
"invitation_copy_link": "【{{systemName}}】 {{userName}} 邀请您加入{{teamName}}团队,链接:{{url}}",
"please_bind_contact": "请绑定联系方式"
"waiting": "待接受"
}

View File

@@ -59,6 +59,7 @@
"to_dataset": "前往知识库",
"unsupported_file_type": "不支持的文件类型",
"upload": "上传",
"variable_invisable_in_share": "自定义变量在免登录链接中不可见",
"view_citations": "查看引用",
"web_site_sync": "Web站点同步"
}

View File

@@ -41,6 +41,7 @@
"app.templateMarket.templateTags.Web_search": "联网搜索",
"app.templateMarket.templateTags.Writing": "文本创作",
"back": "返回",
"can_copy_content_tip": "无法使用浏览器自动复制,请手动复制下面内容",
"chose_condition": "选择条件",
"chosen": "已选",
"classification": "分类",
@@ -132,7 +133,6 @@
"common.Continue_Adding": "继续添加",
"common.Copy": "复制",
"common.Copy Successful": "复制成功",
"common.Copy_failed": "复制失败,请手动复制",
"common.Create Failed": "创建异常",
"common.Create Success": "创建成功",
"common.Create Time": "创建时间",
@@ -335,7 +335,7 @@
"core.app.deterministic": "严谨",
"core.app.edit.Prompt Editor": "提示词编辑",
"core.app.edit.Query extension background prompt": "对话背景描述",
"core.app.edit.Query extension background tip": "描述当前对话的范围,便于 AI 为当前问题进行补全和扩展。填写的内容,通常为该助手",
"core.app.edit.Query extension background tip": "描述当前对话的范围,便于 AI 为当前问题进行补全和扩展。填写的内容,通常为该助手所用",
"core.app.edit_content": "应用信息编辑",
"core.app.error.App name can not be empty": "应用名不能为空",
"core.app.error.Get app failed": "获取应用异常",
@@ -573,8 +573,6 @@
"core.dataset.import.Custom process": "自定义规则",
"core.dataset.import.Custom process desc": "自定义设置数据处理规则",
"core.dataset.import.Custom prompt": "自定义提示词",
"core.dataset.import.Custom split char": "自定义分隔符",
"core.dataset.import.Custom split char Tips": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。",
"core.dataset.import.Custom text": "自定义文本",
"core.dataset.import.Custom text desc": "手动输入一段文本作为数据集",
"core.dataset.import.Data process params": "数据处理参数",
@@ -649,11 +647,11 @@
"core.dataset.training.Auto mode": "补充索引",
"core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。",
"core.dataset.training.Chunk mode": "直接分块",
"core.dataset.training.Full": "预计 5 分钟以上",
"core.dataset.training.Full": "预计 20 分钟以上",
"core.dataset.training.Leisure": "空闲",
"core.dataset.training.QA mode": "问答对提取",
"core.dataset.training.Vector queue": "索引排队",
"core.dataset.training.Waiting": "预计 5 分钟",
"core.dataset.training.Waiting": "预计 20 分钟",
"core.dataset.training.Website Sync": "Web 站点同步",
"core.dataset.training.tag": "排队情况",
"core.dataset.website.Base Url": "根地址",

View File

@@ -5,6 +5,7 @@
"api_url": "接口地址",
"auto_indexes": "自动生成补充索引",
"auto_indexes_tips": "通过大模型进行额外索引生成,提高语义丰富度,提高检索的精度。",
"auto_training_queue": "增强索引排队",
"chunk_max_tokens": "分块上限",
"close_auto_sync": "确认关闭自动同步功能?",
"collection.Create update time": "创建/更新时间",
@@ -25,7 +26,7 @@
"core.dataset.import.Adjust parameters": "调整参数",
"custom_data_process_params": "自定义",
"custom_data_process_params_desc": "自定义设置数据处理规则",
"data.ideal_chunk_length": "理想分块长度",
"custom_split_sign_tip": "允许你根据自定义的分隔符进行分块。通常用于已处理好的数据,使用特定的分隔符来精确分块。可以使用 | 符号表示多个分割符,例如:“。|.” 表示中英文句号。\n尽量避免使用正则相关特殊符号例如: * () [] {} 等。",
"data_amount": "{{dataAmount}} 组数据, {{indexAmount}} 组索引",
"data_index_num": "索引 {{index}}",
"data_process_params": "处理参数",
@@ -51,10 +52,9 @@
"file_model_function_tip": "用于增强索引和 QA 生成",
"filename": "文件名",
"folder_dataset": "文件夹",
"ideal_chunk_length": "理想分块长度",
"ideal_chunk_length_tips": "按结束符号进行分段,并将多个分段组成一个分块,该值决定了分块的预估大小,如果会有上下浮动。",
"image_auto_parse": "图片自动索引",
"image_auto_parse_tips": "调用 VLM 自动标注文档里的图片,并生成额外的检索索引",
"image_training_queue": "图片处理排队",
"import.Auto mode Estimated Price Tips": "需调用文本理解模型需要消耗较多AI 积分:{{price}} 积分/1K tokens",
"import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens",
"import_confirm": "确认上传",
@@ -65,6 +65,8 @@
"import_param_setting": "参数设置",
"import_select_file": "选择文件",
"import_select_link": "输入链接",
"index_size": "索引大小",
"index_size_tips": "向量化时内容的长度,系统会自动按该大小对分块进行进一步的分割。",
"is_open_schedule": "启用定时同步",
"keep_image": "保留图片",
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
@@ -78,7 +80,7 @@
"permission.des.write": "可增加和变更知识库内容",
"preview_chunk": "分块预览",
"preview_chunk_empty": "无法读取该文件内容",
"preview_chunk_intro": "最多展示 10 个分块",
"preview_chunk_intro": "共 {{total}} 个分块,最多展示 10 个",
"preview_chunk_not_selected": "点击左侧文件后进行预览",
"rebuild_embedding_start_tip": "切换索引模型任务已开始",
"rebuilding_index_count": "重建中索引数量:{{count}}",
@@ -86,6 +88,16 @@
"retain_collection": "调整训练参数",
"retrain_task_submitted": "重新训练任务已提交",
"same_api_collection": "存在相同的 API 集合",
"split_chunk_char": "按指定分割符分块",
"split_chunk_size": "按长度分块",
"split_sign_break": "1 个换行符",
"split_sign_break2": "2 个换行符",
"split_sign_custom": "自定义",
"split_sign_exclamatiob": "感叹号",
"split_sign_null": "不设置",
"split_sign_period": "句号",
"split_sign_question": "问号",
"split_sign_semicolon": "分号",
"start_sync_website_tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
"sync_collection_failed": "同步集合错误,请检查是否能正常访问源文件",
"sync_schedule": "定时同步",

View File

@@ -12,6 +12,7 @@
"channel_status_unknown": "未知",
"channel_type": "廠商",
"clear_model": "清空模型",
"confirm_delete_channel": "確認刪除 【{{name}}】渠道?",
"copy_model_id_success": "已復制模型id",
"create_channel": "新增渠道",
"default_url": "默認地址",
@@ -34,6 +35,7 @@
"model_tokens": "輸入/輸出 Tokens",
"request_at": "請求時間",
"request_duration": "請求時長: {{duration}}s",
"retry_times": "重試次數",
"running_test": "測試中",
"search_model": "搜索模型",
"select_channel": "選擇渠道名",

View File

@@ -19,7 +19,7 @@
"delete_org": "刪除部門",
"edit_info": "編輯訊息",
"edit_org_info": "編輯部門資訊",
"expires": "有效期",
"expires": "過期時間",
"forbid_hint": "停用後,該邀請連結將失效。 該操作不可撤銷,是否確認停用?",
"forbid_success": "停用成功",
"forbidden": "停用",
@@ -29,6 +29,7 @@
"has_forbidden": "已失效",
"has_invited": "已邀請",
"ignore": "忽略",
"invitation_copy_link": "【{{systemName}}】 {{userName}} 邀請您加入{{teamName}}團隊,連結:{{url}}",
"invitation_link_auto_clean_hint": "已失效連結將在30天後自動清理",
"invitation_link_description": "連結描述",
"invitation_link_list": "連結列表",
@@ -58,6 +59,5 @@
"user_team_invite_member": "邀請成員",
"user_team_leave_team": "離開團隊",
"user_team_leave_team_failed": "離開團隊失敗",
"waiting": "待接受",
"invitation_copy_link": "【{{systemName}}】 {{userName}} 邀請您加入{{teamName}}團隊,連結:{{url}}"
"waiting": "待接受"
}

View File

@@ -58,6 +58,7 @@
"to_dataset": "前往知識庫",
"unsupported_file_type": "不支援的檔案類型",
"upload": "上傳",
"variable_invisable_in_share": "自定義變量在免登錄鏈接中不可見",
"view_citations": "檢視引用",
"web_site_sync": "網站同步"
}

View File

@@ -37,6 +37,7 @@
"add_new_param": "新增參數",
"app.templateMarket.templateTags.Writing": "文字創作",
"back": "返回",
"can_copy_content_tip": "無法使用瀏覽器自動複製,請手動複製下面內容",
"chose_condition": "選擇條件",
"chosen": "已選擇",
"classification": "分類",
@@ -127,7 +128,6 @@
"common.Continue_Adding": "繼續新增",
"common.Copy": "複製",
"common.Copy Successful": "複製成功",
"common.Copy_failed": "複製失敗,請手動複製",
"common.Create Failed": "建立失敗",
"common.Create Success": "建立成功",
"common.Create Time": "建立時間",
@@ -568,8 +568,6 @@
"core.dataset.import.Custom process": "自訂規則",
"core.dataset.import.Custom process desc": "自訂設定資料處理規則",
"core.dataset.import.Custom prompt": "自訂提示詞",
"core.dataset.import.Custom split char": "自訂分隔符",
"core.dataset.import.Custom split char Tips": "允許您根據自訂的分隔符進行分割。通常用於已處理好的資料,使用特定的分隔符來精確分割。",
"core.dataset.import.Custom text": "自訂文字",
"core.dataset.import.Custom text desc": "手動輸入一段文字作為資料集",
"core.dataset.import.Data process params": "資料處理參數",
@@ -645,11 +643,11 @@
"core.dataset.training.Auto mode": "補充索引",
"core.dataset.training.Auto mode Tip": "透過子索引以及呼叫模型產生相關問題與摘要,來增加資料區塊的語意豐富度,更有利於檢索。需要消耗更多的儲存空間並增加 AI 呼叫次數。",
"core.dataset.training.Chunk mode": "直接分块",
"core.dataset.training.Full": "預計超過 5 分鐘",
"core.dataset.training.Full": "預計 20 分鐘以上",
"core.dataset.training.Leisure": "閒置",
"core.dataset.training.QA mode": "問答對提取",
"core.dataset.training.Vector queue": "索引排隊中",
"core.dataset.training.Waiting": "預計 5 分鐘",
"core.dataset.training.Waiting": "預計 20 分鐘",
"core.dataset.training.Website Sync": "網站同步",
"core.dataset.training.tag": "排隊狀況",
"core.dataset.website.Base Url": "根網址",

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