diff --git a/packages/bot-engine/continueBotFlow.ts b/packages/bot-engine/continueBotFlow.ts index a5adb60ee..4f6e2922e 100644 --- a/packages/bot-engine/continueBotFlow.ts +++ b/packages/bot-engine/continueBotFlow.ts @@ -39,6 +39,7 @@ export const continueBotFlow = async ( reply: string | undefined, { state, version }: Params ): Promise => { + let firstBubbleWasStreamed = false let newSessionState = { ...state } if (!newSessionState.currentBlock) return startBotFlow({ state, version }) @@ -81,6 +82,7 @@ export const continueBotFlow = async ( block.type === IntegrationBlockType.OPEN_AI && block.options.task === 'Create chat completion' ) { + firstBubbleWasStreamed = true if (reply) { const result = await resumeChatCompletion(state, { options: block.options, @@ -125,7 +127,7 @@ export const continueBotFlow = async ( ...group, blocks: group.blocks.slice(blockIndex + 1), }, - { version, state: newSessionState } + { version, state: newSessionState, firstBubbleWasStreamed } ) return { ...chatReply, @@ -157,6 +159,7 @@ export const continueBotFlow = async ( const chatReply = await executeGroup(nextGroup.group, { version, state: newSessionState, + firstBubbleWasStreamed, }) return { diff --git a/packages/bot-engine/executeGroup.ts b/packages/bot-engine/executeGroup.ts index c352640a3..52cdf2c15 100644 --- a/packages/bot-engine/executeGroup.ts +++ b/packages/bot-engine/executeGroup.ts @@ -29,11 +29,18 @@ type ContextProps = { state: SessionState currentReply?: ChatReply currentLastBubbleId?: string + firstBubbleWasStreamed?: boolean } export const executeGroup = async ( group: Group, - { version, state, currentReply, currentLastBubbleId }: ContextProps + { + version, + state, + currentReply, + currentLastBubbleId, + firstBubbleWasStreamed, + }: ContextProps ): Promise => { const messages: ChatReply['messages'] = currentReply?.messages ?? [] let clientSideActions: ChatReply['clientSideActions'] = @@ -44,10 +51,13 @@ export const executeGroup = async ( let newSessionState = state + let index = -1 for (const block of group.blocks) { + index++ nextEdgeId = block.outgoingEdgeId if (isBubbleBlock(block)) { + if (firstBubbleWasStreamed && index === 0) continue messages.push( parseBubbleBlock(block, { version, diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index b939e8fba..374d54702 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/js", - "version": "0.2.5", + "version": "0.2.6", "description": "Javascript library to display typebots on your website", "type": "module", "main": "dist/index.js", diff --git a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx index 9e9d16558..cf59647ef 100644 --- a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx +++ b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx @@ -3,7 +3,6 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/enums import { createEffect, createSignal, - createUniqueId, For, onCleanup, onMount, @@ -109,12 +108,11 @@ export const ConversationContainer = (props: Props) => { })() }) - const streamMessage = (content: string) => { + const streamMessage = ({ id, message }: { id: string; message: string }) => { setIsSending(false) const lastChunk = [...chatChunks()].pop() if (!lastChunk) return - const id = lastChunk.streamingMessageId ?? createUniqueId() - if (!lastChunk.streamingMessageId) + if (lastChunk.streamingMessageId !== id) setChatChunks((displayedChunks) => [ ...displayedChunks, { @@ -122,7 +120,7 @@ export const ConversationContainer = (props: Props) => { streamingMessageId: id, }, ]) - setStreamingMessage({ id, content }) + setStreamingMessage({ id, content: message }) } createEffect(() => { @@ -216,9 +214,7 @@ export const ConversationContainer = (props: Props) => { ...displayedChunks, { input: data.input, - messages: [...chatChunks()].pop()?.streamingMessageId - ? data.messages.slice(1) - : data.messages, + messages: data.messages, clientSideActions: data.clientSideActions, }, ]) diff --git a/packages/embeds/js/src/features/blocks/integrations/openai/streamChat.ts b/packages/embeds/js/src/features/blocks/integrations/openai/streamChat.ts index f3efcabb5..12fcce6c5 100644 --- a/packages/embeds/js/src/features/blocks/integrations/openai/streamChat.ts +++ b/packages/embeds/js/src/features/blocks/integrations/openai/streamChat.ts @@ -1,6 +1,7 @@ import { ClientSideActionContext } from '@/types' import { guessApiHost } from '@/utils/guessApiHost' import { isNotEmpty } from '@typebot.io/lib/utils' +import { createUniqueId } from 'solid-js' let abortController: AbortController | null = null const secondsToWaitBeforeRetries = 3 @@ -13,7 +14,9 @@ export const streamChat = content?: string | undefined role?: 'system' | 'user' | 'assistant' | undefined }[], - { onMessageStream }: { onMessageStream?: (message: string) => void } + { + onMessageStream, + }: { onMessageStream?: (props: { id: string; message: string }) => void } ): Promise<{ message?: string; error?: object }> => { try { abortController = new AbortController() @@ -64,6 +67,8 @@ export const streamChat = const reader = res.body.getReader() const decoder = new TextDecoder() + const id = createUniqueId() + // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read() @@ -72,7 +77,7 @@ export const streamChat = } const chunk = decoder.decode(value) message += chunk - if (onMessageStream) onMessageStream(message) + if (onMessageStream) onMessageStream({ id, message }) if (abortController === null) { reader.cancel() break diff --git a/packages/embeds/js/src/utils/executeClientSideActions.ts b/packages/embeds/js/src/utils/executeClientSideActions.ts index eb2f2502b..37a188b6c 100644 --- a/packages/embeds/js/src/utils/executeClientSideActions.ts +++ b/packages/embeds/js/src/utils/executeClientSideActions.ts @@ -14,7 +14,7 @@ import { injectStartProps } from './injectStartProps' type Props = { clientSideAction: NonNullable[0] context: ClientSideActionContext - onMessageStream?: (message: string) => void + onMessageStream?: (props: { id: string; message: string }) => void } export const executeClientSideAction = async ({ diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json index 433a21a44..8033c7e8d 100644 --- a/packages/embeds/nextjs/package.json +++ b/packages/embeds/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/nextjs", - "version": "0.2.5", + "version": "0.2.6", "description": "Convenient library to display typebots on your Next.js website", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json index 58d806e88..68887caf8 100644 --- a/packages/embeds/react/package.json +++ b/packages/embeds/react/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/react", - "version": "0.2.5", + "version": "0.2.6", "description": "Convenient library to display typebots on your React app", "main": "dist/index.js", "types": "dist/index.d.ts",