mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
Merge branch 'main' into FEATURE/Vision
# Conflicts: # packages/server/src/index.ts # packages/ui/src/views/chatmessage/ChatMessage.js
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flowise",
|
"name": "flowise",
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://flowiseai.com",
|
"homepage": "https://flowiseai.com",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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'
|
||||||
|
|
||||||
class OpenAIAssistant_Agents implements INode {
|
class OpenAIAssistant_Agents implements INode {
|
||||||
label: string
|
label: string
|
||||||
@@ -149,6 +150,11 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
|
|
||||||
const openai = new OpenAI({ apiKey: openAIApiKey })
|
const openai = new OpenAI({ apiKey: openAIApiKey })
|
||||||
|
|
||||||
|
// Start analytics
|
||||||
|
const analyticHandlers = new AnalyticHandler(nodeData, options)
|
||||||
|
await analyticHandlers.init()
|
||||||
|
const parentIds = await analyticHandlers.onChainStart('OpenAIAssistant', input)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const assistantDetails = JSON.parse(assistant.details)
|
const assistantDetails = JSON.parse(assistant.details)
|
||||||
const openAIAssistantId = assistantDetails.id
|
const openAIAssistantId = assistantDetails.id
|
||||||
@@ -171,7 +177,8 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chatmessage = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
const chatmessage = await appDataSource.getRepository(databaseEntities['ChatMessage']).findOneBy({
|
||||||
chatId: options.chatId
|
chatId: options.chatId,
|
||||||
|
chatflowid: options.chatflowid
|
||||||
})
|
})
|
||||||
|
|
||||||
let threadId = ''
|
let threadId = ''
|
||||||
@@ -185,7 +192,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
threadId = thread.id
|
threadId = thread.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all runs
|
// List all runs, in case existing thread is still running
|
||||||
if (!isNewThread) {
|
if (!isNewThread) {
|
||||||
const promise = (threadId: string) => {
|
const promise = (threadId: string) => {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
@@ -221,6 +228,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Run assistant thread
|
// Run assistant thread
|
||||||
|
const llmIds = await analyticHandlers.onLLMStart('ChatOpenAI', input, parentIds)
|
||||||
const runThread = await openai.beta.threads.runs.create(threadId, {
|
const runThread = await openai.beta.threads.runs.create(threadId, {
|
||||||
assistant_id: retrievedAssistant.id
|
assistant_id: retrievedAssistant.id
|
||||||
})
|
})
|
||||||
@@ -253,7 +261,15 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
for (let i = 0; i < actions.length; i += 1) {
|
for (let i = 0; i < actions.length; i += 1) {
|
||||||
const tool = tools.find((tool: any) => tool.name === actions[i].tool)
|
const tool = tools.find((tool: any) => tool.name === actions[i].tool)
|
||||||
if (!tool) continue
|
if (!tool) continue
|
||||||
|
|
||||||
|
// Start tool analytics
|
||||||
|
const toolIds = await analyticHandlers.onToolStart(tool.name, actions[i].toolInput, parentIds)
|
||||||
|
|
||||||
const toolOutput = await tool.call(actions[i].toolInput)
|
const toolOutput = await tool.call(actions[i].toolInput)
|
||||||
|
|
||||||
|
// End tool analytics
|
||||||
|
await analyticHandlers.onToolEnd(toolIds, toolOutput)
|
||||||
|
|
||||||
submitToolOutputs.push({
|
submitToolOutputs.push({
|
||||||
tool_call_id: actions[i].toolCallId,
|
tool_call_id: actions[i].toolCallId,
|
||||||
output: toolOutput
|
output: toolOutput
|
||||||
@@ -302,7 +318,9 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
runThreadId = newRunThread.id
|
runThreadId = newRunThread.id
|
||||||
state = await promise(threadId, newRunThread.id)
|
state = await promise(threadId, newRunThread.id)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Error processing thread: ${state}, Thread ID: ${threadId}`)
|
const errMsg = `Error processing thread: ${state}, Thread ID: ${threadId}`
|
||||||
|
await analyticHandlers.onChainError(parentIds, errMsg)
|
||||||
|
throw new Error(errMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,11 +405,18 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
const bitmap = fsDefault.readFileSync(filePath)
|
const bitmap = fsDefault.readFileSync(filePath)
|
||||||
const base64String = Buffer.from(bitmap).toString('base64')
|
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
|
||||||
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/>`
|
||||||
returnVal += imgHTML
|
returnVal += imgHTML
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imageRegex = /<img[^>]*\/>/g
|
||||||
|
let llmOutput = returnVal.replace(imageRegex, '')
|
||||||
|
llmOutput = llmOutput.replace('<br/>', '')
|
||||||
|
await analyticHandlers.onLLMEnd(llmIds, llmOutput)
|
||||||
|
await analyticHandlers.onChainEnd(parentIds, messageData, true)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: returnVal,
|
text: returnVal,
|
||||||
usedTools,
|
usedTools,
|
||||||
@@ -399,6 +424,7 @@ class OpenAIAssistant_Agents implements INode {
|
|||||||
assistant: { assistantId: openAIAssistantId, threadId, runId: runThreadId, messages: messageData }
|
assistant: { assistantId: openAIAssistantId, threadId, runId: runThreadId, messages: messageData }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
await analyticHandlers.onChainError(parentIds, error, true)
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class RedisCache implements INode {
|
|||||||
redisClient.update = async (prompt: string, llmKey: string, value: Generation[]) => {
|
redisClient.update = async (prompt: string, llmKey: string, value: Generation[]) => {
|
||||||
for (let i = 0; i < value.length; i += 1) {
|
for (let i = 0; i < value.length; i += 1) {
|
||||||
const key = getCacheKey(prompt, llmKey, String(i))
|
const key = getCacheKey(prompt, llmKey, String(i))
|
||||||
if (ttl !== undefined) {
|
if (ttl) {
|
||||||
await client.set(key, JSON.stringify(serializeGeneration(value[i])), 'EX', parseInt(ttl, 10))
|
await client.set(key, JSON.stringify(serializeGeneration(value[i])), 'EX', parseInt(ttl, 10))
|
||||||
} else {
|
} else {
|
||||||
await client.set(key, JSON.stringify(serializeGeneration(value[i])))
|
await client.set(key, JSON.stringify(serializeGeneration(value[i])))
|
||||||
|
|||||||
@@ -162,8 +162,11 @@ class S3_DocumentLoaders implements INode {
|
|||||||
accessKeyId?: string
|
accessKeyId?: string
|
||||||
secretAccessKey?: string
|
secretAccessKey?: string
|
||||||
} = {
|
} = {
|
||||||
accessKeyId,
|
region,
|
||||||
secretAccessKey
|
credentials: {
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.load = async () => {
|
loader.load = async () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class HydeRetriever_Retrievers implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Hyde Retriever'
|
this.label = 'Hyde Retriever'
|
||||||
this.name = 'HydeRetriever'
|
this.name = 'HydeRetriever'
|
||||||
this.version = 1.0
|
this.version = 2.0
|
||||||
this.type = 'HydeRetriever'
|
this.type = 'HydeRetriever'
|
||||||
this.icon = 'hyderetriever.svg'
|
this.icon = 'hyderetriever.svg'
|
||||||
this.category = 'Retrievers'
|
this.category = 'Retrievers'
|
||||||
@@ -36,41 +36,66 @@ class HydeRetriever_Retrievers implements INode {
|
|||||||
type: 'VectorStore'
|
type: 'VectorStore'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Prompt Key',
|
label: 'Select Defined Prompt',
|
||||||
name: 'promptKey',
|
name: 'promptKey',
|
||||||
|
description: 'Select a pre-defined prompt',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'websearch',
|
label: 'websearch',
|
||||||
name: 'websearch'
|
name: 'websearch',
|
||||||
|
description: `Please write a passage to answer the question
|
||||||
|
Question: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'scifact',
|
label: 'scifact',
|
||||||
name: 'scifact'
|
name: 'scifact',
|
||||||
|
description: `Please write a scientific paper passage to support/refute the claim
|
||||||
|
Claim: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'arguana',
|
label: 'arguana',
|
||||||
name: 'arguana'
|
name: 'arguana',
|
||||||
|
description: `Please write a counter argument for the passage
|
||||||
|
Passage: {question}
|
||||||
|
Counter Argument:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'trec-covid',
|
label: 'trec-covid',
|
||||||
name: 'trec-covid'
|
name: 'trec-covid',
|
||||||
|
description: `Please write a scientific paper passage to answer the question
|
||||||
|
Question: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'fiqa',
|
label: 'fiqa',
|
||||||
name: 'fiqa'
|
name: 'fiqa',
|
||||||
|
description: `Please write a financial article passage to answer the question
|
||||||
|
Question: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'dbpedia-entity',
|
label: 'dbpedia-entity',
|
||||||
name: 'dbpedia-entity'
|
name: 'dbpedia-entity',
|
||||||
|
description: `Please write a passage to answer the question.
|
||||||
|
Question: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'trec-news',
|
label: 'trec-news',
|
||||||
name: 'trec-news'
|
name: 'trec-news',
|
||||||
|
description: `Please write a news passage about the topic.
|
||||||
|
Topic: {question}
|
||||||
|
Passage:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'mr-tydi',
|
label: 'mr-tydi',
|
||||||
name: 'mr-tydi'
|
name: 'mr-tydi',
|
||||||
|
description: `Please write a passage in Swahili/Korean/Japanese/Bengali to answer the question in detail.
|
||||||
|
Question: {question}
|
||||||
|
Passage:`
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
default: 'websearch'
|
default: 'websearch'
|
||||||
@@ -78,7 +103,7 @@ class HydeRetriever_Retrievers implements INode {
|
|||||||
{
|
{
|
||||||
label: 'Custom Prompt',
|
label: 'Custom Prompt',
|
||||||
name: 'customPrompt',
|
name: 'customPrompt',
|
||||||
description: 'If custom prompt is used, this will override Prompt Key',
|
description: 'If custom prompt is used, this will override Defined Prompt',
|
||||||
placeholder: 'Please write a passage to answer the question\nQuestion: {question}\nPassage:',
|
placeholder: 'Please write a passage to answer the question\nQuestion: {question}\nPassage:',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
rows: 4,
|
rows: 4,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flowise-components",
|
"name": "flowise-components",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"description": "Flowiseai Components",
|
"description": "Flowiseai Components",
|
||||||
"main": "dist/src/index",
|
"main": "dist/src/index",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"@upstash/redis": "^1.22.1",
|
"@upstash/redis": "^1.22.1",
|
||||||
"@zilliz/milvus2-sdk-node": "^2.2.24",
|
"@zilliz/milvus2-sdk-node": "^2.2.24",
|
||||||
"apify-client": "^2.7.1",
|
"apify-client": "^2.7.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "1.6.2",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"chromadb": "^1.5.11",
|
"chromadb": "^1.5.11",
|
||||||
"cohere-ai": "^6.2.0",
|
"cohere-ai": "^6.2.0",
|
||||||
@@ -51,8 +51,9 @@
|
|||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"langchain": "^0.0.196",
|
"langchain": "^0.0.196",
|
||||||
|
"langfuse": "^1.2.0",
|
||||||
"langfuse-langchain": "^1.0.31",
|
"langfuse-langchain": "^1.0.31",
|
||||||
"langsmith": "^0.0.32",
|
"langsmith": "^0.0.49",
|
||||||
"linkifyjs": "^4.1.1",
|
"linkifyjs": "^4.1.1",
|
||||||
"llmonitor": "^0.5.5",
|
"llmonitor": "^0.5.5",
|
||||||
"mammoth": "^1.5.1",
|
"mammoth": "^1.5.1",
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor'
|
|||||||
import { getCredentialData, getCredentialParam } from './utils'
|
import { getCredentialData, getCredentialParam } from './utils'
|
||||||
import { ICommonObject, INodeData } from './Interface'
|
import { ICommonObject, INodeData } from './Interface'
|
||||||
import CallbackHandler from 'langfuse-langchain'
|
import CallbackHandler from 'langfuse-langchain'
|
||||||
|
import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith'
|
||||||
|
import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse'
|
||||||
|
import monitor from 'llmonitor'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
interface AgentRun extends Run {
|
interface AgentRun extends Run {
|
||||||
actions: AgentAction[]
|
actions: AgentAction[]
|
||||||
@@ -273,3 +277,488 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
|||||||
throw new Error(e)
|
throw new Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AnalyticHandler {
|
||||||
|
nodeData: INodeData
|
||||||
|
options: ICommonObject = {}
|
||||||
|
handlers: ICommonObject = {}
|
||||||
|
|
||||||
|
constructor(nodeData: INodeData, options: ICommonObject) {
|
||||||
|
this.options = options
|
||||||
|
this.nodeData = nodeData
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
if (!this.options.analytic) return
|
||||||
|
|
||||||
|
const analytic = JSON.parse(this.options.analytic)
|
||||||
|
|
||||||
|
for (const provider in analytic) {
|
||||||
|
const providerStatus = analytic[provider].status as boolean
|
||||||
|
|
||||||
|
if (providerStatus) {
|
||||||
|
const credentialId = analytic[provider].credentialId as string
|
||||||
|
const credentialData = await getCredentialData(credentialId ?? '', this.options)
|
||||||
|
if (provider === 'langSmith') {
|
||||||
|
const langSmithProject = analytic[provider].projectName as string
|
||||||
|
const langSmithApiKey = getCredentialParam('langSmithApiKey', credentialData, this.nodeData)
|
||||||
|
const langSmithEndpoint = getCredentialParam('langSmithEndpoint', credentialData, this.nodeData)
|
||||||
|
|
||||||
|
const client = new LangsmithClient({
|
||||||
|
apiUrl: langSmithEndpoint ?? 'https://api.smith.langchain.com',
|
||||||
|
apiKey: langSmithApiKey
|
||||||
|
})
|
||||||
|
|
||||||
|
this.handlers['langSmith'] = { client, langSmithProject }
|
||||||
|
} else if (provider === 'langFuse') {
|
||||||
|
const release = analytic[provider].release as string
|
||||||
|
const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, this.nodeData)
|
||||||
|
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, this.nodeData)
|
||||||
|
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, this.nodeData)
|
||||||
|
|
||||||
|
const langfuse = new Langfuse({
|
||||||
|
secretKey: langFuseSecretKey,
|
||||||
|
publicKey: langFusePublicKey,
|
||||||
|
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com',
|
||||||
|
release
|
||||||
|
})
|
||||||
|
this.handlers['langFuse'] = { client: langfuse }
|
||||||
|
} else if (provider === 'llmonitor') {
|
||||||
|
const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, this.nodeData)
|
||||||
|
const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, this.nodeData)
|
||||||
|
|
||||||
|
monitor.init({
|
||||||
|
appId: llmonitorAppId,
|
||||||
|
apiUrl: llmonitorEndpoint
|
||||||
|
})
|
||||||
|
|
||||||
|
this.handlers['llmonitor'] = { client: monitor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onChainStart(name: string, input: string, parentIds?: ICommonObject) {
|
||||||
|
const returnIds: ICommonObject = {
|
||||||
|
langSmith: {},
|
||||||
|
langFuse: {},
|
||||||
|
llmonitor: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
if (!parentIds || !Object.keys(parentIds).length) {
|
||||||
|
const parentRunConfig: RunTreeConfig = {
|
||||||
|
name,
|
||||||
|
run_type: 'chain',
|
||||||
|
inputs: {
|
||||||
|
text: input
|
||||||
|
},
|
||||||
|
serialized: {},
|
||||||
|
project_name: this.handlers['langSmith'].langSmithProject,
|
||||||
|
client: this.handlers['langSmith'].client
|
||||||
|
}
|
||||||
|
const parentRun = new RunTree(parentRunConfig)
|
||||||
|
await parentRun.postRun()
|
||||||
|
this.handlers['langSmith'].chainRun = { [parentRun.id]: parentRun }
|
||||||
|
returnIds['langSmith'].chainRun = parentRun.id
|
||||||
|
} else {
|
||||||
|
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||||
|
if (parentRun) {
|
||||||
|
const childChainRun = await parentRun.createChild({
|
||||||
|
name,
|
||||||
|
run_type: 'chain',
|
||||||
|
inputs: {
|
||||||
|
text: input
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await childChainRun.postRun()
|
||||||
|
this.handlers['langSmith'].chainRun = { [childChainRun.id]: childChainRun }
|
||||||
|
returnIds['langSmith'].chainRun = childChainRun.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
let langfuseTraceClient: LangfuseTraceClient
|
||||||
|
|
||||||
|
if (!parentIds || !Object.keys(parentIds).length) {
|
||||||
|
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||||
|
langfuseTraceClient = langfuse.trace({
|
||||||
|
name,
|
||||||
|
userId: this.options.chatId,
|
||||||
|
metadata: { tags: ['openai-assistant'] }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (langfuseTraceClient) {
|
||||||
|
const span = langfuseTraceClient.span({
|
||||||
|
name,
|
||||||
|
input: {
|
||||||
|
text: input
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.handlers['langFuse'].trace = { [langfuseTraceClient.id]: langfuseTraceClient }
|
||||||
|
this.handlers['langFuse'].span = { [span.id]: span }
|
||||||
|
returnIds['langFuse'].trace = langfuseTraceClient.id
|
||||||
|
returnIds['langFuse'].span = span.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor) {
|
||||||
|
const runId = uuidv4()
|
||||||
|
await monitor.trackEvent('chain', 'start', {
|
||||||
|
runId,
|
||||||
|
name,
|
||||||
|
userId: this.options.chatId,
|
||||||
|
input
|
||||||
|
})
|
||||||
|
this.handlers['llmonitor'].chainEvent = { [runId]: runId }
|
||||||
|
returnIds['llmonitor'].chainEvent = runId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnIds
|
||||||
|
}
|
||||||
|
|
||||||
|
async onChainEnd(returnIds: ICommonObject, output: string | object, shutdown = false) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun]
|
||||||
|
if (chainRun) {
|
||||||
|
await chainRun.end({
|
||||||
|
outputs: {
|
||||||
|
output
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await chainRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span]
|
||||||
|
if (span) {
|
||||||
|
span.end({
|
||||||
|
output
|
||||||
|
})
|
||||||
|
if (shutdown) {
|
||||||
|
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||||
|
await langfuse.shutdownAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const chainEventId = returnIds['llmonitor'].chainEvent
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && chainEventId) {
|
||||||
|
await monitor.trackEvent('chain', 'end', {
|
||||||
|
runId: chainEventId,
|
||||||
|
output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onChainError(returnIds: ICommonObject, error: string | object, shutdown = false) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun]
|
||||||
|
if (chainRun) {
|
||||||
|
await chainRun.end({
|
||||||
|
error: {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await chainRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span]
|
||||||
|
if (span) {
|
||||||
|
span.end({
|
||||||
|
output: {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (shutdown) {
|
||||||
|
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||||
|
await langfuse.shutdownAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const chainEventId = returnIds['llmonitor'].chainEvent
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && chainEventId) {
|
||||||
|
await monitor.trackEvent('chain', 'end', {
|
||||||
|
runId: chainEventId,
|
||||||
|
output: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLLMStart(name: string, input: string, parentIds: ICommonObject) {
|
||||||
|
const returnIds: ICommonObject = {
|
||||||
|
langSmith: {},
|
||||||
|
langFuse: {},
|
||||||
|
llmonitor: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||||
|
if (parentRun) {
|
||||||
|
const childLLMRun = await parentRun.createChild({
|
||||||
|
name,
|
||||||
|
run_type: 'llm',
|
||||||
|
inputs: {
|
||||||
|
prompts: [input]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await childLLMRun.postRun()
|
||||||
|
this.handlers['langSmith'].llmRun = { [childLLMRun.id]: childLLMRun }
|
||||||
|
returnIds['langSmith'].llmRun = childLLMRun.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace]
|
||||||
|
if (trace) {
|
||||||
|
const generation = trace.generation({
|
||||||
|
name,
|
||||||
|
prompt: input
|
||||||
|
})
|
||||||
|
this.handlers['langFuse'].generation = { [generation.id]: generation }
|
||||||
|
returnIds['langFuse'].generation = generation.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent]
|
||||||
|
|
||||||
|
if (monitor && chainEventId) {
|
||||||
|
const runId = uuidv4()
|
||||||
|
await monitor.trackEvent('llm', 'start', {
|
||||||
|
runId,
|
||||||
|
parentRunId: chainEventId,
|
||||||
|
name,
|
||||||
|
userId: this.options.chatId,
|
||||||
|
input
|
||||||
|
})
|
||||||
|
this.handlers['llmonitor'].llmEvent = { [runId]: runId }
|
||||||
|
returnIds['llmonitor'].llmEvent = runId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnIds
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLLMEnd(returnIds: ICommonObject, output: string) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun]
|
||||||
|
if (llmRun) {
|
||||||
|
await llmRun.end({
|
||||||
|
outputs: {
|
||||||
|
generations: [output]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await llmRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation]
|
||||||
|
if (generation) {
|
||||||
|
generation.end({
|
||||||
|
completion: output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent]
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && llmEventId) {
|
||||||
|
await monitor.trackEvent('llm', 'end', {
|
||||||
|
runId: llmEventId,
|
||||||
|
output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLLMError(returnIds: ICommonObject, error: string | object) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun]
|
||||||
|
if (llmRun) {
|
||||||
|
await llmRun.end({
|
||||||
|
error: {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await llmRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation]
|
||||||
|
if (generation) {
|
||||||
|
generation.end({
|
||||||
|
completion: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent]
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && llmEventId) {
|
||||||
|
await monitor.trackEvent('llm', 'end', {
|
||||||
|
runId: llmEventId,
|
||||||
|
output: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onToolStart(name: string, input: string | object, parentIds: ICommonObject) {
|
||||||
|
const returnIds: ICommonObject = {
|
||||||
|
langSmith: {},
|
||||||
|
langFuse: {},
|
||||||
|
llmonitor: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||||
|
if (parentRun) {
|
||||||
|
const childToolRun = await parentRun.createChild({
|
||||||
|
name,
|
||||||
|
run_type: 'tool',
|
||||||
|
inputs: {
|
||||||
|
input
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await childToolRun.postRun()
|
||||||
|
this.handlers['langSmith'].toolRun = { [childToolRun.id]: childToolRun }
|
||||||
|
returnIds['langSmith'].toolRun = childToolRun.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace]
|
||||||
|
if (trace) {
|
||||||
|
const toolSpan = trace.span({
|
||||||
|
name,
|
||||||
|
input
|
||||||
|
})
|
||||||
|
this.handlers['langFuse'].toolSpan = { [toolSpan.id]: toolSpan }
|
||||||
|
returnIds['langFuse'].toolSpan = toolSpan.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent]
|
||||||
|
|
||||||
|
if (monitor && chainEventId) {
|
||||||
|
const runId = uuidv4()
|
||||||
|
await monitor.trackEvent('tool', 'start', {
|
||||||
|
runId,
|
||||||
|
parentRunId: chainEventId,
|
||||||
|
name,
|
||||||
|
userId: this.options.chatId,
|
||||||
|
input
|
||||||
|
})
|
||||||
|
this.handlers['llmonitor'].toolEvent = { [runId]: runId }
|
||||||
|
returnIds['llmonitor'].toolEvent = runId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnIds
|
||||||
|
}
|
||||||
|
|
||||||
|
async onToolEnd(returnIds: ICommonObject, output: string | object) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun]
|
||||||
|
if (toolRun) {
|
||||||
|
await toolRun.end({
|
||||||
|
outputs: {
|
||||||
|
output
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await toolRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan]
|
||||||
|
if (toolSpan) {
|
||||||
|
toolSpan.end({
|
||||||
|
output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const toolEventId: string = this.handlers['llmonitor'].toolEvent[returnIds['llmonitor'].toolEvent]
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && toolEventId) {
|
||||||
|
await monitor.trackEvent('tool', 'end', {
|
||||||
|
runId: toolEventId,
|
||||||
|
output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onToolError(returnIds: ICommonObject, error: string | object) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||||
|
const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun]
|
||||||
|
if (toolRun) {
|
||||||
|
await toolRun.end({
|
||||||
|
error: {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await toolRun.patchRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||||
|
const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan]
|
||||||
|
if (toolSpan) {
|
||||||
|
toolSpan.end({
|
||||||
|
output: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||||
|
const toolEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].toolEvent]
|
||||||
|
const monitor = this.handlers['llmonitor'].client
|
||||||
|
|
||||||
|
if (monitor && toolEventId) {
|
||||||
|
await monitor.trackEvent('tool', 'end', {
|
||||||
|
runId: toolEventId,
|
||||||
|
output: error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flowise",
|
"name": "flowise",
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"description": "Flowiseai Server",
|
"description": "Flowiseai Server",
|
||||||
"main": "dist/index",
|
"main": "dist/index",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/core": "^1.13.10",
|
"@oclif/core": "^1.13.10",
|
||||||
"async-mutex": "^0.4.0",
|
"async-mutex": "^0.4.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "1.6.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
@@ -61,6 +61,7 @@
|
|||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"pg": "^8.11.1",
|
"pg": "^8.11.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"sanitize-html": "^2.11.0",
|
||||||
"socket.io": "^4.6.1",
|
"socket.io": "^4.6.1",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
"typeorm": "^0.3.6",
|
"typeorm": "^0.3.6",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
|
"@types/sanitize-html": "^2.9.5",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.1.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"oclif": "^3",
|
"oclif": "^3",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class ChatflowPool {
|
|||||||
* @param {IReactFlowNode[]} startingNodes
|
* @param {IReactFlowNode[]} startingNodes
|
||||||
* @param {ICommonObject} overrideConfig
|
* @param {ICommonObject} overrideConfig
|
||||||
*/
|
*/
|
||||||
add(chatflowid: string, endingNodeData: INodeData, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) {
|
add(chatflowid: string, endingNodeData: INodeData | undefined, startingNodes: IReactFlowNode[], overrideConfig?: ICommonObject) {
|
||||||
this.activeChatflows[chatflowid] = {
|
this.activeChatflows[chatflowid] = {
|
||||||
startingNodes,
|
startingNodes,
|
||||||
endingNodeData,
|
endingNodeData,
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export interface IncomingInput {
|
|||||||
export interface IActiveChatflows {
|
export interface IActiveChatflows {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
startingNodes: IReactFlowNode[]
|
startingNodes: IReactFlowNode[]
|
||||||
endingNodeData: INodeData
|
endingNodeData?: INodeData
|
||||||
inSync: boolean
|
inSync: boolean
|
||||||
overrideConfig?: ICommonObject
|
overrideConfig?: ICommonObject
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ import { CachePool } from './CachePool'
|
|||||||
import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components'
|
import { ICommonObject, IMessage, INodeOptionsValue } from 'flowise-components'
|
||||||
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
|
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
|
||||||
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey'
|
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey'
|
||||||
|
import { sanitizeMiddleware } from './utils/XSS'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Client } from 'langchainhub'
|
||||||
|
import { parsePrompt } from './utils/hub'
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
app: express.Application
|
app: express.Application
|
||||||
@@ -115,9 +119,15 @@ export class App {
|
|||||||
// Allow access from *
|
// Allow access from *
|
||||||
this.app.use(cors())
|
this.app.use(cors())
|
||||||
|
|
||||||
|
// Switch off the default 'X-Powered-By: Express' header
|
||||||
|
this.app.disable('x-powered-by')
|
||||||
|
|
||||||
// Add the expressRequestLogger middleware to log all requests
|
// Add the expressRequestLogger middleware to log all requests
|
||||||
this.app.use(expressRequestLogger)
|
this.app.use(expressRequestLogger)
|
||||||
|
|
||||||
|
// Add the sanitizeMiddleware to guard against XSS
|
||||||
|
this.app.use(sanitizeMiddleware)
|
||||||
|
|
||||||
if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) {
|
if (process.env.FLOWISE_USERNAME && process.env.FLOWISE_PASSWORD) {
|
||||||
const username = process.env.FLOWISE_USERNAME
|
const username = process.env.FLOWISE_USERNAME
|
||||||
const password = process.env.FLOWISE_PASSWORD
|
const password = process.env.FLOWISE_PASSWORD
|
||||||
@@ -128,6 +138,7 @@ export class App {
|
|||||||
'/api/v1/verify/apikey/',
|
'/api/v1/verify/apikey/',
|
||||||
'/api/v1/chatflows/apikey/',
|
'/api/v1/chatflows/apikey/',
|
||||||
'/api/v1/public-chatflows',
|
'/api/v1/public-chatflows',
|
||||||
|
'/api/v1/public-chatbotConfig',
|
||||||
'/api/v1/prediction/',
|
'/api/v1/prediction/',
|
||||||
'/api/v1/vector/upsert/',
|
'/api/v1/vector/upsert/',
|
||||||
'/api/v1/node-icon/',
|
'/api/v1/node-icon/',
|
||||||
@@ -190,7 +201,7 @@ export class App {
|
|||||||
|
|
||||||
// Get component credential via name
|
// Get component credential via name
|
||||||
this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => {
|
this.app.get('/api/v1/components-credentials/:name', (req: Request, res: Response) => {
|
||||||
if (!req.params.name.includes('&')) {
|
if (!req.params.name.includes('&')) {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) {
|
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, req.params.name)) {
|
||||||
return res.json(this.nodesPool.componentCredentials[req.params.name])
|
return res.json(this.nodesPool.componentCredentials[req.params.name])
|
||||||
} else {
|
} else {
|
||||||
@@ -198,7 +209,7 @@ export class App {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const returnResponse = []
|
const returnResponse = []
|
||||||
for (const name of req.params.name.split('&')) {
|
for (const name of req.params.name.split('&')) {
|
||||||
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) {
|
if (Object.prototype.hasOwnProperty.call(this.nodesPool.componentCredentials, name)) {
|
||||||
returnResponse.push(this.nodesPool.componentCredentials[name])
|
returnResponse.push(this.nodesPool.componentCredentials[name])
|
||||||
} else {
|
} else {
|
||||||
@@ -318,6 +329,23 @@ export class App {
|
|||||||
return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
return res.status(404).send(`Chatflow ${req.params.id} not found`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get specific chatflow chatbotConfig via id (PUBLIC endpoint, used to retrieve config for embedded chat)
|
||||||
|
// Safe as public endpoint as chatbotConfig doesn't contain sensitive credential
|
||||||
|
this.app.get('/api/v1/public-chatbotConfig/:id', async (req: Request, res: Response) => {
|
||||||
|
const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
if (chatflow && chatflow.chatbotConfig) {
|
||||||
|
try {
|
||||||
|
const parsedConfig = JSON.parse(chatflow.chatbotConfig)
|
||||||
|
return res.json(parsedConfig)
|
||||||
|
} catch (e) {
|
||||||
|
return res.status(500).send(`Error parsing Chatbot Config for Chatflow ${req.params.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.status(404).send(`Chatbot Config for Chatflow ${req.params.id} not found`)
|
||||||
|
})
|
||||||
|
|
||||||
// Save chatflow
|
// Save chatflow
|
||||||
this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => {
|
this.app.post('/api/v1/chatflows', async (req: Request, res: Response) => {
|
||||||
const body = req.body
|
const body = req.body
|
||||||
@@ -980,6 +1008,12 @@ export class App {
|
|||||||
// Download file from assistant
|
// Download file from assistant
|
||||||
this.app.post('/api/v1/openai-assistants-file', async (req: Request, res: Response) => {
|
this.app.post('/api/v1/openai-assistants-file', async (req: Request, res: Response) => {
|
||||||
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', req.body.fileName)
|
const filePath = path.join(getUserHome(), '.flowise', 'openai-assistant', req.body.fileName)
|
||||||
|
//raise error if file path is not absolute
|
||||||
|
if (!path.isAbsolute(filePath)) return res.status(500).send(`Invalid file path`)
|
||||||
|
//raise error if file path contains '..'
|
||||||
|
if (filePath.includes('..')) return res.status(500).send(`Invalid file path`)
|
||||||
|
//only return from the .flowise openai-assistant folder
|
||||||
|
if (!(filePath.includes('.flowise') && filePath.includes('openai-assistant'))) return res.status(500).send(`Invalid file path`)
|
||||||
res.setHeader('Content-Disposition', 'attachment; filename=' + path.basename(filePath))
|
res.setHeader('Content-Disposition', 'attachment; filename=' + path.basename(filePath))
|
||||||
streamFileToUser(res, filePath)
|
streamFileToUser(res, filePath)
|
||||||
})
|
})
|
||||||
@@ -1064,6 +1098,35 @@ export class App {
|
|||||||
await this.buildChatflow(req, res, undefined, true, true)
|
await this.buildChatflow(req, res, undefined, true, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Prompt from Hub
|
||||||
|
// ----------------------------------------
|
||||||
|
this.app.post('/api/v1/load-prompt', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let hub = new Client()
|
||||||
|
const prompt = await hub.pull(req.body.promptName)
|
||||||
|
const templates = parsePrompt(prompt)
|
||||||
|
return res.json({ status: 'OK', prompt: req.body.promptName, templates: templates })
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.json({ status: 'ERROR', prompt: req.body.promptName, error: e?.message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.app.post('/api/v1/prompts-list', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const tags = req.body.tags ? `tags=${req.body.tags}` : ''
|
||||||
|
// Default to 100, TODO: add pagination and use offset & limit
|
||||||
|
const url = `https://api.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false`
|
||||||
|
axios.get(url).then((response) => {
|
||||||
|
if (response.data.repos) {
|
||||||
|
return res.json({ status: 'OK', repos: response.data.repos })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
return res.json({ status: 'ERROR', repos: [] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Prediction
|
// Prediction
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@@ -1419,16 +1482,19 @@ export class App {
|
|||||||
const nodes = parsedFlowData.nodes
|
const nodes = parsedFlowData.nodes
|
||||||
const edges = parsedFlowData.edges
|
const edges = parsedFlowData.edges
|
||||||
|
|
||||||
/* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation) when all these conditions met:
|
/* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met:
|
||||||
* - Node Data already exists in pool
|
* - Node Data already exists in pool
|
||||||
* - Still in sync (i.e the flow has not been modified since)
|
* - Still in sync (i.e the flow has not been modified since)
|
||||||
* - Existing overrideConfig and new overrideConfig are the same
|
* - Existing overrideConfig and new overrideConfig are the same
|
||||||
* - Flow doesn't start with/contain nodes that depend on incomingInput.question
|
* - Flow doesn't start with/contain nodes that depend on incomingInput.question
|
||||||
|
* - Its not an Upsert request
|
||||||
|
* TODO: convert overrideConfig to hash when we no longer store base64 string but filepath
|
||||||
***/
|
***/
|
||||||
const isFlowReusable = () => {
|
const isFlowReusable = () => {
|
||||||
return (
|
return (
|
||||||
Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) &&
|
Object.prototype.hasOwnProperty.call(this.chatflowPool.activeChatflows, chatflowid) &&
|
||||||
this.chatflowPool.activeChatflows[chatflowid].inSync &&
|
this.chatflowPool.activeChatflows[chatflowid].inSync &&
|
||||||
|
this.chatflowPool.activeChatflows[chatflowid].endingNodeData &&
|
||||||
isSameOverrideConfig(
|
isSameOverrideConfig(
|
||||||
isInternal,
|
isInternal,
|
||||||
this.chatflowPool.activeChatflows[chatflowid].overrideConfig,
|
this.chatflowPool.activeChatflows[chatflowid].overrideConfig,
|
||||||
@@ -1440,7 +1506,7 @@ export class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isFlowReusable()) {
|
if (isFlowReusable()) {
|
||||||
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData
|
nodeToExecuteData = this.chatflowPool.activeChatflows[chatflowid].endingNodeData as INodeData
|
||||||
isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData)
|
isStreamValid = isFlowValidForStream(nodes, nodeToExecuteData)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})`
|
`[server]: Reuse existing chatflow ${chatflowid} with ending node ${nodeToExecuteData.label} (${nodeToExecuteData.id})`
|
||||||
@@ -1493,6 +1559,7 @@ export class App {
|
|||||||
const constructedObj = constructGraphs(nodes, edges, true)
|
const constructedObj = constructGraphs(nodes, edges, true)
|
||||||
const nonDirectedGraph = constructedObj.graph
|
const nonDirectedGraph = constructedObj.graph
|
||||||
const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId)
|
const { startingNodeIds, depthQueue } = getStartingNodes(nonDirectedGraph, endingNodeId)
|
||||||
|
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
||||||
|
|
||||||
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
|
logger.debug(`[server]: Start building chatflow ${chatflowid}`)
|
||||||
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
/*** BFS to traverse from Starting Nodes to Ending Node ***/
|
||||||
@@ -1512,13 +1579,18 @@ export class App {
|
|||||||
isUpsert,
|
isUpsert,
|
||||||
incomingInput.stopNodeId
|
incomingInput.stopNodeId
|
||||||
)
|
)
|
||||||
if (isUpsert) return res.status(201).send('Successfully Upserted')
|
if (isUpsert) {
|
||||||
|
this.chatflowPool.add(chatflowid, undefined, startingNodes, incomingInput?.overrideConfig)
|
||||||
|
return res.status(201).send('Successfully Upserted')
|
||||||
|
}
|
||||||
|
|
||||||
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
|
const nodeToExecute = reactFlowNodes.find((node: IReactFlowNode) => node.id === endingNodeId)
|
||||||
if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`)
|
if (!nodeToExecute) return res.status(404).send(`Node ${endingNodeId} not found`)
|
||||||
|
|
||||||
if (incomingInput.overrideConfig)
|
if (incomingInput.overrideConfig) {
|
||||||
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig)
|
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig)
|
||||||
|
}
|
||||||
|
|
||||||
const reactFlowNodeData: INodeData = resolveVariables(
|
const reactFlowNodeData: INodeData = resolveVariables(
|
||||||
nodeToExecute.data,
|
nodeToExecute.data,
|
||||||
reactFlowNodes,
|
reactFlowNodes,
|
||||||
@@ -1527,7 +1599,6 @@ export class App {
|
|||||||
)
|
)
|
||||||
nodeToExecuteData = reactFlowNodeData
|
nodeToExecuteData = reactFlowNodeData
|
||||||
|
|
||||||
const startingNodes = nodes.filter((nd) => startingNodeIds.includes(nd.id))
|
|
||||||
this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
|
this.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1551,6 +1622,7 @@ export class App {
|
|||||||
let result = isStreamValid
|
let result = isStreamValid
|
||||||
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
? await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
||||||
uploads: incomingInput.uploads,
|
uploads: incomingInput.uploads,
|
||||||
|
chatflowid,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
socketIO,
|
socketIO,
|
||||||
socketIOClientId: incomingInput.socketIOClientId,
|
socketIOClientId: incomingInput.socketIOClientId,
|
||||||
@@ -1561,6 +1633,7 @@ export class App {
|
|||||||
chatId
|
chatId
|
||||||
})
|
})
|
||||||
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
: await nodeInstance.run(nodeToExecuteData, incomingInput.question, {
|
||||||
|
chatflowid,
|
||||||
uploads: incomingInput.uploads,
|
uploads: incomingInput.uploads,
|
||||||
chatHistory,
|
chatHistory,
|
||||||
logger,
|
logger,
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express'
|
||||||
|
import sanitizeHtml from 'sanitize-html'
|
||||||
|
|
||||||
|
export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
// decoding is necessary as the url is encoded by the browser
|
||||||
|
const decodedURI = decodeURI(req.url)
|
||||||
|
req.url = sanitizeHtml(decodedURI)
|
||||||
|
for (let p in req.query) {
|
||||||
|
if (Array.isArray(req.query[p])) {
|
||||||
|
const sanitizedQ = []
|
||||||
|
for (const q of req.query[p] as string[]) {
|
||||||
|
sanitizedQ.push(sanitizeHtml(q))
|
||||||
|
}
|
||||||
|
req.query[p] = sanitizedQ
|
||||||
|
} else {
|
||||||
|
req.query[p] = sanitizeHtml(req.query[p] as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export function parsePrompt(prompt: string): any[] {
|
||||||
|
const promptObj = JSON.parse(prompt)
|
||||||
|
let response = []
|
||||||
|
if (promptObj.kwargs.messages) {
|
||||||
|
promptObj.kwargs.messages.forEach((message: any) => {
|
||||||
|
let messageType = message.id.includes('SystemMessagePromptTemplate')
|
||||||
|
? 'systemMessagePrompt'
|
||||||
|
: message.id.includes('HumanMessagePromptTemplate')
|
||||||
|
? 'humanMessagePrompt'
|
||||||
|
: message.id.includes('AIMessagePromptTemplate')
|
||||||
|
? 'aiMessagePrompt'
|
||||||
|
: 'template'
|
||||||
|
let messageTypeDisplay = message.id.includes('SystemMessagePromptTemplate')
|
||||||
|
? 'System Message'
|
||||||
|
: message.id.includes('HumanMessagePromptTemplate')
|
||||||
|
? 'Human Message'
|
||||||
|
: message.id.includes('AIMessagePromptTemplate')
|
||||||
|
? 'AI Message'
|
||||||
|
: 'Message'
|
||||||
|
let template = message.kwargs.prompt.kwargs.template
|
||||||
|
response.push({
|
||||||
|
type: messageType,
|
||||||
|
typeDisplay: messageTypeDisplay,
|
||||||
|
template: template
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (promptObj.kwargs.template) {
|
||||||
|
let template = promptObj.kwargs.template
|
||||||
|
response.push({
|
||||||
|
type: 'template',
|
||||||
|
typeDisplay: 'Prompt',
|
||||||
|
template: template
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flowise-ui",
|
"name": "flowise-ui",
|
||||||
"version": "1.4.1",
|
"version": "1.4.3",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://flowiseai.com",
|
"homepage": "https://flowiseai.com",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import client from './client'
|
||||||
|
|
||||||
|
const getAvailablePrompts = (body) => client.post(`/prompts-list`, body)
|
||||||
|
const getPrompt = (body) => client.post(`/load-prompt`, body)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getAvailablePrompts,
|
||||||
|
getPrompt
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 10 KiB |
@@ -1,8 +1,8 @@
|
|||||||
// assets
|
// assets
|
||||||
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage } from '@tabler/icons'
|
import { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff } from '@tabler/icons'
|
||||||
|
|
||||||
// constant
|
// constant
|
||||||
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage }
|
const icons = { IconTrash, IconFileUpload, IconFileExport, IconCopy, IconSearch, IconMessage, IconPictureInPictureOff }
|
||||||
|
|
||||||
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
// ==============================|| SETTINGS MENU ITEMS ||============================== //
|
||||||
|
|
||||||
@@ -11,6 +11,13 @@ const settings = {
|
|||||||
title: '',
|
title: '',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: 'conversationStarters',
|
||||||
|
title: 'Starter Prompts',
|
||||||
|
type: 'item',
|
||||||
|
url: '',
|
||||||
|
icon: icons.IconPictureInPictureOff
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'viewMessages',
|
id: 'viewMessages',
|
||||||
title: 'View Messages',
|
title: 'View Messages',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import FileCopyIcon from '@mui/icons-material/FileCopy'
|
|||||||
import FileDownloadIcon from '@mui/icons-material/Downloading'
|
import FileDownloadIcon from '@mui/icons-material/Downloading'
|
||||||
import FileDeleteIcon from '@mui/icons-material/Delete'
|
import FileDeleteIcon from '@mui/icons-material/Delete'
|
||||||
import FileCategoryIcon from '@mui/icons-material/Category'
|
import FileCategoryIcon from '@mui/icons-material/Category'
|
||||||
|
import PictureInPictureAltIcon from '@mui/icons-material/PictureInPictureAlt'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
|
||||||
import { IconX } from '@tabler/icons'
|
import { IconX } from '@tabler/icons'
|
||||||
@@ -27,6 +28,7 @@ import TagDialog from '../dialog/TagDialog'
|
|||||||
|
|
||||||
import { generateExportFlowData } from '../../utils/genericHelper'
|
import { generateExportFlowData } from '../../utils/genericHelper'
|
||||||
import useNotifier from '../../utils/useNotifier'
|
import useNotifier from '../../utils/useNotifier'
|
||||||
|
import StarterPromptsDialog from '../dialog/StarterPromptsDialog'
|
||||||
|
|
||||||
const StyledMenu = styled((props) => (
|
const StyledMenu = styled((props) => (
|
||||||
<Menu
|
<Menu
|
||||||
@@ -78,6 +80,8 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
const [categoryDialogProps, setCategoryDialogProps] = useState({})
|
const [categoryDialogProps, setCategoryDialogProps] = useState({})
|
||||||
const [anchorEl, setAnchorEl] = useState(null)
|
const [anchorEl, setAnchorEl] = useState(null)
|
||||||
const open = Boolean(anchorEl)
|
const open = Boolean(anchorEl)
|
||||||
|
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
||||||
|
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
setAnchorEl(event.currentTarget)
|
setAnchorEl(event.currentTarget)
|
||||||
@@ -92,6 +96,20 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
setFlowDialogOpen(true)
|
setFlowDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFlowStarterPrompts = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
setConversationStartersDialogProps({
|
||||||
|
title: 'Starter Prompts - ' + chatflow.name,
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setConversationStartersDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveFlowStarterPrompts = async () => {
|
||||||
|
setConversationStartersDialogOpen(false)
|
||||||
|
await updateFlowsApi.request()
|
||||||
|
}
|
||||||
|
|
||||||
const saveFlowRename = async (chatflowName) => {
|
const saveFlowRename = async (chatflowName) => {
|
||||||
const updateBody = {
|
const updateBody = {
|
||||||
name: chatflowName,
|
name: chatflowName,
|
||||||
@@ -253,6 +271,10 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
Export
|
Export
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Divider sx={{ my: 0.5 }} />
|
<Divider sx={{ my: 0.5 }} />
|
||||||
|
<MenuItem onClick={handleFlowStarterPrompts} disableRipple>
|
||||||
|
<PictureInPictureAltIcon />
|
||||||
|
Starter Prompts
|
||||||
|
</MenuItem>
|
||||||
<MenuItem onClick={handleFlowCategory} disableRipple>
|
<MenuItem onClick={handleFlowCategory} disableRipple>
|
||||||
<FileCategoryIcon />
|
<FileCategoryIcon />
|
||||||
Update Category
|
Update Category
|
||||||
@@ -279,6 +301,12 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
onClose={() => setCategoryDialogOpen(false)}
|
onClose={() => setCategoryDialogOpen(false)}
|
||||||
onSubmit={saveFlowCategory}
|
onSubmit={saveFlowCategory}
|
||||||
/>
|
/>
|
||||||
|
<StarterPromptsDialog
|
||||||
|
show={conversationStartersDialogOpen}
|
||||||
|
dialogProps={conversationStartersDialogProps}
|
||||||
|
onConfirm={saveFlowStarterPrompts}
|
||||||
|
onCancel={() => setConversationStartersDialogOpen(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.button-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch; /* For momentum scroll on mobile devices */
|
||||||
|
scrollbar-width: none; /* For Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 0 0 auto; /* Don't grow, don't shrink, base width on content */
|
||||||
|
margin: 5px; /* Adjust as needed for spacing between buttons */
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Chip } from '@mui/material'
|
||||||
|
import './StarterPromptsCard.css'
|
||||||
|
|
||||||
|
const StarterPromptsCard = ({ isGrid, starterPrompts, onPromptClick }) => {
|
||||||
|
return (
|
||||||
|
<Box className={'button-container'} sx={{ maxWidth: isGrid ? 'inherit' : '400px', m: 1 }}>
|
||||||
|
{starterPrompts.map((sp, index) => (
|
||||||
|
<Chip label={sp.prompt} className={'button'} key={index} onClick={(e) => onPromptClick(sp.prompt, e)} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
StarterPromptsCard.propTypes = {
|
||||||
|
isGrid: PropTypes.bool,
|
||||||
|
starterPrompts: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
onPromptClick: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StarterPromptsCard
|
||||||
@@ -0,0 +1,587 @@
|
|||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import rehypeMathjax from 'rehype-mathjax'
|
||||||
|
import rehypeRaw from 'rehype-raw'
|
||||||
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import remarkMath from 'remark-math'
|
||||||
|
|
||||||
|
// MUI
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Chip,
|
||||||
|
Grid,
|
||||||
|
InputLabel,
|
||||||
|
List,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
OutlinedInput,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
Stack,
|
||||||
|
IconButton,
|
||||||
|
FormControl,
|
||||||
|
Checkbox,
|
||||||
|
MenuItem
|
||||||
|
} from '@mui/material'
|
||||||
|
import MuiAccordion from '@mui/material/Accordion'
|
||||||
|
import MuiAccordionSummary from '@mui/material/AccordionSummary'
|
||||||
|
import MuiAccordionDetails from '@mui/material/AccordionDetails'
|
||||||
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||||
|
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'
|
||||||
|
import ClearIcon from '@mui/icons-material/Clear'
|
||||||
|
import { styled } from '@mui/material/styles'
|
||||||
|
|
||||||
|
//Project Import
|
||||||
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||||
|
import { MemoizedReactMarkdown } from 'ui-component/markdown/MemoizedReactMarkdown'
|
||||||
|
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||||
|
import promptEmptySVG from 'assets/images/prompt_empty.svg'
|
||||||
|
|
||||||
|
import useApi from 'hooks/useApi'
|
||||||
|
import promptApi from 'api/prompt'
|
||||||
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||||
|
|
||||||
|
const NewLineToBr = ({ children = '' }) => {
|
||||||
|
return children.split('\n').reduce(function (arr, line) {
|
||||||
|
return arr.concat(line, <br />)
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const Accordion = styled((props) => <MuiAccordion disableGutters elevation={0} square {...props} />)(({ theme }) => ({
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
'&:not(:last-child)': {
|
||||||
|
borderBottom: 0
|
||||||
|
},
|
||||||
|
'&:before': {
|
||||||
|
display: 'none'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const AccordionSummary = styled((props) => (
|
||||||
|
<MuiAccordionSummary expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />} {...props} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, .05)' : 'rgba(0, 0, 0, .03)',
|
||||||
|
flexDirection: 'row-reverse',
|
||||||
|
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
|
||||||
|
transform: 'rotate(180deg)'
|
||||||
|
},
|
||||||
|
'& .MuiAccordionSummary-content': {
|
||||||
|
marginLeft: theme.spacing(1)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
borderTop: '1px solid rgba(0, 0, 0, .125)'
|
||||||
|
}))
|
||||||
|
|
||||||
|
const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const customization = useSelector((state) => state.customization)
|
||||||
|
const getAvailablePromptsApi = useApi(promptApi.getAvailablePrompts)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [show, dispatch])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (promptType) {
|
||||||
|
getAvailablePromptsApi.request({ tags: promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&' })
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [promptType])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getAvailablePromptsApi.data && getAvailablePromptsApi.data.repos) {
|
||||||
|
setAvailablePrompNameList(getAvailablePromptsApi.data.repos)
|
||||||
|
if (getAvailablePromptsApi.data.repos?.length) handleListItemClick(0, getAvailablePromptsApi.data.repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getAvailablePromptsApi.data])
|
||||||
|
|
||||||
|
const ITEM_HEIGHT = 48
|
||||||
|
const ITEM_PADDING_TOP = 8
|
||||||
|
const MenuProps = {
|
||||||
|
PaperProps: {
|
||||||
|
style: {
|
||||||
|
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||||
|
width: 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = [
|
||||||
|
{ id: 101, name: 'anthropic:claude-instant-1' },
|
||||||
|
{ id: 102, name: 'anthropic:claude-instant-1.2' },
|
||||||
|
{ id: 103, name: 'anthropic:claude-2' },
|
||||||
|
{ id: 104, name: 'google:palm-2-chat-bison' },
|
||||||
|
{ id: 105, name: 'google:palm-2-codechat-bison' },
|
||||||
|
{ id: 106, name: 'google:palm-2-text-bison' },
|
||||||
|
{ id: 107, name: 'meta:llama-2-13b-chat' },
|
||||||
|
{ id: 108, name: 'meta:llama-2-70b-chat' },
|
||||||
|
{ id: 109, name: 'openai:gpt-3.5-turbo' },
|
||||||
|
{ id: 110, name: 'openai:gpt-4' },
|
||||||
|
{ id: 111, name: 'openai:text-davinci-003' }
|
||||||
|
]
|
||||||
|
const [modelName, setModelName] = useState([])
|
||||||
|
|
||||||
|
const usecases = [
|
||||||
|
{ id: 201, name: 'Agents' },
|
||||||
|
{ id: 202, name: 'Agent Stimulation' },
|
||||||
|
{ id: 203, name: 'Autonomous agents' },
|
||||||
|
{ id: 204, name: 'Classification' },
|
||||||
|
{ id: 205, name: 'Chatbots' },
|
||||||
|
{ id: 206, name: 'Code understanding' },
|
||||||
|
{ id: 207, name: 'Code writing' },
|
||||||
|
{ id: 208, name: 'Evaluation' },
|
||||||
|
{ id: 209, name: 'Extraction' },
|
||||||
|
{ id: 210, name: 'Interacting with APIs' },
|
||||||
|
{ id: 211, name: 'Multi-modal' },
|
||||||
|
{ id: 212, name: 'QA over documents' },
|
||||||
|
{ id: 213, name: 'Self-checking' },
|
||||||
|
{ id: 214, name: 'SQL' },
|
||||||
|
{ id: 215, name: 'Summarization' },
|
||||||
|
{ id: 216, name: 'Tagging' }
|
||||||
|
]
|
||||||
|
const [usecase, setUsecase] = useState([])
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{ id: 301, name: 'Chinese' },
|
||||||
|
{ id: 302, name: 'English' },
|
||||||
|
{ id: 303, name: 'French' },
|
||||||
|
{ id: 304, name: 'German' },
|
||||||
|
{ id: 305, name: 'Russian' },
|
||||||
|
{ id: 306, name: 'Spanish' }
|
||||||
|
]
|
||||||
|
const [language, setLanguage] = useState([])
|
||||||
|
const [availablePrompNameList, setAvailablePrompNameList] = useState([])
|
||||||
|
const [selectedPrompt, setSelectedPrompt] = useState({})
|
||||||
|
|
||||||
|
const [accordionExpanded, setAccordionExpanded] = useState(['prompt'])
|
||||||
|
|
||||||
|
const handleAccordionChange = (accordionName) => (event, isExpanded) => {
|
||||||
|
const accordians = [...accordionExpanded]
|
||||||
|
if (!isExpanded) setAccordionExpanded(accordians.filter((accr) => accr !== accordionName))
|
||||||
|
else {
|
||||||
|
accordians.push(accordionName)
|
||||||
|
setAccordionExpanded(accordians)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleListItemClick = async (index, overridePromptNameList = []) => {
|
||||||
|
const prompt = overridePromptNameList.length ? overridePromptNameList[index] : availablePrompNameList[index]
|
||||||
|
|
||||||
|
if (!prompt.detailed) {
|
||||||
|
const createResp = await promptApi.getPrompt({
|
||||||
|
promptName: prompt.full_name
|
||||||
|
})
|
||||||
|
if (createResp.data) {
|
||||||
|
prompt.detailed = createResp.data.templates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSelectedPrompt(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPrompts = async () => {
|
||||||
|
let tags = promptType === 'template' ? 'StringPromptTemplate&' : 'ChatPromptTemplate&'
|
||||||
|
modelName.forEach((item) => {
|
||||||
|
tags += `tags=${item.name}&`
|
||||||
|
})
|
||||||
|
usecase.forEach((item) => {
|
||||||
|
tags += `tags=${item.name}&`
|
||||||
|
})
|
||||||
|
language.forEach((item) => {
|
||||||
|
tags += `tags=${item.name}&`
|
||||||
|
})
|
||||||
|
getAvailablePromptsApi.request({ tags: tags })
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeDuplicates = (value) => {
|
||||||
|
let duplicateRemoved = []
|
||||||
|
|
||||||
|
value.forEach((item) => {
|
||||||
|
if (value.filter((o) => o.id === item.id).length === 1) {
|
||||||
|
duplicateRemoved.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return duplicateRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModelChange = (event) => {
|
||||||
|
const {
|
||||||
|
target: { value }
|
||||||
|
} = event
|
||||||
|
|
||||||
|
setModelName(removeDuplicates(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUsecaseChange = (event) => {
|
||||||
|
const {
|
||||||
|
target: { value }
|
||||||
|
} = event
|
||||||
|
|
||||||
|
setUsecase(removeDuplicates(value))
|
||||||
|
}
|
||||||
|
const handleLanguageChange = (event) => {
|
||||||
|
const {
|
||||||
|
target: { value }
|
||||||
|
} = event
|
||||||
|
|
||||||
|
setLanguage(removeDuplicates(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={show}
|
||||||
|
fullWidth
|
||||||
|
maxWidth={'lg'}
|
||||||
|
aria-labelledby='prompt-dialog-title'
|
||||||
|
aria-describedby='prompt-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='prompt-dialog-title'>
|
||||||
|
Langchain Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'})
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers sx={{ p: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'row', p: 2, pt: 1, alignItems: 'center' }}>
|
||||||
|
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||||
|
<InputLabel size='small' id='model-checkbox-label'>
|
||||||
|
Model
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
id='model-checkbox'
|
||||||
|
labelId='model-checkbox-label'
|
||||||
|
multiple
|
||||||
|
size='small'
|
||||||
|
value={modelName}
|
||||||
|
onChange={handleModelChange}
|
||||||
|
input={<OutlinedInput label='Model' />}
|
||||||
|
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||||
|
endAdornment={
|
||||||
|
modelName.length ? (
|
||||||
|
<IconButton sx={{ mr: 2 }} onClick={() => setModelName([])}>
|
||||||
|
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
'.MuiSvgIcon-root ': {
|
||||||
|
fill: customization.isDarkMode ? 'white !important' : ''
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{models.map((variant) => (
|
||||||
|
<MenuItem key={variant.id} value={variant}>
|
||||||
|
<Checkbox id={variant.id} checked={modelName.findIndex((item) => item.id === variant.id) >= 0} />
|
||||||
|
<ListItemText primary={variant.name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||||
|
<InputLabel size='small' id='usecase-checkbox-label'>
|
||||||
|
Usecase
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
autoWidth={false}
|
||||||
|
labelId='usecase-checkbox-label'
|
||||||
|
id='usecase-checkbox'
|
||||||
|
multiple
|
||||||
|
size='small'
|
||||||
|
value={usecase}
|
||||||
|
onChange={handleUsecaseChange}
|
||||||
|
input={<OutlinedInput label='Usecase' />}
|
||||||
|
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||||
|
endAdornment={
|
||||||
|
usecase.length ? (
|
||||||
|
<IconButton sx={{ mr: 2 }} onClick={() => setUsecase([])}>
|
||||||
|
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
'.MuiSvgIcon-root ': {
|
||||||
|
fill: customization.isDarkMode ? 'white !important' : ''
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{usecases.map((variant) => (
|
||||||
|
<MenuItem key={variant.id} value={variant}>
|
||||||
|
<Checkbox id={variant.id} checked={usecase.findIndex((item) => item.id === variant.id) >= 0} />
|
||||||
|
<ListItemText primary={variant.name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl sx={{ mr: 1, width: '30%' }}>
|
||||||
|
<InputLabel size='small' id='language-checkbox-label'>
|
||||||
|
Language
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId='language-checkbox-label'
|
||||||
|
id='language-checkbox'
|
||||||
|
multiple
|
||||||
|
size='small'
|
||||||
|
value={language}
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
input={<OutlinedInput label='language' />}
|
||||||
|
renderValue={(selected) => selected.map((x) => x.name).join(', ')}
|
||||||
|
endAdornment={
|
||||||
|
language.length ? (
|
||||||
|
<IconButton sx={{ mr: 2 }} onClick={() => setLanguage([])}>
|
||||||
|
<ClearIcon style={{ width: 20, height: 20 }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
'.MuiSvgIcon-root ': {
|
||||||
|
fill: customization.isDarkMode ? 'white !important' : ''
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{languages.map((variant) => (
|
||||||
|
<MenuItem key={variant.id} value={variant}>
|
||||||
|
<Checkbox id={variant.id} checked={language.findIndex((item) => item.id === variant.id) >= 0} />
|
||||||
|
<ListItemText primary={variant.name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl sx={{ width: '10%' }}>
|
||||||
|
<Button disableElevation variant='outlined' onClick={fetchPrompts}>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{availablePrompNameList && availablePrompNameList.length == 0 && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%', pb: 3 }} flexDirection='column'>
|
||||||
|
<Box sx={{ p: 5, height: 'auto' }}>
|
||||||
|
<img style={{ objectFit: 'cover', height: '20vh', width: 'auto' }} src={promptEmptySVG} alt='promptEmptySVG' />
|
||||||
|
</Box>
|
||||||
|
<div>No Available Prompts</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{availablePrompNameList && availablePrompNameList.length > 0 && (
|
||||||
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center', width: '100%' }} flexDirection='column'>
|
||||||
|
<Box sx={{ width: '100%', p: 2 }}>
|
||||||
|
<Grid xs={12} container spacing={1} justifyContent='center' alignItems='center'>
|
||||||
|
<Grid xs={4} item sx={{ textAlign: 'left' }}>
|
||||||
|
<Box sx={{ width: '100%', maxWidth: 360 }}>
|
||||||
|
<Card variant='outlined' sx={{ height: 470, overflow: 'auto', borderRadius: 0 }}>
|
||||||
|
<CardContent sx={{ p: 1 }}>
|
||||||
|
<Typography sx={{ fontSize: 10 }} color='text.secondary' gutterBottom>
|
||||||
|
Available Prompts
|
||||||
|
</Typography>
|
||||||
|
<List component='nav' aria-label='secondary mailbox folder'>
|
||||||
|
{availablePrompNameList.map((item, index) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={item.id}
|
||||||
|
selected={item.id === selectedPrompt?.id}
|
||||||
|
onClick={() => handleListItemClick(index)}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Typography sx={{ fontSize: 16, p: 1, fontWeight: 500 }}>
|
||||||
|
{item.full_name}
|
||||||
|
</Typography>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
marginTop: 5
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.tags.map((tag, index) => (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
label={tag}
|
||||||
|
style={{ marginRight: 5, marginBottom: 5 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ListItemButton>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid xs={8} item sx={{ textAlign: 'left' }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Card sx={{ height: 470, overflow: 'auto' }}>
|
||||||
|
<CardContent sx={{ p: 0.5 }}>
|
||||||
|
<Accordion
|
||||||
|
expanded={accordionExpanded.includes('prompt')}
|
||||||
|
onChange={handleAccordionChange('prompt')}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
aria-controls='panel2d-content'
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
id='panel2d-header'
|
||||||
|
>
|
||||||
|
<Typography>Prompt</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography sx={{ wordWrap: 'true' }} color='text.primary'>
|
||||||
|
{selectedPrompt?.detailed?.map((item) => (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ fontSize: 12 }} color='text.secondary' gutterBottom>
|
||||||
|
{item.typeDisplay.toUpperCase()}
|
||||||
|
</Typography>
|
||||||
|
<Typography>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
whiteSpace: 'pre-wrap -moz-pre-wrap -pre-wrap -o-pre-wrap',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
wordSpacing: '0.1rem',
|
||||||
|
lineHeight: '1.5rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NewLineToBr>{item.template}</NewLineToBr>
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
<Accordion
|
||||||
|
expanded={accordionExpanded.includes('description')}
|
||||||
|
onChange={handleAccordionChange('description')}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
aria-controls='panel1d-content'
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
id='panel1d-header'
|
||||||
|
>
|
||||||
|
<Typography>Description</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography
|
||||||
|
sx={{ wordWrap: 'true', wordSpacing: '0.1rem', lineHeight: '1.5rem' }}
|
||||||
|
color='text.primary'
|
||||||
|
>
|
||||||
|
{selectedPrompt?.description}
|
||||||
|
</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
<Accordion
|
||||||
|
expanded={accordionExpanded.includes('readme')}
|
||||||
|
onChange={handleAccordionChange('readme')}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls='panel3d-content'
|
||||||
|
id='panel3d-header'
|
||||||
|
>
|
||||||
|
<Typography>Readme</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
lineHeight: 1.75,
|
||||||
|
'& a': {
|
||||||
|
display: 'block',
|
||||||
|
marginRight: '2.5rem',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
color: '#16bed7',
|
||||||
|
fontWeight: 500
|
||||||
|
},
|
||||||
|
'& a:hover': { opacity: 0.8 },
|
||||||
|
'& code': {
|
||||||
|
color: '#0ab126',
|
||||||
|
fontWeight: 500,
|
||||||
|
whiteSpace: 'pre-wrap !important'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MemoizedReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeMathjax, rehypeRaw]}
|
||||||
|
components={{
|
||||||
|
code({ inline, className, children, ...props }) {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
return !inline ? (
|
||||||
|
<CodeBlock
|
||||||
|
key={Math.random()}
|
||||||
|
isDialog={true}
|
||||||
|
language={(match && match[1]) || ''}
|
||||||
|
value={String(children).replace(/\n$/, '')}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedPrompt?.readme}
|
||||||
|
</MemoizedReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
{availablePrompNameList && availablePrompNameList.length > 0 && (
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel}>Cancel</Button>
|
||||||
|
<StyledButton
|
||||||
|
disabled={!selectedPrompt?.detailed}
|
||||||
|
onClick={() => onSubmit(selectedPrompt.detailed)}
|
||||||
|
variant='contained'
|
||||||
|
>
|
||||||
|
Load
|
||||||
|
</StyledButton>
|
||||||
|
</DialogActions>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
PromptLangsmithHubDialog.propTypes = {
|
||||||
|
promptType: PropTypes.string,
|
||||||
|
show: PropTypes.bool,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PromptLangsmithHubDialog
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { createPortal } from 'react-dom'
|
||||||
|
import { useDispatch } from 'react-redux'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from 'store/actions'
|
||||||
|
|
||||||
|
// material-ui
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
OutlinedInput,
|
||||||
|
DialogTitle,
|
||||||
|
DialogActions,
|
||||||
|
Box,
|
||||||
|
List,
|
||||||
|
InputAdornment
|
||||||
|
} from '@mui/material'
|
||||||
|
import { IconX, IconTrash, IconPlus, IconBulb } from '@tabler/icons'
|
||||||
|
|
||||||
|
// Project import
|
||||||
|
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||||
|
|
||||||
|
// store
|
||||||
|
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||||
|
import useNotifier from 'utils/useNotifier'
|
||||||
|
|
||||||
|
// API
|
||||||
|
import chatflowsApi from 'api/chatflows'
|
||||||
|
|
||||||
|
const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||||
|
const portalElement = document.getElementById('portal')
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
useNotifier()
|
||||||
|
|
||||||
|
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||||
|
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||||
|
|
||||||
|
const [inputFields, setInputFields] = useState([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const [chatbotConfig, setChatbotConfig] = useState({})
|
||||||
|
|
||||||
|
const addInputField = () => {
|
||||||
|
setInputFields([
|
||||||
|
...inputFields,
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const removeInputFields = (index) => {
|
||||||
|
const rows = [...inputFields]
|
||||||
|
rows.splice(index, 1)
|
||||||
|
setInputFields(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (index, evnt) => {
|
||||||
|
const { name, value } = evnt.target
|
||||||
|
const list = [...inputFields]
|
||||||
|
list[index][name] = value
|
||||||
|
setInputFields(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
try {
|
||||||
|
let value = {
|
||||||
|
starterPrompts: {
|
||||||
|
...inputFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chatbotConfig.starterPrompts = value.starterPrompts
|
||||||
|
const saveResp = await chatflowsApi.updateChatflow(dialogProps.chatflow.id, {
|
||||||
|
chatbotConfig: JSON.stringify(chatbotConfig)
|
||||||
|
})
|
||||||
|
if (saveResp.data) {
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: 'Conversation Starter Prompts Saved',
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'success',
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dispatch({ type: SET_CHATFLOW, chatflow: saveResp.data })
|
||||||
|
}
|
||||||
|
onConfirm()
|
||||||
|
} catch (error) {
|
||||||
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
|
enqueueSnackbar({
|
||||||
|
message: `Failed to save Conversation Starter Prompts: ${errorData}`,
|
||||||
|
options: {
|
||||||
|
key: new Date().getTime() + Math.random(),
|
||||||
|
variant: 'error',
|
||||||
|
persist: true,
|
||||||
|
action: (key) => (
|
||||||
|
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogProps.chatflow && dialogProps.chatflow.chatbotConfig) {
|
||||||
|
try {
|
||||||
|
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
|
||||||
|
setChatbotConfig(chatbotConfig || {})
|
||||||
|
if (chatbotConfig.starterPrompts) {
|
||||||
|
let inputFields = []
|
||||||
|
Object.getOwnPropertyNames(chatbotConfig.starterPrompts).forEach((key) => {
|
||||||
|
if (chatbotConfig.starterPrompts[key]) {
|
||||||
|
inputFields.push(chatbotConfig.starterPrompts[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setInputFields(inputFields)
|
||||||
|
} else {
|
||||||
|
setInputFields([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setInputFields([
|
||||||
|
{
|
||||||
|
prompt: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}, [dialogProps])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
|
||||||
|
else dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
|
||||||
|
}, [show, dispatch])
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={show}
|
||||||
|
fullWidth
|
||||||
|
maxWidth='sm'
|
||||||
|
aria-labelledby='alert-dialog-title'
|
||||||
|
aria-describedby='alert-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||||
|
{dialogProps.title || 'Conversation Starter Prompts'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderRadius: 10,
|
||||||
|
background: '#d8f3dc',
|
||||||
|
padding: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconBulb size={30} color='#2d6a4f' />
|
||||||
|
<span style={{ color: '#2d6a4f', marginLeft: 10, fontWeight: 500 }}>
|
||||||
|
Starter prompts will only be shown when there is no messages on the chat
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Box sx={{ '& > :not(style)': { m: 1 }, pt: 2 }}>
|
||||||
|
<List>
|
||||||
|
{inputFields.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} style={{ display: 'flex', width: '100%' }}>
|
||||||
|
<Box sx={{ width: '95%', mb: 1 }}>
|
||||||
|
<OutlinedInput
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
key={index}
|
||||||
|
type='text'
|
||||||
|
onChange={(e) => handleChange(index, e)}
|
||||||
|
size='small'
|
||||||
|
value={data.prompt}
|
||||||
|
name='prompt'
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position='end' sx={{ padding: '2px' }}>
|
||||||
|
{inputFields.length > 1 && (
|
||||||
|
<IconButton
|
||||||
|
sx={{ height: 30, width: 30 }}
|
||||||
|
size='small'
|
||||||
|
color='error'
|
||||||
|
disabled={inputFields.length === 1}
|
||||||
|
onClick={() => removeInputFields(index)}
|
||||||
|
edge='end'
|
||||||
|
>
|
||||||
|
<IconTrash />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ width: '5%', mb: 1 }}>
|
||||||
|
{index === inputFields.length - 1 && (
|
||||||
|
<IconButton color='primary' onClick={addInputField}>
|
||||||
|
<IconPlus />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel}>Cancel</Button>
|
||||||
|
<StyledButton variant='contained' onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</StyledButton>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
) : null
|
||||||
|
|
||||||
|
return createPortal(component, portalElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
StarterPromptsDialog.propTypes = {
|
||||||
|
show: PropTypes.bool,
|
||||||
|
dialogProps: PropTypes.object,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onConfirm: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StarterPromptsDialog
|
||||||
@@ -147,8 +147,8 @@ export const FlowListTable = ({ data, images, filterFunction, updateFlowsApi })
|
|||||||
}
|
}
|
||||||
|
|
||||||
FlowListTable.propTypes = {
|
FlowListTable.propTypes = {
|
||||||
data: PropTypes.object,
|
data: PropTypes.array,
|
||||||
images: PropTypes.array,
|
images: PropTypes.object,
|
||||||
filterFunction: PropTypes.func,
|
filterFunction: PropTypes.func,
|
||||||
updateFlowsApi: PropTypes.object
|
updateFlowsApi: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import SaveChatflowDialog from 'ui-component/dialog/SaveChatflowDialog'
|
|||||||
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
import APICodeDialog from 'views/chatflows/APICodeDialog'
|
||||||
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
import AnalyseFlowDialog from 'ui-component/dialog/AnalyseFlowDialog'
|
||||||
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
import ViewMessagesDialog from 'ui-component/dialog/ViewMessagesDialog'
|
||||||
|
import StarterPromptsDialog from 'ui-component/dialog/StarterPromptsDialog'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import chatflowsApi from 'api/chatflows'
|
import chatflowsApi from 'api/chatflows'
|
||||||
@@ -45,6 +46,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
const [apiDialogProps, setAPIDialogProps] = useState({})
|
const [apiDialogProps, setAPIDialogProps] = useState({})
|
||||||
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
const [analyseDialogOpen, setAnalyseDialogOpen] = useState(false)
|
||||||
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
const [analyseDialogProps, setAnalyseDialogProps] = useState({})
|
||||||
|
const [conversationStartersDialogOpen, setConversationStartersDialogOpen] = useState(false)
|
||||||
|
const [conversationStartersDialogProps, setConversationStartersDialogProps] = useState({})
|
||||||
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
const [viewMessagesDialogOpen, setViewMessagesDialogOpen] = useState(false)
|
||||||
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
const [viewMessagesDialogProps, setViewMessagesDialogProps] = useState({})
|
||||||
|
|
||||||
@@ -56,6 +59,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
|
|
||||||
if (setting === 'deleteChatflow') {
|
if (setting === 'deleteChatflow') {
|
||||||
handleDeleteFlow()
|
handleDeleteFlow()
|
||||||
|
} else if (setting === 'conversationStarters') {
|
||||||
|
setConversationStartersDialogProps({
|
||||||
|
title: 'Starter Prompts - ' + chatflow.name,
|
||||||
|
chatflow: chatflow
|
||||||
|
})
|
||||||
|
setConversationStartersDialogOpen(true)
|
||||||
} else if (setting === 'analyseChatflow') {
|
} else if (setting === 'analyseChatflow') {
|
||||||
setAnalyseDialogProps({
|
setAnalyseDialogProps({
|
||||||
title: 'Analyse Chatflow',
|
title: 'Analyse Chatflow',
|
||||||
@@ -376,6 +385,12 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
|||||||
/>
|
/>
|
||||||
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
<APICodeDialog show={apiDialogOpen} dialogProps={apiDialogProps} onCancel={() => setAPIDialogOpen(false)} />
|
||||||
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
<AnalyseFlowDialog show={analyseDialogOpen} dialogProps={analyseDialogProps} onCancel={() => setAnalyseDialogOpen(false)} />
|
||||||
|
<StarterPromptsDialog
|
||||||
|
show={conversationStartersDialogOpen}
|
||||||
|
dialogProps={conversationStartersDialogProps}
|
||||||
|
onConfirm={() => setConversationStartersDialogOpen(false)}
|
||||||
|
onCancel={() => setConversationStartersDialogOpen(false)}
|
||||||
|
/>
|
||||||
<ViewMessagesDialog
|
<ViewMessagesDialog
|
||||||
show={viewMessagesDialogOpen}
|
show={viewMessagesDialogOpen}
|
||||||
dialogProps={viewMessagesDialogProps}
|
dialogProps={viewMessagesDialogProps}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useSelector } from 'react-redux'
|
|||||||
// material-ui
|
// material-ui
|
||||||
import { useTheme, styled } from '@mui/material/styles'
|
import { useTheme, styled } from '@mui/material/styles'
|
||||||
import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material'
|
import { Box, Typography, Tooltip, IconButton, Button } from '@mui/material'
|
||||||
|
import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh'
|
||||||
import { tooltipClasses } from '@mui/material/Tooltip'
|
import { tooltipClasses } from '@mui/material/Tooltip'
|
||||||
import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons'
|
import { IconArrowsMaximize, IconEdit, IconAlertTriangle } from '@tabler/icons'
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ import { getInputVariables } from 'utils/genericHelper'
|
|||||||
|
|
||||||
// const
|
// const
|
||||||
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
|
import { FLOWISE_CREDENTIAL_ID } from 'store/constant'
|
||||||
|
import PromptLangsmithHubDialog from '../../ui-component/dialog/PromptLangsmithHubDialog'
|
||||||
|
|
||||||
const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant']
|
const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant']
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
|||||||
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
|
const [reloadTimestamp, setReloadTimestamp] = useState(Date.now().toString())
|
||||||
const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false)
|
const [showFormatPromptValuesDialog, setShowFormatPromptValuesDialog] = useState(false)
|
||||||
const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({})
|
const [formatPromptValuesDialogProps, setFormatPromptValuesDialogProps] = useState({})
|
||||||
|
const [showPromptHubDialog, setShowPromptHubDialog] = useState(false)
|
||||||
|
|
||||||
const onExpandDialogClicked = (value, inputParam) => {
|
const onExpandDialogClicked = (value, inputParam) => {
|
||||||
const dialogProp = {
|
const dialogProp = {
|
||||||
@@ -69,6 +72,17 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
|||||||
setShowExpandDialog(true)
|
setShowExpandDialog(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onShowPromptHubButtonClicked = () => {
|
||||||
|
setShowPromptHubDialog(true)
|
||||||
|
}
|
||||||
|
const onShowPromptHubButtonSubmit = (templates) => {
|
||||||
|
setShowPromptHubDialog(false)
|
||||||
|
for (const t of templates) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(data.inputs, t.type)) {
|
||||||
|
data.inputs[t.type] = t.template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const onFormatPromptValuesClicked = (value, inputParam) => {
|
const onFormatPromptValuesClicked = (value, inputParam) => {
|
||||||
// Preset values if the field is format prompt values
|
// Preset values if the field is format prompt values
|
||||||
let inputValue = value
|
let inputValue = value
|
||||||
@@ -209,6 +223,31 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
|||||||
</CustomWidthTooltip>
|
</CustomWidthTooltip>
|
||||||
)}
|
)}
|
||||||
<Box sx={{ p: 2 }}>
|
<Box sx={{ p: 2 }}>
|
||||||
|
{(data.name === 'promptTemplate' || data.name === 'chatPromptTemplate') &&
|
||||||
|
(inputParam.name === 'template' || inputParam.name === 'systemMessagePrompt') && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
sx={{ borderRadius: 25, width: '100%', mb: 2, mt: 0 }}
|
||||||
|
variant='outlined'
|
||||||
|
onClick={() => onShowPromptHubButtonClicked()}
|
||||||
|
endIcon={<IconAutoFixHigh />}
|
||||||
|
>
|
||||||
|
Langchain Hub
|
||||||
|
</Button>
|
||||||
|
<PromptLangsmithHubDialog
|
||||||
|
promptType={inputParam.name}
|
||||||
|
show={showPromptHubDialog}
|
||||||
|
onCancel={() => setShowPromptHubDialog(false)}
|
||||||
|
onSubmit={onShowPromptHubButtonSubmit}
|
||||||
|
></PromptLangsmithHubDialog>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{inputParam.label}
|
{inputParam.label}
|
||||||
@@ -260,6 +299,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inputParam.type === 'file' && (
|
{inputParam.type === 'file' && (
|
||||||
<File
|
<File
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ const ShareChatbot = ({ isSessionMemory }) => {
|
|||||||
|
|
||||||
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
|
if (isSessionMemory) obj.overrideConfig.generateNewSession = generateNewSession
|
||||||
|
|
||||||
|
if (chatbotConfig?.starterPrompts) obj.starterPrompts = chatbotConfig.starterPrompts
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ const Chatflows = () => {
|
|||||||
variant='contained'
|
variant='contained'
|
||||||
value='card'
|
value='card'
|
||||||
title='Card View'
|
title='Card View'
|
||||||
selectedColor='#00abc0'
|
|
||||||
>
|
>
|
||||||
<IconLayoutGrid />
|
<IconLayoutGrid />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { baseURL, maxScroll } from 'store/constant'
|
|||||||
|
|
||||||
import robotPNG from 'assets/images/robot.png'
|
import robotPNG from 'assets/images/robot.png'
|
||||||
import userPNG from 'assets/images/account.png'
|
import userPNG from 'assets/images/account.png'
|
||||||
|
import StarterPromptsCard from '../../ui-component/cards/StarterPromptsCard'
|
||||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||||
@@ -75,6 +76,9 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||||
|
const getChatflowConfig = useApi(chatflowsApi.getSpecificChatflow)
|
||||||
|
|
||||||
|
const [starterPrompts, setStarterPrompts] = useState([])
|
||||||
|
|
||||||
// drag & drop and file input
|
// drag & drop and file input
|
||||||
const fileUploadRef = useRef(null)
|
const fileUploadRef = useRef(null)
|
||||||
@@ -348,14 +352,23 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle form submission
|
const handlePromptClick = async (promptStarterInput) => {
|
||||||
const handleSubmit = async (e) => {
|
setUserInput(promptStarterInput)
|
||||||
e.preventDefault()
|
handleSubmit(undefined, promptStarterInput)
|
||||||
|
}
|
||||||
|
|
||||||
if (userInput.trim() === '') {
|
// Handle form submission
|
||||||
|
const handleSubmit = async (e, promptStarterInput) => {
|
||||||
|
if (e) e.preventDefault()
|
||||||
|
|
||||||
|
if (!promptStarterInput && userInput.trim() === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let input = userInput
|
||||||
|
|
||||||
|
if (promptStarterInput !== undefined && promptStarterInput.trim() !== '') input = promptStarterInput
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const urls = []
|
const urls = []
|
||||||
previews.map((item) => {
|
previews.map((item) => {
|
||||||
@@ -367,12 +380,12 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
clearPreviews()
|
clearPreviews()
|
||||||
setMessages((prevMessages) => [...prevMessages, { message: userInput, type: 'userMessage', fileUploads: urls }])
|
setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }])
|
||||||
|
|
||||||
// Send user question and history to API
|
// Send user question and history to API
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
question: userInput,
|
question: input,
|
||||||
history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'),
|
history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'),
|
||||||
chatId
|
chatId
|
||||||
}
|
}
|
||||||
@@ -486,7 +499,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
if (getIsChatflowStreamingApi.data) {
|
if (getIsChatflowStreamingApi.data) {
|
||||||
setIsChatFlowAvailableToStream(getIsChatflowStreamingApi.data?.isStreaming ?? false)
|
setIsChatFlowAvailableToStream(getIsChatflowStreamingApi.data?.isStreaming ?? false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getIsChatflowStreamingApi.data])
|
}, [getIsChatflowStreamingApi.data])
|
||||||
|
|
||||||
@@ -498,6 +510,24 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [getAllowChatFlowUploads.data])
|
}, [getAllowChatFlowUploads.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (getChatflowConfig.data) {
|
||||||
|
if (getChatflowConfig.data?.chatbotConfig && JSON.parse(getChatflowConfig.data?.chatbotConfig)) {
|
||||||
|
let config = JSON.parse(getChatflowConfig.data?.chatbotConfig)
|
||||||
|
if (config.starterPrompts) {
|
||||||
|
let inputFields = []
|
||||||
|
Object.getOwnPropertyNames(config.starterPrompts).forEach((key) => {
|
||||||
|
if (config.starterPrompts[key]) {
|
||||||
|
inputFields.push(config.starterPrompts[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setStarterPrompts(inputFields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [getChatflowConfig.data])
|
||||||
|
|
||||||
// Auto scroll chat to bottom
|
// Auto scroll chat to bottom
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
@@ -517,6 +547,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
getChatmessageApi.request(chatflowid)
|
getChatmessageApi.request(chatflowid)
|
||||||
getIsChatflowStreamingApi.request(chatflowid)
|
getIsChatflowStreamingApi.request(chatflowid)
|
||||||
getAllowChatFlowUploads.request(chatflowid)
|
getAllowChatFlowUploads.request(chatflowid)
|
||||||
|
getChatflowConfig.request(chatflowid)
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
setIsRecording(false)
|
setIsRecording(false)
|
||||||
socket = socketIOClient(baseURL)
|
socket = socketIOClient(baseURL)
|
||||||
@@ -762,6 +793,13 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
{messages && messages.length === 1 && (
|
||||||
|
<StarterPromptsCard starterPrompts={starterPrompts || []} onPromptClick={handlePromptClick} isGrid={isDialog} />
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
<div>
|
<div>
|
||||||
{previews && previews.length > 0 && (
|
{previews && previews.length > 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user