From a259d034b8c7492efdbfd8db3a7320fa7d16bd32 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Sat, 1 Jun 2024 09:26:11 +0800 Subject: [PATCH] 4.8.3 (#1654) * Milvus (#1644) * feat: support regx * 4.8.3 test and fix (#1648) * perf: version tip * feat: sandbox support log * fix: debug component render * fix: share page header * fix: input guide auth * fix: iso viewport * remove file * fix: route url * feat: add debug timout * perf: reference select support trigger * perf: session code * perf: theme * perf: load milvus --- .github/workflows/preview-image.yml | 2 - .npmrc | 1 + README.md | 6 +- docSite/assets/imgs/zilliz_key.png | Bin 0 -> 56077 bytes .../content/zh-cn/docs/development/docker.md | 122 ++++++-- .../zh-cn/docs/development/upgrading/482.md | 4 +- .../zh-cn/docs/development/upgrading/483.md | 22 ++ files/docker/docker-compose-milvus.yml | 202 ++++++++++++ .../docker-compose-pgvector.yml} | 6 +- files/docker/docker-compose-zilliz.yml | 140 +++++++++ .../docker-compose/docker-compose | Bin .../fastgpt => docker}/docker-compose/init.sh | 0 files/{deploy/fastgpt => docker}/run.sh | 0 package.json | 8 +- packages/global/common/error/code/plugin.ts | 4 +- packages/global/common/error/code/system.ts | 23 ++ packages/global/common/error/errorCode.ts | 4 +- packages/global/common/error/utils.ts | 5 +- .../global/common/vectorStore/constants.ts | 1 - .../global/core/workflow/runtime/type.d.ts | 3 + .../template/system/ifElse/constant.ts | 3 + .../workflow/template/system/sandbox/index.ts | 14 + packages/global/core/workflow/type/io.d.ts | 2 +- packages/service/common/mongo/sessionRun.ts | 6 +- packages/service/common/system/log.ts | 35 ++- .../service/common/vectorStore/constants.ts | 6 + .../common/vectorStore/controller.d.ts | 17 ++ .../service/common/vectorStore/controller.ts | 25 +- .../common/vectorStore/milvus/class.ts | 287 ++++++++++++++++++ .../service/common/vectorStore/pg/class.ts | 192 +++++++++++- .../common/vectorStore/pg/controller.ts | 195 ------------ .../service/common/vectorStore/pg/index.ts | 3 +- packages/service/common/vectorStore/type.d.ts | 2 + .../service/core/dataset/search/controller.ts | 40 ++- .../core/workflow/dispatch/chat/oneapi.ts | 35 ++- .../core/workflow/dispatch/code/run.ts | 10 +- .../core/workflow/dispatch/tools/runIfElse.ts | 62 ++-- .../workflow/dispatch/tools/runUpdateVar.ts | 4 +- .../service/core/workflow/dispatch/utils.ts | 7 +- packages/service/package.json | 4 +- .../service/support/permission/teamLimit.ts | 12 +- .../common/MySelect/MultipleRowSelect.tsx | 15 +- packages/web/styles/theme.ts | 52 +++- pnpm-lock.yaml | 279 ++++++++++++++++- projects/app/.env.template | 8 +- projects/app/Dockerfile | 4 +- projects/app/i18n/en/workflow.json | 5 + projects/app/i18n/zh/common.json | 2 +- projects/app/i18n/zh/workflow.json | 5 + projects/app/next.config.js | 15 +- projects/app/package.json | 2 +- .../components/ChatBox/Input/ChatInput.tsx | 19 +- .../ChatBox/Input/InputGuideBox.tsx | 4 +- .../app/src/components/ChatBox/Provider.tsx | 22 +- .../ChatBox/components/WholeResponseModal.tsx | 1 + projects/app/src/components/Layout/index.tsx | 19 +- .../src/components/common/NextHead/index.tsx | 4 + .../core/workflow/Flow/hooks/useDebug.tsx | 55 ++-- .../Flow/nodes/NodeIfElse/ListItem.tsx | 12 +- .../RenderInput/templates/Reference.tsx | 2 +- .../Flow/nodes/render/RenderOutput/index.tsx | 2 +- projects/app/src/pages/api/admin/initv481.ts | 4 - .../api/core/app/questionGuides/import.ts | 41 --- .../pages/api/core/chat/inputGuide/query.ts | 18 +- .../detail/components/SimpleEdit/EditForm.tsx | 27 +- .../src/pages/chat/components/ChatHeader.tsx | 12 +- projects/app/src/pages/chat/index.tsx | 2 +- projects/app/src/pages/chat/share.tsx | 1 - .../Import/commonProgress/DataProcess.tsx | 43 +-- .../dataset/list/component/CreateModal.tsx | 68 +++-- projects/app/src/pages/login/fastlogin.tsx | 2 - projects/app/src/web/core/app/templates.ts | 31 +- projects/app/src/web/core/app/utils.ts | 15 +- .../app/src/web/core/chat/inputGuide/api.ts | 13 +- projects/app/src/web/core/workflow/adapt.ts | 4 +- projects/app/src/web/core/workflow/api.ts | 14 +- projects/sandbox/package.json | 2 +- projects/sandbox/src/http-exception.filter.ts | 2 +- .../src/sandbox/dto/create-sandbox.dto.ts | 5 + .../sandbox/src/sandbox/sandbox.controller.ts | 4 +- projects/sandbox/src/worker/runJs.ts | 24 +- 81 files changed, 1777 insertions(+), 596 deletions(-) create mode 100644 docSite/assets/imgs/zilliz_key.png create mode 100644 docSite/content/zh-cn/docs/development/upgrading/483.md create mode 100644 files/docker/docker-compose-milvus.yml rename files/{deploy/fastgpt/docker-compose.yml => docker/docker-compose-pgvector.yml} (99%) create mode 100644 files/docker/docker-compose-zilliz.yml rename files/{deploy/fastgpt => docker}/docker-compose/docker-compose (100%) rename files/{deploy/fastgpt => docker}/docker-compose/init.sh (100%) rename files/{deploy/fastgpt => docker}/run.sh (100%) create mode 100644 packages/global/common/error/code/system.ts delete mode 100644 packages/global/common/vectorStore/constants.ts create mode 100644 packages/service/common/vectorStore/constants.ts create mode 100644 packages/service/common/vectorStore/milvus/class.ts delete mode 100644 packages/service/common/vectorStore/pg/controller.ts delete mode 100644 projects/app/src/pages/api/core/app/questionGuides/import.ts diff --git a/.github/workflows/preview-image.yml b/.github/workflows/preview-image.yml index 90eca3741d..820cca7735 100644 --- a/.github/workflows/preview-image.yml +++ b/.github/workflows/preview-image.yml @@ -4,8 +4,6 @@ on: paths: - 'projects/app/**' - 'packages/**' - branches: - - 'main' workflow_dispatch: jobs: diff --git a/.npmrc b/.npmrc index b82e07751d..6199b36695 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ public-hoist-pattern[]=*tiktoken* +public-hoist-pattern[]=*@zilliz/milvus2-sdk-node* diff --git a/README.md b/README.md index ba1b9546ff..778e69aad4 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b `1` 应用编排能力 - [x] 提供简易模式,无需操作编排 - [x] 工作流编排 - - [x] 源文件引用追踪 - [x] 工具调用 - [x] 插件 - 工作流封装能力 - - [ ] Code sandbox + - [x] Code sandbox + - [ ] 循环调用 `2` 知识库能力 - [x] 多库复用,混用 @@ -65,7 +65,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b - [x] 支持 txt,md,html,pdf,docx,pptx,csv,xlsx (有需要更多可 PR file loader) - [x] 支持 url 读取、CSV 批量导入 - [x] 混合检索 & 重排 - - [ ] 支持文件阅读器 + - [ ] 标签过滤 `3` 应用调试能力 - [x] 知识库单点搜索测试 diff --git a/docSite/assets/imgs/zilliz_key.png b/docSite/assets/imgs/zilliz_key.png new file mode 100644 index 0000000000000000000000000000000000000000..b127ebc1f6d139ef5a5cd4fdb4db72ddedc3bb70 GIT binary patch literal 56077 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 o}} | 环境 | 最低配置(单节点) | 推荐配置 | | ---- | ---- | ---- | | 测试 | 2c2g | 2c4g | | 100w 组向量 | 4c8g 50GB | 4c16g 50GB | -| 500w 组向量 | 8c32g | 16c64g 200GB | +| 500w 组向量 | 8c32g 200GB | 16c64g 200GB | {{< /table >}} -## 部署架构图 +### Milvus版本 -![](/imgs/sealos-fastgpt.webp) +对于千万级以上向量性能更优秀。 + +[点击查看 Milvus 官方推荐配置](https://milvus.io/docs/prerequisite-docker.md) +{{< table "table-hover table-striped-columns" >}} +| 环境 | 最低配置(单节点) | 推荐配置 | +| ---- | ---- | ---- | +| 测试 | 2c8g | 4c16g | +| 100w 组向量 | 未测试 | | +| 500w 组向量 | | | +{{< /table >}} -### 1. 准备好代理环境(国外服务器可忽略) +### zilliz cloud版本 -确保可以访问 OpenAI,具体方案可以参考:[代理方案](/docs/development/proxy/)。或直接在 Sealos 上 [部署 OneAPI](/docs/development/one-api),既解决代理问题也能实现多 Key 轮询、接入其他大模型。 +亿级以上向量首选。 -### 2. 多模型支持 +由于向量库使用了 Cloud,无需占用本地资源,无需太关注。 -FastGPT 使用了 one-api 项目来管理模型池,其可以兼容 OpenAI 、Azure 、国内主流模型和本地模型等。 +## 前置工作 -可选择 [Sealos 快速部署 OneAPI](/docs/development/one-api),更多部署方法可参考该项目的 [README](https://github.com/songquanpeng/one-api),也可以直接通过以下按钮一键部署: +### 1. 确保网络环境 -Deploy on Sealos +如果使用`OpenAI`等国外模型接口,请确保可以正常访问,否则会报错:`Connection error` 等。 方案可以参考:[代理方案](/docs/development/proxy/) -## 一、安装 Docker 和 docker-compose +### 2. 准备 Docker 环境 {{< tabs tabTotal="3" >}} {{< tab tabName="Linux" >}} @@ -79,22 +105,75 @@ brew install orbstack {{< /tab >}} {{< /tabs >}} -## 二、创建目录并下载 docker-compose.yml -依次执行下面命令,创建 FastGPT 文件并拉取`docker-compose.yml`和`config.json`,执行完后目录下会有 2 个文件。 +## 开始部署 + +### 1. 下载 docker-compose.yml + + +非 Linux 环境或无法访问外网环境,可手动创建一个目录,并下载配置文件和对应版本的`docker-compose.yml` -非 Linux 环境或无法访问外网环境,可手动创建一个目录,并下载下面2个链接的文件: [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/deploy/fastgpt/docker-compose.yml),[config.json](https://github.com/labring/FastGPT/blob/main/projects/app/data/config.json) +- [config.json](https://github.com/labring/FastGPT/blob/main/projects/app/data/config.json) +- [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/docker) (注意,不同向量库版本的文件不一样) -**注意: `docker-compose.yml` 配置文件中 Mongo 为 5.x,部分服务器不支持,需手动更改其镜像版本为 4.4.24**(需要自己在docker hub下载,阿里云镜像没做备份) +{{% alert icon="🤖" context="success" %}} + +所有 `docker-compose.yml` 配置文件中 `MongoDB` 为 5.x,需要用到AUX指令集,部分 CPU 不支持,需手动更改其镜像版本为 4.4.24**(需要自己在docker hub下载,阿里云镜像没做备份) + +{{% /alert %}} + +**Linux 快速脚本** ```bash mkdir fastgpt cd fastgpt -curl -O https://raw.githubusercontent.com/labring/FastGPT/main/files/deploy/fastgpt/docker-compose.yml curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data/config.json + +# pgvector 版本(测试推荐,简单快捷) +curl -o docker-compose.yml https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose-pgvector.yml +# milvus 版本 +# curl -o docker-compose.yml https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose-milvus.yml +# zilliz 版本 +# curl -o docker-compose.yml https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose-zilliz.yml ``` -## 三、启动容器 +### 2. 修改 docker-compose.yml 环境变量 + +{{< tabs tabTotal="3" >}} +{{< tab tabName="PgVector版本" >}} +{{< markdownify >}} + +``` +无需操作 +``` + +{{< /markdownify >}} +{{< /tab >}} +{{< tab tabName="Milvus版本" >}} +{{< markdownify >}} + +``` +无需操作 +``` + +{{< /markdownify >}} +{{< /tab >}} +{{< tab tabName="Zilliz版本" >}} +{{< markdownify >}} + +![zilliz_key](/imgs/zilliz_key.png) + +{{% alert icon="🤖" context="success" %}} + +修改`MILVUS_ADDRESS`和`MILVUS_TOKEN`链接参数,分别对应 `zilliz` 的 `Public Endpoint` 和 `Api key`,记得把自己ip加入白名单。 + +{{% /alert %}} + +{{< /markdownify >}} +{{< /tab >}} +{{< /tabs >}} + +### 3. 启动容器 在 docker-compose.yml 同级目录下执行。请确保`docker-compose`版本最好在2.17以上,否则可能无法执行自动化命令。 @@ -107,13 +186,13 @@ sleep 10 docker restart oneapi ``` -## 四、打开 OneAPI 添加模型 +### 4. 打开 OneAPI 添加模型 可以通过`ip:3001`访问OneAPI,默认账号为`root`密码为`123456`。 在OneApi中添加合适的AI模型渠道。[点击查看相关教程](/docs/development/one-api/) -## 五、访问 FastGPT +### 5. 访问 FastGPT 目前可以通过 `ip:3000` 直接访问(注意防火墙)。登录用户名为 `root`,密码为`docker-compose.yml`环境变量里设置的 `DEFAULT_ROOT_PSW`。 @@ -125,7 +204,9 @@ docker restart oneapi ### Mongo 副本集自动初始化失败 -最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。如果你的环境特殊,可以手动初始化副本集: +最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。仍无法正常启动,大部分是因为 cpu 不支持 AUX 指令集,可以切换 Mongo4.x 版本。 + +如果是由于,无法自动初始化副本集合,可以手动初始化副本集: 1. 终端中执行下面命令,创建mongo密钥: @@ -266,13 +347,14 @@ PG 数据库没有连接上/初始化失败,可以查看日志。FastGPT 会 ### Operation `auth_codes.findOne()` buffering timed out after 10000ms -mongo连接失败,查看mongo的运行状态对应日志。 +mongo连接失败,查看mongo的运行状态**对应日志**。 可能原因: 1. mongo 服务有没有起来(有些 cpu 不支持 AVX,无法用 mongo5,需要换成 mongo4.x,可以docker hub找个最新的4.x,修改镜像版本,重新运行) 2. 连接数据库的环境变量填写错误(账号密码,注意host和port,非容器网络连接,需要用公网ip并加上 directConnection=true) 3. 副本集启动失败。导致容器一直重启。 +4. `Illegal instruction.... Waiting for MongoDB to start`: cpu 不支持 AVX,无法用 mongo5,需要换成 mongo4.x ### 首次部署,root用户提示未注册 diff --git a/docSite/content/zh-cn/docs/development/upgrading/482.md b/docSite/content/zh-cn/docs/development/upgrading/482.md index d2180d27d2..8ad1114999 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/482.md +++ b/docSite/content/zh-cn/docs/development/upgrading/482.md @@ -20,10 +20,10 @@ SANDBOX_URL=内网地址 ## Docker 部署 -可以拉取最新 [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/deploy/fastgpt/docker-compose.yml) 文件参考 +可以拉取最新 [docker-compose.yml](https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose.yml) 文件参考 1. 新增一个容器 `sandbox` -2. fastgpt容器新增环境变量: `SANDBOX_URL` +2. fastgpt 和 fastgpt-pro(商业版) 容器新增环境变量: `SANDBOX_URL` 3. sandbox 简易不要开启外网访问,未做凭证校验。 ## V4.8.2 更新说明 diff --git a/docSite/content/zh-cn/docs/development/upgrading/483.md b/docSite/content/zh-cn/docs/development/upgrading/483.md new file mode 100644 index 0000000000..f448f23839 --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/483.md @@ -0,0 +1,22 @@ +--- +title: 'V4.8.3(进行中)' +description: 'FastGPT V4.8.3 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 821 +--- + +## 升级指南 + +- fastgpt 镜像 tag 修改成 v4.8.3 +- fastgpt-sandbox 镜像 tag 修改成 v4.8.3 +- 商业版镜像 tag 修改成 v4.8.3 + +## V4.8.3 更新说明 + +1. 新增 - 支持 Milvus 数据库, 可参考最新的 [docker-compose-milvus.yml](https://github.com/labring/FastGPT/blob/main/files/docker/docker-compose-milvus.yml). +2. 新增 - 给 chat 接口 empty answer 增加 log,便于排查模型问题。 +3. 新增 - ifelse判断器,字符串支持正则。 +4. 新增 - 代码支持 +5. 修复 - 变量更新在 Debug 模式下出错。 \ No newline at end of file diff --git a/files/docker/docker-compose-milvus.yml b/files/docker/docker-compose-milvus.yml new file mode 100644 index 0000000000..2a4526aa81 --- /dev/null +++ b/files/docker/docker-compose-milvus.yml @@ -0,0 +1,202 @@ +# 数据库的默认账号和密码仅首次运行时设置有效 +# 如果修改了账号密码,记得改数据库和项目连接参数,别只改一处~ +# 该配置文件只是给快速启动,测试使用。正式使用,记得务必修改账号密码,以及调整合适的知识库参数,共享内存等。 +# 如何无法访问 dockerhub 和 git,可以用阿里云(阿里云没有arm包) + +version: '3.3' +services: + minio: + container_name: minio + image: minio/minio:RELEASE.2023-03-20T20-16-18Z + environment: + MINIO_ACCESS_KEY: minioadmin + MINIO_SECRET_KEY: minioadmin + ports: + - '9001:9001' + - '9000:9000' + networks: + - fastgpt + volumes: + - ./minio:/minio_data + command: minio server /minio_data --console-address ":9001" + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live'] + interval: 30s + timeout: 20s + retries: 3 + # milvus + milvusEtcd: + container_name: milvusEtcd + image: quay.io/coreos/etcd:v3.5.5 + environment: + - ETCD_AUTO_COMPACTION_MODE=revision + - ETCD_AUTO_COMPACTION_RETENTION=1000 + - ETCD_QUOTA_BACKEND_BYTES=4294967296 + - ETCD_SNAPSHOT_COUNT=50000 + networks: + - fastgpt + volumes: + - ./milvus/etcd:/etcd + command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd + healthcheck: + test: ['CMD', 'etcdctl', 'endpoint', 'health'] + interval: 30s + timeout: 20s + retries: 3 + milvusStandalone: + container_name: milvusStandalone + image: milvusdb/milvus:v2.4.3 + command: ['milvus', 'run', 'standalone'] + security_opt: + - seccomp:unconfined + environment: + ETCD_ENDPOINTS: milvusEtcd:2379 + MINIO_ADDRESS: minio:9000 + networks: + - fastgpt + volumes: + - ./milvus/data:/var/lib/milvus + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9091/healthz'] + interval: 30s + start_period: 90s + timeout: 20s + retries: 3 + depends_on: + - 'milvusEtcd' + - 'minio' + + mongo: + image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云 + container_name: mongo + restart: always + ports: + - 27017:27017 + networks: + - fastgpt + command: mongod --keyFile /data/mongodb.key --replSet rs0 + environment: + - MONGO_INITDB_ROOT_USERNAME=myusername + - MONGO_INITDB_ROOT_PASSWORD=mypassword + volumes: + - ./mongo/data:/data/db + entrypoint: + - bash + - -c + - | + openssl rand -base64 128 > /data/mongodb.key + chmod 400 /data/mongodb.key + chown 999:999 /data/mongodb.key + echo 'const isInited = rs.status().ok === 1 + if(!isInited){ + rs.initiate({ + _id: "rs0", + members: [ + { _id: 0, host: "mongo:27017" } + ] + }) + }' > /data/initReplicaSet.js + # 启动MongoDB服务 + exec docker-entrypoint.sh "$$@" & + + # 等待MongoDB服务启动 + until mongo -u myusername -p mypassword --authenticationDatabase admin --eval "print('waited for connection')" > /dev/null 2>&1; do + echo "Waiting for MongoDB to start..." + sleep 2 + done + + # 执行初始化副本集的脚本 + mongo -u myusername -p mypassword --authenticationDatabase admin /data/initReplicaSet.js + + # 等待docker-entrypoint.sh脚本执行的MongoDB服务进程 + wait $$! + + # fastgpt + 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.3-alpha # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.3-alpha # 阿里云 + ports: + - 3000:3000 + networks: + - fastgpt + depends_on: + - mongo + - milvusStandalone + - sandbox + restart: always + environment: + # root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。 + - DEFAULT_ROOT_PSW=1234 + # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 + - OPENAI_BASE_URL=http://oneapi:3000/v1 + # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) + - CHAT_API_KEY=sk-fastgpt + # 数据库最大连接数 + - DB_MAX_LINK=30 + # 登录凭证密钥 + - TOKEN_KEY=any + # root的密钥,常用于升级时候的初始化请求 + - ROOT_KEY=root_key + # 文件阅读加密 + - FILE_TOKEN_KEY=filetoken + # MongoDB 连接参数. 用户名myusername,密码mypassword。 + - MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin + # zilliz 连接参数 + - MILVUS_ADDRESS=http://milvusStandalone:19530 + - MILVUS_TOKEN=none + # sandbox 地址 + - SANDBOX_URL=http://sandbox:3000 + volumes: + - ./config.json:/app/data/config.json + + # oneapi + mysql: + image: mysql:8.0.36 + container_name: mysql + restart: always + ports: + - 3306:3306 + networks: + - fastgpt + command: --default-authentication-plugin=mysql_native_password + environment: + # 默认root密码,仅首次运行有效 + MYSQL_ROOT_PASSWORD: oneapimmysql + MYSQL_DATABASE: oneapi + volumes: + - ./mysql:/var/lib/mysql + oneapi: + container_name: oneapi + image: ghcr.io/songquanpeng/one-api:latest + ports: + - 3001:3000 + depends_on: + - mysql + networks: + - fastgpt + restart: always + environment: + # mysql 连接参数 + - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi + # 登录凭证加密密钥 + - SESSION_SECRET=oneapikey + # 内存缓存 + - MEMORY_CACHE_ENABLED=true + # 启动聚合更新,减少数据交互频率 + - BATCH_UPDATE_ENABLED=true + # 聚合更新时长 + - BATCH_UPDATE_INTERVAL=10 + # 初始化的 root 密钥(建议部署完后更改,否则容易泄露) + - INITIAL_ROOT_TOKEN=fastgpt + volumes: + - ./oneapi:/data +networks: + fastgpt: diff --git a/files/deploy/fastgpt/docker-compose.yml b/files/docker/docker-compose-pgvector.yml similarity index 99% rename from files/deploy/fastgpt/docker-compose.yml rename to files/docker/docker-compose-pgvector.yml index 1e80de5ee7..d7e9659465 100644 --- a/files/deploy/fastgpt/docker-compose.yml +++ b/files/docker/docker-compose-pgvector.yml @@ -5,6 +5,7 @@ version: '3.3' services: + # db pg: image: pgvector/pgvector:0.7.0-pg15 # docker hub # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 # 阿里云 @@ -67,6 +68,8 @@ services: # 等待docker-entrypoint.sh脚本执行的MongoDB服务进程 wait $$! + + # fastgpt sandbox: container_name: sandbox image: ghcr.io/labring/fastgpt-sandbox:v4.8.2 # git @@ -110,7 +113,8 @@ services: - SANDBOX_URL=http://sandbox:3000 volumes: - ./config.json:/app/data/config.json - - ./fastgpt/tmp:/app/tmp + + # oneapi mysql: image: mysql:8.0.36 container_name: mysql diff --git a/files/docker/docker-compose-zilliz.yml b/files/docker/docker-compose-zilliz.yml new file mode 100644 index 0000000000..c9272cfe5d --- /dev/null +++ b/files/docker/docker-compose-zilliz.yml @@ -0,0 +1,140 @@ +# 数据库的默认账号和密码仅首次运行时设置有效 +# 如果修改了账号密码,记得改数据库和项目连接参数,别只改一处~ +# 该配置文件只是给快速启动,测试使用。正式使用,记得务必修改账号密码,以及调整合适的知识库参数,共享内存等。 +# 如何无法访问 dockerhub 和 git,可以用阿里云(阿里云没有arm包) + +version: '3.3' +services: + mongo: + image: mongo:5.0.18 # dockerhub + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 # 阿里云 + # image: mongo:4.4.29 # cpu不支持AVX时候使用 + container_name: mongo + restart: always + ports: + - 27017:27017 + networks: + - fastgpt + command: mongod --keyFile /data/mongodb.key --replSet rs0 + environment: + - MONGO_INITDB_ROOT_USERNAME=myusername + - MONGO_INITDB_ROOT_PASSWORD=mypassword + volumes: + - ./mongo/data:/data/db + entrypoint: + - bash + - -c + - | + openssl rand -base64 128 > /data/mongodb.key + chmod 400 /data/mongodb.key + chown 999:999 /data/mongodb.key + echo 'const isInited = rs.status().ok === 1 + if(!isInited){ + rs.initiate({ + _id: "rs0", + members: [ + { _id: 0, host: "mongo:27017" } + ] + }) + }' > /data/initReplicaSet.js + # 启动MongoDB服务 + exec docker-entrypoint.sh "$$@" & + + # 等待MongoDB服务启动 + until mongo -u myusername -p mypassword --authenticationDatabase admin --eval "print('waited for connection')" > /dev/null 2>&1; do + echo "Waiting for MongoDB to start..." + sleep 2 + done + + # 执行初始化副本集的脚本 + mongo -u myusername -p mypassword --authenticationDatabase admin /data/initReplicaSet.js + + # 等待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.2 # git + # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.2 # 阿里云 + ports: + - 3000:3000 + networks: + - fastgpt + depends_on: + - mongo + - sandbox + restart: always + environment: + # root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。 + - DEFAULT_ROOT_PSW=1234 + # AI模型的API地址哦。务必加 /v1。这里默认填写了OneApi的访问地址。 + - OPENAI_BASE_URL=http://oneapi:3000/v1 + # AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改) + - CHAT_API_KEY=sk-fastgpt + # 数据库最大连接数 + - DB_MAX_LINK=30 + # 登录凭证密钥 + - TOKEN_KEY=any + # root的密钥,常用于升级时候的初始化请求 + - ROOT_KEY=root_key + # 文件阅读加密 + - FILE_TOKEN_KEY=filetoken + # MongoDB 连接参数. 用户名myusername,密码mypassword。 + - MONGODB_URI=mongodb://myusername:mypassword@mongo:27017/fastgpt?authSource=admin + # zilliz 连接参数 + - MILVUS_ADDRESS=zilliz_cloud_address + - MILVUS_TOKEN=zilliz_cloud_token + # sandbox 地址 + - SANDBOX_URL=http://sandbox:3000 + volumes: + - ./config.json:/app/data/config.json + + # oneapi + mysql: + image: mysql:8.0.36 + container_name: mysql + restart: always + ports: + - 3306:3306 + networks: + - fastgpt + command: --default-authentication-plugin=mysql_native_password + environment: + # 默认root密码,仅首次运行有效 + MYSQL_ROOT_PASSWORD: oneapimmysql + MYSQL_DATABASE: oneapi + volumes: + - ./mysql:/var/lib/mysql + oneapi: + container_name: oneapi + image: ghcr.io/songquanpeng/one-api:latest + ports: + - 3001:3000 + depends_on: + - mysql + networks: + - fastgpt + restart: always + environment: + # mysql 连接参数 + - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi + # 登录凭证加密密钥 + - SESSION_SECRET=oneapikey + # 内存缓存 + - MEMORY_CACHE_ENABLED=true + # 启动聚合更新,减少数据交互频率 + - BATCH_UPDATE_ENABLED=true + # 聚合更新时长 + - BATCH_UPDATE_INTERVAL=10 + # 初始化的 root 密钥(建议部署完后更改,否则容易泄露) + - INITIAL_ROOT_TOKEN=fastgpt + volumes: + - ./oneapi:/data +networks: + fastgpt: diff --git a/files/deploy/fastgpt/docker-compose/docker-compose b/files/docker/docker-compose/docker-compose similarity index 100% rename from files/deploy/fastgpt/docker-compose/docker-compose rename to files/docker/docker-compose/docker-compose diff --git a/files/deploy/fastgpt/docker-compose/init.sh b/files/docker/docker-compose/init.sh similarity index 100% rename from files/deploy/fastgpt/docker-compose/init.sh rename to files/docker/docker-compose/init.sh diff --git a/files/deploy/fastgpt/run.sh b/files/docker/run.sh similarity index 100% rename from files/deploy/fastgpt/run.sh rename to files/docker/run.sh diff --git a/package.json b/package.json index 4bce5cf08a..80c0b7338d 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,15 @@ "react-i18next": "13.5.0", "zhlint": "^0.7.1" }, + "resolutions": { + "react": "18.3.1", + "react-dom": "18.3.1", + "@types/react": "18.3.0", + "@types/react-dom": "18.3.0" + }, "lint-staged": { "./**/**/*.{ts,tsx,scss}": "npm run format-code", - "./**/**/*.md": "npm run format-doc" + "./docSite/**/**/*.md": "npm run format-doc" }, "engines": { "node": ">=18.0.0", diff --git a/packages/global/common/error/code/plugin.ts b/packages/global/common/error/code/plugin.ts index 430f0d2a7d..f2027b6aa2 100644 --- a/packages/global/common/error/code/plugin.ts +++ b/packages/global/common/error/code/plugin.ts @@ -1,6 +1,6 @@ import { ErrType } from '../errorCode'; -/* dataset: 507000 */ +/* dataset: 508000 */ export enum PluginErrEnum { unExist = 'pluginUnExist', unAuth = 'pluginUnAuth' @@ -19,7 +19,7 @@ export default errList.reduce((acc, cur, index) => { return { ...acc, [cur.statusText]: { - code: 507000 + index, + code: 508000 + index, statusText: cur.statusText, message: cur.message, data: null diff --git a/packages/global/common/error/code/system.ts b/packages/global/common/error/code/system.ts new file mode 100644 index 0000000000..7006703821 --- /dev/null +++ b/packages/global/common/error/code/system.ts @@ -0,0 +1,23 @@ +import { ErrType } from '../errorCode'; + +/* dataset: 509000 */ +export enum SystemErrEnum { + communityVersionNumLimit = 'communityVersionNumLimit' +} +const systemErr = [ + { + statusText: SystemErrEnum.communityVersionNumLimit, + message: '超出开源版数量限制,请升级商业版: https://fastgpt.in' + } +]; +export default systemErr.reduce((acc, cur, index) => { + return { + ...acc, + [cur.statusText]: { + code: 509000 + index, + statusText: cur.statusText, + message: cur.message, + data: null + } + }; +}, {} as ErrType<`${SystemErrEnum}`>); diff --git a/packages/global/common/error/errorCode.ts b/packages/global/common/error/errorCode.ts index 4fbc28ae5a..bb67851194 100644 --- a/packages/global/common/error/errorCode.ts +++ b/packages/global/common/error/errorCode.ts @@ -7,6 +7,7 @@ import outLinkErr from './code/outLink'; import teamErr from './code/team'; import userErr from './code/user'; import commonErr from './code/common'; +import SystemErrEnum from './code/system'; export const ERROR_CODE: { [key: number]: string } = { 400: '请求失败', @@ -98,5 +99,6 @@ export const ERROR_RESPONSE: Record< ...teamErr, ...userErr, ...pluginErr, - ...commonErr + ...commonErr, + ...SystemErrEnum }; diff --git a/packages/global/common/error/utils.ts b/packages/global/common/error/utils.ts index e306e54c61..73f0ec6467 100644 --- a/packages/global/common/error/utils.ts +++ b/packages/global/common/error/utils.ts @@ -1,7 +1,10 @@ import { replaceSensitiveText } from '../string/tools'; export const getErrText = (err: any, def = '') => { - const msg: string = typeof err === 'string' ? err : err?.message ?? def; + const msg: string = + typeof err === 'string' + ? err + : err?.response?.data?.message || err?.response?.message || err?.message || def; msg && console.log('error =>', msg); return replaceSensitiveText(msg); }; diff --git a/packages/global/common/vectorStore/constants.ts b/packages/global/common/vectorStore/constants.ts deleted file mode 100644 index 92db0e3585..0000000000 --- a/packages/global/common/vectorStore/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PgDatasetTableName = 'modeldata'; diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index f2dc6861a4..cd7b41f2d3 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -84,6 +84,9 @@ export type DispatchNodeResponseType = { toolCallTokens?: number; toolDetail?: ChatHistoryItemResType[]; toolStop?: boolean; + + // code + codeLog?: string; }; export type DispatchNodeResultType = { diff --git a/packages/global/core/workflow/template/system/ifElse/constant.ts b/packages/global/core/workflow/template/system/ifElse/constant.ts index 49c000ef0e..29e07679af 100644 --- a/packages/global/core/workflow/template/system/ifElse/constant.ts +++ b/packages/global/core/workflow/template/system/ifElse/constant.ts @@ -8,6 +8,8 @@ export enum VariableConditionEnum { startWith = 'startWith', endWith = 'endWith', + reg = 'reg', + greaterThan = 'greaterThan', greaterThanOrEqualTo = 'greaterThanOrEqualTo', lessThan = 'lessThan', @@ -31,6 +33,7 @@ export const stringConditionList = [ { label: '不为空', value: VariableConditionEnum.isNotEmpty }, { label: '等于', value: VariableConditionEnum.equalTo }, { label: '不等于', value: VariableConditionEnum.notEqual }, + { label: '正则', value: VariableConditionEnum.reg }, { label: '包含', value: VariableConditionEnum.include }, { label: '不包含', value: VariableConditionEnum.notInclude }, { label: '开始为', value: VariableConditionEnum.startWith }, diff --git a/packages/global/core/workflow/template/system/sandbox/index.ts b/packages/global/core/workflow/template/system/sandbox/index.ts index 2624d1c87e..7f0bd127fe 100644 --- a/packages/global/core/workflow/template/system/sandbox/index.ts +++ b/packages/global/core/workflow/template/system/sandbox/index.ts @@ -67,6 +67,20 @@ export const CodeNode: FlowNodeTemplateType = { description: '代码运行错误信息,成功时返回空', valueType: WorkflowIOValueTypeEnum.object, type: FlowNodeOutputTypeEnum.static + }, + { + id: 'qLUQfhG0ILRX', + type: FlowNodeOutputTypeEnum.dynamic, + key: 'result', + valueType: WorkflowIOValueTypeEnum.string, + label: 'result' + }, + { + id: 'gR0mkQpJ4Og8', + type: FlowNodeOutputTypeEnum.dynamic, + key: 'data2', + valueType: WorkflowIOValueTypeEnum.string, + label: 'data2' } ] }; diff --git a/packages/global/core/workflow/type/io.d.ts b/packages/global/core/workflow/type/io.d.ts index 2c1023d122..6df9267db5 100644 --- a/packages/global/core/workflow/type/io.d.ts +++ b/packages/global/core/workflow/type/io.d.ts @@ -43,7 +43,7 @@ export type FlowNodeInputItemType = { export type FlowNodeOutputItemType = { id: string; // output unique id(Does not follow the key change) - type: `${FlowNodeOutputTypeEnum}`; + type: FlowNodeOutputTypeEnum; key: `${NodeOutputKeyEnum}` | string; valueType?: WorkflowIOValueTypeEnum; value?: any; diff --git a/packages/service/common/mongo/sessionRun.ts b/packages/service/common/mongo/sessionRun.ts index 6bfa299929..7c8d73de11 100644 --- a/packages/service/common/mongo/sessionRun.ts +++ b/packages/service/common/mongo/sessionRun.ts @@ -2,18 +2,18 @@ import { connectionMongo, ClientSession } from './index'; export const mongoSessionRun = async (fn: (session: ClientSession) => Promise) => { const session = await connectionMongo.startSession(); - session.startTransaction(); try { + session.startTransaction(); const result = await fn(session); await session.commitTransaction(); - await session.endSession(); return result as T; } catch (error) { await session.abortTransaction(); - await session.endSession(); return Promise.reject(error); + } finally { + await session.endSession(); } }; diff --git a/packages/service/common/system/log.ts b/packages/service/common/system/log.ts index 7c7cbb72b0..9157bfa270 100644 --- a/packages/service/common/system/log.ts +++ b/packages/service/common/system/log.ts @@ -1,13 +1,35 @@ import dayjs from 'dayjs'; +import chalk from 'chalk'; + +enum LogLevelEnum { + debug = 'debug', + info = 'info', + warn = 'warn', + error = 'error' +} +const logMap = { + [LogLevelEnum.debug]: { + levelLog: chalk.green('[Debug]') + }, + [LogLevelEnum.info]: { + levelLog: chalk.blue('[Info]') + }, + [LogLevelEnum.warn]: { + levelLog: chalk.yellow('[Warn]') + }, + [LogLevelEnum.error]: { + levelLog: chalk.red('[Error]') + } +}; /* add logger */ export const addLog = { - log(level: 'info' | 'warn' | 'error', msg: string, obj: Record = {}) { + log(level: LogLevelEnum, msg: string, obj: Record = {}) { const stringifyObj = JSON.stringify(obj); const isEmpty = Object.keys(obj).length === 0; console.log( - `[${level.toLocaleUpperCase()}] ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${msg} ${ + `${logMap[level].levelLog} ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${msg} ${ level !== 'error' && !isEmpty ? stringifyObj : '' }` ); @@ -44,14 +66,17 @@ export const addLog = { }); } catch (error) {} }, + debug(msg: string, obj?: Record) { + this.log(LogLevelEnum.debug, msg, obj); + }, info(msg: string, obj?: Record) { - this.log('info', msg, obj); + this.log(LogLevelEnum.info, msg, obj); }, warn(msg: string, obj?: Record) { - this.log('warn', msg, obj); + this.log(LogLevelEnum.warn, msg, obj); }, error(msg: string, error?: any) { - this.log('error', msg, { + this.log(LogLevelEnum.error, msg, { message: error?.message || error, stack: error?.stack, ...(error?.config && { diff --git a/packages/service/common/vectorStore/constants.ts b/packages/service/common/vectorStore/constants.ts new file mode 100644 index 0000000000..5b9206eb5a --- /dev/null +++ b/packages/service/common/vectorStore/constants.ts @@ -0,0 +1,6 @@ +export const DatasetVectorDbName = 'fastgpt'; +export const DatasetVectorTableName = 'modeldata'; + +export const PG_ADDRESS = process.env.PG_URL; +export const MILVUS_ADDRESS = process.env.MILVUS_ADDRESS; +export const MILVUS_TOKEN = process.env.MILVUS_TOKEN; diff --git a/packages/service/common/vectorStore/controller.d.ts b/packages/service/common/vectorStore/controller.d.ts index ee823786f9..03272ea40e 100644 --- a/packages/service/common/vectorStore/controller.d.ts +++ b/packages/service/common/vectorStore/controller.d.ts @@ -1,3 +1,5 @@ +import type { EmbeddingRecallItemType } from './type'; + export type DeleteDatasetVectorProps = ( | { id: string } | { datasetIds: string[]; collectionIds?: string[] } @@ -5,12 +7,19 @@ export type DeleteDatasetVectorProps = ( ) & { teamId: string; }; +export type DelDatasetVectorCtrlProps = DeleteDatasetVectorProps & { + retry?: number; +}; export type InsertVectorProps = { teamId: string; datasetId: string; collectionId: string; }; +export type InsertVectorControllerProps = InsertVectorProps & { + vector: number[]; + retry?: number; +}; export type EmbeddingRecallProps = { teamId: string; @@ -18,3 +27,11 @@ export type EmbeddingRecallProps = { // similarity?: number; // efSearch?: number; }; +export type EmbeddingRecallCtrlProps = EmbeddingRecallProps & { + vector: number[]; + limit: number; + retry?: number; +}; +export type EmbeddingRecallResponse = { + results: EmbeddingRecallItemType[]; +}; diff --git a/packages/service/common/vectorStore/controller.ts b/packages/service/common/vectorStore/controller.ts index 6c682da0da..b2bd216e38 100644 --- a/packages/service/common/vectorStore/controller.ts +++ b/packages/service/common/vectorStore/controller.ts @@ -1,18 +1,25 @@ /* vector crud */ -import { PgVector } from './pg/class'; +import { PgVectorCtrl } from './pg/class'; import { getVectorsByText } from '../../core/ai/embedding'; import { InsertVectorProps } from './controller.d'; import { VectorModelItemType } from '@fastgpt/global/core/ai/model.d'; +import { MILVUS_ADDRESS, PG_ADDRESS } from './constants'; +import { MilvusCtrl } from './milvus/class'; const getVectorObj = () => { - return new PgVector(); + if (PG_ADDRESS) return new PgVectorCtrl(); + if (MILVUS_ADDRESS) return new MilvusCtrl(); + + return new PgVectorCtrl(); }; -export const initVectorStore = getVectorObj().init; -export const deleteDatasetDataVector = getVectorObj().delete; -export const recallFromVectorStore = getVectorObj().recall; -export const getVectorDataByTime = getVectorObj().getVectorDataByTime; -export const getVectorCountByTeamId = getVectorObj().getVectorCountByTeamId; +const Vector = getVectorObj(); + +export const initVectorStore = Vector.init; +export const deleteDatasetDataVector = Vector.delete; +export const recallFromVectorStore = Vector.embRecall; +export const getVectorDataByTime = Vector.getVectorDataByTime; +export const getVectorCountByTeamId = Vector.getVectorCountByTeamId; export const insertDatasetDataVector = async ({ model, @@ -27,9 +34,9 @@ export const insertDatasetDataVector = async ({ input: query, type: 'db' }); - const { insertId } = await getVectorObj().insert({ + const { insertId } = await Vector.insert({ ...props, - vectors + vector: vectors[0] }); return { diff --git a/packages/service/common/vectorStore/milvus/class.ts b/packages/service/common/vectorStore/milvus/class.ts new file mode 100644 index 0000000000..84edaab023 --- /dev/null +++ b/packages/service/common/vectorStore/milvus/class.ts @@ -0,0 +1,287 @@ +import { DataType, LoadState, MilvusClient } from '@zilliz/milvus2-sdk-node'; +import { + DatasetVectorDbName, + DatasetVectorTableName, + MILVUS_ADDRESS, + MILVUS_TOKEN +} from '../constants'; +import type { + DelDatasetVectorCtrlProps, + EmbeddingRecallCtrlProps, + EmbeddingRecallResponse, + InsertVectorControllerProps +} from '../controller.d'; +import { delay } from '@fastgpt/global/common/system/utils'; +import { addLog } from '../../../common/system/log'; + +export class MilvusCtrl { + constructor() {} + getClient = async () => { + if (!MILVUS_ADDRESS) { + return Promise.reject('MILVUS_ADDRESS is not set'); + } + if (global.milvusClient) return global.milvusClient; + + global.milvusClient = new MilvusClient({ + address: MILVUS_ADDRESS, + token: MILVUS_TOKEN + }); + + addLog.info(`Milvus connected`); + + return global.milvusClient; + }; + init = async () => { + const client = await this.getClient(); + + // init db(zilliz cloud will error) + try { + const { db_names } = await client.listDatabases(); + + if (!db_names.includes(DatasetVectorDbName)) { + await client.createDatabase({ + db_name: DatasetVectorDbName + }); + } + + await client.useDatabase({ + db_name: DatasetVectorDbName + }); + } catch (error) {} + + // init collection and index + const { value: hasCollection } = await client.hasCollection({ + collection_name: DatasetVectorTableName + }); + if (!hasCollection) { + const result = await client.createCollection({ + collection_name: DatasetVectorTableName, + description: 'Store dataset vector', + enableDynamicField: true, + fields: [ + { + name: 'id', + data_type: DataType.Int64, + is_primary_key: true, + autoID: true + }, + { + name: 'vector', + data_type: DataType.FloatVector, + dim: 1536 + }, + { name: 'teamId', data_type: DataType.VarChar, max_length: 64 }, + { name: 'datasetId', data_type: DataType.VarChar, max_length: 64 }, + { name: 'collectionId', data_type: DataType.VarChar, max_length: 64 }, + { + name: 'createTime', + data_type: DataType.Int64 + } + ], + index_params: [ + { + field_name: 'vector', + index_name: 'vector_HNSW', + index_type: 'HNSW', + metric_type: 'IP', + params: { efConstruction: 32, M: 64 } + }, + { + field_name: 'teamId', + index_type: 'Trie' + }, + { + field_name: 'datasetId', + index_type: 'Trie' + }, + { + field_name: 'collectionId', + index_type: 'Trie' + }, + { + field_name: 'createTime', + index_type: 'STL_SORT' + } + ] + }); + + addLog.info(`Create milvus collection: `, result); + } + + const { state: colLoadState } = await client.getLoadState({ + collection_name: DatasetVectorTableName + }); + + if ( + colLoadState === LoadState.LoadStateNotExist || + colLoadState === LoadState.LoadStateNotLoad + ) { + await client.loadCollectionSync({ + collection_name: DatasetVectorTableName + }); + addLog.info(`Milvus collection load success`); + } + }; + + insert = async (props: InsertVectorControllerProps): Promise<{ insertId: string }> => { + const client = await this.getClient(); + const { teamId, datasetId, collectionId, vector, retry = 3 } = props; + + try { + const result = await client.insert({ + collection_name: DatasetVectorTableName, + data: [ + { + vector, + teamId: String(teamId), + datasetId: String(datasetId), + collectionId: String(collectionId), + createTime: Date.now() + } + ] + }); + + const insertId = (() => { + if ('int_id' in result.IDs) { + return `${result.IDs.int_id.data?.[0]}`; + } + return `${result.IDs.str_id.data?.[0]}`; + })(); + + return { + insertId: insertId + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + await delay(500); + return this.insert({ + ...props, + retry: retry - 1 + }); + } + }; + delete = async (props: DelDatasetVectorCtrlProps): Promise => { + const { teamId, retry = 2 } = props; + const client = await this.getClient(); + + const teamIdWhere = `(teamId=="${String(teamId)}")`; + const where = await (() => { + if ('id' in props && props.id) return `(id==${props.id})`; + + if ('datasetIds' in props && props.datasetIds) { + const datasetIdWhere = `(datasetId in [${props.datasetIds + .map((id) => `"${String(id)}"`) + .join(',')}])`; + + if ('collectionIds' in props && props.collectionIds) { + return `${datasetIdWhere} and (collectionId in [${props.collectionIds + .map((id) => `"${String(id)}"`) + .join(',')}])`; + } + + return `${datasetIdWhere}`; + } + + if ('idList' in props && Array.isArray(props.idList)) { + if (props.idList.length === 0) return; + return `(id in [${props.idList.map((id) => String(id)).join(',')}])`; + } + return Promise.reject('deleteDatasetData: no where'); + })(); + + if (!where) return; + + const concatWhere = `${teamIdWhere} and ${where}`; + + try { + await client.delete({ + collection_name: DatasetVectorTableName, + filter: concatWhere + }); + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + await delay(500); + return this.delete({ + ...props, + retry: retry - 1 + }); + } + }; + embRecall = async (props: EmbeddingRecallCtrlProps): Promise => { + const client = await this.getClient(); + const { teamId, datasetIds, vector, limit, retry = 2 } = props; + + try { + const { results } = await client.search({ + collection_name: DatasetVectorTableName, + data: vector, + limit, + filter: `(teamId == "${teamId}") and (datasetId in [${datasetIds.map((id) => `"${String(id)}"`).join(',')}])`, + output_fields: ['collectionId'] + }); + + const rows = results as { + score: number; + id: string; + collectionId: string; + }[]; + + return { + results: rows.map((item) => ({ + id: String(item.id), + collectionId: item.collectionId, + score: item.score + })) + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + return this.embRecall({ + ...props, + retry: retry - 1 + }); + } + }; + + getVectorCountByTeamId = async (teamId: string) => { + const client = await this.getClient(); + + const result = await client.query({ + collection_name: DatasetVectorTableName, + output_fields: ['count(*)'], + filter: `teamId == "${String(teamId)}"` + }); + + const total = result.data?.[0]?.['count(*)'] as number; + + return total; + }; + getVectorDataByTime = async (start: Date, end: Date) => { + const client = await this.getClient(); + const startTimestamp = new Date(start).getTime(); + const endTimestamp = new Date(end).getTime(); + + const result = await client.query({ + collection_name: DatasetVectorTableName, + output_fields: ['id', 'teamId', 'datasetId'], + filter: `(createTime >= ${startTimestamp}) and (createTime <= ${endTimestamp})` + }); + + const rows = result.data as { + id: string; + teamId: string; + datasetId: string; + }[]; + + return rows.map((item) => ({ + id: String(item.id), + teamId: item.teamId, + datasetId: item.datasetId + })); + }; +} diff --git a/packages/service/common/vectorStore/pg/class.ts b/packages/service/common/vectorStore/pg/class.ts index eeab2b0eef..ec58aa1f36 100644 --- a/packages/service/common/vectorStore/pg/class.ts +++ b/packages/service/common/vectorStore/pg/class.ts @@ -1,18 +1,180 @@ +/* pg vector crud */ +import { DatasetVectorTableName } from '../constants'; +import { delay } from '@fastgpt/global/common/system/utils'; +import { PgClient, connectPg } from './index'; +import { PgSearchRawType } from '@fastgpt/global/core/dataset/api'; import { - initPg, - insertDatasetDataVector, - deleteDatasetDataVector, - embeddingRecall, - getVectorDataByTime, - getVectorCountByTeamId -} from './controller'; - -export class PgVector { + DelDatasetVectorCtrlProps, + EmbeddingRecallCtrlProps, + EmbeddingRecallResponse, + InsertVectorControllerProps +} from '../controller.d'; +import dayjs from 'dayjs'; + +export class PgVectorCtrl { constructor() {} - init = initPg; - insert = insertDatasetDataVector; - delete = deleteDatasetDataVector; - recall = embeddingRecall; - getVectorCountByTeamId = getVectorCountByTeamId; - getVectorDataByTime = getVectorDataByTime; + init = async () => { + try { + await connectPg(); + await PgClient.query(` + CREATE EXTENSION IF NOT EXISTS vector; + CREATE TABLE IF NOT EXISTS ${DatasetVectorTableName} ( + id BIGSERIAL PRIMARY KEY, + vector VECTOR(1536) NOT NULL, + team_id VARCHAR(50) NOT NULL, + dataset_id VARCHAR(50) NOT NULL, + collection_id VARCHAR(50) NOT NULL, + createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + `); + + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${DatasetVectorTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${DatasetVectorTableName} USING btree(team_id, dataset_id, collection_id);` + ); + await PgClient.query( + `CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${DatasetVectorTableName} USING btree(createtime);` + ); + + console.log('init pg successful'); + } catch (error) { + console.log('init pg error', error); + } + }; + insert = async (props: InsertVectorControllerProps): Promise<{ insertId: string }> => { + const { teamId, datasetId, collectionId, vector, retry = 3 } = props; + + try { + const { rows } = await PgClient.insert(DatasetVectorTableName, { + values: [ + [ + { key: 'vector', value: `[${vector}]` }, + { key: 'team_id', value: String(teamId) }, + { key: 'dataset_id', value: String(datasetId) }, + { key: 'collection_id', value: String(collectionId) } + ] + ] + }); + return { + insertId: rows[0].id + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + await delay(500); + return this.insert({ + ...props, + retry: retry - 1 + }); + } + }; + delete = async (props: DelDatasetVectorCtrlProps): Promise => { + const { teamId, retry = 2 } = props; + + const teamIdWhere = `team_id='${String(teamId)}' AND`; + + const where = await (() => { + if ('id' in props && props.id) return `${teamIdWhere} id=${props.id}`; + + if ('datasetIds' in props && props.datasetIds) { + const datasetIdWhere = `dataset_id IN (${props.datasetIds + .map((id) => `'${String(id)}'`) + .join(',')})`; + + if ('collectionIds' in props && props.collectionIds) { + return `${teamIdWhere} ${datasetIdWhere} AND collection_id IN (${props.collectionIds + .map((id) => `'${String(id)}'`) + .join(',')})`; + } + + return `${teamIdWhere} ${datasetIdWhere}`; + } + + if ('idList' in props && Array.isArray(props.idList)) { + if (props.idList.length === 0) return; + return `${teamIdWhere} id IN (${props.idList.map((id) => String(id)).join(',')})`; + } + return Promise.reject('deleteDatasetData: no where'); + })(); + + if (!where) return; + + try { + await PgClient.delete(DatasetVectorTableName, { + where: [where] + }); + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + await delay(500); + return this.delete({ + ...props, + retry: retry - 1 + }); + } + }; + embRecall = async (props: EmbeddingRecallCtrlProps): Promise => { + const { teamId, datasetIds, vector, limit, retry = 2 } = props; + + try { + const results: any = await PgClient.query( + ` + BEGIN; + SET LOCAL hnsw.ef_search = ${global.systemEnv?.pgHNSWEfSearch || 100}; + select id, collection_id, vector <#> '[${vector}]' AS score + from ${DatasetVectorTableName} + where team_id='${teamId}' + AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) + order by score limit ${limit}; + COMMIT;` + ); + + const rows = results?.[2]?.rows as PgSearchRawType[]; + + return { + results: rows.map((item) => ({ + id: String(item.id), + collectionId: item.collection_id, + score: item.score * -1 + })) + }; + } catch (error) { + if (retry <= 0) { + return Promise.reject(error); + } + return this.embRecall({ + ...props, + retry: retry - 1 + }); + } + }; + getVectorCountByTeamId = async (teamId: string) => { + const total = await PgClient.count(DatasetVectorTableName, { + where: [['team_id', String(teamId)]] + }); + + return total; + }; + getVectorDataByTime = async (start: Date, end: Date) => { + const { rows } = await PgClient.query<{ + id: string; + team_id: string; + dataset_id: string; + }>(`SELECT id, team_id, dataset_id + FROM ${DatasetVectorTableName} + WHERE createtime BETWEEN '${dayjs(start).format('YYYY-MM-DD HH:mm:ss')}' AND '${dayjs( + end + ).format('YYYY-MM-DD HH:mm:ss')}'; + `); + + return rows.map((item) => ({ + id: String(item.id), + teamId: item.team_id, + datasetId: item.dataset_id + })); + }; } diff --git a/packages/service/common/vectorStore/pg/controller.ts b/packages/service/common/vectorStore/pg/controller.ts deleted file mode 100644 index 0ce6203a96..0000000000 --- a/packages/service/common/vectorStore/pg/controller.ts +++ /dev/null @@ -1,195 +0,0 @@ -/* pg vector crud */ -import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants'; -import { delay } from '@fastgpt/global/common/system/utils'; -import { PgClient, connectPg } from './index'; -import { PgSearchRawType } from '@fastgpt/global/core/dataset/api'; -import { EmbeddingRecallItemType } from '../type'; -import { DeleteDatasetVectorProps, EmbeddingRecallProps, InsertVectorProps } from '../controller.d'; -import dayjs from 'dayjs'; - -export async function initPg() { - try { - await connectPg(); - await PgClient.query(` - CREATE EXTENSION IF NOT EXISTS vector; - CREATE TABLE IF NOT EXISTS ${PgDatasetTableName} ( - id BIGSERIAL PRIMARY KEY, - vector VECTOR(1536) NOT NULL, - team_id VARCHAR(50) NOT NULL, - dataset_id VARCHAR(50) NOT NULL, - collection_id VARCHAR(50) NOT NULL, - createtime TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - `); - - await PgClient.query( - `CREATE INDEX CONCURRENTLY IF NOT EXISTS vector_index ON ${PgDatasetTableName} USING hnsw (vector vector_ip_ops) WITH (m = 32, ef_construction = 128);` - ); - await PgClient.query( - `CREATE INDEX CONCURRENTLY IF NOT EXISTS team_dataset_collection_index ON ${PgDatasetTableName} USING btree(team_id, dataset_id, collection_id);` - ); - await PgClient.query( - `CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${PgDatasetTableName} USING btree(createtime);` - ); - - console.log('init pg successful'); - } catch (error) { - console.log('init pg error', error); - } -} - -export const insertDatasetDataVector = async ( - props: InsertVectorProps & { - vectors: number[][]; - retry?: number; - } -): Promise<{ insertId: string }> => { - const { teamId, datasetId, collectionId, vectors, retry = 3 } = props; - - try { - const { rows } = await PgClient.insert(PgDatasetTableName, { - values: [ - [ - { key: 'vector', value: `[${vectors[0]}]` }, - { key: 'team_id', value: String(teamId) }, - { key: 'dataset_id', value: String(datasetId) }, - { key: 'collection_id', value: String(collectionId) } - ] - ] - }); - return { - insertId: rows[0].id - }; - } catch (error) { - if (retry <= 0) { - return Promise.reject(error); - } - await delay(500); - return insertDatasetDataVector({ - ...props, - retry: retry - 1 - }); - } -}; - -export const deleteDatasetDataVector = async ( - props: DeleteDatasetVectorProps & { - retry?: number; - } -): Promise => { - const { teamId, retry = 2 } = props; - - const teamIdWhere = `team_id='${String(teamId)}' AND`; - - const where = await (() => { - if ('id' in props && props.id) return `${teamIdWhere} id=${props.id}`; - - if ('datasetIds' in props && props.datasetIds) { - const datasetIdWhere = `dataset_id IN (${props.datasetIds - .map((id) => `'${String(id)}'`) - .join(',')})`; - - if ('collectionIds' in props && props.collectionIds) { - return `${teamIdWhere} ${datasetIdWhere} AND collection_id IN (${props.collectionIds - .map((id) => `'${String(id)}'`) - .join(',')})`; - } - - return `${teamIdWhere} ${datasetIdWhere}`; - } - - if ('idList' in props && Array.isArray(props.idList)) { - if (props.idList.length === 0) return; - return `${teamIdWhere} id IN (${props.idList.map((id) => `'${String(id)}'`).join(',')})`; - } - return Promise.reject('deleteDatasetData: no where'); - })(); - - if (!where) return; - - try { - await PgClient.delete(PgDatasetTableName, { - where: [where] - }); - } catch (error) { - if (retry <= 0) { - return Promise.reject(error); - } - await delay(500); - return deleteDatasetDataVector({ - ...props, - retry: retry - 1 - }); - } -}; - -export const embeddingRecall = async ( - props: EmbeddingRecallProps & { - vectors: number[][]; - limit: number; - retry?: number; - } -): Promise<{ - results: EmbeddingRecallItemType[]; -}> => { - const { teamId, datasetIds, vectors, limit, retry = 2 } = props; - - try { - const results: any = await PgClient.query( - ` - BEGIN; - SET LOCAL hnsw.ef_search = ${global.systemEnv?.pgHNSWEfSearch || 100}; - select id, collection_id, vector <#> '[${vectors[0]}]' AS score - from ${PgDatasetTableName} - where team_id='${teamId}' - AND dataset_id IN (${datasetIds.map((id) => `'${String(id)}'`).join(',')}) - order by score limit ${limit}; - COMMIT;` - ); - - const rows = results?.[2]?.rows as PgSearchRawType[]; - - return { - results: rows.map((item) => ({ - id: item.id, - collectionId: item.collection_id, - score: item.score * -1 - })) - }; - } catch (error) { - console.log(error); - if (retry <= 0) { - return Promise.reject(error); - } - return embeddingRecall({ - ...props, - retry: retry - 1 - }); - } -}; - -export const getVectorCountByTeamId = async (teamId: string) => { - const total = await PgClient.count(PgDatasetTableName, { - where: [['team_id', String(teamId)]] - }); - - return total; -}; -export const getVectorDataByTime = async (start: Date, end: Date) => { - const { rows } = await PgClient.query<{ - id: string; - team_id: string; - dataset_id: string; - }>(`SELECT id, team_id, dataset_id - FROM ${PgDatasetTableName} - WHERE createtime BETWEEN '${dayjs(start).format('YYYY-MM-DD HH:mm:ss')}' AND '${dayjs(end).format( - 'YYYY-MM-DD HH:mm:ss' - )}'; - `); - - return rows.map((item) => ({ - id: String(item.id), - teamId: item.team_id, - datasetId: item.dataset_id - })); -}; diff --git a/packages/service/common/vectorStore/pg/index.ts b/packages/service/common/vectorStore/pg/index.ts index cbb8c975db..56b8f5ad4b 100644 --- a/packages/service/common/vectorStore/pg/index.ts +++ b/packages/service/common/vectorStore/pg/index.ts @@ -2,6 +2,7 @@ import { delay } from '@fastgpt/global/common/system/utils'; import { addLog } from '../../system/log'; import { Pool } from 'pg'; import type { QueryResultRow } from 'pg'; +import { PG_ADDRESS } from '../constants'; export const connectPg = async (): Promise => { if (global.pgClient) { @@ -9,7 +10,7 @@ export const connectPg = async (): Promise => { } global.pgClient = new Pool({ - connectionString: process.env.PG_URL, + connectionString: PG_ADDRESS, max: Number(process.env.DB_MAX_LINK || 20), min: 10, keepAlive: true, diff --git a/packages/service/common/vectorStore/type.d.ts b/packages/service/common/vectorStore/type.d.ts index 0f76246628..2ccc1f4a69 100644 --- a/packages/service/common/vectorStore/type.d.ts +++ b/packages/service/common/vectorStore/type.d.ts @@ -1,7 +1,9 @@ import type { Pool } from 'pg'; +import { MilvusClient } from '@zilliz/milvus2-sdk-node'; declare global { var pgClient: Pool | null; + var milvusClient: MilvusClient | null; } export type EmbeddingRecallItemType = { diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index 60ade42f48..6fafd33f54 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -85,7 +85,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { const { results } = await recallFromVectorStore({ teamId, datasetIds, - vectors, + vector: vectors[0], limit }); @@ -94,7 +94,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { { teamId, datasetId: { $in: datasetIds }, - collectionId: { $in: results.map((item) => item.collectionId) }, + collectionId: { $in: Array.from(new Set(results.map((item) => item.collectionId))) }, 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } }, 'datasetId collectionId q a chunkIndex indexes' @@ -118,26 +118,24 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { concatResults.sort((a, b) => b.score - a.score); - const formatResult = concatResults - .map((data, index) => { - if (!data.collectionId) { - console.log('Collection is not found', data); - } - - const result: SearchDataResponseItemType = { - id: String(data._id), - q: data.q, - a: data.a, - chunkIndex: data.chunkIndex, - datasetId: String(data.datasetId), - collectionId: String(data.collectionId?._id), - ...getCollectionSourceData(data.collectionId), - score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }] - }; + const formatResult = concatResults.map((data, index) => { + if (!data.collectionId) { + console.log('Collection is not found', data); + } - return result; - }) - .filter((item) => item !== null) as SearchDataResponseItemType[]; + const result: SearchDataResponseItemType = { + id: String(data._id), + q: data.q, + a: data.a, + chunkIndex: data.chunkIndex, + datasetId: String(data.datasetId), + collectionId: String(data.collectionId?._id), + ...getCollectionSourceData(data.collectionId), + score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }] + }; + + return result; + }); return { embeddingRecallResults: formatResult, diff --git a/packages/service/core/workflow/dispatch/chat/oneapi.ts b/packages/service/core/workflow/dispatch/chat/oneapi.ts index 10898e40cb..968061525c 100644 --- a/packages/service/core/workflow/dispatch/chat/oneapi.ts +++ b/packages/service/core/workflow/dispatch/chat/oneapi.ts @@ -45,6 +45,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti import { getHistories } from '../utils'; import { filterSearchResultsByMaxChars } from '../../utils'; import { getHistoryPreview } from '@fastgpt/global/core/chat/utils'; +import { addLog } from '../../../../common/system/log'; export type ChatProps = ModuleDispatchProps< AIChatNodeProps & { @@ -167,21 +168,19 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise { if (res && stream) { @@ -189,7 +188,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise; }) { const write = responseWriteController({ res, @@ -378,6 +380,7 @@ async function streamResponse({ } if (!answer) { + addLog.info(`LLM model response empty`, requestBody); return Promise.reject('core.chat.Chat API is error or undefined'); } diff --git a/packages/service/core/workflow/dispatch/code/run.ts b/packages/service/core/workflow/dispatch/code/run.ts index d8fd77b7b7..c427398bac 100644 --- a/packages/service/core/workflow/dispatch/code/run.ts +++ b/packages/service/core/workflow/dispatch/code/run.ts @@ -25,7 +25,10 @@ export const dispatchRunCode = async (props: RunCodeType): Promise; + data: { + codeReturn: Record; + log: string; + }; }>(sandBoxRequestUrl, { code, variables: customVariables @@ -33,10 +36,11 @@ export const dispatchRunCode = async (props: RunCodeType): Promise isEmpty(variableValue), - [VariableConditionEnum.isNotEmpty]: () => !isEmpty(variableValue), +function checkCondition(condition: VariableConditionEnum, inputValue: any, value: string) { + const operations: Record boolean> = { + [VariableConditionEnum.isEmpty]: () => isEmpty(inputValue), + [VariableConditionEnum.isNotEmpty]: () => !isEmpty(inputValue), - [VariableConditionEnum.equalTo]: () => String(variableValue) === value, - [VariableConditionEnum.notEqual]: () => String(variableValue) !== value, + [VariableConditionEnum.equalTo]: () => String(inputValue) === value, + [VariableConditionEnum.notEqual]: () => String(inputValue) !== value, // number - [VariableConditionEnum.greaterThan]: () => Number(variableValue) > Number(value), - [VariableConditionEnum.lessThan]: () => Number(variableValue) < Number(value), - [VariableConditionEnum.greaterThanOrEqualTo]: () => Number(variableValue) >= Number(value), - [VariableConditionEnum.lessThanOrEqualTo]: () => Number(variableValue) <= Number(value), + [VariableConditionEnum.greaterThan]: () => Number(inputValue) > Number(value), + [VariableConditionEnum.lessThan]: () => Number(inputValue) < Number(value), + [VariableConditionEnum.greaterThanOrEqualTo]: () => Number(inputValue) >= Number(value), + [VariableConditionEnum.lessThanOrEqualTo]: () => Number(inputValue) <= Number(value), // array or string - [VariableConditionEnum.include]: () => isInclude(variableValue, value), - [VariableConditionEnum.notInclude]: () => !isInclude(variableValue, value), + [VariableConditionEnum.include]: () => isInclude(inputValue, value), + [VariableConditionEnum.notInclude]: () => !isInclude(inputValue, value), // string - [VariableConditionEnum.startWith]: () => variableValue?.startsWith(value), - [VariableConditionEnum.endWith]: () => variableValue?.endsWith(value), + [VariableConditionEnum.startWith]: () => inputValue?.startsWith(value), + [VariableConditionEnum.endWith]: () => inputValue?.endsWith(value), + [VariableConditionEnum.reg]: () => { + if (typeof inputValue !== 'string' || !value) return false; + if (value.startsWith('/')) { + value = value.slice(1); + } + if (value.endsWith('/')) { + value = value.slice(0, -1); + } + + const reg = new RegExp(value, 'g'); + const result = reg.test(inputValue); + + return result; + }, // array - [VariableConditionEnum.lengthEqualTo]: () => variableValue?.length === Number(value), - [VariableConditionEnum.lengthNotEqualTo]: () => variableValue?.length !== Number(value), - [VariableConditionEnum.lengthGreaterThan]: () => variableValue?.length > Number(value), - [VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => - variableValue?.length >= Number(value), - [VariableConditionEnum.lengthLessThan]: () => variableValue?.length < Number(value), - [VariableConditionEnum.lengthLessThanOrEqualTo]: () => variableValue?.length <= Number(value) + [VariableConditionEnum.lengthEqualTo]: () => inputValue?.length === Number(value), + [VariableConditionEnum.lengthNotEqualTo]: () => inputValue?.length !== Number(value), + [VariableConditionEnum.lengthGreaterThan]: () => inputValue?.length > Number(value), + [VariableConditionEnum.lengthGreaterThanOrEqualTo]: () => inputValue?.length >= Number(value), + [VariableConditionEnum.lengthLessThan]: () => inputValue?.length < Number(value), + [VariableConditionEnum.lengthLessThanOrEqualTo]: () => inputValue?.length <= Number(value) }; - return (operations[condition] || (() => false))(); + return operations[condition]?.() ?? false; } function getResult( @@ -92,13 +106,13 @@ function getResult( const listResult = list.map((item) => { const { variable, condition: variableCondition, value } = item; - const variableValue = getReferenceVariableValue({ + const inputValue = getReferenceVariableValue({ value: variable, variables, nodes: runtimeNodes }); - return checkCondition(variableCondition as VariableConditionEnum, variableValue, value || ''); + return checkCondition(variableCondition as VariableConditionEnum, inputValue, value || ''); }); return condition === 'AND' ? listResult.every(Boolean) : listResult.some(Boolean); diff --git a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts index 8d2163c082..a5bf188331 100644 --- a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts +++ b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts @@ -16,7 +16,7 @@ type Props = ModuleDispatchProps<{ type Response = DispatchNodeResultType<{}>; export const dispatchUpdateVariable = async (props: Props): Promise => { - const { res, detail, params, variables, runtimeNodes } = props; + const { res, detail, stream, params, variables, runtimeNodes } = props; const { updateList } = params; updateList.forEach((item) => { @@ -54,7 +54,7 @@ export const dispatchUpdateVariable = async (props: Props): Promise => } }); - if (detail) { + if (detail && stream) { responseWrite({ res, event: SseResponseEventEnum.updateVariables, diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index ca0e86f0ea..c81f461723 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -1,3 +1,5 @@ +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { replaceSensitiveText } from '@fastgpt/global/common/string/tools'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import { WorkflowIOValueTypeEnum, @@ -89,11 +91,10 @@ export const removeSystemVariable = (variables: Record) => { export const formatHttpError = (error: any) => { return { - message: error?.message, + message: getErrText(error), + data: error?.response?.data, name: error?.name, method: error?.config?.method, - baseURL: error?.config?.baseURL, - url: error?.config?.url, code: error?.code, status: error?.status }; diff --git a/packages/service/package.json b/packages/service/package.json index 3d7a33bdc0..40316dd50a 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -5,7 +5,9 @@ "@fastgpt/global": "workspace:*", "@node-rs/jieba": "1.10.0", "@xmldom/xmldom": "^0.8.10", + "@zilliz/milvus2-sdk-node": "2.4.2", "axios": "^1.5.1", + "chalk": "^5.3.0", "cheerio": "1.0.0-rc.12", "cookie": "^0.5.0", "date-fns": "2.30.0", @@ -22,7 +24,7 @@ "mongoose": "^7.0.2", "multer": "1.4.5-lts.1", "next": "14.2.3", - "nextjs-cors": "^2.1.2", + "nextjs-cors": "^2.2.0", "node-cron": "^3.0.3", "node-xlsx": "^0.23.0", "papaparse": "5.4.1", diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index fd948021d0..c64884e99f 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -5,6 +5,7 @@ import { MongoPlugin } from '../../core/plugin/schema'; import { MongoDataset } from '../../core/dataset/schema'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; +import { SystemErrEnum } from '@fastgpt/global/common/error/code/system'; export const checkDatasetLimit = async ({ teamId, @@ -13,14 +14,14 @@ export const checkDatasetLimit = async ({ teamId: string; insertLen?: number; }) => { - const [{ standardConstants, totalPoints, usedPoints, datasetMaxSize }, usedSize] = - await Promise.all([getTeamPlanStatus({ teamId }), getVectorCountByTeamId(teamId)]); + const { standardConstants, totalPoints, usedPoints, datasetMaxSize, usedDatasetSize } = + await getTeamPlanStatus({ teamId }); if (!standardConstants) return; - if (usedSize + insertLen >= datasetMaxSize) { + if (usedDatasetSize + insertLen >= datasetMaxSize) { return Promise.reject( - `您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。` + `您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedDatasetSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。` ); } @@ -59,6 +60,9 @@ export const checkTeamDatasetLimit = async (teamId: string) => { if (standardConstants && datasetCount >= standardConstants.maxDatasetAmount) { return Promise.reject(TeamErrEnum.datasetAmountNotEnough); } + if (!global.feConfigs.isPlus && datasetCount >= 30) { + return Promise.reject(SystemErrEnum.communityVersionNumLimit); + } }; export const checkTeamAppLimit = async (teamId: string) => { const [{ standardConstants }, appCount] = await Promise.all([ diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx index 4b19991f32..94f8c206c7 100644 --- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx +++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx @@ -56,11 +56,18 @@ const MultipleRowSelect = ({ }} onClick={() => { const newValue = [...cloneValue]; - newValue[index] = item.value; - setCloneValue(newValue); - if (!hasChildren) { + + if (item.value === selectedValue) { + newValue[index] = undefined; + setCloneValue(newValue); onSelect(newValue); - onClose(); + } else { + newValue[index] = item.value; + setCloneValue(newValue); + if (!hasChildren) { + onSelect(newValue); + onClose(); + } } }} {...(item.value === selectedValue diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index fd53bd1ef8..8748f64d28 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -20,6 +20,8 @@ const { definePartsStyle: numInputPart, defineMultiStyleConfig: numInputMultiSty const { definePartsStyle: checkBoxPart, defineMultiStyleConfig: checkBoxMultiStyle } = createMultiStyleConfigHelpers(checkboxAnatomy.keys); +const shadowLight = '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'; + // 按键 const Button = defineStyleConfig({ baseStyle: { @@ -289,7 +291,7 @@ const Input: ComponentStyleConfig = { borderColor: 'borderColor.low', _focus: { borderColor: 'primary.500', - boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)', + boxShadow: shadowLight, bg: 'white' }, _disabled: { @@ -328,7 +330,7 @@ const NumberInput = numInputMultiStyle({ borderColor: 'myGray.200', _focus: { borderColor: 'primary.500 !important', - boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15) !important', + boxShadow: `${shadowLight} !important`, bg: 'transparent' }, _disabled: { @@ -362,7 +364,7 @@ const Textarea: ComponentStyleConfig = { }, _focus: { borderColor: 'primary.500', - boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)', + boxShadow: shadowLight, bg: 'white' } } @@ -396,7 +398,7 @@ const Select = selectMultiStyle({ field: { borderColor: 'myGray.200', _focusWithin: { - boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)', + boxShadow: shadowLight, borderColor: 'primary.500' } } @@ -408,6 +410,21 @@ const Checkbox = checkBoxMultiStyle({ baseStyle: checkBoxPart({ label: { fontFamily: 'mono' // change the font family of the label + }, + control: { + bg: 'none', + _checked: { + bg: 'primary.50', + borderColor: 'primary.600', + color: 'primary.600', + boxShadow: `${shadowLight} !important`, + _hover: { + bg: 'primary.50' + } + }, + _hover: { + borderColor: 'primary.400' + } } }) }); @@ -437,6 +454,11 @@ export const theme = extendTheme({ }, a: { color: 'primary.600' + }, + '*': { + _focusVisible: { + boxShadow: 'none' + } } } }, @@ -491,6 +513,25 @@ export const theme = extendTheme({ 800: '#2450B5', 900: '#1D4091' }, + blue: { + 1: 'rgba(51, 112, 255, 0.1)', + '015': 'rgba(51, 112, 255, 0.15)', + 3: 'rgba(51, 112, 255, 0.3)', + 5: 'rgba(51, 112, 255, 0.5)', + 7: 'rgba(51, 112, 255, 0.7)', + 9: 'rgba(51, 112, 255, 0.9)', + + 50: '#F0F4FF', + 100: '#E1EAFF', + 200: '#C5D7FF', + 300: '#94B5FF', + 400: '#5E8FFF', + 500: '#487FFF', + 600: '#3370FF', + 700: '#2B5FD9', + 800: '#2450B5', + 900: '#1D4091' + }, red: { 1: 'rgba(217,45,32,0.1)', 3: 'rgba(217,45,32,0.3)', @@ -579,7 +620,8 @@ export const theme = extendTheme({ 5: '0px 0px 1px 0px rgba(19, 51, 107, 0.15), 0px 20px 24px -8px rgba(19, 51, 107, 0.15)', 6: '0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 24px 48px -12px rgba(19, 51, 107, 0.20)', 7: '0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 32px 64px -12px rgba(19, 51, 107, 0.20)', - focus: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' + focus: shadowLight, + outline: 'none' }, breakpoints: { sm: '900px', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4de1abe47d..4c8b394a97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,12 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + react: 18.3.1 + react-dom: 18.3.1 + '@types/react': 18.3.0 + '@types/react-dom': 18.3.0 + importers: .: @@ -102,9 +108,15 @@ importers: '@xmldom/xmldom': specifier: ^0.8.10 version: 0.8.10 + '@zilliz/milvus2-sdk-node': + specifier: 2.4.2 + version: 2.4.2 axios: specifier: ^1.5.1 version: 1.5.1 + chalk: + specifier: ^5.3.0 + version: 5.3.0 cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 @@ -154,8 +166,8 @@ importers: specifier: 14.2.3 version: 14.2.3(@babel/core@7.24.4)(react-dom@18.3.1)(react@18.3.1)(sass@1.58.3) nextjs-cors: - specifier: ^2.1.2 - version: 2.1.2(next@14.2.3) + specifier: ^2.2.0 + version: 2.2.0(next@14.2.3) node-cron: specifier: ^3.0.3 version: 3.0.3 @@ -3162,6 +3174,11 @@ packages: dev: true optional: true + /@colors/colors@1.6.0: + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + dev: false + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -3169,6 +3186,14 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@dabh/diagnostics@2.0.3: + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + dev: false + /@emnapi/core@1.1.1: resolution: {integrity: sha512-eu4KjHfXg3I+UUR7vSuwZXpRo4c8h4Rtb5Lu2F7Z4JqJFl/eidquONEBiRs6viXKpWBC3BaJBy68xGJ2j56idw==} requiresBuild: true @@ -3669,6 +3694,25 @@ packages: engines: {node: '>=16.15'} dev: false + /@grpc/grpc-js@1.10.8: + resolution: {integrity: sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==} + engines: {node: '>=12.10.0'} + dependencies: + '@grpc/proto-loader': 0.7.13 + '@js-sdsl/ordered-map': 4.4.2 + dev: false + + /@grpc/proto-loader@0.7.13: + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} + engines: {node: '>=6'} + hasBin: true + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.3.0 + yargs: 17.7.2 + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -3968,6 +4012,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@js-sdsl/ordered-map@4.4.2: + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + dev: false + /@jsdevtools/ono@7.1.3: resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} dev: false @@ -4797,6 +4845,10 @@ packages: transitivePeerDependencies: - encoding + /@petamoriken/float16@3.8.7: + resolution: {integrity: sha512-/Ri4xDDpe12NT6Ex/DRgHzLlobiQXEW/hmG08w1wj/YU7hLemk97c+zHQFp0iZQ9r7YqgLEXZR2sls4HxBf9NA==} + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -4807,6 +4859,49 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@reactflow/background@11.2.4(immer@9.0.19)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-SYQbCRCU0GuxT/40Tm7ZK+l5wByGnNJSLtZhbL9C/Hl7JhsJXV3UGXr0vrlhVZUBEtkWA7XhZM/5S9XEA5XSFA==} peerDependencies: @@ -5686,6 +5781,10 @@ packages: '@types/superagent': 8.1.7 dev: true + /@types/triple-beam@1.3.5: + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + dev: false + /@types/tunnel@0.0.4: resolution: {integrity: sha512-bQgDBL5XiqrrPUaZd9bZ2esOXcU4GTmgg0n6LHDqoMJezO3VFRZsW8qN6Gp64/LAmjtzNU3iAHBfV3Z2ht5DSg==} dependencies: @@ -6034,6 +6133,19 @@ packages: '@zag-js/dom-query': 0.16.0 dev: false + /@zilliz/milvus2-sdk-node@2.4.2: + resolution: {integrity: sha512-fkPu7XXzfUvHoCnSPVOjqQpWuSnnn9x2NMmmCcIOyRzMeXIsrz4Mf/+M7LUzmT8J9F0Khx65B0rJgCu27YzWQw==} + dependencies: + '@grpc/grpc-js': 1.10.8 + '@grpc/proto-loader': 0.7.13 + '@petamoriken/float16': 3.8.7 + dayjs: 1.11.7 + generic-pool: 3.9.0 + lru-cache: 9.1.2 + protobufjs: 7.3.0 + winston: 3.13.0 + dev: false + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} requiresBuild: true @@ -6433,6 +6545,10 @@ packages: engines: {node: '>=8'} dev: true + /async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + dev: false + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -6951,6 +7067,11 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -7162,7 +7283,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} @@ -7199,6 +7319,13 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true @@ -7210,10 +7337,24 @@ packages: resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==} dev: false + /color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + dev: false + /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true + /colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + dev: false + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -8258,6 +8399,10 @@ packages: engines: {node: '>= 4'} dev: false + /enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + dev: false + /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -9308,6 +9453,10 @@ packages: pend: 1.2.0 dev: false + /fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + dev: false + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -9409,6 +9558,10 @@ packages: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} dev: true + /fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + dev: false + /focus-lock@1.3.4: resolution: {integrity: sha512-Gv0N3mvej3pD+HWkNryrF8sExzEHqhQ6OSFxD4DPxm9n5HGCaHme98ZMBZroNEAJcsdtHxk+skvThGKyUeoEGA==} engines: {node: '>=10'} @@ -9611,6 +9764,11 @@ packages: dev: false optional: true + /generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -9618,7 +9776,6 @@ packages: /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-func-name@2.0.2: resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} @@ -10235,6 +10392,10 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -10422,7 +10583,6 @@ packages: /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} @@ -11197,6 +11357,10 @@ packages: engines: {node: '>=6'} dev: false + /kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + dev: false + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -11345,6 +11509,10 @@ packages: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: false + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: false + /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true @@ -11417,6 +11585,22 @@ packages: wrap-ansi: 6.2.0 dev: true + /logform@2.6.0: + resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + dev: false + + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false + /longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} dev: false @@ -11464,6 +11648,11 @@ packages: yallist: 4.0.0 dev: false + /lru-cache@9.1.2: + resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} + engines: {node: 14 || >=16.14} + dev: false + /luxon@3.4.4: resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} engines: {node: '>=12'} @@ -12444,10 +12633,10 @@ packages: - '@babel/core' - babel-plugin-macros - /nextjs-cors@2.1.2(next@14.2.3): - resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==} + /nextjs-cors@2.2.0(next@14.2.3): + resolution: {integrity: sha512-FZu/A+L59J4POJNqwXYyCPDvsLDeu5HjSBvytzS6lsrJeDz5cmnH45zV+VoNic0hjaeER9xGaiIjZIWzEHnxQg==} peerDependencies: - next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 + next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 dependencies: cors: 2.8.5 next: 14.2.3(@babel/core@7.24.4)(react-dom@18.3.1)(react@18.3.1)(sass@1.58.3) @@ -12728,6 +12917,12 @@ packages: dependencies: wrappy: 1.0.2 + /one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + dependencies: + fn.name: 1.1.0 + dev: false + /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -13331,6 +13526,25 @@ packages: resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==} dev: false + /protobufjs@7.3.0: + resolution: {integrity: sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.8.5 + long: 5.2.3 + dev: false + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -13943,7 +14157,6 @@ packages: /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -14331,6 +14544,12 @@ packages: simple-concat: 1.0.1 dev: false + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true @@ -14473,6 +14692,10 @@ packages: deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' dev: true + /stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + dev: false + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -14878,6 +15101,10 @@ packages: minimatch: 3.1.2 dev: true + /text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + dev: false + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -15001,6 +15228,11 @@ packages: deprecated: Use String.prototype.trim() instead dev: true + /triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + dev: false + /trough@1.0.5: resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==} dev: true @@ -15951,6 +16183,32 @@ packages: execa: 4.1.0 dev: true + /winston-transport@4.7.0: + resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} + engines: {node: '>= 12.0.0'} + dependencies: + logform: 2.6.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + dev: false + + /winston@3.13.0: + resolution: {integrity: sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.0 + dev: false + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -15999,7 +16257,6 @@ packages: /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - dev: true /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -16021,7 +16278,6 @@ packages: /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - dev: true /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} @@ -16034,7 +16290,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true /yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} diff --git a/projects/app/.env.template b/projects/app/.env.template index d2bc081774..13aa25d714 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -18,8 +18,14 @@ OPENAI_BASE_URL=https://api.openai.com/v1 CHAT_API_KEY=sk-xxxx # mongo 数据库连接参数,本地开发时,mongo可能需要增加 directConnection=true 参数,才能连接上。 MONGODB_URI=mongodb://username:password@0.0.0.0:27017/fastgpt?authSource=admin -# PG 数据库连接参数 + +# 向量库优先级: pg > milvus +# PG 向量库连接参数 PG_URL=postgresql://username:password@host:port/postgres +# mivlus 向量库连接参数 +MILVUS_ADDRESS=https://in03-78bd7f60e6e2a7c.api.gcp-us-west1.zillizcloud.com +MILVUS_TOKEN=133964348b00b4b4e4b51bef680a61350950385c8c64a3ec16b1ab92d3c67dcc4e0370fb9dd15791bcd6dadaf765e98a98735d0d + # code sandbox url SANDBOX_URL=http://localhost:3001 # 商业版地址 diff --git a/projects/app/Dockerfile b/projects/app/Dockerfile index 4e09ea7b43..22f80536b3 100644 --- a/projects/app/Dockerfile +++ b/projects/app/Dockerfile @@ -62,9 +62,11 @@ COPY --from=builder --chown=nextjs:nodejs /app/projects/app/.next/server/chunks # copy 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 standload packages COPY --from=mainDeps /app/node_modules/tiktoken ./node_modules/tiktoken RUN rm -rf ./node_modules/tiktoken/encoders +COPY --from=mainDeps /app/node_modules/@zilliz/milvus2-sdk-node ./node_modules/@zilliz/milvus2-sdk-node + # copy package.json to version file COPY --from=builder /app/projects/app/package.json ./package.json diff --git a/projects/app/i18n/en/workflow.json b/projects/app/i18n/en/workflow.json index 8e61365e23..700d91742a 100644 --- a/projects/app/i18n/en/workflow.json +++ b/projects/app/i18n/en/workflow.json @@ -5,7 +5,12 @@ "Reset template": "Reset template", "Reset template confirm": "Are you sure to restore the code template? Be careful to save the current code." }, + "ifelse": { + "Input value": "Input", + "Select value": "Select" + }, "response": { + "Code log": "Log", "Custom inputs": "Custom inputs", "Custom outputs": "Custom outputs", "Error": "Error" diff --git a/projects/app/i18n/zh/common.json b/projects/app/i18n/zh/common.json index c307f9296b..efffb87179 100644 --- a/projects/app/i18n/zh/common.json +++ b/projects/app/i18n/zh/common.json @@ -184,7 +184,7 @@ "not support": "您的浏览器不支持语音输入" }, "system": { - "Commercial version function": "商业版特有功能", + "Commercial version function": "请升级商业版后使用该功能: https://fastgpt.in", "Help Chatbot": "机器人助手", "Use Helper": "使用帮助" }, diff --git a/projects/app/i18n/zh/workflow.json b/projects/app/i18n/zh/workflow.json index f3636f5bc7..2599903163 100644 --- a/projects/app/i18n/zh/workflow.json +++ b/projects/app/i18n/zh/workflow.json @@ -5,7 +5,12 @@ "Reset template": "还原模板", "Reset template confirm": "确认还原代码模板?请注意保存当前代码。" }, + "ifelse": { + "Input value": "输入值", + "Select value": "选择值" + }, "response": { + "Code log": "Log日志", "Custom inputs": "自定义输入", "Custom outputs": "自定义输出", "Error": "错误信息" diff --git a/projects/app/next.config.js b/projects/app/next.config.js index 135f3fb54b..26a34c8734 100644 --- a/projects/app/next.config.js +++ b/projects/app/next.config.js @@ -2,10 +2,12 @@ const { i18n } = require('./next-i18next.config'); const path = require('path'); +const isDev = process.env.NODE_ENV === 'development'; + const nextConfig = { i18n, output: 'standalone', - reactStrictMode: process.env.NODE_ENV === 'development' ? false : true, + reactStrictMode: isDev ? false : true, compress: true, webpack(config, { isServer, nextRuntime }) { Object.assign(config.resolve.alias, { @@ -41,11 +43,9 @@ const nextConfig = { } if (isServer) { - config.externals.push('worker_threads'); + // config.externals.push('@zilliz/milvus2-sdk-node'); if (nextRuntime === 'nodejs') { - // config.output.globalObject = 'self'; - const oldEntry = config.entry; config = { ...config, @@ -89,7 +89,12 @@ const nextConfig = { transpilePackages: ['@fastgpt/*', 'ahooks'], experimental: { // 优化 Server Components 的构建和运行,避免不必要的客户端打包。 - serverComponentsExternalPackages: ['mongoose', 'pg', '@node-rs/jieba'], + serverComponentsExternalPackages: [ + 'mongoose', + 'pg', + '@node-rs/jieba', + '@zilliz/milvus2-sdk-node' + ], outputFileTracingRoot: path.join(__dirname, '../../') } }; diff --git a/projects/app/package.json b/projects/app/package.json index ab51806395..03d75be2cb 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.8.1", + "version": "4.8.3", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/src/components/ChatBox/Input/ChatInput.tsx b/projects/app/src/components/ChatBox/Input/ChatInput.tsx index 202e7072da..35166c147c 100644 --- a/projects/app/src/components/ChatBox/Input/ChatInput.tsx +++ b/projects/app/src/components/ChatBox/Input/ChatInput.tsx @@ -51,16 +51,8 @@ const ChatInput = ({ name: 'files' }); - const { - shareId, - outLinkUid, - teamId, - teamToken, - isChatting, - whisperConfig, - autoTTSResponse, - chatInputGuide - } = useContextSelector(ChatBoxContext, (v) => v); + const { isChatting, whisperConfig, autoTTSResponse, chatInputGuide, outLinkAuthData } = + useContextSelector(ChatBoxContext, (v) => v); const { isPc, whisperModel } = useSystemStore(); const canvasRef = useRef(null); const { t } = useTranslation(); @@ -87,10 +79,7 @@ const ChatInput = ({ maxSize: 1024 * 1024 * 16, // 7 day expired. expiredTime: addDays(new Date(), 7), - shareId, - outLinkUid, - teamId, - teamToken + ...outLinkAuthData }); updateFile(fileIndex, { ...file, @@ -175,7 +164,7 @@ const ChatInput = ({ speakingTimeString, renderAudioGraph, stream - } = useSpeech({ appId, shareId, outLinkUid, teamId, teamToken }); + } = useSpeech({ appId, ...outLinkAuthData }); useEffect(() => { if (!stream) { return; diff --git a/projects/app/src/components/ChatBox/Input/InputGuideBox.tsx b/projects/app/src/components/ChatBox/Input/InputGuideBox.tsx index b1108211ff..f5f0bf6a56 100644 --- a/projects/app/src/components/ChatBox/Input/InputGuideBox.tsx +++ b/projects/app/src/components/ChatBox/Input/InputGuideBox.tsx @@ -24,6 +24,7 @@ export default function InputGuideBox({ const { t } = useTranslation(); const { chatT } = useI18n(); const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide); + const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData); const { data = [] } = useRequest2( async () => { @@ -31,7 +32,8 @@ export default function InputGuideBox({ return await queryChatInputGuideList( { appId, - searchKey: text.slice(0, 50) + searchKey: text.slice(0, 50), + ...outLinkAuthData }, chatInputGuide.customUrl ? chatInputGuide.customUrl : undefined ); diff --git a/projects/app/src/components/ChatBox/Provider.tsx b/projects/app/src/components/ChatBox/Provider.tsx index 970c0443f8..12683a9f5e 100644 --- a/projects/app/src/components/ChatBox/Provider.tsx +++ b/projects/app/src/components/ChatBox/Provider.tsx @@ -45,6 +45,7 @@ type useChatStoreType = OutLinkChatAuthProps & { setChatHistories: React.Dispatch>; isChatting: boolean; chatInputGuide: ChatInputGuideConfigType; + outLinkAuthData: OutLinkChatAuthProps; }; export const ChatBoxContext = createContext({ welcomeText: '', @@ -98,7 +99,8 @@ export const ChatBoxContext = createContext({ chatInputGuide: { open: false, customUrl: '' - } + }, + outLinkAuthData: {} }); export type ChatProviderProps = OutLinkChatAuthProps & { @@ -128,6 +130,16 @@ const Provider = ({ chatInputGuide = defaultChatInputGuideConfig } = useMemo(() => chatConfig, [chatConfig]); + const outLinkAuthData = useMemo( + () => ({ + shareId, + outLinkUid, + teamId, + teamToken + }), + [shareId, outLinkUid, teamId, teamToken] + ); + // segment audio const [audioPlayingChatId, setAudioPlayingChatId] = useState(); const { @@ -141,10 +153,7 @@ const Provider = ({ splitText2Audio } = useAudioPlay({ ttsConfig, - shareId, - outLinkUid, - teamId, - teamToken + ...outLinkAuthData }); const autoTTSResponse = @@ -181,7 +190,8 @@ const Provider = ({ chatHistories, setChatHistories, isChatting, - chatInputGuide + chatInputGuide, + outLinkAuthData }; return {children}; diff --git a/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx index 5e272723da..dbb7dcd061 100644 --- a/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx @@ -321,6 +321,7 @@ export const ResponseBox = React.memo(function ResponseBox({ {/* code */} + ); diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index 3959fd596b..98be4e2814 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -13,19 +13,10 @@ import Auth from './auth'; const Navbar = dynamic(() => import('./navbar')); const NavbarPhone = dynamic(() => import('./navbarPhone')); -const UpdateInviteModal = dynamic( - () => import('@/components/support/user/team/UpdateInviteModal'), - { ssr: false } -); -const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'), { - ssr: false -}); -const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'), { - ssr: false -}); -const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'), { - ssr: false -}); +const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal')); +const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal')); +const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal')); +const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform')); const pcUnShowLayoutRoute: Record = { '/': true, @@ -126,7 +117,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { {feConfigs?.isPlus && ( <> {!!userInfo && } - {isNotSufficientModal && !isHideNavbar && } + {isNotSufficientModal && } {!!userInfo && } {!!userInfo && importantInforms.length > 0 && ( diff --git a/projects/app/src/components/common/NextHead/index.tsx b/projects/app/src/components/common/NextHead/index.tsx index a3f529bc0f..8236071540 100644 --- a/projects/app/src/components/common/NextHead/index.tsx +++ b/projects/app/src/components/common/NextHead/index.tsx @@ -5,6 +5,10 @@ const NextHead = ({ title, icon, desc }: { title?: string; icon?: string; desc?: return ( {title} + {desc && } {icon && } diff --git a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx b/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx index 594a6c0d12..86297caba9 100644 --- a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx +++ b/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx @@ -145,10 +145,27 @@ export const useDebug = () => { node.nodeId === runtimeNode.nodeId ? { ...runtimeNode, - inputs: runtimeNode.inputs.map((input) => ({ - ...input, - value: data[input.key] ?? input.value - })) + inputs: runtimeNode.inputs.map((input) => { + let parseValue = (() => { + try { + if ( + input.valueType === WorkflowIOValueTypeEnum.string || + input.valueType === WorkflowIOValueTypeEnum.number || + input.valueType === WorkflowIOValueTypeEnum.boolean + ) + return data[input.key]; + + return JSON.parse(data[input.key]); + } catch (e) { + return data[input.key]; + } + })(); + + return { + ...input, + value: parseValue ?? input.value + }; + }) } : node ), @@ -168,7 +185,7 @@ export const useDebug = () => { {renderInputs.map((input) => { const required = input.required || false; - console.log(input.valueType); + const RenderInput = (() => { if (input.valueType === WorkflowIOValueTypeEnum.string) { return ( @@ -206,19 +223,23 @@ export const useDebug = () => { ); } - if (typeof input.value === 'string') { - return ( - { - setValue(input.key, e); - }} - /> - ); + + let value = getValues(input.key) || ''; + if (typeof value !== 'string') { + value = JSON.stringify(value, null, 2); } + + return ( + { + setValue(input.key, e); + }} + /> + ); })(); return !!RenderInput ? ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx index 591071e25d..ad602f86fc 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx @@ -31,6 +31,7 @@ import { Position, useReactFlow } from 'reactflow'; import { getRefData } from '@/web/core/workflow/utils'; import DragIcon from '@fastgpt/web/components/common/DndDrag/DragIcon'; import { AppContext } from '@/web/core/app/context/appContext'; +import { useI18n } from '@/web/context/I18n'; const ListItem = ({ provided, @@ -415,6 +416,7 @@ const ConditionValueInput = ({ condition?: VariableConditionEnum; onChange: (e: string) => void; }) => { + const { workflowT } = useI18n(); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); // get value type @@ -439,7 +441,7 @@ const ConditionValueInput = ({ ]} onchange={onChange} value={value} - placeholder={'选择值'} + placeholder={workflowT('ifelse.Select value')} isDisabled={ condition === VariableConditionEnum.isEmpty || condition === VariableConditionEnum.isNotEmpty @@ -450,7 +452,11 @@ const ConditionValueInput = ({ return ( ); } - }, [condition, onChange, value, valueType]); + }, [condition, onChange, value, valueType, workflowT]); return Render; }; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx index 6d0c87095f..e947e866df 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -42,7 +42,7 @@ const Reference = ({ item, nodeId }: RenderInputProps) => { const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const onSelect = useCallback( - (e: any) => { + (e: ReferenceValueProps) => { const workflowStartNode = nodeList.find( (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart ); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx index ee766c62aa..0cb05758ac 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx @@ -16,7 +16,7 @@ import { WorkflowContext } from '@/components/core/workflow/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; const RenderList: { - types: `${FlowNodeOutputTypeEnum}`[]; + types: FlowNodeOutputTypeEnum[]; Component: React.ComponentType; }[] = []; diff --git a/projects/app/src/pages/api/admin/initv481.ts b/projects/app/src/pages/api/admin/initv481.ts index 47fdbd394f..ca370235b2 100644 --- a/projects/app/src/pages/api/admin/initv481.ts +++ b/projects/app/src/pages/api/admin/initv481.ts @@ -1,12 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { PgClient } from '@fastgpt/service/common/vectorStore/pg'; import { NextAPI } from '@/service/middleware/entry'; -import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants'; import { connectionMongo } from '@fastgpt/service/common/mongo'; -import { addLog } from '@fastgpt/service/common/system/log'; /* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */ async function handler(req: NextApiRequest, res: NextApiResponse) { diff --git a/projects/app/src/pages/api/core/app/questionGuides/import.ts b/projects/app/src/pages/api/core/app/questionGuides/import.ts deleted file mode 100644 index da6abb16d6..0000000000 --- a/projects/app/src/pages/api/core/app/questionGuides/import.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user'; -import { NextApiRequest, NextApiResponse } from 'next'; -import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema'; -import axios from 'axios'; -import { NextAPI } from '@/service/middleware/entry'; - -async function handler(req: NextApiRequest, res: NextApiResponse) { - const { textList = [], appId, customUrl } = req.body; - - if (!customUrl) { - const { teamId } = await authUserNotVisitor({ req, authToken: true }); - - const currentQGuide = await MongoChatInputGuide.find({ appId, teamId }); - const currentTexts = currentQGuide.map((item) => item.text); - const textsToDelete = currentTexts.filter((text) => !textList.includes(text)); - - await MongoChatInputGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId }); - - const newTexts = textList.filter((text: string) => !currentTexts.includes(text)); - - const newDocuments = newTexts.map((text: string) => ({ - text: text, - appId: appId, - teamId: teamId - })); - - await MongoChatInputGuide.insertMany(newDocuments); - } else { - try { - const response = await axios.post(customUrl, { - textList, - appId - }); - res.status(200).json(response.data); - } catch (error) { - res.status(500).json({ error }); - } - } -} - -export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/chat/inputGuide/query.ts b/projects/app/src/pages/api/core/chat/inputGuide/query.ts index a6ff75437f..4afca44e19 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/query.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/query.ts @@ -2,21 +2,29 @@ import type { NextApiResponse } from 'next'; import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema'; import { NextAPI } from '@/service/middleware/entry'; import { ApiRequestProps } from '@fastgpt/service/type/next'; -import { authApp } from '@fastgpt/service/support/permission/auth/app'; +import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; +import { authChatCert } from '@/service/support/permission/auth/chat'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -export type QueryChatInputGuideProps = { +export type QueryChatInputGuideBody = OutLinkChatAuthProps & { appId: string; searchKey: string; }; export type QueryChatInputGuideResponse = string[]; async function handler( - req: ApiRequestProps<{}, QueryChatInputGuideProps>, + req: ApiRequestProps, res: NextApiResponse ): Promise { - const { appId, searchKey } = req.query; + const { appId, searchKey } = req.body; - await authApp({ req, appId, authToken: true, authApiKey: true, per: 'r' }); + // tmp auth + const { teamId } = await authChatCert({ req, authToken: true }); + const app = await MongoApp.findOne({ _id: appId, teamId }); + if (!app) { + return Promise.reject(AppErrEnum.unAuthApp); + } const params = { appId, diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx index de6831b4ae..e069e763d2 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx @@ -31,26 +31,17 @@ import { useI18n } from '@/web/context/I18n'; import { useContextSelector } from 'use-context-selector'; import { AppContext } from '@/web/core/app/context/appContext'; -const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'), { - ssr: false -}); -const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'), { - ssr: false -}); -const ToolSelectModal = dynamic(() => import('./ToolSelectModal'), { ssr: false }); -const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'), { ssr: false }); -const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'), { ssr: false }); -const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'), { ssr: false }); -const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig'), { - ssr: false -}); +const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); +const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); +const ToolSelectModal = dynamic(() => import('./ToolSelectModal')); +const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect')); +const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch')); +const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig')); +const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig')); const ScheduledTriggerConfig = dynamic( - () => import('@/components/core/app/ScheduledTriggerConfig'), - { ssr: false } + () => import('@/components/core/app/ScheduledTriggerConfig') ); -const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig'), { - ssr: false -}); +const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig')); const BoxStyles: BoxProps = { px: 5, diff --git a/projects/app/src/pages/chat/components/ChatHeader.tsx b/projects/app/src/pages/chat/components/ChatHeader.tsx index 42eb0b0ddf..3022fa2b95 100644 --- a/projects/app/src/pages/chat/components/ChatHeader.tsx +++ b/projects/app/src/pages/chat/components/ChatHeader.tsx @@ -15,16 +15,16 @@ const ChatHeader = ({ appName, appAvatar, chatModels, - appId, showHistory, + onRoute2AppDetail, onOpenSlider }: { history: ChatItemType[]; appName: string; appAvatar: string; chatModels?: string[]; - appId?: string; showHistory?: boolean; + onRoute2AppDetail?: () => void; onOpenSlider: () => void; }) => { const router = useRouter(); @@ -80,13 +80,7 @@ const ChatHeader = ({ - { - appId && router.push(`/app/detail?appId=${appId}`); - }} - > + {appName} diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 565cf0238f..e06fa8d216 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -335,10 +335,10 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { router.push(`/app/detail?appId=${appId}`)} showHistory /> diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 8de038c466..a1ab3865ca 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -378,7 +378,6 @@ const OutLink = ({ history={chatData.history} showHistory={showHistory === '1'} onOpenSlider={onOpenSlider} - appId={chatData.appId} /> {/* chat box */} diff --git a/projects/app/src/pages/dataset/detail/components/Import/commonProgress/DataProcess.tsx b/projects/app/src/pages/dataset/detail/components/Import/commonProgress/DataProcess.tsx index e0987b6571..bfefd2c8cb 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/commonProgress/DataProcess.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/commonProgress/DataProcess.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Box, Flex, @@ -17,7 +17,7 @@ import { import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio'; -import { TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; +import { TrainingModeEnum, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; import { ImportProcessWayEnum } from '@/web/core/dataset/constants'; import MyTooltip from '@/components/MyTooltip'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -27,6 +27,7 @@ import Preview from '../components/Preview'; import Tag from '@fastgpt/web/components/common/Tag/index'; import { useContextSelector } from 'use-context-selector'; import { DatasetImportContext } from '../Context'; +import { useToast } from '@fastgpt/web/hooks/useToast'; function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean }) { const { t } = useTranslation(); @@ -42,8 +43,10 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean maxChunkSize, priceTip } = useContextSelector(DatasetImportContext, (v) => v); - const { getValues, setValue, register } = processParamsForm; - const [refresh, setRefresh] = useState(false); + const { getValues, setValue, register, watch } = processParamsForm; + const { toast } = useToast(); + const mode = watch('mode'); + const way = watch('way'); const { isOpen: isOpenCustomPrompt, @@ -53,12 +56,21 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean const trainingModeList = useMemo(() => { const list = Object.entries(TrainingTypeMap); + return list; + }, []); - return list.filter(([key, value]) => { - if (feConfigs?.isPlus) return true; - return value.openSource; - }); - }, [feConfigs?.isPlus]); + const onSelectTrainWay = useCallback( + (e: TrainingModeEnum) => { + if (!feConfigs?.isPlus && !TrainingTypeMap[e]?.openSource) { + return toast({ + status: 'warning', + title: t('common.system.Commercial version function') + }); + } + setValue('mode', e); + }, + [feConfigs?.isPlus, setValue, t, toast] + ); return ( @@ -80,11 +92,8 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean }))} px={3} py={2} - value={getValues('mode')} - onChange={(e) => { - setValue('mode', e); - setRefresh(!refresh); - }} + value={mode} + onChange={onSelectTrainWay} gridTemplateColumns={'repeat(3,1fr)'} defaultBg="white" activeBg="white" @@ -105,7 +114,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean title: t('core.dataset.import.Custom process'), desc: t('core.dataset.import.Custom process desc'), value: ImportProcessWayEnum.custom, - children: getValues('way') === ImportProcessWayEnum.custom && ( + children: way === ImportProcessWayEnum.custom && ( {showChunkInput && chunkSizeField && ( @@ -250,11 +259,10 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean py={3} defaultBg="white" activeBg="white" - value={getValues('way')} + value={way} w={'100%'} onChange={(e) => { setValue('way', e); - setRefresh(!refresh); }} > @@ -286,7 +294,6 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean defaultValue={getValues('qaPrompt')} onChange={(e) => { setValue('qaPrompt', e); - setRefresh(!refresh); }} onClose={onCloseCustomPrompt} /> diff --git a/projects/app/src/pages/dataset/list/component/CreateModal.tsx b/projects/app/src/pages/dataset/list/component/CreateModal.tsx index bdf742a3de..4cb46ae79d 100644 --- a/projects/app/src/pages/dataset/list/component/CreateModal.tsx +++ b/projects/app/src/pages/dataset/list/component/CreateModal.tsx @@ -18,21 +18,19 @@ import MyRadio from '@/components/common/MyRadio'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; -import MySelect from '@fastgpt/web/components/common/MySelect'; import AIModelSelector from '@/components/Select/AIModelSelector'; import { useI18n } from '@/web/context/I18n'; const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: string }) => { const { t } = useTranslation(); const { datasetT } = useI18n(); - const [refresh, setRefresh] = useState(false); const { toast } = useToast(); const router = useRouter(); const { isPc, feConfigs, vectorModelList, datasetModelList } = useSystemStore(); const filterNotHiddenVectorModelList = vectorModelList.filter((item) => !item.hidden); - const { register, setValue, getValues, handleSubmit } = useForm({ + const { register, setValue, handleSubmit, watch } = useForm({ defaultValues: { parentId, type: DatasetTypeEnum.dataset, @@ -43,6 +41,10 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st agentModel: datasetModelList[0].model } }); + const avatar = watch('avatar'); + const datasetType = watch('type'); + const vectorModel = watch('vectorModel'); + const agentModel = watch('agentModel'); const { File, onOpen: onOpenSelectFile } = useSelectFile({ fileType: '.jpg,.png', @@ -61,7 +63,6 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st maxH: 300 }); setValue('avatar', src); - setRefresh((state) => !state); } catch (err: any) { toast({ title: getErrText(err, t('common.avatar.Select Failed')), @@ -85,6 +86,22 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st } }); + const onSelectDatasetType = useCallback( + (e: DatasetTypeEnum) => { + if ( + !feConfigs?.isPlus && + (e === DatasetTypeEnum.websiteDataset || e === DatasetTypeEnum.externalFile) + ) { + return toast({ + status: 'warning', + title: t('common.system.Commercial version function') + }); + } + setValue('type', e); + }, + [feConfigs?.isPlus, setValue, t, toast] + ); + return ( void; parentId?: st icon: 'core/dataset/commonDataset', desc: datasetT('Common Dataset Desc') }, - ...(feConfigs.isPlus - ? [ - { - title: datasetT('Website Dataset'), - value: DatasetTypeEnum.websiteDataset, - icon: 'core/dataset/websiteDataset', - desc: datasetT('Website Dataset Desc') - }, - { - title: datasetT('External File'), - value: DatasetTypeEnum.externalFile, - icon: 'core/dataset/externalDataset', - desc: datasetT('External file Dataset Desc') - } - ] - : []) + { + title: datasetT('Website Dataset'), + value: DatasetTypeEnum.websiteDataset, + icon: 'core/dataset/websiteDataset', + desc: datasetT('Website Dataset Desc') + }, + { + title: datasetT('External File'), + value: DatasetTypeEnum.externalFile, + icon: 'core/dataset/externalDataset', + desc: datasetT('External file Dataset Desc') + } ]} - value={getValues('type')} - onChange={(e) => { - setValue('type', e as DatasetTypeEnum); - setRefresh(!refresh); - }} + value={datasetType} + onChange={onSelectDatasetType} /> @@ -141,7 +151,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st void; parentId?: st ({ label: item.name, value: item.model }))} onchange={(e) => { setValue('vectorModel', e); - setRefresh((state) => !state); }} /> @@ -192,14 +201,13 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st ({ label: item.name, value: item.model }))} onchange={(e) => { setValue('agentModel', e); - setRefresh((state) => !state); }} /> diff --git a/projects/app/src/pages/login/fastlogin.tsx b/projects/app/src/pages/login/fastlogin.tsx index 10dbf5bbc0..2a207c7be5 100644 --- a/projects/app/src/pages/login/fastlogin.tsx +++ b/projects/app/src/pages/login/fastlogin.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect } from 'react'; import { useRouter } from 'next/router'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; import type { ResLogin } from '@/global/support/api/userRes.d'; import { useChatStore } from '@/web/core/chat/storeChat'; import { useUserStore } from '@/web/support/user/useUserStore'; @@ -9,7 +8,6 @@ import { postFastLogin } from '@/web/support/user/api'; import { useToast } from '@fastgpt/web/hooks/useToast'; import Loading from '@fastgpt/web/components/common/MyLoading'; import { serviceSideProps } from '@/web/common/utils/i18n'; -import { useQuery } from '@tanstack/react-query'; import { getErrText } from '@fastgpt/global/common/error/utils'; const FastLogin = ({ diff --git a/projects/app/src/web/core/app/templates.ts b/projects/app/src/web/core/app/templates.ts index b26c2bad89..0229e6f83d 100644 --- a/projects/app/src/web/core/app/templates.ts +++ b/projects/app/src/web/core/app/templates.ts @@ -3,6 +3,7 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeInputTypeEnum, + FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; @@ -109,7 +110,7 @@ export const appTemplates: (AppItemType & { key: 'userChatInput', label: 'core.module.input.label.user question', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -220,7 +221,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -228,7 +229,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] } @@ -356,7 +357,7 @@ export const appTemplates: (AppItemType & { key: 'userChatInput', label: 'core.module.input.label.user question', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -467,7 +468,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -475,7 +476,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] } @@ -586,7 +587,7 @@ export const appTemplates: (AppItemType & { key: 'userChatInput', label: 'core.module.input.label.user question', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -698,7 +699,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -706,7 +707,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -795,7 +796,7 @@ export const appTemplates: (AppItemType & { id: 'quoteQA', key: 'quoteQA', label: 'core.module.Dataset quote.label', - type: 'static', + type: FlowNodeOutputTypeEnum.static, valueType: WorkflowIOValueTypeEnum.datasetQuote, description: '特殊数组格式,搜索结果为空时,返回空数组。' } @@ -914,7 +915,7 @@ export const appTemplates: (AppItemType & { key: 'userChatInput', label: 'core.module.input.label.user question', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -1026,7 +1027,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -1034,7 +1035,7 @@ export const appTemplates: (AppItemType & { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -1116,7 +1117,7 @@ export const appTemplates: (AppItemType & { key: 'cqResult', label: '分类结果', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -1232,7 +1233,7 @@ export const appTemplates: (AppItemType & { key: 'quoteQA', label: 'core.module.Dataset quote.label', description: '特殊数组格式,搜索结果为空时,返回空数组。', - type: 'static', + type: FlowNodeOutputTypeEnum.static, valueType: WorkflowIOValueTypeEnum.datasetQuote } ] diff --git a/projects/app/src/web/core/app/utils.ts b/projects/app/src/web/core/app/utils.ts index 959cbd2c29..c3b21d93fa 100644 --- a/projects/app/src/web/core/app/utils.ts +++ b/projects/app/src/web/core/app/utils.ts @@ -7,6 +7,7 @@ import { import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { FlowNodeInputTypeEnum, + FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; @@ -65,7 +66,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { key: 'userChatInput', label: 'core.module.input.label.user question', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }; @@ -181,7 +182,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -189,7 +190,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] } @@ -315,7 +316,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { label: 'core.module.output.label.New context', description: 'core.module.output.description.New context', valueType: WorkflowIOValueTypeEnum.chatHistory, - type: 'static' + type: FlowNodeOutputTypeEnum.static }, { id: 'answerText', @@ -323,7 +324,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { label: 'core.module.output.label.Ai response content', description: 'core.module.output.description.Ai response content', valueType: WorkflowIOValueTypeEnum.string, - type: 'static' + type: FlowNodeOutputTypeEnum.static } ] }, @@ -416,7 +417,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { id: 'quoteQA', key: 'quoteQA', label: 'core.module.Dataset quote.label', - type: 'static', + type: FlowNodeOutputTypeEnum.static, valueType: WorkflowIOValueTypeEnum.datasetQuote } ] @@ -537,7 +538,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { id: 'quoteQA', key: 'quoteQA', label: 'core.module.Dataset quote.label', - type: 'static', + type: FlowNodeOutputTypeEnum.static, valueType: WorkflowIOValueTypeEnum.datasetQuote } ] diff --git a/projects/app/src/web/core/chat/inputGuide/api.ts b/projects/app/src/web/core/chat/inputGuide/api.ts index a1a53ae53c..c65521a2ef 100644 --- a/projects/app/src/web/core/chat/inputGuide/api.ts +++ b/projects/app/src/web/core/chat/inputGuide/api.ts @@ -14,7 +14,7 @@ import type { import type { updateInputGuideBody } from '@/pages/api/core/chat/inputGuide/update'; import type { deleteChatInputGuideQuery } from '@/pages/api/core/chat/inputGuide/delete'; import type { - QueryChatInputGuideProps, + QueryChatInputGuideBody, QueryChatInputGuideResponse } from '@/pages/api/core/chat/inputGuide/query'; @@ -26,10 +26,13 @@ export const getCountChatInputGuideTotal = (data: countChatInputGuideTotalQuery) export const getChatInputGuideList = (data: ChatInputGuideProps) => GET(`/core/chat/inputGuide/list`, data); -export const queryChatInputGuideList = (data: QueryChatInputGuideProps, url?: string) => { - return GET(url ?? `/core/chat/inputGuide/query`, data, { - withCredentials: !url - }); +export const queryChatInputGuideList = (data: QueryChatInputGuideBody, url?: string) => { + if (url) { + return GET(url, data, { + withCredentials: !url + }); + } + return POST(`/core/chat/inputGuide/query`, data); }; export const postChatInputGuides = (data: createInputGuideBody) => diff --git a/projects/app/src/web/core/workflow/adapt.ts b/projects/app/src/web/core/workflow/adapt.ts index 27fcf3ddc3..2985e1f488 100644 --- a/projects/app/src/web/core/workflow/adapt.ts +++ b/projects/app/src/web/core/workflow/adapt.ts @@ -167,7 +167,7 @@ type V1WorkflowType = { }; defaultEditField?: { inputType?: InputTypeEnum; // input type - outputType?: `${FlowNodeOutputTypeEnum}`; + outputType?: FlowNodeOutputTypeEnum; required?: boolean; key?: string; label?: string; @@ -219,7 +219,7 @@ type V1WorkflowType = { }; defaultEditField?: { inputType?: `${FlowNodeInputTypeEnum}`; // input type - outputType?: `${FlowNodeOutputTypeEnum}`; + outputType?: FlowNodeOutputTypeEnum; required?: boolean; key?: string; label?: string; diff --git a/projects/app/src/web/core/workflow/api.ts b/projects/app/src/web/core/workflow/api.ts index 88e1d330ee..9b07d8ca8b 100644 --- a/projects/app/src/web/core/workflow/api.ts +++ b/projects/app/src/web/core/workflow/api.ts @@ -2,7 +2,13 @@ import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api'; export const postWorkflowDebug = (data: PostWorkflowDebugProps) => - POST('/core/workflow/debug', { - ...data, - mode: 'debug' - }); + POST( + '/core/workflow/debug', + { + ...data, + mode: 'debug' + }, + { + timeout: 300000 + } + ); diff --git a/projects/sandbox/package.json b/projects/sandbox/package.json index 2d7d4a3426..4bc5bf16be 100644 --- a/projects/sandbox/package.json +++ b/projects/sandbox/package.json @@ -1,6 +1,6 @@ { "name": "sandbox", - "version": "0.0.1", + "version": "4.8.3", "description": "", "author": "", "private": true, diff --git a/projects/sandbox/src/http-exception.filter.ts b/projects/sandbox/src/http-exception.filter.ts index 8e22045361..dc5c168f54 100644 --- a/projects/sandbox/src/http-exception.filter.ts +++ b/projects/sandbox/src/http-exception.filter.ts @@ -12,7 +12,7 @@ export class HttpExceptionFilter implements ExceptionFilter { response.status(500).send({ success: false, time: new Date(), - msg: getErrText(error) + message: getErrText(error) }); } } diff --git a/projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts b/projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts index 5965007c41..4d04c4cec9 100644 --- a/projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts +++ b/projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts @@ -2,3 +2,8 @@ export class RunCodeDto { code: string; variables: object; } + +export class RunCodeResponse { + codeReturn: Record; + log: string; +} diff --git a/projects/sandbox/src/sandbox/sandbox.controller.ts b/projects/sandbox/src/sandbox/sandbox.controller.ts index 45032e4a02..bd865b9436 100644 --- a/projects/sandbox/src/sandbox/sandbox.controller.ts +++ b/projects/sandbox/src/sandbox/sandbox.controller.ts @@ -1,6 +1,6 @@ import { Controller, Post, Body, HttpCode } from '@nestjs/common'; import { SandboxService } from './sandbox.service'; -import { RunCodeDto } from './dto/create-sandbox.dto'; +import { RunCodeDto, RunCodeResponse } from './dto/create-sandbox.dto'; import { WorkerNameEnum, runWorker } from 'src/worker/utils'; @Controller('sandbox') @@ -10,6 +10,6 @@ export class SandboxController { @Post('/js') @HttpCode(200) runJs(@Body() codeProps: RunCodeDto) { - return runWorker(WorkerNameEnum.runJs, codeProps); + return runWorker(WorkerNameEnum.runJs, codeProps); } } diff --git a/projects/sandbox/src/worker/runJs.ts b/projects/sandbox/src/worker/runJs.ts index 4d1c2b0954..8ea6e2b874 100644 --- a/projects/sandbox/src/worker/runJs.ts +++ b/projects/sandbox/src/worker/runJs.ts @@ -1,4 +1,4 @@ -import { RunCodeDto } from 'src/sandbox/dto/create-sandbox.dto'; +import { RunCodeDto, RunCodeResponse } from 'src/sandbox/dto/create-sandbox.dto'; import { parentPort } from 'worker_threads'; import { workerResponse } from './utils'; @@ -6,19 +6,33 @@ import { workerResponse } from './utils'; const ivm = require('isolated-vm'); parentPort?.on('message', ({ code, variables = {} }: RunCodeDto) => { - const resolve = (data: any) => workerResponse({ parentPort, type: 'success', data }); + const resolve = (data: RunCodeResponse) => workerResponse({ parentPort, type: 'success', data }); const reject = (error: any) => workerResponse({ parentPort, type: 'error', data: error }); const isolate = new ivm.Isolate({ memoryLimit: 32 }); const context = isolate.createContextSync(); const jail = context.global; - // custom log function + // custom function + const logData = []; + const CustomLogStr = 'CUSTOM_LOG'; + code = code.replace(/console\.log/g, `${CustomLogStr}`); + jail.setSync(CustomLogStr, function (...args) { + logData.push( + args + .map((item) => (typeof item === 'object' ? JSON.stringify(item, null, 2) : item)) + .join(', ') + ); + }); + jail.setSync('responseData', function (args: any): any { if (typeof args === 'object') { - resolve(args); + resolve({ + codeReturn: args, + log: logData.join('\n') + }); } else { - reject('Not an invalid response'); + reject('Not an invalid response, must return an object'); } });