add external variable debug (#4204)
* add external variable debug * fix ui * plugin variables
This commit is contained in:
@@ -1,20 +1,26 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Textarea } from '@chakra-ui/react';
|
||||
import { Box, Button, Card, Flex, Switch, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
VariableInputEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} 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 { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
export const VariableInputItem = ({
|
||||
item,
|
||||
@@ -108,23 +114,118 @@ export const VariableInputItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const ExternalVariableInputItem = ({
|
||||
item,
|
||||
variablesForm,
|
||||
showTag = false
|
||||
}: {
|
||||
item: VariableItemType;
|
||||
variablesForm: UseFormReturn<any>;
|
||||
showTag?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, control } = variablesForm;
|
||||
|
||||
return (
|
||||
<Box key={item.id} mb={4} pl={1}>
|
||||
<Box
|
||||
as={'label'}
|
||||
display={'flex'}
|
||||
position={'relative'}
|
||||
mb={1}
|
||||
alignItems={'center'}
|
||||
w={'full'}
|
||||
>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box position={'absolute'} top={'-2px'} left={'-8px'} color={'red.500'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{item.description && <QuestionTip ml={1} label={item.description} />}
|
||||
{showTag && (
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
px={2}
|
||||
py={1}
|
||||
gap={1}
|
||||
ml={2}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('common:core.chat.Variable_Visiable_in_test')}
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
<Controller
|
||||
control={control}
|
||||
name={`variables.${item.key}`}
|
||||
rules={{
|
||||
required: item.required,
|
||||
validate: (value) => {
|
||||
if (item.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return value !== undefined;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
if (item.valueType === WorkflowIOValueTypeEnum.string) {
|
||||
return (
|
||||
<MyTextarea
|
||||
autoHeight
|
||||
minH={40}
|
||||
maxH={160}
|
||||
bg={'myGray.50'}
|
||||
{...register(`variables.${item.key}`, {
|
||||
required: item.required
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (item.valueType === WorkflowIOValueTypeEnum.number) {
|
||||
return <MyNumberInput step={1} bg={'myGray.50'} value={value} onChange={onChange} />;
|
||||
}
|
||||
if (item.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return <Switch isChecked={value} onChange={onChange} />;
|
||||
}
|
||||
return <JsonEditor bg={'myGray.50'} resize value={value} onChange={onChange} />;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const VariableInput = ({
|
||||
chatForm,
|
||||
chatStarted
|
||||
chatStarted,
|
||||
showExternalVariables = false
|
||||
}: {
|
||||
chatStarted: boolean;
|
||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||
chatStarted: boolean;
|
||||
showExternalVariables?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app?.avatar);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
const allVariableList = useContextSelector(ChatBoxContext, (v) => v.allVariableList);
|
||||
|
||||
const externalVariableList = useMemo(
|
||||
() =>
|
||||
allVariableList.filter((item) =>
|
||||
showExternalVariables ? item.type === VariableInputEnum.custom : false
|
||||
),
|
||||
[allVariableList, showExternalVariables]
|
||||
);
|
||||
|
||||
const { getValues, setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
useEffect(() => {
|
||||
variableList.forEach((item) => {
|
||||
allVariableList.forEach((item) => {
|
||||
const val = getValues(`variables.${item.key}`);
|
||||
if (item.defaultValue !== undefined && (val === undefined || val === null || val === '')) {
|
||||
setValue(`variables.${item.key}`, item.defaultValue);
|
||||
@@ -135,34 +236,80 @@ const VariableInput = ({
|
||||
return (
|
||||
<Box py={3}>
|
||||
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||
<Box textAlign={'left'}>
|
||||
<Card
|
||||
order={2}
|
||||
mt={2}
|
||||
w={'400px'}
|
||||
{...MessageCardStyle}
|
||||
bg={'white'}
|
||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||
>
|
||||
{variableList.map((item) => (
|
||||
<VariableInputItem key={item.id} item={item} variablesForm={variablesForm} />
|
||||
))}
|
||||
{!chatStarted && (
|
||||
<Box>
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||
size={'sm'}
|
||||
maxW={'100px'}
|
||||
onClick={handleSubmitChat(() => {
|
||||
chatForm.setValue('chatStarted', true);
|
||||
})}
|
||||
>
|
||||
{t('common:core.chat.Start Chat')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
{externalVariableList.length > 0 && (
|
||||
<Box textAlign={'left'}>
|
||||
<Card
|
||||
order={2}
|
||||
mt={2}
|
||||
w={'400px'}
|
||||
{...MessageCardStyle}
|
||||
bg={'white'}
|
||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||
>
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('common:core.chat.Visiable_in_test')}
|
||||
</Flex>
|
||||
{externalVariableList.map((item) => (
|
||||
<ExternalVariableInputItem key={item.id} item={item} variablesForm={variablesForm} />
|
||||
))}
|
||||
{variableList.length === 0 && !chatStarted && (
|
||||
<Box>
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||
size={'sm'}
|
||||
maxW={'100px'}
|
||||
onClick={handleSubmitChat(() => {
|
||||
chatForm.setValue('chatStarted', true);
|
||||
})}
|
||||
>
|
||||
{t('common:core.chat.Start Chat')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{variableList.length > 0 && (
|
||||
<Box textAlign={'left'}>
|
||||
<Card
|
||||
order={2}
|
||||
mt={2}
|
||||
w={'400px'}
|
||||
{...MessageCardStyle}
|
||||
bg={'white'}
|
||||
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
|
||||
>
|
||||
{variableList.map((item) => (
|
||||
<VariableInputItem key={item.id} item={item} variablesForm={variablesForm} />
|
||||
))}
|
||||
{!chatStarted && (
|
||||
<Box>
|
||||
<Button
|
||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||
size={'sm'}
|
||||
maxW={'100px'}
|
||||
onClick={handleSubmitChat(() => {
|
||||
chatForm.setValue('chatStarted', true);
|
||||
})}
|
||||
>
|
||||
{t('common:core.chat.Start Chat')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useEffect } from 'react';
|
||||
import { ExternalVariableInputItem, VariableInputItem } from './VariableInput';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
|
||||
const VariablePopover = ({
|
||||
showExternalVariables = false
|
||||
}: {
|
||||
showExternalVariables?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variables = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.chatBoxData?.app?.chatConfig?.variables ?? []
|
||||
);
|
||||
const variableList = variables.filter((item) => item.type !== VariableInputEnum.custom);
|
||||
const externalVariableList = variables.filter((item) =>
|
||||
showExternalVariables ? item.type === VariableInputEnum.custom : false
|
||||
);
|
||||
|
||||
const hasExternalVariable = externalVariableList.length > 0;
|
||||
const hasVariable = variableList.length > 0;
|
||||
|
||||
const { getValues, setValue } = variablesForm;
|
||||
|
||||
useEffect(() => {
|
||||
variables.forEach((item) => {
|
||||
const val = getValues(`variables.${item.key}`);
|
||||
if (item.defaultValue !== undefined && (val === undefined || val === null || val === '')) {
|
||||
setValue(`variables.${item.key}`, item.defaultValue);
|
||||
}
|
||||
});
|
||||
}, [variables]);
|
||||
|
||||
return (
|
||||
<MyPopover
|
||||
placement="bottom"
|
||||
trigger={'click'}
|
||||
closeOnBlur={true}
|
||||
Trigger={
|
||||
<Button variant={'whiteBase'} leftIcon={<MyIcon name={'edit'} w={4} />}>
|
||||
{t('common:core.module.Variable')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box p={4}>
|
||||
{hasExternalVariable && (
|
||||
<Box textAlign={'left'}>
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
mb={3}
|
||||
px={3}
|
||||
py={1.5}
|
||||
gap={1}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('common:core.chat.Visiable_in_test')}
|
||||
</Flex>
|
||||
{externalVariableList.map((item) => (
|
||||
<ExternalVariableInputItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
variablesForm={variablesForm}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{hasExternalVariable && hasVariable && <MyDivider h={'1px'} />}
|
||||
{hasVariable && (
|
||||
<Box textAlign={'left'}>
|
||||
{variableList.map((item) => (
|
||||
<VariableInputItem key={item.id} item={item} variablesForm={variablesForm} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Flex w={'full'} justifyContent={'flex-end'}>
|
||||
<Button size={'sm'} onClick={onClose}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export default VariablePopover;
|
||||
@@ -65,6 +65,7 @@ import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import TimeBox from './components/TimeBox';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||
@@ -103,7 +104,8 @@ const ChatBox = ({
|
||||
showVoiceIcon = true,
|
||||
showEmptyIntro = false,
|
||||
active = true,
|
||||
onStartChat
|
||||
onStartChat,
|
||||
chatType
|
||||
}: Props) => {
|
||||
const ScrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
@@ -129,6 +131,8 @@ const ChatBox = ({
|
||||
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
|
||||
const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef);
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const setIsVariableVisible = useContextSelector(ChatItemContext, (v) => v.setIsVariableVisible);
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
|
||||
const isChatRecordsLoaded = useContextSelector(ChatRecordContext, (v) => v.isChatRecordsLoaded);
|
||||
@@ -150,6 +154,12 @@ const ChatBox = ({
|
||||
// Workflow running, there are user input or selection
|
||||
const isInteractive = useMemo(() => checkIsInteractiveByHistories(chatRecords), [chatRecords]);
|
||||
|
||||
const externalVariableList = useMemo(() => {
|
||||
if (chatType === 'chat') {
|
||||
return allVariableList.filter((item) => item.type === VariableInputEnum.custom);
|
||||
}
|
||||
return [];
|
||||
}, [allVariableList, chatType]);
|
||||
// compute variable input is finish.
|
||||
const chatForm = useForm<ChatBoxInputFormType>({
|
||||
defaultValues: {
|
||||
@@ -162,7 +172,9 @@ const ChatBox = ({
|
||||
const chatStartedWatch = watch('chatStarted');
|
||||
const chatStarted =
|
||||
chatBoxData?.appId === appId &&
|
||||
(chatStartedWatch || chatRecords.length > 0 || variableList.length === 0);
|
||||
(chatStartedWatch ||
|
||||
chatRecords.length > 0 ||
|
||||
[...variableList, ...externalVariableList].length === 0);
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = useMemoizedFn((behavior: 'smooth' | 'auto' = 'smooth', delay = 0) => {
|
||||
@@ -891,6 +903,33 @@ const ChatBox = ({
|
||||
}
|
||||
}));
|
||||
|
||||
// Visibility check
|
||||
useEffect(() => {
|
||||
const checkVariableVisibility = () => {
|
||||
if (!ScrollContainerRef.current) return;
|
||||
const container = ScrollContainerRef.current;
|
||||
const variableInput = container.querySelector('#variable-input');
|
||||
if (!variableInput) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const elementRect = variableInput.getBoundingClientRect();
|
||||
|
||||
setIsVariableVisible(
|
||||
elementRect.bottom > containerRect.top && elementRect.top < containerRect.bottom
|
||||
);
|
||||
};
|
||||
|
||||
const container = ScrollContainerRef.current;
|
||||
if (container) {
|
||||
container.addEventListener('scroll', checkVariableVisibility);
|
||||
checkVariableVisibility();
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('scroll', checkVariableVisibility);
|
||||
};
|
||||
}
|
||||
}, [setIsVariableVisible]);
|
||||
|
||||
const RenderRecords = useMemo(() => {
|
||||
return (
|
||||
<ScrollData
|
||||
@@ -906,8 +945,14 @@ const ChatBox = ({
|
||||
{showEmpty && <Empty />}
|
||||
{!!welcomeText && <WelcomeBox welcomeText={welcomeText} />}
|
||||
{/* variable input */}
|
||||
{!!variableList?.length && (
|
||||
<VariableInput chatStarted={chatStarted} chatForm={chatForm} />
|
||||
{(!!variableList?.length || !!externalVariableList?.length) && (
|
||||
<Box id="variable-input">
|
||||
<VariableInput
|
||||
chatStarted={chatStarted}
|
||||
chatForm={chatForm}
|
||||
showExternalVariables={chatType === 'chat'}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{/* chat history */}
|
||||
<Box id={'history'}>
|
||||
@@ -1006,7 +1051,9 @@ const ChatBox = ({
|
||||
chatForm,
|
||||
chatRecords,
|
||||
chatStarted,
|
||||
chatType,
|
||||
delOneMessage,
|
||||
externalVariableList?.length,
|
||||
isChatting,
|
||||
onAddUserDislike,
|
||||
onAddUserLike,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ChatBoxInputFormType } from '../../ChatBox/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
const RenderInput = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -213,36 +214,43 @@ const RenderInput = () => {
|
||||
</Box>
|
||||
)}
|
||||
{/* Filed */}
|
||||
{formatPluginInputs.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
key={`variables.${input.key}`}
|
||||
control={control}
|
||||
name={`variables.${input.key}`}
|
||||
rules={{
|
||||
validate: (value) => {
|
||||
if (!input.required) return true;
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return value !== undefined;
|
||||
{formatPluginInputs
|
||||
.filter((input) => {
|
||||
if (outLinkAuthData && Object.keys(outLinkAuthData).length > 0) {
|
||||
return input.renderTypeList[0] !== FlowNodeInputTypeEnum.customVariable;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
key={`variables.${input.key}`}
|
||||
control={control}
|
||||
name={`variables.${input.key}`}
|
||||
rules={{
|
||||
validate: (value) => {
|
||||
if (!input.required) return true;
|
||||
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||
return value !== undefined;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
return !!value;
|
||||
}
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<RenderPluginInput
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabledInput}
|
||||
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||
input={input}
|
||||
setUploading={setUploading}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => {
|
||||
return (
|
||||
<RenderPluginInput
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabledInput}
|
||||
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||
input={input}
|
||||
setUploading={setUploading}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Run Button */}
|
||||
{onStartChat && onNewChat && (
|
||||
<Flex justifyContent={'end'} mt={8}>
|
||||
|
||||
@@ -157,9 +157,6 @@ const RenderPluginInput = ({
|
||||
const { llmModelList } = useSystemStore();
|
||||
|
||||
const render = (() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.customVariable) {
|
||||
return null;
|
||||
}
|
||||
if (inputType === FlowNodeInputTypeEnum.select && input.list) {
|
||||
return (
|
||||
<MySelect list={input.list} value={value} onChange={onChange} isDisabled={isDisabled} />
|
||||
@@ -246,6 +243,21 @@ const RenderPluginInput = ({
|
||||
<FormLabel fontWeight={'500'}>{t(input.label as any)}</FormLabel>
|
||||
</Box>
|
||||
{input.description && <QuestionTip ml={2} label={t(input.description as any)} />}
|
||||
{inputType === FlowNodeInputTypeEnum.customVariable && (
|
||||
<Flex
|
||||
color={'primary.600'}
|
||||
bg={'primary.100'}
|
||||
px={2}
|
||||
py={1}
|
||||
gap={1}
|
||||
ml={2}
|
||||
fontSize={'mini'}
|
||||
rounded={'sm'}
|
||||
>
|
||||
<MyIcon name={'common/info'} color={'primary.600'} w={4} />
|
||||
{t('common:core.chat.Variable_Visiable_in_test')}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user