diff --git a/packages/components/credentials/RedisCacheUrlApi.credential.ts b/packages/components/credentials/RedisCacheUrlApi.credential.ts
new file mode 100644
index 00000000..fc2e2eb2
--- /dev/null
+++ b/packages/components/credentials/RedisCacheUrlApi.credential.ts
@@ -0,0 +1,25 @@
+import { INodeParams, INodeCredential } from '../src/Interface'
+
+class RedisCacheUrlApi implements INodeCredential {
+ label: string
+ name: string
+ version: number
+ description: string
+ inputs: INodeParams[]
+
+ constructor() {
+ this.label = 'Redis Cache URL'
+ this.name = 'redisCacheUrlApi'
+ this.version = 1.0
+ this.inputs = [
+ {
+ label: 'Redis URL',
+ name: 'redisUrl',
+ type: 'string',
+ default: '127.0.0.1'
+ }
+ ]
+ }
+}
+
+module.exports = { credClass: RedisCacheUrlApi }
diff --git a/packages/components/nodes/cache/RedisCache/RedisCache.ts b/packages/components/nodes/cache/RedisCache/RedisCache.ts
index 38998a46..3b68cf12 100644
--- a/packages/components/nodes/cache/RedisCache/RedisCache.ts
+++ b/packages/components/nodes/cache/RedisCache/RedisCache.ts
@@ -30,7 +30,7 @@ class RedisCache implements INode {
name: 'credential',
type: 'credential',
optional: true,
- credentialNames: ['redisCacheApi']
+ credentialNames: ['redisCacheApi', 'redisCacheUrlApi']
}
this.inputs = [
{
@@ -48,17 +48,24 @@ class RedisCache implements INode {
const ttl = nodeData.inputs?.ttl as string
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
- const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
- const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
- const portStr = getCredentialParam('redisCachePort', credentialData, nodeData)
- const host = getCredentialParam('redisCacheHost', credentialData, nodeData)
+ const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
- const client = new Redis({
- port: portStr ? parseInt(portStr) : 6379,
- host,
- username,
- password
- })
+ let client: Redis
+ if (!redisUrl || redisUrl === '') {
+ const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
+ const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
+ const portStr = getCredentialParam('redisCachePort', credentialData, nodeData)
+ const host = getCredentialParam('redisCacheHost', credentialData, nodeData)
+
+ client = new Redis({
+ port: portStr ? parseInt(portStr) : 6379,
+ host,
+ username,
+ password
+ })
+ } else {
+ client = new Redis(redisUrl)
+ }
const redisClient = new LangchainRedisCache(client)
diff --git a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts
index 4eecb1f5..f15869d7 100644
--- a/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts
+++ b/packages/components/nodes/cache/RedisCache/RedisEmbeddingsCache.ts
@@ -30,7 +30,7 @@ class RedisEmbeddingsCache implements INode {
name: 'credential',
type: 'credential',
optional: true,
- credentialNames: ['redisCacheApi']
+ credentialNames: ['redisCacheApi', 'redisCacheUrlApi']
}
this.inputs = [
{
@@ -63,17 +63,25 @@ class RedisEmbeddingsCache implements INode {
const underlyingEmbeddings = nodeData.inputs?.embeddings as Embeddings
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
- const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
- const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
- const portStr = getCredentialParam('redisCachePort', credentialData, nodeData)
- const host = getCredentialParam('redisCacheHost', credentialData, nodeData)
+ const redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
+
+ let client: Redis
+ if (!redisUrl || redisUrl === '') {
+ const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
+ const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
+ const portStr = getCredentialParam('redisCachePort', credentialData, nodeData)
+ const host = getCredentialParam('redisCacheHost', credentialData, nodeData)
+
+ client = new Redis({
+ port: portStr ? parseInt(portStr) : 6379,
+ host,
+ username,
+ password
+ })
+ } else {
+ client = new Redis(redisUrl)
+ }
- const client = new Redis({
- port: portStr ? parseInt(portStr) : 6379,
- host,
- username,
- password
- })
ttl ??= '3600'
let ttlNumber = parseInt(ttl, 10)
const redisStore = new RedisByteStore({
diff --git a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts
index 0ac26d85..ba2aa5e7 100644
--- a/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts
+++ b/packages/components/nodes/embeddings/AWSBedrockEmbedding/AWSBedrockEmbedding.ts
@@ -79,7 +79,10 @@ class AWSBedrockEmbedding_Embeddings implements INode {
label: 'Model Name',
name: 'model',
type: 'options',
- options: [{ label: 'amazon.titan-embed-text-v1', name: 'amazon.titan-embed-text-v1' }],
+ options: [
+ { label: 'amazon.titan-embed-text-v1', name: 'amazon.titan-embed-text-v1' },
+ { label: 'amazon.titan-embed-g1-text-02', name: 'amazon.titan-embed-g1-text-02' }
+ ],
default: 'amazon.titan-embed-text-v1'
}
]
diff --git a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts
index 58ef6df5..c16e8f54 100644
--- a/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts
+++ b/packages/components/nodes/vectorstores/Qdrant_Existing/Qdrant_Existing.ts
@@ -23,7 +23,7 @@ class Qdrant_Existing_VectorStores implements INode {
constructor() {
this.label = 'Qdrant Load Existing Index'
this.name = 'qdrantExistingIndex'
- this.version = 1.0
+ this.version = 2.0
this.type = 'Qdrant'
this.icon = 'qdrant.png'
this.category = 'Vector Stores'
@@ -55,8 +55,39 @@ class Qdrant_Existing_VectorStores implements INode {
type: 'string'
},
{
- label: 'Qdrant Collection Cofiguration',
+ label: 'Vector Dimension',
+ name: 'qdrantVectorDimension',
+ type: 'number',
+ default: 1536,
+ additionalParams: true
+ },
+ {
+ label: 'Similarity',
+ name: 'qdrantSimilarity',
+ description: 'Similarity measure used in Qdrant.',
+ type: 'options',
+ default: 'Cosine',
+ options: [
+ {
+ label: 'Cosine',
+ name: 'Cosine'
+ },
+ {
+ label: 'Euclid',
+ name: 'Euclid'
+ },
+ {
+ label: 'Dot',
+ name: 'Dot'
+ }
+ ],
+ additionalParams: true
+ },
+ {
+ label: 'Additional Collection Cofiguration',
name: 'qdrantCollectionConfiguration',
+ description:
+ 'Refer to collection docs for more reference',
type: 'json',
optional: true,
additionalParams: true
@@ -98,6 +129,8 @@ class Qdrant_Existing_VectorStores implements INode {
const collectionName = nodeData.inputs?.qdrantCollection as string
let qdrantCollectionConfiguration = nodeData.inputs?.qdrantCollectionConfiguration
const embeddings = nodeData.inputs?.embeddings as Embeddings
+ const qdrantSimilarity = nodeData.inputs?.qdrantSimilarity
+ const qdrantVectorDimension = nodeData.inputs?.qdrantVectorDimension
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
let queryFilter = nodeData.inputs?.queryFilter
@@ -126,7 +159,14 @@ class Qdrant_Existing_VectorStores implements INode {
typeof qdrantCollectionConfiguration === 'object'
? qdrantCollectionConfiguration
: JSON.parse(qdrantCollectionConfiguration)
- dbConfig.collectionConfig = qdrantCollectionConfiguration
+ dbConfig.collectionConfig = {
+ ...qdrantCollectionConfiguration,
+ vectors: {
+ ...qdrantCollectionConfiguration.vectors,
+ size: qdrantVectorDimension ? parseInt(qdrantVectorDimension, 10) : 1536,
+ distance: qdrantSimilarity ?? 'Cosine'
+ }
+ }
}
if (queryFilter) {
diff --git a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts
index 6f3773ff..407a8d22 100644
--- a/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts
+++ b/packages/components/nodes/vectorstores/Qdrant_Upsert/Qdrant_Upsert.ts
@@ -25,7 +25,7 @@ class QdrantUpsert_VectorStores implements INode {
constructor() {
this.label = 'Qdrant Upsert Document'
this.name = 'qdrantUpsert'
- this.version = 1.0
+ this.version = 2.0
this.type = 'Qdrant'
this.icon = 'qdrant.png'
this.category = 'Vector Stores'
@@ -62,6 +62,35 @@ class QdrantUpsert_VectorStores implements INode {
name: 'qdrantCollection',
type: 'string'
},
+ {
+ label: 'Vector Dimension',
+ name: 'qdrantVectorDimension',
+ type: 'number',
+ default: 1536,
+ additionalParams: true
+ },
+ {
+ label: 'Similarity',
+ name: 'qdrantSimilarity',
+ description: 'Similarity measure used in Qdrant.',
+ type: 'options',
+ default: 'Cosine',
+ options: [
+ {
+ label: 'Cosine',
+ name: 'Cosine'
+ },
+ {
+ label: 'Euclid',
+ name: 'Euclid'
+ },
+ {
+ label: 'Dot',
+ name: 'Dot'
+ }
+ ],
+ additionalParams: true
+ },
{
label: 'Top K',
name: 'topK',
@@ -99,6 +128,9 @@ class QdrantUpsert_VectorStores implements INode {
const collectionName = nodeData.inputs?.qdrantCollection as string
const docs = nodeData.inputs?.document as Document[]
const embeddings = nodeData.inputs?.embeddings as Embeddings
+ const qdrantSimilarity = nodeData.inputs?.qdrantSimilarity
+ const qdrantVectorDimension = nodeData.inputs?.qdrantVectorDimension
+
const output = nodeData.outputs?.output as string
const topK = nodeData.inputs?.topK as string
const k = topK ? parseFloat(topK) : 4
@@ -121,7 +153,13 @@ class QdrantUpsert_VectorStores implements INode {
const dbConfig: QdrantLibArgs = {
client,
url: qdrantServerUrl,
- collectionName
+ collectionName,
+ collectionConfig: {
+ vectors: {
+ size: qdrantVectorDimension ? parseInt(qdrantVectorDimension, 10) : 1536,
+ distance: qdrantSimilarity ?? 'Cosine'
+ }
+ }
}
const retrieverConfig: RetrieverConfig = {
diff --git a/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts b/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts
new file mode 100644
index 00000000..9d1c2ea1
--- /dev/null
+++ b/packages/components/nodes/vectorstores/Redis/RedisSearchBase.ts
@@ -0,0 +1,215 @@
+import {
+ getBaseClasses,
+ getCredentialData,
+ getCredentialParam,
+ ICommonObject,
+ INodeData,
+ INodeOutputsValue,
+ INodeParams
+} from '../../../src'
+
+import { Embeddings } from 'langchain/embeddings/base'
+import { VectorStore } from 'langchain/vectorstores/base'
+import { Document } from 'langchain/document'
+import { createClient, SearchOptions } from 'redis'
+import { RedisVectorStore } from 'langchain/vectorstores/redis'
+import { escapeSpecialChars, unEscapeSpecialChars } from './utils'
+
+export abstract class RedisSearchBase {
+ label: string
+ name: string
+ version: number
+ description: string
+ type: string
+ icon: string
+ category: string
+ baseClasses: string[]
+ inputs: INodeParams[]
+ credential: INodeParams
+ outputs: INodeOutputsValue[]
+ redisClient: ReturnType
+
+ protected constructor() {
+ this.type = 'Redis'
+ this.icon = 'redis.svg'
+ this.category = 'Vector Stores'
+ this.baseClasses = [this.type, 'VectorStoreRetriever', 'BaseRetriever']
+ this.credential = {
+ label: 'Connect Credential',
+ name: 'credential',
+ type: 'credential',
+ credentialNames: ['redisCacheUrlApi', 'redisCacheApi']
+ }
+ this.inputs = [
+ {
+ label: 'Embeddings',
+ name: 'embeddings',
+ type: 'Embeddings'
+ },
+ {
+ label: 'Index Name',
+ name: 'indexName',
+ placeholder: '',
+ type: 'string'
+ },
+ {
+ label: 'Replace Index?',
+ name: 'replaceIndex',
+ description: 'Selecting this option will delete the existing index and recreate a new one',
+ default: false,
+ type: 'boolean'
+ },
+ {
+ label: 'Content Field',
+ name: 'contentKey',
+ description: 'Name of the field (column) that contains the actual content',
+ type: 'string',
+ default: 'content',
+ additionalParams: true,
+ optional: true
+ },
+ {
+ label: 'Metadata Field',
+ name: 'metadataKey',
+ description: 'Name of the field (column) that contains the metadata of the document',
+ type: 'string',
+ default: 'metadata',
+ additionalParams: true,
+ optional: true
+ },
+ {
+ label: 'Vector Field',
+ name: 'vectorKey',
+ description: 'Name of the field (column) that contains the vector',
+ type: 'string',
+ default: 'content_vector',
+ additionalParams: true,
+ optional: true
+ },
+ {
+ label: 'Top K',
+ name: 'topK',
+ description: 'Number of top results to fetch. Default to 4',
+ placeholder: '4',
+ type: 'number',
+ additionalParams: true,
+ optional: true
+ }
+ ]
+ this.outputs = [
+ {
+ label: 'Redis Retriever',
+ name: 'retriever',
+ baseClasses: this.baseClasses
+ },
+ {
+ label: 'Redis Vector Store',
+ name: 'vectorStore',
+ baseClasses: [this.type, ...getBaseClasses(RedisVectorStore)]
+ }
+ ]
+ }
+
+ abstract constructVectorStore(
+ embeddings: Embeddings,
+ indexName: string,
+ replaceIndex: boolean,
+ docs: Document>[] | undefined
+ ): Promise
+
+ async init(nodeData: INodeData, _: string, options: ICommonObject, docs: Document>[] | undefined): Promise {
+ const credentialData = await getCredentialData(nodeData.credential ?? '', options)
+ const indexName = nodeData.inputs?.indexName as string
+ let contentKey = nodeData.inputs?.contentKey as string
+ let metadataKey = nodeData.inputs?.metadataKey as string
+ let vectorKey = nodeData.inputs?.vectorKey as string
+ const embeddings = nodeData.inputs?.embeddings as Embeddings
+ const topK = nodeData.inputs?.topK as string
+ const replaceIndex = nodeData.inputs?.replaceIndex as boolean
+ const k = topK ? parseFloat(topK) : 4
+ const output = nodeData.outputs?.output as string
+
+ let redisUrl = getCredentialParam('redisUrl', credentialData, nodeData)
+ if (!redisUrl || redisUrl === '') {
+ const username = getCredentialParam('redisCacheUser', credentialData, nodeData)
+ const password = getCredentialParam('redisCachePwd', credentialData, nodeData)
+ const portStr = getCredentialParam('redisCachePort', credentialData, nodeData)
+ const host = getCredentialParam('redisCacheHost', credentialData, nodeData)
+
+ redisUrl = 'redis://' + username + ':' + password + '@' + host + ':' + portStr
+ }
+
+ this.redisClient = createClient({ url: redisUrl })
+ await this.redisClient.connect()
+
+ const vectorStore = await this.constructVectorStore(embeddings, indexName, replaceIndex, docs)
+ if (!contentKey || contentKey === '') contentKey = 'content'
+ if (!metadataKey || metadataKey === '') metadataKey = 'metadata'
+ if (!vectorKey || vectorKey === '') vectorKey = 'content_vector'
+
+ const buildQuery = (query: number[], k: number, filter?: string[]): [string, SearchOptions] => {
+ const vectorScoreField = 'vector_score'
+
+ let hybridFields = '*'
+ // if a filter is set, modify the hybrid query
+ if (filter && filter.length) {
+ // `filter` is a list of strings, then it's applied using the OR operator in the metadata key
+ hybridFields = `@${metadataKey}:(${filter.map(escapeSpecialChars).join('|')})`
+ }
+
+ const baseQuery = `${hybridFields} => [KNN ${k} @${vectorKey} $vector AS ${vectorScoreField}]`
+ const returnFields = [metadataKey, contentKey, vectorScoreField]
+
+ const options: SearchOptions = {
+ PARAMS: {
+ vector: Buffer.from(new Float32Array(query).buffer)
+ },
+ RETURN: returnFields,
+ SORTBY: vectorScoreField,
+ DIALECT: 2,
+ LIMIT: {
+ from: 0,
+ size: k
+ }
+ }
+
+ return [baseQuery, options]
+ }
+
+ vectorStore.similaritySearchVectorWithScore = async (
+ query: number[],
+ k: number,
+ filter?: string[]
+ ): Promise<[Document, number][]> => {
+ const results = await this.redisClient.ft.search(indexName, ...buildQuery(query, k, filter))
+ const result: [Document, number][] = []
+
+ if (results.total) {
+ for (const res of results.documents) {
+ if (res.value) {
+ const document = res.value
+ if (document.vector_score) {
+ const metadataString = unEscapeSpecialChars(document[metadataKey] as string)
+ result.push([
+ new Document({
+ pageContent: document[contentKey] as string,
+ metadata: JSON.parse(metadataString)
+ }),
+ Number(document.vector_score)
+ ])
+ }
+ }
+ }
+ }
+ return result
+ }
+
+ if (output === 'retriever') {
+ return vectorStore.asRetriever(k)
+ } else if (output === 'vectorStore') {
+ ;(vectorStore as any).k = k
+ return vectorStore
+ }
+ return vectorStore
+ }
+}
diff --git a/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts b/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts
new file mode 100644
index 00000000..9ad472a8
--- /dev/null
+++ b/packages/components/nodes/vectorstores/Redis/Redis_Existing.ts
@@ -0,0 +1,42 @@
+import { ICommonObject, INode, INodeData } from '../../../src/Interface'
+import { Embeddings } from 'langchain/embeddings/base'
+import { VectorStore } from 'langchain/vectorstores/base'
+import { RedisVectorStore, RedisVectorStoreConfig } from 'langchain/vectorstores/redis'
+import { Document } from 'langchain/document'
+
+import { RedisSearchBase } from './RedisSearchBase'
+
+class RedisExisting_VectorStores extends RedisSearchBase implements INode {
+ constructor() {
+ super()
+ this.label = 'Redis Load Existing Index'
+ this.name = 'RedisIndex'
+ this.version = 1.0
+ this.description = 'Load existing index from Redis (i.e: Document has been upserted)'
+
+ // Remove deleteIndex from inputs as it is not applicable while fetching data from Redis
+ let input = this.inputs.find((i) => i.name === 'deleteIndex')
+ if (input) this.inputs.splice(this.inputs.indexOf(input), 1)
+ }
+
+ async constructVectorStore(
+ embeddings: Embeddings,
+ indexName: string,
+ // eslint-disable-next-line unused-imports/no-unused-vars
+ replaceIndex: boolean,
+ _: Document>[]
+ ): Promise {
+ const storeConfig: RedisVectorStoreConfig = {
+ redisClient: this.redisClient,
+ indexName: indexName
+ }
+
+ return new RedisVectorStore(embeddings, storeConfig)
+ }
+
+ async init(nodeData: INodeData, _: string, options: ICommonObject): Promise {
+ return super.init(nodeData, _, options, undefined)
+ }
+}
+
+module.exports = { nodeClass: RedisExisting_VectorStores }
diff --git a/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts b/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts
new file mode 100644
index 00000000..0ed431fb
--- /dev/null
+++ b/packages/components/nodes/vectorstores/Redis/Redis_Upsert.ts
@@ -0,0 +1,61 @@
+import { ICommonObject, INode, INodeData } from '../../../src/Interface'
+import { Embeddings } from 'langchain/embeddings/base'
+import { Document } from 'langchain/document'
+
+import { flatten } from 'lodash'
+import { RedisSearchBase } from './RedisSearchBase'
+import { VectorStore } from 'langchain/vectorstores/base'
+import { RedisVectorStore, RedisVectorStoreConfig } from 'langchain/vectorstores/redis'
+import { escapeAllStrings } from './utils'
+
+class RedisUpsert_VectorStores extends RedisSearchBase implements INode {
+ constructor() {
+ super()
+ this.label = 'Redis Upsert Document'
+ this.name = 'RedisUpsert'
+ this.version = 1.0
+ this.description = 'Upsert documents to Redis'
+ this.inputs.unshift({
+ label: 'Document',
+ name: 'document',
+ type: 'Document',
+ list: true
+ })
+ }
+
+ async constructVectorStore(
+ embeddings: Embeddings,
+ indexName: string,
+ replaceIndex: boolean,
+ docs: Document>[]
+ ): Promise {
+ const storeConfig: RedisVectorStoreConfig = {
+ redisClient: this.redisClient,
+ indexName: indexName
+ }
+ if (replaceIndex) {
+ let response = await this.redisClient.ft.dropIndex(indexName)
+ if (process.env.DEBUG === 'true') {
+ // eslint-disable-next-line no-console
+ console.log(`Redis Vector Store :: Dropping index [${indexName}], Received Response [${response}]`)
+ }
+ }
+ return await RedisVectorStore.fromDocuments(docs, embeddings, storeConfig)
+ }
+
+ async init(nodeData: INodeData, _: string, options: ICommonObject): Promise {
+ const docs = nodeData.inputs?.document as Document[]
+
+ const flattenDocs = docs && docs.length ? flatten(docs) : []
+ const finalDocs = []
+ for (let i = 0; i < flattenDocs.length; i += 1) {
+ const document = new Document(flattenDocs[i])
+ escapeAllStrings(document.metadata)
+ finalDocs.push(document)
+ }
+
+ return super.init(nodeData, _, options, flattenDocs)
+ }
+}
+
+module.exports = { nodeClass: RedisUpsert_VectorStores }
diff --git a/packages/components/nodes/vectorstores/Redis/redis.svg b/packages/components/nodes/vectorstores/Redis/redis.svg
new file mode 100644
index 00000000..90359069
--- /dev/null
+++ b/packages/components/nodes/vectorstores/Redis/redis.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/components/nodes/vectorstores/Redis/utils.ts b/packages/components/nodes/vectorstores/Redis/utils.ts
new file mode 100644
index 00000000..acef03ef
--- /dev/null
+++ b/packages/components/nodes/vectorstores/Redis/utils.ts
@@ -0,0 +1,25 @@
+/*
+ * Escapes all '-' characters.
+ * Redis Search considers '-' as a negative operator, hence we need
+ * to escape it
+ */
+export const escapeSpecialChars = (str: string) => {
+ return str.replaceAll('-', '\\-')
+}
+
+export const escapeAllStrings = (obj: object) => {
+ Object.keys(obj).forEach((key: string) => {
+ // @ts-ignore
+ let item = obj[key]
+ if (typeof item === 'object') {
+ escapeAllStrings(item)
+ } else if (typeof item === 'string') {
+ // @ts-ignore
+ obj[key] = escapeSpecialChars(item)
+ }
+ })
+}
+
+export const unEscapeSpecialChars = (str: string) => {
+ return str.replaceAll('\\-', '-')
+}
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index 9d3f7052..89b36ad1 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -809,18 +809,21 @@ export class App {
* @param {Response} res
* @param {ChatFlow} chatflow
*/
- async validateKey(req: Request, res: Response, chatflow: ChatFlow) {
+ async validateKey(req: Request, chatflow: ChatFlow) {
const chatFlowApiKeyId = chatflow.apikeyid
- const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
+ if (!chatFlowApiKeyId) return true
- if (chatFlowApiKeyId && !authorizationHeader) return res.status(401).send(`Unauthorized`)
+ const authorizationHeader = (req.headers['Authorization'] as string) ?? (req.headers['authorization'] as string) ?? ''
+ if (chatFlowApiKeyId && !authorizationHeader) return false
const suppliedKey = authorizationHeader.split(`Bearer `).pop()
- if (chatFlowApiKeyId && suppliedKey) {
+ if (suppliedKey) {
const keys = await getAPIKeys()
const apiSecret = keys.find((key) => key.id === chatFlowApiKeyId)?.apiSecret
- if (!compareKeys(apiSecret, suppliedKey)) return res.status(401).send(`Unauthorized`)
+ if (!compareKeys(apiSecret, suppliedKey)) return false
+ return true
}
+ return false
}
/**
@@ -846,7 +849,8 @@ export class App {
if (!chatId) chatId = chatflowid
if (!isInternal) {
- await this.validateKey(req, res, chatflow)
+ const isKeyValidated = await this.validateKey(req, chatflow)
+ if (!isKeyValidated) return res.status(401).send('Unauthorized')
}
let isStreamValid = false