@@ -42,7 +42,6 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
|||||||
const [nextPage, setNextPage] = useState(0)
|
const [nextPage, setNextPage] = useState(0)
|
||||||
|
|
||||||
const fetchNewImages = useCallback(async (query: string, page: number) => {
|
const fetchNewImages = useCallback(async (query: string, page: number) => {
|
||||||
console.log('Fetch images', query, page)
|
|
||||||
if (query === '') return searchRandomImages()
|
if (query === '') return searchRandomImages()
|
||||||
if (query.length <= 2) return
|
if (query.length <= 2) return
|
||||||
setError(null)
|
setError(null)
|
||||||
@@ -76,7 +75,6 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
|||||||
if (!bottomAnchor.current) return
|
if (!bottomAnchor.current) return
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
(entities: IntersectionObserverEntry[]) => {
|
(entities: IntersectionObserverEntry[]) => {
|
||||||
console.log('Intersection observer', entities)
|
|
||||||
const target = entities[0]
|
const target = entities[0]
|
||||||
if (target.isIntersecting) fetchNewImages(searchQuery, nextPage + 1)
|
if (target.isIntersecting) fetchNewImages(searchQuery, nextPage + 1)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { SetVariableOptions, Variable } from '@typebot.io/schemas'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
||||||
import { Textarea } from '@/components/inputs'
|
import { Textarea } from '@/components/inputs'
|
||||||
|
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: SetVariableOptions
|
options: SetVariableOptions
|
||||||
@@ -11,16 +12,24 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
||||||
const handleVariableChange = (variable?: Variable) =>
|
const updateVariableId = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable?.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
const handleExpressionChange = (expressionToEvaluate: string) =>
|
|
||||||
|
const updateExpression = (expressionToEvaluate: string) =>
|
||||||
onOptionsChange({ ...options, expressionToEvaluate })
|
onOptionsChange({ ...options, expressionToEvaluate })
|
||||||
const handleValueTypeChange = () =>
|
|
||||||
|
const updateExpressionType = () =>
|
||||||
onOptionsChange({
|
onOptionsChange({
|
||||||
...options,
|
...options,
|
||||||
isCode: options.isCode ? !options.isCode : true,
|
isCode: options.isCode ? !options.isCode : true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const updateClientExecution = (isExecutedOnClient: boolean) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
isExecutedOnClient,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -28,7 +37,7 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
Search or create variable:
|
Search or create variable:
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<VariableSearchInput
|
<VariableSearchInput
|
||||||
onSelectVariable={handleVariableChange}
|
onSelectVariable={updateVariableId}
|
||||||
initialVariableId={options.variableId}
|
initialVariableId={options.variableId}
|
||||||
id="variable-search"
|
id="variable-search"
|
||||||
/>
|
/>
|
||||||
@@ -43,7 +52,7 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
<Switch
|
<Switch
|
||||||
size="sm"
|
size="sm"
|
||||||
isChecked={options.isCode ?? false}
|
isChecked={options.isCode ?? false}
|
||||||
onChange={handleValueTypeChange}
|
onChange={updateExpressionType}
|
||||||
/>
|
/>
|
||||||
<Text fontSize="sm">Code</Text>
|
<Text fontSize="sm">Code</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -52,17 +61,23 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
{options.isCode ?? false ? (
|
{options.isCode ?? false ? (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
defaultValue={options.expressionToEvaluate ?? ''}
|
defaultValue={options.expressionToEvaluate ?? ''}
|
||||||
onChange={handleExpressionChange}
|
onChange={updateExpression}
|
||||||
lang="javascript"
|
lang="javascript"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Textarea
|
<Textarea
|
||||||
id="expression"
|
id="expression"
|
||||||
defaultValue={options.expressionToEvaluate ?? ''}
|
defaultValue={options.expressionToEvaluate ?? ''}
|
||||||
onChange={handleExpressionChange}
|
onChange={updateExpression}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<SwitchWithLabel
|
||||||
|
label="Execute on client?"
|
||||||
|
moreInfoContent="Check this if you need access to client-only variables like `window` or `document`."
|
||||||
|
initialValue={options.isExecutedOnClient ?? false}
|
||||||
|
onCheckChange={updateClientExecution}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ExecuteLogicResponse } from '@/features/chat/types'
|
|||||||
import { extractVariablesFromText } from '@/features/variables/extractVariablesFromText'
|
import { extractVariablesFromText } from '@/features/variables/extractVariablesFromText'
|
||||||
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { ScriptBlock, SessionState } from '@typebot.io/schemas'
|
import { ScriptBlock, SessionState, Variable } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const executeScript = (
|
export const executeScript = (
|
||||||
{ typebot: { variables } }: SessionState,
|
{ typebot: { variables } }: SessionState,
|
||||||
@@ -11,26 +11,37 @@ export const executeScript = (
|
|||||||
): ExecuteLogicResponse => {
|
): ExecuteLogicResponse => {
|
||||||
if (!block.options.content) return { outgoingEdgeId: block.outgoingEdgeId }
|
if (!block.options.content) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
|
|
||||||
const content = parseVariables(variables, { fieldToParse: 'id' })(
|
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||||
|
variables,
|
||||||
block.options.content
|
block.options.content
|
||||||
)
|
)
|
||||||
const args = extractVariablesFromText(variables)(block.options.content).map(
|
|
||||||
(variable) => ({
|
|
||||||
id: variable.id,
|
|
||||||
value: parseGuessedValueType(variable.value),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
clientSideActions: [
|
clientSideActions: [
|
||||||
{
|
{
|
||||||
scriptToExecute: {
|
scriptToExecute: scriptToExecute,
|
||||||
content,
|
|
||||||
args,
|
|
||||||
},
|
|
||||||
lastBubbleBlockId,
|
lastBubbleBlockId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parseScriptToExecuteClientSideAction = (
|
||||||
|
variables: Variable[],
|
||||||
|
contentToEvaluate: string
|
||||||
|
) => {
|
||||||
|
const content = parseVariables(variables, { fieldToParse: 'id' })(
|
||||||
|
contentToEvaluate
|
||||||
|
)
|
||||||
|
const args = extractVariablesFromText(variables)(contentToEvaluate).map(
|
||||||
|
(variable) => ({
|
||||||
|
id: variable.id,
|
||||||
|
value: parseGuessedValueType(variable.value),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,35 @@ import { ExecuteLogicResponse } from '@/features/chat/types'
|
|||||||
import { updateVariables } from '@/features/variables/updateVariables'
|
import { updateVariables } from '@/features/variables/updateVariables'
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
||||||
|
import { parseScriptToExecuteClientSideAction } from '../script/executeScript'
|
||||||
|
|
||||||
export const executeSetVariable = async (
|
export const executeSetVariable = async (
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
block: SetVariableBlock
|
block: SetVariableBlock,
|
||||||
|
lastBubbleBlockId?: string
|
||||||
): Promise<ExecuteLogicResponse> => {
|
): Promise<ExecuteLogicResponse> => {
|
||||||
const { variables } = state.typebot
|
const { variables } = state.typebot
|
||||||
if (!block.options?.variableId)
|
if (!block.options?.variableId)
|
||||||
return {
|
return {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
}
|
}
|
||||||
|
if (block.options.isExecutedOnClient && block.options.expressionToEvaluate) {
|
||||||
|
const scriptToExecute = parseScriptToExecuteClientSideAction(
|
||||||
|
state.typebot.variables,
|
||||||
|
block.options.expressionToEvaluate
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
clientSideActions: [
|
||||||
|
{
|
||||||
|
setVariable: {
|
||||||
|
scriptToExecute,
|
||||||
|
},
|
||||||
|
lastBubbleBlockId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
const evaluatedExpression = block.options.expressionToEvaluate
|
const evaluatedExpression = block.options.expressionToEvaluate
|
||||||
? evaluateSetVariableExpression(variables)(
|
? evaluateSetVariableExpression(variables)(
|
||||||
block.options.expressionToEvaluate
|
block.options.expressionToEvaluate
|
||||||
|
|||||||
@@ -84,8 +84,13 @@ export const sendMessage = publicProcedure
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const containsSetVariableClientSideAction = clientSideActions?.some(
|
||||||
|
(action) => 'setVariable' in action
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!input &&
|
!input &&
|
||||||
|
!containsSetVariableClientSideAction &&
|
||||||
session.state.result.answers.length > 0 &&
|
session.state.result.answers.length > 0 &&
|
||||||
session.state.result.id
|
session.state.result.id
|
||||||
)
|
)
|
||||||
@@ -149,42 +154,33 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
|||||||
dynamicTheme: parseDynamicThemeInState(typebot.theme),
|
dynamicTheme: parseDynamicThemeInState(typebot.theme),
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { messages, input, clientSideActions, newSessionState, logs } =
|
||||||
messages,
|
await startBotFlow(initialState, startParams.startGroupId)
|
||||||
input,
|
|
||||||
clientSideActions,
|
|
||||||
newSessionState: newInitialState,
|
|
||||||
logs,
|
|
||||||
} = await startBotFlow(initialState, startParams.startGroupId)
|
|
||||||
|
|
||||||
if (!input)
|
const containsSetVariableClientSideAction = clientSideActions?.some(
|
||||||
|
(action) => 'setVariable' in action
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!input && !containsSetVariableClientSideAction)
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
typebot: {
|
typebot: {
|
||||||
id: typebot.id,
|
id: typebot.id,
|
||||||
settings: deepParseVariables(newInitialState.typebot.variables)(
|
settings: deepParseVariables(newSessionState.typebot.variables)(
|
||||||
typebot.settings
|
typebot.settings
|
||||||
),
|
),
|
||||||
theme: deepParseVariables(newInitialState.typebot.variables)(
|
theme: deepParseVariables(newSessionState.typebot.variables)(
|
||||||
typebot.theme
|
typebot.theme
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
dynamicTheme: parseDynamicThemeReply(newInitialState),
|
dynamicTheme: parseDynamicThemeReply(newSessionState),
|
||||||
logs,
|
logs,
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionState: ChatSession['state'] = {
|
|
||||||
...newInitialState,
|
|
||||||
currentBlock: {
|
|
||||||
groupId: input.groupId,
|
|
||||||
blockId: input.id,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = (await prisma.chatSession.create({
|
const session = (await prisma.chatSession.create({
|
||||||
data: {
|
data: {
|
||||||
state: sessionState,
|
state: newSessionState,
|
||||||
},
|
},
|
||||||
})) as ChatSession
|
})) as ChatSession
|
||||||
|
|
||||||
@@ -193,17 +189,17 @@ const startSession = async (startParams?: StartParams, userId?: string) => {
|
|||||||
sessionId: session.id,
|
sessionId: session.id,
|
||||||
typebot: {
|
typebot: {
|
||||||
id: typebot.id,
|
id: typebot.id,
|
||||||
settings: deepParseVariables(newInitialState.typebot.variables)(
|
settings: deepParseVariables(newSessionState.typebot.variables)(
|
||||||
typebot.settings
|
typebot.settings
|
||||||
),
|
),
|
||||||
theme: deepParseVariables(newInitialState.typebot.variables)(
|
theme: deepParseVariables(newSessionState.typebot.variables)(
|
||||||
typebot.theme
|
typebot.theme
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
messages,
|
messages,
|
||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
dynamicTheme: parseDynamicThemeReply(newInitialState),
|
dynamicTheme: parseDynamicThemeReply(newSessionState),
|
||||||
logs,
|
logs,
|
||||||
} satisfies ChatReply
|
} satisfies ChatReply
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import {
|
|||||||
ChatReply,
|
ChatReply,
|
||||||
InputBlock,
|
InputBlock,
|
||||||
InputBlockType,
|
InputBlockType,
|
||||||
|
LogicBlockType,
|
||||||
ResultInSession,
|
ResultInSession,
|
||||||
SessionState,
|
SessionState,
|
||||||
|
SetVariableBlock,
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isInputBlock, isNotDefined } from '@typebot.io/lib'
|
import { isInputBlock, isNotDefined, byId, isDefined } from '@typebot.io/lib'
|
||||||
import { executeGroup } from './executeGroup'
|
import { executeGroup } from './executeGroup'
|
||||||
import { getNextGroup } from './getNextGroup'
|
import { getNextGroup } from './getNextGroup'
|
||||||
import { validateEmail } from '@/features/blocks/inputs/email/validateEmail'
|
import { validateEmail } from '@/features/blocks/inputs/email/validateEmail'
|
||||||
@@ -27,6 +29,7 @@ export const continueBotFlow =
|
|||||||
async (
|
async (
|
||||||
reply?: string
|
reply?: string
|
||||||
): Promise<ChatReply & { newSessionState?: SessionState }> => {
|
): Promise<ChatReply & { newSessionState?: SessionState }> => {
|
||||||
|
let newSessionState = { ...state }
|
||||||
const group = state.typebot.groups.find(
|
const group = state.typebot.groups.find(
|
||||||
(group) => group.id === state.currentBlock?.groupId
|
(group) => group.id === state.currentBlock?.groupId
|
||||||
)
|
)
|
||||||
@@ -43,25 +46,37 @@ export const continueBotFlow =
|
|||||||
message: 'Current block not found',
|
message: 'Current block not found',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isInputBlock(block))
|
if (block.type === LogicBlockType.SET_VARIABLE && isDefined(reply)) {
|
||||||
|
const existingVariable = state.typebot.variables.find(
|
||||||
|
byId(block.options.variableId)
|
||||||
|
)
|
||||||
|
if (existingVariable) {
|
||||||
|
const newVariable = {
|
||||||
|
...existingVariable,
|
||||||
|
value: reply,
|
||||||
|
}
|
||||||
|
newSessionState = await updateVariables(state)([newVariable])
|
||||||
|
}
|
||||||
|
} else if (!isInputBlock(block))
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'Current block is not an input block',
|
message: 'Current block is not an input block',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (reply && !isReplyValid(reply, block)) return parseRetryMessage(block)
|
let formattedReply = null
|
||||||
|
|
||||||
const formattedReply = formatReply(reply, block.type)
|
if (isInputBlock(block)) {
|
||||||
|
if (reply && !isReplyValid(reply, block)) return parseRetryMessage(block)
|
||||||
|
|
||||||
if (!formattedReply && !canSkip(block.type)) {
|
formattedReply = formatReply(reply, block.type)
|
||||||
return parseRetryMessage(block)
|
|
||||||
|
if (!formattedReply && !canSkip(block.type)) {
|
||||||
|
return parseRetryMessage(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSessionState = await processAndSaveAnswer(state, block)(formattedReply)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSessionState = await processAndSaveAnswer(
|
|
||||||
state,
|
|
||||||
block
|
|
||||||
)(formattedReply)
|
|
||||||
|
|
||||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||||
|
|
||||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||||
@@ -221,7 +236,7 @@ const computeStorageUsed = async (reply: string) => {
|
|||||||
|
|
||||||
const getOutgoingEdgeId =
|
const getOutgoingEdgeId =
|
||||||
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
({ typebot: { variables } }: Pick<SessionState, 'typebot'>) =>
|
||||||
(block: InputBlock, reply: string | null) => {
|
(block: InputBlock | SetVariableBlock, reply: string | null) => {
|
||||||
if (
|
if (
|
||||||
block.type === InputBlockType.CHOICE &&
|
block.type === InputBlockType.CHOICE &&
|
||||||
!block.options.isMultipleChoice &&
|
!block.options.isMultipleChoice &&
|
||||||
|
|||||||
@@ -75,11 +75,31 @@ export const executeGroup =
|
|||||||
if (
|
if (
|
||||||
'clientSideActions' in executionResponse &&
|
'clientSideActions' in executionResponse &&
|
||||||
executionResponse.clientSideActions
|
executionResponse.clientSideActions
|
||||||
)
|
) {
|
||||||
clientSideActions = [
|
clientSideActions = [
|
||||||
...(clientSideActions ?? []),
|
...(clientSideActions ?? []),
|
||||||
...executionResponse.clientSideActions,
|
...executionResponse.clientSideActions,
|
||||||
]
|
]
|
||||||
|
if (
|
||||||
|
executionResponse.clientSideActions?.find(
|
||||||
|
(action) => 'setVariable' in action
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
newSessionState: {
|
||||||
|
...newSessionState,
|
||||||
|
currentBlock: {
|
||||||
|
groupId: group.id,
|
||||||
|
blockId: block.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clientSideActions,
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (executionResponse.logs)
|
if (executionResponse.logs)
|
||||||
logs = [...(logs ?? []), ...executionResponse.logs]
|
logs = [...(logs ?? []), ...executionResponse.logs]
|
||||||
if (executionResponse.newSessionState)
|
if (executionResponse.newSessionState)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const executeLogic =
|
|||||||
async (block: LogicBlock): Promise<ExecuteLogicResponse> => {
|
async (block: LogicBlock): Promise<ExecuteLogicResponse> => {
|
||||||
switch (block.type) {
|
switch (block.type) {
|
||||||
case LogicBlockType.SET_VARIABLE:
|
case LogicBlockType.SET_VARIABLE:
|
||||||
return executeSetVariable(state, block)
|
return executeSetVariable(state, block, lastBubbleBlockId)
|
||||||
case LogicBlockType.CONDITION:
|
case LogicBlockType.CONDITION:
|
||||||
return executeCondition(state, block)
|
return executeCondition(state, block)
|
||||||
case LogicBlockType.REDIRECT:
|
case LogicBlockType.REDIRECT:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.0.35",
|
"version": "0.0.36",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createSignal, onCleanup, onMount } from 'solid-js'
|
|||||||
import { isMobile } from '@/utils/isMobileSignal'
|
import { isMobile } from '@/utils/isMobileSignal'
|
||||||
import { Avatar } from '../avatars/Avatar'
|
import { Avatar } from '../avatars/Avatar'
|
||||||
|
|
||||||
type Props = { hostAvatarSrc?: string }
|
type Props = { hostAvatarSrc?: string; hideAvatar?: boolean }
|
||||||
|
|
||||||
export const AvatarSideContainer = (props: Props) => {
|
export const AvatarSideContainer = (props: Props) => {
|
||||||
let avatarContainer: HTMLDivElement | undefined
|
let avatarContainer: HTMLDivElement | undefined
|
||||||
@@ -34,12 +34,13 @@ export const AvatarSideContainer = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class={
|
class={
|
||||||
'absolute mb-2 flex items-center top-0 ' +
|
'absolute mb-2 flex items-center top-0' +
|
||||||
(isMobile() ? 'w-6 h-6' : 'w-10 h-10')
|
(isMobile() ? ' w-6 h-6' : ' w-10 h-10') +
|
||||||
|
(props.hideAvatar ? ' opacity-0' : ' opacity-100')
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
top: `${top()}px`,
|
top: `${top()}px`,
|
||||||
transition: 'top 350ms ease-out',
|
transition: 'top 350ms ease-out, opacity 250ms ease-out',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar initialAvatarSrc={props.hostAvatarSrc} />
|
<Avatar initialAvatarSrc={props.hostAvatarSrc} />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Props = Pick<ChatReply, 'messages' | 'input'> & {
|
|||||||
context: BotContext
|
context: BotContext
|
||||||
isLoadingBubbleDisplayed: boolean
|
isLoadingBubbleDisplayed: boolean
|
||||||
hasError: boolean
|
hasError: boolean
|
||||||
|
hideAvatar: boolean
|
||||||
onNewBubbleDisplayed: (blockId: string) => Promise<void>
|
onNewBubbleDisplayed: (blockId: string) => Promise<void>
|
||||||
onScrollToBottom: () => void
|
onScrollToBottom: () => void
|
||||||
onSubmit: (input: string) => void
|
onSubmit: (input: string) => void
|
||||||
@@ -56,6 +57,7 @@ export const ChatChunk = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<AvatarSideContainer
|
<AvatarSideContainer
|
||||||
hostAvatarSrc={props.theme.chat.hostAvatar?.url}
|
hostAvatarSrc={props.theme.chat.hostAvatar?.url}
|
||||||
|
hideAvatar={props.hideAvatar}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -70,7 +70,12 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
)
|
)
|
||||||
for (const action of actionsBeforeFirstBubble) {
|
for (const action of actionsBeforeFirstBubble) {
|
||||||
const response = await executeClientSideAction(action)
|
const response = await executeClientSideAction(action)
|
||||||
if (response) setBlockedPopupUrl(response.blockedPopupUrl)
|
if (response && 'replyToSend' in response) {
|
||||||
|
sendMessage(response.replyToSend)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response && 'blockedPopupUrl' in response)
|
||||||
|
setBlockedPopupUrl(response.blockedPopupUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -122,7 +127,12 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
)
|
)
|
||||||
for (const action of actionsBeforeFirstBubble) {
|
for (const action of actionsBeforeFirstBubble) {
|
||||||
const response = await executeClientSideAction(action)
|
const response = await executeClientSideAction(action)
|
||||||
if (response) setBlockedPopupUrl(response.blockedPopupUrl)
|
if (response && 'replyToSend' in response) {
|
||||||
|
sendMessage(response.replyToSend)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response && 'blockedPopupUrl' in response)
|
||||||
|
setBlockedPopupUrl(response.blockedPopupUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setChatChunks((displayedChunks) => [
|
setChatChunks((displayedChunks) => [
|
||||||
@@ -159,7 +169,12 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
)
|
)
|
||||||
for (const action of actionsToExecute) {
|
for (const action of actionsToExecute) {
|
||||||
const response = await executeClientSideAction(action)
|
const response = await executeClientSideAction(action)
|
||||||
if (response) setBlockedPopupUrl(response.blockedPopupUrl)
|
if (response && 'replyToSend' in response) {
|
||||||
|
sendMessage(response.replyToSend)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response && 'blockedPopupUrl' in response)
|
||||||
|
setBlockedPopupUrl(response.blockedPopupUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +202,7 @@ export const ConversationContainer = (props: Props) => {
|
|||||||
onSkip={handleSkip}
|
onSkip={handleSkip}
|
||||||
context={props.context}
|
context={props.context}
|
||||||
hasError={hasError() && index() === chatChunks().length - 1}
|
hasError={hasError() && index() === chatChunks().length - 1}
|
||||||
|
hideAvatar={!chatChunk.input && index() < chatChunks().length - 1}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { isNotDefined } from '@typebot.io/lib'
|
||||||
|
import type { ScriptToExecute } from '@typebot.io/schemas'
|
||||||
|
|
||||||
|
export const executeSetVariable = async ({
|
||||||
|
content,
|
||||||
|
args,
|
||||||
|
}: ScriptToExecute): Promise<{ replyToSend: string | undefined }> => {
|
||||||
|
try {
|
||||||
|
const func = Function(
|
||||||
|
...args.map((arg) => arg.id),
|
||||||
|
content.includes('return ') ? content : `return ${content}`
|
||||||
|
)
|
||||||
|
const replyToSend = await func(...args.map((arg) => arg.value))
|
||||||
|
return {
|
||||||
|
replyToSend: safeStringify(replyToSend),
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
replyToSend: safeStringify(content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const safeStringify = (val: unknown): string | undefined => {
|
||||||
|
if (isNotDefined(val)) return
|
||||||
|
if (typeof val === 'string') return val
|
||||||
|
try {
|
||||||
|
return JSON.stringify(val)
|
||||||
|
} catch {
|
||||||
|
console.warn('Failed to safely stringify variable value', val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,15 @@ import { executeChatwoot } from '@/features/blocks/integrations/chatwoot'
|
|||||||
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
|
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
|
||||||
import { executeRedirect } from '@/features/blocks/logic/redirect'
|
import { executeRedirect } from '@/features/blocks/logic/redirect'
|
||||||
import { executeScript } from '@/features/blocks/logic/script/executeScript'
|
import { executeScript } from '@/features/blocks/logic/script/executeScript'
|
||||||
|
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
|
||||||
import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
import { executeWait } from '@/features/blocks/logic/wait/utils/executeWait'
|
||||||
import type { ChatReply } from '@typebot.io/schemas'
|
import type { ChatReply } from '@typebot.io/schemas'
|
||||||
|
|
||||||
export const executeClientSideAction = async (
|
export const executeClientSideAction = async (
|
||||||
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
|
clientSideAction: NonNullable<ChatReply['clientSideActions']>[0]
|
||||||
): Promise<{ blockedPopupUrl: string } | void> => {
|
): Promise<
|
||||||
|
{ blockedPopupUrl: string } | { replyToSend: string | undefined } | void
|
||||||
|
> => {
|
||||||
if ('chatwoot' in clientSideAction) {
|
if ('chatwoot' in clientSideAction) {
|
||||||
return executeChatwoot(clientSideAction.chatwoot)
|
return executeChatwoot(clientSideAction.chatwoot)
|
||||||
}
|
}
|
||||||
@@ -23,4 +26,7 @@ export const executeClientSideAction = async (
|
|||||||
if ('wait' in clientSideAction) {
|
if ('wait' in clientSideAction) {
|
||||||
return executeWait(clientSideAction.wait)
|
return executeWait(clientSideAction.wait)
|
||||||
}
|
}
|
||||||
|
if ('setVariable' in clientSideAction) {
|
||||||
|
return executeSetVariable(clientSideAction.setVariable.scriptToExecute)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.0.35",
|
"version": "0.0.36",
|
||||||
"description": "React library to display typebots on your website",
|
"description": "React library to display typebots on your website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const setVariableOptionsSchema = z.object({
|
|||||||
variableId: z.string().optional(),
|
variableId: z.string().optional(),
|
||||||
expressionToEvaluate: z.string().optional(),
|
expressionToEvaluate: z.string().optional(),
|
||||||
isCode: z.boolean().optional(),
|
isCode: z.boolean().optional(),
|
||||||
|
isExecutedOnClient: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setVariableBlockSchema = blockBaseSchema.merge(
|
export const setVariableBlockSchema = blockBaseSchema.merge(
|
||||||
|
|||||||
@@ -220,6 +220,11 @@ const clientSideActionSchema = z
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
.or(
|
||||||
|
z.object({
|
||||||
|
setVariable: z.object({ scriptToExecute: scriptToExecuteSchema }),
|
||||||
|
})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const chatReplySchema = z.object({
|
export const chatReplySchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user