Compare commits
6 Commits
v4.8.3
...
v4.8.4-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05611df056 | ||
|
|
d0085a23e6 | ||
|
|
bc6864c3dc | ||
|
|
b20d075d35 | ||
|
|
19c8a06d51 | ||
|
|
fcb915c988 |
6
.vscode/nextapi.code-snippets
vendored
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 版本。
|
||||
|
||||
如果是由于,无法自动初始化副本集合,可以手动初始化副本集:
|
||||
|
||||
|
||||
36
docSite/content/zh-cn/docs/development/upgrading/484.md
Normal 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。
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
14
packages/global/common/parentFolder/type.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
17
packages/global/common/parentFolder/utils.ts
Normal 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 {};
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
packages/global/core/app/collaborator.d.ts
vendored
Normal 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;
|
||||
};
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
20
packages/global/core/app/type.d.ts
vendored
@@ -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 = {
|
||||
|
||||
@@ -21,7 +21,7 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => ({
|
||||
limit: 1500,
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
usingReRank: false,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchUsingExtensionQuery: false,
|
||||
datasetSearchExtensionBg: ''
|
||||
},
|
||||
selectedTools: [],
|
||||
|
||||
@@ -35,7 +35,10 @@ export const RunAppModule: FlowNodeTemplateType = {
|
||||
required: true
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput
|
||||
{
|
||||
...Input_Template_UserChatInput,
|
||||
toolDescription: '用户问题'
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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[];
|
||||
|
||||
20
packages/global/support/permission/app/constant.ts
Normal 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;
|
||||
15
packages/global/support/permission/app/controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
0
packages/global/support/permission/app/type.d.ts
vendored
Normal file
15
packages/global/support/permission/collaborator.d.ts
vendored
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
71
packages/global/support/permission/controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
21
packages/global/support/permission/type.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
19
packages/global/support/permission/user/constant.ts
Normal 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;
|
||||
15
packages/global/support/permission/user/controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
8
packages/global/support/user/team/type.d.ts
vendored
@@ -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 = {
|
||||
|
||||
4
packages/global/support/user/type.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
15
packages/service/common/file/gridfs/utils.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -43,7 +43,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
let {
|
||||
res,
|
||||
detail,
|
||||
appId,
|
||||
app: { _id: appId },
|
||||
chatId,
|
||||
stream,
|
||||
responseChatItemId,
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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,
|
||||
|
||||
85
packages/service/support/permission/app/auth.ts
Normal 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
|
||||
};
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -198,4 +198,4 @@ export async function authDatasetFile({
|
||||
} catch (error) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
69
packages/service/support/permission/publish/authLink.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
21
packages/service/support/permission/type/auth.d.ts
vendored
Normal 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;
|
||||
};
|
||||
26
packages/service/support/permission/user/auth.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
17
packages/web/components/common/MyBox/FormLabel.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
92
packages/web/components/common/MyModal/EditFolderModal.tsx
Normal 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;
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -105,7 +105,7 @@ export default function Editor({
|
||||
>
|
||||
<Box
|
||||
color={'myGray.400'}
|
||||
fontSize={'11px'}
|
||||
fontSize={'mini'}
|
||||
userSelect={'none'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,9 @@ export const useToast = (props?: UseToastOptions) => {
|
||||
const toast = uToast({
|
||||
position: 'top',
|
||||
duration: 2000,
|
||||
containerStyle: {
|
||||
fontSize: 'sm'
|
||||
},
|
||||
...props
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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=
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||