From 859dc5fb75bfb5de94d077aff47697e2d5eb09b6 Mon Sep 17 00:00:00 2001
From: xxiaoxiong <2482929840@qq.com>
Date: Sat, 16 May 2026 09:04:56 +0800
Subject: [PATCH 1/4] fix: add URL validation and error feedback for Tool Icon
Source
Fixes #6382
Changes:
- Add URL validation for Tool Icon Source field
- Display error message when invalid URL is entered
- Block saving when Tool Icon Source contains invalid URL
- Allow empty Tool Icon Source (optional field)
- Validate on input change for immediate feedback
- Clear error state when dialog is reset
The validation ensures:
- Empty values are allowed (optional field)
- Only http:// and https:// URLs are accepted
- Clear error messages guide users to correct format
- Saving is prevented until validation passes
---
packages/ui/src/views/tools/ToolDialog.jsx | 41 +++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/packages/ui/src/views/tools/ToolDialog.jsx b/packages/ui/src/views/tools/ToolDialog.jsx
index b73b15ec19a..3e50ef489be 100644
--- a/packages/ui/src/views/tools/ToolDialog.jsx
+++ b/packages/ui/src/views/tools/ToolDialog.jsx
@@ -79,6 +79,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
const [toolName, setToolName] = useState('')
const [toolDesc, setToolDesc] = useState('')
const [toolIcon, setToolIcon] = useState('')
+ const [toolIconError, setToolIconError] = useState('')
const [toolSchema, setToolSchema] = useState([])
const [toolFunc, setToolFunc] = useState('')
const [showHowToDialog, setShowHowToDialog] = useState(false)
@@ -97,6 +98,31 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
[]
)
+ const validateToolIconUrl = (url) => {
+ if (!url || url.trim() === '') {
+ setToolIconError('')
+ return true
+ }
+ try {
+ const urlObj = new URL(url)
+ if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
+ setToolIconError('Tool Icon Source must be a valid http or https URL')
+ return false
+ }
+ setToolIconError('')
+ return true
+ } catch (error) {
+ setToolIconError('Tool Icon Source must be a valid URL')
+ return false
+ }
+ }
+
+ const handleToolIconChange = (e) => {
+ const value = e.target.value
+ setToolIcon(value)
+ validateToolIconUrl(value)
+ }
+
const addNewRow = () => {
setTimeout(() => {
setToolSchema((prevRows) => {
@@ -225,6 +251,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
setToolName('')
setToolDesc('')
setToolIcon('')
+ setToolIconError('')
setToolSchema([])
setToolFunc('')
}
@@ -277,6 +304,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
}
const addNewTool = async () => {
+ if (!validateToolIconUrl(toolIcon)) {
+ return
+ }
try {
const obj = {
name: toolName,
@@ -323,6 +353,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
}
const saveTool = async () => {
+ if (!validateToolIconUrl(toolIcon)) {
+ return
+ }
try {
const saveResp = await toolsApi.updateTool(toolId, {
name: toolName,
@@ -513,8 +546,14 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set
placeholder='https://raw.githubusercontent.com/gilbarbara/logos/main/logos/airtable.svg'
value={toolIcon}
name='toolIcon'
- onChange={(e) => setToolIcon(e.target.value)}
+ onChange={handleToolIconChange}
+ error={!!toolIconError}
/>
+ {toolIconError && (
+
+ {toolIconError}
+
+ )}
From 35691aef1a3165f47f777c4c764af6bcba491d56 Mon Sep 17 00:00:00 2001
From: xxiaoxiong <2482929840@qq.com>
Date: Sat, 16 May 2026 09:16:48 +0800
Subject: [PATCH 2/4] fix: apply pagination to AgentFlow chat messages
Fixes #6297
The GET /api/v1/chatmessage/:id endpoint was not respecting the
limit and page query parameters for AgentFlow chatflows, causing
all messages to be returned regardless of pagination settings.
Changes:
- Add skip and take options to the TypeORM query
- Apply pagination when page > -1 and pageSize > -1
- Maintain backward compatibility (no pagination when page/pageSize are -1)
The pagination logic was already present in handleFeedbackQuery but
was missing from the main query path used by AgentFlow chatflows.
Testing:
- Pagination now works correctly for AgentFlow chatflows
- Chatflow chatflows continue to work as before
- Empty or invalid page/pageSize parameters default to no pagination
---
packages/server/src/utils/getChatMessage.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/server/src/utils/getChatMessage.ts b/packages/server/src/utils/getChatMessage.ts
index 57b835f9e4f..c2708846f80 100644
--- a/packages/server/src/utils/getChatMessage.ts
+++ b/packages/server/src/utils/getChatMessage.ts
@@ -115,7 +115,9 @@ export const utilGetChatMessage = async ({
},
order: {
createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC'
- }
+ },
+ skip: page > -1 && pageSize > -1 ? page * pageSize : undefined,
+ take: page > -1 && pageSize > -1 ? pageSize : undefined
})
return messages
From 3320effde78070f92a569eb4aea04390ec4a00e7 Mon Sep 17 00:00:00 2001
From: xxiaoxiong <2482929840@qq.com>
Date: Sat, 16 May 2026 09:32:36 +0800
Subject: [PATCH 3/4] fix(docker): eliminate recursive chown to prevent Railway
build timeout
Fixes #6365
The previous Dockerfile used 'RUN chown -R node:node .' after building,
which recursively changed ownership of ALL files including node_modules
and build artifacts. On Railway, this step alone took ~17 minutes,
causing builds to exceed the 30-minute timeout.
Changes:
- Create workdir with correct ownership upfront
- Switch to node user BEFORE copying files
- Use 'COPY --chown=node:node' to set ownership during copy
- Remove the expensive 'RUN chown -R node:node .' step entirely
Benefits:
- Eliminates 17-minute chown operation
- Build completes well within Railway's 30-minute limit
- More efficient: ownership set once during COPY, not recursively after
- Maintains security: still runs as non-root node user
Testing:
- Docker build completes successfully
- Application runs correctly as node user
- No permission issues with copied files
---
Dockerfile | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 9f54dc911b8..8432fcc1659 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,19 +27,19 @@ ENV NODE_OPTIONS=--max-old-space-size=8192
WORKDIR /usr/src/flowise
-# Copy app source
-COPY . .
+# Create workdir with correct ownership before copying files
+RUN chown node:node /usr/src/flowise
+
+# Switch to non-root user before copying and building
+USER node
+
+# Copy app source with correct ownership
+COPY --chown=node:node . .
# Install dependencies and build (excluding sdk packages not needed for Docker)
RUN pnpm install && \
pnpm build:docker
-# Give the node user ownership of the application files
-RUN chown -R node:node .
-
-# Switch to non-root user (node user already exists in node:20-alpine)
-USER node
-
EXPOSE 3000
-CMD [ "pnpm", "start" ]
\ No newline at end of file
+CMD [ "pnpm", "start" ]
From 156edeef93f2920e24a82209d9834410107d5b40 Mon Sep 17 00:00:00 2001
From: xxiaoxiong <2482929840@qq.com>
Date: Sat, 16 May 2026 10:14:16 +0800
Subject: [PATCH 4/4] feat: add session_history variable to Follow Up Prompts
Fixes #6378
The Follow Up Prompts feature previously only passed the current bot
response as the {history} variable, making it impossible to implement
session-aware prompt rules like "never suggest a question the user
has already asked".
Changes:
- Add optional sessionHistory parameter to generateFollowUpPrompts()
- Introduce new {session_history} variable containing full conversation
- Keep {history} unchanged for backward compatibility (current response only)
- Format session history as "Role: message" pairs separated by newlines
- Apply to both Chatflow and Agentflow v2
Benefits:
- Enables deduplication of suggested questions across multi-turn conversations
- Allows prompts to reference previous context for better suggestions
- Fully backward compatible - existing prompts continue to work unchanged
- Works with all LLM providers (OpenAI, Anthropic, Azure, Google, Mistral, Groq, Ollama)
Usage example:
In Follow Up Prompts configuration, use {session_history} to access
the full conversation:
"Based on the current response: {history}
Previous conversation:
{session_history}
Generate 3 follow-up questions that haven't been asked yet."
Testing:
- Existing prompts using only {history} work unchanged
- New prompts using {session_history} receive formatted conversation history
- Works across all supported LLM providers
---
packages/components/src/followUpPrompts.ts | 16 +++++--
packages/server/src/utils/buildChatflow.ts | 50 ++++++++++++++++------
2 files changed, 50 insertions(+), 16 deletions(-)
diff --git a/packages/components/src/followUpPrompts.ts b/packages/components/src/followUpPrompts.ts
index bc3b2e0f942..4e1fc6f6f56 100644
--- a/packages/components/src/followUpPrompts.ts
+++ b/packages/components/src/followUpPrompts.ts
@@ -23,7 +23,8 @@ export interface FollowUpPromptResult {
export const generateFollowUpPrompts = async (
followUpPromptsConfig: FollowUpPromptConfig,
apiMessageContent: string,
- options: ICommonObject
+ options: ICommonObject,
+ sessionHistory?: string
): Promise => {
if (followUpPromptsConfig) {
if (!followUpPromptsConfig.status) return undefined
@@ -31,7 +32,10 @@ export const generateFollowUpPrompts = async (
if (!providerConfig) return undefined
const credentialId = providerConfig.credentialId as string
const credentialData = await getCredentialData(credentialId ?? '', options)
- const followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent)
+ let followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent)
+ if (sessionHistory) {
+ followUpPromptsPrompt = followUpPromptsPrompt.replace('{session_history}', sessionHistory)
+ }
switch (followUpPromptsConfig.selectedProvider) {
case FollowUpPromptProvider.ANTHROPIC: {
@@ -70,10 +74,14 @@ export const generateFollowUpPrompts = async (
{format_instructions}
`)
const chain = prompt.pipe(llm).pipe(parser)
- const structuredResponse = await chain.invoke({
+ const invokeParams: ICommonObject = {
history: apiMessageContent,
format_instructions: formatInstructions
- })
+ }
+ if (sessionHistory) {
+ invokeParams.session_history = sessionHistory
+ }
+ const structuredResponse = await chain.invoke(invokeParams)
return structuredResponse as FollowUpPromptResult
}
case FollowUpPromptProvider.GOOGLE_GENAI: {
diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts
index 7ab1a74c0ba..590c5e684af 100644
--- a/packages/server/src/utils/buildChatflow.ts
+++ b/packages/server/src/utils/buildChatflow.ts
@@ -662,12 +662,25 @@ export const executeFlow = async ({
if (agentflow.followUpPrompts) {
const followUpPromptsConfig = JSON.parse(agentflow.followUpPrompts)
- const generatedFollowUpPrompts = await generateFollowUpPrompts(followUpPromptsConfig, apiMessage.content, {
- chatId,
- chatflowid: agentflow.id,
- appDataSource,
- databaseEntities
- })
+ // Format session history for the prompt
+ const formattedSessionHistory = chatHistory
+ .map((msg) => {
+ const role = msg.type === 'apiMessage' ? 'Assistant' : 'User'
+ const content = msg.message || msg.content || ''
+ return `${role}: ${content}`
+ })
+ .join('\n')
+ const generatedFollowUpPrompts = await generateFollowUpPrompts(
+ followUpPromptsConfig,
+ apiMessage.content,
+ {
+ chatId,
+ chatflowid: agentflow.id,
+ appDataSource,
+ databaseEntities
+ },
+ formattedSessionHistory
+ )
if (generatedFollowUpPrompts?.questions) {
apiMessage.followUpPrompts = JSON.stringify(generatedFollowUpPrompts.questions)
}
@@ -875,12 +888,25 @@ export const executeFlow = async ({
if (result?.action) apiMessage.action = typeof result.action === 'string' ? result.action : JSON.stringify(result.action)
if (chatflow.followUpPrompts) {
const followUpPromptsConfig = JSON.parse(chatflow.followUpPrompts)
- const followUpPrompts = await generateFollowUpPrompts(followUpPromptsConfig, apiMessage.content, {
- chatId,
- chatflowid,
- appDataSource,
- databaseEntities
- })
+ // Format session history for the prompt
+ const formattedSessionHistory = chatHistory
+ .map((msg) => {
+ const role = msg.type === 'apiMessage' ? 'Assistant' : 'User'
+ const content = msg.message || msg.content || ''
+ return `${role}: ${content}`
+ })
+ .join('\n')
+ const followUpPrompts = await generateFollowUpPrompts(
+ followUpPromptsConfig,
+ apiMessage.content,
+ {
+ chatId,
+ chatflowid,
+ appDataSource,
+ databaseEntities
+ },
+ formattedSessionHistory
+ )
if (followUpPrompts?.questions) {
apiMessage.followUpPrompts = JSON.stringify(followUpPrompts.questions)
}