mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 08:25:07 +00:00

* chore(ui): login page & workflow page (#3046) * login page & number input & multirow select & llm select * workflow * adjust nodes * New file upload (#3058) * feat: toolNode aiNode readFileNode adapt new version * update docker-compose * update tip * feat: adapt new file version * perf: file input * fix: ts * feat: add chat history time label (#3024) * feat:add chat and logs time * feat: add chat history time label * code perf * code perf --------- Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com> * add chatType (#3060) * pref: slow query of full text search (#3044) * Adapt findLast api;perf: markdown zh format. (#3066) * perf: context code * fix: adapt findLast api * perf: commercial plugin run error * perf: markdown zh format * perf: dockerfile proxy (#3067) * fix ui (#3065) * fix ui * fix * feat: support array reference multi-select (#3041) * feat: support array reference multi-select * fix build * fix * fix loop multi-select * adjust condition * fix get value * array and non-array conversion * fix plugin input * merge func * feat: iframe code block;perf: workflow selector type (#3076) * feat: iframe code block * perf: workflow selector type * node pluginoutput check (#3074) * feat: View will move when workflow check error;fix: ui refresh error when continuous file upload (#3077) * fix: plugin output check * fix: ui refresh error when continuous file upload * feat: View will move when workflow check error * add dispatch try catch (#3075) * perf: workflow context split (#3083) * perf: workflow context split * perf: context * 4.8.13 test (#3085) * perf: workflow node ui * chat iframe url * feat: support sub route config (#3071) * feat: support sub route config * dockerfile * fix upload * delete unused code * 4.8.13 test (#3087) * fix: image expired * fix: datacard navbar ui * perf: build action * fix: workflow file upload refresh (#3088) * fix: http tool response (#3097) * loop node dynamic height (#3092) * loop node dynamic height * fix * fix * feat: support push chat log (#3093) * feat: custom uid/metadata * to: custom info * fix: chat push latest * feat: add chat log envs * refactor: move timer to pushChatLog * fix: using precise log --------- Co-authored-by: Finley Ge <m13203533462@163.com> * 4.8.13 test (#3098) * perf: loop node refresh * rename context * comment * fix: ts * perf: push chat log * array reference check & node ui (#3100) * feat: loop start add index (#3101) * feat: loop start add index * update doc * 4.8.13 test (#3102) * fix: loop index;edge parent check * perf: reference invalid check * fix: ts * fix: plugin select files and ai response check (#3104) * fix: plugin select files and ai response check * perf: text editor selector;tool call tip;remove invalid image url; * perf: select file * perf: drop files * feat: source id prefix env (#3103) * 4.8.13 test (#3106) * perf: select file * perf: drop files * perf: env template * 4.8.13 test (#3107) * perf: select file * perf: drop files * fix: imple mode adapt files * perf: push chat log (#3109) * fix: share page load title error (#3111) * 4.8.13 perf (#3112) * fix: share page load title error * update file input doc * perf: auto add file urls * perf: auto ser loop node offset height * 4.8.13 test (#3117) * perf: plugin * updat eaction * feat: add more share config (#3120) * feat: add more share config * add i18n en * fix: missing subroute (#3121) * perf: outlink config (#3128) * update action * perf: outlink config * fix: ts (#3129) * 更新 docSite 文档内容 (#3131) * fix: null pointer (#3130) * fix: null pointer * perf: not input text * update doc url * perf: outlink default value (#3134) * update doc (#3136) * 4.8.13 test (#3137) * update doc * perf: completions chat api * Restore docSite content based on upstream/4.8.13-dev (#3138) * Restore docSite content based on upstream/4.8.13-dev * 4813.md缺少更正 * update doc (#3141) --------- Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: papapatrick <109422393+Patrickill@users.noreply.github.com> Co-authored-by: 勤劳上班的卑微小张 <jiazhan.zhang@ggimage.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com> Co-authored-by: Finley Ge <m13203533462@163.com> Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
224 lines
5.7 KiB
TypeScript
224 lines
5.7 KiB
TypeScript
import React, { useMemo, useRef, useState } from 'react';
|
|
import {
|
|
Menu,
|
|
MenuList,
|
|
MenuItem,
|
|
Box,
|
|
useOutsideClick,
|
|
MenuButton,
|
|
MenuItemProps,
|
|
PlacementWithLogical
|
|
} 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';
|
|
|
|
export type Props = {
|
|
width?: number | string;
|
|
offset?: [number, number];
|
|
Button: React.ReactNode;
|
|
trigger?: 'hover' | 'click';
|
|
iconSize?: string;
|
|
iconRadius?: string;
|
|
|
|
placement?: PlacementWithLogical;
|
|
menuList: {
|
|
label?: string;
|
|
children: {
|
|
isActive?: boolean;
|
|
type?: MenuItemType;
|
|
icon?: IconNameType | string;
|
|
label: string | React.ReactNode;
|
|
description?: string;
|
|
onClick?: () => any;
|
|
menuItemStyles?: MenuItemProps;
|
|
}[];
|
|
}[];
|
|
};
|
|
|
|
const MyMenu = ({
|
|
width = 'auto',
|
|
trigger = 'hover',
|
|
offset,
|
|
iconSize = '1rem',
|
|
Button,
|
|
menuList,
|
|
iconRadius,
|
|
placement = 'bottom-start'
|
|
}: Props) => {
|
|
const typeMapStyle: Record<MenuItemType, MenuItemProps> = {
|
|
primary: {
|
|
_hover: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
},
|
|
_focus: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
},
|
|
_active: {
|
|
backgroundColor: 'primary.50',
|
|
color: 'primary.600'
|
|
}
|
|
},
|
|
danger: {
|
|
color: 'red.600',
|
|
_hover: {
|
|
background: 'red.1'
|
|
},
|
|
_focus: {
|
|
background: 'red.1'
|
|
},
|
|
_active: {
|
|
background: 'red.1'
|
|
}
|
|
}
|
|
};
|
|
|
|
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]);
|
|
|
|
return (
|
|
<Menu
|
|
offset={computeOffset}
|
|
isOpen={isOpen}
|
|
autoSelect={false}
|
|
direction={'ltr'}
|
|
isLazy
|
|
lazyBehavior={'keepMounted'}
|
|
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'} my={1} />}
|
|
{item.children.map((child, index) => (
|
|
<MenuItem
|
|
key={index}
|
|
borderRadius={'sm'}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (child.onClick) {
|
|
setIsOpen(false);
|
|
child.onClick();
|
|
}
|
|
}}
|
|
py={2}
|
|
px={3}
|
|
alignItems={'center'}
|
|
fontSize={'sm'}
|
|
color={child.isActive ? 'primary.700' : 'myGray.600'}
|
|
whiteSpace={'pre-wrap'}
|
|
_notLast={{ mb: 0.5 }}
|
|
{...typeMapStyle[child.type || 'primary']}
|
|
{...child.menuItemStyles}
|
|
>
|
|
{!!child.icon && (
|
|
<Avatar
|
|
src={child.icon as any}
|
|
borderRadius={iconRadius}
|
|
w={iconSize}
|
|
mr={3}
|
|
/>
|
|
)}
|
|
<Box w={'100%'}>
|
|
<Box
|
|
w={'100%'}
|
|
color={child.description ? 'myGray.900' : 'inherit'}
|
|
fontSize={'sm'}
|
|
>
|
|
{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);
|