【gtpJavaScript】使用JavaScript实现套壳gtp与gtp打字输出效果

news2024/12/23 18:13:06

postman测试gtp接口

https://platform.openai.com/docs/api-reference/chat/create?lang=curl

导入到postman中

记得弄一个gtp的key

然后请求测试gtp接口:

纯前端实现gtp请求页面 

目录结构:

 

部分参考:GitHub - xxxjkk/chat-website: 简易版chat网站,拿来即用,静态部署 

 index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <script>
    var password = ""
    var realpassword = atob("NjY4OA==")
    password = prompt('请输入密码 (本网站需输入密码才可进入):', '')
    if (password != realpassword) {
      alert("密码不正确,无法进入本站!!")
      // 密码不正确就关闭
      open(location, '_self').close()
    }  
  </script>

  <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 rel="icon" type="images/x-icon" href="./static/images/favicon.ico">
  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="./static/css/bootstrap.min.css">
  <link rel="stylesheet" href="./static/css/style.css">
  <title>ac-chat</title>
  <style>
    #output {
      display: inline;
    }

    .cursor {
      display: inline-block;
      width: 10px;
      height: 20px;
      background-color: black;
      vertical-align: text-bottom;
      animation: blink 1s infinite;
    }

    @keyframes blink {
      50% {
        opacity: 0;
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-xs-12">
        <div class="title">
          <h2 class="text-center">ChatGTP</h2>
        </div>

        <div class="key">
          <div class="input-group col-sm-6">
            <span class="input-group-addon">
              <input type="checkbox" class="ipt-1">
            </span>
            <input type="password" class="form-control ipt-2" placeholder="使用自己的api key">
          </div>
        </div>

        <div class="answer">
          <div class="tips text-center">
            <h3 class="lead">仅做技术研究探讨使用!</h3>
          </div>
          <div id="chatWindow"></div>
          <div class="input-group ipt">
            <div class="col-xs-12">
              <textarea id="chatInput" class="form-control" rows="1" style="min-height: 40px;"></textarea>
            </div>
            <button id="chatBtn" class="btn btn-primary" type="button">Go !</button>
          </div>
        </div>
      </div>
    </div>

    <div class="row foot">
      <footer class="col-xs-12" style="margin-top: 10px;">
        <p class="lead text-center">“抢走工作的不会是AI,而是率先掌握AI能力的人”</p>
      </footer>
    </div>
  </div>

</body>

<script src="./static/js/jquery-2.1.1.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js"></script>
<script src="./static/js/bootstrap.min.js"></script>
<script src="./static/js/layer/layer.js"></script>
<script src="./static/js/custom.js"></script>

</html>

custom.js

// 封装弹窗layer组件等
var common_ops = {
  // 封装layer.alert(content, options, yes) - 普通信息框
  alert: function (msg, cb) {
    layer.alert(msg, {
      yes: function (index) {
        if (typeof cb == "function") {
          cb();
        }
        layer.close(index);
      }
    });
  },
  // 封装layer.confirm(content, options, yes, cancel) - 询问框
  confirm: function (msg, callback) {
    callback = (callback != undefined) ? callback : { 'ok': null, 'cancel': null };
    layer.confirm(msg, {
      btn: ['确定', '取消']
    }, function (index) {
      //确定事件
      if (typeof callback.ok == "function") {
        callback.ok();
      }
      layer.close(index);
    }, function (index) {
      //取消事件
      if (typeof callback.cancel == "function") {
        callback.cancel();
      }
      layer.close(index);
    });
  }
};

$(document).ready(function () {
  // 查询按钮
  var chatBtn = $('#chatBtn');
  // 查询内容
  var chatInput = $('#chatInput');
  $("#chatInput").resizable();
  // 中间内容
  var chatWindow = $('#chatWindow');

  // 存储对话信息,实现连续对话
  var messages = []

  // 移除加载效果
  function deleteLoading() {
    chatWindow.find('#loading').remove();
  }


  // 将 HTML 字符串转义为纯文本
  function escapeHtml(html) {
    var text = document.createTextNode(html);
    var div = document.createElement('div');
    div.appendChild(text);
    return div.innerHTML;
  }

  // 创建输入的文本
  function addLoading() {
    // 隐藏 “仅做技术研究探讨使用!”
    $(".answer .tips").css({ "display": "none" });
    // 输入框清空
    chatInput.val('');
    // 加载动画
    var messageElement = $('<div id="loading" class="row message-bubble"><img class="chat-icon" src="./static/images/chatgpt.png"><p class="message-text"><img src="./static/images/loading-1.gif" alt="加载动画"></p></div>');
    chatWindow.append(messageElement);
  }

  function scrollToBottom(id) {
    var element = document.getElementById(id);
    element.scrollTop = element.scrollHeight;
  }


  // 添加消息到窗口 用户跟gtp文本消息
  function addMessage(message, imgName) {
    $(".answer .tips").css({ "display": "none" });
    chatInput.val('');
    var escapedMessage = escapeHtml(message);
    var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="./static/images/' + imgName + '"><p class="message-text">' + escapedMessage + '</p></div>');
    chatWindow.append(messageElement);
  }

  // 添加消息到窗口 自定义添加消息(异常啥的)
  function addFailMessage(message) {
    $(".answer .tips").css({ "display": "none" });
    chatInput.val('');
    var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="./static/images/chatgpt.png"><p class="message-text">' + message + '</p></div>');
    chatWindow.append(messageElement);
  }

  // 处理用户输入
  chatBtn.click(function () {
    // 解绑键盘事件 回车之后解绑,防止未获得结果时 又发一个请求
    chatInput.off("keydown", handleEnter);

    // 保存api key与对话数据
    var data = {
      "apiKey": "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", // 这里填写固定 apiKey
    }

    // 判断是否使用自己的api key
    if ($(".key .ipt-1").prop("checked")) {
      var apiKey = $(".key .ipt-2").val();
      if (apiKey.length < 20) {
        common_ops.alert("请输入正确的 api key !", function () {
          chatInput.val('');
          // 重新绑定键盘事件
          chatInput.on("keydown", handleEnter);
        })
        return
      } else {
        data.apiKey = apiKey
      }
    }

    var message = chatInput.val();
    if (message.length == 0) {
      common_ops.alert("请输入内容!", function () {
        chatInput.val('');
        // 重新绑定键盘事件
        chatInput.on("keydown", handleEnter);
      })
      return
    }

    // 创建用户对话行
    addMessage(message, "avatar.png");

    // 将用户消息保存到数组
    messages.push({ "role": "user", "content": message })

    // 收到回复前让按钮不可点击
    chatBtn.attr('disabled', true)

    data.prompt = messages

    // 出现loading动画
    addLoading();

    // 发送信息到后台
    $.ajax({
      url: 'https://open.aiproxy.xyz/v1/chat/completions',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + data.apiKey
      },
      data: JSON.stringify({
        "messages": data.prompt,
        "model": "gpt-3.5-turbo",
        "max_tokens": 2048,
        "temperature": 0.5,
        "top_p": 1,
        "n": 1
      }),
      success: function (res) {
        const resp = res["choices"][0]["message"];
        // 创建回复对话行
        addMessage(resp.content, "chatgpt.png");
        // 收到回复,让按钮可点击
        chatBtn.attr('disabled', false)
        // 重新绑定键盘事件
        chatInput.on("keydown", handleEnter);
        // 去除loading动画
        deleteLoading()
        // 将回复添加到数组
        messages.push(resp)
      },
      error: function (jqXHR, textStatus, errorThrown) {
        // 去除loading动画
        deleteLoading()

        addFailMessage('<span style="color:red;">' + '出错啦!请稍后再试!' + '</span>');
        chatBtn.attr('disabled', false)
        chatInput.on("keydown", handleEnter);
        messages.pop() // 失败就让用户输入信息从数组删除
      }
    });
  });

  // Enter键盘事件
  function handleEnter(e) {
    if (e.keyCode == 13) {
      chatBtn.click();
    }
  }

  // 绑定Enter键盘事件
  chatInput.on("keydown", handleEnter);

  // 禁用右键菜单
  document.addEventListener('contextmenu',function(e){
    e.preventDefault();  // 阻止默认事件
  });

  // 禁止键盘F12键
  document.addEventListener('keydown',function(e){
    if(e.key == 'F12'){
        e.preventDefault(); // 如果按下键F12,阻止事件
    }
  });
});

现在请求到数据之后是一下子全部显示,纯前端如何实现一字一字输出的打字效果呢?

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    
<meta name="referrer" content="no-referrer" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>打字效果</title>
    <style>
        #output {
            display: inline;
        }

        .cursor {
            display: inline-block;
            width: 10px;
            height: 20px;
            background-color: black;
            vertical-align: text-bottom;
            animation: blink 1s infinite;
        }

        @keyframes blink {
            50% {
                opacity: 0;
            }
        }
    </style>
