2024-12-25 17:18:23 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<meta name="renderer" content="webkit">
|
|
|
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
|
|
|
<meta http-equiv="Cache-Control" content="no-siteapp">
|
|
|
|
|
<link rel="icon" type="image/webp" href="/{{.AgentID}}/avatar.webp">
|
|
|
|
|
<title>{{.AgentName}}</title>
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
--color-primary: {{.PrimaryColor}};
|
|
|
|
|
--color-secondary: {{.SecondaryColor}};
|
|
|
|
|
--color-accent: {{.AccentColor}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
* {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
font-family: -apple-system, 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial, 'Liberation Sans',
|
|
|
|
|
'PingFang SC', 'Hiragino Sans GB', 'Source Han Sans CN', 'Source Han Sans SC', 'Microsoft YaHei', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei',
|
|
|
|
|
'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
background-color: #f3f3f3;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main {
|
|
|
|
|
max-width: 480px;
|
|
|
|
|
max-width: 960px;
|
|
|
|
|
width: 60%;
|
|
|
|
|
height: 60vh;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
border-radius: .5rem;
|
|
|
|
|
box-shadow: 0 0 1rem rgba(0, 0, 0, .1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
color: white;
|
|
|
|
|
background: var(--color-primary);
|
|
|
|
|
border-radius: .5rem .5rem 0 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > img {
|
|
|
|
|
width: 3.5rem;
|
|
|
|
|
height: 3.5rem;
|
|
|
|
|
border-radius: .5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > nav > h1 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > nav > b {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-top: .25rem;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > aside {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
display: flex;
|
|
|
|
|
margin-top: -1em;
|
|
|
|
|
margin-right: -1em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > aside > button {
|
|
|
|
|
width: 2.5rem;
|
|
|
|
|
height: 1.5rem;
|
|
|
|
|
background: var(--color-secondary);
|
|
|
|
|
border: none;
|
|
|
|
|
outline: none;
|
|
|
|
|
border-right: 1px solid white;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > aside > button:first-child {
|
|
|
|
|
border-radius: .0 0 0 .5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > aside > button:last-child {
|
|
|
|
|
border-radius: 0 .5rem 0 0;
|
|
|
|
|
border-right: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
header > aside > button:hover {
|
|
|
|
|
background: var(--color-accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
background: white;
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section > img {
|
|
|
|
|
width: 2rem;
|
|
|
|
|
height: 2rem;
|
|
|
|
|
border: 1px solid #f3f3f3;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section > div {
|
|
|
|
|
padding: .75rem;
|
|
|
|
|
border-radius: .5rem;
|
|
|
|
|
background: var(--color-primary);
|
|
|
|
|
color: white;
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
margin-right: 3rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section > div > p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
min-height: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section[role="user"] {
|
|
|
|
|
flex-direction: row-reverse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section[role="user"] > div {
|
|
|
|
|
background: #f3f3f3;
|
|
|
|
|
color: #3c3c3c;
|
|
|
|
|
margin-left: 3rem;
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
article > section[role="system"] > div {
|
|
|
|
|
background: #ba1919;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer {
|
|
|
|
|
min-height: 150px;
|
|
|
|
|
height: 25%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
background: white;
|
|
|
|
|
border-bottom: 1px solid #f3f3f3;
|
|
|
|
|
border-radius: 0 0 .5rem .5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer > textarea {
|
|
|
|
|
width: 100%;
|
|
|
|
|
resize: none;
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
padding: .5rem;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-top: 1px solid #d3d3d3;
|
|
|
|
|
border-bottom: 1px solid #d3d3d3;
|
|
|
|
|
outline: none;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer > menu {
|
|
|
|
|
margin: .5rem;
|
|
|
|
|
padding: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: end;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer > menu > span {
|
|
|
|
|
display: block;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer > menu > div > button {
|
|
|
|
|
width: 5rem;
|
|
|
|
|
height: 1.5rem;
|
|
|
|
|
border-radius: .25rem;
|
|
|
|
|
box-shadow: 0 0 .25rem rgba(0, 0, 0, .1);
|
|
|
|
|
background: var(--color-secondary);
|
|
|
|
|
border: none;
|
|
|
|
|
outline: none;
|
|
|
|
|
border-right: 1px solid white;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
footer > menu > div > button:disabled {
|
|
|
|
|
background: #d3d3d3;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy {
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-size: .8rem;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: var(--color-primary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy a {
|
|
|
|
|
color: #ee82ee;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.copy a:hover {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<main>
|
|
|
|
|
<header>
|
|
|
|
|
<img src="/{{.AgentID}}/avatar.webp" alt="头像">
|
|
|
|
|
<nav>
|
|
|
|
|
<h1>{{.AgentName}}</h1>
|
|
|
|
|
<b>{{.AgentDescription}}</b>
|
|
|
|
|
</nav>
|
|
|
|
|
<aside>
|
|
|
|
|
<button>_</button>
|
|
|
|
|
<button>□</button>
|
2025-01-11 04:31:04 +00:00
|
|
|
|
<button onclick="location.href = '/'">×</button>
|
2024-12-25 17:18:23 +00:00
|
|
|
|
</aside>
|
|
|
|
|
</header>
|
|
|
|
|
<article>
|
|
|
|
|
</article>
|
|
|
|
|
<footer>
|
|
|
|
|
<textarea name="chat-box"></textarea>
|
|
|
|
|
<menu>
|
|
|
|
|
<div>
|
|
|
|
|
<button name="clear">清除 (C)</button>
|
|
|
|
|
<button name="send">发送 (S)</button>
|
|
|
|
|
</div>
|
|
|
|
|
</menu>
|
|
|
|
|
</footer>
|
|
|
|
|
</main>
|
|
|
|
|
<div class="copy">
|
2025-01-11 04:31:04 +00:00
|
|
|
|
{{.FooterHTML}}<br>
|
|
|
|
|
以上内容为 AI 生成,请注意辨别。<br>为了安全和审计需要,我们可能记录您与 AI 的对话内容和您的 IP 地址。请勿向 AI 分享敏感信息。
|
2024-12-25 17:18:23 +00:00
|
|
|
|
</div>
|
|
|
|
|
<template id="chat-record">
|
|
|
|
|
<section>
|
|
|
|
|
<img>
|
|
|
|
|
<div>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
<script>
|
|
|
|
|
(() => {
|
|
|
|
|
function readMessages() {
|
|
|
|
|
return JSON.parse(localStorage.getItem("messages.{{.AgentID}}") || "[]");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeMessages(messages) {
|
|
|
|
|
localStorage.setItem("messages.{{.AgentID}}", JSON.stringify(messages));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rendererMessages() {
|
|
|
|
|
const messages = readMessages();
|
|
|
|
|
const html = messages.map(msg => {
|
|
|
|
|
const section = document.getElementById('chat-record').content.firstElementChild.cloneNode(true);
|
|
|
|
|
section.setAttribute('role', msg.role);
|
|
|
|
|
const img = section.querySelector('img');
|
|
|
|
|
img.src = msg.role == "model" ? "/{{.AgentID}}/avatar.webp" : `/assets/avatar-${msg.role}.webp`;
|
|
|
|
|
img.alt = `${msg.role} avatar`;
|
|
|
|
|
const div = section.querySelector('div');
|
|
|
|
|
div.innerHTML = msg.content.split('\n').map(line => {
|
|
|
|
|
const paragraph = document.createElement('p');
|
|
|
|
|
paragraph.textContent = line;
|
|
|
|
|
return paragraph.outerHTML;
|
|
|
|
|
}).join('');
|
|
|
|
|
return section.outerHTML;
|
|
|
|
|
});
|
|
|
|
|
const target = document.querySelector('article');
|
|
|
|
|
target.innerHTML = html.join('');
|
|
|
|
|
target.scrollTo(0, target.scrollHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateLastMessage(content, renderer = true) {
|
|
|
|
|
const messages = readMessages();
|
|
|
|
|
if (messages.length == 0) return;
|
|
|
|
|
messages[messages.length - 1].content += content;
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
if (renderer) rendererMessages();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sendMessage() {
|
|
|
|
|
let messages = readMessages();
|
|
|
|
|
const pendingMessage = messages.pop();
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
const response = await fetch("/{{.AgentID}}/chat", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(messages.filter(msg => msg.role != "system")),
|
|
|
|
|
});
|
|
|
|
|
messages.push({ role: "model", content: "" });
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
|
|
|
|
|
console.log(response);
|
|
|
|
|
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
const decoder = new TextDecoder("utf-8");
|
|
|
|
|
let chunks = [];
|
|
|
|
|
while (true) {
|
|
|
|
|
const { value, done } = await reader.read();
|
|
|
|
|
if (done) break;
|
|
|
|
|
const events = decoder.decode(value, { stream: true }).split("\n");
|
|
|
|
|
events.forEach(event => {
|
|
|
|
|
if (!event.startsWith("data: ")) return;
|
|
|
|
|
if (event == "data: [DONE]") return;
|
|
|
|
|
const data = JSON.parse(event.slice(6));
|
|
|
|
|
console.log("EventStream Data", data);
|
|
|
|
|
if (data.content) {
|
|
|
|
|
updateLastMessage(data.content, true);
|
|
|
|
|
chunks.push(data.content);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
const fullText = chunks.join("").trim();
|
|
|
|
|
console.log("Full text", fullText);
|
|
|
|
|
messages.pop();
|
|
|
|
|
messages.push({ role: "model", content: fullText });
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
rendererMessages();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.querySelector("[name=clear]").addEventListener("click", function() {
|
|
|
|
|
writeMessages([]);
|
|
|
|
|
rendererMessages();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.querySelector("[name=chat-box]").addEventListener("keydown", function(event) {
|
|
|
|
|
if ((event.key === "Enter" && (event.ctrlKey || event.metaKey)) || (event.key === "s" && event.altKey)) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
document.querySelector("[name=send]").click();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.querySelector("[name=send]").addEventListener("click", function() {
|
|
|
|
|
const textarea = document.querySelector("[name=chat-box]");
|
|
|
|
|
const content = textarea.value.trim();
|
|
|
|
|
if (!content) return;
|
|
|
|
|
textarea.value = "";
|
|
|
|
|
document.querySelector("[name=send]").disabled = true;
|
|
|
|
|
let messages = readMessages();
|
|
|
|
|
messages.push({ role: "user", content });
|
|
|
|
|
messages.push({ role: "model", content: "对方正在输入..." });
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
rendererMessages();
|
|
|
|
|
sendMessage().catch(error => {
|
|
|
|
|
console.error(error);
|
|
|
|
|
let messages = readMessages();
|
|
|
|
|
messages.push({ role: "system", content: `网络错误:${error}` });
|
|
|
|
|
writeMessages(messages);
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
rendererMessages();
|
|
|
|
|
document.querySelector("[name=send]").disabled = false;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
(() => rendererMessages())();
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|