✨ (openai) Add tools and functions support (#1167)
Closes #863 Got helped from #1162 for the implementation. Closing it in favor of this PR. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced `CodeEditor` with additional properties for better form control and validation. - Introduced tools and functions in OpenAI integrations documentation for custom JavaScript execution. - Added capability to define and use custom JavaScript functions with the OpenAI assistant. - Expanded layout metadata options to include various input types and languages. - **Improvements** - Updated the OpenAI actions to support new function execution features. - **Documentation** - Added new sections for tools and functions in the OpenAI integrations guide. - **Refactor** - Refactored components and actions to integrate new features and improve existing functionalities. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -1,11 +1,15 @@
|
||||
import {
|
||||
BoxProps,
|
||||
Fade,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Stack,
|
||||
useColorModeValue,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { VariablesButton } from '@/features/variables/components/VariablesButton'
|
||||
import { Variable } from '@typebot.io/schemas'
|
||||
@ -16,8 +20,10 @@ import { githubLight } from '@uiw/codemirror-theme-github'
|
||||
import { LanguageName, loadLanguage } from '@uiw/codemirror-extensions-langs'
|
||||
import { isDefined } from '@udecode/plate-common'
|
||||
import { CopyButton } from '../CopyButton'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
|
||||
type Props = {
|
||||
label?: string
|
||||
value?: string
|
||||
defaultValue?: string
|
||||
lang: LanguageName
|
||||
@ -27,11 +33,18 @@ type Props = {
|
||||
height?: string
|
||||
maxHeight?: string
|
||||
minWidth?: string
|
||||
moreInfoTooltip?: string
|
||||
helperText?: ReactNode
|
||||
isRequired?: boolean
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
export const CodeEditor = ({
|
||||
label,
|
||||
defaultValue,
|
||||
lang,
|
||||
moreInfoTooltip,
|
||||
helperText,
|
||||
isRequired,
|
||||
onChange,
|
||||
height = '250px',
|
||||
maxHeight = '70vh',
|
||||
@ -86,71 +99,91 @@ export const CodeEditor = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<HStack
|
||||
align="flex-end"
|
||||
spacing={0}
|
||||
borderWidth={'1px'}
|
||||
rounded="md"
|
||||
bg={useColorModeValue('white', '#1A1B26')}
|
||||
width="full"
|
||||
h="full"
|
||||
pos="relative"
|
||||
minW={minWidth}
|
||||
onMouseEnter={onOpen}
|
||||
onMouseLeave={onClose}
|
||||
maxWidth={props.maxWidth}
|
||||
sx={{
|
||||
'& .cm-editor': {
|
||||
maxH: maxHeight,
|
||||
outline: '0px solid transparent !important',
|
||||
rounded: 'md',
|
||||
},
|
||||
'& .cm-scroller': {
|
||||
rounded: 'md',
|
||||
overflow: 'auto',
|
||||
},
|
||||
'& .cm-gutter,.cm-content': {
|
||||
minH: isReadOnly ? '0' : height,
|
||||
},
|
||||
'& .ͼ1 .cm-scroller': {
|
||||
fontSize: '14px',
|
||||
fontFamily:
|
||||
'JetBrainsMono, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',
|
||||
},
|
||||
}}
|
||||
<FormControl
|
||||
isRequired={isRequired}
|
||||
as={Stack}
|
||||
justifyContent="space-between"
|
||||
spacing={2}
|
||||
flex="1"
|
||||
>
|
||||
<CodeMirror
|
||||
data-testid="code-editor"
|
||||
ref={codeEditor}
|
||||
value={props.value ?? value}
|
||||
onChange={handleChange}
|
||||
onBlur={rememberCarretPosition}
|
||||
theme={theme}
|
||||
extensions={[loadLanguage(lang)].filter(isDefined)}
|
||||
editable={!isReadOnly}
|
||||
style={{
|
||||
width: isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%',
|
||||
}}
|
||||
spellCheck={false}
|
||||
basicSetup={{
|
||||
highlightActiveLine: false,
|
||||
}}
|
||||
/>
|
||||
{isVariableButtonDisplayed && (
|
||||
<VariablesButton onSelectVariable={handleVariableSelected} size="sm" />
|
||||
{label && (
|
||||
<FormLabel display="flex" flexShrink={0} gap="1" mb="0" mr="0">
|
||||
{label}{' '}
|
||||
{moreInfoTooltip && (
|
||||
<MoreInfoTooltip>{moreInfoTooltip}</MoreInfoTooltip>
|
||||
)}
|
||||
</FormLabel>
|
||||
)}
|
||||
{isReadOnly && (
|
||||
<Fade in={isOpen}>
|
||||
<CopyButton
|
||||
textToCopy={props.value ?? value}
|
||||
pos="absolute"
|
||||
right={0.5}
|
||||
top={0.5}
|
||||
size="xs"
|
||||
colorScheme="blue"
|
||||
<HStack
|
||||
align="flex-end"
|
||||
spacing={0}
|
||||
borderWidth={'1px'}
|
||||
rounded="md"
|
||||
bg={useColorModeValue('white', '#1A1B26')}
|
||||
width="full"
|
||||
h="full"
|
||||
pos="relative"
|
||||
minW={minWidth}
|
||||
onMouseEnter={onOpen}
|
||||
onMouseLeave={onClose}
|
||||
maxWidth={props.maxWidth}
|
||||
sx={{
|
||||
'& .cm-editor': {
|
||||
maxH: maxHeight,
|
||||
outline: '0px solid transparent !important',
|
||||
rounded: 'md',
|
||||
},
|
||||
'& .cm-scroller': {
|
||||
rounded: 'md',
|
||||
overflow: 'auto',
|
||||
},
|
||||
'& .cm-gutter,.cm-content': {
|
||||
minH: isReadOnly ? '0' : height,
|
||||
},
|
||||
'& .ͼ1 .cm-scroller': {
|
||||
fontSize: '14px',
|
||||
fontFamily:
|
||||
'JetBrainsMono, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CodeMirror
|
||||
data-testid="code-editor"
|
||||
ref={codeEditor}
|
||||
value={props.value ?? value}
|
||||
onChange={handleChange}
|
||||
onBlur={rememberCarretPosition}
|
||||
theme={theme}
|
||||
extensions={[loadLanguage(lang)].filter(isDefined)}
|
||||
editable={!isReadOnly}
|
||||
style={{
|
||||
width: isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%',
|
||||
}}
|
||||
spellCheck={false}
|
||||
basicSetup={{
|
||||
highlightActiveLine: false,
|
||||
}}
|
||||
/>
|
||||
{isVariableButtonDisplayed && (
|
||||
<VariablesButton
|
||||
onSelectVariable={handleVariableSelected}
|
||||
size="sm"
|
||||
/>
|
||||
</Fade>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
{isReadOnly && (
|
||||
<Fade in={isOpen}>
|
||||
<CopyButton
|
||||
textToCopy={props.value ?? value}
|
||||
pos="absolute"
|
||||
right={0.5}
|
||||
top={0.5}
|
||||
size="xs"
|
||||
colorScheme="blue"
|
||||
/>
|
||||
</Fade>
|
||||
)}
|
||||
</HStack>
|
||||
{helperText && <FormHelperText mt="0">{helperText}</FormHelperText>}
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { DropdownList } from '@/components/DropdownList'
|
||||
import { ForgedBlockDefinition, ForgedBlock } from '@typebot.io/forge-schemas'
|
||||
import { PrimitiveList } from '@/components/PrimitiveList'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||
|
||||
const mdComponents = {
|
||||
a: ({ href, children }) => (
|
||||
@ -99,6 +100,8 @@ export const ZodFieldLayout = ({
|
||||
<ZodArrayContent
|
||||
data={data}
|
||||
schema={schema}
|
||||
blockDef={blockDef}
|
||||
blockOptions={blockOptions}
|
||||
layout={layout}
|
||||
onDataChange={onDataChange}
|
||||
isInAccordion
|
||||
@ -111,6 +114,8 @@ export const ZodFieldLayout = ({
|
||||
<ZodArrayContent
|
||||
data={data}
|
||||
schema={schema}
|
||||
blockDef={blockDef}
|
||||
blockOptions={blockOptions}
|
||||
layout={layout}
|
||||
onDataChange={onDataChange}
|
||||
/>
|
||||
@ -229,6 +234,28 @@ export const ZodFieldLayout = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (layout?.inputType === 'code')
|
||||
return (
|
||||
<CodeEditor
|
||||
defaultValue={data ?? layout?.defaultValue}
|
||||
lang={layout.lang ?? 'javascript'}
|
||||
label={layout?.label}
|
||||
placeholder={layout?.placeholder}
|
||||
helperText={
|
||||
layout?.helperText ? (
|
||||
<Markdown components={mdComponents}>
|
||||
{layout.helperText}
|
||||
</Markdown>
|
||||
) : undefined
|
||||
}
|
||||
isRequired={layout?.isRequired}
|
||||
withVariableButton={layout?.withVariableButton}
|
||||
moreInfoTooltip={layout.moreInfoTooltip}
|
||||
onChange={onDataChange}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<TextInput
|
||||
defaultValue={data ?? layout?.defaultValue}
|
||||
@ -254,12 +281,16 @@ export const ZodFieldLayout = ({
|
||||
const ZodArrayContent = ({
|
||||
schema,
|
||||
data,
|
||||
blockDef,
|
||||
blockOptions,
|
||||
layout,
|
||||
isInAccordion,
|
||||
onDataChange,
|
||||
}: {
|
||||
schema: z.ZodTypeAny
|
||||
data: any
|
||||
blockDef?: ForgedBlockDefinition
|
||||
blockOptions?: ForgedBlock['options']
|
||||
layout: ZodLayoutMetadata<ZodTypeAny> | undefined
|
||||
isInAccordion?: boolean
|
||||
onDataChange: (val: any) => void
|
||||
@ -283,6 +314,8 @@ const ZodArrayContent = ({
|
||||
<ZodFieldLayout
|
||||
schema={schema._def.innerType._def.type}
|
||||
data={item}
|
||||
blockDef={blockDef}
|
||||
blockOptions={blockOptions}
|
||||
isInAccordion={isInAccordion}
|
||||
onDataChange={onItemChange}
|
||||
width="full"
|
||||
@ -302,9 +335,11 @@ const ZodArrayContent = ({
|
||||
isOrdered={layout?.isOrdered}
|
||||
>
|
||||
{({ item, onItemChange }) => (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px" maxW="100%">
|
||||
<ZodFieldLayout
|
||||
schema={schema._def.innerType._def.type}
|
||||
blockDef={blockDef}
|
||||
blockOptions={blockOptions}
|
||||
data={item}
|
||||
isInAccordion={isInAccordion}
|
||||
onDataChange={onItemChange}
|
||||
|
@ -33,6 +33,18 @@ Then you can give the OpenAI block access to this sequence of messages:
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Tools
|
||||
|
||||
The tools section allows you to add functions that the OpenAI model can execute. Here is an example of a function named `getWeather` that returns 'Sunny and warm' if you ask about the weather of Paris and 'Rainy and cold' if you ask for any other city 😂.
|
||||
|
||||
A more useful example would be, of course, to call an API to get the weather of the city the user is asking about.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/blocks/integrations/openai/tools.png" alt="OpenAI tools" />
|
||||
</Frame>
|
||||
|
||||
As you can see, the code block expects the body of the Javascript function. You can use the `return` keyword to return values.
|
||||
|
||||
## Ask assistant
|
||||
|
||||
This action allows you to talk with your [OpenAI assistant](https://platform.openai.com/assistants). All you have to do is to provide its ID.
|
||||
@ -44,6 +56,10 @@ This action allows you to talk with your [OpenAI assistant](https://platform.ope
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Functions
|
||||
|
||||
If you defined functions in your assistant, you can define the function to execute in the `Functions` section.
|
||||
|
||||
## Create speech
|
||||
|
||||
This action allows you to transform a text input into an audio URL that you can reuse in your bot.
|
||||
|
BIN
apps/docs/images/blocks/integrations/openai/tools.png
Normal file
BIN
apps/docs/images/blocks/integrations/openai/tools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 171 KiB |
Reference in New Issue
Block a user