You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ReactJS实现类Dialogflow的文本选中标注弹窗功能

React实现文本选中标注关联实体功能(仿Dialogflow效果)

核心需求拆解

实现类似Dialogflow的文本标注功能:选中句子中的目标文本片段后,在选中区域附近弹出弹窗,选择对应实体(如sys.date、sys.time)后,该文本会被标记为关联实体。

实现步骤与代码示例

1. 文本选中监听与区域定位

首先监听用户的文本选中操作,获取选中内容和位置,用于弹窗定位:

import { useState, useEffect, useRef } from 'react';

const TextAnnotator = () => {
  const textRef = useRef(null);
  const [selectedText, setSelectedText] = useState('');
  const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, show: false });
  const [annotations, setAnnotations] = useState([]);

  // 监听文本选中事件
  useEffect(() => {
    const handleSelection = () => {
      const selection = window.getSelection();
      if (!selection || selection.isCollapsed) {
        setPopupPosition(prev => ({ ...prev, show: false }));
        return;
      }

      const selectedContent = selection.toString().trim();
      if (!selectedContent) return;

      // 获取选中区域的位置信息
      const range = selection.getRangeAt(0);
      const rect = range.getBoundingClientRect();
      setSelectedText(selectedContent);
      setPopupPosition({
        top: rect.bottom + window.scrollY + 8,
        left: rect.left + window.scrollX,
        show: true
      });
    };

    window.addEventListener('selectionchange', handleSelection);
    return () => window.removeEventListener('selectionchange', handleSelection);
  }, []);

2. 弹窗渲染与实体关联逻辑

弹窗展示可选实体列表,点击实体后将选中文本与实体关联,更新标注状态:

// 关联实体的处理函数
  const handleEntitySelect = (entity) => {
    // 避免重复标注相同文本片段
    const existingAnnotation = annotations.find(anno => anno.text === selectedText);
    if (existingAnnotation) return;

    setAnnotations(prev => [...prev, { text: selectedText, entity }]);
    setPopupPosition(prev => ({ ...prev, show: false }));
    window.getSelection().removeAllRanges();
  };

  // 渲染带标注的文本
  const renderAnnotatedText = () => {
    let text = "I will arrive tomorrow at 5pm";
    annotations.forEach(anno => {
      const highlighted = `<span style="background: #ffeb3b; padding: 2px 4px; border-radius: 3px;" title="${anno.entity}">${anno.text}</span>`;
      text = text.replace(new RegExp(anno.text, 'g'), highlighted);
    });
    return <div ref={textRef} dangerouslySetInnerHTML={{ __html: text }} />;
  };

3. 完整组件渲染

将文本区域和弹窗整合到组件中:

return (
    <div style={{ padding: '20px', position: 'relative' }}>
      <h3>待标注文本</h3>
      {renderAnnotatedText()}

      {/* 实体选择弹窗 */}
      {popupPosition.show && (
        <div 
          style={{
            position: 'absolute',
            top: popupPosition.top,
            left: popupPosition.left,
            background: '#fff',
            border: '1px solid #ddd',
            borderRadius: '4px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
            padding: '8px 0',
            minWidth: '150px'
          }}
        >
          <div style={{ padding: '6px 12px', cursor: 'pointer' }} onClick={() => handleEntitySelect('sys.date')}>
            @sys.date
          </div>
          <div style={{ padding: '6px 12px', cursor: 'pointer' }} onClick={() => handleEntitySelect('sys.time')}>
            @sys.time
          </div>
        </div>
      )}
    </div>
  );
};

export default TextAnnotator;

关键细节说明

  • 文本渲染:使用dangerouslySetInnerHTML处理带标注的文本,实际项目中可考虑用更安全的文本拆分拼接方式,避免XSS风险。
  • 选区处理:监听selectionchange事件覆盖全局选中操作,若仅需监听特定文本区域,可在事件中判断选区是否属于目标元素。
  • 弹窗定位:通过getBoundingClientRect()获取选中区域的绝对位置,结合window.scrollY/X处理滚动偏移。

内容的提问来源于stack exchange,提问作者Bhavika Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 06:36:27