前端实现类似ChatGPT聊天功能:Flex布局与JavaScript实战

56

自从去年11月份ChatGPT横空出世以来,其热度一直居高不下。许多开发者也纷纷借助其开放的接口,开发出各种国内版本。本文将从前端工程师的角度,探讨如何实现类似ChatGPT的对话功能。

先来看看最终的效果。这里我将源码放在文章末尾,方便大家获取。

因为这是我写在一个项目中的,单独提出来可能配色效果不同,但功能和做法只要我们掌握了,那么自己想怎么写就怎么写!

页面的布局相对简单。熟悉ChatGPT页面的同学应该知道,其布局主要采用了Flex布局。Flex布局的强大之处在于其灵活性和易用性。我主要基于Bootstrap、jQuery和Flex布局完成了这个简易版的对话功能。其中,有两个关键的地方用到了Flex布局。

Flex布局一:头像和文字的对齐

头像和文字的排列采用Flex布局,并且文字和图片顶部对齐,以避免文字过多时,文字与图片垂直居中的问题。实现这种效果,需要设置以下CSS样式:

display: flex;
align-items: flex-start;

其中,align-items: flex-start; 的作用是确保文字与图片顶部对齐。

Flex布局二(重点):搜索框的实现

第二个用到Flex布局的地方是搜索框。

许多人可能觉得这个对话框很简单。Flex布局确实能轻松实现输入框和按钮在同一行显示。但是,仔细观察ChatGPT的官方网站,你会发现其中有很多细节值得学习。

首先,输入框使用的是<textarea>,而不是<input><input>输入的内容无法换行,而<textarea>文本框可以。但是,使用<textarea>会带来一个问题:如果将rows参数设置为1,文本框的高度会非常小,无法达到ChatGPT页面的视觉效果。如果将rows设置得更大,或者直接设置文本框的高度,又会出现输入时光标不在文本框垂直居中的问题。因此,如何解决这个问题是一个值得探讨的地方。通过观察ChatGPT页面的实现方式,我找到了解决方案。如下图所示:

你只需要将<textarea>的边框取消掉,然后使用:focus伪类取消掉选中时的边框效果,再在外层套一个<div>,并设置边框,将<textarea>文本框和按钮包裹在其中即可。

.ipt {
  display: flex;
  align-items: center;
  position: absolute;
  bottom: 60px;
  margin: 0 15px;
  padding-right: 15px;
  border-radius: 15px;
  width: calc(100% - 30px);
  height: 50px;
  border: 1px solid #e7eaec;
}

.ipt textarea {
  resize: none;
  overflow-y: auto;
  border: none;
  box-shadow: none;
}

.ipt textarea:focus {
  border: none !important;
  box-shadow: none !important;
}

最后,将这个输入框定位到页面底部即可。

JavaScript部分

在页面部分,我们需要将用户的问题以及AI的回复添加到页面上,并在添加消息后向上滚动:

// 添加用户消息到窗口
function addUserMessage(message) {
  var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="' + userIcon + '"><p class="message-text">' + message + '</p></div>');
  chatWindow.append(messageElement);
  chatInput.val('');
  chatWindow.animate({ scrollTop: chatWindow.prop('scrollHeight') }, 500);
}

// 添加回复消息到窗口
function addBotMessage(message) {
  var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="' + botIcon + '"><p class="message-text">' + message + '</p></div>');
  chatWindow.append(messageElement);
  chatInput.val('');
  chatWindow.animate({ scrollTop: chatWindow.prop('scrollHeight') }, 500);
}

这里,消息添加到页面后,会清空输入框的内容。接下来,我们需要给输入框添加一个键盘事件,即点击Enter键也可以发送消息:

// 处理 Enter 键按下
chatInput.keypress(function(e) {
  if (e.which == 13) {
    chatBtn.click();
  }
});

最后,是发送消息与获得消息的部分:

// 处理用户输入
chatBtn.click(function() {
  var message = chatInput.val();
  if (message.length == 0) {
    common_ops.alert("请输入内容!")  // 弹窗
    return
  }
  addUserMessage(message);

  chatBtn.attr('disabled', true) // 消息发送后让提交按钮不可点击

  // 发送信息到后台
  $.ajax({
    url: '/chat',
    method: 'POST',
    data: {
      "prompt": JSON.stringify(message)
    },
    success: function(res) {
      res = JSON.parse(res);
      addBotMessage(res.content);
      chatBtn.attr('disabled', false)  // 成功接受消息后让提交按钮再次可以点击
    },
    error: function(jqXHR, textStatus, errorThrown) {
      addBotMessage('<span style="color:red;">' + '出错啦!请稍后再试!' + '</span>');
      chatBtn.attr('disabled', false)
    }
  });
});

这些逻辑都比较简单,就不再赘述。需要注意的是,在发送消息到后台等待响应的过程中,按钮的状态设置为不可点击,直到后台返回消息才可以进行下一次问答。但是,这里我没有处理键盘事件,也就是说你可以点击Enter键继续向后台发送消息,这是一个潜在的bug。如果不需要这个功能,可以直接去掉键盘事件。当然,也可以在发送消息到获得回答的这段时间内,像禁用发送按钮一样,禁止Enter键盘事件或解绑这个键盘事件。这些都可以自行完成。

完整代码

以下是完整的HTML代码,包含了CSS样式和JavaScript脚本:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="../../static/css/bootstrap.min.css" rel="stylesheet">
  <title>chat</title>
  <style>
    .answer {
      width: 100%;
      position: relative;
      height: 70vh;
    }

    .ipt {
      display: flex;
      align-items: center;
      position: absolute;
      bottom: 60px;
      margin: 0 15px;
      padding-right: 15px;
      border-radius: 15px;
      width: calc(100% - 30px);
      height: 50px;
      border: 1px solid #e7eaec;
    }

    .ipt textarea {
      resize: none;
      overflow-y: auto;
      border: none;
      box-shadow: none;
    }

    .ipt textarea:focus {
      border: none !important;
      box-shadow: none !important;
    }

    #chatWindow {
      max-height: calc(70vh - 120px);
      height: auto;
      overflow-y: auto;
    }

    .message-bubble {
      padding: 10px;
      margin: 5px;
      display: flex;
      align-items: flex-start;
      border-bottom: 1px dashed #e7eaec;
    }

    
  
  程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。