</head>

<body>
    <h1>ChatGPT Typing Effect</h1>
    <div id="output"></div><span class="cursor" id="cursor"></span>
    <div id="givenText" style="display: none;">
        <strong>加粗文本</strong><br>
        <em>斜体文本</em><br>
        <u>下划线文本</u><br>
        <span style="color: red;">红色文本</span><br>
        <span style="font-size: 24px;">大字体文本</span><br>
        <a href="https://github.com/azhu021/">链接示例</a>
    </div>

    <script>
        const outputElement = document.getElementById("output");
        const cursorElement = document.getElementById("cursor");
        const givenTextElement = document.getElementById("givenText");
        const givenText = givenTextElement.innerHTML;
        let currentIndex = 0;
        let currentHTML = "";

        function typeText() {
            if (currentIndex < givenText.length) {
                const currentChar = givenText.charAt(currentIndex);
                if (currentChar === "<") {
                    const closingTagIndex = givenText.indexOf(">", currentIndex);

                    currentHTML += givenText.slice(currentIndex, closingTagIndex + 1);
                    currentIndex = closingTagIndex + 1;
                } else {
                    currentHTML += currentChar;
                    currentIndex++;
                }
                outputElement.innerHTML = currentHTML;
                setTimeout(typeText, 100); // 设置打字速度,单位为毫秒
            } else {
                // 当打印完成时,移除光标的闪烁效果
                cursorElement.classList.remove("cursor");
            }
        }
        typeText();
    </script>
