Compare commits

...

6 Commits

Author SHA1 Message Date
Archer
1e48922bc9 Context extract support value type (#1620)
* perf: chat box components

* perf: chatbox context

* feat: extract support value type

* workflow performance

* update doc

* feat: error response

* feat: error response

* oauth sort

* perf: logo

* fix: update laf account

* perf: team permission api

* update type
2024-05-28 23:33:05 +08:00
Archer
8ba8488086 4.8.2-fix (#1612) 2024-05-28 16:55:06 +08:00
Archer
9639139b52 Sandbox (#1610) (#1611) 2024-05-28 14:47:10 +08:00
Archer
d9f5f4ede0 Update README.md 2024-05-27 00:00:17 +08:00
Carson Yang
6609cb98dc Update README (#1589)
Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
2024-05-24 21:12:26 +08:00
Archer
74830f0ac8 Update 481.md 2024-05-24 11:10:56 +08:00
109 changed files with 5106 additions and 551 deletions

View File

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

View File

@@ -49,7 +49,7 @@ jobs:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
--build-arg name=app \
-f projects/app/Dockerfile \
--platform linux/amd64,linux/arm64 \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt image" \
@@ -57,7 +57,6 @@ jobs:
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.
push-to-docker-hub:
needs: build-fastgpt-images
@@ -81,9 +80,9 @@ jobs:
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}:${{env.IMAGE_TAG}}
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
push-to-ali-hub:
needs: build-fastgpt-images
if: github.repository == 'labring/FastGPT'
@@ -107,6 +106,6 @@ jobs:
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}:${{env.IMAGE_TAG}}
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.ALI_IMAGE_NAME }}:${{env.IMAGE_TAG}}
run: docker push ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}

View File

@@ -44,15 +44,14 @@ jobs:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
run: |
docker buildx build \
--build-arg name=app \
--label "org.opencontainers.image.source= https://github.com/ ${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr image" \
-f projects/app/Dockerfile \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr imae" \
--label "org.opencontainers.image.licenses=Apache" \
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-f Dockerfile \
.
helm-check:

25
Makefile Normal file
View File

@@ -0,0 +1,25 @@
# 定义默认变量
proxy=null
image=null
# 定义目标
.PHONY: build
# 检查 target 是否定义
ifndef name
$(error name is not defined)
endif
filePath=./projects/$(name)/Dockerfile
dev:
pnpm --prefix ./projects/$(name) dev
build:
ifeq ($(proxy), taobao)
docker build -f $(filePath) -t $(image) . --build-arg proxy=taobao
else ifeq ($(proxy), clash)
docker build -f $(filePath) -t $(image) . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890
else
docker build -f $(filePath) -t $(image) .
endif

View File

