https://platform.openai.com/docs/api-reference/chat/create?lang=curl
导入到postman中
记得弄一个gtp的key
然后请求测试gtp接口:
目录结构:
index.html
- 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-chattitle>
- <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">ChatGTPh2>
- 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 = $('); '
- 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 = $('); '
- chatWindow.append(messageElement);
- }
-
- // 添加消息到窗口 自定义添加消息(异常啥的)
- function addFailMessage(message) {
- $(".answer .tips").css({ "display": "none" });
- chatInput.val('');
- var messageElement = $('); '
- 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('' + '出错啦!请稍后再试!' + '');
- 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,阻止事件
- }
- });
- });
现在请求到数据之后是一下子全部显示,纯前端如何实现一字一字输出的打字效果呢?
- 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 Effecth1>
- <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 = $('); '
- chatWindow.append(messageElement);
- addMessageTwo(res["id"], escapedMessage)
-
- //XXXXXXXXXXXXXXXXXXXXXXXX
- },
- });
- });
-
- //XXXXXXXXXXXXXXXXXXXXXXXX
-
上述通过前端的js实现一字一字打字输出效果,但还有问题:请求完获取数据之后才开始一字一字输出,如何返回的文本过长 需要等待很久,显然这种方式不行,那有没有那种实时的逐字输出呢? SSE
服务器发送事件(Server-sent Events,简称 SSE)是一种在客户端浏览器和服务器之间进行单向通信的 Web 技术。它允许服务器向客户端推送数据,而不需要客户端主动请求。
单向 vs 双向通信
连接建立
数据格式
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 Exampletitle>
- head>
-
- <body>
- <h1>SSE Exampleh1>
- <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>
效果图: