Compare commits
15 Commits
v4.8.18-al
...
v4.8.18-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59177d307e | ||
|
|
bfb598d686 | ||
|
|
b780fbd6a7 | ||
|
|
7861229325 | ||
|
|
5b0516c28b | ||
|
|
740e8eb30c | ||
|
|
05a357dffe | ||
|
|
781fd45e39 | ||
|
|
80c8897e10 | ||
|
|
e933dacb05 | ||
|
|
cdf4f0e67d | ||
|
|
4dfeb21da3 | ||
|
|
d0d1a2cae8 | ||
|
|
f1f0ae2839 | ||
|
|
10d8c56e23 |
83
.github/workflows/fastgpt-image.yml
vendored
@@ -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} \
|
||||
.
|
||||
|
||||
17
.github/workflows/preview-fastgpt-image.yml
vendored
@@ -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
@@ -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:
|
||||
|
||||
|
||||
BIN
docSite/assets/imgs/image-83.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
docSite/assets/imgs/image-84.png
Normal file
|
After Width: | Height: | Size: 243 KiB |
BIN
docSite/assets/imgs/image-85.png
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
docSite/assets/imgs/image-86.png
Normal file
|
After Width: | Height: | Size: 550 KiB |
BIN
docSite/assets/imgs/image-87.png
Normal file
|
After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 68 KiB |
@@ -7,7 +7,7 @@ toc: true
|
||||
weight: 1210
|
||||
---
|
||||
|
||||
FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件:
|
||||
FastGPT 项目在 Apache License 2.0 许可下开源,但包含以下附加条件:
|
||||
|
||||
+ FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可:
|
||||
|
||||
|
||||
@@ -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+ 版本新配置文件示例
|
||||
|
||||
|
||||
@@ -45,31 +45,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 +90,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 +112,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 +257,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 +292,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
|
||||
}'
|
||||
```
|
||||
|
||||
2. 检查响应参数
|
||||
##### 2. 检查响应参数
|
||||
|
||||
如果能正常调用工具,会返回对应 `tool_calls` 参数。
|
||||
|
||||
@@ -242,7 +330,7 @@ curl --location --request POST 'https://oneapi.xxx/v1/chat/completions' \
|
||||
}
|
||||
```
|
||||
|
||||
3. 通过 `curl` 向 `oneapi` 发起第二轮 stream 模式的 tool 测试。
|
||||
##### 3. 通过 `curl` 向 `oneapi` 发起第二轮 stream 模式的 tool 测试。
|
||||
|
||||
第二轮请求是把工具结果发送给模型。发起后会得到模型回答的结果。
|
||||
|
||||
|
||||
@@ -1175,7 +1175,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 +1197,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 +1217,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",
|
||||
|
||||
@@ -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. 修复 - 插件计费错误。
|
||||
|
||||
@@ -7,7 +7,7 @@ toc: true
|
||||
weight: 908
|
||||
---
|
||||
|
||||
## 工作流中多轮对话场景中如何使连续问题被问题分类节点正确的归类
|
||||
## 多轮对话中如何使连续问题被问题分类节点正确的归类
|
||||
|
||||
问题分类节点具有获取上下文信息的能力,当处理两个关联性较大的问题时,模型的判断准确性往往依赖于这两个问题之间的联系和模型的能力。例如,当用户先问“我该如何使用这个功能?”接着又询问“这个功能有什么限制?”时,模型借助上下文信息,就能够更精准地理解并响应。
|
||||
|
||||
@@ -15,10 +15,26 @@ weight: 908
|
||||
|
||||
建议:构建批量运行脚本进行测试,评估问题分类的准确性。
|
||||
|
||||
## 系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题
|
||||
## 定时执行的时机问题
|
||||
|
||||
发布后,后台生效。
|
||||
系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题:
|
||||
|
||||
定时执行会在应用发布后生效,会在后台生效。
|
||||
|
||||
## AI对话回答要求中的Markdown语法取消
|
||||
|
||||
在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。
|
||||
修改知识库默认提示词, 默认用的是标准模板提示词,会要求按 Markdown 输出,可以去除该要求:
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## 应用在不同来源效果不一致
|
||||
|
||||
Q: 应用在调试和正式发布后,效果不一致;在 API 调用时,效果不一致。
|
||||
|
||||
A: 通常是由于上下文不一致导致,可以在对话日志中,找到对应的记录,并查看运行详情来进行比对。
|
||||
|
||||
| | | |
|
||||
| --- | --- | --- |
|
||||
|  |  |  |
|
||||
|
||||
@@ -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 结构**
|
||||
|
||||
|
||||
@@ -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" %}}
|
||||
|
||||
@@ -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)
|
||||
@@ -32,11 +32,11 @@ weight: 602
|
||||
|
||||
1. ### 创建应用模板
|
||||
|
||||
应用模板配置以及相关资源,都会在 **projects/app/public/appMarketTemplates** 目录下。
|
||||
应用模板配置以及相关资源,都会在 **packages/templates/src** 目录下。
|
||||
|
||||

