Commit 82fa431a by Archer Committed by GitHub

perf: chatui (#7070)

* perf: chatui

* fix: reuse workflow reference input check
parent 778fb386
...@@ -60,8 +60,18 @@ export const getHandleId = ( ...@@ -60,8 +60,18 @@ export const getHandleId = (
return `${nodeId}-${type}-${key}`; return `${nodeId}-${type}-${key}`;
}; };
/**
* 判断输入值是否应按工作流引用解析。
* settingDatasetQuotePrompt 内部渲染 Reference 选择器,虽然 renderType 不是 reference,
* 但它的值仍是 [nodeId, outputId],运行时必须解析成知识库检索结果。
*/
export const nodeInputIsReference = (input: FlowNodeInputItemType) => { export const nodeInputIsReference = (input: FlowNodeInputItemType) => {
if (input.renderTypeList?.[input?.selectedTypeIndex || 0] === FlowNodeInputTypeEnum.reference) { const renderType = input.renderTypeList?.[input?.selectedTypeIndex || 0];
if (
renderType === FlowNodeInputTypeEnum.reference ||
renderType === FlowNodeInputTypeEnum.settingDatasetQuotePrompt
) {
return true; return true;
} }
......
...@@ -120,6 +120,16 @@ describe('nodeInputIsReference', () => { ...@@ -120,6 +120,16 @@ describe('nodeInputIsReference', () => {
}; };
expect(nodeInputIsReference(input)).toBe(true); expect(nodeInputIsReference(input)).toBe(true);
}); });
it('should treat settingDatasetQuotePrompt as reference', () => {
const input: FlowNodeInputItemType = {
key: NodeInputKeyEnum.aiChatDatasetQuote,
label: 'Dataset quote',
renderTypeList: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt],
valueType: WorkflowIOValueTypeEnum.datasetQuote
};
expect(nodeInputIsReference(input)).toBe(true);
});
}); });
describe('getGuideModule', () => { describe('getGuideModule', () => {
......
...@@ -398,7 +398,6 @@ const ChatInput = ({ ...@@ -398,7 +398,6 @@ const ChatInput = ({
</Flex> </Flex>
); );
}, [ }, [
isPc,
InputLeftComponent, InputLeftComponent,
canUploadFile, canUploadFile,
selectFileLabel, selectFileLabel,
...@@ -410,13 +409,10 @@ const ChatInput = ({ ...@@ -410,13 +409,10 @@ const ChatInput = ({
isStopping, isStopping,
isChatting, isChatting,
canSendMessage, canSendMessage,
disableSend,
onOpenSelectFile, onOpenSelectFile,
onSelectFile, onSelectFile,
handleSend, handleSend,
handleStop, handleStop
onStopChat,
onStopSettled
]); ]);
const activeStyles: FlexProps = { const activeStyles: FlexProps = {
...@@ -430,7 +426,7 @@ const ChatInput = ({ ...@@ -430,7 +426,7 @@ const ChatInput = ({
w={'100%'} w={'100%'}
maxW={['100%', '780px']} maxW={['100%', '780px']}
mx={'auto'} mx={'auto'}
pb={['calc(12px + env(safe-area-inset-bottom))', 0]} pb={0}
onDragOver={(e) => e.preventDefault()} onDragOver={(e) => e.preventDefault()}
onDrop={(e) => { onDrop={(e) => {
e.preventDefault(); e.preventDefault();
...@@ -462,7 +458,7 @@ const ChatInput = ({ ...@@ -462,7 +458,7 @@ const ChatInput = ({
h={isDefaultInputHeight ? ChatInputDefaultHeight : undefined} h={isDefaultInputHeight ? ChatInputDefaultHeight : undefined}
minH={mobilePreSpeak ? '48px' : ChatInputDefaultHeight} minH={mobilePreSpeak ? '48px' : ChatInputDefaultHeight}
p={mobilePreSpeak ? [0, 4] : 4} p={mobilePreSpeak ? [0, 4] : 4}
mb={4} mb={0}
position={'relative'} position={'relative'}
borderRadius={['xl', 'xxl']} borderRadius={['xl', 'xxl']}
bg={'white'} bg={'white'}
...@@ -527,7 +523,7 @@ const ChatInput = ({ ...@@ -527,7 +523,7 @@ const ChatInput = ({
{!mobilePreSpeak && <Box>{RenderButtonGroup}</Box>} {!mobilePreSpeak && <Box>{RenderButtonGroup}</Box>}
</Flex> </Flex>
<ComplianceTip type={'chat'} pt={0} pb={0} /> <ComplianceTip type={'chat'} pt={4} pb={0} />
</Box> </Box>
); );
}; };
......
...@@ -268,11 +268,15 @@ const EditForm = ({ ...@@ -268,11 +268,15 @@ const EditForm = ({
<QuestionTip ml={1} label={t('app:use_computer_desc')} /> <QuestionTip ml={1} label={t('app:use_computer_desc')} />
</Flex> </Flex>
{showSandbox && enableSandbox && (
<Box mr={2}> <Box mr={2}>
{showSandbox && enableSandbox ? (
<SandboxTipTag /> <SandboxTipTag />
</Box> ) : (
<MyTag>
{t(showSandbox ? 'app:sandbox_free_not_support' : 'app:sandbox_not_support_tip')}
</MyTag>
)} )}
</Box>
<Switch <Switch
isChecked={appForm.aiSettings.useAgentSandbox ?? false} isChecked={appForm.aiSettings.useAgentSandbox ?? false}
onChange={(e) => onChangeAgentSandbox(e.target.checked)} onChange={(e) => onChangeAgentSandbox(e.target.checked)}
......
import { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } from 'react'; import { useCallback, useEffect, type Dispatch, type SetStateAction } from 'react';
import { useDisclosure } from '@chakra-ui/react'; import { useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { import type {
...@@ -13,8 +13,8 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; ...@@ -13,8 +13,8 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
/** /**
* 管理 ChatAgent 表单中的 Skill 选择与 sandbox 开关联动。 * 管理 ChatAgent 表单中的 Skill 选择与 sandbox 开关联动。
* *
* Skill 运行依赖 Agent sandbox:选择 Skill 时会自动打开 sandbox;已有 Skill 时不允许手动关闭 * Skill 运行依赖 Agent sandbox:选择 Skill 时会自动打开 sandbox;系统未配置或套餐不可用时
* sandbox;系统未配置或套餐不可用时统一给出提示,避免表单保存出无法运行的组合状态。 * 不允许开启 sandbox,但保留关闭入口,避免历史配置无法自助修复。
*/ */
export const useAgentSkillSelect = ({ export const useAgentSkillSelect = ({
appForm, appForm,
...@@ -36,7 +36,6 @@ export const useAgentSkillSelect = ({ ...@@ -36,7 +36,6 @@ export const useAgentSkillSelect = ({
onOpen: onOpenRecharge, onOpen: onOpenRecharge,
onClose: onCloseRecharge onClose: onCloseRecharge
} = useDisclosure(); } = useDisclosure();
const hasShownSandboxUnavailableWarningRef = useRef(false);
const { const {
isOpen: isOpenSkillSelect, isOpen: isOpenSkillSelect,
onOpen: onOpenSkillSelect, onOpen: onOpenSkillSelect,
...@@ -124,18 +123,14 @@ export const useAgentSkillSelect = ({ ...@@ -124,18 +123,14 @@ export const useAgentSkillSelect = ({
return; return;
} }
if (!enableSandbox) { if (!enableSandbox) {
openConfirm({ toast({
title: t('skill:sandbox_plan_not_supported_title'), status: 'warning',
customContent: t('skill:sandbox_plan_not_supported_content'), title: t('app:sandbox_free_not_support')
onConfirm: isTeamAdmin ? onOpenRecharge : undefined, });
confirmText: isTeamAdmin ? t('skill:sandbox_upgrade_action') : t('common:Close'),
cancelText: t('common:Close'),
showCancel: isTeamAdmin
})();
return; return;
} }
} }
if (!checked && hasSelectedAgentSkills) { if (!checked && enableSandbox && hasSelectedAgentSkills) {
toast({ toast({
status: 'warning', status: 'warning',
title: t('skill:sandbox_disable_blocked_toast') title: t('skill:sandbox_disable_blocked_toast')
...@@ -150,39 +145,13 @@ export const useAgentSkillSelect = ({ ...@@ -150,39 +145,13 @@ export const useAgentSkillSelect = ({
} }
})); }));
}, },
[ [enableSandbox, hasSelectedAgentSkills, setAppForm, showSandbox, t, toast]
enableSandbox,
hasSelectedAgentSkills,
setAppForm,
showSandbox,
t,
toast,
openConfirm,
isTeamAdmin,
onOpenRecharge
]
); );
// 按系统/套餐能力同步修复历史 Skill 与虚拟机开关状态。 // 套餐/系统支持时,Skill 仍自动保持 sandbox 开启;不支持时保留历史值,交给用户手动关闭。
useEffect(() => { useEffect(() => {
const sandboxAvailable = showSandbox && enableSandbox; const sandboxAvailable = showSandbox && enableSandbox;
if (!sandboxAvailable && appForm.aiSettings.useAgentSandbox) {
setAppForm((state) => ({
...state,
aiSettings: {
...state.aiSettings,
useAgentSandbox: false
}
}));
if (hasSelectedAgentSkills && !hasShownSandboxUnavailableWarningRef.current) {
hasShownSandboxUnavailableWarningRef.current = true;
openSkillSelect();
}
return;
}
if (sandboxAvailable && hasSelectedAgentSkills && !appForm.aiSettings.useAgentSandbox) { if (sandboxAvailable && hasSelectedAgentSkills && !appForm.aiSettings.useAgentSandbox) {
setAppForm((state) => ({ setAppForm((state) => ({
...state, ...state,
...@@ -196,7 +165,6 @@ export const useAgentSkillSelect = ({ ...@@ -196,7 +165,6 @@ export const useAgentSkillSelect = ({
appForm.aiSettings.useAgentSandbox, appForm.aiSettings.useAgentSandbox,
enableSandbox, enableSandbox,
hasSelectedAgentSkills, hasSelectedAgentSkills,
openSkillSelect,
setAppForm, setAppForm,
showSandbox showSandbox
]); ]);
......
...@@ -278,7 +278,7 @@ const Header = ({ ...@@ -278,7 +278,7 @@ const Header = ({
} }
if (!enableSandbox) { if (!enableSandbox) {
toast({ toast({
title: t('skill:sandbox_plan_not_supported_title'), title: t('app:sandbox_free_not_support'),
status: 'warning' status: 'warning'
}); });
return false; return false;
......
...@@ -75,6 +75,7 @@ const EditForm = ({ ...@@ -75,6 +75,7 @@ const EditForm = ({
const { appDetail } = useContextSelector(AppContext, (v) => v); const { appDetail } = useContextSelector(AppContext, (v) => v);
const selectDatasets = useMemo(() => appForm?.dataset?.datasets, [appForm]); const selectDatasets = useMemo(() => appForm?.dataset?.datasets, [appForm]);
const [, startTst] = useTransition(); const [, startTst] = useTransition();
const isAgentSandboxEnabled = !!appForm.aiSettings.useAgentSandbox;
const { const {
isOpen: isOpenDatasetSelect, isOpen: isOpenDatasetSelect,
...@@ -259,31 +260,28 @@ const EditForm = ({ ...@@ -259,31 +260,28 @@ const EditForm = ({
<FormLabel ml={2}>{t('app:use_agent_sandbox')}</FormLabel> <FormLabel ml={2}>{t('app:use_agent_sandbox')}</FormLabel>
<QuestionTip ml={1} label={t('app:use_computer_desc')} /> <QuestionTip ml={1} label={t('app:use_computer_desc')} />
</Flex> </Flex>
{showSandbox ? (
enableSandbox ? (
<>
<Box mr={2}> <Box mr={2}>
{showSandbox && enableSandbox ? (
<SandboxTipTag /> <SandboxTipTag />
) : (
<SandboxNotSupportTip type={showSandbox ? 'freeDisable' : 'systemDisable'} />
)}
</Box> </Box>
<Switch <Switch
isChecked={appForm.aiSettings.useAgentSandbox ?? false} isChecked={isAgentSandboxEnabled}
onChange={(e) => { onChange={(e) => {
const checked = e.target.checked;
if (checked && (!showSandbox || !enableSandbox)) return;
setAppForm((state) => ({ setAppForm((state) => ({
...state, ...state,
aiSettings: { aiSettings: {
...state.aiSettings, ...state.aiSettings,
useAgentSandbox: e.target.checked useAgentSandbox: checked
} }
})); }));
}} }}
/> />
</>
) : (
<SandboxNotSupportTip type="freeDisable" />
)
) : (
<SandboxNotSupportTip type="systemDisable" />
)}
</Flex> </Flex>
</Box> </Box>
......
...@@ -432,18 +432,14 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => { ...@@ -432,18 +432,14 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return; return;
} }
if (!enableSandbox) { if (!enableSandbox) {
openConfirm({ toast({
title: t('skill:sandbox_plan_not_supported_title'), status: 'warning',
customContent: t('skill:sandbox_plan_not_supported_content'), title: t('app:sandbox_free_not_support')
onConfirm: isTeamAdmin ? onOpenRecharge : undefined, });
confirmText: isTeamAdmin ? t('skill:sandbox_upgrade_action') : t('common:Close'),
cancelText: t('common:Close'),
showCancel: isTeamAdmin
})();
return; return;
} }
} }
if (!checked && selectedAgentSkills.length > 0) { if (!checked && enableSandbox && selectedAgentSkills.length > 0) {
toast({ toast({
status: 'warning', status: 'warning',
title: t('skill:sandbox_disable_blocked_toast') title: t('skill:sandbox_disable_blocked_toast')
...@@ -469,10 +465,7 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => { ...@@ -469,10 +465,7 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
selectedAgentSkills.length, selectedAgentSkills.length,
showSandbox, showSandbox,
t, t,
toast, toast
openConfirm,
isTeamAdmin,
onOpenRecharge
] ]
); );
const skillsRenderType = useMemo( const skillsRenderType = useMemo(
...@@ -554,7 +547,13 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => { ...@@ -554,7 +547,13 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
> >
<InputLabel nodeId={nodeId} input={sandboxInput} /> <InputLabel nodeId={nodeId} input={sandboxInput} />
<Flex alignItems={'center'} gap={1} className={'nodrag'}> <Flex alignItems={'center'} gap={1} className={'nodrag'}>
{showSandbox && enableSandbox && <SandboxTipTag />} {showSandbox && enableSandbox ? (
<SandboxTipTag />
) : (
<MyTag>
{t(showSandbox ? 'app:sandbox_free_not_support' : 'app:sandbox_not_support_tip')}
</MyTag>
)}
<Switch <Switch
isChecked={!!sandboxInput.value} isChecked={!!sandboxInput.value}
onChange={(e) => onChangeAgentSandbox(e.target.checked)} onChange={(e) => onChangeAgentSandbox(e.target.checked)}
......
import React from 'react'; import React from 'react';
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex, Switch } from '@chakra-ui/react';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import InputLabel from './Label'; import InputLabel from './Label';
...@@ -13,6 +13,8 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; ...@@ -13,6 +13,8 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import SandboxTipTag from '@/pageComponents/app/detail/components/SandboxTipTag'; import SandboxTipTag from '@/pageComponents/app/detail/components/SandboxTipTag';
import SandboxNotSupportTip from '@/pageComponents/app/detail/components/SandboxNotSupportTip'; import SandboxNotSupportTip from '@/pageComponents/app/detail/components/SandboxNotSupportTip';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { useTranslation } from 'next-i18next';
const RenderList: Record< const RenderList: Record<
FlowNodeInputTypeEnum, FlowNodeInputTypeEnum,
...@@ -105,6 +107,7 @@ type Props = { ...@@ -105,6 +107,7 @@ type Props = {
mb?: number; mb?: number;
}; };
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => { const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
const { t } = useTranslation();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const { teamPlanStatus } = useUserStore(); const { teamPlanStatus } = useUserStore();
const enableSandbox = !teamPlanStatus?.standard || !!teamPlanStatus?.standard?.enableSandbox; const enableSandbox = !teamPlanStatus?.standard || !!teamPlanStatus?.standard?.enableSandbox;
...@@ -144,14 +147,37 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) ...@@ -144,14 +147,37 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
if (!RenderItem) return null; if (!RenderItem) return null;
const renderInput =
input.key === NodeInputKeyEnum.useAgentSandbox
? {
...input,
customRender: ({
value,
onChange
}: {
value: boolean;
onChange?: (value: boolean) => void;
}) => (
<Switch
isChecked={!!value}
onChange={(e) => {
const checked = e.target.checked;
if (checked && (!showSandbox || !enableSandbox)) return;
onChange?.(checked);
}}
/>
)
}
: input;
return { return {
Component: ( Component: (
<RenderItem.Component inputs={filterProInputs} item={input} nodeId={nodeId} /> <RenderItem.Component inputs={filterProInputs} item={renderInput} nodeId={nodeId} />
), ),
LableRightComponent: RenderItem.LableRightComponent ? ( LableRightComponent: RenderItem.LableRightComponent ? (
<RenderItem.LableRightComponent <RenderItem.LableRightComponent
inputs={filterProInputs} inputs={filterProInputs}
item={input} item={renderInput}
nodeId={nodeId} nodeId={nodeId}
/> />
) : undefined ) : undefined
...@@ -179,17 +205,21 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) ...@@ -179,17 +205,21 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
{/* tmp */} {/* tmp */}
{input.key === NodeInputKeyEnum.useAgentSandbox ? ( {input.key === NodeInputKeyEnum.useAgentSandbox ? (
showSandbox ? ( RenderComponent ? (
enableSandbox ? (
<Flex alignItems={'center'} gap={1}> <Flex alignItems={'center'} gap={1}>
{showSandbox && enableSandbox ? (
<SandboxTipTag /> <SandboxTipTag />
{RenderComponent!.Component}
</Flex>
) : ( ) : (
<SandboxNotSupportTip type="freeDisable" /> <MyTag>
) {t(
showSandbox ? 'app:sandbox_free_not_support' : 'app:sandbox_not_support_tip'
)}
</MyTag>
)}
{RenderComponent.Component}
</Flex>
) : ( ) : (
<SandboxNotSupportTip type="systemDisable" /> <SandboxNotSupportTip type={showSandbox ? 'freeDisable' : 'systemDisable'} />
) )
) : ( ) : (
<> <>
......
...@@ -216,7 +216,7 @@ export const WorkflowUtilsProvider = ({ children }: { children: ReactNode }) => ...@@ -216,7 +216,7 @@ export const WorkflowUtilsProvider = ({ children }: { children: ReactNode }) =>
status: 'warning', status: 'warning',
title: !showSandbox title: !showSandbox
? t('skill:sandbox_system_not_configured_toast') ? t('skill:sandbox_system_not_configured_toast')
: t('skill:sandbox_plan_not_supported_title') : t('app:sandbox_free_not_support')
}); });
} }
return; return;
......
...@@ -198,9 +198,7 @@ const AppChatWindow = () => { ...@@ -198,9 +198,7 @@ const AppChatWindow = () => {
> >
<IconButton <IconButton
aria-label="Open history" aria-label="Open history"
icon={ icon={<MyIcon name="core/chat/sidebar/menu" w="20px" h="20px" color="currentColor" />}
<MyIcon name="core/chat/sidebar/menu" w="20px" h="20px" color="currentColor" />
}
variant="unstyled" variant="unstyled"
{...mobileChatHeaderIconButtonStyle} {...mobileChatHeaderIconButtonStyle}
onClick={onOpenSlider} onClick={onOpenSlider}
...@@ -208,7 +206,13 @@ const AppChatWindow = () => { ...@@ -208,7 +206,13 @@ const AppChatWindow = () => {
<Flex alignItems="center" minW={0} flex="1" justifyContent="center" px={3}> <Flex alignItems="center" minW={0} flex="1" justifyContent="center" px={3}>
<Avatar src={chatBoxData.app.avatar} w="20px" borderRadius="6px" /> <Avatar src={chatBoxData.app.avatar} w="20px" borderRadius="6px" />
<Box ml={2} fontSize="16px" fontWeight={500} color="myGray.900" className="textEllipsis"> <Box
ml={2}
fontSize="16px"
fontWeight={500}
color="myGray.900"
className="textEllipsis"
>
{chatBoxData.app.name} {chatBoxData.app.name}
</Box> </Box>
</Flex> </Flex>
...@@ -219,7 +223,7 @@ const AppChatWindow = () => { ...@@ -219,7 +223,7 @@ const AppChatWindow = () => {
</Flex> </Flex>
)} )}
<Box flex={'1 0 0'} bg={'white'}> <Box flex={'1 0 0'} bg={'white'} pb={4}>
{isPlugin ? ( {isPlugin ? (
<CustomPluginRunBox <CustomPluginRunBox
appId={appId} appId={appId}
......
...@@ -40,6 +40,7 @@ const ChatSliderList = () => { ...@@ -40,6 +40,7 @@ const ChatSliderList = () => {
updateTime: Date; updateTime: Date;
chatGenerateStatus?: ChatGenerateStatusEnum; chatGenerateStatus?: ChatGenerateStatusEnum;
hasBeenRead?: boolean; hasBeenRead?: boolean;
isTemporary?: boolean;
}[] = scopedHistories.map((item) => { }[] = scopedHistories.map((item) => {
const isActiveChat = item.chatId === activeChatId && chatBoxData.chatId === item.chatId; const isActiveChat = item.chatId === activeChatId && chatBoxData.chatId === item.chatId;
...@@ -64,6 +65,7 @@ const ChatSliderList = () => { ...@@ -64,6 +65,7 @@ const ChatSliderList = () => {
updateTime: Date; updateTime: Date;
chatGenerateStatus?: ChatGenerateStatusEnum; chatGenerateStatus?: ChatGenerateStatusEnum;
hasBeenRead?: boolean; hasBeenRead?: boolean;
isTemporary?: boolean;
} = { } = {
id: activeChatId, id: activeChatId,
title: getDisplayHistoryTitle({ title: getDisplayHistoryTitle({
...@@ -71,6 +73,7 @@ const ChatSliderList = () => { ...@@ -71,6 +73,7 @@ const ChatSliderList = () => {
fallbackTitle: t('common:core.chat.New Chat') fallbackTitle: t('common:core.chat.New Chat')
}), }),
updateTime: new Date(), updateTime: new Date(),
isTemporary: true,
chatGenerateStatus: chatGenerateStatus:
chatBoxData.chatId === activeChatId ? chatBoxData.chatGenerateStatus : undefined, chatBoxData.chatId === activeChatId ? chatBoxData.chatGenerateStatus : undefined,
hasBeenRead: chatBoxData.chatId === activeChatId ? chatBoxData.hasBeenRead : undefined hasBeenRead: chatBoxData.chatId === activeChatId ? chatBoxData.hasBeenRead : undefined
...@@ -112,7 +115,7 @@ const ChatSliderList = () => { ...@@ -112,7 +115,7 @@ const ChatSliderList = () => {
overflowY={'auto'} overflowY={'auto'}
overflowX={'hidden'} overflowX={'hidden'}
sx={{ sx={{
'& > div:last-of-type': { '& > div > div:last-of-type:not(.chatHistoryItem)': {
color: 'var(--chakra-colors-myGray-400)' color: 'var(--chakra-colors-myGray-400)'
} }
}} }}
...@@ -120,6 +123,7 @@ const ChatSliderList = () => { ...@@ -120,6 +123,7 @@ const ChatSliderList = () => {
{concatHistory.map((item, i) => ( {concatHistory.map((item, i) => (
<Flex <Flex
position={'relative'} position={'relative'}
className="chatHistoryItem"
key={item.id} key={item.id}
alignItems={'center'} alignItems={'center'}
p="8px" p="8px"
...@@ -172,7 +176,7 @@ const ChatSliderList = () => { ...@@ -172,7 +176,7 @@ const ChatSliderList = () => {
bg={'primary.500'} bg={'primary.500'}
flexShrink={0} flexShrink={0}
/> />
) : ( ) : item.isTemporary ? null : (
<Box <Box
className="time" className="time"
display={'block'} display={'block'}
......
...@@ -72,7 +72,7 @@ const Chat = () => { ...@@ -72,7 +72,7 @@ const Chat = () => {
flex="1 0 0" flex="1 0 0"
w={0} w={0}
position="relative" position="relative"
pr={datasetCiteData ? 0 : undefined} pr={datasetCiteData ? 0 : [0, '16px']}
insertProps={{ insertProps={{
...rightWindowStyle, ...rightWindowStyle,
...(datasetCiteData ...(datasetCiteData
......
...@@ -21,7 +21,8 @@ import { ...@@ -21,7 +21,8 @@ import {
getAppChatConfig, getAppChatConfig,
getHandleId, getHandleId,
isValidReferenceValue, isValidReferenceValue,
isValidReferenceValueFormat isValidReferenceValueFormat,
nodeInputIsReference
} from '@fastgpt/global/core/workflow/utils'; } from '@fastgpt/global/core/workflow/utils';
import { type TFunction } from 'next-i18next'; import { type TFunction } from 'next-i18next';
import { import {
...@@ -525,7 +526,7 @@ export const getNodeAllSource = ({ ...@@ -525,7 +526,7 @@ export const getNodeAllSource = ({
const parentNode = getNodeById(parentId); const parentNode = getNodeById(parentId);
if (parentNode) { if (parentNode) {
parentNode.inputs.forEach((input) => { parentNode.inputs.forEach((input) => {
if (!input.renderTypeList?.includes(FlowNodeInputTypeEnum.reference)) return; if (!nodeInputIsReference(input)) return;
const val = input.value as ReferenceItemValueType | undefined; const val = input.value as ReferenceItemValueType | undefined;
if (!Array.isArray(val) || val.length < 2) return; if (!Array.isArray(val) || val.length < 2) return;
const [refNodeId] = val; const [refNodeId] = val;
...@@ -748,8 +749,7 @@ export const checkWorkflowNodeAndConnection = ({ ...@@ -748,8 +749,7 @@ export const checkWorkflowNodeAndConnection = ({
if (Array.isArray(input.value) && input.value.length === 0) return true; if (Array.isArray(input.value) && input.value.length === 0) return true;
} }
// check reference invalid // check reference invalid
const renderType = input.renderTypeList[input.selectedTypeIndex || 0]; if (nodeInputIsReference(input)) {
if (renderType === FlowNodeInputTypeEnum.reference) {
// 无效引用时,返回 true // 无效引用时,返回 true
const checkValueValid = (value: ReferenceItemValueType) => { const checkValueValid = (value: ReferenceItemValueType) => {
const nodeId = value?.[0]; const nodeId = value?.[0];
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
appWorkflow2AgentForm appWorkflow2AgentForm
} from '@/pageComponents/app/detail/Edit/ChatAgent/utils'; } from '@/pageComponents/app/detail/Edit/ChatAgent/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getDefaultAppForm } from '@fastgpt/global/core/app/utils'; import { getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type'; import type { AppFormEditFormType } from '@fastgpt/global/core/app/formEdit/type';
...@@ -94,6 +94,20 @@ describe('form2AppWorkflow', () => { ...@@ -94,6 +94,20 @@ describe('form2AppWorkflow', () => {
expect(result.nodes).toHaveLength(4); expect(result.nodes).toHaveLength(4);
expect(result.edges).toHaveLength(2); expect(result.edges).toHaveLength(2);
const datasetNode = result.nodes.find(
(node) => node.flowNodeType === FlowNodeTypeEnum.datasetSearchNode
);
const aiNode = result.nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.chatNode);
expect(
aiNode?.inputs.find((input) => input.key === NodeInputKeyEnum.aiChatDatasetQuote)?.value
).toEqual([datasetNode?.nodeId, NodeOutputKeyEnum.datasetQuoteQA]);
expect(
result.edges.some(
(edge) => edge.source === datasetNode?.nodeId && edge.target === aiNode?.nodeId
)
).toBe(true);
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment