Compare commits

...

8 Commits

Author SHA1 Message Date
Archer
16a22bc76a V4.9.5 feature (#4520)
* readme

* Add queue log

* Test interactive (#4509)

* Support nested node interaction (#4503)

* feat: Add a new InteractiveContext type and update InteractiveBasicType, adding an optional context property to support more complex interaction state management.

* feat: Enhance workflow interactivity by adding InteractiveContext support and updating dispatch logic to manage nested contexts and entry nodes more effectively.

* feat: Refactor dispatchWorkFlow to utilize InteractiveContext for improved context management

* feat: Enhance entry node resolution by adding validation for entryNodeIds and recursive search in InteractiveContext

* feat: Remove workflowDepth from InteractiveContext and update recovery logic to utilize parentContext for improved context management

* feat: Update getWorkflowEntryNodeIds to use lastInteractive for improved context handling in runtime nodes

* feat: Add lastInteractive support to enhance context management across workflow components

* feat: Enhance interactive workflow by adding stopForInteractive flag and improving memory edge validation in runtime logic

* feat: Refactor InteractiveContext by removing interactiveAppId and updating runtime edge handling in dispatchRunApp for improved context management

* feat: Simplify runtime node and edge initialization in dispatchRunApp by using ternary operators for improved readability and maintainability

* feat: Improve memory edge validation in initWorkflowEdgeStatus by adding detailed comments for better understanding of subset checks and recursive context searching

* feat: Remove commented-out current level information from InteractiveContext for cleaner code and improved readability

* feat: Simplify stopForInteractive check in dispatchWorkFlow for improved code clarity and maintainability

* feat: Remove stopForInteractive handling and related references for improved code clarity and maintainability

* feat: Add interactive response handling in dispatchRunAppNode for enhanced workflow interactivity

* feat: Add context property to InteractiveBasicType and InteractiveNodeType for improved interactivity management

* feat: remove comments

* feat: Remove the node property from ChatDispatchProps to simplify type definitions

* feat: Remove workflowInteractiveResponse from dispatchRunAppNode for cleaner code

* feat: Refactor interactive value handling in chat history processing for improved clarity

* feat: Simplify initWorkflowEdgeStatus logic for better readability and maintainability

* feat: Add workflowInteractiveResponse to dispatchWorkFlow for enhanced functionality

* feat: Enhance interactive response handling with nested children support

* feat: Remove commented-out code for interactive node handling to improve clarity

* feat: remove  InteractiveContext type

* feat: Refactor UserSelectInteractive and UserInputInteractive params for improved structure and clarity

* feat: remove

* feat: The front end supports extracting the deepest interaction parameters to enhance interaction processing

* feat: The front end supports extracting the deepest interaction parameters to enhance interaction processing

* fix: handle undefined interactive values in runtimeEdges and runtimeNodes initialization

* fix: handle undefined interactive values in runtimeNodes and runtimeEdges initialization

* fix: update runtimeNodes and runtimeEdges initialization to use last interactive value

* fix: remove unused imports and replace getLastInteractiveValue with lastInteractive in runtimeEdges initialization

* fix: import WorkflowInteractiveResponseType and handle lastInteractive as undefined in chatTest

* feat: implement extractDeepestInteractive function and refactor usage in AIResponseBox and ChatBox utils

* fix: refactor initWorkflowEdgeStatus and getWorkflowEntryNodeIds calls in dispatchRunAppNode for recovery handling

* fix: ensure lastInteractive is handled consistently as undefined in runtimeEdges and runtimeNodes initialization

* fix: update dispatchFormInput and dispatchUserSelect to use lastInteractive consistently

* fix: update condition checks in dispatchFormInput and dispatchUserSelect to ensure lastInteractive type is validated correctly

* fix: refactor dispatchRunAppNode to replace isRecovery with childrenInteractive for improved clarity in runtimeNodes and runtimeEdges initialization

* refactor: streamline runtimeNodes and runtimeEdges initialization in dispatchRunAppNode for improved readability and maintainability

* fix: update rewriteNodeOutputByHistories function to accept runtimeNodes and interactive as parameters for improved clarity

* fix: simplify interactiveResponse assignment in dispatchWorkFlow for improved clarity

* fix: update entryNodeIds check in getWorkflowEntryNodeIds to ensure it's an array for improved reliability

* remove some invalid code

---------

Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>

* update doc

* update log

* fix: update debug workflow to conditionally include nextStepSkipNodes… (#4511)

* fix: update debug workflow to conditionally include nextStepSkipNodes based on lastInteractive for improved debugging accuracy

* fix : type error

* remove invalid code

* fix: QA queue

* fix: interactive

* Test log (#4519)

* add log (#4504)

* add log

* update log i18n

* update log

* delete template

* add i18NT

* add team operation log

---------

Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com>

* remove search

* update doc

---------

Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>
Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com>
2025-04-12 12:48:19 +08:00
Shiver
b51a87f5b7 Add oceanbase-version docker-compose.yml & Update deployment documents (#4450)
* Add oceanbase-version docker-compose.yml, update deployment documents

* sync and improve

---------

Co-authored-by: sa-buc <wangyanwen.wyw@sqaobnoxdn006013051242.sa128>
2025-04-11 18:58:10 +08:00
a.e.
bc1ca66b66 fix: downgrade md lib (#4508)
downgrade mdast-util-gfm-autolink-literal to 2.0.0 to avoid patch
missing
2025-04-11 13:31:30 +08:00
Peter Dave Hello
c9e12bb608 Update zh-Hant Traditional Chinese i18n translation (#4507) 2025-04-11 09:57:13 +08:00
heheer
4e7fa29087 fix share page dataset search show (#4506)
* fix share page dataset search show

* add comment

* Fix
2025-04-10 23:06:06 +08:00
Archer
ec3bcfa124 fix: password check (#4497)
* fix: password check

* add doc

* fix: password check
2025-04-10 11:49:35 +08:00
Archer
199f454b6b feat: team permission refine (#4494) (#4498)
* feat: team permission refine (#4402)

* chore: team permission extend

* feat: manage team permission

* chore: api auth

* fix: i18n

* feat: add initv493

* fix: test, org auth manager

* test: app test for refined permission

* update init sh

* fix: add/remove manage permission (#4427)

* fix: add/remove manage permission

* fix: github action fastgpt-test

* fix: mock create model

* fix: team write permission

* fix: ts

* account permission

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
2025-04-10 11:11:54 +08:00
Archer
80f41dd2a9 4.9.4 doc (#4493)
* update doc

* update lock

* update doc

* update doc
2025-04-10 01:52:07 +08:00
112 changed files with 2370 additions and 982 deletions

View File

@@ -15,6 +15,9 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: pnpm/action-setup@v4
with:
version: 10

View File

@@ -10,7 +10,7 @@
<a href="./README_ja.md">日语</a>
</p>
FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!
FastGPT 是一个 AI Agent 构建平台,提供开箱即用的数据处理、模型调用等能力同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的应用场景!
</div>

View File

@@ -126,15 +126,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.4 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.4 # 阿里云
ports:
- 3000:3000
networks:
@@ -184,6 +184,8 @@ services:
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
# 对话文件过期天数
- CHAT_FILE_EXPIRE_TIME=7
volumes:
- ./config.json:/app/data/config.json

View File

@@ -0,0 +1,202 @@
# 数据库的默认账号和密码仅首次运行时设置有效
# 如果修改了账号密码,记得改数据库和项目连接参数,别只改一处~
# 该配置文件只是给快速启动,测试使用。正式使用,记得务必修改账号密码,以及调整合适的知识库参数,共享内存等。
# 如何无法访问 dockerhub 和 git可以用阿里云阿里云没有arm包
version: '3.3'
services:
# vector db
ob:
image: oceanbase/oceanbase-ce # docker hub
# image: quay.io/oceanbase/oceanbase-ce:4.3.5.1-101000042025031818 # 镜像
container_name: ob
restart: always
# ports: # 生产环境建议不要暴露
# - 2881:2881
networks:
- fastgpt
environment:
# 这里的配置只有首次运行生效。修改后,重启镜像是不会生效的。需要把持久化数据删除再重启,才有效果
- OB_SYS_PASSWORD=obsyspassword
# 不同于传统数据库OceanBase 数据库的账号包含更多字段,包括用户名、租户名和集群名。经典格式为“用户名@租户名#集群名”
# 比如用mysql客户端连接时根据本文件的默认配置应该指定 “-uroot@tenantname”
- OB_TENANT_NAME=tenantname
- OB_TENANT_PASSWORD=tenantpassword
# MODE分为MINI和NORMAL 后者会最大程度使用主机资源
- MODE=NORMAL
- OB_SERVER_IP=127.0.0.1
# 更多环境变量配置见oceanbase官方文档 https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000002013494
volumes:
- ./ob/data:/root/ob
- ./ob/config:/root/.obd/cluster
- ./init.sql:/root/boot/init.d/init.sql
healthcheck:
# obclient -h127.0.0.1 -P2881 -uroot@tenantname -ptenantpassword -e "SELECT 1;"
test: ["CMD-SHELL", "obclient -h$OB_SERVER_IP -P2881 -uroot@$OB_TENANT_NAME -p$OB_TENANT_PASSWORD -e \"SELECT 1;\""]
interval: 30s
timeout: 10s
retries: 1000
start_period: 10s
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')"; 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.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.3 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.3 # 阿里云
ports:
- 3000:3000
networks:
- fastgpt
depends_on:
mongo:
condition: service_started
ob:
condition: service_healthy
sandbox:
condition: service_started
restart: always
environment:
# 前端外部可访问的地址,用于自动补全文件资源路径。例如 https:fastgpt.cn不能填 localhost。这个值可以不填不填则发给模型的图片会是一个相对路径而不是全路径模型可能伪造Host。
- FE_DOMAIN=
# root 密码,用户名为: root。如果需要修改 root 密码,直接修改这个环境变量,并重启即可。
- DEFAULT_ROOT_PSW=1234
# # AI Proxy 的地址,如果配了该地址,优先使用
# - AIPROXY_API_ENDPOINT=http://aiproxy:3000
# # AI Proxy 的 Admin Token与 AI Proxy 中的环境变量 ADMIN_KEY
# - AIPROXY_API_TOKEN=aiproxy
# 模型中转地址(如果用了 AI Proxy下面 2 个就不需要了,旧版 OneAPI 用户,使用下面的变量)
- # openai 基本地址,可用作中转。
- OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
- # OpenAI API Key
- CHAT_API_KEY=sk-8990fa15a34b464a805237cfe9561f11
# 数据库最大连接数
- 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
# OceanBase 向量库连接参数
- OCEANBASE_URL=mysql://root%40tenantname:tenantpassword@ob:2881/test
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
- STORE_LOG_LEVEL=warn
# 工作流最大运行次数
- WORKFLOW_MAX_RUN_TIMES=1000
# 批量执行节点,最大输入长度
- WORKFLOW_MAX_LOOP_TIMES=100
# 自定义跨域,不配置时,默认都允许跨域(多个域名通过逗号分割)
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
volumes:
- ./config.json:/app/data/config.json
# AI Proxy
aiproxy:
image: ghcr.io/labring/aiproxy:v0.1.5
# image: registry.cn-hangzhou.aliyuncs.com/labring/aiproxy:v0.1.3 # 阿里云
container_name: aiproxy
restart: unless-stopped
depends_on:
aiproxy_pg:
condition: service_healthy
networks:
- fastgpt
environment:
# 对应 fastgpt 里的AIPROXY_API_TOKEN
- ADMIN_KEY=aiproxy
# 错误日志详情保存时间(小时)
- LOG_DETAIL_STORAGE_HOURS=1
# 数据库连接地址
- SQL_DSN=postgres://postgres:aiproxy@aiproxy_pg:5432/aiproxy
# 最大重试次数
- RETRY_TIMES=3
# 不需要计费
- BILLING_ENABLED=false
# 不需要严格检测模型
- DISABLE_MODEL_CONFIG=true
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/status']
interval: 5s
timeout: 5s
retries: 10
aiproxy_pg:
image: pgvector/pgvector:0.8.0-pg15 # docker hub
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.8.0-pg15 # 阿里云
restart: unless-stopped
container_name: aiproxy_pg
volumes:
- ./aiproxy_pg:/var/lib/postgresql/data
networks:
- fastgpt
environment:
TZ: Asia/Shanghai
POSTGRES_USER: postgres
POSTGRES_DB: aiproxy
POSTGRES_PASSWORD: aiproxy
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'postgres', '-d', 'aiproxy']
interval: 5s
timeout: 5s
retries: 10
networks:
fastgpt:

View File

@@ -0,0 +1,2 @@
ALTER SYSTEM SET ob_vector_memory_limit_percentage = 30;

View File

@@ -85,15 +85,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.4 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.4 # 阿里云
ports:
- 3000:3000
networks:
@@ -142,6 +142,8 @@ services:
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
# 对话文件过期天数
- CHAT_FILE_EXPIRE_TIME=7
volumes:
- ./config.json:/app/data/config.json

View File

@@ -66,15 +66,15 @@ services:
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.4 # 阿里云
networks:
- fastgpt
restart: always
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.3 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.3 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.4 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.4 # 阿里云
ports:
- 3000:3000
networks:
@@ -123,6 +123,8 @@ services:
- ALLOWED_ORIGINS=
# 是否开启IP限制默认不开启
- USE_IP_LIMIT=false
# 对话文件过期天数
- CHAT_FILE_EXPIRE_TIME=7
volumes:
- ./config.json:/app/data/config.json

View File

@@ -135,6 +135,9 @@ curl -O https://raw.githubusercontent.com/labring/FastGPT/main/projects/app/data
# pgvector 版本(测试推荐,简单快捷)
curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-pgvector.yml
# oceanbase 版本需要将init.sql和docker-compose.yml放在同一个文件夹方便挂载
# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-oceanbase/docker-compose.yml
# curl -o init.sql https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-oceanbase/init.sql
# milvus 版本
# curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/main/deploy/docker/docker-compose-milvus.yml
# zilliz 版本
@@ -151,6 +154,13 @@ curl -o docker-compose.yml https://raw.githubusercontent.com/labring/FastGPT/mai
无需操作
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Oceanbase版本" >}}
{{< markdownify >}}
无需操作
{{< /markdownify >}}
{{< /tab >}}
{{< tab tabName="Milvus版本" >}}

View File

@@ -1,5 +1,5 @@
---
title: 'V4.9.4(进行中)'
title: 'V4.9.4'
description: 'FastGPT V4.9.4 更新说明'
icon: 'upgrade'
draft: false
@@ -11,7 +11,7 @@ weight: 796
### 1. 做好数据备份
### 1. 安装 Redis
### 2. 安装 Redis
* docker 部署的用户,参考最新的 `docker-compose.yml` 文件增加 Redis 配置。增加一个 redis 容器,并配置`fastgpt`,`fastgpt-pro`的环境变量,增加 `REDIS_URL` 环境变量。
* Sealos 部署的用户,在数据库里新建一个`redis`数据库,并复制`内网地址的 connection` 作为 `redis` 的链接串。然后配置`fastgpt`,`fastgpt-pro`的环境变量,增加 `REDIS_URL` 环境变量。
@@ -20,14 +20,14 @@ weight: 796
| --- | --- | --- |
| ![](/imgs/sealos-redis1.png) | ![](/imgs/sealos-redis2.png) | ![](/imgs/sealos-redis3.png) |
### 2. 更新镜像 tag
### 3. 更新镜像 tag
- 更新 FastGPT 镜像 tag: v4.9.4-alpha
- 更新 FastGPT 商业版镜像 tag: v4.9.4-alpha
- 更新 FastGPT 镜像 tag: v4.9.4
- 更新 FastGPT 商业版镜像 tag: v4.9.4
- Sandbox 无需更新
- AIProxy 无需更新
### 3. 执行升级脚本
### 4. 执行升级脚本
该脚本仅需商业版用户执行。
@@ -49,8 +49,8 @@ curl --location --request POST 'https://{{host}}/api/admin/initv494' \
2. SMTP 发送邮件插件
3. BullMQ 消息队列。
4. 利用 redis 进行部分数据缓存。
5. 站点同步支持配置训练参数。
6. AI 对话/工具调用,增加返回模型 finish_reason 字段。
5. 站点同步支持配置训练参数和增量同步
6. AI 对话/工具调用,增加返回模型 finish_reason 字段,便于追踪模型输出中断原因
7. 移动端语音输入交互调整
## ⚙️ 优化

View File

@@ -0,0 +1,28 @@
---
title: 'V4.9.5(进行中)'
description: 'FastGPT V4.9.5 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 795
---
## 🚀 新增内容
1. 团队成员权限细分,可分别控制是否可创建在根目录应用/知识库以及 API Key
2. 支持交互节点在嵌套工作流中使用。
3. 团队成员操作日志。
## ⚙️ 优化
1. 繁体中文翻译。
## 🐛 修复
1. password 检测规则错误。
2. 分享链接无法隐藏知识库检索结果。
3. IOS 低版本正则兼容问题。
4. 修复问答提取队列错误后,计数器未清零问题,导致问答提取队列失效。
5. Debug 模式交互节点下一步可能造成死循环。

View File

@@ -12,27 +12,29 @@
"previewIcon": "node ./scripts/icon/index.js",
"api:gen": "tsc ./scripts/openapi/index.ts && node ./scripts/openapi/index.js && npx @redocly/cli build-docs ./scripts/openapi/openapi.json -o ./projects/app/public/openapi/index.html",
"create:i18n": "node ./scripts/i18n/index.js",
"test": "vitest run --exclude 'test/cases/spec'",
"test:all": "vitest run",
"test": "vitest run",
"test:workflow": "vitest run workflow"
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.1",
"@vitest/coverage-v8": "^3.0.2",
"@vitest/coverage-v8": "^3.0.9",
"husky": "^8.0.3",
"i18next": "23.16.8",
"lint-staged": "^13.3.0",
"next-i18next": "15.4.2",
"prettier": "3.2.4",
"react-i18next": "14.1.2",
"vitest": "^3.0.2",
"vitest-mongodb": "^1.0.1",
"vitest": "^3.0.9",
"mongodb-memory-server": "^10.1.4",
"zhlint": "^0.7.4"
},
"lint-staged": {
"./**/**/*.{ts,tsx,scss}": "npm run format-code",
"./docSite/**/**/*.md": "npm run format-doc"
},
"resolutions": {
"mdast-util-gfm-autolink-literal": "2.0.0"
},
"engines": {
"node": ">=18.16.0",
"pnpm": ">=9.0.0"

View File

@@ -77,6 +77,13 @@ export const getHistoryPreview = (
});
};
export const filterModuleTypeList: any[] = [
FlowNodeTypeEnum.pluginModule,
FlowNodeTypeEnum.datasetSearchNode,
FlowNodeTypeEnum.tools,
FlowNodeTypeEnum.pluginOutput
];
export const filterPublicNodeResponseData = ({
flowResponses = [],
responseDetail = false
@@ -87,12 +94,6 @@ export const filterPublicNodeResponseData = ({
const filedList = responseDetail
? ['quoteList', 'moduleType', 'pluginOutput', 'runningTime']
: ['moduleType', 'pluginOutput', 'runningTime'];
const filterModuleTypeList: any[] = [
FlowNodeTypeEnum.pluginModule,
FlowNodeTypeEnum.datasetSearchNode,
FlowNodeTypeEnum.tools,
FlowNodeTypeEnum.pluginOutput
];
return flowResponses
.filter((item) => filterModuleTypeList.includes(item.moduleType))

View File

@@ -23,7 +23,7 @@ import { WorkflowResponseType } from '../../../../service/core/workflow/dispatch
import { AiChatQuoteRoleType } from '../template/system/aiChat/type';
import { LafAccountType, OpenaiAccountType } from '../../../support/user/team/type';
import { CompletionFinishReason } from '../../ai/type';
import { WorkflowInteractiveResponseType } from '../template/system/interactive/type';
export type ExternalProviderType = {
openaiAccount?: OpenaiAccountType;
externalWorkflowVariables?: Record<string, string>;
@@ -55,12 +55,14 @@ export type ChatDispatchProps = {
variables: Record<string, any>; // global variable
query: UserChatItemValueItemType[]; // trigger query
chatConfig: AppSchema['chatConfig'];
lastInteractive?: WorkflowInteractiveResponseType; // last interactive response
stream: boolean;
maxRunTimes: number;
isToolCall?: boolean;
workflowStreamResponse?: WorkflowResponseType;
workflowDispatchDeep?: number;
version?: 'v1' | 'v2';
responseDetail?: boolean;
};
export type ModuleDispatchProps<T> = ChatDispatchProps & {

View File

@@ -10,7 +10,19 @@ import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
import { replaceVariable, valToStr } from '../../../common/string/tools';
import {
InteractiveNodeResponseType,
WorkflowInteractiveResponseType
} from '../template/system/interactive/type';
export const extractDeepestInteractive = (
interactive: WorkflowInteractiveResponseType
): WorkflowInteractiveResponseType => {
if (interactive?.type === 'childrenInteractive' && interactive.params?.childrenResponse) {
return extractDeepestInteractive(interactive.params.childrenResponse);
}
return interactive;
};
export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => {
let limit = 10;
nodes.forEach((node) => {
@@ -34,7 +46,9 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number
1. Get the interactive data
2. Check that the workflow starts at the interaction node
*/
export const getLastInteractiveValue = (histories: ChatItemType[]) => {
export const getLastInteractiveValue = (
histories: ChatItemType[]
): WorkflowInteractiveResponseType | undefined => {
const lastAIMessage = [...histories].reverse().find((item) => item.obj === ChatRoleEnum.AI);
if (lastAIMessage) {
@@ -45,7 +59,11 @@ export const getLastInteractiveValue = (histories: ChatItemType[]) => {
lastValue.type !== ChatItemValueTypeEnum.interactive ||
!lastValue.interactive
) {
return null;
return;
}
if (lastValue.interactive.type === 'childrenInteractive') {
return lastValue.interactive;
}
// Check is user select
@@ -62,38 +80,29 @@ export const getLastInteractiveValue = (histories: ChatItemType[]) => {
}
}
return null;
return;
};
export const initWorkflowEdgeStatus = (
edges: StoreEdgeItemType[] | RuntimeEdgeItemType[],
histories?: ChatItemType[]
edges: StoreEdgeItemType[],
lastInteractive?: WorkflowInteractiveResponseType
): RuntimeEdgeItemType[] => {
// If there is a history, use the last interactive value
if (histories && histories.length > 0) {
const memoryEdges = getLastInteractiveValue(histories)?.memoryEdges;
if (lastInteractive) {
const memoryEdges = lastInteractive.memoryEdges || [];
if (memoryEdges && memoryEdges.length > 0) {
return memoryEdges;
}
}
return (
edges?.map((edge) => ({
...edge,
status: 'waiting'
})) || []
);
return edges?.map((edge) => ({ ...edge, status: 'waiting' })) || [];
};
export const getWorkflowEntryNodeIds = (
nodes: (StoreNodeItemType | RuntimeNodeItemType)[],
histories?: ChatItemType[]
lastInteractive?: WorkflowInteractiveResponseType
) => {
// If there is a history, use the last interactive entry node
if (histories && histories.length > 0) {
const entryNodeIds = getLastInteractiveValue(histories)?.entryNodeIds;
if (lastInteractive) {
const entryNodeIds = lastInteractive.entryNodeIds || [];
if (Array.isArray(entryNodeIds) && entryNodeIds.length > 0) {
return entryNodeIds;
}
@@ -396,10 +405,10 @@ export const textAdaptGptResponse = ({
/* Update runtimeNode's outputs with interactive data from history */
export function rewriteNodeOutputByHistories(
histories: ChatItemType[],
runtimeNodes: RuntimeNodeItemType[]
runtimeNodes: RuntimeNodeItemType[],
lastInteractive?: InteractiveNodeResponseType
) {
const interactive = getLastInteractiveValue(histories);
const interactive = lastInteractive;
if (!interactive?.nodeOutputs) {
return runtimeNodes;
}

View File

@@ -1,6 +1,5 @@
import type { NodeOutputItemType } from '../../../../chat/type';
import type { FlowNodeOutputItemType } from '../../../type/io';
import type { RuntimeEdgeItemType } from '../../../runtime/type';
import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
import { WorkflowIOValueTypeEnum } from 'core/workflow/constants';
import type { ChatCompletionMessageParam } from '../../../../ai/type';
@@ -9,7 +8,6 @@ type InteractiveBasicType = {
entryNodeIds: string[];
memoryEdges: RuntimeEdgeItemType[];
nodeOutputs: NodeOutputItemType[];
toolParams?: {
entryNodeIds: string[]; // 记录工具中,交互节点的 Id而不是起始工作流的入口
memoryMessages: ChatCompletionMessageParam[]; // 这轮工具中,产生的新的 messages
@@ -23,6 +21,13 @@ type InteractiveNodeType = {
nodeOutputs?: NodeOutputItemType[];
};
type ChildrenInteractive = InteractiveNodeType & {
type: 'childrenInteractive';
params: {
childrenResponse?: WorkflowInteractiveResponseType;
};
};
export type UserSelectOptionItemType = {
key: string;
value: string;
@@ -62,5 +67,9 @@ type UserInputInteractive = InteractiveNodeType & {
submitted?: boolean;
};
};
export type InteractiveNodeResponseType = UserSelectInteractive | UserInputInteractive;
export type InteractiveNodeResponseType =
| UserSelectInteractive
| UserInputInteractive
| ChildrenInteractive;
export type WorkflowInteractiveResponseType = InteractiveBasicType & InteractiveNodeResponseType;

View File

@@ -0,0 +1,14 @@
export enum OperationLogEventEnum {
LOGIN = 'LOGIN',
CREATE_INVITATION_LINK = 'CREATE_INVITATION_LINK',
JOIN_TEAM = 'JOIN_TEAM',
CHANGE_MEMBER_NAME = 'CHANGE_MEMBER_NAME',
KICK_OUT_TEAM = 'KICK_OUT_TEAM',
CREATE_DEPARTMENT = 'CREATE_DEPARTMENT',
CHANGE_DEPARTMENT = 'CHANGE_DEPARTMENT',
DELETE_DEPARTMENT = 'DELETE_DEPARTMENT',
RELOCATE_DEPARTMENT = 'RELOCATE_DEPARTMENT',
CREATE_GROUP = 'CREATE_GROUP',
DELETE_GROUP = 'DELETE_GROUP',
ASSIGN_PERMISSION = 'ASSIGN_PERMISSION'
}

View File

@@ -0,0 +1,19 @@
import { SourceMemberType } from '../user/type';
import { OperationLogEventEnum } from './constants';
export type OperationLogSchema = {
_id: string;
tmbId: string;
teamId: string;
timestamp: Date;
event: `${OperationLogEventEnum}`;
metadata?: Record<string, string>;
};
export type OperationListItemType = {
_id: string;
sourceMember: SourceMemberType;
event: `${OperationLogEventEnum}`;
timestamp: Date;
metadata: Record<string, string>;
};

View File

@@ -13,12 +13,15 @@ export type CollaboratorItemType = {
orgId: string;
}>;
export type UpdateClbPermissionProps = {
export type UpdateClbPermissionProps<addOnly = false> = {
members?: string[];
groups?: string[];
orgs?: string[];
permission: PermissionValueType;
};
} & (addOnly extends true
? {}
: {
permission: PermissionValueType;
});
export type DeletePermissionQuery = RequireOnlyOne<{
tmbId?: string;

View File

@@ -5,15 +5,16 @@ export type PerConstructPros = {
per?: PermissionValueType;
isOwner?: boolean;
permissionList?: PermissionListType;
childUpdatePermissionCallback?: () => void;
};
// the Permission helper class
export class Permission {
value: PermissionValueType;
isOwner: boolean;
hasManagePer: boolean;
hasWritePer: boolean;
hasReadPer: boolean;
isOwner: boolean = false;
hasManagePer: boolean = false;
hasWritePer: boolean = false;
hasReadPer: boolean = false;
_permissionList: PermissionListType;
constructor(props?: PerConstructPros) {
@@ -24,11 +25,8 @@ export class Permission {
this.value = per;
}
this.isOwner = isOwner;
this._permissionList = permissionList;
this.hasManagePer = this.checkPer(this._permissionList['manage'].value);
this.hasWritePer = this.checkPer(this._permissionList['write'].value);
this.hasReadPer = this.checkPer(this._permissionList['read'].value);
this.updatePermissions();
}
// add permission(s)
@@ -68,10 +66,21 @@ export class Permission {
return (this.value & perm) === perm;
}
private updatePermissionCallback?: () => void;
setUpdatePermissionCallback(callback: () => void) {
callback();
this.updatePermissionCallback = callback;
}
private updatePermissions() {
this.isOwner = this.value === OwnerPermissionVal;
this.hasManagePer = this.checkPer(this._permissionList['manage'].value);
this.hasWritePer = this.checkPer(this._permissionList['write'].value);
this.hasReadPer = this.checkPer(this._permissionList['read'].value);
this.updatePermissionCallback?.();
}
toBinary() {
return this.value.toString(2);
}
}

View File

@@ -17,23 +17,23 @@ type GroupMemberSchemaType = {
role: `${GroupMemberRole}`;
};
type MemberGroupListItemType<T extends boolean | undefined> = MemberGroupSchemaType & {
members: T extends true
type MemberGroupListItemType<WithMembers extends boolean | undefined> = MemberGroupSchemaType & {
members: WithMembers extends true
? {
tmbId: string;
name: string;
avatar: string;
}[]
: undefined;
count: T extends true ? number : undefined;
owner?: T extends true
count: WithMembers extends true ? number : undefined;
owner?: WithMembers extends true
? {
tmbId: string;
name: string;
avatar: string;
}
: undefined;
permission: T extends true ? Permission : undefined;
permission: WithMembers extends true ? Permission : undefined;
};
type GroupMemberItemType = {

View File

@@ -1,22 +1,50 @@
import { PermissionKeyEnum } from '../constant';
import { PermissionListType } from '../type';
import { PermissionList } from '../constant';
export const TeamPermissionList: PermissionListType = {
import { i18nT } from '../../../../web/i18n/utils';
export enum TeamPermissionKeyEnum {
appCreate = 'appCreate',
datasetCreate = 'datasetCreate',
apikeyCreate = 'apikeyCreate'
}
export const TeamPermissionList: PermissionListType<TeamPermissionKeyEnum> = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
value: 0b100
value: 0b000100
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
value: 0b010
value: 0b000010
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b001
value: 0b000001
},
[TeamPermissionKeyEnum.appCreate]: {
checkBoxType: 'multiple',
description: '',
name: i18nT('account_team:permission_appCreate'),
value: 0b001000
},
[TeamPermissionKeyEnum.datasetCreate]: {
checkBoxType: 'multiple',
description: '',
name: i18nT('account_team:permission_datasetCreate'),
value: 0b010000
},
[TeamPermissionKeyEnum.apikeyCreate]: {
checkBoxType: 'multiple',
description: '',
name: i18nT('account_team:permission_apikeyCreate'),
value: 0b100000
}
};
export const TeamReadPermissionVal = TeamPermissionList['read'].value;
export const TeamWritePermissionVal = TeamPermissionList['write'].value;
export const TeamManagePermissionVal = TeamPermissionList['manage'].value;
export const TeamAppCreatePermissionVal = TeamPermissionList['appCreate'].value;
export const TeamDatasetCreatePermissionVal = TeamPermissionList['datasetCreate'].value;
export const TeamApikeyCreatePermissionVal = TeamPermissionList['apikeyCreate'].value;
export const TeamDefaultPermissionVal = TeamReadPermissionVal;

View File

@@ -1,7 +1,17 @@
import { PerConstructPros, Permission } from '../controller';
import { TeamDefaultPermissionVal, TeamPermissionList } from './constant';
import {
TeamApikeyCreatePermissionVal,
TeamAppCreatePermissionVal,
TeamDatasetCreatePermissionVal,
TeamDefaultPermissionVal,
TeamPermissionList
} from './constant';
export class TeamPermission extends Permission {
hasAppCreatePer: boolean = false;
hasDatasetCreatePer: boolean = false;
hasApikeyCreatePer: boolean = false;
constructor(props?: PerConstructPros) {
if (!props) {
props = {
@@ -12,5 +22,11 @@ export class TeamPermission extends Permission {
}
props.permissionList = TeamPermissionList;
super(props);
this.setUpdatePermissionCallback(() => {
this.hasAppCreatePer = this.checkPer(TeamAppCreatePermissionVal);
this.hasDatasetCreatePer = this.checkPer(TeamDatasetCreatePermissionVal);
this.hasApikeyCreatePer = this.checkPer(TeamApikeyCreatePermissionVal);
});
}
}

View File

@@ -69,7 +69,7 @@ const addCommonMiddleware = (schema: mongoose.Schema) => {
export const getMongoModel = <T>(name: string, schema: mongoose.Schema) => {
if (connectionMongo.models[name]) return connectionMongo.models[name] as Model<T>;
console.log('Load model======', name);
if (process.env.NODE_ENV !== 'test') console.log('Load model======', name);
addCommonMiddleware(schema);
const model = connectionMongo.model<T>(name, schema);

View File

@@ -16,6 +16,7 @@ import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
import { pushChatLog } from './pushChatLog';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = {
chatId: string;
@@ -209,34 +210,24 @@ export const updateInteractiveChat = async ({
}
})();
if (interactiveValue.interactive.type === 'userSelect') {
interactiveValue.interactive = {
...interactiveValue.interactive,
params: {
...interactiveValue.interactive.params,
userSelectedVal: userInteractiveVal
}
};
let finalInteractive = extractDeepestInteractive(interactiveValue.interactive);
if (finalInteractive.type === 'userSelect') {
finalInteractive.params.userSelectedVal = userInteractiveVal;
} else if (
interactiveValue.interactive.type === 'userInput' &&
finalInteractive.type === 'userInput' &&
typeof parsedUserInteractiveVal === 'object'
) {
interactiveValue.interactive = {
...interactiveValue.interactive,
params: {
...interactiveValue.interactive.params,
inputForm: interactiveValue.interactive.params.inputForm.map((item) => {
const itemValue = parsedUserInteractiveVal[item.label];
return itemValue !== undefined
? {
...item,
value: itemValue
}
: item;
}),
submitted: true
}
};
finalInteractive.params.inputForm = finalInteractive.params.inputForm.map((item) => {
const itemValue = parsedUserInteractiveVal[item.label];
return itemValue !== undefined
? {
...item,
value: itemValue
}
: item;
});
finalInteractive.params.submitted = true;
}
if (aiResponse.customFeedbacks) {

View File

@@ -73,6 +73,7 @@ import { dispatchLoopStart } from './loop/runLoopStart';
import { dispatchFormInput } from './interactive/formInput';
import { dispatchToolParams } from './agent/runTool/toolParams';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { filterModuleTypeList } from '@fastgpt/global/core/chat/utils';
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
@@ -140,6 +141,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
} else {
props.workflowDispatchDeep += 1;
}
const isRootRuntime = props.workflowDispatchDeep === 1;
if (props.workflowDispatchDeep > 20) {
return {
@@ -160,25 +162,28 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
let workflowRunTimes = 0;
// set sse response headers
if (stream && res) {
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
if (isRootRuntime) {
res?.setHeader('Connection', 'keep-alive'); // Set keepalive for long connection
if (stream && res) {
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
const sendStreamTimerSign = () => {
setTimeout(() => {
props?.workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: ''
})
});
sendStreamTimerSign();
}, 10000);
};
sendStreamTimerSign();
// 10s sends a message to prevent the browser from thinking that the connection is disconnected
const sendStreamTimerSign = () => {
setTimeout(() => {
props?.workflowStreamResponse?.({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: ''
})
});
sendStreamTimerSign();
}, 10000);
};
sendStreamTimerSign();
}
}
variables = {
@@ -324,10 +329,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
});
if (props.mode === 'debug') {
debugNextStepRunNodes = debugNextStepRunNodes.concat([
...nextStepActiveNodes,
...nextStepSkipNodes
]);
debugNextStepRunNodes = debugNextStepRunNodes.concat(
props.lastInteractive ? nextStepActiveNodes : [...nextStepActiveNodes, ...nextStepSkipNodes]
);
return {
nextStepActiveNodes: [],
nextStepSkipNodes: []
@@ -373,7 +377,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
};
// Tool call, not need interactive response
if (!props.isToolCall) {
if (!props.isToolCall && isRootRuntime) {
props.workflowStreamResponse?.({
event: SseResponseEventEnum.interactive,
data: { interactive: interactiveResult }
@@ -427,14 +431,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
})();
if (!nodeRunResult) return [];
if (res?.closed) {
addLog.warn('Request is closed', {
appId: props.runningAppInfo.id,
nodeId: node.nodeId,
nodeName: node.name
});
return [];
}
/*
特殊情况:
@@ -491,6 +487,15 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
await Promise.all(nextStepSkipNodes.map((node) => checkNodeCanRun(node, skippedNodeIdList)))
).flat();
if (res?.closed) {
addLog.warn('Request is closed', {
appId: props.runningAppInfo.id,
nodeId: node.nodeId,
nodeName: node.name
});
return [];
}
return [
...nextStepActiveNodes,
...nextStepSkipNodes,
@@ -631,8 +636,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
if (
version === 'v2' &&
!props.isToolCall &&
!props.runningAppInfo.isChildApp &&
formatResponseData
isRootRuntime &&
formatResponseData &&
!(!props.responseDetail && filterModuleTypeList.includes(formatResponseData.moduleType))
) {
props.workflowStreamResponse?.({
event: SseResponseEventEnum.flowNodeResponse,
@@ -719,7 +725,9 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
entryNodeIds: nodeInteractiveResponse.entryNodeIds,
interactiveResponse: nodeInteractiveResponse.interactiveResponse
});
chatAssistantResponse.push(interactiveAssistant);
if (isRootRuntime) {
chatAssistantResponse.push(interactiveAssistant);
}
return interactiveAssistant.interactive;
}
})();

View File

@@ -10,7 +10,6 @@ import type {
UserInputInteractive
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { addLog } from '../../../../common/system/log';
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.description]: string;
@@ -29,13 +28,13 @@ export const dispatchFormInput = async (props: Props): Promise<FormInputResponse
histories,
node,
params: { description, userInputForms },
query
query,
lastInteractive
} = props;
const { isEntry } = node;
const interactive = getLastInteractiveValue(histories);
// Interactive node is not the entry node, return interactive result
if (!isEntry || interactive?.type !== 'userInput') {
if (!isEntry || lastInteractive?.type !== 'userInput') {
return {
[DispatchNodeResponseKeyEnum.interactive]: {
type: 'userInput',

View File

@@ -10,7 +10,6 @@ import type {
UserSelectOptionItemType
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.description]: string;
@@ -27,13 +26,13 @@ export const dispatchUserSelect = async (props: Props): Promise<UserSelectRespon
histories,
node,
params: { description, userSelectOptions },
query
query,
lastInteractive
} = props;
const { nodeId, isEntry } = node;
const interactive = getLastInteractiveValue(histories);
// Interactive node is not the entry node, return interactive result
if (!isEntry || interactive?.type !== 'userSelect') {
if (!isEntry || lastInteractive?.type !== 'userSelect') {
return {
[DispatchNodeResponseKeyEnum.interactive]: {
type: 'userSelect',

View File

@@ -18,6 +18,7 @@ import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { getAppVersionById } from '../../../app/version/controller';
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
import { ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
@@ -27,6 +28,7 @@ type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.fileUrlList]?: string[];
}>;
type Response = DispatchNodeResultType<{
[DispatchNodeResponseKeyEnum.interactive]?: ChildrenInteractive;
[NodeOutputKeyEnum.answerText]: string;
[NodeOutputKeyEnum.history]: ChatItemType[];
}>;
@@ -36,6 +38,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
runningAppInfo,
histories,
query,
lastInteractive,
node: { pluginId: appId, version },
workflowStreamResponse,
params,
@@ -100,31 +103,41 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
appId: String(appData._id)
};
const { flowResponses, flowUsages, assistantResponses, runTimes } = await dispatchWorkFlow({
...props,
// Rewrite stream mode
...(system_forbid_stream
? {
stream: false,
workflowStreamResponse: undefined
}
: {}),
runningAppInfo: {
id: String(appData._id),
teamId: String(appData.teamId),
tmbId: String(appData.tmbId),
isChildApp: true
},
runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)),
runtimeEdges: initWorkflowEdgeStatus(edges),
histories: chatHistories,
variables: childrenRunVariables,
query: runtimePrompt2ChatsValue({
files: userInputFiles,
text: userChatInput
}),
chatConfig
});
const childrenInteractive =
lastInteractive?.type === 'childrenInteractive'
? lastInteractive.params.childrenResponse
: undefined;
const entryNodeIds = getWorkflowEntryNodeIds(nodes, childrenInteractive || undefined);
const runtimeNodes = storeNodes2RuntimeNodes(nodes, entryNodeIds);
const runtimeEdges = initWorkflowEdgeStatus(edges, childrenInteractive);
const theQuery = childrenInteractive
? query
: runtimePrompt2ChatsValue({ files: userInputFiles, text: userChatInput });
const { flowResponses, flowUsages, assistantResponses, runTimes, workflowInteractiveResponse } =
await dispatchWorkFlow({
...props,
lastInteractive: childrenInteractive,
// Rewrite stream mode
...(system_forbid_stream
? {
stream: false,
workflowStreamResponse: undefined
}
: {}),
runningAppInfo: {
id: String(appData._id),
teamId: String(appData.teamId),
tmbId: String(appData.tmbId),
isChildApp: true
},
runtimeNodes,
runtimeEdges,
histories: chatHistories,
variables: childrenRunVariables,
query: theQuery,
chatConfig
});
const completeMessages = chatHistories.concat([
{
@@ -142,6 +155,14 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
const usagePoints = flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
return {
[DispatchNodeResponseKeyEnum.interactive]: workflowInteractiveResponse
? {
type: 'childrenInteractive',
params: {
childrenResponse: workflowInteractiveResponse
}
}
: undefined,
assistantResponses: system_forbid_stream ? [] : assistantResponses,
[DispatchNodeResponseKeyEnum.runTimes]: runTimes,
[DispatchNodeResponseKeyEnum.nodeResponse]: {

View File

@@ -0,0 +1,26 @@
import { MongoOperationLog } from './schema';
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
import { TemplateParamsMap } from './constants';
import { retryFn } from '../../../global/common/system/utils';
export function addOperationLog<T extends OperationLogEventEnum>({
teamId,
tmbId,
event,
params
}: {
tmbId: string;
teamId: string;
event: T;
params?: TemplateParamsMap[T];
}) {
console.log('Insert log');
retryFn(() =>
MongoOperationLog.create({
tmbId: tmbId,
teamId: teamId,
event,
metadata: params
})
);
}

View File

@@ -0,0 +1,85 @@
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
import { i18nT } from '../../../web/i18n/utils';
export const operationLogI18nMap = {
[OperationLogEventEnum.LOGIN]: {
content: i18nT('account_team:log_login'),
typeLabel: i18nT('account_team:login')
},
[OperationLogEventEnum.CREATE_INVITATION_LINK]: {
content: i18nT('account_team:log_create_invitation_link'),
typeLabel: i18nT('account_team:create_invitation_link')
},
[OperationLogEventEnum.JOIN_TEAM]: {
content: i18nT('account_team:log_join_team'),
typeLabel: i18nT('account_team:join_team')
},
[OperationLogEventEnum.CHANGE_MEMBER_NAME]: {
content: i18nT('account_team:log_change_member_name'),
typeLabel: i18nT('account_team:change_member_name')
},
[OperationLogEventEnum.KICK_OUT_TEAM]: {
content: i18nT('account_team:log_kick_out_team'),
typeLabel: i18nT('account_team:kick_out_team')
},
[OperationLogEventEnum.CREATE_DEPARTMENT]: {
content: i18nT('account_team:log_create_department'),
typeLabel: i18nT('account_team:create_department')
},
[OperationLogEventEnum.CHANGE_DEPARTMENT]: {
content: i18nT('account_team:log_change_department'),
typeLabel: i18nT('account_team:change_department_name')
},
[OperationLogEventEnum.DELETE_DEPARTMENT]: {
content: i18nT('account_team:log_delete_department'),
typeLabel: i18nT('account_team:delete_department')
},
[OperationLogEventEnum.RELOCATE_DEPARTMENT]: {
content: i18nT('account_team:log_relocate_department'),
typeLabel: i18nT('account_team:relocate_department')
},
[OperationLogEventEnum.CREATE_GROUP]: {
content: i18nT('account_team:log_create_group'),
typeLabel: i18nT('account_team:create_group')
},
[OperationLogEventEnum.DELETE_GROUP]: {
content: i18nT('account_team:log_delete_group'),
typeLabel: i18nT('account_team:delete_group')
},
[OperationLogEventEnum.ASSIGN_PERMISSION]: {
content: i18nT('account_team:log_assign_permission'),
typeLabel: i18nT('account_team:assign_permission')
}
} as const;
export type TemplateParamsMap = {
[OperationLogEventEnum.LOGIN]: { name?: string };
[OperationLogEventEnum.CREATE_INVITATION_LINK]: { name?: string; link: string };
[OperationLogEventEnum.JOIN_TEAM]: { name?: string; link: string };
[OperationLogEventEnum.CHANGE_MEMBER_NAME]: {
name?: string;
memberName: string;
newName: string;
};
[OperationLogEventEnum.KICK_OUT_TEAM]: {
name?: string;
memberName: string;
};
[OperationLogEventEnum.CREATE_DEPARTMENT]: { name?: string; departmentName: string };
[OperationLogEventEnum.CHANGE_DEPARTMENT]: {
name?: string;
departmentName: string;
};
[OperationLogEventEnum.DELETE_DEPARTMENT]: { name?: string; departmentName: string };
[OperationLogEventEnum.RELOCATE_DEPARTMENT]: {
name?: string;
departmentName: string;
};
[OperationLogEventEnum.CREATE_GROUP]: { name?: string; groupName: string };
[OperationLogEventEnum.DELETE_GROUP]: { name?: string; groupName: string };
[OperationLogEventEnum.ASSIGN_PERMISSION]: {
name?: string;
objectName: string;
permission: string;
};
};

View File

@@ -0,0 +1,40 @@
import { Schema, getMongoLogModel } from '../../common/mongo';
import type { OperationLogSchema } from '@fastgpt/global/support/operationLog/type';
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
export const OperationLogCollectionName = 'operationLog';
const OperationLogSchema = new Schema({
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
timestamp: {
type: Date,
default: () => new Date()
},
event: {
type: String,
enum: Object.values(OperationLogEventEnum),
required: true
},
metadata: {
type: Object,
default: {}
}
});
export const MongoOperationLog = getMongoLogModel<OperationLogSchema>(
OperationLogCollectionName,
OperationLogSchema
);

View File

@@ -2,7 +2,7 @@ import { TeamPermission } from '@fastgpt/global/support/permission/user/controll
import { AuthModeType, AuthResponseType } from '../type';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { authUserPer } from '../user/auth';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import { TeamManagePermissionVal } from '@fastgpt/global/support/permission/user/constant';
/*
Team manager can control org
@@ -15,7 +15,7 @@ export const authOrgMember = async ({
} & AuthModeType): Promise<AuthResponseType> => {
const result = await authUserPer({
...props,
per: ManagePermissionVal
per: TeamManagePermissionVal
});
const { teamId, tmbId, isRoot, tmb } = result;

View File

@@ -104,8 +104,11 @@ export async function addSourceMember<T extends { tmbId: string }>({
const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId));
if (!tmb) return;
// @ts-ignore
const formatItem = typeof item.toObject === 'function' ? item.toObject() : item;
return {
...item,
...formatItem,
sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status }
};
})

View File

@@ -5,17 +5,23 @@
"7days": "7 Days",
"accept": "accept",
"action": "operate",
"assign_permission": "Permission change",
"change_department_name": "Department Editor",
"change_member_name": "Member name change",
"confirm_delete_group": "Confirm to delete group?",
"confirm_delete_member": "Confirm to delete member?",
"confirm_delete_org": "Confirm to delete organization?",
"confirm_forbidden": "Confirm forbidden",
"confirm_leave_team": "Confirmed to leave the team? \nAfter exiting, all your resources in the team are transferred to the team owner.",
"copy_link": "Copy link",
"create_department": "Create a sub-department",
"create_group": "Create group",
"create_invitation_link": "Create Invitation Link",
"create_org": "Create organization",
"create_sub_org": "Create sub-organization",
"delete": "delete",
"delete_department": "Delete sub-department",
"delete_group": "Delete a group",
"delete_org": "Delete organization",
"edit_info": "Edit information",
"edit_member": "Edit user",
@@ -37,21 +43,51 @@
"invitation_link_list": "Invitation link list",
"invite_member": "Invite members",
"invited": "Invited",
"join_team": "Join the team",
"kick_out_team": "Remove members",
"label_sync": "Tag sync",
"leave_team_failed": "Leaving the team exception",
"log_assign_permission": "[{{name}}] Updated the permissions of [{{objectName}}]: [Application creation: [{{appCreate}}], Knowledge Base: [{{datasetCreate}}], API Key: [{{apiKeyCreate}}], Management: [{{manage}}]]",
"log_change_department": "【{{name}}】Updated department【{{departmentName}}】",
"log_change_member_name": "【{{name}}】Rename member [{{memberName}}] to 【{{newName}}】",
"log_create_department": "【{{name}}】Department【{{departmentName}}】",
"log_create_group": "【{{name}}】Created group [{{groupName}}]",
"log_create_invitation_link": "【{{name}}】Created invitation link【{{link}}】",
"log_delete_department": "{{name}} deleted department {{departmentName}}",
"log_delete_group": "{{name}} deleted group {{groupName}}",
"log_details": "Details",
"log_join_team": "【{{name}}】Join the team through the invitation link 【{{link}}】",
"log_kick_out_team": "{{name}} removed member {{memberName}}",
"log_login": "【{{name}}】Logined in the system",
"log_relocate_department": "【{{name}}】Displayed department【{{departmentName}}】",
"log_time": "Operation time",
"log_type": "Operation Type",
"log_user": "Operator",
"login": "Log in",
"manage_member": "Managing members",
"member": "member",
"member_group": "Belonging to member group",
"move_member": "Move member",
"move_org": "Move organization",
"operation_log": "log",
"org": "organization",
"org_description": "Organization description",
"org_name": "Organization name",
"owner": "owner",
"permission": "Permissions",
"permission_apikeyCreate": "Create API Key",
"permission_apikeyCreate_Tip": "Can create global APIKeys",
"permission_appCreate": "Create Application",
"permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)",
"permission_datasetCreate": "Create Knowledge Base",
"permission_datasetCreate_Tip": "Can create knowledge bases in the root directory (creation permissions in folders are controlled by the folder)",
"permission_manage": "Admin",
"permission_manage_tip": "Can manage members, create groups, manage all groups, and assign permissions to groups and members",
"relocate_department": "Department Mobile",
"remark": "remark",
"remove_tip": "Confirm to remove {{username}} from the team?",
"retain_admin_permissions": "Keep administrator rights",
"search_log": "Search log",
"search_member_group_name": "Search member/group name",
"total_team_members": "{{amount}} members in total",
"transfer_ownership": "transfer owner",

View File

@@ -100,7 +100,6 @@
"team.group.manage_tip": "Can manage members, create groups, manage all groups, assign permissions to groups and members",
"team.group.members": "member",
"team.group.name": "Group name",
"team.group.permission.manage": "administrator",
"team.group.permission.write": "Workbench/knowledge base creation",
"team.group.permission_tip": "Members with individually configured permissions will follow the individual permission configuration and will no longer be affected by group permissions.\n\nIf a member is in multiple permission groups, the member's permissions are combined.",
"team.group.role.admin": "administrator",
@@ -112,5 +111,6 @@
"team.manage_collaborators": "Manage Collaborators",
"team.no_collaborators": "No Collaborators",
"team.org.org": "Organization",
"team.write_role_member": ""
"team.write_role_member": "Write Permission",
"team.collaborator.added": "Added"
}

View File

@@ -5,6 +5,9 @@
"7days": "7天",
"accept": "接受",
"action": "操作",
"assign_permission": "权限变更",
"change_department_name": "部门编辑",
"change_member_name": "成员改名",
"confirm_delete_from_org": "确认将 {{username}} 移出部门?",
"confirm_delete_from_team": "确认将 {{username}} 移出团队?",
"confirm_delete_group": "确认删除群组?",
@@ -12,13 +15,16 @@
"confirm_forbidden": "确认停用",
"confirm_leave_team": "确认离开该团队? \n退出后您在该团队所有的资源均转让给团队所有者。",
"copy_link": "复制链接",
"create_department": "创建子部门",
"create_group": "创建群组",
"create_invitation_link": "创建邀请链接",
"create_org": "创建部门",
"create_sub_org": "创建子部门",
"delete": "删除",
"delete_department": "删除子部门",
"delete_from_org": "移出部门",
"delete_from_team": "移出团队",
"delete_group": "删除群组",
"delete_org": "删除部门",
"edit_info": "编辑信息",
"edit_member": "编辑用户",
@@ -41,27 +47,37 @@
"invitation_link_list": "链接列表",
"invite_member": "邀请成员",
"invited": "已邀请",
"join_team": "加入团队",
"join_update_time": "加入/更新时间",
"kick_out_team": "移除成员",
"label_sync": "标签同步",
"leave": "已离职",
"leave_team_failed": "离开团队异常",
"log_details": "详情",
"log_time": "操作时间",
"log_type": "操作类型",
"log_user": "操作人员",
"login": "登录",
"manage_member": "管理成员",
"member": "成员",
"member_group": "所属群组",
"move_member": "移动成员",
"move_org": "移动部门",
"notification_recieve": "团队通知接收",
"operation_log": "日志",
"org": "部门",
"org_description": "介绍",
"org_name": "部门名称",
"owner": "所有者",
"permission": "权限",
"please_bind_contact": "请绑定联系方式",
"relocate_department": "部门移动",
"remark": "备注",
"remove_tip": "确认将 {{username}} 移出团队?成员将被标记为“已离职”,不删除操作数据,账号下资源自动转让给团队所有者。",
"restore_tip": "确认将 {{username}} 加入团队吗?仅恢复该成员账号可用性及相关权限,无法恢复账号下资源。",
"restore_tip_title": "恢复确认",
"retain_admin_permissions": "保留管理员权限",
"search_log": "搜索日志",
"search_member": "搜索成员",
"search_member_group_name": "搜索成员/群组名称",
"search_org": "搜索部门",
@@ -77,5 +93,25 @@
"user_team_invite_member": "邀请成员",
"user_team_leave_team": "离开团队",
"user_team_leave_team_failed": "离开团队失败",
"waiting": "待接受"
"waiting": "待接受",
"permission_appCreate": "创建应用",
"permission_datasetCreate": "创建知识库",
"permission_apikeyCreate": "创建 API 密钥",
"permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)",
"permission_datasetCreate_Tip": "可以在根目录创建知识库,(文件夹下的创建权限由文件夹控制)",
"permission_apikeyCreate_Tip": "可以创建全局的 APIKey",
"permission_manage": "管理员",
"permission_manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限",
"log_login": "【{{name}}】登录了系统",
"log_create_invitation_link": "【{{name}}】创建了邀请链接【{{link}}】",
"log_join_team": "【{{name}}】通过邀请链接【{{link}}】加入团队",
"log_change_member_name": "【{{name}}】将成员【{{memberName}}】重命名为【{{newName}}】",
"log_kick_out_team": "【{{name}}】移除了成员【{{memberName}}】",
"log_create_department": "【{{name}}】创建了部门【{{departmentName}}】",
"log_change_department": "【{{name}}】更新了部门【{{departmentName}}】",
"log_delete_department": "【{{name}}】删除了部门【{{departmentName}}】",
"log_relocate_department": "【{{name}}】移动了部门【{{departmentName}}】",
"log_create_group": "【{{name}}】创建了群组【{{groupName}}】",
"log_delete_group": "【{{name}}】删除了群组【{{groupName}}】",
"log_assign_permission": "【{{name}}】更新了【{{objectName}}】的权限:[应用创建:【{{appCreate}}】, 知识库:【{{datasetCreate}}】, API密钥:【{{apiKeyCreate}}】, 管理:【{{manage}}】]"
}

View File

@@ -98,11 +98,9 @@
"team.group.keep_admin": "保留管理员权限",
"team.group.manage_member": "管理成员",
"team.group.manage_tip": "可以管理成员、创建群组、管理所有群组、为群组和成员分配权限",
"team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组则该成员的权限取并集。",
"team.group.members": "成员",
"team.group.name": "群组名称",
"team.group.permission.manage": "管理员",
"team.group.permission.write": "工作台/知识库创建",
"team.group.permission_tip": "单独配置权限的成员,将遵循个人权限配置,不再受群组权限影响。\n若成员在多个权限组则该成员的权限取并集。",
"team.group.role.admin": "管理员",
"team.group.role.member": "成员",
"team.group.role.owner": "所有者",
@@ -112,5 +110,6 @@
"team.manage_collaborators": "管理协作者",
"team.no_collaborators": "暂无协作者",
"team.org.org": "部门",
"team.write_role_member": "可写权限"
"team.write_role_member": "可写权限",
"team.collaborator.added": "已添加"
}

View File

@@ -3,30 +3,30 @@
"add_default_model": "新增預設模型",
"api_key": "API 金鑰",
"bills_and_invoices": "帳單與發票",
"channel": "模型道",
"config_model": "模型配置",
"confirm_logout": "確認登出登入?",
"channel": "模型道",
"config_model": "模型設定",
"confirm_logout": "確認登出登入",
"create_channel": "新增頻道",
"create_model": "新增模型",
"custom_model": "自訂模型",
"default_model": "預設模型",
"default_model_config": "預設模型配置",
"default_model_config": "預設模型設定",
"logout": "登出",
"model.active": "啟用",
"model.alias": "別名",
"model.alias_tip": "模型在系統中展示的名字,方便使用者理解",
"model.censor_tip": "如果需要進行敏感校驗,則開啟該開關",
"model.charsPointsPrice": "模型綜合價格",
"model.charsPointsPrice_tip": "將模型輸入和輸出合併起來進行 Token 計費,語言模型如果單獨配置了輸入和輸出計費,則按輸入和輸出分別計算",
"model.charsPointsPrice_tip": "將模型輸入和輸出合併起來進行 Token 計費,語言模型如果單獨設定了輸入和輸出計費,則按輸入和輸出分別計算",
"model.custom_cq_prompt": "自訂問題分類提示詞",
"model.custom_cq_prompt_tip": "覆蓋系統預設的問題分類提示詞,預設為:\n\"\"\"\n{{prompt}}\n\"\"\"",
"model.custom_extract_prompt": "自訂內容提取提示詞",
"model.custom_extract_prompt_tip": "覆蓋系統的提示詞,默認為:\n\"\"\"\n{{prompt}}\n\"\"\"",
"model.custom_extract_prompt_tip": "覆蓋系統的提示詞,預設為:\n\"\"\"\n{{prompt}}\n\"\"\"",
"model.dataset_process": "用於知識庫文件處理",
"model.defaultConfig": "額外 Body 參數",
"model.defaultConfig_tip": "每次請求時候,都會攜帶該額外 Body 參數",
"model.default_config": "Body 額外字段",
"model.default_config_tip": "發起對話請求時候,合併該配置。例如:\n\"\"\"\n{\n \"temperature\": 1,\n \"max_tokens\": null\n}\n\"\"\"",
"model.default_config": "Body 額外欄位",
"model.default_config_tip": "發起對話請求時候,合併該設定。例如:\n\"\"\"\n{\n \"temperature\": 1,\n \"max_tokens\": null\n}\n\"\"\"",
"model.default_model": "預設模型",
"model.default_system_chat_prompt": "預設提示詞",
"model.default_system_chat_prompt_tip": "模型對話時,都會攜帶該預設提示詞",
@@ -34,35 +34,35 @@
"model.default_token_tip": "索引模型預設文字分塊的長度,必須小於最大上文",
"model.delete_model_confirm": "確認刪除該模型?",
"model.edit_model": "模型參數編輯",
"model.function_call": "支援函數調用",
"model.function_call_tip": "如果模型支援函數調用,則開啟該開關。\n工具呼叫優先權更高。",
"model.function_call": "支援函式呼叫",
"model.function_call_tip": "如果模型支援函式呼叫,則開啟該開關。\n工具呼叫優先權更高。",
"model.input_price": "模型輸入價格",
"model.input_price_tip": "語言模型輸入價格,如果配置了該項,則模型綜合價格會失效",
"model.input_price_tip": "語言模型輸入價格,如果設定了該項,則模型綜合價格會失效",
"model.json_config": "設定檔",
"model.json_config_confirm": "確認使用該配置進行覆蓋?",
"model.json_config_tip": "透過設定檔設定模型,點選確認後,會使用輸入的配置進行全量覆蓋,請確保設定檔輸入正確。\n建議操作前複製目前設定檔進行備份。",
"model.json_config_confirm": "確認使用該設定進行覆蓋?",
"model.json_config_tip": "透過設定檔設定模型,點選確認後,會使用輸入的設定進行全量覆蓋,請確保設定檔輸入正確。\n建議操作前複製目前設定檔進行備份。",
"model.max_quote": "知識庫最大引用",
"model.max_temperature": "最大溫度",
"model.model_id": "模型ID",
"model.model_id_tip": "模型的唯一標識也就是實際請求到服務商model 的值,需要與 OneAPI 頻道中的模型對應。",
"model.model_id": "模型 ID",
"model.model_id_tip": "模型的唯一標識,也就是實際請求到服務商 model 的值,需要與 OneAPI 頻道中的模型對應。",
"model.normalization": "歸一化處理",
"model.normalization_tip": "如果Embedding API 未對向量值進行歸一化,可以啟用該開關,系統會進行歸一化處理。\n\n未歸一化的 API表現為向量檢索得分會大於 1。",
"model.normalization_tip": "如果 Embedding API 未對向量值進行歸一化,可以啟用該開關,系統會進行歸一化處理。\n\n未歸一化的 API表現為向量檢索得分會大於 1。",
"model.output_price": "模型輸出價格",
"model.output_price_tip": "語言模型輸出價格,如果配置了該項,則模型綜合價格會失效",
"model.param_name": "參數名",
"model.reasoning": "支輸出思考",
"model.output_price_tip": "語言模型輸出價格,如果設定了該項,則模型綜合價格會失效",
"model.param_name": "參數名",
"model.reasoning": "支輸出思考",
"model.reasoning_tip": "例如 Deepseek-reasoner可以輸出思考過程。",
"model.request_auth": "自訂請求 Key",
"model.request_auth_tip": "向自訂請求地址發起請求時候攜帶請求頭Authorization: Bearer xxx 進行請求",
"model.request_url": "自訂請求地址",
"model.request_url_tip": "如果填寫該值,則會直接向該地址發起請求,不經過 OneAPI。\n需要遵循 OpenAI 的 API格式並填寫完整請求地址例如\n\nLLM: {{host}}/v1/chat/completions\n\nEmbedding: {{host}}/v1/embeddings\n\nSTT: {{host}}/v1/audio/transcriptions\n\nTTS: {{host}}/v1/audio/speech\n\nRerank: {{host}}/v1/rerank",
"model.request_url_tip": "如果填寫該值,則會直接向該地址發起請求,不經過 OneAPI。\n需要遵循 OpenAI 的 API 格式,並填寫完整請求地址,例如:\n\nLLM: {{host}}/v1/chat/completions\n\nEmbedding: {{host}}/v1/embeddings\n\nSTT: {{host}}/v1/audio/transcriptions\n\nTTS: {{host}}/v1/audio/speech\n\nRerank: {{host}}/v1/rerank",
"model.response_format": "響應格式",
"model.show_stop_sign": "展示停止序列參數",
"model.show_top_p": "展示 Top-p 參數",
"model.test_model": "模型測試",
"model.tool_choice": "支援工具調用",
"model.tool_choice_tag": "工具調用",
"model.tool_choice_tip": "如果該模型支援工具調用,則開啟該開關",
"model.tool_choice": "支援工具呼叫",
"model.tool_choice_tag": "工具呼叫",
"model.tool_choice_tip": "如果該模型支援工具呼叫,則開啟該開關",
"model.used_in_classify": "用於問題分類",
"model.used_in_extract_fields": "用於文字擷取",
"model.used_in_tool_call": "用於工具呼叫節點",
@@ -70,14 +70,14 @@
"model.vision_tag": "視覺",
"model.vision_tip": "如果模型支援圖片識別,則開啟該開關。",
"model.voices": "聲音角色",
"model.voices_tip": "透過一個數組配置多個,例如:\n\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]",
"model.voices_tip": "透過一個陣列設定多個,例如:\n\n[\n {\n \"label\": \"Alloy\",\n \"value\": \"alloy\"\n },\n {\n \"label\": \"Echo\",\n \"value\": \"echo\"\n }\n]",
"model_provider": "模型提供者",
"notifications": "通知",
"personal_information": "個人資訊",
"personalization": "個人化",
"promotion_records": "促銷記錄",
"reset_default": "恢復默認配置",
"reset_default": "恢復預設設定",
"team": "團隊管理",
"third_party": "第三方账号",
"third_party": "第三方賬號",
"usage_records": "使用記錄"
}

View File

@@ -3,7 +3,7 @@
"back": "返回",
"bank_account": "開戶帳號",
"bank_name": "開戶銀行",
"bill_detail": "帳單詳",
"bill_detail": "帳單詳細資訊",
"bill_record": "帳單記錄",
"company_address": "公司地址",
"company_phone": "公司電話",
@@ -12,15 +12,15 @@
"contact_phone": "聯絡電話",
"contact_phone_void": "聯絡電話格式錯誤",
"default_header": "預設抬頭",
"detail": "詳",
"detail": "詳細資訊",
"email_address": "郵件地址",
"extra_ai_points": "AI 積分運算標準",
"extra_dataset_size": "額外知識庫容量",
"generation_time": "生成時間",
"has_invoice": "是否已開票",
"invoice_amount": "開票金額",
"invoice_detail": "發票詳",
"invoice_sending_info": "發票將在 3-7 個工作天內送至郵箱,請耐心等待",
"invoice_detail": "發票詳細資訊",
"invoice_sending_info": "發票將在 3-7 個工作天內送至郵箱,請耐心等待",
"mm": "毫米",
"need_special_invoice": "是否需要專票",
"no": "否",
@@ -31,8 +31,8 @@
"organization_name": "組織名稱",
"payment_method": "支付方式",
"save": "儲存",
"save_failed": "存異常",
"save_success": "存成功",
"save_failed": "存異常",
"save_success": "存成功",
"status": "狀態",
"submit_failed": "提交失敗",
"submit_success": "提交成功",

View File

@@ -28,10 +28,10 @@
"exchange_success": "兌換成功",
"expiration_time": "到期時間",
"expired": "已過期",
"general_info": "通用信息",
"general_info": "通用資訊",
"group": "群組",
"help_chatbot": "機器人助手",
"help_document": "幫助文",
"help_document": "幫助文",
"knowledge_base_capacity": "知識庫容量",
"manage": "管理",
"member_name": "成員名",
@@ -39,36 +39,36 @@
"new_password": "新密碼",
"notification_receiving": "通知接收",
"old_password": "舊密碼",
"openai_account_configuration": "OpenAI 帳號配置",
"openai_account_configuration": "OpenAI 帳號設定",
"openai_account_setting_exception": "設定 OpenAI 帳號異常",
"package_and_usage": "套餐與用量",
"package_details": "套餐詳",
"package_details": "套餐詳細資訊",
"package_expiry_time": "套餐到期時間",
"package_usage_rules": "套餐使用規則:系統優先使用更進階的套餐,原未用完的套餐將延遲生效",
"password": "密碼",
"password_mismatch": "密碼不一致: 兩次密碼不一致",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字",
"password_mismatch": "密碼不一致兩次密碼不一致",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字",
"password_update_error": "修改密碼異常",
"password_update_success": "修改密碼成功",
"pending_usage": "待使用",
"phone_label": "手機號",
"please_bind_notification_receiving_path": "請先綁定通知接收途徑",
"purchase_extra_package": "購買額外套餐",
"reminder_create_bound_notification_account": "提醒建者綁定通知帳號",
"reminder_create_bound_notification_account": "提醒建者綁定通知帳號",
"resource_usage": "資源用量",
"select_avatar": "點選選擇頭像",
"standard_package_and_extra_resource_package": "包含標準套餐與額外資源包",
"storage_capacity": "儲存量",
"team_balance": "團隊餘額",
"team_info": "團隊信息",
"team_info": "團隊資訊",
"token_validity_period": "積分有效期限一年",
"tokens": "積分",
"type": "類型",
"unlimited": "無限制",
"update_password": "修改密碼",
"update_success_tip": "更新數據成功",
"update_success_tip": "更新資料成功",
"upgrade_package": "升級套餐",
"usage_balance": "使用餘額: 使用餘額",
"usage_balance": "使用餘額使用餘額",
"usage_balance_notice": "由於系統升級,原「自動續費從餘額扣款」模式取消,餘額儲值入口關閉。\n您的餘額可用於購買積分",
"user_account": "帳號",
"user_team_team_name": "團隊",

View File

@@ -1,5 +1,5 @@
{
"notification_detail": "通知詳",
"notification_detail": "通知詳細資訊",
"no_notifications": "暫無通知",
"read": "已讀",
"system": "官方",

View File

@@ -1,52 +1,52 @@
{
"Hunyuan": "騰訊混元",
"api_key": "API 鑰",
"api_key": "API 鑰",
"azure": "Azure",
"base_url": "代理地址",
"channel_name": "道名",
"channel_priority": "優先",
"channel_priority_tip": "優先越高的道,越容易被請求到",
"channel_name": "道名",
"channel_priority": "優先順序",
"channel_priority_tip": "優先順序越高的道,越容易被請求到",
"channel_status": "狀態",
"channel_status_auto_disabled": "自動用",
"channel_status_disabled": "用",
"channel_status_auto_disabled": "自動用",
"channel_status_disabled": "用",
"channel_status_enabled": "啟用",
"channel_status_unknown": "未知",
"channel_type": "廠商",
"clear_model": "清空模型",
"confirm_delete_channel": "確認刪除 【{{name}}】道?",
"copy_model_id_success": "已復制模型id",
"create_channel": "新增道",
"default_url": "默認地址",
"detail": "詳",
"edit_channel": "渠道配置",
"confirm_delete_channel": "確認刪除【{{name}}】道?",
"copy_model_id_success": "已復制模型 id",
"create_channel": "新增道",
"default_url": "預設地址",
"detail": "詳細資訊",
"edit_channel": "管道設定",
"enable_channel": "啟用",
"forbid_channel": "用",
"key_type": "API key 格式:",
"log": "調用日誌",
"log_detail": "日誌詳",
"log_request_id_search": "根據 requestId 搜",
"forbid_channel": "用",
"key_type": "API key 格式",
"log": "呼叫日誌",
"log_detail": "日誌詳細資訊",
"log_request_id_search": "根據 requestId 搜",
"log_status": "狀態",
"mapping": "模型映",
"mapping_tip": "需填寫一個有效 Json。\n可在向實際地址送請求時,對模型進行映。\n例如\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\n當 FastGPT 請求 gpt-4o 模型時,會向實際地址送 gpt-4o-test 的模型,而不是 gpt-4o。",
"maxToken_tip": "模型 max_tokens 參數,如果留空,則代表模型不支該參數。",
"max_temperature_tip": "模型 temperature 參數,不填則代表模型不支 temperature 參數。",
"mapping": "模型映",
"mapping_tip": "需填寫一個有效 Json。\n可在向實際地址送請求時,對模型進行映。\n例如\n{\n \n \"gpt-4o\": \"gpt-4o-test\"\n\n}\n\n當 FastGPT 請求 gpt-4o 模型時,會向實際地址送 gpt-4o-test 的模型,而不是 gpt-4o。",
"maxToken_tip": "模型 max_tokens 參數,如果留空,則代表模型不支該參數。",
"max_temperature_tip": "模型 temperature 參數,不填則代表模型不支 temperature 參數。",
"model": "模型",
"model_name": "模型名",
"model_test": "模型測試",
"model_tokens": "輸入/輸出 Tokens",
"request_at": "請求時間",
"request_duration": "請求時長: {{duration}}s",
"request_duration": "請求時長{{duration}}s",
"retry_times": "重試次數",
"running_test": "測試中",
"search_model": "搜模型",
"select_channel": "選擇道名",
"search_model": "搜模型",
"select_channel": "選擇道名",
"select_model": "選擇模型",
"select_model_placeholder": "選擇該道下可用的模型",
"select_provider_placeholder": "搜廠商",
"select_model_placeholder": "選擇該道下可用的模型",
"select_provider_placeholder": "搜廠商",
"selected_model_empty": "至少選擇一個模型",
"start_test": "批測試{{num}}個模型",
"start_test": "批測試{{num}}個模型",
"test_failed": "有{{num}}個模型報錯",
"vlm_model": "圖片理解模型",
"vlm_model_tip": "用於知識庫中對文中的圖片進行額外的索引生成",
"vlm_model_tip": "用於知識庫中對文中的圖片進行額外的索引生成",
"waiting_test": "等待測試"
}

View File

@@ -5,7 +5,7 @@
"copy_invite_link": "複製邀請連結",
"earnings": "收益(¥)",
"invite_url": "邀請連結",
"invite_url_tip": "透過該連結註冊的好友將永久與你綁定,其儲值時你會獲得一定餘額獎勵。\n \n此外好友使用手機號碼註冊時你將立即獲得 5 元獎勵。\n \n獎勵會送到您的預設團隊。",
"invite_url_tip": "透過該連結註冊的好友將永久與你綁定,其儲值時你會獲得一定餘額獎勵。\n \n此外好友使用手機號碼註冊時你將立即獲得 5 元獎勵。\n \n獎勵會送到您的預設團隊。",
"no_invite_records": "暫無邀請紀錄",
"time": "時間",
"total_invited": "累計邀請人數",

View File

@@ -2,5 +2,5 @@
"language": "語言",
"personalization": "個人化",
"timezone": "時區",
"update_data_success": "更新數據成功"
"update_data_success": "更新資料成功"
}

View File

@@ -1,28 +1,34 @@
{
"1person": "1人",
"1year": "1年",
"30mins": "30分鐘",
"7days": "7天",
"1person": "1 人",
"1year": "1 年",
"30mins": "30 分鐘",
"7days": "7 天",
"accept": "接受",
"action": "操作",
"assign_permission": "權限變更",
"change_department_name": "部門編輯",
"change_member_name": "成員改名",
"confirm_delete_group": "確認刪除群組?",
"confirm_delete_member": "確認刪除成員?",
"confirm_delete_org": "確認刪除該部門?",
"confirm_forbidden": "確認停用",
"confirm_leave_team": "確認離開該團隊? \n退出後,您在該團隊所有的資源轉讓給團隊所有者。",
"confirm_leave_team": "確認離開該團隊? \n結束後,您在該團隊所有的資源轉讓給團隊所有者。",
"copy_link": "複製連結",
"create_department": "創建子部門",
"create_group": "建立群組",
"create_invitation_link": "建立邀請連結",
"create_org": "建部門",
"create_sub_org": "建子部門",
"create_org": "建部門",
"create_sub_org": "建子部門",
"delete": "刪除",
"delete_department": "刪除子部門",
"delete_group": "刪除群組",
"delete_org": "刪除部門",
"edit_info": "編輯訊息",
"edit_member": "編輯用戶",
"edit_member": "編輯使用者",
"edit_member_tip": "成員名",
"edit_org_info": "編輯部門資訊",
"expires": "過期時間",
"forbid_hint": "停用後,該邀請連結將失效。 該操作不可撤銷,是否確認停用?",
"forbid_hint": "停用後,該邀請連結將失效。該操作不可撤銷,是否確認停用?",
"forbid_success": "停用成功",
"forbidden": "停用",
"group": "群組",
@@ -32,26 +38,56 @@
"has_invited": "已邀請",
"ignore": "忽略",
"invitation_copy_link": "【{{systemName}}】 {{userName}} 邀請您加入{{teamName}}團隊,連結:{{url}}",
"invitation_link_auto_clean_hint": "已失效連結將在30天後自動清理",
"invitation_link_auto_clean_hint": "已失效連結將在 30 天後自動清理",
"invitation_link_description": "連結描述",
"invitation_link_list": "連結列表",
"invite_member": "邀請成員",
"invited": "已邀請",
"join_team": "加入團隊",
"kick_out_team": "移除成員",
"label_sync": "標籤同步",
"leave_team_failed": "離開團隊異常",
"log_assign_permission": "【{{name}}】更新了【{{objectName}}】的權限:[應用創建:【{{appCreate}}】, 知識庫:【{{datasetCreate}}】, API密鑰:【{{apiKeyCreate}}】, 管理:【{{manage}}】]",
"log_change_department": "【{{name}}】更新了部門【{{departmentName}}】",
"log_change_member_name": "【{{name}}】將成員【{{memberName}}】重命名為【{{newName}}】",
"log_create_department": "【{{name}}】創建了部門【{{departmentName}}】",
"log_create_group": "【{{name}}】創建了群組【{{groupName}}】",
"log_create_invitation_link": "【{{name}}】創建了邀請鏈接【{{link}}】",
"log_delete_department": "{{name}} 刪除了部門 {{departmentName}}",
"log_delete_group": "{{name}} 刪除了群組 {{groupName}}",
"log_details": "詳情",
"log_join_team": "【{{name}}】通過邀請鏈接【{{link}}】加入團隊",
"log_kick_out_team": "{{name}} 移除了成員 {{memberName}}",
"log_login": "【{{name}}】登錄了系統",
"log_relocate_department": "【{{name}}】移動了部門【{{departmentName}}】",
"log_time": "操作時間",
"log_type": "操作類型",
"log_user": "操作人員",
"login": "登入",
"manage_member": "管理成員",
"member": "成員",
"member_group": "所屬成員組",
"move_member": "移動成員",
"move_org": "行動部門",
"operation_log": "紀錄",
"org": "組織",
"org_description": "介紹",
"org_name": "部門名稱",
"owner": "擁有者",
"permission": "權限",
"permission_apikeyCreate": "建立 API 密鑰",
"permission_apikeyCreate_Tip": "可以建立全域的 APIKey",
"permission_appCreate": "建立應用程式",
"permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
"permission_datasetCreate": "建立知識庫",
"permission_datasetCreate_Tip": "可以在根目錄建立知識庫,(資料夾下的建立權限由資料夾控制)",
"permission_manage": "管理員",
"permission_manage_tip": "可以管理成員、建立群組、管理所有群組、為群組和成員分配權限",
"relocate_department": "部門移動",
"remark": "備註",
"remove_tip": "確認將 {{username}} 移出團隊?",
"retain_admin_permissions": "保留管理員權限",
"search_log": "搜索日誌",
"search_member_group_name": "搜尋成員/群組名稱",
"total_team_members": "共 {{amount}} 名成員",
"transfer_ownership": "轉讓所有者",

View File

@@ -1,16 +1,16 @@
{
"configured": "已配置",
"error.no_permission": "請聯管理員配置",
"configured": "已設定",
"error.no_permission": "請聯管理員設定",
"laf_account": "af 帳號",
"no_intro": "暫無說明",
"not_configured": "未配置",
"open_api_notice": "可以填寫 OpenAI/OneAPI 的相關金鑰。\n如果你填寫了該內容在線上平使用【 AI 對話】、【問題分類】和【內容提取】將會走你填寫的 Key不會計費用。\n請注意你的 Key 是否有存取對應模型的權限。 \nGPT 模型可以選擇 FastAI 。",
"not_configured": "未設定",
"open_api_notice": "可以填寫 OpenAI/OneAPI 的相關金鑰。\n如果你填寫了該內容在線上平使用【AI 對話】、【問題分類】和【內容提取】將會走你填寫的 Key不會計費用。\n請注意你的 Key 是否有存取對應模型的權限。 \nGPT 模型可以選擇 FastAI。",
"openai_account_configuration": "OpenAI/OneAPI 帳號",
"request_address_notice": "請求地址,預設為 openai 官方。可填中轉位址,未自動補全 \"v1\"",
"third_party_account": "第三方帳號",
"third_party_account_desc": "管理員可以在這裡配置第三方帳號或變,該帳號將被團隊所有人使用",
"third_party_account_desc": "管理員可以在這裡設定第三方帳號或變,該帳號將被團隊所有人使用",
"unavailable": "取得使用量異常",
"usage": "使用量:",
"value_not_return_tip": "參數配置後,不會再次返回前端,無需洩漏給其他成員",
"value_placeholder": "輸入參數值。\n輸入空值表示刪除該配置。"
"value_not_return_tip": "參數設定後,不會再次返回前端,無需洩漏給其他成員",
"value_placeholder": "輸入參數值。\n輸入空值表示刪除該設定。"
}

View File

@@ -4,17 +4,17 @@
"app_name": "應用程式名",
"auto_index": "索引增強",
"billing_module": "扣費模組",
"confirm_export": "共篩選出 {{total}} 條數據,是否確認出?",
"current_filter_conditions": "前篩選條件:",
"confirm_export": "共篩選出 {{total}} 條資料,是否確認出?",
"current_filter_conditions": "前篩選條件:",
"dashboard": "儀表板",
"details": "詳",
"details": "詳細資訊",
"dingtalk": "釘釘",
"duration_seconds": "時長(秒)",
"embedding_index": "索引生成",
"every_day": "天",
"every_month": "月",
"export_confirm": "出確認",
"export_confirm_tip": "前共 {{total}} 筆使用記錄,確認出?",
"export_confirm": "出確認",
"export_confirm_tip": "前共 {{total}} 筆使用記錄,確認出?",
"export_title": "時間,成員,類型,項目名,AI 積分消耗",
"feishu": "飛書",
"generation_time": "生成時間",
@@ -42,7 +42,7 @@
"total_points": "AI 積分消耗",
"total_points_consumed": "AI 積分消耗",
"total_usage": "總消耗",
"usage_detail": "使用詳",
"usage_detail": "使用詳細資訊",
"user_type": "類型",
"wecom": "企業微信"
}

View File

@@ -3,16 +3,16 @@
"Run": "執行",
"Team Tags Set": "團隊標籤",
"Team_Tags": "團隊標籤",
"ai_point_price": "AI積分計費",
"ai_point_price": "AI 積分計費",
"ai_settings": "AI 設定",
"all_apps": "所有應用程式",
"app.Version name": "版本名稱",
"app.error.publish_unExist_app": "發布失敗,請檢查工具調用是否正常",
"app.error.unExist_app": "部分組件缺失,請刪除",
"app.error.publish_unExist_app": "發布失敗,請檢查工具呼叫是否正常",
"app.error.unExist_app": "部分元件遺失,請刪除",
"app.modules.click to update": "點選更新",
"app.modules.has new version": "有新版本",
"app.modules.not_found": "組件缺失",
"app.modules.not_found_tips": "系統內無法查到該件,請刪除,否則流程無法正常運",
"app.modules.not_found": "元件遺失",
"app.modules.not_found_tips": "系統內無法查到該件,請刪除,否則流程無法正常運",
"app.version_current": "目前版本",
"app.version_initial": "初始版本",
"app.version_name_tips": "版本名稱不能空白",
@@ -20,23 +20,23 @@
"app.version_publish_tips": "此版本將儲存至團隊雲端,同步給整個團隊,同時更新所有發布通道的應用程式版本",
"app_detail": "應用程式詳細資訊",
"auto_execute": "自動執行",
"auto_execute_default_prompt_placeholder": "自動執行時,送的預設問題",
"auto_execute_default_prompt_placeholder": "自動執行時,送的預設問題",
"auto_execute_tip": "開啟後,使用者進入對話式介面將自動觸發工作流程。\n執行順序1、對話開場白2、全域變數3、自動執行。",
"auto_save": "自動儲存",
"chat_debug": "聊天預覽",
"chat_logs": "對話紀錄",
"chat_logs_tips": "紀錄會記錄此應用程式的線上、分享和 API需填寫 chatId對話紀錄",
"config_ai_model_params": "點選配置 AI 模型相關屬性",
"config_ai_model_params": "點選設定 AI 模型相關屬性",
"config_file_upload": "點選設定檔案上傳規則",
"config_question_guide": "配置猜你想問",
"config_question_guide": "設定猜你想問",
"confirm_copy_app_tip": "系統將為您建立一個相同設定的應用程式,但權限不會複製,請確認!",
"confirm_del_app_tip": "確認刪除【{{name}}】及其所有聊天紀錄?",
"confirm_delete_folder_tip": "確認刪除這個資料夾?將會刪除它底下所有應用程式及對應的對話紀錄,請確認!",
"copy_one_app": "建立副本",
"core.app.QG.Switch": "啟用猜你想問",
"core.dataset.import.Custom prompt": "自訂提示詞",
"create_by_curl": "從 CURL 建",
"create_by_template": "從模板創建",
"create_by_curl": "從 CURL 建",
"create_by_template": "從範本建立",
"create_copy_success": "建立副本成功",
"create_empty_app": "建立空白應用程式",
"create_empty_plugin": "建立空白外掛",
@@ -74,22 +74,22 @@
"interval.4_hours": "每 4 小時",
"interval.6_hours": "每 6 小時",
"interval.per_hour": "每小時",
"intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或配置。",
"intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或設定。",
"invalid_json_format": "JSON 格式錯誤",
"llm_not_support_vision": "這個模型不支援圖片辨識",
"llm_use_vision": "圖片辨識",
"llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。",
"logs_chat_user": "使用者",
"logs_empty": "還沒有紀錄喔~",
"logs_export_confirm_tip": "前共 {{total}} 條對話記錄,確認出?",
"logs_export_title": "時間,來源,使用者,聯方式,標題,息總數,用戶贊同饋,用戶反對饋,自定義饋,標註答案,對話詳",
"logs_export_confirm_tip": "前共 {{total}} 條對話記錄,確認出?",
"logs_export_title": "時間,來源,使用者,聯方式,標題,息總數,使用者贊同饋,使用者反對饋,自定義饋,標註答案,對話詳細資訊",
"logs_message_total": "訊息總數",
"logs_source": "源",
"logs_source": "源",
"logs_title": "標題",
"look_ai_point_price": "查看所有模型計費標準",
"look_ai_point_price": "檢視所有模型計費標準",
"mark_count": "標記答案數量",
"max_histories_number": "記憶輪數",
"max_histories_number_tip": "模型最多攜帶多少輪對話進入記憶中,如果記憶超出模型上下文,系統會強制截斷。\n所以儘管配置 30 輪對話,實際運時候,不一定會達到 30 輪。",
"max_histories_number_tip": "模型最多攜帶多少輪對話進入記憶中,如果記憶超出模型上下文,系統會強制截斷。\n所以儘管設定 30 輪對話,實際運時候,不一定會達到 30 輪。",
"max_tokens": "回覆上限",
"module.Custom Title Tip": "這個標題會顯示在對話過程中",
"module.No Modules": "找不到外掛",
@@ -104,10 +104,10 @@
"open_auto_execute": "啟用自動執行",
"open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。",
"or_drag_JSON": "或拖曳 JSON 檔案",
"paste_config_or_drag": "貼上配置或拖入 JSON 文件",
"pdf_enhance_parse": "PDF增強解析",
"paste_config_or_drag": "貼上設定或拖入 JSON 文件",
"pdf_enhance_parse": "PDF 增強解析",
"pdf_enhance_parse_price": "{{price}}積分/頁",
"pdf_enhance_parse_tips": "調用 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
"pdf_enhance_parse_tips": "呼叫 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
"permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限",
"permission.des.read": "可以使用這個應用程式進行對話",
"permission.des.write": "可以檢視和編輯應用程式",
@@ -120,16 +120,16 @@
"publish_success": "發布成功",
"question_guide_tip": "對話結束後,會為你產生 3 個引導性問題。",
"reasoning_response": "輸出思考",
"response_format": "回格式",
"saved_success": "存成功!\n如需在外部使用該版本請點“儲存並發布”",
"response_format": "回格式",
"saved_success": "存成功!\n如需在外部使用該版本請點“儲存並發布”",
"search_app": "搜尋應用程式",
"setting_app": "應用程式設定",
"setting_plugin": "外掛設定",
"show_top_p_tip": "用溫度樣的替代方法稱為Nucleus該模型考慮了具有TOP_P率質量質量的令牌的結果。\n因此0.1表示僅考慮包含最高率質量的令牌。\n默認為 1。",
"simple_tool_tips": "該插件含有特殊輸入,暫不支被簡易應用調用",
"show_top_p_tip": "用溫度樣的替代方法,稱為 Nucleus樣,該模型考慮了具有 TOP_P率質量質量的令牌的結果。\n因此0.1 表示僅考慮包含最高率質量的令牌。\n預設為 1。",
"simple_tool_tips": "該外掛含有特殊輸入,暫不支被簡易應用呼叫",
"source_updateTime": "更新時間",
"stop_sign": "停止序列",
"stop_sign_placeholder": "多個序列號過 | 隔開例如aaa|stop",
"stop_sign_placeholder": "多個序列號過 | 隔開例如aaa|stop",
"stream_response": "流輸出",
"stream_response_tip": "關閉該開關​​,可以強制模型使用非流模式,並且不會直接進行內容輸出。\n可在 AI 回覆的輸出中,取得本次模型輸出的內容進行二次處理。",
"temperature": "溫度",
@@ -152,7 +152,7 @@
"templateMarket.templateTags.Roleplay": "角色扮演",
"templateMarket.templateTags.Web_search": "網路搜尋",
"templateMarket.templateTags.Writing": "文字創作",
"templateMarket.template_guide": "模板說明",
"templateMarket.template_guide": "範本說明",
"template_market": "範本市集",
"template_market_description": "在範本市集探索更多玩法,設定教學與使用指引,帶您理解並上手各種應用程式",
"template_market_empty_data": "找不到合適的範本",
@@ -162,7 +162,7 @@
"transition_to_workflow_create_new_placeholder": "建立新的應用程式,而不是修改目前應用程式",
"transition_to_workflow_create_new_tip": "轉換成工作流程後,將無法轉換回簡易模式,請確認!",
"tts_ai_model": "使用語音合成模型",
"tts_browser": "瀏覽器自帶(免費)",
"tts_browser": "瀏覽器自帶 (免費)",
"tts_close": "關閉",
"type.All": "全部",
"type.Create http plugin tip": "透過 OpenAPI Schema 批次建立外掛,相容 GPTs 格式",
@@ -173,7 +173,7 @@
"type.Create workflow bot": "建立工作流程",
"type.Create workflow tip": "透過低程式碼的方式,建立邏輯複雜的多輪對話 AI 應用程式,建議進階使用者使用",
"type.Http plugin": "HTTP 外掛",
"type.Import from json": "入 JSON 配置",
"type.Import from json": "入 JSON 設定",
"type.Import from json tip": "透過 JSON 設定文件,直接建立應用",
"type.Plugin": "外掛",
"type.Simple bot": "簡易應用程式",

View File

@@ -5,20 +5,20 @@
"ai_reasoning": "思考過程",
"back_to_text": "返回輸入",
"chat.quote.No Data": "找不到該文件",
"chat.quote.deleted": "該數據已被刪除~",
"chat.quote.deleted": "該資料已被刪除~",
"chat_history": "對話紀錄",
"chat_input_guide_lexicon_is_empty": "尚未設定詞彙庫",
"chat_test_app": "調試-{{name}}",
"chat_test_app": "除錯-{{name}}",
"citations": "{{num}} 筆引用",
"click_contextual_preview": "點選檢視上下文預覽",
"completion_finish_close": "連接斷開",
"completion_finish_content_filter": "觸發安全風控",
"completion_finish_function_call": "函數調用",
"completion_finish_length": "超出回限制",
"completion_finish_function_call": "函式呼叫",
"completion_finish_length": "超出回限制",
"completion_finish_null": "未知",
"completion_finish_reason": "完成原因",
"completion_finish_stop": "正常完成",
"completion_finish_tool_calls": "工具調用",
"completion_finish_tool_calls": "工具呼叫",
"config_input_guide": "設定輸入導引",
"config_input_guide_lexicon": "設定詞彙庫",
"config_input_guide_lexicon_title": "設定詞彙庫",
@@ -27,10 +27,10 @@
"contextual_preview": "上下文預覽 {{num}} 筆",
"csv_input_lexicon_tip": "僅支援 CSV 批次匯入,點選下載範本",
"custom_input_guide_url": "自訂詞彙庫網址",
"data_source": "來源知識庫: {{name}}",
"data_source": "來源知識庫{{name}}",
"dataset_quote_type error": "知識庫引用類型錯誤,正確類型:{ datasetId: string }[]",
"delete_all_input_guide_confirm": "確定要清除輸入導引詞彙庫嗎?",
"download_chunks": "下載數據",
"download_chunks": "下載資料",
"empty_directory": "此目錄中已無項目可選~",
"file_amount_over": "超出檔案數量上限 {{max}}",
"file_input": "檔案輸入",
@@ -52,12 +52,12 @@
"not_select_file": "尚未選取檔案",
"plugins_output": "外掛程式輸出",
"press_to_speak": "按住說話",
"query_extension_IO_tokens": "問題化輸入/輸出 Tokens",
"query_extension_IO_tokens": "問題最佳化輸入/輸出 Tokens",
"question_tip": "由上至下,各個模組的回應順序",
"read_raw_source": "開原文",
"read_raw_source": "開原文",
"reasoning_text": "思考過程",
"release_cancel": "鬆開取消",
"release_send": "鬆開送,上滑取消",
"release_send": "鬆開送,上滑取消",
"response.child total points": "子工作流程點數消耗",
"response.dataset_concat_length": "合併總數",
"response.node_inputs": "節點輸入",
@@ -72,7 +72,7 @@
"to_dataset": "前往知識庫",
"unsupported_file_type": "不支援的檔案類型",
"upload": "上傳",
"variable_invisable_in_share": "自定義變在免登錄鏈接中不可見",
"variable_invisable_in_share": "自定義變在免登入連結中不可見",
"view_citations": "檢視引用",
"web_site_sync": "網站同步"
}

View File

@@ -1,10 +1,10 @@
{
"App": "應用程式",
"Click_to_expand": "點擊查看詳情",
"Click_to_expand": "點選檢視詳細資訊",
"Download": "下載",
"Export": "匯出",
"FAQ.ai_point_a": "每次呼叫 AI 模型時,都會消耗一定數量的 AI 點數。詳細的計算標準請參考上方的「AI 點數計算標準」。\nToken 計算採用與 GPT3.5 相同的公式1 Token ≈ 0.7 個中文字 ≈ 0.9 個英文單字,連續出現的字元可能會被視為 1 個 Token。",
"FAQ.ai_point_expire_a": "會過期。目前方案過期後AI 點數將會清空並更新為新方案的 AI 點數。年度方案的 AI 點數有效期為一年,而不是每個月重。",
"FAQ.ai_point_expire_a": "會過期。目前方案過期後AI 點數將會清空並更新為新方案的 AI 點數。年度方案的 AI 點數有效期為一年,而不是每個月重。",
"FAQ.ai_point_expire_q": "AI 點數會過期嗎?",
"FAQ.ai_point_q": "什麼是 AI 點數?",
"FAQ.check_subscription_a": "帳號 - 個人資訊 - 方案詳細資訊 - 使用狀況。您可以檢視已訂閱方案的生效和到期時間。當付費方案到期後,將自動切換為免費版。",
@@ -46,7 +46,7 @@
"click_to_resume": "點選繼續",
"code_editor": "程式碼編輯器",
"code_error.account_error": "帳號名稱或密碼錯誤",
"code_error.account_not_found": "用戶未註冊",
"code_error.account_not_found": "使用者未註冊",
"code_error.app_error.invalid_app_type": "無效的應用程式類型",
"code_error.app_error.invalid_owner": "非法的應用程式擁有者",
"code_error.app_error.not_exist": "應用程式不存在",
@@ -90,7 +90,7 @@
"code_error.team_error.group_name_duplicate": "群組名稱重複",
"code_error.team_error.group_name_empty": "群組名稱不能為空",
"code_error.team_error.group_not_exist": "群組不存在",
"code_error.team_error.invitation_link_invalid": "邀請鏈接已失效",
"code_error.team_error.invitation_link_invalid": "邀請連結已失效",
"code_error.team_error.not_user": "找不到該成員",
"code_error.team_error.org_member_duplicated": "重複的組織成員",
"code_error.team_error.org_member_not_exist": "組織成員不存在",
@@ -102,7 +102,7 @@
"code_error.team_error.too_many_invitations": "您的有效邀請連結數已達上限,請先清理連結",
"code_error.team_error.un_auth": "無權操作此團隊",
"code_error.team_error.user_not_active": "使用者未接受或已離開團隊",
"code_error.team_error.website_sync_not_enough": "免費版無法使用Web站點同步~",
"code_error.team_error.website_sync_not_enough": "免費版無法使用 Web 站點同步~",
"code_error.team_error.you_have_been_in_the_team": "你已經在該團隊中",
"code_error.token_error_code.403": "登入狀態無效,請重新登入",
"code_error.user_error.balance_not_enough": "帳戶餘額不足",
@@ -118,7 +118,7 @@
"common.Cancel": "取消",
"common.Choose": "選擇",
"common.Close": "關閉",
"common.Code": "碼",
"common.Code": "原始碼",
"common.Config": "設定",
"common.Confirm": "確認",
"common.Confirm Create": "確認建立",
@@ -291,7 +291,7 @@
"core.app.Chat Variable": "對話變數",
"core.app.Config schedule plan": "設定排程執行",
"core.app.Config whisper": "設定語音輸入",
"core.app.Config_auto_execute": "點選配置自動執行規則",
"core.app.Config_auto_execute": "點選設定自動執行規則",
"core.app.Interval timer config": "排程執行設定",
"core.app.Interval timer run": "排程執行",
"core.app.Interval timer tip": "可排程執行應用程式",
@@ -304,7 +304,7 @@
"core.app.QG.Custom prompt tip1": "為確保生成的內容遵循正確格式,",
"core.app.QG.Custom prompt tip2": "【黃色部分提示詞】",
"core.app.QG.Custom prompt tip3": "不允許修改",
"core.app.QG.Fixed Prompt": "請嚴格遵循格式規則:以 JSON 格式返回題目:\n['問題1''問題2''問題3']。",
"core.app.QG.Fixed Prompt": "請嚴格遵循格式規則:以 JSON 格式返回題目:\n['問題 1''問題 2''問題 3']。",
"core.app.Question Guide": "猜你想問",
"core.app.Quote prompt": "引用範本提示詞",
"core.app.Quote templates": "引用內容範本",
@@ -373,7 +373,7 @@
"core.app.tip.Add a intro to app": "快來為應用程式寫一個介紹",
"core.app.tip.chatNodeSystemPromptTip": "在此輸入提示詞",
"core.app.tip.systemPromptTip": "模型固定的引導詞,透過調整此內容,可以引導模型對話方向。此內容會固定在上下文的開頭。可透過輸入 / 插入變數。\n如果關聯了知識庫您還可以透過適當的描述引導模型何時去呼叫知識庫搜尋。例如\n您是電影《星際效應》的助手當使用者詢問與《星際效應》相關的內容時請搜尋知識庫並根據搜尋結果回答。",
"core.app.tip.variableTip": "可以在對話開始前,要求用戶填寫一些內容作為本輪對話的特定變。\n該模位於開場引導之後。\n\n輸入框中過 / 激活變量選擇,例如:提示詞、限定詞等",
"core.app.tip.variableTip": "可以在對話開始前,要求使用者填寫一些內容作為本輪對話的特定變。\n該模位於開場引導之後。\n\n輸入框中過 / 啟用變數選擇,例如:提示詞、限定詞等",
"core.app.tip.welcomeTextTip": "每次對話開始前,傳送一段初始內容。支援標準 Markdown 語法。可使用的額外標記:\n[快速按鍵]:使用者點選後可以直接傳送該問題",
"core.app.tool_label.doc": "使用文件",
"core.app.tool_label.github": "GitHub 網址",
@@ -452,7 +452,7 @@
"core.chat.markdown.Edit Question": "編輯問題",
"core.chat.markdown.Quick Question": "點我立即發問",
"core.chat.markdown.Send Question": "傳送問題",
"core.chat.module_unexist": "行失敗:應用缺失組件",
"core.chat.module_unexist": "行失敗:應用遺失元件",
"core.chat.quote.Quote Tip": "此處僅顯示實際引用內容,若資料有更新,此處不會即時更新",
"core.chat.quote.Read Quote": "檢視引用",
"core.chat.quote.afterUpdate": "更新後",
@@ -625,10 +625,10 @@
"core.dataset.search.score.reRank desc": "透過重新排名模型計算句子之間的關聯度,範圍為 0 到 1。",
"core.dataset.search.score.rrf": "綜合排名",
"core.dataset.search.score.rrf desc": "使用倒數排名融合方法,合併多個搜尋結果。",
"core.dataset.search.search mode": "搜方式",
"core.dataset.search.search mode": "搜方式",
"core.dataset.status.active": "已就緒",
"core.dataset.status.syncing": "同步中",
"core.dataset.status.waiting": "排中",
"core.dataset.status.waiting": "排中",
"core.dataset.test.Batch test": "批次測試",
"core.dataset.test.Batch test Placeholder": "選擇一個 CSV 檔案",
"core.dataset.test.Search Test": "搜尋測試",
@@ -644,7 +644,7 @@
"core.dataset.training.Agent queue": "問答訓練排隊中",
"core.dataset.training.Auto mode": "補充索引",
"core.dataset.training.Auto mode Tip": "透過子索引以及呼叫模型產生相關問題與摘要,來增加資料區塊的語意豐富度,更有利於檢索。需要消耗更多的儲存空間並增加 AI 呼叫次數。",
"core.dataset.training.Chunk mode": "直接分",
"core.dataset.training.Chunk mode": "直接分",
"core.dataset.training.Full": "預計 20 分鐘以上",
"core.dataset.training.Leisure": "閒置",
"core.dataset.training.QA mode": "問答對提取",
@@ -689,7 +689,7 @@
"core.module.Variable": "全域變數",
"core.module.Variable Setting": "變數設定",
"core.module.edit.Field Name Cannot Be Empty": "欄位名稱不能為空",
"core.module.edit.Field Value Type Cannot Be Empty": "可選數據類型不能為空",
"core.module.edit.Field Value Type Cannot Be Empty": "可選資料類型不能為空",
"core.module.extract.Add field": "新增欄位",
"core.module.extract.Enum Description": "列舉此欄位可能的值,每行一個",
"core.module.extract.Enum Value": "列舉值",
@@ -743,7 +743,7 @@
"core.module.template.AI support tool tip": "支援函式呼叫的模型可以更好地使用工具呼叫。",
"core.module.template.Basic Node": "基本功能",
"core.module.template.Query extension": "問題最佳化",
"core.module.template.System Plugin": "系統插件",
"core.module.template.System Plugin": "系統外掛",
"core.module.template.System input module": "系統輸入模組",
"core.module.template.Team app": "團隊應用程式",
"core.module.template.Tool module": "工具",
@@ -781,7 +781,7 @@
"core.view_chat_detail": "檢視對話詳細資料",
"core.workflow.Can not delete node": "此節點不允許刪除",
"core.workflow.Change input type tip": "修改輸入類型將清空已填寫的值,請確認!",
"core.workflow.Check Failed": "工作流校驗失敗,請檢查是否失、缺值,連線是否正常",
"core.workflow.Check Failed": "工作流校驗失敗,請檢查是否失、缺值,連線是否正常",
"core.workflow.Confirm stop debug": "確認停止除錯?除錯資訊將不會保留。",
"core.workflow.Copy node": "已複製節點",
"core.workflow.Custom inputs": "自訂輸入",
@@ -826,14 +826,14 @@
"core.workflow.template.Interactive": "互動",
"core.workflow.template.Multimodal": "多模態",
"core.workflow.template.Search": "搜尋",
"core.workflow.tool.Handle": "工具連接器",
"core.workflow.tool.Handle": "工具聯結器",
"core.workflow.tool.Select Tool": "選擇工具",
"core.workflow.value": "值",
"core.workflow.variable": "變數",
"create": "建立",
"cron_job_run_app": "排程任務",
"data_index_custom": "自定義索引",
"data_index_default": "默認索引",
"data_index_default": "預設索引",
"data_index_image": "圖片索引",
"data_index_question": "推測問題索引",
"data_index_summary": "摘要索引",
@@ -879,25 +879,25 @@
"dataset_data_input_chunk_content": "內容",
"dataset_data_input_q": "問題",
"dataset_data_input_qa": "QA 模式",
"dataset_text_model_tip": "用於知識庫預處理階段的文處理,例如自動補充索引、問答對提取。",
"deep_rag_search": "深度搜",
"dataset_text_model_tip": "用於知識庫預處理階段的文處理,例如自動補充索引、問答對提取。",
"deep_rag_search": "深度搜",
"delete_api": "確認刪除此 API 金鑰?\n刪除後該金鑰將立即失效對應的對話記錄不會被刪除請確認",
"embedding_model_not_config": "測到沒有可用的索引模型",
"embedding_model_not_config": "測到沒有可用的索引模型",
"error.Create failed": "建立失敗",
"error.code_error": "驗證碼錯誤",
"error.fileNotFound": "找不到檔案",
"error.inheritPermissionError": "繼承權限錯誤",
"error.invalid_params": "參數無效",
"error.missingParams": "參數不足",
"error.send_auth_code_too_frequently": "請勿頻繁取驗證碼",
"error.send_auth_code_too_frequently": "請勿頻繁取驗證碼",
"error.too_many_request": "請求太頻繁了,請稍後重試",
"error.upload_file_error_filename": "{{name}} 上傳失敗",
"error.upload_image_error": "上傳文件失敗",
"error.username_empty": "帳號不能為空",
"error_collection_not_exist": "集合不存在",
"error_embedding_not_config": "未配置索引模型",
"error_llm_not_config": "未配置文件理解模型",
"error_vlm_not_config": "未配置圖片理解模型",
"error_embedding_not_config": "未設定索引模型",
"error_llm_not_config": "未設定文件理解模型",
"error_vlm_not_config": "未設定圖片理解模型",
"extraction_results": "提取結果",
"field_name": "欄位名稱",
"free": "免費",
@@ -913,7 +913,7 @@
"info.include": "包含標準方案與額外資源包",
"info.node_info": "調整此模組會影響工具呼叫的時機。\n您可以透過精確描述此模組功能引導模型進行工具呼叫。",
"info.old_version_attention": "偵測到您的進階編排為舊版本,系統將自動格式化為新版工作流程。\n\n由於版本差異較大可能導致某些工作流程無法正常排列。請重新手動連接工作流程。如果仍然異常請嘗試刪除對應節點後重新新增。\n\n您可以直接點選除錯來測試工作流程。除錯完成後點選發布。直到您點選發布新工作流程才會真正儲存生效。\n\n在您發布新工作流程之前自動儲存將不會生效。",
"info.open_api_notice": "您可以填寫 OpenAI/OneAPI 的相關金鑰。如果您填寫了此內容,在線上平使用【AI 對話】、【問題分類】和【內容提取】將使用您填寫的金鑰不會計費。請確認您的金鑰是否有存取對應模型的權限。GPT 模型可以選擇 FastAI。",
"info.open_api_notice": "您可以填寫 OpenAI/OneAPI 的相關金鑰。如果您填寫了此內容,在線上平使用【AI 對話】、【問題分類】和【內容提取】將使用您填寫的金鑰不會計費。請確認您的金鑰是否有存取對應模型的權限。GPT 模型可以選擇 FastAI。",
"info.open_api_placeholder": "請求網址,預設為 OpenAI 官方。可填寫中轉網址,未自動補全 \"v1\"",
"info.resource": "資源使用量",
"invalid_variable": "無效變數",
@@ -923,7 +923,7 @@
"item_name": "欄位名稱",
"just_now": "剛剛",
"key_repetition": "鍵值重複",
"llm_model_not_config": "測到沒有可用的語言模型",
"llm_model_not_config": "測到沒有可用的語言模型",
"max_quote_tokens": "引用上限",
"max_quote_tokens_tips": "單次搜尋最大的 token 數量,中文約 1 字=1.7 tokens英文約 1 字=1 token",
"min_similarity": "最低相關度",
@@ -964,14 +964,14 @@
"new_create": "建立新項目",
"no": "否",
"no_laf_env": "系統未設定 LAF 環境",
"not_model_config": "未配置相關模型",
"not_model_config": "未設定相關模型",
"not_yet_introduced": "暫無介紹",
"option": "選項",
"pay.amount": "金額",
"pay.package_tip.buy": "您購買的方案等級低於目前方案,該方案將在目前方案過期後生效。\n您可在帳戶 - 個人資訊 - 方案詳細資訊中檢視方案使用情況。",
"pay.package_tip.renewal": "您正在續約方案。您可在帳戶 - 個人資訊 - 方案詳細資訊中檢視方案使用情況。",
"pay.package_tip.upgrade": "您購買的方案等級高於目前方案,該方案將立即生效,目前方案將延後生效。您可在帳戶 - 個人資訊 - 方案詳細資訊中檢視方案使用情況。",
"pay.wechat": "請微信掃碼付款: {{price}}元\n\n付款完成前請勿關閉頁面",
"pay.wechat": "請微信掃碼付款{{price}}元\n\n付款完成前請勿關閉頁面",
"pay.yuan": "{{amount}} 元",
"permission.Collaborator": "協作者",
"permission.Default permission": "預設權限",
@@ -1022,7 +1022,7 @@
"plugin.path": "路徑",
"prompt_input_placeholder": "請輸入提示詞",
"question_feedback": "工單諮詢",
"read_quote": "查看引用",
"read_quote": "檢視引用",
"required": "必填",
"rerank_weight": "重排權重",
"resume_failed": "恢復失敗",
@@ -1040,7 +1040,7 @@
"support.outlink.Max usage points tip": "此連結最多允許使用多少點數,超出後將無法使用。-1 代表無限制。",
"support.outlink.Usage points": "點數消耗",
"support.outlink.share.Chat_quote_reader": "全文閱讀器",
"support.outlink.share.Full_text tips": "允許閱讀該引用片段來源的完整數據集",
"support.outlink.share.Full_text tips": "允許閱讀該引用片段來源的完整資料集",
"support.outlink.share.Response Quote": "回傳引用",
"support.outlink.share.Response Quote tips": "在分享連結中回傳引用內容,但不允許使用者下載原始文件",
"support.outlink.share.running_node": "執行節點",
@@ -1090,8 +1090,8 @@
"support.user.team.Team Tags Async Success": "同步完成",
"support.user.team.member": "成員",
"support.wallet.Ai point every thousand tokens": "{{points}} 點數/1K tokens",
"support.wallet.Ai point every thousand tokens_input": "輸入:{{points}} 分/1K tokens",
"support.wallet.Ai point every thousand tokens_output": "輸出:{{points}} 分/1K tokens",
"support.wallet.Ai point every thousand tokens_input": "輸入:{{points}} 分/1K tokens",
"support.wallet.Ai point every thousand tokens_output": "輸出:{{points}} 分/1K tokens",
"support.wallet.Amount": "金額",
"support.wallet.App_amount_not_sufficient": "您的應用數量已達上限,請升級套餐後繼續使用。",
"support.wallet.Buy": "購買",

View File

@@ -4,7 +4,7 @@
"api_file": "API 檔案庫",
"api_url": "介面位址",
"auto_indexes": "自動生成補充索引",
"auto_indexes_tips": "過大模型進行額外索引生成,提高語義豐富度,提高檢索的精度。",
"auto_indexes_tips": "過大模型進行額外索引生成,提高語義豐富度,提高檢索的精度。",
"auto_training_queue": "增強索引排隊",
"chunk_max_tokens": "分塊上限",
"chunk_size": "分塊大小",
@@ -12,8 +12,8 @@
"collection.Create update time": "建立/更新時間",
"collection.Training type": "分段模式",
"collection.training_type": "處理模式",
"collection_data_count": "數據量",
"collection_metadata_custom_pdf_parse": "PDF增強解析",
"collection_data_count": "資料量",
"collection_metadata_custom_pdf_parse": "PDF 增強解析",
"collection_metadata_image_parse": "圖片標註",
"collection_not_support_retraining": "此集合類型不支援重新調整參數",
"collection_not_support_sync": "該集合不支援同步",
@@ -22,13 +22,13 @@
"collection_tags": "集合標籤",
"common_dataset": "通用資料集",
"common_dataset_desc": "可透過匯入檔案、網頁連結或手動輸入的方式建立資料集",
"config_sync_schedule": "配置定時同步",
"config_sync_schedule": "設定定時同步",
"confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作需要對您資料集內所有資料重新建立索引可能需要較長時間請確保帳號內剩餘點數充足。\n\n此外您還需要注意修改使用此資料集的應用程式避免與其他索引模型資料集混用。",
"core.dataset.import.Adjust parameters": "調整參數",
"custom_data_process_params": "自訂",
"custom_data_process_params_desc": "自訂資料處理規則",
"custom_split_sign_tip": "允許你根據自定義的分隔符進行分塊。\n通常用於已處理好的數據,使用特定的分隔符來精確分塊。\n可以使用 | 符號表示多個分割符,例如:“。|.” 表示中英文句號。\n\n盡量避免使用正則相關特殊符號例如: * () [] {} 等。",
"data_amount": "{{dataAmount}} 組數據, {{indexAmount}} 組索引",
"custom_split_sign_tip": "允許你根據自定義的分隔符進行分塊。\n通常用於已處理好的資料,使用特定的分隔符來精確分塊。\n可以使用 | 符號表示多個分割符,例如:“。|.”表示中英文句號。\n\n盡量避免使用正則相關特殊符號例如* () [] {} 等。",
"data_amount": "{{dataAmount}} 組資料,{{indexAmount}} 組索引",
"data_error_amount": "{{errorAmount}} 組訓練異常",
"data_index_num": "索引 {{index}}",
"data_process_params": "處理參數",
@@ -37,8 +37,8 @@
"dataset.Completed": "完成",
"dataset.Delete_Chunk": "刪除",
"dataset.Edit_Chunk": "編輯",
"dataset.Error_Message": "報錯信息",
"dataset.No_Error": "暫無異常信息",
"dataset.Error_Message": "報錯資訊",
"dataset.No_Error": "暫無異常資訊",
"dataset.Operation": "操作",
"dataset.ReTrain": "重試",
"dataset.Training Process": "訓練狀態",
@@ -46,13 +46,13 @@
"dataset.Training_Errors": "異常",
"dataset.Training_QA": "{{count}} 組問答對訓練中",
"dataset.Training_Status": "訓練狀態",
"dataset.Training_Waiting": "需等待 {{count}} 組數據",
"dataset.Unsupported operation": "操作不支",
"dataset.Training_Waiting": "需等待 {{count}} 組資料",
"dataset.Unsupported operation": "操作不支",
"dataset.no_collections": "尚無資料集",
"dataset.no_tags": "尚無標籤",
"default_params": "預設",
"default_params_desc": "使用系統默認的參數和規則",
"edit_dataset_config": "編輯知識庫配置",
"default_params_desc": "使用系統預設的參數和規則",
"edit_dataset_config": "編輯知識庫設定",
"enhanced_indexes": "索引增強",
"error.collectionNotFound": "找不到集合",
"external_file": "外部檔案庫",
@@ -62,44 +62,44 @@
"external_read_url_tip": "可以設定您檔案庫的讀取網址,方便對使用者進行讀取權限驗證。目前可使用 {{fileId}} 變數來代表外部檔案識別碼。",
"external_url": "檔案存取網址",
"feishu_dataset": "飛書知識庫",
"feishu_dataset_config": "配置飛書知識庫",
"feishu_dataset_desc": "可通過配置飛書文權限,使用飛書文檔構建知識庫,文不會進行二次儲",
"feishu_dataset_config": "設定飛書知識庫",
"feishu_dataset_desc": "可透過設定飛書文權限,使用飛書文件建構知識庫,文不會進行二次儲",
"file_list": "文件列表",
"file_model_function_tip": "用於增強索引和問答生成",
"filename": "檔案名稱",
"folder_dataset": "資料夾",
"image_auto_parse": "圖片自動索引",
"image_auto_parse_tips": "調用 VLM 自動標註文裡的圖片,並生成額外的檢索索引",
"image_auto_parse_tips": "呼叫 VLM 自動標註文裡的圖片,並生成額外的檢索索引",
"image_training_queue": "圖片處理排隊",
"immediate_sync": "立即同步",
"import.Auto mode Estimated Price Tips": "需呼叫文字理解模型,將消耗較多 AI 點數:{{price}} 點數 / 1K tokens",
"import.Embedding Estimated Price Tips": "僅使用索引模型,消耗少量 AI 點數:{{price}} 點數 / 1K tokens",
"import_confirm": "確認上傳",
"import_data_preview": "數據預覽",
"import_data_process_setting": "數據處理方式設",
"import_file_parse_setting": "文件解析設",
"import_data_preview": "資料預覽",
"import_data_process_setting": "資料處理方式設",
"import_file_parse_setting": "文件解析設",
"import_model_config": "模型選擇",
"import_param_setting": "參數設",
"import_param_setting": "參數設",
"import_select_file": "選擇文件",
"import_select_link": "輸入鏈接",
"import_select_link": "輸入連結",
"index_size": "索引大小",
"index_size_tips": "向量化時內容的長度,系統會自動按該大小對分塊進行進一步的分割。",
"is_open_schedule": "啟用定時同步",
"keep_image": "保留圖片",
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
"open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。",
"params_config": "配置",
"params_setting": "參數設",
"pdf_enhance_parse": "PDF增強解析",
"params_config": "設定",
"params_setting": "參數設",
"pdf_enhance_parse": "PDF 增強解析",
"pdf_enhance_parse_price": "{{price}}積分/頁",
"pdf_enhance_parse_tips": "調用 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
"pdf_enhance_parse_tips": "呼叫 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
"permission.des.manage": "可管理整個資料集的資料和資訊",
"permission.des.read": "可檢視資料集內容",
"permission.des.write": "可新增和變更資料集內容",
"preview_chunk": "分塊預覽",
"preview_chunk_empty": "文件內容為空",
"preview_chunk_intro": "共 {{total}} 個分塊,最多展示 10 個",
"preview_chunk_not_selected": "點左側文件後進行預覽",
"preview_chunk_not_selected": "點左側文件後進行預覽",
"process.Auto_Index": "自動索引生成",
"process.Get QA": "問答對提取",
"process.Image_Index": "圖片索引生成",
@@ -119,13 +119,13 @@
"split_sign_break2": "2 個換行符",
"split_sign_custom": "自定義",
"split_sign_exclamatiob": "驚嘆號",
"split_sign_null": "不設",
"split_sign_null": "不設",
"split_sign_period": "句號",
"split_sign_question": "問號",
"split_sign_semicolon": "分號",
"start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新取,請確認!",
"status_error": "行異常",
"sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來源文件",
"start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新取,請確認!",
"status_error": "行異常",
"sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來原始檔",
"sync_schedule": "定時同步",
"sync_schedule_tip": "只會同步已存在的集合。\n包括連結集合以及 API 知識庫裡所有集合。\n系統會每天進行輪詢更新無法確定特定的更新時間。",
"tag.Add New": "新增",
@@ -144,12 +144,12 @@
"training.Normal": "正常",
"training_mode": "分段模式",
"training_ready": "{{count}} 組",
"vector_model_max_tokens_tip": "每個分塊數據,最大長度為 3000 tokens",
"vector_model_max_tokens_tip": "每個分塊資料,最大長度為 3000 tokens",
"vllm_model": "圖片理解模型",
"website_dataset": "網站同步",
"website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集",
"website_info": "網站資訊",
"yuque_dataset": "語雀知識庫",
"yuque_dataset_config": "配置語雀知識庫",
"yuque_dataset_desc": "可通過配置語雀文權限,使用語雀文檔構建知識庫,文不會進行二次儲"
"yuque_dataset_config": "設定語雀知識庫",
"yuque_dataset_desc": "可透過設定語雀文權限,使用語雀文件建構知識庫,文不會進行二次儲"
}

View File

@@ -8,7 +8,7 @@
"login_success": "登入成功",
"no_remind": "不再提醒",
"password_condition": "密碼最多 60 個字元",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字",
"password_tip": "密碼至少 6 位,且至少包含兩種組合:數字、字母或特殊字",
"policy_tip": "使用即代表您同意我們的",
"privacy": "隱私權政策",
"privacy_policy": "隱私權政策",

View File

@@ -55,7 +55,7 @@
"permission.only_collaborators": "僅協作者可存取",
"permission.team_read": "團隊可存取",
"permission.team_write": "團隊可編輯",
"permission_add_tip": "添加後,您可為其勾選權限。",
"permission_add_tip": "新增後,您可為其勾選權限。",
"permission_des.manage": "可建立資源、邀請及刪除成員",
"permission_des.read": "成員僅能閱讀相關資源,無法建立新資源",
"permission_des.write": "除了可讀取資源外,還可以建立新的資源",
@@ -75,7 +75,7 @@
"synchronization.title": "填寫標籤同步連結,點選同步按鈕即可同步",
"team.Add manager": "新增管理員",
"team.Confirm Invite": "確認邀請",
"team.Create Team": "建新團隊",
"team.Create Team": "建新團隊",
"team.Invite Member Failed Tip": "邀請成員出現異常",
"team.Invite Member Result Tip": "邀請結果提示",
"team.Invite Member Success Tip": "邀請成員完成\n\n成功{{success}} 人\n\n使用者名稱無效{{inValid}}\n\n已在團隊中{{inTeam}}",
@@ -97,10 +97,9 @@
"team.group.group": "群組",
"team.group.keep_admin": "保留管理員權限",
"team.group.manage_member": "管理成員",
"team.group.manage_tip": "可以管理成員、建群組、管理所有群組、為群組和成員分配權限",
"team.group.manage_tip": "可以管理成員、建群組、管理所有群組、為群組和成員分配權限",
"team.group.members": "成員",
"team.group.name": "群組名稱",
"team.group.permission.manage": "管理員",
"team.group.permission.write": "工作臺/知識庫建立",
"team.group.permission_tip": "單獨設定權限的成員,將依照個人權限設定,不再受群組權限影響。\n若成員屬於多個權限群組該成員的權限將會合併。",
"team.group.role.admin": "管理員",
@@ -112,5 +111,6 @@
"team.manage_collaborators": "管理協作者",
"team.no_collaborators": "目前沒有協作者",
"team.org.org": "組織",
"team.write_role_member": "可寫入權限"
"team.write_role_member": "可寫入權限",
"team.collaborator.added": "已新增"
}

View File

@@ -14,13 +14,13 @@
"application_call": "應用程式呼叫",
"assigned_reply": "指定回覆",
"auth_tmb_id": "使用者鑑權",
"auth_tmb_id_tip": "開啟後,對外發布應用程式時,也會根據使用者是否有該知識庫權限進行知識庫過濾。\n\n若未開啟則直接按配置的知識庫進行檢索,不進行權限過濾。",
"auth_tmb_id_tip": "開啟後,對外發布應用程式時,也會根據使用者是否有該知識庫權限進行知識庫過濾。\n\n若未開啟則直接按設定的知識庫進行檢索,不進行權限過濾。",
"can_not_loop": "這個節點不能迴圈。",
"choose_another_application_to_call": "選擇另一個應用程式來呼叫",
"classification_result": "分類結果",
"code.Reset template": "重設範本",
"code.Reset template confirm": "確定要重設程式碼範本嗎?這將會把所有輸入和輸出重設為範本值。請儲存您目前的程式碼。",
"code.Switch language confirm": "切換語言將重代碼,是否繼續?",
"code.Switch language confirm": "切換語言將重代碼,是否繼續?",
"code_execution": "程式碼執行",
"collection_metadata_filter": "資料集詮釋資料篩選器",
"complete_extraction_result": "完整擷取結果",
@@ -52,7 +52,7 @@
"execution_error": "執行錯誤",
"extraction_requirements_description": "擷取需求描述",
"extraction_requirements_description_detail": "提供 AI 相對應的背景知識或需求描述,引導 AI 更好地完成任務。\\n這個輸入框可以使用全域變數。",
"extraction_requirements_placeholder": "例如: 1. 目前時間為: {{cTime}}。\n你是實驗室預約助手你的任務是幫助使用者預約實驗室從文字中取得對應的預約資訊。\n\n2. 你是Google搜尋助手需要從文字中提取出合適的搜尋字詞。",
"extraction_requirements_placeholder": "例如1. 目前時間為{{cTime}}。\n你是實驗室預約助手你的任務是幫助使用者預約實驗室從文字中取得對應的預約資訊。\n\n2. 你是 Google 搜尋助手,需要從文字中提取出合適的搜尋字詞。",
"feedback_text": "回饋文字",
"field_description": "欄位描述",
"field_description_placeholder": "描述這個輸入欄位的功能,如果是工具呼叫參數,這個描述會影響模型產生的品質",
@@ -84,7 +84,7 @@
"intro_http_request": "可以傳送 HTTP 請求,執行更複雜的操作(網路搜尋、資料庫查詢等等)",
"intro_knowledge_base_search_merge": "可以合併多個知識庫搜尋結果並輸出。使用 RRF 合併方法進行最終排序輸出。",
"intro_laf_function_call": "可以呼叫 Laf 帳號下的雲端函式。",
"intro_loop": "輸入一個數組,遍歷數組並將每個數組元素作為輸入元素,執行工作流程。",
"intro_loop": "輸入一個陣列,遍歷陣列並將每個陣列元素作為輸入元素,執行工作流程。",
"intro_plugin_input": "可以設定外掛程式需要的輸入,並利用這些輸入來執行外掛程式",
"intro_question_classification": "根據使用者的歷史紀錄和目前問題判斷這次提問的類型。可以新增多個問題類型,以下是一個範例:\n類型 1打招呼\n類型 2關於產品「使用方式」的問題\n類型 3關於產品「購買」的問題\n類型 4其他問題",
"intro_question_optimization": "使用問題最佳化功能,可以提升知識庫連續對話時的搜尋精準度。使用這個功能後,會先利用 AI 根據脈絡建構一個或多個新的檢索詞彙,這些詞彙更有利於知識庫搜尋。這個模組已內建於知識庫搜尋模組中,如果您只進行一次知識庫搜尋,可以直接使用知識庫內建的自動完成功能。",
@@ -134,7 +134,7 @@
"question_classification": "問題分類",
"question_optimization": "問題最佳化",
"quote_content_placeholder": "可以自訂引用內容的結構,以便更好地適應不同場景。可以使用一些變數來設定範本\n{{q}} - 主要內容\n{{a}} - 輔助資料\n{{source}} - 來源名稱\n{{sourceId}} - 來源 ID\n{{index}} - 第 n 個引用",
"quote_content_tip": "可以自訂引用內容的結構,以更好的適合不同場景。\n可以使用一些變數來進行模板配置\n\n{{id}} - 引用資料唯一id\n\n{{q}} - 主要內容\n\n{{a}} - 輔助數據\n\n{{source}} - 來源名\n\n{{sourceId}} - 來源ID\n\n{{index}} - 第 n 個引用\n\n他們都是可選的下面是預設值\n\n{{default}}",
"quote_content_tip": "可以自訂引用內容的結構,以更好的適合不同場景。\n可以使用一些變數來進行範本設定\n\n{{id}} - 引用資料唯一 id\n\n{{q}} - 主要內容\n\n{{a}} - 輔助資料\n\n{{source}} - 來源名\n\n{{sourceId}} - 來源 ID\n\n{{index}} - 第 n 個引用\n\n他們都是可選的下面是預設值\n\n{{default}}",
"quote_num": "引用數量",
"quote_prompt_tip": "可以使用 {{quote}} 來插入引用內容範本,使用 {{question}} 來插入問題Role=user。\n以下是預設值\n{{default}}",
"quote_role_system_tip": "請注意從「引用範本提示詞」中移除 {{question}} 變數",
@@ -154,7 +154,7 @@
"select_another_application_to_call": "可以選擇另一個應用程式來呼叫",
"special_array_format": "特殊陣列格式,搜尋結果為空時,回傳空陣列。",
"start_with": "開頭為",
"support_code_language": "支import列表pandasnumpy",
"support_code_language": "支import 列表pandasnumpy",
"target_fields_description": "由「描述」和「鍵值」組成一個目標欄位,可以擷取多個目標欄位",
"template.ai_chat": "AI 對話",
"template.ai_chat_intro": "AI 大型語言模型對話",

View File

@@ -1,79 +0,0 @@
diff --git a/lib/index.js b/lib/index.js
index c5ca771c24dd914e342f791716a822431ee32b3a..457d9f8c4625f7d9c7ea1e9ffc13616db1fc6fef 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -126,8 +126,37 @@ function exitLiteralAutolink(token) {
this.exit(token)
}
-/** @type {FromMarkdownTransform} */
+// Regex support detector, for backward compatibility
+// Ref: https://github.com/syntax-tree/mdast-util-gfm-autolink-literal/pull/14
+const regexSupport = {
+ lookbehind: (() => {
+ try {
+ // Using regex literal instead of RegExp constructor
+ ;/(?<=x)/.test('x')
+ return true
+ } catch {
+ return false
+ }
+ })()
+}
+
+/**
+ * Main transform function that uses the appropriate version based on regex support
+ * @type {FromMarkdownTransform}
+ */
function transformGfmAutolinkLiterals(tree) {
+ if (regexSupport.lookbehind) {
+ modernAutolinkTransform(tree)
+ } else {
+ legacyAutolinkTransform(tree)
+ }
+}
+
+/**
+ * Modern version of autolink transform using lookbehind
+ * @type {FromMarkdownTransform}
+ */
+function modernAutolinkTransform(tree) {
findAndReplace(
tree,
[
@@ -138,6 +167,35 @@ function transformGfmAutolinkLiterals(tree) {
)
}
+
+/**
+ * Legacy version of autolink transform for older Node.js versions
+ * @type {FromMarkdownTransform}
+ */
+function legacyAutolinkTransform(tree) {
+ findAndReplace(
+ tree,
+ [
+ [/(https?:\/\/|www(?=\.))([-.\w]+)([^ \t\r\n]*)/gi, findUrl],
+ // [/([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/g, findEmail] # NOTE: original regex in 2.0.0
+ [/(^|\s|\p{P}|\p{S})([-.\w+]+)@([-\w]+(?:\.[-\w]+)+)/gu, findEmailLegacy]
+ ],
+ {ignore: ['link', 'linkReference']}
+ )
+}
+
+/**
+ * Helper function for legacy email matching
+ * @param {string} _ - Unused parameter
+ * @param {string} prefix - Email prefix
+ * @param {string} name - Email name
+ * @param {string} domain - Email domain
+ * @returns {*} The processed email
+ */
+function findEmailLegacy(_, prefix, name, domain) {
+ return findEmail(name + '@' + domain)
+}
+
/**
* @type {ReplaceFunction}
* @param {string} _

233
pnpm-lock.yaml generated
View File

@@ -4,10 +4,8 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies:
mdast-util-gfm-autolink-literal@2.0.1:
hash: f63d515781110436299ab612306211a9621c6dfaec1ce1a19e2f27454dc70251
path: patches/mdast-util-gfm-autolink-literal@2.0.1.patch
overrides:
mdast-util-gfm-autolink-literal: 2.0.0
importers:
@@ -17,8 +15,8 @@ importers:
specifier: ^2.4.1
version: 2.5.8(encoding@0.1.13)(react@18.3.1)
'@vitest/coverage-v8':
specifier: ^3.0.2
version: 3.0.8(vitest@3.0.8(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))
specifier: ^3.0.9
version: 3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))
husky:
specifier: ^8.0.3
version: 8.0.3
@@ -28,9 +26,12 @@ importers:
lint-staged:
specifier: ^13.3.0
version: 13.3.0
mongodb-memory-server:
specifier: ^10.1.4
version: 10.1.4(socks@2.8.4)
next-i18next:
specifier: 15.4.2
version: 15.4.2(i18next@23.16.8)(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.4.2(i18next@23.16.8)(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
prettier:
specifier: 3.2.4
version: 3.2.4
@@ -38,11 +39,8 @@ importers:
specifier: 14.1.2
version: 14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
vitest:
specifier: ^3.0.2
version: 3.0.8(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
vitest-mongodb:
specifier: ^1.0.1
version: 1.0.1(socks@2.8.4)
specifier: ^3.0.9
version: 3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
zhlint:
specifier: ^0.7.4
version: 0.7.4(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)(typescript@5.8.2)
@@ -327,7 +325,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.4.2
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.10.7
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -390,7 +388,7 @@ importers:
version: 4.17.21
next-i18next:
specifier: 15.4.2
version: 15.4.2(i18next@23.16.8)(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.4.2(i18next@23.16.8)(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
papaparse:
specifier: ^5.4.1
version: 5.4.1
@@ -451,7 +449,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.4.2
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.10.7
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -550,7 +548,7 @@ importers:
version: 14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
next-i18next:
specifier: 15.4.2
version: 15.4.2(i18next@23.16.8)(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.4.2(i18next@23.16.8)(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
nprogress:
specifier: ^0.2.0
version: 0.2.0
@@ -3565,11 +3563,11 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
'@vitest/coverage-v8@3.0.8':
resolution: {integrity: sha512-y7SAKsQirsEJ2F8bulBck4DoluhI2EEgTimHd6EEUgJBGKy9tC25cpywh1MH4FvDGoG2Unt7+asVd1kj4qOSAw==}
'@vitest/coverage-v8@3.1.1':
resolution: {integrity: sha512-MgV6D2dhpD6Hp/uroUoAIvFqA8AuvXEFBC2eepG3WFc1pxTfdk1LEqqkWoWhjz+rytoqrnUUCdf6Lzco3iHkLQ==}
peerDependencies:
'@vitest/browser': 3.0.8
vitest: 3.0.8
'@vitest/browser': 3.1.1
vitest: 3.1.1
peerDependenciesMeta:
'@vitest/browser':
optional: true
@@ -3580,6 +3578,9 @@ packages:
'@vitest/expect@3.0.8':
resolution: {integrity: sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==}
'@vitest/expect@3.1.1':
resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==}
'@vitest/mocker@3.0.8':
resolution: {integrity: sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==}
peerDependencies:
@@ -3591,33 +3592,59 @@ packages:
vite:
optional: true
'@vitest/mocker@3.1.1':
resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==}
peerDependencies:
msw: ^2.4.9
vite: ^5.0.0 || ^6.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@3.0.8':
resolution: {integrity: sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==}
'@vitest/pretty-format@3.1.1':
resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==}
'@vitest/runner@1.6.1':
resolution: {integrity: sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==}
'@vitest/runner@3.0.8':
resolution: {integrity: sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==}
'@vitest/runner@3.1.1':
resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==}
'@vitest/snapshot@1.6.1':
resolution: {integrity: sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==}
'@vitest/snapshot@3.0.8':
resolution: {integrity: sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==}
'@vitest/snapshot@3.1.1':
resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==}
'@vitest/spy@1.6.1':
resolution: {integrity: sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==}
'@vitest/spy@3.0.8':
resolution: {integrity: sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==}
'@vitest/spy@3.1.1':
resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==}
'@vitest/utils@1.6.1':
resolution: {integrity: sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==}
'@vitest/utils@3.0.8':
resolution: {integrity: sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==}
'@vitest/utils@3.1.1':
resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==}
'@vue/compiler-core@3.5.13':
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
@@ -6783,8 +6810,8 @@ packages:
mdast-util-from-markdown@2.0.2:
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
mdast-util-gfm-autolink-literal@2.0.1:
resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
mdast-util-gfm-autolink-literal@2.0.0:
resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==}
mdast-util-gfm-footnote@2.1.0:
resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
@@ -9330,6 +9357,11 @@ packages:
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite-node@3.1.1:
resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite@5.4.14:
resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -9401,9 +9433,6 @@ packages:
yaml:
optional: true
vitest-mongodb@1.0.1:
resolution: {integrity: sha512-a9Mc2F35h8qxI1uOgsrCUH28TglClAd8gdXkn7CBqmC6bLr6D2Ibyxp0Xz6/AU0ukAOfuf/6oqUS+ZN0VlxVyQ==}
vitest@1.6.1:
resolution: {integrity: sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==}
engines: {node: ^18.0.0 || >=20.0.0}
@@ -9457,6 +9486,34 @@ packages:
jsdom:
optional: true
vitest@3.1.1:
resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/debug': ^4.1.12
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@vitest/browser': 3.1.1
'@vitest/ui': 3.1.1
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/debug':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
@@ -10740,7 +10797,7 @@ snapshots:
'@chakra-ui/system': 2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
react: 18.3.1
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
dependencies:
'@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/cache': 11.14.0
@@ -12846,7 +12903,7 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitest/coverage-v8@3.0.8(vitest@3.0.8(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))':
'@vitest/coverage-v8@3.1.1(vitest@3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
@@ -12860,7 +12917,7 @@ snapshots:
std-env: 3.8.1
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.0.8(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
vitest: 3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
transitivePeerDependencies:
- supports-color
@@ -12877,6 +12934,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
'@vitest/expect@3.1.1':
dependencies:
'@vitest/spy': 3.1.1
'@vitest/utils': 3.1.1
chai: 5.2.0
tinyrainbow: 2.0.0
'@vitest/mocker@3.0.8(vite@6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))':
dependencies:
'@vitest/spy': 3.0.8
@@ -12885,10 +12949,22 @@ snapshots:
optionalDependencies:
vite: 6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
'@vitest/mocker@3.1.1(vite@6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))':
dependencies:
'@vitest/spy': 3.1.1
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
'@vitest/pretty-format@3.0.8':
dependencies:
tinyrainbow: 2.0.0
'@vitest/pretty-format@3.1.1':
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@1.6.1':
dependencies:
'@vitest/utils': 1.6.1
@@ -12900,6 +12976,11 @@ snapshots:
'@vitest/utils': 3.0.8
pathe: 2.0.3
'@vitest/runner@3.1.1':
dependencies:
'@vitest/utils': 3.1.1
pathe: 2.0.3
'@vitest/snapshot@1.6.1':
dependencies:
magic-string: 0.30.17
@@ -12912,6 +12993,12 @@ snapshots:
magic-string: 0.30.17
pathe: 2.0.3
'@vitest/snapshot@3.1.1':
dependencies:
'@vitest/pretty-format': 3.1.1
magic-string: 0.30.17
pathe: 2.0.3
'@vitest/spy@1.6.1':
dependencies:
tinyspy: 2.2.1
@@ -12920,6 +13007,10 @@ snapshots:
dependencies:
tinyspy: 3.0.2
'@vitest/spy@3.1.1':
dependencies:
tinyspy: 3.0.2
'@vitest/utils@1.6.1':
dependencies:
diff-sequences: 29.6.3
@@ -12933,6 +13024,12 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 2.0.0
'@vitest/utils@3.1.1':
dependencies:
'@vitest/pretty-format': 3.1.1
loupe: 3.1.3
tinyrainbow: 2.0.0
'@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.26.10
@@ -16842,7 +16939,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
mdast-util-gfm-autolink-literal@2.0.1(patch_hash=f63d515781110436299ab612306211a9621c6dfaec1ce1a19e2f27454dc70251):
mdast-util-gfm-autolink-literal@2.0.0:
dependencies:
'@types/mdast': 4.0.4
ccount: 2.0.1
@@ -16890,7 +16987,7 @@ snapshots:
mdast-util-gfm@3.1.0:
dependencies:
mdast-util-from-markdown: 2.0.2
mdast-util-gfm-autolink-literal: 2.0.1(patch_hash=f63d515781110436299ab612306211a9621c6dfaec1ce1a19e2f27454dc70251)
mdast-util-gfm-autolink-literal: 2.0.0
mdast-util-gfm-footnote: 2.1.0
mdast-util-gfm-strikethrough: 2.0.0
mdast-util-gfm-table: 2.0.0
@@ -17647,7 +17744,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.26(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.10
'@types/hoist-non-react-statics': 3.3.6
@@ -19984,6 +20081,27 @@ snapshots:
- tsx
- yaml
vite-node@3.1.1(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0):
dependencies:
cac: 6.7.14
debug: 4.4.0
es-module-lexer: 1.6.0
pathe: 2.0.3
vite: 6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite@5.4.14(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0):
dependencies:
esbuild: 0.21.5
@@ -20006,20 +20124,6 @@ snapshots:
sass: 1.85.1
terser: 5.39.0
vitest-mongodb@1.0.1(socks@2.8.4):
dependencies:
debug: 4.4.0
mongodb-memory-server: 10.1.4(socks@2.8.4)
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- gcp-metadata
- kerberos
- mongodb-client-encryption
- snappy
- socks
- supports-color
vitest@1.6.1(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0):
dependencies:
'@vitest/expect': 1.6.1
@@ -20093,6 +20197,45 @@ snapshots:
- tsx
- yaml
vitest@3.1.1(@types/debug@4.1.12)(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0):
dependencies:
'@vitest/expect': 3.1.1
'@vitest/mocker': 3.1.1(vite@6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0))
'@vitest/pretty-format': 3.1.1
'@vitest/runner': 3.1.1
'@vitest/snapshot': 3.1.1
'@vitest/spy': 3.1.1
'@vitest/utils': 3.1.1
chai: 5.2.0
debug: 4.4.0
expect-type: 1.2.0
magic-string: 0.30.17
pathe: 2.0.3
std-env: 3.8.1
tinybench: 2.9.0
tinyexec: 0.3.2
tinypool: 1.0.2
tinyrainbow: 2.0.0
vite: 6.2.2(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
vite-node: 3.1.1(@types/node@20.17.24)(sass@1.85.1)(terser@5.39.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 20.17.24
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
void-elements@3.1.0: {}
vue@3.5.13(typescript@5.8.2):

View File

@@ -2,6 +2,3 @@ packages:
- packages/*
- projects/*
- scripts/icon
patchedDependencies:
mdast-util-gfm-autolink-literal@2.0.1: patches/mdast-util-gfm-autolink-literal@2.0.1.patch

View File

@@ -6,6 +6,7 @@ import {
import { ChatBoxInputType, UserInputFileItemType } from './type';
import { getFileIcon } from '@fastgpt/global/common/file/icon';
import { ChatItemValueTypeEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
if (!value) {
@@ -82,17 +83,19 @@ export const setUserSelectResultToHistories = (
i !== item.value.length - 1 ||
val.type !== ChatItemValueTypeEnum.interactive ||
!val.interactive
)
) {
return val;
}
if (val.interactive.type === 'userSelect') {
const finalInteractive = extractDeepestInteractive(val.interactive);
if (finalInteractive.type === 'userSelect') {
return {
...val,
interactive: {
...val.interactive,
...finalInteractive,
params: {
...val.interactive.params,
userSelectedVal: val.interactive.params.userSelectOptions.find(
...finalInteractive.params,
userSelectedVal: finalInteractive.params.userSelectOptions.find(
(item) => item.value === interactiveVal
)?.value
}
@@ -100,13 +103,13 @@ export const setUserSelectResultToHistories = (
};
}
if (val.interactive.type === 'userInput') {
if (finalInteractive.type === 'userInput') {
return {
...val,
interactive: {
...val.interactive,
...finalInteractive,
params: {
...val.interactive.params,
...finalInteractive.params,
submitted: true
}
}

View File

@@ -28,6 +28,7 @@ import { isEqual } from 'lodash';
import { useTranslation } from 'next-i18next';
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
const accordionButtonStyle = {
w: 'auto',
@@ -245,11 +246,12 @@ const AIResponseBox = ({
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
}
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
if (value.interactive.type === 'userSelect') {
return <RenderUserSelectInteractive interactive={value.interactive} />;
const finalInteractive = extractDeepestInteractive(value.interactive);
if (finalInteractive.type === 'userSelect') {
return <RenderUserSelectInteractive interactive={finalInteractive} />;
}
if (value.interactive?.type === 'userInput') {
return <RenderUserFormInteractive interactive={value.interactive} />;
if (finalInteractive.type === 'userInput') {
return <RenderUserFormInteractive interactive={finalInteractive} />;
}
}
return null;

View File

@@ -1,20 +1,24 @@
import React from 'react';
import { useTranslation } from 'next-i18next';
import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import PermissionTags from './PermissionTags';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import OrgTags from '../../user/team/OrgTags';
import Tag from '@fastgpt/web/components/common/Tag';
function MemberItemCard({
avatar,
key,
onChange,
onChange: _onChange,
isChecked,
onDelete,
name,
permission,
orgs
orgs,
addOnly,
rightSlot
}: {
avatar: string;
key: string;
@@ -23,44 +27,66 @@ function MemberItemCard({
onDelete?: () => void;
name: string;
permission?: PermissionValueType;
addOnly?: boolean;
orgs?: string[];
rightSlot?: React.ReactNode;
}) {
const isAdded = addOnly && !!permission;
const onChange = () => {
if (!isAdded) _onChange();
};
const { t } = useTranslation();
return (
<>
<HStack
justifyContent="space-between"
alignItems="center"
key={key}
px="3"
py="2"
borderRadius="sm"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
{isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />}
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
<HStack
justifyContent="space-between"
alignItems="center"
key={key}
px="3"
py="2"
borderRadius="sm"
_hover={{
bgColor: 'myGray.50',
cursor: 'pointer'
}}
onClick={onChange}
>
{isChecked !== undefined && (
<Checkbox isChecked={isChecked} pointerEvents="none" disabled={isAdded} />
)}
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
<Box w="full">
<Box fontSize={'sm'}>{name}</Box>
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
<Box w="full">
<Box fontSize={'sm'} className="textEllipsis" maxW="300px">
{name}
</Box>
{permission && <PermissionTags permission={permission} />}
{onDelete !== undefined && (
<MyIcon
name="common/closeLight"
w="1rem"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
onClick={onDelete}
/>
)}
</HStack>
</>
<Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
</Box>
{!isAdded && permission && <PermissionTags permission={permission} />}
{isAdded && (
<Tag
mixBlendMode={'multiply'}
colorSchema="blue"
border="none"
py={2}
px={3}
fontSize={'xs'}
>
{t('user:team.collaborator.added')}
</Tag>
)}
{onDelete !== undefined && (
<MyIcon
name="common/closeLight"
w="1rem"
cursor={'pointer'}
_hover={{
color: 'red.600'
}}
onClick={onDelete}
/>
)}
{rightSlot}
</HStack>
);
}

View File

@@ -1,17 +1,6 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import { ChevronDownIcon } from '@chakra-ui/icons';
import {
Box,
Button,
Checkbox,
Flex,
Grid,
HStack,
ModalBody,
ModalFooter,
Tag,
Text
} from '@chakra-ui/react';
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter, Text } from '@chakra-ui/react';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import MyAvatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -19,27 +8,26 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';
import PermissionSelect from './PermissionSelect';
import PermissionTags from './PermissionTags';
import {
DEFAULT_ORG_AVATAR,
DEFAULT_TEAM_AVATAR,
DEFAULT_USER_AVATAR
} from '@fastgpt/global/common/system/constants';
import Path from '@/components/common/folder/Path';
import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
import { useContextSelector } from 'use-context-selector';
import { CollaboratorContext } from './context';
import { getTeamMembers } from '@/web/support/user/team/api';
import { getGroupList } from '@/web/support/user/team/group/api';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import MemberItemCard from './MemberItemCard';
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
import _ from 'lodash';
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
import { ValueOf } from 'next/dist/shared/lib/constants';
const HoverBoxStyle = {
bgColor: 'myGray.50',
@@ -131,8 +119,8 @@ function MemberModal({
members: selectedMemberList.map((item) => item.tmbId),
groups: selectedGroupList.map((item) => item._id),
orgs: selectedOrgList.map((item) => item._id),
permission: selectedPermission!
}),
permission: addOnly ? undefined : selectedPermission!
} as UpdateClbPermissionProps<ValueOf<typeof addOnly>>),
{
successToast: t('common:common.Add Success'),
onSuccess() {
@@ -285,6 +273,7 @@ function MemberModal({
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
return (
<MemberItemCard
addOnly={addOnly}
avatar={member.avatar}
key={member.tmbId}
name={member.memberName}
@@ -321,49 +310,33 @@ function MemberModal({
};
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
return (
<HStack
justifyContent="space-between"
<MemberItemCard
avatar={org.avatar}
key={org._id}
py="2"
px="3"
borderRadius="sm"
alignItems="center"
_hover={HoverBoxStyle}
onClick={onChange}
>
<Checkbox
isChecked={!!selectedOrgList.find((v) => v._id === org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<HStack w="full">
<Text>{org.name}</Text>
{org.total && (
<>
<Tag size="sm" my="auto">
{org.total}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.total && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
onClickOrg(org);
// setPath(getOrgChildrenPath(org));
e.stopPropagation();
}}
/>
)}
</HStack>
name={org.name}
onChange={onChange}
addOnly={addOnly}
permission={collaborator?.permission.value}
isChecked={!!selectedOrgList.find((v) => String(v._id) === String(org._id))}
rightSlot={
org.total && (
<MyIcon
name="core/chat/chevronRight"
w="16px"
p="4px"
rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
onClickOrg(org);
// setPath(getOrgChildrenPath(org));
e.stopPropagation();
}}
/>
)
}
/>
);
});
return searchKey ? (
@@ -372,6 +345,9 @@ function MemberModal({
<OrgMemberScrollData>
{Orgs}
{orgMembers.map((member) => {
const isChecked = !!selectedMemberList.find(
(v) => v.tmbId === member.tmbId
);
return (
<MemberItemCard
avatar={member.avatar}
@@ -385,7 +361,9 @@ function MemberModal({
return [...state, member];
});
}}
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
isChecked={isChecked}
permission={member.permission.value}
addOnly={addOnly && !!member.permission.value}
orgs={member.orgs}
/>
);
@@ -414,6 +392,7 @@ function MemberModal({
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
addOnly={addOnly}
/>
);
})}

View File

@@ -110,7 +110,15 @@ const CollaboratorContextProvider = ({
} = useRequest2(
async () => {
if (feConfigs.isPlus) {
return onGetCollaboratorList();
const data = await onGetCollaboratorList();
return data.map((item) => {
return {
...item,
permission: new Permission({
per: item.permission.value
})
};
});
}
return [];
},

View File

@@ -10,7 +10,14 @@ function OrgTags({ orgs, type = 'simple' }: { orgs?: string[]; type?: 'simple' |
label={
<VStack gap="1" alignItems={'start'}>
{orgs.map((org, index) => (
<Box key={index} fontSize="sm" fontWeight={400} color="myGray.500">
<Box
key={index}
fontSize="sm"
fontWeight={400}
color="myGray.500"
maxW={'300px'}
className="textEllipsis"
>
{org.slice(1)}
</Box>
))}

View File

@@ -91,7 +91,7 @@ const AccountContainer = ({
}
]
: []),
...(userInfo?.team?.permission.hasManagePer
...(userInfo?.team?.permission.hasApikeyCreatePer
? [
{
icon: 'key',

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { updatePasswordByOld } from '@/web/support/user/api';
import { PasswordRule } from '@/web/support/user/login/constants';
import { checkPasswordRule } from '@/web/support/user/login/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
type FormType = {
@@ -70,9 +70,11 @@ const UpdatePswModal = ({ onClose }: { onClose: () => void }) => {
placeholder={t('account_info:password_tip')}
{...register('newPsw', {
required: true,
pattern: {
value: PasswordRule,
message: t('account_info:password_tip')
validate: (val) => {
if (!checkPasswordRule(val)) {
return t('login:password_tip');
}
return true;
}
})}
></Input>

View File

@@ -303,7 +303,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
})()}
</Td>
<Td maxW={'300px'}>
<VStack gap={0}>
<VStack gap={0} align="start">
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
<Box>
{member.updateTime

View File

@@ -0,0 +1,102 @@
import {
Box,
Button,
Flex,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr
} from '@chakra-ui/react';
import { useState } from 'react';
import { useTranslation } from 'next-i18next';
import MyBox from '@fastgpt/web/components/common/MyBox';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { getOperationLogs } from '@/web/support/user/team/operantionLog/api';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { operationLogI18nMap } from '@fastgpt/service/support/operationLog/constants';
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import UserBox from '@fastgpt/web/components/common/UserBox';
function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation();
const [searchKey, setSearchKey] = useState<string>('');
const {
data: operationLogs = [],
isLoading: loadingLogs,
ScrollData: LogScrollData
} = useScrollPagination(getOperationLogs, {
pageSize: 20,
refreshDeps: [searchKey],
throttleWait: 500,
debounceWait: 200
});
const isLoading = loadingLogs;
return (
<>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
{Tabs}
</Flex>
<MyBox isLoading={isLoading} flex={'1 0 0'} overflow={'auto'}>
<LogScrollData>
<TableContainer overflow={'unset'} fontSize={'sm'}>
<Table overflow={'unset'}>
<Thead>
<Tr bgColor={'white !important'}>
<Th borderLeftRadius="6px" bgColor="myGray.100">
{t('account_team:log_user')}
</Th>
<Th bgColor="myGray.100">{t('account_team:log_time')}</Th>
<Th bgColor="myGray.100">{t('account_team:log_type')}</Th>
<Th bgColor="myGray.100">{t('account_team:log_details')}</Th>
</Tr>
</Thead>
<Tbody>
{operationLogs?.map((log) => {
const i18nData = operationLogI18nMap[log.event];
const metadata = { ...log.metadata };
if (log.event === OperationLogEventEnum.ASSIGN_PERMISSION) {
const permissionValue = parseInt(metadata.permission, 10);
const permission = new TeamPermission({ per: permissionValue });
metadata.appCreate = permission.hasAppCreatePer ? '✔' : '✘';
metadata.datasetCreate = permission.hasDatasetCreatePer ? '✔' : '✘';
metadata.apiKeyCreate = permission.hasApikeyCreatePer ? '✔' : '✘';
metadata.manage = permission.hasManagePer ? '✔' : '✘';
}
return i18nData ? (
<Tr key={log._id} overflow={'unset'}>
<Td>
<UserBox
sourceMember={log.sourceMember}
fontSize="sm"
avatarSize="1rem"
spacing={0.5}
/>
</Td>
<Td>{formatTime2YMDHMS(log.timestamp)}</Td>
<Td>{t(i18nData.typeLabel)}</Td>
<Td>{t(i18nData.content, metadata as any) as string}</Td>
</Tr>
) : null;
})}
</Tbody>
</Table>
</TableContainer>
</LogScrollData>
</MyBox>
</>
);
}
export default OperationLogTable;

View File

@@ -27,6 +27,9 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import {
TeamApikeyCreatePermissionVal,
TeamAppCreatePermissionVal,
TeamDatasetCreatePermissionVal,
TeamManagePermissionVal,
TeamPermissionList,
TeamWritePermissionVal
@@ -42,6 +45,9 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
import { Permission } from '@fastgpt/global/support/permission/controller';
function PermissionManage({
Tabs,
@@ -104,19 +110,18 @@ function PermissionManage({
}, [collaboratorList, searchResult, searchKey]);
const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2(
async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => {
async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: PermissionValueType }) => {
const clb = collaboratorList.find(
(clb) => clb.tmbId === id || clb.groupId === id || clb.orgId === id
);
if (!clb) return;
const updatePer = per === 'write' ? TeamWritePermissionVal : TeamManagePermissionVal;
const permission = new TeamPermission({ per: clb.permission.value });
if (type === 'add') {
permission.addPer(updatePer);
permission.addPer(per);
} else {
permission.removePer(updatePer);
permission.removePer(per);
}
return onUpdateCollaborators({
@@ -132,12 +137,48 @@ function PermissionManage({
useRequest2(onDelOneCollaborator);
const userManage = userInfo?.permission.hasManagePer;
const hasDeletePer = (per: TeamPermission) => {
const hasDeletePer = (per: Permission) => {
if (userInfo?.permission.isOwner) return true;
if (userManage && !per.hasManagePer) return true;
return false;
};
function PermissionCheckBox({
isDisabled,
per,
clbPer,
id
}: {
isDisabled: boolean;
per: PermissionValueType;
clbPer: Permission;
id: string;
}) {
return (
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={isDisabled}
isChecked={clbPer.checkPer(per)}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id,
type: 'add',
per
})
: onUpdatePermission({
id,
type: 'remove',
per
})
}
/>
</Box>
</Td>
);
}
return (
<>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
@@ -174,13 +215,26 @@ function PermissionManage({
</Th>
<Th bg="myGray.100">
<Box mx="auto" w="fit-content">
{t('user:team.group.permission.write')}
{t('account_team:permission_appCreate')}
<QuestionTip ml="1" label={t('account_team:permission_appCreate_tip')} />
</Box>
</Th>
<Th bg="myGray.100">
<Box mx="auto" w="fit-content">
{t('user:team.group.permission.manage')}
<QuestionTip ml="1" label={t('user:team.group.manage_tip')} />
{t('account_team:permission_datasetCreate')}
<QuestionTip ml="1" label={t('account_team:permission_datasetCreate_Tip')} />
</Box>
</Th>
<Th bg="myGray.100">
<Box mx="auto" w="fit-content">
{t('account_team:permission_apikeyCreate')}
<QuestionTip ml="1" label={t('account_team:permission_apikeyCreate_Tip')} />
</Box>
</Th>
<Th bg="myGray.100">
<Box mx="auto" w="fit-content">
{t('account_team:permission_manage')}
<QuestionTip ml="1" label={t('account_team:permission_manage_tip')} />
</Box>
</Th>
<Th bg="myGray.100" borderRightRadius="md">
@@ -210,48 +264,30 @@ function PermissionManage({
<Box>{member.name}</Box>
</HStack>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={member.permission.isOwner || !userManage}
isChecked={member.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: member.tmbId!,
type: 'add',
per: 'write'
})
: onUpdatePermission({
id: member.tmbId!,
type: 'remove',
per: 'write'
})
}
/>
</Box>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner}
isChecked={member.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: member.tmbId!,
type: 'add',
per: 'manage'
})
: onUpdatePermission({
id: member.tmbId!,
type: 'remove',
per: 'manage'
})
}
/>
</Box>
</Td>
<PermissionCheckBox
isDisabled={member.permission.isOwner || !userManage}
per={TeamAppCreatePermissionVal}
clbPer={member.permission}
id={member.tmbId!}
/>
<PermissionCheckBox
isDisabled={member.permission.isOwner || !userManage}
per={TeamDatasetCreatePermissionVal}
clbPer={member.permission}
id={member.tmbId!}
/>
<PermissionCheckBox
isDisabled={member.permission.isOwner || !userManage}
per={TeamApikeyCreatePermissionVal}
clbPer={member.permission}
id={member.tmbId!}
/>
<PermissionCheckBox
isDisabled={member.permission.isOwner || !userInfo?.permission.isOwner}
per={TeamManagePermissionVal}
clbPer={member.permission}
id={member.tmbId!}
/>
<Td>
{hasDeletePer(member.permission) &&
userInfo?.team.tmbId !== member.tmbId && (
@@ -268,7 +304,6 @@ function PermissionManage({
</Tr>
))}
</>
<>
<Tr borderBottom={'1px solid'} borderColor={'myGray.200'} />
<Tr userSelect={'none'}>
@@ -286,40 +321,30 @@ function PermissionManage({
<Td pl={10}>
<MemberTag name={org.name} avatar={org.avatar} />
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={org.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'write' })
: onUpdatePermission({
id: org.orgId!,
type: 'remove',
per: 'write'
})
}
/>
</Box>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={org.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({ id: org.orgId!, type: 'add', per: 'manage' })
: onUpdatePermission({
id: org.orgId!,
type: 'remove',
per: 'manage'
})
}
/>
</Box>
</Td>
<PermissionCheckBox
isDisabled={org.permission.isOwner || !userManage}
per={TeamAppCreatePermissionVal}
clbPer={org.permission}
id={org.orgId!}
/>
<PermissionCheckBox
isDisabled={org.permission.isOwner || !userManage}
per={TeamDatasetCreatePermissionVal}
clbPer={org.permission}
id={org.orgId!}
/>
<PermissionCheckBox
isDisabled={org.permission.isOwner || !userManage}
per={TeamApikeyCreatePermissionVal}
clbPer={org.permission}
id={org.orgId!}
/>
<PermissionCheckBox
isDisabled={org.permission.isOwner || !userInfo?.permission.isOwner}
per={TeamManagePermissionVal}
clbPer={org.permission}
id={org.orgId!}
/>
<Td>
{hasDeletePer(org.permission) && (
<Box mx="auto" w="fit-content">
@@ -358,48 +383,30 @@ function PermissionManage({
avatar={group.avatar}
/>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userManage}
isChecked={group.permission.hasWritePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: group.groupId!,
type: 'add',
per: 'write'
})
: onUpdatePermission({
id: group.groupId!,
type: 'remove',
per: 'write'
})
}
/>
</Box>
</Td>
<Td>
<Box mx="auto" w="fit-content">
<Checkbox
isDisabled={!userInfo?.permission.isOwner}
isChecked={group.permission.hasManagePer}
onChange={(e) =>
e.target.checked
? onUpdatePermission({
id: group.groupId!,
type: 'add',
per: 'manage'
})
: onUpdatePermission({
id: group.groupId!,
type: 'remove',
per: 'manage'
})
}
/>
</Box>
</Td>
<PermissionCheckBox
isDisabled={group.permission.isOwner || !userManage}
per={TeamAppCreatePermissionVal}
clbPer={group.permission}
id={group.groupId!}
/>
<PermissionCheckBox
isDisabled={group.permission.isOwner || !userManage}
per={TeamDatasetCreatePermissionVal}
clbPer={group.permission}
id={group.groupId!}
/>
<PermissionCheckBox
isDisabled={group.permission.isOwner || !userManage}
per={TeamApikeyCreatePermissionVal}
clbPer={group.permission}
id={group.groupId!}
/>
<PermissionCheckBox
isDisabled={group.permission.isOwner || !userInfo?.permission.isOwner}
per={TeamManagePermissionVal}
clbPer={group.permission}
id={group.groupId!}
/>
<Td>
{hasDeletePer(group.permission) && (
<Box mx="auto" w="fit-content">

View File

@@ -13,7 +13,10 @@ import {
SelectOptionsComponent
} from '@/components/core/chat/components/Interactive/InteractiveComponents';
import { UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { initWorkflowEdgeStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import {
getLastInteractiveValue,
initWorkflowEdgeStatus
} from '@fastgpt/global/core/workflow/runtime/utils';
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
@@ -130,10 +133,11 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => {
}
];
const lastInteractive = getLastInteractiveValue(mockHistory);
onNextNodeDebug({
...workflowDebugData,
// Rewrite runtimeEdges
runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, mockHistory),
runtimeEdges: initWorkflowEdgeStatus(workflowDebugData.runtimeEdges, lastInteractive),
query: updatedQuery,
history: mockHistory
});

View File

@@ -36,6 +36,7 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import UserBox from '@fastgpt/web/components/common/UserBox';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
const HttpEditModal = dynamic(() => import('./HttpPluginEditModal'));
const ListItem = () => {
@@ -429,7 +430,7 @@ const ListItem = () => {
members?: string[];
groups?: string[];
orgs?: string[];
permission: number;
permission: PermissionValueType;
}) =>
postUpdateAppCollaborators({
...props,

View File

@@ -1,7 +1,7 @@
import React, { Dispatch } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { LoginPageTypeEnum, PasswordRule } from '@/web/support/user/login/constants';
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
import { postFindPassword } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes.d';
@@ -138,9 +138,11 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
placeholder={t('login:password_tip')}
{...register('password', {
required: true,
pattern: {
value: PasswordRule,
message: t('login:password_tip')
validate: (val) => {
if (!checkPasswordRule(val)) {
return t('login:password_tip');
}
return true;
}
})}
></Input>

View File

@@ -1,7 +1,7 @@
import React, { Dispatch } from 'react';
import { FormControl, Box, Input, Button } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { LoginPageTypeEnum, PasswordRule } from '@/web/support/user/login/constants';
import { LoginPageTypeEnum, checkPasswordRule } from '@/web/support/user/login/constants';
import { postRegister } from '@/web/support/user/api';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import type { ResLogin } from '@/global/support/api/userRes';
@@ -166,9 +166,11 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
placeholder={t('login:password_tip')}
{...register('password', {
required: true,
pattern: {
value: PasswordRule,
message: t('login:password_tip')
validate: (val) => {
if (!checkPasswordRule(val)) {
return t('login:password_tip');
}
return true;
}
})}
></Input>

View File

@@ -18,6 +18,7 @@ const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTa
const PermissionManage = dynamic(
() => import('@/pageComponents/account/team/PermissionManage/index')
);
const OperationLogTable = dynamic(() => import('@/pageComponents/account/team/OperationLog/index'));
const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index'));
const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index'));
const HandleInviteModal = dynamic(
@@ -28,7 +29,8 @@ export enum TeamTabEnum {
member = 'member',
org = 'org',
group = 'group',
permission = 'permission'
permission = 'permission',
operationLog = 'operationLog'
}
const Team = () => {
@@ -57,7 +59,8 @@ const Team = () => {
{ label: t('account_team:member'), value: TeamTabEnum.member },
{ label: t('account_team:org'), value: TeamTabEnum.org },
{ label: t('account_team:group'), value: TeamTabEnum.group },
{ label: t('account_team:permission'), value: TeamTabEnum.permission }
{ label: t('account_team:permission'), value: TeamTabEnum.permission },
{ label: t('account_team:operation_log'), value: TeamTabEnum.operationLog }
]}
px={'1rem'}
value={teamTab}
@@ -150,6 +153,7 @@ const Team = () => {
{teamTab === TeamTabEnum.org && <OrgManage Tabs={Tabs} />}
{teamTab === TeamTabEnum.group && <GroupManage Tabs={Tabs} />}
{teamTab === TeamTabEnum.permission && <PermissionManage Tabs={Tabs} />}
{teamTab === TeamTabEnum.operationLog && <OperationLogTable Tabs={Tabs} />}
</Box>
</Flex>
{invitelinkid && <HandleInviteModal invitelinkid={invitelinkid} />}

View File

@@ -0,0 +1,48 @@
import { NextAPI } from '@/service/middleware/entry';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import {
TeamApikeyCreatePermissionVal,
TeamAppCreatePermissionVal,
TeamDatasetCreatePermissionVal
} from '@fastgpt/global/support/permission/user/constant';
import { retryFn } from '@fastgpt/global/common/system/utils';
async function handler(req: NextApiRequest, _res: NextApiResponse) {
await authCert({ req, authRoot: true });
// 更新团队权限:
// 目前所有有 TeamWritePermission 的,都需要添加三个新的权限。
const rps = await MongoResourcePermission.find({
resourceType: 'team',
teamId: { $exists: true },
resourceId: null
});
for await (const rp of rps) {
const per = new TeamPermission({ per: rp.permission });
console.log(per.hasWritePer, per.value);
if (per.hasWritePer) {
const newPer = per.addPer(
TeamAppCreatePermissionVal,
TeamDatasetCreatePermissionVal,
TeamApikeyCreatePermissionVal
);
rp.permission = newPer.value;
try {
await retryFn(async () => {
await rp.save();
});
} catch (error) {
console.log('更新权限异常', error);
}
}
}
return { success: true };
}
export default NextAPI(handler);

View File

@@ -4,6 +4,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { onCreateApp } from './create';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
export type copyAppQuery = {};
@@ -17,19 +18,16 @@ async function handler(
req: ApiRequestProps<copyAppBody, copyAppQuery>,
res: ApiResponseType<any>
): Promise<copyAppResponse> {
const [{ app, tmbId }] = await Promise.all([
authApp({
req,
authToken: true,
per: WritePermissionVal,
appId: req.body.appId
}),
authUserPer({
req,
authToken: true,
per: WritePermissionVal
})
]);
const { app } = await authApp({
req,
authToken: true,
per: WritePermissionVal,
appId: req.body.appId
});
const { tmbId } = app.parentId
? await authApp({ req, appId: app.parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
const appId = await onCreateApp({
parentId: app.parentId,

View File

@@ -17,6 +17,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
export type CreateAppBody = {
parentId?: ParentIdType;
@@ -36,18 +37,15 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
}
// 凭证校验
const [{ teamId, tmbId, userId }] = await Promise.all([
authUserPer({ req, authToken: true, per: WritePermissionVal }),
...(parentId
? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })]
: [])
]);
const { teamId, tmbId, userId } = parentId
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
// 上限校验
await checkTeamAppLimit(teamId);
const tmb = await MongoTeamMember.findById({ _id: tmbId }, 'userId').populate<{
user: { avatar: string; username: string };
}>('user', 'avatar username');
user: { username: string };
}>('user', 'username');
// 创建app
const appId = await onCreateApp({
@@ -60,7 +58,7 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
chatConfig,
teamId,
tmbId,
userAvatar: tmb?.user?.avatar,
userAvatar: tmb?.avatar,
username: tmb?.user?.username
});

View File

@@ -16,7 +16,7 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
export type CreateAppFolderBody = {
@@ -33,21 +33,9 @@ async function handler(req: ApiRequestProps<CreateAppFolderBody>) {
}
// 凭证校验
const { teamId, tmbId } = await authUserPer({
req,
authToken: true,
per: TeamWritePermissionVal
});
if (parentId) {
// if it is not a root folder
await authApp({
req,
appId: parentId,
per: WritePermissionVal,
authToken: true
});
}
const { teamId, tmbId } = parentId
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
// Create app
await mongoSessionRun(async (session) => {

View File

@@ -9,6 +9,8 @@ import { onCreateApp, type CreateAppBody } from '../create';
import { AppSchema } from '@fastgpt/global/core/app/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
export type createHttpPluginQuery = {};
@@ -29,11 +31,9 @@ async function handler(
return Promise.reject('缺少参数');
}
const { teamId, tmbId, userId } = await authUserPer({
req,
authToken: true,
per: WritePermissionVal
});
const { teamId, tmbId, userId } = parentId
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
await mongoSessionRun(async (session) => {
// create http plugin folder

View File

@@ -20,7 +20,7 @@ import { ClientSession } from 'mongoose';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
@@ -79,7 +79,7 @@ async function handler(req: ApiRequestProps<AppUpdateBody, AppUpdateQuery>) {
await authUserPer({
req,
authToken: true,
per: TeamWritePermissionVal
per: TeamAppCreatePermissionVal
});
}
} else {

View File

@@ -98,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const isPlugin = app.type === AppTypeEnum.plugin;
const userQuestion: UserChatItemType = (() => {
const userQuestion: UserChatItemType = await (async () => {
if (isPlugin) {
return getPluginRunUserQuery({
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
@@ -107,9 +107,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
});
}
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
const latestHumanChat = chatMessages.pop() as UserChatItemType;
if (!latestHumanChat) {
throw new Error('User question is empty');
return Promise.reject('User question is empty');
}
return latestHumanChat;
})();
@@ -136,14 +136,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
const newHistories = concatHistories(histories, chatMessages);
const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
variables = {};
}
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -175,9 +175,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
lastInteractive: interactive,
chatConfig,
histories: newHistories,
stream: true,

View File

@@ -6,11 +6,9 @@ import {
getLLMModel,
getEmbeddingModel,
getDatasetModel,
getDefaultEmbeddingModel,
getVlmModel
getDefaultEmbeddingModel
} from '@fastgpt/service/core/ai/model';
import { checkTeamDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
@@ -18,6 +16,7 @@ import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller';
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
export type DatasetCreateQuery = {};
export type DatasetCreateBody = CreateDatasetParams;
@@ -41,25 +40,20 @@ async function handler(
} = req.body;
// auth
const [{ teamId, tmbId, userId }] = await Promise.all([
authUserPer({
req,
authToken: true,
authApiKey: true,
per: WritePermissionVal
}),
...(parentId
? [
authDataset({
req,
datasetId: parentId,
authToken: true,
authApiKey: true,
per: WritePermissionVal
})
]
: [])
]);
const { teamId, tmbId, userId } = parentId
? await authDataset({
req,
datasetId: parentId,
authToken: true,
authApiKey: true,
per: TeamDatasetCreatePermissionVal
})
: await authUserPer({
req,
authToken: true,
authApiKey: true,
per: TeamDatasetCreatePermissionVal
});
// check model valid
const vectorModelStore = getEmbeddingModel(vectorModel);

View File

@@ -5,8 +5,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import {
OwnerPermissionVal,
PerResourceTypeEnum,
WritePermissionVal
PerResourceTypeEnum
} from '@fastgpt/global/support/permission/constant';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
@@ -16,6 +15,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { getResourceClbsAndGroups } from '@fastgpt/service/support/permission/controller';
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
export type DatasetFolderCreateQuery = {};
export type DatasetFolderCreateBody = {
parentId?: string;
@@ -33,20 +33,20 @@ async function handler(
return Promise.reject(CommonErrEnum.missingParams);
}
const { tmbId, teamId } = await authUserPer({
req,
per: WritePermissionVal,
authToken: true
});
if (parentId) {
await authDataset({
datasetId: parentId,
per: WritePermissionVal,
req,
authToken: true
});
}
const { teamId, tmbId } = parentId
? await authDataset({
req,
datasetId: parentId,
authToken: true,
authApiKey: true,
per: TeamDatasetCreatePermissionVal
})
: await authUserPer({
req,
authToken: true,
authApiKey: true,
per: TeamDatasetCreatePermissionVal
});
await mongoSessionRun(async (session) => {
const dataset = await MongoDataset.create({

View File

@@ -23,7 +23,7 @@ import {
syncCollaborators
} from '@fastgpt/service/support/permission/inheritPermission';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
@@ -104,7 +104,7 @@ async function handler(
await authUserPer({
req,
authToken: true,
per: TeamWritePermissionVal
per: TeamDatasetCreatePermissionVal
});
}
} else {

View File

@@ -10,6 +10,7 @@ import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { defaultApp } from '@/web/core/app/constants';
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/utils';
async function handler(
req: NextApiRequest,
@@ -44,6 +45,7 @@ async function handler(
// auth balance
const { timezone, externalProvider } = await getUserChatInfoAndAuthTeamPoints(tmbId);
const lastInteractive = getLastInteractiveValue(history);
/* start process */
const { flowUsages, flowResponses, debugResponse, newVariables, workflowInteractiveResponse } =
@@ -65,6 +67,7 @@ async function handler(
},
runtimeNodes: nodes,
runtimeEdges: edges,
lastInteractive,
variables,
query: query,
chatConfig: defaultApp.chatConfig,

View File

@@ -4,12 +4,10 @@ import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import {
ManagePermissionVal,
WritePermissionVal
} from '@fastgpt/global/support/permission/constant';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { OpenApiErrEnum } from '@fastgpt/global/common/error/code/openapi';
import { TeamApikeyCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
async function handler(req: ApiRequestProps<EditApiKeyProps>): Promise<string> {
const { appId, name, limit } = req.body;
@@ -19,7 +17,7 @@ async function handler(req: ApiRequestProps<EditApiKeyProps>): Promise<string> {
const { teamId, tmbId } = await authUserPer({
req,
authToken: true,
per: WritePermissionVal
per: TeamApikeyCreatePermissionVal
});
return { teamId, tmbId };
} else {

View File

@@ -9,6 +9,8 @@ import { useIPFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequency
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
import { addOperationLog } from '@fastgpt/service/support/operationLog/addOperationLog';
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { username, password } = req.body as PostLoginProps;
@@ -64,6 +66,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
setCookie(res, token);
addOperationLog({
tmbId: userDetail.team.tmbId,
teamId: userDetail.team.teamId,
event: OperationLogEventEnum.LOGIN
});
return {
user: userDetail,
token

View File

@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Computed start hook params
const startHookText = (() => {
// Chat
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType;
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
// plugin
@@ -245,16 +245,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get chat histories
const newHistories = concatHistories(histories, chatMessages);
const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
// Assign values to runtimeNodes using variables
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
variables = {};
}
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -288,7 +289,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
chatConfig,

View File

@@ -139,7 +139,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Computed start hook params
const startHookText = (() => {
// Chat
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType;
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
// plugin
@@ -245,16 +245,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get chat histories
const newHistories = concatHistories(histories, chatMessages);
const interactive = getLastInteractiveValue(newHistories) || undefined;
// Get runtimeNodes
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, interactive));
if (isPlugin) {
// Assign values to runtimeNodes using variables
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
// Plugin runtime does not need global variables(It has been injected into the pluginInputNode)
variables = {};
}
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
runtimeNodes = rewriteNodeOutputByHistories(runtimeNodes, interactive);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
@@ -288,15 +288,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId,
responseChatItemId,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
runtimeEdges: initWorkflowEdgeStatus(edges, interactive),
variables,
query: removeEmptyUserInput(userQuestion.value),
lastInteractive: interactive,
chatConfig,
histories: newHistories,
stream,
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
workflowStreamResponse: workflowResponseWrite,
version: 'v2'
version: 'v2',
responseDetail
});
}
return Promise.reject('您的工作流版本过低,请重新发布一次');

View File

@@ -31,6 +31,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import TemplateMarketModal from '@/pageComponents/app/list/TemplateMarketModal';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import JsonImportModal from '@/pageComponents/app/list/JsonImportModal';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
const CreateModal = dynamic(() => import('@/pageComponents/app/list/CreateModal'));
const EditFolderModal = dynamic(
@@ -213,7 +214,7 @@ const MyApps = () => {
{(folderDetail
? folderDetail.permission.hasWritePer && folderDetail?.type !== AppTypeEnum.httpPlugin
: userInfo?.team.permission.hasWritePer) && (
: userInfo?.team.permission.hasAppCreatePer) && (
<MyMenu
size="md"
Button={
@@ -327,7 +328,7 @@ const MyApps = () => {
}: {
members?: string[];
groups?: string[];
permission: number;
permission: PermissionValueType;
}) => {
return postUpdateAppCollaborators({
members,

View File

@@ -29,6 +29,7 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
const EditFolderModal = dynamic(
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
@@ -138,7 +139,7 @@ const Dataset = () => {
{(folderDetail
? folderDetail.permission.hasWritePer
: userInfo?.team?.permission.hasWritePer) && (
: userInfo?.team?.permission.hasDatasetCreatePer) && (
<Box pl={[0, 4]}>
<MyMenu
size="md"
@@ -248,7 +249,7 @@ const Dataset = () => {
}: {
members?: string[];
groups?: string[];
permission: number;
permission: PermissionValueType;
}) =>
postUpdateDatasetCollaborators({
members,

View File

@@ -33,9 +33,21 @@ const reduceQueue = () => {
return global.qaQueueLen === 0;
};
const reduceQueueAndReturn = (delay = 0) => {
reduceQueue();
if (delay) {
setTimeout(() => {
generateQA();
}, delay);
} else {
generateQA();
}
};
export async function generateQA(): Promise<any> {
const max = global.systemEnv?.qaMaxProcess || 10;
addLog.debug(`[QA Queue] Queue size: ${global.qaQueueLen}`);
if (global.qaQueueLen >= max) return;
global.qaQueueLen++;
@@ -98,14 +110,12 @@ export async function generateQA(): Promise<any> {
return;
}
if (error) {
reduceQueue();
return generateQA();
return reduceQueueAndReturn();
}
// auth balance
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
reduceQueue();
return generateQA();
return reduceQueueAndReturn();
}
addLog.info(`[QA Queue] Start`);
@@ -137,14 +147,8 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
const qaArr = formatSplitText({ answer, rawText: text, llmModel: modelData }); // 格式化后的QA对
addLog.info(`[QA Queue] Finish`, {
time: Date.now() - startTime,
splitLength: qaArr.length,
usage: chatResponse.usage
});
// get vector and insert
const { insertLen } = await pushDataListToTrainingQueueByCollectionId({
await pushDataListToTrainingQueueByCollectionId({
teamId: data.teamId,
tmbId: data.tmbId,
collectionId: data.collectionId,
@@ -160,21 +164,21 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
await MongoDatasetTraining.findByIdAndDelete(data._id);
// add bill
if (insertLen > 0) {
pushQAUsage({
teamId: data.teamId,
tmbId: data.tmbId,
inputTokens: await countGptMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
billId: data.billId,
model: modelData.model
});
} else {
addLog.info(`QA result 0:`, { answer });
}
pushQAUsage({
teamId: data.teamId,
tmbId: data.tmbId,
inputTokens: await countGptMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
billId: data.billId,
model: modelData.model
});
addLog.info(`[QA Queue] Finish`, {
time: Date.now() - startTime,
splitLength: qaArr.length,
usage: chatResponse.usage
});
reduceQueue();
generateQA();
return reduceQueueAndReturn();
} catch (err: any) {
addLog.error(`[QA Queue] Error`, err);
await MongoDatasetTraining.updateOne(
@@ -188,9 +192,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
}
);
setTimeout(() => {
generateQA();
}, 1000);
return reduceQueueAndReturn(1000);
}
}

View File

@@ -35,6 +35,8 @@ const reduceQueueAndReturn = (delay = 0) => {
/* 索引生成队列。每导入一次,就是一个单独的线程 */
export async function generateVector(): Promise<any> {
const max = global.systemEnv?.vectorMaxProcess || 10;
addLog.debug(`[Vector Queue] Queue size: ${global.vectorQueueLen}`);
if (global.vectorQueueLen >= max) return;
global.vectorQueueLen++;
const start = Date.now();

View File

@@ -5,5 +5,21 @@ export enum LoginPageTypeEnum {
wechat = 'wechat'
}
export const PasswordRule =
/^(?:(?=.*\d)(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])|(?=.*\d)(?=.*[!@#$%^&*_])|(?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[!@#$%^&*_])|(?=.*[A-Z])(?=.*[!@#$%^&*_]))[\dA-Za-z!@#$%^&*_]{6,}$/;
export const checkPasswordRule = (password: string) => {
const patterns = [
/\d/, // Contains digits
/[a-z]/, // Contains lowercase letters
/[A-Z]/, // Contains uppercase letters
/[!@#$%^&*()_+=-]/ // Contains special characters
];
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
// Check length and valid characters
if (!validChars.test(password)) return false;
// Count how many patterns are satisfied
const matchCount = patterns.filter((pattern) => pattern.test(password)).length;
// Must satisfy at least 2 patterns
return matchCount >= 2;
};

View File

@@ -0,0 +1,9 @@
import { GET, POST, PUT } from '@/web/common/api/request';
import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import type { OperationListItemType } from '@fastgpt/global/support/operationLog/type';
export const getOperationLogs = (props: PaginationProps<PaginationProps>) =>
POST<PaginationResponse<OperationListItemType>>(
`/proApi/support/user/team/operationLog/list`,
props
);

View File

@@ -0,0 +1,57 @@
import * as createapi from '@/pages/api/core/app/create';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { TeamAppCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { getFakeUsers } from '@test/datas/users';
import { Call } from '@test/utils/request';
import { expect, it, describe } from 'vitest';
describe('create api', () => {
it('should return 200 when create app success', async () => {
const users = await getFakeUsers(2);
await MongoResourcePermission.create({
resourceType: 'team',
teamId: users.members[0].teamId,
resourceId: null,
tmbId: users.members[0].tmbId,
permission: TeamAppCreatePermissionVal
});
const res = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
auth: users.members[0],
body: {
modules: [],
name: 'testfolder',
type: AppTypeEnum.folder
}
});
expect(res.error).toBeUndefined();
expect(res.code).toBe(200);
const folderId = res.data as string;
const res2 = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
auth: users.members[0],
body: {
modules: [],
name: 'testapp',
type: AppTypeEnum.simple,
parentId: String(folderId)
}
});
expect(res2.error).toBeUndefined();
expect(res2.code).toBe(200);
expect(res2.data).toBeDefined();
const res3 = await Call<createapi.CreateAppBody, {}, {}>(createapi.default, {
auth: users.members[1],
body: {
modules: [],
name: 'testapp',
type: AppTypeEnum.simple,
parentId: String(folderId)
}
});
expect(res3.error).toBe(AppErrEnum.unAuthApp);
expect(res3.code).toBe(500);
});
});

View File

@@ -0,0 +1,54 @@
import * as createapi from '@/pages/api/core/dataset/create';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { TeamDatasetCreatePermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { getFakeUsers } from '@test/datas/users';
import { Call } from '@test/utils/request';
import { vi, describe, it, expect } from 'vitest';
describe('create dataset', () => {
it('should return 200 when create dataset success', async () => {
const users = await getFakeUsers(2);
await MongoResourcePermission.create({
resourceType: 'team',
teamId: users.members[0].teamId,
resourceId: null,
tmbId: users.members[0].tmbId,
permission: TeamDatasetCreatePermissionVal
});
const res = await Call<
createapi.DatasetCreateBody,
createapi.DatasetCreateQuery,
createapi.DatasetCreateResponse
>(createapi.default, {
auth: users.members[0],
body: {
name: 'folder',
intro: 'intro',
avatar: 'avatar',
type: DatasetTypeEnum.folder
}
});
expect(res.error).toBeUndefined();
expect(res.code).toBe(200);
const folderId = res.data as string;
const res2 = await Call<
createapi.DatasetCreateBody,
createapi.DatasetCreateQuery,
createapi.DatasetCreateResponse
>(createapi.default, {
auth: users.members[0],
body: {
name: 'test',
intro: 'intro',
avatar: 'avatar',
type: DatasetTypeEnum.dataset,
parentId: folderId
}
});
expect(res2.error).toBeUndefined();
expect(res2.code).toBe(200);
});
});

View File

@@ -0,0 +1,64 @@
import { EditApiKeyProps } from '@/global/support/openapi/api';
import * as createapi from '@/pages/api/support/openapi/create';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import {
TeamApikeyCreatePermissionVal,
TeamDatasetCreatePermissionVal
} from '@fastgpt/global/support/permission/user/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { getFakeUsers } from '@test/datas/users';
import { Call } from '@test/utils/request';
import { describe, it, expect } from 'vitest';
describe('create dataset', () => {
it('should return 200 when create dataset success', async () => {
const users = await getFakeUsers(2);
await MongoResourcePermission.create({
resourceType: 'team',
teamId: users.members[0].teamId,
resourceId: null,
tmbId: users.members[0].tmbId,
permission: TeamApikeyCreatePermissionVal
});
const res = await Call<EditApiKeyProps>(createapi.default, {
auth: users.members[0],
body: {
name: 'test',
limit: {
maxUsagePoints: 1000
}
}
});
expect(res.error).toBeUndefined();
expect(res.code).toBe(200);
await MongoResourcePermission.create({
resourceType: 'app',
teamId: users.members[1].teamId,
resourceId: null,
tmbId: users.members[1].tmbId,
permission: ManagePermissionVal
});
const app = await MongoApp.create({
name: 'a',
type: 'simple',
tmbId: users.members[1].tmbId,
teamId: users.members[1].teamId
});
const res2 = await Call<EditApiKeyProps>(createapi.default, {
auth: users.members[1],
body: {
appId: app._id,
name: 'test',
limit: {
maxUsagePoints: 1000
}
}
});
expect(res2.error).toBeUndefined();
expect(res2.code).toBe(200);
});
});

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