Skip to content

Commit

Permalink
⚡ Better error toast when previewing bot
Browse files Browse the repository at this point in the history
Closes #475
  • Loading branch information
baptisteArno committed Apr 27, 2023
1 parent 3c6a666 commit d448e64
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 40 deletions.
139 changes: 139 additions & 0 deletions apps/builder/src/components/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { Flex, HStack, IconButton, Stack, Text } from '@chakra-ui/react'
import { AlertIcon, CloseIcon, InfoIcon, SmileIcon } from './icons'
import { CodeEditor } from './inputs/CodeEditor'
import { LanguageName } from '@uiw/codemirror-extensions-langs'

export type ToastProps = {
title?: string
description?: string
details?: {
content: string
lang: LanguageName
}
status?: 'info' | 'error' | 'success'
icon?: React.ReactNode
primaryButton?: React.ReactNode
secondaryButton?: React.ReactNode
onClose: () => void
}

export const Toast = ({
status = 'error',
title,
description,
details,
icon,
primaryButton,
secondaryButton,
onClose,
}: ToastProps) => {
return (
<Flex
p={3}
rounded="md"
bgColor="white"
borderWidth="1px"
shadow="sm"
fontSize="sm"
pos="relative"
maxW="450px"
>
<HStack alignItems="flex-start" pr="7" spacing="3" w="full">
<Icon customIcon={icon} status={status} />{' '}
<Stack spacing={3} flex="1">
<Stack spacing={1}>
{title && <Text fontWeight="semibold">{title}</Text>}
{description && <Text>{description}</Text>}
</Stack>

{details && (
<CodeEditor
isReadOnly
value={details.content}
lang={details.lang}
maxHeight="200px"
maxWidth="calc(450px - 100px)"
/>
)}
{(secondaryButton || primaryButton) && (
<HStack>
{secondaryButton}
{primaryButton}
</HStack>
)}
</Stack>
</HStack>

<IconButton
aria-label="Close"
icon={<CloseIcon />}
size="sm"
onClick={onClose}
variant="ghost"
pos="absolute"
top={1}
right={1}
/>
</Flex>
)
}

const Icon = ({
customIcon,
status,
}: {
customIcon?: React.ReactNode
status: ToastProps['status']
}) => {
const color = parseColor(status)
const icon = parseIcon(status, customIcon)
return (
<Flex
bgColor={`${color}.50`}
boxSize="40px"
justifyContent="center"
alignItems="center"
rounded="full"
flexShrink={0}
>
<Flex
bgColor={`${color}.100`}
boxSize="30px"
justifyContent="center"
alignItems="center"
rounded="full"
fontSize="18px"
color={`${color}.600`}
>
{icon}
</Flex>
</Flex>
)
}

const parseColor = (status: ToastProps['status']) => {
if (!status) return 'red'
switch (status) {
case 'error':
return 'red'
case 'success':
return 'green'
case 'info':
return 'blue'
}
}

const parseIcon = (
status: ToastProps['status'],
customIcon?: React.ReactNode
) => {
if (customIcon) return customIcon
switch (status) {
case 'error':
return <AlertIcon />
case 'success':
return <SmileIcon />
case 'info':
return <InfoIcon />
}
}
17 changes: 17 additions & 0 deletions apps/builder/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,20 @@ export const ShuffleIcon = (props: IconProps) => (
<line x1="4" y1="4" x2="9" y2="9"></line>
</Icon>
)

export const InfoIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</Icon>
)

