Add question guide config (#3403)

* feat:Prompt task (#3337)

* feat:猜你想问自定义功能

* 修改用户输入框部分,去除冗余代码

* 删除不必要的属性

* 删除多余内容

* 修正了格式问题,并实现获取调试和app最新参数

* 修正了几行代码

* feat:Prompt task (#3337)

* feat:猜你想问自定义功能

* 修改用户输入框部分,去除冗余代码

* 删除不必要的属性

* 删除多余内容

* 修正了格式问题,并实现获取调试和app最新参数

* 修正了几行代码

* perf: question gudide code

* fix: i18n

* hunyuan logo

* fix: cq templates

* perf: create question guide code

* udpate svg

---------

Co-authored-by: Jiangween <145003935+Jiangween@users.noreply.github.com>
This commit is contained in:
Archer
2024-12-16 13:49:31 +08:00
committed by GitHub
parent 76d20b2b76
commit bfac393ab1
50 changed files with 775 additions and 397 deletions

View File

@@ -0,0 +1,194 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { Box, Button, Flex, ModalBody, useDisclosure, Switch, BoxProps } from '@chakra-ui/react';
import React from 'react';
import { useTranslation } from 'next-i18next';
import type { AppQGConfigType } from '@fastgpt/global/core/app/type.d';
import MyModal from '@fastgpt/web/components/common/MyModal';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { defaultQGConfig } from '@fastgpt/global/core/app/constants';
import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import AIModelSelector from '@/components/Select/AIModelSelector';
import CustomPromptEditor from '@fastgpt/web/components/common/Textarea/CustomPromptEditor';
import {
PROMPT_QUESTION_GUIDE,
PROMPT_QUESTION_GUIDE_FOOTER
} from '@fastgpt/global/core/ai/prompt/agent';
// question generator config
const QGConfig = ({
value = defaultQGConfig,
onChange
}: {
value?: AppQGConfigType;
onChange: (e: AppQGConfigType) => void;
}) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const isOpenQG = value.open;
const formLabel = isOpenQG
? t('common:core.app.whisper.Open')
: t('common:core.app.whisper.Close');
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/chat/QGFill'} mr={2} w={'20px'} />
<FormLabel>{t('common:core.app.Question Guide')}</FormLabel>
<ChatFunctionTip type={'nextQuestion'} />
<Box flex={1} />
<MyTooltip label={t('app:config_question_guide')}>
<Button
variant={'transparentBase'}
size={'sm'}
mr={'-5px'}
color={'myGray.600'}
onClick={onOpen}
>
{formLabel}
</Button>
</MyTooltip>
{isOpen && <QGConfigModal value={value} onChange={onChange} onClose={onClose} />}
</Flex>
);
};
export default QGConfig;
const LabelStyles: BoxProps = {
display: 'flex',
alignItems: 'center',
fontSize: 'sm',
color: 'myGray.900',
width: ['6rem', '8rem']
};
const QGConfigModal = ({
value,
onClose,
onChange
}: {
value: AppQGConfigType;
onChange: (e: AppQGConfigType) => void;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { llmModelList } = useSystemStore();
const customPrompt = value.customPrompt;
const isOpenQG = value.open;
const model = value?.model || llmModelList?.[0]?.model;
const {
isOpen: isOpenCustomPrompt,
onOpen: onOpenCustomPrompt,
onClose: onCloseCustomPrompt
} = useDisclosure();
return (
<>
<MyModal
title={t('common:core.chat.Question Guide')}
iconSrc="core/chat/QGFill"
isOpen
onClose={onClose}
width="500px"
>
<ModalBody px={[5, 10]} py={[4, 8]} pb={[4, 12]}>
<Flex justifyContent={'space-between'} alignItems={'center'}>
<FormLabel flex={'0 0 100px'}>{t('app:core.app.QG.Switch')}</FormLabel>
<Switch
isChecked={isOpenQG}
onChange={(e) => {
onChange({
...value,
open: e.target.checked
});
}}
/>
</Flex>
{isOpenQG && (
<>
<Flex alignItems={'center'} mt={4}>
<Box {...LabelStyles} mr={2}>
{t('common:core.ai.Model')}
</Box>
<Box flex={'1 0 0'}>
<AIModelSelector
width={'100%'}
value={model}
list={llmModelList.map((item) => ({
value: item.model,
label: item.name
}))}
onchange={(e) => {
onChange({
...value,
model: e
});
}}
/>
</Box>
</Flex>
<Box mt={4}>
<Flex alignItems={'center'} mb={1}>
<FormLabel>{t('app:core.dataset.import.Custom prompt')}</FormLabel>
<QuestionTip ml={1} label={t('common:core.app.QG.Custom prompt tip')} />
<Box flex={1} />
<Button
size="xs"
variant={'transparentBase'}
leftIcon={<MyIcon name={'edit'} w={'14px'} />}
onClick={onOpenCustomPrompt}
>
{t('common:common.Edit')}
</Button>
</Flex>
<Box
position={'relative'}
bg={'myGray.50'}
border={'1px'}
borderColor={'borderColor.base'}
borderRadius={'md'}
maxH={'200px'}
overflow={'auto'}
px={3}
py={2}
fontSize={'sm'}
textAlign={'justify'}
whiteSpace={'pre-wrap'}
_hover={{
'& .mask': {
display: 'block'
}
}}
>
{customPrompt || PROMPT_QUESTION_GUIDE}
</Box>
</Box>
</>
)}
</ModalBody>
</MyModal>
{isOpenCustomPrompt && (
<CustomPromptEditor
defaultValue={customPrompt}
defaultPrompt={PROMPT_QUESTION_GUIDE}
footerPrompt={PROMPT_QUESTION_GUIDE_FOOTER}
onChange={(e) => {
onChange({
...value,
customPrompt: e
});
}}
onClose={onCloseCustomPrompt}
/>
)}
</>
);
};

View File

@@ -1,22 +0,0 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Flex, Switch, type SwitchProps } from '@chakra-ui/react';
import React from 'react';
import { useTranslation } from 'next-i18next';
import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
// question generator switch
const QGSwitch = (props: SwitchProps) => {
const { t } = useTranslation();
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/chat/QGFill'} mr={2} w={'20px'} />
<FormLabel color={'myGray.600'}>{t('common:core.app.Question Guide')}</FormLabel>
<ChatFunctionTip type={'nextQuestion'} />
<Box flex={1} />
<Switch {...props} />
</Flex>
);
};
export default QGSwitch;