</body>

</html>

将其效果移植到custom.js中

//XXXXXXXXXXXXXXXXXXXXXXXX

  let currentIndex = 0;
  let currentHTML = "";
  function addMessageTwo(id, message) {
    if (currentIndex < message.length) {
      currentHTML = ''
      currentHTML += message.slice(0, currentIndex + 1);

      $(`#${id}`).text(currentHTML)
      currentIndex++
      setTimeout(() => addMessageTwo(id, message), 100);
    } else {
      currentIndex = 0
    }
  }
  // 处理用户输入
  chatBtn.click(function () {

//XXXXXXXXXXXXXXXXXXXXXXXX

    // 发送信息到后台
    $.ajax({
      url: 'https://open.aiproxy.xyz/v1/chat/completions',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + data.apiKey
      },
      data: JSON.stringify({
        //XXXXXXXXXXXXXXXXXXXXXXXX
      }),
      success: function (res) {
        const resp = res["choices"][0]["message"];
        // 创建回复对话行
        // addMessage(resp.content, "chatgpt.png");
        $(".answer .tips").css({ "display": "none" });
        chatInput.val('');
        var escapedMessage = escapeHtml(resp.content);
        var messageElement = $('<div class="row message-bubble"><img class="chat-icon" src="./static/images/chatgpt.png"><p id=' + res["id"] + ' class="message-text"></p></div>');
        chatWindow.append(messageElement);
        addMessageTwo(res["id"], escapedMessage)

        //XXXXXXXXXXXXXXXXXXXXXXXX
      },
    });
  });

  //XXXXXXXXXXXXXXXXXXXXXXXX

