mirror of https://github.com/langgenius/dify.git
comments display on canvas
This commit is contained in:
parent
d7f5da5df4
commit
dd8577f832
|
|
@ -1,5 +1,6 @@
|
|||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { useReactFlow, useViewport } from 'reactflow'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
import type { WorkflowCommentList } from '@/service/workflow-comment'
|
||||
|
||||
|
|
@ -9,12 +10,22 @@ type CommentIconProps = {
|
|||
}
|
||||
|
||||
export const CommentIcon: FC<CommentIconProps> = memo(({ comment, onClick }) => {
|
||||
const { flowToScreenPosition } = useReactFlow()
|
||||
const viewport = useViewport()
|
||||
|
||||
const screenPosition = useMemo(() => {
|
||||
return flowToScreenPosition({
|
||||
x: comment.position_x,
|
||||
y: comment.position_y,
|
||||
})
|
||||
}, [comment.position_x, comment.position_y, viewport.x, viewport.y, viewport.zoom, flowToScreenPosition])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute z-40 cursor-pointer"
|
||||
className="absolute z-10 cursor-pointer"
|
||||
style={{
|
||||
left: comment.position_x,
|
||||
top: comment.position_y,
|
||||
left: screenPosition.x,
|
||||
top: screenPosition.y,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
onClick={onClick}
|
||||
|
|
@ -24,7 +35,7 @@ export const CommentIcon: FC<CommentIconProps> = memo(({ comment, onClick }) =>
|
|||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div className="h-10 w-10 overflow-hidden rounded-full">
|
||||
<Avatar
|
||||
avatar={comment.created_by_account.avatar_url}
|
||||
avatar={comment.created_by_account.avatar_url || null}
|
||||
name={comment.created_by_account.name}
|
||||
size={40}
|
||||
className="h-full w-full"
|
||||
|
|
@ -35,6 +46,13 @@ export const CommentIcon: FC<CommentIconProps> = memo(({ comment, onClick }) =>
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}, (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.comment.id === nextProps.comment.id
|
||||
&& prevProps.comment.position_x === nextProps.comment.position_x
|
||||
&& prevProps.comment.position_y === nextProps.comment.position_y
|
||||
&& prevProps.onClick === nextProps.onClick
|
||||
)
|
||||
})
|
||||
|
||||
CommentIcon.displayName = 'CommentIcon'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import type { FC } from 'react'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { RiSendPlane2Fill } from '@remixicon/react'
|
||||
import { useReactFlow, useViewport } from 'reactflow'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Avatar from '@/app/components/base/avatar'
|
||||
|
|
@ -17,6 +18,12 @@ export const CommentInput: FC<CommentInputProps> = memo(({ position, onSubmit, o
|
|||
const [content, setContent] = useState('')
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const { userProfile } = useAppContext()
|
||||
const { flowToScreenPosition } = useReactFlow()
|
||||
const viewport = useViewport()
|
||||
|
||||
const screenPosition = useMemo(() => {
|
||||
return flowToScreenPosition(position)
|
||||
}, [position.x, position.y, viewport.x, viewport.y, viewport.zoom, flowToScreenPosition])
|
||||
|
||||
useEffect(() => {
|
||||
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
||||
|
|
@ -59,8 +66,8 @@ export const CommentInput: FC<CommentInputProps> = memo(({ position, onSubmit, o
|
|||
<div
|
||||
className="absolute z-50 w-96"
|
||||
style={{
|
||||
left: position.x + 10,
|
||||
top: position.y + 10,
|
||||
left: screenPosition.x,
|
||||
top: screenPosition.y,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { useStore } from '../store'
|
||||
import { ControlMode } from '../types'
|
||||
import type { WorkflowComment } from '@/service/workflow-comment'
|
||||
import type { WorkflowCommentList } from '@/service/workflow-comment'
|
||||
import { createWorkflowComment, fetchWorkflowComments } from '@/service/workflow-comment'
|
||||
|
||||
export const useWorkflowComment = () => {
|
||||
const params = useParams()
|
||||
const appId = params.appId as string
|
||||
const reactflow = useReactFlow()
|
||||
const controlMode = useStore(s => s.controlMode)
|
||||
const setControlMode = useStore(s => s.setControlMode)
|
||||
const pendingComment = useStore(s => s.pendingComment)
|
||||
const setPendingComment = useStore(s => s.setPendingComment)
|
||||
const [comments, setComments] = useState<WorkflowComment[]>([])
|
||||
const [comments, setComments] = useState<WorkflowCommentList[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 加载评论列表
|
||||
const loadComments = useCallback(async () => {
|
||||
if (!appId) return
|
||||
|
||||
|
|
@ -32,7 +33,6 @@ export const useWorkflowComment = () => {
|
|||
}
|
||||
}, [appId])
|
||||
|
||||
// 初始化时加载评论
|
||||
useEffect(() => {
|
||||
loadComments()
|
||||
}, [loadComments])
|
||||
|
|
@ -56,47 +56,39 @@ export const useWorkflowComment = () => {
|
|||
})
|
||||
|
||||
console.log('Comment created successfully:', newComment)
|
||||
setComments(prev => [...prev, newComment])
|
||||
await loadComments()
|
||||
setPendingComment(null)
|
||||
setControlMode(ControlMode.Pointer)
|
||||
}
|
||||
catch (error) {
|
||||
catch (error) {
|
||||
console.error('Failed to create comment:', error)
|
||||
setPendingComment(null)
|
||||
setControlMode(ControlMode.Pointer)
|
||||
}
|
||||
}, [appId, pendingComment, setControlMode, setPendingComment, setComments])
|
||||
}, [appId, pendingComment, setControlMode, setPendingComment, loadComments])
|
||||
|
||||
const handleCommentCancel = useCallback(() => {
|
||||
setPendingComment(null)
|
||||
setControlMode(ControlMode.Pointer)
|
||||
}, [setControlMode, setPendingComment])
|
||||
|
||||
const handleCommentIconClick = useCallback((comment: WorkflowComment) => {
|
||||
const handleCommentIconClick = useCallback((comment: WorkflowCommentList) => {
|
||||
// TODO: display comment details
|
||||
console.log('Comment clicked:', comment)
|
||||
}, [])
|
||||
|
||||
const handleCreateComment = useCallback((mousePosition: { pageX: number; pageY: number }) => {
|
||||
if (controlMode === ControlMode.Comment) {
|
||||
const containerElement = document.querySelector('#workflow-container')
|
||||
if (containerElement) {
|
||||
const containerBounds = containerElement.getBoundingClientRect()
|
||||
const position = {
|
||||
x: mousePosition.pageX - containerBounds.left,
|
||||
y: mousePosition.pageY - containerBounds.top,
|
||||
}
|
||||
console.log('Setting pending comment at position:', position)
|
||||
setPendingComment(position)
|
||||
}
|
||||
else {
|
||||
console.error('Could not find workflow container element')
|
||||
}
|
||||
const { screenToFlowPosition } = reactflow
|
||||
const flowPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY })
|
||||
|
||||
console.log('Setting pending comment at flow position:', flowPosition)
|
||||
setPendingComment(flowPosition)
|
||||
}
|
||||
else {
|
||||
else {
|
||||
console.log('Control mode is not Comment:', controlMode)
|
||||
}
|
||||
}, [controlMode, setPendingComment])
|
||||
}, [controlMode, setPendingComment, reactflow])
|
||||
|
||||
return {
|
||||
comments,
|
||||
|
|
|
|||
Loading…
Reference in New Issue