mistpe commited on
Commit
eaab80c
·
verified ·
1 Parent(s): 4fbc83b

Update app/templates/article.html

Browse files
Files changed (1) hide show
  1. app/templates/article.html +688 -611
app/templates/article.html CHANGED
@@ -1,612 +1,689 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}{{ article.title }} - 个人博客{% endblock %}
4
-
5
- {% block extra_css %}
6
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
7
- <style>
8
- /* 文章容器样式 */
9
- .article-container {
10
- max-width: 900px;
11
- margin: 0 auto;
12
- background: white;
13
- border-radius: 20px;
14
- box-shadow: 0 2px 12px rgba(99, 145, 197, 0.08);
15
- border: 2px solid var(--light-blue);
16
- padding: 2.5rem;
17
- }
18
-
19
- /* 文章头部样式 */
20
- .article-header {
21
- margin-bottom: 2.5rem;
22
- padding-bottom: 1.5rem;
23
- border-bottom: 1px solid var(--light-blue);
24
- }
25
-
26
- .article-title {
27
- font-size: 2.5rem;
28
- font-weight: 700;
29
- color: var(--text-dark);
30
- line-height: 1.3;
31
- margin-bottom: 1rem;
32
- background: linear-gradient(135deg, var(--primary-blue), var(--soft-purple));
33
- -webkit-background-clip: text;
34
- -webkit-text-fill-color: transparent;
35
- }
36
-
37
- .article-meta {
38
- display: flex;
39
- align-items: center;
40
- gap: 1.5rem;
41
- color: #64748B;
42
- }
43
-
44
- .meta-item {
45
- display: flex;
46
- align-items: center;
47
- gap: 0.5rem;
48
- }
49
-
50
- .meta-item i {
51
- color: var(--primary-blue);
52
- }
53
-
54
- /* AI 摘要样式 */
55
- .article-summary {
56
- background: var(--warm-cream);
57
- border-radius: 16px;
58
- padding: 1.5rem;
59
- margin: 2rem 0;
60
- position: relative;
61
- }
62
-
63
- .summary-label {
64
- position: absolute;
65
- top: -12px;
66
- left: 16px;
67
- background: var(--primary-blue);
68
- color: white;
69
- padding: 0.25rem 1rem;
70
- border-radius: 20px;
71
- font-size: 0.875rem;
72
- font-weight: 500;
73
- }
74
-
75
- /* 文章内容样式 */
76
- .article-content {
77
- line-height: 1.8;
78
- color: var(--text-dark);
79
- }
80
-
81
- .markdown-body {
82
- font-size: 1.1rem;
83
- }
84
-
85
- .markdown-body h1,
86
- .markdown-body h2,
87
- .markdown-body h3 {
88
- color: var(--primary-blue);
89
- margin-top: 2em;
90
- margin-bottom: 1em;
91
- font-weight: 600;
92
- }
93
-
94
- .markdown-body p {
95
- margin-bottom: 1.5em;
96
- }
97
-
98
- .markdown-body a {
99
- color: var(--primary-blue);
100
- text-decoration: none;
101
- border-bottom: 1px dashed var(--light-blue);
102
- transition: all 0.3s;
103
- }
104
-
105
- .markdown-body a:hover {
106
- border-bottom-style: solid;
107
- color: var(--soft-purple);
108
- }
109
-
110
- .markdown-body code {
111
- background: #F8FAFC;
112
- padding: 0.2em 0.4em;
113
- border-radius: 4px;
114
- font-size: 0.9em;
115
- color: var(--primary-blue);
116
- }
117
-
118
- .markdown-body pre {
119
- background: #F8FAFC;
120
- border-radius: 12px;
121
- padding: 1rem;
122
- overflow-x: auto;
123
- border: 1px solid var(--light-blue);
124
- }
125
-
126
- .markdown-body pre code {
127
- background: none;
128
- padding: 0;
129
- color: inherit;
130
- }
131
-
132
- .markdown-body blockquote {
133
- border-left: 4px solid var(--light-blue);
134
- padding: 0.5rem 0 0.5rem 1rem;
135
- margin: 1.5rem 0;
136
- color: #64748B;
137
- background: #F8FAFC;
138
- }
139
-
140
- .markdown-body img {
141
- max-width: 100%;
142
- border-radius: 12px;
143
- margin: 1.5rem 0;
144
- }
145
-
146
- /* AI 聊天窗口样式 */
147
- .chat-toggle {
148
- position: fixed;
149
- right: 2rem;
150
- bottom: 2rem;
151
- width: 56px;
152
- height: 56px;
153
- border-radius: 28px;
154
- background: linear-gradient(135deg, var(--primary-blue), var(--light-blue));
155
- color: white;
156
- border: none;
157
- cursor: pointer;
158
- display: flex;
159
- align-items: center;
160
- justify-content: center;
161
- font-size: 1.5rem;
162
- box-shadow: 0 4px 12px rgba(99, 145, 197, 0.2);
163
- transition: all 0.3s;
164
- z-index: 998;
165
- }
166
-
167
- .chat-toggle:hover {
168
- transform: translateY(-2px);
169
- box-shadow: 0 6px 16px rgba(99, 145, 197, 0.3);
170
- }
171
-
172
- .chat-window {
173
- position: fixed;
174
- right: 2rem;
175
- bottom: 2rem;
176
- width: 380px;
177
- height: 600px;
178
- background: white;
179
- border-radius: 20px;
180
- box-shadow: 0 4px 20px rgba(99, 145, 197, 0.15);
181
- display: flex;
182
- flex-direction: column;
183
- transform: scale(0);
184
- opacity: 0;
185
- transform-origin: bottom right;
186
- transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
187
- z-index: 999;
188
- border: 1px solid var(--light-blue);
189
- }
190
-
191
- .chat-window.active {
192
- transform: scale(1);
193
- opacity: 1;
194
- }
195
-
196
- .chat-header {
197
- padding: 1.25rem;
198
- background: linear-gradient(135deg, var(--primary-blue), var(--light-blue));
199
- color: white;
200
- border-radius: 20px 20px 0 0;
201
- display: flex;
202
- align-items: center;
203
- gap: 0.75rem;
204
- }
205
-
206
- .chat-title {
207
- font-weight: 600;
208
- flex: 1;
209
- }
210
-
211
- .chat-close {
212
- background: none;
213
- border: none;
214
- color: white;
215
- cursor: pointer;
216
- width: 32px;
217
- height: 32px;
218
- display: flex;
219
- align-items: center;
220
- justify-content: center;
221
- border-radius: 16px;
222
- transition: all 0.3s;
223
- }
224
-
225
- .chat-close:hover {
226
- background: rgba(255, 255, 255, 0.2);
227
- }
228
-
229
- .chat-messages {
230
- flex: 1;
231
- overflow-y: auto;
232
- overflow-x: hidden;
233
- padding: 1.5rem;
234
- display: flex;
235
- flex-direction: column;
236
- gap: 1rem;
237
- width: 100%;
238
- }
239
-
240
- .chat-message {
241
- max-width: 85%;
242
- padding: 1rem;
243
- border-radius: 16px;
244
- line-height: 1.3;
245
- animation: messageSlide 0.3s ease;
246
- word-wrap: break-word;
247
- overflow-wrap: break-word;
248
- width: fit-content;
249
- }
250
-
251
- .chat-message p {
252
- margin: 0;
253
- white-space: pre-wrap;
254
- }
255
-
256
- .chat-message img,
257
- .chat-message pre,
258
- .chat-message code {
259
- max-width: 100%;
260
- overflow-x: hidden;
261
- }
262
- .chat-message.user {
263
- background: var(--primary-blue);
264
- color: white;
265
- margin-left: auto;
266
- }
267
-
268
- .chat-message.assistant {
269
- background: #7b85b8;
270
- color: white;
271
- margin-right: auto;
272
- }
273
-
274
- .chat-input-container {
275
- padding: 1.25rem;
276
- border-top: 1px solid var(--light-blue);
277
- }
278
-
279
- .chat-input-wrapper {
280
- display: flex;
281
- gap: 0.75rem;
282
- align-items: flex-end;
283
- }
284
-
285
- .chat-input {
286
- flex: 1;
287
- min-height: 44px;
288
- max-height: 120px;
289
- padding: 0.75rem 1rem;
290
- border: 2px solid var(--light-blue);
291
- border-radius: 12px;
292
- resize: none;
293
- font-size: 1rem;
294
- line-height: 1.5;
295
- transition: all 0.3s;
296
- }
297
-
298
- .chat-input:focus {
299
- outline: none;
300
- border-color: var(--primary-blue);
301
- box-shadow: 0 0 0 3px rgba(99, 145, 197, 0.1);
302
- }
303
-
304
- .chat-send {
305
- background: var(--primary-blue);
306
- color: white;
307
- width: 44px;
308
- height: 44px;
309
- border: none;
310
- border-radius: 12px;
311
- cursor: pointer;
312
- display: flex;
313
- align-items: center;
314
- justify-content: center;
315
- transition: all 0.3s;
316
- }
317
-
318
- .chat-send:hover {
319
- background: var(--light-blue);
320
- transform: translateY(-2px);
321
- }
322
-
323
- /* 动画 */
324
- @keyframes messageSlide {
325
- from {
326
- opacity: 0;
327
- transform: translateY(10px);
328
- }
329
- to {
330
- opacity: 1;
331
- transform: translateY(0);
332
- }
333
- }
334
-
335
- /* 响应式设计 */
336
- @media (max-width: 768px) {
337
- .article-container {
338
- padding: 1.5rem;
339
- border-radius: 12px;
340
- }
341
-
342
- .article-title {
343
- font-size: 2rem;
344
- }
345
-
346
- .chat-window {
347
- right: 1rem;
348
- bottom: 1rem;
349
- left: 1rem;
350
- width: auto;
351
- height: 500px;
352
- }
353
-
354
- .chat-toggle {
355
- right: 1rem;
356
- bottom: 1rem;
357
- }
358
- }
359
- </style>
360
- {% endblock %}
361
-
362
- {% block content %}
363
- <!-- 文章内容 -->
364
- <article class="article-container">
365
- <header class="article-header">
366
- <h1 class="article-title">{{ article.title }}</h1>
367
- <div class="article-meta">
368
- <div class="meta-item">
369
- <i class="fas fa-calendar"></i>
370
- <span>{{ article.created_at.strftime('%Y-%m-%d') }}</span>
371
- </div>
372
- </div>
373
- </header>
374
-
375
- {% if article.summary %}
376
- <div class="article-summary">
377
- <span class="summary-label">AI 摘要</span>
378
- <p>{{ article.summary }}</p>
379
- </div>
380
- {% endif %}
381
-
382
- <div class="article-content markdown-body">
383
- {{ article.content|markdown }}
384
- </div>
385
- </article>
386
-
387
- <!-- AI 聊天窗口 -->
388
- <button class="chat-toggle" id="chatToggle">
389
- <i class="fas fa-robot"></i>
390
- </button>
391
-
392
- <div class="chat-window" id="chatWindow">
393
- <div class="chat-header">
394
- <i class="fas fa-robot"></i>
395
- <span class="chat-title">AI 智能助手</span>
396
- <button class="chat-close" id="chatClose">
397
- <i class="fas fa-times"></i>
398
- </button>
399
- </div>
400
- <div class="chat-messages" id="chatMessages"></div>
401
- <div class="chat-input-wrapper">
402
- <textarea
403
- id="chatInput"
404
- class="chat-input"
405
- placeholder="输入您的问题..."
406
- rows="1"
407
- ></textarea>
408
- <button class="chat-send" onclick="sendMessage()">
409
- <i class="fas fa-paper-plane"></i>
410
- </button>
411
- </div>
412
- </div>
413
- {% endblock %}
414
-
415
- {% block extra_js %}
416
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
417
- <script>
418
- // 初始化文章上下文
419
- window.articleContext = {
420
- title: {{ article.title|tojson|safe }},
421
- content: {{ article.content|tojson|safe }}
422
- };
423
-
424
- // 配置Marked
425
- marked.setOptions({
426
- breaks: true,
427
- gfm: true
428
- });
429
-
430
- // 聊天窗口控制
431
- const chatToggle = document.getElementById('chatToggle');
432
- const chatWindow = document.getElementById('chatWindow');
433
- const chatClose = document.getElementById('chatClose');
434
- const chatInput = document.getElementById('chatInput');
435
- const chatMessages = document.getElementById('chatMessages');
436
-
437
- // 模型上下文
438
- const modelContext = `这是一篇关于"${window.articleContext.title}"的文章。文章内容:\n\n${window.articleContext.content}\n\n请基于以上文章内容来回答用户的问题。`;
439
-
440
- // 欢迎消息
441
- const welcomeMessage = `您好!我是这篇《${window.articleContext.title}》的AI助手。我已经仔细阅读了全文,可以解答您关于文章内容的任何问题,也提供更深入的讨论和见解,从而帮助您更好地理解文章要点
442
- 让我们开始对话吧!`;
443
- // 初始化消息数组
444
- let messages = [{
445
- role: 'system',
446
- content: modelContext
447
- }];
448
-
449
- // 初始化聊天
450
- function initializeChat() {
451
- displayMessage('assistant', welcomeMessage);
452
- }
453
-
454
- // 切换聊天窗口
455
- function toggleChat() {
456
- chatWindow.classList.toggle('active');
457
- if (chatWindow.classList.contains('active')) {
458
- chatToggle.style.display = 'none';
459
- chatInput.focus();
460
- if (chatMessages.children.length === 0) {
461
- initializeChat();
462
- }
463
- } else {
464
- chatToggle.style.display = 'flex';
465
- }
466
- }
467
-
468
- chatToggle.addEventListener('click', toggleChat);
469
- chatClose.addEventListener('click', toggleChat);
470
-
471
- // 自动调整文本框高度
472
- chatInput.addEventListener('input', function() {
473
- this.style.height = 'auto';
474
- this.style.height = Math.min(this.scrollHeight, 120) + 'px';
475
- });
476
-
477
- // 发送消息
478
- async function sendMessage() {
479
- const messageText = chatInput.value.trim();
480
- if (!messageText) return;
481
-
482
- const userMessage = {
483
- role: 'user',
484
- content: messageText
485
- };
486
-
487
- // 重置输入框
488
- chatInput.value = '';
489
- chatInput.style.height = 'auto';
490
-
491
- // 显示用户消息
492
- displayMessage('user', messageText);
493
-
494
- try {
495
- const currentMessages = [...messages, userMessage];
496
-
497
- const response = await fetch('/api/chat', {
498
- method: 'POST',
499
- headers: {
500
- 'Content-Type': 'application/json'
501
- },
502
- body: JSON.stringify({ messages: currentMessages })
503
- });
504
-
505
- if (response.ok) {
506
- const data = await response.json();
507
-
508
- // 更新消息历史
509
- messages.push(userMessage);
510
- messages.push({
511
- role: 'assistant',
512
- content: data.response
513
- });
514
-
515
- // 显示AI响应
516
- displayMessage('assistant', data.response);
517
- } else {
518
- throw new Error('Network response was not ok');
519
- }
520
- } catch (error) {
521
- console.error('Error:', error);
522
- displayMessage('assistant', '抱歉,发生了错误,请稍后再试。');
523
- }
524
- }
525
-
526
- // 显示消息
527
- function displayMessage(role, content) {
528
- const messageDiv = document.createElement('div');
529
- messageDiv.className = `chat-message ${role}`;
530
-
531
- // 使用marked渲染Markdown内容
532
- messageDiv.innerHTML = marked.parse(content);
533
-
534
- chatMessages.appendChild(messageDiv);
535
- chatMessages.scrollTop = chatMessages.scrollHeight;
536
- }
537
-
538
- // 回车发送消息
539
- chatInput.addEventListener('keypress', function(event) {
540
- if (event.key === 'Enter' && !event.shiftKey) {
541
- event.preventDefault();
542
- sendMessage();
543
- }
544
- });
545
-
546
- // 聊天窗口拖动功能
547
- let isDragging = false;
548
- let currentX;
549
- let currentY;
550
- let initialX;
551
- let initialY;
552
- let xOffset = 0;
553
- let yOffset = 0;
554
-
555
- chatWindow.addEventListener('mousedown', dragStart);
556
- document.addEventListener('mousemove', drag);
557
- document.addEventListener('mouseup', dragEnd);
558
-
559
- function dragStart(e) {
560
- if (e.target.closest('.chat-header') && !e.target.closest('.chat-close')) {
561
- initialX = e.clientX - xOffset;
562
- initialY = e.clientY - yOffset;
563
- isDragging = true;
564
- chatWindow.style.cursor = 'grabbing';
565
- }
566
- }
567
-
568
- function drag(e) {
569
- if (isDragging) {
570
- e.preventDefault();
571
- currentX = e.clientX - initialX;
572
- currentY = e.clientY - initialY;
573
- xOffset = currentX;
574
- yOffset = currentY;
575
-
576
- // 确保窗口不会超出视口边界
577
- const rect = chatWindow.getBoundingClientRect();
578
- const viewportWidth = window.innerWidth;
579
- const viewportHeight = window.innerHeight;
580
-
581
- // 限制X轴移动
582
- if (rect.left < 0) {
583
- currentX -= rect.left;
584
- }
585
- if (rect.right > viewportWidth) {
586
- currentX -= (rect.right - viewportWidth);
587
- }
588
-
589
- // 限制Y轴移动
590
- if (rect.top < 0) {
591
- currentY -= rect.top;
592
- }
593
- if (rect.bottom > viewportHeight) {
594
- currentY -= (rect.bottom - viewportHeight);
595
- }
596
-
597
- setTranslate(currentX, currentY, chatWindow);
598
- }
599
- }
600
-
601
- function dragEnd() {
602
- initialX = currentX;
603
- initialY = currentY;
604
- isDragging = false;
605
- chatWindow.style.cursor = 'default';
606
- }
607
-
608
- function setTranslate(xPos, yPos, el) {
609
- el.style.transform = `translate(${xPos}px, ${yPos}px)`;
610
- }
611
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  {% endblock %}
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ article.title }} - 个人博客{% endblock %}
4
+
5
+ {% block extra_css %}
6
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
7
+ <style>
8
+ /* 文章容器样式 */
9
+ .article-container {
10
+ max-width: 900px;
11
+ margin: 0 auto;
12
+ background: white;
13
+ border-radius: 20px;
14
+ box-shadow: 0 2px 12px rgba(99, 145, 197, 0.08);
15
+ border: 2px solid var(--light-blue);
16
+ padding: 2.5rem;
17
+ }
18
+
19
+ /* 文章头部样式 */
20
+ .article-header {
21
+ margin-bottom: 2.5rem;
22
+ padding-bottom: 1.5rem;
23
+ border-bottom: 1px solid var(--light-blue);
24
+ }
25
+
26
+ .article-title {
27
+ font-size: 2.5rem;
28
+ font-weight: 700;
29
+ color: var(--text-dark);
30
+ line-height: 1.3;
31
+ margin-bottom: 1rem;
32
+ background: linear-gradient(135deg, var(--primary-blue), var(--soft-purple));
33
+ -webkit-background-clip: text;
34
+ -webkit-text-fill-color: transparent;
35
+ }
36
+
37
+ .article-meta {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 1.5rem;
41
+ color: #64748B;
42
+ }
43
+
44
+ .meta-item {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 0.5rem;
48
+ }
49
+
50
+ .meta-item i {
51
+ color: var(--primary-blue);
52
+ }
53
+
54
+ /* AI 摘要样式 */
55
+ .article-summary {
56
+ background: var(--warm-cream);
57
+ border-radius: 16px;
58
+ padding: 1.5rem;
59
+ margin: 2rem 0;
60
+ position: relative;
61
+ }
62
+
63
+ .summary-label {
64
+ position: absolute;
65
+ top: -12px;
66
+ left: 16px;
67
+ background: var(--primary-blue);
68
+ color: white;
69
+ padding: 0.25rem 1rem;
70
+ border-radius: 20px;
71
+ font-size: 0.875rem;
72
+ font-weight: 500;
73
+ }
74
+
75
+ /* 文章内容样式 */
76
+ .article-content {
77
+ line-height: 1.8;
78
+ color: var(--text-dark);
79
+ }
80
+
81
+ .article-content .markdown-body {
82
+ font-size: 1.1rem;
83
+ }
84
+
85
+ .article-content .markdown-body h1,
86
+ .article-content .markdown-body h2,
87
+ .article-content .markdown-body h3 {
88
+ color: var(--primary-blue);
89
+ margin-top: 2em;
90
+ margin-bottom: 1em;
91
+ font-weight: 600;
92
+ }
93
+
94
+ .article-content .markdown-body p {
95
+ margin-bottom: 1.5em;
96
+ }
97
+
98
+ .article-content .markdown-body a {
99
+ color: var(--primary-blue);
100
+ text-decoration: none;
101
+ border-bottom: 1px dashed var(--light-blue);
102
+ transition: all 0.3s;
103
+ }
104
+
105
+ .article-content .markdown-body a:hover {
106
+ border-bottom-style: solid;
107
+ color: var(--soft-purple);
108
+ }
109
+
110
+ .article-content .markdown-body code {
111
+ background: #F8FAFC;
112
+ padding: 0.2em 0.4em;
113
+ border-radius: 4px;
114
+ font-size: 0.9em;
115
+ color: var(--primary-blue);
116
+ }
117
+
118
+ .article-content .markdown-body pre {
119
+ background: #F8FAFC;
120
+ border-radius: 12px;
121
+ padding: 1rem;
122
+ overflow-x: auto;
123
+ border: 1px solid var(--light-blue);
124
+ }
125
+
126
+ .article-content .markdown-body pre code {
127
+ background: none;
128
+ padding: 0;
129
+ color: inherit;
130
+ }
131
+
132
+ .article-content .markdown-body blockquote {
133
+ border-left: 4px solid var(--light-blue);
134
+ padding: 0.5rem 0 0.5rem 1rem;
135
+ margin: 1.5rem 0;
136
+ color: #64748B;
137
+ background: #F8FAFC;
138
+ }
139
+
140
+ .article-content .markdown-body img {
141
+ max-width: 100%;
142
+ border-radius: 12px;
143
+ margin: 1.5rem 0;
144
+ }
145
+
146
+ /* AI 聊天窗口样式 */
147
+ .chat-toggle {
148
+ position: fixed;
149
+ right: 2rem;
150
+ bottom: 2rem;
151
+ width: 56px;
152
+ height: 56px;
153
+ border-radius: 28px;
154
+ background: linear-gradient(135deg, var(--primary-blue), var(--light-blue));
155
+ color: white;
156
+ border: none;
157
+ cursor: pointer;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ font-size: 1.5rem;
162
+ box-shadow: 0 4px 12px rgba(99, 145, 197, 0.2);
163
+ transition: all 0.3s;
164
+ z-index: 998;
165
+ }
166
+
167
+ .chat-toggle:hover {
168
+ transform: translateY(-2px);
169
+ box-shadow: 0 6px 16px rgba(99, 145, 197, 0.3);
170
+ }
171
+
172
+ .chat-window {
173
+ position: fixed;
174
+ right: 2rem;
175
+ bottom: 2rem;
176
+ width: 380px;
177
+ height: 600px;
178
+ background: white;
179
+ border-radius: 20px;
180
+ box-shadow: 0 4px 20px rgba(99, 145, 197, 0.15);
181
+ display: flex;
182
+ flex-direction: column;
183
+ transform: scale(0);
184
+ opacity: 0;
185
+ transform-origin: bottom right;
186
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
187
+ z-index: 999;
188
+ border: 1px solid var(--light-blue);
189
+ }
190
+
191
+ .chat-window.active {
192
+ transform: scale(1);
193
+ opacity: 1;
194
+ }
195
+
196
+ .chat-header {
197
+ padding: 1.25rem;
198
+ background: linear-gradient(135deg, var(--primary-blue), var(--light-blue));
199
+ color: white;
200
+ border-radius: 20px 20px 0 0;
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 0.75rem;
204
+ }
205
+
206
+ .chat-title {
207
+ font-weight: 600;
208
+ flex: 1;
209
+ }
210
+
211
+ .chat-close {
212
+ background: none;
213
+ border: none;
214
+ color: white;
215
+ cursor: pointer;
216
+ width: 32px;
217
+ height: 32px;
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ border-radius: 16px;
222
+ transition: all 0.3s;
223
+ }
224
+
225
+ .chat-close:hover {
226
+ background: rgba(255, 255, 255, 0.2);
227
+ }
228
+
229
+ /* Enhanced Chat Messages Styles */
230
+ .chat-messages {
231
+ flex: 1;
232
+ overflow-y: auto;
233
+ overflow-x: hidden;
234
+ padding: 1.5rem;
235
+ display: flex;
236
+ flex-direction: column;
237
+ gap: 1rem;
238
+ width: 100%;
239
+ background: #f8f9fa;
240
+ }
241
+
242
+ .chat-message {
243
+ max-width: 85%;
244
+ padding: 1rem 1.25rem;
245
+ border-radius: 16px;
246
+ line-height: 1.5;
247
+ animation: messageSlide 0.3s ease;
248
+ word-wrap: break-word;
249
+ overflow-wrap: break-word;
250
+ width: fit-content;
251
+ border: 2px solid var(--light-blue);
252
+ background: white;
253
+ color: var(--text-dark);
254
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
255
+ }
256
+
257
+ .chat-message.user {
258
+ margin-left: auto;
259
+ border-radius: 16px 16px 4px 16px;
260
+ background: #f8f9fa;
261
+ }
262
+
263
+ .chat-message.assistant {
264
+ margin-right: auto;
265
+ border-radius: 16px 16px 16px 4px;
266
+ }
267
+
268
+ .chat-message p {
269
+ margin: 0;
270
+ margin-bottom: 0.75rem;
271
+ white-space: pre-wrap;
272
+ }
273
+
274
+ .chat-message p:last-child {
275
+ margin-bottom: 0;
276
+ }
277
+
278
+ .chat-message pre {
279
+ background: #f1f5f9;
280
+ border-radius: 8px;
281
+ padding: 1rem;
282
+ margin: 0.75rem 0;
283
+ overflow-x: auto;
284
+ border: 1px solid var(--light-blue);
285
+ }
286
+
287
+ .chat-message pre code {
288
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
289
+ font-size: 0.9rem;
290
+ line-height: 1.5;
291
+ color: var(--text-dark);
292
+ background: transparent;
293
+ padding: 0;
294
+ }
295
+
296
+ .chat-message code {
297
+ background: #f1f5f9;
298
+ padding: 0.2em 0.4em;
299
+ border-radius: 4px;
300
+ font-size: 0.9em;
301
+ color: var(--primary-blue);
302
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
303
+ }
304
+
305
+ .chat-message img {
306
+ max-width: 100%;
307
+ height: auto;
308
+ border-radius: 8px;
309
+ margin: 0.75rem 0;
310
+ }
311
+
312
+ .chat-message blockquote {
313
+ border-left: 3px solid var(--light-blue);
314
+ margin: 0.75rem 0;
315
+ padding: 0.5rem 0 0.5rem 1rem;
316
+ color: #64748b;
317
+ background: #f8fafc;
318
+ border-radius: 4px;
319
+ }
320
+
321
+ .chat-message .katex-display {
322
+ margin: 0.75rem 0;
323
+ overflow-x: auto;
324
+ padding: 0.5rem 0;
325
+ }
326
+
327
+ .chat-message .katex {
328
+ font-size: 1.1em;
329
+ }
330
+
331
+ .chat-message table {
332
+ border-collapse: collapse;
333
+ width: 100%;
334
+ margin: 0.75rem 0;
335
+ overflow-x: auto;
336
+ display: block;
337
+ }
338
+
339
+ .chat-message table th,
340
+ .chat-message table td {
341
+ padding: 0.5rem 0.75rem;
342
+ border: 1px solid var(--light-blue);
343
+ }
344
+
345
+ .chat-message table th {
346
+ background: #f8fafc;
347
+ font-weight: 600;
348
+ }
349
+
350
+ .chat-input-container {
351
+ padding: 1.25rem;
352
+ border-top: 1px solid var(--light-blue);
353
+ }
354
+
355
+ .chat-input-wrapper {
356
+ display: flex;
357
+ gap: 0.75rem;
358
+ align-items: flex-end;
359
+ }
360
+
361
+ .chat-input {
362
+ flex: 1;
363
+ min-height: 44px;
364
+ max-height: 120px;
365
+ padding: 0.75rem 1rem;
366
+ border: 2px solid var(--light-blue);
367
+ border-radius: 12px;
368
+ resize: none;
369
+ font-size: 1rem;
370
+ line-height: 1.5;
371
+ transition: all 0.3s;
372
+ }
373
+
374
+ .chat-input:focus {
375
+ outline: none;
376
+ border-color: var(--primary-blue);
377
+ box-shadow: 0 0 0 3px rgba(99, 145, 197, 0.1);
378
+ }
379
+
380
+ .chat-send {
381
+ background: var(--primary-blue);
382
+ color: white;
383
+ width: 44px;
384
+ height: 44px;
385
+ border: none;
386
+ border-radius: 12px;
387
+ cursor: pointer;
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: center;
391
+ transition: all 0.3s;
392
+ }
393
+
394
+ .chat-send:hover {
395
+ background: var(--light-blue);
396
+ transform: translateY(-2px);
397
+ }
398
+
399
+ /* 动画 */
400
+ @keyframes messageSlide {
401
+ from {
402
+ opacity: 0;
403
+ transform: translateY(10px);
404
+ }
405
+ to {
406
+ opacity: 1;
407
+ transform: translateY(0);
408
+ }
409
+ }
410
+
411
+ /* 响应式设计 */
412
+ @media (max-width: 768px) {
413
+ .article-container {
414
+ padding: 1.5rem;
415
+ border-radius: 12px;
416
+ }
417
+
418
+ .article-title {
419
+ font-size: 2rem;
420
+ }
421
+
422
+ .chat-window {
423
+ right: 1rem;
424
+ bottom: 1rem;
425
+ left: 1rem;
426
+ width: auto;
427
+ height: 500px;
428
+ }
429
+
430
+ .chat-toggle {
431
+ right: 1rem;
432
+ bottom: 1rem;
433
+ }
434
+ }
435
+ </style>
436
+ {% endblock %}
437
+
438
+ {% block content %}
439
+ <!-- 文章内容 -->
440
+ <article class="article-container">
441
+ <header class="article-header">
442
+ <h1 class="article-title">{{ article.title }}</h1>
443
+ <div class="article-meta">
444
+ <div class="meta-item">
445
+ <i class="fas fa-calendar"></i>
446
+ <span>{{ article.created_at.strftime('%Y-%m-%d') }}</span>
447
+ </div>
448
+ </div>
449
+ </header>
450
+
451
+ {% if article.summary %}
452
+ <div class="article-summary">
453
+ <span class="summary-label">AI 摘要</span>
454
+ <p>{{ article.summary }}</p>
455
+ </div>
456
+ {% endif %}
457
+
458
+ <div class="article-content markdown-body">
459
+ {{ article.content|markdown }}
460
+ </div>
461
+ </article>
462
+
463
+ <!-- AI 聊天窗口 -->
464
+ <button class="chat-toggle" id="chatToggle">
465
+ <i class="fas fa-robot"></i>
466
+ </button>
467
+
468
+ <div class="chat-window" id="chatWindow">
469
+ <div class="chat-header">
470
+ <i class="fas fa-robot"></i>
471
+ <span class="chat-title">AI 智能助手</span>
472
+ <button class="chat-close" id="chatClose">
473
+ <i class="fas fa-times"></i>
474
+ </button>
475
+ </div>
476
+ <div class="chat-messages" id="chatMessages"></div>
477
+ <div class="chat-input-wrapper">
478
+ <textarea
479
+ id="chatInput"
480
+ class="chat-input"
481
+ placeholder="输入您的问题..."
482
+ rows="1"
483
+ ></textarea>
484
+ <button class="chat-send" onclick="sendMessage()">
485
+ <i class="fas fa-paper-plane"></i>
486
+ </button>
487
+ </div>
488
+ </div>
489
+ {% endblock %}
490
+
491
+ {% block extra_js %}
492
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
493
+ <script>
494
+ // 初始化文章上下文
495
+ window.articleContext = {
496
+ title: {{ article.title|tojson|safe }},
497
+ content: {{ article.content|tojson|safe }}
498
+ };
499
+
500
+ // 配置Marked
501
+ marked.setOptions({
502
+ breaks: true,
503
+ gfm: true
504
+ });
505
+
506
+ // 聊天窗口控制
507
+ const chatToggle = document.getElementById('chatToggle');
508
+ const chatWindow = document.getElementById('chatWindow');
509
+ const chatClose = document.getElementById('chatClose');
510
+ const chatInput = document.getElementById('chatInput');
511
+ const chatMessages = document.getElementById('chatMessages');
512
+
513
+ // 模型上下文
514
+ const modelContext = `这是一篇关于"${window.articleContext.title}"的文章。文章内容:\n\n${window.articleContext.content}\n\n请基于以上文章内容来回答用户的问题。`;
515
+
516
+ // 欢迎消息
517
+ const welcomeMessage = `您好!我是这篇《${window.articleContext.title}》的AI助手。我已经仔细阅读了全文,可以解答您关于文章内容的任何问题,也提供更深入的讨论和见解,从而帮助您更好地理解文章要点
518
+ 让我们开始对话吧!`;
519
+
520
+ // 初始化消息数��
521
+ let messages = [{
522
+ role: 'system',
523
+ content: modelContext
524
+ }];
525
+
526
+ // 初始化聊天
527
+ function initializeChat() {
528
+ displayMessage('assistant', welcomeMessage);
529
+ }
530
+
531
+ // 切换聊天窗口
532
+ function toggleChat() {
533
+ chatWindow.classList.toggle('active');
534
+ if (chatWindow.classList.contains('active')) {
535
+ chatToggle.style.display = 'none';
536
+ chatInput.focus();
537
+ if (chatMessages.children.length === 0) {
538
+ initializeChat();
539
+ }
540
+ } else {
541
+ chatToggle.style.display = 'flex';
542
+ }
543
+ }
544
+
545
+ chatToggle.addEventListener('click', toggleChat);
546
+ chatClose.addEventListener('click', toggleChat);
547
+
548
+ // 自动调整文本框高度
549
+ chatInput.addEventListener('input', function() {
550
+ this.style.height = 'auto';
551
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
552
+ });
553
+
554
+ // 发送消息
555
+ async function sendMessage() {
556
+ const messageText = chatInput.value.trim();
557
+ if (!messageText) return;
558
+
559
+ const userMessage = {
560
+ role: 'user',
561
+ content: messageText
562
+ };
563
+
564
+ // 重置输入框
565
+ chatInput.value = '';
566
+ chatInput.style.height = 'auto';
567
+
568
+ // 显示用户消息
569
+ displayMessage('user', messageText);
570
+
571
+ try {
572
+ const currentMessages = [...messages, userMessage];
573
+
574
+ const response = await fetch('/api/chat', {
575
+ method: 'POST',
576
+ headers: {
577
+ 'Content-Type': 'application/json'
578
+ },
579
+ body: JSON.stringify({ messages: currentMessages })
580
+ });
581
+
582
+ if (response.ok) {
583
+ const data = await response.json();
584
+
585
+ // 更新消息历史
586
+ messages.push(userMessage);
587
+ messages.push({
588
+ role: 'assistant',
589
+ content: data.response
590
+ });
591
+
592
+ // 显示AI响应
593
+ displayMessage('assistant', data.response);
594
+ } else {
595
+ throw new Error('Network response was not ok');
596
+ }
597
+ } catch (error) {
598
+ console.error('Error:', error);
599
+ displayMessage('assistant', '抱歉,发生了错误,请稍后再试。');
600
+ }
601
+ }
602
+
603
+ // 显示消息
604
+ function displayMessage(role, content) {
605
+ const messageDiv = document.createElement('div');
606
+ messageDiv.className = `chat-message ${role}`;
607
+
608
+ // 使用marked渲染Markdown内容
609
+ messageDiv.innerHTML = marked.parse(content);
610
+
611
+ chatMessages.appendChild(messageDiv);
612
+ chatMessages.scrollTop = chatMessages.scrollHeight;
613
+ }
614
+
615
+ // 回车发送消息
616
+ chatInput.addEventListener('keypress', function(event) {
617
+ if (event.key === 'Enter' && !event.shiftKey) {
618
+ event.preventDefault();
619
+ sendMessage();
620
+ }
621
+ });
622
+
623
+ // 聊天窗口拖动功能
624
+ let isDragging = false;
625
+ let currentX;
626
+ let currentY;
627
+ let initialX;
628
+ let initialY;
629
+ let xOffset = 0;
630
+ let yOffset = 0;
631
+
632
+ chatWindow.addEventListener('mousedown', dragStart);
633
+ document.addEventListener('mousemove', drag);
634
+ document.addEventListener('mouseup', dragEnd);
635
+
636
+ function dragStart(e) {
637
+ if (e.target.closest('.chat-header') && !e.target.closest('.chat-close')) {
638
+ initialX = e.clientX - xOffset;
639
+ initialY = e.clientY - yOffset;
640
+ isDragging = true;
641
+ chatWindow.style.cursor = 'grabbing';
642
+ }
643
+ }
644
+
645
+ function drag(e) {
646
+ if (isDragging) {
647
+ e.preventDefault();
648
+ currentX = e.clientX - initialX;
649
+ currentY = e.clientY - initialY;
650
+ xOffset = currentX;
651
+ yOffset = currentY;
652
+
653
+ // 确保窗口不会超出视口边界
654
+ const rect = chatWindow.getBoundingClientRect();
655
+ const viewportWidth = window.innerWidth;
656
+ const viewportHeight = window.innerHeight;
657
+
658
+ // 限制X轴移动
659
+ if (rect.left < 0) {
660
+ currentX -= rect.left;
661
+ }
662
+ if (rect.right > viewportWidth) {
663
+ currentX -= (rect.right - viewportWidth);
664
+ }
665
+
666
+ // 限制Y轴移动
667
+ if (rect.top < 0) {
668
+ currentY -= rect.top;
669
+ }
670
+ if (rect.bottom > viewportHeight) {
671
+ currentY -= (rect.bottom - viewportHeight);
672
+ }
673
+
674
+ setTranslate(currentX, currentY, chatWindow);
675
+ }
676
+ }
677
+
678
+ function dragEnd() {
679
+ initialX = currentX;
680
+ initialY = currentY;
681
+ isDragging = false;
682
+ chatWindow.style.cursor = 'default';
683
+ }
684
+
685
+ function setTranslate(xPos, yPos, el) {
686
+ el.style.transform = `translate(${xPos}px, ${yPos}px)`;
687
+ }
688
+ </script>
689
  {% endblock %}