Feat: App folder and permission (#1726)

* app folder

* feat: app foldere

* fix: run app param error

* perf: select app ux

* perf: folder rerender

* fix: ts

* fix: parentId

* fix: permission

* perf: loading ux

* perf: per select ux

* perf: clb context

* perf: query extension tip

* fix: ts

* perf: app detail per

* perf: default per
This commit is contained in:
Archer
2024-06-11 10:16:24 +08:00
committed by GitHub
parent b20d075d35
commit bc6864c3dc
89 changed files with 2495 additions and 695 deletions

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import type { FlowNodeTemplateType, StoreNodeItemType } from '../workflow/type';
import { AppTypeEnum } from './constants';
import { PermissionTypeEnum } from '../../support/permission/constant';
import { VariableInputEnum } from '../workflow/constants';
@@ -9,14 +8,17 @@ import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/use
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;
@@ -39,6 +41,7 @@ export type AppListItemType = {
name: string;
avatar: string;
intro: string;
type: AppTypeEnum;
defaultPermission: PermissionValueType;
permission: AppPermission;
};

View File

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

View File

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

View File

@@ -105,8 +105,8 @@ export type NodeSourceNodeItemType = {
/* --------------- function type -------------------- */
export type SelectAppItemType = {
id: string;
name: string;
logo: string;
// name: string;
// logo?: string;
};
/* agent */

View File

@@ -17,4 +17,4 @@ export const AppPermissionList: PermissionListType = {
}
};
export const AppDefaultPermission = NullPermission;
export const AppDefaultPermissionVal = NullPermission;

View File

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

View File

@@ -1,9 +1,10 @@
import { Permission } from './controller';
import { PermissionValueType } from './type';
export type CollaboratorItemType = {
teamId: string;
tmbId: string;
permission: PermissionValueType;
permission: Permission;
name: string;
avatar: string;
};

View File

@@ -9,7 +9,7 @@ export const TeamPermissionList = {
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: '可邀请, 删除成员'
description: '可创建资源、邀请、删除成员'
}
};

View File

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

View File

