Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ Download and Install [NodeJS](https://nodejs.org/en/download) >= 18.15.0

3. Open [http://localhost:3000](http://localhost:3000)

## ✨ Features

### Enhanced Sticky Notes
- **Purpose:** Capture long-form thoughts with rich formatting while keeping the note unobtrusive behind flow nodes and connectors.
- **Usage example:**
1. Drag a sticky note onto the canvas.
2. Use the resize handles to expand the note in any direction while it is selected—the blue outline and handles disappear as soon as you click outside—then click the palette icon to switch between the five preset colors and toggle the markdown mode to preview formatted content.
3. Save the flow—the sticky note keeps its size, color, and markdown content when reloaded or duplicated.
- **Dependencies / breaking changes:** No additional dependencies or breaking changes.
- **Layering assurance:** Notes automatically stay behind every agentflow and chatflow node as well as their connectors so they never hide important UI.

## 🐳 Docker

### Docker Compose
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/assets/scss/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@
}
}

.react-flow__node-stickyNote {
z-index: -1 !important;
}
Comment on lines +211 to +213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of !important is generally considered an anti-pattern as it can make styles difficult to override and debug. Furthermore, the normalizeStickyNoteNodes function in genericHelper.js already sets the z-index for sticky notes via inline styles. Inline styles have higher specificity than CSS classes, making this rule redundant. It's recommended to remove this CSS block and rely solely on the JavaScript logic for managing z-index to have a single source of truth.


.spin-animation {
animation: spin 1s linear infinite;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/store/context/ReactFlowContext.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, useState } from 'react'
import { useDispatch } from 'react-redux'
import PropTypes from 'prop-types'
import { getUniqueNodeId, showHideInputParams } from '@/utils/genericHelper'
import { getUniqueNodeId, showHideInputParams, normalizeStickyNoteNodes } from '@/utils/genericHelper'
import { cloneDeep, isEqual } from 'lodash'
import { SET_DIRTY } from '@/store/actions'

Expand Down Expand Up @@ -239,7 +239,7 @@ export const ReactFlowContext = ({ children }) => {
}
}

reactFlowInstance.setNodes([...nodes, duplicatedNode])
reactFlowInstance.setNodes(normalizeStickyNoteNodes([...nodes, duplicatedNode]))
dispatch({ type: SET_DIRTY })
}
}
Expand Down
60 changes: 60 additions & 0 deletions packages/ui/src/utils/genericHelper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,62 @@
import { uniq, get, isEqual } from 'lodash'
import moment from 'moment'

export const DEFAULT_STICKY_NOTE_COLOR = '#FFE770'
const DEFAULT_STICKY_NOTE_Z_INDEX = -1
const DEFAULT_NODE_Z_INDEX = 1

const isStickyNoteNode = (node) => {
if (!node) return false

const nodeName = node?.data?.name
return node.type === 'stickyNote' || nodeName === 'stickyNote' || nodeName === 'stickyNoteAgentflow'
}

export const normalizeStickyNoteNodes = (nodes = []) =>
nodes.map((node) => {
if (!node) return node

if (isStickyNoteNode(node)) {
const color = node?.data?.color || DEFAULT_STICKY_NOTE_COLOR
const currentZIndex = node?.style?.zIndex

const needsColorUpdate = node?.data?.color !== color
const needsZIndexUpdate = currentZIndex !== DEFAULT_STICKY_NOTE_Z_INDEX

if (!needsColorUpdate && !needsZIndexUpdate) {
return node
}

return {
...node,
data: needsColorUpdate
? {
...node.data,
color
}
: node.data,
style: needsZIndexUpdate
? {
...node.style,
zIndex: DEFAULT_STICKY_NOTE_Z_INDEX
}
: node.style
}
}

if (node?.style?.zIndex == null) {
return {
...node,
style: {
...node.style,
zIndex: DEFAULT_NODE_Z_INDEX
}
}
}

return node
})

