mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-01 11:58:38 +00:00
feat: question guide (#1508)
* feat: question guide * fix * fix * fix * change interface * fix
This commit is contained in:
@@ -16,6 +16,11 @@ import { ChatBoxInputFormType, ChatBoxInputType, UserInputFileItemType } from '.
|
||||
import { textareaMinH } from './constants';
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form';
|
||||
import { useChatProviderStore } from './Provider';
|
||||
import QuestionGuide from './components/QustionGuide';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getMyQuestionGuides } from '@/web/core/app/api';
|
||||
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const MessageInput = ({
|
||||
@@ -53,6 +58,7 @@ const MessageInput = ({
|
||||
const { isPc, whisperModel } = useSystemStore();
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useAppStore();
|
||||
|
||||
const havInput = !!inputValue || fileList.length > 0;
|
||||
const hasFileUploading = fileList.some((item) => !item.url);
|
||||
@@ -205,6 +211,23 @@ const MessageInput = ({
|
||||
startSpeak(finishWhisperTranscription);
|
||||
}, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]);
|
||||
|
||||
const { data } = useQuery(
|
||||
[appId, inputValue],
|
||||
async () => {
|
||||
if (!appId) return { list: [], total: 0 };
|
||||
return getMyQuestionGuides({
|
||||
appId,
|
||||
customURL: getAppQGuideCustomURL(appDetail),
|
||||
pageSize: 5,
|
||||
current: 1,
|
||||
searchKey: inputValue
|
||||
});
|
||||
},
|
||||
{
|
||||
enabled: !!appId
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
|
||||
<Box
|
||||
@@ -214,7 +237,7 @@ const MessageInput = ({
|
||||
boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`}
|
||||
borderRadius={['none', 'md']}
|
||||
bg={'white'}
|
||||
overflow={'hidden'}
|
||||
overflow={'display'}
|
||||
{...(isPc
|
||||
? {
|
||||
border: '1px solid',
|
||||
@@ -243,6 +266,21 @@ const MessageInput = ({
|
||||
{t('core.chat.Converting to text')}
|
||||
</Flex>
|
||||
|
||||
{/* popup */}
|
||||
{havInput && (
|
||||
<QuestionGuide
|
||||
guides={data?.list || []}
|
||||
setDropdownValue={(value) => setValue('input', value)}
|
||||
bottom={'100%'}
|
||||
top={'auto'}
|
||||
left={0}
|
||||
right={0}
|
||||
mb={2}
|
||||
overflowY={'auto'}
|
||||
boxShadow={'sm'}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* file preview */}
|
||||
<Flex wrap={'wrap'} px={[2, 4]} userSelect={'none'}>
|
||||
{fileList.map((item, index) => (
|
||||
@@ -377,7 +415,12 @@ const MessageInput = ({
|
||||
// @ts-ignore
|
||||
e.key === 'a' && e.ctrlKey && e.target?.select();
|
||||
|
||||
if ((isPc || window !== parent) && e.keyCode === 13 && !e.shiftKey) {
|
||||
if (
|
||||
(isPc || window !== parent) &&
|
||||
e.keyCode === 13 &&
|
||||
!e.shiftKey &&
|
||||
!(havInput && data?.list.length && data?.list.length > 0)
|
||||
) {
|
||||
handleSend();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@@ -0,0 +1,98 @@
|
||||
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
export default function QuestionGuide({
|
||||
guides,
|
||||
setDropdownValue,
|
||||
...props
|
||||
}: {
|
||||
guides: string[];
|
||||
setDropdownValue?: (value: string) => void;
|
||||
} & BoxProps) {
|
||||
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
|
||||
const { appT } = useI18n();
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: any) => {
|
||||
if (event.keyCode === 38) {
|
||||
setHighlightedIndex((prevIndex) => Math.max(prevIndex - 1, 0));
|
||||
} else if (event.keyCode === 40) {
|
||||
setHighlightedIndex((prevIndex) => Math.min(prevIndex + 1, guides.length - 1));
|
||||
} else if (event.keyCode === 13 && guides[highlightedIndex]) {
|
||||
setDropdownValue?.(guides[highlightedIndex]);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
[highlightedIndex, setDropdownValue, guides]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [handleKeyDown]);
|
||||
|
||||
return guides.length ? (
|
||||
<Box
|
||||
bg={'white'}
|
||||
boxShadow={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
p={2}
|
||||
borderRadius={'md'}
|
||||
position={'absolute'}
|
||||
top={'100%'}
|
||||
w={'auto'}
|
||||
zIndex={99999}
|
||||
maxH={'300px'}
|
||||
overflow={'auto'}
|
||||
className="nowheel"
|
||||
{...props}
|
||||
>
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'} gap={2} mb={2} px={2}>
|
||||
<MyIcon name={'union'} />
|
||||
<Box>{appT('modules.Input Guide')}</Box>
|
||||
</Flex>
|
||||
{guides.map((item, index) => (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
as={'li'}
|
||||
key={item}
|
||||
px={4}
|
||||
py={3}
|
||||
borderRadius={'sm'}
|
||||
cursor={'pointer'}
|
||||
maxH={'300px'}
|
||||
overflow={'auto'}
|
||||
_notLast={{
|
||||
mb: 1
|
||||
}}
|
||||
{...(highlightedIndex === index
|
||||
? {
|
||||
bg: 'primary.50',
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
bg: 'myGray.50',
|
||||
color: 'myGray.600'
|
||||
})}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setDropdownValue?.(item);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
setHighlightedIndex(index);
|
||||
}}
|
||||
>
|
||||
<Box fontSize={'sm'}>{item}</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
) : null;
|
||||
}
|
@@ -58,7 +58,7 @@ import ChatProvider, { useChatProviderStore } from './Provider';
|
||||
import ChatItem from './components/ChatItem';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCreation, useUpdateEffect } from 'ahooks';
|
||||
import { useCreation } from 'ahooks';
|
||||
|
||||
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
||||
|
479
projects/app/src/components/core/app/QGuidesConfig.tsx
Normal file
479
projects/app/src/components/core/app/QGuidesConfig.tsx
Normal file
@@ -0,0 +1,479 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
ModalBody,
|
||||
useDisclosure,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea,
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
Checkbox,
|
||||
useCheckboxGroup,
|
||||
ModalFooter,
|
||||
BoxProps
|
||||
} from '@chakra-ui/react';
|
||||
import React, { ChangeEvent, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { AppQuestionGuideTextConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import MyInput from '@/components/MyInput';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { getMyQuestionGuides } from '@/web/core/app/api';
|
||||
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
const csvTemplate = `"第一列内容"
|
||||
"必填列"
|
||||
"只会将第一列内容导入,其余列会被忽略"
|
||||
"AIGC发展分为几个阶段?"
|
||||
`;
|
||||
|
||||
const QGuidesConfig = ({
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
value: AppQuestionGuideTextConfigType;
|
||||
onChange: (e: AppQuestionGuideTextConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT, commonT } = useI18n();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { isOpen: isOpenTexts, onOpen: onOpenTexts, onClose: onCloseTexts } = useDisclosure();
|
||||
const isOpenQuestionGuide = value.open;
|
||||
const { appDetail } = useAppStore();
|
||||
const [searchKey, setSearchKey] = React.useState<string>('');
|
||||
|
||||
const { data } = useQuery(
|
||||
[appDetail._id, searchKey],
|
||||
async () => {
|
||||
return getMyQuestionGuides({
|
||||
appId: appDetail._id,
|
||||
customURL: getAppQGuideCustomURL(appDetail),
|
||||
pageSize: 30,
|
||||
current: 1,
|
||||
searchKey
|
||||
});
|
||||
},
|
||||
{
|
||||
enabled: !!appDetail._id
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onChange({
|
||||
...value,
|
||||
textList: data?.list || []
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const formLabel = useMemo(() => {
|
||||
if (!isOpenQuestionGuide) {
|
||||
return t('core.app.whisper.Close');
|
||||
}
|
||||
return t('core.app.whisper.Open');
|
||||
}, [t, isOpenQuestionGuide]);
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/inputGuides'} mr={2} w={'20px'} />
|
||||
<Box fontWeight={'medium'}>{appT('modules.Question Guide')}</Box>
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={appT('modules.Config question guide')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{formLabel}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={appT('modules.Question Guide')}
|
||||
iconSrc="core/app/inputGuides"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody px={[5, 16]} pt={[4, 8]} w={'500px'}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
{appT('modules.Question Guide Switch')}
|
||||
<Switch
|
||||
isChecked={isOpenQuestionGuide}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
open: e.target.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{isOpenQuestionGuide && (
|
||||
<>
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
{appT('modules.Question Guide Texts')}
|
||||
<Box fontSize={'xs'} px={2} bg={'myGray.100'} ml={1} rounded={'full'}>
|
||||
{value.textList.length || 0}
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon boxSize={'4'} name={'common/settingLight'} />}
|
||||
onClick={() => {
|
||||
onOpenTexts();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{appT('modules.Config Texts')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<>
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
{appT('modules.Custom question guide URL')}
|
||||
<Flex
|
||||
onClick={() => window.open(getDocPath('/docs/course/custom_link'))}
|
||||
color={'primary.700'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<MyIcon name={'book'} ml={4} mr={1} />
|
||||
{commonT('common.Documents')}
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} />
|
||||
</Flex>
|
||||
<Textarea
|
||||
mt={2}
|
||||
bg={'myGray.50'}
|
||||
defaultValue={value.customURL}
|
||||
onBlur={(e) =>
|
||||
onChange({
|
||||
...value,
|
||||
customURL: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter px={[5, 16]} pb={[4, 8]}>
|
||||
<Button onClick={() => onClose()}>{commonT('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
|
||||
{isOpenTexts && (
|
||||
<TextConfigModal
|
||||
onCloseTexts={onCloseTexts}
|
||||
onOpen={onOpen}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
setSearchKey={setSearchKey}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(QGuidesConfig);
|
||||
|
||||
const TextConfigModal = ({
|
||||
onCloseTexts,
|
||||
onOpen,
|
||||
value,
|
||||
onChange,
|
||||
setSearchKey
|
||||
}: {
|
||||
onCloseTexts: () => void;
|
||||
onOpen: () => void;
|
||||
value: AppQuestionGuideTextConfigType;
|
||||
onChange: (e: AppQuestionGuideTextConfigType) => void;
|
||||
setSearchKey: (key: string) => void;
|
||||
}) => {
|
||||
const { appT, commonT } = useI18n();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [checkboxValue, setCheckboxValue] = React.useState<string[]>([]);
|
||||
const [isEditIndex, setIsEditIndex] = React.useState(-1);
|
||||
const [isAdding, setIsAdding] = React.useState(false);
|
||||
const [showIcons, setShowIcons] = React.useState<number | null>(null);
|
||||
|
||||
const { getCheckboxProps } = useCheckboxGroup();
|
||||
|
||||
const handleFileSelected = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||
const content = e.target?.result as string;
|
||||
const rows = content.split('\n');
|
||||
const texts = rows.map((row) => row.split(',')[0]);
|
||||
const newText = texts.filter((row) => value.textList.indexOf(row) === -1 && !!row);
|
||||
onChange({
|
||||
...value,
|
||||
textList: [...newText, ...value.textList]
|
||||
});
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
const allSelected = useMemo(() => {
|
||||
return value.textList.length === checkboxValue.length && value.textList.length !== 0;
|
||||
}, [value.textList, checkboxValue]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
title={appT('modules.Config Texts')}
|
||||
iconSrc="core/app/inputGuides"
|
||||
isOpen={true}
|
||||
onClose={() => {
|
||||
setCheckboxValue([]);
|
||||
onCloseTexts();
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
<ModalBody w={'500px'} px={0}>
|
||||
<Flex gap={4} px={8} alignItems={'center'} borderBottom={'1px solid #E8EBF0'} pb={4}>
|
||||
<Box flex={1}>
|
||||
<MyInput
|
||||
leftIcon={<MyIcon name={'common/searchLight'} boxSize={4} />}
|
||||
bg={'myGray.50'}
|
||||
w={'full'}
|
||||
h={9}
|
||||
placeholder={commonT('common.Search')}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".csv"
|
||||
style={{ display: 'none' }}
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileSelected}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
fileInputRef.current?.click();
|
||||
}}
|
||||
variant={'whiteBase'}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/importLight'} boxSize={4} />}
|
||||
>
|
||||
{commonT('common.Import')}
|
||||
</Button>
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
fileDownload({
|
||||
text: csvTemplate,
|
||||
type: 'text/csv;charset=utf-8',
|
||||
filename: 'questionGuide_template.csv'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<QuestionTip ml={-2} label={appT('modules.Only support CSV')} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={4}>
|
||||
<Flex justifyContent={'space-between'} px={8}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Checkbox
|
||||
sx={{
|
||||
'.chakra-checkbox__control': {
|
||||
bg: allSelected ? 'primary.50' : 'none',
|
||||
boxShadow: allSelected && '0 0 0 2px #F0F4FF',
|
||||
_hover: {
|
||||
bg: 'primary.50'
|
||||
},
|
||||
border: allSelected && '1px solid #3370FF',
|
||||
color: 'primary.600'
|
||||
},
|
||||
svg: {
|
||||
strokeWidth: '1px !important'
|
||||
}
|
||||
}}
|
||||
value={'all'}
|
||||
size={'lg'}
|
||||
mr={2}
|
||||
isChecked={allSelected}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setCheckboxValue(value.textList);
|
||||
} else {
|
||||
setCheckboxValue([]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box fontSize={'sm'} color={'myGray.600'} fontWeight={'medium'}>
|
||||
{commonT('common.Select all')}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex gap={4}>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
display={checkboxValue.length === 0 ? 'none' : 'flex'}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'delete'} boxSize={4} />}
|
||||
onClick={() => {
|
||||
setCheckboxValue([]);
|
||||
onChange({
|
||||
...value,
|
||||
textList: value.textList.filter((_) => !checkboxValue.includes(_))
|
||||
});
|
||||
}}
|
||||
>
|
||||
{commonT('common.Delete')}
|
||||
</Button>
|
||||
<Button
|
||||
display={checkboxValue.length !== 0 ? 'none' : 'flex'}
|
||||
onClick={() => {
|
||||
onChange({
|
||||
...value,
|
||||
textList: ['', ...value.textList]
|
||||
});
|
||||
setIsEditIndex(0);
|
||||
setIsAdding(true);
|
||||
}}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} boxSize={4} />}
|
||||
>
|
||||
{commonT('common.Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box h={'400px'} pb={4} overflow={'auto'} px={8}>
|
||||
{value.textList.map((text, index) => {
|
||||
const selected = checkboxValue.includes(text);
|
||||
return (
|
||||
<Flex
|
||||
key={index}
|
||||
alignItems={'center'}
|
||||
h={10}
|
||||
mt={2}
|
||||
onMouseEnter={() => setShowIcons(index)}
|
||||
onMouseLeave={() => setShowIcons(null)}
|
||||
>
|
||||
<Checkbox
|
||||
{...getCheckboxProps({ value: text })}
|
||||
sx={{
|
||||
'.chakra-checkbox__control': {
|
||||
bg: selected ? 'primary.50' : 'none',
|
||||
boxShadow: selected ? '0 0 0 2px #F0F4FF' : 'none',
|
||||
_hover: {
|
||||
bg: 'primary.50'
|
||||
},
|
||||
border: selected && '1px solid #3370FF',
|
||||
color: 'primary.600'
|
||||
},
|
||||
svg: {
|
||||
strokeWidth: '1px !important'
|
||||
}
|
||||
}}
|
||||
size={'lg'}
|
||||
mr={2}
|
||||
isChecked={selected}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setCheckboxValue([...checkboxValue, text]);
|
||||
} else {
|
||||
setCheckboxValue(checkboxValue.filter((_) => _ !== text));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{index === isEditIndex ? (
|
||||
<InputGroup alignItems={'center'} h={'full'}>
|
||||
<Input
|
||||
autoFocus
|
||||
h={'full'}
|
||||
defaultValue={text}
|
||||
onBlur={(e) => {
|
||||
setIsEditIndex(-1);
|
||||
if (
|
||||
!e.target.value ||
|
||||
(value.textList.indexOf(e.target.value) !== -1 &&
|
||||
value.textList.indexOf(e.target.value) !== index)
|
||||
) {
|
||||
isAdding &&
|
||||
onChange({
|
||||
...value,
|
||||
textList: value.textList.filter((_, i) => i !== index)
|
||||
});
|
||||
} else {
|
||||
onChange({
|
||||
...value,
|
||||
textList: value.textList?.map((v, i) =>
|
||||
i !== index ? v : e.target.value
|
||||
)
|
||||
});
|
||||
}
|
||||
setIsAdding(false);
|
||||
}}
|
||||
/>
|
||||
<InputRightElement alignItems={'center'} pr={4} display={'flex'}>
|
||||
<MyIcon name={'save'} boxSize={4} cursor={'pointer'} />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<Flex
|
||||
h={10}
|
||||
w={'full'}
|
||||
rounded={'md'}
|
||||
px={4}
|
||||
bg={'myGray.50'}
|
||||
alignItems={'center'}
|
||||
border={'1px solid #F0F1F6'}
|
||||
_hover={{ border: '1px solid #94B5FF' }}
|
||||
>
|
||||
{text}
|
||||
<Box flex={1} />
|
||||
{checkboxValue.length === 0 && (
|
||||
<Box display={showIcons === index ? 'flex' : 'none'}>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
boxSize={4}
|
||||
mr={2}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => setIsEditIndex(index)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
boxSize={4}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const temp = value.textList?.filter((_, i) => i !== index);
|
||||
onChange({
|
||||
...value,
|
||||
textList: temp
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
@@ -16,8 +16,10 @@ import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import {
|
||||
checkChatSupportSelectFileByModules,
|
||||
getAppQuestionGuidesByModules
|
||||
} from '@/web/core/chat/utils';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
resetChatTest: () => void;
|
||||
|
@@ -9,6 +9,7 @@ import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
|
||||
import QGSwitch from '@/components/core/app/QGSwitch';
|
||||
import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
import WhisperConfig from '@/components/core/app/WhisperConfig';
|
||||
import QGuidesConfig from '@/components/core/app/QGuidesConfig';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
@@ -21,11 +22,6 @@ import { WorkflowContext } from '../../context';
|
||||
import { VariableItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import VariableEdit from '@/components/core/app/VariableEdit';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
|
||||
const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const theme = useTheme();
|
||||
@@ -60,6 +56,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<ScheduledTrigger data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<QuestionInputGuide data={data} />
|
||||
</Box>
|
||||
</Box>
|
||||
</NodeCard>
|
||||
</>
|
||||
@@ -240,3 +239,26 @@ function ScheduledTrigger({ data }: { data: FlowNodeItemType }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function QuestionInputGuide({ data }: { data: FlowNodeItemType }) {
|
||||
const { inputs, nodeId } = data;
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const { questionGuideText } = splitGuideModule({ inputs } as StoreNodeItemType);
|
||||
|
||||
return (
|
||||
<QGuidesConfig
|
||||
value={questionGuideText}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
key: NodeInputKeyEnum.questionGuideText,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === NodeInputKeyEnum.questionGuideText),
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user