|
||||
|
||||
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,需要附上对应的地址和教程,后续我们会加入到文档中。
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
44
packages/global/common/string/http.ts
Normal 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
|
||||
};
|
||||
};
|
||||
@@ -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 '';
|
||||
};
|
||||
|
||||
@@ -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 });
|
||||
})();
|
||||
|
||||
3
packages/global/core/workflow/type/io.d.ts
vendored
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,78 @@ 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 substitution(Exclude: 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') {
|
||||
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 +235,10 @@ 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;
|
||||
return json5.parse(replaceJsonBodyString(httpJsonBody));
|
||||
}
|
||||
|
||||
// Raw text, xml
|
||||
httpJsonBody = replaceStringVariables(httpJsonBody);
|
||||
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
|
||||
} catch (error) {
|
||||
@@ -335,40 +389,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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 |
@@ -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]
|
||||
|
||||
@@ -75,6 +75,7 @@ const MyModal = ({
|
||||
py={'10px'}
|
||||
fontSize={'md'}
|
||||
fontWeight={'bold'}
|
||||
minH={['46px', '53px']}
|
||||
>
|
||||
{iconSrc && (
|
||||
<>
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
"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.account_error": "Incorrect account name or password",
|
||||
"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",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"intro_http_request": "Can send an HTTP request to perform more complex operations (network search, database query, etc.)",
|
||||
"intro_knowledge_base_search_merge": "Can merge multiple Dataset search results for output. Uses RRF merging method for final sorting output.",
|
||||
"intro_laf_function_call": "Can call cloud functions under the Laf account.",
|
||||
"intro_loop": "You can input an array, the elements in the array will execute the loop body independently, and output all results as an array.",
|
||||
"intro_loop": "Input an array, iterate through the array and use each array element as an input element to execute the workflow.",
|
||||
"intro_plugin_input": "Can configure what inputs the plugin needs and use these inputs to run the plugin.",
|
||||
"intro_question_classification": "Determine the type of question based on the user's history and current question. Multiple question types can be added. Below is a template example:\nType 1: Greeting\nType 2: Questions about product 'usage'\nType 3: Questions about product 'purchase'\nType 4: Other questions",
|
||||
"intro_question_optimization": "Using question optimization can improve the accuracy of Dataset searches during continuous conversations. After using this function, AI will first construct one or more new search terms based on the context, which are more conducive to Dataset searches. This module is already built into the Dataset search module. If you only perform a single Dataset search, you can directly use the built-in completion function of the Dataset.",
|
||||
@@ -108,10 +108,10 @@
|
||||
"less_than_or_equal_to": "Less Than or Equal To",
|
||||
"loop": "Batch Run",
|
||||
"loop_body": "loop body",
|
||||
"loop_end": "end of loop",
|
||||
"loop_end": "End",
|
||||
"loop_input_array": "array",
|
||||
"loop_result": "Array execution results",
|
||||
"loop_start": "The loop body begins",
|
||||
"loop_start": "Start",
|
||||
"max_dialog_rounds": "Maximum Number of Dialog Rounds",
|
||||
"max_tokens": "Maximum Tokens",
|
||||
"mouse_priority": "Mouse first\n- Press the left button to drag the canvas\n- Hold down shift and left click to select batches",
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
"copy_one_app": "创建副本",
|
||||
"core.app.QG.Switch": "启用猜你想问",
|
||||
"core.dataset.import.Custom prompt": "自定义提示词",
|
||||
"create_by_curl": "从 CURL 创建",
|
||||
"create_by_template": "从模板创建",
|
||||
"create_copy_success": "创建副本成功",
|
||||
"create_empty_app": "创建空白应用",
|
||||
"create_empty_plugin": "创建空白插件",
|
||||
@@ -69,6 +71,7 @@
|
||||
"interval.6_hours": "每6小时",
|
||||
"interval.per_hour": "每小时",
|
||||
"intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!",
|
||||
"invalid_json_format": "JSON 格式错误",
|
||||
"llm_not_support_vision": "该模型不支持图片识别",
|
||||
"llm_use_vision": "图片识别",
|
||||
"llm_use_vision_tip": "点击模型选择后,可以看到模型是否支持图片识别以及控制是否启动图片识别的能力。启动图片识别后,模型会读取文件链接里图片内容,并且如果用户问题少于 500 字,会自动解析用户问题中的图片。",
|
||||
@@ -90,10 +93,11 @@
|
||||
"move_app": "移动应用",
|
||||
"node_not_intro": "这个节点没有介绍",
|
||||
"not_json_file": "请选择JSON文件",
|
||||
"oaste_curl_string": "输入 CURL 代码",
|
||||
"open_auto_execute": "启用自动执行",
|
||||
"open_vision_function_tip": "有图示开关的模型即拥有图片识别能力。若开启,模型会解析文件链接里的图片,并自动解析用户问题中的图片(用户问题≤500字时生效)。",
|
||||
"or_drag_JSON": "或拖入JSON文件",
|
||||
"paste_config": "粘贴配置",
|
||||
"paste_config_or_drag": "粘贴配置或拖入 JSON 文件",
|
||||
"permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限",
|
||||
"permission.des.read": "可使用该应用进行对话",
|
||||
"permission.des.write": "可查看和编辑应用",
|
||||
@@ -150,9 +154,12 @@
|
||||
"type.Create workflow bot": "创建工作流",
|
||||
"type.Create workflow tip": "通过低代码的方式,构建逻辑复杂的多轮对话 AI 应用,推荐高级玩家使用",
|
||||
"type.Http plugin": "HTTP 插件",
|
||||
"type.Import from json": "导入 JSON 配置",
|
||||
"type.Import from json tip": "通过 JSON 配置文件,直接创建应用",
|
||||
"type.Plugin": "插件",
|
||||
"type.Simple bot": "简易应用",
|
||||
"type.Workflow bot": "工作流",
|
||||
"type_not_recognized": "未识别到应用类型",
|
||||
"upload_file_max_amount": "最大文件数量",
|
||||
"upload_file_max_amount_tip": "单轮对话中最大上传文件数量",
|
||||
"variable.select type_desc": "可以为工作流定义全局变量,常用临时缓存。赋值的方式包括:\n1. 从对话页面的 query 参数获取。\n2. 通过 API 的 variables 对象传递。\n3. 通过【变量更新】节点进行赋值。",
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
"code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~",
|
||||
"code_error.token_error_code.403": "登录状态无效,请重新登录",
|
||||
"code_error.user_error.balance_not_enough": "账号余额不足~",
|
||||
"code_error.user_error.bin_visitor": "您的身份校验未通过",
|
||||
"code_error.account_error": "账号名或密码错误",
|
||||
"code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作",
|
||||
"code_error.user_error.un_auth_user": "找不到该用户",
|
||||
"common.Action": "操作",
|
||||
@@ -308,7 +308,6 @@
|
||||
"core.app.Random": "发散",
|
||||
"core.app.Search team tags": "搜索标签",
|
||||
"core.app.Select TTS": "选择语音播放模式",
|
||||
"core.app.Select app from template": "从模板中选择",
|
||||
"core.app.Select quote template": "选择引用提示模板",
|
||||
"core.app.Set a name for your app": "给应用设置一个名称",
|
||||
"core.app.Setting ai property": "点击配置 AI 模型相关属性",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"intro_http_request": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"intro_knowledge_base_search_merge": "可以将多个知识库搜索结果进行合并输出。使用 RRF 的合并方式进行最终排序输出。",
|
||||
"intro_laf_function_call": "可以调用Laf账号下的云函数。",
|
||||
"intro_loop": "可以输入一个数组,数组内元素将独立执行循环体,并将所有结果作为数组输出。",
|
||||
"intro_loop": "输入一个数组,遍历数组并将每一个数组元素作为输入元素,执行工作流。",
|
||||
"intro_plugin_input": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"intro_question_classification": "根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于商品“使用”问题\n类型3: 关于商品“购买”问题\n类型4: 其他问题",
|
||||
"intro_question_optimization": "使用问题优化功能,可以提高知识库连续对话时搜索的精度。使用该功能后,会先利用 AI 根据上下文构建一个或多个新的检索词,这些检索词更利于进行知识库搜索。该模块已内置在知识库搜索模块中,如果您仅进行一次知识库搜索,可直接使用知识库内置的补全功能。",
|
||||
@@ -106,12 +106,12 @@
|
||||
"length_not_equal_to": "长度不等于",
|
||||
"less_than": "小于",
|
||||
"less_than_or_equal_to": "小于等于",
|
||||
"loop": "循环运行",
|
||||
"loop": "批量执行",
|
||||
"loop_body": "循环体",
|
||||
"loop_end": "循环体结束",
|
||||
"loop_end": "结束",
|
||||
"loop_input_array": "数组",
|
||||
"loop_result": "数组执行结果",
|
||||
"loop_start": "循环体开始",
|
||||
"loop_start": "开始",
|
||||
"max_dialog_rounds": "最多携带多少轮对话记录",
|
||||
"max_tokens": "最大 Tokens",
|
||||
"mouse_priority": "鼠标优先\n- 左键按下后可拖动画布\n- 按住 shift 后左键可批量选择",
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
"copy_one_app": "建立副本",
|
||||
"core.app.QG.Switch": "啟用猜你想問",
|
||||
"core.dataset.import.Custom prompt": "自訂提示詞",
|
||||
"create_by_curl": "從 CURL 創建",
|
||||
"create_by_template": "從模板創建",
|
||||
"create_copy_success": "建立副本成功",
|
||||
"create_empty_app": "建立空白應用程式",
|
||||
"create_empty_plugin": "建立空白外掛",
|
||||
@@ -69,6 +71,7 @@
|
||||
"interval.6_hours": "每 6 小時",
|
||||
"interval.per_hour": "每小時",
|
||||
"intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或配置。",
|
||||
"invalid_json_format": "JSON 格式錯誤",
|
||||
"llm_not_support_vision": "這個模型不支援圖片辨識",
|
||||
"llm_use_vision": "圖片辨識",
|
||||
"llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。",
|
||||
@@ -90,10 +93,11 @@
|
||||
"move_app": "移動應用程式",
|
||||
"node_not_intro": "這個節點沒有介紹",
|
||||
"not_json_file": "請選擇 JSON 檔案",
|
||||
"oaste_curl_string": "輸入 CURL 代碼",
|
||||
"open_auto_execute": "啟用自動執行",
|
||||
"open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。",
|
||||
"or_drag_JSON": "或拖曳 JSON 檔案",
|
||||
"paste_config": "貼上設定",
|
||||
"paste_config_or_drag": "貼上配置或拖入 JSON 文件",
|
||||
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
|
||||
"permission.des.read": "可以使用這個應用程式進行對話",
|
||||
"permission.des.write": "可以檢視和編輯應用程式",
|
||||
@@ -150,9 +154,12 @@
|
||||
"type.Create workflow bot": "建立工作流程",
|
||||
"type.Create workflow tip": "透過低程式碼的方式,建立邏輯複雜的多輪對話 AI 應用程式,建議進階使用者使用",
|
||||
"type.Http plugin": "HTTP 外掛",
|
||||
"type.Import from json": "導入 JSON 配置",
|
||||
"type.Import from json tip": "透過 JSON 設定文件,直接建立應用",
|
||||
"type.Plugin": "外掛",
|
||||
"type.Simple bot": "簡易應用程式",
|
||||
"type.Workflow bot": "工作流程",
|
||||
"type_not_recognized": "未識別到應用程式類型",
|
||||
"upload_file_max_amount": "最大檔案數量",
|
||||
"upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量",
|
||||
"variable.select type_desc": "可以為工作流程定義全域變數,常用於暫存。賦值的方式包括:\n1. 從對話頁面的 query 參數取得。\n2. 透過 API 的 variables 物件傳遞。\n3. 透過【變數更新】節點進行賦值。",
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
"code_error.team_error.website_sync_not_enough": "無權使用網站同步",
|
||||
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
|
||||
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
|
||||
"code_error.user_error.bin_visitor": "身份驗證未通過",
|
||||
"code_error.account_error": "帳號名稱或密碼錯誤",
|
||||
"code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作",
|
||||
"code_error.user_error.un_auth_user": "找不到此使用者",
|
||||
"common.Action": "操作",
|
||||
@@ -305,7 +305,6 @@
|
||||
"core.app.Random": "發散",
|
||||
"core.app.Search team tags": "搜尋標籤",
|
||||
"core.app.Select TTS": "選擇語音播放模式",
|
||||
"core.app.Select app from template": "從範本中選擇",
|
||||
"core.app.Select quote template": "選擇引用提示範本",
|
||||
"core.app.Set a name for your app": "為您的應用程式命名",
|
||||
"core.app.Setting ai property": "點選設定 AI 模型相關屬性",
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
"intro_http_request": "可以傳送 HTTP 請求,執行更複雜的操作(網路搜尋、資料庫查詢等等)",
|
||||
"intro_knowledge_base_search_merge": "可以合併多個知識庫搜尋結果並輸出。使用 RRF 合併方法進行最終排序輸出。",
|
||||
"intro_laf_function_call": "可以呼叫 Laf 帳號下的雲端函式。",
|
||||
"intro_loop": "可以輸入一個陣列,陣列中的元素會各自執行迴圈主體,並將所有結果以陣列形式輸出。",
|
||||
"intro_loop": "輸入一個數組,遍歷數組並將每個數組元素作為輸入元素,執行工作流程。",
|
||||
"intro_plugin_input": "可以設定外掛程式需要的輸入,並利用這些輸入來執行外掛程式",
|
||||
"intro_question_classification": "根據使用者的歷史紀錄和目前問題判斷這次提問的類型。可以新增多個問題類型,以下是一個範例:\n類型 1:打招呼\n類型 2:關於產品「使用方式」的問題\n類型 3:關於產品「購買」的問題\n類型 4:其他問題",
|
||||
"intro_question_optimization": "使用問題最佳化功能,可以提升知識庫連續對話時的搜尋精準度。使用這個功能後,會先利用 AI 根據脈絡建構一個或多個新的檢索詞彙,這些詞彙更有利於知識庫搜尋。這個模組已內建於知識庫搜尋模組中,如果您只進行一次知識庫搜尋,可以直接使用知識庫內建的自動完成功能。",
|
||||
@@ -106,12 +106,12 @@
|
||||
"length_not_equal_to": "長度不等於",
|
||||
"less_than": "小於",
|
||||
"less_than_or_equal_to": "小於或等於",
|
||||
"loop": "批次執行",
|
||||
"loop": "大量執行",
|
||||
"loop_body": "迴圈主體",
|
||||
"loop_end": "迴圈結束",
|
||||
"loop_end": "結束",
|
||||
"loop_input_array": "陣列",
|
||||
"loop_result": "陣列執行結果",
|
||||
"loop_start": "迴圈開始",
|
||||
"loop_start": "開始",
|
||||
"max_dialog_rounds": "最多攜帶幾輪對話紀錄",
|
||||
"max_tokens": "最大 Token 數",
|
||||
"mouse_priority": "滑鼠優先\n- 按下左鍵拖曳畫布\n- 按住 Shift 鍵並點選左鍵可批次選取",
|
||||
|
||||
68
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
||||
'@apidevtools/swagger-parser':
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(openapi-types@12.1.3)
|
||||
'@bany/curl-to-json':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
axios:
|
||||
specifier: ^1.5.1
|
||||
version: 1.7.2
|
||||
@@ -139,9 +142,6 @@ importers:
|
||||
'@node-rs/jieba':
|
||||
specifier: 1.10.0
|
||||
version: 1.10.0
|
||||
'@wecom/jssdk':
|
||||
specifier: ^2.2.5
|
||||
version: 2.2.5
|
||||
'@xmldom/xmldom':
|
||||
specifier: ^0.8.10
|
||||
version: 0.8.10
|
||||
@@ -408,9 +408,6 @@ importers:
|
||||
|
||||
projects/app:
|
||||
dependencies:
|
||||
'@bany/curl-to-json':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8
|
||||
'@chakra-ui/anatomy':
|
||||
specifier: 2.2.1
|
||||
version: 2.2.1
|
||||
@@ -584,7 +581,7 @@ importers:
|
||||
version: 1.77.8
|
||||
ts-jest:
|
||||
specifier: ^29.1.0
|
||||
version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3)
|
||||
version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3)
|
||||
use-context-selector:
|
||||
specifier: ^1.4.4
|
||||
version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2)
|
||||
@@ -724,7 +721,7 @@ importers:
|
||||
version: 6.3.4
|
||||
ts-jest:
|
||||
specifier: ^29.1.0
|
||||
version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3)
|
||||
version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3)
|
||||
ts-loader:
|
||||
specifier: ^9.4.3
|
||||
version: 9.5.1(typescript@5.5.3)(webpack@5.92.1)
|
||||
@@ -2025,7 +2022,7 @@ packages:
|
||||
'@emotion/use-insertion-effect-with-fallbacks@1.0.1':
|
||||
resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react: '>=16.8.0'
|
||||
|
||||
'@emotion/utils@1.2.1':
|
||||
resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==}
|
||||
@@ -2648,8 +2645,8 @@ packages:
|
||||
resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==}
|
||||
peerDependencies:
|
||||
monaco-editor: '>= 0.25.0 < 1'
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
'@mongodb-js/saslprep@1.1.9':
|
||||
resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==}
|
||||
@@ -3000,8 +2997,8 @@ packages:
|
||||
'@reactflow/node-resizer@2.2.14':
|
||||
resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
|
||||
'@reactflow/node-toolbar@1.3.14':
|
||||
resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==}
|
||||
@@ -3718,9 +3715,6 @@ packages:
|
||||
'@webassemblyjs/wast-printer@1.12.1':
|
||||
resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==}
|
||||
|
||||
'@wecom/jssdk@2.2.5':
|
||||
resolution: {integrity: sha512-qOBAsfqaiYM8jZHWYs/atHSpJhsLdZVNaxHQdmEQ7ZWul/GZMt4P5VY8Nf7GII7GhG8z/k+r37Dto6qtAaRqow==}
|
||||
|
||||
'@xmldom/xmldom@0.8.10':
|
||||
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -7143,8 +7137,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.1.0
|
||||
'@playwright/test': ^1.41.2
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
sass: ^1.3.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
@@ -7799,8 +7793,8 @@ packages:
|
||||
react-photo-view@1.2.6:
|
||||
resolution: {integrity: sha512-Fq17yxkMIv0oFp7HOJr39HgCZRP6A9K5T5rixJ4flSUYT2OO3V8vNxEExjhIKgIrfmTu+mDnHYEsI9RRWi1JHw==}
|
||||
peerDependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
react-redux@7.2.9:
|
||||
resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==}
|
||||
@@ -7818,8 +7812,8 @@ packages:
|
||||
resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.1
|
||||
react: 18.3.1
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
@@ -7838,8 +7832,8 @@ packages:
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.1
|
||||
react: 18.3.1
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
@@ -8868,8 +8862,8 @@ packages:
|
||||
resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.1
|
||||
react: 18.3.1
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
@@ -8919,8 +8913,8 @@ packages:
|
||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.1
|
||||
react: 18.3.1
|
||||
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
@@ -12925,8 +12919,6 @@ snapshots:
|
||||
'@webassemblyjs/ast': 1.12.1
|
||||
'@xtuc/long': 4.2.2
|
||||
|
||||
'@wecom/jssdk@2.2.5': {}
|
||||
|
||||
'@xmldom/xmldom@0.8.10': {}
|
||||
|
||||
'@xtuc/ieee754@1.2.0': {}
|
||||
@@ -14530,7 +14522,7 @@ snapshots:
|
||||
eslint: 8.56.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.56.0)
|
||||
eslint-plugin-react: 7.34.4(eslint@8.56.0)
|
||||
eslint-plugin-react-hooks: 4.6.2(eslint@8.56.0)
|
||||
@@ -14553,12 +14545,8 @@ snapshots:
|
||||
debug: 4.3.5
|
||||
enhanced-resolve: 5.17.0
|
||||
eslint: 8.56.0
|
||||
<<<<<<< HEAD
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
=======
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
>>>>>>> 29ab002e3 (feat: support wecom sso (#3518))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.5
|
||||
is-core-module: 2.14.0
|
||||
@@ -14569,7 +14557,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@@ -14580,7 +14568,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
@@ -14590,7 +14578,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.56.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.14.0
|
||||
is-glob: 4.0.3
|
||||
@@ -19004,7 +18992,7 @@ snapshots:
|
||||
|
||||
ts-dedent@2.2.0: {}
|
||||
|
||||
ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3):
|
||||
ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3):
|
||||
dependencies:
|
||||
bs-logger: 0.2.6
|
||||
ejs: 3.1.10
|
||||
|
||||
@@ -81,6 +81,8 @@ COPY --from=builder /app/projects/app/package.json ./package.json
|
||||
COPY ./projects/app/data /app/data
|
||||
RUN chown -R nextjs:nodejs /app/data
|
||||
|
||||
# Add tmp directory permission control
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV PORT=3000
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.8.17",
|
||||
"version": "4.8.18",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -10,7 +10,6 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bany/curl-to-json": "^1.2.8",
|
||||
"@chakra-ui/anatomy": "2.2.1",
|
||||
"@chakra-ui/icons": "2.1.1",
|
||||
"@chakra-ui/next-js": "2.1.5",
|
||||
|
||||
@@ -1,53 +1,50 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import {
|
||||
Button,
|
||||
ModalFooter,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button, ModalFooter, ModalBody, Flex, Box, useTheme } from '@chakra-ui/react';
|
||||
import { getTeamList, updateInviteResult } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
const UpdateInviteModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
const { data: inviteList = [], refetch } = useQuery(['getInviteList'], () =>
|
||||
feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
|
||||
const { data: inviteList = [], run: fetchInviteList } = useRequest2(
|
||||
async () => (feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: onAccept, isLoading: isLoadingAccept } = useRequest({
|
||||
mutationFn: updateInviteResult,
|
||||
const { runAsync: onAccept, loading: isLoadingAccept } = useRequest2(updateInviteResult, {
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common:user.team.invite.Accepted')
|
||||
});
|
||||
refetch();
|
||||
fetchInviteList();
|
||||
initUserInfo();
|
||||
}
|
||||
});
|
||||
const { mutate: onReject, isLoading: isLoadingReject } = useRequest({
|
||||
mutationFn: updateInviteResult,
|
||||
const { runAsync: onReject, loading: isLoadingReject } = useRequest2(updateInviteResult, {
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common:user.team.invite.Reject')
|
||||
});
|
||||
refetch();
|
||||
fetchInviteList();
|
||||
initUserInfo();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,7 +56,7 @@ const UpdateInviteModal = () => {
|
||||
<Box>
|
||||
<Box>{t('common:user.team.Processing invitations')}</Box>
|
||||
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('user.team.Processing invitations Tips', { amount: inviteList?.length })}
|
||||
{t('common:user.team.Processing invitations Tips', { amount: inviteList?.length })}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
|
||||
136
projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React, { DragEvent, useCallback, useState } from 'react';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
const ImportAppConfigEditor = ({ value, onChange, rows = 16 }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const { File, onOpen } = useSelectFile({
|
||||
fileType: 'json',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const readJSONFile = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!file.name.endsWith('.json')) {
|
||||
toast({
|
||||
title: t('app:not_json_file'),
|
||||
status: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.target) {
|
||||
const res = JSON.parse(e.target.result as string);
|
||||
onChange(JSON.stringify(res, null, 2));
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[onChange, t, toast]
|
||||
);
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
readJSONFile(file);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
console.log(file);
|
||||
readJSONFile(file);
|
||||
setIsDragging(false);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box w={['100%', '31rem']}>
|
||||
{isDragging ? (
|
||||
<Flex
|
||||
align={'center'}
|
||||
justify={'center'}
|
||||
w={'full'}
|
||||
h={'17.5rem'}
|
||||
borderRadius={'md'}
|
||||
border={'1px dashed'}
|
||||
borderColor={'myGray.400'}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Flex align={'center'} justify={'center'} flexDir={'column'} gap={'0.62rem'}>
|
||||
<MyIcon name={'configmap'} w={'2rem'} color={'primary.500'} />
|
||||
<Box color={'primary.600'} fontSize={'sm'}>
|
||||
{t('app:file_recover')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Box>
|
||||
<Flex justify={'space-between'} align={'center'} pb={3}>
|
||||
<Box fontSize={'sm'} color={'myGray.900'} fontWeight={'500'}>
|
||||
{t('common:common.json_config')}
|
||||
</Box>
|
||||
<Button onClick={onOpen} variant={'whiteBase'} p={0}>
|
||||
<Flex px={'0.88rem'} py={'0.44rem'} color={'myGray.600'} fontSize={'mini'}>
|
||||
<MyIcon name={'file/uploadFile'} w={'1rem'} mr={'0.38rem'} />
|
||||
{t('common:common.upload_file')}
|
||||
</Flex>
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
border={'1px solid'}
|
||||
borderRadius={'md'}
|
||||
borderColor={'myGray.200'}
|
||||
value={value}
|
||||
placeholder={t('app:or_drag_JSON')}
|
||||
rows={rows}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{File && <File onSelect={onSelectFile} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ImportAppConfigEditor);
|
||||
23
projects/app/src/pageComponents/app/constants.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
export const appTypeMap = {
|
||||
[AppTypeEnum.simple]: {
|
||||
icon: 'core/app/simpleBot',
|
||||
title: i18nT('app:type.Create simple bot'),
|
||||
avatar: 'core/app/type/simpleFill',
|
||||
emptyCreateText: i18nT('app:create_empty_app')
|
||||
},
|
||||
[AppTypeEnum.workflow]: {
|
||||
icon: 'core/app/type/workflowFill',
|
||||
avatar: 'core/app/type/workflowFill',
|
||||
title: i18nT('app:type.Create workflow bot'),
|
||||
emptyCreateText: i18nT('app:create_empty_workflow')
|
||||
},
|
||||
[AppTypeEnum.plugin]: {
|
||||
icon: 'core/app/type/pluginFill',
|
||||
avatar: 'core/app/type/pluginFill',
|
||||
title: i18nT('app:type.Create plugin bot'),
|
||||
emptyCreateText: i18nT('app:create_empty_plugin')
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { filterSensitiveNodesData } from '@/web/core/workflow/utils';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { AppChatConfigType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { filterSensitiveFormData } from '@/web/core/app/utils';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
|
||||
const ExportConfigPopover = ({
|
||||
appForm,
|
||||
getWorkflowData,
|
||||
|
||||
chatConfig,
|
||||
appName
|
||||
}: {
|
||||
appName: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
} & RequireOnlyOne<{
|
||||
getWorkflowData: () =>
|
||||
| {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
appForm: AppSimpleEditFormType;
|
||||
}>) => {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const onExportWorkflow = useCallback(
|
||||
async (mode: 'copy' | 'json') => {
|
||||
let config = '';
|
||||
|
||||
if (appForm) {
|
||||
config = JSON.stringify(filterSensitiveFormData(appForm), null, 2);
|
||||
} else if (getWorkflowData) {
|
||||
const workflowData = getWorkflowData();
|
||||
if (!workflowData) return;
|
||||
config = JSON.stringify(
|
||||
{
|
||||
nodes: filterSensitiveNodesData(workflowData.nodes),
|
||||
edges: workflowData.edges,
|
||||
chatConfig
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'copy') {
|
||||
copyData(config, t('app:export_config_successful'));
|
||||
} else if (mode === 'json') {
|
||||
fileDownload({
|
||||
text: config,
|
||||
type: 'application/json;charset=utf-8',
|
||||
filename: `${appName}.json`
|
||||
});
|
||||
}
|
||||
},
|
||||
[appForm, appName, chatConfig, copyData, getWorkflowData, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyPopover
|
||||
placement={'right-start'}
|
||||
offset={[0, 20]}
|
||||
hasArrow
|
||||
trigger={'hover'}
|
||||
w={'8.6rem'}
|
||||
Trigger={
|
||||
<MyBox display={'flex'} cursor={'pointer'}>
|
||||
<MyIcon name={'export'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>
|
||||
</MyBox>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box p={1}>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('copy')}
|
||||
>
|
||||
<MyIcon name={'copy'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.copy_to_clipboard')}</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('json')}
|
||||
>
|
||||
<MyIcon name={'configmap'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.export_to_json')}</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ExportConfigPopover);
|
||||
@@ -10,8 +10,6 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { useRouter } from 'next/router';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { TeamContext, TeamModalContextProvider } from './components/context';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
@@ -28,7 +28,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
console.log('Init data time:', Date.now() - start);
|
||||
|
||||
success = 0;
|
||||
batchUpdateFields();
|
||||
|
||||
// batchUpdateFields();
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -55,48 +56,56 @@ const restore = async () => {
|
||||
};
|
||||
|
||||
const initData = async (batchSize: number) => {
|
||||
try {
|
||||
// 找到没有初始化的数据
|
||||
const dataList = await MongoDatasetData.find(
|
||||
{
|
||||
initFullText: { $exists: false }
|
||||
},
|
||||
'_id teamId datasetId collectionId fullTextToken'
|
||||
)
|
||||
.limit(batchSize)
|
||||
.lean();
|
||||
while (true) {
|
||||
try {
|
||||
// 找到没有初始化的数据
|
||||
const dataList = await MongoDatasetData.find(
|
||||
{
|
||||
initFullText: { $exists: false }
|
||||
},
|
||||
'_id teamId datasetId collectionId fullTextToken'
|
||||
)
|
||||
.limit(batchSize)
|
||||
.lean();
|
||||
|
||||
if (dataList.length === 0) return;
|
||||
if (dataList.length === 0) break;
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// 插入新数据
|
||||
const result = await MongoDatasetDataText.insertMany(
|
||||
dataList.map((item) => ({
|
||||
teamId: item.teamId,
|
||||
datasetId: item.datasetId,
|
||||
collectionId: item.collectionId,
|
||||
dataId: item._id,
|
||||
fullTextToken: item.fullTextToken
|
||||
})),
|
||||
{ ordered: false, session, lean: true }
|
||||
);
|
||||
try {
|
||||
await MongoDatasetDataText.insertMany(
|
||||
dataList.map((item) => ({
|
||||
teamId: item.teamId,
|
||||
datasetId: item.datasetId,
|
||||
collectionId: item.collectionId,
|
||||
dataId: item._id,
|
||||
fullTextToken: item.fullTextToken
|
||||
})),
|
||||
{ ordered: false, lean: true }
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.code === 11000) {
|
||||
console.log('Duplicate key error');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// FullText tmp 把成功插入的新数据的 dataId 更新为已初始化
|
||||
// 把成功插入的新数据的 dataId 更新为已初始化
|
||||
await MongoDatasetData.updateMany(
|
||||
{ _id: { $in: result.map((item) => item.dataId) } },
|
||||
{ $set: { initFullText: true } },
|
||||
{ session }
|
||||
{ _id: { $in: dataList.map((item) => item._id) } },
|
||||
// FullText tmp
|
||||
// { $set: { initFullText: true } }
|
||||
{ $set: { initFullText: true }, $unset: { fullTextToken: 1 } }
|
||||
);
|
||||
|
||||
success += result.length;
|
||||
success += dataList.length;
|
||||
console.log('Success:', success);
|
||||
});
|
||||
|
||||
await initData(batchSize);
|
||||
} catch (error: any) {
|
||||
console.log(error, '===');
|
||||
await delay(500);
|
||||
await initData(batchSize);
|
||||
// await initData(batchSize);
|
||||
} catch (error: any) {
|
||||
console.log(error, '===');
|
||||
await delay(500);
|
||||
// await initData(batchSize);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
'status'
|
||||
);
|
||||
if (!authCert) {
|
||||
return Promise.reject(UserErrEnum.binVisitor);
|
||||
return Promise.reject(UserErrEnum.account_psw_error);
|
||||
}
|
||||
|
||||
if (authCert.status === UserStatusEnum.forbidden) {
|
||||
@@ -38,7 +38,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return Promise.reject(UserErrEnum.binVisitor);
|
||||
return Promise.reject(UserErrEnum.account_psw_error);
|
||||
}
|
||||
|
||||
const userDetail = await getUserDetail({
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { clearCookie } from '@fastgpt/service/support/permission/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
clearCookie(res);
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
clearCookie(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postTransition2Workflow } from '@/web/core/app/api/app';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
import { SimpleAppSnapshotType } from './useSnapshots';
|
||||
import ExportConfigPopover from '@/pageComponents/app/detail/ExportConfigPopover';
|
||||
|
||||
const AppCard = ({
|
||||
appForm,
|
||||
@@ -118,6 +119,7 @@ const AppCard = ({
|
||||
)}
|
||||
{appDetail.permission.isOwner && (
|
||||
<MyMenu
|
||||
size={'xs'}
|
||||
Button={
|
||||
<IconButton
|
||||
variant={'whitePrimary'}
|
||||
@@ -129,6 +131,17 @@ const AppCard = ({
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: (
|
||||
<Flex>
|
||||
<ExportConfigPopover
|
||||
appName={appDetail.name}
|
||||
appForm={appForm}
|
||||
chatConfig={appDetail.chatConfig}
|
||||
/>
|
||||
</Flex>
|
||||
)
|
||||
},
|
||||
{
|
||||
icon: 'core/app/type/workflow',
|
||||
label: t('app:transition_to_workflow'),
|
||||
|
||||
@@ -6,27 +6,27 @@ import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { WorkflowContext } from './context';
|
||||
import { filterSensitiveNodesData } from '@/web/core/workflow/utils';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { publishStatusStyle } from '../constants';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
const ImportSettings = dynamic(() => import('./Flow/ImportSettings'));
|
||||
const ExportConfigPopover = dynamic(
|
||||
() => import('@/pageComponents/app/detail/ExportConfigPopover')
|
||||
);
|
||||
|
||||
const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { appDetail, onOpenInfoEdit, onOpenTeamTagModal, onDelApp } = useContextSelector(
|
||||
AppContext,
|
||||
(v) => v
|
||||
);
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const onOpenInfoEdit = useContextSelector(AppContext, (v) => v.onOpenInfoEdit);
|
||||
const onOpenTeamTagModal = useContextSelector(AppContext, (v) => v.onOpenTeamTagModal);
|
||||
const onDelApp = useContextSelector(AppContext, (v) => v.onDelApp);
|
||||
const flowData2StoreData = useContextSelector(WorkflowContext, (v) => v.flowData2StoreData);
|
||||
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
|
||||
@@ -92,10 +92,11 @@ const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved
|
||||
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
{ExportPopover({
|
||||
chatConfig: appDetail.chatConfig,
|
||||
appName: appDetail.name
|
||||
})}
|
||||
<ExportConfigPopover
|
||||
chatConfig={appDetail.chatConfig}
|
||||
appName={appDetail.name}
|
||||
getWorkflowData={flowData2StoreData}
|
||||
/>
|
||||
</MyBox>
|
||||
{appDetail.permission.hasWritePer && feConfigs?.show_team_chat && (
|
||||
<>
|
||||
@@ -148,6 +149,7 @@ const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved
|
||||
appDetail.permission.hasWritePer,
|
||||
appDetail.permission.isOwner,
|
||||
feConfigs?.show_team_chat,
|
||||
flowData2StoreData,
|
||||
onDelApp,
|
||||
onOpenImport,
|
||||
onOpenInfoEdit,
|
||||
@@ -210,104 +212,4 @@ const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved
|
||||
return Render;
|
||||
};
|
||||
|
||||
function ExportPopover({
|
||||
chatConfig,
|
||||
appName
|
||||
}: {
|
||||
chatConfig: AppChatConfigType;
|
||||
appName: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const flowData2StoreData = useContextSelector(WorkflowContext, (v) => v.flowData2StoreData);
|
||||
|
||||
const onExportWorkflow = useCallback(
|
||||
async (mode: 'copy' | 'json') => {
|
||||
const data = flowData2StoreData();
|
||||
if (data) {
|
||||
if (mode === 'copy') {
|
||||
copyData(
|
||||
JSON.stringify(
|
||||
{
|
||||
nodes: filterSensitiveNodesData(data.nodes),
|
||||
edges: data.edges,
|
||||
chatConfig
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
t('app:export_config_successful')
|
||||
);
|
||||
} else if (mode === 'json') {
|
||||
fileDownload({
|
||||
text: JSON.stringify(
|
||||
{
|
||||
nodes: filterSensitiveNodesData(data.nodes),
|
||||
edges: data.edges,
|
||||
chatConfig
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
type: 'application/json;charset=utf-8',
|
||||
filename: `${appName}.json`
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[appName, chatConfig, copyData, flowData2StoreData, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyPopover
|
||||
placement={'right-start'}
|
||||
offset={[0, 20]}
|
||||
hasArrow
|
||||
trigger={'hover'}
|
||||
w={'8.6rem'}
|
||||
Trigger={
|
||||
<MyBox display={'flex'} size={'md'} rounded={'4px'} cursor={'pointer'}>
|
||||
<MyIcon name={'export'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>
|
||||
</MyBox>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box p={1}>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('copy')}
|
||||
>
|
||||
<MyIcon name={'copy'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.copy_to_clipboard')}</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
py={'0.38rem'}
|
||||
px={1}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
borderRadius={'xs'}
|
||||
onClick={() => onExportWorkflow('json')}
|
||||
>
|
||||
<MyIcon name={'configmap'} w={'1rem'} mr={2} />
|
||||
<Box fontSize={'mini'}>{t('common:common.export_to_json')}</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppCard;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { DragEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { Textarea, Button, ModalBody, ModalFooter, Flex, Box } from '@chakra-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../context';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const ImportAppConfigEditor = dynamic(() => import('@/pageComponents/app/ImportAppConfigEditor'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
@@ -15,63 +17,10 @@ type Props = {
|
||||
|
||||
const ImportSettings = ({ onClose }: Props) => {
|
||||
const { toast } = useToast();
|
||||
const { File, onOpen } = useSelectFile({
|
||||
fileType: 'json',
|
||||
multiple: false
|
||||
});
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [value, setValue] = useState('');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const readJSONFile = useCallback(
|
||||
(file: File) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!file.name.endsWith('.json')) {
|
||||
toast({
|
||||
title: t('app:not_json_file'),
|
||||
status: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.target) {
|
||||
const res = JSON.parse(e.target.result as string);
|
||||
setValue(JSON.stringify(res, null, 2));
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[t, toast]
|
||||
);
|
||||
|
||||
const handleDragEnter = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
}, []);
|
||||
|
||||
const handleDragLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
const handleDrop = useCallback(
|
||||
async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer.files[0];
|
||||
readJSONFile(file);
|
||||
setIsDragging(false);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
readJSONFile(file);
|
||||
},
|
||||
[readJSONFile]
|
||||
);
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
@@ -80,67 +29,10 @@ const ImportSettings = ({ onClose }: Props) => {
|
||||
iconSrc="common/importLight"
|
||||
iconColor="primary.600"
|
||||
title={t('app:import_configs')}
|
||||
size={isPc ? 'lg' : 'md'}
|
||||
size={'md'}
|
||||
>
|
||||
<ModalBody>
|
||||
<File onSelect={onSelectFile} />
|
||||
{isDragging ? (
|
||||
<Flex
|
||||
align={'center'}
|
||||
justify={'center'}
|
||||
w={'31rem'}
|
||||
h={'17.5rem'}
|
||||
borderRadius={'md'}
|
||||
border={'1px dashed'}
|
||||
borderColor={'myGray.400'}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Flex align={'center'} justify={'center'} flexDir={'column'} gap={'0.62rem'}>
|
||||
<MyIcon name={'configmap'} w={'1.5rem'} color={'myGray.500'} />
|
||||
<Box color={'myGray.600'} fontSize={'mini'}>
|
||||
{t('app:file_recover')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<Box w={['100%', '31rem']}>
|
||||
<Flex justify={'space-between'} align={'center'} pb={2}>
|
||||
<Box fontSize={'sm'} color={'myGray.900'} fontWeight={'500'}>
|
||||
{t('common:common.json_config')}
|
||||
</Box>
|
||||
<Button onClick={onOpen} variant={'whiteBase'} p={0}>
|
||||
<Flex px={'0.88rem'} py={'0.44rem'} color={'myGray.600'} fontSize={'mini'}>
|
||||
<MyIcon name={'file/uploadFile'} w={'1rem'} mr={'0.38rem'} />
|
||||
{t('common:common.upload_file')}
|
||||
</Flex>
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
border={'1px solid'}
|
||||
borderRadius={'md'}
|
||||
borderColor={'myGray.200'}
|
||||
value={value}
|
||||
placeholder={
|
||||
isPc
|
||||
? t('app:paste_config') + '\n' + t('app:or_drag_JSON')
|
||||
: t('app:paste_config')
|
||||
}
|
||||
rows={16}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<ImportAppConfigEditor value={value} onChange={setValue} rows={16} />
|
||||
</ModalBody>
|
||||
<ModalFooter justifyItems={'flex-end'}>
|
||||
<Button
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import React from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { ModalBody, Button, ModalFooter, useDisclosure, Textarea, Box } from '@chakra-ui/react';
|
||||
import { ModalBody, Button, ModalFooter, Textarea } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import parse from '@bany/curl-to-json';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
|
||||
type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
||||
const methodMap: { [K in RequestMethod]: string } = {
|
||||
get: 'GET',
|
||||
post: 'POST',
|
||||
put: 'PUT',
|
||||
delete: 'DELETE',
|
||||
patch: 'PATCH'
|
||||
};
|
||||
import { parseCurl } from '@fastgpt/global/common/string/http';
|
||||
|
||||
const CurlImportModal = ({
|
||||
nodeId,
|
||||
@@ -49,22 +40,7 @@ const CurlImportModal = ({
|
||||
|
||||
if (!requestUrl || !requestMethod || !params || !headers || !jsonBody) return;
|
||||
|
||||
const parsed = parse(content);
|
||||
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 parsed = parseCurl(content);
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -82,7 +58,7 @@ const CurlImportModal = ({
|
||||
key: NodeInputKeyEnum.httpMethod,
|
||||
value: {
|
||||
...requestMethod,
|
||||
value: methodMap[parsed.method?.toLowerCase() as RequestMethod] || 'GET'
|
||||
value: parsed.method
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,7 +68,7 @@ const CurlImportModal = ({
|
||||
key: NodeInputKeyEnum.httpParams,
|
||||
value: {
|
||||
...params,
|
||||
value: newParams
|
||||
value: parsed.params
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,7 +78,7 @@ const CurlImportModal = ({
|
||||
key: NodeInputKeyEnum.httpHeaders,
|
||||
value: {
|
||||
...headers,
|
||||
value: newHeaders
|
||||
value: parsed.headers
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,7 +88,7 @@ const CurlImportModal = ({
|
||||
key: NodeInputKeyEnum.httpJsonBody,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: newBody
|
||||
value: parsed.body
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { Box, Flex, Button, ModalBody, Input, Grid, Card } from '@chakra-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
ModalBody,
|
||||
Input,
|
||||
Grid,
|
||||
Card,
|
||||
Textarea,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { postCreateApp } from '@/web/core/app/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import { emptyTemplates } from '@/web/core/app/templates';
|
||||
import { emptyTemplates, parsePluginFromCurlString } from '@/web/core/app/templates';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
@@ -21,10 +31,13 @@ import {
|
||||
getTemplateMarketItemList
|
||||
} from '@/web/core/app/api/template';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { appTypeMap } from '@/pageComponents/app/constants';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
name: string;
|
||||
curlContent: string;
|
||||
};
|
||||
|
||||
export type CreateAppType = AppTypeEnum.simple | AppTypeEnum.workflow | AppTypeEnum.plugin;
|
||||
@@ -44,27 +57,10 @@ const CreateModal = ({
|
||||
const { isPc } = useSystem();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const typeMap = useRef({
|
||||
[AppTypeEnum.simple]: {
|
||||
icon: 'core/app/simpleBot',
|
||||
title: t('app:type.Create simple bot'),
|
||||
avatar: 'core/app/type/simpleFill',
|
||||
emptyCreateText: t('app:create_empty_app')
|
||||
},
|
||||
[AppTypeEnum.workflow]: {
|
||||
icon: 'core/app/type/workflowFill',
|
||||
avatar: 'core/app/type/workflowFill',
|
||||
title: t('app:type.Create workflow bot'),
|
||||
emptyCreateText: t('app:create_empty_workflow')
|
||||
},
|
||||
[AppTypeEnum.plugin]: {
|
||||
icon: 'core/app/type/pluginFill',
|
||||
avatar: 'core/app/type/pluginFill',
|
||||
title: t('app:type.Create plugin bot'),
|
||||
emptyCreateText: t('app:create_empty_plugin')
|
||||
}
|
||||
});
|
||||
const typeData = typeMap.current[type];
|
||||
const [currentCreateType, setCurrentCreateType] = useState<'template' | 'curl'>('template');
|
||||
const isTemplateMode = currentCreateType === 'template';
|
||||
|
||||
const typeData = appTypeMap[type];
|
||||
const { data: templateList = [], loading: isRequestTemplates } = useRequest2(
|
||||
() => getTemplateMarketItemList({ isQuickTemplate: true, type }),
|
||||
{
|
||||
@@ -75,11 +71,12 @@ const CreateModal = ({
|
||||
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
avatar: typeData.avatar,
|
||||
name: ''
|
||||
name: '',
|
||||
curlContent: ''
|
||||
}
|
||||
});
|
||||
const avatar = watch('avatar');
|
||||
|
||||
const avatar = watch('avatar');
|
||||
const {
|
||||
File,
|
||||
onOpen: onOpenSelectFile,
|
||||
@@ -90,12 +87,13 @@ const CreateModal = ({
|
||||
});
|
||||
|
||||
const { runAsync: onclickCreate, loading: isCreating } = useRequest2(
|
||||
async (data: FormType, templateId?: string) => {
|
||||
if (!templateId) {
|
||||
async ({ avatar, name, curlContent }: FormType, templateId?: string) => {
|
||||
// From empty template
|
||||
if (!templateId && currentCreateType !== 'curl') {
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: data.avatar,
|
||||
name: data.name,
|
||||
avatar: avatar,
|
||||
name: name,
|
||||
type,
|
||||
modules: emptyTemplates[type].nodes,
|
||||
edges: emptyTemplates[type].edges,
|
||||
@@ -103,15 +101,31 @@ const CreateModal = ({
|
||||
});
|
||||
}
|
||||
|
||||
const templateDetail = await getTemplateMarketItemDetail(templateId);
|
||||
const { workflow, appAvatar } = await (async () => {
|
||||
if (templateId) {
|
||||
const templateDetail = await getTemplateMarketItemDetail(templateId);
|
||||
return {
|
||||
appAvatar: templateDetail.avatar,
|
||||
workflow: templateDetail.workflow
|
||||
};
|
||||
}
|
||||
if (curlContent) {
|
||||
return {
|
||||
appAvatar: avatar,
|
||||
workflow: parsePluginFromCurlString(curlContent)
|
||||
};
|
||||
}
|
||||
return Promise.reject('No template or curl content');
|
||||
})();
|
||||
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: data.avatar || templateDetail.avatar,
|
||||
name: data.name,
|
||||
type: templateDetail.type as AppTypeEnum,
|
||||
modules: templateDetail.workflow.nodes || [],
|
||||
edges: templateDetail.workflow.edges || [],
|
||||
chatConfig: templateDetail.workflow.chatConfig
|
||||
avatar: appAvatar,
|
||||
name: name,
|
||||
type,
|
||||
modules: workflow.nodes || [],
|
||||
edges: workflow.edges || [],
|
||||
chatConfig: workflow.chatConfig || {}
|
||||
});
|
||||
},
|
||||
{
|
||||
@@ -128,14 +142,13 @@ const CreateModal = ({
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={typeData.icon}
|
||||
title={typeData.title}
|
||||
title={t(typeData.title)}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
isCentered={!isPc}
|
||||
maxW={['90vw', '40rem']}
|
||||
isLoading={isCreating || isRequestTemplates}
|
||||
>
|
||||
<ModalBody px={9} pb={8}>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('common:common.Set Name')}
|
||||
</Box>
|
||||
@@ -161,111 +174,151 @@ const CreateModal = ({
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={[4, 7]} mb={[0, 3]}>
|
||||
<Box color={'myGray.900'} fontWeight={'bold'} fontSize={'sm'}>
|
||||
{t('common:core.app.Select app from template')}
|
||||
</Box>
|
||||
<Box flex={1} />
|
||||
<Flex
|
||||
onClick={() => onOpenTemplateModal(type)}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
fontSize={'xs'}
|
||||
_hover={{ color: 'blue.700' }}
|
||||
>
|
||||
{t('common:core.app.more')}
|
||||
<ChevronRightIcon w={4} h={4} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Grid
|
||||
userSelect={'none'}
|
||||
gridTemplateColumns={templateList.length > 0 ? ['repeat(1,1fr)', 'repeat(2,1fr)'] : '1fr'}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
<Card
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
boxShadow={'3'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
color={'myGray.500'}
|
||||
borderColor={'myGray.200'}
|
||||
h={'8.25rem'}
|
||||
_hover={{
|
||||
color: 'primary.700',
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
onClick={handleSubmit((data) => onclickCreate(data))}
|
||||
>
|
||||
<MyIcon name={'common/addLight'} w={'1.5rem'} />
|
||||
<Box fontSize={'sm'} mt={2}>
|
||||
{typeData.emptyCreateText}
|
||||
|
||||
<Flex mt={[4, 7]} mb={3}>
|
||||
{type === AppTypeEnum.plugin ? (
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{ label: t('app:create_by_template'), value: 'template' },
|
||||
{ label: t('app:create_by_curl'), value: 'curl' }
|
||||
]}
|
||||
value={currentCreateType}
|
||||
fontSize={'xs'}
|
||||
onChange={(e) => setCurrentCreateType(e as 'template' | 'curl')}
|
||||
/>
|
||||
) : (
|
||||
<Box color={'myGray.900'} fontWeight={'bold'} fontSize={'sm'}>
|
||||
{t('app:create_by_template')}
|
||||
</Box>
|
||||
</Card>
|
||||
{templateList.map((item) => (
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{isTemplateMode && (
|
||||
<Flex
|
||||
onClick={() => onOpenTemplateModal(type)}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
fontSize={'xs'}
|
||||
_hover={{ color: 'blue.700' }}
|
||||
>
|
||||
{t('common:core.app.more')}
|
||||
<ChevronRightIcon w={4} h={4} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{isTemplateMode ? (
|
||||
<Grid
|
||||
userSelect={'none'}
|
||||
gridTemplateColumns={
|
||||
templateList.length > 0 ? ['repeat(1,1fr)', 'repeat(2,1fr)'] : '1fr'
|
||||
}
|
||||
gridGap={[2, 4]}
|
||||
>
|
||||
<Card
|
||||
key={item.templateId}
|
||||
p={4}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
boxShadow={'3'}
|
||||
h={'8.25rem'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300',
|
||||
'& .buttons': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
color={'myGray.500'}
|
||||
borderColor={'myGray.200'}
|
||||
h={'8.25rem'}
|
||||
_hover={{
|
||||
color: 'primary.700',
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
onClick={handleSubmit((data) => onclickCreate(data))}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} borderRadius={'sm'} w={'1.5rem'} />
|
||||
<Box ml={3} color={'myGray.900'} fontWeight={500}>
|
||||
{t(item.name as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box fontSize={'xs'} mt={2} color={'myGray.600'} flex={1}>
|
||||
{t(item.intro as any)}
|
||||
</Box>
|
||||
<Box w={'full'} fontSize={'mini'}>
|
||||
<Box color={'myGray.500'}>{`By ${item.author || feConfigs.systemTitle}`}</Box>
|
||||
<Box
|
||||
className="buttons"
|
||||
display={'none'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'absolute'}
|
||||
borderRadius={'lg'}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={1}
|
||||
height={'40px'}
|
||||
bg={'white'}
|
||||
zIndex={1}
|
||||
>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
h={6}
|
||||
borderRadius={'sm'}
|
||||
w={'40%'}
|
||||
onClick={handleSubmit((data) => onclickCreate(data, item.templateId))}
|
||||
>
|
||||
{t('app:templateMarket.Use')}
|
||||
</Button>
|
||||
</Box>
|
||||
<MyIcon name={'common/addLight'} w={'1.5rem'} />
|
||||
<Box fontSize={'sm'} mt={2}>
|
||||
{t(typeData.emptyCreateText)}
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
{templateList.map((item) => (
|
||||
<Card
|
||||
key={item.templateId}
|
||||
p={4}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={'3'}
|
||||
h={'8.25rem'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300',
|
||||
'& .buttons': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} borderRadius={'sm'} w={'1.5rem'} />
|
||||
<Box ml={3} color={'myGray.900'} fontWeight={500}>
|
||||
{t(item.name as any)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box fontSize={'xs'} mt={2} color={'myGray.600'} flex={1}>
|
||||
{t(item.intro as any)}
|
||||
</Box>
|
||||
<Box w={'full'} fontSize={'mini'}>
|
||||
<Box color={'myGray.500'}>{`By ${item.author || feConfigs.systemTitle}`}</Box>
|
||||
<Box
|
||||
className="buttons"
|
||||
display={'none'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'absolute'}
|
||||
borderRadius={'lg'}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={1}
|
||||
height={'40px'}
|
||||
bg={'white'}
|
||||
zIndex={1}
|
||||
>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
h={6}
|
||||
borderRadius={'sm'}
|
||||
w={'40%'}
|
||||
onClick={handleSubmit((data) => onclickCreate(data, item.templateId))}
|
||||
>
|
||||
{t('app:templateMarket.Use')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Box>
|
||||
<Textarea
|
||||
placeholder={t('app:oaste_curl_string')}
|
||||
w={'560px'}
|
||||
h={'260px'}
|
||||
bg={'myGray.50'}
|
||||
{...register('curlContent')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter gap={4}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button variant={'primary'} onClick={handleSubmit((data) => onclickCreate(data))}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
<File
|
||||
onSelect={(e) =>
|
||||
onSelectImage(e, {
|
||||
|
||||
178
projects/app/src/pages/app/list/components/JsonImportModal.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { appTypeMap } from '@/pageComponents/app/constants';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useMemo } from 'react';
|
||||
import { getAppType } from '@fastgpt/global/core/app/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postCreateApp } from '@/web/core/app/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
import ImportAppConfigEditor from '@/pageComponents/app/ImportAppConfigEditor';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
name: string;
|
||||
workflowStr: string;
|
||||
};
|
||||
|
||||
const JsonImportModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||
const router = useRouter();
|
||||
|
||||
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
avatar: '',
|
||||
name: '',
|
||||
workflowStr: ''
|
||||
}
|
||||
});
|
||||
const workflowStr = watch('workflowStr');
|
||||
|
||||
const avatar = watch('avatar');
|
||||
const {
|
||||
File,
|
||||
onOpen: onOpenSelectFile,
|
||||
onSelectImage
|
||||
} = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
});
|
||||
// If the user does not select an avatar, it will follow the type to change
|
||||
const selectedAvatar = useMemo(() => {
|
||||
if (avatar) return avatar;
|
||||
|
||||
const defaultVal = appTypeMap[AppTypeEnum.simple].avatar;
|
||||
if (!workflowStr) return defaultVal;
|
||||
|
||||
try {
|
||||
const workflow = JSON.parse(workflowStr);
|
||||
const type = getAppType(workflow);
|
||||
if (type) return appTypeMap[type].avatar;
|
||||
return defaultVal;
|
||||
} catch (err) {
|
||||
return defaultVal;
|
||||
}
|
||||
}, [avatar, workflowStr]);
|
||||
|
||||
const { runAsync: onSubmit, loading: isCreating } = useRequest2(
|
||||
async ({ name, workflowStr }: FormType) => {
|
||||
const { workflow, appType } = await (async () => {
|
||||
try {
|
||||
const workflow = JSON.parse(workflowStr);
|
||||
const appType = getAppType(workflow);
|
||||
|
||||
if (!appType) {
|
||||
return Promise.reject(t('app:type_not_recognized'));
|
||||
}
|
||||
|
||||
if (appType === AppTypeEnum.simple) {
|
||||
return {
|
||||
workflow: form2AppWorkflow(workflow, t),
|
||||
appType
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
workflow,
|
||||
appType
|
||||
};
|
||||
} catch (err) {
|
||||
return Promise.reject(t('app:invalid_json_format'));
|
||||
}
|
||||
})();
|
||||
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: selectedAvatar,
|
||||
name,
|
||||
type: appType,
|
||||
modules: workflow.nodes,
|
||||
edges: workflow.edges,
|
||||
chatConfig: workflow.chatConfig
|
||||
});
|
||||
},
|
||||
{
|
||||
refreshDeps: [selectedAvatar],
|
||||
onSuccess(id: string) {
|
||||
router.push(`/app/detail?appId=${id}`);
|
||||
loadMyApps();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Create Success')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
isLoading={isCreating}
|
||||
title={t('app:type.Import from json')}
|
||||
iconSrc="common/importLight"
|
||||
iconColor={'primary.600'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('common:common.Set Name')}
|
||||
</Box>
|
||||
<Flex mt={2} alignItems={'center'}>
|
||||
<MyTooltip label={t('common:common.Set Avatar')}>
|
||||
<Avatar
|
||||
flexShrink={0}
|
||||
src={selectedAvatar}
|
||||
w={['1.75rem', '2.25rem']}
|
||||
h={['1.75rem', '2.25rem']}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={onOpenSelectFile}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Input
|
||||
flex={1}
|
||||
ml={3}
|
||||
autoFocus
|
||||
bg={'myWhite.600'}
|
||||
{...register('name', {
|
||||
required: t('common:core.app.error.App name can not be empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={5}>
|
||||
<ImportAppConfigEditor
|
||||
value={workflowStr}
|
||||
onChange={(e) => setValue('workflowStr', e)}
|
||||
rows={10}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={4}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common:common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
<File
|
||||
onSelect={(e) =>
|
||||
onSelectImage(e, {
|
||||
maxH: 300,
|
||||
maxW: 300,
|
||||
callback: (e) => setValue('avatar', e)
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonImportModal;
|
||||
@@ -37,6 +37,7 @@ import { webPushTrack } from '@/web/common/middle/tracks/utils';
|
||||
import { AppTemplateSchemaType, TemplateTypeSchemaType } from '@fastgpt/global/core/app/type';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
import UseGuideModal from '@/components/common/Modal/UseGuideModal';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
|
||||
type TemplateAppType = AppTypeEnum | 'all';
|
||||
|
||||
@@ -94,14 +95,18 @@ const TemplateMarketModal = ({
|
||||
const { runAsync: onUseTemplate, loading: isCreating } = useRequest2(
|
||||
async (template: AppTemplateSchemaType) => {
|
||||
const templateDetail = await getTemplateMarketItemDetail(template.templateId);
|
||||
let workflow = templateDetail.workflow;
|
||||
if (templateDetail.type === AppTypeEnum.simple) {
|
||||
workflow = form2AppWorkflow(workflow, t);
|
||||
}
|
||||
return postCreateApp({
|
||||
parentId,
|
||||
avatar: template.avatar,
|
||||
name: template.name,
|
||||
type: template.type as AppTypeEnum,
|
||||
modules: templateDetail.workflow.nodes || [],
|
||||
edges: templateDetail.workflow.edges || [],
|
||||
chatConfig: templateDetail.workflow.chatConfig
|
||||
modules: workflow.nodes || [],
|
||||
edges: workflow.edges || [],
|
||||
chatConfig: workflow.chatConfig
|
||||
}).then((res) => {
|
||||
webPushTrack.useAppTemplate({
|
||||
id: res,
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import TemplateMarketModal from './components/TemplateMarketModal';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import JsonImportModal from './components/JsonImportModal';
|
||||
|
||||
const CreateModal = dynamic(() => import('./components/CreateModal'));
|
||||
const EditFolderModal = dynamic(
|
||||
@@ -64,6 +65,11 @@ const MyApps = () => {
|
||||
onOpen: onOpenCreateHttpPlugin,
|
||||
onClose: onCloseCreateHttpPlugin
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenJsonImportModal,
|
||||
onOpen: onOpenJsonImportModal,
|
||||
onClose: onCloseJsonImportModal
|
||||
} = useDisclosure();
|
||||
const [editFolder, setEditFolder] = useState<EditFolderFormType>();
|
||||
const [templateModalType, setTemplateModalType] = useState<AppTypeEnum | 'all'>();
|
||||
|
||||
@@ -244,6 +250,16 @@ const MyApps = () => {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'core/app/type/jsonImport',
|
||||
label: t('app:type.Import from json'),
|
||||
description: t('app:type.Import from json tip'),
|
||||
onClick: onOpenJsonImportModal
|
||||
}
|
||||
]
|
||||
},
|
||||
...(isPc
|
||||
? []
|
||||
: [
|
||||
@@ -375,6 +391,7 @@ const MyApps = () => {
|
||||
defaultType={templateModalType}
|
||||
/>
|
||||
)}
|
||||
{isOpenJsonImportModal && <JsonImportModal onClose={onCloseJsonImportModal} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -74,7 +74,6 @@ export const useSelectFile = (props?: {
|
||||
maxW,
|
||||
maxH
|
||||
});
|
||||
console.log(src, '--');
|
||||
callback?.(src);
|
||||
return src;
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { AppItemType } from '@/types/app';
|
||||
import { parseCurl } from '@fastgpt/global/common/string/http';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import {
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
export const emptyTemplates: Record<
|
||||
@@ -253,7 +250,8 @@ export const emptyTemplates: Record<
|
||||
sourceHandle: '448745-source-right',
|
||||
targetHandle: 'loOvhld2ZTKa-target-left'
|
||||
}
|
||||
]
|
||||
],
|
||||
chatConfig: {}
|
||||
},
|
||||
[AppTypeEnum.workflow]: {
|
||||
avatar: 'core/app/type/workflowFill',
|
||||
@@ -264,7 +262,7 @@ export const emptyTemplates: Record<
|
||||
name: i18nT('common:core.module.template.system_config'),
|
||||
intro: i18nT('common:core.module.template.system_config_info'),
|
||||
avatar: 'core/workflow/template/systemConfig',
|
||||
flowNodeType: 'userGuide',
|
||||
flowNodeType: FlowNodeTypeEnum.systemConfig,
|
||||
position: {
|
||||
x: 262.2732338817093,
|
||||
y: -476.00241136598146
|
||||
@@ -273,22 +271,22 @@ export const emptyTemplates: Record<
|
||||
inputs: [
|
||||
{
|
||||
key: 'welcomeText',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: 'string',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: 'core.app.Welcome Text',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'variables',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: 'any',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: 'core.app.Chat Variable',
|
||||
value: []
|
||||
},
|
||||
{
|
||||
key: 'questionGuide',
|
||||
valueType: 'any',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
label: 'core.app.Question Guide',
|
||||
value: {
|
||||
open: false
|
||||
@@ -296,8 +294,8 @@ export const emptyTemplates: Record<
|
||||
},
|
||||
{
|
||||
key: 'tts',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: 'any',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: '',
|
||||
value: {
|
||||
type: 'web'
|
||||
@@ -305,8 +303,8 @@ export const emptyTemplates: Record<
|
||||
},
|
||||
{
|
||||
key: 'whisper',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: 'any',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: '',
|
||||
value: {
|
||||
open: false,
|
||||
@@ -316,8 +314,8 @@ export const emptyTemplates: Record<
|
||||
},
|
||||
{
|
||||
key: 'scheduleTrigger',
|
||||
renderTypeList: ['hidden'],
|
||||
valueType: 'any',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
label: '',
|
||||
value: null
|
||||
}
|
||||
@@ -329,7 +327,7 @@ export const emptyTemplates: Record<
|
||||
name: i18nT('common:core.module.template.work_start'),
|
||||
intro: '',
|
||||
avatar: 'core/workflow/template/workflowStart',
|
||||
flowNodeType: 'workflowStart',
|
||||
flowNodeType: FlowNodeTypeEnum.workflowStart,
|
||||
position: {
|
||||
x: 632.368838596004,
|
||||
y: -347.7446492944009
|
||||
@@ -338,8 +336,8 @@ export const emptyTemplates: Record<
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
renderTypeList: ['reference', 'textarea'],
|
||||
valueType: 'string',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.textarea],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: i18nT('common:core.module.input.label.user question'),
|
||||
required: true,
|
||||
toolDescription: i18nT('common:core.module.input.label.user question')
|
||||
@@ -350,13 +348,14 @@ export const emptyTemplates: Record<
|
||||
id: 'userChatInput',
|
||||
key: 'userChatInput',
|
||||
label: 'common:core.module.input.label.user question',
|
||||
type: 'static',
|
||||
valueType: 'string'
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
edges: []
|
||||
edges: [],
|
||||
chatConfig: {}
|
||||
},
|
||||
[AppTypeEnum.plugin]: {
|
||||
avatar: 'core/app/type/pluginFill',
|
||||
@@ -405,6 +404,387 @@ export const emptyTemplates: Record<
|
||||
outputs: []
|
||||
}
|
||||
],
|
||||
edges: []
|
||||
edges: [],
|
||||
chatConfig: {}
|
||||
}
|
||||
};
|
||||
|
||||
export const parsePluginFromCurlString = (
|
||||
curl: string
|
||||
): {
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
} => {
|
||||
const { url, method, headers, body, params, bodyArray } = parseCurl(curl);
|
||||
|
||||
const allInputs = Array.from(
|
||||
new Map([...params, ...bodyArray].map((item) => [item.key, item])).values()
|
||||
);
|
||||
const formatPluginStartInputs = allInputs
|
||||
.map((item) => {
|
||||
const valueType = item.value === null ? 'string' : typeof item.value;
|
||||
const valueTypeMap = {
|
||||
string: {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
isToolType: true,
|
||||
defaultValue: item.value
|
||||
},
|
||||
number: {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
isToolType: true,
|
||||
defaultValue: item.value
|
||||
},
|
||||
boolean: {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.switch, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.boolean,
|
||||
isToolType: true,
|
||||
defaultValue: item.value
|
||||
},
|
||||
object: {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.JSONEditor, FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.object,
|
||||
isToolType: false,
|
||||
defaultValue: ''
|
||||
}
|
||||
};
|
||||
|
||||
const valueTypeItem = valueTypeMap[valueType as keyof typeof valueTypeMap];
|
||||
if (!valueTypeItem) return;
|
||||
|
||||
return {
|
||||
renderTypeList: valueTypeItem.renderTypeList,
|
||||
selectedTypeIndex: 0,
|
||||
valueType: valueTypeItem.valueType,
|
||||
canEdit: true,
|
||||
key: item.key,
|
||||
label: item.key,
|
||||
description: '',
|
||||
defaultValue: valueTypeItem.defaultValue,
|
||||
required: false,
|
||||
toolDescription: valueTypeItem.isToolType ? item.key : ''
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as FlowNodeInputItemType[];
|
||||
const formatPluginStartOutputs = formatPluginStartInputs.map<FlowNodeOutputItemType>((item) => ({
|
||||
id: item.key,
|
||||
key: item.key,
|
||||
label: item.key,
|
||||
valueType: item.valueType,
|
||||
type: FlowNodeOutputTypeEnum.hidden
|
||||
}));
|
||||
|
||||
const referenceHeaders = headers.map((item) => ({
|
||||
key: item.key,
|
||||
value: item.value,
|
||||
type: item.type
|
||||
}));
|
||||
const referenceParams = params.map((item) => ({
|
||||
key: item.key,
|
||||
value: `{{$pluginInput.${item.key}$}}`,
|
||||
type: item.type
|
||||
}));
|
||||
const referenceBody = Object.entries(JSON.parse(body)).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[key] =
|
||||
typeof value === 'string' ? `###{{$pluginInput.${key}$}}###` : `{{$pluginInput.${key}$}}`;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
const referenceBodyStr = JSON.stringify(referenceBody, null, 2)
|
||||
.replace(/"{{\$/g, '{{$')
|
||||
.replace(/\$}}"/g, '$}}')
|
||||
.replace(/###{{\$/g, '{{$')
|
||||
.replace(/\$}}###/g, '$}}');
|
||||
|
||||
return {
|
||||
nodes: [
|
||||
{
|
||||
nodeId: 'pluginInput',
|
||||
name: i18nT('workflow:template.plugin_start'),
|
||||
intro: i18nT('workflow:intro_plugin_input'),
|
||||
avatar: 'core/workflow/template/workflowStart',
|
||||
flowNodeType: FlowNodeTypeEnum.pluginInput,
|
||||
showStatus: false,
|
||||
position: {
|
||||
x: 427.6554681270263,
|
||||
y: -291.6987155252725
|
||||
},
|
||||
version: '481',
|
||||
inputs: formatPluginStartInputs,
|
||||
outputs: formatPluginStartOutputs
|
||||
},
|
||||
{
|
||||
nodeId: 'pluginOutput',
|
||||
name: i18nT('common:core.module.template.self_output'),
|
||||
intro: i18nT('workflow:intro_custom_plugin_output'),
|
||||
avatar: 'core/workflow/template/pluginOutput',
|
||||
flowNodeType: FlowNodeTypeEnum.pluginOutput,
|
||||
showStatus: false,
|
||||
position: {
|
||||
x: 1870.1072210870427,
|
||||
y: -126.69871552527252
|
||||
},
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
canEdit: true,
|
||||
key: 'result',
|
||||
label: 'result',
|
||||
isToolOutput: true,
|
||||
description: '',
|
||||
required: true,
|
||||
value: ['vumlECDQTjeC', 'httpRawResponse']
|
||||
},
|
||||
{
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.object,
|
||||
canEdit: true,
|
||||
key: 'error',
|
||||
label: 'error',
|
||||
isToolOutput: true,
|
||||
description: '',
|
||||
required: true,
|
||||
value: ['vumlECDQTjeC', 'error']
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
nodeId: 'vumlECDQTjeC',
|
||||
name: 'HTTP 请求',
|
||||
intro: '可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
avatar: 'core/workflow/template/httpRequest',
|
||||
flowNodeType: FlowNodeTypeEnum.httpRequest468,
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 1049.4419012643668,
|
||||
y: -471.49748139163944
|
||||
},
|
||||
version: '481',
|
||||
inputs: [
|
||||
{
|
||||
key: 'system_addInputParam',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.addInputParam],
|
||||
valueType: WorkflowIOValueTypeEnum.dynamic,
|
||||
label: '',
|
||||
required: false,
|
||||
description: '接收前方节点的输出值作为变量,这些变量可以被 HTTP 请求参数使用。',
|
||||
customInputConfig: {
|
||||
selectValueTypeList: [
|
||||
WorkflowIOValueTypeEnum.string,
|
||||
WorkflowIOValueTypeEnum.number,
|
||||
WorkflowIOValueTypeEnum.boolean,
|
||||
WorkflowIOValueTypeEnum.object,
|
||||
WorkflowIOValueTypeEnum.arrayString,
|
||||
WorkflowIOValueTypeEnum.arrayNumber,
|
||||
WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
WorkflowIOValueTypeEnum.arrayObject,
|
||||
WorkflowIOValueTypeEnum.arrayAny,
|
||||
WorkflowIOValueTypeEnum.any,
|
||||
WorkflowIOValueTypeEnum.chatHistory,
|
||||
WorkflowIOValueTypeEnum.datasetQuote,
|
||||
WorkflowIOValueTypeEnum.dynamic,
|
||||
WorkflowIOValueTypeEnum.selectApp,
|
||||
WorkflowIOValueTypeEnum.selectDataset
|
||||
],
|
||||
showDescription: false,
|
||||
showDefaultValue: true
|
||||
},
|
||||
valueDesc: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpMethod',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: '',
|
||||
value: method,
|
||||
required: true,
|
||||
valueDesc: '',
|
||||
description: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpTimeout',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: '',
|
||||
value: 30,
|
||||
min: 5,
|
||||
max: 600,
|
||||
required: true,
|
||||
valueDesc: '',
|
||||
description: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpReqUrl',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: '',
|
||||
description:
|
||||
'新的 HTTP 请求地址。如果出现两个"请求地址",可以删除该模块重新加入,会拉取最新的模块配置。',
|
||||
placeholder: 'https://api.ai.com/getInventory',
|
||||
required: false,
|
||||
value: url,
|
||||
valueDesc: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpHeader',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: referenceHeaders,
|
||||
label: '',
|
||||
description:
|
||||
'自定义请求头,请严格填入 JSON 字符串。\n1. 确保最后一个属性没有逗号\n2. 确保 key 包含双引号\n例如:{"Authorization":"Bearer xxx"}',
|
||||
placeholder: 'common:core.module.input.description.Http Request Header',
|
||||
required: false,
|
||||
valueDesc: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpParams',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: referenceParams,
|
||||
description:
|
||||
'新的 HTTP 请求地址。如果出现两个“请求地址”,可以删除该模块重新加入,会拉取最新的模块配置。',
|
||||
label: '',
|
||||
required: false,
|
||||
valueDesc: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpJsonBody',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: referenceBodyStr,
|
||||
label: '',
|
||||
required: false,
|
||||
valueDesc: '',
|
||||
description: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpFormBody',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: [],
|
||||
label: '',
|
||||
required: false,
|
||||
valueDesc: '',
|
||||
description: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
},
|
||||
{
|
||||
key: 'system_httpContentType',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
value: 'json',
|
||||
label: '',
|
||||
required: false,
|
||||
valueDesc: '',
|
||||
description: '',
|
||||
debugLabel: '',
|
||||
toolDescription: ''
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
id: 'system_addOutputParam',
|
||||
key: 'system_addOutputParam',
|
||||
type: FlowNodeOutputTypeEnum.dynamic,
|
||||
valueType: WorkflowIOValueTypeEnum.dynamic,
|
||||
label: '输出字段提取',
|
||||
customFieldConfig: {
|
||||
selectValueTypeList: [
|
||||
WorkflowIOValueTypeEnum.string,
|
||||
WorkflowIOValueTypeEnum.number,
|
||||
WorkflowIOValueTypeEnum.boolean,
|
||||
WorkflowIOValueTypeEnum.object,
|
||||
WorkflowIOValueTypeEnum.arrayString,
|
||||
WorkflowIOValueTypeEnum.arrayNumber,
|
||||
WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
WorkflowIOValueTypeEnum.arrayObject,
|
||||
WorkflowIOValueTypeEnum.arrayAny,
|
||||
WorkflowIOValueTypeEnum.any,
|
||||
WorkflowIOValueTypeEnum.chatHistory,
|
||||
WorkflowIOValueTypeEnum.datasetQuote,
|
||||
WorkflowIOValueTypeEnum.dynamic,
|
||||
WorkflowIOValueTypeEnum.selectApp,
|
||||
WorkflowIOValueTypeEnum.selectDataset
|
||||
],
|
||||
showDescription: false,
|
||||
showDefaultValue: false
|
||||
},
|
||||
description: '可以通过 JSONPath 语法来提取响应值中的指定字段',
|
||||
valueDesc: ''
|
||||
},
|
||||
{
|
||||
id: 'error',
|
||||
key: 'error',
|
||||
label: '请求错误',
|
||||
description: 'HTTP请求错误信息,成功时返回空',
|
||||
valueType: WorkflowIOValueTypeEnum.object,
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
valueDesc: ''
|
||||
},
|
||||
{
|
||||
id: 'httpRawResponse',
|
||||
key: 'httpRawResponse',
|
||||
required: true,
|
||||
label: '原始响应',
|
||||
description: 'HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。',
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
valueDesc: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nodeId: 'pluginConfig',
|
||||
name: i18nT('common:core.module.template.system_config'),
|
||||
intro: '',
|
||||
avatar: 'core/workflow/template/systemConfig',
|
||||
flowNodeType: FlowNodeTypeEnum.pluginConfig,
|
||||
position: {
|
||||
x: -88.12977161770735,
|
||||
y: -235.2337531748973
|
||||
},
|
||||
version: '4811',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
source: 'pluginInput',
|
||||
target: 'vumlECDQTjeC',
|
||||
sourceHandle: 'pluginInput-source-right',
|
||||
targetHandle: 'vumlECDQTjeC-target-left'
|
||||
},
|
||||
{
|
||||
source: 'vumlECDQTjeC',
|
||||
target: 'pluginOutput',
|
||||
sourceHandle: 'vumlECDQTjeC-source-right',
|
||||
targetHandle: 'pluginOutput-target-left'
|
||||
}
|
||||
],
|
||||
chatConfig: {}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
Input_Template_UserChatInput
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
import { workflowStartNodeId } from './constants';
|
||||
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
|
||||
|
||||
type WorkflowType = {
|
||||
nodes: StoreNodeItemType[];
|
||||
@@ -515,6 +516,13 @@ export function form2AppWorkflow(
|
||||
chatConfig: data.chatConfig
|
||||
};
|
||||
}
|
||||
export function filterSensitiveFormData(appForm: AppSimpleEditFormType) {
|
||||
const defaultAppForm = getDefaultAppForm();
|
||||
return {
|
||||
...appForm,
|
||||
dataset: defaultAppForm.dataset
|
||||
};
|
||||
}
|
||||
|
||||
export const workflowSystemVariables: EditorVariablePickerType[] = [
|
||||
{
|
||||
|
||||