diff --git a/apps/builder/src/features/forge/components/zodLayouts/ZodFieldLayout.tsx b/apps/builder/src/features/forge/components/zodLayouts/ZodFieldLayout.tsx index f4065db99..1d1fcdc49 100644 --- a/apps/builder/src/features/forge/components/zodLayouts/ZodFieldLayout.tsx +++ b/apps/builder/src/features/forge/components/zodLayouts/ZodFieldLayout.tsx @@ -132,7 +132,13 @@ export const ZodFieldLayout = ({ !layout.hiddenItems.includes(v) + ) + : innerSchema._def.values + } label={layout?.label} helperText={ layout?.helperText ? ( diff --git a/apps/docs/editor/blocks/integrations/dify-ai.mdx b/apps/docs/editor/blocks/integrations/dify-ai.mdx new file mode 100644 index 000000000..94df8265c --- /dev/null +++ b/apps/docs/editor/blocks/integrations/dify-ai.mdx @@ -0,0 +1,15 @@ +--- +title: Dify.AI +--- + +This block allows you to integrate your Dify.AI's assistant in your typebot. + +## Create Chat Message + +This action sends a user message to your agent. Then you can save `Answer` to a variable and display it in your typebot. + +You are expected to provide the following parameters: + +- `Query`: The user message you want to send to your agent. +- `Conversation ID`: The conversation ID you want to use for this message. If you don't provide one, a new conversation will be created. This variable content will be updated automatically if a new conversation is created. +- `User`: The user email used to identify the user in the conversation. diff --git a/apps/docs/mint.json b/apps/docs/mint.json index 133be9b85..26e11c0ff 100644 --- a/apps/docs/mint.json +++ b/apps/docs/mint.json @@ -125,7 +125,8 @@ "editor/blocks/integrations/zemantic-ai", "editor/blocks/integrations/mistral", "editor/blocks/integrations/elevenlabs", - "editor/blocks/integrations/anthropic" + "editor/blocks/integrations/anthropic", + "editor/blocks/integrations/dify-ai" ] } ] diff --git a/apps/docs/openapi/builder.json b/apps/docs/openapi/builder.json index 00a19d87d..34ac47a85 100644 --- a/apps/docs/openapi/builder.json +++ b/apps/docs/openapi/builder.json @@ -19591,7 +19591,7 @@ "query": { "type": "string" }, - "conversation_id": { + "conversationVariableId": { "type": "string" }, "user": { @@ -19629,6 +19629,10 @@ } } } + }, + "conversation_id": { + "type": "string", + "description": "Deprecated, use `conversationVariableId` instead" } }, "required": [ diff --git a/apps/docs/openapi/viewer.json b/apps/docs/openapi/viewer.json index 4fbf3df04..6bde40df2 100644 --- a/apps/docs/openapi/viewer.json +++ b/apps/docs/openapi/viewer.json @@ -10419,7 +10419,7 @@ "query": { "type": "string" }, - "conversation_id": { + "conversationVariableId": { "type": "string" }, "user": { @@ -10457,6 +10457,10 @@ } } } + }, + "conversation_id": { + "type": "string", + "description": "Deprecated, use `conversationVariableId` instead" } }, "required": [ diff --git a/packages/forge/blocks/difyAi/actions/createChatMessage.ts b/packages/forge/blocks/difyAi/actions/createChatMessage.ts index 338ac0864..b7d66feea 100644 --- a/packages/forge/blocks/difyAi/actions/createChatMessage.ts +++ b/packages/forge/blocks/difyAi/actions/createChatMessage.ts @@ -4,45 +4,67 @@ import { auth } from '../auth' import { defaultBaseUrl } from '../constants' import { Chunk } from '../types' import ky from 'ky' +import { deprecatedCreateChatMessageOptions } from '../deprecated' export const createChatMessage = createAction({ auth, name: 'Create Chat Message', - options: option.object({ - query: option.string.layout({ - label: 'Query', - placeholder: 'User input/question content', - inputType: 'textarea', - isRequired: true, - }), - conversation_id: option.string.layout({ - label: 'Conversation ID', - moreInfoTooltip: - 'Used to remember the conversation with the user. If empty, a new conversation id is created.', - }), - user: option.string.layout({ - label: 'User', - moreInfoTooltip: - 'The user identifier, defined by the developer, must ensure uniqueness within the app.', - }), - inputs: option.keyValueList.layout({ - accordion: 'Inputs', - }), - responseMapping: option - .saveResponseArray(['Answer', 'Conversation ID', 'Total Tokens'] as const) - .layout({ - accordion: 'Save response', + options: option + .object({ + query: option.string.layout({ + label: 'Query', + placeholder: 'User input/question content', + inputType: 'textarea', + isRequired: true, }), - }), + + conversationVariableId: option.string.layout({ + label: 'Conversation ID', + moreInfoTooltip: + 'Used to remember the conversation with the user. If empty, a new conversation ID is created.', + inputType: 'variableDropdown', + }), + user: option.string.layout({ + label: 'User', + moreInfoTooltip: + 'The user identifier, defined by the developer, must ensure uniqueness within the app.', + }), + inputs: option.keyValueList.layout({ + accordion: 'Inputs', + }), + responseMapping: option + .saveResponseArray( + ['Answer', 'Conversation ID', 'Total Tokens'] as const, + { + item: { + hiddenItems: ['Conversation ID'], + }, + } + ) + .layout({ + accordion: 'Save response', + }), + }) + .merge(deprecatedCreateChatMessageOptions), getSetVariableIds: ({ responseMapping }) => responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [], run: { server: async ({ credentials: { apiEndpoint, apiKey }, - options: { conversation_id, query, user, inputs, responseMapping }, + options: { + conversationVariableId, + conversation_id, + query, + user, + inputs, + responseMapping, + }, variables, logs, }) => { + const existingDifyConversationId = conversationVariableId + ? variables.get(conversationVariableId) + : conversation_id try { const response = await ky( (apiEndpoint ?? defaultBaseUrl) + '/v1/chat-messages', @@ -63,7 +85,7 @@ export const createChatMessage = createAction({ }, {}) ?? {}, query, response_mode: 'streaming', - conversation_id, + conversation_id: existingDifyConversationId, user, files: [], }), @@ -139,9 +161,20 @@ export const createChatMessage = createAction({ if (item === 'Answer') variables.set(mapping.variableId, convertNonMarkdownLinks(answer)) - if (item === 'Conversation ID' && isNotEmpty(conversationId)) + if ( + item === 'Conversation ID' && + isNotEmpty(conversationId) && + isEmpty(existingDifyConversationId?.toString()) + ) variables.set(mapping.variableId, conversationId) + if ( + conversationVariableId && + isNotEmpty(conversationId) && + isEmpty(existingDifyConversationId?.toString()) + ) + variables.set(conversationVariableId, conversationId) + if (item === 'Total Tokens') variables.set(mapping.variableId, totalTokens) }) diff --git a/packages/forge/blocks/difyAi/deprecated.ts b/packages/forge/blocks/difyAi/deprecated.ts new file mode 100644 index 000000000..c3efad9a7 --- /dev/null +++ b/packages/forge/blocks/difyAi/deprecated.ts @@ -0,0 +1,12 @@ +import { option } from '@typebot.io/forge' + +export const deprecatedCreateChatMessageOptions = option.object({ + conversation_id: option.string + .layout({ + label: 'Conversation ID', + moreInfoTooltip: + 'Used to remember the conversation with the user. If empty, a new conversation id is created.', + isHidden: true, + }) + .describe('Deprecated, use `conversationVariableId` instead'), +}) diff --git a/packages/forge/core/index.ts b/packages/forge/core/index.ts index eafddba43..30e3f4961 100644 --- a/packages/forge/core/index.ts +++ b/packages/forge/core/index.ts @@ -1,6 +1,6 @@ -import { ZodRawShape } from 'zod' +import { ZodRawShape, ZodTypeAny } from 'zod' import { AuthDefinition, BlockDefinition, ActionDefinition } from './types' -import { z } from './zod' +import { ZodLayoutMetadata, z } from './zod' export const variableStringSchema = z.custom<`{{${string}}}`>((val) => /^{{.+}}$/g.test(val as string) @@ -137,17 +137,31 @@ export const option = { z.object({ [field]: z.undefined() }), ...schemas, ]), - saveResponseArray: (items: I) => + saveResponseArray: ( + items: I, + layouts?: { + item?: ZodLayoutMetadata + variableId?: ZodLayoutMetadata + } + ) => z .array( z.object({ - item: z.enum(items).optional().layout({ - placeholder: 'Select a response', - defaultValue: items[0], - }), - variableId: z.string().optional().layout({ - inputType: 'variableDropdown', - }), + item: z + .enum(items) + .optional() + .layout({ + ...(layouts?.item ?? {}), + placeholder: 'Select a response', + defaultValue: items[0], + }), + variableId: z + .string() + .optional() + .layout({ + ...(layouts?.variableId ?? {}), + inputType: 'variableDropdown', + }), }) ) .optional(), diff --git a/packages/forge/core/zod/extendWithTypebotLayout.ts b/packages/forge/core/zod/extendWithTypebotLayout.ts index e418313cc..87b805ef1 100644 --- a/packages/forge/core/zod/extendWithTypebotLayout.ts +++ b/packages/forge/core/zod/extendWithTypebotLayout.ts @@ -22,6 +22,7 @@ export interface ZodLayoutMetadata< moreInfoTooltip?: string isHidden?: boolean isDebounceDisabled?: boolean + hiddenItems?: string[] } declare module 'zod' {