上述通过前端的js实现一字一字打字输出效果,但还有问题:请求完获取数据之后才开始一字一字输出,如何返回的文本过长 需要等待很久,显然这种方式不行,那有没有那种实时的逐字输出呢? SSE

SSE(Sever-sent Events) 

服务器发送事件(Server-sent Events,简称 SSE)是一种在客户端浏览器和服务器之间进行单向通信的 Web 技术。它允许服务器向客户端推送数据,而不需要客户端主动请求。

SSE(Server-sent Events)和 WebSocket 的区别

单向 vs 双向通信

  • SSE 是一种单向通信机制,只能服务器向客户端发送数据。客户端无法主动向服务器发送消息。
  • WebSocket 是一种双向通信机制,允许客户端和服务器之间进行双向实时通信。客户端和服务器都可以主动发送和接收消息。

连接建立

  • SSE 基于传统的 HTTP 协议,连接通过 HTTP 请求建立,并保持长时间打开。因此,SSE 连接始终由客户端发起。
  • WebSocket 是一种独立的协议,它在创建连接时需要使用特殊的 WebSocket 握手协议。WebSocket 连接可以由客户端或服务器发起。

数据格式

  • SSE 使用简单的文本格式或者 JSON 格式来传输数据。服务器以文本块的形式将数据发送给客户端。
  • WebSocket 可以传输任意格式的数据,例如文本、二进制数据等。

app.js

const express = require('express');
const app = express()
const router = express.Router();

app.use((req, res, next) => {
	res.setHeader('Access-Control-Allow-Origin', '*');
	res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
	next();
});

router.get('/sse', (req, res) => {

	res.setHeader('Content-Type', 'text/event-stream');
	res.setHeader('Cache-Control', 'no-cache');
	res.setHeader('Connection', 'keep-alive');

	const answer = '众所周知,ChatGPT API 是一个OpenAI 的聊天机器人接口,它可以根据用户的输入生成智能的回复,为了提高聊天的流畅性和响应速度,采用流失输出的响应方式,类似打字机的呈现效果';

	let i = 0;
	const intervalId = setInterval(() => {
		res.write('data:' + answer[i] + '\n\n');
		i++;
		if (i == answer.length) {
			clearInterval(intervalId);
			res.write('event:end\ndata: \n\n');  
		}
	}, 100);
});
app.use('/', router)
app.listen(3333, function () {
	console.log('api server running at http://127.0.0.1:3333')
})  

index.html

<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer" />

<head>
    <title>SSE Example</title>
</head>

<body>
    <h1>SSE Example</h1>
    <button id="startButton">开始</button>
    <div id="output">回答:</div>
    <script>
        const startButton = document.getElementById('startButton');
        const outputElement = document.getElementById('output');
        startButton.addEventListener('click', function () {
            let eventSource = new EventSource('http://172.21.2.52:3333/sse');
            eventSource.onopen = function (event) {
                console.log('成功')
            };
            eventSource.onmessage = function (event) {
                const message = event.data;
                outputElement.innerHTML += message;
            };
        });
    </script>
</body>

</html>

效果图:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

德庄借助纷享销客CRM系统实现高效管理

德庄集团创于1999年&#xff0c;是一家集餐饮产业、食品产业、科技研发及文化研究为一体的现代化民营企业&#xff0c;下属9家子公司、2大现代化食品加工基地、1所研究所、1所培训学校、1个技术中心。拥有德庄、青一色、滟设、香漫谷、饭空等8大子品牌&#xff0c;呈现出良好的…

