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>
效果图: