4.8.5 test fix (#1862)

* app list ui

* feat: photo view

* perf: app dataset filter

* perf: app dataset filter

* fix: chat recently apps

* perf: workflow header phone

* default templates

* default templates

* fix: input guide phone

* fix: i18n

* team chat history

* remove code

* perf: mongo connection

* log level
This commit is contained in:
Archer
2024-06-27 10:09:55 +08:00
committed by GitHub
parent 9d084b633c
commit a3b0ef066b
31 changed files with 8444 additions and 10670 deletions

View File

@@ -1,251 +1,34 @@
import React, { WheelEventHandler, useState, MouseEventHandler, TouchEventHandler, useRef } from 'react';
import {
Box,
Image,
Modal,
ModalCloseButton,
ModalContent,
ModalOverlay,
Skeleton,
useDisclosure
} from '@chakra-ui/react';
import React from 'react';
import { Skeleton } from '@chakra-ui/react';
import MyPhotoView from '@fastgpt/web/components/common/Image/PhotoView';
import { useBoolean } from 'ahooks';
const MdImage = ({ src }: { src?: string }) => {
const imgRef = useRef<HTMLImageElement>(null);
const [isLoading, setIsLoading] = useState(true);
const [succeed, setSucceed] = useState(false);
const { isOpen, onOpen, onClose } = useDisclosure();
const [scale, setScale] = useState(1);
const [positionLeft, setPositionLeft] = useState(0)
const [positionTop, setPositionTop] = useState(0)
const [store] = useState({
scale: 1,
moveable: false,
pageX: 0,
pageY: 0,
pageX2: 0,
pageY2: 0,
originScale: 1
})
// 自定义Modal关闭事件
const customOnClose = (): void => {
setPositionLeft(0)
setPositionTop(0)
setScale(1)
onClose()
}
const handleWheel: WheelEventHandler<HTMLImageElement> = (e) => {
setScale((prevScale) => {
const newScale = prevScale + e.deltaY * 0.5 * -0.01;
if (newScale < 0.5) return 0.5;
if (newScale > 10) return 10;
return newScale;
});
};
const handleTouchStart: TouchEventHandler<HTMLImageElement> = (event) => {
let touches = event.touches;
let events = touches[0];//单指
let events2 = touches[1];//双指
if (touches.length == 1) {// 单指操作
store.pageX = events.pageX
store.pageY = events.pageY
store.moveable = true;
} else {
// 第一个触摸点的坐标
store.pageX = events.pageX;
store.pageY = events.pageY;
store.moveable = true;
if (events2) {
store.pageX2 = events2.pageX;
store.pageY2 = events2.pageY;
}
store.originScale = store.scale || 1;
}
}
const handleTouchMove: TouchEventHandler<HTMLImageElement> = (event) => {
if (!store.moveable) {
return;
}
let touches = event.touches;
let events = touches[0];
let events2 = touches[1];
//最大移动距离
let moveMaxWith = (Number(imgRef.current?.width) * scale - window.innerWidth) / 2;
let moveMaxHeight = (Number(imgRef.current?.height) * scale - window.innerHeight) / 2;
if (touches.length == 1) {
//未放大,不移动图片
if (scale <= 1)
return;
let pageX = store.pageX;
let pageY = store.pageY
let pageX2 = events.pageX;
let pageY2 = events.pageY;
let newLeft = positionLeft + pageX2 / 20 - pageX / 20;
let newTop = positionTop + pageY2 / 20 - pageY / 20;
if (Math.abs(newLeft) > moveMaxWith) {
if (newLeft > 0) {
newLeft = moveMaxWith;
}
else {
newLeft = 0 - moveMaxWith;
}
}
// 放大倍数没超过屏高
if (moveMaxHeight < 0) {
newTop = 0;
}
else if (Math.abs(newTop) > moveMaxHeight) {
if (newTop > 0) {
newTop = moveMaxHeight;
}
else {
newTop = 0 - moveMaxHeight;
}
}
//控制图片移动
setPositionLeft(newLeft)
setPositionTop(newTop)
} else {
// 双指移动
if (events2) {
// 第2个指头坐标在touchmove时候获取
store.pageX2 = events2.pageX;
store.pageY2 = events2.pageY;
// 获取坐标之间的距离
let getDistance = function (start: any, stop: any) {
//用到三角函数
return Math.hypot(stop.x - start.x,
stop.y - start.y);
};
// 双指缩放比例计算
let zoom = getDistance({
x: events.pageX,
y: events.pageY
}, {
x: events2.pageX,
y: events2.pageY
}) / getDistance({
x: store.pageX,
y: store.pageY
}, {
x: store.pageX2,
y: store.pageY2
});
// 应用在元素上的缩放比例
let newScale = store.originScale * zoom;
// 缩放比例限制
if (newScale > 10) {
newScale = 10;
}
if (newScale <= 1) {
newScale = 1;
}
store.scale = newScale;
// 图像应用缩放效果
setScale(newScale);
// 如果是缩小图片,则限制移动的距离
if (Math.abs(positionLeft) > moveMaxWith) {
if (positionLeft > 0) {
setPositionLeft(moveMaxWith);
}
else {
setPositionLeft(0 - moveMaxWith);
}
}
if (moveMaxHeight < 0) {
setPositionTop(0);
}
else if (Math.abs(positionTop) > moveMaxHeight) {
if (positionTop > 0) {
setPositionTop(moveMaxHeight);
}
else {
setPositionTop(0 - moveMaxHeight);
}
}
}
}
}
const handleTouchEnd: TouchEventHandler<HTMLImageElement> = (e) => {
store.moveable = false;
store.pageX2 = 0;
store.pageY2 = 0;
}
const handleTouchCancel: TouchEventHandler<HTMLImageElement> = (e) => {
store.moveable = false;
store.pageX2 = 0;
store.pageY2 = 0;
}
const [isLoaded, { setTrue }] = useBoolean(false);
return (
<>
<Image
<Skeleton isLoaded={isLoaded}>
<MyPhotoView
borderRadius={'md'}
src={src}
alt={''}
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
cursor={succeed ? 'pointer' : 'default'}
loading="lazy"
objectFit={'contain'}
referrerPolicy="no-referrer"
minW={'120px'}
minH={'120px'}
maxH={'500px'}
my={1}
onLoad={() => {
setIsLoading(false);
setSucceed(true);
setTrue();
}}
onError={() => setIsLoading(false)}
onClick={() => {
if (!succeed) return;
onOpen();
onError={() => {
setTrue();
}}
/>
<Modal isOpen={isOpen} onClose={customOnClose} isCentered>
<ModalOverlay />
<ModalContent boxShadow={'none'} maxW={'auto'} w="auto" bg={'transparent'}>
<Image
ref={imgRef}
transform={`scale(${scale})`}
borderRadius={'md'}
src={src}
alt={''}
w={'100%'}
maxH={'80vh'}
referrerPolicy="no-referrer"
fallbackSrc={'/imgs/errImg.png'}
fallbackStrategy={'onError'}
objectFit={'contain'}
position={'relative'}
top={positionTop + 'px'}
left={positionLeft + "px"}
onWheel={handleWheel}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onTouchCancel={handleTouchCancel}
/>
</ModalContent>
<ModalCloseButton bg={'myWhite.500'} zIndex={999999} />
</Modal>
</>
</Skeleton>
);
};

View File

@@ -106,8 +106,9 @@ const InputGuideConfig = ({
iconSrc="core/app/inputGuides"
isOpen={isOpen}
onClose={onClose}
w={'500px'}
>
<ModalBody px={[5, 16]} py={[4, 8]} w={'500px'}>
<ModalBody px={[5, 16]} py={[4, 8]}>
<Flex justifyContent={'space-between'} alignItems={'center'}>
<FormLabel>{t('Is open')}</FormLabel>
<Switch

View File

@@ -11,10 +11,12 @@ const PermissionIconText = ({
defaultPermission,
w = '1rem',
fontSize = 'mini',
iconColor = 'myGray.500',
...props
}: {
permission?: `${PermissionTypeEnum}`;
defaultPermission?: PermissionValueType;
iconColor?: string;
} & StackProps) => {
const { t } = useTranslation();
@@ -31,7 +33,7 @@ const PermissionIconText = ({
return PermissionTypeMap[per] ? (
<HStack spacing={1} fontSize={fontSize} {...props}>
<MyIcon name={PermissionTypeMap[per]?.iconLight as any} w={w} />
<MyIcon name={PermissionTypeMap[per]?.iconLight as any} w={w} color={iconColor} />
<Box lineHeight={1}>{t(PermissionTypeMap[per]?.label)}</Box>
</HStack>
) : null;

View File

@@ -37,7 +37,7 @@ async function handler(req: NextApiRequest) {
fileId,
isQAImport: true
});
console.log(rawText);
// 2. split chunks
const chunks = rawText2Chunks({
rawText,

View File

@@ -96,16 +96,6 @@ const AppCard = () => {
>
{t('core.Chat')}
</Button>
{appDetail.permission.hasWritePer && feConfigs?.show_team_chat && (
<Button
size={['sm', 'md']}
variant={'whitePrimary'}
leftIcon={<DragHandleIcon w={'16px'} />}
onClick={() => setTeamTagsSet(appDetail)}
>
{t('common.Team Tags Set')}
</Button>
)}
{appDetail.permission.hasManagePer && (
<Button
size={['sm', 'md']}
@@ -133,7 +123,16 @@ const AppCard = () => {
icon: 'core/app/type/workflow',
label: appT('Transition to workflow'),
onClick: () => setTransitionCreateNew(true)
}
},
...(appDetail.permission.hasWritePer && feConfigs?.show_team_chat
? [
{
icon: 'core/chat/fileSelect',
label: t('common.Team Tags Set'),
onClick: () => setTeamTagsSet(appDetail)
}
]
: [])
]
},
{

View File

@@ -59,12 +59,18 @@ const Edit = ({
borderRadius={'lg'}
overflowY={['auto', 'unset']}
>
<Box className={styles.EditAppBox} pr={[0, 1]} overflowY={'auto'} minW={'580px'} flex={'1'}>
<Box
className={styles.EditAppBox}
pr={[0, 1]}
overflowY={'auto'}
minW={['auto', '580px']}
flex={'1'}
>
<Box {...cardStyles} boxShadow={'2'}>
<AppCard />
</Box>
<Box mt={4} {...cardStyles} boxShadow={'3.5'} w={'auto'}>
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
<EditForm appForm={appForm} setAppForm={setAppForm} />
</Box>
</Box>

View File

@@ -57,6 +57,7 @@ const BoxStyles: BoxProps = {
};
const LabelStyles: BoxProps = {
w: ['60px', '100px'],
whiteSpace: 'nowrap',
flexShrink: 0,
fontSize: 'xs'
};

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Flex, Button, IconButton } from '@chakra-ui/react';
import { Box, Flex, Button, IconButton, HStack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
@@ -79,9 +79,10 @@ const Header = () => {
pl={[2, 4]}
pr={[2, 6]}
borderBottom={'base'}
alignItems={'center'}
alignItems={['flex-start', 'center']}
userSelect={'none'}
h={'67px'}
h={['auto', '67px']}
flexWrap={'wrap'}
{...(currentTab === TabEnum.appEdit
? {
bg: 'myGray.25'
@@ -115,10 +116,9 @@ const Header = () => {
<Box flex={1} />
{currentTab === TabEnum.appEdit && (
<>
<HStack flexDirection={['column', 'row']} spacing={[2, 3]}>
{!historiesDefaultData && (
<IconButton
mr={[2, 4]}
icon={<MyIcon name={'history'} w={'18px'} />}
aria-label={''}
size={'sm'}
@@ -157,7 +157,6 @@ const Header = () => {
<Box>
<MyTooltip label={t('core.app.Publish app tip')}>
<Button
ml={[2, 4]}
size={'sm'}
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
>
@@ -169,7 +168,7 @@ const Header = () => {
onConfirm={() => onclickPublish()}
/>
)}
</>
</HStack>
)}
</Flex>
{historiesDefaultData && (

View File

@@ -27,7 +27,10 @@ const SelectAppModal = ({
const getAppList = useCallback(
async ({ parentId }: GetResourceFolderListProps) => {
return getMyApps({ parentId }).then((res) =>
return getMyApps({
parentId,
type: [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow]
}).then((res) =>
res
.filter((item) => !filterAppIds.includes(item._id))
.map<GetResourceListItemResponse>((item) => ({

View File

@@ -50,15 +50,18 @@ const CreateModal = ({ onClose, type }: { type: CreateAppType; onClose: () => vo
[AppTypeEnum.simple]: {
icon: 'core/app/simpleBot',
title: appT('type.Create simple bot'),
avatar: '/imgs/app/avatar/simple.svg',
templates: simpleBotTemplates
},
[AppTypeEnum.workflow]: {
icon: 'core/app/type/workflowFill',
avatar: '/imgs/app/avatar/workflow.svg',
title: appT('type.Create workflow bot'),
templates: workflowTemplates
},
[AppTypeEnum.plugin]: {
icon: 'core/app/type/pluginFill',
avatar: '/imgs/app/avatar/plugin.svg',
title: appT('type.Create plugin bot'),
templates: pluginTemplates
}
@@ -67,7 +70,7 @@ const CreateModal = ({ onClose, type }: { type: CreateAppType; onClose: () => vo
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
defaultValues: {
avatar: '',
avatar: typeData.avatar,
name: '',
templateId: typeData.templates[0].id
}
@@ -146,7 +149,7 @@ const CreateModal = ({ onClose, type }: { type: CreateAppType; onClose: () => vo
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
borderRadius={'sm'}
onClick={onOpenSelectFile}
/>
</MyTooltip>

View File

@@ -180,8 +180,8 @@ const ListItem = () => {
})}
>
<HStack>
<Avatar src={app.avatar} borderRadius={'md'} w={'1.5rem'} />
<Box flex={'1 0 0'} fontSize={'1.125rem'}>
<Avatar src={app.avatar} borderRadius={'sm'} w={'1.5rem'} />
<Box flex={'1 0 0'} color={'myGray.900'}>
{app.name}
</Box>
<Box mr={'-1.25rem'}>
@@ -189,7 +189,7 @@ const ListItem = () => {
</Box>
</HStack>
<Box
flex={['1 0 60px', '1 0 80px']}
flex={['1 0 60px', '1 0 72px']}
mt={3}
pr={8}
textAlign={'justify'}
@@ -209,21 +209,26 @@ const ListItem = () => {
<HStack spacing={3.5}>
{owner && (
<HStack spacing={1}>
<Avatar src={owner.avatar} w={'0.875rem'} />
<Avatar src={owner.avatar} w={'0.875rem'} borderRadius={'50%'} />
<Box maxW={'150px'} className="textEllipsis">
{owner.memberName}
</Box>
</HStack>
)}
<PermissionIconText defaultPermission={app.defaultPermission} w={'0.875rem'} />
<PermissionIconText
defaultPermission={app.defaultPermission}
color={'myGray.500'}
iconColor={'myGray.400'}
w={'0.875rem'}
/>
</HStack>
<HStack>
{isPc && (
<HStack spacing={0.5} className="time">
<MyIcon name={'history'} w={'0.85rem'} />
<Box>{formatTimeToChatTime(app.updateTime)}</Box>
<MyIcon name={'history'} w={'0.85rem'} color={'myGray.400'} />
<Box color={'myGray.500'}>{formatTimeToChatTime(app.updateTime)}</Box>
</HStack>
)}
{app.permission.hasManagePer && (
@@ -292,17 +297,21 @@ const ListItem = () => {
}
]
},
{
children: [
{
icon: 'core/chat/chatLight',
label: appT('Go to chat'),
onClick: () => {
router.push(`/chat?appId=${app._id}`);
...([AppTypeEnum.simple, AppTypeEnum.workflow].includes(app.type)
? [
{
children: [
{
icon: 'core/chat/chatLight',
label: appT('Go to chat'),
onClick: () => {
router.push(`/chat?appId=${app._id}`);
}
}
]
}
}
]
},
]
: []),
...(app.permission.isOwner
? [
{

View File

@@ -165,7 +165,7 @@ const ChatHistorySlider = ({
}}
list={[
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
{ label: t('App'), value: TabEnum.app },
...(!isTeamChat ? [{ label: t('App'), value: TabEnum.app }] : []),
{ label: t('core.chat.History'), value: TabEnum.history }
]}
value={currentTab}

View File

@@ -23,7 +23,10 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
const isTeamChat = router.pathname === '/chat/team';
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
return getMyApps({ parentId }).then((res) =>
return getMyApps({
parentId,
type: [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow]
}).then((res) =>
res.map<GetResourceListItemResponse>((item) => ({
id: item._id,
name: item.name,

View File

@@ -73,6 +73,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
avatar: template.avatar,
name: t(template.name),
modules: template.modules,
edges: template.edges,
type: template.type
});
});

View File

@@ -16,17 +16,16 @@ import { systemStartCb } from '@fastgpt/service/common/system/tools';
/**
* connect MongoDB and init data
*/
export function connectToDatabase(): Promise<void> {
export function connectToDatabase() {
return connectMongo({
beforeHook: () => {
initGlobal();
},
afterHook: async () => {
systemStartCb();
// init system config
getInitConfig();
//init vector database, init root user
await Promise.all([initVectorStore(), initRootUser()]);
//init system configinit vector databaseinit root user
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
startMongoWatch();
// cron

View File

@@ -23,7 +23,7 @@ export const simpleBotTemplates: TemplateType = [
{
id: 'simpleChat',
avatar: '/imgs/workflow/AI.png',
name: '简易模板',
name: '简易机器人',
intro: '一个极其简单的 AI 应用,你可以绑定知识库或工具。',
type: AppTypeEnum.simple,
modules: [
@@ -3048,4 +3048,9 @@ export const pluginTemplates: TemplateType = [
}
];
export const defaultAppTemplates = simpleBotTemplates.concat(workflowTemplates[0]);
export const defaultAppTemplates = [
simpleBotTemplates[0],
simpleBotTemplates[1],
workflowTemplates[0],
workflowTemplates[1]
];

View File

@@ -17,6 +17,7 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import { TFunction } from 'next-i18next';
import { ToolModule } from '@fastgpt/global/core/workflow/template/system/tools';
import { useDatasetStore } from '../dataset/store/dataset';
type WorkflowType = {
nodes: StoreNodeItemType[];
@@ -26,6 +27,12 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
chatConfig: AppChatConfigType;
} {
const workflowStartNodeId = 'workflowStartNodeId';
const allDatasets = useDatasetStore.getState().allDatasets;
const selectedDatasets = data.dataset.datasets.filter((item) =>
allDatasets.some((ds) => ds._id === item.datasetId)
);
function systemConfigTemplate(formData: AppSimpleEditFormType): StoreNodeItemType {
return {
nodeId: 'userGuide',
@@ -351,7 +358,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
FlowNodeInputTypeEnum.reference
],
label: 'core.module.input.label.Select dataset',
value: formData.dataset.datasets,
value: selectedDatasets,
valueType: WorkflowIOValueTypeEnum.selectDataset,
list: [],
required: true
@@ -447,7 +454,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
const datasetNodeId = getNanoid(6);
const datasetTool: WorkflowType | null =
formData.dataset.datasets.length > 0
selectedDatasets.length > 0
? {
nodes: [
{
@@ -470,7 +477,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
FlowNodeInputTypeEnum.reference
],
label: 'core.module.input.label.Select dataset',
value: formData.dataset.datasets,
value: selectedDatasets,
valueType: WorkflowIOValueTypeEnum.selectDataset,
list: [],
required: true
@@ -690,7 +697,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType & {
const workflow = (() => {
if (data.selectedTools.length > 0) return toolTemplates(data);
if (data.dataset.datasets.length > 0) return datasetTemplate(data);
if (selectedDatasets.length > 0) return datasetTemplate(data);
return simpleChatTemplate(data);
})();

View File

@@ -12,7 +12,6 @@ type State = {
loadAllDatasets: () => Promise<DatasetSimpleItemType[]>;
myDatasets: DatasetListItemType[];
loadMyDatasets: (parentId?: string) => Promise<any>;
setMyDatasets(val: DatasetListItemType[]): void;
};
export const useDatasetStore = create<State>()(
@@ -34,11 +33,6 @@ export const useDatasetStore = create<State>()(
state.myDatasets = res;
});
return res;
},
setMyDatasets(val) {
set((state) => {
state.myDatasets = val;
});
}
})),
{