chatgpt这么火?前端如何实现类似chatgpt的对话页面

📋 个人简介

  • 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者😜
  • 📝 个人主页:馆主阿牛🔥
  • 🎉 支持我:点赞👍+收藏⭐️+留言📝
  • 📣 系列专栏:前端实用小demo🍁
  • 💬格言:迄今所有人生都大写着失败,但不妨碍我继续向前!🔥

请添加图片描述

目录

    • 📋 个人简介
  • 前言
    • 最终效果
    • 页面布局
      • flex布局一
      • flex布局二(重点)
    • js部分
    • 完整代码
  • 结语

前言

自从去年11月份chatgpt出圈之后,他的热度就居高不减,也出现了很多人借助接口开发的国内版本,那么本篇博客就从前端的角度来看看前端如何实现类似chatgpt的对话功能!

最终效果

源码在文末获取!

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

页面布局

这一块比较简单,分析过chatgpt的页面的就会知道,他的页面布局方式是采用flex布局的,flex布局确实好用,那么我也是基于Bootsrap+jquery+flex布局完成了简易版的对话功能!主要有两个地方用到了flex布局!

flex布局一

在这里插入图片描述
这里的头像和文字采用的就是flex布局,并且文字和图片顶部对齐,防止文字较多依旧和图片中间对齐的bug。
需要设置css:

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

其中align-items: flex-start;的作用就是让文字与图片顶部对齐!

在这里插入图片描述

flex布局二(重点)

第二处用到flex布局的地方就是这个搜索框:
在这里插入图片描述
很多人觉得这个对话框很简单,flex布局实现输入框和按钮在同一行确实简单,但你要好好看看chatgpt的官网,都是有小细节的,这里面还是有很多知识点的。

首先,我要说的是这个输入框用的textarea,而不是input,区别在于,input输入的内容是不能换行的,但textarea文本框可以,但使用textarea的问题是,参数rows设置为一行,这个文本框的高度会很低,达不到chatgpt的那个页面要求,rows设置大一点或者这个文本框的高度给高一点会有一个问题就是输入时他的光标不会在文本框的高度中间,而是在第一行,我们是没法通过其他方式让输入光标垂直居中的,因此这也不符合chatgpt页面的要求,所以这确实是个值的学习的一点!看了chatgpt页面的做法后,我悟了,下面一张图来说明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;
}

最后,将这个输入框定位到页面底部就好!

js部分

首先,页面部分,我们添加消息到页面,包括用户的问题以及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键盘事件或者解绑这个键盘事件,这个你们自己去完成,这里我不在多说(总要留点东西让你们自己去思考去感悟!)

完整代码

<!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;
        }
    
    
        .message-bubble p {
            font-size: 18px;
            margin-left:15px;
        }
    
        .chat-icon {
            width: 30px;
            height: 30px;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <div>
      <div class="row">
          <div class="col-xs-12">
            <div>
              <h1 class="text-center m-b-lg">Chat with ChatGPT</h1>
            </div>
            <div class="answer">
              <div id="chatWindow" class="mb-3"></div>
              <div class="input-group ipt">
                <div class="col-xs-12">
                  <textarea id="chatInput" class="form-control" rows="1"></textarea>
                </div>
                <button id="chatBtn" class="btn btn-primary" type="button">Go !</button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div> 
</body>
<script src="../../static/plugins/jquery-2.1.1.js"></script>
<script src="../../static/js/bootstrap.min.js"></script>
<script src="../../static/plugins/layer/layer.js"></script>
<script src="../../static/js/common.js"></script>
<script>
    $(document).ready(function() {
        var chatBtn = $('#chatBtn');
        var chatInput = $('#chatInput');
        var chatWindow = $('#chatWindow');
        var userIcon = '/static/images/user/{{ current_user.avatar }}'
        var botIcon = '/static/images/aichat/chatgpt.png';
        

        // 添加用户消息到窗口
        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);
        }

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

          // messages.push({"role": "user", "content": 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 键按下
        chatInput.keypress(function(e) {
          if (e.which == 13) {
            chatBtn.click();
          }
        });
  });
</script>
</html>

这里面用到的layer.js就是一个弹窗组件,百度可以搜到,common.js是我自己对layer.js方法的封装,这个页面其实你不这两个js文件也行,因为整个页面只有下面的代码用到了弹窗:

if (message.length == 0){
 	common_ops.alert("请输入内容!")    
  	return  
}

其实简陋点,一个alert就搞定了!

结语

如果你觉得博主写的还不错的话,可以订阅下面的这个flask专栏,这是博主唯一的付费专栏,我做的这个页面也是最近项目的一部分,这个项目也是用flask做的,我会将他全部总结开源到这个flask专栏中。

【flask从入门到实战】专栏9.9火热订阅中,已包含两个项目,全站独一无二的脚手架搭建,直接复制简单无脑操作,项目结构类似Django,感兴趣的可以看看哦!

🏰系列专栏
👉flask框架快速入门

其他专栏请前往博主主页查看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/101.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码看不懂?ChatGPT 帮你解释,详细到爆!

偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f;网上看到一段代码&#xff0c;不知道是什么含义&#xff1f;输入 ChatGPT 帮我们解释下。生信宝典 1: 下面是一段 Linux 代码&#xff0c;请帮…

Linux命令之nano命令

一、nano命令简介 nano是一个小型、免费、友好的编辑器&#xff0c;旨在取代非免费Pine包中的默认编辑器Pico。nano不仅复制了Pico的外观&#xff0c;还实现了Pico中一些缺失&#xff08;或默认禁用&#xff09;的功能&#xff0c;例如“搜索和替换”和“转到行号和列号”。nan…

【面试题】如何避免使用过多的 if else?

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库一、引言相信大家听说过回调地狱——回调函数层层嵌套&#xff0c;极大降低代码可读性。其实&#xff0c;if-else层层嵌套&#xff0c;如下图…

iOS-砸壳篇(两种砸壳方式)

CrackerXI砸壳呢&#xff0c;当时你要是使用 frida-ios-dump 也是可以的&#xff1b; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的&#xff1a;手机中的内网ip 密码 等 最后放到我的砸壳路径里&#xff1a; python dump.py -l查看应用…

【答疑现场】我一个搞嵌入式的,有必要学习Python吗?

【答疑现场】我一个搞嵌入式的&#xff0c;有必要学习Python吗&#xff1f; 文章目录1 写在前面2 一个结论3 Python在嵌入式领域能干啥事4 Python是用来干大事的5 友情推荐6 福利活动大家好&#xff0c;我是架构师李肯&#xff0c;一个专注于嵌入式物联网系统架构设计的攻城狮。…

【蓝桥杯嵌入式】ADC模数转换的原理图解析与代码实现(以第十一届省赛为例)——STM32G4

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

Linux--多线程(1)

目录 一、概念 二、理解 三、创建、退出、合并进程 //man pthread_create //Compile and link with -pthread. //1.为什么没有fun函数&#xff1f; //2.加上sleep来改进 //3.线程结束会不会影响主线程运行&#xff1f; //4.那如果主线程比较少呢&#xff1f; 四、如何…

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…

RecyclerView流程学习

RecyclerView流程学习模块划分绘制流程onMeasuremLayout为nullmLayout开启自动测量未开启自动测量onLayoutonDrawonLayoutChildren缓存预加载滚动和fling模块划分 RecyclerView中根据其功能可以分为以下几个模块&#xff1a; Recycler mRecycler // 缓存管理者&#xff0c;fi…

yolov5的基本配置

yolov5的基本配置train.pydata.yaml数据集标签文件格式:总结train.py def parse_opt(knownFalse):parser argparse.ArgumentParser()parser.add_argument(--weights, typestr, defaultROOT / yolov5s.pt, helpinitial weights path)parser.add_argument(--cfg, typestr, defau…

uniCloud在线升级APP配置教程

app在线升级背景实现思路流程流程背景 因用户需要添加手机h5页面来进数据操作实现思路流程 实现流程图流程 相关文档&#xff1a;帮助文档 https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html 注册服务空间 https://unicloud.dcloud.net.cn/pages/login/login uni升级…

基于Yolv5s的口罩检测

1.Yolov5算法原理和网络结构 YOLOv5按照网络深度和网络宽度的大小&#xff0c;可以分为YO-LOv5s、YOLOv5m、YOLOv5l、YOLOv5x。本文使用YOLOv5s&#xff0c;它的网络结构最为小巧&#xff0c;同时图像推理速度最快达0.007s。YO-LOv5的网络结构主要由四部分组成&#xff0c;分别…

三天吃透MySQL八股文(2023最新整理)

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

博客系统(界面设计)

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录实现博客列表页预期效果导航栏页面主体左右布局左侧区域右侧区域完整代码实现博客详情页预期效果导航栏 左侧右侧完整代码实现…

全国程序员薪酬大曝光!看完我酸了····

2023年&#xff0c;随着互联网产业的蓬勃发展&#xff0c;程序员作为一个自带“高薪多金”标签的热门群体&#xff0c;被越来越多的人所关注。在过去充满未知的一年中&#xff0c;他们的职场现状发生了一定的改变。那么&#xff0c;程序员岗位的整体薪资水平、婚恋现状、职业方…

认识TomcatMavenServlet第一个Servlet程序

文章目录一、什么是Tomcat、什么是Servlet二、Tomcat的下载与使用关于下载启动欢迎页面查看可能出现的问题博客系统静态页面的部署三、什么是Maven四、第一个servlet程序1.创建Maven项目2.引入依赖3.创建目录结构4.编写程序5.打包程序6.部署程序7.验证小结五、servlet程序简化版…

学习 Python 之 Pygame 开发魂斗罗(四)

学习 Python 之 Pygame 开发魂斗罗&#xff08;四&#xff09;继续编写魂斗罗1. 创建子弹类2. 根据玩家方向和状态设置子弹发射的位置(1). 站立向右发射子弹(2). 站立向左发射子弹(3). 站立朝上发射子弹(4). 蹲下发射子弹(5). 向斜方发射子弹(6). 奔跑时发射子弹(7). 跳跃时发射…

图片的美白与美化

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

Python雪花代码

前言 用python画个雪花玩玩&#xff0c;源码在文末公众号哈。 雪花类 class Snow(): #雪花类 def __init__(self): self.r 6 #雪花的半径 self.x ra.randint(-1000,1000) #雪花的横坐标 self.y ra.randint(-500,5…

读书笔记——《富爸爸穷爸爸》

《富爸爸穷爸爸》&#xff0c;以前不屑读这种书。这种书就是那种走进书店放在门口展销位的成功学著作&#xff0c;一眼看上去没什么实在的内容&#xff0c;看上去很不靠谱&#xff0c;感觉就是骗一些社会底层又做着暴富梦的人来买的&#xff0c;但是由于自身原因或环境局限根本…