mistpe commited on
Commit
a5c6b7f
·
verified ·
1 Parent(s): 874ff0c

Update app/templates/article.html

Browse files
Files changed (1) hide show
  1. app/templates/article.html +611 -611
app/templates/article.html CHANGED
@@ -1,612 +1,612 @@
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 }} - Wisdom Hub{% 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 %}