View File

@@ -29,7 +29,7 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
[FnTypeEnum.nextQuestion]: {
icon: '/imgs/app/nextQuestion-icon.svg',
title: t('common:core.app.Question Guide'),
desc: t('common:core.app.Question Guide Tip'),
desc: t('app:question_guide_tip'),
imgUrl: '/imgs/app/nextQuestion.svg'
},
[FnTypeEnum.tts]: {

View File

@@ -2,8 +2,8 @@ import React, { useState, useMemo, useCallback } from 'react';
import { useAudioPlay } from '@/web/common/utils/voice';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import {
AppAutoExecuteConfigType,
AppFileSelectConfigType,
AppQGConfigType,
AppTTSConfigType,
AppWhisperConfigType,
ChatInputGuideConfigType,
@@ -12,8 +12,8 @@ import {
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import {
defaultAppSelectFileConfig,
defaultAutoExecuteConfig,
defaultChatInputGuideConfig,
defaultQGConfig,
defaultTTSConfig,
defaultWhisperConfig
} from '@fastgpt/global/core/app/constants';
@@ -37,7 +37,7 @@ type useChatStoreType = ChatProviderProps & {
welcomeText: string;
variableList: VariableItemType[];
allVariableList: VariableItemType[];
questionGuide: boolean;
questionGuide: AppQGConfigType;
ttsConfig: AppTTSConfigType;
whisperConfig: AppWhisperConfigType;
autoTTSResponse: boolean;
@@ -72,7 +72,11 @@ type useChatStoreType = ChatProviderProps & {
export const ChatBoxContext = createContext<useChatStoreType>({
welcomeText: '',
variableList: [],
questionGuide: false,
questionGuide: {
open: false,
model: undefined,
customPrompt: undefined
},
ttsConfig: {
type: 'none',
model: undefined,
@@ -143,10 +147,16 @@ const Provider = ({
ChatItemContext,
(v) => v.chatBoxData?.app?.chatConfig?.variables ?? []
);
const questionGuide = useContextSelector(
ChatItemContext,
(v) => v.chatBoxData?.app?.chatConfig?.questionGuide ?? false
);
const questionGuide = useContextSelector(ChatItemContext, (v) => {
const val = v.chatBoxData?.app?.chatConfig?.questionGuide;
if (typeof val === 'boolean') {
return {
...defaultQGConfig,
open: val
};
}
return v.chatBoxData?.app?.chatConfig?.questionGuide ?? defaultQGConfig;
});
const ttsConfig = useContextSelector(
ChatItemContext,
(v) => v.chatBoxData?.app?.chatConfig?.ttsConfig ?? defaultTTSConfig

View File

@@ -335,7 +335,7 @@ const ChatBox = ({
// create question guide
const createQuestionGuide = useCallback(async () => {
if (!questionGuide || chatController.current?.signal?.aborted) return;
if (!questionGuide.open || chatController.current?.signal?.aborted) return;
try {
const abortSignal = new AbortController();
questionGuideController.current = abortSignal;
@@ -344,6 +344,7 @@ const ChatBox = ({
{
appId,
chatId,
questionGuide,
...outLinkAuthData
},
abortSignal
@@ -355,7 +356,7 @@ const ChatBox = ({
}, 100);
}
} catch (error) {}
}, [questionGuide, appId, outLinkAuthData, scrollToBottom]);
}, [questionGuide, appId, chatId, outLinkAuthData, scrollToBottom]);
/* Abort chat completions, questionGuide */
const abortRequest = useMemoizedFn((signal: string = 'stop') => {

View File

@@ -8,15 +8,20 @@ import { NextAPI } from '@/service/middleware/entry';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
export type CreateQuestionGuideParams = OutLinkChatAuthProps & {
appId: string;
chatId: string;
questionGuide?: {
open: boolean;
model?: string;
customPrompt?: string;
};
};
async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: NextApiResponse<any>) {
const { appId, chatId } = req.body;
const { appId, chatId, questionGuide: inputQuestionGuide } = req.body;
const [{ tmbId, teamId }] = await Promise.all([
authChatCrud({
req,
@@ -27,6 +32,13 @@ async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: Nex
]);
// Auth app and get questionGuide config
const questionGuide = await (async () => {
if (inputQuestionGuide) {
return inputQuestionGuide;
}
const { chatConfig } = await getAppLatestVersion(appId);
return chatConfig.questionGuide;
})();
// Get histories
const { histories } = await getChatItems({
@@ -38,15 +50,12 @@ async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: Nex
});
const messages = chats2GPTMessages({ messages: histories, reserveId: false });
const qgModel = global.llmModels[0];
const qgModel = questionGuide?.model || global.llmModels[0].model;
const { result, tokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
jsonRes(res, {
data: result
model: qgModel,
customPrompt: questionGuide?.customPrompt
});
pushQuestionGuideUsage({
@@ -54,6 +63,8 @@ async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: Nex
teamId,
tmbId
});
return result;
}
export default NextAPI(handler);

View File

@@ -40,7 +40,7 @@ const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSe
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
const ToolSelectModal = dynamic(() => import('./components/ToolSelectModal'));
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'));
const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'));
const QGConfig = dynamic(() => import('@/components/core/app/QGConfig'));
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig'));
const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig'));
@@ -425,14 +425,14 @@ const EditForm = ({
{/* question guide */}
<Box {...BoxStyles}>
<QGSwitch
isChecked={appForm.chatConfig.questionGuide}
<QGConfig
value={appForm.chatConfig.questionGuide}
onChange={(e) => {
setAppForm((state) => ({
...state,
chatConfig: {
...state.chatConfig,
questionGuide: e.target.checked
questionGuide: e
}
}));
}}

View File

@@ -30,7 +30,7 @@ export const compareSimpleAppSnapshot = (
{
welcomeText: appForm1.chatConfig?.welcomeText || '',
variables: appForm1.chatConfig?.variables || [],
questionGuide: appForm1.chatConfig?.questionGuide || false,
questionGuide: appForm1.chatConfig?.questionGuide || undefined,
ttsConfig: appForm1.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm1.chatConfig?.whisperConfig || undefined,
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
@@ -39,7 +39,7 @@ export const compareSimpleAppSnapshot = (
{
welcomeText: appForm2.chatConfig?.welcomeText || '',
variables: appForm2.chatConfig?.variables || [],
questionGuide: appForm2.chatConfig?.questionGuide || false,
questionGuide: appForm2.chatConfig?.questionGuide || undefined,
ttsConfig: appForm2.chatConfig?.ttsConfig || undefined,
whisperConfig: appForm2.chatConfig?.whisperConfig || undefined,
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,

View File

@@ -3,7 +3,7 @@ import { NodeProps } from 'reactflow';
import { Box } from '@chakra-ui/react';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import QGSwitch from '@/components/core/app/QGSwitch';
import QGConfig from '@/components/core/app/QGConfig';
import TTSSelect from '@/components/core/app/TTSSelect';
import WhisperConfig from '@/components/core/app/WhisperConfig';
import InputGuideConfig from '@/components/core/app/InputGuideConfig';
@@ -13,7 +13,12 @@ import NodeCard from './render/NodeCard';
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { AppChatConfigType, AppDetailType, VariableItemType } from '@fastgpt/global/core/app/type';
import {
AppChatConfigType,
AppDetailType,
AppQGConfigType,
VariableItemType
} from '@fastgpt/global/core/app/type';
import { useMemoizedFn } from 'ahooks';
import VariableEdit from '@/components/core/app/VariableEdit';
import { AppContext } from '@/pages/app/detail/components/context';
@@ -149,17 +154,16 @@ function AutoExecute({ chatConfig: { autoExecute }, setAppDetail }: ComponentPro
);
}
function QuestionGuide({ chatConfig: { questionGuide = false }, setAppDetail }: ComponentProps) {
function QuestionGuide({ chatConfig: { questionGuide }, setAppDetail }: ComponentProps) {
return (
<QGSwitch
isChecked={questionGuide}
<QGConfig
value={questionGuide}
onChange={(e) => {
const value = e.target.checked;
setAppDetail((state) => ({
...state,
chatConfig: {
...state.chatConfig,
questionGuide: value
questionGuide: e
}
}));
}}

View File

@@ -142,7 +142,7 @@ export const AiPointsTable = () => {
{whisperModel?.charsPointsPrice +
t('common:support.wallet.subscription.point') +
' / 60' +
t('common:unit.minute')}
t('common:unit.seconds')}
</Box>
</Flex>
</Box>

View File

@@ -58,10 +58,12 @@ export const emptyTemplates: Record<
},
{
key: 'questionGuide',
valueType: WorkflowIOValueTypeEnum.boolean,
valueType: WorkflowIOValueTypeEnum.object,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: 'core.app.Question Guide',
value: false
value: {
open: false
}
},
{
key: 'tts',
@@ -285,10 +287,12 @@ export const emptyTemplates: Record<
},
{
key: 'questionGuide',
valueType: 'boolean',
valueType: 'any',
renderTypeList: ['hidden'],
label: 'core.app.Question Guide',
value: false
value: {
open: false
}
},
{
key: 'tts',