mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Merge branch 'main' into BUGFIX/XSS
# Conflicts: # packages/server/src/index.ts
This commit is contained in:
@@ -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 () => {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { SystemMessage } from 'langchain/schema'
|
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
|
||||||
|
import { getBufferString, InputValues, MemoryVariables, OutputValues } from 'langchain/memory'
|
||||||
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import { ZepMemory, ZepMemoryInput } from 'langchain/memory/zep'
|
|
||||||
import { ICommonObject } from '../../../src'
|
import { ICommonObject } from '../../../src'
|
||||||
import { getBufferString } from 'langchain/memory'
|
|
||||||
|
|
||||||
class ZepMemory_Memory implements INode {
|
class ZepMemory_Memory implements INode {
|
||||||
label: string
|
label: string
|
||||||
@@ -20,7 +19,7 @@ class ZepMemory_Memory implements INode {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'Zep Memory'
|
this.label = 'Zep Memory'
|
||||||
this.name = 'ZepMemory'
|
this.name = 'ZepMemory'
|
||||||
this.version = 1.0
|
this.version = 2.0
|
||||||
this.type = 'ZepMemory'
|
this.type = 'ZepMemory'
|
||||||
this.icon = 'zep.png'
|
this.icon = 'zep.png'
|
||||||
this.category = 'Memory'
|
this.category = 'Memory'
|
||||||
@@ -41,17 +40,12 @@ class ZepMemory_Memory implements INode {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'http://127.0.0.1:8000'
|
default: 'http://127.0.0.1:8000'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Auto Summary',
|
|
||||||
name: 'autoSummary',
|
|
||||||
type: 'boolean',
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Session Id',
|
label: 'Session Id',
|
||||||
name: 'sessionId',
|
name: 'sessionId',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'If not specified, the first CHAT_MESSAGE_ID will be used as sessionId',
|
description:
|
||||||
|
'If not specified, a random id will be used. Learn <a target="_blank" href="https://docs.flowiseai.com/memory/long-term-memory#ui-and-embedded-chat">more</a>',
|
||||||
default: '',
|
default: '',
|
||||||
additionalParams: true,
|
additionalParams: true,
|
||||||
optional: true
|
optional: true
|
||||||
@@ -60,15 +54,10 @@ class ZepMemory_Memory implements INode {
|
|||||||
label: 'Size',
|
label: 'Size',
|
||||||
name: 'k',
|
name: 'k',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: '10',
|
placeholder: '10',
|
||||||
description: 'Window of size k to surface the last k back-and-forth to use as memory.'
|
description: 'Window of size k to surface the last k back-and-forth to use as memory.',
|
||||||
},
|
additionalParams: true,
|
||||||
{
|
optional: true
|
||||||
label: 'Auto Summary Template',
|
|
||||||
name: 'autoSummaryTemplate',
|
|
||||||
type: 'string',
|
|
||||||
default: 'This is the summary of the following conversation:\n{summary}',
|
|
||||||
additionalParams: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'AI Prefix',
|
label: 'AI Prefix',
|
||||||
@@ -109,36 +98,7 @@ class ZepMemory_Memory implements INode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string
|
return await initalizeZep(nodeData, options)
|
||||||
const autoSummary = nodeData.inputs?.autoSummary as boolean
|
|
||||||
|
|
||||||
const k = nodeData.inputs?.k as string
|
|
||||||
|
|
||||||
let zep = await initalizeZep(nodeData, options)
|
|
||||||
|
|
||||||
// hack to support summary
|
|
||||||
let tmpFunc = zep.loadMemoryVariables
|
|
||||||
zep.loadMemoryVariables = async (values) => {
|
|
||||||
let data = await tmpFunc.bind(zep, values)()
|
|
||||||
if (autoSummary && zep.returnMessages && data[zep.memoryKey] && data[zep.memoryKey].length) {
|
|
||||||
const zepClient = await zep.zepClientPromise
|
|
||||||
const memory = await zepClient.memory.getMemory(zep.sessionId, parseInt(k, 10) ?? 10)
|
|
||||||
if (memory?.summary) {
|
|
||||||
let summary = autoSummaryTemplate.replace(/{summary}/g, memory.summary.content)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('[ZepMemory] auto summary:', summary)
|
|
||||||
data[zep.memoryKey].unshift(new SystemMessage(summary))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// for langchain zep memory compatibility, or we will get "Missing value for input variable chat_history"
|
|
||||||
if (data instanceof Array) {
|
|
||||||
data = {
|
|
||||||
[zep.memoryKey]: data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
return zep
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -169,40 +129,72 @@ const initalizeZep = async (nodeData: INodeData, options: ICommonObject): Promis
|
|||||||
const humanPrefix = nodeData.inputs?.humanPrefix as string
|
const humanPrefix = nodeData.inputs?.humanPrefix as string
|
||||||
const memoryKey = nodeData.inputs?.memoryKey as string
|
const memoryKey = nodeData.inputs?.memoryKey as string
|
||||||
const inputKey = nodeData.inputs?.inputKey as string
|
const inputKey = nodeData.inputs?.inputKey as string
|
||||||
const sessionId = nodeData.inputs?.sessionId as string
|
const k = nodeData.inputs?.k as string
|
||||||
const chatId = options?.chatId as string
|
const chatId = options?.chatId as string
|
||||||
|
|
||||||
let isSessionIdUsingChatMessageId = false
|
let isSessionIdUsingChatMessageId = false
|
||||||
if (!sessionId && chatId) isSessionIdUsingChatMessageId = true
|
let sessionId = ''
|
||||||
|
|
||||||
|
if (!nodeData.inputs?.sessionId && chatId) {
|
||||||
|
isSessionIdUsingChatMessageId = true
|
||||||
|
sessionId = chatId
|
||||||
|
} else {
|
||||||
|
sessionId = nodeData.inputs?.sessionId
|
||||||
|
}
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||||
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
|
||||||
|
|
||||||
const obj: ZepMemoryInput & Partial<ZepMemoryExtendedInput> = {
|
const obj: ZepMemoryInput & ZepMemoryExtendedInput = {
|
||||||
baseURL,
|
baseURL,
|
||||||
sessionId: sessionId ? sessionId : chatId,
|
sessionId: sessionId ? sessionId : chatId,
|
||||||
aiPrefix,
|
aiPrefix,
|
||||||
humanPrefix,
|
humanPrefix,
|
||||||
returnMessages: true,
|
returnMessages: true,
|
||||||
memoryKey,
|
memoryKey,
|
||||||
inputKey
|
inputKey,
|
||||||
|
isSessionIdUsingChatMessageId,
|
||||||
|
k: k ? parseInt(k, 10) : undefined
|
||||||
}
|
}
|
||||||
if (apiKey) obj.apiKey = apiKey
|
if (apiKey) obj.apiKey = apiKey
|
||||||
if (isSessionIdUsingChatMessageId) obj.isSessionIdUsingChatMessageId = true
|
|
||||||
|
|
||||||
return new ZepMemoryExtended(obj)
|
return new ZepMemoryExtended(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ZepMemoryExtendedInput {
|
interface ZepMemoryExtendedInput {
|
||||||
isSessionIdUsingChatMessageId: boolean
|
isSessionIdUsingChatMessageId: boolean
|
||||||
|
k?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZepMemoryExtended extends ZepMemory {
|
class ZepMemoryExtended extends ZepMemory {
|
||||||
isSessionIdUsingChatMessageId? = false
|
isSessionIdUsingChatMessageId? = false
|
||||||
|
lastN?: number
|
||||||
|
|
||||||
constructor(fields: ZepMemoryInput & Partial<ZepMemoryExtendedInput>) {
|
constructor(fields: ZepMemoryInput & ZepMemoryExtendedInput) {
|
||||||
super(fields)
|
super(fields)
|
||||||
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
this.isSessionIdUsingChatMessageId = fields.isSessionIdUsingChatMessageId
|
||||||
|
this.lastN = fields.k
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMemoryVariables(values: InputValues, overrideSessionId = ''): Promise<MemoryVariables> {
|
||||||
|
if (overrideSessionId) {
|
||||||
|
super.sessionId = overrideSessionId
|
||||||
|
}
|
||||||
|
return super.loadMemoryVariables({ ...values, lastN: this.lastN })
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise<void> {
|
||||||
|
if (overrideSessionId) {
|
||||||
|
super.sessionId = overrideSessionId
|
||||||
|
}
|
||||||
|
return super.saveContext(inputValues, outputValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
async clear(overrideSessionId = ''): Promise<void> {
|
||||||
|
if (overrideSessionId) {
|
||||||
|
super.sessionId = overrideSessionId
|
||||||
|
}
|
||||||
|
return super.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"@aws-sdk/client-s3": "^3.427.0",
|
"@aws-sdk/client-s3": "^3.427.0",
|
||||||
"@dqbd/tiktoken": "^1.0.7",
|
"@dqbd/tiktoken": "^1.0.7",
|
||||||
"@elastic/elasticsearch": "^8.9.0",
|
"@elastic/elasticsearch": "^8.9.0",
|
||||||
"@getzep/zep-js": "^0.6.3",
|
"@getzep/zep-js": "^0.9.0",
|
||||||
"@gomomento/sdk": "^1.51.1",
|
"@gomomento/sdk": "^1.51.1",
|
||||||
"@gomomento/sdk-core": "^1.51.1",
|
"@gomomento/sdk-core": "^1.51.1",
|
||||||
"@google-ai/generativelanguage": "^0.2.1",
|
"@google-ai/generativelanguage": "^0.2.1",
|
||||||
|
|||||||
@@ -205,7 +205,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "ZepMemory_0",
|
"id": "ZepMemory_0",
|
||||||
"label": "Zep Memory",
|
"label": "Zep Memory",
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"name": "ZepMemory",
|
"name": "ZepMemory",
|
||||||
"type": "ZepMemory",
|
"type": "ZepMemory",
|
||||||
"baseClasses": ["ZepMemory", "BaseChatMemory", "BaseMemory"],
|
"baseClasses": ["ZepMemory", "BaseChatMemory", "BaseMemory"],
|
||||||
@@ -228,13 +228,6 @@
|
|||||||
"default": "http://127.0.0.1:8000",
|
"default": "http://127.0.0.1:8000",
|
||||||
"id": "ZepMemory_0-input-baseURL-string"
|
"id": "ZepMemory_0-input-baseURL-string"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Auto Summary",
|
|
||||||
"name": "autoSummary",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": true,
|
|
||||||
"id": "ZepMemory_0-input-autoSummary-boolean"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Session Id",
|
"label": "Session Id",
|
||||||
"name": "sessionId",
|
"name": "sessionId",
|
||||||
@@ -251,17 +244,10 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": "10",
|
"default": "10",
|
||||||
"step": 1,
|
"step": 1,
|
||||||
|
"additionalParams": true,
|
||||||
"description": "Window of size k to surface the last k back-and-forths to use as memory.",
|
"description": "Window of size k to surface the last k back-and-forths to use as memory.",
|
||||||
"id": "ZepMemory_0-input-k-number"
|
"id": "ZepMemory_0-input-k-number"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"label": "Auto Summary Template",
|
|
||||||
"name": "autoSummaryTemplate",
|
|
||||||
"type": "string",
|
|
||||||
"default": "This is the summary of the following conversation:\n{summary}",
|
|
||||||
"additionalParams": true,
|
|
||||||
"id": "ZepMemory_0-input-autoSummaryTemplate-string"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "AI Prefix",
|
"label": "AI Prefix",
|
||||||
"name": "aiPrefix",
|
"name": "aiPrefix",
|
||||||
@@ -306,10 +292,8 @@
|
|||||||
"inputAnchors": [],
|
"inputAnchors": [],
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"baseURL": "http://127.0.0.1:8000",
|
"baseURL": "http://127.0.0.1:8000",
|
||||||
"autoSummary": true,
|
|
||||||
"sessionId": "",
|
"sessionId": "",
|
||||||
"k": "10",
|
"k": "10",
|
||||||
"autoSummaryTemplate": "This is the summary of the following conversation:\n{summary}",
|
|
||||||
"aiPrefix": "ai",
|
"aiPrefix": "ai",
|
||||||
"humanPrefix": "human",
|
"humanPrefix": "human",
|
||||||
"memoryKey": "chat_history",
|
"memoryKey": "chat_history",
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ 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 { 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
|
||||||
@@ -1049,6 +1052,56 @@ 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 {
|
||||||
|
const credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: req.body.credential
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!credential) return res.status(404).json({ error: `Credential ${req.body.credential} not found` })
|
||||||
|
|
||||||
|
// Decrypt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData, credential.credentialName, undefined)
|
||||||
|
let hub = new Client({ apiKey: decryptedCredentialData.langsmithApiKey, apiUrl: decryptedCredentialData.langsmithEndpoint })
|
||||||
|
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 credential = await this.AppDataSource.getRepository(Credential).findOneBy({
|
||||||
|
id: req.body.credential
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!credential) return res.status(404).json({ error: `Credential ${req.body.credential} not found` })
|
||||||
|
// Decrypt credentialData
|
||||||
|
const decryptedCredentialData = await decryptCredentialData(credential.encryptedData, credential.credentialName, undefined)
|
||||||
|
|
||||||
|
const headers = {}
|
||||||
|
// @ts-ignore
|
||||||
|
headers['x-api-key'] = decryptedCredentialData.langsmithApiKey
|
||||||
|
|
||||||
|
const tags = req.body.tags ? `tags=${req.body.tags}` : ''
|
||||||
|
// Default to 100, TODO: add pagination and use offset & limit
|
||||||
|
const url = `https://web.hub.langchain.com/repos/?limit=100&${tags}has_commits=true&sort_field=num_likes&sort_direction=desc&is_archived=false`
|
||||||
|
axios.get(url, headers).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
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -558,9 +558,20 @@ export const isStartNodeDependOnInput = (startingNodes: IReactFlowNode[], nodes:
|
|||||||
if (inputVariables.length > 0) return true
|
if (inputVariables.length > 0) return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const whitelistNodeNames = ['vectorStoreToDocument', 'autoGPT']
|
const whitelistNodeNames = ['vectorStoreToDocument', 'autoGPT', 'chatPromptTemplate', 'promptTemplate'] //If these nodes are found, chatflow cannot be reused
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (whitelistNodeNames.includes(node.data.name)) return true
|
if (node.data.name === 'chatPromptTemplate' || node.data.name === 'promptTemplate') {
|
||||||
|
let promptValues: ICommonObject = {}
|
||||||
|
const promptValuesRaw = node.data.inputs?.promptValues
|
||||||
|
if (promptValuesRaw) {
|
||||||
|
try {
|
||||||
|
promptValues = typeof promptValuesRaw === 'object' ? promptValuesRaw : JSON.parse(promptValuesRaw)
|
||||||
|
} catch (exception) {
|
||||||
|
console.error(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getAllValuesFromJson(promptValues).includes(`{{${QUESTION_VAR_PREFIX}}}`)) return true
|
||||||
|
} else if (whitelistNodeNames.includes(node.data.name)) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -913,3 +924,31 @@ export const replaceChatHistory = async (
|
|||||||
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all values from a JSON object
|
||||||
|
* @param {any} obj
|
||||||
|
* @returns {any[]}
|
||||||
|
*/
|
||||||
|
export const getAllValuesFromJson = (obj: any): any[] => {
|
||||||
|
const values: any[] = []
|
||||||
|
|
||||||
|
function extractValues(data: any) {
|
||||||
|
if (typeof data === 'object' && data !== null) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
for (const item of data) {
|
||||||
|
extractValues(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key in data) {
|
||||||
|
extractValues(data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
values.push(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractValues(obj)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 |
@@ -22,7 +22,6 @@ import useConfirm from 'hooks/useConfirm'
|
|||||||
import { uiBaseURL } from '../../store/constant'
|
import { uiBaseURL } from '../../store/constant'
|
||||||
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '../../store/actions'
|
import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '../../store/actions'
|
||||||
|
|
||||||
import ConfirmDialog from '../dialog/ConfirmDialog'
|
|
||||||
import SaveChatflowDialog from '../dialog/SaveChatflowDialog'
|
import SaveChatflowDialog from '../dialog/SaveChatflowDialog'
|
||||||
import TagDialog from '../dialog/TagDialog'
|
import TagDialog from '../dialog/TagDialog'
|
||||||
|
|
||||||
@@ -264,7 +263,6 @@ export default function FlowListMenu({ chatflow, updateFlowsApi }) {
|
|||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</StyledMenu>
|
</StyledMenu>
|
||||||
<ConfirmDialog />
|
|
||||||
<SaveChatflowDialog
|
<SaveChatflowDialog
|
||||||
show={flowDialogOpen}
|
show={flowDialogOpen}
|
||||||
dialogProps={{
|
dialogProps={{
|
||||||
|
|||||||
@@ -0,0 +1,616 @@
|
|||||||
|
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 CredentialInputHandler from 'views/canvas/CredentialInputHandler'
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
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 [credentialId, setCredentialId] = 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({
|
||||||
|
credential: credentialId,
|
||||||
|
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}&`
|
||||||
|
})
|
||||||
|
const createResp = await promptApi.getAvailablePrompts({
|
||||||
|
credential: credentialId,
|
||||||
|
tags: tags
|
||||||
|
})
|
||||||
|
if (createResp.data) {
|
||||||
|
setAvailablePrompNameList(createResp.data.repos)
|
||||||
|
if (createResp.data.repos?.length) await handleListItemClick(0, createResp.data.repos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 clear = () => {
|
||||||
|
setModelName([])
|
||||||
|
setUsecase([])
|
||||||
|
setLanguage([])
|
||||||
|
setSelectedPrompt({})
|
||||||
|
setAvailablePrompNameList([])
|
||||||
|
setAccordionExpanded(['prompt'])
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = show ? (
|
||||||
|
<Dialog
|
||||||
|
onClose={onCancel}
|
||||||
|
open={show}
|
||||||
|
fullWidth
|
||||||
|
maxWidth={credentialId ? 'lg' : 'sm'}
|
||||||
|
aria-labelledby='prompt-dialog-title'
|
||||||
|
aria-describedby='prompt-dialog-description'
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ fontSize: '1rem' }} id='prompt-dialog-title'>
|
||||||
|
Load Prompts from Langsmith Hub ({promptType === 'template' ? 'PromptTemplate' : 'ChatPromptTemplate'})
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers sx={{ p: 1 }}>
|
||||||
|
<Box sx={{ width: credentialId ? '40%' : '100%', display: 'flex', flexDirection: 'row', p: 2, alignItems: 'center' }}>
|
||||||
|
<Typography sx={{ mr: 2 }}>
|
||||||
|
Langsmith Credential
|
||||||
|
<span style={{ color: 'red' }}>*</span>
|
||||||
|
</Typography>
|
||||||
|
<FormControl sx={{ flex: 1 }}>
|
||||||
|
<CredentialInputHandler
|
||||||
|
size='small'
|
||||||
|
sx={{ flexGrow: 1 }}
|
||||||
|
key={credentialId}
|
||||||
|
data={credentialId ? { credential: credentialId } : {}}
|
||||||
|
inputParam={{
|
||||||
|
label: 'Connect Credential',
|
||||||
|
name: 'credential',
|
||||||
|
type: 'credential',
|
||||||
|
credentialNames: ['langsmithApi']
|
||||||
|
}}
|
||||||
|
onSelect={(newValue) => {
|
||||||
|
setCredentialId(newValue)
|
||||||
|
if (!newValue) clear()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
{credentialId && (
|
||||||
|
<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
|
||||||
|
disabled={!credentialId}
|
||||||
|
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}
|
||||||
|
disabled={!credentialId}
|
||||||
|
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'
|
||||||
|
disabled={!credentialId}
|
||||||
|
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
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 />}
|
||||||
|
>
|
||||||
|
Langsmith Prompt 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}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ const Canvas = () => {
|
|||||||
try {
|
try {
|
||||||
await chatflowsApi.deleteChatflow(chatflow.id)
|
await chatflowsApi.deleteChatflow(chatflow.id)
|
||||||
localStorage.removeItem(`${chatflow.id}_INTERNAL`)
|
localStorage.removeItem(`${chatflow.id}_INTERNAL`)
|
||||||
navigate(-1)
|
navigate('/')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||||
enqueueSnackbar({
|
enqueueSnackbar({
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import ItemCard from 'ui-component/cards/ItemCard'
|
|||||||
import { gridSpacing } from 'store/constant'
|
import { gridSpacing } from 'store/constant'
|
||||||
import WorkflowEmptySVG from 'assets/images/workflow_empty.svg'
|
import WorkflowEmptySVG from 'assets/images/workflow_empty.svg'
|
||||||
import LoginDialog from 'ui-component/dialog/LoginDialog'
|
import LoginDialog from 'ui-component/dialog/LoginDialog'
|
||||||
|
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||||
|
|
||||||
// API
|
// API
|
||||||
import chatflowsApi from 'api/chatflows'
|
import chatflowsApi from 'api/chatflows'
|
||||||
@@ -160,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>
|
||||||
@@ -212,6 +212,7 @@ const Chatflows = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
<LoginDialog show={loginDialogOpen} dialogProps={loginDialogProps} onConfirm={onLoginClick} />
|
<LoginDialog show={loginDialogOpen} dialogProps={loginDialogProps} onConfirm={onLoginClick} />
|
||||||
|
<ConfirmDialog />
|
||||||
</MainCard>
|
</MainCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user