mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-16 16:04:34 +00:00

* feat: concat usage code (#5657) * feat: dataset parse queue (#5661) * feat: chat usage concat (#5669) * perf: search test usage * feat: chat usage concat * fix: ts * fix: ts * feat: chat node response store (#5675) * feat: chat node response store * limit export * test * add ai generate node (#5506) * add node copilot * apply code * update dynamic input & output * add code test * usage * dynamic input border render * optimize input & output * optimize code * update style * change card to popover * prompt editor basic * prompt editor * handle key down * update prompt * merge * fix * fix * fix * perf: workflow performance (#5677) * feat: chat node response store * limit export * perf: workflow performance * remove log * fix: app template get duplicate (#5682) * fix: dynamic input lock & code param (#5680) * fix: dynamic input lock & code param * fix * fix * feat: multi node data sync & system tool hot-swapping (#5575) * Enhance file upload functionality and system tool integration (#5257) * Enhance file upload functionality and system tool integration * Add supplementary documents and optimize the upload interface * Refactor file plugin types and update upload configurations * Refactor MinIO configuration variables and clean up API plugin handlers for improved readability and consistency * File name change * Refactor SystemTools component layout * fix i18n * fix * fix * fix * optimize app logs sort (#5310) * log keys config modal * multiple select * api * fontsize * code * chatid * fix build * fix * fix component * change name * log keys config * fix * delete unused * fix * chore: minio service class rewrite * chore: s3 plugin upload * feat: system global cache with multi node sync feature * feat: cache * chore: move images * docs: update & remove useless code * chore: resolve merge conflicts * chore: adjust the code * chore: adjust * deps: upgrade @fastgpt-sdk/plugin to 0.1.17 * perf(s3): s3 config * fix: cache syncKey refresh * fix: update @fastgpt-sdk/plugin to v0.1.18 removing mongo definition for fixing vitest * chore: adjust --------- Co-authored-by: Ctrlz <143257420+ctrlz526@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: Archer <545436317@qq.com> * perf: s3 api code * fix: toolbox empty when second open modal * feat: http tool set (#5599) * feat: http toolSet manual create front end * feat: http toolSet manual create i18n * feat: http toolSet manual create back end * feat: auth, as tool param, adapt mcp * fix: delete unused httpPlugin * fix: delete FlowNodeTypeEnum.httpPlugin * fix: AppTypeEnum include httpToolSet and httpPlugin * fix * delete console * fix * output schema * fix * fix bg * fix base url * fix --------- Co-authored-by: heheer <zhiyu44@qq.com> * feat: app count * perf: type check * feat: catch error * perf: plugin hot-swapping (#5688) * perf: plugin hot-swapping * chore: adjust code * perf: cite data auth * fix http toolset (#5689) * temp * fix http tool set * fix * template author hide * dynamic IO ui * fix: auth test * fix dynamic input & output (#5690) Co-authored-by: Archer <545436317@qq.com> * fix: dynamic output id * doc * feat: model permission (#5666) * feat(permission): model permission definition & api * chore: support update model's collaborators * feat: remove unauthedmodel when paste and import * fix: type error * fix: test setup global model list * fix: http tool api * chore: update fastgpt-sdk version * chore: remove useless code * chore: myModelList cache * perf: user who is not manager can not configure model permission (FE) * perf: model => Set * feat: getMyModels moved to opensource code; cache the myModelList * fix: type error * fix dynamic input reference select type (#5694) * remove unique index * read file usage * perf: connection error * fix: abort token count * fix: debug usage concat * fix: immer clone object * fix: immer clone object * perf: throw error when error chat * update audit i18n * fix: 修复识别pptx文件后,返回内容顺序错乱问题 (#5696) * fix: pptx sort error * fix prompt editor (#5695) * fix prompt editor * fix * fix: redis cache prefix (#5697) * fix: redis cache prefix * fix: cache * fix: get model collaborator by model.model * feat: hint for model per * rename bucket name * model ui * doc * doc --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: Ctrlz <143257420+ctrlz526@users.noreply.github.com> Co-authored-by: Zeng Qingwen <143274079+fishwww-ww@users.noreply.github.com> Co-authored-by: heheer <zhiyu44@qq.com> Co-authored-by: Deepturn <33342819+Deepturn@users.noreply.github.com>
352 lines
8.0 KiB
TypeScript
352 lines
8.0 KiB
TypeScript
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
import {
|
|
Menu,
|
|
MenuList,
|
|
MenuItem,
|
|
Box,
|
|
useOutsideClick,
|
|
MenuButton,
|
|
type MenuItemProps,
|
|
type PlacementWithLogical,
|
|
type AvatarProps,
|
|
type BoxProps,
|
|
type DividerProps
|
|
} from '@chakra-ui/react';
|
|
import MyDivider from '../MyDivider';
|
|
import type { IconNameType } from '../Icon/type';
|
|
import { useSystem } from '../../../hooks/useSystem';
|
|
import Avatar from '../Avatar';
|
|
|
|
export type MenuItemType = 'primary' | 'danger' | 'gray' | 'grayBg';
|
|
export type MenuSizeType = 'sm' | 'md' | 'xs' | 'mini';
|
|
|
|
export type MenuItemData = {
|
|
label?: string;
|
|
children: Array<{
|
|
isActive?: boolean;
|
|
type?: MenuItemType;
|
|
icon?: IconNameType | string;
|
|
label: string | React.ReactNode;
|
|
description?: string;
|
|
onClick?: () => any;
|
|
menuItemStyles?: MenuItemProps;
|
|
}>;
|
|
};
|
|
export type Props = {
|
|
width?: number | string;
|
|
offset?: [number, number];
|
|
Button: React.ReactNode;
|
|
trigger?: 'hover' | 'click';
|
|
size?: MenuSizeType;
|
|
|
|
placement?: PlacementWithLogical;
|
|
menuList: MenuItemData[];
|
|
};
|
|
|
|
const typeMapStyle: Record<MenuItemType, { styles: MenuItemProps; iconColor?: string }> = {
|
|
primary: {
|
|
styles: {
|
|
_hover: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
},
|
|
_focus: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
},
|
|
_active: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
}
|
|
},
|
|
iconColor: 'myGray.600'
|
|
},
|
|
gray: {
|
|
styles: {
|
|
_hover: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
},
|
|
_focus: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
},
|
|
_active: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
}
|
|
},
|
|
iconColor: 'myGray.400'
|
|
},
|
|
grayBg: {
|
|
styles: {
|
|
_hover: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
},
|
|
_focus: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
},
|
|
_active: {
|
|
backgroundColor: 'myGray.05',
|
|
color: 'primary.600'
|
|
}
|
|
},
|
|
iconColor: 'myGray.600'
|
|
},
|
|
danger: {
|
|
styles: {
|
|
color: 'red.600',
|
|
_hover: {
|
|
background: 'red.1'
|
|
},
|
|
_focus: {
|
|
background: 'red.1'
|
|
},
|
|
_active: {
|
|
background: 'red.1'
|
|
}
|
|
},
|
|
iconColor: 'red.600'
|
|
}
|
|
};
|
|
const sizeMapStyle: Record<
|
|
MenuSizeType,
|
|
{
|
|
iconStyle: AvatarProps;
|
|
labelStyle: BoxProps;
|
|
dividerStyle: DividerProps;
|
|
menuItemStyle: MenuItemProps;
|
|
}
|
|
> = {
|
|
mini: {
|
|
iconStyle: {
|
|
w: '14px'
|
|
},
|
|
labelStyle: {
|
|
fontSize: 'mini'
|
|
},
|
|
dividerStyle: {
|
|
my: 0.5
|
|
},
|
|
menuItemStyle: {
|
|
py: 1.5,
|
|
px: 2
|
|
}
|
|
},
|
|
xs: {
|
|
iconStyle: {
|
|
w: '14px'
|
|
},
|
|
labelStyle: {
|
|
fontSize: 'sm'
|
|
},
|
|
dividerStyle: {
|
|
my: 0.5
|
|
},
|
|
menuItemStyle: {
|
|
py: 1.5,
|
|
px: 2
|
|
}
|
|
},
|
|
sm: {
|
|
iconStyle: {
|
|
w: '1rem'
|
|
},
|
|
labelStyle: {
|
|
fontSize: 'sm'
|
|
},
|
|
dividerStyle: {
|
|
my: 1
|
|
},
|
|
menuItemStyle: {
|
|
py: 2,
|
|
px: 3,
|
|
_notLast: {
|
|
mb: 0.5
|
|
}
|
|
}
|
|
},
|
|
md: {
|
|
iconStyle: {
|
|
w: '2rem',
|
|
borderRadius: '6px'
|
|
},
|
|
labelStyle: {
|
|
fontSize: 'sm'
|
|
},
|
|
dividerStyle: {
|
|
my: 1
|
|
},
|
|
menuItemStyle: {
|
|
py: 2,
|
|
px: 3,
|
|
_notLast: {
|
|
mb: 0.5
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const MyMenu = ({
|
|
width = 'auto',
|
|
trigger = 'hover',
|
|
size = 'sm',
|
|
offset,
|
|
Button,
|
|
menuList,
|
|
placement = 'bottom-start'
|
|
}: Props) => {
|
|
const { isPc } = useSystem();
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const closeTimer = useRef<any>();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const formatTrigger = !isPc ? 'click' : trigger;
|
|
|
|
useOutsideClick({
|
|
ref: ref,
|
|
handler: () => {
|
|
setIsOpen(false);
|
|
}
|
|
});
|
|
|
|
const computeOffset = useMemo<[number, number]>(() => {
|
|
if (offset) return offset;
|
|
if (typeof width === 'number') return [-width / 2, 5];
|
|
return [0, 5];
|
|
}, [offset, width]);
|
|
|
|
return (
|
|
<Menu
|
|
offset={computeOffset}
|
|
isOpen={isOpen}
|
|
autoSelect={false}
|
|
direction={'ltr'}
|
|
isLazy
|
|
lazyBehavior={'unmount'}
|
|
placement={placement}
|
|
computePositionOnMount
|
|
>
|
|
<Box
|
|
ref={ref}
|
|
onMouseEnter={() => {
|
|
if (formatTrigger === 'hover') {
|
|
setIsOpen(true);
|
|
}
|
|
clearTimeout(closeTimer.current);
|
|
}}
|
|
onMouseLeave={() => {
|
|
if (formatTrigger === 'hover') {
|
|
closeTimer.current = setTimeout(() => {
|
|
setIsOpen(false);
|
|
}, 100);
|
|
}
|
|
}}
|
|
>
|
|
<Box
|
|
position={'relative'}
|
|
onClickCapture={(e) => {
|
|
e.stopPropagation();
|
|
if (formatTrigger === 'click') {
|
|
setIsOpen(!isOpen);
|
|
}
|
|
}}
|
|
>
|
|
<MenuButton
|
|
w={'100%'}
|
|
h={'100%'}
|
|
position={'absolute'}
|
|
top={0}
|
|
right={0}
|
|
bottom={0}
|
|
left={0}
|
|
/>
|
|
<Box
|
|
position={'relative'}
|
|
color={isOpen ? 'primary.600' : ''}
|
|
w="fit-content"
|
|
h="fit-content"
|
|
borderRadius="sm"
|
|
>
|
|
{Button}
|
|
</Box>
|
|
</Box>
|
|
<MenuList
|
|
minW={isOpen ? `${width}px !important` : '80px'}
|
|
zIndex={100}
|
|
maxW={'300px'}
|
|
p={'6px'}
|
|
border={'1px solid #fff'}
|
|
boxShadow={'3'}
|
|
>
|
|
{menuList.map((item, i) => {
|
|
return (
|
|
<Box key={i}>
|
|
{item.label && <Box fontSize={'sm'}>{item.label}</Box>}
|
|
{i !== 0 && <MyDivider h={'1.5px'} {...sizeMapStyle[size].dividerStyle} />}
|
|
{item.children.map((child, index) => (
|
|
<MenuItem
|
|
key={index}
|
|
borderRadius={'sm'}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (child.onClick) {
|
|
setIsOpen(false);
|
|
child.onClick();
|
|
}
|
|
}}
|
|
alignItems={'center'}
|
|
fontSize={'sm'}
|
|
color={child.isActive ? 'primary.700' : 'myGray.600'}
|
|
whiteSpace={'pre-wrap'}
|
|
{...typeMapStyle[child.type || 'primary'].styles}
|
|
{...sizeMapStyle[size].menuItemStyle}
|
|
{...child.menuItemStyles}
|
|
>
|
|
{!!child.icon && (
|
|
<Avatar
|
|
src={child.icon as any}
|
|
mr={2}
|
|
{...sizeMapStyle[size].iconStyle}
|
|
color={
|
|
child.isActive
|
|
? 'inherit'
|
|
: typeMapStyle[child.type || 'primary'].iconColor
|
|
}
|
|
sx={{
|
|
'[role="menuitem"]:hover &': {
|
|
color: 'inherit'
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
<Box w={'100%'}>
|
|
<Box
|
|
w={'100%'}
|
|
color={child.description ? 'myGray.900' : 'inherit'}
|
|
pr={child.icon ? 4 : 0}
|
|
{...sizeMapStyle[size].labelStyle}
|
|
>
|
|
{child.label}
|
|
</Box>
|
|
{child.description && (
|
|
<Box color={'myGray.500'} fontSize={'mini'} w={'100%'}>
|
|
{child.description}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</MenuItem>
|
|
))}
|
|
</Box>
|
|
);
|
|
})}
|
|
</MenuList>
|
|
</Box>
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
export default React.memo(MyMenu);
|