Run
A lógica de execução das ações fica em um arquivo handlers.ts separado, com as funções createActionHandler() e createFetcherHandler(). Isso separa a definição da ação — esquema e opções — da implementação em tempo de execução.
Um handler de ação pode fazer uma destas coisas:
- Executar uma função no servidor (o caso mais comum)
- Se o bloco for seguido por uma variável transmitível, transmitir a variável no cliente. Caso contrário, executar uma função no servidor.
- Executar uma função no cliente
- Exibir um balão incorporado personalizado
Estrutura do arquivo de handlers
Todos os handlers de um bloco devem ficar em src/handlers.ts, exportados como um vetor padrão:
import { createActionHandler, createFetcherHandler } from "@typebot.io/forge";
import { sendMessage, modelsFetcher } from "./actions/sendMessage";
import { getMessage } from "./actions/getMessage";
export default [
createActionHandler(sendMessage, {
server: async ({ credentials, options, variables, logs }) => {
// Implementation for sendMessage
},
}),
createFetcherHandler(
sendMessage,
modelsFetcher.id,
async ({ credentials, options }) => {
// Implementation for fetcher
return {
data: ["model-1", "model-2"],
};
}
),
createActionHandler(getMessage, {
server: async ({ credentials, options, variables, logs }) => {
// Implementation for getMessage
},
}),
];O vetor de handlers aceita dois tipos:
- Handlers de ação, com
createActionHandler(action, implementation)— executam a lógica da ação - Handlers de buscador, com
createFetcherHandler(action, fetcherId, implementation)— preenchem listas suspensas dinamicamente (veja Fetcher para mais detalhes)
Função no servidor
O handler mais comum executa uma função no servidor.
Exemplo:
import { createActionHandler } from "@typebot.io/forge";
import { sendMessage } from "./actions/sendMessage";
import { ky } from "@typebot.io/lib/ky";
export default [
createActionHandler(sendMessage, {
server: async ({
credentials: { apiKey },
options: { botId, message, responseMapping, threadId },
variables,
logs,
}) => {
const res: ChatNodeResponse = await ky
.post(apiBaseUrl + botId, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
json: {
message,
chat_session_id: isEmpty(threadId) ? undefined : threadId,
},
})
.json();
if (res.error)
logs.add({
status: "error",
description: res.error,
});
responseMapping?.forEach((mapping) => {
if (!mapping.variableId) return;
const item = mapping.item ?? "Message";
if (item === "Message") variables.set(mapping.variableId, res.message);
if (item === "Thread ID")
variables.set(mapping.variableId, res.chat_session_id);
});
},
}),
];Como você pode ver, a função do servidor recebe credentials, options, variables e logs como argumentos.
credentials são as credenciais que o usuário informou no bloco de credenciais.
options são as opções que o usuário informou no bloco de opções.
O objeto variables traz auxiliares para salvar e ler variáveis, quando necessário.
logs permite registrar qualquer coisa durante a execução. Esses registros aparecem como notificação no modo de pré-visualização, ou na aba Resultados em produção.
Função no servidor + transmissão
Se o seu bloco pode transmitir uma mensagem em tempo real (como o da OpenAI), você precisa definir getStreamVariableId na ação e implementar um handler de transmissão.
Na definição da ação (actions/createChatCompletion.ts):
import { createAction, option } from "@typebot.io/forge";
import { auth } from "../auth";
export const createChatCompletion = createAction({
auth,
name: "Create chat completion",
options: option.object({
// ... options
}),
getStreamVariableId: (options) =>
options.responseMapping?.find(
(res) => res.item === "Message content" || !res.item
)?.variableId,
});No arquivo de handlers (handlers.ts):
import { createActionHandler } from "@typebot.io/forge";
import { createChatCompletion } from "./actions/createChatCompletion";
import { OpenAI } from "openai";
import { OpenAIStream } from "@typebot.io/ai";
export default [
createActionHandler(createChatCompletion, {
server: async (params) => {
// Server implementation when streaming is not available
},
stream: {
run: async ({ credentials: { apiKey }, options, variables }) => {
const config = {
apiKey,
baseURL: options.baseUrl,
defaultHeaders: {
"api-key": apiKey,
},
defaultQuery: options.apiVersion
? {
"api-version": options.apiVersion,
}
: undefined,
} satisfies ClientOptions;
const openai = new OpenAI(config);
const response = await openai.chat.completions.create({
model: options.model ?? defaultOpenAIOptions.model,
temperature: options.temperature
? Number(options.temperature)
: undefined,
stream: true,
messages: parseChatCompletionMessages({ options, variables }),
});
return { stream: OpenAIStream(response) };
},
},
}),
];A função getStreamVariableId, na definição da ação, determina qual variável será transmitida.
A função run da transmissão precisa devolver Promise<{ stream?: ReadableStream<any>, error?: { description: string, details?: string, context?: string } }>.
Função no cliente
Se você quiser executar uma função no cliente em vez do servidor, use o objeto web no seu handler.
Isso torna seu bloco compatível apenas com o ambiente Web. Ele não funcionará no WhatsApp, por exemplo: o bloco será simplesmente ignorado.
Exemplo:
import { createActionHandler } from "@typebot.io/forge";
import { shoutName } from "./actions/shoutName";
export default [
createActionHandler(shoutName, {
web: {
parseFunction: ({ options }) => {
return {
args: {
name: options.name ?? null,
},
content: `alert('Hello ' + name)`,
};
},
},
}),
];A função web precisa devolver um objeto com args e content.
args é um objeto com os argumentos passados ao contexto de content. Atenção: os argumentos não podem ser undefined — se precisar passar um argumento não definido, use null.
content é o código que será executado no cliente. Ele pode usar os argumentos passados em args.
Exibir balão incorporado
Se você quiser exibir um balão incorporado personalizado, defina getEmbedSaveVariableId na ação (caso queira salvar os dados do evento) e implemente o handler correspondente. Veja o bloco do Cal.com como exemplo.
Na definição da ação (actions/bookEvent.ts):
import { createAction, option } from "@typebot.io/forge";
import { auth } from "../auth";
export const bookEvent = createAction({
auth,
name: "Book event",
options: option.object({
// ... options
saveResultInVariableId: option.string.meta({ layout: {
inputType: "variableDropdown",
} }),
}),
getEmbedSaveVariableId: (options) => options.saveResultInVariableId,
});No arquivo de handlers (handlers.ts):
import { createActionHandler } from "@typebot.io/forge";
import { bookEvent } from "./actions/bookEvent";
export default [
createActionHandler(bookEvent, {
web: {
displayEmbedBubble: {
parseUrl: ({ options }) => options.url,
parseInitFunction: ({ options }) => ({
args: {
cal: options.cal,
},
content: `// Initialize embed with typebotElement`,
}),
waitForEvent: {
parseFunction: () => ({
args: {},
content: `// Handle event with continueFlow()`,
}),
},
},
},
}),
];A função getEmbedSaveVariableId, na definição da ação, determina qual variável guardará os dados do evento.
O objeto displayEmbedBubble do handler exige:
parseUrl: devolve uma URL a ser exibida como balão de texto em ambientes onde o código não pode ser executado (no WhatsApp, por exemplo)parseInitFunction: devolve uma função a executar na inicialização. O conteúdo dela pode usar a variáveltypebotElementpara obter o elemento DOM onde o bloco é renderizado.waitForEvent.parseFunction(opcional): devolve uma função para tratar o evento. O conteúdo dela pode usar a funçãocontinueFlowpara continuar o fluxo com os dados do evento.