Compare commits

..

18 Commits

Author SHA1 Message Date
Archer
3c97757e4d 4.8.19-feature (#3636)
* feat: sync org from wecom, pref: member list pagination (#3549)

* feat: sync org

* chore: fe

* chore: loading

* chore: type

* pref: team member list change to pagination. Edit a sort of list apis.

* feat: member update avatar

* chore: user avatar move to tmb

* chore: init scripts move user avatar

* chore: sourceMember

* fix: list api sourceMember

* fix: member sync

* fix: pagination

* chore: adjust code

* chore: move changeOwner to pro

* chore: init v4819 script

* chore: adjust code

* chore: UserBox

* perf: scroll page code

* perf: list data

* docs:更新用户答疑 (#3576)

* docs: add custom uid docs (#3572)

* fix: pagination bug (#3577)

* 4.8.19 test (#3584)

* faet: dataset search filter

* fix: scroll page

* fix: collection list api old version (#3591)

* fix: collection list api format

* fix: type error of addSourceMemeber

* fix: scroll fetch (#3592)

* fix: yuque dataset file folder can enter (#3593)

* perf: load members;perf: yuque load;fix: workflow llm params cannot close (#3594)

* chat openapi doc

* feat: dataset openapi doc

* perf: load members

* perf: member load code

* perf: yuque load

* fix: workflow llm params cannot close

* fix: api dataset reference tag preview (#3600)

* perf: doc

* feat: chat page config

* fix: http parse (#3634)

* update doc

* fix: http parse

* fix code run node reset template (#3633)

Co-authored-by: Archer <545436317@qq.com>

* docs:faq (#3627)

* docs:faq

* docsFix

* perf: sleep plugin

* fix: selector

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
2025-01-20 19:42:33 +08:00
shilin
9f33729ca9 feat(core): 添加 sleep 插件 (#3583)
(cherry picked from commit 09bdc63977)
2025-01-20 18:31:47 +08:00
Archer
a8d456f448 Update docker.md (#3599) 2025-01-15 13:16:57 +08:00
Archer
59177d307e fix: http (#3596) 2025-01-15 12:09:12 +08:00
Archer
bfb598d686 doc (#3590) 2025-01-14 17:43:05 +08:00
heheer
b780fbd6a7 fix lexical editor space (#3586)
* fix lexical editor space

* delete console
2025-01-14 17:24:16 +08:00
Archer
7861229325 4.8.18 test (#3589)
* update doc

* add subpath
2025-01-14 17:10:58 +08:00
Archer
5b0516c28b update doc (#3588) 2025-01-14 16:59:59 +08:00
Archer
740e8eb30c update doc (#3587) 2025-01-14 16:53:52 +08:00
Archer
05a357dffe fix: http variable (#3585) 2025-01-14 13:45:45 +08:00
Archer
781fd45e39 update doc (#3581) 2025-01-14 09:57:26 +08:00
Archer
80c8897e10 fix: create plugin by curl (#3580) 2025-01-13 18:27:31 +08:00
heheer
e933dacb05 curl create plugin with inputs (#3573) 2025-01-13 17:37:27 +08:00
Archer
cdf4f0e67d faq (#3574) 2025-01-13 14:08:35 +08:00
Archer
4dfeb21da3 4.8.18 test (#3571)
* rename

* update doc

* doc

* doc

* perf: intro
2025-01-13 10:43:38 +08:00
Archer
d0d1a2cae8 perf: http body;perf: create by json;perf: create by curl (#3570)
* perf: http body

* feat: create app by json (#3557)

* feat: create app by json

* fix build

* perf: create by json;perf: create by curl

* fix: ts

---------

Co-authored-by: heheer <heheer@sealos.io>
2025-01-12 22:49:03 +08:00
Archer
f1f0ae2839 fix: per;fix: invite user un refresh;perf: loginout (#3566)
* perf: loginout

* fix: invite user un refresh

* fix: per

* fix: dockerfile

* perf: docker file
2025-01-11 17:16:17 +08:00
Archer
10d8c56e23 V4.8.18 feature (#3565)
* feat: org CRUD (#3380)

* feat: add org schema

* feat: org manage UI

* feat: OrgInfoModal

* feat: org tree view

* feat: org management

* fix: init root org

* feat: org permission for app

* feat: org support for dataset

* fix: disable org role control

* styles: opt type signatures

* fix: remove unused permission

* feat: delete org collaborator

* perf: Team org ui (#3499)

* perf: org ui

* perf: org ui

* feat: org auth for app & dataset (#3498)

* feat: auth org resource permission

* feat: org auth support for app & dataset

* perf: org permission check (#3500)

* i18n (#3501)

* name

* i18n

* feat: support dataset changeOwner (#3483)

* feat: support dataset changeOwner

* chore: update dataset change owner api

* feat: permission manage UI for org (#3503)

* perf: password check;perf: image upload check;perf: sso login check (#3509)

* perf: password check

* perf: image upload check

* perf: sso login check

* force show update notification modal & fix login page text (#3512)

* fix login page English text

* update notification modal

* perf: notify account (#3515)

* perf(plugin): improve searXNG empty result handling and documentation (#3507)

* perf(plugin): improve searXNG empty result handling and documentation

* 修改了文档和代码部分无搜索的结果的反馈

* refactor: org pathId (#3516)

* optimize payment process (#3517)

* feat: support wecom sso (#3518)

* feat: support wecom sso

* chore: remove unused wecom js-sdk dependency

* fix qrcode script (#3520)

* fix qrcode script

* i18n

* perf: full text collection and search code;perf: rename function (#3519)

* perf: full text collection and search code

* perf: rename function

* perf: notify modal

* remove invalid code

* perf: sso login

* perf: pay process

* 4.8.18 test (#3524)

* perf: remove local token

* perf: index

* perf: file encoding;perf: leave team code;@c121914yu perf: full text search code (#3528)

* perf: text encoding

* perf: leave team code

* perf: full text search code

* fix: http status

* perf: embedding search and vector avatar

* perf: async read file (#3531)

* refactor: team permission  manager (#3535)

* perf: classify org, group and member

* refactor: team per manager

* fix: missing functions

* 4.8.18 test (#3543)

* perf: login check

* doc

* perf: llm model config

* perf: team clb config

* fix: MemberModal UI (#3553)

* fix: adapt MemberModal title and icon

* fix: adapt member modal

* fix: search input placeholder

* fix: add button text

* perf: org permission (#3556)

* docs:用户答疑的官方文档补充 (#3540)

* docs:用户答疑的官方文档补充

* 问题回答的内容修补

* share link random avatar (#3541)

* share link random avatar

* fix

* delete unused code

* share page avatar (#3558)

* feat: init 4818

* share page avatar

* feat: tmp upgrade code (#3559)

* feat: tmp upgrade code

* fulltext search test

* update action

* full text tmp code (#3561)

* full text tmp code

* fix: init

* fix: init

* remove tmp code

* remove tmp code

* 4818-alpha

* 4.8.18 test (#3562)

* full text tmp code

* fix: init

* upgrade code

* account log

* account log

* perf: dockerfile

* upgrade code

* chore: update docs app template submission (#3564)

---------

Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
2025-01-11 15:15:38 +08:00
227 changed files with 4404 additions and 2210 deletions

View File

@@ -173,3 +173,86 @@ jobs:
-t ${Docker_Hub_Tag} \
-t ${Docker_Hub_Latest} \
.
build-fastgpt-images-sub-route-gchat:
runs-on: ubuntu-20.04
steps:
# install env
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Install Dependencies
run: |
sudo apt update && sudo apt install -y nodejs npm
- name: Set up QEMU (optional)
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: network=host
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
# login docker
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Login to Ali Hub
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_HUB_USERNAME }}
password: ${{ secrets.ALI_HUB_PASSWORD }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# Set tag
- name: Set image name and tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
else
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sub-route-gchat:latest" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
-f projects/app/Dockerfile \
--platform linux/amd64,linux/arm64 \
--build-arg base_url=/gchat \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-sub-route-gchat image" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${Git_Tag} \
-t ${Git_Latest} \
-t ${Ali_Tag} \
-t ${Ali_Latest} \
-t ${Docker_Hub_Tag} \
-t ${Docker_Hub_Latest} \
.

View File

@@ -51,6 +51,23 @@ jobs:
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
.
# Add write md step after build
- name: Write md
run: |
echo "# 🤖 Generated by deploy action" > report.md
echo "📦 Preview Image: \`${DOCKER_REPO_TAGGED}\`" >> report.md
cat report.md
- name: Gh Rebot for Sealos
uses: labring/gh-rebot@v0.0.6
if: ${{ (github.event_name == 'pull_request_target') }}
with:
version: v0.0.6
env:
GH_TOKEN: '${{ secrets.GH_PAT }}'
SEALOS_TYPE: 'pr_comment'
SEALOS_FILENAME: 'report.md'
SEALOS_REPLACE_TAG: 'DEFAULT_REPLACE_DEPLOY'
helm-check:
runs-on: ubuntu-20.04

2
dev.md
View File

@@ -1,6 +1,6 @@
## Premise
Since FastGPT is managed in the same way as monorepo, it is recommended to install 'make' first during development.
Since FastGPT is managed in the same way as monorepo, it is recommended to install make first during development.
monorepo Project Name:

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -7,7 +7,7 @@ toc: true
weight: 1210
---
FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件:
FastGPT 项目在 Apache License 2.0 许可下开源,包含以下附加条件:
+ FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可:

View File

@@ -9,9 +9,9 @@ weight: 707
由于环境变量不利于配置复杂的内容,新版 FastGPT 采用了 ConfigMap 的形式挂载配置文件,你可以在 `projects/app/data/config.json` 看到默认的配置文件。可以参考 [docker-compose 快速部署](/docs/development/docker/) 来挂载配置文件。
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
这个配置文件中包含了系统参数和各个模型配置:
下面配置文件示例中包含了系统参数和各个模型配置:
## 4.6.8+ 版本新配置文件示例

View File

@@ -23,19 +23,19 @@ weight: 707
### PgVector版本
体验测试首选
非常轻量,适合数据量在 5000 万以下。
{{< table "table-hover table-striped-columns" >}}
| 环境 | 最低配置(单节点) | 推荐配置 |
| ---- | ---- | ---- |
| 测试 | 2c2g | 2c4g |
| 测试(可以把计算进程设置少一些) | 2c4g | 2c8g |
| 100w 组向量 | 4c8g 50GB | 4c16g 50GB |
| 500w 组向量 | 8c32g 200GB | 16c64g 200GB |
{{< /table >}}
### Milvus版本
生产部署首选,对于千万级以上向量性能更优秀。
对于亿级以上向量性能更优秀。
[点击查看 Milvus 官方推荐配置](https://milvus.io/docs/prerequisite-docker.md)

View File

@@ -19,6 +19,53 @@ images: []
## 二、通用问题
### 通过sealos部署的话是否没有本地部署的一些限制
![](/imgs/faq1.png)
这是索引模型的长度限制,通过任何方式部署都一样的,但不同索引模型的配置不一样,可以在后台修改参数。
### sealos怎么挂载 小程序配置文件
新增配置文件:/app/projects/app/public/xxxx.txt
如图
![](/imgs/faq2.png)
### 数据库3306端口被占用了启动服务失败
![](/imgs/faq3.png)
mysql 只有 oneAPI 用到,外面一般不需要调用,所以可以
- 把 3306:3306 的映射去掉/或者直接改一个映射。
```yaml
# 在 docker-compose.yaml 文件内
# ...
mysql:
image: mysql:8.0.36
ports:
- 3306:3306 # 这个端口被占用了!
# - 3307:3306 # 直接改一个。。和外面的不冲突
# *empty* 或者直接删了,反正外面用不到
oneapi:
container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest
environment:
- SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 这不用改,容器内外网络是隔离的
```
- 另一种做法是可以直接连现有的 mysql 要改 oneAPI 的环境变量。
```yaml
# 在 docker-compose.yaml 文件内
# ...
# mysql: # 要连外面的,这个玩意用不到了
# image: mysql:8.0.36
# ports:
# - 3306:3306 # 这个端口被占用了!
oneapi:
container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest
environment:
- SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 改成外面的链接字符串
```
### 本地部署的限制
具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。
@@ -45,31 +92,6 @@ images: []
1. 问题补全需要经过一轮AI生成。
2. 会进行3~5轮的查询如果数据库性能不足会有明显影响。
### 对话接口报错或返回为空(core.chat.Chat API is error or undefined)
1. 检查 AI 的 key 问题:通过 curl 请求看是否正常。务必用 stream=true 模式。并且 maxToken 等相关参数尽量一致。
2. 如果是国内模型,可能是命中风控了。
3. 查看模型请求日志,检查出入参数是否异常。
```sh
# curl 例子。
curl --location --request POST 'https://xxx.cn/v1/chat/completions' \
--header 'Authorization: Bearer sk-xxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "gpt-3.5-turbo",
"stream": true,
"temperature": 1,
"max_tokens": 3000,
"messages": [
{
"role": "user",
"content": "你是谁"
}
]
}'
```
### 页面中可以正常回复API 报错
页面中是用 stream=true 模式所以API也需要设置 stream=true 来进行测试。部分模型接口(国产居多)非 Stream 的兼容有点垃圾。
@@ -115,6 +137,13 @@ FastGPT 模型配置文件中的 model 必须与 OneAPI 渠道中的模型对应
如果OneAPI中没有配置对应的模型`config.json`中也不要配置,否则容易报错。
### 点击模型测试失败
OneAPI 只会测试渠道的第一个模型,并且只会测试对话模型,向量模型无法自动测试,需要手动发起请求进行测试。[查看测试模型命令示例](/docs/development/faq/#如何检查模型问题)
### get request url failed: Post "https://xxx dial tcp: xxxx
OneAPI 与模型网络不通,需要检查网络配置。
### Incorrect API key provided: sk-xxxx.You can find your api Key at xxx
OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并重启容器(先 docker-compose down 然后再 docker-compose up -d 运行一次)。
@@ -130,6 +159,112 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
## 四、常见模型问题
### 如何检查模型问题
1. 私有部署模型,先确认部署的模型是否正常。
2. 通过 CURL 请求,直接测试上游模型是否正常运行(云端模型或私有模型均进行测试)
3. 通过 CURL 请求,请求 OneAPI 去测试模型是否正常。
4. 在 FastGPT 中使用该模型进行测试。
下面是几个测试 CURL 示例:
{{< tabs tabTotal="5" >}}
{{< tab tabName="LLM模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Embedding模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/embeddings \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "The food was delicious and the waiter...",
"model": "text-embedding-ada-002",
"encoding_format": "float"
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Rerank 模型" >}}
{{< markdownify >}}
```bash
curl --location --request POST 'https://xxxx.com/api/v1/rerank' \
--header 'Authorization: Bearer {{ACCESS_TOKEN}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "bge-rerank-m3",
"query": "导演是谁",
"documents": [
"你是谁?\n我是电影《铃芽之旅》助手"
]
}'
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="TTS 模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/audio/speech \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"input": "The quick brown fox jumped over the lazy dog.",
"voice": "alloy"
}' \
--output speech.mp3
```
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Whisper 模型" >}}
{{< markdownify >}}
```bash
curl https://api.openai.com/v1/audio/transcriptions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: multipart/form-data" \
-F file="@/path/to/file/audio.mp3" \
-F model="whisper-1"
```
{{< /markdownify >}}
{{< /tab >}}
{{< /tabs >}}
### 报错 - 模型响应为空/模型报错
该错误是由于 stream 模式下oneapi 直接结束了流请求,并且未返回任何内容导致。
@@ -169,7 +304,7 @@ curl --location --request POST 'https://api.openai.com/v1/chat/completions' \
需要模型提供商和 oneapi 同时支持工具调用才可使用,测试方法如下:
1. 通过 `curl``oneapi` 发起第一轮 stream 模式的 tool 测试。
##### 1. 通过 `curl` 向 `oneapi` 发起第一轮 stream 模式的 tool 测试。
```bash
curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
@@ -204,7 +339,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
}'
```
2. 检查响应参数
##### 2. 检查响应参数
如果能正常调用工具,会返回对应 `tool_calls` 参数。
@@ -242,7 +377,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
}
```
3. 通过 `curl``oneapi` 发起第二轮 stream 模式的 tool 测试。
##### 3. 通过 `curl` 向 `oneapi` 发起第二轮 stream 模式的 tool 测试。
第二轮请求是把工具结果发送给模型。发起后会得到模型回答的结果。

View File

@@ -55,4 +55,28 @@ curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions'
}
]
}'
```
```
## 自定义用户 ID
`v4.8.13`后支持传入自定义的用户 ID, 并且存入历史记录中。
```sh
curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions' \
--header 'Authorization: Bearer fastgpt-xxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"chatId": "111",
"stream": false,
"detail": false,
"messages": [
{
"content": "导演是谁",
"role": "user"
}
],
"customUid": "xxxxxx"
}'
```
在历史记录中,该条记录的使用者会显示为 `xxxxxx`

View File

@@ -686,7 +686,7 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getHistories
- appId - 应用 Id
- offset - 偏移量,即从第几条数据开始取
- pageSize - 记录数量
- source - 对话源
- source - 对话源。source=api表示获取通过 API 创建的对话(不会获取到页面上的对话记录)
{{% /alert %}}
{{< /markdownify >}}

View File

@@ -733,6 +733,21 @@ data 为集合的 ID。
{{< tab tabName="请求示例" >}}
{{< markdownify >}}
**4.8.19+**
```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/listv2' \
--header 'Authorization: Bearer {{authorization}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"offset":0,
"pageSize": 10,
"datasetId":"6593e137231a2be9c5603ba7",
"parentId": null,
"searchText":""
}'
```
**4.8.19-(不再维护)**
```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/list' \
--header 'Authorization: Bearer {{authorization}}' \
@@ -753,7 +768,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
{{< markdownify >}}
{{% alert icon=" " context="success" %}}
- pageNum: 页码(选填)
- offset: 偏移量
- pageSize: 每页数量最大30选填
- datasetId: 知识库的ID(必填)
- parentId: 父级Id选填
@@ -773,9 +788,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio
"statusText": "",
"message": "",
"data": {
"pageNum": 1,
"pageSize": 10,
"data": [
"list": [
{
"_id": "6593e137231a2be9c5603ba9",
"parentId": null,
@@ -1175,7 +1188,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/v2/l
}'
```
**4.6.7+**
**4.6.7-(即将弃用)**
```bash
curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list' \
@@ -1197,8 +1210,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list
{{% alert icon=" " context="success" %}}
- pageNum: 偏移量(选填)
- pageNum: 页码(选填)
- offset: 偏移量(选填)
- pageSize: 每页数量最大30选填
- collectionId: 集合的ID必填
- searchText: 模糊搜索词(选填)
@@ -1218,9 +1230,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/data/list
"statusText": "",
"message": "",
"data": {
"pageNum": 1,
"pageSize": 10,
"data": [
"list": [
{
"_id": "65abd4b29d1448617cba61db",
"datasetId": "65abc9bd9d1448617cba5e6c",

View File

@@ -1,5 +1,5 @@
---
title: 'V4.8.18(进行中)'
title: 'V4.8.18'
description: 'FastGPT V4.8.18 更新说明'
icon: 'upgrade'
draft: false
@@ -9,6 +9,12 @@ weight: 806
## 更新指南
### 1. 更新镜像:
- 更新 fastgpt 镜像 tag: v4.8.18-fix
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.18-fix
- Sandbox 镜像无需更新
### 2. 运行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
@@ -24,14 +30,18 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4818' \
## 完整更新内容
1. 新增 - 支持部门架构权限模式
2. 新增 - 支持配置自定跨域安全策略,默认全开
3. 优化 - 分享链接随机生成用户头像
4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储
5. 优化 - Mongo 全文索引表分离
6. 优化 - 知识库检索查询语句合并,同时减少查库数量
7. 优化 - 文件编码检测,减少 CSV 文件乱码概率
8. 优化 - 异步读取文件内容,减少进程阻塞
9. 优化 - 文件阅读HTML 直接下载,不允许在线阅读
10. 修复 - HTML 文件上传base64 图片无法自动转图片链接
11. 修复 - 插件计费错误。
1. 新增 - 支持通过 JSON 配置直接创建应用
2. 新增 - 支持通过 CURL 脚本快速创建 HTTP 插件
3. 新增 - 商业版支持部门架构权限模式
4. 新增 - 支持配置自定跨域安全策略,默认全开
5. 新增 - 补充私有部署,模型问题排查文档
6. 优化 - HTTP Body 增加特殊处理,解决字符串变量带换行时无法解析问题
7. 优化 - 分享链接随机生成用户头像
8. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储
9. 优化 - Mongo 全文索引表分离
10. 优化 - 知识库检索查询语句合并,同时减少查库数量
11. 优化 - 文件编码检测,减少 CSV 文件乱码概率。
12. 优化 - 异步读取文件内容,减少进程阻塞。
13. 优化 - 文件阅读HTML 直接下载,不允许在线阅读。
14. 修复 - HTML 文件上传base64 图片无法自动转图片链接。
15. 修复 - 插件计费错误。

View File

@@ -0,0 +1,22 @@
---
title: 'V4.8.19(进行中)'
description: 'FastGPT V4.8.19 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 806
---
## 完整更新内容
1. 新增 - 工作流知识库检索支持按知识库权限进行过滤。
2. 新增 - 飞书/语雀知识库查看原文。
3. 新增 - 流程等待插件,可以等待 n 毫秒后继续执行流程。
4. 优化 - 成员列表分页加载。
5. 优化 - 统一分页加载代码。
6. 优化 - 对话页面加载时,可配置是否为独立页面。
7. 修复 - 语雀文件库导入时,嵌套文件内容无法展开的问题。
8. 修复 - 工作流编排中LLM 参数无法关闭问题。
9. 修复 - 工作流编排中,代码运行节点还原模板问题。
10. 修复 - HTTP 接口适配对象字符串解析。

View File

@@ -7,7 +7,7 @@ toc: true
weight: 908
---
## 工作流中多轮对话场景中如何使连续问题被问题分类节点正确的归类
## 多轮对话中如何使连续问题被问题分类节点正确的归类
问题分类节点具有获取上下文信息的能力,当处理两个关联性较大的问题时,模型的判断准确性往往依赖于这两个问题之间的联系和模型的能力。例如,当用户先问“我该如何使用这个功能?”接着又询问“这个功能有什么限制?”时,模型借助上下文信息,就能够更精准地理解并响应。
@@ -15,10 +15,68 @@ weight: 908
建议:构建批量运行脚本进行测试,评估问题分类的准确性。
## 系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题
## 定时执行的时机问题
发布后,后台生效。
系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题:
定时执行会在应用发布后生效,会在后台生效。
## V4.8.18-FIX2中提到“ 1. 修复 HTTP 节点, {{}} 格式引用变量兼容问题。建议尽快替换 / 模式取变量, {{}} 语法已弃用。”替换{{}}引用格式是仅仅只有在http节点还是所有节点的都会有影响
只有 http 节点用到这个语法。
## 工作流类型的应用在运行预览可以正常提问返回,但是发布免登录窗口之后有问题。
一般是没正确发布,在工作流右上角点击【保存并发布】。
## 如何解决猜你想问使用中文回答显示
注意需要更新到V4.8.17及以上,把猜你想问的提示词改成中文。
![](/imgs/quizApp2.png)
## AI对话回答要求中的Markdown语法取消
在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。
修改知识库默认提示词, 默认用的是标准模板提示词,会要求按 Markdown 输出,可以去除该要求:
| | |
| --- | --- |
| ![](/imgs/image-83.png) | ![](/imgs/image-84.png) |
## 应用在不同来源效果不一致
Q: 应用在调试和正式发布后,效果不一致;在 API 调用时,效果不一致。
A: 通常是由于上下文不一致导致,可以在对话日志中,找到对应的记录,并查看运行详情来进行比对。
| | | |
| --- | --- | --- |
| ![](/imgs/image-85.png) | ![](/imgs/image-86.png) | ![](/imgs/image-87.png) |
在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。
## 工作流操作一个工作流以一个问题分类节点开始根据不同的分类导入到不同的分支访问相应的知识库和AI对话AI对话返回内容后怎么样不进入问题分类节点而是将问题到知识库搜索然后把历史记录一起作为背景再次AI查询。
做个判断器如果是初次开始对话也就是历史记录为0就走问题分类不为零直接走知识库和ai。
## 实时对话,设置 fastgpt 定时,比如每隔 3000MS 去拿一次 webhook发送过来的消息到AI页面
定时执行没有这么高频率的去拿信息的,想要实现在企微里面的实时对话的机器人,
目前通过低代码的工作流构建应该是不行的,只能自己写代码,然后去调用 FastGPT 的 APIKey 回复。企业微信似乎没有提供「自动监听」群聊消息的接口(或是通过 at 机器人这种触发消息推送)。应该只能发消息给应用,接收这个 https://developer.work.weixin.qq.com/document/path/90238 文档中的消息推送实现实时对话。或者是定时去拿群聊消息通过这个文档所示的接口https://developer.work.weixin.qq.com/document/path/98914然后用这个接口 https://developer.work.weixin.qq.com/document/path/90248 去推送消息。
## 工作流连接数据库
工作流提供该连接数据库功能,用这个数据库连接的 plugin 可以实现 text2SQL但是相对危险不建议做写入等操作。
![](/imgs/quizApp1.png)
## 关于循环体,协助理解循环体的循环条件和终止条件、循环的方式,循环体内参数调用后、在循环体内属于是局部作用域的参数还是全局作用域的参数
可理解为 for 函数,传一个数组,每个数据都执行一次。
## 公式无法正常显示
添加相关提示词,引导模型按 Markdown 输出公式
```bash
Latex inline: \(x^2\)
Latex block: $$e=mc^2$$
```

View File

@@ -16,6 +16,25 @@ weight: 910
* **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。
## 知识库支持Excel类文件的导入
xlsx等都可以上传的不止支持CSV。
## 知识库tokens的计算方式
统一按gpt3.5标准。
## 误删除重排模型后重排模型怎么加入到fastgpt
![](/imgs/dataset3.png)
config.json文件里面配置后就可以勾选重排模型
## 线上平台上创建了应用和知识库,到期之后如果短期内不续费,数据是否会被清理。
免费版是三十天不登录后清空知识库,应用不会动。其他付费套餐到期后自动切免费版。
![](/imgs/dataset4.png)
## 基于知识库的查询但是问题相关的答案过多。ai回答到一半就不继续回答。
FastGPT回复长度计算公式:
@@ -37,7 +56,7 @@ FastGPT回复长度计算公式:
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
另外私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。
@@ -61,4 +80,4 @@ FastGPT回复长度计算公式:
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
另外,私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出

View File

@@ -8,4 +8,14 @@ weight: 918
## oneapi 官网是哪个
只有开源的 README没官网GitHub: https://github.com/songquanpeng/one-api
只有开源的 README没官网GitHub: https://github.com/songquanpeng/one-api
## 想做多用户和多chat key
![](/imgs/other1.png)
![](/imgs/other2.png)
多用户问题:只能采取二开或者商业版
多chat key问题1. 私有化部署情况下oneapi解决。2. Saas或者商业版中可以为每个团队设置单独的key。
![](/imgs/other3.png)

View File

@@ -22,10 +22,11 @@ FastGPT v4.8.16 版本开始,商业版用户支持飞书知识库导入,用
## 2. 配置应用权限
创建应用后,进入应用可以配置相关权限,这里需要增加个权限:
创建应用后,进入应用可以配置相关权限,这里需要增加**3个权限**
1. 获取云空间文件夹下的云文档清单
2. 查看新版文档
3. 查看、评论、编辑和管理云空间中所有文件
![alt text](/imgs/image-41.png)

View File

@@ -1,6 +1,6 @@
---
title: "循环执行"
description: "FastGPT 循环运行节点介绍和使用"
title: "批量运行"
description: "FastGPT 批量运行节点介绍和使用"
icon: "input"
draft: false
toc: true
@@ -9,15 +9,15 @@ weight: 260
## 节点概述
【**循环运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。
【**批量运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。
这个节点的设计灵感来自编程语言中的循环结构,但以可视化的方式呈现。
![循环运行节点](/imgs/fastgpt-loop-node.png)
![批量运行节点](/imgs/fastgpt-loop-node.png)
> 在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。
【**循环运行**】节点本质上也是一个 Function它的主要职责是自动化地重复执行特定的工作流程。
【**批量运行**】节点本质上也是一个 Function它的主要职责是自动化地重复执行特定的工作流程。
## 核心特性
@@ -41,9 +41,9 @@ weight: 260
## 应用场景
【**循环运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,循环运行节点能显著提升工作流的效率和自动化程度。
【**批量运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,批量运行节点能显著提升工作流的效率和自动化程度。
【**循环运行**】节点特别适合以下场景:
【**批量运行**】节点特别适合以下场景:
1. **批量数据处理**
- 批量翻译文本
@@ -64,7 +64,7 @@ weight: 260
### 输入参数设置
【**循环运行**】节点需要配置两个核心输入参数:
【**批量运行**】节点需要配置两个核心输入参数:
1. **数组 (必填)**:接收一个数组类型的输入,可以是:
- 字符串数组 (`Array<string>`)
@@ -95,7 +95,7 @@ weight: 260
### 批量处理数组
假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是循环运行节点最基础也最常见的应用场景。
假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是批量运行节点最基础也最常见的应用场景。
#### 实现步骤
@@ -114,9 +114,9 @@ weight: 260
return { textArray: texts };
```
2. 配置循环运行节点
2. 配置批量运行节点
![配置循环运行节点](/imgs/fastgpt-loop-node-example-2.png)
![配置批量运行节点](/imgs/fastgpt-loop-node-example-2.png)
- 数组输入:选择上一步代码运行节点的输出变量 `textArray`。
- 循环体内添加一个【AI 对话】节点,用于处理每个文本。这里我们输入的 prompt 为:`请将这段文本翻译成英文`。
@@ -128,7 +128,7 @@ weight: 260
![运行流程](/imgs/fastgpt-loop-node-example-3.png)
1. 【代码运行】节点执行,生成测试数组
2. 【循环运行】节点接收数组,开始遍历
2. 【批量运行】节点接收数组,开始遍历
3. 对每个数组元素:
- 【AI 对话】节点处理当前元素
- 【指定回复】节点输出翻译后的文本
@@ -144,7 +144,7 @@ weight: 260
- 需要维护上下文的连贯性
- 翻译质量需要多轮优化
【**循环运行**】节点可以很好地解决这些问题。
【**批量运行**】节点可以很好地解决这些问题。
#### 实现步骤
@@ -281,9 +281,9 @@ weight: 260
这里我们用到了 [Jina AI 开源的一个强大的正则表达式](https://x.com/JinaAI_/status/1823756993108304135),它能利用所有可能的边界线索和启发式方法来精确切分文本。
2. 配置循环运行节点
2. 配置批量运行节点
![配置循环运行节点](/imgs/fastgpt-loop-node-example-5.png)
![配置批量运行节点](/imgs/fastgpt-loop-node-example-5.png)
- 数组输入:选择上一步代码运行节点的输出变量 `chunks`。
- 循环体内添加一个【代码运行】节点,对源文本进行格式化。

View File

@@ -54,7 +54,7 @@ FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入
1. **项目开源**
FastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。
FastGPT 遵循**附加条件 Apache License 2.0 开源协议**,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。
2. **独特的 QA 结构**

View File

@@ -53,8 +53,9 @@ FastGPT 商业版软件根据不同的部署方式,分为 3 类收费模式。
{{< table "table-hover table-striped-columns" >}}
| 部署方式 | 特有服务 | 上线时长 | 标品价格 |
| ---- | ---- | ---- | ---- |
| Sealos全托管 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 6000元起/月3个月起<br>或<br>60000元起/年 |
| 自有服务器部署 | 1. 6个版本免费升级支持。 | 14天内 | 具体价格可[联系咨询](https://fael3z0zfze.feishu.cn/share/base/form/shrcnRxj3utrzjywsom96Px4sud) |
| Sealos全托管 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 10000元起/月3个月起<br>或<br>120000元起/年<br>8C32G 资源,额外资源另外收费。 |
| Sealos全托管多节点 | 1. 有效期内免费升级。<br>2. 免运维服务&数据库。 | 半天 | 22000元起/月3个月起<br>或<br>264000元起/年<br>32C128G 资源,额外资源另外收费。 |
| 自有服务器部署 | 1. 6个版本免费升级支持。 | 14天内 | 具体价格和优惠可[联系咨询](https://fael3z0zfze.feishu.cn/share/base/form/shrcnRxj3utrzjywsom96Px4sud) |
{{< /table >}}
{{% alert icon="🤖 " context="success" %}}

View File

@@ -5,8 +5,9 @@ icon: 'currency_yen'
draft: false
toc: true
weight: 1102
type: redirect
target: https://cloud.tryfastgpt.ai/price
---
线上版价格请查看:[https://cloud.tryfastgpt.ai/price](https://cloud.tryfastgpt.ai/price)
线上版价格按套餐订阅模式,具体价格和计费请查看(请正确选择版本,账号不互通):
- [海外版](https://cloud.tryfastgpt.ai/price)
- [国内版](https://cloud.fastgpt.cn/price)

View File

@@ -212,7 +212,7 @@ export default async function (ctx: FunctionContext): Promise<IResponse>{
![](/imgs/translate13.png)
## 循环执
## 批量运
长文反思翻译比较关键的一个部分,就是对多个文本块进行循环反思翻译

View File

@@ -32,11 +32,11 @@ weight: 602
1. ### 创建应用模板
应用模板配置以及相关资源,都会在 **projects/app/public/appMarketTemplates** 目录下。
应用模板配置以及相关资源,都会在 **packages/templates/src** 目录下。
![](/imgs/template_submission2.png)
1. **projects/app/public/appMarketTemplates** 目录下,创建一个文件夹,名称为模板对应的 id。
1. 在**packages/templates/src** 目录下,创建一个文件夹,名称为模板对应的 id。
2. 在刚刚创建的文件夹中,再创建一个 **template.json** 文件,复制粘贴并填写如下配置:
```JSON
@@ -83,4 +83,4 @@ weight: 602
- 写清楚模板的介绍和功能
- 配上模板运行的效果图
- 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key需要附上对应的地址和教程后续我们会加入到文档中。
- 模板参数填写说明,需要在 PR 中写清楚。例如,有些模板需要去某个提供商申请 key需要附上对应的地址和教程后续我们会加入到文档中。

View File

@@ -91,9 +91,9 @@ weight: 604
这个过程不仅提高了效率,还最大限度地减少了人为错误的可能性。
## 循环执
## 批量运
为了处理整个长字幕文件,我们需要一个循环执行机制。这是通过一个简单但有效的判断模块实现的:
为了处理整个长字幕文件,我们需要一个批量运行机制。这是通过一个简单但有效的判断模块实现的:
1. 检查当前翻译的文本块是否为最后一个。
2. 如果不是,则将工作流重定向到格式化原文本块节点。

View File

@@ -159,6 +159,14 @@ services:
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
- STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes:
- ./config.json:/app/data/config.json

View File

@@ -116,6 +116,14 @@ services:
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
- STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes:
- ./config.json:/app/data/config.json

View File

@@ -97,6 +97,14 @@ services:
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
- STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes:
- ./config.json:/app/data/config.json

View File

@@ -3,14 +3,14 @@ import { i18nT } from '../../../../web/i18n/utils';
/* team: 503000 */
export enum UserErrEnum {
unAuthRole = 'unAuthRole',
binVisitor = 'binVisitor',
account_psw_error = 'account_psw_error',
balanceNotEnough = 'balanceNotEnough',
unAuthSso = 'unAuthSso'
}
const errList = [
{
statusText: UserErrEnum.binVisitor,
message: i18nT('common:code_error.user_error.bin_visitor')
statusText: UserErrEnum.account_psw_error,
message: i18nT('common:code_error.account_error')
},
{
statusText: UserErrEnum.balanceNotEnough,

View File

@@ -5,6 +5,6 @@ export const getErrText = (err: any, def = ''): any => {
typeof err === 'string'
? err
: err?.response?.data?.message || err?.response?.message || err?.message || def;
msg && console.log('error =>', msg);
// msg && console.log('error =>', msg);
return replaceSensitiveText(msg);
};

View File

@@ -0,0 +1,44 @@
import parse from '@bany/curl-to-json';
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = {
get: 'GET',
post: 'POST',
put: 'PUT',
delete: 'DELETE',
patch: 'PATCH'
};
export const parseCurl = (curlContent: string) => {
const parsed = parse(curlContent);
if (!parsed.url) {
throw new Error('url not found');
}
const newParams = Object.keys(parsed.params || {}).map((key) => ({
key,
value: parsed.params?.[key],
type: 'string'
}));
const newHeaders = Object.keys(parsed.header || {}).map((key) => ({
key,
value: parsed.header?.[key],
type: 'string'
}));
const newBody = JSON.stringify(parsed.data, null, 2);
const bodyArray = Object.keys(parsed.data || {}).map((key) => ({
key,
value: parsed.data?.[key],
type: 'string'
}));
return {
url: parsed.url,
method: methodMap[parsed.method?.toLowerCase() as RequestMethod] || 'GET',
params: newParams,
headers: newHeaders,
body: newBody,
bodyArray
};
};

View File

@@ -40,7 +40,7 @@ export type FastGPTConfigFileType = {
export type FastGPTFeConfigsType = {
show_emptyChat?: boolean;
register_method?: ['email' | 'phone'];
register_method?: ['email' | 'phone' | 'sync'];
login_method?: ['email' | 'phone']; // Attention: login method is diffrent with oauth
find_password_method?: ['email' | 'phone'];
bind_notification_method?: ['email' | 'phone'];
@@ -76,7 +76,6 @@ export type FastGPTFeConfigsType = {
wecom?: {
corpid?: string;
agentid?: string;
secret?: string;
};
microsoft?: {
clientId?: string;

View File

@@ -33,7 +33,7 @@ export const defaultWhisperConfig: AppWhisperConfigType = {
export const defaultQGConfig: AppQGConfigType = {
open: false,
model: 'gpt-4o-mini',
customPrompt: PROMPT_QUESTION_GUIDE
customPrompt: ''
};
export const defaultChatInputGuideConfig = {

View File

@@ -12,8 +12,9 @@ import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/use
import { StoreEdgeItemType } from '../workflow/type/edge';
import { AppPermission } from '../../support/permission/app/controller';
import { ParentIdType } from '../../common/parentFolder/type';
import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
import { FlowNodeInputTypeEnum } from '../../core/workflow/node/constant';
import { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type';
import { SourceMemberType } from '../../support/user/type';
export type AppSchema = {
_id: string;
@@ -63,6 +64,7 @@ export type AppListItemType = {
permission: AppPermission;
inheritPermission?: boolean;
private?: boolean;
sourceMember: SourceMemberType;
};
export type AppDetailType = AppSchema & {

View File

@@ -5,6 +5,8 @@ import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getAppChatConfig } from '../workflow/utils';
import { StoreNodeItemType } from '../workflow/type/node';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants';
export const getDefaultAppForm = (): AppSimpleEditFormType => {
return {
@@ -127,3 +129,20 @@ export const appWorkflow2Form = ({
return defaultAppForm;
};
export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFormType) => {
if (!config) return '';
if ('aiSettings' in config) {
return AppTypeEnum.simple;
}
if (!('nodes' in config)) return '';
if (config.nodes.some((node) => node.flowNodeType === 'workflowStart')) {
return AppTypeEnum.workflow;
}
if (config.nodes.some((node) => node.flowNodeType === 'pluginInput')) {
return AppTypeEnum.plugin;
}
return '';
};

View File

@@ -1,5 +1,7 @@
import { TeamMemberStatusEnum } from 'support/user/team/constant';
import { StoreEdgeItemType } from '../workflow/type/edge';
import { AppChatConfigType, AppSchema } from './type';
import { SourceMemberType } from 'support/user/type';
export type AppVersionSchemaType = {
_id: string;
@@ -20,4 +22,5 @@ export type VersionListItemType = {
time: Date;
isPublish: boolean | undefined;
tmbId: string;
sourceMember: SourceMemberType;
};

View File

@@ -5,6 +5,7 @@ export type APIFileItem = {
type: 'file' | 'folder';
updateTime: Date;
createTime: Date;
hasChild?: boolean;
};
export type APIFileServer = {

View File

@@ -11,6 +11,7 @@ import {
import { DatasetPermission } from '../../support/permission/dataset/controller';
import { Permission } from '../../support/permission/controller';
import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset';
import { SourceMemberType } from 'support/user/type';
export type DatasetSchemaType = {
_id: string;
@@ -165,6 +166,7 @@ export type DatasetListItemType = {
vectorModel: VectorModelItemType;
inheritPermission: boolean;
private?: boolean;
sourceMember?: SourceMemberType;
};
export type DatasetItemType = Omit<DatasetSchemaType, 'vectorModel' | 'agentModel'> & {

View File

@@ -34,7 +34,7 @@ export function getSourceNameIcon({
}
} catch (error) {}
return 'file/fill/manual';
return 'file/fill/file';
}
/* get dataset data default index */

View File

@@ -152,6 +152,7 @@ export enum NodeInputKeyEnum {
datasetSearchExtensionModel = 'datasetSearchExtensionModel',
datasetSearchExtensionBg = 'datasetSearchExtensionBg',
collectionFilterMatch = 'collectionFilterMatch',
authTmbId = 'authTmbId',
// concat dataset
datasetQuoteList = 'system_datasetQuoteList',

View File

@@ -41,6 +41,10 @@ export type ChatDispatchProps = {
teamId: string;
tmbId: string; // App tmbId
};
runningUserInfo: {
teamId: string;
tmbId: string;
};
uid: string; // Who run this workflow
chatId?: string;

View File

@@ -339,6 +339,7 @@ export function replaceEditorVariable({
const output = node.outputs.find((output) => output.id === id);
if (output) return formatVariableValByType(output.value, output.valueType);
// Use the node's input as the variable value(Example: HTTP data will reference its own dynamic input)
const input = node.inputs.find((input) => input.key === id);
if (input) return getReferenceVariableValue({ value: input.value, nodes, variables });
})();

View File

@@ -89,6 +89,13 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
valueType: WorkflowIOValueTypeEnum.string,
value: ''
},
{
key: NodeInputKeyEnum.authTmbId,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.boolean,
value: false
},
{
...Input_Template_UserChatInput,
toolDescription: i18nT('workflow:content_to_search')

View File

@@ -49,9 +49,10 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
debugLabel?: string;
description?: string; // field desc
required?: boolean;
toolDescription?: string; // If this field is not empty, it is entered as a tool
enum?: string;
toolDescription?: string; // If this field is not empty, it is entered as a tool
// render components params
canEdit?: boolean; // dynamic inputs
isPro?: boolean; // Pro version field

View File

@@ -89,6 +89,7 @@ export type NodeTemplateListItemType = {
hasTokenFee?: boolean; // 是否配置积分
instructions?: string; // 使用说明
courseUrl?: string; // 教程链接
sourceMember?: SourceMember;
};
export type NodeTemplateListType = {

View File

@@ -14,7 +14,8 @@
"openai": "4.61.0",
"openapi-types": "^12.1.3",
"json5": "^2.2.3",
"timezones-list": "^3.0.2"
"timezones-list": "^3.0.2",
"@bany/curl-to-json": "^1.2.8"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",

View File

@@ -12,6 +12,7 @@ export type CreateTeamProps = {
avatar?: string;
defaultTeam?: boolean;
memberName?: string;
memberAvatar?: string;
};
export type UpdateTeamProps = Omit<ThirdPartyAccountType, 'externalWorkflowVariable'> & {
name?: string;

View File

@@ -5,8 +5,6 @@ export const OrgMemberCollectionName = 'team_org_members';
export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`;
// export enum OrgMemberRole {
// owner = 'owner',
// admin = 'admin',
// member = 'member'
// }
export enum SyncOrgSourceEnum {
wecom = 'wecom'
}

View File

@@ -13,6 +13,7 @@ type OrgSchemaType = {
};
type OrgMemberSchemaType = {
_id: string;
teamId: string;
orgId: string;
tmbId: string;
@@ -20,6 +21,6 @@ type OrgMemberSchemaType = {
type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string;
members: OrgMemberSchemaType[];
permission: TeamPermission;
members: OrgMemberSchemaType[];
};

View File

@@ -44,6 +44,7 @@ export type TeamMemberSchema = {
name: string;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
avatar: string;
defaultTeam: boolean;
};

View File

@@ -1,12 +1,12 @@
import { TeamPermission } from '../permission/user/controller';
import { UserStatusEnum } from './constant';
import { TeamMemberStatusEnum } from './team/constant';
import { TeamTmbItemType } from './team/type';
export type UserModelSchema = {
_id: string;
username: string;
password: string;
avatar: string;
promotionRate: number;
inviterId?: string;
openaiKey: string;
@@ -22,7 +22,7 @@ export type UserModelSchema = {
export type UserType = {
_id: string;
username: string;
avatar: string;
avatar: string; // it should be team member's avatar after 4.8.18
timezone: string;
promotionRate: UserModelSchema['promotionRate'];
team: TeamTmbItemType;
@@ -30,3 +30,9 @@ export type UserType = {
notificationAccount?: string;
permission: TeamPermission;
};
export type SourceMemberType = {
name: string;
avatar: string;
status: `${TeamMemberStatusEnum}`;
};

View File

@@ -12,7 +12,8 @@ const staticPluginList = [
'DingTalkWebhook',
'WeWorkWebhook',
'google',
'bing'
'bing',
'delay'
];
// Run in worker thread (Have npm packages)
const packagePluginList = [

View File

@@ -0,0 +1,18 @@
import { delay } from '@fastgpt/global/common/system/utils';
type Props = {
ms: number;
};
type Response = Promise<Number>;
const main = async ({ ms }: Props): Response => {
if (typeof ms !== 'number' || ms <= 0 || ms > 300000) {
return ms;
}
await delay(ms);
return ms;
};
export default main;

View File

@@ -0,0 +1,318 @@
{
"author": "collin",
"version": "4817",
"name": "流程等待",
"avatar": "core/workflow/template/sleep",
"intro": "让工作流等待指定时间后运行",
"showStatus": true,
"weight": 1,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "workflow:template.plugin_start",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 627.6352390819724,
"y": -165.05298493910118
},
"version": "481",
"inputs": [
{
"renderTypeList": ["numberInput", "reference"],
"selectedTypeIndex": 0,
"valueType": "number",
"canEdit": true,
"key": "延迟时长",
"label": "延迟时长",
"description": "需要暂停的时间,单位毫秒",
"defaultValue": 1000,
"list": [
{
"label": "",
"value": ""
}
],
"maxFiles": 5,
"canSelectFile": true,
"canSelectImg": true,
"required": true,
"toolDescription": "需要暂停的时间,单位毫秒",
"max": 300000,
"min": 1
}
],
"outputs": [
{
"id": "ms",
"valueType": "number",
"key": "延迟时长",
"label": "延迟时长",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 1921.839722563351,
"y": -160.05298493910115
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "any",
"canEdit": true,
"key": "result",
"label": "result",
"isToolOutput": true,
"description": "",
"required": true,
"value": ["zCJC6zw7c14i", "httpRawResponse"]
}
],
"outputs": []
},
{
"nodeId": "pluginConfig",
"name": "common:core.module.template.system_config",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": 184.66337662472682,
"y": -216.05298493910115
},
"version": "4811",
"inputs": [],
"outputs": []
},
{
"nodeId": "zCJC6zw7c14i",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1154.4041630064592,
"y": -455.0529849391012
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "delay",
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n\"ms\": {{$pluginInput.ms$}}\n}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "输出字段提取",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"description": "可以通过 JSONPath 语法来提取响应值中的指定字段",
"valueDesc": ""
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "zCJC6zw7c14i",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "zCJC6zw7c14i-target-left"
},
{
"source": "zCJC6zw7c14i",
"target": "pluginOutput",
"sourceHandle": "zCJC6zw7c14i-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": ""
}
}
}

View File

@@ -0,0 +1,21 @@
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { ApiRequestProps } from '../../type/next';
export function parsePaginationRequest(req: ApiRequestProps) {
const {
pageSize = 10,
pageNum = 1,
offset = 0
} = Object.keys(req.body).includes('pageSize')
? req.body
: Object.keys(req.query).includes('pageSize')
? req.query
: {};
if (!pageSize || (pageNum === undefined && offset === undefined)) {
throw new Error(CommonErrEnum.missingParams);
}
return {
pageSize: Number(pageSize),
offset: offset ? Number(offset) : (Number(pageNum) - 1) * Number(pageSize)
};
}

View File

@@ -2,7 +2,7 @@ import type { NextApiResponse, NextApiRequest } from 'next';
import NextCors from 'nextjs-cors';
export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',');
const origin = req.headers.origin;

View File

@@ -99,7 +99,13 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
if (files.some((file) => !file.id || !file.name || typeof file.type === 'undefined')) {
return Promise.reject('Invalid file data format');
}
return files;
const formattedFiles = files.map((file) => ({
...file,
hasChild: file.type === 'folder'
}));
return formattedFiles;
};
const getFileContent = async ({ teamId, apiFileId }: { teamId: string; apiFileId: string }) => {

View File

@@ -284,7 +284,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
{
_id: { $in: collectionIdList }
},
'_id name fileId rawLink externalFileId externalFileUrl',
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
@@ -525,7 +525,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink externalFileId externalFileUrl',
'_id name fileId rawLink apiFileId externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);

View File

@@ -0,0 +1,39 @@
import { getTmbInfoByTmbId } from '../../support/user/team/controller';
import { getResourcePermission } from '../../support/permission/controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller';
// TODO: 需要优化成批量获取权限
export const filterDatasetsByTmbId = async ({
datasetIds,
tmbId
}: {
datasetIds: string[];
tmbId: string;
}) => {
const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });
// First get all permissions
const permissions = await Promise.all(
datasetIds.map(async (datasetId) => {
const per = await getResourcePermission({
teamId,
tmbId,
resourceId: datasetId,
resourceType: PerResourceTypeEnum.dataset
});
if (per === undefined) return false;
const datasetPer = new DatasetPermission({
per,
isOwner: tmbPer.isOwner
});
return datasetPer.hasReadPer;
})
);
// Then filter datasetIds based on permissions
return datasetIds.filter((_, index) => permissions[index]);
};

View File

@@ -210,7 +210,7 @@ export const runToolWithToolChoice = async (
properties[item.key] = {
...jsonSchema,
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
enum: item.enum?.split('\n').filter(Boolean) || undefined
};
});
@@ -227,6 +227,7 @@ export const runToolWithToolChoice = async (
}
};
});
// Filter histories by maxToken
const filterMessages = (
await filterGPTMessageByMaxTokens({

View File

@@ -17,6 +17,7 @@ import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { checkTeamReRankPermission } from '../../../../support/permission/teamLimit';
import { MongoDataset } from '../../../dataset/schema';
import { i18nT } from '../../../../../web/i18n/utils';
import { filterDatasetsByTmbId } from '../../../dataset/utils';
type DatasetSearchProps = ModuleDispatchProps<{
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
@@ -29,6 +30,7 @@ type DatasetSearchProps = ModuleDispatchProps<{
[NodeInputKeyEnum.datasetSearchExtensionModel]: string;
[NodeInputKeyEnum.datasetSearchExtensionBg]: string;
[NodeInputKeyEnum.collectionFilterMatch]: string;
[NodeInputKeyEnum.authTmbId]: boolean;
}>;
export type DatasetSearchResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
@@ -39,6 +41,7 @@ export async function dispatchDatasetSearch(
): Promise<DatasetSearchResponse> {
const {
runningAppInfo: { teamId },
runningUserInfo: { tmbId },
histories,
node,
params: {
@@ -52,7 +55,8 @@ export async function dispatchDatasetSearch(
datasetSearchUsingExtensionQuery,
datasetSearchExtensionModel,
datasetSearchExtensionBg,
collectionFilterMatch
collectionFilterMatch,
authTmbId = false
}
} = props as DatasetSearchProps;
@@ -64,18 +68,20 @@ export async function dispatchDatasetSearch(
return Promise.reject(i18nT('common:core.chat.error.Select dataset empty'));
}
const emptyResult = {
quoteQA: [],
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
query: '',
limit,
searchMode
},
nodeDispatchUsages: [],
[DispatchNodeResponseKeyEnum.toolResponses]: []
};
if (!userChatInput) {
return {
quoteQA: [],
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: 0,
query: '',
limit,
searchMode
},
nodeDispatchUsages: [],
[DispatchNodeResponseKeyEnum.toolResponses]: []
};
return emptyResult;
}
// query extension
@@ -83,13 +89,24 @@ export async function dispatchDatasetSearch(
? getLLMModel(datasetSearchExtensionModel)
: undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({
query: userChatInput,
extensionModel,
extensionBg: datasetSearchExtensionBg,
histories: getHistories(6, histories)
});
const [{ concatQueries, rewriteQuery, aiExtensionResult }, datasetIds] = await Promise.all([
datasetSearchQueryExtension({
query: userChatInput,
extensionModel,
extensionBg: datasetSearchExtensionBg,
histories: getHistories(6, histories)
}),
authTmbId
? filterDatasetsByTmbId({
datasetIds: datasets.map((item) => item.datasetId),
tmbId
})
: Promise.resolve(datasets.map((item) => item.datasetId))
]);
if (datasetIds.length === 0) {
return emptyResult;
}
// console.log(concatQueries, rewriteQuery, aiExtensionResult);
// get vector
@@ -110,7 +127,7 @@ export async function dispatchDatasetSearch(
model: vectorModel.model,
similarity,
limit,
datasetIds: datasets.map((item) => item.datasetId),
datasetIds,
searchMode,
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)),
collectionFilterMatch

View File

@@ -487,16 +487,16 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
if (input.key === dynamicInput?.key) return;
// Skip some special key
if (input.key === NodeInputKeyEnum.childrenNodeIdList) {
if (
[NodeInputKeyEnum.childrenNodeIdList, NodeInputKeyEnum.httpJsonBody].includes(
input.key as any
)
) {
params[input.key] = input.value;
return;
}
// replace {{xx}} variables
// let value = replaceVariable(input.value, variables);
// replace {{$xx.xx$}} variables
// replace {{$xx.xx$}} and {{xx}} variables
let value = replaceEditorVariable({
text: input.value,
nodes: runtimeNodes,
@@ -606,6 +606,11 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
};
}
// Error
if (dispatchRes?.responseData?.error) {
addLog.warn('workflow error', dispatchRes.responseData.error);
}
return {
node,
runStatus: 'run',

View File

@@ -72,6 +72,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
showStatus: false
};
});
const runtimeVariables = {
...filterSystemVariables(props.variables),
appId: String(plugin.id)

View File

@@ -2,6 +2,7 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/
import {
NodeInputKeyEnum,
NodeOutputKeyEnum,
VARIABLE_NODE_ID,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import {
@@ -16,7 +17,9 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { getErrText } from '@fastgpt/global/common/error/utils';
import {
textAdaptGptResponse,
replaceEditorVariable
replaceEditorVariable,
formatVariableValByType,
getReferenceVariableValue
} from '@fastgpt/global/core/workflow/runtime/utils';
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller';
@@ -97,22 +100,86 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
const concatVariables = {
...variables,
...body,
// ...dynamicInput,
...systemVariables
};
const allVariables = {
const allVariables: Record<string, any> = {
[NodeInputKeyEnum.addInputParam]: concatVariables,
...concatVariables
};
// General data for variable substitutionExclude: json body)
const replaceStringVariables = (text: string) => {
return replaceVariable(
replaceEditorVariable({
text,
nodes: runtimeNodes,
variables: allVariables
}),
allVariables
);
return replaceEditorVariable({
text,
nodes: runtimeNodes,
variables: allVariables
});
};
/* Replace the JSON string to reduce parsing errors
1. Replace undefined values with null
2. Replace newline strings
*/
const replaceJsonBodyString = (text: string) => {
const valToStr = (val: any) => {
if (val === undefined) return 'null';
if (val === null) return 'null';
if (typeof val === 'object') return JSON.stringify(val);
if (typeof val === 'string') {
try {
const parsed = JSON.parse(val);
if (typeof parsed === 'object') {
return JSON.stringify(parsed);
}
return val;
} catch (error) {
const str = JSON.stringify(val);
return str.startsWith('"') && str.endsWith('"') ? str.slice(1, -1) : str;
}
}
return String(val);
};
// 1. Replace {{key.key}} variables
const regex1 = /\{\{\$([^.]+)\.([^$]+)\$\}\}/g;
const matches1 = [...text.matchAll(regex1)];
matches1.forEach((match) => {
const nodeId = match[1];
const id = match[2];
const variableVal = (() => {
if (nodeId === VARIABLE_NODE_ID) {
return variables[id];
}
// Find upstream node input/output
const node = runtimeNodes.find((node) => node.nodeId === nodeId);
if (!node) return;
const output = node.outputs.find((output) => output.id === id);
if (output) return formatVariableValByType(output.value, output.valueType);
const input = node.inputs.find((input) => input.key === id);
if (input)
return getReferenceVariableValue({ value: input.value, nodes: runtimeNodes, variables });
})();
const formatVal = valToStr(variableVal);
const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
text = text.replace(regex, () => formatVal);
});
// 2. Replace {{key}} variables
const regex2 = /{{([^}]+)}}/g;
const matches2 = text.match(regex2) || [];
const uniqueKeys2 = [...new Set(matches2.map((match) => match.slice(2, -2)))];
for (const key of uniqueKeys2) {
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => valToStr(allVariables[key]));
}
return text.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');
};
httpReqUrl = replaceStringVariables(httpReqUrl);
@@ -176,15 +243,12 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
}
if (!httpJsonBody) return {};
if (httpContentType === ContentTypes.json) {
httpJsonBody = replaceStringVariables(httpJsonBody);
const replaceJsonBody = httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');
// Json body, parse and return
const jsonParse = json5.parse(replaceJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse);
return removeSignJson;
httpJsonBody = replaceJsonBodyString(httpJsonBody);
console.log(httpJsonBody);
return json5.parse(httpJsonBody);
}
// Raw text, xml
httpJsonBody = replaceStringVariables(httpJsonBody);
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
} catch (error) {
@@ -335,40 +399,40 @@ async function fetchData({
};
}
function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}
// function replaceVariable(text: string, obj: Record<string, any>) {
// for (const [key, value] of Object.entries(obj)) {
// if (value === undefined) {
// text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
// } else {
// const replacement = JSON.stringify(value);
// const unquotedReplacement =
// replacement.startsWith('"') && replacement.endsWith('"')
// ? replacement.slice(1, -1)
// : replacement;
// text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => unquotedReplacement);
// }
// }
// return text || '';
// }
// function removeUndefinedSign(obj: Record<string, any>) {
// for (const key in obj) {
// if (obj[key] === UNDEFINED_SIGN) {
// obj[key] = undefined;
// } else if (Array.isArray(obj[key])) {
// obj[key] = obj[key].map((item: any) => {
// if (item === UNDEFINED_SIGN) {
// return undefined;
// } else if (typeof item === 'object') {
// removeUndefinedSign(item);
// }
// return item;
// });
// } else if (typeof obj[key] === 'object') {
// removeUndefinedSign(obj[key]);
// }
// }
// return obj;
// }
// Replace some special response from system plugin
async function replaceSystemPluginResponse({

View File

@@ -1,8 +1,8 @@
import { MongoTeamMember } from '../../user/team/teamMemberSchema';
import { checkTeamAIPoints } from '../teamLimit';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { TeamSchema } from '@fastgpt/global/support/user/team/type';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
const tmb = await MongoTeamMember.findById(tmbId, 'userId teamId')
@@ -18,7 +18,7 @@ export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
])
.lean();
if (!tmb) return Promise.reject(UserErrEnum.binVisitor);
if (!tmb) return Promise.reject(TeamErrEnum.notUser);
await checkTeamAIPoints(tmb.team._id);

View File

@@ -17,7 +17,6 @@ import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
@@ -151,13 +150,9 @@ export const getClbsAndGroupsWithInfo = async ({
$exists: true
}
})
.populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({
.populate<{ tmb: TeamMemberSchema }>({
path: 'tmb',
select: 'name userId role',
populate: {
path: 'user',
select: 'avatar'
}
select: 'name userId avatar'
})
.lean(),
MongoResourcePermission.find({

View File

@@ -47,14 +47,11 @@ export const OrgSchema = new Schema(
OrgSchema.virtual('members', {
ref: OrgMemberCollectionName,
localField: '_id',
foreignField: 'orgId'
foreignField: 'orgId',
match: function (this: OrgSchemaType) {
return { teamId: this.teamId };
}
});
// OrgSchema.virtual('permission', {
// ref: ResourcePermissionCollectionName,
// localField: '_id',
// foreignField: 'orgId',
// justOne: true
// });
try {
OrgSchema.index({

View File

@@ -41,7 +41,7 @@ export async function getUserDetail({
return {
_id: user._id,
username: user.username,
avatar: user.avatar,
avatar: tmb.avatar,
timezone: user.timezone,
promotionRate: user.promotionRate,
team: tmb,

View File

@@ -3,7 +3,6 @@ const { Schema } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils';
export const userCollectionName = 'users';
@@ -33,11 +32,6 @@ const UserSchema = new Schema({
type: Date,
default: () => new Date()
},
avatar: {
type: String,
default: () => getRandomUserAvatar()
},
promotionRate: {
type: Number,
default: 15
@@ -62,7 +56,10 @@ const UserSchema = new Schema({
ref: userCollectionName
},
fastgpt_sem: Object,
sourceDomain: String
sourceDomain: String,
/** @deprecated */
avatar: String
});
try {

View File

@@ -37,7 +37,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
teamAvatar: tmb.team.avatar,
teamName: tmb.team.name,
memberName: tmb.name,
avatar: tmb.team.avatar,
avatar: tmb.avatar,
balance: tmb.team.balance,
tmbId: String(tmb._id),
teamDomain: tmb.team?.teamDomain,

View File

@@ -7,6 +7,7 @@ import {
TeamMemberCollectionName,
TeamCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils';
const TeamMemberSchema = new Schema({
teamId: {
@@ -19,6 +20,10 @@ const TeamMemberSchema = new Schema({
ref: userCollectionName,
required: true
},
avatar: {
type: String,
default: () => getRandomUserAvatar()
},
name: {
type: String,
default: 'Member'
@@ -39,7 +44,6 @@ const TeamMemberSchema = new Schema({
// Abandoned
role: {
type: String
// enum: Object.keys(TeamMemberRoleMap) // disable enum validation for old data
}
});

View File

@@ -1,4 +1,7 @@
import { SourceMemberType } from '@fastgpt/global/support/user/type';
import { MongoTeam } from './team/teamSchema';
import { MongoTeamMember } from './team/teamMemberSchema';
import { ClientSession } from '../../common/mongo';
/* export dataset limit */
export const updateExportDatasetLimit = async (teamId: string) => {
@@ -67,3 +70,41 @@ export const checkWebSyncLimit = async ({
return Promise.reject(`每个团队,每 ${limitMinutes} 分钟仅使用一次同步功能。`);
}
};
/**
* This function will add a property named sourceMember to the list passed in.
* @param list The list to add the sourceMember property to. [TmbId] property is required.
* @error If member is not found, this item will be skipped.
* @returns The list with the sourceMember property added.
*/
export async function addSourceMember<T extends { tmbId: string }>({
list,
session
}: {
list: T[];
session?: ClientSession;
}): Promise<Array<T & { sourceMember: SourceMemberType }>> {
if (!Array.isArray(list)) return [];
const tmbList = await MongoTeamMember.find(
{
_id: { $in: list.map((item) => String(item.tmbId)) }
},
'tmbId name avatar status',
{
session
}
).lean();
return list
.map((item) => {
const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId));
if (!tmb) return;
return {
...item,
sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status }
};
})
.filter(Boolean) as Array<T & { sourceMember: SourceMemberType }>;
}

View File

@@ -1,8 +1,13 @@
export type PaginationProps<T = {}> = T & {
offset: number;
pageSize: number;
};
export type PaginationResponse<T = any> = {
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
type PaginationProps<T = {}> = T & {
pageSize: number | string;
} & RequireOnlyOne<{
offset: number | string;
pageNum: number | string;
}>;
type PaginationResponse<T = {}> = {
total: number;
list: T[];
};

View File

@@ -23,7 +23,7 @@ function AvatarGroup({
<Flex position="relative">
{avatars.slice(0, max).map((avatar, index) => (
<Avatar
key={avatar + groupId}
key={index}
src={avatar}
position={index > 0 ? 'absolute' : 'relative'}
left={index > 0 ? `${index * 15}px` : 0}

View File

@@ -142,6 +142,7 @@ export const iconPaths = {
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
'core/app/type/httpPlugin': () => import('./icons/core/app/type/httpPlugin.svg'),
'core/app/type/httpPluginFill': () => import('./icons/core/app/type/httpPluginFill.svg'),
'core/app/type/jsonImport': () => import('./icons/core/app/type/jsonImport.svg'),
'core/app/type/plugin': () => import('./icons/core/app/type/plugin.svg'),
'core/app/type/pluginFill': () => import('./icons/core/app/type/pluginFill.svg'),
'core/app/type/pluginLight': () => import('./icons/core/app/type/pluginLight.svg'),
@@ -269,6 +270,7 @@ export const iconPaths = {
import('./icons/core/workflow/template/datasource.svg'),
'core/workflow/template/duckduckgo': () =>
import('./icons/core/workflow/template/duckduckgo.svg'),
'core/workflow/template/sleep': () => import('./icons/core/workflow/template/sleep.svg'),
'core/workflow/template/extractJson': () =>
import('./icons/core/workflow/template/extractJson.svg'),
'core/workflow/template/fetchUrl': () => import('./icons/core/workflow/template/fetchUrl.svg'),

View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_16784_2993)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.5271 4.37083L25.4043 8.01175C26.3626 8.91164 26.8417 9.36158 27.1828 9.89648C27.4727 10.3511 27.6882 10.849 27.8211 11.3715C27.9775 11.9863 27.9775 12.6436 27.9775 13.9582V19.4935C27.0071 19.026 25.919 18.764 24.7697 18.764C20.6828 18.764 17.3698 22.077 17.3698 26.1639C17.3698 27.502 17.7249 28.7571 18.3461 29.8401H12.1799C9.29446 29.8401 7.85176 29.8401 6.75362 29.2697C5.82822 28.789 5.07369 28.0344 4.59299 27.1091C4.02255 26.0109 4.02255 24.5682 4.02255 21.6828V10.3173C4.02255 7.43186 4.02255 5.98915 4.59299 4.89101C5.07369 3.96561 5.82822 3.21108 6.75362 2.73038C7.85176 2.15994 9.29447 2.15994 12.1799 2.15994H15.943C17.1489 2.15994 17.7519 2.15994 18.3216 2.29333C18.806 2.40673 19.2711 2.59087 19.7019 2.83981C20.2085 3.1326 20.6481 3.54535 21.5271 4.37083ZM19.1105 6.20351C18.3195 5.4451 17.924 5.0659 17.5846 5.05216C17.315 5.04124 17.0548 5.15224 16.8761 5.3544C16.6511 5.60893 16.6511 6.15685 16.6511 7.25268V8.50383C16.6511 10.1068 16.6511 10.9083 16.963 11.5205C17.2374 12.0591 17.6753 12.4969 18.2138 12.7713C18.8261 13.0833 19.6276 13.0833 21.2305 13.0833H22.6697C23.8233 13.0833 24.4001 13.0833 24.6566 12.8505C24.8602 12.6658 24.9678 12.3979 24.9487 12.1238C24.9247 11.7782 24.5084 11.379 23.6757 10.5806L19.1105 6.20351Z" fill="url(#paint0_linear_16784_2993)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.5057 21.6108C23.8901 20.1169 26.0117 20.1169 26.3961 21.6108C26.6044 22.4202 27.4368 22.9007 28.2419 22.6764C29.7278 22.2624 30.7886 24.0997 29.6871 25.1796C29.0903 25.7646 29.0903 26.7258 29.6871 27.3108C30.7886 28.3907 29.7278 30.228 28.2419 29.814C27.4368 29.5897 26.6044 30.0703 26.3961 30.8796C26.0117 32.3735 23.8901 32.3735 23.5057 30.8796C23.2974 30.0703 22.4651 29.5897 21.66 29.814C20.174 30.228 19.1133 28.3907 20.2148 27.3108C20.8116 26.7258 20.8116 25.7646 20.2148 25.1796C19.1133 24.0997 20.174 22.2624 21.66 22.6764C22.4651 22.9007 23.2974 22.4202 23.5057 21.6108ZM26.8127 26.2452C26.8127 27.2734 25.9791 28.107 24.9509 28.107C23.9227 28.107 23.0891 27.2734 23.0891 26.2452C23.0891 25.217 23.9227 24.3834 24.9509 24.3834C25.9791 24.3834 26.8127 25.217 26.8127 26.2452Z" fill="url(#paint1_linear_16784_2993)"/>
</g>
<defs>
<linearGradient id="paint0_linear_16784_2993" x1="17.0824" y1="2.15994" x2="17.0824" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#BAC6D8"/>
<stop offset="1" stop-color="#9FA9C2"/>
</linearGradient>
<linearGradient id="paint1_linear_16784_2993" x1="17.0824" y1="2.15994" x2="17.0824" y2="32" gradientUnits="userSpaceOnUse">
<stop stop-color="#BAC6D8"/>
<stop offset="1" stop-color="#9FA9C2"/>
</linearGradient>
<clipPath id="clip0_16784_2993">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,7 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="200" height="200">
<path d="M-70.54222222-70.54222222m582.54222222 0l0 0q582.54222222 0 582.54222222 582.54222222l0 0q0 582.54222222-582.54222222 582.54222222l0 0q-582.54222222 0-582.54222222-582.54222222l0 0q0-582.54222222 582.54222222-582.54222222Z"
fill="#1296db"/>
<path d="M422.64477924 243.86730561H243.86730561v536.26538878h178.77747363V243.86730561zM780.13269439 243.86730561h-178.77747363v536.26538878H780.13269439V243.86730561z"
fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 593 B

View File

@@ -31,7 +31,7 @@ const HttpInput = ({
const onChangeInput = useCallback(
(editorState: EditorState, editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor);
setCurrentValue(text);
onChange?.(text);
},
@@ -39,7 +39,7 @@ const HttpInput = ({
);
const onBlurInput = useCallback(
(editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor);
onBlur?.(text);
},
[onBlur]

View File

@@ -75,6 +75,7 @@ const MyModal = ({
py={'10px'}
fontSize={'md'}
fontWeight={'bold'}
minH={['46px', '53px']}
>
{iconSrc && (
<>

View File

@@ -21,7 +21,15 @@ import type { ButtonProps, MenuItemProps } from '@chakra-ui/react';
import MyIcon from '../Icon';
import { useRequest2 } from '../../../hooks/useRequest';
import MyDivider from '../MyDivider';
import { useScrollPagination } from '../../../hooks/useScrollPagination';
/** 选择组件 Props 类型
* value: 选中的值
* placeholder: 占位符
* list: 列表数据
* isLoading: 是否加载中
* ScrollData: 分页滚动数据控制器 [useScrollPagination] 的返回值
* */
export type SelectProps<T = any> = ButtonProps & {
value?: T;
placeholder?: string;
@@ -34,6 +42,7 @@ export type SelectProps<T = any> = ButtonProps & {
}[];
isLoading?: boolean;
onchange?: (val: T) => any | Promise<any>;
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
};
const MySelect = <T = any,>(
@@ -44,6 +53,7 @@ const MySelect = <T = any,>(
list = [],
onchange,
isLoading = false,
ScrollData,
...props
}: SelectProps<T>,
ref: ForwardedRef<{
@@ -87,6 +97,46 @@ const MySelect = <T = any,>(
const isSelecting = loading || isLoading;
const ListRender = useMemo(() => {
return (
<>
{list.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (onChange && value !== item.value) {
onChange(item.value);
}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
>
<Box>{item.label}</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))}
</>
);
}, [list, value]);
return (
<Box
css={css({
@@ -154,39 +204,7 @@ const MySelect = <T = any,>(
maxH={'40vh'}
overflowY={'auto'}
>
{list.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (onChange && value !== item.value) {
onChange(item.value);
}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
>
<Box>{item.label}</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))}
{ScrollData ? <ScrollData>{ListRender}</ScrollData> : ListRender}
</MenuList>
</Menu>
</Box>

View File

@@ -40,14 +40,14 @@ const PromptEditor = ({
const onChangeInput = useCallback(
(editorState: EditorState, editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor);
onChange?.(text);
},
[onChange]
);
const onBlurInput = useCallback(
(editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
const text = editorStateToText(editor);
onBlur?.(text);
},
[onBlur]

View File

@@ -0,0 +1,23 @@
import { Box, HStack, type StackProps } from '@chakra-ui/react';
import { SourceMemberType } from '@fastgpt/global/support/user/type';
import React from 'react';
import Avatar from '../Avatar';
import { useTranslation } from 'next-i18next';
import Tag from '../Tag';
export type UserBoxProps = {
sourceMember: SourceMemberType;
avatarSize?: string;
} & StackProps;
function UserBox({ sourceMember, avatarSize = '1.25rem', ...props }: UserBoxProps) {
const { t } = useTranslation();
return (
<HStack space="1" {...props}>
<Avatar src={sourceMember.avatar} w={avatarSize} />
<Box>{sourceMember.name}</Box>
{sourceMember.status === 'leave' && <Tag color="gray">{t('common:user_leaved')}</Tag>}
</HStack>
);
}
export default React.memo(UserBox);

View File

@@ -4,7 +4,6 @@ import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons';
import { useTranslation } from 'next-i18next';
import { useToast } from './useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import {
useBoolean,
useLockFn,
@@ -14,37 +13,33 @@ import {
useThrottleEffect
} from 'ahooks';
import { PaginationProps, PaginationResponse } from '../common/fetch/type';
const thresholdVal = 200;
type PagingData<T> = {
pageNum: number;
pageSize: number;
data: T[];
total?: number;
};
export function usePagination<ResT = any>({
api,
pageSize = 10,
params = {},
defaultRequest = true,
type = 'button',
onChange,
refreshDeps,
scrollLoadType = 'bottom',
EmptyTip
}: {
api: (data: any) => Promise<PagingData<ResT>>;
pageSize?: number;
params?: Record<string, any>;
defaultRequest?: boolean;
type?: 'button' | 'scroll';
onChange?: (pageNum: number) => void;
refreshDeps?: any[];
throttleWait?: number;
scrollLoadType?: 'top' | 'bottom';
EmptyTip?: React.JSX.Element;
}) {
export function usePagination<DataT, ResT = {}>(
api: (data: PaginationProps<DataT>) => Promise<PaginationResponse<ResT>>,
{
pageSize = 10,
params,
defaultRequest = true,
type = 'button',
onChange,
refreshDeps,
scrollLoadType = 'bottom',
EmptyTip
}: {
pageSize?: number;
params?: DataT;
defaultRequest?: boolean;
type?: 'button' | 'scroll';
onChange?: (pageNum: number) => void;
refreshDeps?: any[];
throttleWait?: number;
scrollLoadType?: 'top' | 'bottom';
EmptyTip?: React.JSX.Element;
}
) {
const { toast } = useToast();
const { t } = useTranslation();
@@ -64,7 +59,7 @@ export function usePagination<ResT = any>({
setTrue();
try {
const res: PagingData<ResT> = await api({
const res = await api({
pageNum: num,
pageSize,
...params
@@ -93,13 +88,13 @@ export function usePagination<ResT = any>({
);
}
setData((prevData) => (num === 1 ? res.data : [...res.data, ...prevData]));
setData((prevData) => (num === 1 ? res.list : [...res.list, ...prevData]));
adjustScrollPosition();
} else {
setData((prevData) => (num === 1 ? res.data : [...prevData, ...res.data]));
setData((prevData) => (num === 1 ? res.list : [...prevData, ...res.list]));
}
} else {
setData(res.data);
setData(res.list);
}
onChange?.(num);

View File

@@ -16,7 +16,7 @@ import MyBox from '../components/common/MyBox';
import { useTranslation } from 'next-i18next';
type ItemHeight<T> = (index: number, data: T) => number;
const thresholdVal = 200;
const thresholdVal = 100;
export type ScrollListType = ({
children,
@@ -269,8 +269,10 @@ export function useScrollPagination<
({
children,
ScrollContainerRef,
isLoading,
...props
}: {
isLoading?: boolean;
children: ReactNode;
ScrollContainerRef?: RefObject<HTMLDivElement>;
} & BoxProps) => {
@@ -302,7 +304,7 @@ export function useScrollPagination<
);
return (
<Box {...props} ref={ref} overflow={'overlay'}>
<MyBox {...props} ref={ref} overflow={'overlay'} isLoading={isLoading}>
{scrollLoadType === 'top' && total > 0 && isLoading && (
<Box mt={2} fontSize={'xs'} color={'blackAlpha.500'} textAlign={'center'}>
{t('common:common.is_requesting')}
@@ -325,7 +327,7 @@ export function useScrollPagination<
</Box>
)}
{isEmpty && EmptyTip}
</Box>
</MyBox>
);
}
);

View File

@@ -31,6 +31,8 @@
"copy_one_app": "Create Duplicate",
"core.app.QG.Switch": "Enable guess what you want to ask",
"core.dataset.import.Custom prompt": "Custom Prompt",
"create_by_curl": "By CURL",
"create_by_template": "By template",
"create_copy_success": "Duplicate Created Successfully",
"create_empty_app": "Create Default App",
"create_empty_plugin": "Create Default Plugin",
@@ -69,6 +71,7 @@
"interval.6_hours": "Every 6 Hours",
"interval.per_hour": "Every Hour",
"intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!",
"invalid_json_format": "JSON format error",
"llm_not_support_vision": "This model does not support image recognition",
"llm_use_vision": "Vision",
"llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.",
@@ -90,10 +93,11 @@
"move_app": "Move Application",
"node_not_intro": "This node is not introduced",
"not_json_file": "Please select a JSON file",
"oaste_curl_string": "Enter CURL code",
"open_auto_execute": "Enable automatic execution",
"open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).",
"or_drag_JSON": "or drag in JSON file",
"paste_config": "Paste Configuration",
"paste_config_or_drag": "Paste config or drag JSON file here",
"permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.",
"permission.des.read": "Use the app to have conversations",
"permission.des.write": "Can view and edit apps",
@@ -150,9 +154,12 @@
"type.Create workflow bot": "Create Workflow",
"type.Create workflow tip": "Build complex multi-turn dialogue AI applications through low-code methods, recommended for advanced users.",
"type.Http plugin": "HTTP Plugin",
"type.Import from json": "Import JSON",
"type.Import from json tip": "Create applications directly through JSON configuration files",
"type.Plugin": "Plugin",
"type.Simple bot": "Simple App",
"type.Workflow bot": "Workflow",
"type_not_recognized": "App type not recognized",
"upload_file_max_amount": "Maximum File Quantity",
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
"variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.",

View File

@@ -39,6 +39,7 @@
"classification": "Classification",
"click_to_resume": "Click to Resume",
"code_editor": "Code Editor",
"code_error.account_error": "Incorrect account name or password",
"code_error.app_error.invalid_app_type": "Invalid Application Type",
"code_error.app_error.invalid_owner": "Unauthorized Application Owner",
"code_error.app_error.not_exist": "Application Does Not Exist",
@@ -95,7 +96,6 @@
"code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync",
"code_error.token_error_code.403": "Invalid Login Status, Please Re-login",
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
"code_error.user_error.bin_visitor": "Identity Verification Failed",
"code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate",
"code_error.user_error.un_auth_user": "User Not Found",
"common.Action": "Action",
@@ -305,7 +305,6 @@
"core.app.Random": "Divergent",
"core.app.Search team tags": "Search Tags",
"core.app.Select TTS": "Select Voice Playback Mode",
"core.app.Select app from template": "Template",
"core.app.Select quote template": "Select Quote Prompt Template",
"core.app.Set a name for your app": "Set a Name for Your App",
"core.app.Setting ai property": "Click to Configure AI Model Properties",
@@ -1274,6 +1273,7 @@
"user.team.role.Visitor": "visitor",
"user.team.role.writer": "writable member",
"user.type": "Type",
"user_leaved": "Leaved",
"verification": "Verification",
"workflow.template.communication": "Communication",
"xx_search_result": "{{key}} Search Results",

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