math-tutor/templates/index.html

429 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🐉 数学小卧龙 — 苏格拉底导师</title>
<style>
:root {
--bg: #FFF8F0;
--card: #FFFFFF;
--primary: #FF6B6B;
--secondary: #4ECDC4;
--accent: #45B7D1;
--text: #2D3436;
--light: #F8F9FA;
--border: #E9ECEF;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
}
/* 顶部栏 */
header {
background: linear-gradient(135deg, var(--primary), var(--accent));
color: white;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
header .logo { font-size: 28px; }
header h1 { font-size: 18px; font-weight: 600; }
header .tag { font-size: 12px; opacity: 0.85; }
/* 三栏布局 */
main {
display: grid;
grid-template-columns: 1fr 380px;
grid-template-rows: 1fr 1fr;
gap: 12px;
padding: 12px;
height: calc(100vh - 56px);
}
/* 左侧主区域 */
.main-area {
grid-row: 1 / 3;
display: flex;
flex-direction: column;
gap: 12px;
}
/* 画布区 */
.canvas-panel {
flex: 1;
background: var(--card);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
display: flex;
flex-direction: column;
overflow: hidden;
}
.canvas-toolbar {
display: flex;
gap: 6px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.canvas-toolbar button {
padding: 5px 12px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--light);
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.canvas-toolbar button:hover { background: var(--secondary); color: white; border-color: var(--secondary); }
.canvas-toolbar button.active { background: var(--primary); color: white; border-color: var(--primary); }
.canvas-toolbar .color-pick { width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--border); cursor: pointer; padding: 0; }
.drawing-area {
flex: 1;
position: relative;
overflow: hidden;
}
canvas { position: absolute; top: 0; left: 0; cursor: crosshair; }
.svg-display {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
pointer-events: none; opacity: 0.3;
}
.svg-display svg { max-width: 90%; max-height: 90%; }
/* 输入区 */
.input-panel {
background: var(--card);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
padding: 12px;
display: flex;
gap: 8px;
}
.input-panel textarea {
flex: 1;
border: 2px solid var(--border);
border-radius: 8px;
padding: 10px 14px;
font-size: 15px;
resize: none;
height: 60px;
font-family: inherit;
transition: border-color 0.2s;
}
.input-panel textarea:focus {
outline: none;
border-color: var(--primary);
}
.input-panel button {
padding: 10px 24px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: transform 0.1s;
}
.input-panel button:hover { transform: scale(1.03); }
.input-panel button:active { transform: scale(0.97); }
.input-panel .draw-btn { background: var(--secondary); }
/* 右侧面板 */
.right-panels {
display: flex;
flex-direction: column;
gap: 12px;
}
.panel {
background: var(--card);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
padding: 14px;
overflow-y: auto;
}
.panel h3 {
font-size: 14px;
color: var(--accent);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 6px;
}
/* 对话区 */
.chat-panel {
flex: 2;
}
.chat-messages {
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-msg {
padding: 10px 12px;
border-radius: 10px;
font-size: 14px;
line-height: 1.5;
animation: fadeIn 0.3s;
}
.chat-msg.student {
background: var(--light);
align-self: flex-end;
max-width: 85%;
border-bottom-right-radius: 4px;
}
.chat-msg.tutor {
background: linear-gradient(135deg, #E8F8F5, #D5F5E3);
align-self: flex-start;
max-width: 85%;
border-bottom-left-radius: 4px;
}
.chat-msg .role { font-size: 11px; color: #999; margin-bottom: 4px; }
.chat-msg .hint { font-size: 12px; color: var(--accent); margin-top: 6px; padding-top: 6px; border-top: 1px dashed #ccc; }
.typing { opacity: 0.6; }
.typing::after { content: "…"; animation: dots 1.5s infinite; }
@keyframes dots { 0%,20% { content: "." } 40% { content: ".." } 60%,100% { content: "…" } }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
/* SVG 展示区 */
.svg-panel {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 180px;
}
.svg-panel .svg-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.svg-panel .svg-container svg {
max-width: 100%;
max-height: 200px;
}
.svg-placeholder {
color: #ccc;
font-size: 14px;
text-align: center;
}
/* 快捷按钮 */
.quick-btns {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
}
.quick-btns button {
padding: 4px 10px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--light);
cursor: pointer;
font-size: 12px;
}
.quick-btns button:hover { background: var(--accent); color: white; border-color: var(--accent); }
</style>
</head>
<body>
<header>
<span class="logo">🐉</span>
<div>
<h1>数学小卧龙</h1>
<span class="tag">苏格拉底式导师 · 不直接给答案</span>
</div>
</header>
<main>
<!-- 左侧:画布 + 输入 -->
<div class="main-area">
<div class="canvas-panel" id="canvasPanel">
<div class="canvas-toolbar">
<button onclick="setTool('pen')" class="active" id="penBtn">✏️ 画笔</button>
<button onclick="setTool('eraser')" id="eraserBtn">🧹 橡皮</button>
<input type="color" value="#FF6B6B" class="color-pick" id="colorPicker" onchange="setColor(this.value)">
<button onclick="clearCanvas()">🗑️ 清空</button>
<span style="flex:1"></span>
<button onclick="drawShape('pie',[1,1,1,1])">🥧 画饼图</button>
<button onclick="drawShape('numberline',{start:0,end:10})">📏 数轴</button>
<button onclick="drawShape('grid',{rows:3,cols:4})">🔲 格子阵</button>
</div>
<div class="drawing-area" id="drawingArea">
<canvas id="drawCanvas"></canvas>
<div class="svg-display" id="svgOverlay"></div>
</div>
</div>
<div class="input-panel">
<textarea id="questionInput" placeholder="问我任何数学问题吧!比如:½ + ¼ 等于多少?或者 23×17 怎么算?" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();askQuestion()}"></textarea>
<button onclick="askQuestion()">🚀 提问</button>
<button class="draw-btn" onclick="drawRequest()">🎨 画图</button>
</div>
</div>
<!-- 右侧:对话 + SVG -->
<div class="right-panels">
<div class="panel chat-panel">
<h3>💬 苏格拉底对话</h3>
<div class="quick-btns">
<button onclick="askPreset('½ + ¼ 等于多少?')">分数加法</button>
<button onclick="askPreset('23×17 怎么算?')">乘法</button>
<button onclick="askPreset('三角形的面积怎么求?')">几何面积</button>
<button onclick="askPreset('什么是周长?')">周长</button>
</div>
<div class="chat-messages" id="chatMessages">
<div class="chat-msg tutor">
<div class="role">🐉 小卧龙</div>
你好!我是数学小卧龙 🐉<br>我不会直接告诉你答案——但我会用提问和画画,帮你自己发现!准备好了吗?
</div>
</div>
</div>
<div class="panel svg-panel">
<h3>📐 可视化</h3>
<div class="svg-container" id="svgContainer">
<div class="svg-placeholder">提问后这里会出现图形哦~</div>
</div>
</div>
</div>
</main>
<script>
// === 画布引擎 ===
const canvas = document.getElementById('drawCanvas');
const ctx = canvas.getContext('2d');
let tool = 'pen';
let color = '#FF6B6B';
let drawing = false;
function resizeCanvas() {
const area = document.getElementById('drawingArea');
canvas.width = area.clientWidth;
canvas.height = area.clientHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('mousedown', e => { drawing = true; draw(e); });
canvas.addEventListener('mousemove', e => { if(drawing) draw(e); });
canvas.addEventListener('mouseup', () => drawing = false);
canvas.addEventListener('mouseleave', () => drawing = false);
canvas.addEventListener('touchstart', e => { drawing = true; draw(e.touches[0]); });
canvas.addEventListener('touchmove', e => { if(drawing) draw(e.touches[0]); e.preventDefault(); });
canvas.addEventListener('touchend', () => drawing = false);
function draw(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.lineWidth = tool === 'eraser' ? 20 : 3;
ctx.strokeStyle = tool === 'eraser' ? '#FFF8F0' : color;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
if (tool === 'eraser') {
ctx.clearRect(x-10, y-10, 20, 20);
} else {
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
}
function setTool(t) {
tool = t;
document.getElementById('penBtn').classList.toggle('active', t==='pen');
document.getElementById('eraserBtn').classList.toggle('active', t==='eraser');
}
function setColor(c) { color = c; }
function clearCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); }
// === 对话引擎 ===
async function askQuestion() {
const input = document.getElementById('questionInput');
const q = input.value.trim();
if (!q) return;
input.value = '';
addMessage('student', q);
addMessage('tutor', '', true); // typing indicator
try {
const resp = await fetch('/ask', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({question: q})
});
const data = await resp.json();
// 移除 typing
const msgs = document.getElementById('chatMessages');
msgs.removeChild(msgs.lastChild);
addMessage('tutor', data.reply, false, data.visual_hint);
if (data.svg) {
document.getElementById('svgContainer').innerHTML = data.svg;
}
} catch(e) {
const msgs = document.getElementById('chatMessages');
msgs.removeChild(msgs.lastChild);
addMessage('tutor', '哎呀,网络好像出了点问题... 再试一次?😅');
}
}
function askPreset(q) {
document.getElementById('questionInput').value = q;
askQuestion();
}
function addMessage(role, text, typing=false, hint='') {
const div = document.createElement('div');
div.className = `chat-msg ${role}${typing ? ' typing' : ''}`;
div.innerHTML = `<div class="role">${role==='student' ? '🧒 你' : '🐉 小卧龙'}</div>${text}${hint ? `<div class="hint">💡 ${hint}</div>` : ''}`;
const msgs = document.getElementById('chatMessages');
msgs.appendChild(div);
msgs.scrollTop = msgs.scrollHeight;
}
// === 画图请求 ===
async function drawRequest() {
const q = document.getElementById('questionInput').value.trim();
if (!q) return;
try {
const resp = await fetch('/draw', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({type: 'pie', params: {fractions: [1,1,2,1], title: '你的问题可视化'}})
});
const data = await resp.json();
if (data.svg) {
document.getElementById('svgContainer').innerHTML = data.svg;
}
} catch(e) {}
}
function drawShape(type, params) {
fetch('/draw', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({type, params})
}).then(r=>r.json()).then(d=>{
if(d.svg) document.getElementById('svgContainer').innerHTML = d.svg;
});
}
</script>
</body>
</html>