Plugin runtime (#2050)
* feat: plugin run (#1950) * feat: plugin run * fix * ui * fix * change user input type * fix * fix * temp * split out plugin chat * perf: chatbox * perf: chatbox * fix: plugin runtime (#2032) * fix: plugin runtime * fix * fix build * fix build * perf: chat send prompt * perf: chat log ux * perf: chatbox context and share page plugin runtime * perf: plugin run time config * fix: ts * feat: doc * perf: isPc check * perf: variable input render * feat: app search * fix: response box height * fix: phone ui * perf: lock * perf: plugin route * fix: chat (#2049) --------- Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
@@ -1,262 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
BoxProps,
|
||||
Card,
|
||||
Flex,
|
||||
useTheme,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
Image
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import ChatController, { type ChatControllerProps } from './ChatController';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { formatChatValue2InputType } from '../utils';
|
||||
import Markdown, { CodeClassName } from '@/components/Markdown';
|
||||
import styles from '../index.module.scss';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import {
|
||||
ChatItemValueTypeEnum,
|
||||
ChatRoleEnum,
|
||||
ChatStatusEnum
|
||||
} from '@fastgpt/global/core/chat/constants';
|
||||
import FilesBlock from './FilesBox';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
bg: 'myGray.100',
|
||||
color: 'myGray.600'
|
||||
},
|
||||
[ChatStatusEnum.running]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
},
|
||||
[ChatStatusEnum.finish]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
}
|
||||
};
|
||||
|
||||
const ChatItem = ({
|
||||
type,
|
||||
avatar,
|
||||
statusBoxData,
|
||||
children,
|
||||
isLastChild,
|
||||
questionGuides = [],
|
||||
...chatControllerProps
|
||||
}: {
|
||||
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
|
||||
avatar?: string;
|
||||
statusBoxData?: {
|
||||
status: `${ChatStatusEnum}`;
|
||||
name: string;
|
||||
};
|
||||
questionGuides?: string[];
|
||||
children?: React.ReactNode;
|
||||
} & ChatControllerProps) => {
|
||||
const styleMap: BoxProps =
|
||||
type === ChatRoleEnum.Human
|
||||
? {
|
||||
order: 0,
|
||||
borderRadius: '8px 0 8px 8px',
|
||||
justifyContent: 'flex-end',
|
||||
textAlign: 'right',
|
||||
bg: 'primary.100'
|
||||
}
|
||||
: {
|
||||
order: 1,
|
||||
borderRadius: '0 8px 8px 8px',
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'left',
|
||||
bg: 'myGray.50'
|
||||
};
|
||||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const { chat } = chatControllerProps;
|
||||
|
||||
const ContentCard = useMemo(() => {
|
||||
if (type === 'Human') {
|
||||
const { text, files = [] } = formatChatValue2InputType(chat.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
{files.length > 0 && <FilesBlock files={files} />}
|
||||
<Markdown source={text} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* AI */
|
||||
return (
|
||||
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
|
||||
{chat.value.map((value, i) => {
|
||||
const key = `${chat.dataId}-ai-${i}`;
|
||||
|
||||
if (value.text) {
|
||||
let source = (value.text?.content || '').trim();
|
||||
|
||||
if (!source && chat.value.length > 1) return null;
|
||||
|
||||
if (
|
||||
isLastChild &&
|
||||
!isChatting &&
|
||||
questionGuides.length > 0 &&
|
||||
i === chat.value.length - 1
|
||||
) {
|
||||
source = `${source}
|
||||
\`\`\`${CodeClassName.questionGuide}
|
||||
${JSON.stringify(questionGuides)}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Markdown
|
||||
key={key}
|
||||
source={source}
|
||||
showAnimation={isLastChild && isChatting && i === chat.value.length - 1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
||||
return (
|
||||
<Box key={key}>
|
||||
{value.tools.map((tool) => {
|
||||
const toolParams = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.params), null, 2);
|
||||
} catch (error) {
|
||||
return tool.params;
|
||||
}
|
||||
})();
|
||||
const toolResponse = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.response), null, 2);
|
||||
} catch (error) {
|
||||
return tool.response;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<Box key={tool.id}>
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem borderTop={'none'} borderBottom={'none'}>
|
||||
<AccordionButton
|
||||
w={'auto'}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={'1'}
|
||||
_hover={{
|
||||
bg: 'auto'
|
||||
}}
|
||||
>
|
||||
<Avatar src={tool.toolAvatar} w={'1rem'} mr={2} />
|
||||
<Box mr={1} fontSize={'sm'}>
|
||||
{tool.toolName}
|
||||
</Box>
|
||||
{isChatting && !tool.response && (
|
||||
<MyIcon name={'common/loading'} w={'14px'} />
|
||||
)}
|
||||
<AccordionIcon color={'myGray.600'} ml={5} />
|
||||
</AccordionButton>
|
||||
<AccordionPanel
|
||||
py={0}
|
||||
px={0}
|
||||
mt={0}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
maxH={'500px'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{toolParams && toolParams !== '{}' && (
|
||||
<Markdown
|
||||
source={`~~~json#Input
|
||||
${toolParams}`}
|
||||
/>
|
||||
)}
|
||||
{toolResponse && (
|
||||
<Markdown
|
||||
source={`~~~json#Response
|
||||
${toolResponse}`}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
}, [chat.dataId, chat.value, isChatting, isLastChild, questionGuides, type]);
|
||||
|
||||
const chatStatusMap = useMemo(() => {
|
||||
if (!statusBoxData?.status) return;
|
||||
return colorMap[statusBoxData.status];
|
||||
}, [statusBoxData?.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* control icon */}
|
||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||
<Box order={styleMap.order} ml={styleMap.ml}>
|
||||
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
|
||||
</Box>
|
||||
)}
|
||||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
{!!chatStatusMap && statusBoxData && isLastChild && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={3}
|
||||
py={'1.5px'}
|
||||
borderRadius="md"
|
||||
bg={chatStatusMap.bg}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<Box
|
||||
className={styles.statusAnimation}
|
||||
bg={chatStatusMap.color}
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius={'50%'}
|
||||
mt={'1px'}
|
||||
/>
|
||||
<Box ml={2} color={'myGray.600'}>
|
||||
{statusBoxData.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{/* content */}
|
||||
<Box mt={['6px', 2]} textAlign={styleMap.textAlign}>
|
||||
<Card
|
||||
className="markdown"
|
||||
{...MessageCardStyle}
|
||||
bg={styleMap.bg}
|
||||
borderRadius={styleMap.borderRadius}
|
||||
textAlign={'left'}
|
||||
>
|
||||
{ContentCard}
|
||||
{children}
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatItem);
|
||||
@@ -1,325 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import Markdown from '../../Markdown';
|
||||
import { QuoteList } from './QuoteModal';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
function RowRender({
|
||||
children,
|
||||
mb,
|
||||
label,
|
||||
...props
|
||||
}: { children: React.ReactNode; label: string } & BoxProps) {
|
||||
return (
|
||||
<Box mb={3}>
|
||||
<Box fontSize={'sm'} mb={mb} flex={'0 0 90px'}>
|
||||
{label}:
|
||||
</Box>
|
||||
<Box borderRadius={'sm'} fontSize={['xs', 'sm']} bg={'myGray.50'} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
function Row({
|
||||
label,
|
||||
value,
|
||||
rawDom
|
||||
}: {
|
||||
label: string;
|
||||
value?: string | number | boolean | object;
|
||||
rawDom?: React.ReactNode;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const val = value || rawDom;
|
||||
const isObject = typeof value === 'object';
|
||||
|
||||
const formatValue = useMemo(() => {
|
||||
if (isObject) {
|
||||
return `~~~json\n${JSON.stringify(value, null, 2)}`;
|
||||
}
|
||||
return `${value}`;
|
||||
}, [isObject, value]);
|
||||
|
||||
if (rawDom) {
|
||||
return (
|
||||
<RowRender label={label} mb={1}>
|
||||
{rawDom}
|
||||
</RowRender>
|
||||
);
|
||||
}
|
||||
|
||||
if (val === undefined || val === '' || val === 'undefined') return null;
|
||||
|
||||
return (
|
||||
<RowRender
|
||||
label={label}
|
||||
mb={isObject ? 0 : 1}
|
||||
{...(isObject
|
||||
? { transform: 'translateY(-3px)' }
|
||||
: value
|
||||
? { px: 3, py: 2, border: theme.borders.base }
|
||||
: {})}
|
||||
>
|
||||
<Markdown source={formatValue} />
|
||||
</RowRender>
|
||||
);
|
||||
}
|
||||
|
||||
const WholeResponseModal = ({
|
||||
response,
|
||||
showDetail,
|
||||
onClose
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isCentered
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
h={['90vh', '80vh']}
|
||||
minW={['90vw', '600px']}
|
||||
iconSrc="/imgs/modal/wholeRecord.svg"
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.chat.response.Complete Response')}
|
||||
<QuestionTip ml={2} label={'从左往右,为各个模块的响应顺序'}></QuestionTip>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<Flex h={'100%'} flexDirection={'column'}>
|
||||
<ResponseBox response={response} showDetail={showDetail} />
|
||||
</Flex>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default WholeResponseModal;
|
||||
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
showDetail,
|
||||
hideTabs = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
hideTabs?: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
|
||||
const list = useMemo(
|
||||
() =>
|
||||
response.map((item, i) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'} justifyContent={'center'} px={2}>
|
||||
<Image
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
|
||||
?.avatar
|
||||
}
|
||||
alt={''}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
{t(item.moduleName)}
|
||||
</Flex>
|
||||
),
|
||||
value: `${i}`
|
||||
})),
|
||||
[response, t]
|
||||
);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(`0`);
|
||||
|
||||
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hideTabs && (
|
||||
<Box>
|
||||
<LightRowTabs list={list} value={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
)}
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<>
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.totalPoints !== undefined && (
|
||||
<Row
|
||||
label={t('support.wallet.usage.Total points')}
|
||||
value={formatNumber(activeModule.totalPoints)}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
label={t('core.chat.response.module time')}
|
||||
value={`${activeModule?.runningTime || 0}s`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call tokens')}
|
||||
value={`${activeModule?.toolCallTokens}`}
|
||||
/>
|
||||
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
value={activeModule?.contextTotalLen}
|
||||
/>
|
||||
<Row label={workflowT('response.Error')} value={activeModule?.error} />
|
||||
</>
|
||||
|
||||
{/* ai chat */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module temperature')}
|
||||
value={activeModule?.temperature}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('core.chat.response.module historyPreview')}
|
||||
rawDom={
|
||||
activeModule.historyPreview ? (
|
||||
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
|
||||
{activeModule.historyPreview?.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
_notLast={{
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: 'myWhite.700',
|
||||
mb: 2
|
||||
}}
|
||||
pb={2}
|
||||
>
|
||||
<Box fontWeight={'bold'}>{item.obj}</Box>
|
||||
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* dataset search */}
|
||||
<>
|
||||
{activeModule?.searchMode && (
|
||||
<Row
|
||||
label={t('core.dataset.search.search mode')}
|
||||
// @ts-ignore
|
||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
/>
|
||||
)}
|
||||
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
|
||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row
|
||||
label={t('core.chat.response.search using reRank')}
|
||||
value={`${activeModule?.searchUsingReRank}`}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.Extension model')}
|
||||
value={activeModule?.extensionModel}
|
||||
/>
|
||||
<Row
|
||||
label={t('support.wallet.usage.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* classify question */}
|
||||
<>
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
<Row
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
if (!activeModule?.cqList) return '';
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* if-else */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module if else Result')}
|
||||
value={activeModule?.ifElseResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* extract */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.module extract result')}
|
||||
value={activeModule?.extractResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* http */}
|
||||
<>
|
||||
<Row label={'Headers'} value={activeModule?.headers} />
|
||||
<Row label={'Params'} value={activeModule?.params} />
|
||||
<Row label={'Body'} value={activeModule?.body} />
|
||||
<Row
|
||||
label={t('core.chat.response.module http result')}
|
||||
value={activeModule?.httpResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* plugin */}
|
||||
<>
|
||||
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin response detail')}
|
||||
rawDom={<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* text output */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
|
||||
{/* tool call */}
|
||||
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call response detail')}
|
||||
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* code */}
|
||||
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
|
||||
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
|
||||
<Row label={workflowT('response.Code log')} value={activeModule?.codeLog} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { getUnreadCount } from '@/web/support/user/inform/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import Auth from './auth';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const Navbar = dynamic(() => import('./navbar'));
|
||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
||||
@@ -43,7 +44,8 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||
const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
const router = useRouter();
|
||||
const { Loading } = useLoading();
|
||||
const { loading, setScreenWidth, isPc, feConfigs, isNotSufficientModal } = useSystemStore();
|
||||
const { loading, feConfigs, isNotSufficientModal } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const isChatPage = useMemo(
|
||||
@@ -51,21 +53,6 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
[router.pathname, router.query]
|
||||
);
|
||||
|
||||
// listen screen width
|
||||
useEffect(() => {
|
||||
const resize = throttle(() => {
|
||||
setScreenWidth(document.documentElement.clientWidth);
|
||||
}, 300);
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
resize();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resize);
|
||||
};
|
||||
}, [setScreenWidth]);
|
||||
|
||||
const { data, refetch: refetchUnRead } = useQuery(['getUnreadCount'], getUnreadCount, {
|
||||
enabled: !!userInfo && !!feConfigs.isPlus,
|
||||
refetchInterval: 10000
|
||||
|
||||
@@ -32,20 +32,22 @@ export const useFolderDrag = ({
|
||||
e.preventDefault();
|
||||
setTargetId(undefined);
|
||||
},
|
||||
onDrop: async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setTrue();
|
||||
...(isFolder && {
|
||||
onDrop: async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setTrue();
|
||||
|
||||
try {
|
||||
if (targetId && dragId && targetId !== dragId) {
|
||||
await onDrop(dragId, targetId);
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
if (targetId && dragId && targetId !== dragId) {
|
||||
await onDrop(dragId, targetId);
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
setTargetId(undefined);
|
||||
setDragId(undefined);
|
||||
setFalse();
|
||||
},
|
||||
setTargetId(undefined);
|
||||
setDragId(undefined);
|
||||
setFalse();
|
||||
}
|
||||
}),
|
||||
...(activeStyles &&
|
||||
targetId === dataId && {
|
||||
...activeStyles
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ChatBoxContext } from '../Provider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
||||
|
||||
@@ -53,7 +54,9 @@ const ChatInput = ({
|
||||
|
||||
const { isChatting, whisperConfig, autoTTSResponse, chatInputGuide, outLinkAuthData } =
|
||||
useContextSelector(ChatBoxContext, (v) => v);
|
||||
const { isPc, whisperModel } = useSystemStore();
|
||||
const { whisperModel } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -15,38 +15,53 @@ import {
|
||||
defaultWhisperConfig
|
||||
} from '@fastgpt/global/core/app/constants';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
appAvatar?: string;
|
||||
|
||||
chatConfig?: AppChatConfigType;
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps & {
|
||||
welcomeText: string;
|
||||
variableList: 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>>;
|
||||
chatHistories: ChatSiteItemType[];
|
||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
isChatting: boolean;
|
||||
chatInputGuide: ChatInputGuideConfigType;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
variablesForm: UseFormReturn<FieldValues, any>;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
};
|
||||
|
||||
type useChatStoreType = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
welcomeText: string;
|
||||
variableList: 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;
|
||||
};
|
||||
|
||||
export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
welcomeText: '',
|
||||
variableList: [],
|
||||
@@ -100,27 +115,27 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||
open: false,
|
||||
customUrl: ''
|
||||
},
|
||||
outLinkAuthData: {}
|
||||
outLinkAuthData: {},
|
||||
// @ts-ignore
|
||||
variablesForm: undefined
|
||||
});
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
chatConfig?: AppChatConfigType;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Provider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
chatConfig = {},
|
||||
children
|
||||
}: ChatProviderProps) => {
|
||||
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
||||
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
|
||||
chatConfig = {},
|
||||
children,
|
||||
...props
|
||||
}: ChatProviderProps & {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
welcomeText = '',
|
||||
variables = [],
|
||||
@@ -167,12 +182,13 @@ const Provider = ({
|
||||
);
|
||||
|
||||
const value: useChatStoreType = {
|
||||
...props,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
welcomeText,
|
||||
variableList: variables,
|
||||
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
||||
questionGuide,
|
||||
ttsConfig,
|
||||
whisperConfig,
|
||||
@@ -191,7 +207,8 @@ const Provider = ({
|
||||
setChatHistories,
|
||||
isChatting,
|
||||
chatInputGuide,
|
||||
outLinkAuthData
|
||||
outLinkAuthData,
|
||||
variablesForm
|
||||
};
|
||||
|
||||
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Box, BoxProps, Card, Flex } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import ChatController, { type ChatControllerProps } from './ChatController';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { formatChatValue2InputType } from '../utils';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import styles from '../index.module.scss';
|
||||
import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import FilesBlock from './FilesBox';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import AIResponseBox from '../../../components/AIResponseBox';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
bg: 'myGray.100',
|
||||
color: 'myGray.600'
|
||||
},
|
||||
[ChatStatusEnum.running]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
},
|
||||
[ChatStatusEnum.finish]: {
|
||||
bg: 'green.50',
|
||||
color: 'green.700'
|
||||
}
|
||||
};
|
||||
|
||||
const ChatItem = ({
|
||||
type,
|
||||
avatar,
|
||||
statusBoxData,
|
||||
children,
|
||||
isLastChild,
|
||||
questionGuides = [],
|
||||
...chatControllerProps
|
||||
}: {
|
||||
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
|
||||
avatar?: string;
|
||||
statusBoxData?: {
|
||||
status: `${ChatStatusEnum}`;
|
||||
name: string;
|
||||
};
|
||||
questionGuides?: string[];
|
||||
children?: React.ReactNode;
|
||||
} & ChatControllerProps) => {
|
||||
const styleMap: BoxProps =
|
||||
type === ChatRoleEnum.Human
|
||||
? {
|
||||
order: 0,
|
||||
borderRadius: '8px 0 8px 8px',
|
||||
justifyContent: 'flex-end',
|
||||
textAlign: 'right',
|
||||
bg: 'primary.100'
|
||||
}
|
||||
: {
|
||||
order: 1,
|
||||
borderRadius: '0 8px 8px 8px',
|
||||
justifyContent: 'flex-start',
|
||||
textAlign: 'left',
|
||||
bg: 'myGray.50'
|
||||
};
|
||||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const { chat } = chatControllerProps;
|
||||
|
||||
const ContentCard = useMemo(() => {
|
||||
if (type === 'Human') {
|
||||
const { text, files = [] } = formatChatValue2InputType(chat.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
{files.length > 0 && <FilesBlock files={files} />}
|
||||
<Markdown source={text} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/* AI */
|
||||
return (
|
||||
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
|
||||
{chat.value.map((value, i) => {
|
||||
const key = `${chat.dataId}-ai-${i}`;
|
||||
|
||||
return (
|
||||
<AIResponseBox
|
||||
key={key}
|
||||
value={value}
|
||||
index={i}
|
||||
chat={chat}
|
||||
isLastChild={isLastChild}
|
||||
isChatting={isChatting}
|
||||
questionGuides={questionGuides}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
}, [chat, isChatting, isLastChild, questionGuides, type]);
|
||||
|
||||
const chatStatusMap = useMemo(() => {
|
||||
if (!statusBoxData?.status) return;
|
||||
return colorMap[statusBoxData.status];
|
||||
}, [statusBoxData?.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* control icon */}
|
||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||
<Box order={styleMap.order} ml={styleMap.ml}>
|
||||
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
|
||||
</Box>
|
||||
)}
|
||||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
{!!chatStatusMap && statusBoxData && isLastChild && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={3}
|
||||
py={'1.5px'}
|
||||
borderRadius="md"
|
||||
bg={chatStatusMap.bg}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<Box
|
||||
className={styles.statusAnimation}
|
||||
bg={chatStatusMap.color}
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius={'50%'}
|
||||
mt={'1px'}
|
||||
/>
|
||||
<Box ml={2} color={'myGray.600'}>
|
||||
{statusBoxData.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{/* content */}
|
||||
<Box mt={['6px', 2]} textAlign={styleMap.textAlign}>
|
||||
<Card
|
||||
className="markdown"
|
||||
{...MessageCardStyle}
|
||||
bg={styleMap.bg}
|
||||
borderRadius={styleMap.borderRadius}
|
||||
textAlign={'left'}
|
||||
>
|
||||
{ContentCard}
|
||||
{children}
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChatItem);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import MdImage from '@/components/Markdown/img/Image';
|
||||
import { UserInputFileItemType } from '@/components/ChatBox/type';
|
||||
import { UserInputFileItemType } from '@/components/core/chat/ChatContainer/ChatBox/type';
|
||||
|
||||
const FilesBlock = ({ files }: { files: UserInputFileItemType[] }) => {
|
||||
return (
|
||||
@@ -4,8 +4,8 @@ import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import QuoteItem from '../../core/dataset/QuoteItem';
|
||||
import RawSourceBox from '../../core/dataset/RawSourceBox';
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
|
||||
const QuoteModal = ({
|
||||
rawSearch = [],
|
||||
@@ -3,7 +3,6 @@ import { type ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import { Flex, useDisclosure, useTheme, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
@@ -13,10 +12,11 @@ import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
import { strIsLink } from '@fastgpt/global/common/string/tools';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||
const ContextModal = dynamic(() => import('./ContextModal'));
|
||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'));
|
||||
const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal'));
|
||||
|
||||
const isLLMNode = (item: ChatHistoryItemResType) =>
|
||||
item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools;
|
||||
@@ -29,7 +29,7 @@ const ResponseTags = ({
|
||||
showDetail: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
const [quoteModalData, setQuoteModalData] = useState<{
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
@@ -1,32 +1,27 @@
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import React from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Input, Textarea } from '@chakra-ui/react';
|
||||
import { Box, Button, Card, FormControl, Input, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatBoxInputFormType } from '../type.d';
|
||||
import { useRefresh } from '@fastgpt/web/hooks/useRefresh';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
const VariableInput = ({
|
||||
appAvatar,
|
||||
variableList,
|
||||
chatForm,
|
||||
onSubmitVariables
|
||||
chatStarted
|
||||
}: {
|
||||
appAvatar?: string;
|
||||
variableList: VariableItemType[];
|
||||
onSubmitVariables: (e: Record<string, any>) => void;
|
||||
chatStarted: boolean;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, setValue, handleSubmit: handleSubmitChat, watch } = chatForm;
|
||||
const variables = watch('variables');
|
||||
const chatStarted = watch('chatStarted');
|
||||
const { refresh } = useRefresh();
|
||||
|
||||
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
||||
const { register, getValues, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
@@ -61,7 +56,7 @@ const VariableInput = ({
|
||||
{item.type === VariableInputEnum.input && (
|
||||
<Input
|
||||
bg={'myWhite.400'}
|
||||
{...register(`variables.${item.key}`, {
|
||||
{...register(item.key, {
|
||||
required: item.required
|
||||
})}
|
||||
/>
|
||||
@@ -69,7 +64,7 @@ const VariableInput = ({
|
||||
{item.type === VariableInputEnum.textarea && (
|
||||
<Textarea
|
||||
bg={'myWhite.400'}
|
||||
{...register(`variables.${item.key}`, {
|
||||
{...register(item.key, {
|
||||
required: item.required
|
||||
})}
|
||||
rows={5}
|
||||
@@ -77,19 +72,24 @@ const VariableInput = ({
|
||||
/>
|
||||
)}
|
||||
{item.type === VariableInputEnum.select && (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
list={(item.enums || []).map((item) => ({
|
||||
label: item.value,
|
||||
value: item.value
|
||||
}))}
|
||||
{...register(`variables.${item.key}`, {
|
||||
required: item.required
|
||||
})}
|
||||
value={variables[item.key]}
|
||||
onchange={(e) => {
|
||||
refresh();
|
||||
setValue(`variables.${item.key}`, e);
|
||||
<Controller
|
||||
key={item.key}
|
||||
control={control}
|
||||
name={item.key}
|
||||
rules={{ required: item.required }}
|
||||
render={({ field: { ref, value } }) => {
|
||||
return (
|
||||
<MySelect
|
||||
ref={ref}
|
||||
width={'100%'}
|
||||
list={(item.enums || []).map((item) => ({
|
||||
label: item.value,
|
||||
value: item.value
|
||||
}))}
|
||||
value={value}
|
||||
onchange={(e) => setValue(item.key, e)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -100,8 +100,8 @@ const VariableInput = ({
|
||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||
size={'sm'}
|
||||
maxW={'100px'}
|
||||
onClick={handleSubmitChat((data) => {
|
||||
onSubmitVariables(data);
|
||||
onClick={handleSubmitChat(() => {
|
||||
chatForm.setValue('chatStarted', true);
|
||||
})}
|
||||
>
|
||||
{t('core.chat.Start Chat')}
|
||||
@@ -3,8 +3,12 @@ import React from 'react';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
|
||||
const WelcomeBox = ({ welcomeText }: { welcomeText: string }) => {
|
||||
const appAvatar = useContextSelector(ChatBoxContext, (v) => v.appAvatar);
|
||||
|
||||
const WelcomeBox = ({ appAvatar, welcomeText }: { appAvatar?: string; welcomeText: string }) => {
|
||||
return (
|
||||
<Box py={3}>
|
||||
{/* avatar */}
|
||||
@@ -9,7 +9,6 @@ import React, {
|
||||
useEffect
|
||||
} from 'react';
|
||||
import Script from 'next/script';
|
||||
import { throttle } from 'lodash';
|
||||
import type {
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
@@ -20,7 +19,6 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { Box, Flex, Checkbox } from '@chakra-ui/react';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -35,30 +33,25 @@ import type { AdminMarkType } from './components/SelectMarkCollection';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||
import type {
|
||||
generatingMessageProps,
|
||||
StartChatFnProps,
|
||||
ComponentRef,
|
||||
ChatBoxInputType,
|
||||
ChatBoxInputFormType
|
||||
} from './type.d';
|
||||
import type { ComponentRef, ChatBoxInputType, ChatBoxInputFormType } from './type.d';
|
||||
import type { StartChatFnProps, generatingMessageProps } from '../type';
|
||||
import ChatInput from './Input/ChatInput';
|
||||
import ChatBoxDivider from '../core/chat/Divider';
|
||||
import ChatBoxDivider from '../../Divider';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { formatChatValue2InputType } from './utils';
|
||||
import { textareaMinH } from './constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import ChatProvider, { ChatBoxContext } from './Provider';
|
||||
import ChatProvider, { ChatBoxContext, ChatProviderProps } from './Provider';
|
||||
|
||||
import ChatItem from './components/ChatItem';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCreation } from 'ahooks';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import type { StreamResponseType } from '@/web/common/api/fetch';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useThrottleFn } from 'ahooks';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||
@@ -74,29 +67,26 @@ enum FeedbackTypeEnum {
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
type Props = OutLinkChatAuthProps & {
|
||||
feedbackType?: `${FeedbackTypeEnum}`;
|
||||
showMarkIcon?: boolean; // admin mark dataset
|
||||
showVoiceIcon?: boolean;
|
||||
showEmptyIntro?: boolean;
|
||||
appAvatar?: string;
|
||||
userAvatar?: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
showFileSelector?: boolean;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
type Props = OutLinkChatAuthProps &
|
||||
ChatProviderProps & {
|
||||
feedbackType?: `${FeedbackTypeEnum}`;
|
||||
showMarkIcon?: boolean; // admin mark dataset
|
||||
showVoiceIcon?: boolean;
|
||||
showEmptyIntro?: boolean;
|
||||
userAvatar?: string;
|
||||
showFileSelector?: boolean;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
// not chat test params
|
||||
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||
StreamResponseType & {
|
||||
isNewChat?: boolean;
|
||||
}
|
||||
>;
|
||||
onDelMessage?: (e: { contentId: string }) => void;
|
||||
};
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||
StreamResponseType & {
|
||||
isNewChat?: boolean;
|
||||
}
|
||||
>;
|
||||
onDelMessage?: (e: { contentId: string }) => void;
|
||||
};
|
||||
|
||||
/*
|
||||
The input is divided into sections
|
||||
@@ -122,7 +112,6 @@ const ChatBox = (
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
onUpdateVariable,
|
||||
onStartChat,
|
||||
onDelMessage
|
||||
}: Props,
|
||||
@@ -132,10 +121,12 @@ const ChatBox = (
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { isPc, setLoading, feConfigs } = useSystemStore();
|
||||
const { setLoading, 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 [feedbackId, setFeedbackId] = useState<string>();
|
||||
@@ -156,6 +147,7 @@ const ChatBox = (
|
||||
splitText2Audio,
|
||||
chatHistories,
|
||||
setChatHistories,
|
||||
variablesForm,
|
||||
isChatting
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
|
||||
@@ -164,41 +156,44 @@ const ChatBox = (
|
||||
defaultValues: {
|
||||
input: '',
|
||||
files: [],
|
||||
variables: {},
|
||||
chatStarted: false
|
||||
}
|
||||
});
|
||||
const { setValue, watch, handleSubmit } = chatForm;
|
||||
const chatStarted = watch('chatStarted');
|
||||
|
||||
/* variable */
|
||||
const filterVariableNodes = useCreation(
|
||||
() => variableList.filter((item) => item.type !== VariableInputEnum.custom),
|
||||
[variableList]
|
||||
);
|
||||
const { setValue, watch } = chatForm;
|
||||
const chatStartedWatch = watch('chatStarted');
|
||||
const chatStarted = chatStartedWatch || chatHistories.length > 0 || variableList.length === 0;
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = (behavior: 'smooth' | 'auto' = 'smooth') => {
|
||||
if (!ChatBoxRef.current) return;
|
||||
ChatBoxRef.current.scrollTo({
|
||||
top: ChatBoxRef.current.scrollHeight,
|
||||
behavior
|
||||
});
|
||||
};
|
||||
const scrollToBottom = useCallback((behavior: 'smooth' | 'auto' = 'smooth', delay = 0) => {
|
||||
setTimeout(() => {
|
||||
if (!ChatBoxRef.current) {
|
||||
setTimeout(() => {
|
||||
scrollToBottom(behavior);
|
||||
}, 500);
|
||||
} else {
|
||||
ChatBoxRef.current.scrollTo({
|
||||
top: ChatBoxRef.current.scrollHeight,
|
||||
behavior
|
||||
});
|
||||
}
|
||||
}, delay);
|
||||
}, []);
|
||||
|
||||
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
|
||||
const generatingScroll = useCallback(
|
||||
throttle(() => {
|
||||
const { run: generatingScroll } = useThrottleFn(
|
||||
() => {
|
||||
if (!ChatBoxRef.current) return;
|
||||
const isBottom =
|
||||
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
|
||||
ChatBoxRef.current.scrollHeight;
|
||||
|
||||
isBottom && scrollToBottom('auto');
|
||||
}, 100),
|
||||
[]
|
||||
},
|
||||
{
|
||||
wait: 100
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
const generatingMessage = useCallback(
|
||||
({
|
||||
event,
|
||||
@@ -291,7 +286,7 @@ const ChatBox = (
|
||||
})
|
||||
};
|
||||
} else if (event === SseResponseEventEnum.updateVariables && variables) {
|
||||
setValue('variables', variables);
|
||||
variablesForm.reset(variables);
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -299,7 +294,7 @@ const ChatBox = (
|
||||
);
|
||||
generatingScroll();
|
||||
},
|
||||
[generatingScroll, setChatHistories, setValue, splitText2Audio]
|
||||
[generatingScroll, setChatHistories, splitText2Audio, variablesForm]
|
||||
);
|
||||
|
||||
// 重置输入内容
|
||||
@@ -347,13 +342,14 @@ const ChatBox = (
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
[questionGuide, shareId, outLinkUid, teamId, teamToken]
|
||||
[questionGuide, shareId, outLinkUid, teamId, teamToken, scrollToBottom]
|
||||
);
|
||||
|
||||
/* Abort chat completions, questionGuide */
|
||||
const abortRequest = useCallback(() => {
|
||||
chatController.current?.abort('stop');
|
||||
questionGuideController.current?.abort('stop');
|
||||
pluginController.current?.abort('stop');
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@@ -369,8 +365,8 @@ const ChatBox = (
|
||||
autoTTSResponse?: boolean;
|
||||
history?: ChatSiteItemType[];
|
||||
}) => {
|
||||
handleSubmit(
|
||||
async ({ variables }) => {
|
||||
variablesForm.handleSubmit(
|
||||
async (variables) => {
|
||||
if (!onStartChat) return;
|
||||
if (isChatting) {
|
||||
toast({
|
||||
@@ -455,9 +451,7 @@ const ChatBox = (
|
||||
// 清空输入内容
|
||||
resetInputVal({});
|
||||
setQuestionGuide([]);
|
||||
setTimeout(() => {
|
||||
scrollToBottom();
|
||||
}, 100);
|
||||
scrollToBottom('smooth', 100);
|
||||
try {
|
||||
// create abort obj
|
||||
const abortSignal = new AbortController();
|
||||
@@ -470,8 +464,8 @@ const ChatBox = (
|
||||
responseText,
|
||||
isNewChat = false
|
||||
} = await onStartChat({
|
||||
chatList: newChatList,
|
||||
messages,
|
||||
messages: messages.slice(0, -1),
|
||||
responseChatItemId: responseChatId,
|
||||
controller: abortSignal,
|
||||
generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }),
|
||||
variables: requestVariables
|
||||
@@ -542,7 +536,7 @@ const ChatBox = (
|
||||
autoTTSResponse && finishSegmentedAudio();
|
||||
},
|
||||
(err) => {
|
||||
console.log(err?.variables);
|
||||
console.log(err);
|
||||
}
|
||||
)();
|
||||
},
|
||||
@@ -553,18 +547,19 @@ const ChatBox = (
|
||||
finishSegmentedAudio,
|
||||
generatingMessage,
|
||||
generatingScroll,
|
||||
handleSubmit,
|
||||
isChatting,
|
||||
isPc,
|
||||
onStartChat,
|
||||
resetInputVal,
|
||||
scrollToBottom,
|
||||
setAudioPlayingChatId,
|
||||
setChatHistories,
|
||||
splitText2Audio,
|
||||
startSegmentedAudio,
|
||||
t,
|
||||
toast,
|
||||
variableList
|
||||
variableList,
|
||||
variablesForm
|
||||
]
|
||||
);
|
||||
|
||||
@@ -720,7 +715,7 @@ const ChatBox = (
|
||||
},
|
||||
[appId, chatId, feedbackType, setChatHistories, teamId, teamToken]
|
||||
);
|
||||
const onADdUserDislike = useCallback(
|
||||
const onAddUserDislike = useCallback(
|
||||
(chat: ChatSiteItemType) => {
|
||||
if (
|
||||
feedbackType !== FeedbackTypeEnum.user ||
|
||||
@@ -797,30 +792,18 @@ const ChatBox = (
|
||||
[appId, chatId, setChatHistories]
|
||||
);
|
||||
|
||||
const resetVariables = useCallback(
|
||||
(e: Record<string, any> = {}) => {
|
||||
const value: Record<string, any> = { ...e };
|
||||
filterVariableNodes?.forEach((item) => {
|
||||
value[item.key] = e[item.key] || '';
|
||||
});
|
||||
|
||||
setValue('variables', value);
|
||||
},
|
||||
[filterVariableNodes, setValue]
|
||||
);
|
||||
|
||||
const showEmpty = useMemo(
|
||||
() =>
|
||||
feConfigs?.show_emptyChat &&
|
||||
showEmptyIntro &&
|
||||
chatHistories.length === 0 &&
|
||||
!filterVariableNodes?.length &&
|
||||
!variableList?.length &&
|
||||
!welcomeText,
|
||||
[
|
||||
chatHistories.length,
|
||||
feConfigs?.show_emptyChat,
|
||||
showEmptyIntro,
|
||||
filterVariableNodes?.length,
|
||||
variableList?.length,
|
||||
welcomeText
|
||||
]
|
||||
);
|
||||
@@ -878,12 +861,10 @@ const ChatBox = (
|
||||
|
||||
// output data
|
||||
useImperativeHandle(ref, () => ({
|
||||
getChatHistories: () => chatHistories,
|
||||
resetVariables,
|
||||
resetHistory(e) {
|
||||
restartChat() {
|
||||
abortRequest();
|
||||
setValue('chatStarted', e.length > 0);
|
||||
setChatHistories(e);
|
||||
setValue('chatStarted', false);
|
||||
scrollToBottom('smooth', 500);
|
||||
},
|
||||
scrollToBottom,
|
||||
sendPrompt: (question: string) => {
|
||||
@@ -900,41 +881,33 @@ const ChatBox = (
|
||||
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
|
||||
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
|
||||
{showEmpty && <Empty />}
|
||||
{!!welcomeText && <WelcomeBox appAvatar={appAvatar} welcomeText={welcomeText} />}
|
||||
{!!welcomeText && <WelcomeBox welcomeText={welcomeText} />}
|
||||
{/* variable input */}
|
||||
{!!filterVariableNodes?.length && (
|
||||
<VariableInput
|
||||
appAvatar={appAvatar}
|
||||
variableList={filterVariableNodes}
|
||||
chatForm={chatForm}
|
||||
onSubmitVariables={(data) => {
|
||||
setValue('chatStarted', true);
|
||||
onUpdateVariable?.(data);
|
||||
}}
|
||||
/>
|
||||
{!!variableList?.length && (
|
||||
<VariableInput chatStarted={chatStarted} chatForm={chatForm} />
|
||||
)}
|
||||
{/* chat history */}
|
||||
<Box id={'history'}>
|
||||
{chatHistories.map((item, index) => (
|
||||
<Box key={item.dataId} py={5}>
|
||||
{item.obj === 'Human' && (
|
||||
{item.obj === ChatRoleEnum.Human && (
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={item.obj === 'Human' ? userAvatar : appAvatar}
|
||||
avatar={userAvatar}
|
||||
chat={item}
|
||||
onRetry={retryInput(item.dataId)}
|
||||
onDelete={delOneMessage(item.dataId)}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
/>
|
||||
)}
|
||||
{item.obj === 'AI' && (
|
||||
{item.obj === ChatRoleEnum.AI && (
|
||||
<>
|
||||
<ChatItem
|
||||
type={item.obj}
|
||||
avatar={appAvatar}
|
||||
chat={item}
|
||||
isLastChild={index === chatHistories.length - 1}
|
||||
{...(item.obj === 'AI' && {
|
||||
{...(item.obj === ChatRoleEnum.AI && {
|
||||
showVoiceIcon,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
@@ -948,7 +921,7 @@ const ChatBox = (
|
||||
),
|
||||
onAddUserLike: onAddUserLike(item),
|
||||
onCloseUserLike: onCloseUserLike(item),
|
||||
onAddUserDislike: onADdUserDislike(item),
|
||||
onAddUserDislike: onAddUserDislike(item),
|
||||
onReadUserDislike: onReadUserDislike(item)
|
||||
})}
|
||||
>
|
||||
@@ -997,7 +970,7 @@ const ChatBox = (
|
||||
</Box>
|
||||
</Box>
|
||||
{/* message input */}
|
||||
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && (
|
||||
{onStartChat && chatStarted && active && appId && (
|
||||
<ChatInput
|
||||
onSendMessage={sendPrompt}
|
||||
onStop={() => chatController.current?.abort('stop')}
|
||||
@@ -7,15 +7,6 @@ import {
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
export type generatingMessageProps = {
|
||||
event: SseResponseEventEnum;
|
||||
text?: string;
|
||||
name?: string;
|
||||
status?: 'running' | 'finish';
|
||||
tool?: ToolModuleResponseItemType;
|
||||
variables?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type UserInputFileItemType = {
|
||||
id: string;
|
||||
rawFile?: File;
|
||||
@@ -28,7 +19,6 @@ export type UserInputFileItemType = {
|
||||
export type ChatBoxInputFormType = {
|
||||
input: string;
|
||||
files: UserInputFileItemType[];
|
||||
variables: Record<string, any>;
|
||||
chatStarted: boolean;
|
||||
};
|
||||
|
||||
@@ -37,18 +27,8 @@ export type ChatBoxInputType = {
|
||||
files?: UserInputFileItemType[];
|
||||
};
|
||||
|
||||
export type StartChatFnProps = {
|
||||
chatList: ChatSiteItemType[];
|
||||
messages: ChatCompletionMessageParam[];
|
||||
controller: AbortController;
|
||||
variables: Record<string, any>;
|
||||
generatingMessage: (e: generatingMessageProps) => void;
|
||||
};
|
||||
|
||||
export type ComponentRef = {
|
||||
getChatHistories: () => ChatSiteItemType[];
|
||||
resetVariables: (data?: Record<string, any>) => void;
|
||||
resetHistory: (history: ChatSiteItemType[]) => void;
|
||||
restartChat: () => void;
|
||||
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
|
||||
sendPrompt: (question: string) => void;
|
||||
};
|
||||
@@ -2,7 +2,11 @@ import { ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatBoxInputType, UserInputFileItemType } from './type';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
export const formatChatValue2InputType = (value: ChatItemValueItemType[]): ChatBoxInputType => {
|
||||
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
|
||||
if (!value) {
|
||||
return { text: '', files: [] };
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
console.error('value is error', value);
|
||||
return { text: '', files: [] };
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import RenderPluginInput from './renderPluginInput';
|
||||
import { Button, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
|
||||
const RenderInput = () => {
|
||||
const { pluginInputs, variablesForm, histories, onStartChat, onNewChat, onSubmit, isChatting } =
|
||||
useContextSelector(PluginRunContext, (v) => v);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors }
|
||||
} = variablesForm;
|
||||
const isDisabledInput = histories.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{pluginInputs.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
key={input.key}
|
||||
control={control}
|
||||
name={input.key}
|
||||
rules={{ required: input.required }}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<RenderPluginInput
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
label={input.label}
|
||||
description={input.description}
|
||||
isDisabled={isDisabledInput}
|
||||
valueType={input.valueType}
|
||||
placeholder={input.placeholder}
|
||||
required={input.required}
|
||||
min={input.min}
|
||||
max={input.max}
|
||||
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{onStartChat && onNewChat && (
|
||||
<Flex justifyContent={'end'} mt={8}>
|
||||
<Button
|
||||
isLoading={isChatting}
|
||||
onClick={() => {
|
||||
if (histories.length > 0) {
|
||||
return onNewChat();
|
||||
}
|
||||
handleSubmit(onSubmit)();
|
||||
}}
|
||||
>
|
||||
{histories.length > 0 ? t('common.Restart') : t('common.Run')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderInput;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import AIResponseBox from '../../../components/AIResponseBox';
|
||||
|
||||
const RenderOutput = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
|
||||
const pluginOutputs = useMemo(() => {
|
||||
const pluginOutputs = histories?.[1]?.responseData?.find(
|
||||
(item) => item.moduleType === FlowNodeTypeEnum.pluginOutput
|
||||
)?.pluginOutput;
|
||||
|
||||
return JSON.stringify(pluginOutputs, null, 2);
|
||||
}, [histories]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box border={'base'} rounded={'md'} bg={'myGray.25'}>
|
||||
<Box p={4} color={'myGray.900'}>
|
||||
<Box color={'myGray.900'} fontWeight={'bold'}>
|
||||
流输出
|
||||
</Box>
|
||||
{histories.length > 0 && histories[1]?.value.length > 0 ? (
|
||||
<Box mt={2}>
|
||||
{histories[1].value.map((value, i) => {
|
||||
const key = `${histories[1].dataId}-ai-${i}`;
|
||||
return (
|
||||
<AIResponseBox
|
||||
key={key}
|
||||
value={value}
|
||||
index={i}
|
||||
chat={histories[1]}
|
||||
isLastChild={true}
|
||||
isChatting={isChatting}
|
||||
questionGuides={[]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box border={'base'} mt={4} rounded={'md'} bg={'myGray.25'}>
|
||||
<Box p={4} color={'myGray.900'} fontWeight={'bold'}>
|
||||
<Box>插件输出</Box>
|
||||
{histories.length > 0 && histories[1].responseData ? (
|
||||
<Markdown source={`~~~json\n${pluginOutputs}`} />
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderOutput;
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ResponseBox } from '../../../components/WholeResponseModal';
|
||||
import React from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
const RenderResponseDetail = () => {
|
||||
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||
|
||||
const responseData = histories?.[1]?.responseData || [];
|
||||
|
||||
return isChatting ? (
|
||||
<>{'进行中'}</>
|
||||
) : (
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
<ResponseBox response={responseData} showDetail={true} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderResponseDetail;
|
||||
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Switch,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
const RenderPluginInput = ({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
description,
|
||||
isDisabled,
|
||||
valueType,
|
||||
placeholder,
|
||||
required,
|
||||
min,
|
||||
max,
|
||||
isInvalid
|
||||
}: {
|
||||
value: any;
|
||||
onChange: () => void;
|
||||
label: string;
|
||||
description?: string;
|
||||
isDisabled?: boolean;
|
||||
valueType: WorkflowIOValueTypeEnum | undefined;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
min?: number;
|
||||
max?: number;
|
||||
isInvalid: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const render = (() => {
|
||||
if (valueType === WorkflowIOValueTypeEnum.string) {
|
||||
return (
|
||||
<Textarea
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
placeholder={t(placeholder)}
|
||||
bg={'myGray.50'}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.number) {
|
||||
return (
|
||||
<NumberInput
|
||||
step={1}
|
||||
min={min}
|
||||
max={max}
|
||||
bg={'myGray.50'}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
>
|
||||
<NumberInputField value={value} onChange={onChange} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
}
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return (
|
||||
<Switch
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<JsonEditor
|
||||
bg={'myGray.50'}
|
||||
placeholder={t(placeholder || '')}
|
||||
resize
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isInvalid={isInvalid}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
||||
return !!render ? (
|
||||
<Box _notLast={{ mb: 4 }} px={1}>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<Box position={'relative'}>
|
||||
{required && (
|
||||
<Box position={'absolute'} left={-2} top={'-1px'} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{label}
|
||||
</Box>
|
||||
{description && <QuestionTip ml={2} label={description} />}
|
||||
</Flex>
|
||||
{render}
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default RenderPluginInput;
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum PluginRunBoxTabEnum {
|
||||
input = 'input',
|
||||
output = 'output',
|
||||
detail = 'detail'
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import { AIChatItemValueItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { generatingMessageProps } from '../type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getPluginRunContent } from '@fastgpt/global/core/app/plugin/utils';
|
||||
|
||||
type PluginRunContextType = PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: FieldValues) => Promise<any>;
|
||||
};
|
||||
|
||||
export const PluginRunContext = createContext<PluginRunContextType>({
|
||||
pluginInputs: [],
|
||||
//@ts-ignore
|
||||
variablesForm: undefined,
|
||||
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.');
|
||||
}
|
||||
});
|
||||
|
||||
const PluginRunContextProvider = ({
|
||||
children,
|
||||
...props
|
||||
}: PluginRunBoxProps & { children: ReactNode }) => {
|
||||
const { pluginInputs, onStartChat, setHistories, histories, setTab } = props;
|
||||
|
||||
const { toast } = useToast();
|
||||
const chatController = useRef(new AbortController());
|
||||
|
||||
/* Abort chat completions, questionGuide */
|
||||
const abortRequest = useCallback(() => {
|
||||
chatController.current?.abort('stop');
|
||||
}, []);
|
||||
|
||||
const generatingMessage = useCallback(
|
||||
({ event, text = '', status, name, tool, variables }: generatingMessageProps) => {
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1 || item.obj !== ChatRoleEnum.AI) return item;
|
||||
|
||||
const lastValue: AIChatItemValueItemType = JSON.parse(
|
||||
JSON.stringify(item.value[item.value.length - 1])
|
||||
);
|
||||
|
||||
if (event === SseResponseEventEnum.flowNodeStatus && status) {
|
||||
return {
|
||||
...item,
|
||||
status,
|
||||
moduleName: name
|
||||
};
|
||||
} else if (
|
||||
(event === SseResponseEventEnum.answer || event === SseResponseEventEnum.fastAnswer) &&
|
||||
text
|
||||
) {
|
||||
if (!lastValue || !lastValue.text) {
|
||||
const newValue: AIChatItemValueItemType = {
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: text
|
||||
}
|
||||
};
|
||||
return {
|
||||
...item,
|
||||
value: item.value.concat(newValue)
|
||||
};
|
||||
} else {
|
||||
lastValue.text.content += text;
|
||||
return {
|
||||
...item,
|
||||
value: item.value.slice(0, -1).concat(lastValue)
|
||||
};
|
||||
}
|
||||
} else if (event === SseResponseEventEnum.toolCall && tool) {
|
||||
const val: AIChatItemValueItemType = {
|
||||
type: ChatItemValueTypeEnum.tool,
|
||||
tools: [tool]
|
||||
};
|
||||
return {
|
||||
...item,
|
||||
value: item.value.concat(val)
|
||||
};
|
||||
} else if (
|
||||
event === SseResponseEventEnum.toolParams &&
|
||||
tool &&
|
||||
lastValue.type === ChatItemValueTypeEnum.tool &&
|
||||
lastValue?.tools
|
||||
) {
|
||||
lastValue.tools = lastValue.tools.map((item) => {
|
||||
if (item.id === tool.id) {
|
||||
item.params += tool.params;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return {
|
||||
...item,
|
||||
value: item.value.slice(0, -1).concat(lastValue)
|
||||
};
|
||||
} else if (event === SseResponseEventEnum.toolResponse && tool) {
|
||||
// replace tool response
|
||||
return {
|
||||
...item,
|
||||
value: item.value.map((val) => {
|
||||
if (val.type === ChatItemValueTypeEnum.tool && val.tools) {
|
||||
const tools = val.tools.map((item) =>
|
||||
item.id === tool.id ? { ...item, response: tool.response } : item
|
||||
);
|
||||
return {
|
||||
...val,
|
||||
tools
|
||||
};
|
||||
}
|
||||
return val;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
);
|
||||
},
|
||||
[setHistories]
|
||||
);
|
||||
|
||||
const isChatting = useMemo(
|
||||
() => histories[histories.length - 1] && histories[histories.length - 1]?.status !== 'finish',
|
||||
[histories]
|
||||
);
|
||||
|
||||
const { runAsync: onSubmit } = useRequest2(async (e: FieldValues) => {
|
||||
if (!onStartChat) return;
|
||||
if (isChatting) {
|
||||
toast({
|
||||
title: '正在聊天中...请等待结束',
|
||||
status: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTab(PluginRunBoxTabEnum.output);
|
||||
|
||||
// reset controller
|
||||
abortRequest();
|
||||
const abortSignal = new AbortController();
|
||||
chatController.current = abortSignal;
|
||||
|
||||
setHistories([
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
status: 'finish',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: getPluginRunContent({
|
||||
pluginInputs
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
status: 'loading'
|
||||
}
|
||||
]);
|
||||
|
||||
try {
|
||||
const { responseData } = await onStartChat({
|
||||
messages: [],
|
||||
controller: chatController.current,
|
||||
generatingMessage,
|
||||
variables: e
|
||||
});
|
||||
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish',
|
||||
responseData
|
||||
};
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
toast({ title: err.message, status: 'error' });
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish'
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const contextValue: PluginRunContextType = {
|
||||
...props,
|
||||
isChatting,
|
||||
onSubmit
|
||||
};
|
||||
return <PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>;
|
||||
};
|
||||
|
||||
export default PluginRunContextProvider;
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import RenderInput from './components/RenderInput';
|
||||
import PluginRunContextProvider, { PluginRunContext } from './context';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import RenderOutput from './components/RenderOutput';
|
||||
import RenderResponseDetail from './components/RenderResponseDetail';
|
||||
|
||||
const PluginRunBox = () => {
|
||||
const { tab } = useContextSelector(PluginRunContext, (v) => v);
|
||||
|
||||
return (
|
||||
<>
|
||||
{tab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||
{tab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||
{tab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: PluginRunBoxProps) => {
|
||||
return (
|
||||
<PluginRunContextProvider {...props}>
|
||||
<PluginRunBox />
|
||||
</PluginRunContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Render;
|
||||
22
projects/app/src/components/core/chat/ChatContainer/PluginRunBox/type.d.ts
vendored
Normal file
22
projects/app/src/components/core/chat/ChatContainer/PluginRunBox/type.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import React from 'react';
|
||||
import { onStartChatType } from '../type';
|
||||
|
||||
export type PluginRunBoxProps = OutLinkChatAuthProps & {
|
||||
pluginInputs: FlowNodeInputItemType[];
|
||||
variablesForm: UseFormReturn<FieldValues, any>;
|
||||
histories: ChatSiteItemType[]; // chatHistories[1] is the response
|
||||
setHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
|
||||
onStartChat?: onStartChatType;
|
||||
onNewChat?: () => void;
|
||||
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
tab: PluginRunBoxTabEnum;
|
||||
setTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
|
||||
};
|
||||
26
projects/app/src/components/core/chat/ChatContainer/type.d.ts
vendored
Normal file
26
projects/app/src/components/core/chat/ChatContainer/type.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { StreamResponseType } from '@/web/common/api/fetch';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
import { ChatSiteItemType, ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
export type generatingMessageProps = {
|
||||
event: SseResponseEventEnum;
|
||||
text?: string;
|
||||
name?: string;
|
||||
status?: 'running' | 'finish';
|
||||
tool?: ToolModuleResponseItemType;
|
||||
variables?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type StartChatFnProps = {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
responseChatItemId?: string;
|
||||
controller: AbortController;
|
||||
variables: Record<string, any>;
|
||||
generatingMessage: (e: generatingMessageProps) => void;
|
||||
};
|
||||
|
||||
export type onStartChatType = (e: StartChatFnProps) => Promise<
|
||||
StreamResponseType & {
|
||||
isNewChat?: boolean;
|
||||
}
|
||||
>;
|
||||
@@ -0,0 +1,63 @@
|
||||
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 { ComponentRef as ChatComponentRef } from './ChatBox/type';
|
||||
|
||||
export const useChat = () => {
|
||||
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||
|
||||
const [chatRecords, setChatRecords] = useState<ChatSiteItemType[]>([]);
|
||||
const variablesForm = useForm();
|
||||
// plugin
|
||||
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||
|
||||
const resetChatRecords = useCallback(
|
||||
(props?: { records?: ChatSiteItemType[]; variables?: Record<string, any> }) => {
|
||||
const { records = [], variables = {} } = props || {};
|
||||
|
||||
setChatRecords(records);
|
||||
|
||||
// Reset to empty input
|
||||
const data = variablesForm.getValues();
|
||||
for (const key in data) {
|
||||
data[key] = '';
|
||||
}
|
||||
|
||||
variablesForm.reset({
|
||||
...data,
|
||||
...variables
|
||||
});
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
ChatBoxRef.current?.restartChat?.();
|
||||
},
|
||||
ChatBoxRef.current?.restartChat ? 0 : 500
|
||||
);
|
||||
},
|
||||
[variablesForm, setChatRecords]
|
||||
);
|
||||
|
||||
const clearChatRecords = useCallback(() => {
|
||||
setChatRecords([]);
|
||||
|
||||
const data = variablesForm.getValues();
|
||||
for (const key in data) {
|
||||
variablesForm.setValue(key, '');
|
||||
}
|
||||
console.log(ChatBoxRef.current);
|
||||
ChatBoxRef.current?.restartChat?.();
|
||||
}, [variablesForm]);
|
||||
|
||||
return {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
clearChatRecords,
|
||||
resetChatRecords
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
import Markdown, { CodeClassName } from '@/components/Markdown';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
UserChatItemValueItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
|
||||
type props = {
|
||||
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
||||
index: number;
|
||||
chat: ChatSiteItemType;
|
||||
isLastChild: boolean;
|
||||
isChatting: boolean;
|
||||
questionGuides: string[];
|
||||
};
|
||||
|
||||
const AIResponseBox = ({ value, index, chat, isLastChild, isChatting, questionGuides }: props) => {
|
||||
if (value.text) {
|
||||
let source = (value.text?.content || '').trim();
|
||||
|
||||
// First empty line
|
||||
if (!source && chat.value.length > 1) return null;
|
||||
|
||||
// computed question guide
|
||||
if (
|
||||
isLastChild &&
|
||||
!isChatting &&
|
||||
questionGuides.length > 0 &&
|
||||
index === chat.value.length - 1
|
||||
) {
|
||||
source = `${source}
|
||||
\`\`\`${CodeClassName.questionGuide}
|
||||
${JSON.stringify(questionGuides)}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Markdown
|
||||
source={source}
|
||||
showAnimation={isLastChild && isChatting && index === chat.value.length - 1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
||||
return (
|
||||
<Box>
|
||||
{value.tools.map((tool) => {
|
||||
const toolParams = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.params), null, 2);
|
||||
} catch (error) {
|
||||
return tool.params;
|
||||
}
|
||||
})();
|
||||
const toolResponse = (() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.response), null, 2);
|
||||
} catch (error) {
|
||||
return tool.response;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<Accordion key={tool.id} allowToggle>
|
||||
<AccordionItem borderTop={'none'} borderBottom={'none'}>
|
||||
<AccordionButton
|
||||
w={'auto'}
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.200'}
|
||||
boxShadow={'1'}
|
||||
_hover={{
|
||||
bg: 'auto'
|
||||
}}
|
||||
>
|
||||
<Avatar src={tool.toolAvatar} w={'1rem'} h={'1rem'} mr={2} />
|
||||
<Box mr={1} fontSize={'sm'}>
|
||||
{tool.toolName}
|
||||
</Box>
|
||||
{isChatting && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />}
|
||||
<AccordionIcon color={'myGray.600'} ml={5} />
|
||||
</AccordionButton>
|
||||
<AccordionPanel
|
||||
py={0}
|
||||
px={0}
|
||||
mt={0}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
maxH={'500px'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{toolParams && toolParams !== '{}' && (
|
||||
<Markdown
|
||||
source={`~~~json#Input
|
||||
${toolParams}`}
|
||||
/>
|
||||
)}
|
||||
{toolResponse && (
|
||||
<Markdown
|
||||
source={`~~~json#Response
|
||||
${toolResponse}`}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default React.memo(AIResponseBox);
|
||||
@@ -0,0 +1,347 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { QuoteList } from '../ChatContainer/ChatBox/components/QuoteModal';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
function RowRender({
|
||||
children,
|
||||
mb,
|
||||
label,
|
||||
...props
|
||||
}: { children: React.ReactNode; label: string } & BoxProps) {
|
||||
return (
|
||||
<Box mb={3}>
|
||||
<Box fontSize={'sm'} mb={mb} flex={'0 0 90px'}>
|
||||
{label}:
|
||||
</Box>
|
||||
<Box borderRadius={'sm'} fontSize={['xs', 'sm']} bg={'myGray.50'} {...props}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
function Row({
|
||||
label,
|
||||
value,
|
||||
rawDom
|
||||
}: {
|
||||
label: string;
|
||||
value?: string | number | boolean | object;
|
||||
rawDom?: React.ReactNode;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const val = value || rawDom;
|
||||
const isObject = typeof value === 'object';
|
||||
|
||||
const formatValue = useMemo(() => {
|
||||
if (isObject) {
|
||||
return `~~~json\n${JSON.stringify(value, null, 2)}`;
|
||||
}
|
||||
return `${value}`;
|
||||
}, [isObject, value]);
|
||||
|
||||
if (rawDom) {
|
||||
return (
|
||||
<RowRender label={label} mb={1}>
|
||||
{rawDom}
|
||||
</RowRender>
|
||||
);
|
||||
}
|
||||
|
||||
if (val === undefined || val === '' || val === 'undefined') return null;
|
||||
|
||||
return (
|
||||
<RowRender
|
||||
label={label}
|
||||
mb={isObject ? 0 : 1}
|
||||
{...(isObject
|
||||
? { transform: 'translateY(-3px)' }
|
||||
: value
|
||||
? { px: 3, py: 2, border: theme.borders.base }
|
||||
: {})}
|
||||
>
|
||||
<Markdown source={formatValue} />
|
||||
</RowRender>
|
||||
);
|
||||
}
|
||||
|
||||
const WholeResponseModal = ({
|
||||
response,
|
||||
showDetail,
|
||||
onClose
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isCentered
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
h={['90vh', '80vh']}
|
||||
minW={['90vw', '600px']}
|
||||
iconSrc="/imgs/modal/wholeRecord.svg"
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.chat.response.Complete Response')}
|
||||
<QuestionTip ml={2} label={'从左往右,为各个模块的响应顺序'}></QuestionTip>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<ResponseBox response={response} showDetail={showDetail} />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default WholeResponseModal;
|
||||
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
showDetail,
|
||||
hideTabs = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
hideTabs?: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
|
||||
const list = useMemo(
|
||||
() =>
|
||||
response.map((item, i) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'} justifyContent={'center'} px={2}>
|
||||
<Image
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
|
||||
?.avatar
|
||||
}
|
||||
alt={''}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
{t(item.moduleName)}
|
||||
</Flex>
|
||||
),
|
||||
value: `${i}`
|
||||
})),
|
||||
[response, t]
|
||||
);
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(`0`);
|
||||
|
||||
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...(hideTabs ? { overflow: 'auto' } : { display: 'flex', flexDirection: 'column' })}
|
||||
h={'100%'}
|
||||
>
|
||||
{!hideTabs && (
|
||||
<Box>
|
||||
<LightRowTabs
|
||||
list={list}
|
||||
value={currentTab}
|
||||
inlineStyles={{ pt: 0 }}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{activeModule && (
|
||||
<Box
|
||||
py={2}
|
||||
px={4}
|
||||
{...(hideTabs
|
||||
? {}
|
||||
: {
|
||||
flex: '1 0 0',
|
||||
overflow: 'auto'
|
||||
})}
|
||||
>
|
||||
<>
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.totalPoints !== undefined && (
|
||||
<Row
|
||||
label={t('support.wallet.usage.Total points')}
|
||||
value={formatNumber(activeModule.totalPoints)}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
label={t('core.chat.response.module time')}
|
||||
value={`${activeModule?.runningTime || 0}s`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call tokens')}
|
||||
value={`${activeModule?.toolCallTokens}`}
|
||||
/>
|
||||
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
label={t('core.chat.response.context total length')}
|
||||
value={activeModule?.contextTotalLen}
|
||||
/>
|
||||
<Row label={workflowT('response.Error')} value={activeModule?.error} />
|
||||
</>
|
||||
|
||||
{/* ai chat */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module temperature')}
|
||||
value={activeModule?.temperature}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||
<Row
|
||||
label={t('core.chat.response.module historyPreview')}
|
||||
rawDom={
|
||||
activeModule.historyPreview ? (
|
||||
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
|
||||
{activeModule.historyPreview?.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
_notLast={{
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: 'myWhite.700',
|
||||
mb: 2
|
||||
}}
|
||||
pb={2}
|
||||
>
|
||||
<Box fontWeight={'bold'}>{item.obj}</Box>
|
||||
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* dataset search */}
|
||||
<>
|
||||
{activeModule?.searchMode && (
|
||||
<Row
|
||||
label={t('core.dataset.search.search mode')}
|
||||
// @ts-ignore
|
||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
label={t('core.chat.response.module similarity')}
|
||||
value={activeModule?.similarity}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row
|
||||
label={t('core.chat.response.search using reRank')}
|
||||
value={`${activeModule?.searchUsingReRank}`}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.Extension model')}
|
||||
value={activeModule?.extensionModel}
|
||||
/>
|
||||
<Row
|
||||
label={t('support.wallet.usage.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* classify question */}
|
||||
<>
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
<Row
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
if (!activeModule?.cqList) return '';
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* if-else */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module if else Result')}
|
||||
value={activeModule?.ifElseResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* extract */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module extract description')}
|
||||
value={activeModule?.extractDescription}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.module extract result')}
|
||||
value={activeModule?.extractResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* http */}
|
||||
<>
|
||||
<Row label={'Headers'} value={activeModule?.headers} />
|
||||
<Row label={'Params'} value={activeModule?.params} />
|
||||
<Row label={'Body'} value={activeModule?.body} />
|
||||
<Row
|
||||
label={t('core.chat.response.module http result')}
|
||||
value={activeModule?.httpResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* plugin */}
|
||||
<>
|
||||
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
|
||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Plugin response detail')}
|
||||
rawDom={
|
||||
<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
{/* text output */}
|
||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||
|
||||
{/* tool call */}
|
||||
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
|
||||
<Row
|
||||
label={t('core.chat.response.Tool call response detail')}
|
||||
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* code */}
|
||||
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
|
||||
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
|
||||
<Row label={workflowT('response.Code log')} value={activeModule?.codeLog} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
3
projects/app/src/global/core/chat/api.d.ts
vendored
3
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import type { AppChatConfigType, AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
export type GetChatSpeechProps = {
|
||||
ttsConfig: AppTTSConfigType;
|
||||
@@ -39,6 +40,8 @@ export type InitChatResponse = {
|
||||
avatar: string;
|
||||
intro: string;
|
||||
canUse?: boolean;
|
||||
type: `${AppTypeEnum}`;
|
||||
pluginInputs: FlowNodeInputItemType[];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { InitChatResponse } from './api';
|
||||
|
||||
export const defaultChatData: InitChatResponse = {
|
||||
@@ -7,7 +8,9 @@ export const defaultChatData: InitChatResponse = {
|
||||
name: 'Loading',
|
||||
avatar: '/icon/logo.svg',
|
||||
intro: '',
|
||||
canUse: false
|
||||
canUse: false,
|
||||
type: AppTypeEnum.simple,
|
||||
pluginInputs: []
|
||||
},
|
||||
title: '新对话',
|
||||
variables: {},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
|
||||
export type PostWorkflowDebugProps = {
|
||||
nodes: RuntimeNodeItemType[];
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
@@ -54,7 +55,7 @@ const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccoun
|
||||
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
|
||||
|
||||
const Account = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standardConstants;
|
||||
|
||||
@@ -99,7 +100,7 @@ const MyInfo = () => {
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
|
||||
@@ -9,12 +9,14 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const InformTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { Loading } = useLoading();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const {
|
||||
data: informs,
|
||||
isLoading,
|
||||
|
||||
@@ -31,6 +31,7 @@ import Avatar from '@/components/Avatar';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||
|
||||
const UsageTable = () => {
|
||||
@@ -41,7 +42,7 @@ const UsageTable = () => {
|
||||
to: new Date()
|
||||
});
|
||||
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo } = useUserStore();
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import UserInfo from './components/Info';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Script from 'next/script';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const Promotion = dynamic(() => import('./components/Promotion'));
|
||||
const UsageTable = dynamic(() => import('./components/UsageTable'));
|
||||
@@ -34,7 +35,8 @@ enum TabEnum {
|
||||
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, setUserInfo } = useUserStore();
|
||||
const { feConfigs, isPc, systemVersion } = useSystemStore();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const tabList = [
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constan
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
export type ListAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -55,8 +56,8 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
const searchMatch = searchKey
|
||||
? {
|
||||
$or: [
|
||||
{ name: { $regex: searchKey, $options: 'i' } },
|
||||
{ intro: { $regex: searchKey, $options: 'i' } }
|
||||
{ name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } },
|
||||
{ intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }
|
||||
]
|
||||
}
|
||||
: {};
|
||||
@@ -65,7 +66,14 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
return {
|
||||
// get all chat app
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple] },
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] },
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
if (searchKey) {
|
||||
return {
|
||||
teamId,
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
@@ -74,8 +82,7 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
teamId,
|
||||
...(type && Array.isArray(type) && { type: { $in: type } }),
|
||||
...(type && { type }),
|
||||
...parseParentIdInMongo(parentId),
|
||||
...searchMatch
|
||||
...parseParentIdInMongo(parentId)
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { sseErrRes } from '@fastgpt/service/common/response';
|
||||
import { 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 {
|
||||
ChatItemType,
|
||||
ChatItemValueItemType,
|
||||
UserChatItemValueItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import type { UserChatItemValueItemType } 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';
|
||||
@@ -18,10 +13,14 @@ import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
prompt: UserChatItemValueItemType[];
|
||||
messages: ChatCompletionMessageParam[];
|
||||
nodes: RuntimeNodeItemType[];
|
||||
edges: RuntimeEdgeItemType[];
|
||||
variables: Record<string, any>;
|
||||
@@ -29,7 +28,7 @@ export type Props = {
|
||||
appName: string;
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.on('close', () => {
|
||||
res.end();
|
||||
});
|
||||
@@ -38,26 +37,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
res.end();
|
||||
});
|
||||
|
||||
let {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
history = [],
|
||||
prompt,
|
||||
variables = {},
|
||||
appName,
|
||||
appId
|
||||
} = req.body as Props;
|
||||
let { nodes = [], edges = [], messages = [], variables = {}, appName, appId } = req.body as Props;
|
||||
try {
|
||||
await connectToDatabase();
|
||||
if (!history || !nodes || !prompt || prompt.length === 0) {
|
||||
throw new Error('Prams Error');
|
||||
}
|
||||
if (!Array.isArray(nodes)) {
|
||||
throw new Error('Nodes is not array');
|
||||
}
|
||||
if (!Array.isArray(edges)) {
|
||||
throw new Error('Edges is not array');
|
||||
}
|
||||
// [histories, user]
|
||||
const chatMessages = GPTMessages2Chats(messages);
|
||||
const userInput = chatMessages.pop()?.value as UserChatItemValueItemType[] | undefined;
|
||||
|
||||
/* user auth */
|
||||
const [{ app }, { teamId, tmbId }] = await Promise.all([
|
||||
@@ -67,6 +51,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
authToken: true
|
||||
})
|
||||
]);
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
if (!Array.isArray(nodes)) {
|
||||
throw new Error('Nodes is not array');
|
||||
}
|
||||
if (!Array.isArray(edges)) {
|
||||
throw new Error('Edges is not array');
|
||||
}
|
||||
|
||||
// Plugin need to replace inputs
|
||||
if (isPlugin) {
|
||||
nodes = updatePluginInputByVariables(nodes, variables);
|
||||
} else {
|
||||
if (!userInput) {
|
||||
throw new Error('Params Error');
|
||||
}
|
||||
}
|
||||
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
@@ -82,8 +83,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
runtimeNodes: nodes,
|
||||
runtimeEdges: edges,
|
||||
variables,
|
||||
query: removeEmptyUserInput(prompt),
|
||||
histories: history,
|
||||
query: removeEmptyUserInput(userInput),
|
||||
histories: chatMessages,
|
||||
stream: true,
|
||||
detail: true,
|
||||
maxRunTimes: 200
|
||||
@@ -117,6 +118,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
@@ -53,6 +54,8 @@ async function handler(
|
||||
}),
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
const pluginInputs =
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? [];
|
||||
|
||||
return {
|
||||
chatId,
|
||||
@@ -72,7 +75,9 @@ async function handler(
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -47,7 +49,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value userGoodFeedback userBadFeedback ${
|
||||
shareChat.responseDetail
|
||||
shareChat.responseDetail || app.type === AppTypeEnum.plugin
|
||||
? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
: ''
|
||||
} `
|
||||
@@ -56,11 +58,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]);
|
||||
|
||||
// pick share response field
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -82,7 +85,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -58,11 +60,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]);
|
||||
|
||||
// pick share response field
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
app.type !== AppTypeEnum.plugin &&
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||
}
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
@@ -83,7 +86,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
chatModels: getChatModelNameListByModules(nodes),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro
|
||||
intro: app.intro,
|
||||
type: app.type,
|
||||
pluginInputs:
|
||||
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||
?.inputs ?? []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,11 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
ChatItemValueTypeEnum,
|
||||
ChatRoleEnum,
|
||||
ChatSourceEnum
|
||||
} from '@fastgpt/global/core/chat/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
|
||||
@@ -28,10 +32,10 @@ import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
|
||||
import {
|
||||
concatHistories,
|
||||
filterPublicNodeResponseData,
|
||||
getChatTitleFromChatMessage,
|
||||
removeEmptyUserInput
|
||||
} from '@fastgpt/global/core/chat/utils';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
@@ -49,9 +53,17 @@ import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import {
|
||||
getPluginInputsFromStoreNodes,
|
||||
getPluginRunContent
|
||||
} from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
|
||||
type FastGptWebChatProps = {
|
||||
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
|
||||
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
|
||||
appId?: string;
|
||||
};
|
||||
|
||||
@@ -59,14 +71,11 @@ export type Props = ChatCompletionCreateParams &
|
||||
FastGptWebChatProps &
|
||||
OutLinkChatAuthProps & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
responseChatItemId?: string;
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
variables: Record<string, any>;
|
||||
variables: Record<string, any>; // Global variables or plugin inputs
|
||||
};
|
||||
export type ChatResponseType = {
|
||||
newChatId: string;
|
||||
quoteLen?: number;
|
||||
};
|
||||
|
||||
type AuthResponseType = {
|
||||
teamId: string;
|
||||
@@ -89,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.end();
|
||||
});
|
||||
|
||||
const {
|
||||
let {
|
||||
chatId,
|
||||
appId,
|
||||
// share chat
|
||||
@@ -98,41 +107,39 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// team chat
|
||||
teamId: spaceTeamId,
|
||||
teamToken,
|
||||
|
||||
stream = false,
|
||||
detail = false,
|
||||
messages = [],
|
||||
variables = {}
|
||||
variables = {},
|
||||
responseChatItemId = getNanoid()
|
||||
} = req.body as Props;
|
||||
try {
|
||||
const originIp = requestIp.getClientIp(req);
|
||||
|
||||
await connectToDatabase();
|
||||
// body data check
|
||||
if (!messages) {
|
||||
throw new Error('Prams Error');
|
||||
}
|
||||
const originIp = requestIp.getClientIp(req);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
if (!Array.isArray(messages)) {
|
||||
throw new Error('messages is not array');
|
||||
}
|
||||
if (messages.length === 0) {
|
||||
throw new Error('messages is empty');
|
||||
}
|
||||
|
||||
let startTime = Date.now();
|
||||
|
||||
// Web chat params: [Human, AI]
|
||||
/*
|
||||
Web params: chatId + [Human]
|
||||
API params: chatId + [Human]
|
||||
API params: [histories, Human]
|
||||
*/
|
||||
const chatMessages = GPTMessages2Chats(messages);
|
||||
if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) {
|
||||
chatMessages.pop();
|
||||
}
|
||||
|
||||
// user question
|
||||
const question = chatMessages.pop() as UserChatItemType;
|
||||
if (!question) {
|
||||
throw new Error('Question is empty');
|
||||
}
|
||||
// Computed start hook params
|
||||
const startHookText = (() => {
|
||||
// Chat
|
||||
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
|
||||
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
|
||||
|
||||
const { text, files } = chatValue2RuntimePrompt(question.value);
|
||||
// plugin
|
||||
return JSON.stringify(variables);
|
||||
})();
|
||||
|
||||
/*
|
||||
1. auth app permission
|
||||
@@ -149,7 +156,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
outLinkUid,
|
||||
chatId,
|
||||
ip: originIp,
|
||||
question: text
|
||||
question: startHookText
|
||||
});
|
||||
}
|
||||
// team space chat
|
||||
@@ -169,8 +176,45 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
chatId
|
||||
});
|
||||
})();
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
// 1. get and concat history; 2. get app workflow
|
||||
// Check message type
|
||||
if (isPlugin) {
|
||||
detail = true;
|
||||
} else {
|
||||
if (messages.length === 0) {
|
||||
throw new Error('messages is empty');
|
||||
}
|
||||
}
|
||||
|
||||
// Get obj=Human history
|
||||
const userQuestion: UserChatItemType = (() => {
|
||||
if (isPlugin) {
|
||||
return {
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: getPluginRunContent({
|
||||
pluginInputs: getPluginInputsFromStoreNodes(app.modules)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
|
||||
if (!latestHumanChat) {
|
||||
throw new Error('User question is empty');
|
||||
}
|
||||
return latestHumanChat;
|
||||
})();
|
||||
const { text, files } = chatValue2RuntimePrompt(userQuestion.value);
|
||||
|
||||
// Get and concat history;
|
||||
const limit = getMaxHistoryLimitFromNodes(app.modules);
|
||||
const [{ histories }, { nodes, edges, chatConfig }] = await Promise.all([
|
||||
getChatItems({
|
||||
@@ -182,7 +226,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
getAppLatestVersion(app._id, app)
|
||||
]);
|
||||
const newHistories = concatHistories(histories, chatMessages);
|
||||
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
|
||||
|
||||
// Get runtimeNodes
|
||||
const runtimeNodes = isPlugin
|
||||
? updatePluginInputByVariables(
|
||||
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||
variables
|
||||
)
|
||||
: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes));
|
||||
|
||||
/* start flow controller */
|
||||
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
|
||||
@@ -196,10 +247,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
app,
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||
runtimeNodes,
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges),
|
||||
variables,
|
||||
query: removeEmptyUserInput(question.value),
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
histories: newHistories,
|
||||
stream,
|
||||
detail,
|
||||
@@ -245,6 +296,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return ChatSourceEnum.online;
|
||||
})();
|
||||
|
||||
const newTitle = isPlugin
|
||||
? variables.cTime ?? getSystemTime(user.timezone)
|
||||
: getChatTitleFromChatMessage(userQuestion);
|
||||
|
||||
await saveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
@@ -254,11 +309,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appChatConfig: chatConfig,
|
||||
variables: newVariables,
|
||||
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
|
||||
newTitle,
|
||||
shareId,
|
||||
outLinkUid: outLinkUserId,
|
||||
source,
|
||||
content: [
|
||||
question,
|
||||
userQuestion,
|
||||
{
|
||||
dataId: responseChatItemId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
@@ -295,7 +351,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
});
|
||||
|
||||
if (detail) {
|
||||
if (responseDetail) {
|
||||
if (responseDetail || isPlugin) {
|
||||
responseWrite({
|
||||
res,
|
||||
event: SseResponseEventEnum.flowResponses,
|
||||
@@ -312,6 +368,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return assistantResponses[0].text?.content;
|
||||
return assistantResponses;
|
||||
})();
|
||||
|
||||
res.json({
|
||||
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
||||
id: chatId || '',
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, useTheme } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
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';
|
||||
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||
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';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
|
||||
const DetailLogsModal = ({
|
||||
appId,
|
||||
chatId,
|
||||
onClose
|
||||
}: {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystem();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetChatRecords
|
||||
} = useChat();
|
||||
|
||||
const { data: chat, isFetching } = useQuery(
|
||||
['getChatDetail', chatId],
|
||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||
{
|
||||
onSuccess(res) {
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: 'finish' as any
|
||||
}));
|
||||
|
||||
resetChatRecords({
|
||||
records: history,
|
||||
variables: res.variables
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const title = chat?.title;
|
||||
const chatModels = chat?.app?.chatModels;
|
||||
const isPlugin = chat?.app.type === AppTypeEnum.plugin;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox
|
||||
isLoading={isFetching}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
zIndex={3}
|
||||
position={['fixed', 'absolute']}
|
||||
top={[0, '2%']}
|
||||
right={0}
|
||||
h={['100%', '96%']}
|
||||
w={'100%'}
|
||||
maxW={['100%', '600px']}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
>
|
||||
{/* Header */}
|
||||
{isPlugin ? (
|
||||
<Flex
|
||||
alignItems={'flex-start'}
|
||||
justifyContent={'space-between'}
|
||||
px={3}
|
||||
pt={3}
|
||||
bg={'myGray.25'}
|
||||
borderBottom={'base'}
|
||||
>
|
||||
<LightRowTabs<PluginRunBoxTabEnum>
|
||||
list={[
|
||||
{ label: t('common.Input'), value: PluginRunBoxTabEnum.input },
|
||||
...(chatRecords.length > 0
|
||||
? [
|
||||
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
value={pluginRunTab}
|
||||
onChange={setPluginRunTab}
|
||||
inlineStyles={{ px: 0.5, pb: 2 }}
|
||||
gap={5}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
|
||||
<CloseIcon onClick={onClose} />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={[3, 5]}
|
||||
h={['46px', '60px']}
|
||||
borderBottom={theme.borders.base}
|
||||
borderBottomColor={'gray.200'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{isPc ? (
|
||||
<>
|
||||
<Box mr={3} color={'myGray.1000'}>
|
||||
{title}
|
||||
</Box>
|
||||
{chatRecords.length > 0 && (
|
||||
<>
|
||||
<MyTag colorSchema="blue">
|
||||
<MyIcon name={'history'} w={'14px'} />
|
||||
<Box ml={1}>{`${chatRecords.length}条记录`}</Box>
|
||||
</MyTag>
|
||||
{!!chatModels && (
|
||||
<MyTag ml={2} colorSchema={'green'}>
|
||||
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
|
||||
<Box ml={1}>{chatModels.join(',')}</Box>
|
||||
</MyTag>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box flex={1} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
|
||||
<Box ml={1} className="textEllipsis">
|
||||
{title}
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<CloseIcon onClick={onClose} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* Chat container */}
|
||||
<Box pt={2} flex={'1 0 0'}>
|
||||
{isPlugin ? (
|
||||
<Box px={5} pt={2} h={'100%'}>
|
||||
<PluginRunBox
|
||||
pluginInputs={chat?.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chat.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appAvatar={chat?.app.avatar}
|
||||
userAvatar={HUMAN_ICON}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</MyBox>
|
||||
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DetailLogsModal;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Th,
|
||||
Td,
|
||||
Tbody,
|
||||
useTheme,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
HStack
|
||||
@@ -19,31 +18,26 @@ import { useTranslation } from 'next-i18next';
|
||||
import { getAppChatLogs } from '@/web/core/app/api';
|
||||
import dayjs from 'dayjs';
|
||||
import { ChatSourceMap } from '@fastgpt/global/core/chat/constants';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { AppLogsListItemType } from '@/types/app';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef } from '@/components/ChatBox/type.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { addDays } from 'date-fns';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||
import { formatChatValue2InputType } from '@/components/ChatBox/utils';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../context';
|
||||
import { cardStyles } from '../constants';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
|
||||
|
||||
const Logs = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||
|
||||
@@ -225,131 +219,3 @@ const Logs = () => {
|
||||
};
|
||||
|
||||
export default React.memo(Logs);
|
||||
|
||||
const DetailLogsModal = ({
|
||||
appId,
|
||||
chatId,
|
||||
onClose
|
||||
}: {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { isPc } = useSystemStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const { data: chat, isFetching } = useQuery(
|
||||
['getChatDetail', chatId],
|
||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||
{
|
||||
onSuccess(res) {
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: 'finish' as any
|
||||
}));
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const history = useMemo(() => (chat?.history ? chat.history : []), [chat]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
const { text } = formatChatValue2InputType(history[history.length - 2]?.value);
|
||||
return text?.slice(0, 8);
|
||||
}, [history]);
|
||||
const chatModels = chat?.app?.chatModels;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox
|
||||
isLoading={isFetching}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
zIndex={3}
|
||||
position={['fixed', 'absolute']}
|
||||
top={[0, '2%']}
|
||||
right={0}
|
||||
h={['100%', '96%']}
|
||||
w={'100%'}
|
||||
maxW={['100%', '600px']}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={[3, 5]}
|
||||
h={['46px', '60px']}
|
||||
borderBottom={theme.borders.base}
|
||||
borderBottomColor={'gray.200'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{isPc ? (
|
||||
<>
|
||||
<Box mr={3} color={'myGray.1000'}>
|
||||
{title}
|
||||
</Box>
|
||||
<MyTag colorSchema="blue">
|
||||
<MyIcon name={'history'} w={'14px'} />
|
||||
<Box ml={1}>{`${history.length}条记录`}</Box>
|
||||
</MyTag>
|
||||
{!!chatModels && chatModels.length > 0 && (
|
||||
<MyTag ml={2} colorSchema={'green'}>
|
||||
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
|
||||
<Box ml={1}>{chatModels.join(',')}</Box>
|
||||
</MyTag>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
|
||||
<Box ml={1} className="textEllipsis">
|
||||
{title}
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
w={'20px'}
|
||||
h={'20px'}
|
||||
borderRadius={'50%'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
onClick={onClose}
|
||||
>
|
||||
<MyIcon name={'common/closeLight'} w={'12px'} h={'12px'} color={'myGray.700'} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box pt={2} flex={'1 0 0'}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chat?.app.avatar}
|
||||
userAvatar={HUMAN_ICON}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
/>
|
||||
</Box>
|
||||
</MyBox>
|
||||
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,11 +14,15 @@ import { useRouter } from 'next/router';
|
||||
|
||||
import AppCard from '../WorkflowComponents/AppCard';
|
||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import RouteTab from '../RouteTab';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
const isV2Workflow = appDetail?.version === 'v2';
|
||||
@@ -27,6 +31,7 @@ const Header = () => {
|
||||
flowData2StoreDataAndCheck,
|
||||
onSaveWorkflow,
|
||||
setHistoriesDefaultData,
|
||||
setWorkflowTestData,
|
||||
historiesDefaultData,
|
||||
initData
|
||||
} = useContextSelector(WorkflowContext, (v) => v);
|
||||
@@ -62,11 +67,11 @@ const Header = () => {
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{/* {!isPc && (
|
||||
{!isPc && (
|
||||
<Flex pt={2} justifyContent={'center'}>
|
||||
<RouteTab />
|
||||
</Flex>
|
||||
)} */}
|
||||
)}
|
||||
<Flex
|
||||
mt={[2, 0]}
|
||||
py={3}
|
||||
@@ -101,18 +106,18 @@ const Header = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* {isPc && (
|
||||
{isPc && (
|
||||
<Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}>
|
||||
<RouteTab />
|
||||
</Box>
|
||||
)} */}
|
||||
)}
|
||||
<Box flex={1} />
|
||||
|
||||
{currentTab === TabEnum.appEdit && (
|
||||
<>
|
||||
{!historiesDefaultData && (
|
||||
<IconButton
|
||||
// mr={[2, 4]}
|
||||
mr={[2, 4]}
|
||||
icon={<MyIcon name={'history'} w={'18px'} />}
|
||||
aria-label={''}
|
||||
size={'sm'}
|
||||
@@ -129,7 +134,7 @@ const Header = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* <Button
|
||||
<Button
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
||||
variant={'whitePrimary'}
|
||||
@@ -141,7 +146,7 @@ const Header = () => {
|
||||
}}
|
||||
>
|
||||
{t('core.workflow.Debug')}
|
||||
</Button> */}
|
||||
</Button>
|
||||
|
||||
{!historiesDefaultData && (
|
||||
<PopoverConfirm
|
||||
@@ -176,12 +181,15 @@ const Header = () => {
|
||||
}, [
|
||||
appDetail.chatConfig,
|
||||
currentTab,
|
||||
flowData2StoreDataAndCheck,
|
||||
historiesDefaultData,
|
||||
initData,
|
||||
isPc,
|
||||
isV2Workflow,
|
||||
onclickPublish,
|
||||
saveAndBack,
|
||||
setHistoriesDefaultData,
|
||||
setWorkflowTestData,
|
||||
t
|
||||
]);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
setWorkflowData({ nodes, edges });
|
||||
}, [appForm, setWorkflowData]);
|
||||
|
||||
const { resetChatBox, ChatBox } = useChatTest({
|
||||
const { restartChat, ChatContainer } = useChatTest({
|
||||
...workflowData,
|
||||
chatConfig: appForm.chatConfig
|
||||
});
|
||||
@@ -48,13 +48,13 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
resetChatBox();
|
||||
restartChat();
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<ChatBox />
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
|
||||
@@ -15,6 +14,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { cardStyles } from '../constants';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const Edit = ({
|
||||
appForm,
|
||||
@@ -23,7 +23,7 @@ const Edit = ({
|
||||
appForm: AppSimpleEditFormType;
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||
}) => {
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { loadAllDatasets } = useDatasetStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
|
||||
@@ -257,8 +257,8 @@ const EditForm = ({
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'} flex={1}>
|
||||
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
||||
<FormLabel ml={2}>{t('core.app.Tool call')}(实验功能)</FormLabel>
|
||||
<QuestionTip ml={1} label={t('core.app.Tool call tip')} />
|
||||
<FormLabel ml={2}>{appT('Plugin dispatch')}</FormLabel>
|
||||
<QuestionTip ml={1} label={appT('Plugin dispatch tip')} />
|
||||
</Flex>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
@@ -315,7 +315,13 @@ const EditForm = ({
|
||||
<VariableEdit
|
||||
variables={appForm.chatConfig.variables}
|
||||
onChange={(e) => {
|
||||
appForm.chatConfig.variables = e;
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
chatConfig: {
|
||||
...state.chatConfig,
|
||||
variables: e
|
||||
}
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { compareWorkflow } from '@/web/core/workflow/utils';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { publishStatusStyle } from '../constants';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const Header = ({
|
||||
appForm,
|
||||
@@ -30,7 +31,7 @@ const Header = ({
|
||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const router = useRouter();
|
||||
const { appId, appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
|
||||
@@ -17,11 +17,12 @@ import { useRouter } from 'next/router';
|
||||
import AppCard from '../WorkflowComponents/AppCard';
|
||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const router = useRouter();
|
||||
|
||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import React, { forwardRef, ForwardedRef } from 'react';
|
||||
import React from 'react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import { Box, Flex, HStack, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -10,29 +10,27 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { useChatTest } from '@/pages/app/detail/components/useChatTest';
|
||||
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';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
resetChatTest: () => void;
|
||||
};
|
||||
|
||||
const ChatTest = (
|
||||
{
|
||||
isOpen,
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onClose
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
nodes?: StoreNodeItemType[];
|
||||
edges?: StoreEdgeItemType[];
|
||||
onClose: () => void;
|
||||
},
|
||||
ref: ForwardedRef<ChatTestComponentRef>
|
||||
) => {
|
||||
const ChatTest = ({
|
||||
isOpen,
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onClose
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
nodes?: StoreNodeItemType[];
|
||||
edges?: StoreEdgeItemType[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const isPlugin = appDetail.type === AppTypeEnum.plugin;
|
||||
|
||||
const { resetChatBox, ChatBox } = useChatTest({
|
||||
const { restartChat, ChatContainer, pluginRunTab, setPluginRunTab, chatRecords } = useChatTest({
|
||||
nodes,
|
||||
edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
@@ -64,40 +62,76 @@ const ChatTest = (
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
>
|
||||
<Flex py={4} px={5} whiteSpace={'nowrap'}>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'} flex={1}>
|
||||
{t('core.chat.Debug test')}
|
||||
</Box>
|
||||
<MyTooltip label={t('core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||
variant={'whiteDanger'}
|
||||
borderRadius={'md'}
|
||||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
resetChatBox();
|
||||
}}
|
||||
{isPlugin ? (
|
||||
<Flex
|
||||
alignItems={'flex-start'}
|
||||
justifyContent={'space-between'}
|
||||
px={3}
|
||||
pt={3}
|
||||
bg={'myGray.25'}
|
||||
borderBottom={'base'}
|
||||
>
|
||||
<LightRowTabs<PluginRunBoxTabEnum>
|
||||
list={[
|
||||
{ label: t('common.Input'), value: PluginRunBoxTabEnum.input },
|
||||
...(chatRecords.length > 0
|
||||
? [
|
||||
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||
]
|
||||
: [])
|
||||
]}
|
||||
value={pluginRunTab}
|
||||
onChange={setPluginRunTab}
|
||||
inlineStyles={{ px: 0.5, pb: 2 }}
|
||||
gap={5}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('common.Close')}>
|
||||
<IconButton
|
||||
ml={3}
|
||||
icon={<SmallCloseIcon fontSize={'22px'} />}
|
||||
variant={'grayBase'}
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<ChatBox />
|
||||
|
||||
<CloseIcon mt={1} onClick={onClose} />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
py={4}
|
||||
px={5}
|
||||
whiteSpace={'nowrap'}
|
||||
bg={isPlugin ? 'myGray.25' : ''}
|
||||
borderBottom={isPlugin ? '1px solid #F4F4F7' : ''}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'} flex={1}>
|
||||
{t('core.chat.Debug test')}
|
||||
</Box>
|
||||
<MyTooltip label={t('core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||
variant={'whiteDanger'}
|
||||
borderRadius={'md'}
|
||||
aria-label={'delete'}
|
||||
onClick={restartChat}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('common.Close')}>
|
||||
<IconButton
|
||||
ml={4}
|
||||
icon={<SmallCloseIcon fontSize={'22px'} />}
|
||||
variant={'grayBase'}
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(forwardRef(ChatTest));
|
||||
export default React.memo(ChatTest);
|
||||
|
||||
@@ -32,6 +32,7 @@ import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import { useWorkflowUtils } from './hooks/useUtils';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
type ModuleTemplateListProps = {
|
||||
isOpen: boolean;
|
||||
@@ -268,7 +269,7 @@ const RenderList = React.memo(function RenderList({
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -138,7 +138,7 @@ export const useDebug = () => {
|
||||
setRuntimeEdges(undefined);
|
||||
};
|
||||
|
||||
const onclickRun = (data: Record<string, any>) => {
|
||||
const onClickRun = (data: Record<string, any>) => {
|
||||
onStartNodeDebug({
|
||||
entryNodeId: runtimeNode.nodeId,
|
||||
runtimeNodes: runtimeNodes.map((node) =>
|
||||
@@ -261,7 +261,7 @@ export const useDebug = () => {
|
||||
})}
|
||||
</Box>
|
||||
<Flex py={2} justifyContent={'flex-end'} px={6}>
|
||||
<Button onClick={handleSubmit(onclickRun)}>运行</Button>
|
||||
<Button onClick={handleSubmit(onClickRun)}>运行</Button>
|
||||
</Flex>
|
||||
</MyRightDrawer>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ToolTargetHandle } from './Handle/ToolHandle';
|
||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
|
||||
import { useDebug } from '../../hooks/useDebug';
|
||||
import { ResponseBox } from '@/components/ChatBox/components/WholeResponseModal';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
|
||||
import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/utils';
|
||||
@@ -26,6 +25,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||
import { ResponseBox } from '@/components/core/chat/components/WholeResponseModal';
|
||||
|
||||
type Props = FlowNodeItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
@@ -566,11 +566,10 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
||||
w={'420px'}
|
||||
maxH={'100%'}
|
||||
minH={'300px'}
|
||||
overflowY={'auto'}
|
||||
border={'base'}
|
||||
>
|
||||
{/* Status header */}
|
||||
<Flex px={4} mb={1} py={3} alignItems={'center'} borderBottom={'base'}>
|
||||
<Flex h={'54x'} px={4} mb={1} py={3} alignItems={'center'} borderBottom={'base'}>
|
||||
<MyIcon mr={1} name={'core/workflow/debugResult'} w={'20px'} color={'primary.600'} />
|
||||
<Box fontWeight={'bold'} flex={'1'}>
|
||||
{t('core.workflow.debug.Run result')}
|
||||
@@ -606,7 +605,7 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
||||
)}
|
||||
</Flex>
|
||||
{/* Show result */}
|
||||
<Box maxH={'100%'} overflow={'auto'}>
|
||||
<Box maxH={'calc(100%-54px)'} overflow={'auto'}>
|
||||
{!debugResult.message && !response && (
|
||||
<EmptyTip text={t('core.workflow.debug.Not result')} pt={2} pb={5} />
|
||||
)}
|
||||
|
||||
@@ -44,7 +44,7 @@ import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import ChatTest, { type ChatTestComponentRef } from './Flow/ChatTest';
|
||||
import ChatTest from './Flow/ChatTest';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import { uiWorkflow2StoreWorkflow } from './utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -750,7 +750,6 @@ const WorkflowContextProvider = ({
|
||||
}, [edges, nodes]);
|
||||
|
||||
/* chat test */
|
||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
||||
const { isOpen: isOpenTest, onOpen: onOpenTest, onClose: onCloseTest } = useDisclosure();
|
||||
const [workflowTestData, setWorkflowTestData] = useState<{
|
||||
nodes: StoreNodeItemType[];
|
||||
@@ -813,7 +812,7 @@ const WorkflowContextProvider = ({
|
||||
return (
|
||||
<WorkflowContext.Provider value={value}>
|
||||
{children}
|
||||
<ChatTest ref={ChatTestRef} isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
|
||||
<ChatTest isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
|
||||
</WorkflowContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import React from 'react';
|
||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import {
|
||||
@@ -16,6 +15,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from './context';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
|
||||
export const useChatTest = ({
|
||||
nodes,
|
||||
@@ -27,22 +34,19 @@ export const useChatTest = ({
|
||||
chatConfig: AppChatConfigType;
|
||||
}) => {
|
||||
const { userInfo } = useUserStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const startChat = useMemoizedFn(
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
/* get histories */
|
||||
let historyMaxLen = getMaxHistoryLimitFromNodes(nodes);
|
||||
|
||||
const history = chatList.slice(-historyMaxLen - 2, -2);
|
||||
const historyMaxLen = getMaxHistoryLimitFromNodes(nodes);
|
||||
|
||||
// 流请求,获取数据
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
url: '/api/core/chat/chatTest',
|
||||
data: {
|
||||
history,
|
||||
prompt: chatList[chatList.length - 2].value,
|
||||
// Send histories and user messages
|
||||
messages: messages.slice(-historyMaxLen - 2),
|
||||
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||
edges: initWorkflowEdgeStatus(edges),
|
||||
variables,
|
||||
@@ -57,28 +61,57 @@ export const useChatTest = ({
|
||||
}
|
||||
);
|
||||
|
||||
const resetChatBox = useCallback(() => {
|
||||
ChatBoxRef.current?.resetHistory([]);
|
||||
ChatBoxRef.current?.resetVariables();
|
||||
}, []);
|
||||
const pluginInputs =
|
||||
nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs || [];
|
||||
const {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
clearChatRecords
|
||||
} = useChat();
|
||||
|
||||
const CustomChatBox = useMemoizedFn(() => (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appId={appDetail._id}
|
||||
appAvatar={appDetail.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
chatConfig={chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={() => {}}
|
||||
/>
|
||||
));
|
||||
const CustomChatContainer = useMemoizedFn(() =>
|
||||
appDetail.type === AppTypeEnum.plugin ? (
|
||||
<Box h={'100%'} p={3}>
|
||||
<PluginRunBox
|
||||
pluginInputs={pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={appDetail._id}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
onNewChat={clearChatRecords}
|
||||
onStartChat={startChat}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appId={appDetail._id}
|
||||
appAvatar={appDetail.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
chatConfig={chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={() => {}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
resetChatBox,
|
||||
ChatBox: CustomChatBox
|
||||
restartChat: clearChatRecords,
|
||||
ChatContainer: CustomChatContainer,
|
||||
chatRecords,
|
||||
pluginRunTab,
|
||||
setPluginRunTab
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
type FormType = {
|
||||
avatar: string;
|
||||
@@ -44,7 +45,7 @@ const CreateModal = ({ onClose, type }: { type: CreateAppType; onClose: () => vo
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const typeMap = useRef({
|
||||
[AppTypeEnum.simple]: {
|
||||
|
||||
@@ -271,6 +271,21 @@ const ListItem = () => {
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...([AppTypeEnum.plugin].includes(app.type)
|
||||
? [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'core/chat/chatLight',
|
||||
label: appT('Go to run'),
|
||||
onClick: () => {
|
||||
router.push(`/chat?appId=${app._id}`);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ import { AppUpdateParams } from '@/global/core/app/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useThrottleEffect } from 'ahooks';
|
||||
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
|
||||
|
||||
type AppListContextType = {
|
||||
@@ -27,6 +28,8 @@ type AppListContextType = {
|
||||
onUpdateApp: (id: string, data: AppUpdateParams) => Promise<any>;
|
||||
setMoveAppId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
refetchFolderDetail: () => Promise<AppDetailType | null>;
|
||||
searchKey: string;
|
||||
setSearchKey: React.Dispatch<React.SetStateAction<string>>;
|
||||
};
|
||||
|
||||
export const AppListContext = createContext<AppListContextType>({
|
||||
@@ -47,6 +50,10 @@ export const AppListContext = createContext<AppListContextType>({
|
||||
appType: 'ALL',
|
||||
refetchFolderDetail: async function (): Promise<AppDetailType | null> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
searchKey: '',
|
||||
setSearchKey: function (value: React.SetStateAction<string>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -57,6 +64,7 @@ const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
parentId?: string | null;
|
||||
type: AppTypeEnum;
|
||||
};
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
const {
|
||||
data = [],
|
||||
@@ -72,14 +80,22 @@ const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
return [AppTypeEnum.folder, type];
|
||||
})();
|
||||
|
||||
return getMyApps({ parentId, type: formatType });
|
||||
return getMyApps({ parentId, type: formatType, searchKey });
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshOnWindowFocus: true,
|
||||
refreshDeps: [parentId, type]
|
||||
}
|
||||
);
|
||||
useThrottleEffect(
|
||||
() => {
|
||||
loadMyApps();
|
||||
},
|
||||
[searchKey],
|
||||
{
|
||||
wait: 500
|
||||
}
|
||||
);
|
||||
|
||||
const { data: paths = [], runAsync: refetchPaths } = useRequest2(
|
||||
() => getAppFolderPath(parentId),
|
||||
@@ -138,7 +154,9 @@ const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
folderDetail,
|
||||
paths,
|
||||
onUpdateApp,
|
||||
setMoveAppId
|
||||
setMoveAppId,
|
||||
searchKey,
|
||||
setSearchKey
|
||||
};
|
||||
return (
|
||||
<AppListContext.Provider value={contextValue}>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement
|
||||
} from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
@@ -28,11 +36,12 @@ import {
|
||||
getCollaboratorList,
|
||||
postUpdateAppCollaborators
|
||||
} from '@/web/core/app/api/collaborator';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { CreateAppType } from './components/CreateModal';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const CreateModal = dynamic(() => import('./components/CreateModal'));
|
||||
const EditFolderModal = dynamic(
|
||||
@@ -44,7 +53,7 @@ const MyApps = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const {
|
||||
paths,
|
||||
parentId,
|
||||
@@ -55,7 +64,9 @@ const MyApps = () => {
|
||||
setMoveAppId,
|
||||
isFetchingApps,
|
||||
folderDetail,
|
||||
refetchFolderDetail
|
||||
refetchFolderDetail,
|
||||
searchKey,
|
||||
setSearchKey
|
||||
} = useContextSelector(AppListContext, (v) => v);
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
@@ -84,13 +95,26 @@ const MyApps = () => {
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
const RenderSearchInput = useMemo(
|
||||
() => (
|
||||
<InputGroup maxW={['auto', '250px']}>
|
||||
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
|
||||
<MyIcon name={'common/searchLight'} w={'1rem'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
placeholder={appT('Search app')}
|
||||
maxLength={30}
|
||||
bg={'white'}
|
||||
/>
|
||||
</InputGroup>
|
||||
),
|
||||
[searchKey, setSearchKey, appT]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
isLoading={myApps.length === 0 && isFetchingApps}
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
{paths.length > 0 && (
|
||||
<Box pt={[4, 6]} pl={3}>
|
||||
<FolderPath
|
||||
@@ -116,11 +140,7 @@ const MyApps = () => {
|
||||
overflowY={'auto'}
|
||||
overflowX={'hidden'}
|
||||
>
|
||||
<Flex
|
||||
pt={paths.length > 0 ? 4 : [4, 6]}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Flex pt={paths.length > 0 ? 3 : [4, 6]} alignItems={'center'} gap={3}>
|
||||
<LightRowTabs
|
||||
list={[
|
||||
{
|
||||
@@ -146,6 +166,7 @@ const MyApps = () => {
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
fontSize={['sm', 'md']}
|
||||
flexShrink={0}
|
||||
onChange={(e) => {
|
||||
router.push({
|
||||
query: {
|
||||
@@ -155,6 +176,9 @@ const MyApps = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
|
||||
{isPc && RenderSearchInput}
|
||||
|
||||
{userInfo?.team.permission.hasWritePer &&
|
||||
folderDetail?.type !== AppTypeEnum.httpPlugin && (
|
||||
@@ -208,8 +232,14 @@ const MyApps = () => {
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<List />
|
||||
{!isPc && <Box mt={2}>{RenderSearchInput}</Box>}
|
||||
|
||||
<MyBox flex={'1 0 0'} isLoading={myApps.length === 0 && isFetchingApps}>
|
||||
<List />
|
||||
</MyBox>
|
||||
</Box>
|
||||
|
||||
{/* Folder slider */}
|
||||
{!!folderDetail && isPc && (
|
||||
<Box pt={[4, 6]} pr={[4, 6]}>
|
||||
<FolderSlideCard
|
||||
@@ -278,7 +308,7 @@ const MyApps = () => {
|
||||
<CreateModal type={createAppType} onClose={() => setCreateAppType(undefined)} />
|
||||
)}
|
||||
{isOpenCreateHttpPlugin && <HttpEditModal onClose={onCloseCreateHttpPlugin} />}
|
||||
</MyBox>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { Flex, useTheme, Box } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -6,51 +6,47 @@ import Avatar from '@/components/Avatar';
|
||||
import ToolMenu from './ToolMenu';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const ChatHeader = ({
|
||||
chatData,
|
||||
history,
|
||||
appName,
|
||||
appAvatar,
|
||||
chatModels,
|
||||
showHistory,
|
||||
onRoute2AppDetail
|
||||
}: {
|
||||
chatData: InitChatResponse;
|
||||
history: ChatItemType[];
|
||||
appName: string;
|
||||
appAvatar: string;
|
||||
chatModels?: string[];
|
||||
showHistory?: boolean;
|
||||
onRoute2AppDetail?: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
const title = useMemo(
|
||||
() =>
|
||||
getChatTitleFromChatMessage(history[history.length - 2], appName || t('core.chat.New Chat')),
|
||||
[appName, history, t]
|
||||
);
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const chatModels = chatData.app.chatModels;
|
||||
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
|
||||
|
||||
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
|
||||
|
||||
return (
|
||||
return isPc && isPlugin ? null : (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={[3, 5]}
|
||||
h={['46px', '60px']}
|
||||
minH={['46px', '60px']}
|
||||
borderBottom={theme.borders.sm}
|
||||
color={'myGray.900'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
{isPc ? (
|
||||
<>
|
||||
<Box mr={3} color={'myGray.1000'}>
|
||||
{title}
|
||||
<Box mr={3} maxW={'160px'} className="textEllipsis" color={'myGray.1000'}>
|
||||
{chatData.title}
|
||||
</Box>
|
||||
<MyTag>
|
||||
<MyIcon name={'history'} w={'14px'} />
|
||||
@@ -85,15 +81,16 @@ const ChatHeader = ({
|
||||
)}
|
||||
|
||||
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
|
||||
<Avatar src={appAvatar} w={'16px'} />
|
||||
<Avatar src={chatData.app.avatar} w={'16px'} />
|
||||
<Box ml={1} className="textEllipsis" onClick={onRoute2AppDetail}>
|
||||
{appName}
|
||||
{chatData.app.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* control */}
|
||||
<ToolMenu history={history} />
|
||||
{!isPlugin && <ToolMenu history={history} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
type HistoryItemType = {
|
||||
id: string;
|
||||
@@ -65,7 +66,7 @@ const ChatHistorySlider = ({
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.history);
|
||||
@@ -91,8 +92,6 @@ const ChatHistorySlider = ({
|
||||
return !activeChat ? [newChat].concat(formatHistories) : formatHistories;
|
||||
}, [activeChatId, histories, t]);
|
||||
|
||||
const showApps = apps?.length > 0;
|
||||
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
title: t('core.chat.Custom History Title'),
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { PluginRunBoxProps } from '@/components/core/chat/ChatContainer/PluginRunBox/type';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import React, { useEffect } from 'react';
|
||||
import PluginRunBox from '@/components/core/chat/ChatContainer/PluginRunBox';
|
||||
import { Box, Grid, Stack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
|
||||
const CustomPluginRunBox = (props: PluginRunBoxProps) => {
|
||||
const { tab, setTab } = props;
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (isPc && tab === PluginRunBoxTabEnum.input) {
|
||||
setTab(PluginRunBoxTabEnum.output);
|
||||
}
|
||||
}, [isPc, setTab, tab]);
|
||||
|
||||
return isPc ? (
|
||||
<Grid gridTemplateColumns={'450px 1fr'} h={'100%'}>
|
||||
<Box px={3} py={4} borderRight={'base'} h={'100%'} overflowY={'auto'} w={'100%'}>
|
||||
<Box color={'myGray.900'} mb={5}>
|
||||
{t('common.Input')}
|
||||
</Box>
|
||||
<PluginRunBox {...props} tab={PluginRunBoxTabEnum.input} />
|
||||
</Box>
|
||||
<Stack px={3} py={4} h={'100%'} alignItems={'flex-start'} w={'100%'} overflow={'auto'}>
|
||||
<Box display={'inline-block'} mb={5}>
|
||||
<LightRowTabs<PluginRunBoxTabEnum>
|
||||
list={[
|
||||
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||
]}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
inlineStyles={{ px: 0.5, pt: 0 }}
|
||||
gap={5}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} overflow={'auto'} w={'100%'}>
|
||||
<PluginRunBox {...props} />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Grid>
|
||||
) : (
|
||||
<Stack py={2} px={4} h={'100%'}>
|
||||
<LightRowTabs<PluginRunBoxTabEnum>
|
||||
list={[
|
||||
{ label: t('common.Input'), value: PluginRunBoxTabEnum.input },
|
||||
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||
]}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
inlineStyles={{ px: 0.5, pt: 0 }}
|
||||
gap={5}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
<Box mt={3} flex={'1 0 0'} w={'100%'}>
|
||||
<PluginRunBox {...props} />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CustomPluginRunBox);
|
||||
@@ -25,7 +25,7 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
|
||||
return getMyApps({
|
||||
parentId,
|
||||
type: [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow]
|
||||
type: [AppTypeEnum.folder, AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin]
|
||||
}).then((res) =>
|
||||
res.map<GetResourceListItemResponse>((item) => ({
|
||||
id: item._id,
|
||||
@@ -112,6 +112,7 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
{({ onClose }) => (
|
||||
<Box minH={'200px'}>
|
||||
<SelectOneResource
|
||||
maxH={'60vh'}
|
||||
value={activeAppId}
|
||||
onSelect={(id) => {
|
||||
if (!id) return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useChatBox } from '@/components/ChatBox/hooks/useChatBox';
|
||||
import { useChatBox } from '@/components/core/chat/ChatContainer/ChatBox/hooks/useChatBox';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { Box, IconButton } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { useRouter } from 'next/router';
|
||||
import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
|
||||
@@ -9,8 +9,7 @@ import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
@@ -32,6 +31,13 @@ import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
|
||||
|
||||
type Props = { appId: string; chatId: string };
|
||||
|
||||
@@ -46,8 +52,6 @@ const Chat = ({
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
|
||||
const { setLastChatAppId } = useChatStore();
|
||||
const {
|
||||
loadHistories,
|
||||
@@ -59,84 +63,43 @@ const Chat = ({
|
||||
forbidLoadChat,
|
||||
onChangeChatId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
const {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetChatRecords
|
||||
} = useChat();
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : getNanoid();
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: prompts,
|
||||
variables,
|
||||
appId,
|
||||
chatId: completionChatId
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
});
|
||||
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(prompts)[0]);
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
}
|
||||
} else {
|
||||
// update chat
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle
|
||||
});
|
||||
}
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
|
||||
);
|
||||
const { isPc } = useSystem();
|
||||
|
||||
// get chat app info
|
||||
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
|
||||
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
|
||||
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
if (!appId || forbidLoadChat.current) return;
|
||||
|
||||
const res = await getInitChatInfo({ appId, chatId });
|
||||
setChatData(res);
|
||||
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
};
|
||||
|
||||
// reset chat box
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
// reset chat records
|
||||
resetChatRecords({
|
||||
records: history,
|
||||
variables: res.variables
|
||||
});
|
||||
|
||||
setLastChatAppId(appId);
|
||||
setChatData(result);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -157,6 +120,51 @@ const Chat = ({
|
||||
}
|
||||
);
|
||||
|
||||
const onStartChat = useCallback(
|
||||
async ({
|
||||
messages,
|
||||
responseChatItemId,
|
||||
controller,
|
||||
generatingMessage,
|
||||
variables
|
||||
}: StartChatFnProps) => {
|
||||
const completionChatId = chatId || getNanoid();
|
||||
// Just send a user prompt
|
||||
const histories = messages.slice(-1);
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: histories,
|
||||
variables,
|
||||
responseChatItemId,
|
||||
appId,
|
||||
chatId: completionChatId
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
});
|
||||
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]);
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
onChangeChatId(completionChatId, true);
|
||||
}
|
||||
}
|
||||
loadHistories();
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex h={'100%'}>
|
||||
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
|
||||
@@ -168,7 +176,7 @@ const Chat = ({
|
||||
)}
|
||||
|
||||
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']}>
|
||||
{/* pc always show history. */}
|
||||
{((children: React.ReactNode) => {
|
||||
return isPc || !appId ? (
|
||||
@@ -218,29 +226,44 @@ const Chat = ({
|
||||
>
|
||||
{/* header */}
|
||||
<ChatHeader
|
||||
appAvatar={chatData.app.avatar}
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
chatModels={chatData.app.chatModels}
|
||||
chatData={chatData}
|
||||
history={chatRecords}
|
||||
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
|
||||
showHistory
|
||||
/>
|
||||
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
showEmptyIntro
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
/>
|
||||
<Box flex={'1 0 0'} bg={'white'}>
|
||||
{isPlugin ? (
|
||||
<CustomPluginRunBox
|
||||
pluginInputs={chatData.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chatData.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
onNewChat={() => onChangeChatId(getNanoid())}
|
||||
onStartChat={onStartChat}
|
||||
/>
|
||||
) : (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
showEmptyIntro
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onStartChat={onStartChat}
|
||||
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import ChatHeader from './components/ChatHeader';
|
||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
@@ -33,6 +32,13 @@ import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
|
||||
|
||||
type Props = {
|
||||
appName: string;
|
||||
@@ -60,8 +66,7 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
authToken: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
const { isPc } = useSystemStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { isPc } = useSystem();
|
||||
const initSign = useRef(false);
|
||||
const [isEmbed, setIdEmbed] = useState(true);
|
||||
|
||||
@@ -82,17 +87,27 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
onChangeChatId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetChatRecords
|
||||
} = useChat();
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : nanoid();
|
||||
const completionChatId = chatId || getNanoid();
|
||||
const histories = messages.slice(-1);
|
||||
|
||||
//post message to report chat start
|
||||
window.top?.postMessage(
|
||||
{
|
||||
type: 'shareChatStart',
|
||||
data: {
|
||||
question: prompts[0]?.content
|
||||
question: histories[0]?.content
|
||||
}
|
||||
},
|
||||
'*'
|
||||
@@ -100,41 +115,32 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: prompts,
|
||||
messages: histories,
|
||||
variables: {
|
||||
...variables,
|
||||
...customVariables
|
||||
},
|
||||
shareId,
|
||||
chatId: completionChatId,
|
||||
appType: chatData.app.type,
|
||||
outLinkUid
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
});
|
||||
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(prompts)[0]);
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]);
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
} else {
|
||||
// update chat
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}
|
||||
loadHistories();
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
title: newTitle
|
||||
}));
|
||||
|
||||
// hook message
|
||||
@@ -142,7 +148,7 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
{
|
||||
type: 'shareChatFinish',
|
||||
data: {
|
||||
question: prompts[0]?.content,
|
||||
question: histories[0]?.content,
|
||||
answer: responseText
|
||||
}
|
||||
},
|
||||
@@ -155,12 +161,11 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
chatId,
|
||||
customVariables,
|
||||
shareId,
|
||||
chatData.app.type,
|
||||
outLinkUid,
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
loadHistories,
|
||||
onUpdateHistory,
|
||||
appId
|
||||
loadHistories
|
||||
]
|
||||
);
|
||||
|
||||
@@ -173,26 +178,18 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
setChatData(res);
|
||||
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
};
|
||||
|
||||
// reset chat box
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
setChatData(result);
|
||||
resetChatRecords({
|
||||
records: history,
|
||||
variables: res.variables
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -233,7 +230,7 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } }
|
||||
: { p: [0, 5] })}
|
||||
>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']}>
|
||||
{showHistory === '1' &&
|
||||
((children: React.ReactNode) => {
|
||||
return isPc ? (
|
||||
@@ -294,37 +291,52 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
{/* header */}
|
||||
{showHead === '1' ? (
|
||||
<ChatHeader
|
||||
appAvatar={chatData.app.avatar}
|
||||
appName={chatData.app.name}
|
||||
chatData={chatData}
|
||||
history={chatData.history}
|
||||
showHistory={showHistory === '1'}
|
||||
/>
|
||||
) : null}
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onUpdateVariable={(e) => {}}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({
|
||||
contentId,
|
||||
appId: chatData.appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
})
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
shareId={shareId}
|
||||
outLinkUid={outLinkUid}
|
||||
/>
|
||||
<Box flex={1} bg={'white'}>
|
||||
{chatData.app.type === AppTypeEnum.plugin ? (
|
||||
<CustomPluginRunBox
|
||||
pluginInputs={chatData.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chatData.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
onNewChat={() => onChangeChatId(getNanoid())}
|
||||
onStartChat={startChat}
|
||||
/>
|
||||
) : (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({
|
||||
contentId,
|
||||
appId: chatData.appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
})
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
shareId={shareId}
|
||||
outLinkUid={outLinkUid}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { delChatRecordById, getChatHistories, getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -15,8 +15,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
@@ -29,6 +29,13 @@ import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
|
||||
|
||||
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
|
||||
|
||||
@@ -47,8 +54,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
|
||||
const { toast } = useToast();
|
||||
const theme = useTheme();
|
||||
const { isPc } = useSystemStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
|
||||
|
||||
@@ -60,18 +66,28 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
isOpenSlider,
|
||||
onCloseSlider,
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
onChangeAppId
|
||||
onChangeChatId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const {
|
||||
ChatBoxRef,
|
||||
chatRecords,
|
||||
setChatRecords,
|
||||
variablesForm,
|
||||
pluginRunTab,
|
||||
setPluginRunTab,
|
||||
resetChatRecords
|
||||
} = useChat();
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : nanoid();
|
||||
const completionChatId = chatId || getNanoid();
|
||||
// Just send a user prompt
|
||||
const histories = messages.slice(-1);
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: prompts,
|
||||
messages: histories,
|
||||
variables: {
|
||||
...variables,
|
||||
...customVariables
|
||||
@@ -79,37 +95,31 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
appId,
|
||||
teamId,
|
||||
teamToken,
|
||||
chatId: completionChatId
|
||||
chatId: completionChatId,
|
||||
appType: chatData.app.type
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
});
|
||||
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(prompts)[0]);
|
||||
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]);
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
} else {
|
||||
onUpdateHistory({
|
||||
appId: chatData.appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
}
|
||||
loadHistories();
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
title: newTitle
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[
|
||||
chatData.app.type,
|
||||
chatId,
|
||||
customVariables,
|
||||
appId,
|
||||
@@ -117,9 +127,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
teamToken,
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
loadHistories,
|
||||
onUpdateHistory,
|
||||
chatData.appId
|
||||
loadHistories
|
||||
]
|
||||
);
|
||||
|
||||
@@ -129,27 +137,19 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
if (!appId || forbidLoadChat.current) return;
|
||||
|
||||
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
|
||||
setChatData(res);
|
||||
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
};
|
||||
|
||||
// have records.
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
setChatData(result);
|
||||
// reset chat records
|
||||
resetChatRecords({
|
||||
records: history,
|
||||
variables: res.variables
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -230,31 +230,48 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
flexDirection={'column'}
|
||||
>
|
||||
{/* header */}
|
||||
<ChatHeader
|
||||
appAvatar={chatData.app.avatar}
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
showHistory
|
||||
/>
|
||||
<ChatHeader chatData={chatData} history={chatData.history} showHistory />
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onUpdateVariable={(e) => {}}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({ contentId, appId: chatData.appId, chatId, teamId, teamToken })
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
teamId={teamId}
|
||||
teamToken={teamToken}
|
||||
/>
|
||||
{chatData.app.type === AppTypeEnum.plugin ? (
|
||||
<CustomPluginRunBox
|
||||
pluginInputs={chatData.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
setHistories={setChatRecords}
|
||||
appId={chatData.appId}
|
||||
tab={pluginRunTab}
|
||||
setTab={setPluginRunTab}
|
||||
onNewChat={() => onChangeChatId(getNanoid())}
|
||||
onStartChat={startChat}
|
||||
/>
|
||||
) : (
|
||||
<ChatBox
|
||||
ref={ChatBoxRef}
|
||||
chatHistories={chatRecords}
|
||||
setChatHistories={setChatRecords}
|
||||
variablesForm={variablesForm}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
chatConfig={chatData.app?.chatConfig}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({
|
||||
contentId,
|
||||
appId: chatData.appId,
|
||||
chatId,
|
||||
teamId,
|
||||
teamToken
|
||||
})
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
teamId={teamId}
|
||||
teamToken={teamToken}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -31,6 +31,7 @@ import { ImportDataSourceEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollectionPageContext } from './Context';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const FileSourceSelector = dynamic(() => import('../Import/components/FileSourceSelector'));
|
||||
|
||||
@@ -42,7 +43,7 @@ const Header = ({}: {}) => {
|
||||
|
||||
const router = useRouter();
|
||||
const { parentId = '' } = router.query as { parentId: string };
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const lastSearch = useRef('');
|
||||
const { searchText, setSearchText, total, getData, pageNum, onOpenWebsiteModal } =
|
||||
|
||||
@@ -53,13 +53,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const DataCard = () => {
|
||||
const BoxRef = useRef<HTMLDivElement>(null);
|
||||
const theme = useTheme();
|
||||
const lastSearch = useRef('');
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { collectionId = '', datasetId } = router.query as {
|
||||
collectionId: string;
|
||||
datasetId: string;
|
||||
|
||||
@@ -81,7 +81,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
<Box fontSize={'md'}>{t('core.dataset.import.Data process params')}</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box display={['block', 'flex']} mt={4} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 100px'}>{t('core.dataset.import.Training mode')}</FormLabel>
|
||||
<LeftRadio
|
||||
list={trainingModeList.map(([key, value]) => ({
|
||||
@@ -98,8 +98,8 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
display={'flex'}
|
||||
flexWrap={'wrap'}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5}>
|
||||
</Box>
|
||||
<Box display={['block', 'flex']} mt={5}>
|
||||
<FormLabel flex={'0 0 100px'}>{t('core.dataset.import.Process way')}</FormLabel>
|
||||
<LeftRadio
|
||||
list={[
|
||||
@@ -118,10 +118,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box>{t('core.dataset.import.Ideal chunk length')}</Box>
|
||||
<MyTooltip
|
||||
label={t('core.dataset.import.Ideal chunk length Tips')}
|
||||
forceShow
|
||||
>
|
||||
<MyTooltip label={t('core.dataset.import.Ideal chunk length Tips')}>
|
||||
<MyIcon
|
||||
name={'common/questionLight'}
|
||||
ml={1}
|
||||
@@ -175,10 +172,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
<Box mt={3}>
|
||||
<Box>
|
||||
{t('core.dataset.import.Custom split char')}
|
||||
<MyTooltip
|
||||
label={t('core.dataset.import.Custom split char Tips')}
|
||||
forceShow
|
||||
>
|
||||
<MyTooltip label={t('core.dataset.import.Custom split char Tips')}>
|
||||
<MyIcon
|
||||
name={'common/questionLight'}
|
||||
ml={1}
|
||||
@@ -263,16 +257,16 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
setValue('way', e);
|
||||
}}
|
||||
></LeftRadio>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'} pl={'100px'} gap={3}>
|
||||
</Box>
|
||||
<Box mt={5} pl={[0, '100px']} gap={3}>
|
||||
{feConfigs?.show_pay && (
|
||||
<MyTooltip label={priceTip}>
|
||||
<MyTag colorSchema={'gray'} py={'6px'} borderRadius={'md'} px={3}>
|
||||
<MyTag colorSchema={'gray'} py={'6px'} borderRadius={'md'} px={3} whiteSpace={'wrap'}>
|
||||
{priceTip}
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex mt={5} gap={3} justifyContent={'flex-end'}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -283,7 +277,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} w={'0'}>
|
||||
<Box flex={'1 0 0'} w={['auto', '0']}>
|
||||
<Preview showPreviewChunks={showPreviewChunks} />
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
export enum TabEnum {
|
||||
dataCard = 'dataCard',
|
||||
@@ -27,7 +28,7 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
const { datasetT } = useI18n();
|
||||
const router = useRouter();
|
||||
const query = router.query;
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { datasetDetail, vectorTrainingMap, agentTrainingMap, rebuildingCount } =
|
||||
useContextSelector(DatasetPageContext, (v) => v);
|
||||
|
||||
|
||||
@@ -21,13 +21,15 @@ import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { datasetT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const { isPc, feConfigs, vectorModelList, datasetModelList } = useSystemStore();
|
||||
const { feConfigs, vectorModelList, datasetModelList } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
|
||||
const filterNotHiddenVectorModelList = vectorModelList.filter((item) => !item.hidden);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
deleteDatasetCollaborators,
|
||||
getCollaboratorList
|
||||
} from '@/web/core/dataset/api/collaborator';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const EditFolderModal = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
|
||||
@@ -38,7 +39,7 @@ const EditFolderModal = dynamic(
|
||||
const CreateModal = dynamic(() => import('./component/CreateModal'));
|
||||
|
||||
const Dataset = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
const { isPc } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { parentId } = router.query as { parentId: string };
|
||||
|
||||
@@ -4,14 +4,9 @@ import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import {
|
||||
getAppChatConfig,
|
||||
getGuideModule,
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/workflow/utils';
|
||||
import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
type Props = {
|
||||
@@ -23,6 +18,7 @@ type Props = {
|
||||
appChatConfig?: AppChatConfigType;
|
||||
variables?: Record<string, any>;
|
||||
isUpdateUseTime: boolean;
|
||||
newTitle: string;
|
||||
source: `${ChatSourceEnum}`;
|
||||
shareId?: string;
|
||||
outLinkUid?: string;
|
||||
@@ -39,6 +35,7 @@ export async function saveChat({
|
||||
appChatConfig,
|
||||
variables,
|
||||
isUpdateUseTime,
|
||||
newTitle,
|
||||
source,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
@@ -58,7 +55,11 @@ export async function saveChat({
|
||||
...chat?.metadata,
|
||||
...metadata
|
||||
};
|
||||
const title = getChatTitleFromChatMessage(content[0]);
|
||||
const { welcomeText, variables: variableList } = getAppChatConfig({
|
||||
chatConfig: appChatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
isPublicFetch: false
|
||||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.insertMany(
|
||||
@@ -72,39 +73,33 @@ export async function saveChat({
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (chat) {
|
||||
chat.title = title;
|
||||
chat.updateTime = new Date();
|
||||
chat.metadata = metadataUpdate;
|
||||
chat.variables = variables || {};
|
||||
await chat.save({ session });
|
||||
} else {
|
||||
const { welcomeText, variables: variableList } = getAppChatConfig({
|
||||
chatConfig: appChatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
isPublicFetch: false
|
||||
});
|
||||
|
||||
await MongoChat.create(
|
||||
[
|
||||
{
|
||||
chatId,
|
||||
teamId,
|
||||
tmbId,
|
||||
appId,
|
||||
variableList,
|
||||
welcomeText,
|
||||
variables,
|
||||
title,
|
||||
source,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
metadata: metadataUpdate
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
await MongoChat.updateOne(
|
||||
{
|
||||
appId,
|
||||
chatId
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
teamId,
|
||||
tmbId,
|
||||
appId,
|
||||
chatId,
|
||||
variableList,
|
||||
welcomeText,
|
||||
variables: variables || {},
|
||||
title: newTitle,
|
||||
source,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
metadata: metadataUpdate,
|
||||
updateTime: new Date()
|
||||
}
|
||||
},
|
||||
{
|
||||
session,
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (isUpdateUseTime) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
// refer to https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web
|
||||
EventStreamContentType,
|
||||
|
||||
@@ -14,7 +14,7 @@ interface ConfigType {
|
||||
timeout?: number;
|
||||
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
||||
cancelToken?: AbortController;
|
||||
maxQuantity?: number;
|
||||
maxQuantity?: number; // The maximum number of simultaneous requests, usually used to cancel old requests
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
interface ResponseDataType {
|
||||
@@ -31,20 +31,28 @@ const maxQuantityMap: Record<
|
||||
}
|
||||
> = {};
|
||||
|
||||
function requestStart({ url, maxQuantity }: { url: string; maxQuantity?: number }) {
|
||||
if (!maxQuantity) return;
|
||||
const item = maxQuantityMap[url];
|
||||
function checkMaxQuantity({ url, maxQuantity }: { url: string; maxQuantity?: number }) {
|
||||
if (maxQuantity) {
|
||||
const item = maxQuantityMap[url];
|
||||
const controller = new AbortController();
|
||||
|
||||
if (item) {
|
||||
if (item.amount >= maxQuantity && item.sign) {
|
||||
item.sign.abort();
|
||||
delete maxQuantityMap[url];
|
||||
if (item) {
|
||||
if (item.amount >= maxQuantity) {
|
||||
item.sign?.abort?.();
|
||||
maxQuantityMap[url] = {
|
||||
amount: 1,
|
||||
sign: controller
|
||||
};
|
||||
} else {
|
||||
item.amount++;
|
||||
}
|
||||
} else {
|
||||
maxQuantityMap[url] = {
|
||||
amount: 1,
|
||||
sign: controller
|
||||
};
|
||||
}
|
||||
} else {
|
||||
maxQuantityMap[url] = {
|
||||
amount: 1,
|
||||
sign: new AbortController()
|
||||
};
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
function requestFinish({ url }: { url: string }) {
|
||||
@@ -148,7 +156,7 @@ function request(
|
||||
}
|
||||
}
|
||||
|
||||
requestStart({ url, maxQuantity });
|
||||
const controller = checkMaxQuantity({ url, maxQuantity });
|
||||
|
||||
return instance
|
||||
.request({
|
||||
@@ -157,7 +165,7 @@ function request(
|
||||
method,
|
||||
data: ['POST', 'PUT'].includes(method) ? data : null,
|
||||
params: !['POST', 'PUT'].includes(method) ? data : null,
|
||||
signal: cancelToken?.signal,
|
||||
signal: cancelToken?.signal ?? controller?.signal,
|
||||
withCredentials,
|
||||
...config // 用户自定义配置,可以覆盖前面的配置
|
||||
})
|
||||
|
||||
@@ -25,10 +25,6 @@ type State = {
|
||||
setLoginStore: (e: LoginStoreType) => void;
|
||||
loading: boolean;
|
||||
setLoading: (val: boolean) => null;
|
||||
screenWidth: number;
|
||||
setScreenWidth: (val: number) => void;
|
||||
isPc?: boolean;
|
||||
initIsPc(val: boolean): void;
|
||||
gitStar: number;
|
||||
loadGitStar: () => Promise<void>;
|
||||
|
||||
@@ -76,21 +72,7 @@ export const useSystemStore = create<State>()(
|
||||
});
|
||||
return null;
|
||||
},
|
||||
screenWidth: 600,
|
||||
setScreenWidth(val: number) {
|
||||
set((state) => {
|
||||
state.screenWidth = val;
|
||||
state.isPc = val < 900 ? false : true;
|
||||
});
|
||||
},
|
||||
isPc: undefined,
|
||||
initIsPc(val: boolean) {
|
||||
if (get().isPc !== undefined) return;
|
||||
|
||||
set((state) => {
|
||||
state.isPc = val;
|
||||
});
|
||||
},
|
||||
gitStar: 9300,
|
||||
async loadGitStar() {
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,10 @@ import type { ListAppBody } from '@/pages/api/core/app/list';
|
||||
/**
|
||||
* 获取模型列表
|
||||
*/
|
||||
export const getMyApps = (data?: ListAppBody) => POST<AppListItemType[]>('/core/app/list', data);
|
||||
export const getMyApps = (data?: ListAppBody) =>
|
||||
POST<AppListItemType[]>('/core/app/list', data, {
|
||||
maxQuantity: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建一个模型
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ChatHistoryItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ClearHistoriesProps, DelHistoryProps, UpdateHistoryProps } from '@/global/core/chat/api';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import { useChatStore } from './storeChat';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
type ChatContextValueType = {
|
||||
histories: ChatHistoryItemType[];
|
||||
@@ -66,7 +67,6 @@ const ChatContextProvider = ({
|
||||
}: ChatContextValueType & { children: ReactNode }) => {
|
||||
const router = useRouter();
|
||||
const { chatId = '' } = router.query as { chatId: string };
|
||||
const isSystemChat = router.pathname === '/chat';
|
||||
|
||||
const forbidLoadChat = useRef(false);
|
||||
|
||||
@@ -74,7 +74,7 @@ const ChatContextProvider = ({
|
||||
|
||||
const { setLastChatId } = useChatStore();
|
||||
const onChangeChatId = useCallback(
|
||||
(changeChatId = '', forbid = false) => {
|
||||
(changeChatId = getNanoid(), forbid = false) => {
|
||||
if (chatId !== changeChatId) {
|
||||
forbidLoadChat.current = forbid;
|
||||
setLastChatId(changeChatId);
|
||||
|
||||
@@ -15,6 +15,7 @@ type State = {
|
||||
initUserInfo: () => Promise<UserType>;
|
||||
setUserInfo: (user: UserType | null) => void;
|
||||
updateUserInfo: (user: UserUpdateParams) => Promise<void>;
|
||||
|
||||
teamPlanStatus: FeTeamPlanStatusType | null;
|
||||
initTeamPlanStatus: () => Promise<any>;
|
||||
};
|
||||
@@ -68,6 +69,7 @@ export const useUserStore = create<State>()(
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
// team
|
||||
teamPlanStatus: null,
|
||||
initTeamPlanStatus() {
|
||||
return getTeamPlanStatus().then((res) => {
|
||||
|
||||
Reference in New Issue
Block a user