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:
@@ -5,8 +5,8 @@
|
||||
|
||||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: 'zh',
|
||||
locales: ['en', 'zh', 'zh_TW'],
|
||||
defaultLocale: 'zh-CN',
|
||||
locales: ['en', 'zh-CN', 'zh-TW'],
|
||||
localeDetection: false
|
||||
},
|
||||
localePath:
|
||||
|
||||
@@ -85,7 +85,8 @@ const nextConfig = {
|
||||
experimental: {
|
||||
// 优化 Server Components 的构建和运行,避免不必要的客户端打包。
|
||||
serverComponentsExternalPackages: ['mongoose', 'pg', '@node-rs/jieba', 'duck-duck-scrape'],
|
||||
outputFileTracingRoot: path.join(__dirname, '../../')
|
||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||
instrumentationHook: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.8.13",
|
||||
"version": "4.8.14",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
8
projects/app/public/imgs/app/autoExec-icon.svg
Normal file
8
projects/app/public/imgs/app/autoExec-icon.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="36" height="37" viewBox="0 0 36 37" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="0.617554" width="36" height="36" rx="6" fill="#F0EEFF"/>
|
||||
<path d="M14.8629 19.2382L16.7322 14.679C16.7937 14.5289 16.94 14.4308 17.1023 14.4308H19.7262C20.0163 14.4308 20.2099 14.7299 20.0912 14.9946L18.9433 17.552C18.884 17.6843 18.9808 17.8339 19.1258 17.8339H21.1135C21.4463 17.8339 21.6336 18.2166 21.4294 18.4793L17.7127 23.2613C17.4643 23.5809 16.9528 23.3717 16.9996 22.9696L17.3439 20.0131C17.3577 19.8943 17.2648 19.79 17.1452 19.79H15.233C14.9485 19.79 14.755 19.5014 14.8629 19.2382Z" fill="#8774EE"/>
|
||||
<path d="M10.2067 23.1292C9.73113 23.4045 9.11745 23.2438 8.89074 22.7433C7.9809 20.7346 7.7568 18.4713 8.27046 16.3077C8.86108 13.8199 10.3822 11.6529 12.5212 10.2521C14.6601 8.85118 17.2545 8.32284 19.7709 8.77564C21.9595 9.16945 23.9446 10.2794 25.4224 11.9162C25.7906 12.324 25.6927 12.9508 25.2503 13.2767C24.8079 13.6026 24.1892 13.503 23.811 13.1043C22.6426 11.8729 21.1061 11.0377 19.4185 10.734C17.4028 10.3713 15.3247 10.7945 13.6114 11.9167C11.898 13.0388 10.6796 14.7746 10.2065 16.7673C9.81043 18.4356 9.96196 20.1778 10.6239 21.741C10.8382 22.247 10.6822 22.854 10.2067 23.1292Z" fill="#8774EE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6398 21.7804C10.6586 21.8296 10.6739 21.8797 10.6859 21.9302C10.7953 22.3903 10.6241 22.8876 10.2067 23.1292C9.73113 23.4045 9.11745 23.2438 8.89074 22.7433C8.85672 22.6682 8.82366 22.5927 8.79156 22.5169C8.77571 22.4795 8.7601 22.442 8.74472 22.4044C7.95817 20.4821 7.78549 18.3505 8.27046 16.3077C8.86108 13.8199 10.3822 11.6529 12.5212 10.2521C14.6601 8.85118 17.2545 8.32284 19.7709 8.77564C21.8373 9.14745 23.7223 10.1576 25.1701 11.6469C25.1984 11.676 25.2265 11.7053 25.2545 11.7348C25.3112 11.7945 25.3671 11.855 25.4224 11.9162C25.7906 12.324 25.6927 12.9508 25.2503 13.2767C24.8619 13.5628 24.3377 13.521 23.9596 13.2368C23.9181 13.2056 23.8784 13.1715 23.8408 13.1346C23.8307 13.1247 23.8207 13.1146 23.811 13.1043C22.6426 11.8729 21.1061 11.0377 19.4185 10.734C17.4028 10.3713 15.3247 10.7945 13.6114 11.9167C11.898 13.0388 10.6796 14.7746 10.2065 16.7673C9.81043 18.4356 9.96196 20.1778 10.6239 21.741C10.6294 21.7541 10.6347 21.7672 10.6398 21.7804Z" fill="#8774EE"/>
|
||||
<path d="M26.3355 15.2101C26.8441 15.0022 27.4302 15.2451 27.5866 15.7719C28.1567 17.6924 28.1388 19.7484 27.5225 21.6707C26.8045 23.9101 25.3203 25.8248 23.3306 27.0784C21.3408 28.3319 18.973 28.8442 16.6431 28.5251C14.6431 28.2511 12.7808 27.3797 11.2946 26.0363C10.8869 25.6678 10.9209 25.0344 11.328 24.6653C11.7351 24.2963 12.3607 24.3327 12.7774 24.691C13.9423 25.6928 15.377 26.3432 16.9131 26.5536C18.7794 26.8092 20.6761 26.3989 22.2699 25.3948C23.8636 24.3907 25.0526 22.8569 25.6277 21.0632C26.1011 19.5867 26.1337 18.0119 25.733 16.5286C25.5897 15.9981 25.8269 15.4181 26.3355 15.2101Z" fill="#8774EE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.7226 16.4874C25.7258 16.5011 25.7293 16.5149 25.733 16.5286C26.1337 18.0119 26.1011 19.5867 25.6277 21.0632C25.0526 22.8569 23.8636 24.3907 22.2699 25.3948C20.6761 26.3989 18.7794 26.8092 16.9131 26.5536C15.377 26.3432 13.9423 25.6928 12.7774 24.691C12.7666 24.6817 12.7557 24.6727 12.7446 24.6639C12.7035 24.631 12.6605 24.6011 12.6161 24.5743C12.2111 24.3299 11.6854 24.3414 11.328 24.6653C10.9209 25.0344 10.8869 25.6678 11.2946 26.0363C11.3557 26.0916 11.4175 26.1461 11.48 26.1997C11.5108 26.2262 11.5417 26.2525 11.5729 26.2786C13.0103 27.4846 14.766 28.268 16.6431 28.5251C18.973 28.8442 21.3408 28.3319 23.3306 27.0784C25.3203 25.8248 26.8045 23.9101 27.5225 21.6707C28.101 19.8666 28.1523 17.9447 27.685 16.1275C27.6749 16.0882 27.6646 16.0489 27.654 16.0096C27.6325 15.9302 27.61 15.8509 27.5866 15.7719C27.4302 15.2451 26.8441 15.0022 26.3355 15.2101C25.889 15.3927 25.6517 15.8619 25.6973 16.3327C25.7023 16.3844 25.7107 16.436 25.7226 16.4874Z" fill="#8774EE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
91
projects/app/public/imgs/app/autoExec.svg
Normal file
91
projects/app/public/imgs/app/autoExec.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import NextLink from 'next/link';
|
||||
import Badge from '../Badge';
|
||||
@@ -23,14 +23,14 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
const { gitStar, feConfigs } = useSystemStore();
|
||||
const { lastChatAppId, lastChatId } = useChatStore();
|
||||
const { lastChatAppId } = useChatStore();
|
||||
const navbarList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('common:navbar.Chat'),
|
||||
icon: 'core/chat/chatLight',
|
||||
activeIcon: 'core/chat/chatFill',
|
||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
||||
link: `/chat?appId=${lastChatAppId}`,
|
||||
activeLink: ['/chat']
|
||||
},
|
||||
{
|
||||
@@ -55,7 +55,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
activeLink: ['/account']
|
||||
}
|
||||
],
|
||||
[lastChatAppId, lastChatId, t]
|
||||
[lastChatAppId, t]
|
||||
);
|
||||
|
||||
const itemStyles: BoxProps & LinkProps = {
|
||||
@@ -84,6 +84,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
h={'100%'}
|
||||
w={'100%'}
|
||||
userSelect={'none'}
|
||||
pb={2}
|
||||
>
|
||||
{/* logo */}
|
||||
<Box
|
||||
@@ -155,6 +156,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
href={`/account?currentTab=inform`}
|
||||
mb={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<Badge count={unread}>
|
||||
<MyIcon name={'support/user/informLight'} width={'22px'} height={'22px'} />
|
||||
@@ -171,6 +173,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
target="_blank"
|
||||
mb={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} width={'24px'} height={'24px'} />
|
||||
</Link>
|
||||
@@ -186,6 +189,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Badge from '../Badge';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -9,14 +9,14 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { lastChatAppId, lastChatId } = useChatStore();
|
||||
const { lastChatAppId } = useChatStore();
|
||||
const navbarList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('common:navbar.Chat'),
|
||||
icon: 'core/chat/chatLight',
|
||||
activeIcon: 'core/chat/chatFill',
|
||||
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
|
||||
link: `/chat?appId=${lastChatAppId}`,
|
||||
activeLink: ['/chat'],
|
||||
unread: 0
|
||||
},
|
||||
@@ -45,7 +45,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||
unread
|
||||
}
|
||||
],
|
||||
[t, lastChatAppId, lastChatId, unread]
|
||||
[t, lastChatAppId, unread]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { langMap } from '@/web/common/utils/i18n';
|
||||
import { Avatar, Box, Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -14,7 +14,7 @@ const I18nLngSelector = () => {
|
||||
return Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
|
||||
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'1rem'} />
|
||||
<Box>{lang.label}</Box>
|
||||
</Flex>
|
||||
),
|
||||
|
||||
87
projects/app/src/components/core/app/AutoExecConfig.tsx
Normal file
87
projects/app/src/components/core/app/AutoExecConfig.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Box, Button, Flex, ModalBody, Switch, Textarea, useDisclosure } from '@chakra-ui/react';
|
||||
import { defaultAutoExecuteConfig } from '@fastgpt/global/core/app/constants';
|
||||
import { AppAutoExecuteConfigType } from '@fastgpt/global/core/app/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ChatFunctionTip from './Tip';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
const AutoExecConfig = ({
|
||||
value = defaultAutoExecuteConfig,
|
||||
onChange
|
||||
}: {
|
||||
value?: AppAutoExecuteConfigType;
|
||||
onChange: (e: AppAutoExecuteConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const isOpenAutoExec = value.open;
|
||||
const defaultPrompt = value.defaultPrompt;
|
||||
|
||||
const formLabel = isOpenAutoExec
|
||||
? t('common:core.app.whisper.Open')
|
||||
: t('common:core.app.whisper.Close');
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/autoExec'} mr={2} w={'20px'} />
|
||||
<FormLabel color={'myGray.600'}>{t('app:auto_execute')}</FormLabel>
|
||||
<ChatFunctionTip type={'autoExec'} />
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={t('common:core.app.Config_auto_execute')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{formLabel}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={t('common:core.app.Auto execute')}
|
||||
iconSrc="core/app/simpleMode/autoExec"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'}>{t('app:open_auto_execute')}</FormLabel>
|
||||
<Switch
|
||||
isChecked={isOpenAutoExec}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
open: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{isOpenAutoExec && (
|
||||
<Box mt={4}>
|
||||
<FormLabel mb={1}>{t('common:core.app.schedule.Default prompt')}</FormLabel>
|
||||
<Textarea
|
||||
value={defaultPrompt}
|
||||
rows={8}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('app:auto_execute_default_prompt_placeholder')}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
defaultPrompt: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoExecConfig;
|
||||
@@ -139,7 +139,7 @@ const InputGuideConfig = ({
|
||||
onOpenLexiconConfig();
|
||||
}}
|
||||
>
|
||||
{chatT('config_input_guide_lexicon')}
|
||||
{t('chat:config_input_guide_lexicon')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<>
|
||||
@@ -152,7 +152,7 @@ const InputGuideConfig = ({
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<MyIcon name={'book'} w={'17px'} ml={4} mr={1} color={'myGray.600'} />
|
||||
{commonT('common.Documents')}
|
||||
{t('common:common.Documents')}
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} />
|
||||
</Flex>
|
||||
|
||||
@@ -23,7 +23,7 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
const MultipleRowSelect = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
|
||||
);
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
|
||||
// options type:
|
||||
enum CronJobTypeEnum {
|
||||
month = 'month',
|
||||
@@ -233,24 +233,24 @@ const ScheduledTriggerConfig = ({
|
||||
}
|
||||
|
||||
if (cronField[0] === 'month') {
|
||||
return t('core.app.schedule.Every month', {
|
||||
return t('common:core.app.schedule.Every month', {
|
||||
day: cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'week') {
|
||||
return t('core.app.schedule.Every week', {
|
||||
return t('common:core.app.schedule.Every week', {
|
||||
day: cronField[1] === 0 ? t('app:day') : cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'day') {
|
||||
return t('core.app.schedule.Every day', {
|
||||
return t('common:core.app.schedule.Every day', {
|
||||
hour: cronField[1]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'interval') {
|
||||
return t('core.app.schedule.Interval', {
|
||||
return t('common:core.app.schedule.Interval', {
|
||||
interval: cronField[1]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ enum FnTypeEnum {
|
||||
welcome = 'welcome',
|
||||
file = 'file',
|
||||
visionModel = 'visionModel',
|
||||
instruction = 'instruction'
|
||||
instruction = 'instruction',
|
||||
autoExec = 'autoExec'
|
||||
}
|
||||
|
||||
const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
@@ -66,6 +67,12 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
title: t('workflow:plugin.Instructions'),
|
||||
desc: t('workflow:plugin.Instruction_Tip'),
|
||||
imgUrl: '/imgs/app/instruction.svg'
|
||||
},
|
||||
[FnTypeEnum.autoExec]: {
|
||||
icon: '/imgs/app/autoExec-icon.svg',
|
||||
title: t('common:core.app.Auto execute'),
|
||||
desc: t('app:auto_execute_tip'),
|
||||
imgUrl: '/imgs/app/autoExec.svg'
|
||||
}
|
||||
});
|
||||
const data = map.current[type];
|
||||
|
||||
@@ -31,6 +31,7 @@ import ChatFunctionTip from './Tip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import InputTypeConfig from '@/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
@@ -190,92 +191,59 @@ const VariableEdit = ({
|
||||
</Flex>
|
||||
{/* Form render */}
|
||||
{formatVariables.length > 0 && (
|
||||
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead h={8}>
|
||||
<Tr>
|
||||
<Th
|
||||
borderRadius={'none !important'}
|
||||
fontSize={'mini'}
|
||||
bg={'myGray.50'}
|
||||
p={0}
|
||||
px={4}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{t('workflow:Variable_name')}
|
||||
</Th>
|
||||
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
|
||||
{t('common:common.Require Input')}
|
||||
</Th>
|
||||
<Th
|
||||
fontSize={'mini'}
|
||||
borderRadius={'none !important'}
|
||||
bg={'myGray.50'}
|
||||
p={0}
|
||||
px={4}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{t('common:common.Operation')}
|
||||
</Th>
|
||||
<TableContainer mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:common.Require Input')}</Th>
|
||||
<Th>{t('common:common.Operation')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatVariables.map((item, index) => (
|
||||
<Tr key={item.id}>
|
||||
<Td fontWeight={'medium'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{item.required ? (
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => {
|
||||
const formattedItem = {
|
||||
...item,
|
||||
list: item.enums || []
|
||||
};
|
||||
reset(formattedItem);
|
||||
}}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() =>
|
||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{formatVariables.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
h={8}
|
||||
color={'myGray.900'}
|
||||
fontSize={'mini'}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td p={0} px={4} h={8} color={'myGray.900'} fontSize={'mini'}>
|
||||
<Flex alignItems={'center'}>
|
||||
{item.required ? (
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td p={0} px={4} h={8} color={'myGray.600'} fontSize={'mini'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const formattedItem = {
|
||||
...item,
|
||||
list: item.enums || []
|
||||
};
|
||||
reset(formattedItem);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
onChange(variables.filter((variable) => variable.id !== item.id))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{/* Edit modal */}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Switch } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -24,12 +24,9 @@ const WhisperConfig = ({
|
||||
const isOpenWhisper = value.open;
|
||||
const isAutoSend = value.autoSend;
|
||||
|
||||
const formLabel = useMemo(() => {
|
||||
if (!isOpenWhisper) {
|
||||
return t('common:core.app.whisper.Close');
|
||||
}
|
||||
return t('common:core.app.whisper.Open');
|
||||
}, [t, isOpenWhisper]);
|
||||
const formLabel = isOpenWhisper
|
||||
? t('common:core.app.whisper.Open')
|
||||
: t('common:core.app.whisper.Close');
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
|
||||
@@ -33,32 +33,29 @@ const ChatInput = ({
|
||||
onStop,
|
||||
TextareaDom,
|
||||
resetInputVal,
|
||||
chatForm,
|
||||
appId
|
||||
chatForm
|
||||
}: {
|
||||
onSendMessage: SendPromptFnType;
|
||||
onStop: () => void;
|
||||
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
|
||||
resetInputVal: (val: ChatBoxInputType) => void;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
appId: string;
|
||||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { setValue, watch, control } = chatForm;
|
||||
const inputValue = watch('input');
|
||||
|
||||
const {
|
||||
chatId,
|
||||
isChatting,
|
||||
whisperConfig,
|
||||
autoTTSResponse,
|
||||
chatInputGuide,
|
||||
outLinkAuthData,
|
||||
fileSelectConfig
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const whisperConfig = useContextSelector(ChatBoxContext, (v) => v.whisperConfig);
|
||||
const autoTTSResponse = useContextSelector(ChatBoxContext, (v) => v.autoTTSResponse);
|
||||
const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide);
|
||||
const fileSelectConfig = useContextSelector(ChatBoxContext, (v) => v.fileSelectConfig);
|
||||
|
||||
const fileCtrl = useFieldArray({
|
||||
control,
|
||||
@@ -78,8 +75,6 @@ const ChatInput = ({
|
||||
replaceFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig,
|
||||
fileCtrl
|
||||
});
|
||||
|
||||
@@ -2,76 +2,73 @@ import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import {
|
||||
AppChatConfigType,
|
||||
AppAutoExecuteConfigType,
|
||||
AppFileSelectConfigType,
|
||||
AppTTSConfigType,
|
||||
AppWhisperConfigType,
|
||||
ChatInputGuideConfigType,
|
||||
VariableItemType
|
||||
} from '@fastgpt/global/core/app/type';
|
||||
import { ChatHistoryItemResType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import {
|
||||
defaultAppSelectFileConfig,
|
||||
defaultAutoExecuteConfig,
|
||||
defaultChatInputGuideConfig,
|
||||
defaultTTSConfig,
|
||||
defaultWhisperConfig
|
||||
} from '@fastgpt/global/core/app/constants';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { getChatResData } from '@/web/core/chat/api';
|
||||
import { ChatBoxInputFormType } from './type';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
appAvatar?: string;
|
||||
export type ChatProviderProps = {
|
||||
appId: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
chatId: string;
|
||||
outLinkAuthData?: OutLinkChatAuthProps;
|
||||
|
||||
chatHistories: ChatSiteItemType[];
|
||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
chatType: 'log' | 'chat' | 'share' | 'team';
|
||||
showRawSource: boolean;
|
||||
showNodeStatus: boolean;
|
||||
};
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
welcomeText: string;
|
||||
variableList: VariableItemType[];
|
||||
allVariableList: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
whisperConfig: AppWhisperConfigType;
|
||||
autoTTSResponse: boolean;
|
||||
startSegmentedAudio: () => Promise<any>;
|
||||
splitText2Audio: (text: string, done?: boolean | undefined) => void;
|
||||
finishSegmentedAudio: () => void;
|
||||
audioLoading: boolean;
|
||||
audioPlaying: boolean;
|
||||
hasAudio: boolean;
|
||||
playAudioByText: ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}) => Promise<{
|
||||
buffer?: Uint8Array | undefined;
|
||||
}>;
|
||||
cancelAudio: () => void;
|
||||
audioPlayingChatId: string | undefined;
|
||||
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
isChatting: boolean;
|
||||
chatInputGuide: ChatInputGuideConfigType;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
};
|
||||
type useChatStoreType = ChatProviderProps & {
|
||||
welcomeText: string;
|
||||
variableList: VariableItemType[];
|
||||
allVariableList: VariableItemType[];
|
||||
questionGuide: boolean;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
whisperConfig: AppWhisperConfigType;
|
||||
autoTTSResponse: boolean;
|
||||
autoExecute: AppAutoExecuteConfigType;
|
||||
startSegmentedAudio: () => Promise<any>;
|
||||
splitText2Audio: (text: string, done?: boolean | undefined) => void;
|
||||
finishSegmentedAudio: () => void;
|
||||
audioLoading: boolean;
|
||||
audioPlaying: boolean;
|
||||
hasAudio: boolean;
|
||||
playAudioByText: ({
|
||||
text,
|
||||
buffer
|
||||
}: {
|
||||
text: string;
|
||||
buffer?: Uint8Array | undefined;
|
||||
}) => Promise<{
|
||||
buffer?: Uint8Array | undefined;
|
||||
}>;
|
||||
cancelAudio: () => void;
|
||||
audioPlayingChatId: string | undefined;
|
||||
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
isChatting: boolean;
|
||||
chatInputGuide: ChatInputGuideConfigType;
|
||||
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
|
||||
appId: string;
|
||||
chatId: string;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
};
|
||||
|
||||
export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
welcomeText: '',
|
||||
@@ -95,10 +92,6 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
splitText2Audio: function (text: string, done?: boolean | undefined): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
chatHistories: [],
|
||||
setChatHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isChatting: false,
|
||||
audioLoading: false,
|
||||
audioPlaying: false,
|
||||
@@ -132,23 +125,24 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
});
|
||||
|
||||
const Provider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
appId,
|
||||
chatId,
|
||||
outLinkAuthData = {},
|
||||
chatType = 'chat',
|
||||
showRawSource,
|
||||
showNodeStatus,
|
||||
chatConfig = {},
|
||||
children,
|
||||
...props
|
||||
}: ChatProviderProps & {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const chatConfig = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.chatBoxData?.app?.chatConfig || {}
|
||||
);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
|
||||
const {
|
||||
welcomeText = '',
|
||||
variables = [],
|
||||
@@ -156,19 +150,10 @@ const Provider = ({
|
||||
ttsConfig = defaultTTSConfig,
|
||||
whisperConfig = defaultWhisperConfig,
|
||||
chatInputGuide = defaultChatInputGuideConfig,
|
||||
fileSelectConfig = defaultAppSelectFileConfig
|
||||
fileSelectConfig = defaultAppSelectFileConfig,
|
||||
autoExecute = defaultAutoExecuteConfig
|
||||
} = useMemo(() => chatConfig, [chatConfig]);
|
||||
|
||||
const outLinkAuthData = useMemo(
|
||||
() => ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
[shareId, outLinkUid, teamId, teamToken]
|
||||
);
|
||||
|
||||
// segment audio
|
||||
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
|
||||
const {
|
||||
@@ -190,37 +175,34 @@ const Provider = ({
|
||||
|
||||
const isChatting = useMemo(
|
||||
() =>
|
||||
chatHistories[chatHistories.length - 1] &&
|
||||
chatHistories[chatHistories.length - 1]?.status !== 'finish',
|
||||
[chatHistories]
|
||||
chatRecords[chatRecords.length - 1] &&
|
||||
chatRecords[chatRecords.length - 1]?.status !== 'finish',
|
||||
[chatRecords]
|
||||
);
|
||||
const getHistoryResponseData = useCallback(
|
||||
async ({ dataId }: { dataId: string }) => {
|
||||
const aimItem = chatHistories.find((item) => item.dataId === dataId)!;
|
||||
if (!!aimItem?.responseData || !props.chatId) {
|
||||
const aimItem = chatRecords.find((item) => item.dataId === dataId)!;
|
||||
if (!!aimItem?.responseData || !chatId) {
|
||||
return aimItem.responseData || [];
|
||||
} else {
|
||||
let resData = await getChatResData({
|
||||
appId: props.appId,
|
||||
chatId: props.chatId,
|
||||
appId: appId,
|
||||
chatId: chatId,
|
||||
dataId,
|
||||
...outLinkAuthData
|
||||
});
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item) => (item.dataId === dataId ? { ...item, responseData: resData } : item))
|
||||
);
|
||||
return resData;
|
||||
}
|
||||
},
|
||||
[chatHistories, outLinkAuthData, props.appId, props.chatId, setChatHistories]
|
||||
[chatRecords, chatId, appId, outLinkAuthData, setChatRecords]
|
||||
);
|
||||
const value: useChatStoreType = {
|
||||
...props,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
welcomeText,
|
||||
autoExecute,
|
||||
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
||||
allVariableList: variables,
|
||||
questionGuide,
|
||||
@@ -238,12 +220,11 @@ const Provider = ({
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
isChatting,
|
||||
chatInputGuide,
|
||||
appId,
|
||||
chatId,
|
||||
outLinkAuthData,
|
||||
variablesForm,
|
||||
getHistoryResponseData,
|
||||
chatType,
|
||||
showRawSource,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
export type ChatControllerProps = {
|
||||
isLastChild: boolean;
|
||||
@@ -24,6 +25,19 @@ export type ChatControllerProps = {
|
||||
onAddUserDislike?: () => void;
|
||||
};
|
||||
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
bg: 'white',
|
||||
borderRight: 'base'
|
||||
};
|
||||
const controlContainerStyle = {
|
||||
className: 'control',
|
||||
color: 'myGray.400',
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
const ChatController = ({
|
||||
chat,
|
||||
showVoiceIcon,
|
||||
@@ -35,34 +49,21 @@ const ChatController = ({
|
||||
onAddUserDislike,
|
||||
onAddUserLike
|
||||
}: ChatControllerProps & FlexProps) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
isChatting,
|
||||
setChatHistories,
|
||||
audioLoading,
|
||||
audioPlaying,
|
||||
hasAudio,
|
||||
playAudioByText,
|
||||
cancelAudio,
|
||||
audioPlayingChatId,
|
||||
setAudioPlayingChatId
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const controlIconStyle = {
|
||||
w: '14px',
|
||||
cursor: 'pointer',
|
||||
p: '5px',
|
||||
bg: 'white',
|
||||
borderRight: theme.borders.base
|
||||
};
|
||||
const controlContainerStyle = {
|
||||
className: 'control',
|
||||
color: 'myGray.400',
|
||||
display: 'flex'
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const audioLoading = useContextSelector(ChatBoxContext, (v) => v.audioLoading);
|
||||
const audioPlaying = useContextSelector(ChatBoxContext, (v) => v.audioPlaying);
|
||||
const hasAudio = useContextSelector(ChatBoxContext, (v) => v.hasAudio);
|
||||
const playAudioByText = useContextSelector(ChatBoxContext, (v) => v.playAudioByText);
|
||||
const cancelAudio = useContextSelector(ChatBoxContext, (v) => v.cancelAudio);
|
||||
const audioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.audioPlayingChatId);
|
||||
const setAudioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.setAudioPlayingChatId);
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
|
||||
const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]);
|
||||
|
||||
return (
|
||||
@@ -70,7 +71,7 @@ const ChatController = ({
|
||||
{...controlContainerStyle}
|
||||
borderRadius={'sm'}
|
||||
overflow={'hidden'}
|
||||
border={theme.borders.base}
|
||||
border={'base'}
|
||||
// 最后一个子元素,没有border
|
||||
css={css({
|
||||
'& > *:last-child, & > *:last-child svg': {
|
||||
@@ -87,7 +88,7 @@ const ChatController = ({
|
||||
onClick={() => copyData(chatText)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
{!!onDelete && !isChatting && (
|
||||
{!!onDelete && !isChatting && chatType !== 'log' && (
|
||||
<>
|
||||
{onRetry && (
|
||||
<MyTooltip label={t('common:core.chat.retry')}>
|
||||
@@ -125,12 +126,7 @@ const ChatController = ({
|
||||
onClick={cancelAudio}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyImage
|
||||
src="/icon/speaking.gif"
|
||||
w={'23px'}
|
||||
alt={''}
|
||||
borderRight={theme.borders.base}
|
||||
/>
|
||||
<MyImage src="/icon/speaking.gif" w={'23px'} alt={''} borderRight={'base'} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -154,8 +150,8 @@ const ChatController = ({
|
||||
text: chatText
|
||||
});
|
||||
|
||||
if (!setChatHistories || !response.buffer) return;
|
||||
setChatHistories((state) =>
|
||||
if (!setChatRecords || !response.buffer) return;
|
||||
setChatRecords((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === chat.dataId
|
||||
? {
|
||||
|
||||
@@ -216,7 +216,7 @@ const ChatItem = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
{/* control icon */}
|
||||
<Flex w={'100%'} alignItems={'flex-end'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||
<Flex order={styleMap.order} ml={styleMap.ml} align={'center'} gap={'0.62rem'}>
|
||||
{chat.time && (isPc || isChatLog) && (
|
||||
|
||||
@@ -19,10 +19,12 @@ const ContextModal = ({ onClose, dataId }: { onClose: () => void; dataId: string
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
res
|
||||
?.map((item) => {
|
||||
if (item.pluginDetail || item.toolDetail) {
|
||||
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
|
||||
}
|
||||
return item;
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
return flatResData.find(isLLMNode)?.historyPreview || [];
|
||||
|
||||
@@ -7,18 +7,22 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
const QuoteModal = ({
|
||||
rawSearch = [],
|
||||
onClose,
|
||||
canEditDataset,
|
||||
showRawSource,
|
||||
chatItemId,
|
||||
metadata
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
onClose: () => void;
|
||||
canEditDataset: boolean;
|
||||
showRawSource: boolean;
|
||||
chatItemId: string;
|
||||
metadata?: {
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
@@ -37,6 +41,13 @@ const QuoteModal = ({
|
||||
[metadata, rawSearch]
|
||||
);
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
chatItemId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
@@ -49,7 +60,7 @@ const QuoteModal = ({
|
||||
title={
|
||||
<Box>
|
||||
{metadata ? (
|
||||
<RawSourceBox {...metadata} canView={showRawSource} />
|
||||
<RawSourceBox {...metadata} {...RawSourceBoxProps} canView={showRawSource} />
|
||||
) : (
|
||||
<>{t('common:core.chat.Quote Amount', { amount: rawSearch.length })}</>
|
||||
)}
|
||||
@@ -64,6 +75,7 @@ const QuoteModal = ({
|
||||
rawSearch={filterResults}
|
||||
canEditDataset={canEditDataset}
|
||||
canViewSource={showRawSource}
|
||||
chatItemId={chatItemId}
|
||||
/>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
@@ -74,16 +86,25 @@ const QuoteModal = ({
|
||||
export default QuoteModal;
|
||||
|
||||
export const QuoteList = React.memo(function QuoteList({
|
||||
chatItemId,
|
||||
rawSearch = [],
|
||||
canEditDataset,
|
||||
canViewSource
|
||||
}: {
|
||||
chatItemId?: string;
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
canEditDataset: boolean;
|
||||
canViewSource: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
chatItemId,
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
{rawSearch.map((item, i) => (
|
||||
@@ -101,6 +122,7 @@ export const QuoteList = React.memo(function QuoteList({
|
||||
quoteItem={item}
|
||||
canViewSource={canViewSource}
|
||||
canEditDataset={canEditDataset}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -250,6 +250,7 @@ const ResponseTags = ({
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal
|
||||
{...quoteModalData}
|
||||
chatItemId={historyItem.dataId}
|
||||
canEditDataset={notSharePage}
|
||||
showRawSource={showRawSource}
|
||||
onClose={() => setQuoteModalData(undefined)}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const TimeBox = ({ time }: { time: Date }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
|
||||
{t(formatTimeToChatItemTime(time) as any, {
|
||||
time: dayjs(time).format('HH#mm')
|
||||
}).replace('#', ':')}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeBox;
|
||||
@@ -1,17 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Button, Card, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
@@ -21,10 +11,10 @@ import { ChatBoxInputFormType } from '../type.d';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
export const VariableInputItem = ({
|
||||
item,
|
||||
@@ -47,13 +37,7 @@ export const VariableInputItem = ({
|
||||
>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
left={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
<Box position={'absolute'} top={'-2px'} left={'-8px'} color={'red.500'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
@@ -134,8 +118,11 @@ const VariableInput = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const { reset, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
|
||||
const { setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
return variableList.reduce((acc: Record<string, any>, item) => {
|
||||
@@ -144,9 +131,12 @@ const VariableInput = ({
|
||||
}, {});
|
||||
}, [variableList]);
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
reset(defaultValues);
|
||||
}, [defaultValues]);
|
||||
useEffect(() => {
|
||||
const values = variablesForm.getValues('variables');
|
||||
// If form is not empty, do not reset the variables
|
||||
if (Object.values(values).filter(Boolean).length > 0) return;
|
||||
setValue('variables', defaultValues);
|
||||
}, [defaultValues, setValue, variablesForm]);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -4,10 +4,10 @@ import { MessageCardStyle } from '../constants';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const WelcomeBox = ({ welcomeText }: { welcomeText: string }) => {
|
||||
const appAvatar = useContextSelector(ChatBoxContext, (v) => v.appAvatar);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -14,21 +14,24 @@ import { ChatBoxInputFormType, UserInputFileItemType } from '../type';
|
||||
import { AppFileSelectConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { documentFileType } from '@fastgpt/global/common/file/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
type UseFileUploadOptions = {
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
chatId: string;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
fileCtrl: UseFieldArrayReturn<ChatBoxInputFormType, 'files', 'id'>;
|
||||
};
|
||||
|
||||
export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { outLinkAuthData, chatId, fileSelectConfig, fileCtrl } = props;
|
||||
const { fileSelectConfig, fileCtrl } = props;
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
|
||||
const {
|
||||
update: updateFiles,
|
||||
remove: removeFiles,
|
||||
@@ -137,7 +140,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
[maxSelectFiles, appendFiles, toast, t, maxSize]
|
||||
);
|
||||
|
||||
const uploadFiles = async () => {
|
||||
const uploadFiles = useCallback(async () => {
|
||||
const filterFiles = fileList.filter((item) => item.status === 0);
|
||||
|
||||
if (filterFiles.length === 0) return;
|
||||
@@ -158,7 +161,10 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { previewUrl } = await uploadFile2DB({
|
||||
file: copyFile.rawFile,
|
||||
bucketName: 'chat',
|
||||
outLinkAuthData,
|
||||
data: {
|
||||
appId,
|
||||
...outLinkAuthData
|
||||
},
|
||||
metadata: {
|
||||
chatId
|
||||
},
|
||||
@@ -186,7 +192,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
);
|
||||
|
||||
removeFiles(errorFileIndex);
|
||||
};
|
||||
}, [appId, chatId, fileList, outLinkAuthData, removeFiles, replaceFiles, t, toast, updateFiles]);
|
||||
|
||||
const sortFileList = useMemo(() => {
|
||||
// Sort: Document, image
|
||||
|
||||
@@ -3,9 +3,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
ForwardedRef,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import Script from 'next/script';
|
||||
@@ -16,7 +14,7 @@ import type {
|
||||
} from '@fastgpt/global/core/chat/type.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { Box, Flex, Checkbox, BoxProps } from '@chakra-ui/react';
|
||||
import { Box, Checkbox } from '@chakra-ui/react';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -25,6 +23,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
closeCustomFeedback,
|
||||
delChatRecordById,
|
||||
updateChatAdminFeedback,
|
||||
updateChatUserFeedback
|
||||
} from '@/web/core/chat/api';
|
||||
@@ -33,12 +32,7 @@ import type { AdminMarkType } from './components/SelectMarkCollection';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||
import type {
|
||||
ComponentRef,
|
||||
ChatBoxInputType,
|
||||
ChatBoxInputFormType,
|
||||
SendPromptFnType
|
||||
} from './type.d';
|
||||
import type { ChatBoxInputType, ChatBoxInputFormType, SendPromptFnType } from './type.d';
|
||||
import type { StartChatFnProps, generatingMessageProps } from '../type';
|
||||
import ChatInput from './Input/ChatInput';
|
||||
import ChatBoxDivider from '../../Divider';
|
||||
@@ -67,9 +61,11 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import TimeBox from './components/TimeBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||
@@ -87,75 +83,45 @@ enum FeedbackTypeEnum {
|
||||
|
||||
type Props = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
isReady?: boolean;
|
||||
feedbackType?: `${FeedbackTypeEnum}`;
|
||||
showMarkIcon?: boolean; // admin mark dataset
|
||||
showVoiceIcon?: boolean;
|
||||
showEmptyIntro?: boolean;
|
||||
userAvatar?: string;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
ScrollData: ({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
ScrollContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
} & BoxProps) => React.JSX.Element;
|
||||
// not chat test params
|
||||
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||
StreamResponseType & {
|
||||
isNewChat?: boolean;
|
||||
}
|
||||
>;
|
||||
onDelMessage?: (e: { contentId: string }) => void;
|
||||
};
|
||||
|
||||
const ChatTimeBox = ({ time }: { time: Date }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
|
||||
{t(formatTimeToChatItemTime(time) as any, {
|
||||
time: dayjs(time).format('HH#mm')
|
||||
}).replace('#', ':')}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatBox = (
|
||||
{
|
||||
feedbackType = FeedbackTypeEnum.hidden,
|
||||
showMarkIcon = false,
|
||||
showVoiceIcon = true,
|
||||
showEmptyIntro = false,
|
||||
appAvatar,
|
||||
userAvatar,
|
||||
active = true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
onStartChat,
|
||||
onDelMessage,
|
||||
ScrollData
|
||||
}: Props,
|
||||
ref: ForwardedRef<ComponentRef>
|
||||
) => {
|
||||
const ChatBoxRef = useRef<HTMLDivElement>(null);
|
||||
const ChatBox = ({
|
||||
isReady = true,
|
||||
feedbackType = FeedbackTypeEnum.hidden,
|
||||
showMarkIcon = false,
|
||||
showVoiceIcon = true,
|
||||
showEmptyIntro = false,
|
||||
active = true,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
onStartChat
|
||||
}: Props) => {
|
||||
const ScrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { setLoading, feConfigs } = useSystemStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
||||
const chatController = useRef(new AbortController());
|
||||
const questionGuideController = useRef(new AbortController());
|
||||
const pluginController = useRef(new AbortController());
|
||||
const isNewChatReplace = useRef(false);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [feedbackId, setFeedbackId] = useState<string>();
|
||||
const [readFeedbackData, setReadFeedbackData] = useState<{
|
||||
dataId: string;
|
||||
@@ -164,26 +130,35 @@ const ChatBox = (
|
||||
const [adminMarkData, setAdminMarkData] = useState<AdminMarkType & { dataId: string }>();
|
||||
const [questionGuides, setQuestionGuide] = useState<string[]>([]);
|
||||
|
||||
const {
|
||||
welcomeText,
|
||||
variableList,
|
||||
allVariableList,
|
||||
questionGuide,
|
||||
startSegmentedAudio,
|
||||
finishSegmentedAudio,
|
||||
setAudioPlayingChatId,
|
||||
splitText2Audio,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
isChatting
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
const userAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.userAvatar);
|
||||
const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
const isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded);
|
||||
const setIsChatRecordsLoaded = useContextSelector(
|
||||
ChatRecordContext,
|
||||
(v) => v.setIsChatRecordsLoaded
|
||||
);
|
||||
const ScrollData = useContextSelector(ChatRecordContext, (v) => v.ScrollData);
|
||||
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const welcomeText = useContextSelector(ChatBoxContext, (v) => v.welcomeText);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
const allVariableList = useContextSelector(ChatBoxContext, (v) => v.allVariableList);
|
||||
const questionGuide = useContextSelector(ChatBoxContext, (v) => v.questionGuide);
|
||||
const autoExecute = useContextSelector(ChatBoxContext, (v) => v.autoExecute);
|
||||
const startSegmentedAudio = useContextSelector(ChatBoxContext, (v) => v.startSegmentedAudio);
|
||||
const finishSegmentedAudio = useContextSelector(ChatBoxContext, (v) => v.finishSegmentedAudio);
|
||||
const setAudioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.setAudioPlayingChatId);
|
||||
const splitText2Audio = useContextSelector(ChatBoxContext, (v) => v.splitText2Audio);
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
|
||||
// Workflow running, there are user input or selection
|
||||
const isInteractive = useMemo(
|
||||
() => checkIsInteractiveByHistories(chatHistories),
|
||||
[chatHistories]
|
||||
);
|
||||
const isInteractive = useMemo(() => checkIsInteractiveByHistories(chatRecords), [chatRecords]);
|
||||
|
||||
// compute variable input is finish.
|
||||
const chatForm = useForm<ChatBoxInputFormType>({
|
||||
@@ -195,18 +170,18 @@ const ChatBox = (
|
||||
});
|
||||
const { setValue, watch } = chatForm;
|
||||
const chatStartedWatch = watch('chatStarted');
|
||||
const chatStarted = chatStartedWatch || chatHistories.length > 0 || variableList.length === 0;
|
||||
const chatStarted = chatStartedWatch || chatRecords.length > 0 || variableList.length === 0;
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = useMemoizedFn((behavior: 'smooth' | 'auto' = 'smooth', delay = 0) => {
|
||||
setTimeout(() => {
|
||||
if (!ChatBoxRef.current) {
|
||||
if (!ScrollContainerRef.current) {
|
||||
setTimeout(() => {
|
||||
scrollToBottom(behavior);
|
||||
}, 500);
|
||||
} else {
|
||||
ChatBoxRef.current.scrollTo({
|
||||
top: ChatBoxRef.current.scrollHeight,
|
||||
ScrollContainerRef.current.scrollTo({
|
||||
top: ScrollContainerRef.current.scrollHeight,
|
||||
behavior
|
||||
});
|
||||
}
|
||||
@@ -216,10 +191,10 @@ const ChatBox = (
|
||||
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
|
||||
const { run: generatingScroll } = useThrottleFn(
|
||||
(force?: boolean) => {
|
||||
if (!ChatBoxRef.current) return;
|
||||
if (!ScrollContainerRef.current) return;
|
||||
const isBottom =
|
||||
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
|
||||
ChatBoxRef.current.scrollHeight;
|
||||
ScrollContainerRef.current.scrollTop + ScrollContainerRef.current.clientHeight + 150 >=
|
||||
ScrollContainerRef.current.scrollHeight;
|
||||
|
||||
if (isBottom || force) {
|
||||
scrollToBottom('auto');
|
||||
@@ -241,7 +216,7 @@ const ChatBox = (
|
||||
autoTTSResponse,
|
||||
variables
|
||||
}: generatingMessageProps & { autoTTSResponse?: boolean }) => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
if (item.obj !== ChatRoleEnum.AI) return item;
|
||||
@@ -370,11 +345,9 @@ const ChatBox = (
|
||||
|
||||
const result = await postQuestionGuide(
|
||||
{
|
||||
appId,
|
||||
messages: chats2GPTMessages({ messages: histories, reserveId: false }).slice(-6),
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
...outLinkAuthData
|
||||
},
|
||||
abortSignal
|
||||
);
|
||||
@@ -386,14 +359,14 @@ const ChatBox = (
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
[questionGuide, shareId, outLinkUid, teamId, teamToken, scrollToBottom]
|
||||
[questionGuide, appId, outLinkAuthData, scrollToBottom]
|
||||
);
|
||||
|
||||
/* Abort chat completions, questionGuide */
|
||||
const abortRequest = useMemoizedFn(() => {
|
||||
chatController.current?.abort('stop');
|
||||
questionGuideController.current?.abort('stop');
|
||||
pluginController.current?.abort('stop');
|
||||
const abortRequest = useMemoizedFn((signal: string = 'stop') => {
|
||||
chatController.current?.abort(signal);
|
||||
questionGuideController.current?.abort(signal);
|
||||
pluginController.current?.abort(signal);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -403,9 +376,10 @@ const ChatBox = (
|
||||
({
|
||||
text = '',
|
||||
files = [],
|
||||
history = chatHistories,
|
||||
history = chatRecords,
|
||||
autoTTSResponse = false,
|
||||
isInteractivePrompt = false
|
||||
isInteractivePrompt = false,
|
||||
hideInUI = false
|
||||
}) => {
|
||||
variablesForm.handleSubmit(
|
||||
async ({ variables = {} }) => {
|
||||
@@ -452,6 +426,7 @@ const ChatBox = (
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
time: new Date(),
|
||||
hideInUI,
|
||||
value: [
|
||||
...files.map((file) => ({
|
||||
type: ChatItemValueTypeEnum.file,
|
||||
@@ -491,7 +466,7 @@ const ChatBox = (
|
||||
];
|
||||
|
||||
// Update histories(Interactive input does not require new session rounds)
|
||||
setChatHistories(
|
||||
setChatRecords(
|
||||
isInteractivePrompt
|
||||
? // 把交互的结果存储到对话记录中,交互模式下,不需要新的会话轮次
|
||||
setUserSelectResultToHistories(newChatList.slice(0, -2), text)
|
||||
@@ -533,11 +508,9 @@ const ChatBox = (
|
||||
});
|
||||
}
|
||||
|
||||
isNewChatReplace.current = isNewChat;
|
||||
|
||||
// Set last chat finish status
|
||||
let newChatHistories: ChatSiteItemType[] = [];
|
||||
setChatHistories((state) => {
|
||||
setChatRecords((state) => {
|
||||
newChatHistories = state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -577,11 +550,11 @@ const ChatBox = (
|
||||
if (!err?.responseText) {
|
||||
resetInputVal({ text, files });
|
||||
// 这里的 newChatList 没包含用户交互输入的内容,所以重置后刚好是正确的。
|
||||
setChatHistories(newChatList.slice(0, newChatList.length - 2));
|
||||
setChatRecords(newChatList.slice(0, newChatList.length - 2));
|
||||
}
|
||||
|
||||
// set finish status
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -603,26 +576,37 @@ const ChatBox = (
|
||||
);
|
||||
|
||||
// retry input
|
||||
const onDelMessage = useCallback(
|
||||
(contentId: string) => {
|
||||
return delChatRecordById({
|
||||
appId,
|
||||
chatId,
|
||||
contentId,
|
||||
...outLinkAuthData
|
||||
});
|
||||
},
|
||||
[appId, chatId, outLinkAuthData]
|
||||
);
|
||||
const retryInput = useMemoizedFn((dataId?: string) => {
|
||||
if (!dataId || !onDelMessage) return;
|
||||
|
||||
return async () => {
|
||||
setLoading(true);
|
||||
const index = chatHistories.findIndex((item) => item.dataId === dataId);
|
||||
const delHistory = chatHistories.slice(index);
|
||||
setIsLoading(true);
|
||||
const index = chatRecords.findIndex((item) => item.dataId === dataId);
|
||||
const delHistory = chatRecords.slice(index);
|
||||
try {
|
||||
await Promise.all(
|
||||
delHistory.map((item) => {
|
||||
if (item.dataId) {
|
||||
return onDelMessage({ contentId: item.dataId });
|
||||
return onDelMessage(item.dataId);
|
||||
}
|
||||
})
|
||||
);
|
||||
setChatHistories((state) => (index === 0 ? [] : state.slice(0, index)));
|
||||
setChatRecords((state) => (index === 0 ? [] : state.slice(0, index)));
|
||||
|
||||
sendPrompt({
|
||||
...formatChatValue2InputType(delHistory[0].value),
|
||||
history: chatHistories.slice(0, index)
|
||||
history: chatRecords.slice(0, index)
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
@@ -630,27 +614,22 @@ const ChatBox = (
|
||||
title: getErrText(error, 'Retry failed')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
setIsLoading(false);
|
||||
};
|
||||
});
|
||||
// delete one message(One human and the ai response)
|
||||
const delOneMessage = useMemoizedFn((dataId?: string) => {
|
||||
if (!dataId || !onDelMessage) return;
|
||||
const delOneMessage = useMemoizedFn((dataId: string) => {
|
||||
return () => {
|
||||
setChatHistories((state) => {
|
||||
setChatRecords((state) => {
|
||||
let aiIndex = -1;
|
||||
|
||||
return state.filter((chat, i) => {
|
||||
if (chat.dataId === dataId) {
|
||||
aiIndex = i + 1;
|
||||
onDelMessage({
|
||||
contentId: dataId
|
||||
});
|
||||
onDelMessage(dataId);
|
||||
return false;
|
||||
} else if (aiIndex === i && chat.obj === ChatRoleEnum.AI && chat.dataId) {
|
||||
onDelMessage({
|
||||
contentId: chat.dataId
|
||||
});
|
||||
onDelMessage(chat.dataId);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -694,7 +673,7 @@ const ChatBox = (
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
|
||||
const isGoodFeedback = !!chat.userGoodFeedback;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId
|
||||
? {
|
||||
@@ -722,7 +701,7 @@ const ChatBox = (
|
||||
if (feedbackType !== FeedbackTypeEnum.admin) return;
|
||||
return () => {
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId ? { ...chatItem, userGoodFeedback: undefined } : chatItem
|
||||
)
|
||||
@@ -748,7 +727,7 @@ const ChatBox = (
|
||||
if (chat.userBadFeedback) {
|
||||
return () => {
|
||||
if (!chat.dataId || !chatId || !appId) return;
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === chat.dataId ? { ...chatItem, userBadFeedback: undefined } : chatItem
|
||||
)
|
||||
@@ -789,7 +768,7 @@ const ChatBox = (
|
||||
index: i
|
||||
});
|
||||
// update dom
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.obj === ChatRoleEnum.AI && chatItem.dataId === chat.dataId
|
||||
? {
|
||||
@@ -807,11 +786,11 @@ const ChatBox = (
|
||||
() =>
|
||||
feConfigs?.show_emptyChat &&
|
||||
showEmptyIntro &&
|
||||
chatHistories.length === 0 &&
|
||||
chatRecords.length === 0 &&
|
||||
!variableList?.length &&
|
||||
!welcomeText,
|
||||
[
|
||||
chatHistories.length,
|
||||
chatRecords.length,
|
||||
feConfigs?.show_emptyChat,
|
||||
showEmptyIntro,
|
||||
variableList?.length,
|
||||
@@ -820,26 +799,21 @@ const ChatBox = (
|
||||
);
|
||||
const statusBoxData = useCreation(() => {
|
||||
if (!isChatting) return;
|
||||
const chatContent = chatHistories[chatHistories.length - 1];
|
||||
const chatContent = chatRecords[chatRecords.length - 1];
|
||||
if (!chatContent) return;
|
||||
|
||||
return {
|
||||
status: chatContent.status || ChatStatusEnum.loading,
|
||||
name: t(chatContent.moduleName || ('' as any)) || t('common:common.Loading')
|
||||
};
|
||||
}, [chatHistories, isChatting, t]);
|
||||
}, [chatRecords, isChatting, t]);
|
||||
|
||||
// page change and abort request
|
||||
useEffect(() => {
|
||||
isNewChatReplace.current = false;
|
||||
setQuestionGuide([]);
|
||||
return () => {
|
||||
chatController.current?.abort('leave');
|
||||
if (!isNewChatReplace.current) {
|
||||
questionGuideController.current?.abort('leave');
|
||||
}
|
||||
};
|
||||
}, [router.query]);
|
||||
setValue('chatStarted', false);
|
||||
abortRequest('leave');
|
||||
}, [router.query, setValue, chatId]);
|
||||
|
||||
// add listener
|
||||
useEffect(() => {
|
||||
@@ -866,14 +840,31 @@ const ChatBox = (
|
||||
eventBus.off(EventNameEnum.sendQuestion);
|
||||
eventBus.off(EventNameEnum.editQuestion);
|
||||
};
|
||||
}, [resetInputVal, sendPrompt]);
|
||||
}, [isReady, resetInputVal, sendPrompt]);
|
||||
|
||||
// Auto send prompt
|
||||
useEffect(() => {
|
||||
if (
|
||||
isReady &&
|
||||
autoExecute.open &&
|
||||
chatStarted &&
|
||||
chatRecords.length === 0 &&
|
||||
isChatRecordsLoaded
|
||||
) {
|
||||
sendPrompt({
|
||||
text: autoExecute.defaultPrompt || 'AUTO_EXECUTE',
|
||||
hideInUI: true
|
||||
});
|
||||
}
|
||||
}, [isReady, chatStarted, autoExecute?.open, chatRecords, isChatRecordsLoaded]);
|
||||
|
||||
// output data
|
||||
useImperativeHandle(ref, () => ({
|
||||
useImperativeHandle(ChatBoxRef, () => ({
|
||||
restartChat() {
|
||||
abortRequest();
|
||||
|
||||
setChatHistories([]);
|
||||
setChatRecords([]);
|
||||
setIsChatRecordsLoaded(false);
|
||||
setValue('chatStarted', false);
|
||||
},
|
||||
scrollToBottom(behavior = 'auto') {
|
||||
@@ -884,7 +875,7 @@ const ChatBox = (
|
||||
const RenderRecords = useMemo(() => {
|
||||
return (
|
||||
<ScrollData
|
||||
ScrollContainerRef={ChatBoxRef}
|
||||
ScrollContainerRef={ScrollContainerRef}
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
w={'100%'}
|
||||
@@ -901,98 +892,94 @@ const ChatBox = (
|
||||
)}
|
||||
{/* chat history */}
|
||||
<Box id={'history'}>
|
||||
{chatHistories.map((item, index) => (
|
||||
<>
|
||||
{chatRecords.map((item, index) => (
|
||||
<Box key={item.dataId}>
|
||||
{/* 并且时间和上一条的time相差超过十分钟 */}
|
||||
{index !== 0 &&
|
||||
item.time &&
|
||||
chatHistories[index - 1].time !== undefined &&
|
||||
new Date(item.time).getTime() -
|
||||
new Date(chatHistories[index - 1].time!).getTime() >
|
||||
10 * 60 * 1000 && <ChatTimeBox time={item.time} />}
|
||||
chatRecords[index - 1].time !== undefined &&
|
||||
new Date(item.time).getTime() - new Date(chatRecords[index - 1].time!).getTime() >
|
||||
10 * 60 * 1000 && <TimeBox time={item.time} />}
|
||||
|
||||
<Box key={item.dataId} py={6}>
|
||||
{item.obj === ChatRoleEnum.Human && (
|
||||
<Box py={item.hideInUI ? 0 : 6}>
|
||||
{item.obj === ChatRoleEnum.Human && !item.hideInUI && (
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={userAvatar}
|
||||
chat={item}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
isLastChild={index === chatRecords.length - 1}
|
||||
/>
|
||||
)}
|
||||
{item.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatHistories[index - 1]?.value)?.text
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
}}
|
||||
>
|
||||
<ResponseTags
|
||||
showTags={index !== chatHistories.length - 1 || !isChatting}
|
||||
historyItem={item}
|
||||
/>
|
||||
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatRecords.length - 1}
|
||||
{...{
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
statusBoxData,
|
||||
questionGuides,
|
||||
onMark: onMark(
|
||||
item,
|
||||
formatChatValue2InputType(chatRecords[index - 1]?.value)?.text
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
}}
|
||||
>
|
||||
<ResponseTags
|
||||
showTags={index !== chatRecords.length - 1 || !isChatting}
|
||||
historyItem={item}
|
||||
/>
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('common:core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={i}>
|
||||
<MyTooltip
|
||||
label={t('common:core.app.feedback.close custom feedback')}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
>
|
||||
<Checkbox
|
||||
onChange={onCloseCustomFeedback(item, i)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
<Box fontSize={'sm'}>
|
||||
<ChatBoxDivider
|
||||
icon="core/app/markLight"
|
||||
text={t('common:core.chat.Admin Mark Content')}
|
||||
/>
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
<Box color={'black'}>{item.adminFeedback.q}</Box>
|
||||
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
<Box fontSize={'sm'}>
|
||||
<ChatBoxDivider
|
||||
icon="core/app/markLight"
|
||||
text={t('common:core.chat.Admin Mark Content')}
|
||||
/>
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
<Box color={'black'}>{item.adminFeedback.q}</Box>
|
||||
<Box color={'myGray.600'}>{item.adminFeedback.a}</Box>
|
||||
</Box>
|
||||
)}
|
||||
</ChatItem>
|
||||
</>
|
||||
</Box>
|
||||
)}
|
||||
</ChatItem>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -1002,7 +989,7 @@ const ChatBox = (
|
||||
ScrollData,
|
||||
appAvatar,
|
||||
chatForm,
|
||||
chatHistories,
|
||||
chatRecords,
|
||||
chatStarted,
|
||||
delOneMessage,
|
||||
isChatting,
|
||||
@@ -1029,23 +1016,28 @@ const ChatBox = (
|
||||
]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'} position={'relative'}>
|
||||
<MyBox
|
||||
isLoading={isLoading}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
h={'100%'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Script src={getWebReqUrl('/js/html2pdf.bundle.min.js')} strategy="lazyOnload"></Script>
|
||||
{/* chat box container */}
|
||||
{RenderRecords}
|
||||
{/* message input */}
|
||||
{onStartChat && chatStarted && active && appId && !isInteractive && (
|
||||
{onStartChat && chatStarted && active && !isInteractive && (
|
||||
<ChatInput
|
||||
onSendMessage={sendPrompt}
|
||||
onStop={() => chatController.current?.abort('stop')}
|
||||
TextareaDom={TextareaDom}
|
||||
resetInputVal={resetInputVal}
|
||||
chatForm={chatForm}
|
||||
appId={appId}
|
||||
/>
|
||||
)}
|
||||
{/* user feedback modal */}
|
||||
{!!feedbackId && chatId && appId && (
|
||||
{!!feedbackId && chatId && (
|
||||
<FeedbackModal
|
||||
appId={appId}
|
||||
teamId={teamId}
|
||||
@@ -1056,7 +1048,7 @@ const ChatBox = (
|
||||
outLinkUid={outLinkUid}
|
||||
onClose={() => setFeedbackId(undefined)}
|
||||
onSuccess={(content: string) => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item) =>
|
||||
item.dataId === feedbackId ? { ...item, userBadFeedback: content } : item
|
||||
)
|
||||
@@ -1071,7 +1063,7 @@ const ChatBox = (
|
||||
content={readFeedbackData.content}
|
||||
onClose={() => setReadFeedbackData(undefined)}
|
||||
onCloseFeedback={() => {
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === readFeedbackData.dataId
|
||||
? { ...chatItem, userBadFeedback: undefined }
|
||||
@@ -1106,7 +1098,7 @@ const ChatBox = (
|
||||
});
|
||||
|
||||
// update dom
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === adminMarkData.dataId
|
||||
? {
|
||||
@@ -1124,7 +1116,7 @@ const ChatBox = (
|
||||
dataId: readFeedbackData.dataId,
|
||||
userBadFeedback: undefined
|
||||
});
|
||||
setChatHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === readFeedbackData.dataId
|
||||
? { ...chatItem, userBadFeedback: undefined }
|
||||
@@ -1136,17 +1128,16 @@ const ChatBox = (
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
const ForwardChatBox = forwardRef(ChatBox);
|
||||
|
||||
const ChatBoxContainer = (props: Props, ref: ForwardedRef<ComponentRef>) => {
|
||||
const ChatBoxContainer = (props: Props) => {
|
||||
return (
|
||||
<ChatProvider {...props}>
|
||||
<ForwardChatBox {...props} ref={ref} />
|
||||
<ChatBox {...props} />
|
||||
</ChatProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(forwardRef(ChatBoxContainer));
|
||||
export default React.memo(ChatBoxContainer);
|
||||
|
||||
@@ -29,6 +29,7 @@ export type ChatBoxInputType = {
|
||||
text?: string;
|
||||
files?: UserInputFileItemType[];
|
||||
isInteractivePrompt?: boolean;
|
||||
hideInUI?: boolean;
|
||||
};
|
||||
|
||||
export type SendPromptFnType = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useFieldArray } from 'react-hook-form';
|
||||
import RenderPluginInput from './renderPluginInput';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
@@ -16,34 +16,36 @@ import { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { ChatBoxInputFormType } from '../../ChatBox/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
const RenderInput = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
pluginInputs,
|
||||
variablesForm,
|
||||
histories,
|
||||
onStartChat,
|
||||
onNewChat,
|
||||
onSubmit,
|
||||
isChatting,
|
||||
chatConfig,
|
||||
chatId,
|
||||
outLinkAuthData
|
||||
} = useContextSelector(PluginRunContext, (v) => v);
|
||||
const pluginInputs = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.pluginInputs);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const onStartChat = useContextSelector(PluginRunContext, (v) => v.onStartChat);
|
||||
const onNewChat = useContextSelector(PluginRunContext, (v) => v.onNewChat);
|
||||
const onSubmit = useContextSelector(PluginRunContext, (v) => v.onSubmit);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
const fileSelectConfig = useContextSelector(PluginRunContext, (v) => v.fileSelectConfig);
|
||||
const instruction = useContextSelector(PluginRunContext, (v) => v.instruction);
|
||||
const chatId = useContextSelector(PluginRunContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(PluginRunContext, (v) => v.outLinkAuthData);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
getValues,
|
||||
formState: { errors }
|
||||
} = variablesForm;
|
||||
|
||||
/* ===> Global files(abandon) */
|
||||
const fileCtrl = useFieldArray({
|
||||
control: variablesForm.control,
|
||||
control,
|
||||
name: 'files'
|
||||
});
|
||||
const {
|
||||
@@ -58,9 +60,7 @@ const RenderInput = () => {
|
||||
removeFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig: chatConfig?.fileSelectConfig,
|
||||
fileSelectConfig,
|
||||
fileCtrl
|
||||
});
|
||||
const isDisabledInput = histories.length > 0;
|
||||
@@ -169,7 +169,7 @@ const RenderInput = () => {
|
||||
return (
|
||||
<Box>
|
||||
{/* instruction */}
|
||||
{chatConfig?.instruction && (
|
||||
{instruction && (
|
||||
<Box
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
@@ -179,7 +179,7 @@ const RenderInput = () => {
|
||||
color={'myGray.600'}
|
||||
mb={4}
|
||||
>
|
||||
<Markdown source={chatConfig.instruction} />
|
||||
<Markdown source={instruction} />
|
||||
</Box>
|
||||
)}
|
||||
{/* file select(Abandoned) */}
|
||||
|
||||
@@ -7,9 +7,13 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import AIResponseBox from '../../../components/AIResponseBox';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import ComplianceTip from '@/components/common/ComplianceTip/index';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
const RenderOutput = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
|
||||
const pluginOutputs = useMemo(() => {
|
||||
const pluginOutputs = histories?.[1]?.responseData?.find(
|
||||
(item) => item.moduleType === FlowNodeTypeEnum.pluginOutput
|
||||
|
||||
@@ -4,16 +4,20 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
const RenderResponseDetail = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
const responseData = histories?.[1]?.responseData || [];
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
|
||||
const responseData = chatRecords?.[1]?.responseData || [];
|
||||
|
||||
return isChatting ? (
|
||||
<>{t('chat:in_progress')}</>
|
||||
) : (
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
<ResponseBox useMobile={true} response={responseData} />
|
||||
<ResponseBox useMobile={true} response={responseData} dataId={chatRecords?.[1]?.dataId} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useFileUpload } from '../../ChatBox/hooks/useFileUpload';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FilePreview from '../../components/FilePreview';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
@@ -18,6 +17,8 @@ import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
@@ -33,10 +34,9 @@ const FileSelector = ({
|
||||
value: any;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { variablesForm, histories, chatId, outLinkAuthData } = useContextSelector(
|
||||
PluginRunContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const histories = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const fileCtrl = useFieldArray({
|
||||
control: variablesForm.control,
|
||||
@@ -53,8 +53,6 @@ const FileSelector = ({
|
||||
replaceFiles,
|
||||
hasFileUploading
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig: {
|
||||
canSelectFile: input.canSelectFile ?? true,
|
||||
canSelectImg: input.canSelectImg ?? false,
|
||||
@@ -83,7 +81,7 @@ const FileSelector = ({
|
||||
useRequest2(uploadFiles, {
|
||||
manual: false,
|
||||
errorToast: t('common:upload_file_error'),
|
||||
refreshDeps: [fileList, outLinkAuthData, chatId]
|
||||
refreshDeps: [fileList]
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import {
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
RuntimeUserPromptType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues, useForm } from 'react-hook-form';
|
||||
import { AIChatItemValueItemType, RuntimeUserPromptType } from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
@@ -14,48 +10,51 @@ import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/c
|
||||
import { generatingMessageProps } from '../type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { AppFileSelectConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { defaultAppSelectFileConfig } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
type PluginRunContextType = OutLinkChatAuthProps &
|
||||
PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: ChatBoxInputFormType) => Promise<any>;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
};
|
||||
type PluginRunContextType = PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: ChatBoxInputFormType) => Promise<any>;
|
||||
instruction: string;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
};
|
||||
|
||||
export const PluginRunContext = createContext<PluginRunContextType>({
|
||||
pluginInputs: [],
|
||||
histories: [],
|
||||
setHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
appId: '',
|
||||
tab: PluginRunBoxTabEnum.input,
|
||||
setTab: function (value: React.SetStateAction<PluginRunBoxTabEnum>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
isChatting: false,
|
||||
onSubmit: function (e: FieldValues): Promise<any> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
outLinkAuthData: {},
|
||||
//@ts-ignore
|
||||
variablesForm: undefined
|
||||
instruction: '',
|
||||
fileSelectConfig: defaultAppSelectFileConfig,
|
||||
appId: '',
|
||||
chatId: '',
|
||||
outLinkAuthData: {}
|
||||
});
|
||||
|
||||
const PluginRunContextProvider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
children,
|
||||
...props
|
||||
}: PluginRunBoxProps & { children: ReactNode }) => {
|
||||
const { pluginInputs, onStartChat, setHistories, histories, setTab } = props;
|
||||
const { onStartChat } = props;
|
||||
|
||||
const pluginInputs = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.pluginInputs);
|
||||
const setTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
const chatConfig = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.chatConfig);
|
||||
|
||||
const { instruction = '', fileSelectConfig = defaultAppSelectFileConfig } = useMemo(
|
||||
() => chatConfig || {},
|
||||
[chatConfig]
|
||||
);
|
||||
|
||||
const { toast } = useToast();
|
||||
const chatController = useRef(new AbortController());
|
||||
@@ -65,23 +64,9 @@ const PluginRunContextProvider = ({
|
||||
chatController.current?.abort('stop');
|
||||
}, []);
|
||||
|
||||
const outLinkAuthData = useMemo(
|
||||
() => ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
[shareId, outLinkUid, teamId, teamToken]
|
||||
);
|
||||
|
||||
const variablesForm = useForm<ChatBoxInputFormType>({
|
||||
defaultValues: {}
|
||||
});
|
||||
|
||||
const generatingMessage = useCallback(
|
||||
({ event, text = '', status, name, tool }: generatingMessageProps) => {
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1 || item.obj !== ChatRoleEnum.AI) return item;
|
||||
|
||||
@@ -165,12 +150,14 @@ const PluginRunContextProvider = ({
|
||||
})
|
||||
);
|
||||
},
|
||||
[setHistories]
|
||||
[setChatRecords]
|
||||
);
|
||||
|
||||
const isChatting = useMemo(
|
||||
() => histories[histories.length - 1] && histories[histories.length - 1]?.status !== 'finish',
|
||||
[histories]
|
||||
() =>
|
||||
chatRecords[chatRecords.length - 1] &&
|
||||
chatRecords[chatRecords.length - 1]?.status !== 'finish',
|
||||
[chatRecords]
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
@@ -189,7 +176,7 @@ const PluginRunContextProvider = ({
|
||||
const abortSignal = new AbortController();
|
||||
chatController.current = abortSignal;
|
||||
|
||||
setHistories([
|
||||
setChatRecords([
|
||||
{
|
||||
...getPluginRunUserQuery({
|
||||
pluginInputs,
|
||||
@@ -255,7 +242,7 @@ const PluginRunContextProvider = ({
|
||||
});
|
||||
}
|
||||
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -267,7 +254,7 @@ const PluginRunContextProvider = ({
|
||||
);
|
||||
} catch (err: any) {
|
||||
toast({ title: err.message, status: 'error' });
|
||||
setHistories((state) =>
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
@@ -284,7 +271,7 @@ const PluginRunContextProvider = ({
|
||||
isChatting,
|
||||
onStartChat,
|
||||
pluginInputs,
|
||||
setHistories,
|
||||
setChatRecords,
|
||||
setTab,
|
||||
t,
|
||||
toast
|
||||
@@ -295,8 +282,8 @@ const PluginRunContextProvider = ({
|
||||
...props,
|
||||
isChatting,
|
||||
onSubmit,
|
||||
outLinkAuthData,
|
||||
variablesForm
|
||||
instruction,
|
||||
fileSelectConfig
|
||||
};
|
||||
return <PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>;
|
||||
};
|
||||
|
||||
@@ -2,29 +2,23 @@ import React from 'react';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import RenderInput from './components/RenderInput';
|
||||
import PluginRunContextProvider, { PluginRunContext } from './context';
|
||||
import PluginRunContextProvider from './context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import RenderOutput from './components/RenderOutput';
|
||||
import RenderResponseDetail from './components/RenderResponseDetail';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const PluginRunBox = () => {
|
||||
const { tab } = useContextSelector(PluginRunContext, (v) => v);
|
||||
const PluginRunBox = (props: PluginRunBoxProps) => {
|
||||
const tab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const formatTab = props.showTab || tab;
|
||||
|
||||
return (
|
||||
<>
|
||||
{tab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||
{tab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||
{tab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: PluginRunBoxProps) => {
|
||||
return (
|
||||
<PluginRunContextProvider {...props}>
|
||||
<PluginRunBox />
|
||||
{formatTab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||
{formatTab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||
{formatTab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||
</PluginRunContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Render;
|
||||
export default PluginRunBox;
|
||||
|
||||
@@ -7,18 +7,12 @@ import React from 'react';
|
||||
import { onStartChatType } from '../type';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
|
||||
export type PluginRunBoxProps = OutLinkChatAuthProps & {
|
||||
pluginInputs: FlowNodeInputItemType[];
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
histories: ChatSiteItemType[]; // chatHistories[1] is the response
|
||||
setHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
export type PluginRunBoxProps = {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
outLinkAuthData?: OutLinkChatAuthProps;
|
||||
|
||||
onStartChat?: onStartChatType;
|
||||
onNewChat?: () => void;
|
||||
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
tab: PluginRunBoxTabEnum;
|
||||
setTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
|
||||
chatConfig?: AppChatConfigType;
|
||||
showTab?: PluginRunBoxTabEnum; // 如何设置了该字段,全局都 tab 不生效
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { FieldArrayWithId } from 'react-hook-form';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import { Box, CircularProgress, Flex, HStack, Image } from '@chakra-ui/react';
|
||||
import { Box, CircularProgress, Flex, HStack } from '@chakra-ui/react';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
|
||||
const RenderFilePreview = ({
|
||||
fileList,
|
||||
@@ -29,6 +30,8 @@ const RenderFilePreview = ({
|
||||
{fileList.map((item, index) => {
|
||||
const isFile = item.type === ChatFileTypeEnum.file;
|
||||
const isImage = item.type === ChatFileTypeEnum.image;
|
||||
const icon = getFileIcon(item.name);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
key={index}
|
||||
@@ -73,7 +76,7 @@ const RenderFilePreview = ({
|
||||
{isImage && (
|
||||
<MyImage
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
src={item.icon || item.url}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
borderRadius={'md'}
|
||||
@@ -82,7 +85,7 @@ const RenderFilePreview = ({
|
||||
)}
|
||||
{isFile && (
|
||||
<HStack alignItems={'center'} h={'full'}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<MyIcon name={icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} pr={2} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './PluginRunBox/constants';
|
||||
import {
|
||||
ChatBoxInputFormType,
|
||||
ComponentRef as ChatComponentRef,
|
||||
SendPromptFnType
|
||||
} from './ChatBox/type';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
import { getChatRecords } from '@/web/core/chat/api';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import type { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRecords';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
|
||||
export const useChat = (params?: { chatId?: string; appId: string; type?: GetChatTypeEnum }) => {
|
||||
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||
const variablesForm = useForm<ChatBoxInputFormType>();
|
||||
// plugin
|
||||
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||
|
||||
const resetVariables = useCallback(
|
||||
(props?: { variables?: Record<string, any> }) => {
|
||||
const { variables = {} } = props || {};
|
||||
|
||||
// Reset to empty input
|
||||
const data = variablesForm.getValues();
|
||||
|
||||
// Reset the old variables to empty
|
||||
const resetVariables: Record<string, any> = {};
|
||||
for (const key in data.variables) {
|
||||
resetVariables[key] = (() => {
|
||||
if (Array.isArray(data.variables[key])) {
|
||||
return [];
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
}
|
||||
|
||||
variablesForm.reset({
|
||||
...data,
|
||||
variables: {
|
||||
...resetVariables,
|
||||
...variables
|
||||
}
|
||||
});
|
||||
},
|
||||
[variablesForm]
|
||||
);
|
||||
|
||||
const clearChatRecords = useCallback(() => {
|
||||
const data = variablesForm.getValues();
|
||||
for (const key in data.variables) {
|
||||
variablesForm.setValue(`variables.${key}`, '');
|
||||
}
|
||||
|
||||
ChatBoxRef.current?.restartChat?.();
|
||||
}, [variablesForm]);
|
||||
|
||||
const {
|
||||
data: chatRecords,
|
||||
ScrollData,
|
||||
setData: setChatRecords,
|
||||
total: totalRecordsCount
|
||||
} = useScrollPagination(
|
||||
async (data: getPaginationRecordsBody): Promise<PaginationResponse<ChatSiteItemType>> => {
|
||||
const res = await getChatRecords(data);
|
||||
|
||||
// First load scroll to bottom
|
||||
if (data.offset === 0) {
|
||||
function scrollToBottom() {
|
||||
requestAnimationFrame(
|
||||
ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom
|
||||
);
|
||||
}
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
return {
|
||||
...res,
|
||||
list: res.list.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}))
|
||||
};
|
||||
},
|
||||
{
|
||||
pageSize: 10,
|
||||
refreshDeps: [params],
|
||||
params,
|
||||
scrollLoadType: 'top'
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
ChatBoxRef,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
clearChatRecords,
|
||||
resetVariables,
|
||||
chatRecords,
|
||||
ScrollData,
|
||||
setChatRecords,
|
||||
totalRecordsCount
|
||||
};
|
||||
};
|
||||
|
||||
export const onSendPrompt: SendPromptFnType = (e) => eventBus.emit(EventNameEnum.sendQuestion, e);
|
||||
@@ -8,12 +8,6 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
@@ -31,15 +25,16 @@ import {
|
||||
UserSelectInteractive
|
||||
} from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { isEqual } from 'lodash';
|
||||
import { onSendPrompt } from '../ChatContainer/useChat';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { SendPromptFnType } from '../ChatContainer/ChatBox/type';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
|
||||
type props = {
|
||||
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
||||
@@ -47,6 +42,8 @@ type props = {
|
||||
isChatting: boolean;
|
||||
};
|
||||
|
||||
const onSendPrompt: SendPromptFnType = (e) => eventBus.emit(EventNameEnum.sendQuestion, e);
|
||||
|
||||
const RenderText = React.memo(function RenderText({
|
||||
showAnimation,
|
||||
text
|
||||
@@ -215,6 +212,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} gap={2} w={'250px'}>
|
||||
{interactive.params.description && <Markdown source={interactive.params.description} />}
|
||||
{interactive.params.inputForm?.map((input) => (
|
||||
<Box key={input.label}>
|
||||
<Flex mb={1} alignItems={'center'}>
|
||||
|
||||
@@ -31,10 +31,12 @@ type sideTabItemType = {
|
||||
/* Per response value */
|
||||
export const WholeResponseContent = ({
|
||||
activeModule,
|
||||
hideTabs
|
||||
hideTabs,
|
||||
dataId
|
||||
}: {
|
||||
activeModule: ChatHistoryItemResType;
|
||||
hideTabs?: boolean;
|
||||
dataId?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -231,7 +233,14 @@ export const WholeResponseContent = ({
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('common:core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList canEditDataset canViewSource rawSearch={activeModule.quoteList} />}
|
||||
rawDom={
|
||||
<QuoteList
|
||||
canEditDataset
|
||||
canViewSource
|
||||
chatItemId={dataId}
|
||||
rawSearch={activeModule.quoteList}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -529,10 +538,12 @@ const SideTabItem = ({
|
||||
/* Modal main container */
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
dataId,
|
||||
hideTabs = false,
|
||||
useMobile = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
dataId?: string;
|
||||
hideTabs?: boolean;
|
||||
useMobile?: boolean;
|
||||
}) {
|
||||
@@ -655,7 +666,7 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={'5 0 0'} w={0} height={'100%'}>
|
||||
<WholeResponseContent activeModule={activeModule} hideTabs={hideTabs} />
|
||||
<WholeResponseContent dataId={dataId} activeModule={activeModule} hideTabs={hideTabs} />
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
@@ -715,7 +726,11 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'}>
|
||||
<WholeResponseContent activeModule={activeModule} hideTabs={hideTabs} />
|
||||
<WholeResponseContent
|
||||
dataId={dataId}
|
||||
activeModule={activeModule}
|
||||
hideTabs={hideTabs}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
@@ -754,7 +769,7 @@ const WholeResponseModal = ({ onClose, dataId }: { onClose: () => void; dataId:
|
||||
}
|
||||
>
|
||||
{!!response?.length ? (
|
||||
<ResponseBox response={response} />
|
||||
<ResponseBox response={response} dataId={dataId} />
|
||||
) : (
|
||||
<EmptyTip text={t('chat:no_workflow_response')} />
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
|
||||
|
||||
const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal'));
|
||||
|
||||
@@ -45,12 +46,13 @@ const scoreTheme: Record<
|
||||
const QuoteItem = ({
|
||||
quoteItem,
|
||||
canViewSource,
|
||||
canEditDataset
|
||||
canEditDataset,
|
||||
...RawSourceBoxProps
|
||||
}: {
|
||||
quoteItem: SearchDataResponseItemType;
|
||||
canViewSource?: boolean;
|
||||
canEditDataset?: boolean;
|
||||
}) => {
|
||||
} & Omit<readCollectionSourceBody, 'collectionId'>) => {
|
||||
const { t } = useTranslation();
|
||||
const [editInputData, setEditInputData] = useState<{ dataId: string; collectionId: string }>();
|
||||
|
||||
@@ -196,6 +198,7 @@ const QuoteItem = ({
|
||||
sourceName={quoteItem.sourceName}
|
||||
sourceId={quoteItem.sourceId}
|
||||
canView={canViewSource}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{quoteItem.id && canEditDataset && (
|
||||
|
||||
@@ -6,10 +6,10 @@ import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollect
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read';
|
||||
|
||||
type Props = BoxProps &
|
||||
ShareChatAuthProps & {
|
||||
readCollectionSourceBody & {
|
||||
sourceName?: string;
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
@@ -18,11 +18,18 @@ type Props = BoxProps &
|
||||
|
||||
const RawSourceBox = ({
|
||||
sourceId,
|
||||
collectionId,
|
||||
sourceName = '',
|
||||
canView = true,
|
||||
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
|
||||
...props
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -33,8 +40,13 @@ const RawSourceBox = ({
|
||||
const icon = useMemo(() => getSourceNameIcon({ sourceId, sourceName }), [sourceId, sourceName]);
|
||||
const read = getCollectionSourceAndOpen({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -56,7 +68,7 @@ const RawSourceBox = ({
|
||||
: {})}
|
||||
{...props}
|
||||
>
|
||||
<MyIcon name={icon as any} w={['16px', '20px']} mr={2} />
|
||||
<MyIcon name={icon as any} w={['1rem', '1.25rem']} mr={2} />
|
||||
<Box
|
||||
maxW={['200px', '300px']}
|
||||
className={props.className ?? 'textEllipsis'}
|
||||
|
||||
@@ -45,6 +45,18 @@ const SearchParamsTip = ({
|
||||
borderRadius={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'primary.1'}
|
||||
sx={{
|
||||
'&::-webkit-scrollbar': {
|
||||
height: '6px',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'myGray.250 !important',
|
||||
'&:hover': {
|
||||
backgroundColor: 'myGray.300 !important'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table fontSize={'xs'} overflow={'overlay'}>
|
||||
<Thead>
|
||||
|
||||
1
projects/app/src/global/core/ai/api.d.ts
vendored
1
projects/app/src/global/core/ai/api.d.ts
vendored
@@ -2,5 +2,6 @@ import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||
|
||||
export type CreateQuestionGuideParams = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
messages: ChatCompletionMessageParam[];
|
||||
};
|
||||
|
||||
1
projects/app/src/global/core/app/api.d.ts
vendored
1
projects/app/src/global/core/app/api.d.ts
vendored
@@ -15,7 +15,6 @@ export type AppUpdateParams = {
|
||||
};
|
||||
|
||||
export type PostPublishAppProps = {
|
||||
type: AppTypeEnum;
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
|
||||
5
projects/app/src/global/core/chat/api.d.ts
vendored
5
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -5,7 +5,8 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { RequestPaging } from '@/types';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
export type GetChatSpeechProps = {
|
||||
export type GetChatSpeechProps = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
ttsConfig: AppTTSConfigType;
|
||||
input: string;
|
||||
shareId?: string;
|
||||
@@ -73,7 +74,7 @@ export type DelHistoryProps = OutLinkChatAuthProps & {
|
||||
chatId: string;
|
||||
};
|
||||
export type ClearHistoriesProps = OutLinkChatAuthProps & {
|
||||
appId?: string;
|
||||
appId: string;
|
||||
};
|
||||
|
||||
/* -------- chat item ---------- */
|
||||
|
||||
@@ -28,10 +28,12 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) {
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
historyItem.responseData
|
||||
?.map((item) => {
|
||||
if (item.pluginDetail || item.toolDetail) {
|
||||
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
|
||||
}
|
||||
return item;
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
|
||||
|
||||
55
projects/app/src/instrumentation.ts
Normal file
55
projects/app/src/instrumentation.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { exit } from 'process';
|
||||
|
||||
/*
|
||||
Init system
|
||||
*/
|
||||
export async function register() {
|
||||
try {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
// 基础系统初始化
|
||||
const [
|
||||
{ connectMongo },
|
||||
{ systemStartCb },
|
||||
{ initGlobalVariables },
|
||||
{ getInitConfig },
|
||||
{ initVectorStore },
|
||||
{ initRootUser },
|
||||
{ getSystemPluginCb },
|
||||
{ startMongoWatch },
|
||||
{ startCron },
|
||||
{ startTrainingQueue }
|
||||
] = await Promise.all([
|
||||
import('@fastgpt/service/common/mongo/init'),
|
||||
import('@fastgpt/service/common/system/tools'),
|
||||
import('@/service/common/system'),
|
||||
import('@/service/common/system'),
|
||||
import('@fastgpt/service/common/vectorStore/controller'),
|
||||
import('@/service/mongo'),
|
||||
import('@/service/core/app/plugin'),
|
||||
import('@/service/common/system/volumnMongoWatch'),
|
||||
import('@/service/common/system/cron'),
|
||||
import('@/service/core/dataset/training/utils')
|
||||
]);
|
||||
|
||||
// 执行初始化流程
|
||||
systemStartCb();
|
||||
initGlobalVariables();
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectMongo();
|
||||
|
||||
//init system config;init vector database;init root user
|
||||
await Promise.all([getInitConfig(), initVectorStore(), initRootUser()]);
|
||||
|
||||
getSystemPluginCb();
|
||||
startMongoWatch();
|
||||
startCron();
|
||||
startTrainingQueue(true);
|
||||
|
||||
console.log('Init system success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Init system error', error);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
Read db file content and response 3000 words
|
||||
*/
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { authFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetSourceReadTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { readDatasetSourceRawText } from '@fastgpt/service/core/dataset/read';
|
||||
@@ -26,7 +26,7 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
|
||||
|
||||
const { teamId } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
return authFile({
|
||||
return authCollectionFile({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
|
||||
@@ -9,7 +9,17 @@ import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { authFrequencyLimit } from '@/service/common/frequencyLimit/api';
|
||||
import { addSeconds } from 'date-fns';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
export type UploadChatFileProps = {
|
||||
appId: string;
|
||||
} & OutLinkChatAuthProps;
|
||||
export type UploadDatasetFileProps = {
|
||||
datasetId: string;
|
||||
};
|
||||
|
||||
const authUploadLimit = (tmbId: string) => {
|
||||
if (!global.feConfigs.uploadFileMaxAmount) return;
|
||||
@@ -28,15 +38,43 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const upload = getUploadModel({
|
||||
maxSize: global.feConfigs?.uploadFileMaxSize
|
||||
});
|
||||
const { file, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
const { file, bucketName, metadata, data } = await upload.doUpload<
|
||||
UploadChatFileProps | UploadDatasetFileProps
|
||||
>(req, res);
|
||||
filePaths.push(file.path);
|
||||
const { teamId, tmbId, outLinkUid } = await authChatCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
|
||||
await authUploadLimit(outLinkUid || tmbId);
|
||||
const { teamId, uid } = await (async () => {
|
||||
if (bucketName === 'chat') {
|
||||
const chatData = data as UploadChatFileProps;
|
||||
const authData = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...chatData
|
||||
});
|
||||
return {
|
||||
teamId: authData.teamId,
|
||||
uid: authData.uid
|
||||
};
|
||||
}
|
||||
if (bucketName === 'dataset') {
|
||||
const chatData = data as UploadDatasetFileProps;
|
||||
const authData = await authDataset({
|
||||
datasetId: chatData.datasetId,
|
||||
per: WritePermissionVal,
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
return {
|
||||
teamId: authData.teamId,
|
||||
uid: authData.tmbId
|
||||
};
|
||||
}
|
||||
return Promise.reject('bucketName is empty');
|
||||
})();
|
||||
|
||||
await authUploadLimit(uid);
|
||||
|
||||
addLog.info(`Upload file success ${file.originalname}, cost ${Date.now() - start}ms`);
|
||||
|
||||
@@ -46,7 +84,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
|
||||
const fileId = await uploadFile({
|
||||
teamId,
|
||||
tmbId,
|
||||
uid,
|
||||
bucketName,
|
||||
path: file.path,
|
||||
filename: file.originalname,
|
||||
@@ -61,7 +99,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
previewUrl: `${ReadFileBaseUrl}/${file.originalname}?token=${await createFileToken({
|
||||
bucketName,
|
||||
teamId,
|
||||
tmbId,
|
||||
uid,
|
||||
fileId
|
||||
})}`
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
import { UploadImgProps } from '@fastgpt/global/common/file/api';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
/*
|
||||
Upload avatar image
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const body = req.body as UploadImgProps;
|
||||
|
||||
const { teamId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId } = await authCert({ req, authToken: true });
|
||||
|
||||
const imgId = await uploadMongoImg({
|
||||
teamId,
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
|
||||
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
async function handler(
|
||||
req: ApiRequestProps<
|
||||
OutLinkChatAuthProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
}
|
||||
>,
|
||||
res: NextApiResponse<any>
|
||||
) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { messages } = req.body as CreateQuestionGuideParams;
|
||||
const { messages } = req.body;
|
||||
|
||||
const { tmbId, teamId } = await authChatCert({
|
||||
req,
|
||||
@@ -40,3 +50,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
|
||||
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { messages } = req.body;
|
||||
|
||||
const { tmbId, teamId } = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
const qgModel = global.llmModels[0];
|
||||
|
||||
const { result, tokens } = await createQuestionGuide({
|
||||
messages,
|
||||
model: qgModel.model
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: result
|
||||
});
|
||||
|
||||
pushQuestionGuideUsage({
|
||||
tokens,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -43,10 +43,10 @@ async function handler(
|
||||
});
|
||||
|
||||
return { id: appId };
|
||||
} else {
|
||||
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
|
||||
}
|
||||
|
||||
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,14 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
|
||||
async function handler(
|
||||
req: ApiRequestProps<PostPublishAppProps>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<{}> {
|
||||
const { appId } = req.query as { appId: string };
|
||||
const {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
chatConfig,
|
||||
type,
|
||||
isPublish,
|
||||
versionName
|
||||
} = req.body as PostPublishAppProps;
|
||||
const { nodes = [], edges = [], chatConfig, isPublish, versionName } = req.body;
|
||||
|
||||
const { tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
@@ -50,11 +47,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
chatConfig,
|
||||
updateTime: new Date(),
|
||||
version: 'v2',
|
||||
type,
|
||||
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||
: null,
|
||||
// 只有发布才会更新定时器
|
||||
...(isPublish && {
|
||||
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||
: null
|
||||
}),
|
||||
'pluginData.nodeVersion': _id
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { sseErrRes } from '@fastgpt/service/common/response';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import {
|
||||
DispatchNodeResponseKeyEnum,
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import type { UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||
import {
|
||||
concatHistories,
|
||||
getChatTitleFromChatMessage,
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import {
|
||||
@@ -18,27 +24,37 @@ import {
|
||||
updatePluginInputByVariables
|
||||
} from '@fastgpt/global/core/workflow/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { chatValue2RuntimePrompt, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import {
|
||||
getLastInteractiveValue,
|
||||
getMaxHistoryLimitFromNodes,
|
||||
getWorkflowEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
rewriteNodeOutputByHistories,
|
||||
storeNodes2RuntimeNodes
|
||||
storeNodes2RuntimeNodes,
|
||||
textAdaptGptResponse
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { saveChat, updateInteractiveChat } from '@fastgpt/service/core/chat/saveChat';
|
||||
|
||||
export type Props = {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
responseChatItemId: string;
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
variables: Record<string, any>;
|
||||
appId: string;
|
||||
appName: string;
|
||||
chatId: string;
|
||||
chatConfig: AppChatConfigType;
|
||||
};
|
||||
|
||||
@@ -55,10 +71,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
messages = [],
|
||||
responseChatItemId,
|
||||
variables = {},
|
||||
appName,
|
||||
appId,
|
||||
chatConfig
|
||||
chatConfig,
|
||||
chatId
|
||||
} = req.body as Props;
|
||||
try {
|
||||
if (!Array.isArray(nodes)) {
|
||||
@@ -71,15 +89,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
|
||||
|
||||
/* user auth */
|
||||
const [{ app }, { teamId, tmbId }] = await Promise.all([
|
||||
authApp({ req, authToken: true, appId, per: ReadPermissionVal }),
|
||||
authCert({
|
||||
req,
|
||||
authToken: true
|
||||
})
|
||||
]);
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
const { app, teamId, tmbId } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
@@ -99,48 +114,79 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return latestHumanChat;
|
||||
})();
|
||||
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, chatMessages));
|
||||
const limit = getMaxHistoryLimitFromNodes(nodes);
|
||||
const [{ histories }, chatDetail, { user }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
offset: 0,
|
||||
limit,
|
||||
field: `dataId obj value nodeOutputs`
|
||||
}),
|
||||
MongoChat.findOne({ appId: app._id, chatId }, 'source variableList variables'),
|
||||
// auth balance
|
||||
getUserChatInfoAndAuthTeamPoints(tmbId)
|
||||
]);
|
||||
|
||||
// Plugin need to replace inputs
|
||||
if (chatDetail?.variables) {
|
||||
variables = {
|
||||
...chatDetail.variables,
|
||||
...variables
|
||||
};
|
||||
}
|
||||
|
||||
const newHistories = concatHistories(histories, chatMessages);
|
||||
|
||||
// Get runtimeNodes
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
|
||||
if (isPlugin) {
|
||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||
variables = {};
|
||||
} else {
|
||||
if (!userQuestion.value) {
|
||||
throw new Error('Params Error');
|
||||
}
|
||||
}
|
||||
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
|
||||
|
||||
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
|
||||
const workflowResponseWrite = getWorkflowResponseWrite({
|
||||
res,
|
||||
detail: true,
|
||||
streamResponse: true
|
||||
streamResponse: true,
|
||||
id: chatId,
|
||||
showNodeStatus: true
|
||||
});
|
||||
|
||||
/* start process */
|
||||
const { flowResponses, flowUsages } = await dispatchWorkFlow({
|
||||
const { flowResponses, assistantResponses, newVariables, flowUsages } = await dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'test',
|
||||
user,
|
||||
uid: tmbId,
|
||||
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: tmbId,
|
||||
user,
|
||||
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges, chatMessages),
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
chatConfig,
|
||||
histories: chatMessages,
|
||||
histories: newHistories,
|
||||
stream: true,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite
|
||||
});
|
||||
|
||||
workflowResponseWrite({
|
||||
event: SseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: null,
|
||||
finish_reason: 'stop'
|
||||
})
|
||||
});
|
||||
responseWrite({
|
||||
res,
|
||||
event: SseResponseEventEnum.answer,
|
||||
@@ -152,7 +198,44 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
data: JSON.stringify(flowResponses)
|
||||
});
|
||||
|
||||
res.end();
|
||||
// save chat
|
||||
const isInteractiveRequest = !!getLastInteractiveValue(histories);
|
||||
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? getSystemTime(user.timezone)
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
|
||||
const aiResponse: AIChatItemType & { dataId?: string } = {
|
||||
dataId: responseChatItemId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: assistantResponses,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
|
||||
};
|
||||
|
||||
if (isInteractiveRequest) {
|
||||
await updateInteractiveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
nodes,
|
||||
appChatConfig: chatConfig,
|
||||
variables: newVariables,
|
||||
isUpdateUseTime: false, // owner update use time
|
||||
newTitle,
|
||||
source: ChatSourceEnum.test,
|
||||
content: [userQuestion, aiResponse]
|
||||
});
|
||||
}
|
||||
|
||||
pushChatUsage({
|
||||
appName,
|
||||
@@ -165,8 +248,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
} catch (err: any) {
|
||||
res.status(500);
|
||||
sseErrRes(res, err);
|
||||
res.end();
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,44 +1,48 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { ClearHistoriesProps } from '@/global/core/chat/api';
|
||||
import { authOutLink } from '@/service/support/permission/auth/outLink';
|
||||
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
|
||||
/* clear chat history */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query as ClearHistoriesProps;
|
||||
async function handler(req: ApiRequestProps<{}, ClearHistoriesProps>, res: NextApiResponse) {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query;
|
||||
|
||||
let chatAppId = appId!;
|
||||
const {
|
||||
teamId: chatTeamId,
|
||||
tmbId,
|
||||
uid,
|
||||
authType
|
||||
} = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query
|
||||
});
|
||||
|
||||
const match = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
chatAppId = appId;
|
||||
if (shareId && outLinkUid && authType === 'outLink') {
|
||||
return {
|
||||
shareId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (teamId && teamToken) {
|
||||
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
|
||||
return {
|
||||
teamId,
|
||||
teamId: chatTeamId,
|
||||
appId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (appId) {
|
||||
const { tmbId } = await authCert({ req, authToken: true, authApiKey: true });
|
||||
|
||||
if (teamId && teamToken && authType === 'teamDomain') {
|
||||
return {
|
||||
teamId: chatTeamId,
|
||||
appId,
|
||||
outLinkUid: uid
|
||||
};
|
||||
}
|
||||
if (authType === 'token') {
|
||||
return {
|
||||
teamId: chatTeamId,
|
||||
tmbId,
|
||||
appId,
|
||||
source: ChatSourceEnum.online
|
||||
@@ -54,24 +58,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
await deleteChatFiles({ chatIdList: idList });
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
return mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.deleteMany(
|
||||
{
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await MongoChat.deleteMany(
|
||||
{
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
|
||||
|
||||
/* clear chat history */
|
||||
@@ -18,8 +17,7 @@ async function handler(req: ApiRequestProps<{}, DelHistoryProps>, res: NextApiRe
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: WritePermissionVal
|
||||
...req.query
|
||||
});
|
||||
|
||||
await deleteChatFiles({ chatIdList: [chatId] });
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import type { AdminUpdateFeedbackParams } from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -20,9 +19,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: ReadPermissionVal
|
||||
chatId
|
||||
});
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
|
||||
@@ -5,7 +5,6 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type { CloseCustomFeedbackParams } from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
|
||||
/* remove custom feedback */
|
||||
@@ -21,9 +20,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: ReadPermissionVal
|
||||
chatId
|
||||
});
|
||||
await authCert({ req, authToken: true });
|
||||
|
||||
|
||||
@@ -1,71 +1,42 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { UpdateChatFeedbackProps } from '@fastgpt/global/core/chat/api';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const {
|
||||
appId,
|
||||
chatId,
|
||||
dataId,
|
||||
shareId,
|
||||
teamId,
|
||||
teamToken,
|
||||
outLinkUid,
|
||||
userBadFeedback,
|
||||
userGoodFeedback
|
||||
} = req.body as UpdateChatFeedbackProps;
|
||||
async function handler(req: ApiRequestProps<UpdateChatFeedbackProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, dataId, userBadFeedback, userGoodFeedback } = req.body;
|
||||
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
teamId,
|
||||
teamToken,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
if (!dataId) {
|
||||
throw new Error('dataId is required');
|
||||
}
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
...(userBadFeedback === undefined && { userBadFeedback: '' }),
|
||||
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
|
||||
},
|
||||
$set: {
|
||||
...(userBadFeedback !== undefined && { userBadFeedback }),
|
||||
...(userGoodFeedback !== undefined && { userGoodFeedback })
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
if (!chatId || !dataId) {
|
||||
return Promise.reject('chatId or dataId is empty');
|
||||
}
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
...(userBadFeedback === undefined && { userBadFeedback: '' }),
|
||||
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
|
||||
},
|
||||
$set: {
|
||||
...(userBadFeedback !== undefined && { userBadFeedback }),
|
||||
...(userGoodFeedback !== undefined && { userGoodFeedback })
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { GetHistoriesProps } from '@/global/core/chat/api';
|
||||
import { addMonths } from 'date-fns';
|
||||
|
||||
export type getHistoriesQuery = {};
|
||||
|
||||
export type getHistoriesBody = PaginationProps<GetHistoriesProps>;
|
||||
@@ -17,8 +19,7 @@ async function handler(
|
||||
req: ApiRequestProps<getHistoriesBody, getHistoriesQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<PaginationResponse<getHistoriesResponse>> {
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } =
|
||||
req.body as getHistoriesBody;
|
||||
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } = req.body;
|
||||
|
||||
const match = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
@@ -28,7 +29,7 @@ async function handler(
|
||||
shareId,
|
||||
outLinkUid: uid,
|
||||
updateTime: {
|
||||
$gte: new Date(new Date().setDate(new Date().getDate() - 30))
|
||||
$gte: addMonths(new Date(), -1)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -62,7 +63,8 @@ async function handler(
|
||||
await MongoChat.find(match, 'chatId title top customTitle appId updateTime')
|
||||
.sort({ top: -1, updateTime: -1 })
|
||||
.skip(offset)
|
||||
.limit(pageSize),
|
||||
.limit(pageSize)
|
||||
.lean(),
|
||||
MongoChat.countDocuments(match)
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GetChatRecordsProps } from '@/global/core/chat/api';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { transformPreviewHistories } from '@/global/core/chat/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
@@ -11,7 +10,6 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { authOutLink } from '@/service/support/permission/auth/outLink';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
@@ -42,14 +40,13 @@ async function handler(
|
||||
};
|
||||
}
|
||||
|
||||
const [app] = await Promise.all([
|
||||
const [app, { responseDetail, showNodeStatus, authType }] = await Promise.all([
|
||||
MongoApp.findById(appId, 'type').lean(),
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.body,
|
||||
per: ReadPermissionVal
|
||||
...req.body
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -57,21 +54,14 @@ async function handler(
|
||||
return Promise.reject(AppErrEnum.unExist);
|
||||
}
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
const shareChat = await (async () => {
|
||||
if (type === GetChatTypeEnum.outLink)
|
||||
return await authOutLink({
|
||||
shareId: req.body.shareId,
|
||||
outLinkUid: req.body.outLinkUid
|
||||
}).then((result) => result.shareChat);
|
||||
})();
|
||||
const isOutLink = authType === GetChatTypeEnum.outLink;
|
||||
|
||||
const fieldMap = {
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time ${
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time hideInUI ${
|
||||
DispatchNodeResponseKeyEnum.nodeResponse
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
};
|
||||
|
||||
const { total, histories } = await getChatItems({
|
||||
@@ -82,10 +72,8 @@ async function handler(
|
||||
limit: pageSize
|
||||
});
|
||||
|
||||
const responseDetail = !shareChat || shareChat.responseDetail;
|
||||
|
||||
// Remove important information
|
||||
if (shareChat && app.type !== AppTypeEnum.plugin) {
|
||||
if (isOutLink && app.type !== AppTypeEnum.plugin) {
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({
|
||||
@@ -93,7 +81,7 @@ async function handler(
|
||||
responseDetail
|
||||
});
|
||||
|
||||
if (shareChat.showNodeStatus === false) {
|
||||
if (showNodeStatus === false) {
|
||||
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
ReadPermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
|
||||
export type getResDataQuery = OutLinkChatAuthProps & {
|
||||
chatId?: string;
|
||||
@@ -32,29 +26,13 @@ async function handler(
|
||||
return {};
|
||||
}
|
||||
|
||||
// 1. Un login api: share chat, team chat
|
||||
// 2. Login api: account chat, chat log
|
||||
const authData = await (() => {
|
||||
try {
|
||||
return authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
} catch (error) {
|
||||
return authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
appId,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
const [chatData] = await Promise.all([
|
||||
const [{ responseDetail }, chatData] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query
|
||||
}),
|
||||
MongoChatItem.findOne(
|
||||
{
|
||||
appId,
|
||||
@@ -62,8 +40,7 @@ async function handler(
|
||||
dataId
|
||||
},
|
||||
'obj responseData'
|
||||
).lean(),
|
||||
shareId ? MongoOutLink.findOne({ shareId }).lean() : Promise.resolve(null)
|
||||
).lean()
|
||||
]);
|
||||
|
||||
if (chatData?.obj !== ChatRoleEnum.AI) {
|
||||
@@ -73,8 +50,7 @@ async function handler(
|
||||
const flowResponses = chatData.responseData ?? {};
|
||||
return req.query.shareId
|
||||
? filterPublicNodeResponseData({
|
||||
// @ts-ignore
|
||||
responseDetail: authData.responseDetail,
|
||||
responseDetail,
|
||||
flowResponses: chatData.responseData
|
||||
})
|
||||
: flowResponses;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schem
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||
@@ -21,7 +21,7 @@ async function handler(
|
||||
const { appId, searchKey } = req.body;
|
||||
|
||||
// tmp auth
|
||||
const { teamId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId } = await authChatCrud({ req, authToken: true, ...req.body });
|
||||
const app = await MongoApp.findOne({ _id: appId, teamId });
|
||||
if (!app) {
|
||||
return Promise.reject(AppErrEnum.unAuthApp);
|
||||
|
||||
@@ -5,21 +5,19 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import type { DeleteChatItemProps } from '@/global/core/chat/api.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
|
||||
async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, contentId } = req.query;
|
||||
|
||||
if (!contentId || !chatId) {
|
||||
return jsonRes(res);
|
||||
return Promise.reject('contentId or chatId is empty');
|
||||
}
|
||||
|
||||
await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
...req.query,
|
||||
per: WritePermissionVal
|
||||
...req.query
|
||||
});
|
||||
|
||||
await MongoChatItem.deleteOne({
|
||||
@@ -28,7 +26,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA
|
||||
dataId: contentId
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
return;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
|
||||
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
|
||||
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
|
||||
import { getAudioSpeechModel } from '@fastgpt/service/core/ai/model';
|
||||
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/*
|
||||
1. get tts from chatItem store
|
||||
2. get tts from ai
|
||||
4. push bill
|
||||
*/
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: ApiRequestProps<GetChatSpeechProps>, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { ttsConfig, input } = req.body as GetChatSpeechProps;
|
||||
const { ttsConfig, input } = req.body;
|
||||
|
||||
if (!ttsConfig.model || !ttsConfig.voice) {
|
||||
throw new Error('model or voice not found');
|
||||
}
|
||||
|
||||
const { teamId, tmbId, authType } = await authChatCert({
|
||||
const { teamId, tmbId, authType } = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
authApiKey: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
const ttsModel = getAudioSpeechModel(ttsConfig.model);
|
||||
@@ -90,3 +92,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -16,11 +16,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
|
||||
|
||||
// auth link permission
|
||||
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
const { outLinkConfig, uid, appId } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
// auth app permission
|
||||
const [tmb, chat, app] = await Promise.all([
|
||||
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoTeamMember.findById(outLinkConfig.tmbId, '_id userId').populate('userId', 'avatar').lean(),
|
||||
MongoChat.findOne({ appId, chatId, shareId }).lean(),
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
@@ -35,7 +35,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
// pick share response field
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -66,9 +65,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||
@@ -12,12 +12,13 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
|
||||
async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResponse) {
|
||||
let { teamId, appId, chatId, teamToken } = req.query;
|
||||
|
||||
if (!teamId || !appId || !teamToken) {
|
||||
throw new Error('teamId, appId, teamToken are required');
|
||||
return Promise.reject('teamId, appId, teamToken are required');
|
||||
}
|
||||
|
||||
const { uid } = await authTeamSpaceToken({
|
||||
@@ -32,7 +33,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
]);
|
||||
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
return Promise.reject(AppErrEnum.unExist);
|
||||
}
|
||||
|
||||
// auth chat permission
|
||||
@@ -43,8 +44,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// get app and history
|
||||
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
|
||||
|
||||
// pick share response field
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
@@ -74,9 +73,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): CreateCo
|
||||
// 2. upload file
|
||||
const fileId = await uploadFile({
|
||||
teamId,
|
||||
tmbId,
|
||||
uid: tmbId,
|
||||
bucketName,
|
||||
path: file.path,
|
||||
filename: file.originalname,
|
||||
|
||||
@@ -5,40 +5,142 @@ import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant
|
||||
import { createFileToken } from '@fastgpt/service/support/permission/controller';
|
||||
import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
|
||||
|
||||
export type readCollectionSourceQuery = {};
|
||||
|
||||
export type readCollectionSourceBody = {
|
||||
collectionId: string;
|
||||
} & ShareChatAuthProps;
|
||||
|
||||
appId?: string;
|
||||
chatId?: string;
|
||||
chatItemId?: string;
|
||||
} & OutLinkChatAuthProps;
|
||||
|
||||
export type readCollectionSourceResponse = {
|
||||
type: 'url';
|
||||
value: string;
|
||||
};
|
||||
|
||||
const authCollectionInChat = async ({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId
|
||||
}: {
|
||||
collectionId: string;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
chatItemId: string;
|
||||
}) => {
|
||||
try {
|
||||
const chatItem = (await MongoChatItem.findOne(
|
||||
{
|
||||
appId,
|
||||
chatId,
|
||||
dataId: chatItemId
|
||||
},
|
||||
'responseData'
|
||||
).lean()) as AIChatItemType;
|
||||
|
||||
if (!chatItem) return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
|
||||
// 找 responseData 里,是否有该文档 id
|
||||
const responseData = chatItem.responseData || [];
|
||||
const flatResData: ChatHistoryItemResType[] =
|
||||
responseData
|
||||
?.map((item) => {
|
||||
return [
|
||||
item,
|
||||
...(item.pluginDetail || []),
|
||||
...(item.toolDetail || []),
|
||||
...(item.loopDetail || [])
|
||||
];
|
||||
})
|
||||
.flat() || [];
|
||||
|
||||
if (
|
||||
flatResData.some((item) => {
|
||||
if (item.quoteList) {
|
||||
return item.quoteList.some((quote) => quote.collectionId === collectionId);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {}
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<readCollectionSourceBody, readCollectionSourceQuery>
|
||||
): Promise<readCollectionSourceResponse> {
|
||||
const { collection, teamId, tmbId } = await authDatasetCollection({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
collectionId: req.body.collectionId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
const { collectionId, appId, chatId, chatItemId, shareId, outLinkUid, teamId, teamToken } =
|
||||
req.body;
|
||||
|
||||
const {
|
||||
collection,
|
||||
teamId: userTeamId,
|
||||
tmbId: uid,
|
||||
authType
|
||||
} = await (async () => {
|
||||
if (!appId || !chatId || !chatItemId) {
|
||||
return authDatasetCollection({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
collectionId: req.body.collectionId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
1. auth chat read permission
|
||||
2. auth collection quote in chat
|
||||
3. auth outlink open show quote
|
||||
*/
|
||||
const [authRes, collection] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
getCollectionWithDataset(collectionId),
|
||||
authCollectionInChat({ appId, chatId, chatItemId, collectionId })
|
||||
]);
|
||||
|
||||
if (!authRes.showRawSource) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
}
|
||||
|
||||
return {
|
||||
...authRes,
|
||||
collection
|
||||
};
|
||||
})();
|
||||
|
||||
const sourceUrl = await (async () => {
|
||||
if (collection.type === DatasetCollectionTypeEnum.file && collection.fileId) {
|
||||
const token = await createFileToken({
|
||||
bucketName: BucketNameEnum.dataset,
|
||||
teamId,
|
||||
tmbId,
|
||||
fileId: collection.fileId
|
||||
teamId: userTeamId,
|
||||
uid,
|
||||
fileId: collection.fileId,
|
||||
customExpireMinutes: authType === 'outLink' ? 5 : undefined
|
||||
});
|
||||
|
||||
return `${ReadFileBaseUrl}?token=${token}`;
|
||||
return `${ReadFileBaseUrl}/${collection.name}?token=${token}`;
|
||||
}
|
||||
if (collection.type === DatasetCollectionTypeEnum.link && collection.rawLink) {
|
||||
return collection.rawLink;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
|
||||
|
||||
export type PostPreviewFilesChunksProps = {
|
||||
type: DatasetSourceReadTypeEnum;
|
||||
@@ -35,7 +35,7 @@ async function handler(
|
||||
|
||||
const { teamId } = await (async () => {
|
||||
if (type === DatasetSourceReadTypeEnum.fileLocal) {
|
||||
return authFile({
|
||||
return authCollectionFile({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { authOutLinkCrud } from '@fastgpt/service/support/permission/publish/authLink';
|
||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
@@ -30,7 +30,7 @@ async function handler(
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: OwnerPermissionVal });
|
||||
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: ManagePermissionVal });
|
||||
|
||||
await MongoOutLink.findByIdAndUpdate(_id, {
|
||||
name,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getUploadModel } from '@fastgpt/service/common/file/multer';
|
||||
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
|
||||
import fs from 'fs';
|
||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authChatCert } from '@/service/support/permission/auth/chat';
|
||||
import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
||||
@@ -27,6 +27,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
>(req, res);
|
||||
|
||||
req.body.appId = appId;
|
||||
req.body.shareId = shareId;
|
||||
req.body.outLinkUid = outLinkUid;
|
||||
req.body.teamId = spaceTeamId;
|
||||
@@ -43,7 +44,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
}
|
||||
|
||||
// auth role
|
||||
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
|
||||
const { teamId, tmbId } = await authChatCrud({ req, authToken: true, ...req.body });
|
||||
|
||||
// auth app
|
||||
// const app = await MongoApp.findById(appId, 'modules').lean();
|
||||
|
||||
@@ -86,7 +86,7 @@ type AuthResponseType = {
|
||||
showNodeStatus?: boolean;
|
||||
authType: `${AuthUserTypeEnum}`;
|
||||
apikey?: string;
|
||||
canWrite: boolean;
|
||||
responseAllData: boolean;
|
||||
outLinkUserId?: string;
|
||||
sourceName?: string;
|
||||
};
|
||||
@@ -160,7 +160,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
authType,
|
||||
sourceName,
|
||||
apikey,
|
||||
canWrite,
|
||||
responseAllData,
|
||||
outLinkUserId = customUid,
|
||||
showNodeStatus
|
||||
} = await (async () => {
|
||||
@@ -328,12 +328,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await updateInteractiveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId,
|
||||
tmbId: tmbId,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables,
|
||||
newTitle
|
||||
newVariables
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
@@ -361,7 +358,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
|
||||
|
||||
/* select fe response field */
|
||||
const feResponseData = canWrite
|
||||
const feResponseData = responseAllData
|
||||
? flowResponses
|
||||
: filterPublicNodeResponseData({ flowResponses, responseDetail });
|
||||
|
||||
@@ -482,10 +479,10 @@ const authShareChat = async ({
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
authType,
|
||||
canWrite: false,
|
||||
responseAllData: false,
|
||||
responseDetail,
|
||||
outLinkUserId: uid,
|
||||
showNodeStatus
|
||||
};
|
||||
@@ -525,10 +522,10 @@ const authTeamSpaceChat = async ({
|
||||
tmbId: app.tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: true,
|
||||
authType: AuthUserTypeEnum.outLink,
|
||||
apikey: '',
|
||||
canWrite: false,
|
||||
responseAllData: false,
|
||||
responseDetail: true,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
};
|
||||
@@ -610,11 +607,11 @@ const authHeaderRequest = async ({
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: true,
|
||||
apikey,
|
||||
authType,
|
||||
sourceName,
|
||||
canWrite: true
|
||||
responseAllData: true,
|
||||
responseDetail: true
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, Box, useTheme } from '@chakra-ui/react';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
@@ -14,53 +12,52 @@ import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/Plugin
|
||||
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { PcHeader } from '@/pages/chat/components/ChatHeader';
|
||||
import { GetChatTypeEnum } from '@/global/core/chat/constants';
|
||||
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider, {
|
||||
ChatRecordContext
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
|
||||
const DetailLogsModal = ({
|
||||
appId,
|
||||
chatId,
|
||||
onClose
|
||||
}: {
|
||||
type Props = {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
const theme = useTheme();
|
||||
|
||||
const params = useMemo(() => {
|
||||
return {
|
||||
chatId,
|
||||
appId,
|
||||
loadCustomFeedbacks: true,
|
||||
type: GetChatTypeEnum.normal
|
||||
};
|
||||
}, [appId, chatId]);
|
||||
const {
|
||||
ChatBoxRef,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetVariables,
|
||||
chatRecords,
|
||||
ScrollData,
|
||||
setChatRecords,
|
||||
totalRecordsCount
|
||||
} = useChat(params);
|
||||
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
|
||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
|
||||
const { data: chat, isFetching } = useQuery(
|
||||
['getChatDetail', chatId],
|
||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
|
||||
const { data: chat, loading: isFetching } = useRequest2(
|
||||
async () => {
|
||||
const res = await getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true });
|
||||
res.userAvatar = HUMAN_ICON;
|
||||
|
||||
setChatBoxData(res);
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
});
|
||||
|
||||
return res;
|
||||
},
|
||||
{
|
||||
onSuccess(res) {
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
});
|
||||
manual: false,
|
||||
refreshDeps: [chatId],
|
||||
onError(e) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -68,12 +65,11 @@ const DetailLogsModal = ({
|
||||
const title = chat?.title;
|
||||
const chatModels = chat?.app?.chatModels;
|
||||
const isPlugin = chat?.app.type === AppTypeEnum.plugin;
|
||||
const loading = isFetching;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
isLoading={isFetching}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
zIndex={3}
|
||||
@@ -124,7 +120,7 @@ const DetailLogsModal = ({
|
||||
alignItems={'center'}
|
||||
px={[3, 5]}
|
||||
h={['46px', '60px']}
|
||||
borderBottom={theme.borders.base}
|
||||
borderBottom={'base'}
|
||||
borderBottomColor={'gray.200'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
@@ -154,32 +150,15 @@ const DetailLogsModal = ({
|
||||
<Box pt={2} flex={'1 0 0'}>
|
||||
{isPlugin ? (
|
||||
<Box px={5} pt={2} h={'100%'}>
|
||||
<PluginRunBox
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
pluginInputs={chat?.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chat.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
/>
|
||||
<PluginRunBox appId={appId} chatId={chatId} />
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
ScrollData={ScrollData}
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appAvatar={chat?.app.avatar}
|
||||
userAvatar={HUMAN_ICON}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
chatType="log"
|
||||
showRawSource
|
||||
showNodeStatus
|
||||
@@ -191,4 +170,24 @@ const DetailLogsModal = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DetailLogsModal;
|
||||
|
||||
const Render = (props: Props) => {
|
||||
const { appId, chatId } = props;
|
||||
const params = useMemo(() => {
|
||||
return {
|
||||
chatId,
|
||||
appId,
|
||||
loadCustomFeedbacks: true,
|
||||
type: GetChatTypeEnum.normal
|
||||
};
|
||||
}, [appId, chatId]);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={params}>
|
||||
<DetailLogsModal {...props} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
export default Render;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { fileToBase64 } from '@/web/common/file/utils';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { subRoute } from '@fastgpt/web/common/system/utils';
|
||||
|
||||
enum UsingWayEnum {
|
||||
link = 'link',
|
||||
@@ -74,7 +75,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
|
||||
});
|
||||
|
||||
const baseUrl = feConfigs?.customSharePageDomain || location?.origin;
|
||||
const linkUrl = `${baseUrl}/chat/share?shareId=${share?.shareId}${
|
||||
const linkUrl = `${baseUrl}${subRoute ? `${subRoute}/` : '/'}chat/share?shareId=${share?.shareId}${
|
||||
getValues('showHistory') ? '' : '&showHistory=0'
|
||||
}`;
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ function EditLinkModal({
|
||||
</Flex>
|
||||
<Switch {...register('responseDetail')} isChecked={responseDetail} />
|
||||
</Flex>
|
||||
{/* <Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.show_complete_quote')}</FormLabel>
|
||||
<QuestionTip
|
||||
@@ -431,8 +431,17 @@ function EditLinkModal({
|
||||
label={t('common:support.outlink.share.show_complete_quote_tips' || '')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch {...register('showRawSource')} isChecked={showRawSource} />
|
||||
</Flex> */}
|
||||
<Switch
|
||||
{...register('showRawSource', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={showRawSource}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { AppSchema, AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -19,21 +19,25 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postTransition2Workflow } from '@/web/core/app/api/app';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
import { SimpleAppSnapshotType } from './useSnapshots';
|
||||
|
||||
const AppCard = () => {
|
||||
const AppCard = ({
|
||||
appForm,
|
||||
setPast
|
||||
}: {
|
||||
appForm: AppSimpleEditFormType;
|
||||
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { appDetail, setAppDetail, onOpenInfoEdit, onDelApp } = useContextSelector(
|
||||
AppContext,
|
||||
(v) => v
|
||||
);
|
||||
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const onOpenInfoEdit = useContextSelector(AppContext, (v) => v.onOpenInfoEdit);
|
||||
const onDelApp = useContextSelector(AppContext, (v) => v.onDelApp);
|
||||
|
||||
const appId = appDetail._id;
|
||||
const { feConfigs } = useSystemStore();
|
||||
@@ -42,7 +46,18 @@ const AppCard = () => {
|
||||
// transition to workflow
|
||||
const [transitionCreateNew, setTransitionCreateNew] = useState<boolean>();
|
||||
const { runAsync: onTransition, loading: transiting } = useRequest2(
|
||||
() => postTransition2Workflow({ appId, createNew: transitionCreateNew }),
|
||||
async () => {
|
||||
const { nodes, edges } = form2AppWorkflow(appForm, t);
|
||||
await onSaveApp({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appForm.chatConfig,
|
||||
isPublish: false,
|
||||
versionName: t('app:transition_to_workflow')
|
||||
});
|
||||
|
||||
return postTransition2Workflow({ appId, createNew: transitionCreateNew });
|
||||
},
|
||||
{
|
||||
onSuccess: ({ id }) => {
|
||||
if (id) {
|
||||
@@ -52,10 +67,8 @@ const AppCard = () => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
type: AppTypeEnum.workflow
|
||||
}));
|
||||
setPast([]);
|
||||
router.reload();
|
||||
}
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
@@ -118,7 +131,7 @@ const AppCard = () => {
|
||||
children: [
|
||||
{
|
||||
icon: 'core/app/type/workflow',
|
||||
label: appT('transition_to_workflow'),
|
||||
label: t('app:transition_to_workflow'),
|
||||
onClick: () => setTransitionCreateNew(true)
|
||||
},
|
||||
...(appDetail.permission.hasWritePer && feConfigs?.show_team_chat
|
||||
@@ -159,15 +172,15 @@ const AppCard = () => {
|
||||
</Box>
|
||||
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
|
||||
{transitionCreateNew !== undefined && (
|
||||
<MyModal isOpen title={appT('transition_to_workflow')} iconSrc="core/app/type/workflow">
|
||||
<MyModal isOpen title={t('app:transition_to_workflow')} iconSrc="core/app/type/workflow">
|
||||
<ModalBody>
|
||||
<Box mb={3}>{appT('transition_to_workflow_create_new_tip')}</Box>
|
||||
<Box mb={3}>{t('app:transition_to_workflow_create_new_tip')}</Box>
|
||||
<HStack cursor={'pointer'} onClick={() => setTransitionCreateNew((state) => !state)}>
|
||||
<Checkbox
|
||||
isChecked={transitionCreateNew}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
/>
|
||||
<Box>{appT('transition_to_workflow_create_new_placeholder')}</Box>
|
||||
<Box>{t('app:transition_to_workflow_create_new_placeholder')}</Box>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
@@ -12,8 +12,13 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../context';
|
||||
import { useChatTest } from '../useChatTest';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import ChatItemContextProvider from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
type Props = { appForm: AppSimpleEditFormType };
|
||||
const ChatTest = ({ appForm }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
@@ -32,13 +37,21 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
setWorkflowData({ nodes, edges });
|
||||
}, [appForm, setWorkflowData, allDatasets, t]);
|
||||
|
||||
const { restartChat, ChatContainer } = useChatTest({
|
||||
const { ChatContainer, restartChat, loading } = useChatTest({
|
||||
...workflowData,
|
||||
chatConfig: appForm.chatConfig
|
||||
chatConfig: appForm.chatConfig,
|
||||
isReady: true
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex position={'relative'} flexDirection={'column'} h={'100%'} py={4}>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
display={'flex'}
|
||||
position={'relative'}
|
||||
flexDirection={'column'}
|
||||
h={'100%'}
|
||||
py={4}
|
||||
>
|
||||
<Flex px={[2, 5]}>
|
||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'} flex={1} color={'myGray.900'}>
|
||||
{appT('chat_debug')}
|
||||
@@ -61,8 +74,29 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
<Box flex={1}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatTest);
|
||||
const Render = ({ appForm }: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const chatRecordProviderParams = useMemo(
|
||||
() => ({
|
||||
chatId: chatId,
|
||||
appId: appDetail._id
|
||||
}),
|
||||
[appDetail._id, chatId]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest appForm={appForm} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Render);
|
||||
|
||||
@@ -132,7 +132,7 @@ const Edit = ({
|
||||
flex={'1'}
|
||||
>
|
||||
<Box {...cardStyles} boxShadow={'2'}>
|
||||
<AppCard />
|
||||
<AppCard appForm={appForm} setPast={setPast} />
|
||||
</Box>
|
||||
|
||||
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
|
||||
|
||||
@@ -87,7 +87,6 @@ const Header = ({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appForm.chatConfig,
|
||||
type: AppTypeEnum.simple,
|
||||
isPublish,
|
||||
versionName
|
||||
});
|
||||
|
||||
@@ -37,7 +37,8 @@ export const compareSimpleAppSnapshot = (
|
||||
scheduledTriggerConfig: appForm1.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: appForm1.chatConfig?.fileSelectConfig || undefined,
|
||||
instruction: appForm1.chatConfig?.instruction || ''
|
||||
instruction: appForm1.chatConfig?.instruction || '',
|
||||
autoExecute: appForm1.chatConfig?.autoExecute || undefined
|
||||
},
|
||||
{
|
||||
welcomeText: appForm2.chatConfig?.welcomeText || '',
|
||||
@@ -48,7 +49,8 @@ export const compareSimpleAppSnapshot = (
|
||||
scheduledTriggerConfig: appForm2.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: appForm2.chatConfig?.fileSelectConfig || undefined,
|
||||
instruction: appForm2.chatConfig?.instruction || ''
|
||||
instruction: appForm2.chatConfig?.instruction || '',
|
||||
autoExecute: appForm2.chatConfig?.autoExecute || undefined
|
||||
}
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -147,7 +147,6 @@ const AppCard = ({
|
||||
appDetail.name,
|
||||
appDetail.permission.hasWritePer,
|
||||
appDetail.permission.isOwner,
|
||||
currentTab,
|
||||
feConfigs?.show_team_chat,
|
||||
onDelApp,
|
||||
onOpenImport,
|
||||
@@ -252,10 +251,6 @@ function ExportPopover({
|
||||
trigger={'hover'}
|
||||
w={'8.6rem'}
|
||||
Trigger={
|
||||
// <Flex align={'center'} w={'100%'} py={2} px={3}>
|
||||
// <Avatar src={'export'} borderRadius={'sm'} w={'1rem'} mr={3} />
|
||||
// {t('app:export_configs')}
|
||||
// </Flex>
|
||||
<MyBox display={'flex'} size={'md'} rounded={'4px'} cursor={'pointer'}>
|
||||
<MyIcon name={'export'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -14,27 +14,34 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider, {
|
||||
ChatRecordContext
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
const ChatTest = ({
|
||||
isOpen,
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onClose
|
||||
}: {
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
nodes?: StoreNodeItemType[];
|
||||
edges?: StoreEdgeItemType[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const isPlugin = appDetail.type === AppTypeEnum.plugin;
|
||||
|
||||
const { restartChat, ChatContainer, pluginRunTab, setPluginRunTab, chatRecords } = useChatTest({
|
||||
const { restartChat, ChatContainer, loading } = useChatTest({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
chatConfig: appDetail.chatConfig,
|
||||
isReady: isOpen
|
||||
});
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -48,8 +55,10 @@ const ChatTest = ({
|
||||
right={0}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Flex
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
zIndex={300}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
@@ -131,9 +140,30 @@ const ChatTest = ({
|
||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatTest);
|
||||
const Render = (Props: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const chatRecordProviderParams = useMemo(
|
||||
() => ({
|
||||
chatId: chatId,
|
||||
appId: appDetail._id
|
||||
}),
|
||||
[appDetail._id, chatId]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatItemContextProvider>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest {...Props} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Render);
|
||||
|
||||
@@ -240,7 +240,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
position={'absolute'}
|
||||
top={'10px'}
|
||||
left={0}
|
||||
pt={'20px'}
|
||||
pt={5}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 20px)' : '0'}
|
||||
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
|
||||
@@ -254,7 +254,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
{/* Header */}
|
||||
<Box px={'5'} mb={3} whiteSpace={'nowrap'} overflow={'hidden'}>
|
||||
{/* Tabs */}
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={2}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<FillRowTabs
|
||||
list={[
|
||||
@@ -288,8 +288,14 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
{/* close icon */}
|
||||
<IconButton
|
||||
size={'sm'}
|
||||
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
|
||||
borderColor={'myGray.300'}
|
||||
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.600'} />}
|
||||
bg={'myGray.100'}
|
||||
_hover={{
|
||||
bg: 'myGray.200',
|
||||
'& svg': {
|
||||
color: 'primary.600'
|
||||
}
|
||||
}}
|
||||
variant={'grayBase'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
|
||||
@@ -177,20 +177,15 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
position={'absolute'}
|
||||
transform={arrowTransform}
|
||||
pointerEvents={'all'}
|
||||
w={highlightEdge ? '14px' : '10px'}
|
||||
h={highlightEdge ? '14px' : '10px'}
|
||||
w={highlightEdge ? '12px' : '10px'}
|
||||
h={highlightEdge ? '12px' : '10px'}
|
||||
zIndex={highlightEdge ? defaultZIndex + 1000 : defaultZIndex}
|
||||
>
|
||||
<MyIcon
|
||||
name={'core/workflow/edgeArrow'}
|
||||
name={highlightEdge ? 'core/workflow/edgeArrowBold' : 'core/workflow/edgeArrow'}
|
||||
w={'100%'}
|
||||
color={edgeColor}
|
||||
{...(highlightEdge
|
||||
? {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {})}
|
||||
></MyIcon>
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
@@ -223,7 +218,7 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
...style,
|
||||
...(highlightEdge
|
||||
? {
|
||||
strokeWidth: 5
|
||||
strokeWidth: 4
|
||||
}
|
||||
: { strokeWidth: 3, zIndex: 2 })
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
@@ -333,7 +333,7 @@ export const useDebug = () => {
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<Box position={'relative'}>
|
||||
{required && (
|
||||
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
|
||||
<Box position={'absolute'} left={'-8px'} top={'-2px'} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -36,8 +36,8 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return {
|
||||
[NodeInputKeyEnum.code]: (item: FlowNodeInputItemType) => {
|
||||
return (
|
||||
<Box>
|
||||
<Flex mb={1} alignItems={'flex-end'}>
|
||||
<Box mt={-3}>
|
||||
<Flex mb={2} alignItems={'flex-end'}>
|
||||
<Box flex={'1'}>{'Javascript ' + workflowT('Code')}</Box>
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
@@ -88,7 +88,7 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
</>
|
||||
)}
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
<IOTitle text={t('common:common.Input')} mb={-1} />
|
||||
<RenderInput
|
||||
nodeId={nodeId}
|
||||
flowInputList={commonInputs}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const NodeComment = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs } = data;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import IOTitle from '../../components/IOTitle';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const { inputs, outputs, nodeId } = data;
|
||||
@@ -53,14 +54,15 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
}: Omit<FlowNodeInputItemType, 'value'> & {
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box>
|
||||
<Box mt={-2}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
<Box flex={'1 0 0'} fontSize={'sm'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
{t('common:core.module.extract.Target field')}
|
||||
</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'ghost'}
|
||||
variant={'grayGhost'}
|
||||
px={2}
|
||||
color={'myGray.600'}
|
||||
leftIcon={<AddIcon fontSize={'10px'} />}
|
||||
onClick={() => setEditExtractField(defaultField)}
|
||||
@@ -68,48 +70,47 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
{t('common:core.module.extract.Add field')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={2}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom="none"
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderRadius={'none !important'}>{t('common:item_name')}</Th>
|
||||
<Th>{t('common:item_description')}</Th>
|
||||
<Th>{t('common:required')}</Th>
|
||||
<Th borderRadius={'none !important'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
|
||||
<TableContainer borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} mt={2}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common:item_name')}</Th>
|
||||
<Th>{t('common:item_description')}</Th>
|
||||
<Th>{t('common:required')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{extractKeys.map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.desc}</Td>
|
||||
<Td>
|
||||
{item.required ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => {
|
||||
setEditExtractField(item);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -128,13 +129,13 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Table,
|
||||
TableContainer,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
Tr
|
||||
} from '@chakra-ui/react';
|
||||
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
FlowNodeInputMap,
|
||||
FlowNodeInputTypeEnum,
|
||||
@@ -37,6 +36,8 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import InputFormEditModal, { defaultFormInput } from './InputFormEditModal';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
@@ -120,10 +121,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Box>
|
||||
<HStack className="nodrag" cursor={'default'} mb={3}>
|
||||
<FormLabel>{t('workflow:user_form_input_config')}</FormLabel>
|
||||
<FormLabel fontSize={'sm'} color={'myGray.600'}>
|
||||
{t('workflow:user_form_input_config')}
|
||||
</FormLabel>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant={'grayGhost'}
|
||||
px={2}
|
||||
color={'myGray.600'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
@@ -144,17 +149,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<TableContainer borderWidth={'1px'} borderRadius={'md'} borderBottom="none">
|
||||
<Table bg={'white'}>
|
||||
<TableContainer borderWidth={'1px'} borderRadius={'md'}>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('workflow:user_form_input_name')}
|
||||
</Th>
|
||||
<Th>{t('workflow:user_form_input_name')}</Th>
|
||||
<Th>{t('workflow:user_form_input_description')}</Th>
|
||||
<Th>{t('common:common.Require Input')}</Th>
|
||||
<Th borderBottomRightRadius={'none !important'}>{t('user:operations')}</Th>
|
||||
<Th>{t('user:operations')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -163,35 +165,35 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return (
|
||||
<Tr key={index}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} fontSize={'mini'} fontWeight={'medium'}>
|
||||
{!!icon && (
|
||||
<MyIcon name={icon as any} w={'14px'} mr={1} color={'primary.600'} />
|
||||
<MyIcon name={icon as any} w={'14px'} mr={1} color={'myGray.400'} />
|
||||
)}
|
||||
{item.label}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.description}</Td>
|
||||
<Td>{item.required ? '✅' : ''}</Td>
|
||||
<Td>{item.description || '-'}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete(item.key);
|
||||
}}
|
||||
/>
|
||||
{item.required ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => onDelete(item.key)}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,6 @@ import {
|
||||
FormControl,
|
||||
HStack,
|
||||
Input,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Stack,
|
||||
Switch,
|
||||
Textarea
|
||||
@@ -28,7 +23,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
|
||||
@@ -100,6 +100,7 @@ const PluginOutputEditModal = ({
|
||||
|
||||
data.key = data?.key?.trim();
|
||||
data.label = data.key;
|
||||
data.required = true;
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const VariableTable = ({
|
||||
variables = [],
|
||||
@@ -16,58 +17,61 @@ const VariableTable = ({
|
||||
const showToolColumn = variables.some((item) => item.isTool);
|
||||
|
||||
return (
|
||||
<Box bg={'white'} borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
|
||||
<Th borderBottomRightRadius={'none !important'}></Th>
|
||||
<TableContainer
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
>
|
||||
<Table variant={'workflow'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:Variable_name')}</Th>
|
||||
<Th>{t('common:core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
|
||||
<Th>{t('user:operations')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item, index) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
{!!item.icon ? (
|
||||
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
) : (
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
)}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
{showToolColumn && (
|
||||
<Td>
|
||||
{item.isTool ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
|
||||
</Flex>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Td>
|
||||
)}
|
||||
<Td>
|
||||
<Flex>
|
||||
<MyIconButton icon={'common/settingLight'} onClick={() => onEdit(item.key)} />
|
||||
<MyIconButton
|
||||
icon={'delete'}
|
||||
hoverColor={'red.500'}
|
||||
onClick={() => onDelete(item.key)}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{!!item.icon && (
|
||||
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'primary.600'} />
|
||||
)}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
{showToolColumn && <Th>{item.isTool ? '✅' : '-'}</Th>}
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => onEdit(item.key)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete(item.key);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import FileSelect from '@/components/core/app/FileSelect';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { userFilesInput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
import Container from '../components/Container';
|
||||
import AutoExecConfig from '@/components/core/app/AutoExecConfig';
|
||||
|
||||
type ComponentProps = {
|
||||
chatConfig: AppChatConfigType;
|
||||
@@ -80,6 +81,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Box mt={4} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<ScheduledTrigger {...componentsProps} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<AutoExecute {...componentsProps} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
|
||||
<QuestionInputGuide {...componentsProps} />
|
||||
</Box>
|
||||
@@ -128,6 +132,23 @@ function ChatStartVariable({ chatConfig: { variables = [] }, setAppDetail }: Com
|
||||
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
||||
}
|
||||
|
||||
function AutoExecute({ chatConfig: { autoExecute }, setAppDetail }: ComponentProps) {
|
||||
return (
|
||||
<AutoExecConfig
|
||||
value={autoExecute}
|
||||
onChange={(e) =>
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
chatConfig: {
|
||||
...state.chatConfig,
|
||||
autoExecute: e
|
||||
}
|
||||
}))
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function QuestionGuide({ chatConfig: { questionGuide = false }, setAppDetail }: ComponentProps) {
|
||||
return (
|
||||
<QGSwitch
|
||||
|
||||
@@ -5,7 +5,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
Thead,
|
||||
Tr
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
|
||||
@@ -36,7 +36,7 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<NodeCard selected={selected} {...data}>
|
||||
<Container>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mb={1.5}>
|
||||
<FormLabel>{t('workflow:tool_custom_field')}</FormLabel>
|
||||
<FormLabel fontSize={'sm'}>{t('workflow:tool_custom_field')}</FormLabel>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
@@ -54,38 +54,89 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('workflow:tool_params.params_name')}</Th>
|
||||
<Th>{t('workflow:tool_params.params_description')}</Th>
|
||||
<Th>{t('common:common.Operation')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
<TableContainer
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr h={8}>
|
||||
<Th p={0} px={4} bg={'myGray.50'} borderBottomLeftRadius={'none !important'}>
|
||||
{t('workflow:tool_params.params_name')}
|
||||
</Th>
|
||||
<Th p={0} px={4} bg={'myGray.50'}>
|
||||
{t('workflow:tool_params.params_description')}
|
||||
</Th>
|
||||
<Th p={0} px={4} bg={'myGray.50'} borderBottomRightRadius={'none !important'}>
|
||||
{t('common:common.Operation')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
h={10}
|
||||
>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
>
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.toolDescription}</Td>
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
<Flex alignItems={'center'} fontSize={'xs'}>
|
||||
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
|
||||
{item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
{item.toolDescription}
|
||||
</Td>
|
||||
<Td
|
||||
p={0}
|
||||
px={4}
|
||||
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
|
||||
whiteSpace={'nowrap'}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
p={1}
|
||||
color={'myGray.500'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
bg={'transparent'}
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={() => setEditField(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
>
|
||||
<MyIcon name={'common/settingLight'} w={'16px'} />
|
||||
</Flex>
|
||||
<Flex
|
||||
p={1}
|
||||
color={'myGray.500'}
|
||||
rounded={'sm'}
|
||||
alignItems={'center'}
|
||||
bg={'transparent'}
|
||||
transition={'background 0.1s'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
bg: 'myGray.05',
|
||||
color: 'red.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -98,14 +149,16 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
>
|
||||
<MyIcon name={'delete'} w={'16px'} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
<Box position={'relative'}>
|
||||
<Box mb={-4} borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
|
||||
<Box mb={-3} borderBottomRadius={'lg'} overflow={'hidden'}>
|
||||
<Divider
|
||||
showBorderBottom={false}
|
||||
icon={<MyIcon name="phoneTabbar/tool" w={'16px'} h={'16px'} />}
|
||||
|
||||
@@ -123,7 +123,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
return (
|
||||
<Container key={index} w={'full'} mx={0}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex w={'60px'}>{t('common:core.workflow.variable')}</Flex>
|
||||
<Flex w={'80px'}>{t('common:core.workflow.variable')}</Flex>
|
||||
<VariableSelector
|
||||
nodeId={nodeId}
|
||||
variable={updateItem.variable}
|
||||
@@ -162,7 +162,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex mt={2} w={'full'} alignItems={'center'} className="nodrag">
|
||||
<Flex w={'60px'}>
|
||||
<Flex w={'80px'}>
|
||||
<Box>{t('common:core.workflow.value')}</Box>
|
||||
<MyTooltip
|
||||
label={
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user