export const SmileIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<circle cx="12" cy="12" r="10"></circle>
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</Icon>
)
5 changes: 4 additions & 1 deletion apps/builder/src/components/inputs/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ type Props = {
debounceTimeout?: number
withVariableButton?: boolean
height?: string
maxHeight?: string
onChange?: (value: string) => void
}
export const CodeEditor = ({
defaultValue,
lang,
onChange,
height = '250px',
maxHeight = '70vh',
withVariableButton = true,
isReadOnly = false,
debounceTimeout = 1000,
Expand Down Expand Up @@ -93,9 +95,10 @@ export const CodeEditor = ({
pos="relative"
onMouseEnter={onOpen}
onMouseLeave={onClose}
maxWidth={props.maxWidth}
sx={{
'& .cm-editor': {
maxH: '70vh',
maxH: maxHeight,
outline: '0px solid transparent !important',
rounded: 'md',
},
Expand Down
14 changes: 12 additions & 2 deletions apps/builder/src/features/preview/components/WebPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { WebhookIcon } from '@/components/icons'
import { useEditor } from '@/features/editor/providers/EditorProvider'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { useToast } from '@/hooks/useToast'
import { UseToastOptions } from '@chakra-ui/react'
import { Standard } from '@typebot.io/react'
import { ChatReply } from '@typebot.io/schemas'

Expand All @@ -15,7 +15,17 @@ export const WebPreview = () => {

const handleNewLogs = (logs: ChatReply['logs']) => {
logs?.forEach((log) => {
showToast(log as UseToastOptions)
showToast({
icon: <WebhookIcon />,
title: 'An error occured',
description: log.description,
details: log.details
? {
lang: 'json',
content: JSON.stringify(log.details, null, 2),
}
: undefined,
})
console.error(log)
})
}
Expand Down
22 changes: 0 additions & 22 deletions apps/builder/src/hooks/useToast.ts

This file was deleted.

39 changes: 39 additions & 0 deletions apps/builder/src/hooks/useToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Toast, ToastProps } from '@/components/Toast'
import { useToast as useChakraToast } from '@chakra-ui/react'
import { useCallback } from 'react'

export const useToast = () => {
const toast = useChakraToast()

const showToast = useCallback(
({
title,
description,
status = 'error',
icon,
details,
primaryButton,
secondaryButton,
}: Omit<ToastProps, 'onClose'>) => {
toast({
position: 'top-right',
duration: details ? null : undefined,
render: ({ onClose }) => (
<Toast
title={title}
description={description}
status={status}
icon={icon}
details={details}
onClose={onClose}
primaryButton={primaryButton}
secondaryButton={secondaryButton}
/>
),
})
},
[toast]
)

return { showToast }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { transformStringVariablesToList } from '@/features/variables/transformVariablesToList'
import prisma from '@/lib/prisma'
import {
ChatReply,
SessionState,
Variable,
VariableWithUnknowValue,
Expand All @@ -19,7 +20,6 @@ import { updateVariables } from '@/features/variables/updateVariables'
import { parseVariables } from '@/features/variables/parseVariables'
import { saveSuccessLog } from '@/features/logs/saveSuccessLog'
import { parseVariableNumber } from '@/features/variables/parseVariableNumber'
import { HTTPError } from 'got'

export const createChatCompletionOpenAI = async (
state: SessionState,
Expand All @@ -29,9 +29,15 @@ export const createChatCompletionOpenAI = async (
}: { outgoingEdgeId?: string; options: ChatCompletionOpenAIOptions }
): Promise<ExecuteIntegrationResponse> => {
let newSessionState = state
const noCredentialsError = {
status: 'error',
description: 'Make sure to select an OpenAI account',
}
if (!options.credentialsId) {
console.error('OpenAI block has no credentials')
return { outgoingEdgeId }
return {
outgoingEdgeId,
logs: [noCredentialsError],
}
}
const credentials = await prisma.credentials.findUnique({
where: {
Expand All @@ -40,7 +46,7 @@ export const createChatCompletionOpenAI = async (
})
if (!credentials) {
console.error('Could not find credentials in database')
return { outgoingEdgeId }
return { outgoingEdgeId, logs: [noCredentialsError] }
}
const { apiKey } = decrypt(
credentials.data,
Expand Down Expand Up @@ -107,21 +113,24 @@ export const createChatCompletionOpenAI = async (
newSessionState,
}
} catch (err) {
const log = {
const log: NonNullable<ChatReply['logs']>[number] = {
status: 'error',
description: 'OpenAI block returned error',
details: '',
}

if (err instanceof HTTPError) {
console.error(err.response.body)
log.details = JSON.stringify(err.response.body, null, 2).substring(
0,
1000
)
} else {
console.error(err)
log.details = JSON.stringify(err, null, 2).substring(0, 1000)
if (err && typeof err === 'object') {
if ('response' in err) {
const { status, data } = err.response as {
status: string
data: string
}
log.details = {
status,
data,
}
} else if ('message' in err) {
log.details = err.message
}
}

state.result &&
Expand Down

4 comments on commit d448e64

@vercel
Copy link

@vercel vercel bot commented on d448e64 Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on d448e64 Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-typebot-io.vercel.app
builder-v2-git-main-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on d448e64 Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

chat.tuanpakya.com
chat.webisharp.com
dicanatural.online
digitalhelp.com.au
goalsettingbot.com
pant.maxbot.com.br
pantherview.cr8.ai
positivobra.com.br
rollingball.cr8.ai
survey.digienge.io
this-is-a-test.com
zap.techadviser.in
ai.digitaldaftar.in
bot.boston-voip.com
bot.cabinpromos.com
bot.carnaval.studio
bot.digitalbled.com
bot.dsignagency.com
bot.eventhub.com.au
bot.jepierre.com.br
bot.leadgenpod.site
bot.ltmidias.com.br
bot.viralsangat.com
bot.winglabs.com.br
carsalesenquiry.com
chat.marius.digital
chat.sr7digital.com
chatbot.matthesv.de
chatbot.repplai.com
demo.botscientis.us
demo.wemakebots.xyz
hrbot.robomotion.io
inearephones.cr8.ai
kbsub.wpwakanda.com
limitenahora.com.br
live.botscientis.us
mentoria.omelhor.vc
nutrisamirbayde.com
order.maitempah.com
profileadscloud.com
quest.wpwakanda.com
support.wawplus.com
survey1.digienge.io
surveys.essiell.com
test.botscientis.us
test.getreview.help
test.reventepro.com
typebot.stillio.com
wordsandimagery.com
815639944.21000000.one
83720273.bouclidom.com
aplicacao.bmind.com.br
apply.ansuraniphone.my
bbutton.wpwwakanda.com
bolsamaisbrasil.app.br
bot.ilmuseoaiborghi.it
ted.meujalecobrasil.com.br
type.dericsoncalari.com.br
bot.pinpointinteractive.com
bot.polychromes-project.com
bot.seidinembroseanchetu.it
chat.semanalimpanome.com.br
designguide.techyscouts.com
liveconvert2.kandalearn.com
presente.empresarias.com.mx
register.algorithmpress.com
sell.sellthemotorhome.co.uk
anamnese.odontopavani.com.br
austin.channelautomation.com
bot.marketingplusmindset.com
bot.seidibergamoseanchetu.it
desabafe.sergiolimajr.com.br
download.venturemarketing.in
piazzatorre.barrettamario.it
type.cookieacademyonline.com
upload.atlasoutfittersk9.com
bot.brigadeirosemdrama.com.br
forms.escoladeautomacao.com.br
onboarding.libertydreamcare.ie
type.talitasouzamarques.com.br
agendamento.sergiolimajr.com.br
anamnese.clinicamegasjdr.com.br
bookings.littlepartymonkeys.com
bot.comercializadoraomicron.com
elevateyourmind.groovepages.com
viewer-v2-typebot-io.vercel.app
yourfeedback.comebackreward.com
bot.cabin-rentals-of-georgia.net
gerador.verificadordehospedes.com
personal-trainer.barrettamario.it
preagendamento.sergiolimajr.com.br
studiotecnicoimmobiliaremerelli.it
download.thailandmicespecialist.com
register.thailandmicespecialist.com
bot.studiotecnicoimmobiliaremerelli.it
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
chrome-os-inquiry-system.itschromeos.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com

@vercel
Copy link

@vercel vercel bot commented on d448e64 Apr 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./apps/docs

docs-git-main-typebot-io.vercel.app
docs-typebot-io.vercel.app
docs.typebot.io

Please sign in to comment.