实现一个 AI 聊天输入框:从基础到高级的指南

引言

随着人工智能和自然语言处理(NLP)技术的快速发展,基于 AI 的聊天系统变得越来越普遍。无论是简单的问答系统还是复杂的客服聊天机器人,聊天输入框都是用户与系统交互的关键组件。本文将详细介绍如何实现一个功能丰富的 AI 聊天输入框,从基础组件到高级功能,包括用户输入处理、界面设计以及与 AI 模型的集成。

功能介绍

基础功能

  • 输入数据并发送聊天消息;
  • 支持shift+enter换行;

高级功能

  • 表单内容支持:允许嵌套select元素;

实现步骤

实现基础功能

普通的聊天输入框只需要一个简单的input元素即可;

import React, { useState } from 'react';

const ChatInput = ({ onSend }) => {
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (input.trim()) {
      onSend(input);
      setInput('');
    }
  };

  return (
    <div className="chat-input">
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && handleSend()}
      />
      <button onClick={handleSend}>Send</button>
    </div>
  );
};

export default ChatInput;

支持换行的聊天输入框只需要一个简单的textarea元素即可;

import React, { useState } from 'react';

const ChatInput = ({ onSend }) => {
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (input.trim()) {
      onSend(input);
      setInput('');
    }
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      if (e.shiftKey) {
        // 插入换行符
        setInput(input + '\n');
      } else {
        // 发送消息
        e.preventDefault(); // 防止输入框获得默认的按键行为
        handleSend();
      }
    }
  };

  return (
    <div className="chat-input">
      <textarea
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyPress={handleKeyPress}
        rows="3" // 可以根据需要调整行数
      />
      <button onClick={handleSend}>Send</button>
    </div>
  );
};

export default ChatInput;

实现高级功能

表单内容支持:允许嵌套select元素

需要将元素的contenteditable设置为true,最后需要提取元素的字符内容

select元素不能采用受控组件的方式,会有数据同步的问题

export function getSelectValueAndTextContentByDiv(htmlString) {
  const parser = new DOMParser();
  const htmlDocument = parser.parseFromString(htmlString, 'text/html');
  const rootNode = htmlDocument.body;
  const iconCharRegex = /[\uE000-\uF8FF]/g;
  const lines: string[] = [];

  rootNode.childNodes.forEach(child => {
    let lineText = '';

    if (child.nodeType === Node.ELEMENT_NODE) {
      if (child.nodeName === 'SELECT') {
        const selectedOption = child.options[child.selectedIndex].text;
        lineText += selectedOption;
      } else {
        lineText += getTextFromNode(child);
      }
    } else if (child.nodeType === Node.TEXT_NODE) {
      lineText += child.textContent.replace(iconCharRegex, '').trim();
    }
    if (lineText) {
      lines.push(lineText);
    }
  });

  function getTextFromNode(node) {
    if (node.nodeType === Node.TEXT_NODE) {
      return node.textContent.replace(iconCharRegex, '').trim();
    } else if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'SELECT') {
      return node.options[node.selectedIndex].text;
    } else {
      return Array.from(node.childNodes).map(getTextFromNode).join('');
    }
  }

  const result = lines.join('\n');
  return result;
}

同时需要支持输入时换行处理

function insertNewLineAtCaret(el) {
    const selection = window.getSelection();
    if (!el || !selection.rangeCount) return;
    const range = selection.getRangeAt(0);
    let container = range.startContainer;

    let newLineDiv = document.createElement('div');
    newLineDiv.appendChild(document.createElement('br'));

    if (container.nodeType === Node.TEXT_NODE && range.startOffset !== 0 && range.startOffset !== container.length) {
      const secondPart = container.splitText(range.startOffset);
      newLineDiv = document.createElement('div');
      newLineDiv.appendChild(secondPart);
    }

    while (container && container.parentNode !== el) {
      container = container.parentNode;
    }

    if (range.startOffset === 0) {
      el.insertBefore(newLineDiv, container);
    } else {
      el.insertBefore(newLineDiv, container.nextSibling);
    }

    const newRange = document.createRange();
    newRange.selectNodeContents(newLineDiv);
    newRange.collapse(true);
    selection.removeAllRanges();
    selection.addRange(newRange);
  }

目前采用的是最简单基础的方式,之后会分析富文本编辑器,比如 QuillDraft.js实现的方式。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值