@@ -1,4 +1,4 @@
import { AppTypeMap } from '@fastgpt/global/core/app/constants';
import { AppTypeEnum, AppTypeMap } from '@fastgpt/global/core/app/constants';
import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
@@ -7,7 +7,7 @@ import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { AppDefaultPermission } from '@fastgpt/global/support/permission/app/constant';
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
export const AppCollectionName = 'apps';
@@ -22,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,
@@ -38,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,
@@ -104,13 +109,13 @@ const AppSchema = new Schema({
// the default permission of a app
defaultPermission: {
type: Number,
default: AppDefaultPermission
default: AppDefaultPermissionVal
}
});
try {
AppSchema.index({ updateTime: -1 });
AppSchema.index({ teamId: 1 });
AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
} catch (error) {
console.log(error);

View File

@@ -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;
@@ -32,27 +34,25 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
const {
res,
teamId,
tmbId,
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
const { app: appData } = await authAppByTmbId({
appId: app.id,
teamId,
tmbId,
per: ReadPermissionVal
});
if (!appData) {
return Promise.reject('App not found');
}
if (res && stream) {
responseWrite({
res,
@@ -64,6 +64,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
}
const chatHistories = getHistories(history, histories);
const { files } = chatValue2RuntimePrompt(query);
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
...props,
@@ -71,11 +72,11 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
runtimeNodes: storeNodes2RuntimeNodes(appData.modules, getDefaultEntryNodeIds(appData.modules)),
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
histories: chatHistories,
query,
variables: {
...props.variables,
userChatInput
}
query: runtimePrompt2ChatsValue({
files,
text: userChatInput
}),
variables: props.variables
});
const completeMessages = chatHistories.concat([

View File

@@ -5,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,
@@ -66,7 +67,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
export const checkTeamAppLimit = async (teamId: string) => {
const [{ standardConstants }, appCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoApp.count({ teamId })
MongoApp.count({ teamId, type: { $in: [AppTypeEnum.advanced, AppTypeEnum.simple] } })
]);
if (standardConstants && appCount >= standardConstants.maxAppAmount) {

View File

@@ -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'),

View File

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

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 301 B

View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

@@ -6,7 +6,7 @@ const Loading = ({
text = '',
bg = 'rgba(255,255,255,0.5)',
zIndex = 1000,
size = 'xl'
size = 'lg'
}: {
fixed?: boolean;
text?: string;
@@ -17,7 +17,7 @@ const Loading = ({
return (
<Flex
position={fixed ? 'fixed' : 'absolute'}
zIndex={zIndex}
zIndex={fixed ? zIndex : 10}
bg={bg}
borderRadius={'md'}
top={0}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import {
Menu,
MenuList,
@@ -10,21 +10,24 @@ import {
} 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: {
label?: string;
children: {
isActive?: boolean;
type?: MenuItemType;
icon?: string;
icon?: IconNameType | string;
label: string | React.ReactNode;
description?: string;
onClick: () => any;
}[];
}[];
@@ -33,7 +36,8 @@ export type Props = {
const MyMenu = ({
width = 'auto',
trigger = 'hover',
offset = [0, 5],
offset,
iconSize = '1rem',
Button,
menuList
}: Props) => {
@@ -45,8 +49,8 @@ const MyMenu = ({
}
},
danger: {
color: 'red.600',
_hover: {
color: 'red.600',
background: 'red.1'
}
}
@@ -70,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={() => {
@@ -90,7 +100,8 @@ const MyMenu = ({
>
<Box
position={'relative'}
onClickCapture={() => {
onClickCapture={(e) => {
e.stopPropagation();
if (trigger === 'click') {
setIsOpen(!isOpen);
}
@@ -124,8 +135,7 @@ const MyMenu = ({
<MenuItem
key={index}
{...menuItemStyles}
{...typeMapStyle[child.type || 'primary']}
onClick={(e) => {
onClickCapture={(e) => {
e.stopPropagation();
setIsOpen(false);
child.onClick && child.onClick();
@@ -133,9 +143,19 @@ const MyMenu = ({
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={'16px'} mr={2} />}
<Box>{child.label}</Box>
{!!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>

View File

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

View File

@@ -78,7 +78,7 @@ const MyModal = ({
{title}
<Box flex={1} />
{onClose && (
<ModalCloseButton position={'relative'} fontSize={'sm'} top={0} right={0} />
<ModalCloseButton position={'relative'} fontSize={'xs'} top={0} right={0} />
)}
</ModalHeader>
)}

View File

@@ -1,13 +1,13 @@
import React, { useMemo } from 'react';
import { Flex, type FlexProps } from '@chakra-ui/react';
type ColorSchemaType = 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora';
type ColorSchemaType = 'white' | 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora';
interface Props extends FlexProps {
export type TagProps = FlexProps & {
children: React.ReactNode | React.ReactNode[];
colorSchema?: ColorSchemaType;
type?: 'fill' | 'borderFill' | 'borderSolid';
}
};
const colorMap: Record<
ColorSchemaType,
@@ -17,6 +17,11 @@ const colorMap: Record<
color: string;
}
> = {
white: {
borderColor: 'myGray.400',
bg: 'white',
color: 'myGray.700'
},
yellow: {
borderColor: 'yellow.200',
bg: 'yellow.50',
@@ -54,7 +59,7 @@ const colorMap: Record<
}
};
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: Props) => {
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: TagProps) => {
const theme = useMemo(() => {
return colorMap[colorSchema];
}, [colorSchema]);

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ const Button = defineStyleConfig({
sizes: {
xs: {
fontSize: 'xs',
px: '8px',
px: '2',
py: '0',
h: '24px',
fontWeight: 'normal',
@@ -54,7 +54,7 @@ const Button = defineStyleConfig({
},
sm: {
fontSize: 'sm',
px: '14px',
px: '3',
py: 0,
fontWeight: 'normal',
h: '30px',
@@ -71,9 +71,9 @@ const Button = defineStyleConfig({
},
md: {
fontSize: 'sm',
px: '20px',
px: '4',
py: 0,
h: '36px',
h: '34px',
fontWeight: 'normal',
borderRadius: '8px'
},
@@ -81,14 +81,14 @@ const Button = defineStyleConfig({
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',
@@ -252,7 +252,7 @@ const Button = defineStyleConfig({
transparentBase: {
color: 'myGray.800',
fontWeight: '500',
bg: 'white',
bg: 'transparent',
transition: 'background 0.1s',
_hover: {
bg: 'myGray.150'
@@ -263,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: {
@@ -459,8 +475,8 @@ const Checkbox = checkBoxMultiStyle({
const Modal = modalMultiStyle({
baseStyle: modalPart({
body: {
py: [2, 4],
px: [5, 7]
py: 4,
px: 7
},
footer: {
pt: 2

View File

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