V4.8.14 dev (#3234)
* feat: rewrite chat context (#3176) * feat: add app auto execute (#3115) * feat: add app auto execute * auto exec configtion * chatting animation * change icon * fix * fix * fix link * feat: add chat context to all chatbox * perf: loading ui --------- Co-authored-by: heheer <heheer@sealos.io> * app auto exec (#3179) * add chat records loaded state (#3184) * perf: chat store reset storage (#3186) * perf: chat store reset storage * perf: auto exec code * chore: workflow ui (#3175) * chore: workflow ui * fix * change icon color config * change popover to mymenu * 4.8.14 test (#3189) * update doc * fix: token check * perf: icon button * update doc * feat: share page support configuration Whether to allow the original view (#3194) * update doc * perf: fix index (#3206) * perf: i18n * perf: Add service entry (#3226) * 4.8.14 test (#3228) * fix: ai log * fix: text splitter * fix: reference unselect & user form description & simple to advance (#3229) * fix: reference unselect & user form description & simple to advance * change abort position * perf * perf: code (#3232) * perf: code * update doc * fix: create btn permission (#3233) * update doc * fix: refresh chatbox listener * perf: check invalid reference * perf: check invalid reference * update doc * fix: ui props --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import NextLink from 'next/link';
|
||||
import Badge from '../Badge';
|
||||
@@ -23,14 +23,14 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
const { gitStar, feConfigs } = useSystemStore();
|
||||
const { lastChatAppId, lastChatId } = useChatStore();
|
||||
const { lastChatAppId } = useChatStore();
|
||||
const navbarList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('common:navbar.Chat'),
|
||||
icon: 'core/chat/chatLight',
|
||||
activeIcon: 'core/chat/chatFill',
|
||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
||||
link: `/chat?appId=${lastChatAppId}`,
|
||||
activeLink: ['/chat']
|
||||
},
|
||||
{
|
||||
@@ -55,7 +55,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
activeLink: ['/account']
|
||||
}
|
||||
],
|
||||
[lastChatAppId, lastChatId, t]
|
||||
[lastChatAppId, t]
|
||||
);
|
||||
|
||||
const itemStyles: BoxProps & LinkProps = {
|
||||
@@ -84,6 +84,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
h={'100%'}
|
||||
w={'100%'}
|
||||
userSelect={'none'}
|
||||
pb={2}
|
||||
>
|
||||
{/* logo */}
|
||||
<Box
|
||||
@@ -155,6 +156,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
href={`/account?currentTab=inform`}
|
||||
mb={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<Badge count={unread}>
|
||||
<MyIcon name={'support/user/informLight'} width={'22px'} height={'22px'} />
|
||||
@@ -171,6 +173,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
target="_blank"
|
||||
mb={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} width={'24px'} height={'24px'} />
|
||||
</Link>
|
||||
@@ -186,6 +189,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Badge from '../Badge';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -9,14 +9,14 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { lastChatAppId, lastChatId } = useChatStore();
|
||||
const { lastChatAppId } = useChatStore();
|
||||
const navbarList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('common:navbar.Chat'),
|
||||
icon: 'core/chat/chatLight',
|
||||
activeIcon: 'core/chat/chatFill',
|
||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
||||
link: `/chat?appId=${lastChatAppId}`,
|
||||
activeLink: ['/chat'],
|
||||
unread: 0
|
||||
},
|
||||
@@ -45,7 +45,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||
unread
|
||||
}
|
||||
],
|
||||
[t, lastChatAppId, lastChatId, unread]
|
||||
[t, lastChatAppId, unread]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { langMap } from '@/web/common/utils/i18n';
|
||||
import { Avatar, Box, Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -14,7 +14,7 @@ const I18nLngSelector = () => {
|
||||
return Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
|
||||
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'1rem'} />
|
||||
<Box>{lang.label}</Box>
|
||||
</Flex>
|
||||
),
|
||||
|
||||
87
projects/app/src/components/core/app/AutoExecConfig.tsx
Normal file
87
projects/app/src/components/core/app/AutoExecConfig.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Box, Button, Flex, ModalBody, Switch, Textarea, useDisclosure } from '@chakra-ui/react';
|
||||
import { defaultAutoExecuteConfig } from '@fastgpt/global/core/app/constants';
|
||||
import { AppAutoExecuteConfigType } from '@fastgpt/global/core/app/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ChatFunctionTip from './Tip';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
const AutoExecConfig = ({
|
||||
value = defaultAutoExecuteConfig,
|
||||
onChange
|
||||
}: {
|
||||
value?: AppAutoExecuteConfigType;
|
||||
onChange: (e: AppAutoExecuteConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const isOpenAutoExec = value.open;
|
||||
const defaultPrompt = value.defaultPrompt;
|
||||
|
||||
const formLabel = isOpenAutoExec
|
||||
? t('common:core.app.whisper.Open')
|
||||
: t('common:core.app.whisper.Close');
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/autoExec'} mr={2} w={'20px'} />
|
||||
<FormLabel color={'myGray.600'}>{t('app:auto_execute')}</FormLabel>
|
||||
<ChatFunctionTip type={'autoExec'} />
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={t('common:core.app.Config_auto_execute')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{formLabel}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={t('common:core.app.Auto execute')}
|
||||
iconSrc="core/app/simpleMode/autoExec"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'}>{t('app:open_auto_execute')}</FormLabel>
|
||||
<Switch
|
||||
isChecked={isOpenAutoExec}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
open: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{isOpenAutoExec && (
|
||||
<Box mt={4}>
|
||||
<FormLabel mb={1}>{t('common:core.app.schedule.Default prompt')}</FormLabel>
|
||||
<Textarea
|
||||
value={defaultPrompt}
|
||||
rows={8}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('app:auto_execute_default_prompt_placeholder')}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
defaultPrompt: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoExecConfig;
|
||||
@@ -139,7 +139,7 @@ const InputGuideConfig = ({
|
||||
onOpenLexiconConfig();
|
||||
}}
|
||||
>
|
||||
{chatT('config_input_guide_lexicon')}
|
||||
{t('chat:config_input_guide_lexicon')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<>
|
||||
@@ -152,7 +152,7 @@ const InputGuideConfig = ({
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<MyIcon name={'book'} w={'17px'} ml={4} mr={1} color={'myGray.600'} />
|
||||
{commonT('common.Documents')}
|
||||
{t('common:common.Documents')}
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} />
|
||||
</Flex>
|
||||
|
||||
@@ -23,7 +23,7 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
const MultipleRowSelect = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
|
||||
);
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
// options type:
|
||||
enum CronJobTypeEnum {
|
||||
month = 'month',
|
||||
@@ -233,24 +233,24 @@ const ScheduledTriggerConfig = ({
|
||||
}
|
||||
|
||||
if (cronField[0] === 'month') {
|
||||
return t('core.app.schedule.Every month', {
|
||||
return t('common:core.app.schedule.Every month', {
|
||||
day: cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'week') {
|
||||
return t('core.app.schedule.Every week', {
|
||||
return t('common:core.app.schedule.Every week', {
|
||||
day: cronField[1] === 0 ? t('app:day') : cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'day') {
|
||||
return t('core.app.schedule.Every day', {
|
||||
return t('common:core.app.schedule.Every day', {
|
||||
hour: cronField[1]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'interval') {
|
||||
return t('core.app.schedule.Interval', {
|
||||
return t('common:core.app.schedule.Interval', {
|
||||
interval: cronField[1]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ enum FnTypeEnum {
|
||||
welcome = 'welcome',
|
||||
file = 'file',
|
||||
visionModel = 'visionModel',
|
||||
instruction = 'instruction'
|
||||
instruction = 'instruction',
|
||||
autoExec = 'autoExec'
|
||||
}
|
||||
|
||||
const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
@@ -66,6 +67,12 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
title: t('workflow:plugin.Instructions'),
|
||||
desc: t('workflow:plugin.Instruction_Tip'),
|
||||
imgUrl: '/imgs/app/instruction.svg'
|
||||
},
|
||||
[FnTypeEnum.autoExec]: {
|
||||
icon: '/imgs/app/autoExec-icon.svg',
|
||||
title: t('common:core.app.Auto execute'),
|
||||
desc: t('app:auto_execute_tip'),
|
||||
imgUrl: '/imgs/app/autoExec.svg'
|
||||
}
|
||||
});
|
||||
const data = map.current[type];
|
||||
|
||||
@@ -31,6 +31,7 @@ import ChatFunctionTip from './Tip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import InputTypeConfig from '@/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
@@ -190,92 +191,59 @@ const VariableEdit = ({
|
||||
</Flex>
|
||||
{/* Form render */}
|
||||
{formatVariables.length > 0 && (
|
||||
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead h={8}>
|
||||
<Tr>
|
||||
<Th
|
||||
borderRadius={'none !important'}
|
||||
fontSize={'mini'}
|
||||
bg={'myGray.50'}
|
||||
p={0}
|
||||
px={4}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{t('workflow:Variable_name')}
|
||||
</Th>
|
||||
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
|
||||
{t('common:common.Require Input')}
|
||||
</Th>
|
||||
<Th
|
||||
fontSize={'mini'}
|
||||
borderRadius={'none !important'}
|
||||
bg={'myGray.50'}
|
||||
p={0}
|
||||
px={4}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{t('common:common.Operation')}
|
||||
</Th>
|
||||
<TableContainer mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:common.Require Input')}</Th>
|
||||
<Th>{t('common:common.Operation')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatVariables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td fontWeight={'medium'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{item.required ? (
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => {
|
||||
const formattedItem = {
|
||||
...item,
|
||||
list: item.enums || []
|
||||
};
|
||||
reset(formattedItem);
|
||||
}}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() =>
|
||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatVariables.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
h={8}
|
||||
color={'myGray.900'}
|
||||
fontSize={'mini'}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td p={0} px={4} h={8} color={'myGray.900'} fontSize={'mini'}>
|
||||
<Flex alignItems={'center'}>
|
||||
{item.required ? (
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td p={0} px={4} h={8} color={'myGray.600'} fontSize={'mini'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const formattedItem = {
|
||||
...item,
|
||||
list: item.enums || []
|
||||
};
|
||||
reset(formattedItem);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* Edit modal */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Switch } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -24,12 +24,9 @@ const WhisperConfig = ({
|
||||
const isOpenWhisper = value.open;
|
||||
const isAutoSend = value.autoSend;
|
||||
|
||||
const formLabel = useMemo(() => {
|
||||
if (!isOpenWhisper) {
|
||||
return t('common:core.app.whisper.Close');
|
||||
}
|
||||
return t('common:core.app.whisper.Open');
|
||||
}, [t, isOpenWhisper]);
|
||||
const formLabel = isOpenWhisper
|
||||
? t('common:core.app.whisper.Open')
|
||||
: t('common:core.app.whisper.Close');
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
|
||||
@@ -33,32 +33,29 @@ const ChatInput = ({
|
||||
onStop,
|
||||
TextareaDom,
|
||||
resetInputVal,
|
||||
chatForm,
|
||||
appId
|
||||
chatForm
|
||||
}: {
|
||||
onSendMessage: SendPromptFnType;
|
||||
onStop: () => void;
|
||||
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
||||
resetInputVal: (val: ChatBoxInputType) => void;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
appId: string;
|
||||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { setValue, watch, control } = chatForm;
|
||||
const inputValue = watch('input');
|
||||
|
||||
const {
|
||||
chatId,
|
||||
isChatting,
|
||||
whisperConfig,
|
||||
autoTTSResponse,
|
||||
chatInputGuide,
|
||||
outLinkAuthData,
|
||||
fileSelectConfig
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const whisperConfig = useContextSelector(ChatBoxContext, (v) => v.whisperConfig);
|
||||
const autoTTSResponse = useContextSelector(ChatBoxContext, (v) => v.autoTTSResponse);
|
||||
const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide);
|
||||
const fileSelectConfig = useContextSelector(ChatBoxContext, (v) => v.fileSelectConfig);
|
||||
|
||||
const fileCtrl = useFieldArray({
|
||||
control,
|
||||
@@ -78,8 +75,6 @@ const ChatInput = ({
|
||||
replaceFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig,
|
||||
fileCtrl
|
||||
});
|
||||
|
||||
@@ -2,76 +2,73 @@ import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import {
|
||||
AppChatConfigType,
|
||||
AppAutoExecuteConfigType,
|
||||
AppFileSelectConfigType,
|
||||
AppTTSConfigType,
|
||||
AppWhisperConfigType,
|
||||
ChatInputGuideConfigType,
|
||||
VariableItemType
|
||||
} from '@fastgpt/global/core/app/type';
|
||||
import { ChatHistoryItemResType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import {
|
||||
defaultAppSelectFileConfig,
|
||||
defaultAutoExecuteConfig,
|
||||
defaultChatInputGuideConfig,
|
||||
defaultTTSConfig,
|
||||
defaultWhisperConfig
|
||||
} from '@fastgpt/global/core/app/constants';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { getChatResData } from '@/web/core/chat/api';
|
||||
import { ChatBoxInputFormType } from './type';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
appAvatar?: string;
|
||||
export type ChatProviderProps = {
|
||||
appId: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
chatId: string;
|
||||
outLinkAuthData?: OutLinkChatAuthProps;
|
||||
|
||||
chatHistories: ChatSiteItemType[];
|
||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
chatType: 'log' | 'chat' | 'share' | 'team';
|
||||
showRawSource: boolean;
|
||||
showNodeStatus: boolean;
|
||||
};
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
welcomeText: string;
|
||||
variableList: VariableItemType[];
|
||||
allVariableList: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
whisperConfig: AppWhisperConfigType;
|
||||
autoTTSResponse: boolean;
|
||||
startSegmentedAudio: () => Promise<any>;
|
||||
splitText2Audio: (text: string, done?: boolean | undefined) => void;
|
||||
finishSegmentedAudio: () => void;
|
||||
audioLoading: boolean;
|
||||
audioPlaying: boolean;
|
||||
hasAudio: boolean;
|
||||
playAudioByText: ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}) => Promise<{
|
||||
buffer?: Uint8Array | undefined;
|
||||
}>;
|
||||
cancelAudio: () => void;
|
||||
audioPlayingChatId: string | undefined;
|
||||
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
isChatting: boolean;
|
||||
chatInputGuide: ChatInputGuideConfigType;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
};
|
||||
type useChatStoreType = ChatProviderProps & {
|
||||
welcomeText: string;
|
||||
variableList: VariableItemType[];
|
||||
allVariableList: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
whisperConfig: AppWhisperConfigType;
|
||||
autoTTSResponse: boolean;
|
||||
autoExecute: AppAutoExecuteConfigType;
|
||||
startSegmentedAudio: () => Promise<any>;
|
||||
splitText2Audio: (text: string, done?: boolean | undefined) => void;
|
||||
finishSegmentedAudio: () => void;
|
||||
audioLoading: boolean;
|
||||
audioPlaying: boolean;
|
||||
hasAudio: boolean;
|
||||
playAudioByText: ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}) => Promise<{
|
||||
buffer?: Uint8Array | undefined;
|
||||
}>;
|
||||
cancelAudio: () => void;
|
||||
audioPlayingChatId: string | undefined;
|
||||
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
isChatting: boolean;
|
||||
chatInputGuide: ChatInputGuideConfigType;
|
||||
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
|
||||
appId: string;
|
||||
chatId: string;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
};
|
||||
|
||||
export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
welcomeText: '',
|
||||
@@ -95,10 +92,6 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
splitText2Audio: function (text: string, done?: boolean | undefined): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
chatHistories: [],
|
||||
setChatHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isChatting: false,
|
||||
audioLoading: false,
|
||||
audioPlaying: false,
|
||||
@@ -132,23 +125,24 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
});
|
||||
|
||||
const Provider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
appId,
|
||||
chatId,
|
||||
outLinkAuthData = {},
|
||||
chatType = 'chat',
|
||||
showRawSource,
|
||||
showNodeStatus,
|
||||
chatConfig = {},
|
||||
children,
|
||||
...props
|
||||
}: ChatProviderProps & {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const chatConfig = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.chatBoxData?.app?.chatConfig || {}
|
||||
);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
|
||||
const {
|
||||
welcomeText = '',
|
||||
variables = [],
|
||||
@@ -156,19 +150,10 @@ const Provider = ({
|
||||
ttsConfig = defaultTTSConfig,
|
||||
whisperConfig = defaultWhisperConfig,
|
||||
chatInputGuide = defaultChatInputGuideConfig,
|
||||
fileSelectConfig = defaultAppSelectFileConfig
|
||||
fileSelectConfig = defaultAppSelectFileConfig,
|
||||
autoExecute = defaultAutoExecuteConfig
|
||||
} = useMemo(() => chatConfig, [chatConfig]);
|
||||
|
||||
const outLinkAuthData = useMemo(
|
||||
() => ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
[shareId, outLinkUid, teamId, teamToken]
|
||||
);
|
||||
|
||||
// segment audio
|
||||
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
|
||||
const {
|
||||
@@ -190,37 +175,34 @@ const Provider = ({
|
||||
|
||||
const isChatting = useMemo(
|
||||
() =>
|
||||
chatHistories[chatHistories.length - 1] &&
|
||||
chatHistories[chatHistories.length - 1]?.status !== 'finish',
|
||||
[chatHistories]
|
||||
chatRecords[chatRecords.length - 1] &&
|
||||
chatRecords[chatRecords.length - 1]?.status !== 'finish',
|
||||
[chatRecords]
|
||||
);
|
||||
const getHistoryResponseData = useCallback(
|
||||
async ({ dataId }: { dataId: string }) => {
|
||||
const aimItem = chatHistories.find((item) => item.dataId === dataId)!;
|
||||
if (!!aimItem?.responseData || !props.chatId) {
|
||||
const aimItem = chatRecords.find((item) => item.dataId === dataId)!;
|
||||
if (!!aimItem?.responseData || !chatId) {
|
||||
return aimItem.responseData || [];
|
||||
} else {
|
||||
let resData = await getChatResData({
|
||||
appId: props.appId,
|
||||
chatId: props.chatId,
|
||||
appId: appId,
|
||||
chatId: chatId,
|
||||
dataId,
|
||||
...outLinkAuthData
|
||||
});
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item) => (item.dataId === dataId ? { ...item, responseData: resData } : item))
|
||||
);
|
||||
return resData;
|
||||
}
|
||||
},
|
||||
[chatHistories, outLinkAuthData, props.appId, props.chatId, setChatHistories]
|
||||
[chatRecords, chatId, appId, outLinkAuthData, setChatRecords]
|
||||
);
|
||||
const value: useChatStoreType = {
|
||||
...props,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
welcomeText,
|
||||
autoExecute,
|
||||
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
||||
allVariableList: variables,
|
||||
questionGuide,
|
||||
@@ -238,12 +220,11 @@ const Provider = ({
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
isChatting,
|
||||
chatInputGuide,
|
||||
appId,
|
||||
chatId,
|
||||
outLinkAuthData,
|
||||
variablesForm,
|
||||
getHistoryResponseData,
|
||||
chatType,
|
||||
showRawSource,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
export type ChatControllerProps = {
|
||||
isLastChild: boolean;
|
||||
@@ -24,6 +25,19 @@ export type ChatControllerProps = {
|
||||
onAddUserDislike?: () => void;
|
||||
};
|
||||
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
bg: 'white',
|
||||
borderRight: 'base'
|
||||
};
|
||||
const controlContainerStyle = {
|
||||
className: 'control',
|
||||
color: 'myGray.400',
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
const ChatController = ({
|
||||
chat,
|
||||
showVoiceIcon,
|
||||
@@ -35,34 +49,21 @@ const ChatController = ({
|
||||
onAddUserDislike,
|
||||
onAddUserLike
|
||||
}: ChatControllerProps & FlexProps) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
isChatting,
|
||||
setChatHistories,
|
||||
audioLoading,
|
||||
audioPlaying,
|
||||
hasAudio,
|
||||
playAudioByText,
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
bg: 'white',
|
||||
borderRight: theme.borders.base
|
||||
};
|
||||
const controlContainerStyle = {
|
||||
className: 'control',
|
||||
color: 'myGray.400',
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const audioLoading = useContextSelector(ChatBoxContext, (v) => v.audioLoading);
|
||||
const audioPlaying = useContextSelector(ChatBoxContext, (v) => v.audioPlaying);
|
||||
const hasAudio = useContextSelector(ChatBoxContext, (v) => v.hasAudio);
|
||||
const playAudioByText = useContextSelector(ChatBoxContext, (v) => v.playAudioByText);
|
||||
const cancelAudio = useContextSelector(ChatBoxContext, (v) => v.cancelAudio);
|
||||
const audioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.audioPlayingChatId);
|
||||
const setAudioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.setAudioPlayingChatId);
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
|
||||
const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]);
|
||||
|
||||
return (
|
||||
@@ -70,7 +71,7 @@ const ChatController = ({
|
||||
{...controlContainerStyle}
|
||||
borderRadius={'sm'}
|
||||
overflow={'hidden'}
|
||||
border={theme.borders.base}
|
||||
border={'base'}
|
||||
// 最后一个子元素,没有border
|
||||
css={css({
|
||||
'& > *:last-child, & > *:last-child svg': {
|
||||
@@ -87,7 +88,7 @@ const ChatController = ({
|
||||
onClick={() => copyData(chatText)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
{!!onDelete && !isChatting && (
|
||||
{!!onDelete && !isChatting && chatType !== 'log' && (
|
||||
<>
|
||||
{onRetry && (
|
||||
<MyTooltip label={t('common:core.chat.retry')}>
|
||||
@@ -125,12 +126,7 @@ const ChatController = ({
|
||||
onClick={cancelAudio}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyImage
|
||||
src="/icon/speaking.gif"
|
||||
w={'23px'}
|
||||
alt={''}
|
||||
borderRight={theme.borders.base}
|
||||
/>
|
||||
<MyImage src="/icon/speaking.gif" w={'23px'} alt={''} borderRight={'base'} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -154,8 +150,8 @@ const ChatController = ({
|
||||
text: chatText
|
||||
});
|
||||
|
||||
if (!setChatHistories || !response.buffer) return;
|
||||
setChatHistories((state) =>
|
||||
if (!setChatRecords || !response.buffer) return;
|
||||
setChatRecords((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === chat.dataId
|
||||
? {
|
||||
|
||||
@@ -216,7 +216,7 @@ const ChatItem = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
{/* control icon */}
|
||||
<Flex w={'100%'} alignItems={'flex-end'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||
<Flex order={styleMap.order} ml={styleMap.ml} align={'center'} gap={'0.62rem'}>
|
||||
{chat.time && (isPc || isChatLog) && (
|
||||
|
||||
@@ -19,10 +19,12 @@ const ContextModal = ({ onClose, dataId }: { onClose: () => void; dataId: string
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
res
|
||||
?.map((item) => {
|
||||
if (item.pluginDetail || item.toolDetail) {
|
||||
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
|
||||
}
|
||||
return item;
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
return flatResData.find(isLLMNode)?.historyPreview || [];
|
||||
|
||||
@@ -7,18 +7,22 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
const QuoteModal = ({
|
||||
rawSearch = [],
|
||||
onClose,
|
||||
canEditDataset,
|
||||
showRawSource,
|
||||
chatItemId,
|
||||
metadata
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
onClose: () => void;
|
||||
canEditDataset: boolean;
|
||||
showRawSource: boolean;
|
||||
chatItemId: string;
|
||||
metadata?: {
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
@@ -37,6 +41,13 @@ const QuoteModal = ({
|
||||
[metadata, rawSearch]
|
||||
);
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
chatItemId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
@@ -49,7 +60,7 @@ const QuoteModal = ({
|
||||
title={
|
||||
<Box>
|
||||
{metadata ? (
|
||||
<RawSourceBox {...metadata} canView={showRawSource} />
|
||||
<RawSourceBox {...metadata} {...RawSourceBoxProps} canView={showRawSource} />
|
||||
) : (
|
||||
<>{t('common:core.chat.Quote Amount', { amount: rawSearch.length })}</>
|
||||
)}
|
||||
@@ -64,6 +75,7 @@ const QuoteModal = ({
|
||||
rawSearch={filterResults}
|
||||
canEditDataset={canEditDataset}
|
||||
canViewSource={showRawSource}
|
||||
chatItemId={chatItemId}
|
||||
/>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
@@ -74,16 +86,25 @@ const QuoteModal = ({
|
||||
export default QuoteModal;
|
||||
|
||||
export const QuoteList = React.memo(function QuoteList({
|
||||
chatItemId,
|
||||
rawSearch = [],
|
||||
canEditDataset,
|
||||
canViewSource
|
||||
}: {
|
||||
chatItemId?: string;
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
canEditDataset: boolean;
|
||||
canViewSource: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
chatItemId,
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{rawSearch.map((item, i) => (
|
||||
@@ -101,6 +122,7 @@ export const QuoteList = React.memo(function QuoteList({
|
||||
quoteItem={item}
|
||||
canViewSource={canViewSource}
|
||||
canEditDataset={canEditDataset}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -250,6 +250,7 @@ const ResponseTags = ({
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal
|
||||
{...quoteModalData}
|
||||
chatItemId={historyItem.dataId}
|
||||
canEditDataset={notSharePage}
|
||||
showRawSource={showRawSource}
|
||||
onClose={() => setQuoteModalData(undefined)}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const TimeBox = ({ time }: { time: Date }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
|
||||
{t(formatTimeToChatItemTime(time) as any, {
|
||||
time: dayjs(time).format('HH#mm')
|
||||
}).replace('#', ':')}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeBox;
|
||||
@@ -1,17 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Button, Card, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
@@ -21,10 +11,10 @@ import { ChatBoxInputFormType } from '../type.d';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
export const VariableInputItem = ({
|
||||
item,
|
||||
@@ -47,13 +37,7 @@ export const VariableInputItem = ({
|
||||
>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
left={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
<Box position={'absolute'} top={'-2px'} left={'-8px'} color={'red.500'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
@@ -134,8 +118,11 @@ const VariableInput = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const { reset, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
|
||||
const { setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
return variableList.reduce((acc: Record<string, any>, item) => {
|
||||
@@ -144,9 +131,12 @@ const VariableInput = ({
|
||||
}, {});
|
||||
}, [variableList]);
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
reset(defaultValues);
|
||||
}, [defaultValues]);
|
||||
useEffect(() => {
|
||||
const values = variablesForm.getValues('variables');
|
||||
// If form is not empty, do not reset the variables
|
||||
if (Object.values(values).filter(Boolean).length > 0) return;
|
||||
setValue('variables', defaultValues);
|
||||
}, [defaultValues, setValue, variablesForm]);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { MessageCardStyle } from '../constants';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const WelcomeBox = ({ welcomeText }: { welcomeText: string }) => {
|
||||
const appAvatar = useContextSelector(ChatBoxContext, (v) => v.appAvatar);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -14,21 +14,24 @@ import { ChatBoxInputFormType, UserInputFileItemType } from '../type';
|
||||
import { AppFileSelectConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { documentFileType } from '@fastgpt/global/common/file/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
type UseFileUploadOptions = {
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
chatId: string;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
fileCtrl: UseFieldArrayReturn<ChatBoxInputFormType, 'files', 'id'>;
|
||||
};
|
||||
|
||||
export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { outLinkAuthData, chatId, fileSelectConfig, fileCtrl } = props;
|
||||
const { fileSelectConfig, fileCtrl } = props;
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
|
||||
const {
|
||||
update: updateFiles,
|
||||
remove: removeFiles,
|
||||
@@ -137,7 +140,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
[maxSelectFiles, appendFiles, toast, t, maxSize]
|
||||
);
|
||||
|
||||
const uploadFiles = async () => {
|
||||
const uploadFiles = useCallback(async () => {
|
||||
const filterFiles = fileList.filter((item) => item.status === 0);
|
||||
|
||||
if (filterFiles.length === 0) return;
|
||||
@@ -158,7 +161,10 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { previewUrl } = await uploadFile2DB({
|
||||
file: copyFile.rawFile,
|
||||
bucketName: 'chat',
|
||||
outLinkAuthData,
|
||||
data: {
|
||||
appId,
|
||||
...outLinkAuthData
|
||||
},
|
||||
metadata: {
|
||||
chatId
|
||||
},
|
||||
@@ -186,7 +192,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
);
|
||||
|
||||
removeFiles(errorFileIndex);
|
||||
};
|
||||
}, [appId, chatId, fileList, outLinkAuthData, removeFiles, replaceFiles, t, toast, updateFiles]);
|
||||
|
||||
const sortFileList = useMemo(() => {
|
||||
// Sort: Document, image
|
||||
|
||||
@@ -3,9 +3,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
ForwardedRef,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import Script from 'next/script';
|
||||
@@ -16,7 +14,7 @@ import type {
|
||||
} from '@fastgpt/global/core/chat/type.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { Box, Flex, Checkbox, BoxProps } from '@chakra-ui/react';
|
||||
import { Box, Checkbox } from '@chakra-ui/react';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -25,6 +23,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
closeCustomFeedback,
|
||||
delChatRecordById,
|
||||
updateChatAdminFeedback,
|
||||
updateChatUserFeedback
|
||||
} from '@/web/core/chat/api';
|
||||
@@ -33,12 +32,7 @@ import type { AdminMarkType } from './components/SelectMarkCollection';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||
import type {
|
||||
ComponentRef,
|
||||
ChatBoxInputType,
|
||||
ChatBoxInputFormType,
|
||||
SendPromptFnType
|
||||
} from './type.d';
|
||||
import type { ChatBoxInputType, ChatBoxInputFormType, SendPromptFnType } from './type.d';
|
||||
import type { StartChatFnProps, generatingMessageProps } from '../type';
|
||||
import ChatInput from './Input/ChatInput';
|
||||
import ChatBoxDivider from '../../Divider';
|
||||
@@ -67,9 +61,11 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import TimeBox from './components/TimeBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||
@@ -87,75 +83,45 @@ enum FeedbackTypeEnum {
|
||||
|
||||
type Props = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
isReady?: boolean;
|
||||
feedbackType?: `${FeedbackTypeEnum}`;
|
||||
showMarkIcon?: boolean; // admin mark dataset
|
||||
showVoiceIcon?: boolean;
|
||||
showEmptyIntro?: boolean;
|
||||
userAvatar?: string;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
ScrollData: ({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ScrollContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
} & BoxProps) => React.JSX.Element;
|
||||
// not chat test params
|
||||
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||
StreamResponseType & {
|
||||
isNewChat?: boolean;
|
||||
}
|
||||
>;
|
||||
onDelMessage?: (e: { contentId: string }) => void;
|
||||
};
|
||||
|
||||
const ChatTimeBox = ({ time }: { time: Date }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
|
||||
{t(formatTimeToChatItemTime(time) as any, {
|
||||
time: dayjs(time).format('HH#mm')
|
||||
}).replace('#', ':')}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatBox = (
|
||||
{
|
||||
feedbackType = FeedbackTypeEnum.hidden,
|
||||
showMarkIcon = false,
|
||||
showVoiceIcon = true,
|
||||
showEmptyIntro = false,
|
||||
appAvatar,
|
||||
userAvatar,
|
||||
active = true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
onStartChat,
|
||||
onDelMessage,
|
||||
ScrollData
|
||||
}: Props,
|
||||
ref: ForwardedRef<ComponentRef>
|
||||
) => {
|
||||
const ChatBoxRef = useRef<HTMLDivElement>(null);
|
||||
const ChatBox = ({
|
||||
isReady = true,
|
||||
feedbackType = FeedbackTypeEnum.hidden,
|
||||
showMarkIcon = false,
|
||||
showVoiceIcon = true,
|
||||
showEmptyIntro = false,
|
||||
active = true,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
onStartChat
|
||||
}: Props) => {
|
||||
const ScrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { setLoading, feConfigs } = useSystemStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
||||
const chatController = useRef(new AbortController());
|
||||
const questionGuideController = useRef(new AbortController());
|
||||
const pluginController = useRef(new AbortController());
|
||||
const isNewChatReplace = useRef(false);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [feedbackId, setFeedbackId] = useState<string>();
|
||||
const [readFeedbackData, setReadFeedbackData] = useState<{
|
||||
dataId: string;
|
||||
@@ -164,26 +130,35 @@ const ChatBox = (
|
||||
const [adminMarkData, setAdminMarkData] = useState<AdminMarkType & { dataId: string }>();
|
||||
const [questionGuides, setQuestionGuide] = useState<string[]>([]);
|
||||
|
||||
const {
|
||||
welcomeText,
|
||||
variableList,
|
||||
allVariableList,
|
||||
questionGuide,
|
||||
startSegmentedAudio,
|
||||
finishSegmentedAudio,
|
||||
setAudioPlayingChatId,
|
||||
splitText2Audio,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
isChatting
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
const userAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.userAvatar);
|
||||
const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
const isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded);
|
||||
const setIsChatRecordsLoaded = useContextSelector(
|
||||
ChatRecordContext,
|
||||
(v) => v.setIsChatRecordsLoaded
|
||||
);
|
||||
const ScrollData = useContextSelector(ChatRecordContext, (v) => v.ScrollData);
|
||||
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const welcomeText = useContextSelector(ChatBoxContext, (v) => v.welcomeText);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
const allVariableList = useContextSelector(ChatBoxContext, (v) => v.allVariableList);
|
||||
const questionGuide = useContextSelector(ChatBoxContext, (v) => v.questionGuide);
|
||||
const autoExecute = useContextSelector(ChatBoxContext, (v) => v.autoExecute);
|
||||
const startSegmentedAudio = useContextSelector(ChatBoxContext, (v) => v.startSegmentedAudio);
|
||||
const finishSegmentedAudio = useContextSelector(ChatBoxContext, (v) => v.finishSegmentedAudio);
|
||||
const setAudioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.setAudioPlayingChatId);
|
||||
const splitText2Audio = useContextSelector(ChatBoxContext, (v) => v.splitText2Audio);
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
|
||||
// Workflow running, there are user input or selection
|
||||
const isInteractive = useMemo(
|
||||
() => checkIsInteractiveByHistories(chatHistories),
|
||||
[chatHistories]
|
||||
);
|
||||
const isInteractive = useMemo(() => checkIsInteractiveByHistories(chatRecords), [chatRecords]);
|
||||
|
||||
// compute variable input is finish.
|
||||
const chatForm = useForm<ChatBoxInputFormType>({
|
||||
@@ -195,18 +170,18 @@ const ChatBox = (
|
||||
});
|
||||
const { setValue, watch } = chatForm;
|
||||
const chatStartedWatch = watch('chatStarted');
|
||||
const chatStarted = chatStartedWatch || chatHistories.length > 0 || variableList.length === 0;
|
||||
const chatStarted = chatStartedWatch || chatRecords.length > 0 || variableList.length === 0;
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = useMemoizedFn((behavior: 'smooth' | 'auto' = 'smooth', delay = 0) => {
|
||||
setTimeout(() => {
|
||||
if (!ChatBoxRef.current) {
|
||||
if (!ScrollContainerRef.current) {
|
||||
setTimeout(() => {
|
||||
scrollToBottom(behavior);
|
||||
}, 500);
|
||||
} else {
|
||||
ChatBoxRef.current.scrollTo({
|
||||
top: ChatBoxRef.current.scrollHeight,
|
||||
ScrollContainerRef.current.scrollTo({
|
||||
top: ScrollContainerRef.current.scrollHeight,
|
||||
behavior
|
||||
});
|
||||
}
|
||||
@@ -216,10 +191,10 @@ const ChatBox = (
|
||||
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
|
||||
const { run: generatingScroll } = useThrottleFn(
|
||||
(force?: boolean) => {
|
||||
if (!ChatBoxRef.current) return;
|
||||
if (!ScrollContainerRef.current) return;
|
||||
const isBottom =
|
||||
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
|
||||
ChatBoxRef.current.scrollHeight;
|
||||
ScrollContainerRef.current.scrollTop + ScrollContainerRef.current.clientHeight + 150 >=
|
||||
ScrollContainerRef.current.scrollHeight;
|
||||
|
||||
if (isBottom || force) {
|
||||
scrollToBottom('auto');
|
||||
@@ -241,7 +216,7 @@ const ChatBox = (
|
||||
autoTTSResponse,
|
||||
variables
|
||||
}: generatingMessageProps & { autoTTSResponse?: boolean }) => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
if (item.obj !== ChatRoleEnum.AI) return item;
|
||||
@@ -370,11 +345,9 @@ const ChatBox = (
|
||||
|
||||
const result = await postQuestionGuide(
|
||||
{
|
||||
appId,
|
||||
messages: chats2GPTMessages({ messages: histories, reserveId: false }).slice(-6),
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
...outLinkAuthData
|
||||
},
|
||||
abortSignal
|
||||
);
|
||||
@@ -386,14 +359,14 @@ const ChatBox = (
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
[questionGuide, shareId, outLinkUid, teamId, teamToken, scrollToBottom]
|
||||
[questionGuide, appId, outLinkAuthData, scrollToBottom]
|
||||
);
|
||||
|
||||
/* Abort chat completions, questionGuide */
|
||||
const abortRequest = useMemoizedFn(() => {
|
||||
chatController.current?.abort('stop');
|
||||
questionGuideController.current?.abort('stop');
|
||||
pluginController.current?.abort('stop');
|
||||
const abortRequest = useMemoizedFn((signal: string = 'stop') => {
|
||||
chatController.current?.abort(signal);
|
||||
questionGuideController.current?.abort(signal);
|
||||
pluginController.current?.abort(signal);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -403,9 +376,10 @@ const ChatBox = (
|
||||
({
|
||||
text = '',
|
||||
files = [],
|
||||
history = chatHistories,
|
||||
history = chatRecords,
|
||||
autoTTSResponse = false,
|
||||
isInteractivePrompt = false
|
||||
isInteractivePrompt = false,
|
||||
hideInUI = false
|
||||
}) => {
|
||||
variablesForm.handleSubmit(
|
||||
async ({ variables = {} }) => {
|
||||
@@ -452,6 +426,7 @@ const ChatBox = (
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
time: new Date(),
|
||||
hideInUI,
|
||||
value: [
|
||||
...files.map((file) => ({
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
@@ -491,7 +466,7 @@ const ChatBox = (
|
||||
];
|
||||
|
||||
// Update histories(Interactive input does not require new session rounds)
|
||||
setChatHistories(
|
||||
setChatRecords(
|
||||
isInteractivePrompt
|
||||
? // 把交互的结果存储到对话记录中,交互模式下,不需要新的会话轮次
|
||||
setUserSelectResultToHistories(newChatList.slice(0, -2), text)
|
||||
@@ -533,11 +508,9 @@ const ChatBox = (
|
||||
});
|
||||
}
|
||||
|
||||
isNewChatReplace.current = isNewChat;
|
||||
|
||||
// Set last chat finish status
|
||||
let newChatHistories: ChatSiteItemType[] = [];
|
||||
setChatHistories((state) => {
|
||||
setChatRecords((state) => {
|
||||
newChatHistories = state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -577,11 +550,11 @@ const ChatBox = (
|
||||
if (!err?.responseText) {
|
||||
resetInputVal({ text, files });
|
||||
// 这里的 newChatList 没包含用户交互输入的内容,所以重置后刚好是正确的。
|
||||
setChatHistories(newChatList.slice(0, newChatList.length - 2));
|
||||
setChatRecords(newChatList.slice(0, newChatList.length - 2));
|
||||
}
|
||||
|
||||
// set finish status
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -603,26 +576,37 @@ const ChatBox = (
|
||||
);
|
||||
|
||||
// retry input
|
||||
const onDelMessage = useCallback(
|
||||
(contentId: string) => {
|
||||
return delChatRecordById({
|
||||
appId,
|
||||
chatId,
|
||||
contentId,
|
||||
...outLinkAuthData
|
||||
});
|
||||
},
|
||||
[appId, chatId, outLinkAuthData]
|
||||
);
|
||||
const retryInput = useMemoizedFn((dataId?: string) => {
|
||||
if (!dataId || !onDelMessage) return;
|
||||
|
||||
return async () => {
|
||||
setLoading(true);
|
||||
const index = chatHistories.findIndex((item) => item.dataId === dataId);
|
||||
const delHistory = chatHistories.slice(index);
|
||||
setIsLoading(true);
|
||||
const index = chatRecords.findIndex((item) => item.dataId === dataId);
|
||||
const delHistory = chatRecords.slice(index);
|
||||
try {
|
||||
await Promise.all(
|
||||
delHistory.map((item) => {
|
||||
if (item.dataId) {
|
||||
return onDelMessage({ contentId: item.dataId });
|
||||
return onDelMessage(item.dataId);
|
||||
}
|
||||
})
|
||||
);
|
||||
setChatHistories((state) => (index === 0 ? [] : state.slice(0, index)));
|
||||
setChatRecords((state) => (index === 0 ? [] : state.slice(0, index)));
|
||||
|
||||
sendPrompt({
|
||||
...formatChatValue2InputType(delHistory[0].value),
|
||||
history: chatHistories.slice(0, index)
|
||||
history: chatRecords.slice(0, index)
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
@@ -630,27 +614,22 @@ const ChatBox = (
|
||||
title: getErrText(error, 'Retry failed')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
setIsLoading(false);
|
||||
};
|
||||
});
|
||||
// delete one message(One human and the ai response)
|
||||
const delOneMessage = useMemoizedFn((dataId?: string) => {
|
||||
if (!dataId || !onDelMessage) return;
|
||||
const delOneMessage = useMemoizedFn((dataId: string) => {
|
||||
return () => {
|
||||
setChatHistories((state) => {
|
||||
setChatRecords((state) => {
|
||||
let aiIndex = -1;
|
||||
|
||||
return state.filter((chat, i) => {
|
||||
if (chat.dataId === dataId) {
|
||||
aiIndex = i + 1;
|
||||
onDelMessage({
|
||||
contentId: dataId
|
||||
});
|
||||
onDelMessage(dataId);
|
||||
return false;
|
||||
} else if (aiIndex === i && chat.obj === ChatRoleEnum.AI && chat.dataId) {
|
||||
onDelMessage({
|
||||
contentId: chat.dataId
|
||||
});
|
||||
onDelMessage(chat.dataId);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -694,7 +673,7 @@ const ChatBox = (
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
|
||||
const isGoodFeedback = !!chat.userGoodFeedback;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId
|
||||
? {
|
||||
@@ -722,7 +701,7 @@ const ChatBox = (
|
||||
if (feedbackType !== FeedbackTypeEnum.admin) return;
|
||||
return () => {
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId ? { ...chatItem, userGoodFeedback: undefined } : chatItem
|
||||
)
|
||||
@@ -748,7 +727,7 @@ const ChatBox = (
|
||||
if (chat.userBadFeedback) {
|
||||
return () => {
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId ? { ...chatItem, userBadFeedback: undefined } : chatItem
|
||||
)
|
||||
@@ -789,7 +768,7 @@ const ChatBox = (
|
||||
index: i
|
||||
});
|
||||
// update dom
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.obj === ChatRoleEnum.AI && chatItem.dataId === chat.dataId
|
||||
? {
|
||||
@@ -807,11 +786,11 @@ const ChatBox = (
|
||||
() =>
|
||||
feConfigs?.show_emptyChat &&
|
||||
showEmptyIntro &&
|
||||
chatHistories.length === 0 &&
|
||||
chatRecords.length === 0 &&
|
||||
!variableList?.length &&
|
||||
!welcomeText,
|
||||
[
|
||||
chatHistories.length,
|
||||
chatRecords.length,
|
||||
feConfigs?.show_emptyChat,
|
||||
showEmptyIntro,
|
||||
variableList?.length,
|
||||
@@ -820,26 +799,21 @@ const ChatBox = (
|
||||
);
|
||||
const statusBoxData = useCreation(() => {
|
||||
if (!isChatting) return;
|
||||
const chatContent = chatHistories[chatHistories.length - 1];
|
||||
const chatContent = chatRecords[chatRecords.length - 1];
|
||||
if (!chatContent) return;
|
||||
|
||||
return {
|
||||
status: chatContent.status || ChatStatusEnum.loading,
|
||||
name: t(chatContent.moduleName || ('' as any)) || t('common:common.Loading')
|
||||
};
|
||||
}, [chatHistories, isChatting, t]);
|
||||
}, [chatRecords, isChatting, t]);
|
||||
|
||||
// page change and abort request
|
||||
useEffect(() => {
|
||||
isNewChatReplace.current = false;
|
||||
setQuestionGuide([]);
|
||||
return () => {
|
||||
chatController.current?.abort('leave');
|
||||
if (!isNewChatReplace.current) {
|
||||
questionGuideController.current?.abort('leave');
|
||||
}
|
||||
};
|
||||
}, [router.query]);
|
||||
setValue('chatStarted', false);
|
||||
abortRequest('leave');
|
||||
}, [router.query, setValue, chatId]);
|
||||
|
||||
// add listener
|
||||
useEffect(() => {
|
||||
@@ -866,14 +840,31 @@ const ChatBox = (
|
||||
eventBus.off(EventNameEnum.sendQuestion);
|
||||
eventBus.off(EventNameEnum.editQuestion);
|
||||
};
|
||||
}, [resetInputVal, sendPrompt]);
|
||||
}, [isReady, resetInputVal, sendPrompt]);
|
||||
|
||||
// Auto send prompt
|
||||
useEffect(() => {
|
||||
if (
|
||||
isReady &&
|
||||
autoExecute.open &&
|
||||
chatStarted &&
|
||||
chatRecords.length === 0 &&
|
||||
isChatRecordsLoaded
|
||||
) {
|
||||
sendPrompt({
|
||||
text: autoExecute.defaultPrompt || 'AUTO_EXECUTE',
|
||||
hideInUI: true
|
||||
});
|
||||
}
|
||||
}, [isReady, chatStarted, autoExecute?.open, chatRecords, isChatRecordsLoaded]);
|
||||
|
||||
// output data
|
||||
useImperativeHandle(ref, () => ({
|
||||
useImperativeHandle(ChatBoxRef, () => ({
|
||||
restartChat() {
|
||||
abortRequest();
|
||||
|
||||
setChatHistories([]);
|
||||
setChatRecords([]);
|
||||
setIsChatRecordsLoaded(false);
|
||||
setValue('chatStarted', false);
|
||||
},
|
||||
scrollToBottom(behavior = 'auto') {
|
||||
@@ -884,7 +875,7 @@ const ChatBox = (
|
||||
const RenderRecords = useMemo(() => {
|
||||
return (
|
||||
<ScrollData
|
||||
ScrollContainerRef={ChatBoxRef}
|
||||
ScrollContainerRef={ScrollContainerRef}
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
w={'100%'}
|
||||
@@ -901,98 +892,94 @@ const ChatBox = (
|
||||
)}
|
||||
{/* chat history */}
|
||||
<Box id={'history'}>
|
||||
{chatHistories.map((item, index) => (
|
||||
<>
|
||||
{chatRecords.map((item, index) => (
|
||||
<Box key={item.dataId}>
|
||||
{/* 并且时间和上一条的time相差超过十分钟 */}
|
||||
{index !== 0 &&
|
||||
item.time &&
|
||||
chatHistories[index - 1].time !== undefined &&
|
||||
new Date(item.time).getTime() -
|
||||
new Date(chatHistories[index - 1].time!).getTime() >
|
||||
10 * 60 * 1000 && <ChatTimeBox time={item.time} />}
|
||||
chatRecords[index - 1].time !== undefined &&
|
||||
new Date(item.time).getTime() - new Date(chatRecords[index - 1].time!).getTime() >
|
||||
10 * 60 * 1000 && <TimeBox time={item.time} />}
|
||||
|
||||
<Box key={item.dataId} py={6}>
|
||||
{item.obj === ChatRoleEnum.Human && (
|
||||
<Box py={item.hideInUI ? 0 : 6}>
|
||||
{item.obj === ChatRoleEnum.Human && !item.hideInUI && (
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={userAvatar}
|
||||
chat={item}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
isLastChild={index === chatRecords.length - 1}
|
||||
/>
|
||||
)}
|
||||
{item.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatHistories[index - 1]?.value)?.text
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
}}
|
||||
>
|
||||
<ResponseTags
|
||||
showTags={index !== chatHistories.length - 1 || !isChatting}
|
||||
historyItem={item}
|
||||
/>
|
||||
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatRecords.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatRecords[index - 1]?.value)?.text
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
}}
|
||||
>
|
||||
<ResponseTags
|
||||
showTags={index !== chatRecords.length - 1 || !isChatting}
|
||||
historyItem={item}
|
||||
/>
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
<Box fontSize={'sm'}>
|
||||
<ChatBoxDivider
|
||||
icon="core/app/markLight"
|
||||
text={t('common:core.chat.Admin Mark Content')}
|
||||
/>
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
<Box color={'black'}>{item.adminFeedback.q}</Box>
|
||||
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
<Box fontSize={'sm'}>
|
||||
<ChatBoxDivider
|
||||
icon="core/app/markLight"
|
||||
text={t('common:core.chat.Admin Mark Content')}
|
||||
/>
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
<Box color={'black'}>{item.adminFeedback.q}</Box>
|
||||
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
|
||||
</Box>
|
||||
)}
|
||||
</ChatItem>
|
||||
</>
|
||||
</Box>
|
||||
)}
|
||||
</ChatItem>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -1002,7 +989,7 @@ const ChatBox = (
|
||||
ScrollData,
|
||||
appAvatar,
|
||||
chatForm,
|
||||
chatHistories,
|
||||
chatRecords,
|
||||
chatStarted,
|
||||
delOneMessage,
|
||||
isChatting,
|
||||
@@ -1029,23 +1016,28 @@ const ChatBox = (
|
||||
]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'} position={'relative'}>
|
||||
<MyBox
|
||||
isLoading={isLoading}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
h={'100%'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Script src={getWebReqUrl('/js/html2pdf.bundle.min.js')} strategy="lazyOnload"></Script>
|
||||
{/* chat box container */}
|
||||
{RenderRecords}
|
||||
{/* message input */}
|
||||
{onStartChat && chatStarted && active && appId && !isInteractive && (
|
||||
{onStartChat && chatStarted && active && !isInteractive && (
|
||||
<ChatInput
|
||||
onSendMessage={sendPrompt}
|
||||
onStop={() => chatController.current?.abort('stop')}
|
||||
TextareaDom={TextareaDom}
|
||||
resetInputVal={resetInputVal}
|
||||
chatForm={chatForm}
|
||||
appId={appId}
|
||||
/>
|
||||
)}
|
||||
{/* user feedback modal */}
|
||||
{!!feedbackId && chatId && appId && (
|
||||
{!!feedbackId && chatId && (
|
||||
<FeedbackModal
|
||||
appId={appId}
|
||||
teamId={teamId}
|
||||
@@ -1056,7 +1048,7 @@ const ChatBox = (
|
||||
outLinkUid={outLinkUid}
|
||||
onClose={() => setFeedbackId(undefined)}
|
||||
onSuccess={(content: string) => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === feedbackId ? { ...item, userBadFeedback: content } : item
|
||||
)
|
||||
@@ -1071,7 +1063,7 @@ const ChatBox = (
|
||||
content={readFeedbackData.content}
|
||||
onClose={() => setReadFeedbackData(undefined)}
|
||||
onCloseFeedback={() => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === readFeedbackData.dataId
|
||||
? { ...chatItem, userBadFeedback: undefined }
|
||||
@@ -1106,7 +1098,7 @@ const ChatBox = (
|
||||
});
|
||||
|
||||
// update dom
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === adminMarkData.dataId
|
||||
? {
|
||||
@@ -1124,7 +1116,7 @@ const ChatBox = (
|
||||
dataId: readFeedbackData.dataId,
|
||||
userBadFeedback: undefined
|
||||
});
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === readFeedbackData.dataId
|
||||
? { ...chatItem, userBadFeedback: undefined }
|
||||
@@ -1136,17 +1128,16 @@ const ChatBox = (
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
const ForwardChatBox = forwardRef(ChatBox);
|
||||
|
||||
const ChatBoxContainer = (props: Props, ref: ForwardedRef<ComponentRef>) => {
|
||||
const ChatBoxContainer = (props: Props) => {
|
||||
return (
|
||||
<ChatProvider {...props}>
|
||||
<ForwardChatBox {...props} ref={ref} />
|
||||
<ChatBox {...props} />
|
||||
</ChatProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(forwardRef(ChatBoxContainer));
|
||||
export default React.memo(ChatBoxContainer);
|
||||
|
||||
@@ -29,6 +29,7 @@ export type ChatBoxInputType = {
|
||||
text?: string;
|
||||
files?: UserInputFileItemType[];
|
||||
isInteractivePrompt?: boolean;
|
||||
hideInUI?: boolean;
|
||||
};
|
||||
|
||||
export type SendPromptFnType = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useFieldArray } from 'react-hook-form';
|
||||
import RenderPluginInput from './renderPluginInput';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
@@ -16,34 +16,36 @@ import { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { ChatBoxInputFormType } from '../../ChatBox/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
const RenderInput = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
pluginInputs,
|
||||
variablesForm,
|
||||
histories,
|
||||
onStartChat,
|
||||
onNewChat,
|
||||
onSubmit,
|
||||
isChatting,
|
||||
chatConfig,
|
||||
chatId,
|
||||
outLinkAuthData
|
||||
} = useContextSelector(PluginRunContext, (v) => v);
|
||||
const pluginInputs = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.pluginInputs);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const onStartChat = useContextSelector(PluginRunContext, (v) => v.onStartChat);
|
||||
const onNewChat = useContextSelector(PluginRunContext, (v) => v.onNewChat);
|
||||
const onSubmit = useContextSelector(PluginRunContext, (v) => v.onSubmit);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
const fileSelectConfig = useContextSelector(PluginRunContext, (v) => v.fileSelectConfig);
|
||||
const instruction = useContextSelector(PluginRunContext, (v) => v.instruction);
|
||||
const chatId = useContextSelector(PluginRunContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(PluginRunContext, (v) => v.outLinkAuthData);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
getValues,
|
||||
formState: { errors }
|
||||
} = variablesForm;
|
||||
|
||||
/* ===> Global files(abandon) */
|
||||
const fileCtrl = useFieldArray({
|
||||
control: variablesForm.control,
|
||||
control,
|
||||
name: 'files'
|
||||
});
|
||||
const {
|
||||
@@ -58,9 +60,7 @@ const RenderInput = () => {
|
||||
removeFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig: chatConfig?.fileSelectConfig,
|
||||
fileSelectConfig,
|
||||
fileCtrl
|
||||
});
|
||||
const isDisabledInput = histories.length > 0;
|
||||
@@ -169,7 +169,7 @@ const RenderInput = () => {
|
||||
return (
|
||||
<Box>
|
||||
{/* instruction */}
|
||||
{chatConfig?.instruction && (
|
||||
{instruction && (
|
||||
<Box
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
@@ -179,7 +179,7 @@ const RenderInput = () => {
|
||||
color={'myGray.600'}
|
||||
mb={4}
|
||||
>
|
||||
<Markdown source={chatConfig.instruction} />
|
||||
<Markdown source={instruction} />
|
||||
</Box>
|
||||
)}
|
||||
{/* file select(Abandoned) */}
|
||||
|
||||
@@ -7,9 +7,13 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import AIResponseBox from '../../../components/AIResponseBox';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import ComplianceTip from '@/components/common/ComplianceTip/index';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
const RenderOutput = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
|
||||
const pluginOutputs = useMemo(() => {
|
||||
const pluginOutputs = histories?.[1]?.responseData?.find(
|
||||
(item) => item.moduleType === FlowNodeTypeEnum.pluginOutput
|
||||
|
||||
@@ -4,16 +4,20 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
const RenderResponseDetail = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
const responseData = histories?.[1]?.responseData || [];
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
|
||||
const responseData = chatRecords?.[1]?.responseData || [];
|
||||
|
||||
return isChatting ? (
|
||||
<>{t('chat:in_progress')}</>
|
||||
) : (
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
<ResponseBox useMobile={true} response={responseData} />
|
||||
<ResponseBox useMobile={true} response={responseData} dataId={chatRecords?.[1]?.dataId} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useFileUpload } from '../../ChatBox/hooks/useFileUpload';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FilePreview from '../../components/FilePreview';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
@@ -18,6 +17,8 @@ import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
@@ -33,10 +34,9 @@ const FileSelector = ({
|
||||
value: any;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { variablesForm, histories, chatId, outLinkAuthData } = useContextSelector(
|
||||
PluginRunContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const fileCtrl = useFieldArray({
|
||||
control: variablesForm.control,
|
||||
@@ -53,8 +53,6 @@ const FileSelector = ({
|
||||
replaceFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig: {
|
||||
canSelectFile: input.canSelectFile ?? true,
|
||||
canSelectImg: input.canSelectImg ?? false,
|
||||
@@ -83,7 +81,7 @@ const FileSelector = ({
|
||||
useRequest2(uploadFiles, {
|
||||
manual: false,
|
||||
errorToast: t('common:upload_file_error'),
|
||||
refreshDeps: [fileList, outLinkAuthData, chatId]
|
||||
refreshDeps: [fileList]
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import {
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
RuntimeUserPromptType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues, useForm } from 'react-hook-form';
|
||||
import { AIChatItemValueItemType, RuntimeUserPromptType } from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
@@ -14,48 +10,51 @@ import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/c
|
||||
import { generatingMessageProps } from '../type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { AppFileSelectConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { defaultAppSelectFileConfig } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
type PluginRunContextType = OutLinkChatAuthProps &
|
||||
PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: ChatBoxInputFormType) => Promise<any>;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
};
|
||||
type PluginRunContextType = PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: ChatBoxInputFormType) => Promise<any>;
|
||||
instruction: string;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
};
|
||||
|
||||
export const PluginRunContext = createContext<PluginRunContextType>({
|
||||
pluginInputs: [],
|
||||
histories: [],
|
||||
setHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
appId: '',
|
||||
tab: PluginRunBoxTabEnum.input,
|
||||
setTab: function (value: React.SetStateAction<PluginRunBoxTabEnum>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isChatting: false,
|
||||
onSubmit: function (e: FieldValues): Promise<any> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
outLinkAuthData: {},
|
||||
//@ts-ignore
|
||||
variablesForm: undefined
|
||||
instruction: '',
|
||||
fileSelectConfig: defaultAppSelectFileConfig,
|
||||
appId: '',
|
||||
chatId: '',
|
||||
outLinkAuthData: {}
|
||||
});
|
||||
|
||||
const PluginRunContextProvider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
children,
|
||||
...props
|
||||
}: PluginRunBoxProps & { children: ReactNode }) => {
|
||||
const { pluginInputs, onStartChat, setHistories, histories, setTab } = props;
|
||||
const { onStartChat } = props;
|
||||
|
||||
const pluginInputs = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.pluginInputs);
|
||||
const setTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const chatConfig = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.chatConfig);
|
||||
|
||||
const { instruction = '', fileSelectConfig = defaultAppSelectFileConfig } = useMemo(
|
||||
() => chatConfig || {},
|
||||
[chatConfig]
|
||||
);
|
||||
|
||||
const { toast } = useToast();
|
||||
const chatController = useRef(new AbortController());
|
||||
@@ -65,23 +64,9 @@ const PluginRunContextProvider = ({
|
||||
chatController.current?.abort('stop');
|
||||
}, []);
|
||||
|
||||
const outLinkAuthData = useMemo(
|
||||
() => ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
[shareId, outLinkUid, teamId, teamToken]
|
||||
);
|
||||
|
||||
const variablesForm = useForm<ChatBoxInputFormType>({
|
||||
defaultValues: {}
|
||||
});
|
||||
|
||||
const generatingMessage = useCallback(
|
||||
({ event, text = '', status, name, tool }: generatingMessageProps) => {
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1 || item.obj !== ChatRoleEnum.AI) return item;
|
||||
|
||||
@@ -165,12 +150,14 @@ const PluginRunContextProvider = ({
|
||||
})
|
||||
);
|
||||
},
|
||||
[setHistories]
|
||||
[setChatRecords]
|
||||
);
|
||||
|
||||
const isChatting = useMemo(
|
||||
() => histories[histories.length - 1] && histories[histories.length - 1]?.status !== 'finish',
|
||||
[histories]
|
||||
() =>
|
||||
chatRecords[chatRecords.length - 1] &&
|
||||
chatRecords[chatRecords.length - 1]?.status !== 'finish',
|
||||
[chatRecords]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
@@ -189,7 +176,7 @@ const PluginRunContextProvider = ({
|
||||
const abortSignal = new AbortController();
|
||||
chatController.current = abortSignal;
|
||||
|
||||
setHistories([
|
||||
setChatRecords([
|
||||
{
|
||||
...getPluginRunUserQuery({
|
||||
pluginInputs,
|
||||
@@ -255,7 +242,7 @@ const PluginRunContextProvider = ({
|
||||
});
|
||||
}
|
||||
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -267,7 +254,7 @@ const PluginRunContextProvider = ({
|
||||
);
|
||||
} catch (err: any) {
|
||||
toast({ title: err.message, status: 'error' });
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -284,7 +271,7 @@ const PluginRunContextProvider = ({
|
||||
isChatting,
|
||||
onStartChat,
|
||||
pluginInputs,
|
||||
setHistories,
|
||||
setChatRecords,
|
||||
setTab,
|
||||
t,
|
||||
toast
|
||||
@@ -295,8 +282,8 @@ const PluginRunContextProvider = ({
|
||||
...props,
|
||||
isChatting,
|
||||
onSubmit,
|
||||
outLinkAuthData,
|
||||
variablesForm
|
||||
instruction,
|
||||
fileSelectConfig
|
||||
};
|
||||
return <PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -2,29 +2,23 @@ import React from 'react';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import RenderInput from './components/RenderInput';
|
||||
import PluginRunContextProvider, { PluginRunContext } from './context';
|
||||
import PluginRunContextProvider from './context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import RenderOutput from './components/RenderOutput';
|
||||
import RenderResponseDetail from './components/RenderResponseDetail';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const PluginRunBox = () => {
|
||||
const { tab } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const PluginRunBox = (props: PluginRunBoxProps) => {
|
||||
const tab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const formatTab = props.showTab || tab;
|
||||
|
||||
return (
|
||||
<>
|
||||
{tab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||
{tab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||
{tab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: PluginRunBoxProps) => {
|
||||
return (
|
||||
<PluginRunContextProvider {...props}>
|
||||
<PluginRunBox />
|
||||
{formatTab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||
{formatTab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||
{formatTab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||
</PluginRunContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Render;
|
||||
export default PluginRunBox;
|
||||
|
||||
@@ -7,18 +7,12 @@ import React from 'react';
|
||||
import { onStartChatType } from '../type';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
|
||||
export type PluginRunBoxProps = OutLinkChatAuthProps & {
|
||||
pluginInputs: FlowNodeInputItemType[];
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
histories: ChatSiteItemType[]; // chatHistories[1] is the response
|
||||
setHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
export type PluginRunBoxProps = {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
outLinkAuthData?: OutLinkChatAuthProps;
|
||||
|
||||
onStartChat?: onStartChatType;
|
||||
onNewChat?: () => void;
|
||||
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
tab: PluginRunBoxTabEnum;
|
||||
setTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
|
||||
chatConfig?: AppChatConfigType;
|
||||
showTab?: PluginRunBoxTabEnum; // 如何设置了该字段,全局都 tab 不生效
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { FieldArrayWithId } from 'react-hook-form';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import { Box, CircularProgress, Flex, HStack, Image } from '@chakra-ui/react';
|
||||
import { Box, CircularProgress, Flex, HStack } from '@chakra-ui/react';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
|
||||
const RenderFilePreview = ({
|
||||
fileList,
|
||||
@@ -29,6 +30,8 @@ const RenderFilePreview = ({
|
||||
{fileList.map((item, index) => {
|
||||
const isFile = item.type === ChatFileTypeEnum.file;
|
||||
const isImage = item.type === ChatFileTypeEnum.image;
|
||||
const icon = getFileIcon(item.name);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
key={index}
|
||||
@@ -73,7 +76,7 @@ const RenderFilePreview = ({
|
||||
{isImage && (
|
||||
<MyImage
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
src={item.icon || item.url}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
borderRadius={'md'}
|
||||
@@ -82,7 +85,7 @@ const RenderFilePreview = ({
|
||||
)}
|
||||
{isFile && (
|
||||
<HStack alignItems={'center'} h={'full'}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<MyIcon name={icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} pr={2} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './PluginRunBox/constants';
|
||||
import {
|
||||
ChatBoxInputFormType,
|
||||
ComponentRef as ChatComponentRef,
|
||||
SendPromptFnType
|
||||
} from './ChatBox/type';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
import { getChatRecords } from '@/web/core/chat/api';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import type { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
|
||||
export const useChat = (params?: { chatId?: string; appId: string; type?: GetChatTypeEnum }) => {
|
||||
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||
const variablesForm = useForm<ChatBoxInputFormType>();
|
||||
// plugin
|
||||
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||
|
||||
const resetVariables = useCallback(
|
||||
(props?: { variables?: Record<string, any> }) => {
|
||||
const { variables = {} } = props || {};
|
||||
|
||||
// Reset to empty input
|
||||
const data = variablesForm.getValues();
|
||||
|
||||
// Reset the old variables to empty
|
||||
const resetVariables: Record<string, any> = {};
|
||||
for (const key in data.variables) {
|
||||
resetVariables[key] = (() => {
|
||||
if (Array.isArray(data.variables[key])) {
|
||||
return [];
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
}
|
||||
|
||||
variablesForm.reset({
|
||||
...data,
|
||||
variables: {
|
||||
...resetVariables,
|
||||
...variables
|
||||
}
|
||||
});
|
||||
},
|
||||
[variablesForm]
|
||||
);
|
||||
|
||||
const clearChatRecords = useCallback(() => {
|
||||
const data = variablesForm.getValues();
|
||||
for (const key in data.variables) {
|
||||
variablesForm.setValue(`variables.${key}`, '');
|
||||
}
|
||||
|
||||
ChatBoxRef.current?.restartChat?.();
|
||||
}, [variablesForm]);
|
||||
|
||||
const {
|
||||
data: chatRecords,
|
||||
ScrollData,
|
||||
setData: setChatRecords,
|
||||
total: totalRecordsCount
|
||||
} = useScrollPagination(
|
||||
async (data: getPaginationRecordsBody): Promise<PaginationResponse<ChatSiteItemType>> => {
|
||||
const res = await getChatRecords(data);
|
||||
|
||||
// First load scroll to bottom
|
||||
if (data.offset === 0) {
|
||||
function scrollToBottom() {
|
||||
requestAnimationFrame(
|
||||
ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom
|
||||
);
|
||||
}
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
return {
|
||||
...res,
|
||||
list: res.list.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}))
|
||||
};
|
||||
},
|
||||
{
|
||||
pageSize: 10,
|
||||
refreshDeps: [params],
|
||||
params,
|
||||
scrollLoadType: 'top'
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
ChatBoxRef,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
clearChatRecords,
|
||||
resetVariables,
|
||||
chatRecords,
|
||||
ScrollData,
|
||||
setChatRecords,
|
||||
totalRecordsCount
|
||||
};
|
||||
};
|
||||
|
||||
export const onSendPrompt: SendPromptFnType = (e) => eventBus.emit(EventNameEnum.sendQuestion, e);
|
||||
@@ -8,12 +8,6 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
@@ -31,15 +25,16 @@ import {
|
||||
UserSelectInteractive
|
||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { isEqual } from 'lodash';
|
||||
import { onSendPrompt } from '../ChatContainer/useChat';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { SendPromptFnType } from '../ChatContainer/ChatBox/type';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
|
||||
type props = {
|
||||
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
||||
@@ -47,6 +42,8 @@ type props = {
|
||||
isChatting: boolean;
|
||||
};
|
||||
|
||||
const onSendPrompt: SendPromptFnType = (e) => eventBus.emit(EventNameEnum.sendQuestion, e);
|
||||
|
||||
const RenderText = React.memo(function RenderText({
|
||||
showAnimation,
|
||||
text
|
||||
@@ -215,6 +212,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} gap={2} w={'250px'}>
|
||||
{interactive.params.description && <Markdown source={interactive.params.description} />}
|
||||
{interactive.params.inputForm?.map((input) => (
|
||||
<Box key={input.label}>
|
||||
<Flex mb={1} alignItems={'center'}>
|
||||
|
||||
@@ -31,10 +31,12 @@ type sideTabItemType = {
|
||||
/* Per response value */
|
||||
export const WholeResponseContent = ({
|
||||
activeModule,
|
||||
hideTabs
|
||||
hideTabs,
|
||||
dataId
|
||||
}: {
|
||||
activeModule: ChatHistoryItemResType;
|
||||
hideTabs?: boolean;
|
||||
dataId?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -231,7 +233,14 @@ export const WholeResponseContent = ({
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('common:core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList canEditDataset canViewSource rawSearch={activeModule.quoteList} />}
|
||||
rawDom={
|
||||
<QuoteList
|
||||
canEditDataset
|
||||
canViewSource
|
||||
chatItemId={dataId}
|
||||
rawSearch={activeModule.quoteList}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -529,10 +538,12 @@ const SideTabItem = ({
|
||||
/* Modal main container */
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
dataId,
|
||||
hideTabs = false,
|
||||
useMobile = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
dataId?: string;
|
||||
hideTabs?: boolean;
|
||||
useMobile?: boolean;
|
||||
}) {
|
||||
@@ -655,7 +666,7 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={'5 0 0'} w={0} height={'100%'}>
|
||||
<WholeResponseContent activeModule={activeModule} hideTabs={hideTabs} />
|
||||
<WholeResponseContent dataId={dataId} activeModule={activeModule} hideTabs={hideTabs} />
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
@@ -715,7 +726,11 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'}>
|
||||
<WholeResponseContent activeModule={activeModule} hideTabs={hideTabs} />
|
||||
<WholeResponseContent
|
||||
dataId={dataId}
|
||||
activeModule={activeModule}
|
||||
hideTabs={hideTabs}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
@@ -754,7 +769,7 @@ const WholeResponseModal = ({ onClose, dataId }: { onClose: () => void; dataId:
|
||||
}
|
||||
>
|
||||
{!!response?.length ? (
|
||||
<ResponseBox response={response} />
|
||||
<ResponseBox response={response} dataId={dataId} />
|
||||
) : (
|
||||
<EmptyTip text={t('chat:no_workflow_response')} />
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
|
||||
|
||||
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
|
||||
|
||||
@@ -45,12 +46,13 @@ const scoreTheme: Record<
|
||||
const QuoteItem = ({
|
||||
quoteItem,
|
||||
canViewSource,
|
||||
canEditDataset
|
||||
canEditDataset,
|
||||
...RawSourceBoxProps
|
||||
}: {
|
||||
quoteItem: SearchDataResponseItemType;
|
||||
canViewSource?: boolean;
|
||||
canEditDataset?: boolean;
|
||||
}) => {
|
||||
} & Omit<readCollectionSourceBody, 'collectionId'>) => {
|
||||
const { t } = useTranslation();
|
||||
const [editInputData, setEditInputData] = useState<{ dataId: string; collectionId: string }>();
|
||||
|
||||
@@ -196,6 +198,7 @@ const QuoteItem = ({
|
||||
sourceName={quoteItem.sourceName}
|
||||
sourceId={quoteItem.sourceId}
|
||||
canView={canViewSource}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{quoteItem.id && canEditDataset && (
|
||||
|
||||
@@ -6,10 +6,10 @@ import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollect
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
|
||||
|
||||
type Props = BoxProps &
|
||||
ShareChatAuthProps & {
|
||||
readCollectionSourceBody & {
|
||||
sourceName?: string;
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
@@ -18,11 +18,18 @@ type Props = BoxProps &
|
||||
|
||||
const RawSourceBox = ({
|
||||
sourceId,
|
||||
collectionId,
|
||||
sourceName = '',
|
||||
canView = true,
|
||||
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
|
||||
...props
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -33,8 +40,13 @@ const RawSourceBox = ({
|
||||
const icon = useMemo(() => getSourceNameIcon({ sourceId, sourceName }), [sourceId, sourceName]);
|
||||
const read = getCollectionSourceAndOpen({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -56,7 +68,7 @@ const RawSourceBox = ({
|
||||
: {})}
|
||||
{...props}
|
||||
>
|
||||
<MyIcon name={icon as any} w={['16px', '20px']} mr={2} />
|
||||
<MyIcon name={icon as any} w={['1rem', '1.25rem']} mr={2} />
|
||||
<Box
|
||||
maxW={['200px', '300px']}
|
||||
className={props.className ?? 'textEllipsis'}
|
||||
|
||||
@@ -45,6 +45,18 @@ const SearchParamsTip = ({
|
||||
borderRadius={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'primary.1'}
|
||||
sx={{
|
||||
'&::-webkit-scrollbar': {
|
||||
height: '6px',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'myGray.250 !important',
|
||||
'&:hover': {
|
||||
backgroundColor: 'myGray.300 !important'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table fontSize={'xs'} overflow={'overlay'}>
|
||||
<Thead>
|
||||
|
||||
1
projects/app/src/global/core/ai/api.d.ts
vendored
1
projects/app/src/global/core/ai/api.d.ts
vendored
@@ -2,5 +2,6 @@ import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||
|
||||
export type CreateQuestionGuideParams = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
messages: ChatCompletionMessageParam[];
|
||||
};
|
||||
|
||||
1
projects/app/src/global/core/app/api.d.ts
vendored
1
projects/app/src/global/core/app/api.d.ts
vendored
@@ -15,7 +15,6 @@ export type AppUpdateParams = {
|
||||
};
|
||||
|
||||
export type PostPublishAppProps = {
|
||||
type: AppTypeEnum;
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
|
||||
5
projects/app/src/global/core/chat/api.d.ts
vendored
5
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -5,7 +5,8 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { RequestPaging } from '@/types';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
export type GetChatSpeechProps = {
|
||||
export type GetChatSpeechProps = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
input: string;
|
||||
shareId?: string;
|
||||
@@ -73,7 +74,7 @@ export type DelHistoryProps = OutLinkChatAuthProps & {
|
||||
chatId: string;
|
||||
};
|
||||
export type ClearHistoriesProps = OutLinkChatAuthProps & {
|
||||
appId?: string;
|
||||
appId: string;
|
||||
};
|
||||
|
||||
/* -------- chat item ---------- */
|
||||
|
||||
@@ -28,10 +28,12 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) {
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
historyItem.responseData
|
||||
?.map((item) => {
|
||||
if (item.pluginDetail || item.toolDetail) {
|
||||
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
|
||||
}
|
||||
return item;
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
|
||||
|
||||
55
projects/app/src/instrumentation.ts
Normal file
55
projects/app/src/instrumentation.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { exit } from 'process';
|
||||
|
||||
/*
|
||||
Init system
|
||||
*/
|
||||
export async function register() {
|
||||
try {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
// 基础系统初始化
|
||||
const [
|
||||
{ connectMongo },
|
||||
{ systemStartCb },
|
||||
{ initGlobalVariables },
|
||||
{ getInitConfig },
|
||||
{ initVectorStore },
|
||||
{ initRootUser },
|
||||
{ getSystemPluginCb },
|
||||
{ startMongoWatch },
|
||||
{ startCron },
|
||||
{ startTrainingQueue }
|
||||
] = await Promise.all([
|
||||
import('@fastgpt/service/common/mongo/init'),
|
||||
import('@fastgpt/service/common/system/tools'),
|
||||
import('@/service/common/system'),
|
||||
import('@/service/common/system'),
|
||||
import('@fastgpt/service/common/vectorStore/controller'),
|
||||
import('@/service/mongo'),
|
||||
import('@/service/core/app/plugin'),
|
||||
import('@/service/common/system/volumnMongoWatch'),
|
||||
import('@/service/common/system/cron'),
|
||||
import('@/service/core/dataset/training/utils')
|
||||
]);
|
||||
|
||||
// 执行初始化流程
|
||||
systemStartCb();
|
||||
initGlobalVariables();
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectMongo();
|
||||
|
||||
//init system config;init vector database;init root user
|
||||
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
|
||||
|
||||
getSystemPluginCb();
|
||||
startMongoWatch();
|
||||
startCron();
|
||||
startTrainingQueue(true);
|
||||
|
||||
console.log('Init system success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Init system error', error);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
Read db file content and response 3000 words
|
||||
*/
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { authFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetSourceReadTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { readDatasetSourceRawText } from '@fastgpt/service/core/dataset/read';
|
||||
@@ -26,7 +26,7 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
|
||||
|
||||
const { teamId } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
return authFile({
|
||||
return authCollectionFile({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
|
||||
@@ -9,7 +9,17 @@ import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { authFrequencyLimit } from '@/service/common/frequencyLimit/api';
|
||||
import { addSeconds } from 'date-fns';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type UploadChatFileProps = {
|
||||
appId: string;
|
||||
} & OutLinkChatAuthProps;
|
||||
export type UploadDatasetFileProps = {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
const authUploadLimit = (tmbId: string) => {
|
||||
if (!global.feConfigs.uploadFileMaxAmount) return;
|
||||
@@ -28,15 +38,43 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const upload = getUploadModel({
|
||||
maxSize: global.feConfigs?.uploadFileMaxSize
|
||||
});
|
||||
const { file, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
const { file, bucketName, metadata, data } = await upload.doUpload<
|
||||
UploadChatFileProps | UploadDatasetFileProps
|
||||
>(req, res);
|
||||
filePaths.push(file.path);
|
||||
const { teamId, tmbId, outLinkUid } = await authChatCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
|
||||
await authUploadLimit(outLinkUid || tmbId);
|
||||
const { teamId, uid } = await (async () => {
|
||||
if (bucketName === 'chat') {
|
||||
const chatData = data as UploadChatFileProps;
|
||||
const authData = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...chatData
|
||||
});
|
||||
return {
|
||||
teamId: authData.teamId,
|
||||
uid: authData.uid
|
||||
};
|
||||
}
|
||||
if (bucketName === 'dataset') {
|
||||
const chatData = data as UploadDatasetFileProps;
|
||||
const authData = await authDataset({
|
||||
datasetId: chatData.datasetId,
|
||||
per: WritePermissionVal,
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
return {
|
||||
teamId: authData.teamId,
|
||||
uid: authData.tmbId
|
||||
};
|
||||
}
|
||||
return Promise.reject('bucketName is empty');
|
||||
})();
|
||||
|
||||
await authUploadLimit(uid);
|
||||
|
||||
addLog.info(`Upload file success ${file.originalname}, cost ${Date.now() - start}ms`);
|
||||
|
||||
@@ -46,7 +84,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
|
||||
const fileId = await uploadFile({
|
||||
teamId,
|
||||
tmbId,
|
||||
uid,
|
||||
bucketName,
|
||||
path: file.path,
|
||||
filename: file.originalname,
|
||||
@@ -61,7 +99,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
previewUrl: `${ReadFileBaseUrl}/${file.originalname}?token=${await createFileToken({
|
||||
bucketName,
|
||||
teamId,
|
||||
tmbId,
|
||||
uid,
|
||||
fileId
|
||||
})}`
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
import { UploadImgProps } from '@fastgpt/global/common/file/api';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
/*
|
||||
Upload avatar image
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const body = req.body as UploadImgProps;
|
||||
|
||||
const { teamId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId } = await authCert({ req, authToken: true });
|
||||
|
||||
const imgId = await uploadMongoImg({
|
||||
teamId,
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
|
||||
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
async function handler(
|
||||
req: ApiRequestProps<
|
||||
OutLinkChatAuthProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
}
|
||||
>,
|
||||
res: NextApiResponse<any>
|
||||
) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { messages } = req.body as CreateQuestionGuideParams;
|
||||
const { messages } = req.body;
|
||||
|
||||
const { tmbId, teamId } = await authChatCert({
|
||||
req,
|
||||
@@ -40,3 +50,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
|
||||
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { messages } = req.body;
|
||||
|
||||
const { tmbId, teamId } = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
const qgModel = global.llmModels[0];
|
||||
|
||||
const { result, tokens } = await createQuestionGuide({
|
||||
messages,
|
||||
model: qgModel.model
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: result
|
||||
});
|
||||
|
||||
pushQuestionGuideUsage({
|
||||
tokens,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -43,10 +43,10 @@ async function handler(
|
||||
});
|
||||
|
||||
return { id: appId };
|
||||
} else {
|
||||
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
|
||||
}
|
||||
|
||||
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,14 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
|
||||
async function handler(
|
||||
req: ApiRequestProps<PostPublishAppProps>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<{}> {
|
||||
const { appId } = req.query as { appId: string };
|
||||
const {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
chatConfig,
|
||||
type,
|
||||
isPublish,
|
||||
versionName
|
||||
} = req.body as PostPublishAppProps;
|
||||
const { nodes = [], edges = [], chatConfig, isPublish, versionName } = req.body;
|
||||
|
||||
const { tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
@@ -50,11 +47,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
chatConfig,
|
||||
updateTime: new Date(),
|
||||
version: 'v2',
|
||||
type,
|
||||
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||
: null,
|
||||
// 只有发布才会更新定时器
|
||||
...(isPublish && {
|
||||
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||
: null
|
||||
}),
|
||||
'pluginData.nodeVersion': _id
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { sseErrRes } from '@fastgpt/service/common/response';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import type { UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||
import {
|
||||
concatHistories,
|
||||
getChatTitleFromChatMessage,
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import {
|
||||
@@ -18,27 +24,37 @@ import {
|
||||
updatePluginInputByVariables
|
||||
} from '@fastgpt/global/core/workflow/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { chatValue2RuntimePrompt, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import {
|
||||
getLastInteractiveValue,
|
||||
getMaxHistoryLimitFromNodes,
|
||||
getWorkflowEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
rewriteNodeOutputByHistories,
|
||||
storeNodes2RuntimeNodes
|
||||
storeNodes2RuntimeNodes,
|
||||
textAdaptGptResponse
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { saveChat, updateInteractiveChat } from '@fastgpt/service/core/chat/saveChat';
|
||||
|
||||
export type Props = {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
responseChatItemId: string;
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
variables: Record<string, any>;
|
||||
appId: string;
|
||||
appName: string;
|
||||
chatId: string;
|
||||
chatConfig: AppChatConfigType;
|
||||
};
|
||||
|
||||
@@ -55,10 +71,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
messages = [],
|
||||
responseChatItemId,
|
||||
variables = {},
|
||||
appName,
|
||||
appId,
|
||||
chatConfig
|
||||
chatConfig,
|
||||
chatId
|
||||
} = req.body as Props;
|
||||
try {
|
||||
if (!Array.isArray(nodes)) {
|
||||
@@ -71,15 +89,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
|
||||
|
||||
/* user auth */
|
||||
const [{ app }, { teamId, tmbId }] = await Promise.all([
|
||||
authApp({ req, authToken: true, appId, per: ReadPermissionVal }),
|
||||
authCert({
|
||||
req,
|
||||
authToken: true
|
||||
})
|
||||
]);
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
const { app, teamId, tmbId } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
@@ -99,48 +114,79 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return latestHumanChat;
|
||||
})();
|
||||
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, chatMessages));
|
||||
const limit = getMaxHistoryLimitFromNodes(nodes);
|
||||
const [{ histories }, chatDetail, { user }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
offset: 0,
|
||||
limit,
|
||||
field: `dataId obj value nodeOutputs`
|
||||
}),
|
||||
MongoChat.findOne({ appId: app._id, chatId }, 'source variableList variables'),
|
||||
// auth balance
|
||||
getUserChatInfoAndAuthTeamPoints(tmbId)
|
||||
]);
|
||||
|
||||
// Plugin need to replace inputs
|
||||
if (chatDetail?.variables) {
|
||||
variables = {
|
||||
...chatDetail.variables,
|
||||
...variables
|
||||
};
|
||||
}
|
||||
|
||||
const newHistories = concatHistories(histories, chatMessages);
|
||||
|
||||
// Get runtimeNodes
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
||||
if (isPlugin) {
|
||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||
variables = {};
|
||||
} else {
|
||||
if (!userQuestion.value) {
|
||||
throw new Error('Params Error');
|
||||
}
|
||||
}
|
||||
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
||||
|
||||
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
|
||||
const workflowResponseWrite = getWorkflowResponseWrite({
|
||||
res,
|
||||
detail: true,
|
||||
streamResponse: true
|
||||
streamResponse: true,
|
||||
id: chatId,
|
||||
showNodeStatus: true
|
||||
});
|
||||
|
||||
/* start process */
|
||||
const { flowResponses, flowUsages } = await dispatchWorkFlow({
|
||||
const { flowResponses, assistantResponses, newVariables, flowUsages } = await dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'test',
|
||||
user,
|
||||
uid: tmbId,
|
||||
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: tmbId,
|
||||
user,
|
||||
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges, chatMessages),
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
chatConfig,
|
||||
histories: chatMessages,
|
||||
histories: newHistories,
|
||||
stream: true,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite
|
||||
});
|
||||
|
||||
workflowResponseWrite({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: null,
|
||||
finish_reason: 'stop'
|
||||
})
|
||||
});
|
||||
responseWrite({
|
||||
res,
|
||||
event: SseResponseEventEnum.answer,
|
||||
@@ -152,7 +198,44 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
data: JSON.stringify(flowResponses)
|
||||
});
|
||||
|
||||
res.end();
|
||||
// save chat
|
||||
const isInteractiveRequest = !!getLastInteractiveValue(histories);
|
||||
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? getSystemTime(user.timezone)
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
|
||||
const aiResponse: AIChatItemType & { dataId?: string } = {
|
||||
dataId: responseChatItemId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: assistantResponses,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
|
||||
};
|
||||
|
||||
if (isInteractiveRequest) {
|
||||
await updateInteractiveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
nodes,
|
||||
appChatConfig: chatConfig,
|
||||
variables: newVariables,
|
||||
isUpdateUseTime: false, // owner update use time
|
||||
newTitle,
|
||||
source: ChatSourceEnum.test,
|
||||
content: [userQuestion, aiResponse]
|
||||
});
|
||||
}
|
||||
|
||||
pushChatUsage({
|
||||
appName,
|
||||
@@ -165,8 +248,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
} catch (err: any) {
|
||||
res.status(500);
|
||||
sseErrRes(res, err);
|
||||
res.end();
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,44 +1,48 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { ClearHistoriesProps } from '@/global/core/chat/api';
|
||||
import { authOutLink } from '@/service/support/permission/auth/outLink';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
|
||||
/* clear chat history */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query as ClearHistoriesProps;
|
||||
async function handler(req: ApiRequestProps<{}, ClearHistoriesProps>, res: NextApiResponse) {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query;
|
||||
|
||||
let chatAppId = appId!;
|
||||
const {
|
||||
teamId: chatTeamId,
|
||||
tmbId,
|
||||
uid,
|
||||
authType
|
||||
} = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query
|
||||
});
|
||||
|
||||
const match = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
chatAppId = appId;
|
||||
if (shareId && outLinkUid && authType === 'outLink') {
|
||||
return {
|
||||
shareId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (teamId && teamToken) {
|
||||
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
|
||||
return {
|
||||
teamId,
|
||||
teamId: chatTeamId,
|
||||
appId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (appId) {
|
||||
const { tmbId } = await authCert({ req, authToken: true, authApiKey: true });
|
||||
|
||||
if (teamId && teamToken && authType === 'teamDomain') {
|
||||
return {
|
||||
teamId: chatTeamId,
|
||||
appId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (authType === 'token') {
|
||||
return {
|
||||
teamId: chatTeamId,
|
||||
tmbId,
|
||||
appId,
|
||||
source: ChatSourceEnum.online
|
||||
@@ -54,24 +58,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
await deleteChatFiles({ chatIdList: idList });
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
return mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.deleteMany(
|
||||
{
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
|
||||
|
||||
/* clear chat history */
|
||||
@@ -18,8 +17,7 @@ async function handler(req: ApiRequestProps<{}, DelHistoryProps>, res: NextApiRe
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: WritePermissionVal
|
||||
...req.query
|
||||
});
|
||||
|
||||
await deleteChatFiles({ chatIdList: [chatId] });
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import type { AdminUpdateFeedbackParams } from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -20,9 +19,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: ReadPermissionVal
|
||||
chatId
|
||||
});
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
|
||||
@@ -5,7 +5,6 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type { CloseCustomFeedbackParams } from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
|
||||
/* remove custom feedback */
|
||||
@@ -21,9 +20,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: ReadPermissionVal
|
||||
chatId
|
||||
});
|
||||
await authCert({ req, authToken: true });
|
||||
|
||||
|
||||
@@ -1,71 +1,42 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { UpdateChatFeedbackProps } from '@fastgpt/global/core/chat/api';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const {
|
||||
appId,
|
||||
chatId,
|
||||
dataId,
|
||||
shareId,
|
||||
teamId,
|
||||
teamToken,
|
||||
outLinkUid,
|
||||
userBadFeedback,
|
||||
userGoodFeedback
|
||||
} = req.body as UpdateChatFeedbackProps;
|
||||
async function handler(req: ApiRequestProps<UpdateChatFeedbackProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, dataId, userBadFeedback, userGoodFeedback } = req.body;
|
||||
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
teamId,
|
||||
teamToken,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
if (!dataId) {
|
||||
throw new Error('dataId is required');
|
||||
}
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
...(userBadFeedback === undefined && { userBadFeedback: '' }),
|
||||
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
|
||||
},
|
||||
$set: {
|
||||
...(userBadFeedback !== undefined && { userBadFeedback }),
|
||||
...(userGoodFeedback !== undefined && { userGoodFeedback })
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
if (!chatId || !dataId) {
|
||||
return Promise.reject('chatId or dataId is empty');
|
||||
}
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
...(userBadFeedback === undefined && { userBadFeedback: '' }),
|
||||
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
|
||||
},
|
||||
$set: {
|
||||
...(userBadFeedback !== undefined && { userBadFeedback }),
|
||||
...(userGoodFeedback !== undefined && { userGoodFeedback })
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { GetHistoriesProps } from '@/global/core/chat/api';
|
||||
import { addMonths } from 'date-fns';
|
||||
|
||||
export type getHistoriesQuery = {};
|
||||
|
||||
export type getHistoriesBody = PaginationProps<GetHistoriesProps>;
|
||||
@@ -17,8 +19,7 @@ async function handler(
|
||||
req: ApiRequestProps<getHistoriesBody, getHistoriesQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<PaginationResponse<getHistoriesResponse>> {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } =
|
||||
req.body as getHistoriesBody;
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } = req.body;
|
||||
|
||||
const match = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
@@ -28,7 +29,7 @@ async function handler(
|
||||
shareId,
|
||||
outLinkUid: uid,
|
||||
updateTime: {
|
||||
$gte: new Date(new Date().setDate(new Date().getDate() - 30))
|
||||
$gte: addMonths(new Date(), -1)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -62,7 +63,8 @@ async function handler(
|
||||
await MongoChat.find(match, 'chatId title top customTitle appId updateTime')
|
||||
.sort({ top: -1, updateTime: -1 })
|
||||
.skip(offset)
|
||||
.limit(pageSize),
|
||||
.limit(pageSize)
|
||||
.lean(),
|
||||
MongoChat.countDocuments(match)
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GetChatRecordsProps } from '@/global/core/chat/api';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { transformPreviewHistories } from '@/global/core/chat/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
@@ -11,7 +10,6 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { authOutLink } from '@/service/support/permission/auth/outLink';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
@@ -42,14 +40,13 @@ async function handler(
|
||||
};
|
||||
}
|
||||
|
||||
const [app] = await Promise.all([
|
||||
const [app, { responseDetail, showNodeStatus, authType }] = await Promise.all([
|
||||
MongoApp.findById(appId, 'type').lean(),
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body,
|
||||
per: ReadPermissionVal
|
||||
...req.body
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -57,21 +54,14 @@ async function handler(
|
||||
return Promise.reject(AppErrEnum.unExist);
|
||||
}
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
const shareChat = await (async () => {
|
||||
if (type === GetChatTypeEnum.outLink)
|
||||
return await authOutLink({
|
||||
shareId: req.body.shareId,
|
||||
outLinkUid: req.body.outLinkUid
|
||||
}).then((result) => result.shareChat);
|
||||
})();
|
||||
const isOutLink = authType === GetChatTypeEnum.outLink;
|
||||
|
||||
const fieldMap = {
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time ${
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time hideInUI ${
|
||||
DispatchNodeResponseKeyEnum.nodeResponse
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
};
|
||||
|
||||
const { total, histories } = await getChatItems({
|
||||
@@ -82,10 +72,8 @@ async function handler(
|
||||
limit: pageSize
|
||||
});
|
||||
|
||||
const responseDetail = !shareChat || shareChat.responseDetail;
|
||||
|
||||
// Remove important information
|
||||
if (shareChat && app.type !== AppTypeEnum.plugin) {
|
||||
if (isOutLink && app.type !== AppTypeEnum.plugin) {
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({
|
||||
@@ -93,7 +81,7 @@ async function handler(
|
||||
responseDetail
|
||||
});
|
||||
|
||||
if (shareChat.showNodeStatus === false) {
|
||||
if (showNodeStatus === false) {
|
||||
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
ReadPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
|
||||
export type getResDataQuery = OutLinkChatAuthProps & {
|
||||
chatId?: string;
|
||||
@@ -32,29 +26,13 @@ async function handler(
|
||||
return {};
|
||||
}
|
||||
|
||||
// 1. Un login api: share chat, team chat
|
||||
// 2. Login api: account chat, chat log
|
||||
const authData = await (() => {
|
||||
try {
|
||||
return authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
} catch (error) {
|
||||
return authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
const [chatData] = await Promise.all([
|
||||
const [{ responseDetail }, chatData] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query
|
||||
}),
|
||||
MongoChatItem.findOne(
|
||||
{
|
||||
appId,
|
||||
@@ -62,8 +40,7 @@ async function handler(
|
||||
dataId
|
||||
},
|
||||
'obj responseData'
|
||||
).lean(),
|
||||
shareId ? MongoOutLink.findOne({ shareId }).lean() : Promise.resolve(null)
|
||||
).lean()
|
||||
]);
|
||||
|
||||
if (chatData?.obj !== ChatRoleEnum.AI) {
|
||||
@@ -73,8 +50,7 @@ async function handler(
|
||||
const flowResponses = chatData.responseData ?? {};
|
||||
return req.query.shareId
|
||||
? filterPublicNodeResponseData({
|
||||
// @ts-ignore
|
||||
responseDetail: authData.responseDetail,
|
||||
responseDetail,
|
||||
flowResponses: chatData.responseData
|
||||
})
|
||||
: flowResponses;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schem
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||
@@ -21,7 +21,7 @@ async function handler(
|
||||
const { appId, searchKey } = req.body;
|
||||
|
||||
// tmp auth
|
||||
const { teamId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId } = await authChatCrud({ req, authToken: true, ...req.body });
|
||||
const app = await MongoApp.findOne({ _id: appId, teamId });
|
||||
if (!app) {
|
||||
return Promise.reject(AppErrEnum.unAuthApp);
|
||||
|
||||
@@ -5,21 +5,19 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import type { DeleteChatItemProps } from '@/global/core/chat/api.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, contentId } = req.query;
|
||||
|
||||
if (!contentId || !chatId) {
|
||||
return jsonRes(res);
|
||||
return Promise.reject('contentId or chatId is empty');
|
||||
}
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: WritePermissionVal
|
||||
...req.query
|
||||
});
|
||||
|
||||
await MongoChatItem.deleteOne({
|
||||
@@ -28,7 +26,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA
|
||||
dataId: contentId
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
return;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
|
||||
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
|
||||
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
|
||||
import { getAudioSpeechModel } from '@fastgpt/service/core/ai/model';
|
||||
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/*
|
||||
1. get tts from chatItem store
|
||||
2. get tts from ai
|
||||
4. push bill
|
||||
*/
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: ApiRequestProps<GetChatSpeechProps>, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { ttsConfig, input } = req.body as GetChatSpeechProps;
|
||||
const { ttsConfig, input } = req.body;
|
||||
|
||||
if (!ttsConfig.model || !ttsConfig.voice) {
|
||||
throw new Error('model or voice not found');
|
||||
}
|
||||
|
||||
const { teamId, tmbId, authType } = await authChatCert({
|
||||
const { teamId, tmbId, authType } = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
const ttsModel = getAudioSpeechModel(ttsConfig.model);
|
||||
@@ -90,3 +92,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -16,11 +16,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
|
||||
|
||||
// auth link permission
|
||||
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
const { outLinkConfig, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
// auth app permission
|
||||
const [tmb, chat, app] = await Promise.all([
|
||||
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoTeamMember.findById(outLinkConfig.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoChat.findOne({ appId, chatId, shareId }).lean(),
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
@@ -35,7 +35,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
// pick share response field
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -66,9 +65,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||
@@ -12,12 +12,13 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
|
||||
async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResponse) {
|
||||
let { teamId, appId, chatId, teamToken } = req.query;
|
||||
|
||||
if (!teamId || !appId || !teamToken) {
|
||||
throw new Error('teamId, appId, teamToken are required');
|
||||
return Promise.reject('teamId, appId, teamToken are required');
|
||||
}
|
||||
|
||||
const { uid } = await authTeamSpaceToken({
|
||||
@@ -32,7 +33,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
]);
|
||||
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
return Promise.reject(AppErrEnum.unExist);
|
||||
}
|
||||
|
||||
// auth chat permission
|
||||
@@ -43,8 +44,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// get app and history
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
|
||||
// pick share response field
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
@@ -74,9 +73,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): CreateCo
|
||||
// 2. upload file
|
||||
const fileId = await uploadFile({
|
||||
teamId,
|
||||
tmbId,
|
||||
uid: tmbId,
|
||||
bucketName,
|
||||
path: file.path,
|
||||
filename: file.originalname,
|
||||
|
||||
@@ -5,40 +5,142 @@ import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant
|
||||
import { createFileToken } from '@fastgpt/service/support/permission/controller';
|
||||
import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
|
||||
|
||||
export type readCollectionSourceQuery = {};
|
||||
|
||||
export type readCollectionSourceBody = {
|
||||
collectionId: string;
|
||||
} & ShareChatAuthProps;
|
||||
|
||||
appId?: string;
|
||||
chatId?: string;
|
||||
chatItemId?: string;
|
||||
} & OutLinkChatAuthProps;
|
||||
|
||||
export type readCollectionSourceResponse = {
|
||||
type: 'url';
|
||||
value: string;
|
||||
};
|
||||
|
||||
const authCollectionInChat = async ({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId
|
||||
}: {
|
||||
collectionId: string;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
chatItemId: string;
|
||||
}) => {
|
||||
try {
|
||||
const chatItem = (await MongoChatItem.findOne(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId: chatItemId
|
||||
},
|
||||
'responseData'
|
||||
).lean()) as AIChatItemType;
|
||||
|
||||
if (!chatItem) return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
|
||||
// 找 responseData 里,是否有该文档 id
|
||||
const responseData = chatItem.responseData || [];
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
responseData
|
||||
?.map((item) => {
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
|
||||
if (
|
||||
flatResData.some((item) => {
|
||||
if (item.quoteList) {
|
||||
return item.quoteList.some((quote) => quote.collectionId === collectionId);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {}
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<readCollectionSourceBody, readCollectionSourceQuery>
|
||||
): Promise<readCollectionSourceResponse> {
|
||||
const { collection, teamId, tmbId } = await authDatasetCollection({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
collectionId: req.body.collectionId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
const { collectionId, appId, chatId, chatItemId, shareId, outLinkUid, teamId, teamToken } =
|
||||
req.body;
|
||||
|
||||
const {
|
||||
collection,
|
||||
teamId: userTeamId,
|
||||
tmbId: uid,
|
||||
authType
|
||||
} = await (async () => {
|
||||
if (!appId || !chatId || !chatItemId) {
|
||||
return authDatasetCollection({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
collectionId: req.body.collectionId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
1. auth chat read permission
|
||||
2. auth collection quote in chat
|
||||
3. auth outlink open show quote
|
||||
*/
|
||||
const [authRes, collection] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
getCollectionWithDataset(collectionId),
|
||||
authCollectionInChat({ appId, chatId, chatItemId, collectionId })
|
||||
]);
|
||||
|
||||
if (!authRes.showRawSource) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
}
|
||||
|
||||
return {
|
||||
...authRes,
|
||||
collection
|
||||
};
|
||||
})();
|
||||
|
||||
const sourceUrl = await (async () => {
|
||||
if (collection.type === DatasetCollectionTypeEnum.file && collection.fileId) {
|
||||
const token = await createFileToken({
|
||||
bucketName: BucketNameEnum.dataset,
|
||||
teamId,
|
||||
tmbId,
|
||||
fileId: collection.fileId
|
||||
teamId: userTeamId,
|
||||
uid,
|
||||
fileId: collection.fileId,
|
||||
customExpireMinutes: authType === 'outLink' ? 5 : undefined
|
||||
});
|
||||
|
||||
return `${ReadFileBaseUrl}?token=${token}`;
|
||||
return `${ReadFileBaseUrl}/${collection.name}?token=${token}`;
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.link && collection.rawLink) {
|
||||
return collection.rawLink;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
|
||||
export type PostPreviewFilesChunksProps = {
|
||||
type: DatasetSourceReadTypeEnum;
|
||||
@@ -35,7 +35,7 @@ async function handler(
|
||||
|
||||
const { teamId } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
return authFile({
|
||||
return authCollectionFile({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
@@ -30,7 +30,7 @@ async function handler(
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: OwnerPermissionVal });
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: ManagePermissionVal });
|
||||
|
||||
await MongoOutLink.findByIdAndUpdate(_id, {
|
||||
name,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getUploadModel } from '@fastgpt/service/common/file/multer';
|
||||
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
|
||||
import fs from 'fs';
|
||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
||||
@@ -27,6 +27,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
>(req, res);
|
||||
|
||||
req.body.appId = appId;
|
||||
req.body.shareId = shareId;
|
||||
req.body.outLinkUid = outLinkUid;
|
||||
req.body.teamId = spaceTeamId;
|
||||
@@ -43,7 +44,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// auth role
|
||||
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authChatCrud({ req, authToken: true, ...req.body });
|
||||
|
||||
// auth app
|
||||
// const app = await MongoApp.findById(appId, 'modules').lean();
|
||||
|
||||
@@ -86,7 +86,7 @@ type AuthResponseType = {
|
||||
showNodeStatus?: boolean;
|
||||
authType: `${AuthUserTypeEnum}`;
|
||||
apikey?: string;
|
||||
canWrite: boolean;
|
||||
responseAllData: boolean;
|
||||
outLinkUserId?: string;
|
||||
sourceName?: string;
|
||||
};
|
||||
@@ -160,7 +160,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
authType,
|
||||
sourceName,
|
||||
apikey,
|
||||
canWrite,
|
||||
responseAllData,
|
||||
outLinkUserId = customUid,
|
||||
showNodeStatus
|
||||
} = await (async () => {
|
||||
@@ -328,12 +328,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await updateInteractiveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables,
|
||||
newTitle
|
||||
newVariables
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
@@ -361,7 +358,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
|
||||
|
||||
/* select fe response field */
|
||||
const feResponseData = canWrite
|
||||
const feResponseData = responseAllData
|
||||
? flowResponses
|
||||
: filterPublicNodeResponseData({ flowResponses, responseDetail });
|
||||
|
||||
@@ -482,10 +479,10 @@ const authShareChat = async ({
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
authType,
|
||||
canWrite: false,
|
||||
responseAllData: false,
|
||||
responseDetail,
|
||||
outLinkUserId: uid,
|
||||
showNodeStatus
|
||||
};
|
||||
@@ -525,10 +522,10 @@ const authTeamSpaceChat = async ({
|
||||
tmbId: app.tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: true,
|
||||
authType: AuthUserTypeEnum.outLink,
|
||||
apikey: '',
|
||||
canWrite: false,
|
||||
responseAllData: false,
|
||||
responseDetail: true,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
};
|
||||
@@ -610,11 +607,11 @@ const authHeaderRequest = async ({
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: true,
|
||||
apikey,
|
||||
authType,
|
||||
sourceName,
|
||||
canWrite: true
|
||||
responseAllData: true,
|
||||
responseDetail: true
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, Box, useTheme } from '@chakra-ui/react';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
@@ -14,53 +12,52 @@ import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/Plugin
|
||||
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { PcHeader } from '@/pages/chat/components/ChatHeader';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider, {
|
||||
ChatRecordContext
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
|
||||
const DetailLogsModal = ({
|
||||
appId,
|
||||
chatId,
|
||||
onClose
|
||||
}: {
|
||||
type Props = {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
const theme = useTheme();
|
||||
|
||||
const params = useMemo(() => {
|
||||
return {
|
||||
chatId,
|
||||
appId,
|
||||
loadCustomFeedbacks: true,
|
||||
type: GetChatTypeEnum.normal
|
||||
};
|
||||
}, [appId, chatId]);
|
||||
const {
|
||||
ChatBoxRef,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetVariables,
|
||||
chatRecords,
|
||||
ScrollData,
|
||||
setChatRecords,
|
||||
totalRecordsCount
|
||||
} = useChat(params);
|
||||
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
|
||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
|
||||
const { data: chat, isFetching } = useQuery(
|
||||
['getChatDetail', chatId],
|
||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
|
||||
const { data: chat, loading: isFetching } = useRequest2(
|
||||
async () => {
|
||||
const res = await getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true });
|
||||
res.userAvatar = HUMAN_ICON;
|
||||
|
||||
setChatBoxData(res);
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
{
|
||||
onSuccess(res) {
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
});
|
||||
manual: false,
|
||||
refreshDeps: [chatId],
|
||||
onError(e) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -68,12 +65,11 @@ const DetailLogsModal = ({
|
||||
const title = chat?.title;
|
||||
const chatModels = chat?.app?.chatModels;
|
||||
const isPlugin = chat?.app.type === AppTypeEnum.plugin;
|
||||
const loading = isFetching;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
isLoading={isFetching}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
zIndex={3}
|
||||
@@ -124,7 +120,7 @@ const DetailLogsModal = ({
|
||||
alignItems={'center'}
|
||||
px={[3, 5]}
|
||||
h={['46px', '60px']}
|
||||
borderBottom={theme.borders.base}
|
||||
borderBottom={'base'}
|
||||
borderBottomColor={'gray.200'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
@@ -154,32 +150,15 @@ const DetailLogsModal = ({
|
||||
<Box pt={2} flex={'1 0 0'}>
|
||||
{isPlugin ? (
|
||||
<Box px={5} pt={2} h={'100%'}>
|
||||
<PluginRunBox
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
pluginInputs={chat?.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chat.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
/>
|
||||
<PluginRunBox appId={appId} chatId={chatId} />
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
ScrollData={ScrollData}
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appAvatar={chat?.app.avatar}
|
||||
userAvatar={HUMAN_ICON}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
chatType="log"
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
@@ -191,4 +170,24 @@ const DetailLogsModal = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DetailLogsModal;
|
||||
|
||||
const Render = (props: Props) => {
|
||||
const { appId, chatId } = props;
|
||||
const params = useMemo(() => {
|
||||
return {
|
||||
chatId,
|
||||
appId,
|
||||
loadCustomFeedbacks: true,
|
||||
type: GetChatTypeEnum.normal
|
||||
};
|
||||
}, [appId, chatId]);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={params}>
|
||||
<DetailLogsModal {...props} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
export default Render;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { fileToBase64 } from '@/web/common/file/utils';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { subRoute } from '@fastgpt/web/common/system/utils';
|
||||
|
||||
enum UsingWayEnum {
|
||||
link = 'link',
|
||||
@@ -74,7 +75,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
|
||||
});
|
||||
|
||||
const baseUrl = feConfigs?.customSharePageDomain || location?.origin;
|
||||
const linkUrl = `${baseUrl}/chat/share?shareId=${share?.shareId}${
|
||||
const linkUrl = `${baseUrl}${subRoute ? `${subRoute}/` : '/'}chat/share?shareId=${share?.shareId}${
|
||||
getValues('showHistory') ? '' : '&showHistory=0'
|
||||
}`;
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ function EditLinkModal({
|
||||
</Flex>
|
||||
<Switch {...register('responseDetail')} isChecked={responseDetail} />
|
||||
</Flex>
|
||||
{/* <Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.show_complete_quote')}</FormLabel>
|
||||
<QuestionTip
|
||||
@@ -431,8 +431,17 @@ function EditLinkModal({
|
||||
label={t('common:support.outlink.share.show_complete_quote_tips' || '')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch {...register('showRawSource')} isChecked={showRawSource} />
|
||||
</Flex> */}
|
||||
<Switch
|
||||
{...register('showRawSource', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={showRawSource}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { AppSchema, AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -19,21 +19,25 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postTransition2Workflow } from '@/web/core/app/api/app';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
import { SimpleAppSnapshotType } from './useSnapshots';
|
||||
|
||||
const AppCard = () => {
|
||||
const AppCard = ({
|
||||
appForm,
|
||||
setPast
|
||||
}: {
|
||||
appForm: AppSimpleEditFormType;
|
||||
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { appDetail, setAppDetail, onOpenInfoEdit, onDelApp } = useContextSelector(
|
||||
AppContext,
|
||||
(v) => v
|
||||
);
|
||||
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const onOpenInfoEdit = useContextSelector(AppContext, (v) => v.onOpenInfoEdit);
|
||||
const onDelApp = useContextSelector(AppContext, (v) => v.onDelApp);
|
||||
|
||||
const appId = appDetail._id;
|
||||
const { feConfigs } = useSystemStore();
|
||||
@@ -42,7 +46,18 @@ const AppCard = () => {
|
||||
// transition to workflow
|
||||
const [transitionCreateNew, setTransitionCreateNew] = useState<boolean>();
|
||||
const { runAsync: onTransition, loading: transiting } = useRequest2(
|
||||
() => postTransition2Workflow({ appId, createNew: transitionCreateNew }),
|
||||
async () => {
|
||||
const { nodes, edges } = form2AppWorkflow(appForm, t);
|
||||
await onSaveApp({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appForm.chatConfig,
|
||||
isPublish: false,
|
||||
versionName: t('app:transition_to_workflow')
|
||||
});
|
||||
|
||||
return postTransition2Workflow({ appId, createNew: transitionCreateNew });
|
||||
},
|
||||
{
|
||||
onSuccess: ({ id }) => {
|
||||
if (id) {
|
||||
@@ -52,10 +67,8 @@ const AppCard = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
type: AppTypeEnum.workflow
|
||||
}));
|
||||
setPast([]);
|
||||
router.reload();
|
||||
}
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
@@ -118,7 +131,7 @@ const AppCard = () => {
|
||||
children: [
|
||||
{
|
||||
icon: 'core/app/type/workflow',
|
||||
label: appT('transition_to_workflow'),
|
||||
label: t('app:transition_to_workflow'),
|
||||
onClick: () => setTransitionCreateNew(true)
|
||||
},
|
||||
...(appDetail.permission.hasWritePer && feConfigs?.show_team_chat
|
||||
@@ -159,15 +172,15 @@ const AppCard = () => {
|
||||
</Box>
|
||||
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
|
||||
{transitionCreateNew !== undefined && (
|
||||
<MyModal isOpen title={appT('transition_to_workflow')} iconSrc="core/app/type/workflow">
|
||||
<MyModal isOpen title={t('app:transition_to_workflow')} iconSrc="core/app/type/workflow">
|
||||
<ModalBody>
|
||||
<Box mb={3}>{appT('transition_to_workflow_create_new_tip')}</Box>
|
||||
<Box mb={3}>{t('app:transition_to_workflow_create_new_tip')}</Box>
|
||||
<HStack cursor={'pointer'} onClick={() => setTransitionCreateNew((state) => !state)}>
|
||||
<Checkbox
|
||||
isChecked={transitionCreateNew}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
/>
|
||||
<Box>{appT('transition_to_workflow_create_new_placeholder')}</Box>
|
||||
<Box>{t('app:transition_to_workflow_create_new_placeholder')}</Box>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
@@ -12,8 +12,13 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../context';
|
||||
import { useChatTest } from '../useChatTest';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import ChatItemContextProvider from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
type Props = { appForm: AppSimpleEditFormType };
|
||||
const ChatTest = ({ appForm }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
@@ -32,13 +37,21 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
setWorkflowData({ nodes, edges });
|
||||
}, [appForm, setWorkflowData, allDatasets, t]);
|
||||
|
||||
const { restartChat, ChatContainer } = useChatTest({
|
||||
const { ChatContainer, restartChat, loading } = useChatTest({
|
||||
...workflowData,
|
||||
chatConfig: appForm.chatConfig
|
||||
chatConfig: appForm.chatConfig,
|
||||
isReady: true
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex position={'relative'} flexDirection={'column'} h={'100%'} py={4}>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
display={'flex'}
|
||||
position={'relative'}
|
||||
flexDirection={'column'}
|
||||
h={'100%'}
|
||||
py={4}
|
||||
>
|
||||
<Flex px={[2, 5]}>
|
||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'} flex={1} color={'myGray.900'}>
|
||||
{appT('chat_debug')}
|
||||
@@ -61,8 +74,29 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
<Box flex={1}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatTest);
|
||||
const Render = ({ appForm }: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const chatRecordProviderParams = useMemo(
|
||||
() => ({
|
||||
chatId: chatId,
|
||||
appId: appDetail._id
|
||||
}),
|
||||
[appDetail._id, chatId]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest appForm={appForm} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Render);
|
||||
|
||||
@@ -132,7 +132,7 @@ const Edit = ({
|
||||
flex={'1'}
|
||||
>
|
||||
<Box {...cardStyles} boxShadow={'2'}>
|
||||
<AppCard />
|
||||
<AppCard appForm={appForm} setPast={setPast} />
|
||||
</Box>
|
||||
|
||||
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
|
||||
|
||||
@@ -87,7 +87,6 @@ const Header = ({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appForm.chatConfig,
|
||||
type: AppTypeEnum.simple,
|
||||
isPublish,
|
||||
versionName
|
||||
});
|
||||
|
||||
@@ -37,7 +37,8 @@ export const compareSimpleAppSnapshot = (
|
||||
scheduledTriggerConfig: appForm1.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: appForm1.chatConfig?.fileSelectConfig || undefined,
|
||||
instruction: appForm1.chatConfig?.instruction || ''
|
||||
instruction: appForm1.chatConfig?.instruction || '',
|
||||
autoExecute: appForm1.chatConfig?.autoExecute || undefined
|
||||
},
|
||||
{
|
||||
welcomeText: appForm2.chatConfig?.welcomeText || '',
|
||||
@@ -48,7 +49,8 @@ export const compareSimpleAppSnapshot = (
|
||||
scheduledTriggerConfig: appForm2.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: appForm2.chatConfig?.fileSelectConfig || undefined,
|
||||
instruction: appForm2.chatConfig?.instruction || ''
|
||||
instruction: appForm2.chatConfig?.instruction || '',
|
||||
autoExecute: appForm2.chatConfig?.autoExecute || undefined
|
||||
}
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -147,7 +147,6 @@ const AppCard = ({
|
||||
appDetail.name,
|
||||
appDetail.permission.hasWritePer,
|
||||
appDetail.permission.isOwner,
|
||||
currentTab,
|
||||
feConfigs?.show_team_chat,
|
||||
onDelApp,
|
||||
onOpenImport,
|
||||
@@ -252,10 +251,6 @@ function ExportPopover({
|
||||
trigger={'hover'}
|
||||
w={'8.6rem'}
|
||||
Trigger={
|
||||
// <Flex align={'center'} w={'100%'} py={2} px={3}>
|
||||
// <Avatar src={'export'} borderRadius={'sm'} w={'1rem'} mr={3} />
|
||||
// {t('app:export_configs')}
|
||||
// </Flex>
|
||||
<MyBox display={'flex'} size={'md'} rounded={'4px'} cursor={'pointer'}>
|
||||
<MyIcon name={'export'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -14,27 +14,34 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider, {
|
||||
ChatRecordContext
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ChatTest = ({
|
||||
isOpen,
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onClose
|
||||
}: {
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
nodes?: StoreNodeItemType[];
|
||||
edges?: StoreEdgeItemType[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const isPlugin = appDetail.type === AppTypeEnum.plugin;
|
||||
|
||||
const { restartChat, ChatContainer, pluginRunTab, setPluginRunTab, chatRecords } = useChatTest({
|
||||
const { restartChat, ChatContainer, loading } = useChatTest({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
chatConfig: appDetail.chatConfig,
|
||||
isReady: isOpen
|
||||
});
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -48,8 +55,10 @@ const ChatTest = ({
|
||||
right={0}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Flex
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
zIndex={300}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
@@ -131,9 +140,30 @@ const ChatTest = ({
|
||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatTest);
|
||||
const Render = (Props: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const chatRecordProviderParams = useMemo(
|
||||
() => ({
|
||||
chatId: chatId,
|
||||
appId: appDetail._id
|
||||
}),
|
||||
[appDetail._id, chatId]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest {...Props} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Render);
|
||||
|
||||
@@ -240,7 +240,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
position={'absolute'}
|
||||
top={'10px'}
|
||||
left={0}
|
||||
pt={'20px'}
|
||||
pt={5}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 20px)' : '0'}
|
||||
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
|
||||
@@ -254,7 +254,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
{/* Header */}
|
||||
<Box px={'5'} mb={3} whiteSpace={'nowrap'} overflow={'hidden'}>
|
||||
{/* Tabs */}
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={2}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<FillRowTabs
|
||||
list={[
|
||||
@@ -288,8 +288,14 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
{/* close icon */}
|
||||
<IconButton
|
||||
size={'sm'}
|
||||
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
|
||||
borderColor={'myGray.300'}
|
||||
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.600'} />}
|
||||
bg={'myGray.100'}
|
||||
_hover={{
|
||||
bg: 'myGray.200',
|
||||
'& svg': {
|
||||
color: 'primary.600'
|
||||
}
|
||||
}}
|
||||
variant={'grayBase'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
|
||||
@@ -177,20 +177,15 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
position={'absolute'}
|
||||
transform={arrowTransform}
|
||||
pointerEvents={'all'}
|
||||
w={highlightEdge ? '14px' : '10px'}
|
||||
h={highlightEdge ? '14px' : '10px'}
|
||||
w={highlightEdge ? '12px' : '10px'}
|
||||
h={highlightEdge ? '12px' : '10px'}
|
||||
zIndex={highlightEdge ? defaultZIndex + 1000 : defaultZIndex}
|
||||
>
|
||||
<MyIcon
|
||||
name={'core/workflow/edgeArrow'}
|
||||
name={highlightEdge ? 'core/workflow/edgeArrowBold' : 'core/workflow/edgeArrow'}
|
||||
w={'100%'}
|
||||
color={edgeColor}
|
||||
{...(highlightEdge
|
||||
? {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {})}
|
||||
></MyIcon>
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
@@ -223,7 +218,7 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
...style,
|
||||
...(highlightEdge
|
||||
? {
|
||||
strokeWidth: 5
|
||||
strokeWidth: 4
|
||||
}
|
||||
: { strokeWidth: 3, zIndex: 2 })
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
@@ -333,7 +333,7 @@ export const useDebug = () => {
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<Box position={'relative'}>
|
||||
{required && (
|
||||
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
|
||||
<Box position={'absolute'} left={'-8px'} top={'-2px'} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -36,8 +36,8 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return {
|
||||
[NodeInputKeyEnum.code]: (item: FlowNodeInputItemType) => {
|
||||
return (
|
||||
<Box>
|
||||
<Flex mb={1} alignItems={'flex-end'}>
|
||||
<Box mt={-3}>
|
||||
<Flex mb={2} alignItems={'flex-end'}>
|
||||
<Box flex={'1'}>{'Javascript ' + workflowT('Code')}</Box>
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
@@ -88,7 +88,7 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
</>
|
||||
)}
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
<IOTitle text={t('common:common.Input')} mb={-1} />
|
||||
<RenderInput
|
||||
nodeId={nodeId}
|
||||
flowInputList={commonInputs}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const NodeComment = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs } = data;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import IOTitle from '../../components/IOTitle';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const { inputs, outputs, nodeId } = data;
|
||||
@@ -53,14 +54,15 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
}: Omit<FlowNodeInputItemType, 'value'> & {
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
<Box mt={-2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
<Box flex={'1 0 0'} fontSize={'sm'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
{t('common:core.module.extract.Target field')}
|
||||
</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'ghost'}
|
||||
variant={'grayGhost'}
|
||||
px={2}
|
||||
color={'myGray.600'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
@@ -68,48 +70,47 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
{t('common:core.module.extract.Add field')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderRadius={'none !important'}>{t('common:item_name')}</Th>
|
||||
<Th>{t('common:item_description')}</Th>
|
||||
<Th>{t('common:required')}</Th>
|
||||
<Th borderRadius={'none !important'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
|
||||
<TableContainer borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} mt={2}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common:item_name')}</Th>
|
||||
<Th>{t('common:item_description')}</Th>
|
||||
<Th>{t('common:required')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>
|
||||
{item.required ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => {
|
||||
setEditExtractField(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -128,13 +129,13 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
Tr
|
||||
} from '@chakra-ui/react';
|
||||
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
FlowNodeInputMap,
|
||||
FlowNodeInputTypeEnum,
|
||||
@@ -37,6 +36,8 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import InputFormEditModal, { defaultFormInput } from './InputFormEditModal';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
@@ -120,10 +121,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Box>
|
||||
<HStack className="nodrag" cursor={'default'} mb={3}>
|
||||
<FormLabel>{t('workflow:user_form_input_config')}</FormLabel>
|
||||
<FormLabel fontSize={'sm'} color={'myGray.600'}>
|
||||
{t('workflow:user_form_input_config')}
|
||||
</FormLabel>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant={'grayGhost'}
|
||||
px={2}
|
||||
color={'myGray.600'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
@@ -144,17 +149,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<TableContainer borderWidth={'1px'} borderRadius={'md'} borderBottom="none">
|
||||
<Table bg={'white'}>
|
||||
<TableContainer borderWidth={'1px'} borderRadius={'md'}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('workflow:user_form_input_name')}
|
||||
</Th>
|
||||
<Th>{t('workflow:user_form_input_name')}</Th>
|
||||
<Th>{t('workflow:user_form_input_description')}</Th>
|
||||
<Th>{t('common:common.Require Input')}</Th>
|
||||
<Th borderBottomRightRadius={'none !important'}>{t('user:operations')}</Th>
|
||||
<Th>{t('user:operations')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -163,35 +165,35 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Tr key={index}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} fontSize={'mini'} fontWeight={'medium'}>
|
||||
{!!icon && (
|
||||
<MyIcon name={icon as any} w={'14px'} mr={1} color={'primary.600'} />
|
||||
<MyIcon name={icon as any} w={'14px'} mr={1} color={'myGray.400'} />
|
||||
)}
|
||||
{item.label}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.description}</Td>
|
||||
<Td>{item.required ? '✅' : ''}</Td>
|
||||
<Td>{item.description || '-'}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete(item.key);
|
||||
}}
|
||||
/>
|
||||
{item.required ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => onDelete(item.key)}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,6 @@ import {
|
||||
FormControl,
|
||||
HStack,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Stack,
|
||||
Switch,
|
||||
Textarea
|
||||
@@ -28,7 +23,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
|
||||
@@ -100,6 +100,7 @@ const PluginOutputEditModal = ({
|
||||
|
||||
data.key = data?.key?.trim();
|
||||
data.label = data.key;
|
||||
data.required = true;
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const VariableTable = ({
|
||||
variables = [],
|
||||
@@ -16,58 +17,61 @@ const VariableTable = ({
|
||||
const showToolColumn = variables.some((item) => item.isTool);
|
||||
|
||||
return (
|
||||
<Box bg={'white'} borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
|
||||
<Th borderBottomRightRadius={'none !important'}></Th>
|
||||
<TableContainer
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
|
||||
<Th>{t('user:operations')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
{!!item.icon ? (
|
||||
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
) : (
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
)}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
{showToolColumn && (
|
||||
<Td>
|
||||
{item.isTool ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Td>
|
||||
)}
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton icon={'common/settingLight'} onClick={() => onEdit(item.key)} />
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => onDelete(item.key)}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{!!item.icon && (
|
||||
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'primary.600'} />
|
||||
)}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
{showToolColumn && <Th>{item.isTool ? '✅' : '-'}</Th>}
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => onEdit(item.key)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete(item.key);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import FileSelect from '@/components/core/app/FileSelect';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { userFilesInput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
import Container from '../components/Container';
|
||||
import AutoExecConfig from '@/components/core/app/AutoExecConfig';
|
||||
|
||||
type ComponentProps = {
|
||||
chatConfig: AppChatConfigType;
|
||||
@@ -80,6 +81,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Box mt={4} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<ScheduledTrigger {...componentsProps} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<AutoExecute {...componentsProps} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<QuestionInputGuide {...componentsProps} />
|
||||
</Box>
|
||||
@@ -128,6 +132,23 @@ function ChatStartVariable({ chatConfig: { variables = [] }, setAppDetail }: Com
|
||||
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
||||
}
|
||||
|
||||
function AutoExecute({ chatConfig: { autoExecute }, setAppDetail }: ComponentProps) {
|
||||
return (
|
||||
<AutoExecConfig
|
||||
value={autoExecute}
|
||||
onChange={(e) =>
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
chatConfig: {
|
||||
...state.chatConfig,
|
||||
autoExecute: e
|
||||
}
|
||||
}))
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function QuestionGuide({ chatConfig: { questionGuide = false }, setAppDetail }: ComponentProps) {
|
||||
return (
|
||||
<QGSwitch
|
||||
|
||||
@@ -5,7 +5,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Thead,
|
||||
Tr
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
|
||||
@@ -36,7 +36,7 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<NodeCard selected={selected} {...data}>
|
||||
<Container>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mb={1.5}>
|
||||
<FormLabel>{t('workflow:tool_custom_field')}</FormLabel>
|
||||
<FormLabel fontSize={'sm'}>{t('workflow:tool_custom_field')}</FormLabel>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
@@ -54,38 +54,89 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:tool_params.params_name')}</Th>
|
||||
<Th>{t('workflow:tool_params.params_description')}</Th>
|
||||
<Th>{t('common:common.Operation')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
<TableContainer
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr h={8}>
|
||||
<Th p={0} px={4} bg={'myGray.50'} borderBottomLeftRadius={'none !important'}>
|
||||
{t('workflow:tool_params.params_name')}
|
||||
</Th>
|
||||
<Th p={0} px={4} bg={'myGray.50'}>
|
||||
{t('workflow:tool_params.params_description')}
|
||||
</Th>
|
||||
<Th p={0} px={4} bg={'myGray.50'} borderBottomRightRadius={'none !important'}>
|
||||
{t('common:common.Operation')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
h={10}
|
||||
>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.toolDescription}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{item.toolDescription}
|
||||
</Td>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
whiteSpace={'nowrap'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
p={1}
|
||||
color={'myGray.500'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
bg={'transparent'}
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
>
|
||||
<MyIcon name={'common/settingLight'} w={'16px'} />
|
||||
</Flex>
|
||||
<Flex
|
||||
p={1}
|
||||
color={'myGray.500'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
bg={'transparent'}
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -98,14 +149,16 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
>
|
||||
<MyIcon name={'delete'} w={'16px'} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
<Box position={'relative'}>
|
||||
<Box mb={-4} borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
|
||||
<Box mb={-3} borderBottomRadius={'lg'} overflow={'hidden'}>
|
||||
<Divider
|
||||
showBorderBottom={false}
|
||||
icon={<MyIcon name="phoneTabbar/tool" w={'16px'} h={'16px'} />}
|
||||
|
||||
@@ -123,7 +123,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
return (
|
||||
<Container key={index} w={'full'} mx={0}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex w={'60px'}>{t('common:core.workflow.variable')}</Flex>
|
||||
<Flex w={'80px'}>{t('common:core.workflow.variable')}</Flex>
|
||||
<VariableSelector
|
||||
nodeId={nodeId}
|
||||
variable={updateItem.variable}
|
||||
@@ -162,7 +162,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex mt={2} w={'full'} alignItems={'center'} className="nodrag">
|
||||
<Flex w={'60px'}>
|
||||
<Flex w={'80px'}>
|
||||
<Box>{t('common:core.workflow.value')}</Box>
|
||||
<MyTooltip
|
||||
label={
|
||||
|
||||
@@ -53,6 +53,8 @@ export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
|
||||
w={handleSize}
|
||||
h={handleSize}
|
||||
border={'4px solid #8774EE'}
|
||||
rounded={'xs'}
|
||||
bg={'white'}
|
||||
transform={'translate(0,0) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
@@ -105,6 +107,8 @@ export const ToolSourceHandle = () => {
|
||||
w={handleSize}
|
||||
h={handleSize}
|
||||
border={'4px solid #8774EE'}
|
||||
rounded={'xs'}
|
||||
bg={'white'}
|
||||
transform={'translate(0,0) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
|
||||
@@ -20,7 +20,6 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||
@@ -29,6 +28,7 @@ import { getDocPath } from '@/web/common/system/doc';
|
||||
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
|
||||
import { WorkflowEventContext } from '../../../context/workflowEventContext';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
type Props = FlowNodeItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -166,7 +166,7 @@ const NodeCard = (props: Props) => {
|
||||
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
|
||||
|
||||
{/* avatar and name */}
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} mb={intro ? 1 : 0}>
|
||||
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -202,15 +202,13 @@ const NodeCard = (props: Props) => {
|
||||
<Box ml={2} fontSize={'18px'} fontWeight={'medium'} color={'myGray.900'}>
|
||||
{t(name as any)}
|
||||
</Box>
|
||||
<MyIcon
|
||||
className="controller-rename"
|
||||
<Button
|
||||
display={'none'}
|
||||
name={'edit'}
|
||||
w={'14px'}
|
||||
variant={'grayGhost'}
|
||||
size={'xs'}
|
||||
ml={0.5}
|
||||
className="controller-rename"
|
||||
cursor={'pointer'}
|
||||
ml={1}
|
||||
color={'myGray.500'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => {
|
||||
onOpenCustomTitleModal({
|
||||
defaultVal: name,
|
||||
@@ -230,7 +228,9 @@ const NodeCard = (props: Props) => {
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<MyIcon name={'edit'} w={'14px'} />
|
||||
</Button>
|
||||
<Box flex={1} />
|
||||
{hasNewVersion && (
|
||||
<MyTooltip label={t('app:app.modules.click to update')}>
|
||||
@@ -248,7 +248,7 @@ const NodeCard = (props: Props) => {
|
||||
onClick={onOpenConfirmSync(onClickSyncVersion)}
|
||||
>
|
||||
<Box>{t('app:app.modules.has new version')}</Box>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
<MyIcon name={'help'} w={'14px'} ml={1} />
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
)}
|
||||
@@ -263,32 +263,20 @@ const NodeCard = (props: Props) => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
fontSize={'sm'}
|
||||
color={'primary.700'}
|
||||
p={1}
|
||||
rounded={'sm'}
|
||||
cursor={'default'}
|
||||
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
>
|
||||
<Button variant={'grayGhost'} size={'xs'} color={'primary.600'} px={1}>
|
||||
{t('common:core.module.Diagram')}
|
||||
</Box>
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{!!nodeTemplate?.diagram && node?.courseUrl && (
|
||||
<Box bg={'myGray.300'} w={'1px'} h={'12px'} mx={1} />
|
||||
<Box bg={'myGray.300'} w={'1px'} h={'12px'} ml={1} mr={0.5} />
|
||||
)}
|
||||
{node?.courseUrl && !hasNewVersion && (
|
||||
<MyTooltip label={t('workflow:Node.Open_Node_Course')}>
|
||||
<MyIcon
|
||||
cursor={'pointer'}
|
||||
name="book"
|
||||
color={'primary.600'}
|
||||
w={'18px'}
|
||||
<MyIconButton
|
||||
ml={1}
|
||||
_hover={{
|
||||
color: 'primary.800'
|
||||
}}
|
||||
icon="book"
|
||||
color={'primary.600'}
|
||||
onClick={() => window.open(getDocPath(node.courseUrl || ''), '_blank')}
|
||||
/>
|
||||
</MyTooltip>
|
||||
@@ -378,7 +366,7 @@ const NodeCard = (props: Props) => {
|
||||
>
|
||||
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
|
||||
{Header}
|
||||
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 4 : 0} gap={2}>
|
||||
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 3 : 0} gap={2}>
|
||||
{!isFolded ? children : <Box h={4} />}
|
||||
</Flex>
|
||||
{RenderHandle}
|
||||
@@ -521,7 +509,7 @@ const MenuRender = React.memo(function MenuRender({
|
||||
className="nodrag controller-menu"
|
||||
display={'none'}
|
||||
flexDirection={'column'}
|
||||
gap={3}
|
||||
gap={2}
|
||||
position={'absolute'}
|
||||
top={'-20px'}
|
||||
right={0}
|
||||
@@ -532,16 +520,18 @@ const MenuRender = React.memo(function MenuRender({
|
||||
pt={'20px'}
|
||||
>
|
||||
{menuList.map((item) => (
|
||||
<Box key={item.icon}>
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={item.variant}
|
||||
leftIcon={<MyIcon name={item.icon as any} w={'13px'} />}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{t(item.label as any)}
|
||||
</Button>
|
||||
</Box>
|
||||
<Button
|
||||
key={item.icon}
|
||||
h={8}
|
||||
fontSize={'sm'}
|
||||
pl={2}
|
||||
pr={6}
|
||||
variant={item.variant}
|
||||
leftIcon={<MyIcon name={item.icon as any} w={'16px'} mr={-1} />}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{t(item.label as any)}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
<DebugInputModal />
|
||||
@@ -592,31 +582,32 @@ const NodeIntro = React.memo(function NodeIntro({
|
||||
<Box fontSize={'sm'} color={'myGray.500'} flex={'1 0 0'}>
|
||||
{t(intro as any)}
|
||||
</Box>
|
||||
<Flex
|
||||
p={'7px'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
_hover={{
|
||||
bg: NodeIsTool ? 'myGray.100' : 'transparent'
|
||||
}}
|
||||
cursor={NodeIsTool ? 'pointer' : 'default'}
|
||||
onClick={() => {
|
||||
if (!NodeIsTool) return;
|
||||
onOpenIntroModal({
|
||||
defaultVal: intro,
|
||||
onSuccess(e) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'intro',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'edit'} w={'18px'} opacity={NodeIsTool ? 1 : 0} />
|
||||
</Flex>
|
||||
{NodeIsTool && (
|
||||
<Flex
|
||||
p={'7px'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
_hover={{
|
||||
bg: 'myGray.100'
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
onOpenIntroModal({
|
||||
defaultVal: intro,
|
||||
onSuccess(e) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'attr',
|
||||
key: 'intro',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'edit'} w={'18px'} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
<EditIntroModal maxLength={500} />
|
||||
</>
|
||||
|
||||
@@ -115,7 +115,6 @@ function Reference({
|
||||
content: workflowT('confirm_delete_field_tip')
|
||||
});
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const keys = useMemo(() => {
|
||||
return inputs.map((input) => input.key);
|
||||
@@ -199,7 +198,7 @@ function Reference({
|
||||
/>
|
||||
|
||||
<MyIcon
|
||||
className="delete"
|
||||
className={'delete'}
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
|
||||
@@ -69,7 +69,7 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
|
||||
w={'100%'}
|
||||
>
|
||||
<Button
|
||||
h={'36px'}
|
||||
h={10}
|
||||
leftIcon={<MyIcon name={'common/selectLight'} w={'14px'} />}
|
||||
onClick={onOpenDatasetSelect}
|
||||
>
|
||||
@@ -79,19 +79,20 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
h={10}
|
||||
border={theme.borders.base}
|
||||
borderColor={'myGray.200'}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Avatar src={item.avatar} w={'18px'} borderRadius={'xs'} />
|
||||
<Box
|
||||
ml={3}
|
||||
ml={1.5}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg']}
|
||||
fontSize={['sm', 'sm']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import OutputLabel from './Label';
|
||||
import { RenderOutputProps } from './type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import VariableTable from '../../NodePluginIO/VariableTable';
|
||||
@@ -18,11 +17,6 @@ import { defaultOutput } from './FieldEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./FieldEditModal'));
|
||||
|
||||
const RenderList: {
|
||||
types: FlowNodeOutputTypeEnum[];
|
||||
Component: React.ComponentType<RenderOutputProps>;
|
||||
}[] = [];
|
||||
|
||||
const RenderOutput = ({
|
||||
nodeId,
|
||||
flowOutputList
|
||||
@@ -77,7 +71,7 @@ const RenderOutput = ({
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box position={'relative'} fontWeight={'medium'}>
|
||||
<Box position={'relative'} fontWeight={'medium'} fontSize={'sm'}>
|
||||
{t((addOutput.label || 'common:core.workflow.Custom outputs') as any)}
|
||||
</Box>
|
||||
{addOutput.description && <QuestionTip ml={1} label={addOutput.description} />}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user