• 【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

    1. html>
    2. <html lang="en">
    3. <head>
    4. <script>
    5. var password = ""
    6. var realpassword = atob("NjY4OA==")
    7. password = prompt('请输入密码 (本网站需输入密码才可进入):', '')
    8. if (password != realpassword) {
    9. alert("密码不正确,无法进入本站!!")
    10. // 密码不正确就关闭
    11. open(location, '_self').close()
    12. }
    13. script>
    14. <meta charset="UTF-8">
    15. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    16. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    17. <link rel="icon" type="images/x-icon" href="./static/images/favicon.ico">
    18. <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
    19. <link rel="stylesheet" href="./static/css/bootstrap.min.css">
    20. <link rel="stylesheet" href="./static/css/style.css">
    21. <title>ac-chattitle>
    22. <style>
    23. #output {
    24. display: inline;
    25. }
    26. .cursor {
    27. display: inline-block;
    28. width: 10px;
    29. height: 20px;
    30. background-color: black;
    31. vertical-align: text-bottom;
    32. animation: blink 1s infinite;
    33. }
    34. @keyframes blink {
    35. 50% {
    36. opacity: 0;
    37. }
    38. }
    39. style>
    40. head>
    41. <body>
    42. <div class="container">
    43. <div class="row">
    44. <div class="col-xs-12">
    45. <div class="title">
    46. <h2 class="text-center">ChatGTPh2>
    47. div>
    48. <div class="key">
    49. <div class="input-group col-sm-6">
    50. <span class="input-group-addon">
    51. <input type="checkbox" class="ipt-1">
    52. span>
    53. <input type="password" class="form-control ipt-2" placeholder="使用自己的api key">
    54. div>
    55. div>
    56. <div class="answer">
    57. <div class="tips text-center">
    58. <h3 class="lead">仅做技术研究探讨使用!h3>
    59. div>
    60. <div id="chatWindow">div>
    61. <div class="input-group ipt">
    62. <div class="col-xs-12">
    63. <textarea id="chatInput" class="form-control" rows="1" style="min-height: 40px;">textarea>
    64. div>
    65. <button id="chatBtn" class="btn btn-primary" type="button">Go !button>
    66. div>
    67. div>
    68. div>
    69. div>
    70. <div class="row foot">
    71. <footer class="col-xs-12" style="margin-top: 10px;">
    72. <p class="lead text-center">“抢走工作的不会是AI,而是率先掌握AI能力的人”p>
    73. footer>
    74. div>
    75. div>
    76. body>
    77. <script src="./static/js/jquery-2.1.1.js">script>
    78. <script src="https://code.jquery.com/ui/1.13.1/jquery-ui.min.js">script>
    79. <script src="./static/js/bootstrap.min.js">script>
    80. <script src="./static/js/layer/layer.js">script>
    81. <script src="./static/js/custom.js">script>
    82. html>

    custom.js

    1. // 封装弹窗layer组件等
    2. var common_ops = {
    3. // 封装layer.alert(content, options, yes) - 普通信息框
    4. alert: function (msg, cb) {
    5. layer.alert(msg, {
    6. yes: function (index) {
    7. if (typeof cb == "function") {
    8. cb();
    9. }
    10. layer.close(index);
    11. }
    12. });
    13. },
    14. // 封装layer.confirm(content, options, yes, cancel) - 询问框
    15. confirm: function (msg, callback) {
    16. callback = (callback != undefined) ? callback : { 'ok': null, 'cancel': null };
    17. layer.confirm(msg, {
    18. btn: ['确定', '取消']
    19. }, function (index) {
    20. //确定事件
    21. if (typeof callback.ok == "function") {
    22. callback.ok();
    23. }
    24. layer.close(index);
    25. }, function (index) {
    26. //取消事件
    27. if (typeof callback.cancel == "function") {
    28. callback.cancel();
    29. }
    30. layer.close(index);
    31. });
    32. }
    33. };
    34. $(document).ready(function () {
    35. // 查询按钮
    36. var chatBtn = $('#chatBtn');
    37. // 查询内容
    38. var chatInput = $('#chatInput');
    39. $("#chatInput").resizable();
    40. // 中间内容
    41. var chatWindow = $('#chatWindow');
    42. // 存储对话信息,实现连续对话
    43. var messages = []
    44. // 移除加载效果
    45. function deleteLoading() {
    46. chatWindow.find('#loading').remove();
    47. }
    48. // 将 HTML 字符串转义为纯文本
    49. function escapeHtml(html) {
    50. var text = document.createTextNode(html);
    51. var div = document.createElement('div');
    52. div.appendChild(text);
    53. return div.innerHTML;
    54. }
    55. // 创建输入的文本
    56. function addLoading() {
    57. // 隐藏 “仅做技术研究探讨使用!”
    58. $(".answer .tips").css({ "display": "none" });
    59. // 输入框清空
    60. chatInput.val('');
    61. // 加载动画
    62. var messageElement = $('

      加载动画

      '
      );
    63. chatWindow.append(messageElement);
    64. }
    65. function scrollToBottom(id) {
    66. var element = document.getElementById(id);
    67. element.scrollTop = element.scrollHeight;
    68. }
    69. // 添加消息到窗口 用户跟gtp文本消息
    70. function addMessage(message, imgName) {
    71. $(".answer .tips").css({ "display": "none" });
    72. chatInput.val('');
    73. var escapedMessage = escapeHtml(message);
    74. var messageElement = $('
      '">

      ' + escapedMessage + '

      '
      );
    75. chatWindow.append(messageElement);
    76. }
    77. // 添加消息到窗口 自定义添加消息(异常啥的)
    78. function addFailMessage(message) {
    79. $(".answer .tips").css({ "display": "none" });
    80. chatInput.val('');
    81. var messageElement = $('

      ' + message + '

      '
      );
    82. chatWindow.append(messageElement);
    83. }
    84. // 处理用户输入
    85. chatBtn.click(function () {
    86. // 解绑键盘事件 回车之后解绑,防止未获得结果时 又发一个请求
    87. chatInput.off("keydown", handleEnter);
    88. // 保存api key与对话数据
    89. var data = {
    90. "apiKey": "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", // 这里填写固定 apiKey
    91. }
    92. // 判断是否使用自己的api key
    93. if ($(".key .ipt-1").prop("checked")) {
    94. var apiKey = $(".key .ipt-2").val();
    95. if (apiKey.length < 20) {
    96. common_ops.alert("请输入正确的 api key !", function () {
    97. chatInput.val('');
    98. // 重新绑定键盘事件
    99. chatInput.on("keydown", handleEnter);
    100. })
    101. return
    102. } else {
    103. data.apiKey = apiKey
    104. }
    105. }
    106. var message = chatInput.val();
    107. if (message.length == 0) {
    108. common_ops.alert("请输入内容!", function () {
    109. chatInput.val('');
    110. // 重新绑定键盘事件
    111. chatInput.on("keydown", handleEnter);
    112. })
    113. return
    114. }
    115. // 创建用户对话行
    116. addMessage(message, "avatar.png");
    117. // 将用户消息保存到数组
    118. messages.push({ "role": "user", "content": message })
    119. // 收到回复前让按钮不可点击
    120. chatBtn.attr('disabled', true)
    121. data.prompt = messages
    122. // 出现loading动画
    123. addLoading();
    124. // 发送信息到后台
    125. $.ajax({
    126. url: 'https://open.aiproxy.xyz/v1/chat/completions',
    127. method: 'POST',
    128. headers: {
    129. 'Content-Type': 'application/json',
    130. 'Authorization': 'Bearer ' + data.apiKey
    131. },
    132. data: JSON.stringify({
    133. "messages": data.prompt,
    134. "model": "gpt-3.5-turbo",
    135. "max_tokens": 2048,
    136. "temperature": 0.5,
    137. "top_p": 1,
    138. "n": 1
    139. }),
    140. success: function (res) {
    141. const resp = res["choices"][0]["message"];
    142. // 创建回复对话行
    143. addMessage(resp.content, "chatgpt.png");
    144. // 收到回复,让按钮可点击
    145. chatBtn.attr('disabled', false)
    146. // 重新绑定键盘事件
    147. chatInput.on("keydown", handleEnter);
    148. // 去除loading动画
    149. deleteLoading()
    150. // 将回复添加到数组
    151. messages.push(resp)
    152. },
    153. error: function (jqXHR, textStatus, errorThrown) {
    154. // 去除loading动画
    155. deleteLoading()
    156. addFailMessage('' + '出错啦!请稍后再试!' + '');
    157. chatBtn.attr('disabled', false)
    158. chatInput.on("keydown", handleEnter);
    159. messages.pop() // 失败就让用户输入信息从数组删除
    160. }
    161. });
    162. });
    163. // Enter键盘事件
    164. function handleEnter(e) {
    165. if (e.keyCode == 13) {
    166. chatBtn.click();
    167. }
    168. }
    169. // 绑定Enter键盘事件
    170. chatInput.on("keydown", handleEnter);
    171. // 禁用右键菜单
    172. document.addEventListener('contextmenu',function(e){
    173. e.preventDefault(); // 阻止默认事件
    174. });
    175. // 禁止键盘F12键
    176. document.addEventListener('keydown',function(e){
    177. if(e.key == 'F12'){
    178. e.preventDefault(); // 如果按下键F12,阻止事件
    179. }
    180. });
    181. });

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

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="referrer" content="no-referrer" />
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>打字效果title>
    8. <style>
    9. #output {
    10. display: inline;
    11. }
    12. .cursor {
    13. display: inline-block;
    14. width: 10px;
    15. height: 20px;
    16. background-color: black;
    17. vertical-align: text-bottom;
    18. animation: blink 1s infinite;
    19. }
    20. @keyframes blink {
    21. 50% {
    22. opacity: 0;
    23. }
    24. }
    25. style>
    26. head>
    27. <body>
    28. <h1>ChatGPT Typing Effecth1>
    29. <div id="output">div><span class="cursor" id="cursor">span>
    30. <div id="givenText" style="display: none;">
    31. <strong>加粗文本strong><br>
    32. <em>斜体文本em><br>
    33. <u>下划线文本u><br>
    34. <span style="color: red;">红色文本span><br>
    35. <span style="font-size: 24px;">大字体文本span><br>
    36. <a href="https://github.com/azhu021/">链接示例a>
    37. div>
    38. <script>
    39. const outputElement = document.getElementById("output");
    40. const cursorElement = document.getElementById("cursor");
    41. const givenTextElement = document.getElementById("givenText");
    42. const givenText = givenTextElement.innerHTML;
    43. let currentIndex = 0;
    44. let currentHTML = "";
    45. function typeText() {
    46. if (currentIndex < givenText.length) {
    47. const currentChar = givenText.charAt(currentIndex);
    48. if (currentChar === "<") {
    49. const closingTagIndex = givenText.indexOf(">", currentIndex);
    50. currentHTML += givenText.slice(currentIndex, closingTagIndex + 1);
    51. currentIndex = closingTagIndex + 1;
    52. } else {
    53. currentHTML += currentChar;
    54. currentIndex++;
    55. }
    56. outputElement.innerHTML = currentHTML;
    57. setTimeout(typeText, 100); // 设置打字速度,单位为毫秒
    58. } else {
    59. // 当打印完成时,移除光标的闪烁效果
    60. cursorElement.classList.remove("cursor");
    61. }
    62. }
    63. typeText();
    64. script>
    65. body>
    66. html>

    将其效果移植到custom.js中

    1. //XXXXXXXXXXXXXXXXXXXXXXXX
    2. let currentIndex = 0;
    3. let currentHTML = "";
    4. function addMessageTwo(id, message) {
    5. if (currentIndex < message.length) {
    6. currentHTML = ''
    7. currentHTML += message.slice(0, currentIndex + 1);
    8. $(`#${id}`).text(currentHTML)
    9. currentIndex++
    10. setTimeout(() => addMessageTwo(id, message), 100);
    11. } else {
    12. currentIndex = 0
    13. }
    14. }
    15. // 处理用户输入
    16. chatBtn.click(function () {
    17. //XXXXXXXXXXXXXXXXXXXXXXXX
    18. // 发送信息到后台
    19. $.ajax({
    20. url: 'https://open.aiproxy.xyz/v1/chat/completions',
    21. method: 'POST',
    22. headers: {
    23. 'Content-Type': 'application/json',
    24. 'Authorization': 'Bearer ' + data.apiKey
    25. },
    26. data: JSON.stringify({
    27. //XXXXXXXXXXXXXXXXXXXXXXXX
    28. }),
    29. success: function (res) {
    30. const resp = res["choices"][0]["message"];
    31. // 创建回复对话行
    32. // addMessage(resp.content, "chatgpt.png");
    33. $(".answer .tips").css({ "display": "none" });
    34. chatInput.val('');
    35. var escapedMessage = escapeHtml(resp.content);
    36. var messageElement = $('

      '
      );
    37. chatWindow.append(messageElement);
    38. addMessageTwo(res["id"], escapedMessage)
    39. //XXXXXXXXXXXXXXXXXXXXXXXX
    40. },
    41. });
    42. });
    43. //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

    1. const express = require('express');
    2. const app = express()
    3. const router = express.Router();
    4. app.use((req, res, next) => {
    5. res.setHeader('Access-Control-Allow-Origin', '*');
    6. res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    7. next();
    8. });
    9. router.get('/sse', (req, res) => {
    10. res.setHeader('Content-Type', 'text/event-stream');
    11. res.setHeader('Cache-Control', 'no-cache');
    12. res.setHeader('Connection', 'keep-alive');
    13. const answer = '众所周知,ChatGPT API 是一个OpenAI 的聊天机器人接口,它可以根据用户的输入生成智能的回复,为了提高聊天的流畅性和响应速度,采用流失输出的响应方式,类似打字机的呈现效果';
    14. let i = 0;
    15. const intervalId = setInterval(() => {
    16. res.write('data:' + answer[i] + '\n\n');
    17. i++;
    18. if (i == answer.length) {
    19. clearInterval(intervalId);
    20. res.write('event:end\ndata: \n\n');
    21. }
    22. }, 100);
    23. });
    24. app.use('/', router)
    25. app.listen(3333, function () {
    26. console.log('api server running at http://127.0.0.1:3333')
    27. })

    index.html

    1. DOCTYPE html>
    2. <html>
    3. <meta charset="UTF-8">
    4. <meta name="referrer" content="no-referrer" />
    5. <head>
    6. <title>SSE Exampletitle>
    7. head>
    8. <body>
    9. <h1>SSE Exampleh1>
    10. <button id="startButton">开始button>
    11. <div id="output">回答:div>
    12. <script>
    13. const startButton = document.getElementById('startButton');
    14. const outputElement = document.getElementById('output');
    15. startButton.addEventListener('click', function () {
    16. let eventSource = new EventSource('http://172.21.2.52:3333/sse');
    17. eventSource.onopen = function (event) {
    18. console.log('成功')
    19. };
    20. eventSource.onmessage = function (event) {
    21. const message = event.data;
    22. outputElement.innerHTML += message;
    23. };
    24. });
    25. script>
    26. body>
    27. html>

    效果图:

  • 相关阅读:
    云上攻防-云服务篇&对象存储&Bucket桶&任意上传&域名接管&AccessKey泄漏
    upload-labs通关(Pass01-Pass05)
    Kafka KRaft模式探索
    Jmeter快速入门
    三个烂怂八股文,变成两个场景题,打得我一脸问号。
    【数据库】openGauss3.1.0版本做了哪些优化
    elment-plus图标input上面带的图标为什么不显示
    【LeetCode-简单】121. 买卖股票的最佳时机(详解)
    网络链接失败怀疑是服务器处于非正常状态?如何用本地电脑查看服务器是否正常?
    HCIA网络课程第九周作业
  • 原文地址:https://blog.csdn.net/weixin_52479803/article/details/132575564