Files
xiaoli-llm-chat/chat.html
T
shuiyu1123 7f8f607f26 同步当前架构和数据 (2026-06-11)
- 训练脚本: train_v2.py + llm_chat_improved.py
- API生成: api_correction.py
- 高温多样化: regenerate_all_high_temp.py
- 每日数据生成: daily_data_gen.py
- 数据: xiaoli_train_v2.json(40776条) + vocab_v2.json
- 蒸馏问题: exam_distill_questions等
- Web服务: web_server.py + chat.html
- 新增 .gitignore 排除旧版文件
2026-06-11 14:07:52 +00:00

284 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小狸 v1.2 聊天</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
width: 100%;
max-width: 560px;
background: white;
border-radius: 24px;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
padding: 20px;
text-align: center;
position: relative;
}
.header .avatar {
width: 64px; height: 64px;
background: white;
border-radius: 50%;
margin: 0 auto 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.header h1 { font-size: 24px; color: white; }
.header p { color: rgba(255,255,255,0.9); font-size: 12px; margin-top: 2px; }
.chat-box {
height: 420px;
overflow-y: auto;
padding: 16px;
background: #fafafa;
}
.chat-box::-webkit-scrollbar { width: 5px; }
.chat-box::-webkit-scrollbar-thumb { background: #ddd; border-radius: 10px; }
.msg { margin-bottom: 14px; display: flex; animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.msg.user { justify-content: flex-end; }
.msg.bot { justify-content: flex-start; }
.bubble {
max-width: 80%;
padding: 10px 14px;
border-radius: 16px;
font-size: 14px;
line-height: 1.6;
word-break: break-word;
}
.msg.user .bubble {
background: #ff9a9e;
color: white;
border-bottom-right-radius: 4px;
}
.msg.bot .bubble {
background: white;
color: #333;
border-bottom-left-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.msg.bot .avatar-small {
width: 30px; height: 30px;
background: linear-gradient(135deg, #ff9a9e, #fecfef);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
margin-right: 8px;
flex-shrink: 0;
color: white;
}
.time-tag { font-size: 11px; color: #bbb; margin-top: 3px; }
.input-area {
padding: 12px 16px;
background: white;
border-top: 1px solid #f0f0f0;
display: flex;
gap: 8px;
}
.input-area input {
flex: 1;
padding: 10px 14px;
border: 2px solid #f0f0f0;
border-radius: 25px;
font-size: 14px;
outline: none;
}
.input-area input:focus { border-color: #ff9a9e; }
.input-area button {
width: 42px; height: 42px;
background: linear-gradient(135deg, #ff9a9e, #fecfef);
border: none;
border-radius: 50%;
color: white;
font-size: 18px;
cursor: pointer;
flex-shrink: 0;
}
.input-area button:hover { transform: scale(1.08); }
.input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
.typing-indicator { display: none; padding: 10px 14px; background: white; border-radius: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
.typing-indicator.active { display: flex; }
.typing-indicator span {
width: 7px; height: 7px; background: #ff9a9e; border-radius: 50%;
margin: 0 2px; animation: bounce 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce { 0%,80%,100% { transform: translateY(0); } 40% { transform: translateY(-8px); } }
.controls {
display: flex;
align-items: center;
gap: 12px;
padding: 6px 16px;
background: #fff8f8;
border-top: 1px solid #f0f0f0;
}
.controls label { font-size: 12px; color: #999; }
.controls input[type=range] { flex: 1; accent-color: #ff9a9e; height: 4px; }
.controls .val { font-size: 12px; color: #e07070; font-weight: bold; min-width: 30px; }
.controls .clear-btn {
font-size: 11px; color: #ccc; cursor: pointer; padding: 3px 8px; border-radius: 10px; border: 1px solid #eee;
}
.controls .clear-btn:hover { color: #e07070; border-color: #ffd4d4; }
.status-bar { padding: 4px 16px; font-size: 11px; color: #bbb; text-align: center; background: #fafafa; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="avatar">🦊</div>
<h1>小狸 v1.2</h1>
<p id="headerInfo">V2架构 · 21M参数 · 新训练启动 🔥</p>
</div>
<div class="chat-box" id="chatBox"></div>
<!-- 温度控制 -->
<div class="controls">
<label>🌡️</label>
<input type="range" id="tempSlider" min="0.05" max="0.5" step="0.05" value="0.15">
<span class="val" id="tempVal">0.15</span>
<span class="clear-btn" onclick="clearChat()">🗑️ 清空</span>
</div>
<div class="input-area">
<input type="text" id="input" placeholder="跟小狸说点什么吧..." autofocus>
<button id="sendBtn" onclick="send()">➤</button>
</div>
<div class="status-bar" id="statusBar">按回车发送 · 建议温度 0.1~0.2</div>
</div>
<script>
const chatBox = document.getElementById('chatBox');
const input = document.getElementById('input');
const sendBtn = document.getElementById('sendBtn');
const statusBar = document.getElementById('statusBar');
const tempSlider = document.getElementById('tempSlider');
const tempVal = document.getElementById('tempVal');
// 对话历史(用于多轮)
let history = [];
tempSlider.addEventListener('input', () => {
tempVal.textContent = parseFloat(tempSlider.value).toFixed(2);
});
addMsg('bot', '你好呀!我是小狸,一个8岁的小女孩~🦊\n你可以连续跟我聊天,我会记住之前说过的话哦!');
function addMsg(role, text) {
const div = document.createElement('div');
div.className = 'msg ' + role;
if (role === 'bot') {
const av = document.createElement('div');
av.className = 'avatar-small';
av.textContent = '🦊';
div.appendChild(av);
}
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.innerHTML = text.replace(/\n/g, '<br>');
div.appendChild(bubble);
const time = document.createElement('div');
time.className = 'time-tag';
time.textContent = new Date().toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit'});
if (role === 'user') {
bubble.appendChild(document.createElement('br'));
bubble.appendChild(time);
} else {
bubble.appendChild(document.createElement('br'));
bubble.appendChild(time);
}
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
}
function showTyping() {
const div = document.createElement('div');
div.className = 'msg bot';
div.id = 'typing';
const av = document.createElement('div');
av.className = 'avatar-small';
av.textContent = '🦊';
div.appendChild(av);
const indicator = document.createElement('div');
indicator.className = 'typing-indicator active';
for (let i = 0; i < 3; i++) indicator.innerHTML += '<span>';
div.appendChild(indicator);
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
}
function hideTyping() {
const t = document.getElementById('typing');
if (t) t.remove();
}
async function send() {
const q = input.value.trim();
if (!q) return;
input.value = '';
sendBtn.disabled = true;
statusBar.textContent = '小狸正在思考...';
addMsg('user', q);
showTyping();
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
question: q,
temperature: parseFloat(tempSlider.value),
history: history.length > 0 ? history : null
})
});
const data = await res.json();
hideTyping();
if (data.answer) {
// 记录到历史
history.push({q: q, a: data.answer});
addMsg('bot', data.answer + ' <span style="color:#ccc;font-size:11px">' + data.time + '</span>');
} else {
addMsg('bot', '唔…我没想好怎么回答😅');
}
} catch (e) {
hideTyping();
addMsg('bot', '哎呀,出错了…再试试?');
}
sendBtn.disabled = false;
statusBar.textContent = '按回车发送 · 温度 ' + parseFloat(tempSlider.value).toFixed(2);
input.focus();
}
function clearChat() {
chatBox.innerHTML = '';
history = [];
addMsg('bot', '🔄 对话已重置!重新开始吧~');
}
input.addEventListener('keydown', e => {
if (e.key === 'Enter') send();
});
</script>
</body>
</html>