diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx
index dd368660ce..a43aa9699f 100644
--- a/web/app/components/workflow-app/components/workflow-panel.tsx
+++ b/web/app/components/workflow-app/components/workflow-panel.tsx
@@ -11,6 +11,7 @@ import ChatRecord from '@/app/components/workflow/panel/chat-record'
import ChatVariablePanel from '@/app/components/workflow/panel/chat-variable-panel'
import GlobalVariablePanel from '@/app/components/workflow/panel/global-variable-panel'
import VersionHistoryPanel from '@/app/components/workflow/panel/version-history-panel'
+import CommentsPanel from '@/app/components/workflow/panel/comments-panel'
import { useStore as useAppStore } from '@/app/components/app/store'
import MessageLogModal from '@/app/components/base/message-log-modal'
import type { PanelProps } from '@/app/components/workflow/panel'
@@ -50,6 +51,7 @@ const WorkflowPanelOnRight = () => {
const showChatVariablePanel = useStore(s => s.showChatVariablePanel)
const showGlobalVariablePanel = useStore(s => s.showGlobalVariablePanel)
const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)
+ const controlMode = useStore(s => s.controlMode)
return (
<>
@@ -88,6 +90,7 @@ const WorkflowPanelOnRight = () => {
)
}
+ {controlMode === 'comment' && }
>
)
}
diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts
index 90b0da25d2..42eae29705 100644
--- a/web/app/components/workflow/hooks/use-workflow-comment.ts
+++ b/web/app/components/workflow/hooks/use-workflow-comment.ts
@@ -73,9 +73,16 @@ export const useWorkflowComment = () => {
}, [setControlMode, setPendingComment])
const handleCommentIconClick = useCallback((comment: WorkflowCommentList) => {
- // TODO: display comment details
- console.log('Comment clicked:', comment)
- }, [])
+ try {
+ const store = useStore.getState()
+ store.setControlMode(ControlMode.Comment)
+ store.setActiveCommentId(comment.id)
+ reactflow.setCenter(comment.position_x, comment.position_y, { zoom: 1, duration: 600 })
+ }
+ catch (e) {
+ console.error('Failed to open comments panel:', e)
+ }
+ }, [reactflow])
const handleCreateComment = useCallback((mousePosition: { pageX: number; pageY: number }) => {
if (controlMode === ControlMode.Comment) {
diff --git a/web/app/components/workflow/panel/comments-panel/index.tsx b/web/app/components/workflow/panel/comments-panel/index.tsx
new file mode 100644
index 0000000000..64a2f39792
--- /dev/null
+++ b/web/app/components/workflow/panel/comments-panel/index.tsx
@@ -0,0 +1,88 @@
+import { memo, useCallback, useMemo } from 'react'
+import { useReactFlow } from 'reactflow'
+import { RiCloseLine } from '@remixicon/react'
+import { useStore } from '@/app/components/workflow/store'
+import type { WorkflowCommentList } from '@/service/workflow-comment'
+import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment'
+import Avatar from '@/app/components/base/avatar'
+import cn from '@/utils/classnames'
+import { ControlMode } from '@/app/components/workflow/types'
+
+const CommentsPanel = () => {
+ const activeCommentId = useStore(s => s.activeCommentId)
+ const setActiveCommentId = useStore(s => s.setActiveCommentId)
+ const setControlMode = useStore(s => s.setControlMode)
+ const { comments, loading } = useWorkflowComment()
+ const reactFlow = useReactFlow()
+
+ const handleSelect = useCallback((comment: WorkflowCommentList) => {
+ // center viewport on the comment position and activate it
+ reactFlow.setCenter(comment.position_x, comment.position_y, { zoom: 1, duration: 600 })
+ setActiveCommentId(comment.id)
+ }, [reactFlow, setActiveCommentId])
+
+ const sorted = useMemo(() => {
+ return [...comments].sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
+ }, [comments])
+
+ return (
+
+
+ Comments
+
+
{
+ setControlMode(ControlMode.Pointer)
+ setActiveCommentId(null)
+ }}
+ >
+
+
+
+
+
+ {loading ? 'Loading…' : `${sorted.length} comment${sorted.length === 1 ? '' : 's'}`}
+
+
+ {sorted.map((c) => {
+ const isActive = activeCommentId === c.id
+ return (
+
handleSelect(c)}
+ >
+
+
+
+
+
{c.created_by_account.name}
+ {c.resolved &&
Resolved}
+
+
{c.content}
+
+ {c.reply_count} replies • {c.mention_count} mentions
+
+
+
+
+ )
+ })}
+ {!loading && sorted.length === 0 && (
+
No comments yet
+ )}
+
+
+ )
+}
+
+export default memo(CommentsPanel)
diff --git a/web/app/components/workflow/store/workflow/panel-slice.ts b/web/app/components/workflow/store/workflow/panel-slice.ts
index 855f45f264..b6809b5243 100644
--- a/web/app/components/workflow/store/workflow/panel-slice.ts
+++ b/web/app/components/workflow/store/workflow/panel-slice.ts
@@ -10,6 +10,8 @@ export type PanelSliceShape = {
setShowInputsPanel: (showInputsPanel: boolean) => void
showDebugAndPreviewPanel: boolean
setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void
+ showCommentsPanel: boolean
+ setShowCommentsPanel: (showCommentsPanel: boolean) => void
panelMenu?: {
top: number
left: number
@@ -19,6 +21,8 @@ export type PanelSliceShape = {
setShowVariableInspectPanel: (showVariableInspectPanel: boolean) => void
initShowLastRunTab: boolean
setInitShowLastRunTab: (initShowLastRunTab: boolean) => void
+ activeCommentId?: string | null
+ setActiveCommentId: (commentId: string | null) => void
}
export const createPanelSlice: StateCreator = set => ({
@@ -31,10 +35,14 @@ export const createPanelSlice: StateCreator = set => ({
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
showDebugAndPreviewPanel: false,
setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })),
+ showCommentsPanel: false,
+ setShowCommentsPanel: showCommentsPanel => set(() => ({ showCommentsPanel })),
panelMenu: undefined,
setPanelMenu: panelMenu => set(() => ({ panelMenu })),
showVariableInspectPanel: false,
setShowVariableInspectPanel: showVariableInspectPanel => set(() => ({ showVariableInspectPanel })),
initShowLastRunTab: false,
setInitShowLastRunTab: initShowLastRunTab => set(() => ({ initShowLastRunTab })),
+ activeCommentId: null,
+ setActiveCommentId: (commentId: string | null) => set(() => ({ activeCommentId: commentId })),
})