[gtp&JavaScript] Utilice JavaScript para lograr efectos de salida de escritura gtp y gtp sin cáscara

cartero prueba la interfaz gtp

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

Importar al cartero

Recuerde obtener una clave gtp

Luego solicite probar la interfaz gtp:

Implementación pura de front-end de la página de solicitud gtp 

Estructura de directorios:

 

Referencia parcial: GitHub - xxxjkk/chat-website: versión simple del sitio web de chat, lista para usar, implementación estática 

 índice.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>

personalizado.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,阻止事件
    }
  });
});

Ahora, después de solicitar los datos, se muestran todos a la vez. ¿Cómo puede el front-end puro lograr el efecto de escritura de generar palabra por palabra?

<!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>

Portar sus efectos a 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

El efecto mencionado anteriormente de escribir palabra por palabra se logra a través del front-end js, pero todavía hay un problema: la salida palabra por palabra se inicia después de que se solicitan los datos y lleva mucho tiempo. para devolver el texto si es demasiado largo. Obviamente este método no funciona, por lo que hay ¿Qué pasa si no hay una salida palabra por palabra en tiempo real? ESS

SSE (Eventos enviados por servidor) 

Los eventos enviados por el servidor (SSE) son una tecnología web que permite la comunicación unidireccional entre el navegador del cliente y el servidor. Permite que el servidor envíe datos al cliente sin que el cliente los solicite activamente.

La diferencia entre SSE (Eventos enviados por el servidor) y WebSocket

Comunicación unidireccional versus bidireccional

  • SSE es un mecanismo de comunicación unidireccional donde solo el servidor puede enviar datos al cliente. El cliente no puede enviar mensajes activamente al servidor.
  • WebSocket es un mecanismo de comunicación bidireccional que permite la comunicación bidireccional en tiempo real entre un cliente y un servidor. Tanto los clientes como los servidores pueden enviar y recibir mensajes activamente.

Conexión establecida

  • SSE se basa en el protocolo HTTP tradicional. Las conexiones se establecen mediante solicitudes HTTP y permanecen abiertas durante mucho tiempo. Por lo tanto, las conexiones SSE siempre las inicia el cliente.
  • WebSocket es un protocolo independiente que requiere el uso de un protocolo de enlace WebSocket especial al crear una conexión. Las conexiones WebSocket pueden ser iniciadas por el cliente o el servidor.

Formato de datos

  • SSE utiliza formato de texto simple o formato JSON para transmitir datos. El servidor envía datos al cliente en fragmentos de texto.
  • WebSocket puede transmitir datos en cualquier formato, como texto, datos binarios, etc.

aplicación.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')
})  

índice.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>

Representación:

Supongo que te gusta

Origin blog.csdn.net/weixin_52479803/article/details/132575564
Recomendado
Clasificación