基于Python的大区域SPI标准降水指数自动批量化处理

1.引言 标准化降水指数&#xff08;SPI&#xff09;是一个广泛使用的指数&#xff0c;用于描述一系列时间尺度上的气象干旱的特征。但是经过研究发现&#xff0c;目前的处理方法基本都是单点进行计算&#xff0c;缺少多点&#xff08;大区域&#xff09;的批量计算过程。因此本…

嵌入式Linux开发实操(十六):Linux驱动模型driver model

嵌入式linux下驱动模型: 1、驱动的绑定 驱动程序绑定driver binding 驱动程序绑定是将设备device与可以控制它的设备驱动程序driver相关联的过程。总线驱动程序bus driver通常会处理,因为有特定于总线bus的结构来表示设备device和驱动程序driver。使用通用的设备device和设…

es倒排索引深入解读

文章目录 一. Lucene二.倒排索引算法2.1 Posting List压缩算法2.1.1 FOR2.1.2 RoaringBitmap压缩 2.3 FST压缩算法2.3.1 trie前缀树原理2.3.2 FST构建过程NFADFAFSMFSAFST:有限状态转换机构建原理FST在lucene中实现原理 1.什么是搜索引擎? 全文搜索引擎: 自然语言处理(NLP)、爬…

Full authentication is required to access this resource解决办法

我们在使用postman调接口时候&#xff0c;有的时候需要权限才可以访问&#xff0c;否则可能会报下面这个错误 {"timestamp": xxxxxx,"status": 401,"error": "Unauthorized","message": "Full authentication is requ…

Matlab信号处理1:模拟去除信号噪声

由于工作内容涉及信号系统、信号处理相关知识&#xff0c;本人本硕均为计算机相关专业&#xff0c;专业、研究方向均未涉及信号相关知识&#xff0c;因此需进行系统地学习。之前已将《信号与系统》快速过了一遍&#xff0c;但感觉较抽象且理解较浅显。在此系统地学习如何使用Ma…

PTA L1-011 A-B C++解法

我的答案 #include<iostream> #include <string> using namespace std;int main() {//先用数组去存储输入的A和B&#xff0c;然后遍历数组A&#xff0c;B&#xff0c;相同的字母去除&#xff0c;不同的字母留下&#xff0c;最后输出string A, B;getline(cin, A);g…

Nomad 系列-安装

系列文章 Nomad 系列文章 Nomad 简介 开新坑&#xff01;近期算是把自己的家庭实验室环境初步搞好了&#xff0c;终于可以开始进入正题研究了。 首先开始的是 HashiCorp Nomad 系列&#xff0c;欢迎阅读。 关于 Nomad 的简介&#xff0c;之前在 大规模 IoT 边缘容器集群管…

java IO流(二) 字符流 缓冲流 原始流与缓冲流性能分析

字符流 前面学习的字节流虽然可以读取文件中的字节数据&#xff0c;但是如果文件中有中文&#xff0c;使用字节流来读取&#xff0c;就有可能读到半个汉字的情况&#xff0c;这样会导致乱码。虽然使用读取全部字节的方法不会出现乱码&#xff0c;但是如果文件过大又不太合适。…

递归入门,例题详解,汉诺塔问题,全排列问题,整数划分问题,两数相加

问题一&#xff1a;阶乘 对于阶乘n!&#xff0c;也就是从1一直乘到n&#xff0c;我们可以很简单的使用一个for循环来解决这个问题&#xff0c;但是如果使用递归的思路&#xff0c;那么我们需要思考如果将当前的问题分解为规模更小的问题&#xff0c;对于n的阶乘&#xff0c;我…

论文解读 | KPConv——点云上的可形变卷积网络

