Compare commits
12 Commits
v4.8.15-fi
...
v4.8.15-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c41def5fbb | ||
|
|
b596976a96 | ||
|
|
e71708ee76 | ||
|
|
ddddd998c8 | ||
|
|
181b854342 | ||
|
|
40af63b1dd | ||
|
|
d2a56a2fed | ||
|
|
4b9b0dbbb9 | ||
|
|
c0135f5f21 | ||
|
|
8a4715293e | ||
|
|
69dc927a5a | ||
|
|
d5752ddbaa |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.3(需要初始化)'
|
||||
title: '升级到 V4.3(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.3 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4(需要初始化)'
|
||||
title: '升级到 V4.4(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.1(需要初始化)'
|
||||
title: '升级到 V4.4.1(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.1 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.2(需要初始化)'
|
||||
title: '升级到 V4.4.2(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.4.5(需要初始化)'
|
||||
title: 'V4.4.5(包含升级脚本)'
|
||||
description: 'FastGPT V4.4.5 更新'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6(需要初始化)'
|
||||
title: 'V4.6(包含升级脚本)'
|
||||
description: 'FastGPT V4.6 更新'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.2(需要初始化)'
|
||||
title: 'V4.6.2(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.2'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.3(需要初始化)'
|
||||
title: 'V4.6.3(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.3'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.4(需要初始化)'
|
||||
title: 'V4.6.4(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.4'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.9(需要初始化)'
|
||||
title: 'V4.6.9(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.9更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.7.1(需要初始化)'
|
||||
title: 'V4.7.1(包含升级脚本)'
|
||||
description: 'FastGPT V4.7.1 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.1(需要初始化)'
|
||||
title: 'V4.8.1(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.1 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.10(需要初始化)'
|
||||
title: 'V4.8.10(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.10 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.12(需要初始化)'
|
||||
title: 'V4.8.12(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.12 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.15(初始化)'
|
||||
title: 'V4.8.15(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.15 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -23,23 +23,35 @@ weight: 809
|
||||
|
||||
## 升级指南
|
||||
|
||||
- 更新 FastGPT 镜像 tag: v4.8.15
|
||||
- 更新 FastGPT 商业版镜像 tag: v4.8.15 (fastgpt-pro镜像)
|
||||
- 更新 fastgpt 镜像 tag: v4.8.15-fix2
|
||||
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.15
|
||||
- Sandbox 镜像,可以不更新
|
||||
|
||||
|
||||
## 运行初始化脚本
|
||||
## 运行升级脚本
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/admin/initv4815' \
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv4815' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
会重置应用定时执行的字段,把 null 去掉,减少索引大小。
|
||||
|
||||
----
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/refreshFreeUser' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
重新计算一次免费版用户的时长,之前有版本升级时没有重新计算时间,导致会误发通知。
|
||||
|
||||
|
||||
## 完整更新内容
|
||||
|
||||
@@ -61,4 +73,4 @@ curl --location --request POST 'https://{{host}}/admin/initv4815' \
|
||||
16. 修复 - 语言播放鉴权问题。
|
||||
17. 修复 - 插件应用知识库引用上限始终为 3000
|
||||
18. 修复 - 工作流编辑记录存储上限,去掉本地存储,增加异常离开时,强制自动保存。
|
||||
19. 修复 - 工作流特殊变量替换问题。($开头的字符串无法替换)
|
||||
19. 修复 - 工作流特殊变量替换问题。($开头的字符串无法替换)
|
||||
|
||||
20
docSite/content/zh-cn/docs/development/upgrading/4816.md
Normal file
20
docSite/content/zh-cn/docs/development/upgrading/4816.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: 'V4.8.16(进行中)'
|
||||
description: 'FastGPT V4.8.16 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 808
|
||||
---
|
||||
|
||||
|
||||
## 完整更新内容
|
||||
|
||||
1.
|
||||
2. 新增 - 商业版支持 API 知识库和链接集合定时同步。
|
||||
3. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。
|
||||
4. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。
|
||||
5. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。
|
||||
6. 修复 - 站点同步知识库,链接同步时未使用选择器。
|
||||
7. 修复 - 简易模式转工作流,没有把系统配置项转化。
|
||||
8. 修复 - 插件独立运行,变量初始值未赋上。
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.4(需要初始化)'
|
||||
title: 'V4.8.4(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.4 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.5(需要初始化)'
|
||||
title: 'V4.8.5(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.5 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.6(需要初始化)'
|
||||
title: 'V4.8.6(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.6 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.8(需要初始化)'
|
||||
title: 'V4.8.8(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.8 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -55,14 +55,14 @@ docker-compose up -d
|
||||
|
||||
## 执行升级初始化脚本
|
||||
|
||||
镜像更新完后,可以查看文档中的`版本介绍`,通常需要执行升级脚本的版本都会标明`需要初始化`,打开对应的文档,参考说明执行初始化脚本即可,大部分时候都是需要发送一个`POST`请求。
|
||||
镜像更新完后,可以查看文档中的`版本介绍`,通常需要执行升级脚本的版本都会标明`包含升级脚本`,打开对应的文档,参考说明执行**升级脚本**即可,大部分时候都是需要发送一个`POST`请求。
|
||||
|
||||
|
||||
## QA
|
||||
|
||||
### 为什么需要初始化
|
||||
### 为什么需要执行升级脚本
|
||||
|
||||
数据表出现大幅度变更,无法通过设置默认值,或复杂度较高时,会通过初始化来更新部分数据表字段。
|
||||
数据表出现大幅度变更,无法通过设置默认值,或复杂度较高时,会通过升级脚本来更新部分数据表字段。
|
||||
严格按初始化步骤进行操作,不会造成旧数据丢失。但在初始化过程中,如果数据量大,需要初始化的时间较长,这段时间可能会造成服务无法正常使用。
|
||||
|
||||
### {{host}} 是什么
|
||||
|
||||
@@ -121,8 +121,8 @@ services:
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -79,8 +79,8 @@ services:
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -60,8 +60,8 @@ services:
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -72,7 +72,7 @@ export const ERROR_RESPONSE: Record<
|
||||
[ERROR_ENUM.tooManyRequest]: {
|
||||
code: 429,
|
||||
statusText: ERROR_ENUM.tooManyRequest,
|
||||
message: 'Too many request',
|
||||
message: i18nT('common:error.too_many_request'),
|
||||
data: null
|
||||
},
|
||||
[ERROR_ENUM.insufficientQuota]: {
|
||||
|
||||
@@ -29,7 +29,7 @@ export const simpleText = (text = '') => {
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export function replaceVariable(text: any, obj: Record<string, string | number>) {
|
||||
if (!(typeof text === 'string')) return text;
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
|
||||
5
packages/global/core/dataset/api.d.ts
vendored
5
packages/global/core/dataset/api.d.ts
vendored
@@ -17,6 +17,9 @@ export type DatasetUpdateBody = {
|
||||
externalReadUrl?: DatasetSchemaType['externalReadUrl'];
|
||||
defaultPermission?: DatasetSchemaType['defaultPermission'];
|
||||
apiServer?: DatasetSchemaType['apiServer'];
|
||||
|
||||
// sync schedule
|
||||
autoSync?: boolean;
|
||||
};
|
||||
|
||||
/* ================= collection ===================== */
|
||||
@@ -47,6 +50,8 @@ export type CreateDatasetCollectionParams = DatasetCollectionChunkMetadataType &
|
||||
tags?: string[];
|
||||
|
||||
createTime?: Date;
|
||||
updateTime?: Date;
|
||||
nextSyncTime?: Date;
|
||||
};
|
||||
|
||||
export type ApiCreateDatasetCollectionParams = DatasetCollectionChunkMetadataType & {
|
||||
|
||||
@@ -82,7 +82,8 @@ export const DatasetCollectionTypeMap = {
|
||||
|
||||
export enum DatasetCollectionSyncResultEnum {
|
||||
sameRaw = 'sameRaw',
|
||||
success = 'success'
|
||||
success = 'success',
|
||||
failed = 'failed'
|
||||
}
|
||||
export const DatasetCollectionSyncResultMap = {
|
||||
[DatasetCollectionSyncResultEnum.sameRaw]: {
|
||||
@@ -90,6 +91,9 @@ export const DatasetCollectionSyncResultMap = {
|
||||
},
|
||||
[DatasetCollectionSyncResultEnum.success]: {
|
||||
label: i18nT('common:core.dataset.collection.sync.result.success')
|
||||
},
|
||||
[DatasetCollectionSyncResultEnum.failed]: {
|
||||
label: i18nT('dataset:sync_collection_failed')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
9
packages/global/core/dataset/type.d.ts
vendored
9
packages/global/core/dataset/type.d.ts
vendored
@@ -34,8 +34,7 @@ export type DatasetSchemaType = {
|
||||
inheritPermission: boolean;
|
||||
apiServer?: APIFileServer;
|
||||
|
||||
syncSchedule?: { cronString: string; timezone: string };
|
||||
syncNextTime?: Date;
|
||||
autoSync?: boolean;
|
||||
|
||||
// abandon
|
||||
externalReadUrl?: string;
|
||||
@@ -65,11 +64,13 @@ export type DatasetCollectionSchemaType = {
|
||||
fileId?: string; // local file id
|
||||
rawLink?: string; // link url
|
||||
externalFileId?: string; //external file id
|
||||
apiFileId?: string; // api file id
|
||||
externalFileUrl?: string; // external import url
|
||||
|
||||
nextSyncTime?: Date;
|
||||
|
||||
rawTextLength?: number;
|
||||
hashRawText?: string;
|
||||
externalFileUrl?: string; // external import url
|
||||
apiFileId?: string; // api file id
|
||||
metadata?: {
|
||||
webPageSelector?: string;
|
||||
relatedImgId?: string; // The id of the associated image collections
|
||||
|
||||
@@ -251,6 +251,7 @@ export const getReferenceVariableValue = ({
|
||||
return variables[outputId];
|
||||
}
|
||||
|
||||
// 避免 value 刚好就是二个元素的字符串数组
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
if (!node) {
|
||||
return value;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
TrainingModeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
|
||||
import { MongoDatasetCollection } from './schema';
|
||||
import {
|
||||
@@ -24,6 +27,7 @@ import { getLLMModel, getVectorModel } from '../../ai/model';
|
||||
import { pushDataListToTrainingQueue } from '../training/controller';
|
||||
import { MongoImage } from '../../../common/file/image/schema';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
export const createCollectionAndInsertData = async ({
|
||||
dataset,
|
||||
@@ -72,6 +76,17 @@ export const createCollectionAndInsertData = async ({
|
||||
|
||||
hashRawText: hashStr(rawText),
|
||||
rawTextLength: rawText.length,
|
||||
nextSyncTime: (() => {
|
||||
if (!dataset.autoSync) return undefined;
|
||||
if (
|
||||
[DatasetCollectionTypeEnum.link, DatasetCollectionTypeEnum.apiFile].includes(
|
||||
createCollectionParams.type
|
||||
)
|
||||
) {
|
||||
return addDays(new Date(), 1);
|
||||
}
|
||||
return undefined;
|
||||
})(),
|
||||
session
|
||||
});
|
||||
|
||||
@@ -155,10 +170,8 @@ export async function createOneCollection({
|
||||
|
||||
fileId,
|
||||
rawLink,
|
||||
|
||||
externalFileId,
|
||||
externalFileUrl,
|
||||
|
||||
apiFileId,
|
||||
|
||||
hashRawText,
|
||||
@@ -166,7 +179,10 @@ export async function createOneCollection({
|
||||
metadata = {},
|
||||
session,
|
||||
tags,
|
||||
createTime
|
||||
|
||||
createTime,
|
||||
updateTime,
|
||||
nextSyncTime
|
||||
}: CreateOneCollectionParams) {
|
||||
// Create collection tags
|
||||
const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session });
|
||||
@@ -197,7 +213,10 @@ export async function createOneCollection({
|
||||
rawTextLength,
|
||||
hashRawText,
|
||||
tags: collectionTags,
|
||||
createTime
|
||||
|
||||
createTime,
|
||||
updateTime,
|
||||
nextSyncTime
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { TrainingTypeMap, DatasetCollectionTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
@@ -10,100 +10,95 @@ import {
|
||||
|
||||
export const DatasetColCollectionName = 'dataset_collections';
|
||||
|
||||
const DatasetCollectionSchema = new Schema(
|
||||
{
|
||||
parentId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetColCollectionName,
|
||||
default: null
|
||||
},
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
},
|
||||
datasetId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetCollectionName,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: Object.keys(DatasetCollectionTypeMap),
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
forbid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// chunk filed
|
||||
trainingType: {
|
||||
type: String,
|
||||
enum: Object.keys(TrainingTypeMap)
|
||||
},
|
||||
chunkSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
chunkSplitter: {
|
||||
type: String
|
||||
},
|
||||
qaPrompt: {
|
||||
type: String
|
||||
},
|
||||
ocrParse: Boolean,
|
||||
|
||||
tags: {
|
||||
type: [String],
|
||||
default: []
|
||||
},
|
||||
|
||||
// local file collection
|
||||
fileId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dataset.files'
|
||||
},
|
||||
// web link collection
|
||||
rawLink: String,
|
||||
// api collection
|
||||
apiFileId: String,
|
||||
// external collection
|
||||
externalFileId: String,
|
||||
externalFileUrl: String, // external import url
|
||||
|
||||
// metadata
|
||||
rawTextLength: Number,
|
||||
hashRawText: String,
|
||||
metadata: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
const DatasetCollectionSchema = new Schema({
|
||||
parentId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetColCollectionName,
|
||||
default: null
|
||||
},
|
||||
{
|
||||
// Auto update updateTime
|
||||
timestamps: {
|
||||
updatedAt: 'updateTime'
|
||||
}
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
},
|
||||
datasetId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetCollectionName,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: Object.keys(DatasetCollectionTypeMap),
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
forbid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// chunk filed
|
||||
trainingType: {
|
||||
type: String,
|
||||
enum: Object.keys(TrainingTypeMap)
|
||||
},
|
||||
chunkSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
chunkSplitter: {
|
||||
type: String
|
||||
},
|
||||
qaPrompt: {
|
||||
type: String
|
||||
},
|
||||
ocrParse: Boolean,
|
||||
|
||||
tags: {
|
||||
type: [String],
|
||||
default: []
|
||||
},
|
||||
|
||||
// local file collection
|
||||
fileId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dataset.files'
|
||||
},
|
||||
// web link collection
|
||||
rawLink: String,
|
||||
// api collection
|
||||
apiFileId: String,
|
||||
// external collection
|
||||
externalFileId: String,
|
||||
externalFileUrl: String, // external import url
|
||||
|
||||
// next sync time
|
||||
nextSyncTime: Date,
|
||||
|
||||
// metadata
|
||||
rawTextLength: Number,
|
||||
hashRawText: String,
|
||||
metadata: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
// auth file
|
||||
@@ -122,6 +117,16 @@ try {
|
||||
// create time filter
|
||||
DatasetCollectionSchema.index({ teamId: 1, datasetId: 1, createTime: 1 });
|
||||
|
||||
// next sync time filter
|
||||
DatasetCollectionSchema.index(
|
||||
{ type: 1, nextSyncTime: -1 },
|
||||
{
|
||||
partialFilterExpression: {
|
||||
nextSyncTime: { $exists: true }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get collection by external file id
|
||||
DatasetCollectionSchema.index(
|
||||
{ datasetId: 1, externalFileId: 1 },
|
||||
|
||||
@@ -163,6 +163,10 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
|
||||
...sourceReadType
|
||||
});
|
||||
|
||||
if (!rawText) {
|
||||
return DatasetCollectionSyncResultEnum.failed;
|
||||
}
|
||||
|
||||
// Check if the original text is the same: skip if same
|
||||
const hashRawText = hashStr(rawText);
|
||||
if (collection.hashRawText && hashRawText === collection.hashRawText) {
|
||||
@@ -178,28 +182,30 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
|
||||
createCollectionParams: {
|
||||
teamId: collection.teamId,
|
||||
tmbId: collection.tmbId,
|
||||
datasetId: collection.datasetId._id,
|
||||
name: collection.name,
|
||||
datasetId: collection.datasetId._id,
|
||||
parentId: collection.parentId,
|
||||
type: collection.type,
|
||||
|
||||
trainingType: collection.trainingType,
|
||||
chunkSize: collection.chunkSize,
|
||||
chunkSplitter: collection.chunkSplitter,
|
||||
qaPrompt: collection.qaPrompt,
|
||||
|
||||
fileId: collection.fileId,
|
||||
rawLink: collection.rawLink,
|
||||
externalFileId: collection.externalFileId,
|
||||
externalFileUrl: collection.externalFileUrl,
|
||||
apiFileId: collection.apiFileId,
|
||||
|
||||
rawTextLength: rawText.length,
|
||||
hashRawText,
|
||||
rawTextLength: rawText.length,
|
||||
|
||||
metadata: collection.metadata,
|
||||
|
||||
tags: collection.tags,
|
||||
createTime: collection.createTime,
|
||||
|
||||
parentId: collection.parentId,
|
||||
trainingType: collection.trainingType,
|
||||
chunkSize: collection.chunkSize,
|
||||
chunkSplitter: collection.chunkSplitter,
|
||||
qaPrompt: collection.qaPrompt,
|
||||
metadata: collection.metadata
|
||||
updateTime: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -91,17 +91,7 @@ const DatasetSchema = new Schema({
|
||||
type: Object
|
||||
},
|
||||
|
||||
syncSchedule: {
|
||||
cronString: {
|
||||
type: String
|
||||
},
|
||||
timezone: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
syncNextTime: {
|
||||
type: Date
|
||||
},
|
||||
autoSync: Boolean,
|
||||
|
||||
// abandoned
|
||||
externalReadUrl: {
|
||||
@@ -112,7 +102,6 @@ const DatasetSchema = new Schema({
|
||||
|
||||
try {
|
||||
DatasetSchema.index({ teamId: 1 });
|
||||
DatasetSchema.index({ syncSchedule: 1, syncNextTime: -1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,8 @@ export async function pushDataListToTrainingQueue({
|
||||
a: item.a,
|
||||
chunkIndex: item.chunkIndex ?? 0,
|
||||
weight: weight ?? 0,
|
||||
indexes: item.indexes
|
||||
indexes: item.indexes,
|
||||
retryCount: 5
|
||||
})),
|
||||
{
|
||||
session,
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function getTmpData<T extends TmpDataEnum>({
|
||||
}).lean()) as TmpDataSchema<TmpDataType<T>> | null;
|
||||
}
|
||||
|
||||
export async function setTmpData<T extends TmpDataEnum>({
|
||||
export function setTmpData<T extends TmpDataEnum>({
|
||||
type,
|
||||
metadata,
|
||||
data
|
||||
@@ -33,7 +33,7 @@ export async function setTmpData<T extends TmpDataEnum>({
|
||||
metadata: TmpDataMetadata<T>;
|
||||
data: TmpDataType<T>;
|
||||
}) {
|
||||
return await MongoTmpData.updateOne(
|
||||
return MongoTmpData.updateOne(
|
||||
{
|
||||
dataId: getDataId(type, metadata)
|
||||
},
|
||||
@@ -43,7 +43,8 @@ export async function setTmpData<T extends TmpDataEnum>({
|
||||
expireAt: addMilliseconds(Date.now(), TmpDataExpireTime[type])
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
upsert: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M17.0801 5.93836H17.0764C18.9297 5.93842 19.8659 5.94335 20.5834 6.30893C21.2232 6.6349 21.7433 7.15504 22.0693 7.79479C22.4399 8.52209 22.4399 9.47418 22.4399 11.3784V16.56C22.4399 18.4641 22.4399 19.4162 22.0693 20.1435C21.7433 20.7833 21.2232 21.3034 20.5834 21.6294C19.8561 22 18.904 22 16.9999 22H7.00005C5.09588 22 4.14379 22 3.41649 21.6294C2.77674 21.3034 2.25661 20.7833 1.93064 20.1435C1.56006 19.4162 1.56006 18.4641 1.56006 16.56V11.3784C1.56006 9.47418 1.56006 8.52209 1.93064 7.79479C2.25661 7.15504 2.77674 6.6349 3.41649 6.30893C4.14379 5.93835 5.09588 5.93835 7.00006 5.93835H7.15448V5.59988C7.15448 3.61165 8.76625 1.99988 10.7545 1.99988H13.4801C15.4684 1.99988 17.0801 3.61165 17.0801 5.59988V5.93836ZM10.7545 3.99988H13.4801C14.3638 3.99988 15.0801 4.71622 15.0801 5.59988V5.93835H9.15448V5.59988C9.15448 4.71622 9.87082 3.99988 10.7545 3.99988ZM16.9999 7.93835H7.00006C6.01497 7.93835 5.39643 7.93991 4.92981 7.97803C4.48653 8.01425 4.35919 8.07326 4.32447 8.09095C4.06104 8.22517 3.84687 8.43934 3.71265 8.70277C3.69496 8.73748 3.63596 8.86483 3.59974 9.30811C3.57866 9.5661 3.56876 9.87054 3.56412 10.2577H20.4358C20.4312 9.87054 20.4213 9.5661 20.4002 9.3081C20.364 8.86483 20.305 8.73748 20.2873 8.70277C20.153 8.43934 19.9389 8.22517 19.6754 8.09095C19.6407 8.07326 19.5134 8.01425 19.0701 7.97803C18.6035 7.93991 17.9849 7.93835 16.9999 7.93835ZM20.4399 12.0577H14.6355V15.1893C14.6355 15.5206 14.3669 15.7893 14.0355 15.7893H9.96433C9.63296 15.7893 9.36433 15.5206 9.36433 15.1893V12.0577H3.56006V16.56C3.56006 17.5451 3.56161 18.1636 3.59974 18.6302C3.63596 19.0735 3.69496 19.2008 3.71265 19.2356C3.84687 19.499 4.06104 19.7132 4.32447 19.8474C4.35919 19.8651 4.48653 19.9241 4.92981 19.9603C5.39643 19.9984 6.01496 20 7.00005 20H16.9999C17.9849 20 18.6035 19.9984 19.0701 19.9603C19.5134 19.9241 19.6407 19.8651 19.6754 19.8474C19.9389 19.7132 20.153 19.499 20.2873 19.2356C20.305 19.2008 20.364 19.0735 20.4002 18.6302C20.4383 18.1636 20.4399 17.5451 20.4399 16.56V12.0577ZM12.8355 12.0577H11.1643V13.9893H12.8355V12.0577Z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" >
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2334 4.94869H14.2303C15.7747 4.94875 16.5549 4.95285 17.1528 5.2575C17.6859 5.52915 18.1194 5.96259 18.391 6.49572C18.6998 7.1018 18.6998 7.89521 18.6998 9.48202V13.8C18.6998 15.3869 18.6998 16.1803 18.391 16.7863C18.1194 17.3195 17.6859 17.7529 17.1528 18.0246C16.5467 18.3334 15.7533 18.3334 14.1665 18.3334H5.83332C4.2465 18.3334 3.4531 18.3334 2.84701 18.0246C2.31389 17.7529 1.88044 17.3195 1.6088 16.7863C1.29999 16.1803 1.29999 15.3869 1.29999 13.8V9.48202C1.29999 7.89521 1.29999 7.1018 1.6088 6.49572C1.88044 5.96259 2.31389 5.52915 2.84701 5.2575C3.4531 4.94869 4.24651 4.94869 5.83332 4.94869H5.96201V4.66663C5.96201 3.00977 7.30515 1.66663 8.96201 1.66663H11.2334C12.8902 1.66663 14.2334 3.00977 14.2334 4.66663V4.94869ZM8.96201 3.33329H11.2334C11.9698 3.33329 12.5667 3.93025 12.5667 4.66663V4.94869H7.62867V4.66663C7.62867 3.93025 8.22563 3.33329 8.96201 3.33329ZM14.1665 6.61536H5.83332C5.01241 6.61536 4.49697 6.61665 4.10811 6.64842C3.73872 6.6786 3.63259 6.72778 3.60367 6.74252C3.38414 6.85437 3.20567 7.03285 3.09381 7.25237C3.07907 7.2813 3.0299 7.38742 2.99972 7.75682C2.98216 7.97181 2.97391 8.22551 2.97004 8.54814H17.0298C17.0259 8.22551 17.0177 7.97181 17.0001 7.75682C16.9699 7.38742 16.9207 7.2813 16.906 7.25237C16.7941 7.03285 16.6157 6.85437 16.3961 6.74252C16.3672 6.72778 16.2611 6.6786 15.8917 6.64842C15.5028 6.61665 14.9874 6.61536 14.1665 6.61536ZM17.0332 10.0481H12.1962V12.6578C12.1962 12.9339 11.9723 13.1578 11.6962 13.1578H8.30355C8.02741 13.1578 7.80355 12.9339 7.80355 12.6578V10.0481H2.96665V13.8C2.96665 14.6209 2.96795 15.1364 2.99972 15.5252C3.0299 15.8946 3.07907 16.0008 3.09381 16.0297C3.20567 16.2492 3.38414 16.4277 3.60367 16.5395C3.63259 16.5543 3.73872 16.6035 4.10811 16.6336C4.49696 16.6654 5.01241 16.6667 5.83332 16.6667H14.1665C14.9874 16.6667 15.5028 16.6654 15.8917 16.6336C16.2611 16.6035 16.3672 16.5543 16.3961 16.5395C16.6157 16.4277 16.7941 16.2492 16.906 16.0297C16.9207 16.0008 16.9699 15.8946 17.0001 15.5252C17.0319 15.1364 17.0332 14.6209 17.0332 13.8V10.0481ZM10.6962 10.0481H9.30355V11.6578H10.6962V10.0481Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -42,7 +42,7 @@ export const cronString2Fields = (cronString?: string) => {
|
||||
};
|
||||
|
||||
export const cronString2Label = (
|
||||
cronString: string,
|
||||
cronString = '',
|
||||
t: any // i18nT
|
||||
) => {
|
||||
const cronField = cronString2Fields(cronString);
|
||||
|
||||
@@ -193,6 +193,18 @@ export const MultipleRowArraySelect = ({
|
||||
ref: ref,
|
||||
handler: onClose
|
||||
});
|
||||
const onChange = useCallback(
|
||||
(val: any[][]) => {
|
||||
// Filter invalid value
|
||||
const validList = val.filter((item) => {
|
||||
const listItem = list.find((v) => v.value === item[0]);
|
||||
if (!listItem) return false;
|
||||
return listItem.children?.some((v) => v.value === item[1]);
|
||||
});
|
||||
onSelect(validList);
|
||||
},
|
||||
[onSelect]
|
||||
);
|
||||
|
||||
const RenderList = useCallback(
|
||||
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
|
||||
@@ -213,9 +225,9 @@ export const MultipleRowArraySelect = ({
|
||||
const newValue = [parentValue, item.value];
|
||||
|
||||
if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) {
|
||||
onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||
onChange(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||
} else {
|
||||
onSelect([...newValues, newValue]);
|
||||
onChange([...newValues, newValue]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,14 +43,16 @@ export const useI18nLng = () => {
|
||||
setLang(lang);
|
||||
|
||||
await i18n?.changeLanguage?.(lang);
|
||||
if (prevLang && prevLang !== lang) {
|
||||
|
||||
if (!i18n.hasResourceBundle(lang, 'common') && prevLang !== lang) {
|
||||
window?.location?.reload?.();
|
||||
}
|
||||
};
|
||||
|
||||
const setUserDefaultLng = () => {
|
||||
const setUserDefaultLng = (forceGetDefaultLng: boolean = false) => {
|
||||
if (!navigator || !localStorage) return;
|
||||
if (getLang()) return onChangeLng(getLang() as string);
|
||||
|
||||
if (getLang() && !forceGetDefaultLng) return onChangeLng(getLang() as string);
|
||||
|
||||
const lang = languageMap[navigator.language] || 'en';
|
||||
|
||||
|
||||
@@ -874,6 +874,7 @@
|
||||
"error.fileNotFound": "File not found~",
|
||||
"error.inheritPermissionError": "Inherit permission Error",
|
||||
"error.missingParams": "Insufficient parameters",
|
||||
"error.too_many_request": "Too many request",
|
||||
"error.upload_file_error_filename": "{{name}} Upload Failed",
|
||||
"error.username_empty": "Account cannot be empty",
|
||||
"extraction_results": "Extraction Results",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "Import",
|
||||
"api_file": "API Dataset",
|
||||
"api_url": "API Url",
|
||||
"chunk_max_tokens": "max_tokens",
|
||||
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
||||
"collection.Create update time": "Creation/Update Time",
|
||||
"collection.Training type": "Training",
|
||||
"collection_not_support_retraining": "This collection type does not support retuning parameters",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "Collection Tags",
|
||||
"common_dataset": "General Dataset",
|
||||
"common_dataset_desc": "Build a Dataset by importing files, web links, or manual input.",
|
||||
"config_sync_schedule": "Configure scheduled synchronization",
|
||||
"confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.",
|
||||
"core.dataset.import.Adjust parameters": "Adjust parameters",
|
||||
"custom_data_process_params": "Custom",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.",
|
||||
"import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens",
|
||||
"is_open_schedule": "Enable scheduled synchronization",
|
||||
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
||||
"open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.",
|
||||
"permission.des.manage": "Can manage the entire knowledge base data and information",
|
||||
"permission.des.read": "View knowledge base content",
|
||||
"permission.des.write": "Ability to add and change knowledge base content",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "The retraining task has been submitted",
|
||||
"same_api_collection": "The same API set exists",
|
||||
"start_sync_website_tip": "Confirm to start synchronizing data? \nThe old data will be deleted and retrieved again, please confirm!",
|
||||
"sync_collection_failed": "Synchronization collection error, please check whether the source file can be accessed normally",
|
||||
"sync_schedule": "Timing synchronization",
|
||||
"sync_schedule_tip": "Only existing collections will be synchronized. \nIncludes linked collections and all collections in the API knowledge base. \nThe system will poll for updates every day, and the specific update time cannot be determined.",
|
||||
"tag.Add New": "Add New",
|
||||
"tag.Add_new_tag": "Add New Tag",
|
||||
"tag.Edit_tag": "Edit Tag",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "Total {{total}} tags",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt",
|
||||
"training_mode": "Chunk mode",
|
||||
"vector_model_max_tokens_tip": "Each chunk of data has a maximum length of 3000 tokens",
|
||||
"website_dataset": "Website Sync",
|
||||
"website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link."
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@
|
||||
"error.fileNotFound": "文件找不到了~",
|
||||
"error.inheritPermissionError": "权限继承错误",
|
||||
"error.missingParams": "参数缺失",
|
||||
"error.too_many_request": "请求太频繁了,请稍后重试",
|
||||
"error.upload_file_error_filename": "{{name}} 上传失败",
|
||||
"error.username_empty": "账号不能为空",
|
||||
"extraction_results": "提取结果",
|
||||
@@ -1179,7 +1180,6 @@
|
||||
"user.password_message": "密码最少 4 位最多 60 位",
|
||||
"user.team.Balance": "团队余额",
|
||||
"user.team.Check Team": "切换",
|
||||
|
||||
"user.team.Invite Member": "邀请成员",
|
||||
"user.team.Invite Member Tips": "对方可查阅或使用团队内的其他资源",
|
||||
"user.team.Leave Team": "离开团队",
|
||||
@@ -1192,13 +1192,10 @@
|
||||
"user.team.Processing invitations Tips": "你有 {{amount}} 个需要处理的团队邀请",
|
||||
"user.team.Remove Member Confirm Tip": "确认将 {{username}} 移出团队?",
|
||||
"user.team.Select Team": "团队选择",
|
||||
|
||||
"user.team.Switch Team Failed": "切换团队异常",
|
||||
"user.team.Tags Async": "保存",
|
||||
|
||||
"user.team.Team Tags Async": "标签同步",
|
||||
"user.team.Team Tags Async Success": "链接报错成功,标签信息更新",
|
||||
|
||||
"user.team.invite.Accept Confirm": "确认加入该团队?",
|
||||
"user.team.invite.Accepted": "已加入团队",
|
||||
"user.team.invite.Deal Width Footer Tip": "处理完会自动关闭噢~",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "添加文件",
|
||||
"api_file": "API 文件库",
|
||||
"api_url": "接口地址",
|
||||
"chunk_max_tokens": "分块上限",
|
||||
"close_auto_sync": "确认关闭自动同步功能?",
|
||||
"collection.Create update time": "创建/更新时间",
|
||||
"collection.Training type": "训练模式",
|
||||
"collection_not_support_retraining": "该集合类型不支持重新调整参数",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "集合标签",
|
||||
"common_dataset": "通用知识库",
|
||||
"common_dataset_desc": "可通过导入文件、网页链接或手动录入形式构建知识库",
|
||||
"config_sync_schedule": "配置定时同步",
|
||||
"confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作,需要对您知识库内所有数据进行重新索引,时间可能较长,请确保账号内剩余积分充足。\n\n此外,你还需要注意修改选择该知识库的应用,避免它们与其他索引模型知识库混用。",
|
||||
"core.dataset.import.Adjust parameters": "调整参数",
|
||||
"custom_data_process_params": "自定义",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "按结束符号进行分段,并将多个分段组成一个分块,该值决定了分块的预估大小,如果会有上下浮动。",
|
||||
"import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多AI 积分:{{price}} 积分/1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens",
|
||||
"is_open_schedule": "启用定时同步",
|
||||
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
||||
"open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。",
|
||||
"permission.des.manage": "可管理整个知识库数据和信息",
|
||||
"permission.des.read": "可查看知识库内容",
|
||||
"permission.des.write": "可增加和变更知识库内容",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "重新训练任务已提交",
|
||||
"same_api_collection": "存在相同的 API 集合",
|
||||
"start_sync_website_tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
|
||||
"sync_collection_failed": "同步集合错误,请检查是否能正常访问源文件",
|
||||
"sync_schedule": "定时同步",
|
||||
"sync_schedule_tip": "仅会同步已存在的集合。包括链接集合以及 API 知识库里所有集合。系统会每天进行轮询更新,无法确定具体的更新时间。",
|
||||
"tag.Add New": "新建",
|
||||
"tag.Add_new_tag": "新建标签",
|
||||
"tag.Edit_tag": "编辑标签",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "共{{total}}个标签",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引",
|
||||
"training_mode": "处理方式",
|
||||
"vector_model_max_tokens_tip": "每个分块数据,最大长度为 3000 tokens",
|
||||
"website_dataset": "Web 站点同步",
|
||||
"website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库"
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@
|
||||
"error.fileNotFound": "找不到檔案",
|
||||
"error.inheritPermissionError": "繼承權限錯誤",
|
||||
"error.missingParams": "參數不足",
|
||||
"error.too_many_request": "請求太頻繁了,請稍後重試",
|
||||
"error.upload_file_error_filename": "{{name}} 上傳失敗",
|
||||
"error.username_empty": "帳號不能為空",
|
||||
"extraction_results": "提取結果",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "新增文件",
|
||||
"api_file": "API 檔案庫",
|
||||
"api_url": "介面位址",
|
||||
"chunk_max_tokens": "分塊上限",
|
||||
"close_auto_sync": "確認關閉自動同步功能?",
|
||||
"collection.Create update time": "建立/更新時間",
|
||||
"collection.Training type": "分段模式",
|
||||
"collection_not_support_retraining": "此集合類型不支援重新調整參數",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "集合標籤",
|
||||
"common_dataset": "通用資料集",
|
||||
"common_dataset_desc": "可透過匯入檔案、網頁連結或手動輸入的方式建立資料集",
|
||||
"config_sync_schedule": "配置定時同步",
|
||||
"confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作,需要對您資料集內所有資料重新建立索引,可能需要較長時間,請確保帳號內剩餘點數充足。\n\n此外,您還需要注意修改使用此資料集的應用程式,避免與其他索引模型資料集混用。",
|
||||
"core.dataset.import.Adjust parameters": "調整參數",
|
||||
"custom_data_process_params": "自訂",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "依結束符號進行分段,並將多個分段組成一個分塊,此值決定了分塊的預估大小,可能會有上下浮動。",
|
||||
"import.Auto mode Estimated Price Tips": "需呼叫文字理解模型,將消耗較多 AI 點數:{{price}} 點數 / 1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "僅使用索引模型,消耗少量 AI 點數:{{price}} 點數 / 1K tokens",
|
||||
"is_open_schedule": "啟用定時同步",
|
||||
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
|
||||
"open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。",
|
||||
"permission.des.manage": "可管理整個資料集的資料和資訊",
|
||||
"permission.des.read": "可檢視資料集內容",
|
||||
"permission.des.write": "可新增和變更資料集內容",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "重新訓練任務已提交",
|
||||
"same_api_collection": "存在相同的 API 集合",
|
||||
"start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新獲取,請確認!",
|
||||
"sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來源文件",
|
||||
"sync_schedule": "定時同步",
|
||||
"sync_schedule_tip": "只會同步已存在的集合。\n包括連結集合以及 API 知識庫裡所有集合。\n系統會每天進行輪詢更新,無法確定特定的更新時間。",
|
||||
"tag.Add New": "新增",
|
||||
"tag.Add_new_tag": "新增標籤",
|
||||
"tag.Edit_tag": "編輯標籤",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "共 {{total}} 個標籤",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "資料集有索引正在訓練或重建中",
|
||||
"training_mode": "分段模式",
|
||||
"vector_model_max_tokens_tip": "每個分塊數據,最大長度為 3000 tokens",
|
||||
"website_dataset": "網站同步",
|
||||
"website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集"
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ LOG_LEVEL=debug
|
||||
STORE_LOG_LEVEL=warn
|
||||
|
||||
# 安全配置
|
||||
# 启动 IP 限流(true),部分接口增加了 ip 限流策略,防止非正常请求操作。
|
||||
USE_IP_LIMIT=false
|
||||
# 工作流最大运行次数,避免极端的死循环情况
|
||||
WORKFLOW_MAX_RUN_TIMES=500
|
||||
# 循环最大运行次数,避免极端的死循环情况
|
||||
|
||||
@@ -121,44 +121,55 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
</Box>
|
||||
{/* 导航列表 */}
|
||||
<Box flex={1}>
|
||||
{navbarList.map((item) => (
|
||||
<Box
|
||||
key={item.link}
|
||||
{...itemStyles}
|
||||
{...(item.activeLink.includes(router.pathname)
|
||||
? {
|
||||
color: 'primary.600',
|
||||
bg: 'white',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 4px 4px 0px rgba(19, 51, 107, 0.05)'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.500',
|
||||
bg: 'transparent',
|
||||
_hover: {
|
||||
bg: isSecondNavbarPage ? 'white' : 'rgba(255,255,255,0.9)'
|
||||
{navbarList.map((item) => {
|
||||
const isActive = item.activeLink.includes(router.pathname);
|
||||
return (
|
||||
<Box
|
||||
key={item.link}
|
||||
{...itemStyles}
|
||||
{...(isActive
|
||||
? {
|
||||
bg: 'white',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 4px 4px 0px rgba(19, 51, 107, 0.05)'
|
||||
}
|
||||
})}
|
||||
{...(item.link !== router.asPath
|
||||
? {
|
||||
onClick: () => router.push(item.link)
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<MyIcon
|
||||
name={
|
||||
item.activeLink.includes(router.pathname)
|
||||
? (item.activeIcon as any)
|
||||
: (item.icon as any)
|
||||
}
|
||||
width={'20px'}
|
||||
height={'20px'}
|
||||
/>
|
||||
<Box fontSize={'12px'} transform={'scale(0.9)'} mt={'5px'} lineHeight={1}>
|
||||
{item.label}
|
||||
: {
|
||||
bg: 'transparent',
|
||||
_hover: {
|
||||
bg: isSecondNavbarPage ? 'white' : 'rgba(255,255,255,0.9)'
|
||||
}
|
||||
})}
|
||||
{...(item.link !== router.asPath
|
||||
? {
|
||||
onClick: () => router.push(item.link)
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<MyIcon
|
||||
{...(isActive
|
||||
? {
|
||||
name: item.activeIcon as any,
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
name: item.icon as any,
|
||||
color: 'myGray.400'
|
||||
})}
|
||||
width={'20px'}
|
||||
height={'20px'}
|
||||
/>
|
||||
<Box
|
||||
fontSize={'12px'}
|
||||
transform={'scale(0.9)'}
|
||||
mt={'5px'}
|
||||
lineHeight={1}
|
||||
color={isActive ? 'primary.700' : 'myGray.500'}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{unread > 0 && (
|
||||
@@ -191,10 +202,10 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...itemStyles}
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
color={'myGray.400'}
|
||||
height={'48px'}
|
||||
>
|
||||
<Avatar src={item.avatar} borderRadius={'md'} />
|
||||
<Avatar src={item.avatar} borderRadius={'md'} width={'26px'} height={'26px'} />
|
||||
</Link>
|
||||
</MyTooltip>
|
||||
))}
|
||||
@@ -208,7 +219,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...itemStyles}
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
color={'myGray.400'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
|
||||
|
||||
@@ -29,6 +29,16 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const avatarSize = useMemo(() => {
|
||||
const size = {
|
||||
sm: '1rem',
|
||||
md: '1.2rem',
|
||||
lg: '1.4rem'
|
||||
};
|
||||
//@ts-ignore
|
||||
return props.size ? size[props.size] : size['md'];
|
||||
}, [props.size]);
|
||||
|
||||
const avatarList = list.map((item) => {
|
||||
const modelData =
|
||||
llmModelList.find((model) => model.model === item.value) ||
|
||||
@@ -43,7 +53,7 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
mr={2}
|
||||
src={modelData?.avatar || HUGGING_FACE_ICON}
|
||||
fallbackSrc={HUGGING_FACE_ICON}
|
||||
w={'18px'}
|
||||
w={avatarSize}
|
||||
/>
|
||||
<Box>{item.label}</Box>
|
||||
</Flex>
|
||||
@@ -56,14 +66,14 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
? avatarList.concat({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar borderRadius={'0'} mr={2} src={LOGO_ICON} w={'18px'} />
|
||||
<Avatar borderRadius={'0'} mr={2} src={LOGO_ICON} w={avatarSize} />
|
||||
<Box>{t('common:support.user.Price')}</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: 'price'
|
||||
})
|
||||
: avatarList;
|
||||
}, [feConfigs.show_pay, avatarList, t]);
|
||||
}, [feConfigs.show_pay, avatarList, avatarSize, t]);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: string) => {
|
||||
@@ -73,7 +83,7 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
}
|
||||
return onchange?.(e);
|
||||
},
|
||||
[onchange, router]
|
||||
[onOpenAiPointsModal, onchange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Textarea } from '@chakra-ui/react';
|
||||
@@ -121,21 +121,16 @@ const VariableInput = ({
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
|
||||
const { setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
return variableList.reduce((acc: Record<string, any>, item) => {
|
||||
acc[item.key] = item.defaultValue;
|
||||
return acc;
|
||||
}, {});
|
||||
}, [variableList]);
|
||||
const { getValues, setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
useEffect(() => {
|
||||
const values = variablesForm.getValues('variables');
|
||||
// If form is not empty, do not reset the variables
|
||||
if (Object.values(values).filter(Boolean).length > 0) return;
|
||||
setValue('variables', defaultValues);
|
||||
}, [defaultValues, setValue, variablesForm]);
|
||||
variableList.forEach((item) => {
|
||||
const val = getValues(`variables.${item.key}`);
|
||||
if (item.defaultValue !== undefined && (val === undefined || val === null || val === '')) {
|
||||
setValue(`variables.${item.key}`, item.defaultValue);
|
||||
}
|
||||
});
|
||||
}, [variableList]);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -805,7 +805,7 @@ const ChatBox = ({
|
||||
setQuestionGuide([]);
|
||||
setValue('chatStarted', false);
|
||||
abortRequest('leave');
|
||||
}, [abortRequest, setValue]);
|
||||
}, [chatId, appId, abortRequest, setValue]);
|
||||
|
||||
// Add listener
|
||||
useEffect(() => {
|
||||
|
||||
@@ -110,17 +110,15 @@ const RenderInput = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultFormValues = formatPluginInputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
|
||||
reset({
|
||||
files: [],
|
||||
variables: defaultFormValues
|
||||
variables: formatPluginInputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -164,7 +162,7 @@ const RenderInput = () => {
|
||||
files: historyFileList
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [histories]);
|
||||
}, [histories, formatPluginInputs]);
|
||||
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
|
||||
@@ -172,7 +172,6 @@ const RenderPluginInput = ({
|
||||
return (
|
||||
<Textarea
|
||||
value={value}
|
||||
defaultValue={input.defaultValue}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
placeholder={t(input.placeholder as any)}
|
||||
@@ -192,7 +191,6 @@ const RenderPluginInput = ({
|
||||
isInvalid={isInvalid}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
defaultValue={input.defaultValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -203,7 +201,6 @@ const RenderPluginInput = ({
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
defaultChecked={!!input.defaultValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -216,7 +213,6 @@ const RenderPluginInput = ({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isInvalid={isInvalid}
|
||||
defaultValue={input.defaultValue}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -2,12 +2,15 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import React, { useMemo } from 'react';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import { Box, Flex, Grid, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getAiPointUsageCardRoute } from '@/web/support/wallet/sub/constants';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const AiPointsModal = dynamic(() =>
|
||||
import('@/pages/price/components/Points').then((mod) => mod.AiPointsModal)
|
||||
);
|
||||
|
||||
const StandardPlanContentList = ({
|
||||
level,
|
||||
@@ -18,7 +21,12 @@ const StandardPlanContentList = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { subPlans } = useSystemStore();
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planContent = useMemo(() => {
|
||||
const plan = subPlans?.standard?.[level];
|
||||
@@ -95,9 +103,7 @@ const StandardPlanContentList = ({
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.wallet.subscription.AI points click to read tip')}
|
||||
onClick={() => {
|
||||
router.push(getAiPointUsageCardRoute());
|
||||
}}
|
||||
onClick={onOpenAiPointsModal}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -121,6 +127,7 @@ const StandardPlanContentList = ({
|
||||
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.web_site_sync')}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{isOpenAiPointsModal && <AiPointsModal onClose={onCloseAiPointsModal} />}
|
||||
</Grid>
|
||||
) : null;
|
||||
};
|
||||
|
||||
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -42,7 +42,7 @@ export type InitChatResponse = {
|
||||
appId: string;
|
||||
userAvatar?: string;
|
||||
title?: string;
|
||||
variables: Record<string, any>;
|
||||
variables?: Record<string, any>;
|
||||
app: {
|
||||
chatConfig?: AppChatConfigType;
|
||||
chatModels?: string[];
|
||||
|
||||
@@ -35,12 +35,12 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
if (parentId) {
|
||||
// if it is not a root app
|
||||
// check the parent folder permission
|
||||
await authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true });
|
||||
}
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
authUserPer({ req, authToken: true, per: WritePermissionVal }),
|
||||
...(parentId
|
||||
? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })]
|
||||
: [])
|
||||
]);
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
|
||||
@@ -25,6 +25,16 @@ export type ListAppBody = {
|
||||
searchKey?: string;
|
||||
};
|
||||
|
||||
/*
|
||||
获取 APP 列表权限
|
||||
1. 校验 folder 权限和获取 team 权限(owner 单独处理)
|
||||
2. 获取 team 下所有 app 权限。获取我的所有组。并计算出我所有的app权限。
|
||||
3. 过滤我有的权限的 app,以及当前 parentId 的 app(由于权限继承问题,这里没法一次性根据 id 去获取)
|
||||
4. 根据过滤条件获取 app 列表
|
||||
5. 遍历搜索出来的 app,并赋予权限(继承的 app,使用 parent 的权限)
|
||||
6. 再根据 read 权限进行一次过滤。
|
||||
*/
|
||||
|
||||
async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemType[]> {
|
||||
const { parentId, type, getRecentlyChat, searchKey } = req.body;
|
||||
|
||||
@@ -75,6 +85,24 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
);
|
||||
|
||||
const findAppsQuery = (() => {
|
||||
if (getRecentlyChat) {
|
||||
return {
|
||||
// get all chat app
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] }
|
||||
};
|
||||
}
|
||||
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
const appPerQuery = teamPer.isOwner
|
||||
? {}
|
||||
: parentId
|
||||
? {
|
||||
$or: [idList, parseParentIdInMongo(parentId)]
|
||||
}
|
||||
: idList;
|
||||
|
||||
const searchMatch = searchKey
|
||||
? {
|
||||
$or: [
|
||||
@@ -83,31 +111,17 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
]
|
||||
}
|
||||
: {};
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const appIdQuery = teamPer.isOwner
|
||||
? {}
|
||||
: { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
|
||||
if (getRecentlyChat) {
|
||||
return {
|
||||
// get all chat app
|
||||
...appIdQuery,
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] },
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
if (searchKey) {
|
||||
return {
|
||||
...appIdQuery,
|
||||
...appPerQuery,
|
||||
teamId,
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...appIdQuery,
|
||||
...appPerQuery,
|
||||
teamId,
|
||||
...(type && (Array.isArray(type) ? { type: { $in: type } } : { type })),
|
||||
...parseParentIdInMongo(parentId)
|
||||
@@ -143,24 +157,30 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
.map((item) => item.permission)
|
||||
);
|
||||
|
||||
// Count app collaborators
|
||||
const clbCount = perList.filter((item) => String(item.resourceId) === appId).length;
|
||||
|
||||
return {
|
||||
Per: new AppPermission({
|
||||
per: tmbPer ?? groupPer ?? AppDefaultPermissionVal,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
}),
|
||||
privateApp: AppFolderTypeList.includes(app.type) ? clbCount <= 1 : clbCount === 0
|
||||
};
|
||||
return new AppPermission({
|
||||
per: tmbPer ?? groupPer ?? AppDefaultPermissionVal,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
});
|
||||
};
|
||||
|
||||
// Inherit app
|
||||
if (app.inheritPermission && parentId && !AppFolderTypeList.includes(app.type)) {
|
||||
return getPer(String(parentId));
|
||||
} else {
|
||||
return getPer(String(app._id));
|
||||
const getClbCount = (appId: string) => {
|
||||
return perList.filter((item) => String(item.resourceId) === String(appId)).length;
|
||||
};
|
||||
|
||||
// Inherit app, check parent folder clb
|
||||
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
|
||||
return {
|
||||
Per: getPer(String(app.parentId)),
|
||||
privateApp: getClbCount(String(app.parentId)) <= 1
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
Per: getPer(String(app._id)),
|
||||
privateApp: AppFolderTypeList.includes(app.type)
|
||||
? getClbCount(String(app._id)) <= 1
|
||||
: getClbCount(String(app._id)) === 0
|
||||
};
|
||||
})();
|
||||
|
||||
return {
|
||||
|
||||
@@ -38,6 +38,7 @@ async function handler(
|
||||
type: AppTypeEnum.workflow,
|
||||
modules: app.modules,
|
||||
edges: app.edges,
|
||||
chatConfig: app.chatConfig,
|
||||
teamId: app.teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
@@ -10,7 +10,6 @@ import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { getScheduleTriggerApp } from '@/service/core/app/utils';
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<PostPublishAppProps>,
|
||||
|
||||
@@ -52,7 +52,7 @@ async function handler(
|
||||
appId,
|
||||
title: chat?.title,
|
||||
userAvatar: undefined,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -43,7 +43,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
title: chat?.title,
|
||||
//@ts-ignore
|
||||
userAvatar: tmb?.userId?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -50,7 +50,7 @@ async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResp
|
||||
appId,
|
||||
title: chat?.title,
|
||||
userAvatar: team?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
|
||||
export type DatasetCreateQuery = {};
|
||||
export type DatasetCreateBody = CreateDatasetParams;
|
||||
@@ -29,12 +30,25 @@ async function handler(
|
||||
} = req.body;
|
||||
|
||||
// auth
|
||||
const { teamId, tmbId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
}),
|
||||
...(parentId
|
||||
? [
|
||||
authDataset({
|
||||
req,
|
||||
datasetId: parentId,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
})
|
||||
]
|
||||
: [])
|
||||
]);
|
||||
|
||||
// check model valid
|
||||
const vectorModelStore = getVectorModel(vectorModel);
|
||||
|
||||
@@ -74,6 +74,16 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
);
|
||||
|
||||
const findDatasetQuery = (() => {
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
const datasetPerQuery = teamPer.isOwner
|
||||
? {}
|
||||
: parentId
|
||||
? {
|
||||
$or: [idList, parseParentIdInMongo(parentId)]
|
||||
}
|
||||
: idList;
|
||||
|
||||
const searchMatch = searchKey
|
||||
? {
|
||||
$or: [
|
||||
@@ -82,21 +92,17 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
]
|
||||
}
|
||||
: {};
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const appIdQuery = teamPer.isOwner
|
||||
? {}
|
||||
: { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
|
||||
if (searchKey) {
|
||||
return {
|
||||
...appIdQuery,
|
||||
...datasetPerQuery,
|
||||
teamId,
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...appIdQuery,
|
||||
...datasetPerQuery,
|
||||
teamId,
|
||||
...(type ? (Array.isArray(type) ? { type: { $in: type } } : { type }) : {}),
|
||||
...parseParentIdInMongo(parentId)
|
||||
@@ -121,23 +127,33 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
.filter((item) => String(item.resourceId) === datasetId && !!item.groupId)
|
||||
.map((item) => item.permission)
|
||||
);
|
||||
|
||||
const clbCount = perList.filter((item) => String(item.resourceId) === datasetId).length;
|
||||
|
||||
return {
|
||||
Per: new DatasetPermission({
|
||||
per: tmbPer ?? groupPer ?? DatasetDefaultPermissionVal,
|
||||
isOwner: String(dataset.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
}),
|
||||
privateDataset: dataset.type === 'folder' ? clbCount <= 1 : clbCount === 0
|
||||
};
|
||||
return new DatasetPermission({
|
||||
per: tmbPer ?? groupPer ?? DatasetDefaultPermissionVal,
|
||||
isOwner: String(dataset.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
});
|
||||
};
|
||||
const getClbCount = (datasetId: string) => {
|
||||
return perList.filter((item) => String(item.resourceId) === String(datasetId)).length;
|
||||
};
|
||||
|
||||
// inherit
|
||||
if (dataset.inheritPermission && parentId && dataset.type !== DatasetTypeEnum.folder) {
|
||||
return getPer(String(parentId));
|
||||
} else {
|
||||
return getPer(String(dataset._id));
|
||||
if (
|
||||
dataset.inheritPermission &&
|
||||
dataset.parentId &&
|
||||
dataset.type !== DatasetTypeEnum.folder
|
||||
) {
|
||||
return {
|
||||
Per: getPer(String(dataset.parentId)),
|
||||
privateDataset: getClbCount(String(dataset.parentId)) <= 1
|
||||
};
|
||||
}
|
||||
return {
|
||||
Per: getPer(String(dataset._id)),
|
||||
privateDataset:
|
||||
dataset.type === DatasetTypeEnum.folder
|
||||
? getClbCount(String(dataset._id)) <= 1
|
||||
: getClbCount(String(dataset._id)) === 0
|
||||
};
|
||||
})();
|
||||
|
||||
return {
|
||||
@@ -148,21 +164,19 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
})
|
||||
.filter((app) => app.permission.hasReadPer);
|
||||
|
||||
const data = await Promise.all(
|
||||
formatDatasets.map<DatasetListItemType>((item) => ({
|
||||
_id: item._id,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
intro: item.intro,
|
||||
type: item.type,
|
||||
permission: item.permission,
|
||||
vectorModel: getVectorModel(item.vectorModel),
|
||||
inheritPermission: item.inheritPermission,
|
||||
tmbId: item.tmbId,
|
||||
updateTime: item.updateTime,
|
||||
private: item.privateDataset
|
||||
}))
|
||||
);
|
||||
const data = formatDatasets.map<DatasetListItemType>((item) => ({
|
||||
_id: item._id,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
intro: item.intro,
|
||||
type: item.type,
|
||||
permission: item.permission,
|
||||
vectorModel: getVectorModel(item.vectorModel),
|
||||
inheritPermission: item.inheritPermission,
|
||||
tmbId: item.tmbId,
|
||||
updateTime: item.updateTime,
|
||||
private: item.privateDataset
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -99,4 +99,4 @@ async function handler(req: NextApiRequest) {
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(1, 2), handler);
|
||||
export default NextAPI(useReqFrequencyLimit(1, 15), handler);
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { DatasetTypeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
DatasetTypeEnum,
|
||||
TrainingModeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { ClientSession } from 'mongoose';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
@@ -22,6 +26,8 @@ import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { TeamWritePermissionVal } 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';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
export type DatasetUpdateQuery = {};
|
||||
export type DatasetUpdateResponse = any;
|
||||
@@ -51,7 +57,8 @@ async function handler(
|
||||
websiteConfig,
|
||||
externalReadUrl,
|
||||
apiServer,
|
||||
status
|
||||
status,
|
||||
autoSync
|
||||
} = req.body;
|
||||
|
||||
if (!id) {
|
||||
@@ -101,7 +108,7 @@ async function handler(
|
||||
agentModel: agentModel?.model
|
||||
});
|
||||
|
||||
const onUpdate = async (session?: ClientSession) => {
|
||||
const onUpdate = async (session: ClientSession) => {
|
||||
await MongoDataset.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
@@ -117,14 +124,21 @@ async function handler(
|
||||
...(!!apiServer?.authorization && {
|
||||
'apiServer.authorization': apiServer.authorization
|
||||
}),
|
||||
...(isMove && { inheritPermission: true })
|
||||
...(isMove && { inheritPermission: true }),
|
||||
...(typeof autoSync === 'boolean' && { autoSync })
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await updateSyncSchedule({
|
||||
teamId: dataset.teamId,
|
||||
datasetId: dataset._id,
|
||||
autoSync,
|
||||
session
|
||||
});
|
||||
};
|
||||
|
||||
if (isMove) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await mongoSessionRun(async (session) => {
|
||||
if (isMove) {
|
||||
if (isFolder && dataset.inheritPermission) {
|
||||
const parentClbsAndGroups = await getResourceClbsAndGroups({
|
||||
teamId: dataset.teamId,
|
||||
@@ -149,17 +163,16 @@ async function handler(
|
||||
collaborators: parentClbsAndGroups,
|
||||
session
|
||||
});
|
||||
return onUpdate(session);
|
||||
}
|
||||
return onUpdate(session);
|
||||
});
|
||||
} else {
|
||||
return onUpdate();
|
||||
}
|
||||
} else {
|
||||
return onUpdate(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
export default NextAPI(handler);
|
||||
|
||||
async function updateTraining({
|
||||
const updateTraining = async ({
|
||||
teamId,
|
||||
datasetId,
|
||||
agentModel
|
||||
@@ -167,7 +180,7 @@ async function updateTraining({
|
||||
teamId: string;
|
||||
datasetId: string;
|
||||
agentModel?: string;
|
||||
}) {
|
||||
}) => {
|
||||
if (!agentModel) return;
|
||||
|
||||
await MongoDatasetTraining.updateMany(
|
||||
@@ -184,4 +197,48 @@ async function updateTraining({
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const updateSyncSchedule = async ({
|
||||
teamId,
|
||||
datasetId,
|
||||
autoSync,
|
||||
session
|
||||
}: {
|
||||
teamId: string;
|
||||
datasetId: string;
|
||||
autoSync?: boolean;
|
||||
session: ClientSession;
|
||||
}) => {
|
||||
if (typeof autoSync !== 'boolean') return;
|
||||
|
||||
// Update all collection nextSyncTime
|
||||
if (autoSync) {
|
||||
await MongoDatasetCollection.updateMany(
|
||||
{
|
||||
teamId,
|
||||
datasetId,
|
||||
type: { $in: [DatasetCollectionTypeEnum.apiFile, DatasetCollectionTypeEnum.link] }
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
nextSyncTime: addDays(new Date(), 1)
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
} else {
|
||||
await MongoDatasetCollection.updateMany(
|
||||
{
|
||||
teamId,
|
||||
datasetId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
nextSyncTime: 1
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,71 +1,63 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getUserDetail } from '@fastgpt/service/support/user/controller';
|
||||
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
||||
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { username, password } = req.body as PostLoginProps;
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { username, password } = req.body as PostLoginProps;
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 检测用户是否存在
|
||||
const authCert = await MongoUser.findOne(
|
||||
{
|
||||
username
|
||||
},
|
||||
'status'
|
||||
);
|
||||
if (!authCert) {
|
||||
throw new Error('用户未注册');
|
||||
}
|
||||
|
||||
if (authCert.status === UserStatusEnum.forbidden) {
|
||||
throw new Error('账号已停用,无法登录');
|
||||
}
|
||||
|
||||
const user = await MongoUser.findOne({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('密码错误');
|
||||
}
|
||||
|
||||
const userDetail = await getUserDetail({
|
||||
tmbId: user?.lastLoginTmbId,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
MongoUser.findByIdAndUpdate(user._id, {
|
||||
lastLoginTmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
const token = createJWT({
|
||||
...userDetail,
|
||||
isRoot: username === 'root'
|
||||
});
|
||||
|
||||
setCookie(res, token);
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
user: userDetail,
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
if (!username || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 检测用户是否存在
|
||||
const authCert = await MongoUser.findOne(
|
||||
{
|
||||
username
|
||||
},
|
||||
'status'
|
||||
);
|
||||
if (!authCert) {
|
||||
throw new Error('用户未注册');
|
||||
}
|
||||
|
||||
if (authCert.status === UserStatusEnum.forbidden) {
|
||||
throw new Error('账号已停用,无法登录');
|
||||
}
|
||||
|
||||
const user = await MongoUser.findOne({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('密码错误');
|
||||
}
|
||||
|
||||
const userDetail = await getUserDetail({
|
||||
tmbId: user?.lastLoginTmbId,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
MongoUser.findByIdAndUpdate(user._id, {
|
||||
lastLoginTmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
const token = createJWT({
|
||||
...userDetail,
|
||||
isRoot: username === 'root'
|
||||
});
|
||||
|
||||
setCookie(res, token);
|
||||
|
||||
return {
|
||||
user: userDetail,
|
||||
token
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(120, 10), handler);
|
||||
|
||||
@@ -89,7 +89,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
removeFilesByPaths(filePaths);
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(1, 2), handler);
|
||||
export default NextAPI(useReqFrequencyLimit(1, 1), handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
||||
@@ -48,7 +48,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
|
||||
setChatBoxData(res);
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
|
||||
return res;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
@@ -31,7 +31,8 @@ import { cardStyles } from '../constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useMount } from 'ahooks';
|
||||
|
||||
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
|
||||
|
||||
const Logs = () => {
|
||||
@@ -41,9 +42,9 @@ const Logs = () => {
|
||||
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||
const { teamMembers, loadAndGetTeamMembers } = useUserStore();
|
||||
|
||||
useEffect(() => {
|
||||
useMount(() => {
|
||||
loadAndGetTeamMembers();
|
||||
}, []);
|
||||
});
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
@@ -140,11 +141,11 @@ const Logs = () => {
|
||||
) : (
|
||||
<HStack>
|
||||
<Avatar
|
||||
src={teamMembers.find((v) => v.tmbId === item.tmbId)?.avatar}
|
||||
src={teamMembers?.find((v) => v.tmbId === item.tmbId)?.avatar}
|
||||
w="1.25rem"
|
||||
/>
|
||||
<Box fontSize={'sm'} ml={1}>
|
||||
{teamMembers.find((v) => v.tmbId === item.tmbId)?.memberName}
|
||||
{teamMembers?.find((v) => v.tmbId === item.tmbId)?.memberName}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
@@ -657,10 +657,12 @@ const RenderList = React.memo(function RenderList({
|
||||
<Box mt={2} color={'myGray.500'} maxH={'100px'} overflow={'hidden'}>
|
||||
{t(template.intro as any) || t('common:core.workflow.Not intro')}
|
||||
</Box>
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
{type === TemplateTypeEnum.systemPlugin && (
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
NodePositionChange,
|
||||
XYPosition,
|
||||
useReactFlow,
|
||||
getNodesBounds,
|
||||
Rect,
|
||||
NodeRemoveChange,
|
||||
NodeSelectionChange,
|
||||
EdgeRemoveChange
|
||||
@@ -26,15 +24,12 @@ import { WorkflowContext } from '../../context';
|
||||
import { THelperLine } from '@fastgpt/global/core/workflow/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useDebounceEffect, useMemoizedFn } from 'ahooks';
|
||||
import {
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../../context/workflowInitContext';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import { AppContext } from '../../../context';
|
||||
import { WorkflowEventContext } from '../../context/workflowEventContext';
|
||||
import { WorkflowStatusContext } from '../../context/workflowStatusContext';
|
||||
|
||||
/*
|
||||
Compute helper lines for snapping nodes to each other
|
||||
@@ -282,18 +277,22 @@ export const useWorkflow = () => {
|
||||
const edges = useContextSelector(WorkflowNodeEdgeContext, (state) => state.edges);
|
||||
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
|
||||
const onEdgesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onEdgesChange);
|
||||
const { setConnectingEdge, nodeList, onChangeNode, pushPastSnapshot } = useContextSelector(
|
||||
WorkflowContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const pushPastSnapshot = useContextSelector(WorkflowContext, (v) => v.pushPastSnapshot);
|
||||
|
||||
const setHoverEdgeId = useContextSelector(WorkflowEventContext, (v) => v.setHoverEdgeId);
|
||||
const setMenu = useContextSelector(WorkflowEventContext, (v) => v.setMenu);
|
||||
const resetParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.resetParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const { getIntersectingNodes } = useReactFlow();
|
||||
const { isDowningCtrl } = useKeyboard();
|
||||
|
||||
const { resetParentNodeSizeAndPosition } = useLoopNode();
|
||||
|
||||
/* helper line */
|
||||
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>();
|
||||
const [helperLineVertical, setHelperLineVertical] = useState<THelperLine>();
|
||||
@@ -669,73 +668,6 @@ export const useWorkflow = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useLoopNode = () => {
|
||||
const nodes = useContextSelector(WorkflowInitContext, (state) => state.nodes);
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
onNodesChange([
|
||||
{
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: rect.x - 70,
|
||||
y: rect.y - offsetHeight - 240
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
return {
|
||||
resetParentNodeSizeAndPosition
|
||||
};
|
||||
};
|
||||
|
||||
export default function Dom() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
When the childNodes of loopFlow change, it automatically calculates the rectangular width, height, and position of the childNodes,
|
||||
thereby further updating the width and height properties of the loop node.
|
||||
*/
|
||||
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { Background, NodeProps } from 'reactflow';
|
||||
@@ -31,8 +30,8 @@ import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||
import { AppContext } from '../../../../context';
|
||||
import { isValidArrayReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
||||
import { ReferenceArrayValueType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { useLoopNode } from '../../hooks/useWorkflow';
|
||||
import { useSize } from 'ahooks';
|
||||
import { WorkflowStatusContext } from '../../../context/workflowStatusContext';
|
||||
|
||||
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -40,8 +39,10 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
|
||||
const { resetParentNodeSizeAndPosition } = useLoopNode();
|
||||
const resetParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.resetParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const {
|
||||
nodeWidth,
|
||||
@@ -50,11 +51,11 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
loopNodeInputHeight = Input_Template_LOOP_NODE_OFFSET
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
nodeWidth: Number(
|
||||
inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value?.toFixed(0)
|
||||
nodeWidth: Math.round(
|
||||
Number(inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value) || 500
|
||||
),
|
||||
nodeHeight: Number(
|
||||
inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value?.toFixed(0)
|
||||
nodeHeight: Math.round(
|
||||
Number(inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value) || 500
|
||||
),
|
||||
loopInputArray: inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray),
|
||||
loopNodeInputHeight: inputs.find(
|
||||
@@ -113,7 +114,7 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return JSON.stringify(
|
||||
nodeList.filter((node) => node.parentNodeId === nodeId).map((node) => node.nodeId)
|
||||
);
|
||||
}, [nodeId, nodeList]);
|
||||
}, [nodeId, nodeList.length]);
|
||||
useEffect(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -148,39 +149,54 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
}, 50);
|
||||
}, [size?.height]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
const RenderInputDom = useMemo(() => {
|
||||
return (
|
||||
<NodeCard selected={selected} maxW="full" menuForbid={{ copy: true }} {...data}>
|
||||
<Container position={'relative'} flex={1}>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
<Box mb={6} maxW={'500px'} ref={inputBoxRef}>
|
||||
<RenderInput nodeId={nodeId} flowInputList={inputs} />
|
||||
</Box>
|
||||
<FormLabel required fontWeight={'medium'} mb={3} color={'myGray.600'}>
|
||||
{t('workflow:loop_body')}
|
||||
</FormLabel>
|
||||
<Box
|
||||
flex={1}
|
||||
position={'relative'}
|
||||
border={'base'}
|
||||
bg={'myGray.100'}
|
||||
rounded={'8px'}
|
||||
{...(!isFolded && {
|
||||
minW: nodeWidth,
|
||||
minH: nodeHeight
|
||||
})}
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
</Container>
|
||||
<Container>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
<Box mb={6} maxW={'500px'} ref={inputBoxRef}>
|
||||
<RenderInput nodeId={nodeId} flowInputList={inputs} />
|
||||
</Box>
|
||||
);
|
||||
}, [selected, isFolded, nodeWidth, nodeHeight, data, t, nodeId, inputs, outputs]);
|
||||
}, [inputs, nodeId]);
|
||||
const RenderChildrenNodes = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<FormLabel required fontWeight={'medium'} mb={3} color={'myGray.600'}>
|
||||
{t('workflow:loop_body')}
|
||||
</FormLabel>
|
||||
<Box
|
||||
flex={1}
|
||||
position={'relative'}
|
||||
border={'base'}
|
||||
bg={'myGray.100'}
|
||||
rounded={'8px'}
|
||||
{...(!isFolded && {
|
||||
minW: nodeWidth,
|
||||
minH: nodeHeight
|
||||
})}
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}, [isFolded, nodeHeight, nodeWidth, t]);
|
||||
|
||||
return Render;
|
||||
const MemoRenderOutput = useMemo(() => {
|
||||
return (
|
||||
<Container>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
);
|
||||
}, [nodeId, outputs]);
|
||||
|
||||
return (
|
||||
<NodeCard selected={selected} maxW="full" menuForbid={{ copy: true }} {...data}>
|
||||
<Container position={'relative'} flex={1}>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
{RenderInputDom}
|
||||
{RenderChildrenNodes}
|
||||
</Container>
|
||||
{MemoRenderOutput}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeLoop);
|
||||
|
||||
@@ -23,13 +23,15 @@ const typeMap = {
|
||||
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.string,
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.number,
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.boolean,
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object,
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.any
|
||||
};
|
||||
|
||||
const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, outputs } = data;
|
||||
const { nodeList, onChangeNode } = useContextSelector(WorkflowContext, (v) => v);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const loopStartNode = useMemo(
|
||||
() => nodeList.find((node) => node.nodeId === nodeId),
|
||||
|
||||
@@ -85,18 +85,28 @@ type Props = {
|
||||
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const filterInputs = useMemo(() => {
|
||||
const filterProInputs = useMemo(() => {
|
||||
return flowInputList.filter((input) => {
|
||||
if (input.isPro && !feConfigs?.isPlus) return false;
|
||||
return true;
|
||||
});
|
||||
}, [feConfigs?.isPlus, flowInputList]);
|
||||
|
||||
const filterInputs = useMemo(() => {
|
||||
return filterProInputs.filter((input) => {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
const isDynamic = !!input.canEdit;
|
||||
|
||||
if (renderType === FlowNodeInputTypeEnum.hidden || isDynamic) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [feConfigs?.isPlus, flowInputList]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterInputs.map((input) => {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
const isDynamic = !!input.canEdit;
|
||||
|
||||
const RenderComponent = (() => {
|
||||
if (renderType === FlowNodeInputTypeEnum.custom && CustomComponent?.[input.key]) {
|
||||
@@ -106,10 +116,10 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
const Component = RenderList.find((item) => item.types.includes(renderType))?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component inputs={filterInputs} item={input} nodeId={nodeId} />;
|
||||
return <Component inputs={filterProInputs} item={input} nodeId={nodeId} />;
|
||||
})();
|
||||
|
||||
return renderType !== FlowNodeInputTypeEnum.hidden && !isDynamic ? (
|
||||
return (
|
||||
<Box key={input.key} _notLast={{ mb }} position={'relative'}>
|
||||
{!!input.label && !hideLabelTypeList.includes(renderType) && (
|
||||
<InputLabel nodeId={nodeId} input={input} />
|
||||
@@ -120,7 +130,7 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : null;
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -115,7 +115,10 @@ export const useReference = ({
|
||||
|
||||
const Reference = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const isArray = item.valueType?.includes('array') ?? false;
|
||||
|
||||
const onSelect = useCallback(
|
||||
@@ -255,21 +258,24 @@ const MultipleReferenceSelector = ({
|
||||
}, [getSelectValue, value]);
|
||||
|
||||
useEffect(() => {
|
||||
const validList = formatList.filter((item) => item.nodeName && item.outputName);
|
||||
if (validList.length !== value?.length) {
|
||||
onSelect(validList.map((item) => item.rawValue));
|
||||
// Adapt array type from old version
|
||||
if (Array.isArray(value) && typeof value[0] === 'string') {
|
||||
// @ts-ignore
|
||||
onSelect([value]);
|
||||
}
|
||||
}, [formatList, onSelect, value]);
|
||||
|
||||
const invalidList = useMemo(() => {
|
||||
return formatList.filter((item) => item.nodeName && item.outputName);
|
||||
}, [formatList]);
|
||||
|
||||
const ArraySelector = useMemo(() => {
|
||||
return (
|
||||
<MultipleRowArraySelect
|
||||
label={
|
||||
formatList.length > 0 ? (
|
||||
invalidList.length > 0 ? (
|
||||
<Grid py={3} gridTemplateColumns={'1fr 1fr'} gap={2} fontSize={'sm'}>
|
||||
{formatList.map(({ nodeName, outputName }, index) => {
|
||||
if (!nodeName || !outputName) return null;
|
||||
|
||||
{invalidList.map(({ nodeName, outputName }, index) => {
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -325,7 +331,7 @@ const MultipleReferenceSelector = ({
|
||||
popDirection={popDirection}
|
||||
/>
|
||||
);
|
||||
}, [formatList, list, onSelect, placeholder, popDirection, value]);
|
||||
}, [invalidList, list, onSelect, placeholder, popDirection, value]);
|
||||
|
||||
return ArraySelector;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useCreation, useMemoizedFn } from 'ahooks';
|
||||
import React, { Dispatch, SetStateAction, ReactNode, useEffect, useMemo } from 'react';
|
||||
import { Edge, EdgeChange, Node, NodeChange, useEdgesState, useNodesState } from 'reactflow';
|
||||
|
||||
@@ -50,7 +50,7 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowNodeItemType>([]);
|
||||
const getNodes = useMemoizedFn(() => nodes);
|
||||
const nodeListString = JSON.stringify(nodes.map((node) => node.data));
|
||||
const nodeList = useMemo(
|
||||
const nodeList = useCreation(
|
||||
() => JSON.parse(nodeListString) as FlowNodeItemType[],
|
||||
[nodeListString]
|
||||
);
|
||||
@@ -73,7 +73,7 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
: item
|
||||
)
|
||||
);
|
||||
}, [edges.length]);
|
||||
}, [nodeList, edges.length]);
|
||||
|
||||
const actionContextValue = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useDebounceEffect } from 'ahooks';
|
||||
import { useDebounceEffect, useMemoizedFn } from 'ahooks';
|
||||
import React, { ReactNode, useMemo, useRef, useState } from 'react';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowInitContext, WorkflowNodeEdgeContext } from './workflowInitContext';
|
||||
@@ -7,10 +7,18 @@ import { AppContext } from '../../context';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getNodesBounds, Node } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
|
||||
type WorkflowStatusContextType = {
|
||||
isSaved: boolean;
|
||||
leaveSaveSign: React.MutableRefObject<boolean>;
|
||||
resetParentNodeSizeAndPosition: (parentId: string) => void;
|
||||
};
|
||||
|
||||
export const WorkflowStatusContext = createContext<WorkflowStatusContextType>({
|
||||
@@ -75,12 +83,72 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
}
|
||||
});
|
||||
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
onNodesChange([
|
||||
{
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: Math.round(rect.x - 70),
|
||||
y: Math.round(rect.y - offsetHeight - 240)
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
isSaved,
|
||||
leaveSaveSign
|
||||
leaveSaveSign,
|
||||
resetParentNodeSizeAndPosition
|
||||
};
|
||||
}, [isSaved]);
|
||||
}, [isSaved, resetParentNodeSizeAndPosition]);
|
||||
return (
|
||||
<WorkflowStatusContext.Provider value={contextValue}>{children}</WorkflowStatusContext.Provider>
|
||||
);
|
||||
|
||||
@@ -107,8 +107,10 @@ export const useChatTest = ({
|
||||
async () => {
|
||||
if (!appId || !chatId) return;
|
||||
const res = await getInitChatInfo({ appId, chatId });
|
||||
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
@@ -74,7 +74,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
|
||||
// reset chat variables
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
@@ -22,8 +22,7 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
@@ -37,6 +36,8 @@ import ChatRecordContextProvider, {
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
|
||||
|
||||
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
|
||||
|
||||
type Props = {
|
||||
@@ -102,7 +103,8 @@ const OutLink = (props: Props) => {
|
||||
setChatBoxData(res);
|
||||
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
|
||||
return res;
|
||||
@@ -299,8 +301,9 @@ const OutLink = (props: Props) => {
|
||||
|
||||
const Render = (props: Props) => {
|
||||
const { shareId, authToken, customUid, appId } = props;
|
||||
const { localUId, loaded } = useShareChatStore();
|
||||
const { localUId } = useShareChatStore();
|
||||
const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore();
|
||||
const { setUserDefaultLng } = useI18nLng();
|
||||
|
||||
const chatHistoryProviderParams = useMemo(() => {
|
||||
return { shareId, outLinkUid: authToken || customUid || localUId };
|
||||
@@ -317,6 +320,7 @@ const Render = (props: Props) => {
|
||||
|
||||
useMount(() => {
|
||||
setSource('share');
|
||||
setUserDefaultLng(true);
|
||||
});
|
||||
|
||||
// Set outLinkAuthData
|
||||
|
||||
@@ -79,7 +79,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
|
||||
// reset chat records
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Flex, Input } from '@chakra-ui/react';
|
||||
import { Box, Flex, Switch, Input } from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -33,6 +33,8 @@ import EditAPIDatasetInfoModal, {
|
||||
EditAPIDatasetInfoFormType
|
||||
} from './components/EditApiServiceModal';
|
||||
import { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
|
||||
|
||||
const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
@@ -52,7 +54,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
const vectorModel = watch('vectorModel');
|
||||
const agentModel = watch('agentModel');
|
||||
|
||||
const { datasetModelList, vectorModelList } = useSystemStore();
|
||||
const { feConfigs, datasetModelList, vectorModelList } = useSystemStore();
|
||||
const { ConfirmModal: ConfirmDelModal } = useConfirm({
|
||||
content: t('common:core.dataset.Delete Confirm'),
|
||||
type: 'delete'
|
||||
@@ -62,6 +64,10 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
content: t('dataset:confirm_to_rebuild_embedding_tip'),
|
||||
type: 'delete'
|
||||
});
|
||||
const { openConfirm: onOpenConfirmSyncSchedule, ConfirmModal: ConfirmSyncScheduleModal } =
|
||||
useConfirm({
|
||||
title: t('common:common.confirm.Common Tip')
|
||||
});
|
||||
|
||||
const { File } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
@@ -132,6 +138,8 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
reset(datasetDetail);
|
||||
}, [datasetDetail, datasetDetail._id, reset]);
|
||||
|
||||
const isTraining = rebuildingCount > 0 || trainingCount > 0;
|
||||
|
||||
return (
|
||||
<Box w={'100%'} h={'100%'} p={6}>
|
||||
<Box>
|
||||
@@ -177,7 +185,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
|
||||
<MyDivider my={4} h={'2px'} maxW={'500px'} />
|
||||
|
||||
<Box overflow={'hidden'}>
|
||||
<Box>
|
||||
<Flex w={'100%'} flexDir={'column'}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('common:core.dataset.Dataset ID')}
|
||||
@@ -186,16 +194,23 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Flex>
|
||||
|
||||
<Box mt={5} w={'100%'}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('common:core.ai.model.Vector Model')}
|
||||
</FormLabel>
|
||||
<Flex alignItems={'center'} fontSize={'mini'}>
|
||||
<FormLabel fontWeight={'500'} flex={'1 0 0'}>
|
||||
{t('common:core.ai.model.Vector Model')}
|
||||
</FormLabel>
|
||||
<MyTooltip label={t('dataset:vector_model_max_tokens_tip')}>
|
||||
<Box>
|
||||
{t('dataset:chunk_max_tokens')}: {vectorModel.maxToken}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box pt={2}>
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={vectorModel.model}
|
||||
fontSize={'mini'}
|
||||
disableTip={
|
||||
rebuildingCount > 0 || trainingCount > 0
|
||||
isTraining
|
||||
? t(
|
||||
'dataset:the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt'
|
||||
)
|
||||
@@ -217,13 +232,6 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Flex mt={2} w={'100%'} alignItems={'center'}>
|
||||
<FormLabel flex={1} fontSize={'mini'} w={0} fontWeight={'500'}>
|
||||
{t('common:core.Max Token')}
|
||||
</FormLabel>
|
||||
<Box fontSize={'mini'}>{vectorModel.maxToken}</Box>
|
||||
</Flex>
|
||||
|
||||
<Box pt={5}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('common:core.ai.model.Dataset Agent Model')}
|
||||
@@ -247,7 +255,34 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* <MyDivider my={4} h={'2px'} maxW={'500px'} /> */}
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex alignItems={'center'} pt={5}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('dataset:sync_schedule')}
|
||||
</FormLabel>
|
||||
<QuestionTip ml={1} label={t('dataset:sync_schedule_tip')} />
|
||||
<Box flex={1} />
|
||||
<Switch
|
||||
isChecked={!!datasetDetail.autoSync}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
const autoSync = e.target.checked;
|
||||
const text = autoSync ? t('dataset:open_auto_sync') : t('dataset:close_auto_sync');
|
||||
|
||||
onOpenConfirmSyncSchedule(
|
||||
async () => {
|
||||
return updateDataset({
|
||||
id: datasetId,
|
||||
autoSync
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
text
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{datasetDetail.type === DatasetTypeEnum.externalFile && (
|
||||
<>
|
||||
@@ -330,6 +365,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
<File onSelect={onSelectFile} />
|
||||
<ConfirmDelModal />
|
||||
<ConfirmRebuildModal countDown={10} />
|
||||
<ConfirmSyncScheduleModal />
|
||||
{editedDataset && (
|
||||
<EditResourceModal
|
||||
{...editedDataset}
|
||||
|
||||
@@ -38,8 +38,7 @@ const MetaDataCard = ({ datasetId }: { datasetId: string }) => {
|
||||
const metadataList = useMemo<{ label?: string; value?: any }[]>(() => {
|
||||
if (!collection) return [];
|
||||
|
||||
const webSelector =
|
||||
collection?.datasetId?.websiteConfig?.selector || collection?.metadata?.webPageSelector;
|
||||
const webSelector = collection?.metadata?.webPageSelector;
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -23,5 +23,7 @@ export const langMap = {
|
||||
|
||||
export const serviceSideProps = (content: any, ns: I18nNsType = []) => {
|
||||
const lang = content.req?.cookies?.NEXT_LOCALE || content.locale;
|
||||
return serverSideTranslations(lang, ['common', ...ns], null);
|
||||
|
||||
const extraLng = content.req?.cookies?.NEXT_LOCALE ? undefined : content.locales;
|
||||
return serverSideTranslations(lang, ['common', ...ns], null, extraLng);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ComponentRef as ChatComponentRef } from '@/components/core/chat/ChatCon
|
||||
import { useForm, UseFormReturn } from 'react-hook-form';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { AppChatConfigType, VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
|
||||
type ChatBoxDataType = {
|
||||
@@ -29,7 +29,10 @@ type ChatItemContextType = {
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
pluginRunTab: PluginRunBoxTabEnum;
|
||||
setPluginRunTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
|
||||
resetVariables: (props?: { variables?: Record<string, any> }) => void;
|
||||
resetVariables: (props?: {
|
||||
variables?: Record<string, any>;
|
||||
variableList?: VariableItemType[];
|
||||
}) => void;
|
||||
clearChatRecords: () => void;
|
||||
chatBoxData: ChatBoxDataType;
|
||||
setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>;
|
||||
@@ -44,7 +47,10 @@ export const ChatItemContext = createContext<ChatItemContextType>({
|
||||
setPluginRunTab: function (value: React.SetStateAction<PluginRunBoxTabEnum>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
resetVariables: function (props?: { variables?: Record<string, any> }): void {
|
||||
resetVariables: function (props?: {
|
||||
variables?: Record<string, any>;
|
||||
variableList?: VariableItemType[];
|
||||
}): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
clearChatRecords: function (): void {
|
||||
@@ -69,27 +75,21 @@ const ChatItemContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||
|
||||
const resetVariables = useCallback(
|
||||
(props?: { variables?: Record<string, any> }) => {
|
||||
const { variables = {} } = props || {};
|
||||
(props?: { variables?: Record<string, any>; variableList?: VariableItemType[] }) => {
|
||||
const { variables, variableList = [] } = props || {};
|
||||
|
||||
// Reset to empty input
|
||||
const data = variablesForm.getValues();
|
||||
|
||||
// Reset the old variables to empty
|
||||
const resetVariables: Record<string, any> = {};
|
||||
for (const key in data.variables) {
|
||||
resetVariables[key] = (() => {
|
||||
if (Array.isArray(data.variables[key])) {
|
||||
return [];
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
let newVariableValue: Record<string, any> = {};
|
||||
if (variables) {
|
||||
variableList.forEach((item) => {
|
||||
newVariableValue[item.key] = variables[item.key];
|
||||
});
|
||||
} else {
|
||||
variableList.forEach((item) => {
|
||||
newVariableValue[item.key] = item.defaultValue;
|
||||
});
|
||||
}
|
||||
|
||||
variablesForm.setValue('variables', {
|
||||
...resetVariables,
|
||||
...variables
|
||||
});
|
||||
variablesForm.setValue('variables', newVariableValue);
|
||||
},
|
||||
[variablesForm]
|
||||
);
|
||||
|
||||
@@ -119,6 +119,7 @@ export const storeNode2FlowNode = ({
|
||||
|
||||
selectedTypeIndex: storeInput.selectedTypeIndex ?? templateInput.selectedTypeIndex,
|
||||
value: storeInput.value ?? templateInput.value,
|
||||
valueType: storeInput.valueType ?? templateInput.valueType,
|
||||
label: storeInput.label ?? templateInput.label
|
||||
};
|
||||
})
|
||||
@@ -148,7 +149,8 @@ export const storeNode2FlowNode = ({
|
||||
|
||||
id: storeOutput.id ?? templateOutput.id,
|
||||
label: storeOutput.label ?? templateOutput.label,
|
||||
value: storeOutput.value ?? templateOutput.value
|
||||
value: storeOutput.value ?? templateOutput.value,
|
||||
valueType: storeOutput.valueType ?? templateOutput.valueType
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
export const AI_POINT_USAGE_CARD_ROUTE = '/price#point-card';
|
||||
export const getAiPointUsageCardRoute = () => {
|
||||
const subPlans = useSystemStore.getState().subPlans;
|
||||
return subPlans?.planDescriptionUrl
|
||||
? getDocPath(subPlans.planDescriptionUrl)
|
||||
: AI_POINT_USAGE_CARD_ROUTE;
|
||||
};
|
||||
|
||||
export const EXTRA_PLAN_CARD_ROUTE = '/price#extra-plan';
|
||||
export const getExtraPlanCardRoute = () => {
|
||||
const subPlans = useSystemStore.getState().subPlans;
|
||||
|
||||
Reference in New Issue
Block a user