From 10aa16b47173228fbd6d484f344d13b975295b75 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Tue, 16 Sep 2025 09:51:12 +0800 Subject: [PATCH] add workflow comment panel --- .../components/workflow-panel.tsx | 3 + .../workflow/hooks/use-workflow-comment.ts | 13 ++- .../workflow/panel/comments-panel/index.tsx | 88 +++++++++++++++++++ .../workflow/store/workflow/panel-slice.ts | 8 ++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 web/app/components/workflow/panel/comments-panel/index.tsx 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 })), })