Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e48922bc9 | ||
|
|
8ba8488086 | ||
|
|
9639139b52 | ||
|
|
d9f5f4ede0 | ||
|
|
6609cb98dc | ||
|
|
74830f0ac8 |
108
.github/workflows/build-sandbox-image.yml
vendored
Normal file
108
.github/workflows/build-sandbox-image.yml
vendored
Normal 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}}
|
||||
11
.github/workflows/fastgpt-image.yml
vendored
11
.github/workflows/fastgpt-image.yml
vendored
@@ -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}}
|
||||
|
||||
7
.github/workflows/preview-image.yml
vendored
7
.github/workflows/preview-image.yml
vendored
@@ -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
25
Makefile
Normal 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
|
||||
12
README.md
12
README.md
@@ -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">
|
||||
|
||||
25
README_en.md
25
README_en.md
@@ -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">
|
||||
|
||||
[](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 |
|
||||
| ------------------------------------------------- |
|
||||
|  |
|
||||
+ 🌐 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
45
dev.md
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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' )
|
||||
|
||||
|
||||
@@ -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}]
|
||||
```
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
34
docSite/content/docs/development/upgrading/482.md
Normal file
34
docSite/content/docs/development/upgrading/482.md
Normal 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. 修复 - 定时任务无法输入内容
|
||||
@@ -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
|
||||
|
||||
@@ -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 = `请帮我执行一个“问题分类”任务,将问题分类为以下几种类型之一:
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -113,7 +113,8 @@ export enum FlowNodeTypeEnum {
|
||||
stopTool = 'stopTool',
|
||||
lafModule = 'lafModule',
|
||||
ifElseNode = 'ifElseNode',
|
||||
variableUpdate = 'variableUpdate'
|
||||
variableUpdate = 'variableUpdate',
|
||||
code = 'code'
|
||||
}
|
||||
|
||||
export const EDGE_TYPE = 'default';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
];
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const JS_TEMPLATE = `function main({data1, data2}){
|
||||
|
||||
return {
|
||||
result: data1,
|
||||
data2
|
||||
}
|
||||
}`;
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -115,6 +115,7 @@ export type ClassifyQuestionAgentItemType = {
|
||||
key: string;
|
||||
};
|
||||
export type ContextExtractAgentItemType = {
|
||||
valueType: 'string' | 'number' | 'boolean';
|
||||
desc: string;
|
||||
key: string;
|
||||
required: boolean;
|
||||
|
||||
@@ -14,7 +14,6 @@ export type CreateTeamProps = {
|
||||
lafAccount?: LafAccountType;
|
||||
};
|
||||
export type UpdateTeamProps = {
|
||||
teamId: string;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
teamDomain?: string;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
55
packages/service/core/workflow/dispatch/code/run.ts
Normal file
55
packages/service/core/workflow/dispatch/code/run.ts
Normal 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)
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
160
packages/web/components/common/Textarea/CodeEditor/Editor.tsx
Normal file
160
packages/web/components/common/Textarea/CodeEditor/Editor.tsx
Normal 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);
|
||||
36
packages/web/components/common/Textarea/CodeEditor/index.tsx
Normal file
36
packages/web/components/common/Textarea/CodeEditor/index.tsx
Normal 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);
|
||||
@@ -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
3471
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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=
|
||||
# 首页路径
|
||||
|
||||
@@ -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}"]
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "辅助数据",
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
{
|
||||
"Field required": "必填"
|
||||
"Code": "代码",
|
||||
"Field required": "必填",
|
||||
"code": {
|
||||
"Reset template": "还原模板",
|
||||
"Reset template confirm": "确认还原代码模板?请注意保存当前代码。"
|
||||
},
|
||||
"response": {
|
||||
"Custom inputs": "自定义输入",
|
||||
"Custom outputs": "自定义输出",
|
||||
"Error": "错误信息"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, '../../')
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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/)
|
||||
24
projects/app/public/imgs/workflow/code.svg
Normal file
24
projects/app/public/imgs/workflow/code.svg
Normal 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 |
@@ -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,
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 = [],
|
||||
@@ -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';
|
||||
@@ -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';
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
@@ -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>({
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -79,7 +79,6 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
};
|
||||
} catch (err) {
|
||||
await putUpdateTeam({
|
||||
teamId: userInfo?.team.teamId || '',
|
||||
lafAccount: { token: '', appid: '', pat: '' }
|
||||
});
|
||||
initUserInfo();
|
||||
|
||||
@@ -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 & {
|
||||
|
||||
@@ -72,7 +72,6 @@ const AddInputParam = (props: RenderInputProps) => {
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={() => setEditField(item.dynamicParamDefaultValue ?? {})}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: '' }
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
() =>
|
||||
|
||||
@@ -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');
|
||||
|
||||
21
projects/app/src/pages/api/support/user/team/update.ts
Normal file
21
projects/app/src/pages/api/support/user/team/update.ts
Normal 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);
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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`
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -118,7 +118,6 @@ function responseError(
|
||||
})
|
||||
.then((res) => {
|
||||
putUpdateTeam({
|
||||
teamId: useUserStore.getState().userInfo?.team.teamId || '',
|
||||
lafAccount: {
|
||||
...useUserStore.getState().userInfo?.team?.lafAccount,
|
||||
token: res
|
||||
|
||||
@@ -85,3 +85,18 @@ export const FlowValueTypeMap = {
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
||||
export const fnValueTypeSelect = [
|
||||
{
|
||||
label: 'String',
|
||||
value: 'string'
|
||||
},
|
||||
{
|
||||
label: 'Number',
|
||||
value: 'number'
|
||||
},
|
||||
{
|
||||
label: 'Boolean',
|
||||
value: 'boolean'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
11
projects/sandbox/.dockerignore
Normal file
11
projects/sandbox/.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
||||
|
||||
.yalc/
|
||||
yalc.lock
|
||||
testApi/
|
||||
25
projects/sandbox/.eslintrc.js
Normal file
25
projects/sandbox/.eslintrc.js
Normal 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
34
projects/sandbox/.gitignore
vendored
Normal 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/
|
||||
50
projects/sandbox/Dockerfile
Normal file
50
projects/sandbox/Dockerfile
Normal 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"]
|
||||
73
projects/sandbox/README.md
Normal file
73
projects/sandbox/README.md
Normal 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>
|
||||
<!--[](https://opencollective.com/nest#backer)
|
||||
[](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).
|
||||
9
projects/sandbox/nest-cli.json
Normal file
9
projects/sandbox/nest-cli.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"plugins": ["@nestjs/swagger"]
|
||||
}
|
||||
}
|
||||
69
projects/sandbox/package.json
Normal file
69
projects/sandbox/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
10
projects/sandbox/src/app.module.ts
Normal file
10
projects/sandbox/src/app.module.ts
Normal 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 {}
|
||||
18
projects/sandbox/src/http-exception.filter.ts
Normal file
18
projects/sandbox/src/http-exception.filter.ts
Normal 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)
|
||||
});
|
||||
}
|
||||
}
|
||||
38
projects/sandbox/src/main.ts
Normal file
38
projects/sandbox/src/main.ts
Normal 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);
|
||||
15
projects/sandbox/src/response.ts
Normal file
15
projects/sandbox/src/response.ts
Normal 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
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
4
projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts
Normal file
4
projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class RunCodeDto {
|
||||
code: string;
|
||||
variables: object;
|
||||
}
|
||||
20
projects/sandbox/src/sandbox/sandbox.controller.spec.ts
Normal file
20
projects/sandbox/src/sandbox/sandbox.controller.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
15
projects/sandbox/src/sandbox/sandbox.controller.ts
Normal file
15
projects/sandbox/src/sandbox/sandbox.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
9
projects/sandbox/src/sandbox/sandbox.module.ts
Normal file
9
projects/sandbox/src/sandbox/sandbox.module.ts
Normal 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
Reference in New Issue
Block a user