export const getUniqueNodeId = (nodeData, nodes) => {
let suffix = 0

Expand Down Expand Up @@ -223,6 +279,10 @@ export const initNode = (nodeData, newNodeId, isAgentflow) => {

nodeData.id = newNodeId

if (nodeData.type === 'StickyNote') {
nodeData.color = nodeData.color || '#FFE770'
}
Comment on lines +282 to +284
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The default color for sticky notes is hardcoded here, but a constant DEFAULT_STICKY_NOTE_COLOR was introduced in this same file for this purpose. Using the constant will improve maintainability and prevent inconsistencies.

Suggested change
if (nodeData.type === 'StickyNote') {
nodeData.color = nodeData.color || '#FFE770'
}
if (nodeData.type === 'StickyNote') {
nodeData.color = nodeData.color || DEFAULT_STICKY_NOTE_COLOR
}


return nodeData
}

Expand Down
15 changes: 9 additions & 6 deletions packages/ui/src/views/agentflowsv2/Canvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ import {
initNode,
updateOutdatedNodeData,
updateOutdatedNodeEdge,
isValidConnectionAgentflowV2
isValidConnectionAgentflowV2,
normalizeStickyNoteNodes
} from '@/utils/genericHelper'
import useNotifier from '@/utils/useNotifier'
import { usePrompt } from '@/utils/usePrompt'
Expand Down Expand Up @@ -162,7 +163,7 @@ const AgentflowCanvas = () => {
const flowData = JSON.parse(file)
const nodes = flowData.nodes || []

setNodes(nodes)
setNodes(normalizeStickyNoteNodes(nodes))
setEdges(flowData.edges || [])
setTimeout(() => setDirty(), 0)
} catch (e) {
Expand Down Expand Up @@ -204,7 +205,7 @@ const AgentflowCanvas = () => {

const handleSaveFlow = (chatflowName) => {
if (reactFlowInstance) {
const nodes = reactFlowInstance.getNodes().map((node) => {
const nodes = normalizeStickyNoteNodes(reactFlowInstance.getNodes()).map((node) => {
const nodeData = cloneDeep(node.data)
if (Object.prototype.hasOwnProperty.call(nodeData.inputs, FLOWISE_CREDENTIAL_ID)) {
nodeData.credential = nodeData.inputs[FLOWISE_CREDENTIAL_ID]
Expand Down Expand Up @@ -333,6 +334,7 @@ const AgentflowCanvas = () => {
newNode.type = 'iteration'
} else if (nodeData.type === 'StickyNote') {
newNode.type = 'stickyNote'
newNode.style = { zIndex: 0 }
} else {
newNode.type = 'agentFlow'
}
Expand Down Expand Up @@ -408,7 +410,8 @@ const AgentflowCanvas = () => {

setSelectedNode(newNode)
setNodes((nds) => {
return (nds ?? []).concat(newNode).map((node) => {
const updatedNodes = normalizeStickyNoteNodes((nds ?? []).concat(newNode))
return updatedNodes.map((node) => {
if (node.id === newNode.id) {
node.data = {
...node.data,
Expand Down Expand Up @@ -448,7 +451,7 @@ const AgentflowCanvas = () => {
}
}

setNodes(cloneNodes)
setNodes(normalizeStickyNoteNodes(cloneNodes))
setEdges(cloneEdges.filter((edge) => !toBeRemovedEdges.includes(edge)))
setDirty()
setIsSyncNodesButtonEnabled(false)
Expand Down Expand Up @@ -528,7 +531,7 @@ const AgentflowCanvas = () => {
if (getSpecificChatflowApi.data) {
const chatflow = getSpecificChatflowApi.data
const initialFlow = chatflow.flowData ? JSON.parse(chatflow.flowData) : []
setNodes(initialFlow.nodes || [])
setNodes(normalizeStickyNoteNodes(initialFlow.nodes || []))
setEdges(initialFlow.edges || [])
dispatch({ type: SET_CHATFLOW, chatflow })
} else if (getSpecificChatflowApi.error) {
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/views/agentflowsv2/MarketplaceCanvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import MarketplaceCanvasHeader from '@/views/marketplaces/MarketplaceCanvasHeade
import StickyNote from './StickyNote'
import EditNodeDialog from '@/views/agentflowsv2/EditNodeDialog'
import { flowContext } from '@/store/context/ReactFlowContext'
import { normalizeStickyNoteNodes } from '@/utils/genericHelper'

// icons
import { IconMagnetFilled, IconMagnetOff, IconArtboard, IconArtboardOff } from '@tabler/icons-react'
Expand Down Expand Up @@ -52,7 +53,7 @@ const MarketplaceCanvasV2 = () => {
useEffect(() => {
if (flowData) {
const initialFlow = JSON.parse(flowData)
setNodes(initialFlow.nodes || [])
setNodes(normalizeStickyNoteNodes(initialFlow.nodes || []))
setEdges(initialFlow.edges || [])
}

Expand Down
Loading