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

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>

效果图:

猜你喜欢

转载自blog.csdn.net/weixin_52479803/article/details/132575564