+
${statusIcon} ${statusText}
+
${message}
+ ${details ? `
${details}
` : ''}
+
+
+
+
+ `
+}
+
+export const generateSuccessPage = (credentialId: string): string => {
+ return generateOAuth2ResponsePage({
+ title: 'OAuth2 Authorization Success',
+ statusIcon: '✓',
+ statusText: 'Authorization Successful',
+ statusColor: '#4caf50',
+ message: 'You can close this window now.',
+ postMessageType: 'OAUTH2_SUCCESS',
+ postMessageData: {
+ credentialId,
+ success: true,
+ message: 'OAuth2 authorization completed successfully'
+ },
+ autoCloseDelay: 1000
+ })
+}
+
+export const generateErrorPage = (error: string, message: string, details?: string): string => {
+ return generateOAuth2ResponsePage({
+ title: 'OAuth2 Authorization Error',
+ statusIcon: '✗',
+ statusText: 'Authorization Failed',
+ statusColor: '#f44336',
+ message,
+ details,
+ postMessageType: 'OAUTH2_ERROR',
+ postMessageData: {
+ success: false,
+ message,
+ error
+ },
+ autoCloseDelay: 3000
+ })
+}
diff --git a/packages/server/src/services/openai-realtime/index.ts b/packages/server/src/services/openai-realtime/index.ts
index f16c96b1..f5126e97 100644
--- a/packages/server/src/services/openai-realtime/index.ts
+++ b/packages/server/src/services/openai-realtime/index.ts
@@ -23,6 +23,7 @@ import { Organization } from '../../enterprise/database/entities/organization.en
const SOURCE_DOCUMENTS_PREFIX = '\n\n----FLOWISE_SOURCE_DOCUMENTS----\n\n'
const ARTIFACTS_PREFIX = '\n\n----FLOWISE_ARTIFACTS----\n\n'
+const TOOL_ARGS_PREFIX = '\n\n----FLOWISE_TOOL_ARGS----\n\n'
const buildAndInitTool = async (chatflowid: string, _chatId?: string, _apiMessageId?: string) => {
const appServer = getRunningExpressApp()
@@ -211,6 +212,11 @@ const executeAgentTool = async (
}
}
+ if (typeof toolOutput === 'string' && toolOutput.includes(TOOL_ARGS_PREFIX)) {
+ const _splitted = toolOutput.split(TOOL_ARGS_PREFIX)
+ toolOutput = _splitted[0]
+ }
+
return {
output: toolOutput,
sourceDocuments,
diff --git a/packages/server/src/utils/constants.ts b/packages/server/src/utils/constants.ts
index 3d3d6796..52cfa402 100644
--- a/packages/server/src/utils/constants.ts
+++ b/packages/server/src/utils/constants.ts
@@ -39,6 +39,8 @@ export const WHITELIST_URLS = [
'/api/v1/loginmethod',
'/api/v1/pricing',
'/api/v1/user/test',
+ '/api/v1/oauth2-credential/callback',
+ '/api/v1/oauth2-credential/refresh',
AzureSSO.LOGIN_URI,
AzureSSO.LOGOUT_URI,
AzureSSO.CALLBACK_URI,
diff --git a/packages/ui/src/api/oauth2.js b/packages/ui/src/api/oauth2.js
new file mode 100644
index 00000000..6546a504
--- /dev/null
+++ b/packages/ui/src/api/oauth2.js
@@ -0,0 +1,13 @@
+import client from './client'
+
+const authorize = (credentialId) => client.post(`/oauth2-credential/authorize/${credentialId}`)
+
+const refresh = (credentialId) => client.post(`/oauth2-credential/refresh/${credentialId}`)
+
+const getCallback = (queryParams) => client.get(`/oauth2-credential/callback?${queryParams}`)
+
+export default {
+ authorize,
+ refresh,
+ getCallback
+}
diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js
index dae926a8..6bafd558 100644
--- a/packages/ui/src/utils/genericHelper.js
+++ b/packages/ui/src/utils/genericHelper.js
@@ -1118,42 +1118,87 @@ const _showHideOperation = (nodeData, inputParam, displayType, index) => {
if (path.includes('$index')) {
path = path.replace('$index', index)
}
- const groundValue = get(nodeData.inputs, path, '')
+ let groundValue = get(nodeData.inputs, path, '')
+ if (groundValue && typeof groundValue === 'string' && groundValue.startsWith('[') && groundValue.endsWith(']')) {
+ groundValue = JSON.parse(groundValue)
+ }
- if (Array.isArray(comparisonValue)) {
- if (displayType === 'show' && !comparisonValue.includes(groundValue)) {
- inputParam.display = false
+ // Handle case where groundValue is an array
+ if (Array.isArray(groundValue)) {
+ if (Array.isArray(comparisonValue)) {
+ // Both are arrays - check if there's any intersection
+ const hasIntersection = comparisonValue.some((val) => groundValue.includes(val))
+ if (displayType === 'show' && !hasIntersection) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && hasIntersection) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'string') {
+ // comparisonValue is string, groundValue is array - check if array contains the string
+ const matchFound = groundValue.some((val) => comparisonValue === val || new RegExp(comparisonValue).test(val))
+ if (displayType === 'show' && !matchFound) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && matchFound) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'boolean' || typeof comparisonValue === 'number') {
+ // For boolean/number comparison with array, check if array contains the value
+ const matchFound = groundValue.includes(comparisonValue)
+ if (displayType === 'show' && !matchFound) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && matchFound) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'object') {
+ // For object comparison with array, use deep equality check
+ const matchFound = groundValue.some((val) => isEqual(comparisonValue, val))
+ if (displayType === 'show' && !matchFound) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && matchFound) {
+ inputParam.display = false
+ }
}
- if (displayType === 'hide' && comparisonValue.includes(groundValue)) {
- inputParam.display = false
- }
- } else if (typeof comparisonValue === 'string') {
- if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
- inputParam.display = false
- }
- if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
- inputParam.display = false
- }
- } else if (typeof comparisonValue === 'boolean') {
- if (displayType === 'show' && comparisonValue !== groundValue) {
- inputParam.display = false
- }
- if (displayType === 'hide' && comparisonValue === groundValue) {
- inputParam.display = false
- }
- } else if (typeof comparisonValue === 'object') {
- if (displayType === 'show' && !isEqual(comparisonValue, groundValue)) {
- inputParam.display = false
- }
- if (displayType === 'hide' && isEqual(comparisonValue, groundValue)) {
- inputParam.display = false
- }
- } else if (typeof comparisonValue === 'number') {
- if (displayType === 'show' && comparisonValue !== groundValue) {
- inputParam.display = false
- }
- if (displayType === 'hide' && comparisonValue === groundValue) {
- inputParam.display = false
+ } else {
+ // Original logic for non-array groundValue
+ if (Array.isArray(comparisonValue)) {
+ if (displayType === 'show' && !comparisonValue.includes(groundValue)) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && comparisonValue.includes(groundValue)) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'string') {
+ if (displayType === 'show' && !(comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && (comparisonValue === groundValue || new RegExp(comparisonValue).test(groundValue))) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'boolean') {
+ if (displayType === 'show' && comparisonValue !== groundValue) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && comparisonValue === groundValue) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'object') {
+ if (displayType === 'show' && !isEqual(comparisonValue, groundValue)) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && isEqual(comparisonValue, groundValue)) {
+ inputParam.display = false
+ }
+ } else if (typeof comparisonValue === 'number') {
+ if (displayType === 'show' && comparisonValue !== groundValue) {
+ inputParam.display = false
+ }
+ if (displayType === 'hide' && comparisonValue === groundValue) {
+ inputParam.display = false
+ }
}
}
})
diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx b/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx
index abd025a6..fecdb30a 100644
--- a/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx
+++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx
@@ -18,6 +18,7 @@ import { IconHandStop, IconX } from '@tabler/icons-react'
// API
import credentialsApi from '@/api/credentials'
+import oauth2Api from '@/api/oauth2'
// Hooks
import useApi from '@/hooks/useApi'
@@ -212,6 +213,149 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr
}
}
+ const setOAuth2 = async () => {
+ try {
+ let credentialId = null
+
+ // First save or add the credential
+ if (dialogProps.type === 'ADD') {
+ // Add new credential first
+ const obj = {
+ name,
+ credentialName: componentCredential.name,
+ plainDataObj: credentialData
+ }
+ const createResp = await credentialsApi.createCredential(obj)
+ if (createResp.data) {
+ credentialId = createResp.data.id
+ }
+ } else {
+ // Save existing credential first
+ const saveObj = {
+ name,
+ credentialName: componentCredential.name
+ }
+
+ let plainDataObj = {}
+ for (const key in credentialData) {
+ if (credentialData[key] !== REDACTED_CREDENTIAL_VALUE) {
+ plainDataObj[key] = credentialData[key]
+ }
+ }
+ if (Object.keys(plainDataObj).length) saveObj.plainDataObj = plainDataObj
+
+ const saveResp = await credentialsApi.updateCredential(credential.id, saveObj)
+ if (saveResp.data) {
+ credentialId = credential.id
+ }
+ }
+
+ if (!credentialId) {
+ throw new Error('Failed to save credential')
+ }
+
+ const authResponse = await oauth2Api.authorize(credentialId)
+
+ if (authResponse.data && authResponse.data.success && authResponse.data.authorizationUrl) {
+ // Open the authorization URL in a new window/tab
+ const authWindow = window.open(
+ authResponse.data.authorizationUrl,
+ '_blank',
+ 'width=600,height=700,scrollbars=yes,resizable=yes'
+ )
+
+ if (!authWindow) {
+ throw new Error('Failed to open authorization window. Please check if popups are blocked.')
+ }
+
+ // Listen for messages from the popup window
+ const handleMessage = (event) => {
+ // Verify origin if needed (you may want to add origin checking)
+ if (event.data && (event.data.type === 'OAUTH2_SUCCESS' || event.data.type === 'OAUTH2_ERROR')) {
+ window.removeEventListener('message', handleMessage)
+
+ if (event.data.type === 'OAUTH2_SUCCESS') {
+ enqueueSnackbar({
+ message: 'OAuth2 authorization completed successfully',
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'success',
+ action: (key) => (
+