- 训练脚本: 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 排除旧版文件
284 lines
8.7 KiB
HTML
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> |