Compare commits

...

6 Commits

Author SHA1 Message Date
Archer
05611df056 perf: vector generate (#1748) 2024-06-12 16:42:46 +08:00
Archer
d0085a23e6 4.8.4 (#1746) 2024-06-12 15:17:21 +08:00
Archer
bc6864c3dc Feat: App folder and permission (#1726)
* app folder

* feat: app foldere

* fix: run app param error

* perf: select app ux

* perf: folder rerender

* fix: ts

* fix: parentId

* fix: permission

* perf: loading ux

* perf: per select ux

* perf: clb context

* perf: query extension tip

* fix: ts

* perf: app detail per

* perf: default per
2024-06-11 10:16:24 +08:00
Archer
b20d075d35 Updae theme and fix some bug (#1711) 2024-06-07 12:54:30 +08:00
Archer
19c8a06d51 Permission (#1687)
Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
2024-06-04 17:52:00 +08:00
Archer
fcb915c988 Update docker.md 2024-06-02 12:11:02 +08:00
305 changed files with 6590 additions and 3229 deletions

View File

@@ -35,17 +35,19 @@
"scope": "typescriptreact",
"prefix": "context",
"body": [
"import { ReactNode } from 'react';",
"import React, { ReactNode } from 'react';",
"import { createContext } from 'use-context-selector';",
"",
"type ContextType = {$1};",
"",
"export const Context = createContext<ContextType>({});",
"",
"export const ContextProvider = ({ children }: { children: ReactNode }) => {",
"const ContextProvider = ({ children }: { children: ReactNode }) => {",
" const contextValue: ContextType = {};",
" return <Context.Provider value={contextValue}>{children}</Context.Provider>;",
"};",
"",
"export default ContextProvider"
],
"description": "FastGPT usecontext template"
}

View File

@@ -11,7 +11,7 @@ weight: 708
**开发环境下**,你需要将示例配置文件 `config.json` 复制成 `config.local.json` 文件才会生效。
这个配置文件中包含了系统参数和各个模型配置`使用时务必去掉注释!!!!!!!!!!!!!!`
这个配置文件中包含了系统参数和各个模型配置
## 4.6.8+ 版本新配置文件
@@ -158,7 +158,7 @@ llm模型全部合并
1. [部署 ReRank 模型](/docs/development/custom-models/bge-rerank/)
1. 找到 FastGPT 的配置文件中的 `reRankModels` 4.6.6 以前是 `ReRankModels`
2. 修改对应的值:(记得去掉注释)
2. 修改对应的值:
```json
{

View File

@@ -204,7 +204,7 @@ docker restart oneapi
### Mongo 副本集自动初始化失败
最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。仍无法正常启动,大部分是因为 cpu 不支持 AUX 指令集,可以切换 Mongo4.x 版本。
最新的 docker-compose 示例优化 Mongo 副本集初始化,实现了全自动。目前在 unbuntu20,22 centos7, wsl2, mac, window 均通过测试。仍无法正常启动,大部分是因为 cpu 不支持 AVX 指令集,可以切换 Mongo4.x 版本。
如果是由于,无法自动初始化副本集合,可以手动初始化副本集:

View File

@@ -0,0 +1,36 @@
---
title: 'V4.8.4(进行中)'
description: 'FastGPT V4.8.4 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 820
---
## 升级指南
### 1. 修改镜像
- fastgpt 镜像 tag 修改成 v4.8.4
- fastgpt-sandbox 镜像 tag 修改成 v4.8.4 (选择性,无变更)
- 商业版镜像 tag 修改成 v4.8.4
### 2. 商业版用户执行初始化
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 商业版的域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/init/484' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
## V4.8.4 更新说明
1. 新增 - 应用使用新权限系统。
2. 新增 - 应用支持文件夹。
3. 优化 - 文本分割增加连续换行、制表符清除,避免大文本性能问题。
4. 修复 - Debug 模式下,相同 source 和 target 内容,导致连线显示异常。
5. 修复 - 定时执行初始化错误。
6. 修复 - 应用调用传参异常。
7. 调整组件库全局theme。

View File

@@ -154,6 +154,8 @@ services:
- MILVUS_TOKEN=none
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
volumes:
- ./config.json:/app/data/config.json

View File

@@ -111,6 +111,8 @@ services:
- PG_URL=postgresql://username:password@pg:5432/postgres
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
volumes:
- ./config.json:/app/data/config.json

View File

@@ -92,6 +92,8 @@ services:
- MILVUS_TOKEN=zilliz_cloud_token
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
volumes:
- ./config.json:/app/data/config.json

View File

@@ -2,3 +2,17 @@ export type ParentTreePathItemType = {
parentId: string;
parentName: string;
};
export type ParentIdType = string | null | undefined;
export type GetResourceFolderListProps = {
parentId: ParentIdType;
};
export type GetResourceFolderListItemResponse = {
name: string;
id: string;
};
export type GetResourceListItemResponse = GetResourceFolderListItemResponse & {
avatar: string;
isFolder: boolean;
};

View File

@@ -0,0 +1,17 @@
import { ParentIdType } from './type';
export const parseParentIdInMongo = (parentId: ParentIdType) => {
if (parentId === undefined) return {};
if (parentId === null || parentId === '')
return {
parentId: null
};
const pattern = /^[0-9a-fA-F]{24}$/;
if (pattern.test(parentId))
return {
parentId
};
return {};
};

View File

@@ -102,6 +102,8 @@ const commonSplit = (props: SplitProps): SplitResponse => {
text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) {
return match.replace(/\n/g, codeBlockMarker);
});
// replace invalid \n
text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n');
// The larger maxLen is, the next sentence is less likely to trigger splitting
const stepReges: { reg: RegExp; maxLen: number }[] = [
@@ -338,7 +340,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
*/
export const splitText2Chunks = (props: SplitProps): SplitResponse => {
let { text = '' } = props;
const start = Date.now();
const splitWithCustomSign = text.split(CUSTOM_SPLIT_SIGN);
const splitResult = splitWithCustomSign.map((item) => {
@@ -348,7 +350,7 @@ export const splitText2Chunks = (props: SplitProps): SplitResponse => {
return commonSplit(props);
});
console.log(Date.now() - start);
return {
chunks: splitResult.map((item) => item.chunks).flat(),
chars: splitResult.reduce((sum, item) => sum + item.chars, 0)

View File

@@ -31,7 +31,6 @@ export type FastGPTFeConfigsType = {
show_openai_account?: boolean;
show_promotion?: boolean;
show_team_chat?: boolean;
hide_app_flow?: boolean;
concatMd?: string;
docUrl?: string;
chatbotUrl?: string;

View File

@@ -0,0 +1,11 @@
import { UpdateClbPermissionProps } from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type';
export type UpdateAppCollaboratorBody = UpdateClbPermissionProps & {
appId: string;
};
export type AppCollaboratorDeleteParams = {
appId: string;
tmbId: string;
};

View File

@@ -1,10 +1,14 @@
import { AppTTSConfigType, AppWhisperConfigType } from './type';
export enum AppTypeEnum {
folder = 'folder',
simple = 'simple',
advanced = 'advanced'
}
export const AppTypeMap = {
[AppTypeEnum.folder]: {
label: 'folder'
},
[AppTypeEnum.simple]: {
label: 'simple'
},

View File

@@ -1,5 +1,4 @@
import type { FlowNodeTemplateType, StoreNodeItemType } from '../workflow/type';
import { AppTypeEnum } from './constants';
import { PermissionTypeEnum } from '../../support/permission/constant';
import { VariableInputEnum } from '../workflow/constants';
@@ -7,14 +6,19 @@ import { SelectedDatasetType } from '../workflow/api';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/user/team/type.d';
import { StoreEdgeItemType } from '../workflow/type/edge';
import { PermissionValueType } from '../../support/permission/type';
import { AppPermission } from '../../support/permission/app/controller';
import { ParentIdType } from '../../common/parentFolder/type';
export type AppSchema = {
_id: string;
parentId?: ParentIdType;
teamId: string;
tmbId: string;
name: string;
type: `${AppTypeEnum}`;
type: AppTypeEnum;
version?: 'v1' | 'v2';
name: string;
avatar: string;
intro: string;
updateTime: number;
@@ -27,9 +31,9 @@ export type AppSchema = {
scheduledTriggerConfig?: AppScheduledTriggerConfigType | null;
scheduledTriggerNextTime?: Date;
permission: `${PermissionTypeEnum}`;
inited?: boolean;
teamTags: string[];
defaultPermission: PermissionValueType;
};
export type AppListItemType = {
@@ -37,13 +41,13 @@ export type AppListItemType = {
name: string;
avatar: string;
intro: string;
isOwner: boolean;
permission: `${PermissionTypeEnum}`;
type: AppTypeEnum;
defaultPermission: PermissionValueType;
permission: AppPermission;
};
export type AppDetailType = AppSchema & {
isOwner: boolean;
canWrite: boolean;
permission: AppPermission;
};
export type AppSimpleEditFormType = {

View File

@@ -21,7 +21,7 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => ({
limit: 1500,
searchMode: DatasetSearchModeEnum.embedding,
usingReRank: false,
datasetSearchUsingExtensionQuery: true,
datasetSearchUsingExtensionQuery: false,
datasetSearchExtensionBg: ''
},
selectedTools: [],

View File

@@ -35,7 +35,10 @@ export const RunAppModule: FlowNodeTemplateType = {
required: true
},
Input_Template_History,
Input_Template_UserChatInput
{
...Input_Template_UserChatInput,
toolDescription: '用户问题'
}
],
outputs: [
{

View File

@@ -1,6 +1,6 @@
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/index.d';
import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { getHandleConfig } from '../utils';
export const SystemConfigNode: FlowNodeTemplateType = {

View File

@@ -20,6 +20,7 @@ import { RuntimeNodeItemType } from '../runtime/type';
import { PluginTypeEnum } from '../../plugin/constants';
import { RuntimeEdgeItemType, StoreEdgeItemType } from './edge';
import { NextApiResponse } from 'next';
import { AppDetailType, AppSchema } from '../../app/type';
export type FlowNodeCommonType = {
flowNodeType: FlowNodeTypeEnum; // render node card
@@ -105,8 +106,8 @@ export type NodeSourceNodeItemType = {
/* --------------- function type -------------------- */
export type SelectAppItemType = {
id: string;
name: string;
logo: string;
// name: string;
// logo?: string;
};
/* agent */
@@ -131,7 +132,7 @@ export type ChatDispatchProps = {
teamId: string;
tmbId: string;
user: UserModelSchema;
appId?: string;
app: AppDetailType | AppSchema;
chatId?: string;
responseChatItemId?: string;
histories: ChatItemType[];

View File

@@ -0,0 +1,20 @@
import { NullPermission, PermissionKeyEnum, PermissionList } from '../constant';
import { PermissionListType } from '../type';
export enum AppPermissionKeyEnum {}
export const AppPermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: '可使用该应用进行对话'
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
description: '可查看和编辑应用'
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: '写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限'
}
};
export const AppDefaultPermissionVal = NullPermission;

View File

@@ -0,0 +1,15 @@
import { PerConstructPros, Permission } from '../controller';
import { AppDefaultPermissionVal } from './constant';
export class AppPermission extends Permission {
constructor(props?: PerConstructPros) {
if (!props) {
props = {
per: AppDefaultPermissionVal
};
} else if (!props?.per) {
props.per = AppDefaultPermissionVal;
}
super(props);
}
}

View File

View File

@@ -0,0 +1,15 @@
import { Permission } from './controller';
import { PermissionValueType } from './type';
export type CollaboratorItemType = {
teamId: string;
tmbId: string;
permission: Permission;
name: string;
avatar: string;
};
export type UpdateClbPermissionProps = {
tmbIds: string[];
permission: PermissionValueType;
};

View File

@@ -1,3 +1,6 @@
import { Permission } from './controller';
import { PermissionListType } from './type';
export enum AuthUserTypeEnum {
token = 'token',
root = 'root',
@@ -8,7 +11,10 @@ export enum AuthUserTypeEnum {
export enum PermissionTypeEnum {
'private' = 'private',
'public' = 'public'
'public' = 'public',
clbPrivate = 'clbPrivate',
publicRead = 'publicRead',
publicWrite = 'publicWrite'
}
export const PermissionTypeMap = {
[PermissionTypeEnum.private]: {
@@ -18,11 +24,56 @@ export const PermissionTypeMap = {
[PermissionTypeEnum.public]: {
iconLight: 'support/permission/publicLight',
label: 'permission.Public'
},
[PermissionTypeEnum.publicRead]: {
iconLight: 'support/permission/publicLight',
label: '团队可访问'
},
[PermissionTypeEnum.publicWrite]: {
iconLight: 'support/permission/publicLight',
label: '团队可编辑'
},
[PermissionTypeEnum.clbPrivate]: {
iconLight: 'support/permission/privateLight',
label: '仅协作者'
}
};
export enum ResourceTypeEnum {
export enum PerResourceTypeEnum {
team = 'team',
app = 'app',
dataset = 'dataset'
}
/* new permission */
export enum PermissionKeyEnum {
read = 'read',
write = 'write',
manage = 'manage'
}
export const PermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
name: '读权限',
description: '',
value: 0b100,
checkBoxType: 'single'
},
[PermissionKeyEnum.write]: {
name: '写权限',
description: '',
value: 0b110, // 如果某个资源有特殊要求,再重写这个值
checkBoxType: 'single'
},
[PermissionKeyEnum.manage]: {
name: '管理员',
description: '',
value: 0b111,
checkBoxType: 'single'
}
};
export const NullPermission = 0;
export const OwnerPermissionVal = ~0 >>> 0;
export const ReadPermissionVal = PermissionList['read'].value;
export const WritePermissionVal = PermissionList['write'].value;
export const ManagePermissionVal = PermissionList['manage'].value;

View File

@@ -0,0 +1,71 @@
import { PermissionValueType } from './type';
import { PermissionList, NullPermission, OwnerPermissionVal } from './constant';
export type PerConstructPros = {
per?: PermissionValueType;
isOwner?: boolean;
};
// the Permission helper class
export class Permission {
value: PermissionValueType;
isOwner: boolean;
hasManagePer: boolean;
hasWritePer: boolean;
hasReadPer: boolean;
constructor(props?: PerConstructPros) {
const { per = NullPermission, isOwner = false } = props || {};
if (isOwner) {
this.value = OwnerPermissionVal;
} else {
this.value = per;
}
this.isOwner = isOwner;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
}
// add permission(s)
// it can be chaining called.
// @example
// const perm = new Permission(permission)
// perm.add(PermissionList['read'])
// perm.add(PermissionList['read'], PermissionList['write'])
// perm.add(PermissionList['read']).add(PermissionList['write'])
addPer(...perList: PermissionValueType[]) {
for (let oer of perList) {
this.value = this.value | oer;
}
this.updatePermissions();
return this.value;
}
removePer(...perList: PermissionValueType[]) {
for (let per of perList) {
this.value = this.value & ~per;
}
this.updatePermissions();
return this.value;
}
checkPer(perm: PermissionValueType): boolean {
// if the permission is owner permission, only owner has this permission.
if (perm === OwnerPermissionVal) {
return this.value === OwnerPermissionVal;
} else if (this.hasManagePer) {
// The manager has all permissions except the owner permission
return true;
}
return (this.value & perm) === perm;
}
private updatePermissions() {
this.isOwner = this.value === OwnerPermissionVal;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
}
}

View File

@@ -1,6 +1,20 @@
import { AuthUserTypeEnum } from './constant';
import { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum } from './constant';
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux.
// The lowest 3 bits present the permission of reading, writing and managing.
// The higher bits are advanced permissions or extended permissions, which could be customized.
export type PermissionValueType = number;
export type PermissionListType<T = {}> = Record<
T | PermissionKeyEnum,
{
name: string;
description: string;
value: PermissionValueType;
checkBoxType: 'single' | 'multiple';
}
>;
export type AuthResponseType = {
teamId: string;
@@ -17,4 +31,9 @@ export type ResourcePermissionType = {
tmbId: string;
resourceType: ResourceType;
permission: PermissionValueType;
resourceId: string;
};
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
tmbId: TeamMemberWithUserSchema;
};

View File

@@ -0,0 +1,19 @@
import { PermissionKeyEnum, PermissionList, ReadPermissionVal } from '../constant';
import { PermissionListType } from '../type';
export const TeamPermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: '成员仅可阅读相关资源,无法新建资源'
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
description: '除了可读资源外,还可以新建新的资源'
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: '可创建资源、邀请、删除成员'
}
};
export const TeamDefaultPermissionVal = ReadPermissionVal;

View File

@@ -0,0 +1,15 @@
import { PerConstructPros, Permission } from '../controller';
import { TeamDefaultPermissionVal } from './constant';
export class TeamPermission extends Permission {
constructor(props?: PerConstructPros) {
if (!props) {
props = {
per: TeamDefaultPermissionVal
};
} else if (!props?.per) {
props.per = TeamDefaultPermissionVal;
}
super(props);
}
}

View File

@@ -1,22 +1,25 @@
import { TeamMemberRoleEnum } from '../user/team/constant';
import { PermissionTypeEnum } from './constant';
import { Permission } from './controller';
/* team public source, or owner source in team */
export function mongoRPermission({
teamId,
tmbId,
role
permission
}: {
teamId: string;
tmbId: string;
role: `${TeamMemberRoleEnum}`;
permission: Permission;
}) {
if (permission.isOwner) {
return {
teamId
};
}
return {
teamId,
...(role === TeamMemberRoleEnum.visitor && { permission: PermissionTypeEnum.public }),
...(role === TeamMemberRoleEnum.admin && {
$or: [{ permission: PermissionTypeEnum.public }, { tmbId }]
})
$or: [{ permission: PermissionTypeEnum.public }, { tmbId }]
};
}
export function mongoOwnerPermission({ teamId, tmbId }: { teamId: string; tmbId: string }) {

View File

@@ -1,4 +1,4 @@
import { PermissionValueType } from 'support/permission/type';
import { PermissionValueType } from '../../permission/type';
import { TeamMemberRoleEnum } from './constant';
import { LafAccountType, TeamMemberSchema } from './type';
@@ -22,8 +22,7 @@ export type UpdateTeamProps = {
/* ------------- member ----------- */
export type DelMemberProps = {
teamId: string;
memberId: string;
tmbId: string;
};
export type UpdateTeamMemberProps = {
teamId: string;
@@ -34,7 +33,7 @@ export type UpdateTeamMemberProps = {
export type InviteMemberProps = {
teamId: string;
usernames: string[];
role: `${TeamMemberRoleEnum}`;
permission: PermissionValueType;
};
export type UpdateInviteProps = {
tmbId: string;
@@ -44,9 +43,3 @@ export type InviteMemberResponse = Record<
'invite' | 'inValid' | 'inTeam',
{ username: string; userId: string }[]
>;
export type UpdateTeamMemberPermissionProps = {
teamId: string;
memberIds: string[];
permission: PermissionValueType;
};

View File

@@ -2,6 +2,7 @@ import type { UserModelSchema } from '../type';
import type { TeamMemberRoleEnum, TeamMemberStatusEnum } from './constant';
import { LafAccountType } from './type';
import { PermissionValueType, ResourcePermissionType } from '../../permission/type';
import { TeamPermission } from '../../permission/user/controller';
export type TeamSchema = {
_id: string;
@@ -49,7 +50,7 @@ export type TeamMemberWithTeamAndUserSchema = Omit<TeamMemberWithTeamSchema, 'us
userId: UserModelSchema;
};
export type TeamItemType = {
export type TeamTmbItemType = {
userId: string;
teamId: string;
teamName: string;
@@ -61,9 +62,8 @@ export type TeamItemType = {
defaultTeam: boolean;
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
canWrite: boolean;
lafAccount?: LafAccountType;
defaultPermission: PermissionValueType;
permission: TeamPermission;
};
export type TeamMemberItemType = {
@@ -75,7 +75,7 @@ export type TeamMemberItemType = {
// TODO: this should be deprecated.
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
permission: PermissionValueType;
permission: TeamPermission;
};
export type TeamTagItemType = {

View File

@@ -1,5 +1,5 @@
import { UserStatusEnum } from './constant';
import { TeamItemType } from './team/type';
import { TeamTmbItemType } from './team/type';
export type UserModelSchema = {
_id: string;
@@ -29,6 +29,6 @@ export type UserType = {
timezone: string;
promotionRate: UserModelSchema['promotionRate'];
openaiAccount: UserModelSchema['openaiAccount'];
team: TeamItemType;
team: TeamTmbItemType;
standardInfo?: standardInfoType;
};

View File

@@ -8,7 +8,7 @@ import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MongoRawTextBuffer } from '../../buffer/rawText/schema';
import { readRawContentByFileBuffer } from '../read/utils';
import { PassThrough } from 'stream';
import { gridFsStream2Buffer } from './utils';
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
MongoFileSchema;
@@ -113,35 +113,16 @@ export async function getDownloadStream({
fileId: string;
}) {
const bucket = getGridBucket(bucketName);
const stream = bucket.openDownloadStream(new Types.ObjectId(fileId));
const copyStream = stream.pipe(new PassThrough());
const encodeStream = bucket.openDownloadStream(new Types.ObjectId(fileId), { end: 100 });
const rawStream = bucket.openDownloadStream(new Types.ObjectId(fileId));
/* get encoding */
const buffer = await (() => {
return new Promise<Buffer>((resolve, reject) => {
let tmpBuffer: Buffer = Buffer.from([]);
stream.on('data', (chunk) => {
if (tmpBuffer.length < 20) {
tmpBuffer = Buffer.concat([tmpBuffer, chunk]);
}
if (tmpBuffer.length >= 20) {
resolve(tmpBuffer);
}
});
stream.on('end', () => {
resolve(tmpBuffer);
});
stream.on('error', (err) => {
reject(err);
});
});
})();
const buffer = await gridFsStream2Buffer(encodeStream);
const encoding = detectFileEncoding(buffer);
return {
fileStream: copyStream,
fileStream: rawStream,
encoding
// encoding: 'utf-8'
};
@@ -169,32 +150,21 @@ export const readFileContentFromMongo = async ({
filename: fileBuffer.metadata?.filename || ''
};
}
const start = Date.now();
const [file, { encoding, fileStream }] = await Promise.all([
getFileById({ bucketName, fileId }),
getDownloadStream({ bucketName, fileId })
]);
// console.log('get file stream', Date.now() - start);
if (!file) {
return Promise.reject(CommonErrEnum.fileNotFound);
}
const extension = file?.filename?.split('.')?.pop()?.toLowerCase() || '';
const fileBuffers = await (() => {
return new Promise<Buffer>((resolve, reject) => {
let buffer = Buffer.from([]);
fileStream.on('data', (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
});
fileStream.on('end', () => {
resolve(buffer);
});
fileStream.on('error', (err) => {
reject(err);
});
});
})();
const fileBuffers = await gridFsStream2Buffer(fileStream);
// console.log('get file buffer', Date.now() - start);
const { rawText } = await readRawContentByFileBuffer({
extension,

View File

@@ -0,0 +1,15 @@
export const gridFsStream2Buffer = (stream: NodeJS.ReadableStream) => {
return new Promise<Buffer>((resolve, reject) => {
let tmpBuffer: Buffer = Buffer.from([]);
stream.on('data', (chunk) => {
tmpBuffer = Buffer.concat([tmpBuffer, chunk]);
});
stream.on('end', () => {
resolve(tmpBuffer);
});
stream.on('error', (err) => {
reject(err);
});
});
};

View File

@@ -70,7 +70,7 @@ export const countGptMessagesTokens = (
callbackMap[id] = (data) => {
// 检测是否有内存泄漏
addLog.info(`Count token time: ${Date.now() - start}, token: ${data}`);
addLog.debug(`Count token time: ${Date.now() - start}, token: ${data}`);
// console.log(process.memoryUsage());
resolve(data);

View File

@@ -1,11 +1,12 @@
import dayjs from 'dayjs';
import chalk from 'chalk';
import { isProduction } from './constants';
enum LogLevelEnum {
debug = 'debug',
info = 'info',
warn = 'warn',
error = 'error'
debug = 0,
info = 1,
warn = 2,
error = 3
}
const logMap = {
[LogLevelEnum.debug]: {
@@ -21,20 +22,35 @@ const logMap = {
levelLog: chalk.red('[Error]')
}
};
const envLogLevelMap: Record<string, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
const logLevel = (() => {
if (!isProduction) return LogLevelEnum.debug;
const envLogLevel = (process.env.LOG_LEVEL || 'info').toLocaleLowerCase();
if (!envLogLevel || envLogLevelMap[envLogLevel] === undefined) return LogLevelEnum.info;
return envLogLevelMap[envLogLevel];
})();
/* add logger */
export const addLog = {
log(level: LogLevelEnum, msg: string, obj: Record<string, any> = {}) {
if (level < logLevel) return;
const stringifyObj = JSON.stringify(obj);
const isEmpty = Object.keys(obj).length === 0;
console.log(
`${logMap[level].levelLog} ${dayjs().format('YYYY-MM-DD HH:mm:ss')} ${msg} ${
level !== 'error' && !isEmpty ? stringifyObj : ''
level !== LogLevelEnum.error && !isEmpty ? stringifyObj : ''
}`
);
level === 'error' && console.error(obj);
level === LogLevelEnum.error && console.error(obj);
const lokiUrl = process.env.LOKI_LOG_URL as string;
if (!lokiUrl) return;

View File

@@ -3,6 +3,7 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getLLMModel } from '../ai/model';
import { MongoAppVersion } from './version/schema';
import { MongoApp } from './schema';
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
nodes
@@ -65,3 +66,40 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
chatConfig: app?.chatConfig || {}
};
};
/* Get apps */
export async function findAppAndAllChildren({
teamId,
appId,
fields
}: {
teamId: string;
appId: string;
fields?: string;
}): Promise<AppSchema[]> {
const find = async (id: string) => {
const children = await MongoApp.find(
{
teamId,
parentId: id
},
fields
).lean();
let apps = children;
for (const child of children) {
const grandChildrenIds = await find(child._id);
apps = apps.concat(grandChildrenIds);
}
return apps;
};
const [app, childDatasets] = await Promise.all([MongoApp.findById(appId, fields), find(appId)]);
if (!app) {
return Promise.reject('Dataset not found');
}
return [app, ...childDatasets];
}

View File

@@ -1,4 +1,4 @@
import { AppTypeMap } from '@fastgpt/global/core/app/constants';
import { AppTypeEnum, AppTypeMap } from '@fastgpt/global/core/app/constants';
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
@@ -7,6 +7,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
export const AppCollectionName = 'apps';
@@ -21,6 +22,11 @@ export const chatConfigType = {
};
const AppSchema = new Schema({
parentId: {
type: Schema.Types.ObjectId,
ref: AppCollectionName,
default: null
},
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
@@ -37,8 +43,8 @@ const AppSchema = new Schema({
},
type: {
type: String,
default: 'advanced',
enum: Object.keys(AppTypeMap)
default: AppTypeEnum.advanced,
enum: Object.values(AppTypeEnum)
},
version: {
type: String,
@@ -98,12 +104,18 @@ const AppSchema = new Schema({
inited: {
type: Boolean
},
// the default permission of a app
defaultPermission: {
type: Number,
default: AppDefaultPermissionVal
}
});
try {
AppSchema.index({ updateTime: -1 });
AppSchema.index({ teamId: 1 });
AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
} catch (error) {
console.log(error);

View File

@@ -142,17 +142,17 @@ export const delCollectionRelatedSource = async ({
.map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean);
// delete files
await delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
});
// delete images
await delImgByRelatedId({
teamId,
relateIds: relatedImageIds,
session
});
// delete files
await delFileByFileIdList({
bucketName: BucketNameEnum.dataset,
fileIdList
});
};
/**
* delete collection and it related data
@@ -182,14 +182,16 @@ export async function delCollectionAndRelatedSources({
);
const collectionIds = collections.map((item) => String(item._id));
await delCollectionRelatedSource({ collections, session });
// delete training data
await MongoDatasetTraining.deleteMany({
teamId,
datasetIds: { $in: datasetIds },
collectionId: { $in: collectionIds }
});
/* file and imgs */
await delCollectionRelatedSource({ collections, session });
// delete dataset.datas
await MongoDatasetData.deleteMany(
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
@@ -199,6 +201,7 @@ export async function delCollectionAndRelatedSources({
// delete collections
await MongoDatasetCollection.deleteMany(
{
teamId,
_id: { $in: collectionIds }
},
{ session }

View File

@@ -42,7 +42,7 @@ export async function findCollectionAndChild({
return collections;
}
const [collection, childCollections] = await Promise.all([
MongoDatasetCollection.findById(collectionId, fields),
MongoDatasetCollection.findById(collectionId, fields).lean(),
find(collectionId)
]);

View File

@@ -82,17 +82,18 @@ export async function delDatasetRelevantData({
teamId,
datasetId: { $in: datasetIds }
},
'_id teamId fileId metadata'
'_id teamId datasetId fileId metadata'
).lean();
// image and file
await delCollectionRelatedSource({ collections, session });
// delete training data
await MongoDatasetTraining.deleteMany({
teamId,
datasetId: { $in: datasetIds }
});
// image and file
await delCollectionRelatedSource({ collections, session });
// delete dataset.datas
await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }, { session });

View File

@@ -407,13 +407,13 @@ export function responseStatus({
/* get system variable */
export function getSystemVariable({
user,
appId,
app,
chatId,
responseChatItemId,
histories = []
}: Props) {
return {
appId,
appId: String(app._id),
chatId,
responseChatItemId,
histories,

View File

@@ -43,7 +43,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
let {
res,
detail,
appId,
app: { _id: appId },
chatId,
stream,
responseChatItemId,

View File

@@ -17,6 +17,8 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
import { getHistories } from '../utils';
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
@@ -31,28 +33,26 @@ type Response = DispatchNodeResultType<{
export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const {
res,
teamId,
app: workflowApp,
stream,
detail,
histories,
query,
params: { userChatInput, history, app }
} = props;
let start = Date.now();
if (!userChatInput) {
return Promise.reject('Input is empty');
}
const appData = await MongoApp.findOne({
_id: app.id,
teamId
// 检查该工作流的tmb是否有调用该app的权限不是校验对话的人是否有权限
const { app: appData } = await authAppByTmbId({
appId: app.id,
teamId: workflowApp.teamId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
});
if (!appData) {
return Promise.reject('App not found');
}
if (res && stream) {
responseWrite({
res,
@@ -64,18 +64,19 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
}
const chatHistories = getHistories(history, histories);
const { files } = chatValue2RuntimePrompt(query);
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
...props,
appId: app.id,
app: appData,
runtimeNodes: storeNodes2RuntimeNodes(appData.modules, getDefaultEntryNodeIds(appData.modules)),
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
histories: chatHistories,
query,
variables: {
...props.variables,
userChatInput
}
query: runtimePrompt2ChatsValue({
files,
text: userChatInput
}),
variables: props.variables
});
const completeMessages = chatHistories.concat([

View File

@@ -21,7 +21,7 @@ const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
export const dispatchLafRequest = async (props: LafRequestProps): Promise<LafResponse> => {
let {
appId,
app: { _id: appId },
chatId,
responseChatItemId,
variables,

View File

@@ -0,0 +1,85 @@
/* Auth app permission */
import { MongoApp } from '../../../core/app/schema';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import { AuthPropsType } from '../type/auth.d';
import { parseHeaderCert } from '../controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { getResourcePermission } from '../controller';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { AuthResponseType } from '../type/auth.d';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
export const authAppByTmbId = async ({
teamId,
tmbId,
appId,
per
}: {
teamId: string;
tmbId: string;
appId: string;
per: PermissionValueType;
}) => {
const { permission: tmbPer } = await getTmbInfoByTmbId({ tmbId });
const app = await (async () => {
// get app and per
const [app, rp] = await Promise.all([
MongoApp.findOne({ _id: appId, teamId }).lean(),
getResourcePermission({
teamId,
tmbId,
resourceId: appId,
resourceType: PerResourceTypeEnum.app
}) // this could be null
]);
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = tmbPer.isOwner || String(app.tmbId) === tmbId;
const Per = new AppPermission({ per: rp?.permission ?? app.defaultPermission, isOwner });
if (!Per.checkPer(per)) {
return Promise.reject(AppErrEnum.unAuthApp);
}
return {
...app,
permission: Per
};
})();
return { app };
};
export const authApp = async ({
appId,
per,
...props
}: AuthPropsType & {
appId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
}
> => {
const result = await parseHeaderCert(props);
const { teamId, tmbId } = result;
const { app } = await authAppByTmbId({
teamId,
tmbId,
appId,
per
});
return {
...result,
permission: app.permission,
app
};
};

View File

@@ -1,72 +0,0 @@
import { MongoApp } from '../../../core/app/schema';
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
import { AuthModeType } from '../type';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
// 模型使用权校验
export async function authApp({
appId,
per = 'owner',
...props
}: AuthModeType & {
appId: string;
}): Promise<
AuthResponseType & {
teamOwner: boolean;
app: AppDetailType;
role: `${TeamMemberRoleEnum}`;
}
> {
const result = await parseHeaderCert(props);
const { teamId, tmbId } = result;
const { role } = await getTmbInfoByTmbId({ tmbId });
const { app, isOwner, canWrite } = await (async () => {
// get app
const app = await MongoApp.findOne({ _id: appId, teamId }).lean();
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = String(app.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
const canWrite =
isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);
if (per === 'r') {
if (!isOwner && app.permission !== PermissionTypeEnum.public) {
return Promise.reject(AppErrEnum.unAuthApp);
}
}
if (per === 'w' && !canWrite) {
return Promise.reject(AppErrEnum.unAuthApp);
}
if (per === 'owner' && !isOwner) {
return Promise.reject(AppErrEnum.unAuthApp);
}
return {
app: {
...app,
isOwner,
canWrite
},
isOwner,
canWrite
};
})();
return {
...result,
app,
role,
isOwner,
canWrite,
teamOwner: role === TeamMemberRoleEnum.owner
};
}

View File

@@ -198,4 +198,4 @@ export async function authDatasetFile({
} catch (error) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
}
}
}

View File

@@ -1,96 +0,0 @@
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { AuthModeType } from '../type';
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { AppDetailType } from '@fastgpt/global/core/app/type';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { parseHeaderCert } from '../controller';
import { MongoOutLink } from '../../outLink/schema';
import { MongoApp } from '../../../core/app/schema';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { getTmbInfoByTmbId } from '../../user/team/controller';
/* crud outlink permission */
export async function authOutLinkCrud({
outLinkId,
per = 'owner',
...props
}: AuthModeType & {
outLinkId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
outLink: OutLinkSchema;
}
> {
const result = await parseHeaderCert(props);
const { tmbId, teamId } = result;
const { role } = await getTmbInfoByTmbId({ tmbId });
const { app, outLink, isOwner, canWrite } = await (async () => {
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId });
if (!outLink) {
throw new Error(OutLinkErrEnum.unExist);
}
const app = await MongoApp.findById(outLink.appId);
if (!app) {
return Promise.reject(AppErrEnum.unExist);
}
const isOwner = String(outLink.tmbId) === tmbId || role === TeamMemberRoleEnum.owner;
const canWrite =
isOwner ||
(app.permission === PermissionTypeEnum.public && role !== TeamMemberRoleEnum.visitor);
if (per === 'r' && !isOwner && app.permission !== PermissionTypeEnum.public) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
if (per === 'w' && !canWrite) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
if (per === 'owner' && !isOwner) {
return Promise.reject(OutLinkErrEnum.unAuthLink);
}
return {
app: {
...app,
isOwner: String(app.tmbId) === tmbId,
canWrite
},
outLink,
isOwner,
canWrite
};
})();
return {
...result,
app,
outLink,
isOwner,
canWrite
};
}
/* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) {
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = await MongoOutLink.findOne({ shareId });
if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
return {
appId: shareChat.appId,
shareChat
};
}

View File

@@ -1,66 +0,0 @@
import { AuthResponseType } from '@fastgpt/global/support/permission/type';
import { AuthModeType } from '../type';
import { TeamItemType } from '@fastgpt/global/support/user/team/type';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { UserErrEnum } from '../../../../global/common/error/code/user';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
export async function authUserNotVisitor(props: AuthModeType): Promise<
AuthResponseType & {
team: TeamItemType;
role: `${TeamMemberRoleEnum}`;
}
> {
const { teamId, tmbId } = await parseHeaderCert(props);
const team = await getTmbInfoByTmbId({ tmbId });
if (team.role === TeamMemberRoleEnum.visitor) {
return Promise.reject(UserErrEnum.binVisitor);
}
return {
teamId,
tmbId,
team,
role: team.role,
isOwner: team.role === TeamMemberRoleEnum.owner, // teamOwner
canWrite: true
};
}
/* auth user role */
export async function authUserRole(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const result = await parseHeaderCert(props);
const { role: userRole, canWrite } = await getTmbInfoByTmbId({ tmbId: result.tmbId });
return {
...result,
isOwner: true,
role: userRole,
teamOwner: userRole === TeamMemberRoleEnum.owner,
canWrite
};
}
/* auth teamMember in team role */
export async function authTeamOwner(props: AuthModeType): Promise<
AuthResponseType & {
role: `${TeamMemberRoleEnum}`;
teamOwner: boolean;
}
> {
const authRes = await authUserRole(props);
if (authRes.role !== TeamMemberRoleEnum.owner) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
return authRes;
}

View File

@@ -3,10 +3,39 @@ import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import jwt from 'jsonwebtoken';
import { NextApiResponse } from 'next';
import type { AuthModeType, ReqHeaderAuthType } from './type.d';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { AuthUserTypeEnum, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { authOpenApiKey } from '../openapi/auth';
import { FileTokenQuery } from '@fastgpt/global/common/file/type';
import { MongoResourcePermission } from './schema';
export const getResourcePermission = async ({
resourceType,
teamId,
tmbId,
resourceId
}: {
resourceType: PerResourceTypeEnum;
teamId: string;
tmbId: string;
resourceId?: string;
}) => {
const per = await MongoResourcePermission.findOne({
tmbId,
teamId,
resourceType,
resourceId
});
if (!per) {
return null;
}
return per;
};
export const delResourcePermissionById = (id: string) => {
return MongoResourcePermission.findByIdAndRemove(id);
};
/* 下面代码等迁移 */
/* create token */
export function createJWT(user: { _id?: string; team?: { teamId?: string; tmbId: string } }) {
const key = process.env.TOKEN_KEY as string;

View File

@@ -0,0 +1,69 @@
import { AppDetailType } from '@fastgpt/global/core/app/type';
import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import { parseHeaderCert } from '../controller';
import { MongoOutLink } from '../../outLink/schema';
import { OutLinkErrEnum } from '@fastgpt/global/common/error/code/outLink';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import { AuthPropsType } from '../type/auth';
import { AuthResponseType } from '../type/auth';
import { authAppByTmbId } from '../app/auth';
/* crud outlink permission */
export async function authOutLinkCrud({
outLinkId,
per,
...props
}: AuthPropsType & {
outLinkId: string;
}): Promise<
AuthResponseType & {
app: AppDetailType;
outLink: OutLinkSchema;
}
> {
const result = await parseHeaderCert(props);
const { tmbId, teamId } = result;
const { app, outLink } = await (async () => {
const outLink = await MongoOutLink.findOne({ _id: outLinkId, teamId });
if (!outLink) {
throw new Error(OutLinkErrEnum.unExist);
}
const { app } = await authAppByTmbId({
teamId,
tmbId,
appId: outLink.appId,
per: ManagePermissionVal
});
return {
outLink,
app
};
})();
return {
...result,
permission: app.permission,
app,
outLink
};
}
/* outLink exist and it app exist */
export async function authOutLinkValid({ shareId }: { shareId?: string }) {
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = await MongoOutLink.findOne({ shareId });
if (!shareChat) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
return {
appId: shareChat.appId,
shareChat
};
}

View File

@@ -1,16 +0,0 @@
import { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { MongoResourcePermission } from './schema';
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
export async function getResourcePermission({
tmbId,
resourceType
}: {
tmbId: string;
resourceType: ResourceTypeEnum;
}) {
return (await MongoResourcePermission.findOne({
tmbId,
resourceType
})) as ResourcePermissionType;
}

View File

@@ -1,127 +0,0 @@
// PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux.
// The lowest 3 bits present the permission of reading, writing and managing.
// The higher bits are advanced permissions or extended permissions, which could be customized.
export type PermissionValueType = number;
export type PermissionListType = { [key: string]: PermissionValueType };
export const NullPermission: PermissionValueType = 0;
// the Permission helper class
export class Permission {
value: PermissionValueType;
constructor(value: PermissionValueType) {
this.value = value;
}
// add permission(s)
// it can be chaining called.
// @example
// const perm = new Permission(permission)
// perm.add(PermissionList['read'])
// perm.add(PermissionList['read'], PermissionList['write'])
// perm.add(PermissionList['read']).add(PermissionList['write'])
add(...perm: PermissionValueType[]): Permission {
for (let p of perm) {
this.value = addPermission(this.value, p);
}
return this;
}
remove(...perm: PermissionValueType[]): Permission {
for (let p of perm) {
this.value = removePermission(this.value, p);
}
return this;
}
check(perm: PermissionValueType): Permission | boolean {
if (checkPermission(this.value, perm)) {
return this;
} else {
return false;
}
}
}
export function constructPermission(permList: PermissionValueType[]) {
return new Permission(NullPermission).add(...permList);
}
// The base Permissions List
// It can be extended, for example:
// export const UserPermissionList: PermissionListType = {
// ...PermissionList,
// 'Invite': 0b1000
// }
export const PermissionList: PermissionListType = {
Read: 0b100,
Write: 0b010,
Manage: 0b001
};
// list of permissions. could be customized.
// ! removal of the basic permissions is not recommended.
// const PermList: Array<PermissionType> = [ReadPerm, WritePerm, ManagePerm];
// return the list of permissions
// @param Perm(optional): the list of permissions to be added
// export function getPermList(Perm?: PermissionType[]): Array<PermissionType> {
// if (Perm === undefined) {
// return PermList;
// } else {
// return PermList.concat(Perm);
// }
// }
// check the permission
// @param [val]: The permission value to be checked
// @parma [perm]: Which Permission value will be checked
// @returns [booean]: if the [val] has the [perm]
// example:
// const perm = user.permission // get this permisiion from db or somewhere else
// const ok = checkPermission(perm, PermissionList['Read'])
export function checkPermission(val: PermissionValueType, perm: PermissionValueType): boolean {
return (val & perm) === perm;
}
// add the permission
// it can be chaining called.
// return the new permission value based on [val] added with [perm]
// @param val: PermissionValueType
// @param perm: PermissionValueType
// example:
// const basePerm = 0b001; // Manage only
export function addPermission(
val: PermissionValueType,
perm: PermissionValueType
): PermissionValueType {
return val | perm;
}
// remove the permission
export function removePermission(
val: PermissionValueType,
perm: PermissionValueType
): PermissionValueType {
return val & ~perm;
}
// export function parsePermission(val: PermissionValueType, list: PermissionValueType[]) {
// const result: [[string, boolean]] = [] as any;
// list.forEach((perm) => {
// result.push([perm[0], checkPermission(val, perm)]);
// });
// return result;
// }
export function hasManage(val: PermissionValueType) {
return checkPermission(val, PermissionList['Manage']);
}
export function hasWrite(val: PermissionValueType) {
return checkPermission(val, PermissionList['Write']);
}
export function hasRead(val: PermissionValueType) {
return checkPermission(val, PermissionList['Read']);
}

View File

@@ -2,11 +2,13 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { Model, connectionMongo } from '../../../common/mongo';
import { Model, connectionMongo } from '../../common/mongo';
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { ResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
const { Schema, model, models } = connectionMongo;
export const ResourcePermissionCollectionName = 'resource_permission';
export const ResourcePermissionSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
@@ -17,30 +19,41 @@ export const ResourcePermissionSchema = new Schema({
ref: TeamMemberCollectionName
},
resourceType: {
type: Object.values(ResourceTypeEnum),
type: Object.values(PerResourceTypeEnum),
required: true
},
permission: {
type: Number,
required: true
},
// Resrouce ID: App or DataSet or any other resource type.
// It is null if the resourceType is team.
resourceId: {
type: Schema.Types.ObjectId
}
});
try {
ResourcePermissionSchema.index(
{
resourceType: 1,
teamId: 1,
tmbId: 1,
resourceId: 1
},
{
unique: true
}
);
ResourcePermissionSchema.index({
resourceType: 1,
teamId: 1,
resourceType: 1
});
ResourcePermissionSchema.index({
tmbId: 1,
resourceType: 1
resourceId: 1
});
} catch (error) {
console.log(error);
}
export const ResourcePermissionCollectionName = 'resource_permission';
export const MongoResourcePermission: Model<ResourcePermissionType> =
models[ResourcePermissionCollectionName] ||
model(ResourcePermissionCollectionName, ResourcePermissionSchema);

View File

@@ -1,4 +1,3 @@
import { getVectorCountByTeamId } from '../../common/vectorStore/controller';
import { getTeamPlanStatus, getTeamStandPlan } from '../../support/wallet/sub/utils';
import { MongoApp } from '../../core/app/schema';
import { MongoPlugin } from '../../core/plugin/schema';
@@ -6,6 +5,7 @@ import { MongoDataset } from '../../core/dataset/schema';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { SystemErrEnum } from '@fastgpt/global/common/error/code/system';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
export const checkDatasetLimit = async ({
teamId,
@@ -67,7 +67,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
export const checkTeamAppLimit = async (teamId: string) => {
const [{ standardConstants }, appCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoApp.count({ teamId })
MongoApp.count({ teamId, type: { $in: [AppTypeEnum.advanced, AppTypeEnum.simple] } })
]);
if (standardConstants && appCount >= standardConstants.maxAppAmount) {

View File

@@ -1,4 +1,5 @@
import { ApiRequestProps } from '../../type/next';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
export type ReqHeaderAuthType = {
cookie?: string;
@@ -8,10 +9,11 @@ export type ReqHeaderAuthType = {
userid?: string;
authorization?: string;
};
export type AuthModeType = {
req: ApiRequestProps;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
per?: 'r' | 'w' | 'owner';
per?: PermissionValueType | 'r' | 'w' | 'owner'; // this is for compatibility
};

View File

@@ -0,0 +1,21 @@
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { Permission } from '@fastgpt/global/support/permission/controller';
import { ApiRequestProps } from '../../../type/next';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
export type AuthPropsType = {
req: ApiRequestProps;
authToken?: boolean;
authRoot?: boolean;
authApiKey?: boolean;
per: PermissionValueType;
};
export type AuthResponseType = {
teamId: string;
tmbId: string;
authType?: `${AuthUserTypeEnum}`;
appId?: string;
apikey?: string;
permission: Permission;
};

View File

@@ -0,0 +1,26 @@
import { AuthResponseType } from '../type/auth.d';
import { AuthPropsType } from '../type/auth.d';
import { TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
import { parseHeaderCert } from '../controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
/* auth user role */
export async function authUserPer(props: AuthPropsType): Promise<
AuthResponseType & {
tmb: TeamTmbItemType;
}
> {
const result = await parseHeaderCert(props);
const tmb = await getTmbInfoByTmbId({ tmbId: result.tmbId });
if (!tmb.permission.checkPer(props.per)) {
return Promise.reject(TeamErrEnum.unAuthTeam);
}
return {
...result,
permission: tmb.permission,
tmb
};
}

View File

@@ -1,4 +1,4 @@
import { TeamItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type';
import { TeamTmbItemType, TeamMemberWithTeamSchema } from '@fastgpt/global/support/user/team/type';
import { ClientSession, Types } from '../../../common/mongo';
import {
TeamMemberRoleEnum,
@@ -8,13 +8,22 @@ import {
import { MongoTeamMember } from './teamMemberSchema';
import { MongoTeam } from './teamSchema';
import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
import { getResourcePermission } from '../../permission/controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
async function getTeamMember(match: Record<string, any>): Promise<TeamItemType> {
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
if (!tmb) {
return Promise.reject('member not exist');
}
const tmbPer = await getResourcePermission({
resourceType: PerResourceTypeEnum.team,
teamId: tmb.teamId._id,
tmbId: tmb._id
});
return {
userId: String(tmb.userId),
teamId: String(tmb.teamId._id),
@@ -27,9 +36,11 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamItemType>
role: tmb.role,
status: tmb.status,
defaultTeam: tmb.defaultTeam,
canWrite: tmb.role !== TeamMemberRoleEnum.visitor,
lafAccount: tmb.teamId.lafAccount,
defaultPermission: tmb.teamId.defaultPermission
permission: new TeamPermission({
per: tmbPer?.permission ?? tmb.teamId.defaultPermission,
isOwner: tmb.role === TeamMemberRoleEnum.owner
})
};
}

View File

@@ -3,7 +3,7 @@ const { Schema, model, models } = connectionMongo;
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
import { userCollectionName } from '../../user/schema';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { NullPermission } from '../../permission/resourcePermission/permisson';
import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant';
const TeamSchema = new Schema({
name: {
@@ -16,7 +16,7 @@ const TeamSchema = new Schema({
},
defaultPermission: {
type: Number,
default: NullPermission
default: TeamDefaultPermissionVal
},
avatar: {
type: String,

View File

@@ -49,11 +49,13 @@ const DateRangePicker = ({
py={1}
borderRadius={'sm'}
cursor={'pointer'}
bg={'myWhite.600'}
bg={'myGray.100'}
fontSize={'sm'}
onClick={() => setShowSelected(true)}
>
<Box>{formatSelected}</Box>
<Box color={'myGray.600'} fontWeight={'400'}>
{formatSelected}
</Box>
<MyIcon ml={2} name={'date'} w={'16px'} color={'myGray.600'} />
</Flex>
{showSelected && (

View File

@@ -5,7 +5,7 @@ import { DraggableProvided } from 'react-beautiful-dnd';
const DragIcon = ({ provided, ...props }: { provided: DraggableProvided } & BoxProps) => {
return (
<Box {...provided.dragHandleProps} {...props}>
<Box {...provided.dragHandleProps} {...props} lineHeight={1}>
<DragHandleIcon color={'myGray.500'} _hover={{ color: 'primary.600' }} />
</Box>
);

View File

@@ -12,7 +12,7 @@ const EmptyTip = ({ text, ...props }: Props) => {
return (
<Flex mt={5} flexDirection={'column'} alignItems={'center'} py={'10vh'} {...props}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
<Box mt={2} color={'myGray.500'} fontSize={'sm'}>
{text || t('common.empty.Common Tip')}
</Box>
</Flex>

View File

@@ -46,6 +46,7 @@ export const iconPaths = {
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
'common/resultLight': () => import('./icons/common/resultLight.svg'),
'common/retryLight': () => import('./icons/common/retryLight.svg'),
'common/rightArrowFill': () => import('./icons/common/rightArrowFill.svg'),
'common/rightArrowLight': () => import('./icons/common/rightArrowLight.svg'),
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
'common/saveFill': () => import('./icons/common/saveFill.svg'),
@@ -71,6 +72,7 @@ export const iconPaths = {
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'),
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
'core/app/simpleMode/chat': () => import('./icons/core/app/simpleMode/chat.svg'),
'core/app/simpleMode/dataset': () => import('./icons/core/app/simpleMode/dataset.svg'),
@@ -180,6 +182,7 @@ export const iconPaths = {
kbTest: () => import('./icons/kbTest.svg'),
menu: () => import('./icons/menu.svg'),
minus: () => import('./icons/minus.svg'),
'modal/AddClb': () => import('./icons/modal/AddClb.svg'),
'modal/concat': () => import('./icons/modal/concat.svg'),
'modal/confirmPay': () => import('./icons/modal/confirmPay.svg'),
'modal/edit': () => import('./icons/modal/edit.svg'),

View File

@@ -1,52 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style=" background: rgb(255, 255, 255); display: block; shape-rendering: auto;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g transform="rotate(0 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(30 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(60 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(90 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(120 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(150 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(180 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(210 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(240 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(270 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(300 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate>
</rect>
</g><g transform="rotate(330 50 50)">
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
</rect>
</g>
<!-- [ldio] generated by https://loading.io/ --></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="330" height="330"
style="shape-rendering: auto; display: block; background: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<g transform="rotate(0 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.9166666666666666s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(30 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.8333333333333334s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(60 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.75s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
</animate>
</rect>
</g>
<g transform="rotate(90 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.6666666666666666s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(120 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.5833333333333334s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(150 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.5s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
</animate>
</rect>
</g>
<g transform="rotate(180 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.4166666666666667s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(210 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.3333333333333333s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(240 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.25s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
</animate>
</rect>
</g>
<g transform="rotate(270 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.16666666666666666s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(300 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="-0.08333333333333333s" dur="1s" keyTimes="0;1" values="1;0"
attributeName="opacity"></animate>
</rect>
</g>
<g transform="rotate(330 50 50)">
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
<animate repeatCount="indefinite" begin="0s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
</animate>
</rect>
</g>
<g></g>
</g><!-- [ldio] generated by https://loading.io -->
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<path
d="M5.56201 4.59992C5.56201 3.41205 6.9982 2.81716 7.83815 3.65711L11.2382 7.0572C11.7589 7.5779 11.7589 8.42212 11.2382 8.94282L7.83815 12.3429C6.9982 13.1829 5.56201 12.588 5.56201 11.4001V4.59992Z" />
</svg>

After

Width:  |  Height:  |  Size: 301 B

View File

@@ -0,0 +1,19 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="5.9541" fill="url(#paint0_linear_5533_28322)" />
<path
d="M15.1466 6.22388C15.593 5.96615 16.3168 5.96615 16.7632 6.22388L23.5846 10.1622C24.4774 10.6776 24.4774 11.5134 23.5846 12.0288L16.853 15.9153C16.4066 16.1731 15.6828 16.1731 15.2364 15.9153L8.41507 11.977C7.52225 11.4616 7.52225 10.6258 8.41506 10.1104L15.1466 6.22388Z"
fill="white" />
<path
d="M7.18506 14.3387C7.18506 13.617 7.6917 13.3245 8.31667 13.6853L14.6222 17.3258C14.9793 17.532 15.2688 18.0335 15.2688 18.4458V25.1119C15.2688 25.8336 14.7622 26.1261 14.1372 25.7653L7.83169 22.1248C7.47457 21.9186 7.18506 21.4172 7.18506 21.0048V14.3387Z"
fill="white" />
<path
d="M16.7302 18.4962C16.7302 18.0838 17.0197 17.5823 17.3768 17.3762L23.6833 13.7351C24.3083 13.3742 24.8149 13.6668 24.8149 14.3884V21.0537C24.8149 21.4661 24.5254 21.9675 24.1683 22.1737L17.8618 25.8148C17.2368 26.1756 16.7302 25.8831 16.7302 25.1614V18.4962Z"
fill="white" />
<defs>
<linearGradient id="paint0_linear_5533_28322" x1="16" y1="0" x2="4.88889" y2="29.3333"
gradientUnits="userSpaceOnUse">
<stop stop-color="#67BFFF" />
<stop offset="1" stop-color="#5BA6FF" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.00303 3.64414C6.69861 3.64414 5.64117 4.70158 5.64117 6.006C5.64117 7.31042 6.69861 8.36786 8.00303 8.36786C9.30744 8.36786 10.3649 7.31042 10.3649 6.006C10.3649 4.70158 9.30744 3.64414 8.00303 3.64414ZM3.9745 6.006C3.9745 3.78111 5.77813 1.97748 8.00303 1.97748C10.2279 1.97748 12.0315 3.78111 12.0315 6.006C12.0315 8.23089 10.2279 10.0345 8.00303 10.0345C5.77813 10.0345 3.9745 8.23089 3.9745 6.006ZM12.0234 2.73039C12.1961 2.30378 12.6819 2.09793 13.1085 2.27062C14.5833 2.86762 15.6261 4.31403 15.6261 6.006C15.6261 7.69797 14.5833 9.14439 13.1085 9.74138C12.6819 9.91407 12.1961 9.70823 12.0234 9.28161C11.8507 8.855 12.0565 8.36917 12.4831 8.19649C13.3502 7.84549 13.9595 6.9959 13.9595 6.006C13.9595 5.01611 13.3502 4.16651 12.4831 3.81552C12.0565 3.64283 11.8507 3.157 12.0234 2.73039ZM6.77537 11.563H10C10.4603 11.563 10.8334 11.9361 10.8334 12.3964C10.8334 12.8566 10.4603 13.2297 10 13.2297H6.80483C6.04904 13.2297 5.52394 13.2302 5.11329 13.2582C4.71013 13.2857 4.47852 13.337 4.30339 13.4095C3.72467 13.6492 3.26488 14.109 3.02516 14.6877C2.95262 14.8629 2.90135 15.0945 2.87385 15.4976C2.84583 15.9083 2.84538 16.4334 2.84538 17.1892C2.84538 17.6494 2.47228 18.0225 2.01204 18.0225C1.55181 18.0225 1.17871 17.6494 1.17871 17.1892L1.17871 17.1597C1.17871 16.4403 1.1787 15.8583 1.21105 15.3842C1.24434 14.8962 1.31469 14.462 1.48536 14.0499C1.89424 13.0628 2.67848 12.2786 3.66559 11.8697C4.07764 11.699 4.51182 11.6287 4.99984 11.5954C5.47392 11.563 6.05597 11.563 6.77537 11.563ZM15.5916 11.563C16.0518 11.563 16.4249 11.9361 16.4249 12.3964V13.9594H17.988C18.4482 13.9594 18.8213 14.3325 18.8213 14.7928C18.8213 15.253 18.4482 15.6261 17.988 15.6261H16.4249V17.1892C16.4249 17.6494 16.0518 18.0225 15.5916 18.0225C15.1314 18.0225 14.7583 17.6494 14.7583 17.1892V15.6261H13.1952C12.735 15.6261 12.3619 15.253 12.3619 14.7928C12.3619 14.3325 12.735 13.9594 13.1952 13.9594H14.7583V12.3964C14.7583 11.9361 15.1314 11.563 15.5916 11.563Z"
fill="#3370FF" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,5 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"
fill="#3370FF" />
</svg>
d="M7.00303 2.64414C5.69861 2.64414 4.64117 3.70158 4.64117 5.006C4.64117 6.31042 5.69861 7.36786 7.00303 7.36786C8.30744 7.36786 9.36488 6.31042 9.36488 5.006C9.36488 3.70158 8.30744 2.64414 7.00303 2.64414ZM2.9745 5.006C2.9745 2.78111 4.77813 0.977478 7.00303 0.977478C9.22792 0.977478 11.0315 2.78111 11.0315 5.006C11.0315 7.23089 9.22792 9.03453 7.00303 9.03453C4.77813 9.03453 2.9745 7.23089 2.9745 5.006ZM11.0234 1.73039C11.1961 1.30378 11.6819 1.09793 12.1085 1.27062C13.5833 1.86762 14.6261 3.31403 14.6261 5.006C14.6261 6.69797 13.5833 8.14439 12.1085 8.74138C11.6819 8.91407 11.1961 8.70823 11.0234 8.28161C10.8507 7.855 11.0565 7.36917 11.4831 7.19649C12.3502 6.84549 12.9595 5.9959 12.9595 5.006C12.9595 4.01611 12.3502 3.16651 11.4831 2.81552C11.0565 2.64283 10.8507 2.157 11.0234 1.73039ZM5.77537 10.563H9.00002C9.46026 10.563 9.83335 10.9361 9.83335 11.3964C9.83335 11.8566 9.46026 12.2297 9.00002 12.2297H5.80483C5.04904 12.2297 4.52394 12.2302 4.11329 12.2582C3.71013 12.2857 3.47852 12.337 3.30339 12.4095C2.72467 12.6492 2.26488 13.109 2.02516 13.6877C1.95262 13.8629 1.90135 14.0945 1.87385 14.4976C1.84583 14.9083 1.84538 15.4334 1.84538 16.1892C1.84538 16.6494 1.47228 17.0225 1.01204 17.0225C0.551807 17.0225 0.178711 16.6494 0.178711 16.1892L0.178711 16.1597C0.178705 15.4403 0.1787 14.8583 0.211047 14.3842C0.244344 13.8962 0.314685 13.462 0.485364 13.0499C0.894235 12.0628 1.67848 11.2786 2.66559 10.8697C3.07764 10.699 3.51182 10.6287 3.99984 10.5954C4.47392 10.563 5.05597 10.563 5.77537 10.563ZM14.5916 10.563C15.0518 10.563 15.4249 10.9361 15.4249 11.3964V12.9594H16.988C17.4482 12.9594 17.8213 13.3325 17.8213 13.7928C17.8213 14.253 17.4482 14.6261 16.988 14.6261H15.4249V16.1892C15.4249 16.6494 15.0518 17.0225 14.5916 17.0225C14.1314 17.0225 13.7583 16.6494 13.7583 16.1892V14.6261H12.1952C11.735 14.6261 11.3619 14.253 11.3619 13.7928C11.3619 13.3325 11.735 12.9594 12.1952 12.9594H13.7583V11.3964C13.7583 10.9361 14.1314 10.563 14.5916 10.563Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
const FormLabel = ({
children,
...props
}: BoxProps & {
children: React.ReactNode;
}) => {
return (
<Box color={'myGray.900'} fontSize={'sm'} {...props}>
{children}
</Box>
);
};
export default FormLabel;

View File

@@ -1,16 +1,17 @@
import React, { forwardRef } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Box, BoxProps, SpinnerProps } from '@chakra-ui/react';
import Loading from '../MyLoading';
type Props = BoxProps & {
isLoading?: boolean;
text?: string;
size?: SpinnerProps['size'];
};
const MyBox = ({ text, isLoading, children, ...props }: Props, ref: any) => {
const MyBox = ({ text, isLoading, children, size, ...props }: Props, ref: any) => {
return (
<Box ref={ref} position={isLoading ? 'relative' : 'unset'} {...props}>
{isLoading && <Loading fixed={false} text={text} />}
{isLoading && <Loading fixed={false} text={text} size={size} />}
{children}
</Box>
);

View File

@@ -3,7 +3,14 @@ import { Divider, type DividerProps } from '@chakra-ui/react';
const MyDivider = (props: DividerProps) => {
const { h } = props;
return <Divider my={4} borderBottomWidth={h || '1x'} {...props}></Divider>;
return (
<Divider
my={4}
borderBottomWidth={h || '2px'}
borderColor={props.color || 'myGray.200'}
{...props}
></Divider>
);
};
export default MyDivider;

View File

@@ -56,7 +56,7 @@ const CustomRightDrawer = ({
)}
</>
)}
<Box flex={'1'} fontSize={'lg'}>
<Box flex={'1'} fontSize={'md'}>
{title}
</Box>
<CloseButton position={'relative'} fontSize={'sm'} top={0} right={0} onClick={onClose} />

View File

@@ -61,7 +61,7 @@ const MyRightDrawer = ({
)}
</>
)}
<Box flex={'1'} fontSize={'lg'}>
<Box flex={'1'} fontSize={'md'}>
{title}
</Box>
<DrawerCloseButton position={'relative'} fontSize={'sm'} top={0} right={0} />

View File

@@ -1,21 +1,23 @@
import React from 'react';
import { Spinner, Flex, Box } from '@chakra-ui/react';
import { Spinner, Flex, Box, SpinnerProps } from '@chakra-ui/react';
const Loading = ({
fixed = true,
text = '',
bg = 'rgba(255,255,255,0.5)',
zIndex = 1000
zIndex = 1000,
size = 'lg'
}: {
fixed?: boolean;
text?: string;
bg?: string;
zIndex?: number;
size?: SpinnerProps['size'];
}) => {
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={zIndex}
zIndex={fixed ? zIndex : 10}
bg={bg}
borderRadius={'md'}
top={0}
@@ -31,7 +33,7 @@ const Loading = ({
speed="0.65s"
emptyColor="myGray.100"
color="primary.500"
size="xl"
size={size}
/>
{text && (
<Box mt={2} color="primary.600" fontWeight={'bold'}>

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import {
Menu,
MenuList,
@@ -9,27 +9,35 @@ import {
MenuItemProps
} from '@chakra-ui/react';
import MyIcon from '../Icon';
import MyDivider from '../MyDivider';
import type { IconNameType } from '../Icon/type';
type MenuItemType = 'primary' | 'danger';
export type MenuItemType = 'primary' | 'danger';
export type Props = {
width?: number | string;
offset?: [number, number];
Button: React.ReactNode;
trigger?: 'hover' | 'click';
iconSize?: string;
menuList: {
isActive?: boolean;
label: string | React.ReactNode;
icon?: string;
type?: MenuItemType;
onClick: () => any;
label?: string;
children: {
isActive?: boolean;
type?: MenuItemType;
icon?: IconNameType | string;
label: string | React.ReactNode;
description?: string;
onClick: () => any;
}[];
}[];
};
const MyMenu = ({
width = 'auto',
trigger = 'hover',
offset = [0, 5],
offset,
iconSize = '1rem',
Button,
menuList
}: Props) => {
@@ -41,17 +49,19 @@ const MyMenu = ({
}
},
danger: {
color: 'red.600',
_hover: {
color: 'red.600',
background: 'red.1'
}
}
};
const menuItemStyles: MenuItemProps = {
borderRadius: 'sm',
py: 3,
py: 2,
px: 3,
display: 'flex',
alignItems: 'center'
alignItems: 'center',
fontSize: 'sm'
};
const ref = useRef<HTMLDivElement>(null);
const closeTimer = useRef<any>();
@@ -64,8 +74,14 @@ const MyMenu = ({
}
});
const computeOffset = useMemo<[number, number]>(() => {
if (offset) return offset;
if (typeof width === 'number') return [-width / 2, 5];
return [0, 5];
}, [offset]);
return (
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'} isLazy>
<Menu offset={computeOffset} isOpen={isOpen} autoSelect={false} direction={'ltr'} isLazy>
<Box
ref={ref}
onMouseEnter={() => {
@@ -84,7 +100,8 @@ const MyMenu = ({
>
<Box
position={'relative'}
onClickCapture={() => {
onClickCapture={(e) => {
e.stopPropagation();
if (trigger === 'click') {
setIsOpen(!isOpen);
}
@@ -109,23 +126,41 @@ const MyMenu = ({
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
}
>
{menuList.map((item, i) => (
<MenuItem
key={i}
{...menuItemStyles}
{...typeMapStyle[item.type || 'primary']}
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
item.onClick && item.onClick();
}}
color={item.isActive ? 'primary.700' : 'myGray.600'}
whiteSpace={'pre-wrap'}
>
{!!item.icon && <MyIcon name={item.icon as any} w={'16px'} mr={2} />}
{item.label}
</MenuItem>
))}
{menuList.map((item, i) => {
return (
<Box key={i}>
{item.label && <Box fontSize={'sm'}>{item.label}</Box>}
{i !== 0 && <MyDivider h={'1.5px'} my={1} />}
{item.children.map((child, index) => (
<MenuItem
key={index}
{...menuItemStyles}
onClickCapture={(e) => {
e.stopPropagation();
setIsOpen(false);
child.onClick && child.onClick();
}}
color={child.isActive ? 'primary.700' : 'myGray.600'}
whiteSpace={'pre-wrap'}
_notLast={{ mb: 0.5 }}
{...typeMapStyle[child.type || 'primary']}
>
{!!child.icon && <MyIcon name={child.icon as any} w={iconSize} mr={3} />}
<Box>
<Box color={child.description ? 'myGray.900' : 'inherit'} fontSize={'sm'}>
{child.label}
</Box>
{child.description && (
<Box color={'myGray.500'} fontSize={'mini'}>
{child.description}
</Box>
)}
</Box>
</MenuItem>
))}
</Box>
);
})}
</MenuList>
</Box>
</Menu>

View File

@@ -0,0 +1,92 @@
import React, { useMemo } from 'react';
import { ModalFooter, ModalBody, Input, Button, Box, Textarea } from '@chakra-ui/react';
import MyModal from './index';
import { useTranslation } from 'next-i18next';
import { useRequest2 } from '../../../hooks/useRequest';
import FormLabel from '../MyBox/FormLabel';
import { useForm } from 'react-hook-form';
export type EditFolderFormType = {
id?: string;
name?: string;
intro?: string;
};
type CommitType = {
name: string;
intro?: string;
};
const EditFolderModal = ({
onClose,
onCreate,
onEdit,
id,
name,
intro
}: EditFolderFormType & {
onClose: () => void;
onCreate: (data: CommitType) => any;
onEdit: (data: CommitType & { id: string }) => any;
}) => {
const { t } = useTranslation();
const isEdit = !!id;
const { register, handleSubmit } = useForm<EditFolderFormType>({
defaultValues: {
name,
intro
}
});
const typeMap = useMemo(
() =>
isEdit
? {
title: t('dataset.Edit Folder')
}
: {
title: t('dataset.Create Folder')
},
[isEdit, t]
);
const { run: onSave, loading } = useRequest2(
({ name = '', intro }: EditFolderFormType) => {
if (!name) return;
if (isEdit) return onEdit({ id, name, intro });
return onCreate({ name, intro });
},
{
onSuccess: (res) => {
onClose();
}
}
);
return (
<MyModal isOpen onClose={onClose} iconSrc="common/folderFill" title={typeMap.title}>
<ModalBody>
<Box>
<FormLabel mb={1}>{t('common.Input name')}</FormLabel>
<Input
{...register('name', { required: true })}
bg={'myGray.50'}
autoFocus
maxLength={20}
/>
</Box>
<Box mt={4}>
<FormLabel mb={1}>{t('common.Input folder description')}</FormLabel>
<Textarea {...register('intro')} bg={'myGray.50'} maxLength={200} />
</Box>
</ModalBody>
<ModalFooter>
<Button isLoading={loading} onClick={handleSubmit(onSave)}>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default EditFolderModal;

View File

@@ -64,6 +64,7 @@ const MyModal = ({
borderBottom={'1px solid #F4F6F8'}
roundedTop={'lg'}
py={'10px'}
fontSize={'md'}
>
{iconSrc && (
<>
@@ -77,7 +78,7 @@ const MyModal = ({
{title}
<Box flex={1} />
{onClose && (
<ModalCloseButton position={'relative'} fontSize={'sm'} top={0} right={0} />
<ModalCloseButton position={'relative'} fontSize={'xs'} top={0} right={0} />
)}
</ModalHeader>
)}

View File

@@ -1,7 +1,7 @@
import { Box, Flex, useDisclosure, useOutsideClick } from '@chakra-ui/react';
import React, { useRef } from 'react';
import { useTranslation } from 'next-i18next';
import FillTag from '../Tag/index';
import MyTag from '../Tag/index';
import MyIcon from '../Icon';
export type SelectProps = {
@@ -51,7 +51,7 @@ const MultipleSelect = ({
if (!listItem) return null;
return (
<FillTag colorSchema="blue" p={2} cursor={'default'}>
<MyTag colorSchema="blue" p={2} cursor={'default'}>
{listItem.alias || listItem.label}
<MyIcon
name={'common/closeLight'}
@@ -63,7 +63,7 @@ const MultipleSelect = ({
onSelect(value.filter((i) => i !== item));
}}
/>
</FillTag>
</MyTag>
);
})}
{value.length === 0 && placeholder && (

View File

@@ -16,12 +16,13 @@ import { useLoading } from '../../../hooks/useLoading';
import MyIcon from '../Icon';
export type SelectProps = ButtonProps & {
value?: string;
value?: string | number;
placeholder?: string;
list: {
alias?: string;
label: string | React.ReactNode;
value: string;
description?: string;
value: string | number;
}[];
isLoading?: boolean;
onchange?: (val: any) => void;
@@ -125,18 +126,27 @@ const MySelect = (
{...menuItemStyles}
{...(value === item.value
? {
color: 'primary.500',
bg: 'myWhite.300'
color: 'primary.600',
bg: 'myGray.100'
}
: {})}
: {
color: 'myGray.900'
})}
onClick={() => {
if (onchange && value !== item.value) {
onchange(item.value);
}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
>
{item.label}
<Box>{item.label}</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
))}
</MenuList>

View File

@@ -9,7 +9,7 @@ type Props = IconProps & {
const QuestionTip = ({ label, maxW, ...props }: Props) => {
return (
<MyTooltip label={label} maxW={maxW}>
<QuestionOutlineIcon {...props} />
<QuestionOutlineIcon w={'0.9rem'} {...props} />
</MyTooltip>
);
};

View File

@@ -17,7 +17,7 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
})}
>
<Tooltip
className="tooltip"
className="chakra-tooltip"
bg={'white'}
arrowShadowColor={'rgba(0,0,0,0.05)'}
hasArrow

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { Box, Flex, useTheme, Grid, type GridProps } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyTooltip from '../MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import QuestionTip from '../MyTooltip/QuestionTip';
// @ts-ignore
@@ -95,6 +94,7 @@ const LeftRadio = ({
color={'myGray.900'}
fontWeight={item.desc ? '500' : 'normal'}
whiteSpace={'nowrap'}
fontSize={'sm'}
>
{typeof item.title === 'string' ? t(item.title) : item.title}
</Box>

View File

@@ -23,6 +23,7 @@ const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: P
borderColor={'borderColor.base'}
bg={'myGray.50'}
gap={'4px'}
fontSize={'sm'}
{...props}
>
{list.map((item) => (

View File

@@ -1,69 +1,83 @@
import React, { useMemo } from 'react';
import { Flex, type FlexProps } from '@chakra-ui/react';
interface Props extends FlexProps {
children: React.ReactNode | React.ReactNode[];
colorSchema?: 'blue' | 'green' | 'gray' | 'purple';
type?: 'fill' | 'solid';
}
type ColorSchemaType = 'white' | 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora';
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: Props) => {
export type TagProps = FlexProps & {
children: React.ReactNode | React.ReactNode[];
colorSchema?: ColorSchemaType;
type?: 'fill' | 'borderFill' | 'borderSolid';
};
const colorMap: Record<
ColorSchemaType,
{
borderColor: string;
bg: string;
color: string;
}
> = {
white: {
borderColor: 'myGray.400',
bg: 'white',
color: 'myGray.700'
},
yellow: {
borderColor: 'yellow.200',
bg: 'yellow.50',
color: 'yellow.600'
},
green: {
borderColor: 'green.200',
bg: 'green.50',
color: 'green.600'
},
red: {
borderColor: 'red.200',
bg: 'red.50',
color: 'red.600'
},
gray: {
borderColor: 'myGray.200',
bg: 'myGray.50',
color: 'myGray.700'
},
blue: {
borderColor: 'primary.200',
bg: 'primary.50',
color: 'primary.600'
},
purple: {
borderColor: '#ECF',
bg: '#F6EEFA',
color: '#A558C9'
},
adora: {
borderColor: '#D3CAFF',
bg: '#F0EEFF',
color: '#6F5DD7'
}
};
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: TagProps) => {
const theme = useMemo(() => {
const fillMap = {
blue: {
borderColor: 'primary.200',
bg: 'primary.50',
color: 'primary.700'
},
green: {
borderColor: 'green.200',
bg: 'green.50',
color: 'green.600'
},
purple: {
borderColor: '#ECF',
bg: '#F6EEFA',
color: '#A558C9'
},
gray: {
borderColor: 'myGray.200',
bg: 'myGray.50',
color: 'myGray.700'
}
};
const solidMap = {
blue: {
borderColor: 'primary.200',
color: 'primary.600'
},
green: {
borderColor: 'green.200',
color: 'green.600'
},
purple: {
borderColor: '#ECF',
color: '#9E53C1'
},
gray: {
borderColor: 'myGray.200',
color: 'myGray.700'
}
};
return type === 'fill' ? fillMap[colorSchema] : solidMap[colorSchema];
return colorMap[colorSchema];
}, [colorSchema]);
return (
<Flex
px={2}
px={2.5}
lineHeight={1}
py={1}
borderRadius={'sm'}
fontSize={'sm'}
fontSize={'xs'}
alignItems={'center'}
whiteSpace={'nowrap'}
borderWidth={'1px'}
{...props}
{...theme}
{...props}
borderColor={type !== 'fill' ? theme.borderColor : 'transparent'}
bg={type !== 'borderSolid' ? theme.bg : 'transparent'}
>
{children}
</Flex>

View File

@@ -105,7 +105,7 @@ export default function Editor({
>
<Box
color={'myGray.400'}
fontSize={'11px'}
fontSize={'mini'}
userSelect={'none'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}

View File

@@ -6,7 +6,7 @@
border-radius: var(--chakra-radii-md);
padding: 8px 12px;
background: var(--chakra-colors-gray-50);
font-size: 14px;
font-size: var(--chakra-fontSizes-sm);
overflow-y: auto;
}
@@ -18,7 +18,7 @@
border-radius: var(--chakra-radii-sm);
padding: 6px 8px;
background: #fff;
font-size: 14px;
font-size: var(--chakra-fontSizes-sm);
overflow-y: auto;
}

View File

@@ -142,7 +142,7 @@ const NodeInputSelect = ({
return (
<MyMenu
offset={[0, -1]}
offset={[-0.5, -0.5]}
Button={
<Button
size={'xs'}
@@ -154,7 +154,7 @@ const NodeInputSelect = ({
<Box fontWeight={'medium'}>{renderTypeData.title}</Box>
</Button>
}
menuList={filterMenuList}
menuList={[{ children: filterMenuList }]}
/>
);
};

View File

@@ -72,6 +72,7 @@ export const useConfirm = (props?: {
}) => {
const timer = useRef<any>();
const [countDownAmount, setCountDownAmount] = useState(countDown);
const [requesting, setRequesting] = useState(false);
useEffect(() => {
timer.current = setInterval(() => {
@@ -85,14 +86,15 @@ export const useConfirm = (props?: {
}, []);
return (
<MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '500px']}>
<ModalBody pt={5} whiteSpace={'pre-wrap'}>
<MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '400px']}>
<ModalBody pt={5} whiteSpace={'pre-wrap'} fontSize={'sm'}>
{customContent}
</ModalBody>
{!hideFooter && (
<ModalFooter>
{showCancel && (
<Button
size={'sm'}
variant={'whiteBase'}
onClick={() => {
onClose();
@@ -104,13 +106,18 @@ export const useConfirm = (props?: {
)}
<Button
size={'sm'}
bg={bg ? bg : map.bg}
isDisabled={countDownAmount > 0}
ml={4}
isLoading={isLoading}
onClick={() => {
onClose();
typeof confirmCb.current === 'function' && confirmCb.current();
isLoading={isLoading || requesting}
onClick={async () => {
setRequesting(true);
try {
typeof confirmCb.current === 'function' && (await confirmCb.current());
onClose();
} catch (error) {}
setRequesting(false);
}}
>
{countDownAmount > 0 ? `${countDownAmount}s` : confirmText}

View File

@@ -53,12 +53,13 @@ export const useRequest2 = <TData, TParams extends any[]>(
plugin?: UseRequestFunProps<TData, TParams>[2]
) => {
const { t } = useTranslation();
const { errorToast, successToast, ...rest } = options || {};
const { errorToast = 'Error', successToast, ...rest } = options || {};
const { toast } = useToast();
const res = ahooksUseRequest<TData, TParams>(
server,
{
manual: true,
...rest,
onError: (err, params) => {
rest?.onError?.(err, params);

View File

@@ -115,7 +115,7 @@ export function useScrollPagination<
<MyBox isLoading={isLoading} ref={containerRef} overflow={'overlay'} {...props}>
<Box ref={wrapperRef}>{children}</Box>
{noMore.current && list.length > 0 && (
<Box py={4} textAlign={'center'} color={'myGray.600'} fontSize={'sm'}>
<Box py={4} textAlign={'center'} color={'myGray.600'} fontSize={'xs'}>
{t('common.No more data')}
</Box>
)}

View File

@@ -5,6 +5,9 @@ export const useToast = (props?: UseToastOptions) => {
const toast = uToast({
position: 'top',
duration: 2000,
containerStyle: {
fontSize: 'sm'
},
...props
});

View File

@@ -16,6 +16,7 @@
"@lexical/selection": "^0.14.5",
"@lexical/text": "0.12.6",
"@lexical/utils": "0.12.6",
"react-hook-form": "7.43.1",
"@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^4.24.10",
"ahooks": "^3.7.11",

View File

@@ -4,13 +4,14 @@ import {
switchAnatomy,
selectAnatomy,
numberInputAnatomy,
checkboxAnatomy
checkboxAnatomy,
tableAnatomy,
radioAnatomy
} from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/styled-system';
const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(
modalAnatomy.keys
);
const { definePartsStyle: modalPart, defineMultiStyleConfig: modalMultiStyle } =
createMultiStyleConfigHelpers(modalAnatomy.keys);
const { definePartsStyle: switchPart, defineMultiStyleConfig: switchMultiStyle } =
createMultiStyleConfigHelpers(switchAnatomy.keys);
const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle } =
@@ -19,6 +20,10 @@ const { definePartsStyle: numInputPart, defineMultiStyleConfig: numInputMultiSty
createMultiStyleConfigHelpers(numberInputAnatomy.keys);
const { definePartsStyle: checkBoxPart, defineMultiStyleConfig: checkBoxMultiStyle } =
createMultiStyleConfigHelpers(checkboxAnatomy.keys);
const { definePartsStyle: tablePart, defineMultiStyleConfig: tableMultiStyle } =
createMultiStyleConfigHelpers(tableAnatomy.keys);
const { definePartsStyle: radioParts, defineMultiStyleConfig: radioStyle } =
createMultiStyleConfigHelpers(radioAnatomy.keys);
const shadowLight = '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)';
@@ -32,7 +37,7 @@ const Button = defineStyleConfig({
sizes: {
xs: {
fontSize: 'xs',
px: '8px',
px: '2',
py: '0',
h: '24px',
fontWeight: 'normal',
@@ -49,7 +54,7 @@ const Button = defineStyleConfig({
},
sm: {
fontSize: 'sm',
px: '14px',
px: '3',
py: 0,
fontWeight: 'normal',
h: '30px',
@@ -65,25 +70,25 @@ const Button = defineStyleConfig({
borderRadius: '8px'
},
md: {
fontSize: 'md',
px: '20px',
fontSize: 'sm',
px: '4',
py: 0,
h: '36px',
h: '34px',
fontWeight: 'normal',
borderRadius: '8px'
},
mdSquare: {
fontSize: 'md',
fontSize: 'sm',
px: '0',
py: 0,
h: '36px',
w: '36px',
h: '34px',
w: '34px',
fontWeight: 'normal',
borderRadius: '6px'
},
lg: {
fontSize: 'md',
px: '20px',
px: '4',
py: 0,
h: '40px',
fontWeight: 'normal',
@@ -247,7 +252,7 @@ const Button = defineStyleConfig({
transparentBase: {
color: 'myGray.800',
fontWeight: '500',
bg: 'white',
bg: 'transparent',
transition: 'background 0.1s',
_hover: {
bg: 'myGray.150'
@@ -258,6 +263,22 @@ const Button = defineStyleConfig({
_disabled: {
color: 'myGray.800 !important'
}
},
transparentDanger: {
color: 'myGray.800',
fontWeight: '500',
bg: 'transparent',
transition: 'background 0.1s',
_hover: {
bg: 'myGray.150',
color: 'red.600'
},
_active: {
bg: 'myGray.150'
},
_disabled: {
color: 'myGray.800 !important'
}
}
},
defaultProps: {
@@ -267,9 +288,6 @@ const Button = defineStyleConfig({
});
const Input: ComponentStyleConfig = {
baseStyle: {
fontsize: '1rem'
},
sizes: {
sm: defineStyle({
field: {
@@ -312,13 +330,15 @@ const NumberInput = numInputMultiStyle({
sm: defineStyle({
field: {
h: '32px',
borderRadius: 'md'
borderRadius: 'md',
fontsize: 'sm'
}
}),
md: defineStyle({
field: {
h: '40px',
borderRadius: 'md'
borderRadius: 'md',
fontsize: 'sm'
}
})
},
@@ -359,6 +379,7 @@ const Textarea: ComponentStyleConfig = {
border: '1px solid',
borderRadius: 'md',
borderColor: 'myGray.200',
fontSize: 'sm',
_hover: {
borderColor: ''
},
@@ -406,12 +427,34 @@ const Select = selectMultiStyle({
}
});
const Radio = radioStyle({
baseStyle: radioParts({
control: {
_hover: {
borderColor: 'primary.300',
bg: 'primary.50'
},
_checked: {
borderColor: 'primary.600',
bg: 'primary.50',
boxShadow: shadowLight,
_before: {
bg: 'primary.600'
},
_hover: {
bg: 'primary.50'
}
}
}
})
});
const Checkbox = checkBoxMultiStyle({
baseStyle: checkBoxPart({
label: {
fontFamily: 'mono' // change the font family of the label
},
control: {
borderRadius: 'xs',
bg: 'none',
_checked: {
bg: 'primary.50',
@@ -429,11 +472,11 @@ const Checkbox = checkBoxMultiStyle({
})
});
const Modal = defineMultiStyleConfig({
baseStyle: definePartsStyle({
const Modal = modalMultiStyle({
baseStyle: modalPart({
body: {
py: [3, 5],
px: [5, 7]
py: 4,
px: 7
},
footer: {
pt: 2
@@ -441,20 +484,62 @@ const Modal = defineMultiStyleConfig({
})
});
const Table = tableMultiStyle({
sizes: {
md: defineStyle({
table: {
fontsize: 'sm'
},
thead: {
tr: {
bg: 'myGray.100',
fontSize: 'sm',
th: {
borderBottom: 'none',
overflow: 'hidden',
'&:first-of-type': {
borderLeftRadius: 'md'
},
'&:last-of-type': {
borderRightRadius: 'md'
}
}
}
},
tbody: {
tr: {
td: {
overflow: 'hidden',
'&:first-of-type': {
borderLeftRadius: 'md'
},
'&:last-of-type': {
borderRightRadius: 'md'
}
}
}
}
})
},
defaultProps: {
size: 'md'
}
});
// 全局主题
export const theme = extendTheme({
styles: {
global: {
'html, body': {
fontSize: '14px',
color: 'myGray.900',
fontWeight: 400,
color: 'myGray.600',
fontWeight: 'normal',
height: '100%',
overflow: 'hidden'
},
a: {
color: 'primary.600'
},
'*': {
_focusVisible: {
boxShadow: 'none'
@@ -586,16 +671,17 @@ export const theme = extendTheme({
body: 'PingFang,Noto Sans,-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'
},
fontSizes: {
mini: '0.75rem',
xs: '0.8rem',
sm: '0.93rem',
sm: '0.875rem',
md: '1rem',
lg: '1.15rem',
xl: '1.3rem',
'2xl': '1.45rem',
'3xl': '1.6rem',
'4xl': '1.75rem',
'5xl': '1.9rem',
'6xl': '2.05rem'
lg: '1.25rem',
xl: '1.5rem',
'2xl': '1.75rem',
'3xl': '2rem',
'4xl': '2.25rem',
'5xl': '2.8rem',
'6xl': '3.6rem'
},
borders: {
sm: '1px solid #E8EBF0',
@@ -644,6 +730,8 @@ export const theme = extendTheme({
Select,
NumberInput,
Checkbox,
Modal
Modal,
Table,
Radio
}
});

View File

@@ -16,6 +16,6 @@
"incremental": true,
"baseUrl": ".",
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts", "../../projects/app/src/components/common/Modal/EditResourceModal.tsx"],
"exclude": ["node_modules","./components/common/Icon/constants.ts"]
}

6
pnpm-lock.yaml generated
View File

@@ -307,6 +307,9 @@ importers:
react-dom:
specifier: 18.3.1
version: 18.3.1(react@18.3.1)
react-hook-form:
specifier: 7.43.1
version: 7.43.1(react@18.3.1)
react-i18next:
specifier: 13.5.0
version: 13.5.0(i18next@23.10.0)(react-dom@18.3.1)(react@18.3.1)
@@ -419,6 +422,9 @@ importers:
js-yaml:
specifier: ^4.1.0
version: 4.1.0
json5:
specifier: ^2.2.3
version: 2.2.3
jsonwebtoken:
specifier: ^9.0.2
version: 9.0.2

View File

@@ -32,5 +32,7 @@ SANDBOX_URL=http://localhost:3001
PRO_URL=
# 首页路径
HOME_URL=/
# 日志等级: debug, info, warn, error
LOG_LEVEL=info
# Loki Log Path
# LOKI_LOG_URL=

View File

@@ -1,37 +1,36 @@
// 已使用 json5 进行解析,会自动去掉注释,无需手动去除
{
"feConfigs": {
"lafEnv": "https://laf.dev"
"lafEnv": "https://laf.dev" // laf环境。 https://laf.run (杭州阿里云) ,或者私有化的laf环境。如果使用 Laf openapi 功能,需要最新版的 laf 。
},
"systemEnv": {
"openapiPrefix": "fastgpt",
"vectorMaxProcess": 15,
"qaMaxProcess": 15,
"pgHNSWEfSearch": 100,
"tokenWorkers": 20
"pgHNSWEfSearch": 100 // 向量搜索参数。越大搜索越精确但是速度越慢。设置为100有99%+精度。
},
"llmModels": [
{
"model": "gpt-3.5-turbo",
"name": "gpt-3.5-turbo",
"maxContext": 16000,
"avatar": "/imgs/model/openai.svg",
"maxResponse": 4000,
"quoteMaxToken": 13000,
"maxTemperature": 1.2,
"charsPointsPrice": 0,
"censor": false,
"vision": false,
"datasetProcess": true,
"usedInClassify": true,
"usedInExtractFields": true,
"usedInToolCall": true,
"usedInQueryExtension": true,
"toolChoice": true,
"functionCall": true,
"customCQPrompt": "",
"customExtractPrompt": "",
"defaultSystemChatPrompt": "",
"defaultConfig": {}
"model": "gpt-3.5-turbo", // 模型名(对应OneAPI中渠道的模型名)
"name": "gpt-3.5-turbo", // 模型别名
"avatar": "/imgs/model/openai.svg", // 模型的logo
"maxContext": 16000, // 最大上下文
"maxResponse": 4000, // 最大回复
"quoteMaxToken": 13000, // 最大引用内容
"maxTemperature": 1.2, // 最大温度
"charsPointsPrice": 0, // n积分/1k token商业版
"censor": false, // 是否开启敏感校验(商业版)
"vision": false, // 是否支持图片输入
"datasetProcess": true, // 是否设置为知识库处理模型QA务必保证至少有一个为true否则知识库会报错
"usedInClassify": true, // 是否用于问题分类务必保证至少有一个为true
"usedInExtractFields": true, // 是否用于内容提取务必保证至少有一个为true
"usedInToolCall": true, // 是否用于工具调用务必保证至少有一个为true
"usedInQueryExtension": true, // 是否用于问题优化务必保证至少有一个为true
"toolChoice": true, // 是否支持工具选择分类内容提取工具调用会用到。目前只有gpt支持
"functionCall": false, // 是否支持函数调用(分类,内容提取,工具调用会用到。会优先使用 toolChoice如果为false则使用 functionCall如果仍为 false则使用提示词模式
"customCQPrompt": "", // 自定义文本分类提示词(不支持工具和函数调用的模型
"customExtractPrompt": "", // 自定义内容提取提示词
"defaultSystemChatPrompt": "", // 对话默认携带的系统提示词
"defaultConfig": {} // 请求API时挟带一些默认配置比如 GLM4 的 top_p
},
{
"model": "gpt-4-0125-preview",
@@ -82,40 +81,16 @@
],
"vectorModels": [
{
"model": "text-embedding-3-large",
"name": "Embedding-3-large",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {},
"defaultConfig": {
"dimensions": 1024
}
},
{
"model": "text-embedding-3-small",
"name": "Embedding-3-small",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {}
},
{
"model": "text-embedding-ada-002",
"name": "text-embedding-ada-002",
"avatar": "/imgs/model/openai.svg",
"charsPointsPrice": 0,
"defaultToken": 512,
"maxToken": 3000,
"weight": 100,
"dbConfig": {},
"queryConfig": {}
"model": "text-embedding-ada-002", // 模型名与OneAPI对应
"name": "Embedding-2", // 模型展示名
"avatar": "/imgs/model/openai.svg", // logo
"charsPointsPrice": 0, // n积分/1k token
"defaultToken": 700, // 默认文本分割时候的 token
"maxToken": 3000, // 最大 token
"weight": 100, // 优先训练权重
"defaultConfig": {}, // 自定义额外参数。例如,如果希望使用 embedding3-large 的话,可以传入 dimensions:1024来返回1024维度的向量。目前必须小于1536维度
"dbConfig": {}, // 存储时的额外参数(非对称向量模型时候需要用到)
"queryConfig": {} // 参训时的额外参数
}
],
"reRankModels": [],
@@ -125,36 +100,12 @@
"name": "OpenAI TTS1",
"charsPointsPrice": 0,
"voices": [
{
"label": "Alloy",
"value": "alloy",
"bufferId": "openai-Alloy"
},
{
"label": "Echo",
"value": "echo",
"bufferId": "openai-Echo"
},
{
"label": "Fable",
"value": "fable",
"bufferId": "openai-Fable"
},
{
"label": "Onyx",
"value": "onyx",
"bufferId": "openai-Onyx"
},
{
"label": "Nova",
"value": "nova",
"bufferId": "openai-Nova"
},
{
"label": "Shimmer",
"value": "shimmer",
"bufferId": "openai-Shimmer"
}
{ "label": "Alloy", "value": "alloy", "bufferId": "openai-Alloy" },
{ "label": "Echo", "value": "echo", "bufferId": "openai-Echo" },
{ "label": "Fable", "value": "fable", "bufferId": "openai-Fable" },
{ "label": "Onyx", "value": "onyx", "bufferId": "openai-Onyx" },
{ "label": "Nova", "value": "nova", "bufferId": "openai-Nova" },
{ "label": "Shimmer", "value": "shimmer", "bufferId": "openai-Shimmer" }
]
}
],

View File

@@ -9,13 +9,18 @@
"Chat Logs Tips": "Logs will record online, shared and API (chatId required) conversation records for this app",
"Chat logs": "Chat Logs",
"Confirm Del App Tip": "Confirm to delete this app and all its chat records?",
"Confirm delete folder tip": "Are you sure to delete this folder? All the following applications and corresponding chat records will be deleted, please confirm!",
"Connection is invalid": "Connection is invalid",
"Connection type is different": "Connection type is different",
"Copy Module Config": "Copy Config",
"Create bot": "App",
"Create one ai app": "Create AI app",
"Dataset Quote Template": "Knowledge Base QA Mode",
"Edit app": "Edit app",
"Export Config Successful": "Config copied, please check for important data",
"Export Configs": "Export Configs",
"Feedback Count": "User Feedback",
"Go to chat": "To chat",
"Import Configs": "Import Configs",
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
"Input Field Settings": "Input Field Settings",
@@ -25,6 +30,7 @@
"Logs Time": "Time",
"Logs Title": "Title",
"Mark Count": "Marked Answer Count",
"Move app": "Move app",
"My Apps": "My Apps",
"Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config",

View File

@@ -49,6 +49,7 @@
"Delete Success": "Delete Success",
"Delete Tip": "Delete Tip",
"Delete Warning": "Delete Warning",
"Delete folder": "Delete",
"Detail": "Detail",
"Documents": "Documents",
"Done": "Done",
@@ -64,6 +65,8 @@
"Import failed": "Import failed",
"Import success": "Import success",
"Input": "Input",
"Input folder description": "Folder description",
"Input name": "Folder name",
"Intro": "Intro",
"Invalid Json": "Invalid JSON format, please check.",
"Last Step": "Last Step",
@@ -71,6 +74,7 @@
"Load Failed": "Load Failed",
"Loading": "Loading...",
"More settings": "More settings",
"Move": "Move",
"MultipleRowSelect": {
"No data": "No data available"
},
@@ -84,6 +88,7 @@
"OK": "OK",
"Open": "Open",
"Opened": "Opened",
"Operation": "Operation",
"Other": "Other",
"Output": "Output",
"Params": "Params",
@@ -161,7 +166,9 @@
"folder": {
"Drag Tip": "Drag me",
"Move Success": "Move successful",
"Move to": "Move to",
"No Folder": "No subdirectories, place here",
"Open folder": "Open folder",
"Root Path": "Root directory",
"empty": "This directory has nothing selectable~"
},
@@ -1212,6 +1219,13 @@
"Tools": "Tools"
},
"permission": {
"Collaborator": "",
"Default permission": "Default permission",
"Manage": "Manage",
"Not collaborator": "Not collaborator",
"Owner": "Owner",
"Permission": "Permission",
"Permission config": "Permission config",
"Private": "Private",
"Private Tip": "Only available to oneself",
"Public": "Team",
@@ -1290,6 +1304,9 @@
"Response Quote tips": "Return quote content in the share link, but will not allow users to download the original document"
}
},
"permission": {
"Permission": "Permission"
},
"standard": {
"AI Bonus Points": "AI points",
"Expired Time": "End time",
@@ -1555,9 +1572,6 @@
"Invite Member Success Tip": "Invitation completed\nSuccess: {{success}}\nInvalid usernames: {{inValid}}\nAlready in team: {{inTeam}}",
"Invite Member Tips": "The invitee can access or use other resources within the team",
"Invite Role Admin Alias": "Invite Admin",
"Invite Role Admin Tip": "Admin\nCan create, edit, and use team resources",
"Invite Role Visitor Alias": "Invite Visitor",
"Invite Role Visitor Tip": "Visitor\nCan only use resources, no creation or editing rights",
"Leave Team": "Leave Team",
"Leave Team Failed": "Failed to leave team",
"Manage": "Team Management",

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