mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-29 07:01:04 +03:00
Feature/OpenAI Assistant V2 (#2258)
* add gpt4 turbo to assistant * OpenAI Assistant V2 * update langfuse handler
This commit is contained in:
+2
-1
@@ -64,6 +64,7 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@qdrant/openapi-typescript-fetch": "1.2.1",
|
"@qdrant/openapi-typescript-fetch": "1.2.1",
|
||||||
"@google/generative-ai": "^0.7.0"
|
"@google/generative-ai": "^0.7.0",
|
||||||
|
"openai": "4.38.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams, IUsedTool } from '../../../src/Interface'
|
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams, IUsedTool } from '../../../src/Interface'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
import { DataSource } from 'typeorm'
|
import { DataSource } from 'typeorm'
|
||||||
import { getCredentialData, getCredentialParam, getUserHome } from '../../../src/utils'
|
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { ImageFileContentBlock, TextContentBlock } from 'openai/resources/beta/threads/messages/messages'
|
|
||||||
import * as fsDefault from 'node:fs'
|
|
||||||
import * as path from 'node:path'
|
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { flatten, uniqWith, isEqual } from 'lodash'
|
import { flatten, uniqWith, isEqual } from 'lodash'
|
||||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
import { zodToJsonSchema } from 'zod-to-json-schema'
|
||||||
import { AnalyticHandler } from '../../../src/handler'
|
import { AnalyticHandler } from '../../../src/handler'
|
||||||
import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation'
|
import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation'
|
||||||
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
|
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
|
||||||
|
import { addFileToStorage } from '../../../src/storageUtils'
|
||||||
|
|
||||||
|
const lenticularBracketRegex = /【[^】]*】/g
|
||||||
|
const imageRegex = /<img[^>]*\/>/g
|
||||||
|
|
||||||
class OpenAIAssistant_Agents implements INode {
|
class OpenAIAssistant_Agents implements INode {
|
||||||
label: string
|
label: string
|
||||||
@@ -168,6 +169,9 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
tools = flatten(tools)
|
tools = flatten(tools)
|
||||||
const formattedTools = tools?.map((tool: any) => formatToOpenAIAssistantTool(tool)) ?? []
|
const formattedTools = tools?.map((tool: any) => formatToOpenAIAssistantTool(tool)) ?? []
|
||||||
|
|
||||||
|
const usedTools: IUsedTool[] = []
|
||||||
|
const fileAnnotations = []
|
||||||
|
|
||||||
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
||||||
id: selectedAssistantId
|
id: selectedAssistantId
|
||||||
})
|
})
|
||||||
@@ -195,7 +199,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
if (formattedTools.length) {
|
if (formattedTools.length) {
|
||||||
let filteredTools = []
|
let filteredTools = []
|
||||||
for (const tool of retrievedAssistant.tools) {
|
for (const tool of retrievedAssistant.tools) {
|
||||||
if (tool.type === 'code_interpreter' || tool.type === 'retrieval') filteredTools.push(tool)
|
if (tool.type === 'code_interpreter' || tool.type === 'file_search') filteredTools.push(tool)
|
||||||
}
|
}
|
||||||
filteredTools = uniqWith([...filteredTools, ...formattedTools], isEqual)
|
filteredTools = uniqWith([...filteredTools, ...formattedTools], isEqual)
|
||||||
// filter out tool with empty function
|
// filter out tool with empty function
|
||||||
@@ -236,7 +240,8 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
(runStatus === 'cancelled' ||
|
(runStatus === 'cancelled' ||
|
||||||
runStatus === 'completed' ||
|
runStatus === 'completed' ||
|
||||||
runStatus === 'expired' ||
|
runStatus === 'expired' ||
|
||||||
runStatus === 'failed')
|
runStatus === 'failed' ||
|
||||||
|
runStatus === 'requires_action')
|
||||||
) {
|
) {
|
||||||
clearInterval(timeout)
|
clearInterval(timeout)
|
||||||
resolve()
|
resolve()
|
||||||
@@ -259,11 +264,235 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
|
|
||||||
// Run assistant thread
|
// Run assistant thread
|
||||||
const llmIds = await analyticHandlers.onLLMStart('ChatOpenAI', input, parentIds)
|
const llmIds = await analyticHandlers.onLLMStart('ChatOpenAI', input, parentIds)
|
||||||
const runThread = await openai.beta.threads.runs.create(threadId, {
|
|
||||||
assistant_id: retrievedAssistant.id
|
let text = ''
|
||||||
|
let runThreadId = ''
|
||||||
|
let isStreamingStarted = false
|
||||||
|
|
||||||
|
if (isStreaming) {
|
||||||
|
const streamThread = await openai.beta.threads.runs.create(threadId, {
|
||||||
|
assistant_id: retrievedAssistant.id,
|
||||||
|
stream: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const usedTools: IUsedTool[] = []
|
for await (const event of streamThread) {
|
||||||
|
if (event.event === 'thread.run.created') {
|
||||||
|
runThreadId = event.data.id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event === 'thread.message.delta') {
|
||||||
|
const chunk = event.data.delta.content?.[0]
|
||||||
|
|
||||||
|
if (chunk && 'text' in chunk) {
|
||||||
|
if (chunk.text?.annotations?.length) {
|
||||||
|
const message_content = chunk.text
|
||||||
|
const annotations = chunk.text?.annotations
|
||||||
|
|
||||||
|
// Iterate over the annotations
|
||||||
|
for (let index = 0; index < annotations.length; index++) {
|
||||||
|
const annotation = annotations[index]
|
||||||
|
let filePath = ''
|
||||||
|
|
||||||
|
// Gather citations based on annotation attributes
|
||||||
|
const file_citation = (annotation as OpenAI.Beta.Threads.Messages.FileCitationAnnotation).file_citation
|
||||||
|
if (file_citation) {
|
||||||
|
const cited_file = await openai.files.retrieve(file_citation.file_id)
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
||||||
|
if (!disableFileDownload) {
|
||||||
|
filePath = await downloadFile(openAIApiKey, cited_file, fileName, options.chatflowid, threadId)
|
||||||
|
fileAnnotations.push({
|
||||||
|
filePath,
|
||||||
|
fileName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const file_path = (annotation as OpenAI.Beta.Threads.Messages.FilePathAnnotation).file_path
|
||||||
|
if (file_path) {
|
||||||
|
const cited_file = await openai.files.retrieve(file_path.file_id)
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
||||||
|
if (!disableFileDownload) {
|
||||||
|
filePath = await downloadFile(
|
||||||
|
openAIApiKey,
|
||||||
|
cited_file,
|
||||||
|
fileName,
|
||||||
|
options.chatflowid,
|
||||||
|
threadId
|
||||||
|
)
|
||||||
|
fileAnnotations.push({
|
||||||
|
filePath,
|
||||||
|
fileName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the text with a footnote
|
||||||
|
message_content.value = message_content.value?.replace(
|
||||||
|
`${annotation.text}`,
|
||||||
|
`${disableFileDownload ? '' : filePath}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove lenticular brackets
|
||||||
|
message_content.value = message_content.value?.replace(lenticularBracketRegex, '')
|
||||||
|
|
||||||
|
text += message_content.value ?? ''
|
||||||
|
|
||||||
|
if (message_content.value) {
|
||||||
|
if (!isStreamingStarted) {
|
||||||
|
isStreamingStarted = true
|
||||||
|
socketIO.to(socketIOClientId).emit('start', message_content.value)
|
||||||
|
}
|
||||||
|
socketIO.to(socketIOClientId).emit('token', message_content.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileAnnotations.length) {
|
||||||
|
if (!isStreamingStarted) {
|
||||||
|
isStreamingStarted = true
|
||||||
|
socketIO.to(socketIOClientId).emit('start', '')
|
||||||
|
}
|
||||||
|
socketIO.to(socketIOClientId).emit('fileAnnotations', fileAnnotations)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text += chunk.text?.value
|
||||||
|
if (!isStreamingStarted) {
|
||||||
|
isStreamingStarted = true
|
||||||
|
socketIO.to(socketIOClientId).emit('start', chunk.text?.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketIO.to(socketIOClientId).emit('token', chunk.text?.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk && 'image_file' in chunk && chunk.image_file?.file_id) {
|
||||||
|
const fileId = chunk.image_file.file_id
|
||||||
|
const fileObj = await openai.files.retrieve(fileId)
|
||||||
|
|
||||||
|
const buffer = await downloadImg(openai, fileId, `${fileObj.filename}.png`, options.chatflowid, threadId)
|
||||||
|
const base64String = Buffer.from(buffer).toString('base64')
|
||||||
|
|
||||||
|
// TODO: Use a file path and retrieve image on the fly. Storing as base64 to localStorage and database will easily hit limits
|
||||||
|
const imgHTML = `<img src="data:image/png;base64,${base64String}" width="100%" height="max-content" alt="${fileObj.filename}" /><br/>`
|
||||||
|
text += imgHTML
|
||||||
|
|
||||||
|
if (!isStreamingStarted) {
|
||||||
|
isStreamingStarted = true
|
||||||
|
socketIO.to(socketIOClientId).emit('start', imgHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketIO.to(socketIOClientId).emit('token', imgHTML)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event === 'thread.run.requires_action') {
|
||||||
|
if (event.data.required_action?.submit_tool_outputs.tool_calls) {
|
||||||
|
const actions: ICommonObject[] = []
|
||||||
|
event.data.required_action.submit_tool_outputs.tool_calls.forEach((item) => {
|
||||||
|
const functionCall = item.function
|
||||||
|
let args = {}
|
||||||
|
try {
|
||||||
|
args = JSON.parse(functionCall.arguments)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing arguments, default to empty object')
|
||||||
|
}
|
||||||
|
actions.push({
|
||||||
|
tool: functionCall.name,
|
||||||
|
toolInput: args,
|
||||||
|
toolCallId: item.id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitToolOutputs = []
|
||||||
|
for (let i = 0; i < actions.length; i += 1) {
|
||||||
|
const tool = tools.find((tool: any) => tool.name === actions[i].tool)
|
||||||
|
if (!tool) continue
|
||||||
|
|
||||||
|
// Start tool analytics
|
||||||
|
const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, {
|
||||||
|
sessionId: threadId,
|
||||||
|
chatId: options.chatId,
|
||||||
|
input
|
||||||
|
})
|
||||||
|
await analyticHandlers.onToolEnd(toolIds, toolOutput)
|
||||||
|
submitToolOutputs.push({
|
||||||
|
tool_call_id: actions[i].toolCallId,
|
||||||
|
output: toolOutput
|
||||||
|
})
|
||||||
|
usedTools.push({
|
||||||
|
tool: tool.name,
|
||||||
|
toolInput: actions[i].toolInput,
|
||||||
|
toolOutput
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
await analyticHandlers.onToolEnd(toolIds, e)
|
||||||
|
console.error('Error executing tool', e)
|
||||||
|
throw new Error(
|
||||||
|
`Error executing tool. Tool: ${tool.name}. Thread ID: ${threadId}. Run ID: ${runThreadId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stream = openai.beta.threads.runs.submitToolOutputsStream(threadId, runThreadId, {
|
||||||
|
tool_outputs: submitToolOutputs
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const event of stream) {
|
||||||
|
if (event.event === 'thread.message.delta') {
|
||||||
|
const chunk = event.data.delta.content?.[0]
|
||||||
|
if (chunk && 'text' in chunk && chunk.text?.value) {
|
||||||
|
text += chunk.text.value
|
||||||
|
if (!isStreamingStarted) {
|
||||||
|
isStreamingStarted = true
|
||||||
|
socketIO.to(socketIOClientId).emit('start', chunk.text.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketIO.to(socketIOClientId).emit('token', chunk.text.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socketIO.to(socketIOClientId).emit('usedTools', usedTools)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting tool outputs:', error)
|
||||||
|
await openai.beta.threads.runs.cancel(threadId, runThreadId)
|
||||||
|
|
||||||
|
const errMsg = `Error submitting tool outputs. Thread ID: ${threadId}. Run ID: ${runThreadId}`
|
||||||
|
|
||||||
|
await analyticHandlers.onLLMError(llmIds, errMsg)
|
||||||
|
await analyticHandlers.onChainError(parentIds, errMsg, true)
|
||||||
|
|
||||||
|
throw new Error(errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List messages
|
||||||
|
const messages = await openai.beta.threads.messages.list(threadId)
|
||||||
|
const messageData = messages.data ?? []
|
||||||
|
const assistantMessages = messageData.filter((msg) => msg.role === 'assistant')
|
||||||
|
if (!assistantMessages.length) return ''
|
||||||
|
|
||||||
|
// Remove images from the logging text
|
||||||
|
let llmOutput = text.replace(imageRegex, '')
|
||||||
|
llmOutput = llmOutput.replace('<br/>', '')
|
||||||
|
|
||||||
|
await analyticHandlers.onLLMEnd(llmIds, llmOutput)
|
||||||
|
await analyticHandlers.onChainEnd(parentIds, messageData, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
usedTools,
|
||||||
|
fileAnnotations,
|
||||||
|
assistant: { assistantId: openAIAssistantId, threadId, runId: runThreadId, messages: messageData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const promise = (threadId: string, runId: string) => {
|
const promise = (threadId: string, runId: string) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -299,8 +528,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
|
|
||||||
// Start tool analytics
|
// Start tool analytics
|
||||||
const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds)
|
const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds)
|
||||||
if (options.socketIO && options.socketIOClientId)
|
if (socketIO && socketIOClientId) socketIO.to(socketIOClientId).emit('tool', tool.name)
|
||||||
options.socketIO.to(options.socketIOClientId).emit('tool', tool.name)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, {
|
const toolOutput = await tool.call(actions[i].toolInput, undefined, undefined, {
|
||||||
@@ -360,7 +588,10 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Polling run status
|
// Polling run status
|
||||||
let runThreadId = runThread.id
|
const runThread = await openai.beta.threads.runs.create(threadId, {
|
||||||
|
assistant_id: retrievedAssistant.id
|
||||||
|
})
|
||||||
|
runThreadId = runThread.id
|
||||||
let state = await promise(threadId, runThread.id)
|
let state = await promise(threadId, runThread.id)
|
||||||
while (state === 'requires_action') {
|
while (state === 'requires_action') {
|
||||||
state = await promise(threadId, runThread.id)
|
state = await promise(threadId, runThread.id)
|
||||||
@@ -389,17 +620,14 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
if (!assistantMessages.length) return ''
|
if (!assistantMessages.length) return ''
|
||||||
|
|
||||||
let returnVal = ''
|
let returnVal = ''
|
||||||
const fileAnnotations = []
|
|
||||||
for (let i = 0; i < assistantMessages[0].content.length; i += 1) {
|
for (let i = 0; i < assistantMessages[0].content.length; i += 1) {
|
||||||
if (assistantMessages[0].content[i].type === 'text') {
|
if (assistantMessages[0].content[i].type === 'text') {
|
||||||
const content = assistantMessages[0].content[i] as TextContentBlock
|
const content = assistantMessages[0].content[i] as OpenAI.Beta.Threads.Messages.TextContentBlock
|
||||||
|
|
||||||
if (content.text.annotations) {
|
if (content.text.annotations) {
|
||||||
const message_content = content.text
|
const message_content = content.text
|
||||||
const annotations = message_content.annotations
|
const annotations = message_content.annotations
|
||||||
|
|
||||||
const dirPath = path.join(getUserHome(), '.flowise', 'openai-assistant')
|
|
||||||
|
|
||||||
// Iterate over the annotations
|
// Iterate over the annotations
|
||||||
for (let index = 0; index < annotations.length; index++) {
|
for (let index = 0; index < annotations.length; index++) {
|
||||||
const annotation = annotations[index]
|
const annotation = annotations[index]
|
||||||
@@ -407,13 +635,13 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
|
|
||||||
// Gather citations based on annotation attributes
|
// Gather citations based on annotation attributes
|
||||||
const file_citation = (annotation as OpenAI.Beta.Threads.Messages.FileCitationAnnotation).file_citation
|
const file_citation = (annotation as OpenAI.Beta.Threads.Messages.FileCitationAnnotation).file_citation
|
||||||
|
|
||||||
if (file_citation) {
|
if (file_citation) {
|
||||||
const cited_file = await openai.files.retrieve(file_citation.file_id)
|
const cited_file = await openai.files.retrieve(file_citation.file_id)
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
||||||
filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', fileName)
|
|
||||||
if (!disableFileDownload) {
|
if (!disableFileDownload) {
|
||||||
await downloadFile(cited_file, filePath, dirPath, openAIApiKey)
|
filePath = await downloadFile(openAIApiKey, cited_file, fileName, options.chatflowid, threadId)
|
||||||
fileAnnotations.push({
|
fileAnnotations.push({
|
||||||
filePath,
|
filePath,
|
||||||
fileName
|
fileName
|
||||||
@@ -425,9 +653,8 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
const cited_file = await openai.files.retrieve(file_path.file_id)
|
const cited_file = await openai.files.retrieve(file_path.file_id)
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
const fileName = cited_file.filename.split(/[\/\\]/).pop() ?? cited_file.filename
|
||||||
filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', fileName)
|
|
||||||
if (!disableFileDownload) {
|
if (!disableFileDownload) {
|
||||||
await downloadFile(cited_file, filePath, dirPath, openAIApiKey)
|
filePath = await downloadFile(openAIApiKey, cited_file, fileName, options.chatflowid, threadId)
|
||||||
fileAnnotations.push({
|
fileAnnotations.push({
|
||||||
filePath,
|
filePath,
|
||||||
fileName
|
fileName
|
||||||
@@ -448,19 +675,14 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
returnVal += content.text.value
|
returnVal += content.text.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenticularBracketRegex = /【[^】]*】/g
|
|
||||||
returnVal = returnVal.replace(lenticularBracketRegex, '')
|
returnVal = returnVal.replace(lenticularBracketRegex, '')
|
||||||
} else {
|
} else {
|
||||||
const content = assistantMessages[0].content[i] as ImageFileContentBlock
|
const content = assistantMessages[0].content[i] as OpenAI.Beta.Threads.Messages.ImageFileContentBlock
|
||||||
const fileId = content.image_file.file_id
|
const fileId = content.image_file.file_id
|
||||||
const fileObj = await openai.files.retrieve(fileId)
|
const fileObj = await openai.files.retrieve(fileId)
|
||||||
const dirPath = path.join(getUserHome(), '.flowise', 'openai-assistant')
|
|
||||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', `${fileObj.filename}.png`)
|
|
||||||
|
|
||||||
await downloadImg(openai, fileId, filePath, dirPath)
|
const buffer = await downloadImg(openai, fileId, `${fileObj.filename}.png`, options.chatflowid, threadId)
|
||||||
|
const base64String = Buffer.from(buffer).toString('base64')
|
||||||
const bitmap = fsDefault.readFileSync(filePath)
|
|
||||||
const base64String = Buffer.from(bitmap).toString('base64')
|
|
||||||
|
|
||||||
// TODO: Use a file path and retrieve image on the fly. Storing as base64 to localStorage and database will easily hit limits
|
// TODO: Use a file path and retrieve image on the fly. Storing as base64 to localStorage and database will easily hit limits
|
||||||
const imgHTML = `<img src="data:image/png;base64,${base64String}" width="100%" height="max-content" alt="${fileObj.filename}" /><br/>`
|
const imgHTML = `<img src="data:image/png;base64,${base64String}" width="100%" height="max-content" alt="${fileObj.filename}" /><br/>`
|
||||||
@@ -468,7 +690,6 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageRegex = /<img[^>]*\/>/g
|
|
||||||
let llmOutput = returnVal.replace(imageRegex, '')
|
let llmOutput = returnVal.replace(imageRegex, '')
|
||||||
llmOutput = llmOutput.replace('<br/>', '')
|
llmOutput = llmOutput.replace('<br/>', '')
|
||||||
|
|
||||||
@@ -488,7 +709,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadImg = async (openai: OpenAI, fileId: string, filePath: string, dirPath: string) => {
|
const downloadImg = async (openai: OpenAI, fileId: string, fileName: string, ...paths: string[]) => {
|
||||||
const response = await openai.files.content(fileId)
|
const response = await openai.files.content(fileId)
|
||||||
|
|
||||||
// Extract the binary data from the Response object
|
// Extract the binary data from the Response object
|
||||||
@@ -496,15 +717,14 @@ const downloadImg = async (openai: OpenAI, fileId: string, filePath: string, dir
|
|||||||
|
|
||||||
// Convert the binary data to a Buffer
|
// Convert the binary data to a Buffer
|
||||||
const image_data_buffer = Buffer.from(image_data)
|
const image_data_buffer = Buffer.from(image_data)
|
||||||
|
const mime = 'image/png'
|
||||||
|
|
||||||
// Save the image to a specific location
|
await addFileToStorage(mime, image_data_buffer, fileName, ...paths)
|
||||||
if (!fsDefault.existsSync(dirPath)) {
|
|
||||||
fsDefault.mkdirSync(path.dirname(filePath), { recursive: true })
|
return image_data_buffer
|
||||||
}
|
|
||||||
fsDefault.writeFileSync(filePath, image_data_buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadFile = async (fileObj: any, filePath: string, dirPath: string, openAIApiKey: string) => {
|
const downloadFile = async (openAIApiKey: string, fileObj: any, fileName: string, ...paths: string[]) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`https://api.openai.com/v1/files/${fileObj.id}/content`, {
|
const response = await fetch(`https://api.openai.com/v1/files/${fileObj.id}/content`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -515,20 +735,17 @@ const downloadFile = async (fileObj: any, filePath: string, dirPath: string, ope
|
|||||||
throw new Error(`HTTP error! status: ${response.status}`)
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
// Extract the binary data from the Response object
|
||||||
if (!fsDefault.existsSync(dirPath)) {
|
const data = await response.arrayBuffer()
|
||||||
fsDefault.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
||||||
}
|
|
||||||
const dest = fsDefault.createWriteStream(filePath)
|
|
||||||
response.body.pipe(dest)
|
|
||||||
response.body.on('end', () => resolve())
|
|
||||||
dest.on('error', reject)
|
|
||||||
})
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// Convert the binary data to a Buffer
|
||||||
console.log('File downloaded and written to', filePath)
|
const data_buffer = Buffer.from(data)
|
||||||
|
const mime = 'application/octet-stream'
|
||||||
|
|
||||||
|
return await addFileToStorage(mime, data_buffer, fileName, ...paths)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error downloading or writing the file:', error)
|
console.error('Error downloading or writing the file:', error)
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
"node-html-markdown": "^1.3.0",
|
"node-html-markdown": "^1.3.0",
|
||||||
"notion-to-md": "^3.1.1",
|
"notion-to-md": "^3.1.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"openai": "^4.32.1",
|
"openai": "^4.38.3",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"pdfjs-dist": "^3.7.107",
|
"pdfjs-dist": "^3.7.107",
|
||||||
"pg": "^8.11.2",
|
"pg": "^8.11.2",
|
||||||
|
|||||||
@@ -420,6 +420,11 @@ export class AnalyticHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (langfuseTraceClient) {
|
if (langfuseTraceClient) {
|
||||||
|
langfuseTraceClient.update({
|
||||||
|
input: {
|
||||||
|
text: input
|
||||||
|
}
|
||||||
|
})
|
||||||
const span = langfuseTraceClient.span({
|
const span = langfuseTraceClient.span({
|
||||||
name,
|
name,
|
||||||
input: {
|
input: {
|
||||||
@@ -472,6 +477,14 @@ export class AnalyticHandler {
|
|||||||
span.end({
|
span.end({
|
||||||
output
|
output
|
||||||
})
|
})
|
||||||
|
const langfuseTraceClient = this.handlers['langFuse'].trace[returnIds['langFuse'].trace]
|
||||||
|
if (langfuseTraceClient) {
|
||||||
|
langfuseTraceClient.update({
|
||||||
|
output: {
|
||||||
|
output
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
if (shutdown) {
|
if (shutdown) {
|
||||||
const langfuse: Langfuse = this.handlers['langFuse'].client
|
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||||
await langfuse.shutdownAsync()
|
await langfuse.shutdownAsync()
|
||||||
@@ -513,6 +526,14 @@ export class AnalyticHandler {
|
|||||||
error
|
error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const langfuseTraceClient = this.handlers['langFuse'].trace[returnIds['langFuse'].trace]
|
||||||
|
if (langfuseTraceClient) {
|
||||||
|
langfuseTraceClient.update({
|
||||||
|
output: {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
if (shutdown) {
|
if (shutdown) {
|
||||||
const langfuse: Langfuse = this.handlers['langFuse'].client
|
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||||
await langfuse.shutdownAsync()
|
await langfuse.shutdownAsync()
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export const addBase64FilesToStorage = async (file: string, chatflowid: string,
|
|||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
const mime = splitDataURI[0].split(':')[1].split(';')[0]
|
const mime = splitDataURI[0].split(':')[1].split(';')[0]
|
||||||
|
|
||||||
const key = chatflowid + '/' + filename
|
const Key = chatflowid + '/' + filename
|
||||||
const putObjCmd = new PutObjectCommand({
|
const putObjCmd = new PutObjectCommand({
|
||||||
Bucket,
|
Bucket,
|
||||||
Key: key,
|
Key,
|
||||||
ContentEncoding: 'base64', // required for binary data
|
ContentEncoding: 'base64', // required for binary data
|
||||||
ContentType: mime,
|
ContentType: mime,
|
||||||
Body: bf
|
Body: bf
|
||||||
@@ -61,6 +61,7 @@ export const addFileToStorage = async (mime: string, bf: Buffer, fileName: strin
|
|||||||
Body: bf
|
Body: bf
|
||||||
})
|
})
|
||||||
await s3Client.send(putObjCmd)
|
await s3Client.send(putObjCmd)
|
||||||
|
return 'FILE-STORAGE::' + fileName
|
||||||
} else {
|
} else {
|
||||||
const dir = path.join(getStoragePath(), ...paths)
|
const dir = path.join(getStoragePath(), ...paths)
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
@@ -69,6 +70,7 @@ export const addFileToStorage = async (mime: string, bf: Buffer, fileName: strin
|
|||||||
|
|
||||||
const filePath = path.join(dir, fileName)
|
const filePath = path.join(dir, fileName)
|
||||||
fs.writeFileSync(filePath, bf)
|
fs.writeFileSync(filePath, bf)
|
||||||
|
return 'FILE-STORAGE::' + fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
|
import openAIAssistantVectorStoreService from '../../services/openai-assistants-vector-store'
|
||||||
|
|
||||||
|
const getAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.getAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.getAssistantVectorStore(req.query.credential as string, req.params.id)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.listAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.listAssistantVectorStore(req.query.credential as string)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (!req.body) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.createAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.createAssistantVectorStore(req.query.credential as string, req.body)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!req.body) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.updateAssistantVectorStore(
|
||||||
|
req.query.credential as string,
|
||||||
|
req.params.id,
|
||||||
|
req.body
|
||||||
|
)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.deleteAssistantVectorStore - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.updateAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.deleteAssistantVectorStore(
|
||||||
|
req.query.credential as string,
|
||||||
|
req.params.id as string
|
||||||
|
)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFilesToAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (!req.body) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const files = req.files ?? []
|
||||||
|
const uploadFiles: { filePath: string; fileName: string }[] = []
|
||||||
|
|
||||||
|
if (Array.isArray(files)) {
|
||||||
|
for (const file of files) {
|
||||||
|
uploadFiles.push({
|
||||||
|
filePath: file.path,
|
||||||
|
fileName: file.originalname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.uploadFilesToAssistantVectorStore(
|
||||||
|
req.query.credential as string,
|
||||||
|
req.params.id as string,
|
||||||
|
uploadFiles
|
||||||
|
)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteFilesFromAssistantVectorStore = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (!req.body) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - body not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.params === 'undefined' || !req.params.id) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - id not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResponse = await openAIAssistantVectorStoreService.deleteFilesFromAssistantVectorStore(
|
||||||
|
req.query.credential as string,
|
||||||
|
req.params.id as string,
|
||||||
|
req.body.file_ids
|
||||||
|
)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAssistantVectorStore,
|
||||||
|
listAssistantVectorStore,
|
||||||
|
createAssistantVectorStore,
|
||||||
|
updateAssistantVectorStore,
|
||||||
|
deleteAssistantVectorStore,
|
||||||
|
uploadFilesToAssistantVectorStore,
|
||||||
|
deleteFilesFromAssistantVectorStore
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { Request, Response, NextFunction } from 'express'
|
import { Request, Response, NextFunction } from 'express'
|
||||||
import path from 'path'
|
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import openaiAssistantsService from '../../services/openai-assistants'
|
import openaiAssistantsService from '../../services/openai-assistants'
|
||||||
import { getUserHome } from '../../utils'
|
|
||||||
import contentDisposition from 'content-disposition'
|
import contentDisposition from 'content-disposition'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import { streamStorageFile } from 'flowise-components'
|
||||||
|
|
||||||
// List available assistants
|
// List available assistants
|
||||||
const getAllOpenaiAssistants = async (req: Request, res: Response, next: NextFunction) => {
|
const getAllOpenaiAssistants = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
@@ -48,27 +47,57 @@ const getSingleOpenaiAssistant = async (req: Request, res: Response, next: NextF
|
|||||||
// Download file from assistant
|
// Download file from assistant
|
||||||
const getFileFromAssistant = async (req: Request, res: Response, next: NextFunction) => {
|
const getFileFromAssistant = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', req.body.fileName)
|
if (!req.body.chatflowId || !req.body.chatId || !req.body.fileName) {
|
||||||
//raise error if file path is not absolute
|
return res.status(500).send(`Invalid file path`)
|
||||||
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
|
}
|
||||||
//raise error if file path contains '..'
|
const chatflowId = req.body.chatflowId as string
|
||||||
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
|
const chatId = req.body.chatId as string
|
||||||
//only return from the .flowise openai-assistant folder
|
const fileName = req.body.fileName as string
|
||||||
if (!(filePath.includes('.flowise') && filePath.includes('openai-assistant'))) return res.status(500).send(`Invalid file path`)
|
res.setHeader('Content-Disposition', contentDisposition(fileName))
|
||||||
if (fs.existsSync(filePath)) {
|
const fileStream = await streamStorageFile(chatflowId, chatId, fileName)
|
||||||
res.setHeader('Content-Disposition', contentDisposition(path.basename(filePath)))
|
|
||||||
const fileStream = fs.createReadStream(filePath)
|
if (!fileStream) throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: getFileFromAssistant`)
|
||||||
|
|
||||||
|
if (fileStream instanceof fs.ReadStream && fileStream?.pipe) {
|
||||||
fileStream.pipe(res)
|
fileStream.pipe(res)
|
||||||
} else {
|
} else {
|
||||||
return res.status(404).send(`File ${req.body.fileName} not found`)
|
res.send(fileStream)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadAssistantFiles = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (typeof req.query === 'undefined' || !req.query.credential) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.PRECONDITION_FAILED,
|
||||||
|
`Error: openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore - credential not provided!`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const files = req.files ?? []
|
||||||
|
const uploadFiles: { filePath: string; fileName: string }[] = []
|
||||||
|
|
||||||
|
if (Array.isArray(files)) {
|
||||||
|
for (const file of files) {
|
||||||
|
uploadFiles.push({
|
||||||
|
filePath: file.path,
|
||||||
|
fileName: file.originalname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiResponse = await openaiAssistantsService.uploadFilesToAssistant(req.query.credential as string, uploadFiles)
|
||||||
|
return res.json(apiResponse)
|
||||||
|
} catch (error) {
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAllOpenaiAssistants,
|
getAllOpenaiAssistants,
|
||||||
getSingleOpenaiAssistant,
|
getSingleOpenaiAssistant,
|
||||||
getFileFromAssistant
|
getFileFromAssistant,
|
||||||
|
uploadAssistantFiles
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export class App {
|
|||||||
'/api/v1/components-credentials-icon/',
|
'/api/v1/components-credentials-icon/',
|
||||||
'/api/v1/chatflows-streaming',
|
'/api/v1/chatflows-streaming',
|
||||||
'/api/v1/chatflows-uploads',
|
'/api/v1/chatflows-uploads',
|
||||||
'/api/v1/openai-assistants-file',
|
'/api/v1/openai-assistants-file/download',
|
||||||
'/api/v1/feedback',
|
'/api/v1/feedback',
|
||||||
'/api/v1/get-upload-file',
|
'/api/v1/get-upload-file',
|
||||||
'/api/v1/ip'
|
'/api/v1/ip'
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import nodeLoadMethodRouter from './node-load-methods'
|
|||||||
import nodesRouter from './nodes'
|
import nodesRouter from './nodes'
|
||||||
import openaiAssistantsRouter from './openai-assistants'
|
import openaiAssistantsRouter from './openai-assistants'
|
||||||
import openaiAssistantsFileRouter from './openai-assistants-files'
|
import openaiAssistantsFileRouter from './openai-assistants-files'
|
||||||
|
import openaiAssistantsVectorStoreRouter from './openai-assistants-vector-store'
|
||||||
import predictionRouter from './predictions'
|
import predictionRouter from './predictions'
|
||||||
import promptListsRouter from './prompts-lists'
|
import promptListsRouter from './prompts-lists'
|
||||||
import publicChatbotRouter from './public-chatbots'
|
import publicChatbotRouter from './public-chatbots'
|
||||||
@@ -65,6 +66,7 @@ router.use('/node-load-method', nodeLoadMethodRouter)
|
|||||||
router.use('/nodes', nodesRouter)
|
router.use('/nodes', nodesRouter)
|
||||||
router.use('/openai-assistants', openaiAssistantsRouter)
|
router.use('/openai-assistants', openaiAssistantsRouter)
|
||||||
router.use('/openai-assistants-file', openaiAssistantsFileRouter)
|
router.use('/openai-assistants-file', openaiAssistantsFileRouter)
|
||||||
|
router.use('/openai-assistants-vector-store', openaiAssistantsVectorStoreRouter)
|
||||||
router.use('/prediction', predictionRouter)
|
router.use('/prediction', predictionRouter)
|
||||||
router.use('/prompts-list', promptListsRouter)
|
router.use('/prompts-list', promptListsRouter)
|
||||||
router.use('/public-chatbotConfig', publicChatbotRouter)
|
router.use('/public-chatbotConfig', publicChatbotRouter)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import multer from 'multer'
|
||||||
|
import path from 'path'
|
||||||
import openaiAssistantsController from '../../controllers/openai-assistants'
|
import openaiAssistantsController from '../../controllers/openai-assistants'
|
||||||
const router = express.Router()
|
|
||||||
|
|
||||||
// CREATE
|
const router = express.Router()
|
||||||
router.post('/', openaiAssistantsController.getFileFromAssistant)
|
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
|
||||||
|
|
||||||
|
router.post('/download/', openaiAssistantsController.getFileFromAssistant)
|
||||||
|
router.post('/upload/', upload.array('files'), openaiAssistantsController.uploadAssistantFiles)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import multer from 'multer'
|
||||||
|
import path from 'path'
|
||||||
|
import openaiAssistantsVectorStoreController from '../../controllers/openai-assistants-vector-store'
|
||||||
|
|
||||||
|
const router = express.Router()
|
||||||
|
const upload = multer({ dest: `${path.join(__dirname, '..', '..', '..', 'uploads')}/` })
|
||||||
|
|
||||||
|
// CREATE
|
||||||
|
router.post('/', openaiAssistantsVectorStoreController.createAssistantVectorStore)
|
||||||
|
|
||||||
|
// READ
|
||||||
|
router.get('/:id', openaiAssistantsVectorStoreController.getAssistantVectorStore)
|
||||||
|
|
||||||
|
// LIST
|
||||||
|
router.get('/', openaiAssistantsVectorStoreController.listAssistantVectorStore)
|
||||||
|
|
||||||
|
// UPDATE
|
||||||
|
router.put(['/', '/:id'], openaiAssistantsVectorStoreController.updateAssistantVectorStore)
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
router.delete(['/', '/:id'], openaiAssistantsVectorStoreController.deleteAssistantVectorStore)
|
||||||
|
|
||||||
|
// POST
|
||||||
|
router.post('/:id', upload.array('files'), openaiAssistantsVectorStoreController.uploadFilesToAssistantVectorStore)
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
router.patch(['/', '/:id'], openaiAssistantsVectorStoreController.deleteFilesFromAssistantVectorStore)
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
import path from 'path'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { uniqWith, isEqual } from 'lodash'
|
import { uniqWith, isEqual, cloneDeep } from 'lodash'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
import { Assistant } from '../../database/entities/Assistant'
|
import { Assistant } from '../../database/entities/Assistant'
|
||||||
import { Credential } from '../../database/entities/Credential'
|
import { Credential } from '../../database/entities/Credential'
|
||||||
import { getUserHome, decryptCredentialData, getAppVersion } from '../../utils'
|
import { decryptCredentialData, getAppVersion } from '../../utils'
|
||||||
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
import { getErrorMessage } from '../../errors/utils'
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
|
|
||||||
@@ -34,6 +32,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
|||||||
}
|
}
|
||||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
|
||||||
|
// Prepare tools
|
||||||
let tools = []
|
let tools = []
|
||||||
if (assistantDetails.tools) {
|
if (assistantDetails.tools) {
|
||||||
for (const tool of assistantDetails.tools ?? []) {
|
for (const tool of assistantDetails.tools ?? []) {
|
||||||
@@ -43,40 +42,25 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assistantDetails.uploadFiles) {
|
// Save tool_resources to be stored later into database
|
||||||
// Base64 strings
|
const savedToolResources = cloneDeep(assistantDetails.tool_resources)
|
||||||
let files: string[] = []
|
|
||||||
const fileBase64 = assistantDetails.uploadFiles
|
// Cleanup tool_resources for creating assistant
|
||||||
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
if (assistantDetails.tool_resources) {
|
||||||
files = JSON.parse(fileBase64)
|
for (const toolResource in assistantDetails.tool_resources) {
|
||||||
} else {
|
if (toolResource === 'file_search') {
|
||||||
files = [fileBase64]
|
assistantDetails.tool_resources['file_search'] = {
|
||||||
}
|
vector_store_ids: assistantDetails.tool_resources['file_search'].vector_store_ids
|
||||||
|
}
|
||||||
const uploadedFiles = []
|
} else if (toolResource === 'code_interpreter') {
|
||||||
for (const file of files) {
|
assistantDetails.tool_resources['code_interpreter'] = {
|
||||||
const splitDataURI = file.split(',')
|
file_ids: assistantDetails.tool_resources['code_interpreter'].file_ids
|
||||||
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
}
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
}
|
||||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
|
}
|
||||||
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
|
|
||||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
fs.writeFileSync(filePath, bf)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdFile = await openai.files.create({
|
|
||||||
file: fs.createReadStream(filePath),
|
|
||||||
purpose: 'assistants'
|
|
||||||
})
|
|
||||||
uploadedFiles.push(createdFile)
|
|
||||||
|
|
||||||
fs.unlinkSync(filePath)
|
|
||||||
}
|
|
||||||
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the assistant doesn't exist, create a new one
|
||||||
if (!assistantDetails.id) {
|
if (!assistantDetails.id) {
|
||||||
const newAssistant = await openai.beta.assistants.create({
|
const newAssistant = await openai.beta.assistants.create({
|
||||||
name: assistantDetails.name,
|
name: assistantDetails.name,
|
||||||
@@ -84,12 +68,15 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
|||||||
instructions: assistantDetails.instructions,
|
instructions: assistantDetails.instructions,
|
||||||
model: assistantDetails.model,
|
model: assistantDetails.model,
|
||||||
tools,
|
tools,
|
||||||
file_ids: (assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)
|
tool_resources: assistantDetails.tool_resources,
|
||||||
|
temperature: assistantDetails.temperature,
|
||||||
|
top_p: assistantDetails.top_p
|
||||||
})
|
})
|
||||||
assistantDetails.id = newAssistant.id
|
assistantDetails.id = newAssistant.id
|
||||||
} else {
|
} else {
|
||||||
const retrievedAssistant = await openai.beta.assistants.retrieve(assistantDetails.id)
|
const retrievedAssistant = await openai.beta.assistants.retrieve(assistantDetails.id)
|
||||||
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
|
let filteredTools = uniqWith([...retrievedAssistant.tools.filter((tool) => tool.type === 'function'), ...tools], isEqual)
|
||||||
|
// Remove empty functions
|
||||||
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
||||||
|
|
||||||
await openai.beta.assistants.update(assistantDetails.id, {
|
await openai.beta.assistants.update(assistantDetails.id, {
|
||||||
@@ -98,17 +85,16 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
|||||||
instructions: assistantDetails.instructions ?? '',
|
instructions: assistantDetails.instructions ?? '',
|
||||||
model: assistantDetails.model,
|
model: assistantDetails.model,
|
||||||
tools: filteredTools,
|
tools: filteredTools,
|
||||||
file_ids: uniqWith(
|
tool_resources: assistantDetails.tool_resources,
|
||||||
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
|
temperature: assistantDetails.temperature,
|
||||||
isEqual
|
top_p: assistantDetails.top_p
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const newAssistantDetails = {
|
const newAssistantDetails = {
|
||||||
...assistantDetails
|
...assistantDetails
|
||||||
}
|
}
|
||||||
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
|
if (savedToolResources) newAssistantDetails.tool_resources = savedToolResources
|
||||||
|
|
||||||
requestBody.details = JSON.stringify(newAssistantDetails)
|
requestBody.details = JSON.stringify(newAssistantDetails)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -117,7 +103,7 @@ const createAssistant = async (requestBody: any): Promise<any> => {
|
|||||||
const newAssistant = new Assistant()
|
const newAssistant = new Assistant()
|
||||||
Object.assign(newAssistant, requestBody)
|
Object.assign(newAssistant, requestBody)
|
||||||
|
|
||||||
const assistant = await appServer.AppDataSource.getRepository(Assistant).create(newAssistant)
|
const assistant = appServer.AppDataSource.getRepository(Assistant).create(newAssistant)
|
||||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
||||||
|
|
||||||
await appServer.telemetry.sendTelemetry('assistant_created', {
|
await appServer.telemetry.sendTelemetry('assistant_created', {
|
||||||
@@ -249,42 +235,26 @@ const updateAssistant = async (assistantId: string, requestBody: any): Promise<a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assistantDetails.uploadFiles) {
|
// Save tool_resources to be stored later into database
|
||||||
// Base64 strings
|
const savedToolResources = cloneDeep(assistantDetails.tool_resources)
|
||||||
let files: string[] = []
|
|
||||||
const fileBase64 = assistantDetails.uploadFiles
|
|
||||||
if (fileBase64.startsWith('[') && fileBase64.endsWith(']')) {
|
|
||||||
files = JSON.parse(fileBase64)
|
|
||||||
} else {
|
|
||||||
files = [fileBase64]
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadedFiles = []
|
// Cleanup tool_resources before updating
|
||||||
for (const file of files) {
|
if (assistantDetails.tool_resources) {
|
||||||
const splitDataURI = file.split(',')
|
for (const toolResource in assistantDetails.tool_resources) {
|
||||||
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
|
if (toolResource === 'file_search') {
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
assistantDetails.tool_resources['file_search'] = {
|
||||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', filename)
|
vector_store_ids: assistantDetails.tool_resources['file_search'].vector_store_ids
|
||||||
if (!fs.existsSync(path.join(getUserHome(), '.flowise', 'openai-assistant'))) {
|
}
|
||||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
} else if (toolResource === 'code_interpreter') {
|
||||||
|
assistantDetails.tool_resources['code_interpreter'] = {
|
||||||
|
file_ids: assistantDetails.tool_resources['code_interpreter'].file_ids
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
fs.writeFileSync(filePath, bf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdFile = await openai.files.create({
|
|
||||||
file: fs.createReadStream(filePath),
|
|
||||||
purpose: 'assistants'
|
|
||||||
})
|
|
||||||
uploadedFiles.push(createdFile)
|
|
||||||
|
|
||||||
fs.unlinkSync(filePath)
|
|
||||||
}
|
}
|
||||||
assistantDetails.files = [...assistantDetails.files, ...uploadedFiles]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const retrievedAssistant = await openai.beta.assistants.retrieve(openAIAssistantId)
|
const retrievedAssistant = await openai.beta.assistants.retrieve(openAIAssistantId)
|
||||||
let filteredTools = uniqWith([...retrievedAssistant.tools, ...tools], isEqual)
|
let filteredTools = uniqWith([...retrievedAssistant.tools.filter((tool) => tool.type === 'function'), ...tools], isEqual)
|
||||||
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
filteredTools = filteredTools.filter((tool) => !(tool.type === 'function' && !(tool as any).function))
|
||||||
|
|
||||||
await openai.beta.assistants.update(openAIAssistantId, {
|
await openai.beta.assistants.update(openAIAssistantId, {
|
||||||
@@ -293,23 +263,22 @@ const updateAssistant = async (assistantId: string, requestBody: any): Promise<a
|
|||||||
instructions: assistantDetails.instructions,
|
instructions: assistantDetails.instructions,
|
||||||
model: assistantDetails.model,
|
model: assistantDetails.model,
|
||||||
tools: filteredTools,
|
tools: filteredTools,
|
||||||
file_ids: uniqWith(
|
tool_resources: assistantDetails.tool_resources,
|
||||||
[...retrievedAssistant.file_ids, ...(assistantDetails.files ?? []).map((file: OpenAI.Files.FileObject) => file.id)],
|
temperature: assistantDetails.temperature,
|
||||||
isEqual
|
top_p: assistantDetails.top_p
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const newAssistantDetails = {
|
const newAssistantDetails = {
|
||||||
...assistantDetails,
|
...assistantDetails,
|
||||||
id: openAIAssistantId
|
id: openAIAssistantId
|
||||||
}
|
}
|
||||||
if (newAssistantDetails.uploadFiles) delete newAssistantDetails.uploadFiles
|
if (savedToolResources) newAssistantDetails.tool_resources = savedToolResources
|
||||||
|
|
||||||
const updateAssistant = new Assistant()
|
const updateAssistant = new Assistant()
|
||||||
body.details = JSON.stringify(newAssistantDetails)
|
body.details = JSON.stringify(newAssistantDetails)
|
||||||
Object.assign(updateAssistant, body)
|
Object.assign(updateAssistant, body)
|
||||||
|
|
||||||
await appServer.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
|
appServer.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
|
||||||
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
const dbResponse = await appServer.AppDataSource.getRepository(Assistant).save(assistant)
|
||||||
return dbResponse
|
return dbResponse
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import OpenAI from 'openai'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { Credential } from '../../database/entities/Credential'
|
||||||
|
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
|
||||||
|
import { getErrorMessage } from '../../errors/utils'
|
||||||
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
|
import { decryptCredentialData } from '../../utils'
|
||||||
|
|
||||||
|
const getAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const dbResponse = await openai.beta.vectorStores.retrieve(vectorStoreId)
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.getAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listAssistantVectorStore = async (credentialId: string) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const dbResponse = await openai.beta.vectorStores.list()
|
||||||
|
return dbResponse.data
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.listAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAssistantVectorStore = async (credentialId: string, obj: OpenAI.Beta.VectorStores.VectorStoreCreateParams) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const dbResponse = await openai.beta.vectorStores.create(obj)
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.createAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAssistantVectorStore = async (
|
||||||
|
credentialId: string,
|
||||||
|
vectorStoreId: string,
|
||||||
|
obj: OpenAI.Beta.VectorStores.VectorStoreUpdateParams
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const dbResponse = await openai.beta.vectorStores.update(vectorStoreId, obj)
|
||||||
|
const vectorStoreFiles = await openai.beta.vectorStores.files.list(vectorStoreId)
|
||||||
|
if (vectorStoreFiles.data?.length) {
|
||||||
|
const files = []
|
||||||
|
for (const file of vectorStoreFiles.data) {
|
||||||
|
const fileData = await openai.files.retrieve(file.id)
|
||||||
|
files.push(fileData)
|
||||||
|
}
|
||||||
|
;(dbResponse as any).files = files
|
||||||
|
}
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.updateAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAssistantVectorStore = async (credentialId: string, vectorStoreId: string) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const dbResponse = await openai.beta.vectorStores.del(vectorStoreId)
|
||||||
|
return dbResponse
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.deleteAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFilesToAssistantVectorStore = async (
|
||||||
|
credentialId: string,
|
||||||
|
vectorStoreId: string,
|
||||||
|
files: { filePath: string; fileName: string }[]
|
||||||
|
): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const uploadedFiles = []
|
||||||
|
for (const file of files) {
|
||||||
|
const createdFile = await openai.files.create({
|
||||||
|
file: new File([new Blob([fs.readFileSync(file.filePath)])], file.fileName),
|
||||||
|
purpose: 'assistants'
|
||||||
|
})
|
||||||
|
uploadedFiles.push(createdFile)
|
||||||
|
fs.unlinkSync(file.filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const file_ids = [...uploadedFiles.map((file) => file.id)]
|
||||||
|
|
||||||
|
const res = await openai.beta.vectorStores.fileBatches.createAndPoll(vectorStoreId, {
|
||||||
|
file_ids
|
||||||
|
})
|
||||||
|
if (res.status === 'completed' && res.file_counts.completed === uploadedFiles.length) return uploadedFiles
|
||||||
|
else if (res.status === 'failed')
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - Upload failed!'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
'Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - Upload cancelled!'
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteFilesFromAssistantVectorStore = async (credentialId: string, vectorStoreId: string, file_ids: string[]) => {
|
||||||
|
try {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const deletedFileIds = []
|
||||||
|
let count = 0
|
||||||
|
for (const file of file_ids) {
|
||||||
|
const res = await openai.beta.vectorStores.files.del(vectorStoreId, file)
|
||||||
|
if (res.deleted) {
|
||||||
|
deletedFileIds.push(file)
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { deletedFileIds, count }
|
||||||
|
} catch (error) {
|
||||||
|
throw new InternalFlowiseError(
|
||||||
|
StatusCodes.INTERNAL_SERVER_ERROR,
|
||||||
|
`Error: openaiAssistantsVectorStoreService.uploadFilesToAssistantVectorStore - ${getErrorMessage(error)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAssistantVectorStore,
|
||||||
|
listAssistantVectorStore,
|
||||||
|
createAssistantVectorStore,
|
||||||
|
updateAssistantVectorStore,
|
||||||
|
deleteAssistantVectorStore,
|
||||||
|
uploadFilesToAssistantVectorStore,
|
||||||
|
deleteFilesFromAssistantVectorStore
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
import fs from 'fs'
|
||||||
import { StatusCodes } from 'http-status-codes'
|
import { StatusCodes } from 'http-status-codes'
|
||||||
import { decryptCredentialData } from '../../utils'
|
import { decryptCredentialData } from '../../utils'
|
||||||
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
|
||||||
@@ -59,8 +60,18 @@ const getSingleOpenaiAssistant = async (credentialId: string, assistantId: strin
|
|||||||
const dbResponse = await openai.beta.assistants.retrieve(assistantId)
|
const dbResponse = await openai.beta.assistants.retrieve(assistantId)
|
||||||
const resp = await openai.files.list()
|
const resp = await openai.files.list()
|
||||||
const existingFiles = resp.data ?? []
|
const existingFiles = resp.data ?? []
|
||||||
if (dbResponse.file_ids && dbResponse.file_ids.length) {
|
if (dbResponse.tool_resources?.code_interpreter?.file_ids?.length) {
|
||||||
;(dbResponse as any).files = existingFiles.filter((file) => dbResponse.file_ids.includes(file.id))
|
;(dbResponse.tool_resources.code_interpreter as any).files = [
|
||||||
|
...existingFiles.filter((file) => dbResponse.tool_resources?.code_interpreter?.file_ids?.includes(file.id))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (dbResponse.tool_resources?.file_search?.vector_store_ids?.length) {
|
||||||
|
// Since there can only be 1 vector store per assistant
|
||||||
|
const vectorStoreId = dbResponse.tool_resources.file_search.vector_store_ids[0]
|
||||||
|
const vectorStoreFiles = await openai.beta.vectorStores.files.list(vectorStoreId)
|
||||||
|
const fileIds = vectorStoreFiles.data?.map((file) => file.id) ?? []
|
||||||
|
;(dbResponse.tool_resources.file_search as any).files = [...existingFiles.filter((file) => fileIds.includes(file.id))]
|
||||||
|
;(dbResponse.tool_resources.file_search as any).vector_store_object = await openai.beta.vectorStores.retrieve(vectorStoreId)
|
||||||
}
|
}
|
||||||
return dbResponse
|
return dbResponse
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -71,7 +82,38 @@ const getSingleOpenaiAssistant = async (credentialId: string, assistantId: strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadFilesToAssistant = async (credentialId: string, files: { filePath: string; fileName: string }[]) => {
|
||||||
|
const appServer = getRunningExpressApp()
|
||||||
|
const credential = await appServer.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: credentialId
|
||||||
|
})
|
||||||
|
if (!credential) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Credential ${credentialId} not found in the database!`)
|
||||||
|
}
|
||||||
|
// Decrpyt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||||
|
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||||
|
if (!openAIApiKey) {
|
||||||
|
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `OpenAI ApiKey not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
const uploadedFiles = []
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const createdFile = await openai.files.create({
|
||||||
|
file: new File([new Blob([fs.readFileSync(file.filePath)])], file.fileName),
|
||||||
|
purpose: 'assistants'
|
||||||
|
})
|
||||||
|
uploadedFiles.push(createdFile)
|
||||||
|
fs.unlinkSync(file.filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uploadedFiles
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAllOpenaiAssistants,
|
getAllOpenaiAssistants,
|
||||||
getSingleOpenaiAssistant
|
getSingleOpenaiAssistant,
|
||||||
|
uploadFilesToAssistant
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1066,6 +1066,9 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If agent is openAIAssistant, streaming is enabled
|
||||||
|
if (endingNodeData.name === 'openAIAssistant') return true
|
||||||
} else if (endingNodeData.category === 'Engine') {
|
} else if (endingNodeData.category === 'Engine') {
|
||||||
// Engines that are available to stream
|
// Engines that are available to stream
|
||||||
const whitelistEngine = ['contextChatEngine', 'simpleChatEngine', 'queryEngine', 'subQuestionQueryEngine']
|
const whitelistEngine = ['contextChatEngine', 'simpleChatEngine', 'queryEngine', 'subQuestionQueryEngine']
|
||||||
|
|||||||
@@ -1,20 +1,49 @@
|
|||||||
import client from './client'
|
import client from './client'
|
||||||
|
|
||||||
|
// OpenAI Assistant
|
||||||
|
const getAssistantObj = (id, credentialId) => client.get(`/openai-assistants/${id}?credential=${credentialId}`)
|
||||||
|
|
||||||
|
const getAllAvailableAssistants = (credentialId) => client.get(`/openai-assistants?credential=${credentialId}`)
|
||||||
|
|
||||||
|
// Assistant
|
||||||
|
const createNewAssistant = (body) => client.post(`/assistants`, body)
|
||||||
|
|
||||||
const getAllAssistants = () => client.get('/assistants')
|
const getAllAssistants = () => client.get('/assistants')
|
||||||
|
|
||||||
const getSpecificAssistant = (id) => client.get(`/assistants/${id}`)
|
const getSpecificAssistant = (id) => client.get(`/assistants/${id}`)
|
||||||
|
|
||||||
const getAssistantObj = (id, credential) => client.get(`/openai-assistants/${id}?credential=${credential}`)
|
|
||||||
|
|
||||||
const getAllAvailableAssistants = (credential) => client.get(`/openai-assistants?credential=${credential}`)
|
|
||||||
|
|
||||||
const createNewAssistant = (body) => client.post(`/assistants`, body)
|
|
||||||
|
|
||||||
const updateAssistant = (id, body) => client.put(`/assistants/${id}`, body)
|
const updateAssistant = (id, body) => client.put(`/assistants/${id}`, body)
|
||||||
|
|
||||||
const deleteAssistant = (id, isDeleteBoth) =>
|
const deleteAssistant = (id, isDeleteBoth) =>
|
||||||
isDeleteBoth ? client.delete(`/assistants/${id}?isDeleteBoth=true`) : client.delete(`/assistants/${id}`)
|
isDeleteBoth ? client.delete(`/assistants/${id}?isDeleteBoth=true`) : client.delete(`/assistants/${id}`)
|
||||||
|
|
||||||
|
// Vector Store
|
||||||
|
const getAssistantVectorStore = (id, credentialId) => client.get(`/openai-assistants-vector-store/${id}?credential=${credentialId}`)
|
||||||
|
|
||||||
|
const listAssistantVectorStore = (credentialId) => client.get(`/openai-assistants-vector-store?credential=${credentialId}`)
|
||||||
|
|
||||||
|
const createAssistantVectorStore = (credentialId, body) => client.post(`/openai-assistants-vector-store?credential=${credentialId}`, body)
|
||||||
|
|
||||||
|
const updateAssistantVectorStore = (id, credentialId, body) =>
|
||||||
|
client.put(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, body)
|
||||||
|
|
||||||
|
const deleteAssistantVectorStore = (id, credentialId) => client.delete(`/openai-assistants-vector-store/${id}?credential=${credentialId}`)
|
||||||
|
|
||||||
|
// Vector Store Files
|
||||||
|
const uploadFilesToAssistantVectorStore = (id, credentialId, formData) =>
|
||||||
|
client.post(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteFilesFromAssistantVectorStore = (id, credentialId, body) =>
|
||||||
|
client.patch(`/openai-assistants-vector-store/${id}?credential=${credentialId}`, body)
|
||||||
|
|
||||||
|
// Files
|
||||||
|
const uploadFilesToAssistant = (credentialId, formData) =>
|
||||||
|
client.post(`/openai-assistants-file/upload?credential=${credentialId}`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAllAssistants,
|
getAllAssistants,
|
||||||
getSpecificAssistant,
|
getSpecificAssistant,
|
||||||
@@ -22,5 +51,13 @@ export default {
|
|||||||
getAllAvailableAssistants,
|
getAllAvailableAssistants,
|
||||||
createNewAssistant,
|
createNewAssistant,
|
||||||
updateAssistant,
|
updateAssistant,
|
||||||
deleteAssistant
|
deleteAssistant,
|
||||||
|
getAssistantVectorStore,
|
||||||
|
listAssistantVectorStore,
|
||||||
|
updateAssistantVectorStore,
|
||||||
|
createAssistantVectorStore,
|
||||||
|
uploadFilesToAssistant,
|
||||||
|
uploadFilesToAssistantVectorStore,
|
||||||
|
deleteFilesFromAssistantVectorStore,
|
||||||
|
deleteAssistantVectorStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const [chatMessages, setChatMessages] = useState([])
|
const [chatMessages, setChatMessages] = useState([])
|
||||||
const [stats, setStats] = useState([])
|
const [stats, setStats] = useState([])
|
||||||
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
const [selectedMessageIndex, setSelectedMessageIndex] = useState(0)
|
||||||
|
const [selectedChatId, setSelectedChatId] = useState('')
|
||||||
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
|
||||||
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
const [sourceDialogProps, setSourceDialogProps] = useState({})
|
||||||
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
const [chatTypeFilter, setChatTypeFilter] = useState([])
|
||||||
@@ -283,6 +284,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const loadedMessages = []
|
const loadedMessages = []
|
||||||
for (let i = 0; i < chatmessages.length; i += 1) {
|
for (let i = 0; i < chatmessages.length; i += 1) {
|
||||||
const chatmsg = chatmessages[i]
|
const chatmsg = chatmessages[i]
|
||||||
|
setSelectedChatId(chatmsg.chatId)
|
||||||
if (!prevDate) {
|
if (!prevDate) {
|
||||||
prevDate = chatmsg.createdDate.split('T')[0]
|
prevDate = chatmsg.createdDate.split('T')[0]
|
||||||
loadedMessages.push({
|
loadedMessages.push({
|
||||||
@@ -383,8 +385,8 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
const downloadFile = async (fileAnnotation) => {
|
const downloadFile = async (fileAnnotation) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${baseURL}/api/v1/openai-assistants-file`,
|
`${baseURL}/api/v1/openai-assistants-file/download`,
|
||||||
{ fileName: fileAnnotation.fileName },
|
{ fileName: fileAnnotation.fileName, chatflowId: dialogProps.chatflow.id, chatId: selectedChatId },
|
||||||
{ responseType: 'blob' }
|
{ responseType: 'blob' }
|
||||||
)
|
)
|
||||||
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
||||||
@@ -444,6 +446,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||||||
setChatMessages([])
|
setChatMessages([])
|
||||||
setChatTypeFilter([])
|
setChatTypeFilter([])
|
||||||
setSelectedMessageIndex(0)
|
setSelectedMessageIndex(0)
|
||||||
|
setSelectedChatId('')
|
||||||
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
setStartDate(new Date().setMonth(new Date().getMonth() - 1))
|
||||||
setEndDate(new Date())
|
setEndDate(new Date())
|
||||||
setStats([])
|
setStats([])
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const StyledPopper = styled(Popper)({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Dropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => {
|
export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, disableClearable = false }) => {
|
||||||
const customization = useSelector((state) => state.customization)
|
const customization = useSelector((state) => state.customization)
|
||||||
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
|
const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value)
|
||||||
const getDefaultOptionValue = () => ''
|
const getDefaultOptionValue = () => ''
|
||||||
@@ -31,6 +31,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
disableClearable={disableClearable}
|
disableClearable={disableClearable}
|
||||||
size='small'
|
size='small'
|
||||||
|
loading={loading}
|
||||||
options={options || []}
|
options={options || []}
|
||||||
value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()}
|
value={findMatchingOptions(options, internalValue) || getDefaultOptionValue()}
|
||||||
onChange={(e, selection) => {
|
onChange={(e, selection) => {
|
||||||
@@ -61,6 +62,7 @@ export const Dropdown = ({ name, value, options, onSelect, disabled = false, dis
|
|||||||
Dropdown.propTypes = {
|
Dropdown.propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
loading: PropTypes.bool,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FormControl, Button } from '@mui/material'
|
|||||||
import { IconUpload } from '@tabler/icons'
|
import { IconUpload } from '@tabler/icons'
|
||||||
import { getFileName } from '@/utils/genericHelper'
|
import { getFileName } from '@/utils/genericHelper'
|
||||||
|
|
||||||
export const File = ({ value, fileType, onChange, disabled = false }) => {
|
export const File = ({ value, formDataUpload, fileType, onChange, onFormDataChange, disabled = false }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
const [myValue, setMyValue] = useState(value ?? '')
|
const [myValue, setMyValue] = useState(value ?? '')
|
||||||
@@ -54,8 +54,33 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFormDataUpload = async (e) => {
|
||||||
|
if (!e.target.files) return
|
||||||
|
|
||||||
|
if (e.target.files.length === 1) {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
const { name } = file
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('files', file)
|
||||||
|
setMyValue(`,filename:${name}`)
|
||||||
|
onChange(`,filename:${name}`)
|
||||||
|
onFormDataChange(formData)
|
||||||
|
} else if (e.target.files.length > 0) {
|
||||||
|
const formData = new FormData()
|
||||||
|
const values = []
|
||||||
|
for (let i = 0; i < e.target.files.length; i++) {
|
||||||
|
formData.append('files', e.target.files[i])
|
||||||
|
values.push(`,filename:${e.target.files[i].name}`)
|
||||||
|
}
|
||||||
|
setMyValue(JSON.stringify(values))
|
||||||
|
onChange(JSON.stringify(values))
|
||||||
|
onFormDataChange(formData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
<FormControl sx={{ mt: 1, width: '100%' }} size='small'>
|
||||||
|
{!formDataUpload && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
@@ -65,6 +90,7 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
|||||||
>
|
>
|
||||||
{myValue ? getFileName(myValue) : 'Choose a file to upload'}
|
{myValue ? getFileName(myValue) : 'Choose a file to upload'}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
@@ -74,7 +100,13 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
|||||||
sx={{ marginRight: '1rem' }}
|
sx={{ marginRight: '1rem' }}
|
||||||
>
|
>
|
||||||
{'Upload File'}
|
{'Upload File'}
|
||||||
<input type='file' multiple accept={fileType} hidden onChange={(e) => handleFileUpload(e)} />
|
<input
|
||||||
|
type='file'
|
||||||
|
multiple
|
||||||
|
accept={fileType}
|
||||||
|
hidden
|
||||||
|
onChange={(e) => (formDataUpload ? handleFormDataUpload(e) : handleFileUpload(e))}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)
|
)
|
||||||
@@ -83,6 +115,8 @@ export const File = ({ value, fileType, onChange, disabled = false }) => {
|
|||||||
File.propTypes = {
|
File.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
fileType: PropTypes.string,
|
fileType: PropTypes.string,
|
||||||
|
formDataUpload: PropTypes.bool,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
onFormDataChange: PropTypes.func,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -642,3 +642,15 @@ export const getOS = () => {
|
|||||||
|
|
||||||
return os
|
return os
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatBytes = (bytes, decimals = 2) => {
|
||||||
|
if (!+bytes) return '0 Bytes'
|
||||||
|
|
||||||
|
const k = 1024
|
||||||
|
const dm = decimals < 0 ? 0 : decimals
|
||||||
|
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
import { Box, Typography, Button, IconButton, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
import {
|
||||||
|
Chip,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Stack,
|
||||||
|
OutlinedInput
|
||||||
|
} from '@mui/material'
|
||||||
|
|
||||||
import { StyledButton } from '@/ui-component/button/StyledButton'
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser'
|
||||||
@@ -15,9 +29,10 @@ import CredentialInputHandler from '@/views/canvas/CredentialInputHandler'
|
|||||||
import { File } from '@/ui-component/file/File'
|
import { File } from '@/ui-component/file/File'
|
||||||
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
||||||
import DeleteConfirmDialog from './DeleteConfirmDialog'
|
import DeleteConfirmDialog from './DeleteConfirmDialog'
|
||||||
|
import AssistantVectorStoreDialog from './AssistantVectorStoreDialog'
|
||||||
|
|
||||||
// Icons
|
// Icons
|
||||||
import { IconX } from '@tabler/icons'
|
import { IconX, IconPlus } from '@tabler/icons'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import assistantsApi from '@/api/assistants'
|
import assistantsApi from '@/api/assistants'
|
||||||
@@ -28,8 +43,13 @@ import useApi from '@/hooks/useApi'
|
|||||||
// utils
|
// utils
|
||||||
import useNotifier from '@/utils/useNotifier'
|
import useNotifier from '@/utils/useNotifier'
|
||||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
|
import { maxScroll } from '@/store/constant'
|
||||||
|
|
||||||
const assistantAvailableModels = [
|
const assistantAvailableModels = [
|
||||||
|
{
|
||||||
|
label: 'gpt-4-turbo',
|
||||||
|
name: 'gpt-4-turbo'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'gpt-4-turbo-preview',
|
label: 'gpt-4-turbo-preview',
|
||||||
name: 'gpt-4-turbo-preview'
|
name: 'gpt-4-turbo-preview'
|
||||||
@@ -74,6 +94,8 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
const dialogRef = useRef()
|
||||||
|
|
||||||
const getSpecificAssistantApi = useApi(assistantsApi.getSpecificAssistant)
|
const getSpecificAssistantApi = useApi(assistantsApi.getSpecificAssistant)
|
||||||
const getAssistantObjApi = useApi(assistantsApi.getAssistantObj)
|
const getAssistantObjApi = useApi(assistantsApi.getAssistantObj)
|
||||||
@@ -86,12 +108,17 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
const [assistantModel, setAssistantModel] = useState('')
|
const [assistantModel, setAssistantModel] = useState('')
|
||||||
const [assistantCredential, setAssistantCredential] = useState('')
|
const [assistantCredential, setAssistantCredential] = useState('')
|
||||||
const [assistantInstructions, setAssistantInstructions] = useState('')
|
const [assistantInstructions, setAssistantInstructions] = useState('')
|
||||||
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'retrieval'])
|
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'file_search'])
|
||||||
const [assistantFiles, setAssistantFiles] = useState([])
|
const [toolResources, setToolResources] = useState({})
|
||||||
const [uploadAssistantFiles, setUploadAssistantFiles] = useState('')
|
const [temperature, setTemperature] = useState(1)
|
||||||
|
const [topP, setTopP] = useState(1)
|
||||||
|
const [uploadCodeInterpreterFiles, setUploadCodeInterpreterFiles] = useState('')
|
||||||
|
const [uploadVectorStoreFiles, setUploadVectorStoreFiles] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
const [deleteDialogProps, setDeleteDialogProps] = useState({})
|
const [deleteDialogProps, setDeleteDialogProps] = useState({})
|
||||||
|
const [assistantVectorStoreDialogOpen, setAssistantVectorStoreDialogOpen] = useState(false)
|
||||||
|
const [assistantVectorStoreDialogProps, setAssistantVectorStoreDialogProps] = useState({})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
@@ -111,8 +138,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantDesc(assistantDetails.description)
|
setAssistantDesc(assistantDetails.description)
|
||||||
setAssistantModel(assistantDetails.model)
|
setAssistantModel(assistantDetails.model)
|
||||||
setAssistantInstructions(assistantDetails.instructions)
|
setAssistantInstructions(assistantDetails.instructions)
|
||||||
|
setTemperature(assistantDetails.temperature)
|
||||||
|
setTopP(assistantDetails.top_p)
|
||||||
setAssistantTools(assistantDetails.tools ?? [])
|
setAssistantTools(assistantDetails.tools ?? [])
|
||||||
setAssistantFiles(assistantDetails.files ?? [])
|
setToolResources(assistantDetails.tool_resources ?? {})
|
||||||
}
|
}
|
||||||
}, [getSpecificAssistantApi.data])
|
}, [getSpecificAssistantApi.data])
|
||||||
|
|
||||||
@@ -124,14 +153,48 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getAssistantObjApi.error) {
|
if (getAssistantObjApi.error) {
|
||||||
syncData(getAssistantObjApi.error)
|
let errMsg = ''
|
||||||
|
if (error?.response?.data) {
|
||||||
|
errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
}
|
}
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to get assistant: ${errMsg}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getAssistantObjApi.error])
|
}, [getAssistantObjApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getSpecificAssistantApi.error) {
|
if (getSpecificAssistantApi.error) {
|
||||||
syncData(getSpecificAssistantApi.error)
|
let errMsg = ''
|
||||||
|
if (error?.response?.data) {
|
||||||
|
errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
}
|
}
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to get assistant: ${errMsg}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getSpecificAssistantApi.error])
|
}, [getSpecificAssistantApi.error])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -147,8 +210,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantDesc(assistantDetails.description)
|
setAssistantDesc(assistantDetails.description)
|
||||||
setAssistantModel(assistantDetails.model)
|
setAssistantModel(assistantDetails.model)
|
||||||
setAssistantInstructions(assistantDetails.instructions)
|
setAssistantInstructions(assistantDetails.instructions)
|
||||||
|
setTemperature(assistantDetails.temperature)
|
||||||
|
setTopP(assistantDetails.top_p)
|
||||||
setAssistantTools(assistantDetails.tools ?? [])
|
setAssistantTools(assistantDetails.tools ?? [])
|
||||||
setAssistantFiles(assistantDetails.files ?? [])
|
setToolResources(assistantDetails.tool_resources ?? {})
|
||||||
} else if (dialogProps.type === 'EDIT' && dialogProps.assistantId) {
|
} else if (dialogProps.type === 'EDIT' && dialogProps.assistantId) {
|
||||||
// When assistant dialog is opened from OpenAIAssistant node in canvas
|
// When assistant dialog is opened from OpenAIAssistant node in canvas
|
||||||
getSpecificAssistantApi.request(dialogProps.assistantId)
|
getSpecificAssistantApi.request(dialogProps.assistantId)
|
||||||
@@ -170,9 +235,12 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantDesc('')
|
setAssistantDesc('')
|
||||||
setAssistantModel('')
|
setAssistantModel('')
|
||||||
setAssistantInstructions('')
|
setAssistantInstructions('')
|
||||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
setTemperature(1)
|
||||||
setUploadAssistantFiles('')
|
setTopP(1)
|
||||||
setAssistantFiles([])
|
setAssistantTools(['code_interpreter', 'file_search'])
|
||||||
|
setUploadCodeInterpreterFiles('')
|
||||||
|
setUploadVectorStoreFiles('')
|
||||||
|
setToolResources({})
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -185,9 +253,12 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantDesc('')
|
setAssistantDesc('')
|
||||||
setAssistantModel('')
|
setAssistantModel('')
|
||||||
setAssistantInstructions('')
|
setAssistantInstructions('')
|
||||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
setTemperature(1)
|
||||||
setUploadAssistantFiles('')
|
setTopP(1)
|
||||||
setAssistantFiles([])
|
setAssistantTools(['code_interpreter', 'file_search'])
|
||||||
|
setUploadCodeInterpreterFiles('')
|
||||||
|
setUploadVectorStoreFiles('')
|
||||||
|
setToolResources({})
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -199,7 +270,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantDesc(data.description)
|
setAssistantDesc(data.description)
|
||||||
setAssistantModel(data.model)
|
setAssistantModel(data.model)
|
||||||
setAssistantInstructions(data.instructions)
|
setAssistantInstructions(data.instructions)
|
||||||
setAssistantFiles(data.files ?? [])
|
setTemperature(data.temperature)
|
||||||
|
setTopP(data.top_p)
|
||||||
|
setToolResources(data.tool_resources ?? {})
|
||||||
|
|
||||||
let tools = []
|
let tools = []
|
||||||
if (data.tools && data.tools.length) {
|
if (data.tools && data.tools.length) {
|
||||||
@@ -210,6 +283,31 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
setAssistantTools(tools)
|
setAssistantTools(tools)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onEditAssistantVectorStoreClick = (vectorStoreObject) => {
|
||||||
|
const dialogProp = {
|
||||||
|
title: `Edit ${vectorStoreObject.name ? vectorStoreObject.name : vectorStoreObject.id}`,
|
||||||
|
type: 'EDIT',
|
||||||
|
cancelButtonName: 'Cancel',
|
||||||
|
confirmButtonName: 'Save',
|
||||||
|
data: vectorStoreObject,
|
||||||
|
credential: assistantCredential
|
||||||
|
}
|
||||||
|
setAssistantVectorStoreDialogProps(dialogProp)
|
||||||
|
setAssistantVectorStoreDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddAssistantVectorStoreClick = () => {
|
||||||
|
const dialogProp = {
|
||||||
|
title: `Add Vector Store`,
|
||||||
|
type: 'ADD',
|
||||||
|
cancelButtonName: 'Cancel',
|
||||||
|
confirmButtonName: 'Add',
|
||||||
|
credential: assistantCredential
|
||||||
|
}
|
||||||
|
setAssistantVectorStoreDialogProps(dialogProp)
|
||||||
|
setAssistantVectorStoreDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
const addNewAssistant = async () => {
|
const addNewAssistant = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -219,9 +317,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
description: assistantDesc,
|
description: assistantDesc,
|
||||||
model: assistantModel,
|
model: assistantModel,
|
||||||
instructions: assistantInstructions,
|
instructions: assistantInstructions,
|
||||||
|
temperature: temperature ? parseFloat(temperature) : null,
|
||||||
|
top_p: topP ? parseFloat(topP) : null,
|
||||||
tools: assistantTools,
|
tools: assistantTools,
|
||||||
files: assistantFiles,
|
tool_resources: toolResources
|
||||||
uploadFiles: uploadAssistantFiles
|
|
||||||
}
|
}
|
||||||
const obj = {
|
const obj = {
|
||||||
details: JSON.stringify(assistantDetails),
|
details: JSON.stringify(assistantDetails),
|
||||||
@@ -247,7 +346,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error)
|
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to add new Assistant: ${
|
message: `Failed to add new Assistant: ${
|
||||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
@@ -264,7 +362,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
onCancel()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,9 +373,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
description: assistantDesc,
|
description: assistantDesc,
|
||||||
model: assistantModel,
|
model: assistantModel,
|
||||||
instructions: assistantInstructions,
|
instructions: assistantInstructions,
|
||||||
|
temperature: temperature ? parseFloat(temperature) : null,
|
||||||
|
top_p: topP ? parseFloat(topP) : null,
|
||||||
tools: assistantTools,
|
tools: assistantTools,
|
||||||
files: assistantFiles,
|
tool_resources: toolResources
|
||||||
uploadFiles: uploadAssistantFiles
|
|
||||||
}
|
}
|
||||||
const obj = {
|
const obj = {
|
||||||
details: JSON.stringify(assistantDetails),
|
details: JSON.stringify(assistantDetails),
|
||||||
@@ -303,7 +401,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error)
|
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to save Assistant: ${
|
message: `Failed to save Assistant: ${
|
||||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
@@ -320,7 +417,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
onCancel()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +441,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error)
|
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to sync Assistant: ${
|
message: `Failed to sync Assistant: ${
|
||||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
@@ -365,10 +460,124 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadFormDataToVectorStore = async (formData) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const vectorStoreId = toolResources.file_search?.vector_store_ids?.length ? toolResources.file_search.vector_store_ids[0] : ''
|
||||||
|
const uploadResp = await assistantsApi.uploadFilesToAssistantVectorStore(vectorStoreId, assistantCredential, formData)
|
||||||
|
if (uploadResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'File uploaded successfully!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploadedFiles = uploadResp.data
|
||||||
|
const existingFiles = toolResources?.file_search.files ?? []
|
||||||
|
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
file_search: {
|
||||||
|
...toolResources?.file_search,
|
||||||
|
files: [...existingFiles, ...uploadedFiles]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to upload file: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFormDataToCodeInterpreter = async (formData) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const uploadResp = await assistantsApi.uploadFilesToAssistant(assistantCredential, formData)
|
||||||
|
if (uploadResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'File uploaded successfully!',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploadedFiles = uploadResp.data
|
||||||
|
const existingFiles = toolResources?.code_interpreter?.files ?? []
|
||||||
|
const existingFileIds = toolResources?.code_interpreter?.file_ids ?? []
|
||||||
|
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
code_interpreter: {
|
||||||
|
...toolResources?.code_interpreter,
|
||||||
|
files: [...existingFiles, ...uploadedFiles],
|
||||||
|
file_ids: [...existingFileIds, ...uploadedFiles.map((file) => file.id)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to upload file: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const detachVectorStore = () => {
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
file_search: {
|
||||||
|
files: [],
|
||||||
|
vector_store_object: null,
|
||||||
|
vector_store_ids: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
setDeleteDialogProps({
|
setDeleteDialogProps({
|
||||||
title: `Delete Assistant`,
|
title: `Delete Assistant`,
|
||||||
description: `Delete Assistant ${assistantName}?`,
|
description: `Select delete method for ${assistantName}`,
|
||||||
cancelButtonName: 'Cancel'
|
cancelButtonName: 'Cancel'
|
||||||
})
|
})
|
||||||
setDeleteDialogOpen(true)
|
setDeleteDialogOpen(true)
|
||||||
@@ -394,7 +603,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
onConfirm()
|
onConfirm()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error)
|
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
message: `Failed to delete Assistant: ${
|
message: `Failed to delete Assistant: ${
|
||||||
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
@@ -414,8 +622,35 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFileDeleteClick = async (fileId) => {
|
const onFileDeleteClick = async (fileId, toolType) => {
|
||||||
setAssistantFiles(assistantFiles.filter((file) => file.id !== fileId))
|
if (toolType === 'code_interpreter') {
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
code_interpreter: {
|
||||||
|
...toolResources.code_interpreter,
|
||||||
|
files: toolResources.code_interpreter.files.filter((file) => file.id !== fileId),
|
||||||
|
file_ids: toolResources.code_interpreter.file_ids.filter((file_id) => file_id !== fileId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (toolType === 'file_search') {
|
||||||
|
// Remove from toolResources
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
file_search: {
|
||||||
|
...toolResources.file_search,
|
||||||
|
files: toolResources.file_search.files.filter((file) => file.id !== fileId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Remove files from vector store
|
||||||
|
try {
|
||||||
|
const vectorStoreId = toolResources.file_search?.vector_store_ids?.length
|
||||||
|
? toolResources.file_search.vector_store_ids[0]
|
||||||
|
: ''
|
||||||
|
await assistantsApi.deleteFilesFromAssistantVectorStore(vectorStoreId, assistantCredential, { file_ids: [fileId] })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const component = show ? (
|
const component = show ? (
|
||||||
@@ -430,8 +665,45 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }} id='alert-dialog-title'>
|
||||||
{dialogProps.title}
|
{dialogProps.title}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}>
|
<DialogContent
|
||||||
|
ref={dialogRef}
|
||||||
|
sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxHeight: '75vh', position: 'relative', px: 3, pb: 3 }}
|
||||||
|
>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 2 }}>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
OpenAI Credential
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<CredentialInputHandler
|
||||||
|
key={assistantCredential}
|
||||||
|
data={assistantCredential ? { credential: assistantCredential } : {}}
|
||||||
|
inputParam={{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['openAIApi']
|
||||||
|
}}
|
||||||
|
onSelect={(newValue) => setAssistantCredential(newValue)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Assistant Model
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Dropdown
|
||||||
|
key={assistantModel}
|
||||||
|
name={assistantModel}
|
||||||
|
options={assistantAvailableModels}
|
||||||
|
onSelect={(newValue) => setAssistantModel(newValue)}
|
||||||
|
value={assistantModel ?? 'choose an option'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Typography variant='overline'>Assistant Name</Typography>
|
<Typography variant='overline'>Assistant Name</Typography>
|
||||||
@@ -440,6 +712,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
id='assistantName'
|
id='assistantName'
|
||||||
type='string'
|
type='string'
|
||||||
|
size='small'
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder='My New Assistant'
|
placeholder='My New Assistant'
|
||||||
value={assistantName}
|
value={assistantName}
|
||||||
@@ -455,6 +728,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
id='assistantDesc'
|
id='assistantDesc'
|
||||||
type='string'
|
type='string'
|
||||||
|
size='small'
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder='Description of what the Assistant does'
|
placeholder='Description of what the Assistant does'
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -491,6 +765,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
id='assistantIcon'
|
id='assistantIcon'
|
||||||
type='string'
|
type='string'
|
||||||
|
size='small'
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
||||||
value={assistantIcon}
|
value={assistantIcon}
|
||||||
@@ -498,40 +773,6 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
onChange={(e) => setAssistantIcon(e.target.value)}
|
onChange={(e) => setAssistantIcon(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
|
||||||
<Typography variant='overline'>
|
|
||||||
Assistant Model
|
|
||||||
<span style={{ color: 'red' }}> *</span>
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
<Dropdown
|
|
||||||
key={assistantModel}
|
|
||||||
name={assistantModel}
|
|
||||||
options={assistantAvailableModels}
|
|
||||||
onSelect={(newValue) => setAssistantModel(newValue)}
|
|
||||||
value={assistantModel ?? 'choose an option'}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
|
||||||
<Typography variant='overline'>
|
|
||||||
OpenAI Credential
|
|
||||||
<span style={{ color: 'red' }}> *</span>
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
<CredentialInputHandler
|
|
||||||
key={assistantCredential}
|
|
||||||
data={assistantCredential ? { credential: assistantCredential } : {}}
|
|
||||||
inputParam={{
|
|
||||||
label: 'Connect Credential',
|
|
||||||
name: 'credential',
|
|
||||||
type: 'credential',
|
|
||||||
credentialNames: ['openAIApi']
|
|
||||||
}}
|
|
||||||
onSelect={(newValue) => setAssistantCredential(newValue)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Typography variant='overline'>Assistant Instruction</Typography>
|
<Typography variant='overline'>Assistant Instruction</Typography>
|
||||||
@@ -542,6 +783,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
id='assistantInstructions'
|
id='assistantInstructions'
|
||||||
type='string'
|
type='string'
|
||||||
|
size='small'
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
||||||
multiline={true}
|
multiline={true}
|
||||||
@@ -551,6 +793,48 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
onChange={(e) => setAssistantInstructions(e.target.value)}
|
onChange={(e) => setAssistantInstructions(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>Assistant Temperature</Typography>
|
||||||
|
<TooltipWithParser
|
||||||
|
title={
|
||||||
|
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<OutlinedInput
|
||||||
|
id='assistantTemp'
|
||||||
|
type='number'
|
||||||
|
size='small'
|
||||||
|
fullWidth
|
||||||
|
value={temperature}
|
||||||
|
name='assistantTemp'
|
||||||
|
onChange={(e) => setTemperature(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>Assistant Top P</Typography>
|
||||||
|
<TooltipWithParser
|
||||||
|
title={
|
||||||
|
'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered.'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<OutlinedInput
|
||||||
|
id='assistantTopP'
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
size='small'
|
||||||
|
value={topP}
|
||||||
|
name='assistantTopP'
|
||||||
|
min='0'
|
||||||
|
max='1'
|
||||||
|
onChange={(e) => setTopP(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{assistantCredential && (
|
||||||
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Typography variant='overline'>Assistant Tools</Typography>
|
<Typography variant='overline'>Assistant Tools</Typography>
|
||||||
@@ -565,21 +849,30 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
name: 'code_interpreter'
|
name: 'code_interpreter'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Retrieval',
|
label: 'File Search',
|
||||||
name: 'retrieval'
|
name: 'file_search'
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
onSelect={(newValue) => {
|
||||||
|
newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([])
|
||||||
|
setTimeout(() => {
|
||||||
|
dialogRef?.current?.scrollTo({ top: maxScroll })
|
||||||
|
}, 100)
|
||||||
|
}}
|
||||||
value={assistantTools ?? 'choose an option'}
|
value={assistantTools ?? 'choose an option'}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
|
{assistantTools?.length > 0 && assistantTools.includes('code_interpreter') && (
|
||||||
|
<Card sx={{ mb: 2, border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||||
|
<CardContent>
|
||||||
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
<Typography variant='overline'>Knowledge Files</Typography>
|
<Typography variant='overline'>Code Interpreter Files</Typography>
|
||||||
<TooltipWithParser title='Allow assistant to use the content from uploaded files for retrieval and code interpreter. MAX: 20 files' />
|
<TooltipWithParser title='Code Interpreter enables the assistant to write and run code. This tool can process files with diverse data and formatting, and generate files such as graphs' />
|
||||||
</Stack>
|
</Stack>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
{toolResources?.code_interpreter?.files?.length > 0 && (
|
||||||
{assistantFiles.map((file, index) => (
|
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||||
|
{toolResources?.code_interpreter?.files?.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
@@ -594,23 +887,120 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
paddingRight: 15,
|
paddingRight: 15,
|
||||||
paddingTop: 5,
|
paddingTop: 5,
|
||||||
paddingBottom: 5,
|
paddingBottom: 5,
|
||||||
marginRight: 10
|
marginRight: 10,
|
||||||
|
marginBottom: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>{file.filename}</span>
|
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>
|
||||||
<IconButton sx={{ height: 15, width: 15, p: 0 }} onClick={() => onFileDeleteClick(file.id)}>
|
{file.filename}
|
||||||
|
</span>
|
||||||
|
<IconButton
|
||||||
|
sx={{ height: 15, width: 15, p: 0 }}
|
||||||
|
onClick={() => onFileDeleteClick(file.id, 'code_interpreter')}
|
||||||
|
>
|
||||||
<IconX />
|
<IconX />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<File
|
<File
|
||||||
key={uploadAssistantFiles}
|
key={uploadCodeInterpreterFiles}
|
||||||
fileType='*'
|
fileType='*'
|
||||||
onChange={(newValue) => setUploadAssistantFiles(newValue)}
|
formDataUpload={true}
|
||||||
value={uploadAssistantFiles ?? 'Choose a file to upload'}
|
value={uploadCodeInterpreterFiles ?? 'Choose a file to upload'}
|
||||||
|
onChange={(newValue) => setUploadCodeInterpreterFiles(newValue)}
|
||||||
|
onFormDataChange={(formData) => uploadFormDataToCodeInterpreter(formData)}
|
||||||
/>
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{assistantTools?.length > 0 && assistantTools.includes('file_search') && (
|
||||||
|
<Card sx={{ mb: 2, border: '1px solid #e0e0e0', borderRadius: `${customization.borderRadius}px` }}>
|
||||||
|
<CardContent>
|
||||||
|
<Stack sx={{ position: 'relative', alignItems: 'center' }} direction='row'>
|
||||||
|
<Typography variant='overline'>File Search Files</Typography>
|
||||||
|
<TooltipWithParser title='File search enables the assistant with knowledge from files that you or your users upload. Once a file is uploaded, the assistant automatically decides when to retrieve content based on user requests' />
|
||||||
|
</Stack>
|
||||||
|
{toolResources?.file_search?.vector_store_object && (
|
||||||
|
<Chip
|
||||||
|
label={
|
||||||
|
toolResources?.file_search?.vector_store_object?.name
|
||||||
|
? toolResources?.file_search?.vector_store_object?.name
|
||||||
|
: toolResources?.file_search?.vector_store_object?.id
|
||||||
|
}
|
||||||
|
component='a'
|
||||||
|
sx={{ mb: 2, mt: 1 }}
|
||||||
|
variant='outlined'
|
||||||
|
clickable
|
||||||
|
color='primary'
|
||||||
|
onDelete={detachVectorStore}
|
||||||
|
onClick={() =>
|
||||||
|
onEditAssistantVectorStoreClick(toolResources?.file_search?.vector_store_object)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{toolResources?.file_search?.files?.length > 0 && (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
|
||||||
|
{toolResources?.file_search?.files?.map((file, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: 'max-content',
|
||||||
|
height: 'max-content',
|
||||||
|
borderRadius: 15,
|
||||||
|
background: 'rgb(254,252,191)',
|
||||||
|
paddingLeft: 15,
|
||||||
|
paddingRight: 15,
|
||||||
|
paddingTop: 5,
|
||||||
|
paddingBottom: 5,
|
||||||
|
marginRight: 10,
|
||||||
|
marginBottom: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ color: 'rgb(116,66,16)', marginRight: 10 }}>
|
||||||
|
{file.filename}
|
||||||
|
</span>
|
||||||
|
<IconButton
|
||||||
|
sx={{ height: 15, width: 15, p: 0 }}
|
||||||
|
onClick={() => onFileDeleteClick(file.id, 'file_search')}
|
||||||
|
>
|
||||||
|
<IconX />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!toolResources.file_search || !toolResources.file_search?.vector_store_ids?.length ? (
|
||||||
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
component='label'
|
||||||
|
fullWidth
|
||||||
|
startIcon={<IconPlus />}
|
||||||
|
sx={{ marginRight: '1rem' }}
|
||||||
|
onClick={() => onAddAssistantVectorStoreClick()}
|
||||||
|
>
|
||||||
|
Add Vector Store
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<File
|
||||||
|
key={uploadVectorStoreFiles}
|
||||||
|
fileType='*'
|
||||||
|
formDataUpload={true}
|
||||||
|
value={uploadVectorStoreFiles ?? 'Choose a file to upload'}
|
||||||
|
onChange={(newValue) => setUploadVectorStoreFiles(newValue)}
|
||||||
|
onFormDataChange={(formData) => uploadFormDataToVectorStore(formData)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||||
@@ -639,6 +1029,35 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =
|
|||||||
onDelete={() => deleteAssistant()}
|
onDelete={() => deleteAssistant()}
|
||||||
onDeleteBoth={() => deleteAssistant(true)}
|
onDeleteBoth={() => deleteAssistant(true)}
|
||||||
/>
|
/>
|
||||||
|
<AssistantVectorStoreDialog
|
||||||
|
show={assistantVectorStoreDialogOpen}
|
||||||
|
dialogProps={assistantVectorStoreDialogProps}
|
||||||
|
onCancel={() => setAssistantVectorStoreDialogOpen(false)}
|
||||||
|
onDelete={(vectorStoreId) => {
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
file_search: {
|
||||||
|
vector_store_object: null,
|
||||||
|
files: [],
|
||||||
|
vector_store_ids: toolResources.file_search.vector_store_ids.filter((id) => vectorStoreId !== id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setAssistantVectorStoreDialogOpen(false)
|
||||||
|
}}
|
||||||
|
onConfirm={(vectorStoreObj, files) => {
|
||||||
|
setToolResources({
|
||||||
|
...toolResources,
|
||||||
|
file_search: {
|
||||||
|
...toolResources.file_search,
|
||||||
|
vector_store_object: vectorStoreObj,
|
||||||
|
files: files ? files : toolResources.file_search?.files,
|
||||||
|
vector_store_ids: [vectorStoreObj.id]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setAssistantVectorStoreDialogOpen(false)
|
||||||
|
}}
|
||||||
|
setError={setError}
|
||||||
|
/>
|
||||||
{loading && <BackdropLoader open={loading} />}
|
{loading && <BackdropLoader open={loading} />}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
@@ -0,0 +1,386 @@
|
|||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { omit } from 'lodash'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions'
|
||||||
|
|
||||||
|
// Material
|
||||||
|
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
// Project imports
|
||||||
|
import { StyledButton } from '@/ui-component/button/StyledButton'
|
||||||
|
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
|
||||||
|
import { SwitchInput } from '@/ui-component/switch/Switch'
|
||||||
|
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
|
||||||
|
import { BackdropLoader } from '@/ui-component/loading/BackdropLoader'
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
import { IconX } from '@tabler/icons'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import assistantsApi from '@/api/assistants'
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
import useApi from '@/hooks/useApi'
|
||||||
|
|
||||||
|
// utils
|
||||||
|
import useNotifier from '@/utils/useNotifier'
|
||||||
|
import { formatBytes } from '@/utils/genericHelper'
|
||||||
|
|
||||||
|
// const
|
||||||
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||||
|
|
||||||
|
const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, onDelete, setError }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
// ==============================|| Snackbar ||============================== //
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const getAssistantVectorStoreApi = useApi(assistantsApi.getAssistantVectorStore)
|
||||||
|
const listAssistantVectorStoreApi = useApi(assistantsApi.listAssistantVectorStore)
|
||||||
|
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const [isExpirationOn, setExpirationOnOff] = useState(false)
|
||||||
|
const [expirationDays, setExpirationDays] = useState(7)
|
||||||
|
const [availableVectorStoreOptions, setAvailableVectorStoreOptions] = useState([{ label: '- Create New -', name: '-create-' }])
|
||||||
|
const [selectedVectorStore, setSelectedVectorStore] = useState('')
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAssistantVectorStoreApi.data) {
|
||||||
|
if (getAssistantVectorStoreApi.data.name) {
|
||||||
|
setName(getAssistantVectorStoreApi.data.name)
|
||||||
|
} else {
|
||||||
|
setName('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getAssistantVectorStoreApi.data.id) {
|
||||||
|
setSelectedVectorStore(getAssistantVectorStoreApi.data.id)
|
||||||
|
} else {
|
||||||
|
setSelectedVectorStore('')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getAssistantVectorStoreApi.data.expires_after && getAssistantVectorStoreApi.data.expires_after.days) {
|
||||||
|
setExpirationDays(getAssistantVectorStoreApi.data.expires_after.days)
|
||||||
|
setExpirationOnOff(true)
|
||||||
|
} else {
|
||||||
|
setExpirationDays(7)
|
||||||
|
setExpirationOnOff(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAssistantVectorStoreApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (listAssistantVectorStoreApi.data) {
|
||||||
|
let vectorStores = []
|
||||||
|
for (let i = 0; i < listAssistantVectorStoreApi.data.length; i += 1) {
|
||||||
|
vectorStores.push({
|
||||||
|
label: listAssistantVectorStoreApi.data[i]?.name ?? listAssistantVectorStoreApi.data[i].id,
|
||||||
|
name: listAssistantVectorStoreApi.data[i].id,
|
||||||
|
description: `${listAssistantVectorStoreApi.data[i]?.file_counts?.total} files (${formatBytes(
|
||||||
|
listAssistantVectorStoreApi.data[i]?.usage_bytes
|
||||||
|
)})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
vectorStores = vectorStores.filter((vs) => vs.name !== '-create-')
|
||||||
|
vectorStores.unshift({ label: '- Create New -', name: '-create-' })
|
||||||
|
setAvailableVectorStoreOptions(vectorStores)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [listAssistantVectorStoreApi.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAssistantVectorStoreApi.error) {
|
||||||
|
setError(getAssistantVectorStoreApi.error)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAssistantVectorStoreApi.error])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||||
|
getAssistantVectorStoreApi.request(dialogProps.data.id, dialogProps.credential)
|
||||||
|
listAssistantVectorStoreApi.request(dialogProps.credential)
|
||||||
|
} else if (dialogProps.type === 'ADD') {
|
||||||
|
listAssistantVectorStoreApi.request(dialogProps.credential)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setName('')
|
||||||
|
setExpirationOnOff(false)
|
||||||
|
setExpirationDays(7)
|
||||||
|
setSelectedVectorStore('')
|
||||||
|
setAvailableVectorStoreOptions([{ label: '- Create New -', name: '-create-' }])
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
}, [show, dispatch])
|
||||||
|
|
||||||
|
const deleteVectorStore = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const deleteResp = await assistantsApi.deleteAssistantVectorStore(selectedVectorStore, dialogProps.credential)
|
||||||
|
if (deleteResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Vector Store deleted',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onDelete(selectedVectorStore)
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to delete Vector Store: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNewVectorStore = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const obj = {
|
||||||
|
name: name !== '' ? name : null,
|
||||||
|
expires_after: isExpirationOn ? { anchor: 'last_active_at', days: parseFloat(expirationDays) } : null
|
||||||
|
}
|
||||||
|
const createResp = await assistantsApi.createAssistantVectorStore(dialogProps.credential, obj)
|
||||||
|
if (createResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'New Vector Store added',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onConfirm(createResp.data)
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
setError(error)
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to add new Vector Store: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveVectorStore = async (selectedVectorStoreId) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const saveObj = {
|
||||||
|
name: name !== '' ? name : null,
|
||||||
|
expires_after: isExpirationOn ? { anchor: 'last_active_at', days: parseFloat(expirationDays) } : null
|
||||||
|
}
|
||||||
|
const saveResp = await assistantsApi.updateAssistantVectorStore(selectedVectorStoreId, dialogProps.credential, saveObj)
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Vector Store saved',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if ('files' in saveResp.data) {
|
||||||
|
const files = saveResp.data.files
|
||||||
|
onConfirm(omit(saveResp.data, ['files']), files)
|
||||||
|
} else {
|
||||||
|
onConfirm(saveResp.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('error=', error)
|
||||||
|
setError(error)
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Vector Store: ${
|
||||||
|
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
|
||||||
|
}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
fullWidth
|
||||||
|
maxWidth='sm'
|
||||||
|
open={show}
|
||||||
|
onClose={onCancel}
|
||||||
|
aria-labelledby='alert-dialog-title'
|
||||||
|
aria-describedby='alert-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
|
{dialogProps.title}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Select Vector Store
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Dropdown
|
||||||
|
name={selectedVectorStore}
|
||||||
|
options={availableVectorStoreOptions}
|
||||||
|
loading={listAssistantVectorStoreApi.loading}
|
||||||
|
onSelect={(newValue) => {
|
||||||
|
setSelectedVectorStore(newValue)
|
||||||
|
if (newValue === '-create-') {
|
||||||
|
setName('')
|
||||||
|
setExpirationOnOff(false)
|
||||||
|
setExpirationDays(7)
|
||||||
|
} else {
|
||||||
|
getAssistantVectorStoreApi.request(newValue, dialogProps.credential)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={selectedVectorStore ?? 'choose an option'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{selectedVectorStore !== '' && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>Vector Store Name</Typography>
|
||||||
|
</Stack>
|
||||||
|
<OutlinedInput
|
||||||
|
id='vsName'
|
||||||
|
type='string'
|
||||||
|
fullWidth
|
||||||
|
placeholder={'My Vector Store'}
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>Vector Store Expiration</Typography>
|
||||||
|
</Stack>
|
||||||
|
<SwitchInput onChange={(newValue) => setExpirationOnOff(newValue)} value={isExpirationOn} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{isExpirationOn && (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||||
|
<Typography variant='overline'>
|
||||||
|
Expiration Days
|
||||||
|
<span style={{ color: 'red' }}> *</span>
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
<OutlinedInput
|
||||||
|
id='expDays'
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
value={expirationDays}
|
||||||
|
onChange={(e) => setExpirationDays(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{dialogProps.type === 'EDIT' && (
|
||||||
|
<StyledButton color='error' variant='contained' onClick={() => deleteVectorStore()}>
|
||||||
|
Delete
|
||||||
|
</StyledButton>
|
||||||
|
)}
|
||||||
|
<StyledButton
|
||||||
|
disabled={!selectedVectorStore}
|
||||||
|
variant='contained'
|
||||||
|
onClick={() => (selectedVectorStore === '-create-' ? addNewVectorStore() : saveVectorStore(selectedVectorStore))}
|
||||||
|
>
|
||||||
|
{dialogProps.confirmButtonName}
|
||||||
|
</StyledButton>
|
||||||
|
</DialogActions>
|
||||||
|
<ConfirmDialog />
|
||||||
|
{loading && <BackdropLoader open={loading} />}
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
AssistantVectorStoreDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
setError: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssistantVectorStoreDialog
|
||||||
@@ -20,14 +20,13 @@ const DeleteConfirmDialog = ({ show, dialogProps, onCancel, onDelete, onDeleteBo
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<span>{dialogProps.description}</span>
|
<span>{dialogProps.description}</span>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20 }}>
|
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }}>
|
||||||
<StyledButton sx={{ mb: 1 }} color='orange' variant='contained' onClick={onDelete}>
|
<Button sx={{ flex: 1, mb: 1, mr: 1 }} color='error' variant='outlined' onClick={onDelete}>
|
||||||
Delete only from Flowise
|
Only Flowise
|
||||||
|
</Button>
|
||||||
|
<StyledButton sx={{ flex: 1, mb: 1, ml: 1 }} color='error' variant='contained' onClick={onDeleteBoth}>
|
||||||
|
OpenAI and Flowise
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
<StyledButton sx={{ mb: 1 }} color='error' variant='contained' onClick={onDeleteBoth}>
|
|
||||||
Delete from both OpenAI and Flowise
|
|
||||||
</StyledButton>
|
|
||||||
<Button onClick={onCancel}>{dialogProps.cancelButtonName}</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -355,6 +355,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateLastMessageFileAnnotations = (fileAnnotations) => {
|
||||||
|
setMessages((prevMessages) => {
|
||||||
|
let allMessages = [...cloneDeep(prevMessages)]
|
||||||
|
if (allMessages[allMessages.length - 1].type === 'userMessage') return allMessages
|
||||||
|
allMessages[allMessages.length - 1].fileAnnotations = fileAnnotations
|
||||||
|
return allMessages
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Handle errors
|
// Handle errors
|
||||||
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
const handleError = (message = 'Oops! There seems to be an error. Please try again.') => {
|
||||||
message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '')
|
message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '')
|
||||||
@@ -482,8 +491,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||||||
const downloadFile = async (fileAnnotation) => {
|
const downloadFile = async (fileAnnotation) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${baseURL}/api/v1/openai-assistants-file`,
|
`${baseURL}/api/v1/openai-assistants-file/download`,
|
||||||
{ fileName: fileAnnotation.fileName },
|
{ fileName: fileAnnotation.fileName, chatflowId: chatflowid, chatId: chatId },
|
||||||
{ responseType: 'blob' }
|
{ responseType: 'blob' }
|
||||||
)
|
)
|
||||||
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
const blob = new Blob([response.data], { type: response.headers['content-type'] })
|
||||||
@@ -611,6 +620,8 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews
|
|||||||
|
|
||||||
socket.on('usedTools', updateLastMessageUsedTools)
|
socket.on('usedTools', updateLastMessageUsedTools)
|
||||||
|
|
||||||
|
socket.on('fileAnnotations', updateLastMessageFileAnnotations)
|
||||||
|
|
||||||
socket.on('token', updateLastMessage)
|
socket.on('token', updateLastMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Generated
+21445
-17341
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user