@@ -53,10 +53,9 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
- [x] 提供简易模式,无需操作编排
- [x] 工作流编排
- [x] 源文件引用追踪
- [x] 模块封装,实现多级复
- [x] Tool 模块
- [ ] 嵌入 [Laf](https://github.com/labring/laf),实现在线编写 HTTP 模块。初版已完成。
- [ ] 插件封装功能,支持低代码渲染
- [x] 工具调
- [x] 插件 - 工作流封装能力
- [ ] Code sandbox
`2` 知识库能力
- [x] 多库复用,混用
@@ -67,14 +66,13 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
- [x] 支持 url 读取、CSV 批量导入
- [x] 混合检索 & 重排
- [ ] 支持文件阅读器
- [ ] 更多的数据预处理方案
`3` 应用调试能力
- [x] 知识库单点搜索测试
- [x] 对话时反馈引用并可修改与删除
- [x] 完整上下文呈现
- [x] 完整模块中间值呈现
- [ ] 高级编排 DeBug 模式
- [x] 高级编排 DeBug 模式
`4` OpenAPI 接口
- [x] completions 接口 (chat 模式对齐 GPT 接口)
@@ -89,6 +87,8 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
`6` 其他
- [x] 支持语音输入和输出 (可配置语音输入语音回答)
- [x] 模糊输入提示
- [ ] 模板市场
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-7d09f1.svg" alt="#" align="right">

View File

@@ -10,28 +10,31 @@
<a href="./README_ja.md">日语</a>
</p>
FastGPT is a knowledge-based Q&A system built on the LLM, offers out-of-the-box data processing and model invocation capabilities, allows for workflow orchestration through Flow visualization!
FastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.
</div>
<p align="center">
<a href="https://fastgpt.in/">
<img height="21" src="https://img.shields.io/badge/在线使用-d4eaf7?style=flat-square&logo=spoj&logoColor=7d09f1" alt="cloud">
<img height="21" src="https://img.shields.io/badge/Try it Online-d4eaf7?style=flat-square&logo=spoj&logoColor=7d09f1" alt="cloud">
</a>
<a href="https://doc.fastgpt.in/docs/intro">
<img height="21" src="https://img.shields.io/badge/相关文档-7d09f1?style=flat-square" alt="document">
<img height="21" src="https://img.shields.io/badge/Documents-7d09f1?style=flat-square" alt="document">
</a>
<a href="https://doc.fastgpt.in/docs/development">
<img height="21" src="https://img.shields.io/badge/本地开发-%23d4eaf7?style=flat-square&logo=xcode&logoColor=7d09f1" alt="development">
</a>
<a href="/#-%E7%9B%B8%E5%85%B3%E9%A1%B9%E7%9B%AE">
<img height="21" src="https://img.shields.io/badge/相关项目-7d09f1?style=flat-square" alt="project">
<img height="21" src="https://img.shields.io/badge/Local Development-%23d4eaf7?style=flat-square&logo=xcode&logoColor=7d09f1" alt="development">
</a>
<a href="https://github.com/labring/FastGPT/blob/main/LICENSE">
<img height="21" src="https://img.shields.io/badge/License-Apache--2.0-ffffff?style=flat-square&labelColor=d4eaf7&color=7d09f1" alt="license">
</a>
</p>
<div align="center">
[![discord](https://theme.zdassets.com/theme_assets/678183/cc59daa07820943e943c2fc283b9079d7003ff76.svg)](https://discord.gg/mp68xkZn2Q)
</div>
https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409bd33f6d4
## 🛸 Use Cloud Services
@@ -117,11 +120,11 @@ Project tech stack: NextJs + TS + ChakraUI + Mongo + Postgres (Vector plugin)
- [Version Updates & Upgrades](https://doc.fastgpt.in/docs/installation/upgrading)
## 🏘️ Community
## 🏘️ Community & support
| Community Group |
| ------------------------------------------------- |
| ![](https://oss.laf.run/htr4n1-images/fastgpt-qr-code.jpg) |
+ 🌐 Visit the [FastGPT website](https://fastgpt.in/) for full documentation and useful links.
+ 💬 Join our [Discord server](https://discord.gg/mp68xkZn2Q) is to chat with FastGPT developers and other FastGPT users. This is a good place to learn about FastGPT, ask questions, and share your experiences.
+ 🐞 Create [GitHub Issues](https://github.com/labring/FastGPT/issues/new/choose) for bug reports and feature requests.
<a href="#readme">
<img src="https://img.shields.io/badge/-Back_to_Top-7d09f1.svg" alt="#" align="right">

45
dev.md
View File

@@ -1,17 +1,40 @@
# 打包命令
## Premise
Since FastGPT is managed in the same way as monorepo, it is recommended to install 'make' first during development.
monorepo Project Name:
- app: main project
-......
## Dev
```sh
# Build image, not proxy
docker build -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.4.7 --build-arg name=app .
# Give automatic script code execution permission (on non-Linux systems, you can manually execute the postinstall.sh file content)
chmod -R +x ./scripts/
# Executing under the code root directory installs all dependencies within the root package, projects, and packages
pnpm i
# build image with proxy
docker build -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.4.7 --build-arg name=app --build-arg proxy=taobao .
# Not make cmd
cd projects/app
pnpm dev
# Make cmd
make dev name=app
```
# Pg 常用索引
```sql
CREATE INDEX IF NOT EXISTS modelData_dataset_id_index ON modeldata (dataset_id);
CREATE INDEX IF NOT EXISTS modelData_collection_id_index ON modeldata (collection_id);
CREATE INDEX IF NOT EXISTS modelData_teamId_index ON modeldata (team_id);
```
## Build
```sh
# Docker cmd: Build image, not proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app
# Make cmd: Build image, not proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1
# Docker cmd: Build image with proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app --build-arg proxy=taobao
# Make cmd: Build image with proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao
```

View File

@@ -16,8 +16,9 @@ weight: 705
- [Git](http://git-scm.com/)
- [Docker](https://www.docker.com/)(构建镜像)
- [Node.js v18.x (不推荐最新的,可能有兼容问题)](http://nodejs.org)
- [pnpm](https://pnpm.io/) 版本 8.x.x
- [Node.js v18.17 / v20.x](http://nodejs.org)
- [pnpm](https://pnpm.io/) 版本 8.6.0 (目前官方的开发环境)
- make命令: 根据不同平台,百度安装 (官方是GNU Make 4.3)
## 开始本地开发
@@ -72,24 +73,34 @@ Mongo 数据库需要注意,需要注意在连接地址中增加 `directConnec
### 5. 运行
可参考项目根目录下的 `dev.md`
```bash
# 给自动化脚本代码执行权限(非 linux 系统, 可以手动执行里面的 postinstall.sh 文件内容)
chmod -R +x ./scripts/
# 代码根目录下执行,会安装根 package、projects 和 packages 内所有依赖
pnpm i
# 切换到应用目录
cd projects/app
# 开发模式运行
# 非 Make 运行
cd projects/app
pnpm dev
# Make 运行
make dev name=app
```
### 6. 部署打包
```bash
# 根目录下执行
docker build -t dockername/fastgpt:tag --build-arg name=app .
# 使用代理
docker build -t dockername/fastgpt:tag --build-arg name=app --build-arg proxy=taobao .
# Docker cmd: Build image, not proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app
# Make cmd: Build image, not proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1
# Docker cmd: Build image with proxy
docker build -f ./projects/app/Dockerfile -t registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 . --build-arg name=app --build-arg proxy=taobao
# Make cmd: Build image with proxy
make build name=app image=registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 proxy=taobao
```
## 提交代码至开源仓库
@@ -101,21 +112,21 @@ docker build -t dockername/fastgpt:tag --build-arg name=app --build-arg proxy=ta
如果遇到问题,比如合并冲突或不知道如何打开拉取请求,请查看 GitHub 的[拉取请求教程](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests),了解如何解决合并冲突和其他问题。一旦您的 PR 被合并,您将自豪地被列为[贡献者表](https://github.com/labring/FastGPT/graphs/contributors)中的一员。
## QA
### 本地数据库无法连接
1. 如果你是连接远程的数据库,先检查对应的端口是否开放。
2. 如果是本地运行的数据库,可尝试`host`改成`localhost``127.0.0.1`
3. 本地连接远程的 Mongo需要增加 `directConnection=true` 参数,才能连接上副本集的数据库。
4. mongo使用`mongocompass`客户端进行连接测试和可视化管理。
5. pg使用`navicat`进行连接和管理。
### sh ./scripts/postinstall.sh 没权限
FastGPT 在`pnpm i`后会执行`postinstall`脚本,用于自动生成`ChakraUI``Type`。如果没有权限,可以先执行`chmod -R +x ./scripts/`,再执行`pnpm i`
### 长时间运行后崩溃
似乎是由于 tiktoken 库的开发环境问题,生产环境中未遇到,暂时可忽略。
仍不可行的话,可以手动执行`./scripts/postinstall.sh`里的内容。
### TypeError: Cannot read properties of null (reading 'useMemo' )

View File

@@ -211,10 +211,10 @@ data: {"id":"","object":"","created":0,"choices":[{"delta":{"content":"《"},"in
{{< markdownify >}}
```bash
event: moduleStatus
event: flowNodeStatus
data: {"status":"running","name":"知识库搜索"}
event: moduleStatus
event: flowNodeStatus
data: {"status":"running","name":"AI 对话"}
event: answer
@@ -238,7 +238,7 @@ data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{},"index"
event: answer
data: [DONE]
event: appStreamResponse
event: flowResponses
data: [{"moduleName":"知识库搜索","moduleType":"datasetSearchNode","runningTime":1.78},{"question":"导演是谁","quoteList":[{"id":"654f2e49b64caef1d9431e8b","q":"电影《铃芽之旅》的导演是谁?","a":"电影《铃芽之旅》的导演是新海诚!","indexes":[{"type":"qa","dataId":"3515487","text":"电影《铃芽之旅》的导演是谁?","_id":"654f2e49b64caef1d9431e8c","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8935586214065552},{"id":"6552e14c50f4a2a8e632af11","q":"导演是谁?","a":"电影《铃芽之旅》的导演是新海诚。","indexes":[{"defaultIndex":true,"type":"qa","dataId":"3644565","text":"导演是谁?\n电影《铃芽之旅》的导演是新海诚。","_id":"6552e14dde5cc7ba3954e417"}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8890955448150635},{"id":"654f34a0b64caef1d946337e","q":"本作的主人公是谁?","a":"本作的主人公是名叫铃芽的少女。","indexes":[{"type":"qa","dataId":"3515541","text":"本作的主人公是谁?","_id":"654f34a0b64caef1d946337f","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8738770484924316},{"id":"654f3002b64caef1d944207a","q":"电影《铃芽之旅》男主角是谁?","a":"电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。","indexes":[{"type":"qa","dataId":"3515538","text":"电影《铃芽之旅》男主角是谁?","_id":"654f3002b64caef1d944207b","defaultIndex":true}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8607980012893677},{"id":"654f2fc8b64caef1d943fd46","q":"电影《铃芽之旅》的编剧是谁?","a":"新海诚是本片的编剧。","indexes":[{"defaultIndex":true,"type":"qa","dataId":"3515550","text":"电影《铃芽之旅》的编剧是谁22","_id":"654f2fc8b64caef1d943fd47"}],"datasetId":"646627f4f7b896cfd8910e38","collectionId":"653279b16cd42ab509e766e8","sourceName":"data (81).csv","sourceId":"64fd3b6423aa1307b65896f6","score":0.8468944430351257}],"moduleName":"AI 对话","moduleType":"chatNode","runningTime":1.86}]
```

View File

@@ -1,5 +1,5 @@
---
title: 'V4.8.1'
title: 'V4.8.1(需要初始化)'
description: 'FastGPT V4.8.1 更新说明'
icon: 'upgrade'
draft: false
@@ -36,4 +36,4 @@ curl --location --request POST 'https://{{host}}/api/admin/clearInvalidData' \
使用 Chat api 接口需要注意,增加了 event: updateVariables 事件,用于更新变量。
[点击查看升级说明](https://github.com/labring/FastGPT/releases/tag/v4.8.1)
[点击查看升级说明](https://github.com/labring/FastGPT/releases/tag/v4.8.1)

View File

@@ -0,0 +1,34 @@
---
title: 'V4.8.2'
description: 'FastGPT V4.8.2 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 822
---
## Sealos 升级说明
1. 在应用管理中新建一个应用镜像为registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.1
2. 无需外网访问地址
3. 部署完后,复制应用的内网地址
4. 点击变更`FastGPT - 修改环境变量,增加下面的环境变量即可
```
SANDBOX_URL=内网地址
```
## Docker 部署
可以拉取最新 [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/deploy/fastgpt/docker-compose.yml) 文件参考
1. 新增一个容器 `sandbox`
2. fastgpt容器新增环境变量: `SANDBOX_URL`
3. sandbox 简易不要开启外网访问,未做凭证校验。
## V4.8.2 更新说明
1. 新增 - js代码运行节点更完整的type提醒后续继续完善
2. 新增 - 内容提取节点支持数据类型选择
3. 修复 - 新增的站点同步无法使用
4. 修复 - 定时任务无法输入内容

View File

@@ -67,10 +67,17 @@ services:
# 等待docker-entrypoint.sh脚本执行的MongoDB服务进程
wait $$!
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.2 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.1 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.1 # 阿里云
image: ghcr.io/labring/fastgpt:v4.8.2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.2 # 阿里云
ports:
- 3000:3000
networks:
@@ -78,6 +85,7 @@ services:
depends_on:
- mongo
- pg
- sandbox
restart: always
environment:
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
@@ -98,6 +106,8 @@ services:
- MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin
# pg 连接参数
- PG_URL=postgresql://username:password@pg:5432/postgres
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
volumes:
- ./config.json:/app/data/config.json
- ./fastgpt/tmp:/app/tmp

View File

@@ -24,22 +24,26 @@ A2:
`
};
export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录> 中提取指定 JSON 信息,你仅需返回 JSON 字符串,无需回答问题。
export const Prompt_ExtractJson = `你可以从 <对话记录></对话记录> 中提取指定 Json 信息,你仅需返回 Json 字符串,无需回答问题。
<提取要求>
{{description}}
</提取要求>
<字段说明>
1. 下面的 JSON 字符串均按照 JSON Schema 的规则描述
2. key 代表字段名description 代表字段的描述enum 是可选值,代表可选的 value。
3. 如果没有可提取的内容,忽略该字段。
4. 本次需提取的JSON Schema{{json}}
</字段说明>
<提取规则>
- 本次需提取的 json 字符串,需符合 JsonSchema 的规则。
- type 代表数据类型; key 代表字段名; description 代表字段的描述; enum 是枚举值,代表可选的 value。
- 如果没有可提取的内容,忽略该字段。
</提取规则>
<JsonSchema>
{{json}}
</JsonSchema>
<对话记录>
{{text}}
</对话记录>
`;
提取的 json 字符串:`;
export const Prompt_CQJson = `请帮我执行一个“问题分类”任务,将问题分类为以下几种类型之一:

View File

@@ -108,7 +108,11 @@ export enum NodeInputKeyEnum {
ifElseList = 'ifElseList',
// variable update
updateList = 'updateList'
updateList = 'updateList',
// code
code = 'code',
codeType = 'codeType' // js|py
}
export enum NodeOutputKeyEnum {
@@ -121,6 +125,7 @@ export enum NodeOutputKeyEnum {
error = 'error',
text = 'system_text',
addOutputParam = 'system_addOutputParam',
rawResponse = 'system_rawResponse',
// dataset
datasetQuoteQA = 'quoteQA',

View File

@@ -113,7 +113,8 @@ export enum FlowNodeTypeEnum {
stopTool = 'stopTool',
lafModule = 'lafModule',
ifElseNode = 'ifElseNode',
variableUpdate = 'variableUpdate'
variableUpdate = 'variableUpdate',
code = 'code'
}
export const EDGE_TYPE = 'default';

View File

@@ -31,6 +31,9 @@ export type DispatchNodeResponseType = {
runningTime?: number;
query?: string;
textOutput?: string;
error?: Record<string, any>;
customInputs?: Record<string, any>;
customOutputs?: Record<string, any>;
// bill
tokens?: number;

View File

@@ -22,6 +22,7 @@ import type { FlowNodeTemplateType } from '../type';
import { LafModule } from './system/laf';
import { IfElseNode } from './system/ifElse/index';
import { VariableUpdateNode } from './system/variableUpdate';
import { CodeNode } from './system/sandbox';
/* app flow module templates */
export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
@@ -40,7 +41,8 @@ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [
AiQueryExtension,
LafModule,
IfElseNode,
VariableUpdateNode
VariableUpdateNode,
CodeNode
];
/* plugin flow module templates */
export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
@@ -59,7 +61,8 @@ export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [
AiQueryExtension,
LafModule,
IfElseNode,
VariableUpdateNode
VariableUpdateNode,
CodeNode
];
/* all module */
@@ -84,5 +87,6 @@ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [
AiQueryExtension,
LafModule,
IfElseNode,
VariableUpdateNode
VariableUpdateNode,
CodeNode
];

View File

@@ -56,7 +56,7 @@ export const ContextExtractModule: FlowNodeTemplateType = {
label: '',
valueType: WorkflowIOValueTypeEnum.any,
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
value: [] // {desc: string; key: string; required: boolean; enum: string[]}[]
value: [] // {valueType: string; desc: string; key: string; required: boolean; enum: string[]}[]
}
],
outputs: [

View File

@@ -0,0 +1,7 @@
export const JS_TEMPLATE = `function main({data1, data2}){
return {
result: data1,
data2
}
}`;

View File

@@ -0,0 +1,72 @@
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum
} from '../../../node/constant';
import { FlowNodeTemplateType } from '../../../type';
import { getHandleConfig } from '../../utils';
import { Input_Template_DynamicInput } from '../../input';
import { Output_Template_AddOutput } from '../../output';
import { JS_TEMPLATE } from './constants';
export const CodeNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.code,
templateType: FlowNodeTemplateTypeEnum.tools,
flowNodeType: FlowNodeTypeEnum.code,
sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true),
avatar: '/imgs/workflow/code.svg',
name: '代码运行',
intro: '执行一段简单的脚本代码,通常用于进行复杂的数据处理。',
showStatus: true,
version: '482',
inputs: [
{
...Input_Template_DynamicInput,
description: '这些变量会作为代码的运行的输入参数',
editField: {
key: true,
valueType: true
}
},
{
key: NodeInputKeyEnum.codeType,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
value: 'js'
},
{
key: NodeInputKeyEnum.code,
renderTypeList: [FlowNodeInputTypeEnum.custom],
label: '',
value: JS_TEMPLATE
}
],
outputs: [
{
...Output_Template_AddOutput,
description: '将代码中 return 的对象作为输出,传递给后续的节点'
},
{
id: NodeOutputKeyEnum.rawResponse,
key: NodeOutputKeyEnum.rawResponse,
label: '完整响应数据',
valueType: WorkflowIOValueTypeEnum.object,
type: FlowNodeOutputTypeEnum.static
},
{
id: NodeOutputKeyEnum.error,
key: NodeOutputKeyEnum.error,
label: '运行错误',
description: '代码运行错误信息,成功时返回空',
valueType: WorkflowIOValueTypeEnum.object,
type: FlowNodeOutputTypeEnum.static
}
]
};

View File

@@ -115,6 +115,7 @@ export type ClassifyQuestionAgentItemType = {
key: string;
};
export type ContextExtractAgentItemType = {
valueType: 'string' | 'number' | 'boolean';
desc: string;
key: string;
required: boolean;

View File

@@ -14,7 +14,6 @@ export type CreateTeamProps = {
lafAccount?: LafAccountType;
};
export type UpdateTeamProps = {
teamId: string;
name?: string;
avatar?: string;
teamDomain?: string;

View File

@@ -11,6 +11,7 @@ export type BillSchemaType = {
status: 'SUCCESS' | 'REFUND' | 'NOTPAY' | 'CLOSED';
type: `${BillTypeEnum}`;
price: number;
hasInvoice: boolean;
metadata: {
payWay: `${BillPayWayEnum}`;
subMode?: `${SubModeEnum}`;
@@ -20,7 +21,6 @@ export type BillSchemaType = {
extraPoints?: number;
invoice: boolean;
};
username: string;
};
export type ChatNodeUsageType = {

View File

@@ -183,7 +183,7 @@ ${description ? `- ${description}` : ''}
> = {};
extractKeys.forEach((item) => {
properties[item.key] = {
type: 'string',
type: item.valueType || 'string',
description: item.desc,
...(item.enum ? { enum: item.enum.split('\n') } : {})
};
@@ -198,7 +198,7 @@ ${description ? `- ${description}` : ''}
required: []
}
};
console.log(properties);
return {
filterMessages,
agentFunction
@@ -319,12 +319,16 @@ const completions = async ({
content: replaceVariable(extractModel.customExtractPrompt || Prompt_ExtractJson, {
description,
json: extractKeys
.map(
(item) =>
`{"key":"${item.key}", "description":"${item.desc}"${
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
}}`
)
.map((item) => {
const valueType = item.valueType || 'string';
if (valueType !== 'string' && valueType !== 'number') {
item.enum = undefined;
}
return `{"type":${item.valueType || 'string'}, "key":"${item.key}", "description":"${item.desc}" ${
item.enum ? `, "enum":"[${item.enum.split('\n')}]"` : ''
}}`;
})
.join('\n'),
text: `${histories.map((item) => `${item.obj}:${chatValue2RuntimePrompt(item.value).text}`).join('\n')}
Human: ${content}`
@@ -365,6 +369,7 @@ Human: ${content}`
arg: json5.parse(jsonStr) as Record<string, any>
};
} catch (error) {
console.log('Extract error, ai answer:', answer);
console.log(error);
return {
rawResponse: answer,

View File

@@ -1,38 +0,0 @@
// import { addLog } from '../../../../common/system/log';
// const ivm = require('isolated-vm');
// export const runJsCode = ({
// code,
// variables
// }: {
// code: string;
// variables: Record<string, any>;
// }) => {
// const isolate = new ivm.Isolate({ memoryLimit: 16 });
// const context = isolate.createContextSync();
// const jail = context.global;
// return new Promise((resolve, reject) => {
// // custom log function
// jail.setSync('responseData', function (args: any): any {
// if (typeof args === 'object') {
// resolve(args);
// } else {
// reject('Not an invalid response');
// }
// });
// // Add global variables
// jail.setSync('variables', new ivm.ExternalCopy(variables).copyInto());
// try {
// const scriptCode = `
// ${code}
// responseData(main(variables))`;
// context.evalSync(scriptCode, { timeout: 2000 });
// } catch (err) {
// addLog.error('Error during script execution:', err);
// reject(err);
// }
// });
// };

View File

@@ -0,0 +1,55 @@
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/type/index.d';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import axios from 'axios';
import { formatHttpError } from '../utils';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
type RunCodeType = ModuleDispatchProps<{
[NodeInputKeyEnum.codeType]: 'js';
[NodeInputKeyEnum.code]: string;
[key: string]: any;
}>;
type RunCodeResponse = DispatchNodeResultType<{
[NodeOutputKeyEnum.error]?: any;
[NodeOutputKeyEnum.rawResponse]?: Record<string, any>;
[key: string]: any;
}>;
export const dispatchRunCode = async (props: RunCodeType): Promise<RunCodeResponse> => {
const {
params: { codeType, code, ...customVariables }
} = props;
const sandBoxRequestUrl = `${process.env.SANDBOX_URL}/sandbox/js`;
try {
const { data: runResult } = await axios.post<{
success: boolean;
data: Record<string, any>;
}>(sandBoxRequestUrl, {
code,
variables: customVariables
});
if (runResult.success) {
return {
[NodeOutputKeyEnum.rawResponse]: runResult.data,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
customInputs: customVariables,
customOutputs: runResult.data
},
...runResult.data
};
} else {
throw new Error('Run code failed');
}
} catch (error) {
return {
[NodeOutputKeyEnum.error]: formatHttpError(error),
[DispatchNodeResponseKeyEnum.nodeResponse]: {
customInputs: customVariables,
error: formatHttpError(error)
}
};
}
};

View File

@@ -46,6 +46,7 @@ import { dispatchSystemConfig } from './init/systemConfig';
import { dispatchUpdateVariable } from './tools/runUpdateVar';
import { addLog } from '../../../common/system/log';
import { surrenderProcess } from '../../../common/system/tools';
import { dispatchRunCode } from './code/run';
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
@@ -66,6 +67,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.lafModule]: dispatchLafRequest,
[FlowNodeTypeEnum.ifElseNode]: dispatchIfElse,
[FlowNodeTypeEnum.variableUpdate]: dispatchUpdateVariable,
[FlowNodeTypeEnum.code]: dispatchRunCode,
// none
[FlowNodeTypeEnum.systemConfig]: dispatchSystemConfig,

View File

@@ -9,7 +9,7 @@ import {
SseResponseEventEnum
} from '@fastgpt/global/core/workflow/runtime/constants';
import axios from 'axios';
import { valueTypeFormat } from '../utils';
import { formatHttpError, valueTypeFormat } from '../utils';
import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
import { addLog } from '../../../../common/system/log';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
@@ -310,14 +310,3 @@ function removeUndefinedSign(obj: Record<string, any>) {
}
return obj;
}
function formatHttpError(error: any) {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
}

View File

@@ -3,12 +3,7 @@ import {
WorkflowIOValueTypeEnum,
NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import {
RuntimeEdgeItemType,
RuntimeNodeItemType
} from '@fastgpt/global/core/workflow/runtime/type';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/runtime/type';
export const filterToolNodeIdByEdges = ({
nodeId,
@@ -91,3 +86,15 @@ export const removeSystemVariable = (variables: Record<string, any>) => {
return copyVariables;
};
export const formatHttpError = (error: any) => {
return {
message: error?.message,
name: error?.name,
method: error?.config?.method,
baseURL: error?.config?.baseURL,
url: error?.config?.url,
code: error?.code,
status: error?.status
};
};

View File

@@ -5,6 +5,7 @@ import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { UserErrEnum } from '../../../../global/common/error/code/user';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export async function authUserNotVisitor(props: AuthModeType): Promise<
AuthResponseType & {
@@ -47,3 +48,19 @@ export async function authUserRole(props: AuthModeType): Promise<
canWrite
};
}
/* auth teamMember in team role */
export async function authTeamOwner(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const authRes = await authUserRole(props);
if (authRes.role !== TeamMemberRoleEnum.owner) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
return authRes;
}

View File

@@ -7,6 +7,7 @@ import {
} from '@fastgpt/global/support/user/team/constant';
import { MongoTeamMember } from './teamMemberSchema';
import { MongoTeam } from './teamSchema';
import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
async function getTeamMember(match: Record<string, any>): Promise<TeamItemType> {
const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
@@ -108,3 +109,18 @@ export async function createDefaultTeam({
});
}
}
export async function updateTeam({
teamId,
name,
avatar,
teamDomain,
lafAccount
}: UpdateTeamProps & { teamId: string }) {
await MongoTeam.findByIdAndUpdate(teamId, {
name,
avatar,
teamDomain,
lafAccount
});
}

View File

@@ -0,0 +1,160 @@
import React, { useCallback, useRef, useState } from 'react';
import Editor, { Monaco, loader } from '@monaco-editor/react';
import { Box, BoxProps } from '@chakra-ui/react';
import MyIcon from '../../Icon';
loader.config({
paths: { vs: '/js/monaco-editor.0.45.0/vs' }
});
type EditorVariablePickerType = {
key: string;
label: string;
};
export type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
height?: number;
resize?: boolean;
defaultValue?: string;
value?: string;
onChange?: (e: string) => void;
onOpenModal?: () => void;
variables?: EditorVariablePickerType[];
defaultHeight?: number;
};
const options = {
lineNumbers: 'on',
guides: {
indentation: false
},
automaticLayout: true,
minimap: {
enabled: false
},
scrollbar: {
verticalScrollbarSize: 4,
horizontalScrollbarSize: 8,
alwaysConsumeMouseWheel: false
},
lineNumbersMinChars: 0,
fontSize: 14,
scrollBeyondLastLine: false,
folding: true,
overviewRulerBorder: false,
tabSize: 2
};
const MyEditor = ({
defaultValue,
value,
onChange,
resize,
variables = [],
defaultHeight = 200,
onOpenModal,
...props
}: Props) => {
const [height, setHeight] = useState(defaultHeight);
const initialY = useRef(0);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
initialY.current = e.clientY;
const handleMouseMove = (e: MouseEvent) => {
const deltaY = e.clientY - initialY.current;
initialY.current = e.clientY;
setHeight((prevHeight) => (prevHeight + deltaY < 100 ? 100 : prevHeight + deltaY));
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, []);
const beforeMount = useCallback((monaco: Monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: false,
allowComments: false,
schemas: [
{
uri: 'http://myserver/foo-schema.json', // 一个假设的 URI
fileMatch: ['*'], // 匹配所有文件
schema: {} // 空的 Schema
}
]
});
monaco.editor.defineTheme('JSONEditorTheme', {
base: 'vs', // 可以基于已有的主题进行定制
inherit: true, // 继承基础主题的设置
rules: [{ token: 'variable', foreground: '2B5FD9' }],
colors: {
'editor.background': '#ffffff00',
'editorLineNumber.foreground': '#aaa',
'editorOverviewRuler.border': '#ffffff00',
'editor.lineHighlightBackground': '#F7F8FA',
'scrollbarSlider.background': '#E8EAEC',
'editorIndentGuide.activeBackground': '#ddd',
'editorIndentGuide.background': '#eee'
}
});
}, []);
return (
<Box
borderWidth={'1px'}
borderRadius={'md'}
borderColor={'myGray.200'}
py={2}
height={height}
position={'relative'}
pl={2}
{...props}
>
<Editor
height={'100%'}
defaultLanguage="typescript"
options={options as any}
theme="JSONEditorTheme"
beforeMount={beforeMount}
defaultValue={defaultValue}
value={value}
onChange={(e) => {
onChange?.(e || '');
}}
/>
{resize && (
<Box
position={'absolute'}
right={'-1'}
bottom={'-1'}
zIndex={10}
cursor={'ns-resize'}
px={'4px'}
onMouseDown={handleMouseDown}
>
<MyIcon name={'common/editor/resizer'} width={'16px'} height={'16px'} />
</Box>
)}
{!!onOpenModal && (
<Box
zIndex={10}
position={'absolute'}
bottom={0}
right={2}
cursor={'pointer'}
onClick={onOpenModal}
>
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
</Box>
)}
</Box>
);
};
export default React.memo(MyEditor);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import MyEditor, { type Props as EditorProps } from './Editor';
import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
import MyModal from '../../MyModal';
import { useTranslation } from 'next-i18next';
type Props = Omit<EditorProps, 'resize'> & {};
const CodeEditor = (props: Props) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<MyEditor {...props} resize onOpenModal={onOpen} />
<MyModal
isOpen={isOpen}
onClose={onClose}
iconSrc="modal/edit"
title={t('Code editor')}
w={'full'}
>
<ModalBody>
<MyEditor {...props} bg={'myGray.50'} defaultHeight={600} />
</ModalBody>
<ModalFooter>
<Button mr={2} onClick={onClose}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
</>
);
};
export default React.memo(CodeEditor);

View File

@@ -44,18 +44,17 @@ export const useConfirm = (props?: {
const confirmCb = useRef<Function>();
const cancelCb = useRef<any>();
const openConfirm = (
confirm?: Function,
cancel?: any,
customContent?: string | React.ReactNode
) => {
confirmCb.current = confirm;
cancelCb.current = cancel;
const openConfirm = useCallback(
(confirm?: Function, cancel?: any, customContent?: string | React.ReactNode) => {
confirmCb.current = confirm;
cancelCb.current = cancel;
customContent && setCustomContent(customContent);
customContent && setCustomContent(customContent);
return onOpen;
};
return onOpen;
},
[]
);
const ConfirmModal = useCallback(
({

3471
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ CHAT_API_KEY=sk-xxxx
MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin
# PG 数据库连接参数
PG_URL=postgresql://username:password@host:port/postgres
# code sandbox url
SANDBOX_URL=http://localhost:3001
# 商业版地址
PRO_URL=
# 首页路径

View File

@@ -2,7 +2,6 @@
FROM node:18.17-alpine AS mainDeps
WORKDIR /app
ARG name
ARG proxy
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
@@ -13,7 +12,7 @@ RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
# copy packages and one project
COPY pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ./packages ./packages
COPY ./projects/$name/package.json ./projects/$name/package.json
COPY ./projects/app/package.json ./projects/app/package.json
RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
@@ -23,28 +22,26 @@ RUN pnpm i
FROM node:18.17-alpine AS builder
WORKDIR /app
ARG name
ARG proxy
# copy common node_modules and one project node_modules
COPY package.json pnpm-workspace.yaml .npmrc ./
COPY --from=mainDeps /app/node_modules ./node_modules
COPY --from=mainDeps /app/packages ./packages
COPY ./projects/$name ./projects/$name
COPY --from=mainDeps /app/projects/$name/node_modules ./projects/$name/node_modules
COPY ./projects/app ./projects/app
COPY --from=mainDeps /app/projects/app/node_modules ./projects/app/node_modules
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm --filter=$name build
RUN pnpm --filter=app build
# --------- runner -----------
FROM node:18.17-alpine AS runner
WORKDIR /app
ARG name
ARG proxy
# create user and use it
@@ -56,23 +53,23 @@ RUN apk add --no-cache curl ca-certificates \
&& update-ca-certificates
# copy running files
COPY --from=builder /app/projects/$name/public /app/projects/$name/public
COPY --from=builder /app/projects/$name/next.config.js /app/projects/$name/next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/standalone /app/
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static /app/projects/$name/.next/static
COPY --from=builder /app/projects/app/public /app/projects/app/public
COPY --from=builder /app/projects/app/next.config.js /app/projects/app/next.config.js
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/standalone /app/
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/static /app/projects/app/.next/static
# copy server chunks
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/server/chunks /app/projects/$name/.next/server/chunks
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/chunks /app/projects/app/.next/server/chunks
# copy worker
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/server/worker /app/projects/$name/.next/server/worker
COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/worker /app/projects/app/.next/server/worker
# copy tiktoken but not copy ./node_modules/tiktoken/encoders
COPY --from=mainDeps /app/node_modules/tiktoken ./node_modules/tiktoken
RUN rm -rf ./node_modules/tiktoken/encoders
# copy package.json to version file
COPY --from=builder /app/projects/$name/package.json ./package.json
COPY --from=builder /app/projects/app/package.json ./package.json
# copy config
COPY ./projects/$name/data /app/data
COPY ./projects/app/data /app/data
RUN chown -R nextjs:nodejs /app/data
@@ -84,6 +81,6 @@ EXPOSE 3000
USER nextjs
ENV serverPath=./projects/$name/server.js
ENV serverPath=./projects/app/server.js
ENTRYPOINT ["sh","-c","node --max-old-space-size=4096 ${serverPath}"]

View File

@@ -1,7 +1,9 @@
{
"Add new": "Add new",
"App": "App",
"Code editor": "Code edit",
"Export": "Export",
"Field name": "Name",
"Folder": "Folder",
"Is open": "Opened",
"Login": "Login",

View File

@@ -1,3 +1,13 @@
{
"Field required": "Required"
"Code": "Code",
"Field required": "Required",
"code": {
"Reset template": "Reset template",
"Reset template confirm": "Are you sure to restore the code template? Be careful to save the current code."
},
"response": {
"Custom inputs": "Custom inputs",
"Custom outputs": "Custom outputs",
"Error": "Error"
}
}

View File

@@ -1,7 +1,9 @@
{
"Add new": "新增",
"App": "应用",
"Code editor": "代码编辑",
"Export": "导出",
"Field name": "字段名",
"Folder": "文件夹",
"Is open": "是否开启",
"Login": "登录",
@@ -475,7 +477,7 @@
"context total length": "上下文总长度",
"module cq": "问题分类列表",
"module cq result": "分类结果",
"module extract description": "提取要求描述",
"module extract description": "提取背景描述",
"module extract result": "提取结果",
"module historyPreview": "完整记录",
"module http body": "请求体",
@@ -583,8 +585,7 @@
"success": "开始同步"
}
},
"training": {
}
"training": {}
},
"data": {
"Auxiliary Data": "辅助数据",

View File

@@ -1,3 +1,13 @@
{
"Field required": "必填"
"Code": "代码",
"Field required": "必填",
"code": {
"Reset template": "还原模板",
"Reset template confirm": "确认还原代码模板?请注意保存当前代码。"
},
"response": {
"Custom inputs": "自定义输入",
"Custom outputs": "自定义输出",
"Error": "错误信息"
}
}

View File

@@ -89,7 +89,7 @@ const nextConfig = {
transpilePackages: ['@fastgpt/*', 'ahooks'],
experimental: {
// 优化 Server Components 的构建和运行,避免不必要的客户端打包。
serverComponentsExternalPackages: ['mongoose', 'pg'],
serverComponentsExternalPackages: ['mongoose', 'pg', '@node-rs/jieba'],
outputFileTracingRoot: path.join(__dirname, '../../')
}
};

View File

@@ -1,32 +1,12 @@
### FastGPT V4.8
### FastGPT V4.8.2
本次更新的重点是对工作流 (高级编排) 进行了重构,使其更加简洁和强大。但由于新旧工作流机制有较大变化,尽管我们进行了一定的自动转换,仍有部分工作流需要您手动重建。请尽快更新到新版本,并对工作流进行必要的调试和重新发布。
❗ 重要提示:
1⃣ 旧工作流更新后暂不失效,打开旧工作流会弹出自动转换提示,重新编排后点 “发布” 按钮发布新工作流
2⃣ 发布新工作流前,工作流自动保存功能暂不生效
3⃣ 应用和插件新增 version 字段,标识适用新/旧版工作流,以实现兼容
✨ 新增功能亮点:
1⃣ 判断器:支持 if/elseIf/else 判断逻辑,工作流控制更灵活
2⃣ 变量更新节点:运行中可动态修改工作流输出变量或全局变量值
3⃣ 工作流自动保存和版本管理:自动保存修改,支持查看和回滚历史版本
4⃣ 工作流调试模式:更直观高效,可调试单节点或逐步执行,实时查看输入输出数据
5⃣ 定时执行应用:支持简单配置实现各种定时任务
🛠️ 其他优化与修复:
- 优化工作流节点连线方式,支持四向连接,易构建循环工作流
- 显著提升工作流上下文数据传递性能
- 简易模式下修改配置自动刷新调试框,免手动保存
- 改进 worker 进程管理,支持 Token 计算任务分配,提高效率
- 工具调用支持 string、boolean、number 数据类型
- 完善 completions 接口对 size 参数限制
- 重构 Node.js API 中间件和服务端代码
- 对话记录长度调整为偶数,最大长度增至 50 轮,避免奇数导致部分模型不兼容
- HTTP 节点出错将终止进程,避免异常影响
- 修复工具调用名称不能以数字开头问题
- 修复分享链接 query 参数缓存 bug
- 修复工具调用和 HTTP 模块兼容性问题
- 新增 - 知识库重新选择向量模型重建
- 新增 - 对话框支持问题模糊检索提示,可自定义预设问题词库
- 新增 - js代码运行节点
- 新增 - 外部文件源知识库: [点击查看文档](https://doc.fastai.site/docs/course/externalfile/)
- 新增 - 内容提取节点增加完全提取成功输出
- 新增 - HTTP节点增加错误输出可以自行判断处理
- 优化 - 插件输入的 debug 模式,支持全量参数输入渲染
- [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow/intro)
- [使用文档](https://doc.fastgpt.in/docs/intro/)
- [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)

View File

@@ -0,0 +1,24 @@
<svg t="1716862994060" class="icon" viewBox="0 0 1218 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4475"
width="256" height="256">
<path
d="M1169.592417 975.469194V145.592417c0-53.383886-43.677725-97.061611-97.061611-97.061611H145.592417c-53.383886 0-97.061611 43.677725-97.061611 97.061611v829.876777h1121.061611z"
fill="#7CDEDC" p-id="4476"></path>
<path
d="M1193.85782 975.469194V145.592417c0-66.899716-54.427299-121.327014-121.327014-121.327014H145.592417C78.692701 24.265403 24.265403 78.692701 24.265403 145.592417v854.14218h1169.592417v-24.265403z m-1121.061611 0V145.592417c0-40.139829 32.656379-72.796209 72.796208-72.796208h926.938389c40.139829 0 72.796209 32.656379 72.796208 72.796208v829.876777l24.265403-24.265403H48.530806l24.265403 24.265403z"
fill="#6E6E96" p-id="4477"></path>
<path d="M48.530806 237.800948h1145.327014v48.530806H48.530806z" fill="#6E6E96" p-id="4478"></path>
<path
d="M1072.530806 48.530806H145.592417c-53.383886 0-97.061611 43.677725-97.061611 97.061611v111.620853h1121.061611V145.592417c0-53.383886-43.677725-97.061611-97.061611-97.061611z"
fill="#FFF491" p-id="4479"></path>
<path
d="M1072.530806 24.265403H145.592417C78.692701 24.265403 24.265403 78.692701 24.265403 145.592417v135.886256h1169.592417V145.592417c0-66.899716-54.427299-121.327014-121.327014-121.327014z m72.796208 121.327014v111.620853l24.265403-24.265403H48.530806l24.265403 24.265403V145.592417c0-40.139829 32.656379-72.796209 72.796208-72.796208h926.938389c40.139829 0 72.796209 32.656379 72.796208 72.796208zM374.725763 431.74946l-167.43128 167.436132L190.138844 616.341232l17.155639 17.15564 167.43128 167.43128 34.320986-34.31128-167.43128-167.43128v34.31128l167.43128-167.426427zM813.934408 466.070445l167.431279 167.426427v-34.31128l-167.431279 167.43128 34.311279 34.31128 167.43128-167.43128L1032.832607 616.341232l-17.15564-17.15564-167.43128-167.436132z"
fill="#6E6E96" p-id="4480"></path>
<path
d="M531.412322 618.767773m-41.251185 0a41.251185 41.251185 0 1 0 82.50237 0 41.251185 41.251185 0 1 0-82.50237 0Z"
fill="#6E6E96" p-id="4481"></path>
<path
d="M706.123223 618.767773m-41.251185 0a41.251185 41.251185 0 1 0 82.50237 0 41.251185 41.251185 0 1 0-82.50237 0Z"
fill="#6E6E96" p-id="4482"></path>
<path d="M72.796209 281.478673h1072.530805v53.383886H72.796209z" fill="#6E6E96" opacity=".15" p-id="4483"></path>
<path d="M72.796209 189.270142h1072.530805v43.677725H72.796209z" fill="#6E6E96" opacity=".15" p-id="4484"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -7,7 +7,6 @@ import MyTooltip from '../../MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { compressImgFileAndUpload } from '@/web/common/file/controller';
import { customAlphabet } from 'nanoid';
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
import { addDays } from 'date-fns';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
@@ -15,9 +14,10 @@ import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'
import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '../type';
import { textareaMinH } from '../constants';
import { UseFormReturn, useFieldArray } from 'react-hook-form';
import { useChatProviderStore } from '../Provider';
import { ChatBoxContext } from '../Provider';
import dynamic from 'next/dynamic';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import { useContextSelector } from 'use-context-selector';
import { getNanoid } from '@fastgpt/global/common/string/tools';
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
@@ -60,7 +60,7 @@ const ChatInput = ({
whisperConfig,
autoTTSResponse,
chatInputGuide
} = useChatProviderStore();
} = useContextSelector(ChatBoxContext, (v) => v);
const { isPc, whisperModel } = useSystemStore();
const canvasRef = useRef<HTMLCanvasElement>(null);
const { t } = useTranslation();
@@ -119,7 +119,7 @@ const ChatInput = ({
reader.readAsDataURL(file);
reader.onload = () => {
const item = {
id: nanoid(),
id: getNanoid(6),
rawFile: file,
type: ChatFileTypeEnum.image,
name: file.name,
@@ -132,7 +132,7 @@ const ChatInput = ({
};
} else {
resolve({
id: nanoid(),
id: getNanoid(6),
rawFile: file,
type: ChatFileTypeEnum.file,
name: file.name,

View File

@@ -7,7 +7,8 @@ import { queryChatInputGuideList } from '@/web/core/chat/inputGuide/api';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useTranslation } from 'next-i18next';
import HighlightText from '@fastgpt/web/components/common/String/HighlightText';
import { useChatProviderStore } from '../Provider';
import { ChatBoxContext } from '../Provider';
import { useContextSelector } from 'use-context-selector';
export default function InputGuideBox({
appId,
@@ -22,7 +23,7 @@ export default function InputGuideBox({
}) {
const { t } = useTranslation();
const { chatT } = useI18n();
const { chatInputGuide } = useChatProviderStore();
const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide);
const { data = [] } = useRequest2(
async () => {

View File

@@ -1,16 +0,0 @@
import React, { useState } from 'react';
const MarkModal = () => {
const [adminMarkData, setAdminMarkData] = useState<{
chatItemId: string;
dataId?: string;
datasetId?: string;
collectionId?: string;
q: string;
a: string;
}>();
return <div>MarkModal</div>;
};
export default MarkModal;

View File

@@ -1,7 +1,6 @@
import React, { useContext, createContext, useState, useMemo, useEffect, useCallback } from 'react';
import React, { useState, useMemo } from 'react';
import { useAudioPlay } from '@/web/common/utils/voice';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import {
AppChatConfigType,
AppTTSConfigType,
@@ -15,6 +14,7 @@ import {
defaultTTSConfig,
defaultWhisperConfig
} from '@fastgpt/global/core/app/constants';
import { createContext } from 'use-context-selector';
type useChatStoreType = OutLinkChatAuthProps & {
welcomeText: string;
@@ -46,7 +46,7 @@ type useChatStoreType = OutLinkChatAuthProps & {
isChatting: boolean;
chatInputGuide: ChatInputGuideConfigType;
};
const StateContext = createContext<useChatStoreType>({
export const ChatBoxContext = createContext<useChatStoreType>({
welcomeText: '',
variableList: [],
questionGuide: false,
@@ -109,8 +109,6 @@ export type ChatProviderProps = OutLinkChatAuthProps & {
children: React.ReactNode;
};
export const useChatProviderStore = () => useContext(StateContext);
const Provider = ({
shareId,
outLinkUid,
@@ -186,7 +184,7 @@ const Provider = ({
chatInputGuide
};
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;
};
export default React.memo(Provider);

View File

@@ -7,7 +7,8 @@ import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { formatChatValue2InputType } from '../utils';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { useChatProviderStore } from '../Provider';
import { ChatBoxContext } from '../Provider';
import { useContextSelector } from 'use-context-selector';
export type ChatControllerProps = {
isLastChild: boolean;
@@ -45,7 +46,7 @@ const ChatController = ({
cancelAudio,
audioPlayingChatId,
setAudioPlayingChatId
} = useChatProviderStore();
} = useContextSelector(ChatBoxContext, (v) => v);
const controlIconStyle = {
w: '14px',
cursor: 'pointer',

View File

@@ -25,8 +25,9 @@ import {
ChatStatusEnum
} from '@fastgpt/global/core/chat/constants';
import FilesBlock from './FilesBox';
import { useChatProviderStore } from '../Provider';
import { ChatBoxContext } from '../Provider';
import Avatar from '@/components/Avatar';
import { useContextSelector } from 'use-context-selector';
const colorMap = {
[ChatStatusEnum.loading]: {
@@ -78,7 +79,7 @@ const ChatItem = ({
bg: 'myGray.50'
};
const { isChatting } = useChatProviderStore();
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
const { chat } = chatControllerProps;
const ContentCard = useMemo(() => {

View File

@@ -4,8 +4,8 @@ import { ModalBody, Box, useTheme } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import QuoteItem from '../core/dataset/QuoteItem';
import RawSourceBox from '../core/dataset/RawSourceBox';
import QuoteItem from '../../core/dataset/QuoteItem';
import RawSourceBox from '../../core/dataset/RawSourceBox';
const QuoteModal = ({
rawSearch = [],

View File

@@ -7,7 +7,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import dynamic from 'next/dynamic';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import MyTooltip from '../MyTooltip';
import MyTooltip from '../../MyTooltip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import ChatBoxDivider from '@/components/core/chat/Divider';

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { ModalBody, useTheme, ModalFooter, Button, Box, Card, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import Avatar from '../Avatar';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import DatasetSelectModal, { useDatasetSelect } from '@/components/core/dataset/SelectModal';

View File

@@ -1,53 +1,79 @@
import React, { useMemo, useState } from 'react';
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import Tabs from '../Tabs';
import Tabs from '../../Tabs';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '../MyTooltip';
import MyTooltip from '../../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import Markdown from '../Markdown';
import Markdown from '../../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import { formatNumber } from '@fastgpt/global/common/math/tools';
import { useI18n } from '@/web/context/I18n';
function RowRender({
children,
mb,
label,
...props
}: { children: React.ReactNode; label: string } & BoxProps) {
return (
<Box mb={3}>
<Box fontSize={['sm', 'md']} mb={mb} flex={'0 0 90px'}>
{label}:
</Box>
<Box borderRadius={'sm'} fontSize={'sm'} bg={'myGray.50'} {...props}>
{children}
</Box>
</Box>
);
}
function Row({
label,
value,
rawDom
}: {
label: string;
value?: string | number | boolean;
value?: string | number | boolean | object;
rawDom?: React.ReactNode;
}) {
const { t } = useTranslation();
const theme = useTheme();
const val = value || rawDom;
const strValue = `${value}`;
const isCodeBlock = strValue.startsWith('~~~json');
const isObject = typeof value === 'object';
return val !== undefined && val !== '' && val !== 'undefined' ? (
<Box mb={3}>
<Box fontSize={['sm', 'md']} mb={isCodeBlock ? 0 : 1} flex={'0 0 90px'}>
{t(label)}:
</Box>
<Box
borderRadius={'sm'}
fontSize={'sm'}
bg={'myGray.50'}
{...(isCodeBlock
? { transform: 'translateY(-3px)' }
: value
? { px: 3, py: 2, border: theme.borders.base }
: {})}
>
{value && <Markdown source={strValue} />}
const formatValue = useMemo(() => {
if (isObject) {
return `~~~json\n${JSON.stringify(value, null, 2)}`;
}
return `${value}`;
}, [isObject, value]);
if (rawDom) {
return (
<RowRender label={label} mb={1}>
{rawDom}
</Box>
</Box>
) : null;
</RowRender>
);
}
if (val === undefined || val === '' || val === 'undefined') return null;
return (
<RowRender
label={label}
mb={isObject ? 0 : 1}
{...(isObject
? { transform: 'translateY(-3px)' }
: value
? { px: 3, py: 2, border: theme.borders.base }
: {})}
>
<Markdown source={formatValue} />
</RowRender>
);
}
const WholeResponseModal = ({
@@ -98,6 +124,7 @@ export const ResponseBox = React.memo(function ResponseBox({
}) {
const theme = useTheme();
const { t } = useTranslation();
const { workflowT } = useI18n();
const list = useMemo(
() =>
@@ -158,6 +185,7 @@ export const ResponseBox = React.memo(function ResponseBox({
label={t('core.chat.response.context total length')}
value={activeModule?.contextTotalLen}
/>
<Row label={workflowT('response.Error')} value={activeModule?.error} />
</>
{/* ai chat */}
@@ -251,47 +279,26 @@ export const ResponseBox = React.memo(function ResponseBox({
label={t('core.chat.response.module extract description')}
value={activeModule?.extractDescription}
/>
{activeModule?.extractResult && (
<Row
label={t('core.chat.response.module extract result')}
value={`~~~json\n${JSON.stringify(activeModule?.extractResult, null, 2)}`}
/>
)}
<Row
label={t('core.chat.response.module extract result')}
value={activeModule?.extractResult}
/>
</>
{/* http */}
<>
{activeModule?.headers && (
<Row
label={'Headers'}
value={`~~~json\n${JSON.stringify(activeModule?.headers, null, 2)}`}
/>
)}
{activeModule?.params && (
<Row
label={'Params'}
value={`~~~json\n${JSON.stringify(activeModule?.params, null, 2)}`}
/>
)}
{activeModule?.body && (
<Row label={'Body'} value={`~~~json\n${JSON.stringify(activeModule?.body, null, 2)}`} />
)}
{activeModule?.httpResult && (
<Row
label={t('core.chat.response.module http result')}
value={`~~~json\n${JSON.stringify(activeModule?.httpResult, null, 2)}`}
/>
)}
<Row label={'Headers'} value={activeModule?.headers} />
<Row label={'Params'} value={activeModule?.params} />
<Row label={'Body'} value={activeModule?.body} />
<Row
label={t('core.chat.response.module http result')}
value={activeModule?.httpResult}
/>
</>
{/* plugin */}
<>
{activeModule?.pluginOutput && (
<Row
label={t('core.chat.response.plugin output')}
value={`~~~json\n${JSON.stringify(activeModule?.pluginOutput, null, 2)}`}
/>
)}
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
<Row
label={t('core.chat.response.Plugin response detail')}
@@ -310,6 +317,10 @@ export const ResponseBox = React.memo(function ResponseBox({
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
/>
)}
{/* code */}
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
</Box>
</>
);

View File

@@ -15,14 +15,12 @@ import type {
ChatSiteItemType,
UserChatItemValueItemType
} from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { Box, Flex, Checkbox } from '@chakra-ui/react';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -32,7 +30,7 @@ import {
updateChatAdminFeedback,
updateChatUserFeedback
} from '@/web/core/chat/api';
import type { AdminMarkType } from './SelectMarkCollection';
import type { AdminMarkType } from './components/SelectMarkCollection';
import MyTooltip from '../MyTooltip';
@@ -52,7 +50,7 @@ import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/c
import { formatChatValue2InputType } from './utils';
import { textareaMinH } from './constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import ChatProvider, { useChatProviderStore } from './Provider';
import ChatProvider, { ChatBoxContext } from './Provider';
import ChatItem from './components/ChatItem';
@@ -60,11 +58,12 @@ import dynamic from 'next/dynamic';
import { useCreation } from 'ahooks';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import type { StreamResponseType } from '@/web/common/api/fetch';
import { useContextSelector } from 'use-context-selector';
const ResponseTags = dynamic(() => import('./ResponseTags'));
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
const ReadFeedbackModal = dynamic(() => import('./ReadFeedbackModal'));
const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection'));
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
const ReadFeedbackModal = dynamic(() => import('./components/ReadFeedbackModal'));
const SelectMarkCollection = dynamic(() => import('./components/SelectMarkCollection'));
const Empty = dynamic(() => import('./components/Empty'));
const WelcomeBox = dynamic(() => import('./components/WelcomeBox'));
const VariableInput = dynamic(() => import('./components/VariableInput'));
@@ -158,7 +157,7 @@ const ChatBox = (
chatHistories,
setChatHistories,
isChatting
} = useChatProviderStore();
} = useContextSelector(ChatBoxContext, (v) => v);
// compute variable input is finish.
const chatForm = useForm<ChatBoxInputFormType>({

View File

@@ -102,7 +102,13 @@ const Navbar = ({ unread }: { unread: number }) => {
cursor={'pointer'}
onClick={() => router.push('/account')}
>
<Avatar w={'36px'} h={'36px'} src={userInfo?.avatar} fallbackSrc={HUMAN_ICON} />
<Avatar
w={'36px'}
h={'36px'}
src={userInfo?.avatar}
fallbackSrc={HUMAN_ICON}
borderRadius={'50%'}
/>
</Box>
{/* 导航列表 */}
<Box flex={1}>

View File

@@ -99,7 +99,7 @@ const ScheduledTriggerConfig = ({
const timezone = value?.timezone;
const cronString = value?.cronString;
const defaultPrompt = value?.defaultPrompt || '';
const defaultPrompt = value?.defaultPrompt;
const cronSelectList = useRef<MultipleSelectProps['list']>([
{
@@ -134,15 +134,10 @@ const ScheduledTriggerConfig = ({
timezone?: string;
defaultPrompt?: string;
}) => {
if (!cronString) {
onChange(undefined);
return;
}
onChange({
...value,
cronString,
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
defaultPrompt: defaultPrompt || ''
cronString: cronString ?? value?.cronString ?? '0 0 * * *',
timezone: timezone ?? value?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
defaultPrompt: defaultPrompt ?? value?.defaultPrompt ?? ''
});
},
[onChange, value]
@@ -314,7 +309,7 @@ const ScheduledTriggerConfig = ({
</Box>
</Flex>
<Box mt={5}>
<Box>{t('core.app.schedule.Default prompt')}</Box>
<Box mb={1}>{t('core.app.schedule.Default prompt')}</Box>
<Textarea
value={defaultPrompt}
rows={8}

View File

@@ -7,9 +7,10 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const ButtonEdge = (props: EdgeProps) => {
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData);
const { nodes, setEdges, workflowDebugData, hoverEdgeId } = useContextSelector(
WorkflowContext,
(v) => v
);
const {
id,
@@ -50,6 +51,7 @@ const ButtonEdge = (props: EdgeProps) => {
});
const isToolEdge = sourceHandleId === NodeOutputKeyEnum.selectedTools;
const isHover = hoverEdgeId === id;
const { newTargetX, newTargetY } = useMemo(() => {
if (targetPosition === 'left') {
@@ -116,24 +118,23 @@ const ButtonEdge = (props: EdgeProps) => {
})();
return (
<EdgeLabelRenderer>
{highlightEdge && (
<Flex
alignItems={'center'}
justifyContent={'center'}
position={'absolute'}
transform={`translate(-55%, -50%) translate(${labelX}px,${labelY}px)`}
pointerEvents={'all'}
w={'17px'}
h={'17px'}
bg={'white'}
borderRadius={'17px'}
cursor={'pointer'}
zIndex={1000}
onClick={() => onDelConnect(id)}
>
<MyIcon name={'core/workflow/closeEdge'} w={'100%'}></MyIcon>
</Flex>
)}
<Flex
display={isHover || highlightEdge ? 'flex' : 'none'}
alignItems={'center'}
justifyContent={'center'}
position={'absolute'}
transform={`translate(-55%, -50%) translate(${labelX}px,${labelY}px)`}
pointerEvents={'all'}
w={'17px'}
h={'17px'}
bg={'white'}
borderRadius={'17px'}
cursor={'pointer'}
zIndex={1000}
onClick={() => onDelConnect(id)}
>
<MyIcon name={'core/workflow/closeEdge'} w={'100%'}></MyIcon>
</Flex>
{!isToolEdge && (
<Flex
alignItems={'center'}
@@ -161,6 +162,7 @@ const ButtonEdge = (props: EdgeProps) => {
</EdgeLabelRenderer>
);
}, [
isHover,
highlightEdge,
labelX,
labelY,

View File

@@ -11,7 +11,8 @@ import ReactFlow, {
NodeChange,
OnConnectStartParams,
addEdge,
EdgeChange
EdgeChange,
Edge
} from 'reactflow';
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
@@ -58,7 +59,8 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
),
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')),
[FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')),
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate'))
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')),
[FlowNodeTypeEnum.code]: dynamic(() => import('./nodes/NodeCode'))
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge
@@ -73,13 +75,16 @@ const Container = React.memo(function Container() {
});
const { isDowningCtrl } = useKeyboard();
const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge);
const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
const onNodesChange = useContextSelector(WorkflowContext, (v) => v.onNodesChange);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const onEdgesChange = useContextSelector(WorkflowContext, (v) => v.onEdgesChange);
const {
setConnectingEdge,
reactFlowWrapper,
nodes,
onNodesChange,
edges,
setEdges,
onEdgesChange,
setHoverEdgeId
} = useContextSelector(WorkflowContext, (v) => v);
/* node */
const handleNodesChange = useCallback(
@@ -158,6 +163,17 @@ const Container = React.memo(function Container() {
[onConnect, t, toast]
);
/* edge */
const onEdgeMouseEnter = useCallback(
(e: any, edge: Edge) => {
setHoverEdgeId(edge.id);
},
[setHoverEdgeId]
);
const onEdgeMouseLeave = useCallback(() => {
setHoverEdgeId(undefined);
}, [setHoverEdgeId]);
return (
<>
<ReactFlow
@@ -177,6 +193,8 @@ const Container = React.memo(function Container() {
onConnect={customOnConnect}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
onEdgeMouseEnter={onEdgeMouseEnter}
onEdgeMouseLeave={onEdgeMouseLeave}
>
<FlowController />
</ReactFlow>

View File

@@ -0,0 +1,106 @@
import React, { useCallback, useMemo } from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container';
import RenderInput from './render/RenderInput';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import IOTitle from '../components/IOTitle';
import RenderToolInput from './render/RenderToolInput';
import RenderOutput from './render/RenderOutput';
import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor';
import { Box, Flex } from '@chakra-ui/react';
import { useI18n } from '@/web/context/I18n';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { workflowT } = useI18n();
const { nodeId, inputs, outputs } = data;
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
const { ConfirmModal, openConfirm } = useConfirm({
content: workflowT('code.Reset template confirm')
});
const CustomComponent = useMemo(() => {
return {
[NodeInputKeyEnum.code]: (item: FlowNodeInputItemType) => {
return (
<Box>
<Flex mb={1} alignItems={'flex-end'}>
<Box flex={'1'}>Javascript{workflowT('Code')}</Box>
<Box
cursor={'pointer'}
color={'primary.500'}
fontSize={'xs'}
onClick={openConfirm(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: JS_TEMPLATE
}
});
})}
>
{workflowT('code.Reset template')}
</Box>
</Flex>
<CodeEditor
bg={'white'}
borderRadius={'sm'}
value={item.value}
onChange={(e) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: e
}
});
}}
/>
</Box>
);
}
};
}, [nodeId, onChangeNode, openConfirm, workflowT]);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
{toolInputs.length > 0 && (
<>
<Container>
<IOTitle text={t('core.module.tool.Tool input')} />
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
</Container>
</>
)}
<Container>
<IOTitle text={t('common.Input')} />
<RenderInput
nodeId={nodeId}
flowInputList={commonInputs}
CustomComponent={CustomComponent}
/>
</Container>
<Container>
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<ConfirmModal />
</NodeCard>
);
};
export default React.memo(NodeCode);

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React from 'react';
import {
Box,
Button,
@@ -15,8 +15,11 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
export const defaultField: ContextExtractAgentItemType = {
valueType: 'string',
required: false,
defaultValue: '',
desc: '',
@@ -34,10 +37,11 @@ const ExtractFieldModal = ({
onSubmit: (data: ContextExtractAgentItemType) => void;
}) => {
const { t } = useTranslation();
const { register, handleSubmit, watch } = useForm<ContextExtractAgentItemType>({
const { register, setValue, handleSubmit, watch } = useForm<ContextExtractAgentItemType>({
defaultValues: defaultField
});
const required = watch('required');
const valueType = watch('valueType');
return (
<MyModal
@@ -68,8 +72,21 @@ const ExtractFieldModal = ({
</Flex>
)}
<Flex alignItems={'center'} mt={5}>
<Box flex={['0 0 80px', '0 0 100px']}>{t('core.module.Data Type')}</Box>
<Box flex={'1 0 0'}>
<MySelect
list={fnValueTypeSelect}
value={valueType}
onchange={(e: any) => {
setValue('valueType', e);
}}
/>
</Box>
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box flex={['0 0 80px', '0 0 100px']}>{t('core.module.Field key')}</Box>
<Box flex={['0 0 80px', '0 0 100px']}>{t('Field name')}</Box>
<Input
bg={'myGray.50'}
placeholder="name/age/sql"
@@ -84,21 +101,23 @@ const ExtractFieldModal = ({
{...register('desc', { required: true })}
/>
</Flex>
<Box mt={5}>
<Flex alignItems={'center'}>
{t('core.module.extract.Enum Value')}({t('common.choosable')})
<MyTooltip label={t('core.module.extract.Enum Description')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
{(valueType === 'string' || valueType === 'number') && (
<Box mt={5}>
<Flex alignItems={'center'}>
{t('core.module.extract.Enum Value')}({t('common.choosable')})
<MyTooltip label={t('core.module.extract.Enum Description')} forceShow>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Textarea
rows={5}
bg={'myGray.50'}
placeholder={'apple\npeach\nwatermelon'}
{...register('enum')}
/>
</Box>
<Textarea
rows={5}
bg={'myGray.50'}
placeholder={'apple\npeach\nwatermelon'}
{...register('enum')}
/>
</Box>
)}
</ModalBody>
<ModalFooter>

View File

@@ -18,7 +18,6 @@ import NodeCard from '../render/NodeCard';
import Container from '../../components/Container';
import { AddIcon } from '@chakra-ui/icons';
import RenderInput from '../render/RenderInput';
import Divider from '../../components/Divider';
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
import RenderOutput from '../render/RenderOutput';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -79,7 +78,7 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
<Table bg={'white'}>
<Thead>
<Tr>
<Th bg={'myGray.50'}> key</Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>
<Th bg={'myGray.50'}></Th>

View File

@@ -79,7 +79,6 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
};
} catch (err) {
await putUpdateTeam({
teamId: userInfo?.team.teamId || '',
lafAccount: { token: '', appid: '', pat: '' }
});
initUserInfo();

View File

@@ -2,10 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Box, Button, Card, Flex } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@/components/Avatar';
import type {
FlowNodeItemType,
FlowNodeTemplateType
} from '@fastgpt/global/core/workflow/type/index.d';
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -16,7 +13,7 @@ import { ToolTargetHandle } from './Handle/ToolHandle';
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
import { useDebug } from '../../hooks/useDebug';
import { ResponseBox } from '@/components/ChatBox/WholeResponseModal';
import { ResponseBox } from '@/components/ChatBox/components/WholeResponseModal';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import { storeNode2FlowNode, updateFlowNodeVersion } from '@/web/core/workflow/utils';
@@ -27,7 +24,6 @@ import { useI18n } from '@/web/context/I18n';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyTooltip from '@/components/MyTooltip';
import { isEqual } from 'lodash';
import { useSystemStore } from '@/web/common/system/useSystemStore';
type Props = FlowNodeItemType & {

View File

@@ -72,7 +72,6 @@ const AddInputParam = (props: RenderInputProps) => {
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
onClick={() => setEditField(item.dynamicParamDefaultValue ?? {})}
>
{t('common.Add New')}

View File

@@ -13,6 +13,7 @@ import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
const RenderList: {
types: `${FlowNodeOutputTypeEnum}`[];
@@ -61,6 +62,7 @@ const RenderOutput = ({
<Box position={'relative'} fontWeight={'medium'}>
{t('core.workflow.Custom outputs')}
</Box>
<QuestionTip ml={1} label={addOutput.description} />
<Box flex={'1 0 0'} />
<Button
variant={'whitePrimary'}

View File

@@ -20,6 +20,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
const EditFieldModal = ({
defaultValue = defaultEditFormData,
@@ -35,21 +36,6 @@ const EditFieldModal = ({
});
const valueType = watch('valueType');
const selectTypeList = useRef([
{
label: t('core.module.valueType.string'),
value: 'string'
},
{
label: t('core.module.valueType.number'),
value: 'number'
},
{
label: t('core.module.valueType.boolean'),
value: 'boolean'
}
]);
const { mutate: onclickSubmit } = useRequest({
mutationFn: async (e: FlowNodeInputItemType) => {
const inputConfig: FlowNodeInputItemType = {
@@ -105,7 +91,7 @@ const EditFieldModal = ({
<Box flex={'0 0 80px'}>{t('core.module.Data Type')}</Box>
<Box flex={'1 0 0'}>
<MySelect
list={selectTypeList.current}
list={fnValueTypeSelect}
value={valueType}
onchange={(e: any) => {
setValue('valueType', e);

View File

@@ -70,6 +70,8 @@ type WorkflowContextType = {
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
hoverEdgeId?: string;
setHoverEdgeId: React.Dispatch<React.SetStateAction<string | undefined>>;
// connect
connectingEdge?: OnConnectStartParams;
@@ -216,6 +218,9 @@ export const WorkflowContext = createContext<WorkflowContextType>({
isShowVersionHistories: false,
setIsShowVersionHistories: function (value: React.SetStateAction<boolean>): void {
throw new Error('Function not implemented.');
},
setHoverEdgeId: function (value: React.SetStateAction<string | undefined>): void {
throw new Error('Function not implemented.');
}
});
@@ -233,6 +238,7 @@ const WorkflowContextProvider = ({
/* edge */
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [hoverEdgeId, setHoverEdgeId] = useState<string>();
const onDelEdge = useCallback(
({
nodeId,
@@ -673,6 +679,8 @@ const WorkflowContextProvider = ({
// edge
edges,
setEdges,
hoverEdgeId,
setHoverEdgeId,
onEdgesChange,
connectingEdge,
setConnectingEdge,

View File

@@ -84,7 +84,6 @@ const LafAccountModal = ({
mutationFn: async (data: LafAccountType) => {
if (!userInfo?.team.teamId) return;
return putUpdateTeam({
teamId: userInfo?.team.teamId,
lafAccount: data
});
},
@@ -142,7 +141,6 @@ const LafAccountModal = ({
onClick={() => {
onResetForm();
putUpdateTeam({
teamId: userInfo?.team.teamId || '',
lafAccount: { token: '', appid: '', pat: '' }
});
}}

View File

@@ -83,7 +83,6 @@ function EditModal({
mutationFn: async (data: FormDataType) => {
if (!data.id) return Promise.resolve('');
return putUpdateTeam({
teamId: data.id,
name: data.name,
avatar: data.avatar
});

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'next-i18next';
import { useQuery } from '@tanstack/react-query';
import {
getTeamMembers,
putUpdateMember,
delRemoveMember,
getTeamList,
delLeaveTeam,
@@ -68,17 +67,10 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
['getMembers', userInfo?.team?.teamId],
() => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
return getTeamMembers();
}
);
const { mutate: onUpdateMember, isLoading: isLoadingUpdateMember } = useRequest({
mutationFn: putUpdateMember,
onSuccess() {
refetchMembers();
}
});
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
mutationFn: delRemoveMember,
onSuccess() {
@@ -147,13 +139,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
<TeamList />
<TeamCard />
<Loading
loading={
isLoadingUpdateMember ||
isLoadingRemoveMember ||
isLoadingTeams ||
isLoadingLeaveTeam ||
isSwitchTeam
}
loading={isLoadingRemoveMember || isLoadingTeams || isLoadingLeaveTeam || isSwitchTeam}
fixed={false}
/>
</Box>

View File

@@ -57,7 +57,7 @@ const TeamTagsAsync = ({ onClose }: { onClose: () => void }) => {
// tags Async
const { mutate: onclickUpdate, isLoading: isUpdating } = useRequest({
mutationFn: async (data: FormType) => {
return putUpdateTeam({ teamDomain: data.teamDomain, teamId: teamInfo?.teamId });
return putUpdateTeam({ teamDomain: data.teamDomain });
},
onSuccess() {
initUserInfo();

View File

@@ -59,7 +59,7 @@ const UsageTable = () => {
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
if (!userInfo?.team?.teamId) return [];
return getTeamMembers(userInfo.team.teamId);
return getTeamMembers();
});
const tmbList = useMemo(
() =>

View File

@@ -148,7 +148,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if ((await targetCol.countDocuments()) > 1) {
// 除了root
console.log('team_members 中有数据,无法自动将 buffer.tts 迁移到 team_members请手动操作');
console.log('team_members 中有数据,无法自动将 team.tts 迁移到 team_members请手动操作');
} else {
await sourceCol.rename('team_members', { dropTarget: true });
console.log('success rename team.members -> team_members');

View File

@@ -0,0 +1,21 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
import { authTeamOwner } from '@fastgpt/service/support/permission/auth/user';
import { updateTeam } from '@fastgpt/service/support/user/team/controller';
export type updateQuery = {};
export type updateBody = {};
export type updateResponse = {};
async function handler(req: ApiRequestProps<updateBody, updateQuery>, res: ApiResponseType<any>) {
const body = req.body as UpdateTeamProps;
const { teamId } = await authTeamOwner({ req, authToken: true });
await updateTeam({ teamId, ...body });
}
export default NextAPI(handler);

View File

@@ -78,17 +78,16 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) =>
mutationFn: async (websiteConfig: DatasetSchemaType['websiteConfig']) => {
onCloseWebsiteModal();
await checkTeamWebSyncLimit();
const billId = await postCreateTrainingUsage({
name: t('core.dataset.training.Website Sync'),
datasetId: datasetId
});
await postWebsiteSync({ datasetId: datasetId, billId });
await updateDataset({
id: datasetId,
websiteConfig,
status: DatasetStatusEnum.syncing
});
const billId = await postCreateTrainingUsage({
name: t('core.dataset.training.Website Sync'),
datasetId: datasetId
});
await postWebsiteSync({ datasetId: datasetId, billId });
return;
},

View File

@@ -39,7 +39,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import MyTooltip from '@/components/MyTooltip';
import { useUserStore } from '@/web/support/user/useUserStore';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useContextSelector } from 'use-context-selector';

View File

@@ -26,13 +26,13 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
const redirectUri = `${location.origin}/login/provider`;
const oAuthList = [
...(feConfigs?.oauth?.github
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
? [
{
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/gitFill',
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
label: t('support.user.login.Wechat'),
provider: OAuthEnum.wechat,
icon: 'common/wechatFill',
pageType: LoginPageTypeEnum.wechat
}
]
: []),
@@ -46,13 +46,13 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
}
]
: []),
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
...(feConfigs?.oauth?.github
? [
{
label: t('support.user.login.Wechat'),
provider: OAuthEnum.wechat,
icon: 'common/wechatFill',
pageType: LoginPageTypeEnum.wechat
label: t('support.user.login.Github'),
provider: OAuthEnum.github,
icon: 'common/gitFill',
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
}
]
: []),

View File

@@ -1,11 +1,10 @@
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
export const getChatModelNameListByModules = (modules: StoreNodeItemType[]): string[] => {
const chatModules = modules.filter((item) => item.flowNodeType === FlowNodeTypeEnum.chatNode);
return chatModules
export const getChatModelNameListByModules = (nodes: StoreNodeItemType[]): string[] => {
return nodes
.map((item) => {
const model = item.inputs.find((input) => input.key === 'model')?.value;
const model = item.inputs.find((input) => input.key === NodeInputKeyEnum.aiModel)?.value;
return global.llmModels.find((item) => item.model === model)?.name || '';
})
.filter(Boolean);

View File

@@ -118,7 +118,6 @@ function responseError(
})
.then((res) => {
putUpdateTeam({
teamId: useUserStore.getState().userInfo?.team.teamId || '',
lafAccount: {
...useUserStore.getState().userInfo?.team?.lafAccount,
token: res

View File

@@ -85,3 +85,18 @@ export const FlowValueTypeMap = {
description: ''
}
};
export const fnValueTypeSelect = [
{
label: 'String',
value: 'string'
},
{
label: 'Number',
value: 'number'
},
{
label: 'Boolean',
value: 'boolean'
}
];

View File

@@ -22,18 +22,15 @@ export const getTeamList = (status: `${TeamMemberSchema['status']}`) =>
GET<TeamItemType[]>(`/proApi/support/user/team/list`, { status });
export const postCreateTeam = (data: CreateTeamProps) =>
POST<string>(`/proApi/support/user/team/create`, data);
export const putUpdateTeam = (data: UpdateTeamProps) =>
PUT(`/proApi/support/user/team/update`, data);
export const putUpdateTeam = (data: UpdateTeamProps) => PUT(`/support/user/team/update`, data);
export const putSwitchTeam = (teamId: string) =>
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
/* --------------- team member ---------------- */
export const getTeamMembers = (teamId: string) =>
GET<TeamMemberItemType[]>(`/proApi/support/user/team/member/list`, { teamId });
export const getTeamMembers = () =>
GET<TeamMemberItemType[]>(`/proApi/support/user/team/member/list`);
export const postInviteTeamMember = (data: InviteMemberProps) =>
POST<InviteMemberResponse>(`/proApi/support/user/team/member/invite`, data);
export const putUpdateMember = (data: UpdateTeamMemberProps) =>
PUT(`/proApi/support/user/team/member/update`, data);
export const putUpdateMemberName = (name: string) =>
PUT(`/proApi/support/user/team/member/updateName`, { name });
export const delRemoveMember = (props: DelMemberProps) =>

View File

@@ -0,0 +1,11 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
.yalc/
yalc.lock
testApi/

View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module'
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended'],
root: true,
env: {
node: true,
jest: true
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-var-requires': 'off'
}
};

34
projects/sandbox/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies
node_modules/
# next.js
.next/
out/
# production
build/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
platform.json
testApi/
local/
.husky/
data/*.local.*
storage/

View File

@@ -0,0 +1,50 @@
# --------- install dependence -----------
FROM python:3.11-alpine AS python_base
# 安装make和g++
RUN apk add --no-cache make g++
FROM node:20.13-alpine AS install
WORKDIR /app
ARG proxy
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache make g++
# copy py3.11
COPY --from=python_base /usr/local /usr/local
RUN npm install -g pnpm@8.6.2
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY ./projects/sandbox/package.json ./projects/sandbox/package.json
RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
RUN pnpm i
# --------- builder -----------
FROM node:20.13-alpine AS builder
WORKDIR /app
COPY package.json pnpm-workspace.yaml /app
COPY --from=install /app/node_modules /app/node_modules
COPY ./projects/sandbox /app/projects/sandbox
COPY --from=install /app/projects/sandbox /app/projects/sandbox
RUN npm install -g pnpm@8.6.2
RUN pnpm --filter=sandbox build
# --------- runner -----------
FROM node:20.13-alpine AS runner
WORKDIR /app
COPY --from=builder /app/node_modules /app/node_modules
COPY --from=builder /app/projects/sandbox /app/projects/sandbox
ENV NODE_ENV=production
CMD ["node", "projects/sandbox/dist/main.js"]

View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ pnpm install
```
## Running the app
```bash
# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod
```
## Test
```bash
# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"plugins": ["@nestjs/swagger"]
}
}

View File

@@ -0,0 +1,69 @@
{
"name": "sandbox",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@fastify/static": "^7.0.4",
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-fastify": "^10.3.8",
"@nestjs/swagger": "^7.3.1",
"fastify": "^4.27.0",
"isolated-vm": "^4.7.2",
"node-gyp": "^10.1.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"jest": "^29.5.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { SandboxController } from './sandbox/sandbox.controller';
import { SandboxService } from './sandbox/sandbox.service';
@Module({
imports: [],
controllers: [SandboxController],
providers: [SandboxService]
})
export class AppModule {}

View File

@@ -0,0 +1,18 @@
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { FastifyRequest, FastifyReply } from 'fastify';
import { getErrText } from './utils';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(error: any, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<FastifyReply>();
const request = ctx.getRequest<FastifyRequest>();
response.status(500).send({
success: false,
time: new Date(),
msg: getErrText(error)
});
}
}

View File

@@ -0,0 +1,38 @@
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { HttpExceptionFilter } from './http-exception.filter';
import { ResponseInterceptor } from './response';
async function bootstrap(port: number) {
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
// 使用全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(new ResponseInterceptor());
const config = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
try {
await app.listen(port, '0.0.0.0');
console.log(`Application is running on: ${await app.getUrl()}`);
} catch (error) {
if (error.code === 'EADDRINUSE') {
console.warn(`Port ${port} is already in use, trying next port...`);
await bootstrap(port + 1);
} else {
console.error(`Failed to start application: ${error.message}`);
process.exit(1);
}
}
}
bootstrap(3000);

View File

@@ -0,0 +1,15 @@
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
success: true,
data
}))
);
}
}

View File

@@ -0,0 +1,4 @@
export class RunCodeDto {
code: string;
variables: object;
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SandboxController } from './sandbox.controller';
import { SandboxService } from './sandbox.service';
describe('SandboxController', () => {
let controller: SandboxController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SandboxController],
providers: [SandboxService]
}).compile();
controller = module.get<SandboxController>(SandboxController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,15 @@
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
import { SandboxService } from './sandbox.service';
import { RunCodeDto } from './dto/create-sandbox.dto';
import { WorkerNameEnum, runWorker } from 'src/worker/utils';
@Controller('sandbox')
export class SandboxController {
constructor(private readonly sandboxService: SandboxService) {}
@Post('/js')
@HttpCode(200)
runJs(@Body() codeProps: RunCodeDto) {
return runWorker(WorkerNameEnum.runJs, codeProps);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { SandboxService } from './sandbox.service';
import { SandboxController } from './sandbox.controller';
@Module({
controllers: [SandboxController],
providers: [SandboxService]
})
export class SandboxModule {}

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