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" ] 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) } 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 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} + + )}