From 7774ff994454c9bba37fc259fa54461bb82fb907 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Thu, 9 Oct 2025 15:07:36 +0800 Subject: [PATCH 01/61] fix version not display --- .../components/workflow-app/components/workflow-panel.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx index 539c47e909..693e6e78cb 100644 --- a/web/app/components/workflow-app/components/workflow-panel.tsx +++ b/web/app/components/workflow-app/components/workflow-panel.tsx @@ -7,7 +7,6 @@ import { useStore } from '@/app/components/workflow/store' import { useIsChatMode, } from '../hooks' -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 type { PanelProps } from '@/app/components/workflow/panel' @@ -69,7 +68,6 @@ const WorkflowPanelOnRight = () => { const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel) 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 ( @@ -104,11 +102,6 @@ const WorkflowPanelOnRight = () => { ) } - { - showWorkflowVersionHistoryPanel && ( - - ) - } {controlMode === 'comment' && } ) From 6a9c9cadd0498c4a1e61217c4d5deafea7578b8e Mon Sep 17 00:00:00 2001 From: hjlarry Date: Thu, 9 Oct 2025 15:44:56 +0800 Subject: [PATCH 02/61] fix comment hover the variable panel --- web/app/components/workflow/operator/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/workflow/operator/index.tsx b/web/app/components/workflow/operator/index.tsx index 57c2988d24..90783f7959 100644 --- a/web/app/components/workflow/operator/index.tsx +++ b/web/app/components/workflow/operator/index.tsx @@ -65,7 +65,7 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => { return (
Date: Thu, 9 Oct 2025 15:50:23 +0800 Subject: [PATCH 03/61] comment reply auto scoll down to bottom --- .../components/workflow/comment/thread.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/web/app/components/workflow/comment/thread.tsx b/web/app/components/workflow/comment/thread.tsx index b727d8e640..3098198a6b 100644 --- a/web/app/components/workflow/comment/thread.tsx +++ b/web/app/components/workflow/comment/thread.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC, ReactNode } from 'react' -import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useReactFlow, useViewport } from 'reactflow' import { useTranslation } from 'react-i18next' import { RiArrowDownSLine, RiArrowUpSLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, RiDeleteBinLine, RiMoreFill } from '@remixicon/react' @@ -193,6 +193,25 @@ export const CommentThread: FC = memo(({ }, [editingReply, onReplyEdit]) const replies = comment.replies || [] + const messageListRef = useRef(null) + const previousReplyCountRef = useRef(replies.length) + const previousCommentIdRef = useRef(comment.id) + + useEffect(() => { + const container = messageListRef.current + if (!container) + return + + const isNewComment = comment.id !== previousCommentIdRef.current + const hasNewReply = replies.length > previousReplyCountRef.current + + if (isNewComment || hasNewReply) + container.scrollTop = container.scrollHeight + + previousCommentIdRef.current = comment.id + previousReplyCountRef.current = replies.length + }, [comment.id, replies.length]) + const mentionsByTarget = useMemo(() => { const map = new Map() for (const mention of comment.mentions || []) { @@ -272,7 +291,10 @@ export const CommentThread: FC = memo(({
-
+
Date: Thu, 9 Oct 2025 16:36:20 +0800 Subject: [PATCH 04/61] fix switch to cursor mode comment input still exists --- web/app/components/workflow/hooks/use-workflow-comment.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts index 64df63b2f2..aa717252a8 100644 --- a/web/app/components/workflow/hooks/use-workflow-comment.ts +++ b/web/app/components/workflow/hooks/use-workflow-comment.ts @@ -124,6 +124,11 @@ export const useWorkflowComment = () => { setPendingComment(null) }, [setPendingComment]) + useEffect(() => { + if (controlMode !== ControlMode.Comment) + setPendingComment(null) + }, [controlMode, setPendingComment]) + const handleCommentIconClick = useCallback(async (comment: WorkflowCommentList) => { setPendingComment(null) From 61c46bea40dfd3057ec95f132a25406bc1767110 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Thu, 9 Oct 2025 16:55:53 +0800 Subject: [PATCH 05/61] fix missing i18n --- .../components/workflow/panel/comments-panel/index.tsx | 8 +++++--- web/i18n/de-DE/workflow.ts | 2 ++ web/i18n/en-US/workflow.ts | 2 ++ web/i18n/es-ES/workflow.ts | 2 ++ web/i18n/fa-IR/workflow.ts | 2 ++ web/i18n/fr-FR/workflow.ts | 2 ++ web/i18n/hi-IN/workflow.ts | 2 ++ web/i18n/id-ID/workflow.ts | 2 ++ web/i18n/it-IT/workflow.ts | 2 ++ web/i18n/ja-JP/workflow.ts | 2 ++ web/i18n/ko-KR/workflow.ts | 2 ++ web/i18n/zh-Hans/workflow.ts | 2 ++ web/i18n/zh-Hant/workflow.ts | 2 ++ 13 files changed, 29 insertions(+), 3 deletions(-) diff --git a/web/app/components/workflow/panel/comments-panel/index.tsx b/web/app/components/workflow/panel/comments-panel/index.tsx index 1bb7e27c51..8f68ecbf0e 100644 --- a/web/app/components/workflow/panel/comments-panel/index.tsx +++ b/web/app/components/workflow/panel/comments-panel/index.tsx @@ -1,5 +1,6 @@ import { memo, useCallback, useMemo, useState } from 'react' import { RiCheckLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, RiFilter3Line } from '@remixicon/react' +import { useTranslation } from 'react-i18next' import { useStore } from '@/app/components/workflow/store' import type { WorkflowCommentList } from '@/service/workflow-comment' import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment' @@ -13,6 +14,7 @@ import { useAppContext } from '@/context/app-context' import { collaborationManager } from '@/app/components/workflow/collaboration' const CommentsPanel = () => { + const { t } = useTranslation() const activeCommentId = useStore(s => s.activeCommentId) const setActiveCommentId = useStore(s => s.setActiveCommentId) const setControlMode = useStore(s => s.setControlMode) @@ -63,7 +65,7 @@ const CommentsPanel = () => { return (
-
Comments
+
{t('workflow.comments.panelTitle')}
diff --git a/web/i18n/de-DE/workflow.ts b/web/i18n/de-DE/workflow.ts index 23a78063b7..b45c3f2c35 100644 --- a/web/i18n/de-DE/workflow.ts +++ b/web/i18n/de-DE/workflow.ts @@ -195,6 +195,8 @@ const translation = { comments: { panelTitle: 'Kommentar', loading: 'Laden…', + reply: 'Antworten', + noComments: 'Noch keine Kommentare', placeholder: { add: 'Kommentar hinzufügen', reply: 'Antworten', diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 3f4e722543..9109a4601f 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -201,6 +201,8 @@ const translation = { comments: { panelTitle: 'Comment', loading: 'Loading…', + reply: 'Reply', + noComments: 'No comments yet', placeholder: { add: 'Add a comment', reply: 'Reply', diff --git a/web/i18n/es-ES/workflow.ts b/web/i18n/es-ES/workflow.ts index 631cb473fb..e47ad076b4 100644 --- a/web/i18n/es-ES/workflow.ts +++ b/web/i18n/es-ES/workflow.ts @@ -195,6 +195,8 @@ const translation = { comments: { panelTitle: 'Comentario', loading: 'Cargando…', + reply: 'Responder', + noComments: 'Aún no hay comentarios', placeholder: { add: 'Añadir un comentario', reply: 'Responder', diff --git a/web/i18n/fa-IR/workflow.ts b/web/i18n/fa-IR/workflow.ts index 8fb1aac21a..b57d37e745 100644 --- a/web/i18n/fa-IR/workflow.ts +++ b/web/i18n/fa-IR/workflow.ts @@ -195,6 +195,8 @@ const translation = { comments: { panelTitle: 'دیدگاه', loading: 'در حال بارگذاری…', + reply: 'پاسخ', + noComments: 'هنوز دیدگاهی ثبت نشده است', placeholder: { add: 'افزودن دیدگاه', reply: 'پاسخ', diff --git a/web/i18n/fr-FR/workflow.ts b/web/i18n/fr-FR/workflow.ts index 8af75e2d07..c6eda423c5 100644 --- a/web/i18n/fr-FR/workflow.ts +++ b/web/i18n/fr-FR/workflow.ts @@ -195,6 +195,8 @@ const translation = { comments: { panelTitle: 'Commentaire', loading: 'Chargement…', + reply: 'Répondre', + noComments: 'Aucun commentaire pour l’instant', placeholder: { add: 'Ajouter un commentaire', reply: 'Répondre', diff --git a/web/i18n/hi-IN/workflow.ts b/web/i18n/hi-IN/workflow.ts index fd1df11199..ce0900d2ad 100644 --- a/web/i18n/hi-IN/workflow.ts +++ b/web/i18n/hi-IN/workflow.ts @@ -198,6 +198,8 @@ const translation = { comments: { panelTitle: 'टिप्पणी', loading: 'लोड हो रहा है…', + reply: 'जवाब दें', + noComments: 'अभी तक कोई टिप्पणी नहीं', placeholder: { add: 'टिप्पणी जोड़ें', reply: 'जवाब दें', diff --git a/web/i18n/id-ID/workflow.ts b/web/i18n/id-ID/workflow.ts index 9fc0293380..f6ef6e08f1 100644 --- a/web/i18n/id-ID/workflow.ts +++ b/web/i18n/id-ID/workflow.ts @@ -189,6 +189,8 @@ const translation = { comments: { panelTitle: 'Komentar', loading: 'Memuat…', + reply: 'Balas', + noComments: 'Belum ada komentar', placeholder: { add: 'Tambahkan komentar', reply: 'Balas', diff --git a/web/i18n/it-IT/workflow.ts b/web/i18n/it-IT/workflow.ts index 62382c1b74..e4602df5a8 100644 --- a/web/i18n/it-IT/workflow.ts +++ b/web/i18n/it-IT/workflow.ts @@ -200,6 +200,8 @@ const translation = { comments: { panelTitle: 'Commento', loading: 'Caricamento…', + reply: 'Rispondi', + noComments: 'Ancora nessun commento', placeholder: { add: 'Aggiungi un commento', reply: 'Rispondi', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 8e1edad026..5a947c4209 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -200,6 +200,8 @@ const translation = { comments: { panelTitle: 'コメント', loading: '読み込み中…', + reply: '返信', + noComments: 'まだコメントがありません', placeholder: { add: 'コメントを追加', reply: '返信', diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts index b058fba5eb..6f87ffb5ce 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -203,6 +203,8 @@ const translation = { comments: { panelTitle: '댓글', loading: '불러오는 중…', + reply: '답글', + noComments: '아직 댓글이 없습니다', placeholder: { add: '댓글 추가', reply: '답글', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 8cb195d87e..f92d923bc1 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -201,6 +201,8 @@ const translation = { comments: { panelTitle: '评论', loading: '加载中…', + reply: '回复', + noComments: '暂无评论', placeholder: { add: '添加评论', reply: '回复', diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index d00961fbef..df737dc1eb 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -195,6 +195,8 @@ const translation = { comments: { panelTitle: '評論', loading: '載入中…', + reply: '回覆', + noComments: '暫無評論', placeholder: { add: '新增評論', reply: '回覆', From a40e11cb3e8c55a828ad5195e3da691a24471f4d Mon Sep 17 00:00:00 2001 From: hjlarry Date: Thu, 9 Oct 2025 17:02:39 +0800 Subject: [PATCH 06/61] only can edit own replies --- web/app/components/workflow/comment/thread.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/workflow/comment/thread.tsx b/web/app/components/workflow/comment/thread.tsx index 3098198a6b..14f6b00d74 100644 --- a/web/app/components/workflow/comment/thread.tsx +++ b/web/app/components/workflow/comment/thread.tsx @@ -307,12 +307,13 @@ export const CommentThread: FC = memo(({
{replies.map((reply) => { const isReplyEditing = editingReply?.id === reply.id + const isOwnReply = reply.created_by_account?.id === userProfile?.id return (
- {!isReplyEditing && ( + {isOwnReply && !isReplyEditing && (
{activeReplyMenuId === reply.id && ( -
+
{activeReplyMenuId === reply.id && ( -
+
- {activeReplyMenuId === reply.id && ( -
+ - -
- )} -
+ +
+ + + + + )} {isReplyEditing ? (
From 45d5d9e44fc4aa4a8e0aea2bd85cad96cfedaeb4 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Sat, 11 Oct 2025 13:37:43 +0800 Subject: [PATCH 34/61] fix: mention input cannot scroll --- .../workflow/comment/mention-input.tsx | 171 +++++++++++++++++- 1 file changed, 162 insertions(+), 9 deletions(-) diff --git a/web/app/components/workflow/comment/mention-input.tsx b/web/app/components/workflow/comment/mention-input.tsx index 6972d935fa..c369ae3a2e 100644 --- a/web/app/components/workflow/comment/mention-input.tsx +++ b/web/app/components/workflow/comment/mention-input.tsx @@ -1,7 +1,15 @@ 'use client' import type { FC, ReactNode } from 'react' -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { + memo, + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' import { createPortal } from 'react-dom' import { useParams } from 'next/navigation' import { useTranslation } from 'react-i18next' @@ -42,6 +50,10 @@ export const MentionInput: FC = memo(({ const { t } = useTranslation() const appId = params.appId as string const textareaRef = useRef(null) + const highlightContentRef = useRef(null) + const actionContainerRef = useRef(null) + const actionRightRef = useRef(null) + const baseTextareaHeightRef = useRef(null) const workflowStore = useWorkflowStore() const mentionUsersFromStore = useStore(state => ( @@ -55,6 +67,11 @@ export const MentionInput: FC = memo(({ const [selectedMentionIndex, setSelectedMentionIndex] = useState(0) const [mentionedUserIds, setMentionedUserIds] = useState([]) const resolvedPlaceholder = placeholder ?? t('workflow.comments.placeholder.add') + const BASE_PADDING = 4 + const [shouldReserveButtonGap, setShouldReserveButtonGap] = useState(isEditing) + const [shouldReserveHorizontalSpace, setShouldReserveHorizontalSpace] = useState(() => !isEditing) + const [paddingRight, setPaddingRight] = useState(() => BASE_PADDING + (isEditing ? 0 : 48)) + const [paddingBottom, setPaddingBottom] = useState(() => BASE_PADDING + (isEditing ? 32 : 0)) const mentionNameList = useMemo(() => { const names = mentionUsers @@ -153,6 +170,104 @@ export const MentionInput: FC = memo(({ useEffect(() => { loadMentionableUsers() }, [loadMentionableUsers]) + const syncHighlightScroll = useCallback(() => { + const textarea = textareaRef.current + const highlightContent = highlightContentRef.current + if (!textarea || !highlightContent) + return + + const { scrollTop, scrollLeft } = textarea + highlightContent.style.transform = `translate(${-scrollLeft}px, ${-scrollTop}px)` + }, []) + + const evaluateContentLayout = useCallback(() => { + const textarea = textareaRef.current + if (!textarea) + return + + const extraBottom = Math.max(0, paddingBottom - BASE_PADDING) + const effectiveClientHeight = textarea.clientHeight - extraBottom + + if (baseTextareaHeightRef.current === null) + baseTextareaHeightRef.current = effectiveClientHeight + + const baseHeight = baseTextareaHeightRef.current ?? effectiveClientHeight + const hasMultiline = effectiveClientHeight > baseHeight + 1 + const shouldReserveVertical = isEditing ? true : hasMultiline + + setShouldReserveButtonGap(shouldReserveVertical) + setShouldReserveHorizontalSpace(!hasMultiline) + }, [isEditing, paddingBottom]) + + const updateLayoutPadding = useCallback(() => { + const actionEl = actionContainerRef.current + const rect = actionEl?.getBoundingClientRect() + const rightRect = actionRightRef.current?.getBoundingClientRect() + let actionWidth = 0 + if (rightRect) + actionWidth = Math.ceil(rightRect.width) + else if (rect) + actionWidth = Math.ceil(rect.width) + + const actionHeight = rect ? Math.ceil(rect.height) : 0 + const fallbackWidth = Math.max(0, paddingRight - BASE_PADDING) + const fallbackHeight = Math.max(0, paddingBottom - BASE_PADDING) + const effectiveWidth = actionWidth > 0 ? actionWidth : fallbackWidth + const effectiveHeight = actionHeight > 0 ? actionHeight : fallbackHeight + + const nextRight = BASE_PADDING + (shouldReserveHorizontalSpace ? effectiveWidth : 0) + const nextBottom = BASE_PADDING + (shouldReserveButtonGap ? effectiveHeight : 0) + + setPaddingRight(prev => (prev === nextRight ? prev : nextRight)) + setPaddingBottom(prev => (prev === nextBottom ? prev : nextBottom)) + }, [shouldReserveButtonGap, shouldReserveHorizontalSpace, paddingRight, paddingBottom]) + + const setActionContainerRef = useCallback((node: HTMLDivElement | null) => { + actionContainerRef.current = node + + if (!isEditing) + actionRightRef.current = node + else if (!node) + actionRightRef.current = null + + if (node && typeof window !== 'undefined') + window.requestAnimationFrame(() => updateLayoutPadding()) + }, [isEditing, updateLayoutPadding]) + + const setActionRightRef = useCallback((node: HTMLDivElement | null) => { + actionRightRef.current = node + + if (node && typeof window !== 'undefined') + window.requestAnimationFrame(() => updateLayoutPadding()) + }, [updateLayoutPadding]) + + useLayoutEffect(() => { + syncHighlightScroll() + }, [value, syncHighlightScroll]) + + useLayoutEffect(() => { + evaluateContentLayout() + }, [value, evaluateContentLayout]) + + useLayoutEffect(() => { + updateLayoutPadding() + }, [updateLayoutPadding, isEditing, shouldReserveButtonGap]) + + useEffect(() => { + const handleResize = () => { + evaluateContentLayout() + updateLayoutPadding() + } + + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, [evaluateContentLayout, updateLayoutPadding]) + + useEffect(() => { + baseTextareaHeightRef.current = null + evaluateContentLayout() + setShouldReserveHorizontalSpace(!isEditing) + }, [isEditing, evaluateContentLayout]) const filteredMentionUsers = useMemo(() => { if (!mentionQuery) return mentionUsers @@ -198,8 +313,15 @@ export const MentionInput: FC = memo(({ else { setShowMentionDropdown(false) } + + if (typeof window !== 'undefined') { + window.requestAnimationFrame(() => { + evaluateContentLayout() + syncHighlightScroll() + }) + } }, 0) - }, [onChange]) + }, [onChange, evaluateContentLayout, syncHighlightScroll]) const handleMentionButtonClick = useCallback((e: React.MouseEvent) => { e.preventDefault() @@ -222,8 +344,15 @@ export const MentionInput: FC = memo(({ setMentionPosition(cursorPosition) setShowMentionDropdown(true) setSelectedMentionIndex(0) + + if (typeof window !== 'undefined') { + window.requestAnimationFrame(() => { + evaluateContentLayout() + syncHighlightScroll() + }) + } }, 0) - }, [value, onChange]) + }, [value, onChange, evaluateContentLayout, syncHighlightScroll]) const insertMention = useCallback((user: UserProfile) => { const textarea = textareaRef.current @@ -247,8 +376,14 @@ export const MentionInput: FC = memo(({ const newCursorPos = mentionPosition + extraSpace + user.name.length + 2 // (space) + @ + name + space textarea.setSelectionRange(newCursorPos, newCursorPos) textarea.focus() + if (typeof window !== 'undefined') { + window.requestAnimationFrame(() => { + evaluateContentLayout() + syncHighlightScroll() + }) + } }, 0) - }, [value, mentionPosition, onChange, mentionedUserIds]) + }, [value, mentionPosition, onChange, mentionedUserIds, evaluateContentLayout, syncHighlightScroll]) const handleSubmit = useCallback((e?: React.MouseEvent) => { if (e) { @@ -330,9 +465,16 @@ export const MentionInput: FC = memo(({ 'pointer-events-none absolute inset-0 z-0 overflow-hidden whitespace-pre-wrap break-words p-1 leading-6', 'body-lg-regular text-text-primary', )} + style={{ paddingRight, paddingBottom }} > - {highlightedValue} - {'​'} +
+ {highlightedValue} + {'​'} +