Files
FastGPT/packages/web/components/common/MyMenu/Multiple.tsx
Archer c30f069f2f V4.9.11 feature (#4969)
* Feat: Images dataset collection (#4941)

* New pic (#4858)

* 更新数据集相关类型,添加图像文件ID和预览URL支持;优化数据集导入功能,新增图像数据集处理组件;修复部分国际化文本;更新文件上传逻辑以支持新功能。

* 与原先代码的差别

* 新增 V4.9.10 更新说明,支持 PG 设置`systemEnv.hnswMaxScanTuples`参数,优化 LLM stream 调用超时,修复全文检索多知识库排序问题。同时更新数据集索引,移除 datasetId 字段以简化查询。

* 更换成fileId_image逻辑,并增加训练队列匹配的逻辑

* 新增图片集合判断逻辑,优化预览URL生成流程,确保仅在数据集为图片集合时生成预览URL,并添加相关日志输出以便调试。

* Refactor Docker Compose configuration to comment out exposed ports for production environments, update image versions for pgvector, fastgpt, and mcp_server, and enhance Redis service with a health check. Additionally, standardize dataset collection labels in constants and improve internationalization strings across multiple languages.

* Enhance TrainingStates component by adding internationalization support for the imageParse training mode and update defaultCounts to include imageParse mode in trainingDetail API.

* Enhance dataset import context by adding additional steps for image dataset import process and improve internationalization strings for modal buttons in the useEditTitle hook.

* Update DatasetImportContext to conditionally render MyStep component based on data source type, improving the import process for non-image datasets.

* Refactor image dataset handling by improving internationalization strings, enhancing error messages, and streamlining the preview URL generation process.

* 图片上传到新建的 dataset_collection_images 表,逻辑跟随更改

* 修改了除了controller的其他部分问题

* 把图片数据集的逻辑整合到controller里面

* 补充i18n

* 补充i18n

* resolve评论:主要是上传逻辑的更改和组件复用

* 图片名称的图标显示

* 修改编译报错的命名问题

* 删除不需要的collectionid部分

* 多余文件的处理和改动一个删除按钮

* 除了loading和统一的imageId,其他都resolve掉的

* 处理图标报错

* 复用了MyPhotoView并采用全部替换的方式将imageFileId变成imageId

* 去除不必要文件修改

* 报错和字段修改

* 增加上传成功后删除临时文件的逻辑以及回退一些修改

* 删除path字段,将图片保存到gridfs内,并修改增删等操作的代码

* 修正编译错误

---------

Co-authored-by: archer <545436317@qq.com>

* perf: image dataset

* feat: insert image

* perf: image icon

* fix: training state

---------

Co-authored-by: Zhuangzai fa <143257420+ctrlz526@users.noreply.github.com>

* fix: ts (#4948)

* Thirddatasetmd (#4942)

* add thirddataset.md

* fix thirddataset.md

* fix

* delete wrong png

---------

Co-authored-by: dreamer6680 <146868355@qq.com>

* perf: api dataset code

* perf: log

* add secondary.tsx (#4946)

* add secondary.tsx

* fix

---------

Co-authored-by: dreamer6680 <146868355@qq.com>

* perf: multiple menu

* perf: i18n

* feat: parse queue (#4960)

* feat: parse queue

* feat: sync parse queue

* fix thirddataset.md (#4962)

* fix thirddataset-4.png (#4963)

* feat: Dataset template import (#4934)

* 模版导入部分除了文档还没写

* 修复模版导入的 build 错误

* Document production

* compress pictures

* Change some constants to variables

---------

Co-authored-by: Archer <545436317@qq.com>

* perf: template import

* doc

* llm pargraph

* bocha tool

* fix: del collection

---------

Co-authored-by: Zhuangzai fa <143257420+ctrlz526@users.noreply.github.com>
Co-authored-by: dreamer6680 <1468683855@qq.com>
Co-authored-by: dreamer6680 <146868355@qq.com>
2025-06-06 14:48:44 +08:00

332 lines
6.9 KiB
TypeScript

import React, { useMemo, useRef, useState } from 'react';
import {
Box,
Flex,
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';
import MyPopover from '../MyPopover';
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;
menuList?: MenuItemData[];
}>;
};
export type Props = {
label?: string;
width?: number | string;
offset?: [number, number];
Trigger: React.ReactNode;
trigger?: 'hover' | 'click';
size?: MenuSizeType;
placement?: PlacementWithLogical;
hasArrow?: boolean;
onClose?: () => void;
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 MenuItem = ({
item,
size,
onClose
}: {
item: MenuItemData['children'][number];
size: MenuSizeType;
onClose: () => void;
}) => {
return (
<Box
px={3}
py={2}
cursor="pointer"
borderRadius="md"
_hover={{
bg: 'primary.50',
color: 'primary.600'
}}
onClick={(e) => {
if (item.onClick) {
item.onClick();
}
if (!item.menuList) {
onClose();
}
}}
>
<Flex alignItems="center" w="100%">
{!!item.icon && (
<Avatar
src={item.icon as any}
mr={2}
{...sizeMapStyle[size].iconStyle}
color={item.isActive ? 'inherit' : typeMapStyle[item.type || 'primary'].iconColor}
/>
)}
<Box flex="1">
<Box
color={item.description ? 'myGray.900' : 'inherit'}
{...sizeMapStyle[size].labelStyle}
>
{item.label}
</Box>
{item.description && (
<Box color={'myGray.500'} fontSize={'mini'}>
{item.description}
</Box>
)}
</Box>
</Flex>
</Box>
);
};
const MultipleMenu = (props: Props) => {
const {
width = 'auto',
trigger = 'hover',
size = 'sm',
offset,
Trigger,
menuList,
hasArrow = false,
placement = 'bottom-start'
} = props;
const { isPc } = useSystem();
const formatTrigger = !isPc ? 'click' : trigger;
return (
<MyPopover
placement={placement}
offset={offset}
hasArrow={hasArrow}
trigger={formatTrigger}
w={width}
zIndex={999}
closeOnBlur={false}
autoFocus={false}
Trigger={Trigger}
>
{({ onClose }) => {
const onCloseFn = () => {
onClose();
props?.onClose?.();
};
return (
<Box
bg="white"
maxW="300px"
p="6px"
border={'1px solid #fff'}
boxShadow={'3'}
borderRadius={'md'}
>
{menuList.map((group, i) => (
<Box key={i}>
{i !== 0 && <MyDivider h={'1.5px'} {...sizeMapStyle[size].dividerStyle} />}
{group.label && (
<Box fontSize="sm" px={3} py={1} color="myGray.500">
{group.label}
</Box>
)}
{group.children.map((item, index) => {
return (
<Box key={index}>
{item.menuList ? (
<MultipleMenu
{...props}
placement={'left'}
trigger={'hover'}
menuList={item.menuList}
onClose={onCloseFn}
Trigger={
<Box>
<MenuItem item={item} size={size} onClose={onCloseFn} />
</Box>
}
hasArrow
/>
) : (
<MenuItem item={item} size={size} onClose={onCloseFn} />
)}
</Box>
);
})}
</Box>
))}
</Box>
);
}}
</MyPopover>
);
};
export default React.memo(MultipleMenu);