lexlepty commited on
Commit
a788b32
·
verified ·
1 Parent(s): b88427a

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +599 -0
templates/index.html ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>数学题目识别与解答系统</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
9
+ <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
10
+ <style>
11
+ :root {
12
+ --primary: #4F46E5;
13
+ --primary-hover: #4338CA;
14
+ --secondary: #E0E7FF;
15
+ --text: #1F2937;
16
+ --background: #F9FAFB;
17
+ }
18
+
19
+ body {
20
+ font-family: system-ui, -apple-system, sans-serif;
21
+ background-color: var(--background);
22
+ color: var(--text);
23
+ }
24
+
25
+ .card {
26
+ background: white;
27
+ border-radius: 1rem;
28
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
29
+ transition: transform 0.2s;
30
+ }
31
+
32
+ .btn {
33
+ background-color: var(--primary);
34
+ color: white;
35
+ padding: 0.75rem 1.5rem;
36
+ border-radius: 0.5rem;
37
+ transition: all 0.2s;
38
+ font-weight: 500;
39
+ }
40
+
41
+ .btn:hover {
42
+ background-color: var(--primary-hover);
43
+ transform: translateY(-1px);
44
+ }
45
+
46
+ .btn:active {
47
+ transform: translateY(0);
48
+ }
49
+
50
+ .btn-secondary {
51
+ background-color: var(--secondary);
52
+ color: var(--primary);
53
+ }
54
+
55
+ .btn-secondary:hover {
56
+ background-color: #D1D5DB;
57
+ }
58
+
59
+ .loading {
60
+ position: fixed;
61
+ top: 0;
62
+ left: 0;
63
+ width: 100%;
64
+ height: 100%;
65
+ background: rgba(255, 255, 255, 0.9);
66
+ display: none;
67
+ justify-content: center;
68
+ align-items: center;
69
+ z-index: 1000;
70
+ }
71
+
72
+ .spinner {
73
+ width: 50px;
74
+ height: 50px;
75
+ border: 5px solid var(--secondary);
76
+ border-top: 5px solid var(--primary);
77
+ border-radius: 50%;
78
+ animation: spin 1s linear infinite;
79
+ }
80
+
81
+ @keyframes spin {
82
+ 0% { transform: rotate(0deg); }
83
+ 100% { transform: rotate(360deg); }
84
+ }
85
+
86
+ .preview-container {
87
+ aspect-ratio: 16/9;
88
+ overflow: hidden;
89
+ border-radius: 0.5rem;
90
+ background-color: var(--background);
91
+ border: 2px dashed #E5E7EB;
92
+ }
93
+
94
+ .preview-image {
95
+ width: 100%;
96
+ height: 100%;
97
+ object-fit: contain;
98
+ }
99
+
100
+ .solution-box {
101
+ border: 1px solid #E5E7EB;
102
+ border-radius: 0.5rem;
103
+ padding: 1rem;
104
+ margin-bottom: 1rem;
105
+ background-color: #FFFFFF;
106
+ }
107
+
108
+ .solution-box h3 {
109
+ color: var(--primary);
110
+ margin-bottom: 0.5rem;
111
+ }
112
+
113
+ .copy-btn {
114
+ position: absolute;
115
+ right: 1rem;
116
+ top: 1rem;
117
+ padding: 0.5rem 1rem;
118
+ font-size: 0.875rem;
119
+ opacity: 0;
120
+ transition: opacity 0.2s;
121
+ }
122
+
123
+ .solution-box:hover .copy-btn {
124
+ opacity: 1;
125
+ }
126
+ /* 在现有的 style 标签中添加 */
127
+ .prose {
128
+ font-size: 1rem;
129
+ line-height: 1.75;
130
+ color: var(--text);
131
+ }
132
+
133
+ .prose p {
134
+ margin-bottom: 1.25em;
135
+ }
136
+
137
+ .prose .math {
138
+ overflow-x: auto;
139
+ margin: 1em 0;
140
+ }
141
+
142
+ #solution-content,
143
+ #analysis-content {
144
+ opacity: 0;
145
+ transition: opacity 0.3s ease;
146
+ }
147
+
148
+ .solution-box {
149
+ position: relative;
150
+ margin-bottom: 1.5rem;
151
+ padding: 1.5rem;
152
+ }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <div class="loading">
157
+ <div class="spinner"></div>
158
+ </div>
159
+
160
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
161
+ <h1 class="text-4xl font-bold text-center mb-8 text-gray-900">数学题目识别与解答系统</h1>
162
+
163
+ <!-- 上传部分 -->
164
+ <div class="card p-6 mb-8">
165
+ <h2 class="text-2xl font-semibold mb-4">图片上传</h2>
166
+ <div class="space-y-4">
167
+ <input type="file"
168
+ id="image-input"
169
+ accept="image/*"
170
+ class="hidden"
171
+ onchange="handleImageUpload(event)">
172
+ <label for="image-input"
173
+ class="btn inline-block cursor-pointer">
174
+ 选择图片
175
+ </label>
176
+ <div class="preview-container">
177
+ <img id="preview-image"
178
+ class="preview-image"
179
+ src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
180
+ alt="预览">
181
+ </div>
182
+ <button onclick="processImage()" class="btn w-full">
183
+ 开始识别
184
+ </button>
185
+ </div>
186
+ </div>
187
+
188
+ <!-- 识别结果 -->
189
+ <div id="result-section" class="card p-6 mb-8" style="display: none;">
190
+ <h2 class="text-2xl font-semibold mb-4">识别结果</h2>
191
+ <div class="grid md:grid-cols-2 gap-6">
192
+ <div>
193
+ <h3 class="text-lg font-medium mb-2">源代码</h3>
194
+ <textarea id="source-text"
195
+ class="w-full h-64 p-4 border rounded-lg resize-y font-mono text-sm"
196
+ readonly></textarea>
197
+ <div class="flex gap-2 mt-2">
198
+ <button onclick="copyContent('text')" class="btn">复制文本</button>
199
+ <button onclick="copyContent('formulas')" class="btn">复制公式</button>
200
+ <button onclick="copyContent('all')" class="btn">复制全部</button>
201
+ </div>
202
+ </div>
203
+ <div>
204
+ <h3 class="text-lg font-medium mb-2">预览</h3>
205
+ <div id="preview-text"
206
+ class="w-full h-64 p-4 border rounded-lg overflow-y-auto bg-white"></div>
207
+ </div>
208
+ </div>
209
+ <div class="mt-6">
210
+ <button onclick="getSolution()" class="btn w-full">
211
+ 获取解答
212
+ </button>
213
+ </div>
214
+ </div>
215
+
216
+ <!-- 解答部分的 HTML -->
217
+ <!-- 解答部分的样式 -->
218
+ <style>
219
+ .math-solution-text {
220
+ font-size: 1rem;
221
+ line-height: 1.75;
222
+ color: #1F2937;
223
+ background-color: white;
224
+ padding: 1rem 1.5rem;
225
+ border-radius: 0.5rem;
226
+ margin-top: 0.5rem;
227
+ white-space: pre-wrap;
228
+ word-wrap: break-word;
229
+ border: 1px solid #E5E7EB;
230
+ min-height: 2rem;
231
+ }
232
+
233
+ .math-solution-text .math {
234
+ display: inline-block;
235
+ margin: 0.5em 0;
236
+ overflow-x: auto;
237
+ max-width: 100%;
238
+ }
239
+
240
+ .math-solution-text p {
241
+ margin-bottom: 1em;
242
+ }
243
+ </style>
244
+
245
+ <!-- 解答部分的 HTML -->
246
+ <div id="solution-section" class="card p-6" style="display: none;">
247
+ <!-- 答案部分 -->
248
+ <div class="solution-box relative mb-4">
249
+ <h3 class="text-xl font-medium">答案</h3>
250
+ <button onclick="copySolutionContent('answer')"
251
+ class="copy-btn btn-secondary">
252
+ 复制答案
253
+ </button>
254
+ <div id="answer-content" class="math-solution-text"></div>
255
+ </div>
256
+
257
+ <!-- 解析部分 -->
258
+ <div class="solution-box relative">
259
+ <h3 class="text-xl font-medium">解析</h3>
260
+ <button onclick="copySolutionContent('analysis')"
261
+ class="copy-btn btn-secondary">
262
+ 复制解析
263
+ </button>
264
+ <div id="analysis-content" class="math-solution-text"></div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <script>
270
+ let currentResult = null;
271
+ let originalLatexContent = '';
272
+
273
+ function toggleLoading(show) {
274
+ document.querySelector('.loading').style.display = show ? 'flex' : 'none';
275
+ }
276
+
277
+ function showToast(message, type = 'info') {
278
+ alert(message); // 简单起见使用alert
279
+ }
280
+
281
+ function handleImageUpload(event) {
282
+ const file = event.target.files[0];
283
+ if (!file) return;
284
+
285
+ const reader = new FileReader();
286
+ reader.onload = (e) => {
287
+ document.getElementById('preview-image').src = e.target.result;
288
+ };
289
+ reader.readAsDataURL(file);
290
+ }
291
+
292
+ async function processImage() {
293
+ const fileInput = document.getElementById('image-input');
294
+ const file = fileInput.files[0];
295
+
296
+ if (!file) {
297
+ showToast('请先选择图片', 'error');
298
+ return;
299
+ }
300
+
301
+ toggleLoading(true);
302
+
303
+ try {
304
+ const formData = new FormData();
305
+ formData.append('file', file);
306
+
307
+ const response = await fetch('/process', {
308
+ method: 'POST',
309
+ body: formData
310
+ });
311
+
312
+ if (!response.ok) {
313
+ throw new Error('处理失败');
314
+ }
315
+
316
+ const result = await response.json();
317
+
318
+ if (result.error) {
319
+ throw new Error(result.error);
320
+ }
321
+
322
+ currentResult = result.result;
323
+
324
+ // 更新预览图片
325
+ document.getElementById('preview-image').src =
326
+ `data:image/png;base64,${result.original_image}`;
327
+
328
+ // 更新源代码视图
329
+ document.getElementById('source-text').value =
330
+ JSON.stringify(currentResult, null, 2);
331
+
332
+ // 保存原始带LaTeX的内容
333
+ originalLatexContent = currentResult.text;
334
+ currentResult.formulas.forEach((formula, index) => {
335
+ originalLatexContent = originalLatexContent.replace(
336
+ `[formula_${index + 1}]`,
337
+ `$${formula}$`
338
+ );
339
+ });
340
+
341
+ // 更新预览视图
342
+ const previewDiv = document.getElementById('preview-text');
343
+ previewDiv.innerHTML = originalLatexContent;
344
+ MathJax.typesetPromise([previewDiv]).catch(console.error);
345
+
346
+ document.getElementById('result-section').style.display = 'block';
347
+ showToast('识别成功');
348
+
349
+ } catch (error) {
350
+ showToast(error.message, 'error');
351
+ console.error('处理错误:', error);
352
+ } finally {
353
+ toggleLoading(false);
354
+ }
355
+ }
356
+
357
+ async function copyContent(type) {
358
+ if (!currentResult) return;
359
+
360
+ try {
361
+ let content = '';
362
+ switch (type) {
363
+ case 'text':
364
+ content = currentResult.text;
365
+ break;
366
+ case 'formulas':
367
+ content = currentResult.formulas.join('\n');
368
+ break;
369
+ case 'all':
370
+ content = originalLatexContent;
371
+ break;
372
+ }
373
+
374
+ await navigator.clipboard.writeText(content);
375
+ showToast('复制成功');
376
+ } catch (err) {
377
+ showToast('复制失败', 'error');
378
+ console.error('复制错误:', err);
379
+ }
380
+ }
381
+
382
+ async function copySolutionContent(type) {
383
+ const element = document.getElementById(`${type}-content`);
384
+ if (!element) return;
385
+
386
+ try {
387
+ const content = element.getAttribute('data-original') || element.textContent;
388
+ await navigator.clipboard.writeText(content);
389
+ showToast('复制成功');
390
+ } catch (err) {
391
+ showToast('复制失败', 'error');
392
+ console.error('复制错误:', err);
393
+ }
394
+ }
395
+
396
+ async function getSolution() {
397
+ if (!currentResult) {
398
+ showToast('没有可解答的内容', 'error');
399
+ return;
400
+ }
401
+
402
+ toggleLoading(true);
403
+
404
+ const solutionSection = document.getElementById('solution-section');
405
+ const answerContent = document.getElementById('answer-content');
406
+ const analysisContent = document.getElementById('analysis-content');
407
+
408
+ solutionSection.style.display = 'block';
409
+ answerContent.innerHTML = '';
410
+ analysisContent.innerHTML = '';
411
+
412
+ try {
413
+ const response = await fetch('/solve', {
414
+ method: 'POST',
415
+ headers: {
416
+ 'Content-Type': 'application/json'
417
+ },
418
+ body: JSON.stringify({
419
+ text: currentResult.text,
420
+ formulas: currentResult.formulas
421
+ })
422
+ });
423
+
424
+ if (!response.ok) throw new Error('获取解答失败');
425
+
426
+ const reader = response.body.getReader();
427
+ const decoder = new TextDecoder();
428
+ let answer = '';
429
+ let analysis = '';
430
+
431
+ while (true) {
432
+ const {value, done} = await reader.read();
433
+ if (done) break;
434
+
435
+ const chunk = decoder.decode(value);
436
+ const lines = chunk.split('\n');
437
+ for (const line of lines) {
438
+ if (!line.trim() || !line.startsWith('data: ')) continue;
439
+
440
+ const data = JSON.parse(line.slice(5));
441
+
442
+ if (data.content) {
443
+ if (data.type === 'answer') {
444
+ // 将 $$ 替换为 \[ 和 \]
445
+ answer += data.content.replace(/\$\$(.*?)\$\$/g, '\\[$1\\]');
446
+ answerContent.innerHTML = answer;
447
+ answerContent.setAttribute('data-original', answer);
448
+ await MathJax.typesetPromise([answerContent]);
449
+ } else if (data.type === 'analysis') {
450
+ // 直接使用内容,保持 $ 符号的处理方式与预览一致
451
+ analysis += data.content;
452
+ analysisContent.innerHTML = analysis;
453
+ analysisContent.setAttribute('data-original', analysis);
454
+ await MathJax.typesetPromise([analysisContent]);
455
+ analysisContent.style.opacity = '1';
456
+ }
457
+ }
458
+ }
459
+ }
460
+ } catch (error) {
461
+ showToast(error.message, 'error');
462
+ answerContent.innerHTML = `<p class="text-red-500">获取解答失败: ${error.message}</p>`;
463
+ } finally {
464
+ toggleLoading(false);
465
+ }
466
+ }
467
+ async function copySolutionContent(type) {
468
+ const element = document.getElementById(`${type}-content`);
469
+ if (!element) return;
470
+
471
+ try {
472
+ const content = element.getAttribute('data-original');
473
+ if (content) {
474
+ await navigator.clipboard.writeText(content);
475
+ showToast('复制成功');
476
+
477
+ // 添加复制成功的动画效果
478
+ const button = element.previousElementSibling;
479
+ const originalText = button.textContent;
480
+ button.textContent = '复制成功';
481
+ button.classList.add('bg-green-100');
482
+
483
+ setTimeout(() => {
484
+ button.textContent = originalText;
485
+ button.classList.remove('bg-green-100');
486
+ }, 1000);
487
+ }
488
+ } catch (err) {
489
+ showToast('复制失败', 'error');
490
+ console.error('复制错误:', err);
491
+ }
492
+ }
493
+
494
+ window.MathJax = {
495
+ tex: {
496
+ inlineMath: [['$', '$'], ['\\(', '\\)']],
497
+ displayMath: [['$$', '$$'], ['\\[', '\\]']],
498
+ processEscapes: true
499
+ },
500
+ options: {
501
+ ignoreHtmlClass: 'tex2jax_ignore',
502
+ processHtmlClass: 'tex2jax_process'
503
+ },
504
+ startup: {
505
+ pageReady() {
506
+ return MathJax.startup.defaultPageReady();
507
+ }
508
+ }
509
+ };
510
+
511
+ // 添加复制成功的动画效果
512
+ function addCopyAnimation(button) {
513
+ const originalText = button.textContent;
514
+ button.textContent = '已复制';
515
+ button.classList.add('bg-green-500');
516
+
517
+ setTimeout(() => {
518
+ button.textContent = originalText;
519
+ button.classList.remove('bg-green-500');
520
+ }, 1000);
521
+ }
522
+
523
+ // 优化解答和解析的展示逻辑
524
+ function updateSolutionDisplay(content, type) {
525
+ const container = document.getElementById(`${type}-content`);
526
+ if (!container) return;
527
+
528
+ // 保存原始内容用于复制
529
+ const originalContent = content;
530
+ container.setAttribute('data-original', originalContent);
531
+
532
+ // 渲染内容
533
+ container.innerHTML = content;
534
+
535
+ // 渲染数学公式
536
+ MathJax.typesetPromise([container]).then(() => {
537
+ // 添加滑入动画
538
+ container.style.opacity = '0';
539
+ container.style.transform = 'translateY(20px)';
540
+ container.style.transition = 'all 0.5s ease';
541
+
542
+ requestAnimationFrame(() => {
543
+ container.style.opacity = '1';
544
+ container.style.transform = 'translateY(0)';
545
+ });
546
+ }).catch(console.error);
547
+ }
548
+
549
+ // 优化错误处理
550
+ function handleError(error, container) {
551
+ const errorMessage = document.createElement('div');
552
+ errorMessage.className = 'bg-red-50 border-l-4 border-red-500 p-4 my-4';
553
+ errorMessage.innerHTML = `
554
+ <div class="flex items-center">
555
+ <div class="flex-shrink-0">
556
+ <svg class="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
557
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
558
+ </svg>
559
+ </div>
560
+ <div class="ml-3">
561
+ <p class="text-sm text-red-700">
562
+ ${error.message || '发生错误,请稍后重试'}
563
+ </p>
564
+ </div>
565
+ </div>
566
+ `;
567
+ container.appendChild(errorMessage);
568
+ }
569
+
570
+ // 全局错误处理
571
+ window.addEventListener('error', (event) => {
572
+ console.error('全局错误:', event.error);
573
+ showToast('操作出错,请刷新页面重试', 'error');
574
+ });
575
+
576
+ // 优化图片上传体验
577
+ document.getElementById('image-input').addEventListener('change', (event) => {
578
+ const file = event.target.files[0];
579
+ if (!file) return;
580
+
581
+ // 验证文件类型
582
+ if (!file.type.startsWith('image/')) {
583
+ showToast('请选择图片文件', 'error');
584
+ event.target.value = '';
585
+ return;
586
+ }
587
+
588
+ // 验证文件大小(最大10MB)
589
+ if (file.size > 10 * 1024 * 1024) {
590
+ showToast('图片大小不能超过10MB', 'error');
591
+ event.target.value = '';
592
+ return;
593
+ }
594
+
595
+ handleImageUpload(event);
596
+ });
597
+ </script>
598
+ </body>
599
+ </html>