fix: implement server-side search for Agentflows page#6398
Conversation
Fixes FlowiseAI#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
Fixes FlowiseAI#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
Fixes FlowiseAI#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
Fixes FlowiseAI#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
Fixes FlowiseAI#5868 The Agentflows page previously used client-side filtering which only searched within the currently loaded page. If a target agentflow existed on a different page, it would not be found. Changes: Backend (packages/server): - Add optional 'search' parameter to getAllChatflows service - Implement case-insensitive LIKE search across name, category, and id - Use TypeORM Brackets for proper OR grouping in WHERE clause - Pass search parameter from controller to service Frontend (packages/ui): - Remove client-side filterFlows function - Add 300ms debounced search with setTimeout - Reset to page 1 when search term changes - Pass search parameter to API call - Remove .filter(filterFlows) from card view - Remove filterFunction prop from table view Benefits: - Search now works across all pages, not just current page - Debouncing reduces API calls during typing - Case-insensitive search for better UX - Consistent behavior between card and table views - Pagination resets to page 1 on new search Testing: - Create multiple agentflows across multiple pages - Search for agentflow on page 2 while viewing page 1 - Verify search finds and displays the correct results - Verify debouncing works (no API call on every keystroke) - Verify pagination resets to page 1 on search
There was a problem hiding this comment.
Code Review
This pull request introduces server-side search for chatflows, improves Docker security by adopting non-root user practices, and integrates session history into follow-up prompt generation. It also adds URL validation for tool icons and pagination for chat messages. Review feedback identifies a pagination indexing bug in the message utility, a lack of character escaping in SQL search queries, and the use of non-idiomatic React patterns for debouncing. Furthermore, the reviewer suggests using .replaceAll() to ensure all template placeholders are correctly substituted.
| createdDate: sortOrder === 'DESC' ? 'DESC' : 'ASC' | ||
| } | ||
| }, | ||
| skip: page > -1 && pageSize > -1 ? page * pageSize : undefined, |
There was a problem hiding this comment.
The pagination logic here assumes 0-indexed pages (page * pageSize), but the rest of the application (including handleFeedbackQuery in this same file and getAllChatflows in the service layer) uses 1-indexed pages. If a user requests page 1, this implementation will skip the first page entirely. It should be updated to (page - 1) * pageSize to maintain consistency and correctness.
| skip: page > -1 && pageSize > -1 ? page * pageSize : undefined, | |
| skip: page > 0 && pageSize > 0 ? (page - 1) * pageSize : undefined, |
References
- Maintain consistency with existing code patterns, even if they are not considered best practice.
| qb.where('LOWER(chat_flow.name) LIKE LOWER(:search)', { search: `%${search}%` }) | ||
| .orWhere('LOWER(chat_flow.category) LIKE LOWER(:search)', { search: `%${search}%` }) | ||
| .orWhere('LOWER(chat_flow.id) LIKE LOWER(:search)', { search: `%${search}%` }) |
There was a problem hiding this comment.
When using the LIKE operator, special characters such as % and _ in the user's search input should be escaped to prevent them from being interpreted as wildcards. Additionally, applying LOWER() to chat_flow.id is redundant for UUIDs and can hinder index usage. Use simple chained .replace() calls for escaping to maintain readability and avoid complex regex.
const escapedSearch = search.replace('%', '\\%').replace('_', '\\_')
qb.where('LOWER(chat_flow.name) LIKE LOWER(:search)', { search: '%' + escapedSearch + '%' })
.orWhere('LOWER(chat_flow.category) LIKE LOWER(:search)', { search: '%' + escapedSearch + '%' })
.orWhere('chat_flow.id LIKE :search', { search: '%' + escapedSearch + '%' })References
- Prioritize code readability and understandability over conciseness. A series of simple, chained operations can be preferable to a single, more complex one (e.g., a complex regex with a replacer function) if it improves understandability and reduces the potential for future errors.
| if (window.searchTimeout) clearTimeout(window.searchTimeout) | ||
| window.searchTimeout = setTimeout(() => { | ||
| refresh(1, pageLimit, agentflowVersion, searchValue) | ||
| }, 300) |
There was a problem hiding this comment.
| let followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent) | ||
| if (sessionHistory) { | ||
| followUpPromptsPrompt = followUpPromptsPrompt.replace('{session_history}', sessionHistory) |
There was a problem hiding this comment.
The .replace() method only replaces the first occurrence of a substring. If the prompt template contains multiple placeholders for {history} or {session_history}, only the first one will be substituted. Using .replaceAll() ensures all instances are correctly replaced.
| let followUpPromptsPrompt = providerConfig.prompt.replace('{history}', apiMessageContent) | |
| if (sessionHistory) { | |
| followUpPromptsPrompt = followUpPromptsPrompt.replace('{session_history}', sessionHistory) | |
| let followUpPromptsPrompt = providerConfig.prompt.replaceAll('{history}', apiMessageContent) | |
| if (sessionHistory) { | |
| followUpPromptsPrompt = followUpPromptsPrompt.replaceAll('{session_history}', sessionHistory) | |
| } |
References
- Prioritize code readability and understandability over conciseness.
Description
Fixes #5868
The Agentflows page previously used client-side filtering which only searched within the currently loaded page. If a target agentflow existed on a different page, it would not be found, leading to a poor user experience.
Problem
Current behavior:
Root cause:
Solution
Implement server-side search with debounced API calls.
Backend Changes
packages/server/src/services/chatflows/index.tssearchparameter togetAllChatflows()name,category, andidBracketsfor proper OR groupingpackages/server/src/controllers/chatflows/index.tssearchquery parameterFrontend Changes
packages/ui/src/views/agentflows/index.jsxfilterFlowsfunctionsetTimeoutsearchparameter to API call.filter(filterFlows)from card viewfilterFunctionprop from table viewBenefits
Testing
Test Scenario
Edge Cases
Type of Change
Checklist