@ -19,7 +19,7 @@
|
||||
"@typebot.io/variables": "workspace:*",
|
||||
"@udecode/plate-common": "21.1.5",
|
||||
"@udecode/plate-serializer-md": "24.4.0",
|
||||
"ai": "2.2.24",
|
||||
"ai": "2.2.31",
|
||||
"chrono-node": "2.7.0",
|
||||
"date-fns": "2.30.0",
|
||||
"google-auth-library": "8.9.0",
|
||||
@ -28,7 +28,7 @@
|
||||
"libphonenumber-js": "1.10.37",
|
||||
"node-html-parser": "6.1.5",
|
||||
"nodemailer": "6.9.3",
|
||||
"openai": "4.19.0",
|
||||
"openai": "4.24.1",
|
||||
"qs": "6.11.2",
|
||||
"stripe": "12.13.0"
|
||||
},
|
||||
|
172
packages/forge/blocks/openai/actions/askAssistant.tsx
Normal file
172
packages/forge/blocks/openai/actions/askAssistant.tsx
Normal file
@ -0,0 +1,172 @@
|
||||
import { createAction, option } from '@typebot.io/forge'
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib'
|
||||
import { auth } from '../auth'
|
||||
import { ClientOptions, OpenAI } from 'openai'
|
||||
import { baseOptions } from '../baseOptions'
|
||||
|
||||
export const askAssistant = createAction({
|
||||
auth,
|
||||
baseOptions,
|
||||
name: 'Ask Assistant',
|
||||
options: option.object({
|
||||
assistantId: option.string.layout({
|
||||
label: 'Assistant ID',
|
||||
placeholder: 'Select an assistant',
|
||||
moreInfoTooltip: 'The OpenAI assistant you want to ask question to.',
|
||||
fetcher: 'fetchAssistants',
|
||||
}),
|
||||
threadId: option.string.layout({
|
||||
label: 'Thread ID',
|
||||
moreInfoTooltip:
|
||||
'Used to remember the conversation with the user. If empty, a new thread is created.',
|
||||
}),
|
||||
message: option.string.layout({
|
||||
label: 'Message',
|
||||
inputType: 'textarea',
|
||||
}),
|
||||
responseMapping: option
|
||||
.saveResponseArray(['Message', 'Thread ID'] as const)
|
||||
.layout({
|
||||
accordion: 'Save response',
|
||||
}),
|
||||
}),
|
||||
fetchers: [
|
||||
{
|
||||
id: 'fetchAssistants',
|
||||
fetch: async ({ options, credentials }) => {
|
||||
const config = {
|
||||
apiKey: credentials.apiKey,
|
||||
baseURL: options.baseUrl,
|
||||
defaultHeaders: {
|
||||
'api-key': credentials.apiKey,
|
||||
},
|
||||
defaultQuery: options.apiVersion
|
||||
? {
|
||||
'api-version': options.apiVersion,
|
||||
}
|
||||
: undefined,
|
||||
} satisfies ClientOptions
|
||||
|
||||
const openai = new OpenAI(config)
|
||||
|
||||
const response = await openai.beta.assistants.list()
|
||||
|
||||
return response.data
|
||||
.map((assistant) =>
|
||||
assistant.name
|
||||
? {
|
||||
label: assistant.name,
|
||||
value: assistant.id,
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
.filter(isDefined)
|
||||
},
|
||||
dependencies: ['baseUrl', 'apiVersion'],
|
||||
},
|
||||
],
|
||||
getSetVariableIds: ({ responseMapping }) =>
|
||||
responseMapping?.map((r) => r.variableId).filter(isDefined) ?? [],
|
||||
run: {
|
||||
server: async ({
|
||||
credentials: { apiKey },
|
||||
options: {
|
||||
baseUrl,
|
||||
apiVersion,
|
||||
assistantId,
|
||||
message,
|
||||
responseMapping,
|
||||
threadId,
|
||||
},
|
||||
variables,
|
||||
logs,
|
||||
}) => {
|
||||
if (isEmpty(assistantId)) {
|
||||
logs.add('Assistant ID is empty')
|
||||
return
|
||||
}
|
||||
if (isEmpty(message)) {
|
||||
logs.add('Message is empty')
|
||||
return
|
||||
}
|
||||
const config = {
|
||||
apiKey,
|
||||
baseURL: baseUrl,
|
||||
defaultHeaders: {
|
||||
'api-key': apiKey,
|
||||
},
|
||||
defaultQuery: apiVersion
|
||||
? {
|
||||
'api-version': apiVersion,
|
||||
}
|
||||
: undefined,
|
||||
} satisfies ClientOptions
|
||||
|
||||
const openai = new OpenAI(config)
|
||||
|
||||
// Create a thread if needed
|
||||
const currentThreadId = isEmpty(threadId)
|
||||
? (await openai.beta.threads.create({})).id
|
||||
: threadId
|
||||
|
||||
// Add a message to the thread
|
||||
const createdMessage = await openai.beta.threads.messages.create(
|
||||
currentThreadId,
|
||||
{
|
||||
role: 'user',
|
||||
content: message,
|
||||
}
|
||||
)
|
||||
|
||||
const run = await openai.beta.threads.runs.create(currentThreadId, {
|
||||
assistant_id: assistantId,
|
||||
})
|
||||
|
||||
async function waitForRun(run: OpenAI.Beta.Threads.Runs.Run) {
|
||||
// Poll for status change
|
||||
while (run.status === 'queued' || run.status === 'in_progress') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
run = await openai.beta.threads.runs.retrieve(currentThreadId, run.id)
|
||||
}
|
||||
|
||||
// Check the run status
|
||||
if (
|
||||
run.status === 'cancelled' ||
|
||||
run.status === 'cancelling' ||
|
||||
run.status === 'failed' ||
|
||||
run.status === 'expired'
|
||||
) {
|
||||
throw new Error(run.status)
|
||||
}
|
||||
}
|
||||
|
||||
await waitForRun(run)
|
||||
|
||||
const responseMessages = (
|
||||
await openai.beta.threads.messages.list(currentThreadId, {
|
||||
after: createdMessage.id,
|
||||
order: 'asc',
|
||||
})
|
||||
).data
|
||||
|
||||
responseMapping?.forEach((mapping) => {
|
||||
if (!mapping.variableId) return
|
||||
if (!mapping.item || mapping.item === 'Message') {
|
||||
let message = ''
|
||||
const messageContents = responseMessages[0].content
|
||||
for (const content of messageContents) {
|
||||
switch (content.type) {
|
||||
case 'text':
|
||||
message += (message !== '' ? '\n\n' : '') + content.text.value
|
||||
break
|
||||
}
|
||||
}
|
||||
variables.set(mapping.variableId, message)
|
||||
}
|
||||
if (mapping.item === 'Thread ID')
|
||||
variables.set(mapping.variableId, currentThreadId)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
@ -20,22 +20,20 @@ export const parseChatCompletionMessages = ({
|
||||
const dialogue = variables.get(message.dialogueVariableId) ?? []
|
||||
const dialogueArr = Array.isArray(dialogue) ? dialogue : [dialogue]
|
||||
|
||||
return dialogueArr.map<OpenAI.Chat.ChatCompletionMessageParam>(
|
||||
(dialogueItem, index) => {
|
||||
if (index === 0 && message.startsBy === 'assistant')
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: dialogueItem,
|
||||
}
|
||||
return dialogueArr.map((dialogueItem, index) => {
|
||||
if (index === 0 && message.startsBy === 'assistant')
|
||||
return {
|
||||
role:
|
||||
index % (message.startsBy === 'assistant' ? 1 : 2) === 0
|
||||
? 'user'
|
||||
: 'assistant',
|
||||
role: 'assistant',
|
||||
content: dialogueItem,
|
||||
}
|
||||
return {
|
||||
role:
|
||||
index % (message.startsBy === 'assistant' ? 1 : 2) === 0
|
||||
? 'user'
|
||||
: 'assistant',
|
||||
content: dialogueItem,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (!message.content) return
|
||||
|
@ -4,6 +4,7 @@ import { createSpeech } from './actions/createSpeech'
|
||||
import { createBlock } from '@typebot.io/forge'
|
||||
import { auth } from './auth'
|
||||
import { baseOptions } from './baseOptions'
|
||||
import { askAssistant } from './actions/askAssistant'
|
||||
|
||||
export const openAIBlock = createBlock({
|
||||
id: 'openai' as const,
|
||||
@ -13,5 +14,5 @@ export const openAIBlock = createBlock({
|
||||
DarkLogo: OpenAIDarkLogo,
|
||||
auth,
|
||||
options: baseOptions,
|
||||
actions: [createChatCompletion, createSpeech],
|
||||
actions: [createChatCompletion, askAssistant, createSpeech],
|
||||
})
|
||||
|
@ -7,8 +7,8 @@
|
||||
"author": "Baptiste Arnaud",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"ai": "2.2.24",
|
||||
"openai": "4.19.0"
|
||||
"ai": "2.2.31",
|
||||
"openai": "4.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typebot.io/forge": "workspace:*",
|
||||
|
Reference in New Issue
Block a user