✨ Add Zemantic AI Integration block (#752)
Co-authored-by: Baptiste Arnaud <contact@baptiste-arnaud.fr>
This commit is contained in:
@ -99,9 +99,10 @@ export const NumberInput = <HasVariable extends boolean>({
|
||||
isRequired={isRequired}
|
||||
justifyContent="space-between"
|
||||
width={label ? 'full' : 'auto'}
|
||||
spacing={0}
|
||||
>
|
||||
{label && (
|
||||
<FormLabel mb="0" flexShrink={0}>
|
||||
<FormLabel mb="2" flexShrink={0}>
|
||||
{label}{' '}
|
||||
{moreInfoTooltip && (
|
||||
<MoreInfoTooltip>{moreInfoTooltip}</MoreInfoTooltip>
|
||||
|
@ -0,0 +1,47 @@
|
||||
import { Select } from '@/components/inputs/Select'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
|
||||
type Props = {
|
||||
credentialsId: string
|
||||
blockId: string
|
||||
defaultValue: string
|
||||
onChange: (projectId: string | undefined) => void
|
||||
}
|
||||
|
||||
export const ProjectsDropdown = ({
|
||||
defaultValue,
|
||||
onChange,
|
||||
credentialsId,
|
||||
}: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const { workspace } = useWorkspace()
|
||||
const { showToast } = useToast()
|
||||
|
||||
const { data } = trpc.zemanticAi.listProjects.useQuery(
|
||||
{
|
||||
credentialsId,
|
||||
workspaceId: workspace?.id as string,
|
||||
},
|
||||
{
|
||||
enabled: !!typebot && !!workspace,
|
||||
onError: (error) => {
|
||||
showToast({
|
||||
description: error.message,
|
||||
status: 'error',
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={data?.projects as { label: string; value: string }[]}
|
||||
selectedItem={defaultValue}
|
||||
onSelect={onChange}
|
||||
placeholder="Select a project"
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
||||
import { TableListItemProps } from '@/components/TableList'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { Variable } from '@typebot.io/schemas'
|
||||
import {
|
||||
ZemanticAiOptions,
|
||||
searchResponseValues,
|
||||
} from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
|
||||
|
||||
type Props = TableListItemProps<ZemanticAiOptions['responseMapping'][number]>
|
||||
|
||||
export const SearchResponseItem = ({ item, onItemChange }: Props) => {
|
||||
const changeValueToExtract = (
|
||||
valueToExtract: (typeof searchResponseValues)[number]
|
||||
) => {
|
||||
onItemChange({ ...item, valueToExtract })
|
||||
}
|
||||
|
||||
const changeVariableId = (variable: Pick<Variable, 'id'> | undefined) => {
|
||||
onItemChange({ ...item, variableId: variable ? variable.id : undefined })
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<DropdownList
|
||||
currentItem={item.valueToExtract ?? 'Summary'}
|
||||
items={searchResponseValues}
|
||||
onItemSelect={changeValueToExtract}
|
||||
/>
|
||||
<VariableSearchInput
|
||||
onSelectVariable={changeVariableId}
|
||||
initialVariableId={item.variableId}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
import { TextInput } from '@/components/inputs/TextInput'
|
||||
import { TextLink } from '@/components/TextLink'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
Stack,
|
||||
ModalFooter,
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const zemanticAIDashboardPage = 'https://zemantic.ai/dashboard/settings'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onNewCredentials: (id: string) => void
|
||||
}
|
||||
|
||||
export const ZemanticAiCredentialsModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onNewCredentials,
|
||||
}: Props) => {
|
||||
const { workspace } = useWorkspace()
|
||||
const { showToast } = useToast()
|
||||
const [apiKey, setApiKey] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
|
||||
const {
|
||||
credentials: {
|
||||
listCredentials: { refetch: refetchCredentials },
|
||||
},
|
||||
} = trpc.useContext()
|
||||
const { mutate } = trpc.credentials.createCredentials.useMutation({
|
||||
onMutate: () => setIsCreating(true),
|
||||
onSettled: () => setIsCreating(false),
|
||||
onError: (err) => {
|
||||
showToast({
|
||||
description: err.message,
|
||||
status: 'error',
|
||||
})
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
refetchCredentials()
|
||||
onNewCredentials(data.credentialsId)
|
||||
onClose()
|
||||
},
|
||||
})
|
||||
|
||||
const createZemanticAiCredentials = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!workspace) return
|
||||
mutate({
|
||||
credentials: {
|
||||
type: 'zemanticAi',
|
||||
workspaceId: workspace.id,
|
||||
name,
|
||||
data: {
|
||||
apiKey,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Add Zemantic AI account</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<form onSubmit={createZemanticAiCredentials}>
|
||||
<ModalBody as={Stack} spacing="6">
|
||||
<TextInput
|
||||
isRequired
|
||||
label="Name"
|
||||
onChange={setName}
|
||||
placeholder="My account"
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
<TextInput
|
||||
isRequired
|
||||
type="password"
|
||||
label="API key"
|
||||
helperText={
|
||||
<>
|
||||
You can generate an API key{' '}
|
||||
<TextLink href={zemanticAIDashboardPage} isExternal>
|
||||
here
|
||||
</TextLink>
|
||||
.
|
||||
</>
|
||||
}
|
||||
onChange={setApiKey}
|
||||
placeholder="ze..."
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isCreating}
|
||||
isDisabled={apiKey === '' || name === ''}
|
||||
colorScheme="blue"
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Icon, IconProps } from '@chakra-ui/react'
|
||||
|
||||
export const ZemanticAiLogo = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...props}>
|
||||
<g transform="matrix(.049281 0 0 .064343 -.27105 -3.4424)">
|
||||
<path
|
||||
d="m99.5 205.5v221h-94v-373h94v152z"
|
||||
fill="#8771b1"
|
||||
opacity=".991"
|
||||
/>
|
||||
<path
|
||||
d="m284.5 426.5v-221-152h94v373h-94z"
|
||||
fill="#f05b4e"
|
||||
opacity=".99"
|
||||
/>
|
||||
<path d="m99.5 205.5h93v221h-93v-221z" fill="#ec9896" />
|
||||
<path d="m192.5 205.5h92v221h-92v-221z" fill="#efe894" />
|
||||
<path d="m398.5 298.5h94v128h-94v-128z" fill="#46bb91" opacity=".989" />
|
||||
</g>
|
||||
</Icon>
|
||||
)
|
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { ZemanticAiOptions } from '@typebot.io/schemas'
|
||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||
import { SetVariableLabel } from '@/components/SetVariableLabel'
|
||||
|
||||
type Props = {
|
||||
options: ZemanticAiOptions
|
||||
}
|
||||
|
||||
export const ZemanticAiNodeBody = ({
|
||||
options: { query, projectId, responseMapping },
|
||||
}: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
return (
|
||||
<Stack>
|
||||
<Text
|
||||
color={query && projectId ? 'currentcolor' : 'gray.500'}
|
||||
noOfLines={1}
|
||||
>
|
||||
{query && projectId ? `Ask: ${query}` : 'Configure...'}
|
||||
</Text>
|
||||
{typebot &&
|
||||
responseMapping
|
||||
.map((mapping) => mapping.variableId)
|
||||
.map((variableId, idx) =>
|
||||
variableId ? (
|
||||
<SetVariableLabel
|
||||
key={variableId + idx}
|
||||
variables={typebot.variables}
|
||||
variableId={variableId}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
import { TextInput, Textarea, NumberInput } from '@/components/inputs'
|
||||
import { CredentialsDropdown } from '@/features/credentials/components/CredentialsDropdown'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Stack,
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import { isEmpty } from '@typebot.io/lib'
|
||||
import { ZemanticAiBlock } from '@typebot.io/schemas'
|
||||
import { ZemanticAiCredentialsModal } from './ZemanticAiCredentialsModal'
|
||||
import { ProjectsDropdown } from './ProjectsDropdown'
|
||||
import { SearchResponseItem } from './SearchResponseItem'
|
||||
import { TableList } from '@/components/TableList'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
|
||||
type Props = {
|
||||
block: ZemanticAiBlock
|
||||
onOptionsChange: (options: ZemanticAiBlock['options']) => void
|
||||
}
|
||||
|
||||
export const ZemanticAiSettings = ({
|
||||
block: { id: blockId, options },
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const { workspace } = useWorkspace()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
const updateCredentialsId = (credentialsId: string | undefined) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
credentialsId,
|
||||
responseMapping: [
|
||||
{
|
||||
id: createId(),
|
||||
valueToExtract: 'Summary',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const updateProjectId = (projectId: string | undefined) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
projectId: isEmpty(projectId) ? undefined : projectId,
|
||||
})
|
||||
}
|
||||
|
||||
const updateQuery = (query: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
query: isEmpty(query) ? undefined : query,
|
||||
})
|
||||
}
|
||||
|
||||
const updateMaxResults = (
|
||||
maxResults: number | `{{${string}}}` | undefined
|
||||
) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
maxResults: maxResults as number,
|
||||
})
|
||||
}
|
||||
|
||||
const updateSystemPrompt = (systemPrompt: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
systemPrompt: isEmpty(systemPrompt) ? undefined : systemPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
const updatePrompt = (prompt: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
prompt: isEmpty(prompt) ? undefined : prompt,
|
||||
})
|
||||
}
|
||||
|
||||
const updateResponseMapping = (
|
||||
responseMapping: typeof options.responseMapping
|
||||
) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
responseMapping,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
{workspace && (
|
||||
<>
|
||||
<CredentialsDropdown
|
||||
type="zemanticAi"
|
||||
workspaceId={workspace.id}
|
||||
currentCredentialsId={options?.credentialsId}
|
||||
onCredentialsSelect={updateCredentialsId}
|
||||
onCreateNewClick={onOpen}
|
||||
credentialsName="Zemantic AI account"
|
||||
/>
|
||||
<ZemanticAiCredentialsModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onNewCredentials={updateCredentialsId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{options?.credentialsId && (
|
||||
<>
|
||||
<ProjectsDropdown
|
||||
credentialsId={options?.credentialsId as string}
|
||||
defaultValue={(options?.projectId as string) ?? ''}
|
||||
onChange={updateProjectId}
|
||||
blockId={blockId as string}
|
||||
/>
|
||||
<TextInput
|
||||
label="Query:"
|
||||
moreInfoTooltip="The question you want to ask or search against the documents in the project."
|
||||
defaultValue={options?.query ?? ''}
|
||||
onChange={updateQuery}
|
||||
withVariableButton={true}
|
||||
placeholder="Content"
|
||||
/>
|
||||
<NumberInput
|
||||
label="Max Results:"
|
||||
moreInfoTooltip="The maximum number of document chunk results to return from your search."
|
||||
direction="column"
|
||||
defaultValue={options?.maxResults}
|
||||
onValueChange={updateMaxResults}
|
||||
placeholder="i.e. 3"
|
||||
w="full"
|
||||
/>
|
||||
<Accordion allowMultiple={true}>
|
||||
<AccordionItem>
|
||||
<AccordionButton>
|
||||
<Text w="full" textAlign="left">
|
||||
Advanced settings
|
||||
</Text>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing={6}>
|
||||
<Textarea
|
||||
label="System Prompt:"
|
||||
moreInfoTooltip="System prompt to send to the summarization LLM. This is prepended to the prompt and helps guide system behavior."
|
||||
defaultValue={options?.systemPrompt ?? ''}
|
||||
onChange={updateSystemPrompt}
|
||||
placeholder="System Prompt"
|
||||
withVariableButton={true}
|
||||
/>
|
||||
<Textarea
|
||||
label="Prompt:"
|
||||
moreInfoTooltip="Prompt to send to the summarization LLM."
|
||||
defaultValue={options?.prompt ?? ''}
|
||||
onChange={updatePrompt}
|
||||
placeholder="Prompt"
|
||||
withVariableButton={true}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton>
|
||||
<Text w="full" textAlign="left">
|
||||
Save answer
|
||||
</Text>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pt="4">
|
||||
<TableList
|
||||
initialItems={options.responseMapping ?? []}
|
||||
Item={SearchResponseItem}
|
||||
onItemsChange={updateResponseMapping}
|
||||
newItemDefaultProps={{ valueToExtract: 'Summary' }}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
||||
import { decrypt } from '@typebot.io/lib/api'
|
||||
import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
|
||||
import got from 'got'
|
||||
|
||||
export const listProjects = authenticatedProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: 'GET',
|
||||
path: '/zemantic-ai/projects',
|
||||
protect: true,
|
||||
summary: 'List Zemantic AI projects',
|
||||
tags: ['ZemanticAi'],
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
credentialsId: z.string(),
|
||||
workspaceId: z.string(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.object({
|
||||
projects: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
|
||||
const workspace = await prisma.workspace.findFirst({
|
||||
where: { id: workspaceId },
|
||||
select: {
|
||||
members: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
credentials: {
|
||||
where: {
|
||||
id: credentialsId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
data: true,
|
||||
iv: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!workspace || isReadWorkspaceFobidden(workspace, user))
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'No workspace found',
|
||||
})
|
||||
|
||||
const credentials = workspace.credentials.at(0)
|
||||
|
||||
if (!credentials)
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'No credentials found',
|
||||
})
|
||||
|
||||
const data = (await decrypt(
|
||||
credentials.data,
|
||||
credentials.iv
|
||||
)) as ZemanticAiCredentials['data']
|
||||
|
||||
const url = 'https://api.zemantic.ai/v1/projects'
|
||||
|
||||
try {
|
||||
const response = await got
|
||||
.get(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${data.apiKey}`,
|
||||
},
|
||||
})
|
||||
.json()
|
||||
|
||||
const projectsData = response as {
|
||||
id: string
|
||||
name: string
|
||||
}[]
|
||||
|
||||
return {
|
||||
projects: projectsData.map((project) => ({
|
||||
label: project.name,
|
||||
value: project.id,
|
||||
})),
|
||||
}
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: 'Could not list projects',
|
||||
cause: e,
|
||||
})
|
||||
}
|
||||
})
|
@ -0,0 +1,6 @@
|
||||
import { router } from '@/helpers/server/trpc'
|
||||
import { listProjects } from './listProjects'
|
||||
|
||||
export const zemanticAiRouter = router({
|
||||
listProjects,
|
||||
})
|
@ -8,7 +8,7 @@ import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integ
|
||||
import { encrypt } from '@typebot.io/lib/api/encryption'
|
||||
import { z } from 'zod'
|
||||
import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp'
|
||||
import { Credentials } from '@typebot.io/schemas'
|
||||
import { Credentials, zemanticAiCredentialsSchema } from '@typebot.io/schemas'
|
||||
import { isDefined } from '@typebot.io/lib/utils'
|
||||
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
|
||||
|
||||
@ -37,6 +37,7 @@ export const createCredentials = authenticatedProcedure
|
||||
googleSheetsCredentialsSchema.pick(inputShape),
|
||||
openAICredentialsSchema.pick(inputShape),
|
||||
whatsAppCredentialsSchema.pick(inputShape),
|
||||
zemanticAiCredentialsSchema.pick(inputShape),
|
||||
]),
|
||||
})
|
||||
)
|
||||
|
@ -8,6 +8,7 @@ import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integ
|
||||
import { z } from 'zod'
|
||||
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
||||
import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp'
|
||||
import { zemanticAiCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
|
||||
|
||||
export const listCredentials = authenticatedProcedure
|
||||
.meta({
|
||||
@ -26,7 +27,8 @@ export const listCredentials = authenticatedProcedure
|
||||
.or(smtpCredentialsSchema.shape.type)
|
||||
.or(googleSheetsCredentialsSchema.shape.type)
|
||||
.or(openAICredentialsSchema.shape.type)
|
||||
.or(whatsAppCredentialsSchema.shape.type),
|
||||
.or(whatsAppCredentialsSchema.shape.type)
|
||||
.or(zemanticAiCredentialsSchema.shape.type),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
|
@ -40,6 +40,7 @@ import { TypebotLinkIcon } from '@/features/blocks/logic/typebotLink/components/
|
||||
import { AbTestIcon } from '@/features/blocks/logic/abTest/components/AbTestIcon'
|
||||
import { PictureChoiceIcon } from '@/features/blocks/inputs/pictureChoice/components/PictureChoiceIcon'
|
||||
import { PixelLogo } from '@/features/blocks/integrations/pixel/components/PixelLogo'
|
||||
import { ZemanticAiLogo } from '@/features/blocks/integrations/zemanticAi/ZemanticAiLogo'
|
||||
|
||||
type BlockIconProps = { type: BlockType } & IconProps
|
||||
|
||||
@ -118,6 +119,8 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps): JSX.Element => {
|
||||
return <OpenAILogo fill={openAIColor} {...props} />
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return <PixelLogo {...props} />
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return <ZemanticAiLogo {...props} />
|
||||
case 'start':
|
||||
return <FlagIcon {...props} />
|
||||
}
|
||||
|
@ -84,5 +84,7 @@ export const BlockLabel = ({ type }: Props): JSX.Element => {
|
||||
return <Text fontSize="sm">{scopedT('openai.label')}</Text>
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return <Text fontSize="sm">{scopedT('pixel.label')}</Text>
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return <Text fontSize="sm">{scopedT('zemanticAi.label')}</Text>
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import { AbTestNodeBody } from '@/features/blocks/logic/abTest/components/AbTest
|
||||
import { PictureChoiceNode } from '@/features/blocks/inputs/pictureChoice/components/PictureChoiceNode'
|
||||
import { PixelNodeBody } from '@/features/blocks/integrations/pixel/components/PixelNodeBody'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { ZemanticAiNodeBody } from '@/features/blocks/integrations/zemanticAi/ZemanticAiNodeBody'
|
||||
|
||||
type Props = {
|
||||
block: Block | StartBlock
|
||||
@ -200,6 +201,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
case IntegrationBlockType.PIXEL: {
|
||||
return <PixelNodeBody options={block.options} />
|
||||
}
|
||||
case IntegrationBlockType.ZEMANTIC_AI: {
|
||||
return <ZemanticAiNodeBody options={block.options} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>{scopedT('text')}</Text>
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import { AbTestSettings } from '@/features/blocks/logic/abTest/components/AbTest
|
||||
import { PictureChoiceSettings } from '@/features/blocks/inputs/pictureChoice/components/PictureChoiceSettings'
|
||||
import { SettingsHoverBar } from './SettingsHoverBar'
|
||||
import { PixelSettings } from '@/features/blocks/integrations/pixel/components/PixelSettings'
|
||||
import { ZemanticAiSettings } from '@/features/blocks/integrations/zemanticAi/ZemanticAiSettings'
|
||||
|
||||
type Props = {
|
||||
block: BlockWithOptions
|
||||
@ -314,5 +315,10 @@ export const BlockSettings = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.ZEMANTIC_AI: {
|
||||
return (
|
||||
<ZemanticAiSettings block={block} onOptionsChange={updateOptions} />
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,5 +63,7 @@ export const getHelpDocUrl = (blockType: BlockWithOptions['type']): string => {
|
||||
return 'https://docs.typebot.io/editor/blocks/logic/jump'
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return 'https://docs.typebot.io/editor/blocks/integrations/pixel'
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return 'https://docs.typebot.io/editor/blocks/integrations/zemantic-ai'
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import {
|
||||
defaultAbTestOptions,
|
||||
BlockWithItems,
|
||||
defaultTypebotLinkOptions,
|
||||
zemanticAiDefaultOptions,
|
||||
} from '@typebot.io/schemas'
|
||||
import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||
|
||||
@ -143,6 +144,8 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
|
||||
return {}
|
||||
case IntegrationBlockType.PIXEL:
|
||||
return {}
|
||||
case IntegrationBlockType.ZEMANTIC_AI:
|
||||
return zemanticAiDefaultOptions
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import { customDomainsRouter } from '@/features/customDomains/api/router'
|
||||
import { whatsAppRouter } from '@/features/whatsapp/router'
|
||||
import { openAIRouter } from '@/features/blocks/integrations/openai/api/router'
|
||||
import { generateUploadUrl } from '@/features/upload/api/generateUploadUrl'
|
||||
import { zemanticAiRouter } from '@/features/blocks/integrations/zemanticAi/api/router'
|
||||
|
||||
export const trpcRouter = router({
|
||||
getAppVersionProcedure,
|
||||
@ -35,6 +36,7 @@ export const trpcRouter = router({
|
||||
whatsApp: whatsAppRouter,
|
||||
openAI: openAIRouter,
|
||||
generateUploadUrl,
|
||||
zemanticAi: zemanticAiRouter,
|
||||
})
|
||||
|
||||
export type AppRouter = typeof trpcRouter
|
||||
|
@ -251,6 +251,7 @@ export default {
|
||||
'editor.sidebarBlock.chatwoot.label': 'Chatwoot',
|
||||
'editor.sidebarBlock.openai.label': 'OpenAI',
|
||||
'editor.sidebarBlock.pixel.label': 'Pixel',
|
||||
'editor.sidebarBlock.zemanticAi.label': 'Zemantic AI',
|
||||
'editor.blockCard.bubbleBlock.tooltip.label':
|
||||
'Ein PDF, ein iframe, eine Website einbetten...',
|
||||
'editor.blockCard.inputBlock.tooltip.files.label': 'Dateien hochladen',
|
||||
|
@ -245,6 +245,7 @@ export default {
|
||||
'editor.sidebarBlock.chatwoot.label': 'Chatwoot',
|
||||
'editor.sidebarBlock.openai.label': 'OpenAI',
|
||||
'editor.sidebarBlock.pixel.label': 'Pixel',
|
||||
'editor.sidebarBlock.zemanticAi.label': 'Zemantic AI',
|
||||
'editor.blockCard.bubbleBlock.tooltip.label':
|
||||
'Embed a pdf, an iframe, a website...',
|
||||
'editor.blockCard.inputBlock.tooltip.files.label': 'Upload Files',
|
||||
|
@ -251,6 +251,7 @@ export default {
|
||||
'editor.sidebarBlock.chatwoot.label': 'Chatwoot',
|
||||
'editor.sidebarBlock.openai.label': 'OpenAI',
|
||||
'editor.sidebarBlock.pixel.label': 'Pixel',
|
||||
'editor.sidebarBlock.zemanticAi.label': 'Zemantic AI',
|
||||
'editor.blockCard.bubbleBlock.tooltip.label':
|
||||
'Intégrer un pdf, un iframe, un site web...',
|
||||
'editor.blockCard.inputBlock.tooltip.files.label': 'Télécharger des fichiers',
|
||||
|
@ -252,6 +252,7 @@ export default {
|
||||
'editor.sidebarBlock.chatwoot.label': 'Chatwoot',
|
||||
'editor.sidebarBlock.openai.label': 'OpenAI',
|
||||
'editor.sidebarBlock.pixel.label': 'Pixel',
|
||||
'editor.sidebarBlock.zemanticAi.label': 'Zemantic AI',
|
||||
'editor.blockCard.bubbleBlock.tooltip.label':
|
||||
'Incorporar pdf, iframe, website...',
|
||||
'editor.blockCard.inputBlock.tooltip.files.label': 'Carregar Ficheiros',
|
||||
|
@ -253,6 +253,7 @@ export default {
|
||||
'editor.sidebarBlock.chatwoot.label': 'Chatwoot',
|
||||
'editor.sidebarBlock.openai.label': 'OpenAI',
|
||||
'editor.sidebarBlock.pixel.label': 'Pixel',
|
||||
'editor.sidebarBlock.zemanticAi.label': 'Zemantic AI',
|
||||
'editor.blockCard.bubbleBlock.tooltip.label':
|
||||
'Incorporar pdf, iframe, website...',
|
||||
'editor.blockCard.inputBlock.tooltip.files.label': 'Carregar Ficheiros',
|
||||
|
Reference in New Issue
Block a user