add openai assistant
This commit is contained in:
parent
12fb5a3a3b
commit
0f293e5a59
|
|
@ -0,0 +1,224 @@
|
|||
import { ICommonObject, IDatabaseEntity, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
|
||||
import OpenAI from 'openai'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { getCredentialData, getCredentialParam, getUserHome } from '../../../src/utils'
|
||||
import { MessageContentImageFile, MessageContentText } from 'openai/resources/beta/threads/messages/messages'
|
||||
import * as fsDefault from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import fetch from 'node-fetch'
|
||||
|
||||
class OpenAIAssistant_Agents implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
baseClasses: string[]
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'OpenAI Assistant'
|
||||
this.name = 'openAIAssistant'
|
||||
this.version = 1.0
|
||||
this.type = 'OpenAIAssistant'
|
||||
this.category = 'Agents'
|
||||
this.icon = 'openai.png'
|
||||
this.description = `An agent that uses OpenAI Assistant API to pick the tool and args to call`
|
||||
this.baseClasses = [this.type]
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Select Assistant',
|
||||
name: 'selectedAssistant',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'listAssistants'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
loadMethods = {
|
||||
async listAssistants(_: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> {
|
||||
const returnData: INodeOptionsValue[] = []
|
||||
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
if (appDataSource === undefined || !appDataSource) {
|
||||
return returnData
|
||||
}
|
||||
|
||||
const assistants = await appDataSource.getRepository(databaseEntities['Assistant']).find()
|
||||
|
||||
for (let i = 0; i < assistants.length; i += 1) {
|
||||
const assistantDetails = JSON.parse(assistants[i].details)
|
||||
const data = {
|
||||
label: assistantDetails.name,
|
||||
name: assistants[i].id,
|
||||
description: assistantDetails.instructions
|
||||
} as INodeOptionsValue
|
||||
returnData.push(data)
|
||||
}
|
||||
return returnData
|
||||
}
|
||||
}
|
||||
|
||||
async init(): Promise<any> {
|
||||
return null
|
||||
}
|
||||
|
||||
async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise<void> {
|
||||
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
let sessionId = nodeData.inputs?.sessionId as string
|
||||
|
||||
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
||||
id: selectedAssistantId
|
||||
})
|
||||
|
||||
if (!assistant) throw new Error(`Assistant ${selectedAssistantId} not found`)
|
||||
|
||||
if (!sessionId && options.chatId) {
|
||||
const chatmsg = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
||||
chatId: options.chatId
|
||||
})
|
||||
if (!chatmsg) throw new Error(`Chat Message with Chat Id: ${options.chatId} not found`)
|
||||
sessionId = chatmsg.sessionId
|
||||
}
|
||||
|
||||
const credentialData = await getCredentialData(assistant.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
if (!openAIApiKey) throw new Error(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
options.logger.info(`Clearing OpenAI Thread ${sessionId}`)
|
||||
await openai.beta.threads.del(sessionId)
|
||||
options.logger.info(`Successfully cleared OpenAI Thread ${sessionId}`)
|
||||
}
|
||||
|
||||
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | object> {
|
||||
const selectedAssistantId = nodeData.inputs?.selectedAssistant as string
|
||||
const appDataSource = options.appDataSource as DataSource
|
||||
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||
|
||||
const assistant = await appDataSource.getRepository(databaseEntities['Assistant']).findOneBy({
|
||||
id: selectedAssistantId
|
||||
})
|
||||
|
||||
if (!assistant) throw new Error(`Assistant ${selectedAssistantId} not found`)
|
||||
|
||||
const credentialData = await getCredentialData(assistant.credential ?? '', options)
|
||||
const openAIApiKey = getCredentialParam('openAIApiKey', credentialData, nodeData)
|
||||
if (!openAIApiKey) throw new Error(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
|
||||
// Retrieve assistant
|
||||
const assistantDetails = JSON.parse(assistant.details)
|
||||
const openAIAssistantId = assistantDetails.id
|
||||
const retrievedAssistant = await openai.beta.assistants.retrieve(openAIAssistantId)
|
||||
|
||||
const chatmessage = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
||||
chatId: options.chatId
|
||||
})
|
||||
|
||||
let threadId = ''
|
||||
if (!chatmessage) {
|
||||
const thread = await openai.beta.threads.create({})
|
||||
threadId = thread.id
|
||||
} else {
|
||||
const thread = await openai.beta.threads.retrieve(chatmessage.sessionId)
|
||||
threadId = thread.id
|
||||
}
|
||||
|
||||
// Add message to thread
|
||||
await openai.beta.threads.messages.create(threadId, {
|
||||
role: 'user',
|
||||
content: input
|
||||
})
|
||||
|
||||
// Run assistant thread
|
||||
const runThread = await openai.beta.threads.runs.create(threadId, {
|
||||
assistant_id: retrievedAssistant.id
|
||||
})
|
||||
|
||||
const promise = (threadId: string, runId: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setInterval(async () => {
|
||||
const run = await openai.beta.threads.runs.retrieve(threadId, runId)
|
||||
const state = run.status
|
||||
if (state === 'completed') {
|
||||
clearInterval(timeout)
|
||||
resolve(run)
|
||||
} else if (state === 'cancelled' || state === 'expired' || state === 'failed') {
|
||||
clearInterval(timeout)
|
||||
reject(new Error(`Error processing thread: ${state}, Thread ID: ${threadId}, Run ID: ${runId}`))
|
||||
}
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
|
||||
// Polling run status
|
||||
await promise(threadId, runThread.id)
|
||||
|
||||
// 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 ''
|
||||
|
||||
let returnVal = ''
|
||||
for (let i = 0; i < assistantMessages[0].content.length; i += 1) {
|
||||
if (assistantMessages[0].content[i].type === 'text') {
|
||||
const content = assistantMessages[0].content[i] as MessageContentText
|
||||
returnVal += content.text.value
|
||||
|
||||
//TODO: handle annotations
|
||||
} else {
|
||||
const content = assistantMessages[0].content[i] as MessageContentImageFile
|
||||
const fileId = content.image_file.file_id
|
||||
const fileObj = await openai.files.retrieve(fileId)
|
||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', `${fileObj.filename}.png`)
|
||||
|
||||
await downloadFile(fileObj, filePath, openAIApiKey)
|
||||
|
||||
const bitmap = fsDefault.readFileSync(filePath)
|
||||
const base64String = Buffer.from(bitmap).toString('base64')
|
||||
|
||||
const imgHTML = `<img src="data:image/png;base64,${base64String}" width="100%" height="max-content" alt="${fileObj.filename}" /><br/>`
|
||||
returnVal += imgHTML
|
||||
}
|
||||
}
|
||||
|
||||
return { text: returnVal, assistant: { assistantId: openAIAssistantId, threadId, runId: runThread.id, messages: messageData } }
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (fileObj: any, filePath: string, openAIApiKey: string) => {
|
||||
try {
|
||||
const response = await fetch(`https://api.openai.com/v1/files/${fileObj.id}/content`, {
|
||||
method: 'GET',
|
||||
headers: { Accept: '*/*', Authorization: `Bearer ${openAIApiKey}` }
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const dest = fsDefault.createWriteStream(filePath)
|
||||
response.body.pipe(dest)
|
||||
response.body.on('end', () => resolve())
|
||||
dest.on('error', reject)
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('File downloaded and written to', filePath)
|
||||
} catch (error) {
|
||||
console.error('Error downloading or writing the file:', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: OpenAIAssistant_Agents }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -47,6 +47,14 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
label: 'gpt-4',
|
||||
name: 'gpt-4'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-1106-preview',
|
||||
name: 'gpt-4-1106-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-vision-preview',
|
||||
name: 'gpt-4-vision-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0613',
|
||||
name: 'gpt-4-0613'
|
||||
|
|
@ -63,6 +71,10 @@ class ChatOpenAI_ChatModels implements INode {
|
|||
label: 'gpt-3.5-turbo',
|
||||
name: 'gpt-3.5-turbo'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-1106',
|
||||
name: 'gpt-3.5-turbo-1106'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0613',
|
||||
name: 'gpt-3.5-turbo-0613'
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
"node-html-markdown": "^1.3.0",
|
||||
"notion-to-md": "^3.1.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"openai": "^4.16.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pdfjs-dist": "^3.7.107",
|
||||
"pg": "^8.11.2",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,15 @@ export interface ITool {
|
|||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface IAssistant {
|
||||
id: string
|
||||
details: string
|
||||
credential: string
|
||||
iconSrc?: string
|
||||
updatedDate: Date
|
||||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface ICredential {
|
||||
id: string
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
/* eslint-disable */
|
||||
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { IAssistant } from '../../Interface'
|
||||
|
||||
@Entity()
|
||||
export class Assistant implements IAssistant {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column({ type: 'text' })
|
||||
details: string
|
||||
|
||||
@Column()
|
||||
credential: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
iconSrc?: string
|
||||
|
||||
@CreateDateColumn()
|
||||
createdDate: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedDate: Date
|
||||
}
|
||||
|
|
@ -2,10 +2,12 @@ import { ChatFlow } from './ChatFlow'
|
|||
import { ChatMessage } from './ChatMessage'
|
||||
import { Credential } from './Credential'
|
||||
import { Tool } from './Tool'
|
||||
import { Assistant } from './Assistant'
|
||||
|
||||
export const entities = {
|
||||
ChatFlow,
|
||||
ChatMessage,
|
||||
Credential,
|
||||
Tool
|
||||
Tool,
|
||||
Assistant
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAssistantEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS \`assistant\` (
|
||||
\`id\` varchar(36) NOT NULL,
|
||||
\`credential\` varchar(255) NOT NULL,
|
||||
\`details\` text NOT NULL,
|
||||
\`iconSrc\` varchar(255) DEFAULT NULL,
|
||||
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE assistant`)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { ModifyTool1694001465232 } from './1694001465232-ModifyTool'
|
|||
import { AddApiConfig1694099200729 } from './1694099200729-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
import { AddChatHistory1694658767766 } from './1694658767766-AddChatHistory'
|
||||
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
|
||||
|
||||
export const mysqlMigrations = [
|
||||
Init1693840429259,
|
||||
|
|
@ -15,5 +16,6 @@ export const mysqlMigrations = [
|
|||
ModifyTool1694001465232,
|
||||
AddApiConfig1694099200729,
|
||||
AddAnalytic1694432361423,
|
||||
AddChatHistory1694658767766
|
||||
AddChatHistory1694658767766,
|
||||
AddAssistantEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAssistantEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS assistant (
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"credential" varchar NOT NULL,
|
||||
"details" text NOT NULL,
|
||||
"iconSrc" varchar NULL,
|
||||
"createdDate" timestamp NOT NULL DEFAULT now(),
|
||||
"updatedDate" timestamp NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_3c7cea7a044ac4c92764576cdbf" PRIMARY KEY (id)
|
||||
);`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE assistant`)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { ModifyTool1693997339912 } from './1693997339912-ModifyTool'
|
|||
import { AddApiConfig1694099183389 } from './1694099183389-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
import { AddChatHistory1694658756136 } from './1694658756136-AddChatHistory'
|
||||
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
|
||||
|
||||
export const postgresMigrations = [
|
||||
Init1693891895163,
|
||||
|
|
@ -15,5 +16,6 @@ export const postgresMigrations = [
|
|||
ModifyTool1693997339912,
|
||||
AddApiConfig1694099183389,
|
||||
AddAnalytic1694432361423,
|
||||
AddChatHistory1694658756136
|
||||
AddChatHistory1694658756136,
|
||||
AddAssistantEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddAssistantEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS "assistant" ("id" varchar PRIMARY KEY NOT NULL, "details" text NOT NULL, "credential" varchar NOT NULL, "iconSrc" varchar, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE assistant`)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { ModifyTool1693924207475 } from './1693924207475-ModifyTool'
|
|||
import { AddApiConfig1694090982460 } from './1694090982460-AddApiConfig'
|
||||
import { AddAnalytic1694432361423 } from './1694432361423-AddAnalytic'
|
||||
import { AddChatHistory1694657778173 } from './1694657778173-AddChatHistory'
|
||||
import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEntity'
|
||||
|
||||
export const sqliteMigrations = [
|
||||
Init1693835579790,
|
||||
|
|
@ -15,5 +16,6 @@ export const sqliteMigrations = [
|
|||
ModifyTool1693924207475,
|
||||
AddApiConfig1694090982460,
|
||||
AddAnalytic1694432361423,
|
||||
AddChatHistory1694657778173
|
||||
AddChatHistory1694657778173,
|
||||
AddAssistantEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Server } from 'socket.io'
|
|||
import logger from './utils/logger'
|
||||
import { expressRequestLogger } from './utils/logger'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import OpenAI from 'openai'
|
||||
import { Between, IsNull, FindOptionsWhere } from 'typeorm'
|
||||
import {
|
||||
IChatFlow,
|
||||
|
|
@ -57,6 +58,7 @@ import { ChatFlow } from './database/entities/ChatFlow'
|
|||
import { ChatMessage } from './database/entities/ChatMessage'
|
||||
import { Credential } from './database/entities/Credential'
|
||||
import { Tool } from './database/entities/Tool'
|
||||
import { Assistant } from './database/entities/Assistant'
|
||||
import { ChatflowPool } from './ChatflowPool'
|
||||
import { CachePool } from './CachePool'
|
||||
import { ICommonObject, INodeOptionsValue } from 'flowise-components'
|
||||
|
|
@ -469,8 +471,8 @@ export class App {
|
|||
const parsedFlowData: IReactFlowObject = JSON.parse(flowData)
|
||||
const nodes = parsedFlowData.nodes
|
||||
|
||||
if (isClearFromViewMessageDialog)
|
||||
clearSessionMemoryFromViewMessageDialog(
|
||||
if (isClearFromViewMessageDialog) {
|
||||
await clearSessionMemoryFromViewMessageDialog(
|
||||
nodes,
|
||||
this.nodesPool.componentNodes,
|
||||
chatId,
|
||||
|
|
@ -478,7 +480,9 @@ export class App {
|
|||
sessionId,
|
||||
memoryType
|
||||
)
|
||||
else clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId)
|
||||
} else {
|
||||
await clearAllSessionMemory(nodes, this.nodesPool.componentNodes, chatId, this.AppDataSource, sessionId)
|
||||
}
|
||||
|
||||
const deleteOptions: FindOptionsWhere<ChatMessage> = { chatflowid, chatId }
|
||||
if (memoryType) deleteOptions.memoryType = memoryType
|
||||
|
|
@ -631,6 +635,224 @@ export class App {
|
|||
return res.json(results)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// Assistant
|
||||
// ----------------------------------------
|
||||
|
||||
// Get all assistants
|
||||
this.app.get('/api/v1/assistants', async (req: Request, res: Response) => {
|
||||
const assistants = await this.AppDataSource.getRepository(Assistant).find()
|
||||
return res.json(assistants)
|
||||
})
|
||||
|
||||
// Get specific assistant
|
||||
this.app.get('/api/v1/assistants/:id', async (req: Request, res: Response) => {
|
||||
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
return res.json(assistant)
|
||||
})
|
||||
|
||||
// Get assistant object
|
||||
this.app.get('/api/v1/openai-assistants/:id', async (req: Request, res: Response) => {
|
||||
const credentialId = req.query.credential as string
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${credentialId} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const retrievedAssistant = await openai.beta.assistants.retrieve(req.params.id)
|
||||
|
||||
return res.json(retrievedAssistant)
|
||||
})
|
||||
|
||||
// List available assistants
|
||||
this.app.get('/api/v1/openai-assistants', async (req: Request, res: Response) => {
|
||||
const credentialId = req.query.credential as string
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: credentialId
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${credentialId} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
const retrievedAssistants = await openai.beta.assistants.list()
|
||||
|
||||
return res.json(retrievedAssistants.data)
|
||||
})
|
||||
|
||||
// Add assistant
|
||||
this.app.post('/api/v1/assistants', async (req: Request, res: Response) => {
|
||||
const body = req.body
|
||||
|
||||
if (!body.details) return res.status(500).send(`Invalid request body`)
|
||||
|
||||
const assistantDetails = JSON.parse(body.details)
|
||||
|
||||
if (!assistantDetails.id) {
|
||||
try {
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: body.credential
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
|
||||
let tools = []
|
||||
if (assistantDetails.tools) {
|
||||
for (const tool of assistantDetails.tools ?? []) {
|
||||
tools.push({
|
||||
type: tool
|
||||
})
|
||||
}
|
||||
}
|
||||
const newAssistant = await openai.beta.assistants.create({
|
||||
name: assistantDetails.name,
|
||||
description: assistantDetails.description,
|
||||
instructions: assistantDetails.instructions,
|
||||
model: assistantDetails.model,
|
||||
tools
|
||||
})
|
||||
|
||||
const newAssistantDetails = {
|
||||
...assistantDetails,
|
||||
id: newAssistant.id
|
||||
}
|
||||
|
||||
body.details = JSON.stringify(newAssistantDetails)
|
||||
} catch (error) {
|
||||
return res.status(500).send(`Error creating new assistant: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
const newAssistant = new Assistant()
|
||||
Object.assign(newAssistant, body)
|
||||
|
||||
const assistant = this.AppDataSource.getRepository(Assistant).create(newAssistant)
|
||||
const results = await this.AppDataSource.getRepository(Assistant).save(assistant)
|
||||
|
||||
return res.json(results)
|
||||
})
|
||||
|
||||
// Update assistant
|
||||
this.app.put('/api/v1/assistants/:id', async (req: Request, res: Response) => {
|
||||
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
|
||||
if (!assistant) {
|
||||
res.status(404).send(`Assistant ${req.params.id} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const openAIAssistantId = JSON.parse(assistant.details)?.id
|
||||
|
||||
const body = req.body
|
||||
const assistantDetails = JSON.parse(body.details)
|
||||
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: body.credential
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
|
||||
let tools = []
|
||||
if (assistantDetails.tools) {
|
||||
for (const tool of assistantDetails.tools ?? []) {
|
||||
tools.push({
|
||||
type: tool
|
||||
})
|
||||
}
|
||||
}
|
||||
await openai.beta.assistants.update(openAIAssistantId, {
|
||||
name: assistantDetails.name,
|
||||
description: assistantDetails.description,
|
||||
instructions: assistantDetails.instructions,
|
||||
model: assistantDetails.model,
|
||||
tools
|
||||
})
|
||||
|
||||
const newAssistantDetails = {
|
||||
...assistantDetails,
|
||||
id: openAIAssistantId
|
||||
}
|
||||
|
||||
const updateAssistant = new Assistant()
|
||||
body.details = JSON.stringify(newAssistantDetails)
|
||||
Object.assign(updateAssistant, body)
|
||||
|
||||
this.AppDataSource.getRepository(Assistant).merge(assistant, updateAssistant)
|
||||
const result = await this.AppDataSource.getRepository(Assistant).save(assistant)
|
||||
|
||||
return res.json(result)
|
||||
} catch (error) {
|
||||
return res.status(500).send(`Error updating assistant: ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Delete assistant
|
||||
this.app.delete('/api/v1/assistants/:id', async (req: Request, res: Response) => {
|
||||
const assistant = await this.AppDataSource.getRepository(Assistant).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
|
||||
if (!assistant) {
|
||||
res.status(404).send(`Assistant ${req.params.id} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const body = req.body
|
||||
const assistantDetails = JSON.parse(body.details)
|
||||
|
||||
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||
id: body.credential
|
||||
})
|
||||
|
||||
if (!credential) return res.status(404).send(`Credential ${body.credential} not found`)
|
||||
|
||||
// Decrpyt credentialData
|
||||
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData)
|
||||
const openAIApiKey = decryptedCredentialData['openAIApiKey']
|
||||
if (!openAIApiKey) return res.status(404).send(`OpenAI ApiKey not found`)
|
||||
|
||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||
|
||||
await openai.beta.assistants.del(assistantDetails.id)
|
||||
|
||||
const results = await this.AppDataSource.getRepository(Assistant).delete({ id: req.params.id })
|
||||
return res.json(results)
|
||||
} catch (error) {
|
||||
return res.status(500).send(`Error deleting assistant: ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// Configuration
|
||||
// ----------------------------------------
|
||||
|
|
@ -1121,18 +1343,25 @@ export class App {
|
|||
logger,
|
||||
appDataSource: this.AppDataSource,
|
||||
databaseEntities,
|
||||
analytic: chatflow.analytic
|
||||
analytic: chatflow.analytic,
|
||||
chatId
|
||||
})
|
||||
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
||||
chatHistory: incomingInput.history,
|
||||
logger,
|
||||
appDataSource: this.AppDataSource,
|
||||
databaseEntities,
|
||||
analytic: chatflow.analytic
|
||||
analytic: chatflow.analytic,
|
||||
chatId
|
||||
})
|
||||
|
||||
result = typeof result === 'string' ? { text: result } : result
|
||||
|
||||
// Retrieve threadId from assistant if exists
|
||||
if (typeof result === 'object' && result.assistant) {
|
||||
sessionId = result.assistant.threadId
|
||||
}
|
||||
|
||||
const userMessage: Omit<IChatMessage, 'id'> = {
|
||||
role: 'userMessage',
|
||||
content: incomingInput.question,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { ChatFlow } from '../database/entities/ChatFlow'
|
|||
import { ChatMessage } from '../database/entities/ChatMessage'
|
||||
import { Credential } from '../database/entities/Credential'
|
||||
import { Tool } from '../database/entities/Tool'
|
||||
import { Assistant } from '../database/entities/Assistant'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { CachePool } from '../CachePool'
|
||||
|
||||
|
|
@ -41,7 +42,13 @@ const QUESTION_VAR_PREFIX = 'question'
|
|||
const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
|
||||
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
||||
|
||||
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential }
|
||||
export const databaseEntities: IDatabaseEntity = {
|
||||
ChatFlow: ChatFlow,
|
||||
ChatMessage: ChatMessage,
|
||||
Tool: Tool,
|
||||
Credential: Credential,
|
||||
Assistant: Assistant
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
|
|
@ -313,12 +320,14 @@ export const clearAllSessionMemory = async (
|
|||
sessionId?: string
|
||||
) => {
|
||||
for (const node of reactFlowNodes) {
|
||||
if (node.data.category !== 'Memory') continue
|
||||
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
|
||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||
const nodeModule = await import(nodeInstanceFilePath)
|
||||
const newNodeInstance = new nodeModule.nodeClass()
|
||||
|
||||
if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId
|
||||
if (sessionId && node.data.inputs) {
|
||||
node.data.inputs.sessionId = sessionId
|
||||
}
|
||||
|
||||
if (newNodeInstance.clearSessionMemory) {
|
||||
await newNodeInstance?.clearSessionMemory(node.data, { chatId, appDataSource, databaseEntities, logger })
|
||||
|
|
@ -345,8 +354,8 @@ export const clearSessionMemoryFromViewMessageDialog = async (
|
|||
) => {
|
||||
if (!sessionId) return
|
||||
for (const node of reactFlowNodes) {
|
||||
if (node.data.category !== 'Memory') continue
|
||||
if (node.data.label !== memoryType) continue
|
||||
if (node.data.category !== 'Memory' && node.data.type !== 'OpenAIAssistant') continue
|
||||
if (memoryType && node.data.label !== memoryType) continue
|
||||
const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string
|
||||
const nodeModule = await import(nodeInstanceFilePath)
|
||||
const newNodeInstance = new nodeModule.nodeClass()
|
||||
|
|
@ -912,6 +921,8 @@ export const decryptCredentialData = async (
|
|||
): Promise<ICredentialDataDecrypted> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
const decryptedData = AES.decrypt(encryptedData, encryptKey)
|
||||
const decryptedDataStr = decryptedData.toString(enc.Utf8)
|
||||
if (!decryptedDataStr) return {}
|
||||
try {
|
||||
if (componentCredentialName && componentCredentials) {
|
||||
const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
"reactflow": "^11.5.6",
|
||||
"redux": "^4.0.5",
|
||||
"rehype-mathjax": "^4.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"socket.io-client": "^4.6.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import client from './client'
|
||||
|
||||
const getAllAssistants = () => client.get('/assistants')
|
||||
|
||||
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 deleteAssistant = (id) => client.delete(`/assistants/${id}`)
|
||||
|
||||
export default {
|
||||
getAllAssistants,
|
||||
getSpecificAssistant,
|
||||
getAssistantObj,
|
||||
getAllAvailableAssistants,
|
||||
createNewAssistant,
|
||||
updateAssistant,
|
||||
deleteAssistant
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// assets
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock } from '@tabler/icons'
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock }
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot }
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
|
|
@ -35,6 +35,14 @@ const dashboard = {
|
|||
icon: icons.IconTool,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'assistants',
|
||||
title: 'Assistants',
|
||||
type: 'item',
|
||||
url: '/assistants',
|
||||
icon: icons.IconRobot,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'credentials',
|
||||
title: 'Credentials',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ const APIKey = Loadable(lazy(() => import('views/apikey')))
|
|||
// tools routing
|
||||
const Tools = Loadable(lazy(() => import('views/tools')))
|
||||
|
||||
// assistants routing
|
||||
const Assistants = Loadable(lazy(() => import('views/assistants')))
|
||||
|
||||
// credentials routing
|
||||
const Credentials = Loadable(lazy(() => import('views/credentials')))
|
||||
|
||||
|
|
@ -45,6 +48,10 @@ const MainRoutes = {
|
|||
path: '/tools',
|
||||
element: <Tools />
|
||||
},
|
||||
{
|
||||
path: '/assistants',
|
||||
element: <Assistants />
|
||||
},
|
||||
{
|
||||
path: '/credentials',
|
||||
element: <Credentials />
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useState, useEffect, forwardRef } from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
|
|
@ -263,9 +264,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
}
|
||||
|
||||
const transformChatPKToParams = (chatPK) => {
|
||||
const chatId = chatPK.split('_')[0]
|
||||
const memoryType = chatPK.split('_')[1]
|
||||
const sessionId = chatPK.split('_')[2]
|
||||
let [c1, c2, ...rest] = chatPK.split('_')
|
||||
const chatId = c1
|
||||
const memoryType = c2
|
||||
const sessionId = rest.join('_')
|
||||
|
||||
const params = { chatId }
|
||||
if (memoryType !== 'null') params.memoryType = memoryType
|
||||
|
|
@ -601,7 +603,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
|
|||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,545 @@
|
|||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { Box, Typography, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material'
|
||||
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import { MultiDropdown } from 'ui-component/dropdown/MultiDropdown'
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
|
||||
// Icons
|
||||
import { IconX } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import assistantsApi from 'api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
|
||||
const assistantAvailableModels = [
|
||||
{
|
||||
label: 'gpt-4',
|
||||
name: 'gpt-4'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-1106-preview',
|
||||
name: 'gpt-4-1106-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-vision-preview',
|
||||
name: 'gpt-4-vision-preview'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-0613',
|
||||
name: 'gpt-4-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k',
|
||||
name: 'gpt-4-32k'
|
||||
},
|
||||
{
|
||||
label: 'gpt-4-32k-0613',
|
||||
name: 'gpt-4-32k-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo',
|
||||
name: 'gpt-3.5-turbo'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-1106',
|
||||
name: 'gpt-3.5-turbo-1106'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-0613',
|
||||
name: 'gpt-3.5-turbo-0613'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k',
|
||||
name: 'gpt-3.5-turbo-16k'
|
||||
},
|
||||
{
|
||||
label: 'gpt-3.5-turbo-16k-0613',
|
||||
name: 'gpt-3.5-turbo-16k-0613'
|
||||
}
|
||||
]
|
||||
|
||||
const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const getSpecificAssistantApi = useApi(assistantsApi.getSpecificAssistant)
|
||||
const getAssistantObjApi = useApi(assistantsApi.getAssistantObj)
|
||||
|
||||
const [assistantId, setAssistantId] = useState('')
|
||||
const [openAIAssistantId, setOpenAIAssistantId] = useState('')
|
||||
const [assistantName, setAssistantName] = useState('')
|
||||
const [assistantDesc, setAssistantDesc] = useState('')
|
||||
const [assistantIcon, setAssistantIcon] = useState(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
const [assistantModel, setAssistantModel] = useState('')
|
||||
const [assistantCredential, setAssistantCredential] = useState('')
|
||||
const [assistantInstructions, setAssistantInstructions] = useState('')
|
||||
const [assistantTools, setAssistantTools] = useState(['code_interpreter', 'retrieval'])
|
||||
|
||||
useEffect(() => {
|
||||
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||
}, [show, dispatch])
|
||||
|
||||
useEffect(() => {
|
||||
if (getSpecificAssistantApi.data) {
|
||||
setAssistantId(getSpecificAssistantApi.data.id)
|
||||
setAssistantIcon(getSpecificAssistantApi.data.iconSrc)
|
||||
setAssistantCredential(getSpecificAssistantApi.data.credential)
|
||||
|
||||
const assistantDetails = JSON.parse(getSpecificAssistantApi.data.details)
|
||||
setOpenAIAssistantId(assistantDetails.id)
|
||||
setAssistantName(assistantDetails.name)
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
}
|
||||
}, [getSpecificAssistantApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAssistantObjApi.data) {
|
||||
setOpenAIAssistantId(getAssistantObjApi.data.id)
|
||||
setAssistantName(getAssistantObjApi.data.name)
|
||||
setAssistantDesc(getAssistantObjApi.data.description)
|
||||
setAssistantModel(getAssistantObjApi.data.model)
|
||||
setAssistantInstructions(getAssistantObjApi.data.instructions)
|
||||
|
||||
let tools = []
|
||||
if (getAssistantObjApi.data.tools && getAssistantObjApi.data.tools.length) {
|
||||
for (const tool of getAssistantObjApi.data.tools) {
|
||||
tools.push(tool.type)
|
||||
}
|
||||
}
|
||||
setAssistantTools(tools)
|
||||
}
|
||||
}, [getAssistantObjApi.data])
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
// When assistant dialog is opened from Assistants dashboard
|
||||
setAssistantId(dialogProps.data.id)
|
||||
setAssistantIcon(dialogProps.data.iconSrc)
|
||||
setAssistantCredential(dialogProps.data.credential)
|
||||
|
||||
const assistantDetails = JSON.parse(dialogProps.data.details)
|
||||
setOpenAIAssistantId(assistantDetails.id)
|
||||
setAssistantName(assistantDetails.name)
|
||||
setAssistantDesc(assistantDetails.description)
|
||||
setAssistantModel(assistantDetails.model)
|
||||
setAssistantInstructions(assistantDetails.instructions)
|
||||
setAssistantTools(assistantDetails.tools ?? [])
|
||||
} else if (dialogProps.type === 'EDIT' && dialogProps.assistantId) {
|
||||
// When assistant dialog is opened from OpenAIAssistant node in canvas
|
||||
getSpecificAssistantApi.request(dialogProps.assistantId)
|
||||
} else if (dialogProps.type === 'ADD' && dialogProps.selectedOpenAIAssistantId && dialogProps.credential) {
|
||||
// When assistant dialog is to add new assistant from existing
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential(dialogProps.credential)
|
||||
|
||||
getAssistantObjApi.request(dialogProps.selectedOpenAIAssistantId, dialogProps.credential)
|
||||
} else if (dialogProps.type === 'ADD' && !dialogProps.selectedOpenAIAssistantId) {
|
||||
// When assistant dialog is to add a blank new assistant
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential('')
|
||||
|
||||
setOpenAIAssistantId('')
|
||||
setAssistantName('')
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
}
|
||||
|
||||
return () => {
|
||||
setAssistantId('')
|
||||
setAssistantIcon(`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`)
|
||||
setAssistantCredential('')
|
||||
|
||||
setOpenAIAssistantId('')
|
||||
setAssistantName('')
|
||||
setAssistantDesc('')
|
||||
setAssistantModel('')
|
||||
setAssistantInstructions('')
|
||||
setAssistantTools(['code_interpreter', 'retrieval'])
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialogProps])
|
||||
|
||||
const addNewAssistant = async () => {
|
||||
try {
|
||||
const assistantDetails = {
|
||||
id: openAIAssistantId,
|
||||
name: assistantName,
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
tools: assistantTools
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
iconSrc: assistantIcon,
|
||||
credential: assistantCredential
|
||||
}
|
||||
|
||||
const createResp = await assistantsApi.createNewAssistant(obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Assistant added',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(createResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Assistant: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const saveAssistant = async () => {
|
||||
try {
|
||||
const assistantDetails = {
|
||||
name: assistantName,
|
||||
description: assistantDesc,
|
||||
model: assistantModel,
|
||||
instructions: assistantInstructions,
|
||||
tools: assistantTools
|
||||
}
|
||||
const obj = {
|
||||
details: JSON.stringify(assistantDetails),
|
||||
iconSrc: assistantIcon,
|
||||
credential: assistantCredential
|
||||
}
|
||||
const saveResp = await assistantsApi.updateAssistant(assistantId, obj)
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Assistant saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(saveResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Assistant: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssistant = async () => {
|
||||
const confirmPayload = {
|
||||
title: `Delete Assistant`,
|
||||
description: `Delete Assistant ${assistantName}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const delResp = await assistantsApi.deleteAssistant(assistantId)
|
||||
if (delResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Assistant deleted',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete Assistant: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='md'
|
||||
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'>
|
||||
Assistant Name
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The name of the assistant. The maximum length is 256 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantName'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='My New Assistant'
|
||||
value={assistantName}
|
||||
name='assistantName'
|
||||
onChange={(e) => setAssistantName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Description
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The description of the assistant. The maximum length is 512 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantDesc'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='Description of what the Assistant does'
|
||||
multiline={true}
|
||||
rows={3}
|
||||
value={assistantDesc}
|
||||
name='assistantDesc'
|
||||
onChange={(e) => setAssistantDesc(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Assistant Icon Src</Typography>
|
||||
</Stack>
|
||||
<div
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 5,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
alt={assistantName}
|
||||
src={assistantIcon}
|
||||
/>
|
||||
</div>
|
||||
<OutlinedInput
|
||||
id='assistantIcon'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder={`https://api.dicebear.com/7.x/bottts/svg?seed=${uuidv4()}`}
|
||||
value={assistantIcon}
|
||||
name='assistantIcon'
|
||||
onChange={(e) => setAssistantIcon(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<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 sx={{ p: 2 }}>
|
||||
<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 sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Instruction
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title={'The system instructions that the assistant uses. The maximum length is 32768 characters.'}
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='assistantInstructions'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='You are a personal math tutor. When asked a question, write and run Python code to answer the question.'
|
||||
multiline={true}
|
||||
rows={3}
|
||||
value={assistantInstructions}
|
||||
name='assistantInstructions'
|
||||
onChange={(e) => setAssistantInstructions(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistant Tools
|
||||
<TooltipWithParser
|
||||
style={{ marginLeft: 10 }}
|
||||
title='A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.'
|
||||
/>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<MultiDropdown
|
||||
key={JSON.stringify(assistantTools)}
|
||||
name={JSON.stringify(assistantTools)}
|
||||
options={[
|
||||
{
|
||||
label: 'Code Interpreter',
|
||||
name: 'code_interpreter'
|
||||
},
|
||||
{
|
||||
label: 'Retrieval',
|
||||
name: 'retrieval'
|
||||
}
|
||||
]}
|
||||
onSelect={(newValue) => (newValue ? setAssistantTools(JSON.parse(newValue)) : setAssistantTools([]))}
|
||||
value={assistantTools ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{dialogProps.type === 'EDIT' && (
|
||||
<StyledButton color='error' variant='contained' onClick={() => deleteAssistant()}>
|
||||
Delete
|
||||
</StyledButton>
|
||||
)}
|
||||
<StyledButton
|
||||
disabled={!(assistantModel && assistantCredential)}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' ? addNewAssistant() : saveAssistant())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AssistantDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default AssistantDialog
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack, Typography, Dialog, DialogContent, DialogTitle, DialogActions, Box } from '@mui/material'
|
||||
import CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||
import { Dropdown } from 'ui-component/dropdown/Dropdown'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import assistantsApi from 'api/assistants'
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const getAllAvailableAssistantsApi = useApi(assistantsApi.getAllAvailableAssistants)
|
||||
|
||||
const [credentialId, setCredentialId] = useState('')
|
||||
const [availableAssistantsOptions, setAvailableAssistantsOptions] = useState([])
|
||||
const [selectedOpenAIAssistantId, setSelectedOpenAIAssistantId] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setCredentialId('')
|
||||
setAvailableAssistantsOptions([])
|
||||
setSelectedOpenAIAssistantId('')
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAvailableAssistantsApi.data && getAllAvailableAssistantsApi.data.length) {
|
||||
const assistants = []
|
||||
for (let i = 0; i < getAllAvailableAssistantsApi.data.length; i += 1) {
|
||||
assistants.push({
|
||||
label: getAllAvailableAssistantsApi.data[i].name,
|
||||
name: getAllAvailableAssistantsApi.data[i].id,
|
||||
description: getAllAvailableAssistantsApi.data[i].instructions
|
||||
})
|
||||
}
|
||||
setAvailableAssistantsOptions(assistants)
|
||||
}
|
||||
}, [getAllAvailableAssistantsApi.data])
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='xs'
|
||||
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'>
|
||||
OpenAI Credential
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<CredentialInputHandler
|
||||
key={credentialId}
|
||||
data={credentialId ? { credential: credentialId } : {}}
|
||||
inputParam={{
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['openAIApi']
|
||||
}}
|
||||
onSelect={(newValue) => {
|
||||
setCredentialId(newValue)
|
||||
if (newValue) getAllAvailableAssistantsApi.request(newValue)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{credentialId && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>
|
||||
Assistants
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Dropdown
|
||||
name={selectedOpenAIAssistantId}
|
||||
options={availableAssistantsOptions}
|
||||
onSelect={(newValue) => setSelectedOpenAIAssistantId(newValue)}
|
||||
value={selectedOpenAIAssistantId ?? 'choose an option'}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
{selectedOpenAIAssistantId && (
|
||||
<DialogActions>
|
||||
<StyledButton variant='contained' onClick={() => onAssistantSelected(selectedOpenAIAssistantId, credentialId)}>
|
||||
Load
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
)}
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
LoadAssistantDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onAssistantSelected: PropTypes.func
|
||||
}
|
||||
|
||||
export default LoadAssistantDialog
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
// material-ui
|
||||
import { Grid, Box, Stack, Button } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
|
||||
// project imports
|
||||
import MainCard from 'ui-component/cards/MainCard'
|
||||
import ItemCard from 'ui-component/cards/ItemCard'
|
||||
import { gridSpacing } from 'store/constant'
|
||||
import ToolEmptySVG from 'assets/images/tools_empty.svg'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import AssistantDialog from './AssistantDialog'
|
||||
import LoadAssistantDialog from './LoadAssistantDialog'
|
||||
|
||||
// API
|
||||
import assistantsApi from 'api/assistants'
|
||||
|
||||
// Hooks
|
||||
import useApi from 'hooks/useApi'
|
||||
|
||||
// icons
|
||||
import { IconPlus, IconFileImport } from '@tabler/icons'
|
||||
|
||||
// ==============================|| CHATFLOWS ||============================== //
|
||||
|
||||
const Assistants = () => {
|
||||
const theme = useTheme()
|
||||
const customization = useSelector((state) => state.customization)
|
||||
|
||||
const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants)
|
||||
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
const [showLoadDialog, setShowLoadDialog] = useState(false)
|
||||
const [loadDialogProps, setLoadDialogProps] = useState({})
|
||||
|
||||
const loadExisting = () => {
|
||||
const dialogProp = {
|
||||
title: 'Load Existing Assistant'
|
||||
}
|
||||
setLoadDialogProps(dialogProp)
|
||||
setShowLoadDialog(true)
|
||||
}
|
||||
|
||||
const onAssistantSelected = (selectedOpenAIAssistantId, credential) => {
|
||||
setShowLoadDialog(false)
|
||||
addNew(selectedOpenAIAssistantId, credential)
|
||||
}
|
||||
|
||||
const addNew = (selectedOpenAIAssistantId, credential) => {
|
||||
const dialogProp = {
|
||||
title: 'Add New Assistant',
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
selectedOpenAIAssistantId,
|
||||
credential
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const edit = (selectedAssistant) => {
|
||||
const dialogProp = {
|
||||
title: 'Edit Assistant',
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: selectedAssistant
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllAssistantsApi.request()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAssistantsApi.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<Grid sx={{ mb: 1.25 }} container direction='row'>
|
||||
<h1>OpenAI Assistants</h1>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<Grid item>
|
||||
<Button variant='outlined' sx={{ mr: 2 }} onClick={loadExisting} startIcon={<IconFileImport />}>
|
||||
Load
|
||||
</Button>
|
||||
<StyledButton variant='contained' sx={{ color: 'white' }} onClick={addNew} startIcon={<IconPlus />}>
|
||||
Add
|
||||
</StyledButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Grid container spacing={gridSpacing}>
|
||||
{!getAllAssistantsApi.loading &&
|
||||
getAllAssistantsApi.data &&
|
||||
getAllAssistantsApi.data.map((data, index) => (
|
||||
<Grid key={index} item lg={3} md={4} sm={6} xs={12}>
|
||||
<ItemCard
|
||||
data={{
|
||||
name: JSON.parse(data.details)?.name,
|
||||
description: JSON.parse(data.details)?.instructions,
|
||||
iconSrc: data.iconSrc
|
||||
}}
|
||||
onClick={() => edit(data)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{!getAllAssistantsApi.loading && (!getAllAssistantsApi.data || getAllAssistantsApi.data.length === 0) && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={ToolEmptySVG} alt='ToolEmptySVG' />
|
||||
</Box>
|
||||
<div>No Assistants Added Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<LoadAssistantDialog
|
||||
show={showLoadDialog}
|
||||
dialogProps={loadDialogProps}
|
||||
onCancel={() => setShowLoadDialog(false)}
|
||||
onAssistantSelected={onAssistantSelected}
|
||||
></LoadAssistantDialog>
|
||||
<AssistantDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
></AssistantDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Assistants
|
||||
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
|
|||
import socketIOClient from 'socket.io-client'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import rehypeMathjax from 'rehype-mathjax'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import remarkMath from 'remark-math'
|
||||
|
||||
|
|
@ -287,7 +288,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||
{/* Messages are being rendered in Markdown format */}
|
||||
<MemoizedReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
|
|
|
|||
Loading…
Reference in New Issue