| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- 'use client'
- import React, { useCallback, useState } from 'react'
- import { RiDeleteBinLine, RiSendPlane2Line, RiUser3Line } from '@remixicon/react'
- import useSWR from 'swr'
- import { Mention, MentionsInput } from 'react-mentions'
- import classNames from '@/utils/classnames'
- import { formatDistanceToNow } from '@/utils/time'
- import { useToastContext } from '@/app/components/base/toast'
- import { del, get, post } from '@/service/base'
- import styles from './segment-comments.module.css'
- type SegmentCommentsProps = {
- datasetId: string
- segmentId: string
- }
- type Comment = {
- id: string
- content: string
- account_id: string
- account_name: string
- account_avatar: string
- mentioned_account_ids: string[]
- created_at: string
- }
- const SegmentComments: React.FC<SegmentCommentsProps> = ({
- datasetId,
- segmentId,
- }) => {
- const { notify } = useToastContext()
- const [content, setContent] = useState('')
- const [mentionedIds, setMentionedIds] = useState<string[]>([])
- const [isSubmitting, setIsSubmitting] = useState(false)
- // 获取团队成员列表(用于@提及)
- const { data: membersData } = useSWR('/workspaces/current/members', get)
- const members = membersData?.accounts || []
- // 获取评论列表
- const { data: commentsData, mutate } = useSWR(
- `/datasets/${datasetId}/segments/${segmentId}/comments`,
- get,
- )
- const comments: Comment[] = commentsData?.comments || []
- // 提交评论
- const handleSubmit = useCallback(async () => {
- if (!content.trim() || isSubmitting)
- return
- setIsSubmitting(true)
- // 调试日志
- console.log('Submitting comment with mentioned_account_ids:', mentionedIds)
- console.log('Comment content:', content)
- try {
- await post(`/datasets/${datasetId}/segments/${segmentId}/comments`, {
- body: {
- content: content.trim(),
- mentioned_account_ids: mentionedIds,
- },
- })
- setContent('')
- setMentionedIds([])
- mutate() // 刷新评论列表
- notify({
- type: 'success',
- message: '评论发送成功',
- })
- }
- catch {
- notify({
- type: 'error',
- message: '评论发送失败',
- })
- }
- finally {
- setIsSubmitting(false)
- }
- }, [content, datasetId, segmentId, mentionedIds, mutate, notify, isSubmitting])
- // 删除评论
- const handleDelete = useCallback(async (commentId: string) => {
- try {
- await del(`/datasets/${datasetId}/segments/${segmentId}/comments/${commentId}`)
- mutate() // 刷新评论列表
- notify({
- type: 'success',
- message: '删除成功',
- })
- }
- catch {
- notify({
- type: 'error',
- message: '删除失败',
- })
- }
- }, [datasetId, segmentId, mutate, notify])
- // 处理@提及
- const handleMentionChange = useCallback((event: any, newValue: string, newPlainTextValue: string, mentions: any[]) => {
- console.log('Mention change - mentions:', mentions)
- console.log('Mention change - newValue:', newValue)
- setContent(newValue)
- setMentionedIds(mentions.map(m => m.id))
- }, [])
- return (
- <div className="border-t border-divider-subtle bg-background-section-burn">
- <div className="px-4 py-3">
- <div className="system-md-semibold mb-3 text-text-primary">
- 评论 ({comments.length})
- </div>
- {/* 评论输入框 */}
- <div className="border-components-input-border mb-4 rounded-lg border bg-components-input-bg-normal">
- <div className={styles.mentionsWrapper}>
- <MentionsInput
- value={content}
- onChange={handleMentionChange}
- placeholder="添加评论... 使用 @ 提及团队成员"
- >
- <Mention
- trigger="@"
- data={members.map((m: any) => ({
- id: m.id,
- display: m.name,
- }))}
- markup="@[__display__](__id__)"
- displayTransform={(_id, display) => `@${display}`}
- />
- </MentionsInput>
- </div>
- <div className="flex items-center justify-between border-t border-divider-subtle px-3 py-2">
- <div className="system-xs-regular text-text-tertiary">
- 使用 @ 提及团队成员
- </div>
- <button
- onClick={handleSubmit}
- disabled={!content.trim() || isSubmitting}
- className={classNames(
- 'flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',
- content.trim() && !isSubmitting
- ? 'bg-util-colors-blue-blue-600 text-white hover:bg-util-colors-blue-blue-700'
- : 'cursor-not-allowed bg-components-button-tertiary-bg text-components-button-tertiary-text',
- )}
- >
- <RiSendPlane2Line className="h-4 w-4" />
- {isSubmitting ? '发送中...' : '发送'}
- </button>
- </div>
- </div>
- {/* 评论列表 */}
- <div className="space-y-3">
- {comments.length === 0
- ? (
- <div className="py-8 text-center">
- <p className="system-sm-regular text-text-quaternary">
- 暂无评论,快来发表第一条评论吧!
- </p>
- </div>
- )
- : (
- comments.map(comment => (
- <div
- key={comment.id}
- className="rounded-lg border border-components-panel-border bg-components-panel-bg p-3"
- >
- <div className="mb-2 flex items-start justify-between">
- <div className="flex items-center gap-2">
- {/* 头像 */}
- {comment.account_avatar
- ? (
- <img
- src={comment.account_avatar}
- alt={comment.account_name}
- className="h-6 w-6 rounded-full"
- />
- )
- : (
- <div className="flex h-6 w-6 items-center justify-center rounded-full bg-util-colors-blue-blue-100">
- <RiUser3Line className="h-3.5 w-3.5 text-util-colors-blue-blue-600" />
- </div>
- )}
- {/* 名称和时间 */}
- <div>
- <div className="system-sm-semibold text-text-primary">
- {comment.account_name}
- </div>
- <div className="system-xs-regular text-text-quaternary">
- {formatDistanceToNow(new Date(comment.created_at))}
- </div>
- </div>
- </div>
- {/* 删除按钮 */}
- <button
- onClick={() => handleDelete(comment.id)}
- className="flex h-6 w-6 items-center justify-center rounded hover:bg-state-destructive-hover"
- title="删除"
- >
- <RiDeleteBinLine className="h-3.5 w-3.5 text-text-tertiary hover:text-text-destructive" />
- </button>
- </div>
- {/* 评论内容 */}
- <div className="system-sm-regular whitespace-pre-wrap break-words text-text-secondary">
- {comment.content}
- </div>
- </div>
- ))
- )}
- </div>
- </div>
- </div>
- )
- }
- export default SegmentComments
|