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) }