mylomen-server/static/ai-chat.html

326 lines
10 KiB
HTML
Raw Normal View History

2024-04-30 20:30:12 +08:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/* CSS样式 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
2024-04-30 22:11:21 +08:00
background-color: var(--white);
background: url("/v1/static/img/003.jpeg");
2024-04-30 20:30:12 +08:00
}
.chat-container {
2024-04-30 22:11:21 +08:00
max-width: 85%;
max-height: 85%;
2024-04-30 20:30:12 +08:00
margin: 20px auto;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
}
.chat-box {
2024-04-30 22:11:21 +08:00
height: 500px;
2024-04-30 20:30:12 +08:00
overflow-y: scroll;
border-bottom: 1px solid #ccc;
padding-bottom: 10px;
2024-05-16 19:55:15 +08:00
white-space: pre-line;
2024-04-30 20:30:12 +08:00
}
#user-input {
width: calc(100% - 100px);
padding: 8px;
margin-top: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
padding: 8px 15px;
margin-left: 10px;
border-radius: 5px;
}
.message {
margin-bottom: 10px;
2024-04-30 22:11:21 +08:00
justify-content: flex-end;
background-color: #d6eaff;
color: #000000;
}
2024-05-20 10:19:46 +08:00
.gpt {
2024-04-30 22:11:21 +08:00
margin-bottom: 10px;
justify-content: flex-start;
background-color: #e5ece7;
color: #000;
2024-04-30 20:30:12 +08:00
}
2024-05-20 10:19:46 +08:00
.gpt span {
2024-05-16 19:55:15 +08:00
white-space: pre-line;
}
2024-04-30 20:30:12 +08:00
.sender {
font-weight: bold;
}
</style>
2024-05-20 10:19:46 +08:00
<!-- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>-->
2024-05-17 11:45:43 +08:00
2024-04-30 20:30:12 +08:00
</head>
<body>
<div class="chat-container" id="chat-container">
<div class="chat-box" id="chat-box">
<!-- 聊天消息将会在这里显示 -->
</div>
<input type="text" id="user-input" placeholder="Type a message...">
<button id="send-button" onclick="sendMessage()">Send</button>
</div>
</body>
2024-05-20 10:19:46 +08:00
<script type="module">
import {marked} from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
2024-04-30 20:30:12 +08:00
// --------------------init--------------------------------------
2024-04-30 20:48:27 +08:00
const API_KEY = "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";
const ENDPOINT = "https://openrouter.ai/api/v1/chat/completions";
2024-04-30 20:30:12 +08:00
// 获取输入框元素
const userInput = document.getElementById('user-input');
// 监听输入框的回车事件
userInput.addEventListener('keydown', (event) => {
if (event.key === "Enter") {
// 处理回车事件
sendMessage();
}
});
// 历史消息
const messages = [];
// 等待
let waiting = false;
// -------------------------------------------------------------------------------------------------
2024-05-20 10:19:46 +08:00
let reg = /\\n/ig; //o为要替换的关键字不能加引号否则替换不生效i忽略大小写g表示全局查找。
2024-04-30 20:30:12 +08:00
/**
* 发送消息
*/
function sendMessage() {
// 等待
if (waiting) {
alert('等待回复中');
return;
}
waiting = true;
// 获取到用户的输入
const message = userInput.value.trim();
// 判断用户输入是否为空
if (message === '') {
alert('请输入内容');
waiting = false;
return;
}
// 将用户输入显示在聊天框中
displayUserMessage(message);
userInput.value = '';
// 创建ChatGPT的回复并获取到显示回复的容器
const htmlSpanElement = displayChatGPTMessageAndGetContainer();
// 发送消息到ChatGPT
addMessage("user", message);
2024-05-20 10:19:46 +08:00
const body = JSON.stringify({model: "google/gemma-7b-it:free", messages: messages, stream: false});
alert(body)
2024-04-30 20:30:12 +08:00
ssePost(
// 请求地址
ENDPOINT,
// 请求头
2024-05-20 10:19:46 +08:00
{"Content-Type": "application/json", Authorization: "Bearer " + API_KEY},
2024-04-30 20:30:12 +08:00
// params这里没有参数
{},
// body
body,
// 收到事件时的回调。这里将事件的data显示在htmlSpanElement中
2024-05-17 11:45:43 +08:00
(event) => {
const content = getContent(event.data);
2024-05-20 10:19:46 +08:00
// console.log(event.data)
if (content) {
let ccc = content.toString().replace(reg, "<br/>")
console.log(ccc);
htmlSpanElement.innerHTML += ccc;
}
2024-05-17 11:45:43 +08:00
},
2024-04-30 20:30:12 +08:00
// 结束时的回调。1.将消息添加到历史消息中 2.将等待状态设置为false
2024-05-17 11:45:43 +08:00
() => {
addMessage("assistant", htmlSpanElement.innerHTML);
2024-05-20 10:19:46 +08:00
htmlSpanElement.innerHTML = marked.parse(htmlSpanElement.innerHTML);
2024-05-17 11:45:43 +08:00
waiting = false;
},
2024-04-30 20:30:12 +08:00
// 发生错误时的回调
2024-05-20 10:19:46 +08:00
(error) => {
console.log(error);
waiting = false
}
2024-04-30 20:30:12 +08:00
);
}
// 匹配回复内容的正则表达式
const contentPattern = /"content":"(.*?)"}/;
/**
* 获取回复内容
* @param data 数据
* @returns 回复内容
*/
function getContent(data) {
2024-05-16 19:13:30 +08:00
const match = data?.match(contentPattern);
2024-04-30 20:30:12 +08:00
if (match) {
return match[1];
} else {
return null;
}
}
/**
* 将消息添加到历史消息中
* @param role 角色。user或者assistant
* @param content 消息内容
*/
function addMessage(role, content) {
messages.push({role: role, content: content});
}
const chatBox = document.getElementById('chat-box');
/**
* 将用户输入显示在聊天框中
* @param text 用户的输入
*/
function displayUserMessage(text) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
const senderSpan = document.createElement('span');
senderSpan.classList.add('sender');
senderSpan.textContent = 'You: ';
const textSpan = document.createElement('span');
textSpan.textContent = text;
messageDiv.appendChild(senderSpan);
messageDiv.appendChild(textSpan);
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}
/**
* 将ChatGPT的回复显示在聊天框中
* @returns {HTMLSpanElement}
*/
function displayChatGPTMessageAndGetContainer() {
const messageDiv = document.createElement('div');
2024-04-30 22:11:21 +08:00
messageDiv.classList.add('gpt');
2024-04-30 20:30:12 +08:00
const senderSpan = document.createElement('span');
senderSpan.classList.add('sender');
senderSpan.textContent = 'ChatGPT: ';
const textSpan = document.createElement('span');
messageDiv.appendChild(senderSpan);
messageDiv.appendChild(textSpan);
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
return textSpan;
}
function objectToQueryString(obj) {
return Object.keys(obj)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
.join('&');
}
/**
* 发送POST请求并处理sse事件
* @param url 请求地址
* @param headers 请求头
* @param params 请求参数
* @param body 请求体
* @param onEvent 收到事件时的回调
* @param onEnd 结束时的回调
* @param onError 发生错误时的回调
*/
function ssePost(url, headers, params, body, onEvent, onEnd, onError) {
// 拼接url
if (Object.keys(params).length > 0) {
url += '?' + objectToQueryString(params);
}
// 发送请求
fetch(url, {
method: 'POST',
headers: headers,
body: body,
}).then(async response => {
// 判断响应状态码
if (!response.ok) {
onError(new Error('Network response was not ok'));
return;
}
// 异步处理响应流
const reader = response.body.getReader();
// 响应缓冲区
let buffer = '';
// 响应的前一个字符,用于判断一个事件是否结束
let before = '';
// 循环读取响应流,直到响应流结束
while (true) {
// 读取响应流
const {done, value} = await reader.read();
// 响应流结束
if (done) {
break;
}
// 将响应流转换为文本
const text = new TextDecoder().decode(value);
// 遍历文本
for (const element of text) {
// 判断是否为事件结束。连续两个'\n'表示一个事件结束
if (element === '\n' && before === '\n') {
// 将事件中的字段分割出来。例如event: message \n data: hello world \n id: 123 \n\n
const eventAndData = buffer.substring(0, buffer.length - 1).split('\n');
// 将事件中的字段转换为对象, 例如:{event: message, data: hello world, id: 123}
const resultObject = {};
eventAndData.forEach(pair => {
const colonIndex = pair.indexOf(':');
if (colonIndex === -1) {
return;
}
resultObject[pair.substring(0, colonIndex)] = pair.substring(colonIndex + 2);
});
// 回调
onEvent(resultObject);
// 清空缓冲区
buffer = '';
} else
// 不是事件结束,将字符添加到缓冲区
{
before = element;
buffer += element;
}
}
}
// 结束时的回调
onEnd();
}).catch(error => {
// 发生错误时的回调
onError(error);
})
}
</script>
</html>