原创 | 文 BFT机器人 《KPConv: Flexible and Deformable Convolution for Point Clouds》是一篇发表于2019年的研究论文&#xff0c;作者为Hugues Thomas、Charles R. Qi、Jean-Emmanuel Deschaud、Beatriz Marcotegui和Franois Goulette。这篇论文关注于点云数据上的卷积操作…

前置微小信号放大器是什么

前置微小信号放大器是一种专门用于放大微弱输入信号的电子设备。它常用于电子测量、信号传输、音频放大等领域&#xff0c;能够将微小的输入信号放大到足够大的幅度&#xff0c;以便后续处理或传输。下面我们将从工作原理、应用和发展趋势三个方面&#xff0c;详细探讨前置微小…

在国内PMP的含金量高吗?

首先我们需要了解一下PMP证书的用处 PMP含金量是毋庸置疑的&#xff0c;从事项目/产品/运营/管理/IT行业的社会人基本都会将这个证收入囊中。其他有升职涨薪计划的也在悄咪咪报考蓄力中&#xff0c;能在职业生涯锦上添花&#xff0c;精益求精。 一&#xff0c;PMP证书的优势体…

什么是安全运营中心(SOC),应该了解什么

安全运营中心&#xff08;SOC&#xff09; 是一种企业监视和警报设施&#xff0c;可帮助组织检测安全威胁、监视安全事件和分析性能数据以改进公司运营。 什么是安全运营中心&#xff08;SOC&#xff09; 安全运营中心&#xff08;SOC&#xff09;是一个中央监视和监视中心&a…

安装气象站的重要意义是什么?

气象站是一种用于观测、记录和报告天气数据的设备或系统。它通常包括各种传感器、供电系统和环境监控主机&#xff0c;用于测量和记录气温、湿度、风速、风向、气压、降雨量、雪深等气象参数。 气象站有多种类型&#xff0c;包括自动气象站、人工气象站和便携式气象站。自动气…

elementUI可拖拉宽度抽屉

1&#xff0c;需求&#xff1a; 在elementUI的抽屉基础上&#xff0c;添加可拖动侧边栏宽度的功能&#xff0c;实现效果如下&#xff1a; 2&#xff0c;在原组件上添加自定义命令 <el-drawer v-drawerDrag"left" :visible.sync"drawerVisible" direc…

【狂神】SpringMVC 怎样才能直接手动输入.jsp的页面就可以访问了?

看秦老师视频的时候&#xff0c;觉得非常疑惑&#xff0c;为什么可以直接输入form.jsp就能跳转到相应地页面。如果你和我一样眼瞎&#xff0c;那确实是有点崩溃。注意看&#xff1a; 发现了吗&#xff1f;这几个文件并没有放在WEB-INF文件下&#xff0c;所以视图解析器便不生效…

实用技巧:使用Python进行文本处理

引言 作为一个Linux持续学习者&#xff0c;我们经常需要处理文本文件&#xff0c;例如提取特定内容、格式化数据或者进行文本分析等。在这篇文章中&#xff0c;我将介绍使用Python进行文本处理的一些实用技巧&#xff0c;帮助你更有效地处理文本数据。无需担心&#xff0c;你不…

pdf可以编辑修改内容吗?看看这几种编辑方法

pdf可以编辑修改内容吗&#xff1f;PDF文件是一种广泛使用的文件格式&#xff0c;但是有时候我们需要对PDF文件进行编辑和修改。那么&#xff0c;PDF文件可以编辑修改内容吗&#xff1f;答案是肯定的。下面介绍几种编辑PDF文件的方法。 第一种方法是使用【迅捷PDF编辑器】。 这…

java JUC并发编程 第六章 CAS

系列文章目录 第一章 java JUC并发编程 Future: link 第二章 java JUC并发编程 多线程锁: link 第三章 java JUC并发编程 中断机制: link 第四章 java JUC并发编程 java内存模型JMM: link 第五章 java JUC并发编程 volatile与JMM: link 第六章 java JUC并发编程 CAS: link 文章…