From fdd673a3a974c0f88e12d81f2a9115c00b826aae Mon Sep 17 00:00:00 2001 From: hjlarry Date: Tue, 16 Sep 2025 13:39:31 +0800 Subject: [PATCH] improve comments panel --- .../workflow/panel/comments-panel/index.tsx | 145 ++++++++++++++---- web/service/workflow-comment.ts | 6 +- 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/web/app/components/workflow/panel/comments-panel/index.tsx b/web/app/components/workflow/panel/comments-panel/index.tsx index 64a2f39792..4154b772cd 100644 --- a/web/app/components/workflow/panel/comments-panel/index.tsx +++ b/web/app/components/workflow/panel/comments-panel/index.tsx @@ -1,19 +1,27 @@ -import { memo, useCallback, useMemo } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { useReactFlow } from 'reactflow' -import { RiCloseLine } from '@remixicon/react' +import { RiCheckboxCircleFill, RiCheckboxCircleLine, 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' +import { resolveWorkflowComment } from '@/service/workflow-comment' +import { useParams } from 'next/navigation' +import { useFormatTimeFromNow } from '@/app/components/workflow/hooks' 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 { comments, loading, loadComments } = useWorkflowComment() const reactFlow = useReactFlow() + const params = useParams() + const appId = params.appId as string + const { formatTimeFromNow } = useFormatTimeFromNow() + + const [filter, setFilter] = useState<'all' | 'unresolved'>('all') const handleSelect = useCallback((comment: WorkflowCommentList) => { // center viewport on the comment position and activate it @@ -21,15 +29,44 @@ const CommentsPanel = () => { 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]) + const filteredSorted = useMemo(() => { + let data = comments + if (filter === 'unresolved') + data = data.filter(c => !c.resolved) + return data + }, [comments, filter]) + + const handleResolve = useCallback(async (comment: WorkflowCommentList) => { + if (comment.resolved) return + if (!appId) return + try { + await resolveWorkflowComment(appId, comment.id) + await loadComments() + setActiveCommentId(comment.id) + } + catch (e) { + console.error('Resolve comment failed', e) + } + }, [appId, loadComments, setActiveCommentId]) return (
-
- Comments -
+
+
Comments
+
+
+ + +
{ @@ -41,43 +78,89 @@ const CommentsPanel = () => {
-
- {loading ? 'Loading…' : `${sorted.length} comment${sorted.length === 1 ? '' : 's'}`} -
- {sorted.map((c) => { + {filteredSorted.map((c) => { const isActive = activeCommentId === c.id return (
handleSelect(c)} > -
- -
-
+
+ {/* Participants stacked avatars above creator name */} + {(() => { + const creator = { + id: c.created_by, + name: c.created_by_account?.name || 'User', + avatar_url: c.created_by_account?.avatar_url || null, + } + const collaborators = (c.participants || []).filter(p => p.id !== creator.id) + const all = [creator, ...collaborators] + if (!all.length) return null + const shouldShowCount = all.length >= 4 + const maxVisible = shouldShowCount ? 2 : 3 + const visibleUsers = all.slice(0, maxVisible) + const remainingCount = all.length - maxVisible + return ( +
+ {visibleUsers.map((p, index) => ( +
+ +
+ ))} + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ ) + })()} + {/* Header row: creator + time + right-top status/action icons */} +
+
{c.created_by_account.name}
- {c.resolved && Resolved} +
+ {formatTimeFromNow(c.updated_at * 1000)} +
-
{c.content}
-
- {c.reply_count} replies • {c.mention_count} mentions +
+ {c.resolved ? ( + + ) : ( + { handleResolve(c) }} + /> + )} +
+
+ {/* Content */} +
{c.content}
+ {/* Footer */} +
+
+ {c.reply_count} replies
) })} - {!loading && sorted.length === 0 && ( + {!loading && filteredSorted.length === 0 && (
No comments yet
)}
diff --git a/web/service/workflow-comment.ts b/web/service/workflow-comment.ts index 7d91515888..e9a34a4bf3 100644 --- a/web/service/workflow-comment.ts +++ b/web/service/workflow-comment.ts @@ -15,12 +15,12 @@ export type WorkflowCommentList = { content: string created_by: string created_by_account: UserProfile - created_at: string - updated_at: string + created_at: number + updated_at: number resolved: boolean resolved_by?: string resolved_by_account?: UserProfile - resolved_at?: string + resolved_at?: number mention_count: number reply_count: number participants: UserProfile[]