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>
|
||||
|
||||
Reference in New Issue
Block a user