Feature: Follow-up Prompts (#3280)

* Add migrations - add follow up prompts column to chatflow and chat message

* Add configuration tab for follow-up prompts

* Add follow up prompts functionality

* Pin zod version in components - this was causing a type error with structured outputs

* Generate follow up prompts if enabled and return it in stream, response, and save to database

* Show follow up prompts after getting response

* Add google gen ai for generating follow up prompts and fix issues

* Add config for google gen ai and update model options

* Update follow-up prompts ui and styles

* Release/2.1.0 (#3204)

flowise@2.1.0 release

* Chore/update flowise embed version to 2.0.0 (#3205)

* update flowise-embed version on lock file

* add agent messages to share chatbot

* Update pnpm-lock.yaml

* update flowise-embed version

* update flowise-embed to 1.3.9

* update embed version to 2.0

* Bugfix/CodeInterpreter E2B Credential (#3206)

* Base changes for ServerSide Events (instead of socket.io)

* lint fixes

* adding of interface and separate methods for streaming events

* lint

* first draft, handles both internal and external prediction end points.

* lint fixes

* additional internal end point for streaming and associated changes

* return streamresponse as true to build agent flow

* 1) JSON formatting for internal events
2) other fixes

* 1) convert internal event to metadata to maintain consistency with external response

* fix action and metadata streaming

* fix for error when agent flow is aborted

* prevent subflows from streaming and other code cleanup

* prevent streaming from enclosed tools

* add fix for preventing chaintool streaming

* update lock file

* add open when hidden to sse

* Streaming errors

* Streaming errors

* add fix for showing error message

* add code interpreter

* add artifacts to view message dialog

* Update pnpm-lock.yaml

* uncomment e2b credential

---------

Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>

* Release/2.1.0 (#3207)

* flowise@2.1.0 release

* update flowise-components@2.1.1

* Bugfix/Add artifacts migration script to other database types (#3210)

add artifacts migration script to other database types

* Release/2.1.1 (#3213)

release @2.1.1

* Bugfix/Add header to allow sse on nginx (#3214)

add header to allow sse on nginx

* Bugfix/remove invalid markdown (#3219)

remove invalid markdown

* Correct "as" casing (#3216)

* Correct "as" casing

* Remove "version" line from docker compose file

* Update docker-compose.yml

---------

Co-authored-by: Henry Heng <henryheng@flowiseai.com>

* chore: update unstructured API url and doc reference (#3224)

chore: udpate unstructured API url and doc reference

* Feature/add ability to specify dynamic metadata to jsonlines (#3238)

* add ability to specify dynamic metadata to jsonlines

* fix additional metadata

* Bugfix/Buffer Memory for Anthropic (#3242)

fix buffer memory

* Added env vars to ui and api URL  (#3141)

* feat: add environment vars to split application in different deployments for better scalability

* update: package.json

added start script ui

---------

Co-authored-by: patrick <patrick.alves@br.experian.com>

* Added 1-click deployment link for Alibaba Cloud.  (#3251)

* Added a link for Alibaba Cloud Deployment

* change service name

---------

Co-authored-by: yehan <gn398171@alibaba-inc.com>

* Chore/Groq Llama3.2 (#3255)

* add gemini flash

* add gemin flash to vertex

* add gemin-1.5-flash-preview to vertex

* add azure gpt 4o

* add claude 3.5 sonnet

* add mistral nemo

* add groq llama3.1

* add gpt4o-mini to azure

* o1 mini

* add groq llama 3.2

* Bugfix/Prevent streaming of chatflow tool and chain tool (#3257)

prevent streaming of chatflow tool and chain tool

* Bugfix/Enable Custom Tool Optional Input Schema (#3258)

* prevent streaming of chatflow tool and chain tool

* enable optional input schema

* Bugfix/Searxng tool not working (#3263)

fix searxng tool not working

* LunaryAI automatic Thread and User tracking (#3233)

* Lunary Thread/User tracking

* Clean console logs

* Clean

* Remove commented lines

* Remove commented line

* feat: enable autofocus to the `new chatflow title` to improve usability (#3260)

This dialog has only one input and it is the primary one, there is no need for an extra click to be able to set the title

* feat: save a new Chatflow when the `ENTER` key is pressed (#3261)

This simple event handler improve the usability of the UI by avoiding having to use the mouse or having to tab twice and then hit enter to save a flow

* feat: save Chatflow title when the `ENTER` key is pressed or discard upon `ESC` is pressed (#3265)

This simple event handler improves the usability of the UI by avoiding having to use the mouse to save or dicard title changes

* feat: enable autofocus to the `edit chatflow title` field to improve UI usability (#3264)

feat: enable autofocus to the `edit chatflow title` field to improve usability

The canvas header has only one input and it is the primary one, there is no need for an extra click to be able to edit the title

* feat: add search keyboard shortcut based on the current platform (#3267)

* feat: highlight valid/invalid connection between nodes (#3266)

Change the inputs background to green/red to hint compatible connections, in adition to the `not-allowed` mouse cursor for incompatible connections

* Bugfix/add fixes for search of view header (#3271)

add fixes for search of view header

* fix: warning when passing a boolean to border property of a Card (#3275)

By default MainCard wrappers like NodeCardWrapper and CardWrapper add a a solid border of 1px, but if the `MainCard.border` prop is used (`false`) the border prop was wrongly set to a boolean instead of string

* feat: add shortcut text hint to the search field (#3269)

* feat: add shortcut text hint to the search field

* fix: search box width to fit the shortcut hint text

* fix: error when not running on Mac due to an undefined `os` variable

* fix: warning when a non-boolean values was used to set `checked` prop of a SwitchInput component (#3276)

fix: warning when a non-boolean values was used to set`checked` prop of SwitchInput component

The problem was that in the useEffect hook the plain value was used without validation like in useState

* Bugfix/Throw error to prevent SSE from retrying (#3281)

throw error to prevent SSE from retrying

* Pin zod version in components - this was causing a type error with structured outputs

* Fix conflicts in pnpm lock

* fix ui changes for follow up prompts

* Fix button disable state in follow-up prompts configuration

* Fix follow-up prompts not showing up for agent flows

* Show follow up prompts if last message is apiMessage and follow up prompts are available

---------

Co-authored-by: Henry Heng <henryheng@flowiseai.com>
Co-authored-by: Vinod Paidimarry <vinodkiran@outlook.in>
Co-authored-by: Cross <github@dillfrescott.com>
Co-authored-by: cragwolfe <cragcw@gmail.com>
Co-authored-by: patrickreinan <patrickreinan@gmail.com>
Co-authored-by: patrick <patrick.alves@br.experian.com>
Co-authored-by: yehan <34835250+yehanyh@users.noreply.github.com>
Co-authored-by: yehan <gn398171@alibaba-inc.com>
Co-authored-by: Vincelwt <vincelwt@users.noreply.github.com>
Co-authored-by: Humberto Rodríguez A. <rhumbertgz@users.noreply.github.com>
Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Ilango
2024-10-04 21:14:28 +05:30
committed by GitHub
parent 490855729b
commit c9d8b8716b
25 changed files with 1076 additions and 203 deletions
@@ -0,0 +1,45 @@
import Box from '@mui/material/Box'
import PropTypes from 'prop-types'
import { Chip } from '@mui/material'
import './StarterPromptsCard.css'
import { useSelector } from 'react-redux'
const FollowUpPromptsCard = ({ isGrid, followUpPrompts, sx, onPromptClick }) => {
const customization = useSelector((state) => state.customization)
return (
<Box
className={'button-container'}
sx={{ width: '100%', maxWidth: isGrid ? 'inherit' : '400px', p: 1.5, display: 'flex', gap: 1, ...sx }}
>
{followUpPrompts.map((fp, index) => (
<Chip
label={fp}
className={'button'}
key={index}
onClick={(e) => onPromptClick(fp, e)}
sx={{
backgroundColor: 'transparent',
border: '1px solid',
boxShadow: '0px 2px 1px -1px rgba(0,0,0,0.2)',
color: '#2196f3',
transition: 'all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
'&:hover': {
backgroundColor: customization.isDarkMode ? 'rgba(0, 0, 0, 0.12)' : 'rgba(0, 0, 0, 0.05)',
border: '1px solid'
}
}}
/>
))}
</Box>
)
}
FollowUpPromptsCard.propTypes = {
isGrid: PropTypes.bool,
followUpPrompts: PropTypes.array,
sx: PropTypes.object,
onPromptClick: PropTypes.func
}
export default FollowUpPromptsCard
@@ -10,6 +10,7 @@ import ChatFeedback from '@/ui-component/extended/ChatFeedback'
import AnalyseFlow from '@/ui-component/extended/AnalyseFlow'
import StarterPrompts from '@/ui-component/extended/StarterPrompts'
import Leads from '@/ui-component/extended/Leads'
import FollowUpPrompts from '@/ui-component/extended/FollowUpPrompts'
const CHATFLOW_CONFIGURATION_TABS = [
{
@@ -20,6 +21,10 @@ const CHATFLOW_CONFIGURATION_TABS = [
label: 'Starter Prompts',
id: 'conversationStarters'
},
{
label: 'Follow-up Prompts',
id: 'followUpPrompts'
},
{
label: 'Speech to Text',
id: 'speechToText'
@@ -116,6 +121,7 @@ const ChatflowConfigurationDialog = ({ show, dialogProps, onCancel }) => {
<TabPanel key={index} value={tabValue} index={index}>
{item.id === 'rateLimiting' && <RateLimit />}
{item.id === 'conversationStarters' ? <StarterPrompts dialogProps={dialogProps} /> : null}
{item.id === 'followUpPrompts' ? <FollowUpPrompts dialogProps={dialogProps} /> : null}
{item.id === 'speechToText' ? <SpeechToText dialogProps={dialogProps} /> : null}
{item.id === 'chatFeedback' ? <ChatFeedback dialogProps={dialogProps} /> : null}
{item.id === 'allowedDomains' ? <AllowedDomains dialogProps={dialogProps} /> : null}
@@ -0,0 +1,527 @@
import PropTypes from 'prop-types'
import { Box, Button, FormControl, ListItem, ListItemAvatar, ListItemText, MenuItem, Select, Typography } from '@mui/material'
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
// Project Imports
import { StyledButton } from '@/ui-component/button/StyledButton'
import { SwitchInput } from '@/ui-component/switch/Switch'
import chatflowsApi from '@/api/chatflows'
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction, SET_CHATFLOW } from '@/store/actions'
import useNotifier from '@/utils/useNotifier'
import anthropicIcon from '@/assets/images/anthropic.svg'
import azureOpenAiIcon from '@/assets/images/azure_openai.svg'
import mistralAiIcon from '@/assets/images/mistralai.svg'
import openAiIcon from '@/assets/images/openai.svg'
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
import CredentialInputHandler from '@/views/canvas/CredentialInputHandler'
import { Input } from '@/ui-component/input/Input'
import { AsyncDropdown } from '@/ui-component/dropdown/AsyncDropdown'
// Icons
import { IconX } from '@tabler/icons-react'
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
const promptDescription =
'Prompt to generate questions based on the conversation history. You can use variable {history} to refer to the conversation history.'
const defaultPrompt =
'Given the following conversations: {history}. Please help me predict the three most likely questions that human would ask and keeping each question short and concise.'
// update when adding new providers
const FollowUpPromptProviders = {
ANTHROPIC: 'chatAnthropic',
AZURE_OPENAI: 'azureChatOpenAI',
GOOGLE_GENAI: 'chatGoogleGenerativeAI',
MISTRALAI: 'chatMistralAI',
OPENAI: 'chatOpenAI'
}
const followUpPromptsOptions = {
[FollowUpPromptProviders.ANTHROPIC]: {
label: 'Anthropic Claude',
name: FollowUpPromptProviders.ANTHROPIC,
icon: anthropicIcon,
inputs: [
{
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['anthropicApi']
},
{
label: 'Model Name',
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels'
},
{
label: 'Prompt',
name: 'prompt',
type: 'string',
rows: 4,
description: promptDescription,
optional: true,
default: defaultPrompt
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
optional: true,
default: 0.9
}
]
},
[FollowUpPromptProviders.AZURE_OPENAI]: {
label: 'Azure ChatOpenAI',
name: FollowUpPromptProviders.AZURE_OPENAI,
icon: azureOpenAiIcon,
inputs: [
{
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['azureOpenAIApi']
},
{
label: 'Model Name',
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels'
},
{
label: 'Prompt',
name: 'prompt',
type: 'string',
rows: 4,
description: promptDescription,
optional: true,
default: defaultPrompt
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
optional: true,
default: 0.9
}
]
},
[FollowUpPromptProviders.GOOGLE_GENAI]: {
label: 'Google Gemini',
name: FollowUpPromptProviders.GOOGLE_GENAI,
icon: azureOpenAiIcon,
inputs: [
{
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['googleGenerativeAI']
},
{
label: 'Model Name',
name: 'modelName',
type: 'options',
default: 'gemini-1.5-pro-latest',
options: [
{ label: 'gemini-1.5-flash-latest', name: 'gemini-1.5-flash-latest' },
{ label: 'gemini-1.5-pro-latest', name: 'gemini-1.5-pro-latest' }
]
},
{
label: 'Prompt',
name: 'prompt',
type: 'string',
rows: 4,
description: promptDescription,
optional: true,
default: defaultPrompt
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
optional: true,
default: 0.9
}
]
},
[FollowUpPromptProviders.MISTRALAI]: {
label: 'Mistral AI',
name: FollowUpPromptProviders.MISTRALAI,
icon: mistralAiIcon,
inputs: [
{
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['mistralAIApi']
},
{
label: 'Model Name',
name: 'modelName',
type: 'options',
options: [
{ label: 'mistral-large-latest', name: 'mistral-large-latest' },
{ label: 'mistral-large-2402', name: 'mistral-large-2402' }
]
},
{
label: 'Prompt',
name: 'prompt',
type: 'string',
rows: 4,
description: promptDescription,
optional: true,
default: defaultPrompt
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
optional: true,
default: 0.9
}
]
},
[FollowUpPromptProviders.OPENAI]: {
label: 'OpenAI',
name: FollowUpPromptProviders.OPENAI,
icon: openAiIcon,
inputs: [
{
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['openAIApi']
},
{
label: 'Model Name',
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels'
},
{
label: 'Prompt',
name: 'prompt',
type: 'string',
rows: 4,
description: promptDescription,
optional: true,
default: defaultPrompt
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
optional: true,
default: 0.9
}
]
}
}
const FollowUpPrompts = ({ dialogProps }) => {
const dispatch = useDispatch()
useNotifier()
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
const [followUpPromptsConfig, setFollowUpPromptsConfig] = useState({})
const [chatbotConfig, setChatbotConfig] = useState({})
const [selectedProvider, setSelectedProvider] = useState('none')
const handleChange = (key, value) => {
setFollowUpPromptsConfig({
...followUpPromptsConfig,
[key]: value
})
}
const handleSelectedProviderChange = (event) => {
const selectedProvider = event.target.value
setSelectedProvider(selectedProvider)
handleChange('selectedProvider', selectedProvider)
}
const setValue = (value, providerName, inputParamName) => {
let newVal = {}
if (!Object.prototype.hasOwnProperty.call(followUpPromptsConfig, providerName)) {
newVal = { ...followUpPromptsConfig, [providerName]: {} }
} else {
newVal = { ...followUpPromptsConfig }
}
newVal[providerName][inputParamName] = value
if (inputParamName === 'status' && value === true) {
// ensure that the others are turned off
Object.keys(followUpPromptsOptions).forEach((key) => {
const provider = followUpPromptsOptions[key]
if (provider.name !== providerName) {
newVal[provider.name] = { ...followUpPromptsConfig[provider.name], status: false }
}
})
}
setFollowUpPromptsConfig(newVal)
return newVal
}
const onSave = async () => {
// TODO: saving without changing the prompt will not save the prompt
try {
let value = {
followUpPrompts: { status: followUpPromptsConfig.status }
}
chatbotConfig.followUpPrompts = value.followUpPrompts
// if the prompt is not set, save the default prompt
if (!followUpPromptsConfig[followUpPromptsConfig.selectedProvider].prompt) {
followUpPromptsConfig[followUpPromptsConfig.selectedProvider].prompt = followUpPromptsOptions[
followUpPromptsConfig.selectedProvider
].inputs.find((input) => input.name === 'prompt').default
}
if (!followUpPromptsConfig[followUpPromptsConfig.selectedProvider].temperature) {
followUpPromptsConfig[followUpPromptsConfig.selectedProvider].temperature = followUpPromptsOptions[
followUpPromptsConfig.selectedProvider
].inputs.find((input) => input.name === 'temperature').default
}
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
chatbotConfig: JSON.stringify(chatbotConfig),
followUpPrompts: JSON.stringify(followUpPromptsConfig)
})
if (saveResp.data) {
enqueueSnackbar({
message: 'Follow-up Prompts configuration saved',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
}
} catch (error) {
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
enqueueSnackbar({
message: `Failed to save follow-up prompts configuration: ${errorData}`,
options: {
key: new Date().getTime() + Math.random(),
variant: 'error',
persist: true,
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
}
}
useEffect(() => {
if (dialogProps.chatflow && dialogProps.chatflow.followUpPrompts) {
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
let followUpPromptsConfig = JSON.parse(dialogProps.chatflow.followUpPrompts)
setChatbotConfig(chatbotConfig || {})
if (followUpPromptsConfig) {
setFollowUpPromptsConfig(followUpPromptsConfig)
setSelectedProvider(followUpPromptsConfig.selectedProvider)
}
}
return () => {}
}, [dialogProps])
const checkDisabled = () => {
if (followUpPromptsConfig && followUpPromptsConfig.status) {
if (selectedProvider === 'none') {
return true
}
const provider = followUpPromptsOptions[selectedProvider]
for (let inputParam of provider.inputs) {
if (!inputParam.optional) {
const param = inputParam.name === 'credential' ? 'credentialId' : inputParam.name
if (
!followUpPromptsConfig[selectedProvider] ||
!followUpPromptsConfig[selectedProvider][param] ||
followUpPromptsConfig[selectedProvider][param] === ''
) {
return true
}
}
}
}
return false
}
return (
<>
<Box
sx={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
justifyContent: 'start',
gap: 3,
mb: 2
}}
>
<SwitchInput
label='Enable Follow-up Prompts'
onChange={(value) => handleChange('status', value)}
value={followUpPromptsConfig.status}
/>
{followUpPromptsConfig && followUpPromptsConfig.status && (
<>
<Typography variant='h5'>Providers</Typography>
<FormControl fullWidth>
<Select size='small' value={selectedProvider} onChange={handleSelectedProviderChange}>
<MenuItem value='none'>None</MenuItem>
{Object.values(followUpPromptsOptions).map((provider) => (
<MenuItem key={provider.name} value={provider.name}>
{provider.label}
</MenuItem>
))}
</Select>
</FormControl>
{selectedProvider !== 'none' && (
<>
<ListItem sx={{ p: 0 }} alignItems='center'>
<ListItemAvatar>
<div
style={{
width: 50,
height: 50,
borderRadius: '50%',
backgroundColor: 'white'
}}
>
<img
style={{
width: '100%',
height: '100%',
padding: 10,
objectFit: 'contain'
}}
alt='AI'
src={followUpPromptsOptions[selectedProvider].icon}
/>
</div>
</ListItemAvatar>
<ListItemText
primary={followUpPromptsOptions[selectedProvider].label}
secondary={
<a target='_blank' rel='noreferrer' href={followUpPromptsOptions[selectedProvider].url}>
{followUpPromptsOptions[selectedProvider].url}
</a>
}
/>
</ListItem>
{followUpPromptsOptions[selectedProvider].inputs.map((inputParam, index) => (
<Box key={index} sx={{ px: 2, width: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography>
{inputParam.label}
{!inputParam.optional && <span style={{ color: 'red' }}>&nbsp;*</span>}
{inputParam.description && (
<TooltipWithParser style={{ marginLeft: 10 }} title={inputParam.description} />
)}
</Typography>
</div>
{inputParam.type === 'credential' && (
<CredentialInputHandler
key={`${selectedProvider}-${inputParam.name}`}
data={
followUpPromptsConfig[selectedProvider]?.credentialId
? { credential: followUpPromptsConfig[selectedProvider].credentialId }
: {}
}
inputParam={inputParam}
onSelect={(newValue) => setValue(newValue, selectedProvider, 'credentialId')}
/>
)}
{(inputParam.type === 'string' ||
inputParam.type === 'password' ||
inputParam.type === 'number') && (
<Input
key={`${selectedProvider}-${inputParam.name}`}
inputParam={inputParam}
onChange={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
followUpPromptsConfig[selectedProvider] &&
followUpPromptsConfig[selectedProvider][inputParam.name]
? followUpPromptsConfig[selectedProvider][inputParam.name]
: inputParam.default ?? ''
}
/>
)}
{inputParam.type === 'asyncOptions' && (
<>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<AsyncDropdown
key={`${selectedProvider}-${inputParam.name}`}
name={inputParam.name}
nodeData={{
name: followUpPromptsOptions[selectedProvider].name,
inputParams: followUpPromptsOptions[selectedProvider].inputs
}}
value={
followUpPromptsConfig[selectedProvider] &&
followUpPromptsConfig[selectedProvider][inputParam.name]
? followUpPromptsConfig[selectedProvider][inputParam.name]
: inputParam.default ?? 'choose an option'
}
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
/>
</div>
</>
)}
{inputParam.type === 'options' && (
<Dropdown
name={inputParam.name}
options={inputParam.options}
onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)}
value={
followUpPromptsConfig[selectedProvider] &&
followUpPromptsConfig[selectedProvider][inputParam.name]
? followUpPromptsConfig[selectedProvider][inputParam]
: inputParam.default ?? 'choose an option'
}
/>
)}
</Box>
))}
</>
)}
</>
)}
</Box>
<StyledButton disabled={checkDisabled()} variant='contained' onClick={onSave}>
Save
</StyledButton>
</>
)
}
FollowUpPrompts.propTypes = {
dialogProps: PropTypes.object
}
export default FollowUpPrompts