Introducing The Forge (#1072)

The Forge allows anyone to easily create their own Typebot Block.

Closes #380
This commit is contained in:
Baptiste Arnaud
2023-12-13 10:22:02 +01:00
committed by GitHub
parent c373108b55
commit 5e019bbb22
184 changed files with 42659 additions and 37411 deletions

View File

@@ -0,0 +1,86 @@
import { TypingBubble } from '@/components'
import { isMobile } from '@/utils/isMobileSignal'
import { createSignal, onCleanup, onMount } from 'solid-js'
import { clsx } from 'clsx'
import { CustomEmbedBubble as CustomEmbedBubbleProps } from '@typebot.io/schemas'
import { executeCode } from '@/features/blocks/logic/script/executeScript'
type Props = {
content: CustomEmbedBubbleProps['content']
onTransitionEnd: (offsetTop?: number) => void
onCompleted: (reply?: string) => void
}
let typingTimeout: NodeJS.Timeout
export const showAnimationDuration = 400
export const CustomEmbedBubble = (props: Props) => {
let ref: HTMLDivElement | undefined
const [isTyping, setIsTyping] = createSignal(true)
let containerRef: HTMLDivElement | undefined
onMount(() => {
console.log(
props.content.initFunction.content,
props.content.initFunction.args
)
executeCode({
args: {
...props.content.initFunction.args,
typebotElement: containerRef,
},
content: props.content.initFunction.content,
})
if (props.content.waitForEventFunction)
executeCode({
args: {
...props.content.waitForEventFunction.args,
continueFlow: props.onCompleted,
},
content: props.content.waitForEventFunction.content,
})
typingTimeout = setTimeout(() => {
setIsTyping(false)
setTimeout(
() => props.onTransitionEnd(ref?.offsetTop),
showAnimationDuration
)
}, 2000)
})
onCleanup(() => {
if (typingTimeout) clearTimeout(typingTimeout)
})
return (
<div class="flex flex-col w-full animate-fade-in" ref={ref}>
<div class="flex w-full items-center">
<div class="flex relative z-10 items-start typebot-host-bubble w-full max-w-full">
<div
class="flex items-center absolute px-4 py-2 bubble-typing z-10 "
style={{
width: isTyping() ? '64px' : '100%',
height: isTyping() ? '32px' : '100%',
}}
>
{isTyping() && <TypingBubble />}
</div>
<div
class={clsx(
'p-2 z-20 text-fade-in w-full',
isTyping() ? 'opacity-0' : 'opacity-100'
)}
style={{
height: isTyping() ? (isMobile() ? '32px' : '36px') : undefined,
}}
>
<div class="w-full h-full overflow-scroll" ref={containerRef} />
</div>
</div>
</div>
</div>
)
}

View File

@@ -9,15 +9,16 @@ const maxRetryAttempts = 3
export const streamChat =
(context: ClientSideActionContext & { retryAttempt?: number }) =>
async (
messages: {
async ({
messages,
onMessageStream,
}: {
messages?: {
content?: string | undefined
role?: 'system' | 'user' | 'assistant' | undefined
}[],
{
onMessageStream,
}: { onMessageStream?: (props: { id: string; message: string }) => void }
): Promise<{ message?: string; error?: object }> => {
}[]
onMessageStream?: (props: { id: string; message: string }) => void
}): Promise<{ message?: string; error?: object }> => {
try {
abortController = new AbortController()
@@ -51,7 +52,7 @@ export const streamChat =
return streamChat({
...context,
retryAttempt: (context.retryAttempt ?? 0) + 1,
})(messages, { onMessageStream })
})({ messages, onMessageStream })
}
return {
error: (await res.json()) || 'Failed to fetch the chat response.',

View File

@@ -21,3 +21,18 @@ const parseContent = (content: string) => {
.replace(/<\/script>/g, '')
return contentWithoutScriptTags
}
export const executeCode = async ({
args,
content,
}: {
content: string
args: Record<string, unknown>
}) => {
try {
const func = AsyncFunction(...Object.keys(args), content)
await func(...Object.keys(args).map((key) => args[key]))
} catch (err) {
console.warn('Script threw an error:', err)
}
}