samlax12 commited on
Commit
efa89f6
·
verified ·
1 Parent(s): 4d1d3eb

Update templates/student.html

Browse files
Files changed (1) hide show
  1. templates/student.html +908 -908
templates/student.html CHANGED
@@ -1,909 +1,909 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI学习助手</title>
7
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
9
- <style>
10
- :root {
11
- --primary-color: #4361ee;
12
- --secondary-color: #3f37c9;
13
- --accent-color: #4cc9f0;
14
- --success-color: #4caf50;
15
- --warning-color: #ff9800;
16
- --danger-color: #f44336;
17
- --light-color: #f8f9fa;
18
- --dark-color: #212529;
19
- --border-color: #dee2e6;
20
- --border-radius: 0.375rem;
21
- }
22
-
23
- body {
24
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
- margin: 0;
26
- padding: 0;
27
- height: 100vh;
28
- background-color: #f5f7fa;
29
- color: #333;
30
- display: flex;
31
- flex-direction: column;
32
- }
33
-
34
- .header {
35
- background-color: #fff;
36
- border-bottom: 1px solid var(--border-color);
37
- padding: 1rem;
38
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
39
- }
40
-
41
- .header-content {
42
- max-width: 1600px;
43
- margin: 0 auto;
44
- display: flex;
45
- justify-content: space-between;
46
- align-items: center;
47
- }
48
-
49
- .header h1 {
50
- margin: 0;
51
- font-size: 1.5rem;
52
- color: var(--primary-color);
53
- }
54
-
55
- .main-container {
56
- flex: 1;
57
- display: flex;
58
- max-width: 1600px;
59
- margin: 0 auto;
60
- padding: 1rem;
61
- width: 100%;
62
- box-sizing: border-box;
63
- height: calc(100vh - 70px); /* Header height + padding */
64
- overflow: hidden;
65
- }
66
-
67
- .chat-container {
68
- flex: 1;
69
- display: flex;
70
- flex-direction: column;
71
- background-color: #fff;
72
- border-radius: var(--border-radius);
73
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
74
- border: 1px solid var(--border-color);
75
- overflow: hidden;
76
- height: 100%;
77
- max-width: 800px;
78
- margin: 0 auto;
79
- }
80
-
81
- /* When plugins are active, adjust the chat container */
82
- .main-container.with-plugin .chat-container {
83
- margin: 0;
84
- max-width: 350px;
85
- }
86
-
87
- .chat-messages {
88
- flex: 1;
89
- overflow-y: auto;
90
- padding: 1rem;
91
- display: flex;
92
- flex-direction: column;
93
- }
94
-
95
- .message {
96
- max-width: 90%;
97
- margin-bottom: 1rem;
98
- padding: 0.75rem 1rem;
99
- border-radius: var(--border-radius);
100
- position: relative;
101
- }
102
-
103
- .user-message {
104
- background-color: #e3f2fd;
105
- align-self: flex-end;
106
- color: #0d47a1;
107
- }
108
-
109
- .bot-message {
110
- background-color: #f5f5f5;
111
- align-self: flex-start;
112
- color: #333;
113
- border-left: 3px solid var(--primary-color);
114
- }
115
-
116
- .input-container {
117
- padding: 1rem;
118
- border-top: 1px solid var(--border-color);
119
- background-color: #f9f9f9;
120
- }
121
-
122
- .input-row {
123
- display: flex;
124
- gap: 0.5rem;
125
- }
126
-
127
- .input-field {
128
- flex: 1;
129
- padding: 0.75rem 1rem;
130
- border: 1px solid var(--border-color);
131
- border-radius: var(--border-radius);
132
- resize: none;
133
- font-size: 0.95rem;
134
- }
135
-
136
- .input-field:focus {
137
- outline: none;
138
- border-color: var(--primary-color);
139
- box-shadow: 0 0 0 2px rgba(67, 97, 238, 0.1);
140
- }
141
-
142
- .send-button {
143
- padding: 0.75rem 1.5rem;
144
- background-color: var(--primary-color);
145
- color: white;
146
- border: none;
147
- border-radius: var(--border-radius);
148
- cursor: pointer;
149
- font-weight: 500;
150
- transition: background-color 0.2s;
151
- }
152
-
153
- .send-button:hover {
154
- background-color: var(--secondary-color);
155
- }
156
-
157
- .send-button:disabled {
158
- background-color: #ccc;
159
- cursor: not-allowed;
160
- }
161
-
162
- .plugin-container {
163
- display: none;
164
- flex-direction: column;
165
- background-color: #fff;
166
- border-radius: var(--border-radius);
167
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
168
- border: 1px solid var(--border-color);
169
- overflow: hidden;
170
- height: 100%;
171
- flex: 2;
172
- margin-left: 1rem;
173
- }
174
-
175
- .plugin-header {
176
- padding: 0.75rem 1rem;
177
- border-bottom: 1px solid var(--border-color);
178
- background-color: #f9f9f9;
179
- display: flex;
180
- justify-content: space-between;
181
- align-items: center;
182
- }
183
-
184
- .plugin-title {
185
- margin: 0;
186
- font-size: 1rem;
187
- font-weight: 500;
188
- }
189
-
190
- .plugin-close {
191
- background: none;
192
- border: none;
193
- cursor: pointer;
194
- font-size: 1rem;
195
- color: #777;
196
- }
197
-
198
- .plugin-content {
199
- flex: 1;
200
- overflow-y: auto;
201
- }
202
-
203
- /* 代码执行插件样式 */
204
- .code-plugin .plugin-content {
205
- padding: 0;
206
- }
207
-
208
- /* 可视化插件样式 */
209
- .visualization-plugin .visualization-form {
210
- margin-bottom: 1rem;
211
- padding: 1rem;
212
- }
213
-
214
- .visualization-plugin .visualization-result {
215
- margin-top: 1rem;
216
- text-align: center;
217
- padding: 0 1rem;
218
- }
219
-
220
- .visualization-plugin .visualization-image {
221
- max-width: 100%;
222
- border-radius: var(--border-radius);
223
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
224
- }
225
-
226
- /* 思维导图插件样式 */
227
- .mindmap-plugin .mindmap-form {
228
- margin-bottom: 1rem;
229
- padding: 1rem;
230
- }
231
-
232
- .mindmap-plugin .mindmap-result {
233
- margin-top: 1rem;
234
- text-align: center;
235
- padding: 0 1rem;
236
- }
237
-
238
- .mindmap-plugin .mindmap-image {
239
- max-width: 100%;
240
- border-radius: var(--border-radius);
241
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
242
- }
243
-
244
- /* 参考资料样式 */
245
- .reference-container {
246
- margin-top: 10px;
247
- border-top: 1px dashed #ddd;
248
- padding-top: 10px;
249
- }
250
-
251
- .reference-toggle {
252
- color: #666;
253
- font-size: 0.9rem;
254
- cursor: pointer;
255
- display: flex;
256
- align-items: center;
257
- }
258
-
259
- .reference-content {
260
- display: none;
261
- margin-top: 10px;
262
- padding: 10px;
263
- background-color: #f9f9f9;
264
- border-radius: 5px;
265
- font-size: 0.85rem;
266
- }
267
-
268
- /* 代码样式 */
269
- pre {
270
- background-color: #f8f9fa;
271
- border-radius: 5px;
272
- padding: 12px;
273
- margin: 10px 0;
274
- overflow-x: auto;
275
- border: 1px solid #eee;
276
- }
277
-
278
- code {
279
- font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
280
- font-size: 14px;
281
- }
282
-
283
- /* 响应式样式 */
284
- @media (max-width: 1200px) {
285
- .main-container.with-plugin .chat-container {
286
- max-width: 300px;
287
- }
288
- }
289
-
290
- @media (max-width: 992px) {
291
- .main-container {
292
- flex-direction: column;
293
- }
294
-
295
- .main-container.with-plugin .chat-container {
296
- max-width: 100%;
297
- margin-bottom: 1rem;
298
- height: 40vh;
299
- }
300
-
301
- .plugin-container {
302
- margin-left: 0;
303
- height: calc(60vh - 32px);
304
- }
305
- }
306
-
307
- @media (max-width: 768px) {
308
- .main-container {
309
- padding: 0.5rem;
310
- }
311
- }
312
- </style>
313
- </head>
314
- <body>
315
- <header class="header">
316
- <div class="header-content">
317
- <h1>{{ agent_name }}</h1>
318
- <div>
319
- <span class="badge bg-primary">AI学习助手</span>
320
- </div>
321
- </div>
322
- </header>
323
-
324
- <div class="main-container" id="main-container">
325
- <div class="chat-container">
326
- <div class="chat-messages" id="chat-messages">
327
- <div class="message bot-message">
328
- <p>你好!我是{{ agent_name }},有什么可以帮助你的吗?</p>
329
- {% if agent_description %}
330
- <p>{{ agent_description }}</p>
331
- {% endif %}
332
- </div>
333
- </div>
334
-
335
- <div class="input-container">
336
- <div class="input-row">
337
- <textarea class="input-field" id="user-input" placeholder="输入您的问题..." rows="2"></textarea>
338
- <button class="send-button" id="send-button">
339
- <i class="bi bi-send"></i>
340
- </button>
341
- </div>
342
- </div>
343
- </div>
344
-
345
- <!-- 代码执行插件 -->
346
- <div class="plugin-container code-plugin" id="code-plugin">
347
- <div class="plugin-header">
348
- <h3 class="plugin-title">Python代码执行</h3>
349
- <button class="plugin-close" id="close-code-plugin">
350
- <i class="bi bi-x-lg"></i>
351
- </button>
352
- </div>
353
- <div class="plugin-content">
354
- <iframe id="code-execution-frame" src="" style="width: 100%; height: 100%; border: none;"></iframe>
355
- </div>
356
- </div>
357
-
358
- <!-- 3D可视化插件 -->
359
- <div class="plugin-container visualization-plugin" id="visualization-plugin">
360
- <div class="plugin-header">
361
- <h3 class="plugin-title">3D可视化</h3>
362
- <button class="plugin-close" id="close-visualization-plugin">
363
- <i class="bi bi-x-lg"></i>
364
- </button>
365
- </div>
366
- <div class="plugin-content">
367
- <div class="visualization-result" id="visualization-result">
368
- <div class="text-center py-4">
369
- <div class="spinner-border text-primary" role="status">
370
- <span class="visually-hidden">加载中...</span>
371
- </div>
372
- <p class="mt-3">正在准备3D可视化...</p>
373
- </div>
374
- </div>
375
- </div>
376
- </div>
377
-
378
- <!-- 思维导图插件 -->
379
- <div class="plugin-container mindmap-plugin" id="mindmap-plugin">
380
- <div class="plugin-header">
381
- <h3 class="plugin-title">思维导图</h3>
382
- <button class="plugin-close" id="close-mindmap-plugin">
383
- <i class="bi bi-x-lg"></i>
384
- </button>
385
- </div>
386
- <div class="plugin-content">
387
- <div class="mindmap-result" id="mindmap-result">
388
- <div class="text-center py-4">
389
- <div class="spinner-border text-primary" role="status">
390
- <span class="visually-hidden">加载中...</span>
391
- </div>
392
- <p class="mt-3">正在生成思维导图...</p>
393
- </div>
394
- </div>
395
- </div>
396
- </div>
397
- </div>
398
-
399
- <script>
400
- // 全局变量
401
- const agentId = "{{ agent_id }}";
402
- const token = "{{ token }}";
403
- let executionContext = null;
404
- const mainContainer = document.getElementById('main-container');
405
-
406
- // DOM元素
407
- const chatMessages = document.getElementById('chat-messages');
408
- const userInput = document.getElementById('user-input');
409
- const sendButton = document.getElementById('send-button');
410
-
411
- // 代码执行插件元素
412
- const codePlugin = document.getElementById('code-plugin');
413
- const closeCodePlugin = document.getElementById('close-code-plugin');
414
- const codeExecutionFrame = document.getElementById('code-execution-frame');
415
-
416
- // 3D可视化插件元素
417
- const visualizationPlugin = document.getElementById('visualization-plugin');
418
- const closeVisualizationPlugin = document.getElementById('close-visualization-plugin');
419
- const visualizationResult = document.getElementById('visualization-result');
420
-
421
- // 思维导图插件元素
422
- const mindmapPlugin = document.getElementById('mindmap-plugin');
423
- const closeMindmapPlugin = document.getElementById('close-mindmap-plugin');
424
- const mindmapResult = document.getElementById('mindmap-result');
425
-
426
- // 页面加载完成后执行
427
- document.addEventListener('DOMContentLoaded', function() {
428
- // 初始化发送按钮事件
429
- sendButton.addEventListener('click', sendMessage);
430
-
431
- // 初始化输入框回车事件
432
- userInput.addEventListener('keypress', function(e) {
433
- if (e.key === 'Enter' && !e.shiftKey) {
434
- e.preventDefault();
435
- sendMessage();
436
- }
437
- });
438
-
439
- // 初始化插件关闭事件
440
- closeCodePlugin.addEventListener('click', () => {
441
- codePlugin.style.display = 'none';
442
- // 清空iframe源以停止任何运行中的代码
443
- codeExecutionFrame.src = '';
444
- updateMainContainerLayout();
445
- });
446
-
447
- closeVisualizationPlugin.addEventListener('click', () => {
448
- visualizationPlugin.style.display = 'none';
449
- updateMainContainerLayout();
450
- });
451
-
452
- closeMindmapPlugin.addEventListener('click', () => {
453
- mindmapPlugin.style.display = 'none';
454
- updateMainContainerLayout();
455
- });
456
- });
457
-
458
- // 更新主容器布局
459
- function updateMainContainerLayout() {
460
- // 检查是否有任何插件处于显示状态
461
- const isAnyPluginVisible =
462
- codePlugin.style.display === 'flex' ||
463
- visualizationPlugin.style.display === 'flex' ||
464
- mindmapPlugin.style.display === 'flex';
465
-
466
- // 更新主容器类名
467
- if (isAnyPluginVisible) {
468
- mainContainer.classList.add('with-plugin');
469
- } else {
470
- mainContainer.classList.remove('with-plugin');
471
- }
472
- }
473
-
474
- // 发送消息
475
- async function sendMessage() {
476
- const message = userInput.value.trim();
477
- if (!message) return;
478
-
479
- // 添加用户消息
480
- addMessage(message, true);
481
-
482
- // 清空输入框
483
- userInput.value = '';
484
-
485
- // 禁用发送按钮
486
- sendButton.disabled = true;
487
-
488
- try {
489
- // 发送请求
490
- const response = await fetch(`/api/student/chat/${agentId}`, {
491
- method: 'POST',
492
- headers: {
493
- 'Content-Type': 'application/json'
494
- },
495
- body: JSON.stringify({
496
- message: message,
497
- token: token
498
- })
499
- });
500
-
501
- const data = await response.json();
502
-
503
- if (data.success) {
504
- // 处理回复内容,移除参考来源部分
505
- const processedContent = processResponseContent(data.message);
506
-
507
- // 添加机器人回复
508
- const messageElement = addMessage(processedContent, false);
509
-
510
- // 添加参考信息(如果有)
511
- if (data.references && data.references.length > 0) {
512
- addReferences(messageElement, data.references);
513
- }
514
-
515
- // 检查是否需要显示工具
516
- if (data.tools && data.tools.length > 0) {
517
- // 隐藏所有插件
518
- hideAllPlugins();
519
-
520
- // 使用新的插件激活函数
521
- activatePlugins(data.message, data.tools);
522
- }
523
- } else {
524
- // 添加错误消息
525
- addMessage(`错误: ${data.message}`, false);
526
- }
527
- } catch (error) {
528
- console.error('发送请求出错:', error);
529
- addMessage('发送请求时出错,请重试', false);
530
- } finally {
531
- // 恢复发送按钮
532
- sendButton.disabled = false;
533
- }
534
- }
535
-
536
- // 处理回复内容,移除参考来源部分
537
- function processResponseContent(content) {
538
- // 移除 "===参考来源开始===" 到 "===参考来源结束===" 之间的内容
539
- return content.replace(/===参考来源开始===[\s\S]*?===参考来源结束===/, '');
540
- }
541
-
542
- // 添加参考信息显示函数
543
- function addReferences(messageElement, references) {
544
- // 创建参考容器
545
- const referenceContainer = document.createElement('div');
546
- referenceContainer.className = 'reference-container';
547
-
548
- // 创建参考切换按钮
549
- const toggleButton = document.createElement('div');
550
- toggleButton.className = 'reference-toggle';
551
- toggleButton.textContent = '参考来源';
552
- toggleButton.style.color = '#666';
553
- toggleButton.style.fontSize = '0.9rem';
554
- toggleButton.style.cursor = 'pointer';
555
- toggleButton.style.display = 'flex';
556
- toggleButton.style.alignItems = 'center';
557
-
558
- // 创建参考内容
559
- const referenceContent = document.createElement('div');
560
- referenceContent.className = 'reference-content';
561
-
562
- // 添加参考项
563
- references.forEach(ref => {
564
- const refItem = document.createElement('div');
565
- refItem.style.marginBottom = '8px';
566
- refItem.style.paddingBottom = '8px';
567
- refItem.style.borderBottom = '1px solid #eee';
568
-
569
- refItem.innerHTML = `
570
- <div><strong>[${ref.index}]</strong> ${ref.summary}</div>
571
- <div><small>来源: ${ref.file_name}</small></div>
572
- `;
573
-
574
- referenceContent.appendChild(refItem);
575
- });
576
-
577
- // 添加切换功能
578
- toggleButton.addEventListener('click', () => {
579
- if (referenceContent.style.display === 'none') {
580
- referenceContent.style.display = 'block';
581
- toggleButton.textContent = '折叠参考来源';
582
- } else {
583
- referenceContent.style.display = 'none';
584
- toggleButton.textContent = '参考来源';
585
- }
586
- });
587
-
588
- // 组装并添加到消息
589
- referenceContainer.appendChild(toggleButton);
590
- referenceContainer.appendChild(referenceContent);
591
- messageElement.appendChild(referenceContainer);
592
- }
593
-
594
- // 添加消息函数
595
- function addMessage(content, isUser) {
596
- const messageDiv = document.createElement('div');
597
- messageDiv.className = isUser ? 'message user-message' : 'message bot-message';
598
-
599
- // 处理内容中的换行符和代码块
600
- const processedContent = processMessageContent(content);
601
- messageDiv.innerHTML = processedContent;
602
-
603
- chatMessages.appendChild(messageDiv);
604
-
605
- // 滚动到底部
606
- chatMessages.scrollTop = chatMessages.scrollHeight;
607
-
608
- // 对代码块应用高亮
609
- if (!isUser) {
610
- applySyntaxHighlighting(messageDiv);
611
- }
612
-
613
- return messageDiv;
614
- }
615
-
616
- // 处理消息内容中的换行符和代码块
617
- function processMessageContent(content) {
618
- // 先替换代码块,保护它们不受其他替换的影响
619
- const codeBlocks = [];
620
- let processedContent = content.replace(/```(\w*)\n([\s\S]*?)\n```/g, function(match, language, code) {
621
- const id = codeBlocks.length;
622
- codeBlocks.push({ language, code });
623
- return `__CODE_BLOCK_${id}__`;
624
- });
625
-
626
- // 替换普通换行符
627
- processedContent = processedContent.replace(/\n/g, '<br>');
628
-
629
- // 恢复代码块
630
- processedContent = processedContent.replace(/__CODE_BLOCK_(\d+)__/g, function(match, id) {
631
- const { language, code } = codeBlocks[id];
632
- const langClass = language ? ` class="language-${language}"` : '';
633
- return `<pre><code${langClass}>${escapeHtml(code)}</code></pre>`;
634
- });
635
-
636
- return processedContent;
637
- }
638
-
639
- // 应用语法高亮
640
- function applySyntaxHighlighting(element) {
641
- // 检查是否已加载highlight.js
642
- if (typeof hljs !== 'undefined') {
643
- // 找出所有代码块并应用高亮
644
- element.querySelectorAll('pre code').forEach((block) => {
645
- hljs.highlightBlock(block);
646
- });
647
- }
648
- }
649
-
650
- // 转义HTML字符
651
- function escapeHtml(text) {
652
- return text
653
- .replace(/&/g, "&amp;")
654
- .replace(/</g, "&lt;")
655
- .replace(/>/g, "&gt;")
656
- .replace(/"/g, "&quot;")
657
- .replace(/'/g, "&#039;");
658
- }
659
-
660
- // 隐藏所有插件
661
- function hideAllPlugins() {
662
- codePlugin.style.display = 'none';
663
- visualizationPlugin.style.display = 'none';
664
- mindmapPlugin.style.display = 'none';
665
- updateMainContainerLayout();
666
- }
667
-
668
- // 提取代码块
669
- function extractCodeBlocks(message) {
670
- const codeBlocks = [];
671
- const codeRegex = /```python\n([\s\S]*?)\n```/g;
672
-
673
- let match;
674
- while ((match = codeRegex.exec(message)) !== null) {
675
- codeBlocks.push(match[1]);
676
- }
677
-
678
- return codeBlocks;
679
- }
680
-
681
- // 提取3D可视化函数代码
682
- function extract3DVisualizationCode(message) {
683
- // 尝试提取函数代码块
684
- const codeRegex = /```python\s*(import[\s\S]*?def create_3d_plot\(\):[\s\S]*?return[\s\S]*?})\s*```/i;
685
- const match = codeRegex.exec(message);
686
-
687
- if (match) {
688
- return match[1].trim();
689
- }
690
-
691
- return null;
692
- }
693
-
694
- // 提取思维导图内容
695
- function extractMindmapContent(message) {
696
- // 尝试提取@startmindmap和@endmindmap之间的内容
697
- const mindmapRegex = /@startmindmap\n([\s\S]*?)@endmindmap/;
698
- const match = mindmapRegex.exec(message);
699
-
700
- if (match) {
701
- return `@startmindmap\n${match[1]}\n@endmindmap`;
702
- }
703
-
704
- return null;
705
- }
706
-
707
- // 激活代码执行插件
708
- function activateCodePlugin(message) {
709
- // 提取代码块
710
- const codeBlocks = extractCodeBlocks(message);
711
-
712
- if (codeBlocks.length > 0) {
713
- const isAlreadyVisible = codePlugin.style.display === 'flex';
714
-
715
- // 显示插件容器
716
- codePlugin.style.display = 'flex';
717
- updateMainContainerLayout();
718
-
719
- // 获取iframe
720
- const iframe = document.getElementById('code-execution-frame');
721
-
722
- // 如果插件已经可见且iframe已加载
723
- if (isAlreadyVisible && iframe.contentWindow) {
724
- // 发送新代码到现有iframe
725
- iframe.contentWindow.postMessage({
726
- type: 'setCode',
727
- code: codeBlocks[0]
728
- }, '*');
729
- } else {
730
- // 设置iframe源
731
- let src = '/code_execution.html';
732
- if (codeBlocks.length > 0) {
733
- src += `?code=${encodeURIComponent(codeBlocks[0])}`;
734
- }
735
-
736
- iframe.src = src;
737
-
738
- // 设置iframe加载事件处理程序
739
- iframe.onload = function() {
740
- if (codeBlocks.length > 0) {
741
- iframe.contentWindow.postMessage({
742
- type: 'setCode',
743
- code: codeBlocks[0]
744
- }, '*');
745
- }
746
- };
747
- }
748
- }
749
- }
750
-
751
- // 激活3D可视化插件
752
- // 激活3D可视化插件
753
- function activate3DVisualization(message) {
754
- const code = extract3DVisualizationCode(message);
755
-
756
- if (code) {
757
- // 显示插件容器
758
- visualizationPlugin.style.display = 'flex';
759
- updateMainContainerLayout();
760
-
761
- // 显示加载状态
762
- visualizationResult.innerHTML = `
763
- <div class="text-center py-4">
764
- <div class="spinner-border text-primary" role="status">
765
- <span class="visually-hidden">生成中...</span>
766
- </div>
767
- <p class="mt-3">正在生成3D图形,请稍候...</p>
768
- </div>
769
- `;
770
-
771
- // 自动生成可视化
772
- fetch('/api/visualization/3d-surface', {
773
- method: 'POST',
774
- headers: {
775
- 'Content-Type': 'application/json'
776
- },
777
- body: JSON.stringify({ code: code })
778
- })
779
- .then(response => response.json())
780
- .then(data => {
781
- if (data.success) {
782
- // 直接嵌入HTML
783
- visualizationResult.innerHTML = `
784
- <div class="visualization-iframe-container" style="width:100%; height:500px;">
785
- <iframe src="${data.html_url}" style="width:100%; height:100%; border:none;"></iframe>
786
- </div>
787
- <div class="text-center mt-3">
788
- <p>3D图形生成成功</p>
789
- <a href="${data.html_url}" class="btn btn-sm btn-outline-primary" target="_blank">
790
- <i class="bi bi-arrows-fullscreen"></i> 全屏查看
791
- </a>
792
- </div>
793
- `;
794
- } else {
795
- visualizationResult.innerHTML = `
796
- <div class="alert alert-danger">
797
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
798
- 生成失败: ${data.message}
799
- </div>
800
- `;
801
- }
802
- })
803
- .catch(error => {
804
- console.error('生成3D图形出错:', error);
805
- visualizationResult.innerHTML = `
806
- <div class="alert alert-danger">
807
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
808
- 生成3D图形时发生错误,请重试
809
- </div>
810
- `;
811
- });
812
- }
813
- }
814
- // 激活思维导图插件
815
- function activateMindmap(message) {
816
- const content = extractMindmapContent(message);
817
-
818
- if (content) {
819
- // 显示插件容器
820
- mindmapPlugin.style.display = 'flex';
821
- updateMainContainerLayout();
822
-
823
- // 显示加载状态
824
- mindmapResult.innerHTML = `
825
- <div class="text-center py-4">
826
- <div class="spinner-border text-primary" role="status">
827
- <span class="visually-hidden">生成中...</span>
828
- </div>
829
- <p class="mt-3">正在生成思维导图,请稍候...</p>
830
- </div>
831
- `;
832
-
833
- // 自动生成思维导图
834
- fetch('/api/visualization/mindmap', {
835
- method: 'POST',
836
- headers: {
837
- 'Content-Type': 'application/json'
838
- },
839
- body: JSON.stringify({ content: content })
840
- })
841
- .then(response => response.json())
842
- .then(data => {
843
- if (data.success) {
844
- mindmapResult.innerHTML = `
845
- <div class="text-center">
846
- <img src="${data.url}" class="mindmap-image" alt="思维导图">
847
- <p class="mt-3">生成成功!</p>
848
- </div>
849
- `;
850
- }
851
- else {
852
- mindmapResult.innerHTML = `
853
- <div class="alert alert-danger">
854
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
855
- 生成失败: ${data.message}
856
- </div>
857
- `;
858
- }
859
- })
860
- .catch(error => {
861
- console.error('生成思维导图出错:', error);
862
- mindmapResult.innerHTML = `
863
- <div class="alert alert-danger">
864
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
865
- 生成思维导图时发生错误,请重试
866
- </div>
867
- `;
868
- });
869
- }
870
- }
871
-
872
- // 根据消息内容激活适当的插件
873
- function activatePlugins(message, tools) {
874
- // 检查并激活代码执行插件
875
- if (tools.includes('code') && message.includes('```python')) {
876
- activateCodePlugin(message);
877
- }
878
-
879
- // 检查并激活3D可视化插件
880
- if (tools.includes('visualization') &&
881
- (message.includes('def create_3d_plot') ||
882
- message.includes('3D') || message.includes('可视化'))) {
883
- activate3DVisualization(message);
884
- }
885
-
886
- // 检查并激活思维导图插件
887
- if (tools.includes('mindmap') &&
888
- (message.includes('@startmindmap') ||
889
- message.includes('思维导图'))) {
890
- activateMindmap(message);
891
- }
892
- }
893
- </script>
894
-
895
- <!-- 添加highlight.js用于代码高亮 -->
896
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
897
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
898
- <script>
899
- // 初始化代码高亮
900
- document.addEventListener('DOMContentLoaded', function() {
901
- if (typeof hljs !== 'undefined') {
902
- hljs.configure({
903
- languages: ['python']
904
- });
905
- }
906
- });
907
- </script>
908
- </body>
909
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI学习助手</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
9
+ <style>
10
+ :root {
11
+ --primary-color: #4361ee;
12
+ --secondary-color: #3f37c9;
13
+ --accent-color: #4cc9f0;
14
+ --success-color: #4caf50;
15
+ --warning-color: #ff9800;
16
+ --danger-color: #f44336;
17
+ --light-color: #f8f9fa;
18
+ --dark-color: #212529;
19
+ --border-color: #dee2e6;
20
+ --border-radius: 0.375rem;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
+ margin: 0;
26
+ padding: 0;
27
+ height: 100vh;
28
+ background-color: #f5f7fa;
29
+ color: #333;
30
+ display: flex;
31
+ flex-direction: column;
32
+ }
33
+
34
+ .header {
35
+ background-color: #fff;
36
+ border-bottom: 1px solid var(--border-color);
37
+ padding: 1rem;
38
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
39
+ }
40
+
41
+ .header-content {
42
+ max-width: 1600px;
43
+ margin: 0 auto;
44
+ display: flex;
45
+ justify-content: space-between;
46
+ align-items: center;
47
+ }
48
+
49
+ .header h1 {
50
+ margin: 0;
51
+ font-size: 1.5rem;
52
+ color: var(--primary-color);
53
+ }
54
+
55
+ .main-container {
56
+ flex: 1;
57
+ display: flex;
58
+ max-width: 1600px;
59
+ margin: 0 auto;
60
+ padding: 1rem;
61
+ width: 100%;
62
+ box-sizing: border-box;
63
+ height: calc(100vh - 70px); /* Header height + padding */
64
+ overflow: hidden;
65
+ }
66
+
67
+ .chat-container {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ background-color: #fff;
72
+ border-radius: var(--border-radius);
73
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
74
+ border: 1px solid var(--border-color);
75
+ overflow: hidden;
76
+ height: 100%;
77
+ max-width: 800px;
78
+ margin: 0 auto;
79
+ }
80
+
81
+ /* When plugins are active, adjust the chat container */
82
+ .main-container.with-plugin .chat-container {
83
+ margin: 0;
84
+ max-width: 350px;
85
+ }
86
+
87
+ .chat-messages {
88
+ flex: 1;
89
+ overflow-y: auto;
90
+ padding: 1rem;
91
+ display: flex;
92
+ flex-direction: column;
93
+ }
94
+
95
+ .message {
96
+ max-width: 90%;
97
+ margin-bottom: 1rem;
98
+ padding: 0.75rem 1rem;
99
+ border-radius: var(--border-radius);
100
+ position: relative;
101
+ }
102
+
103
+ .user-message {
104
+ background-color: #e3f2fd;
105
+ align-self: flex-end;
106
+ color: #0d47a1;
107
+ }
108
+
109
+ .bot-message {
110
+ background-color: #f5f5f5;
111
+ align-self: flex-start;
112
+ color: #333;
113
+ border-left: 3px solid var(--primary-color);
114
+ }
115
+
116
+ .input-container {
117
+ padding: 1rem;
118
+ border-top: 1px solid var(--border-color);
119
+ background-color: #f9f9f9;
120
+ }
121
+
122
+ .input-row {
123
+ display: flex;
124
+ gap: 0.5rem;
125
+ }
126
+
127
+ .input-field {
128
+ flex: 1;
129
+ padding: 0.75rem 1rem;
130
+ border: 1px solid var(--border-color);
131
+ border-radius: var(--border-radius);
132
+ resize: none;
133
+ font-size: 0.95rem;
134
+ }
135
+
136
+ .input-field:focus {
137
+ outline: none;
138
+ border-color: var(--primary-color);
139
+ box-shadow: 0 0 0 2px rgba(67, 97, 238, 0.1);
140
+ }
141
+
142
+ .send-button {
143
+ padding: 0.75rem 1.5rem;
144
+ background-color: var(--primary-color);
145
+ color: white;
146
+ border: none;
147
+ border-radius: var(--border-radius);
148
+ cursor: pointer;
149
+ font-weight: 500;
150
+ transition: background-color 0.2s;
151
+ }
152
+
153
+ .send-button:hover {
154
+ background-color: var(--secondary-color);
155
+ }
156
+
157
+ .send-button:disabled {
158
+ background-color: #ccc;
159
+ cursor: not-allowed;
160
+ }
161
+
162
+ .plugin-container {
163
+ display: none;
164
+ flex-direction: column;
165
+ background-color: #fff;
166
+ border-radius: var(--border-radius);
167
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
168
+ border: 1px solid var(--border-color);
169
+ overflow: hidden;
170
+ height: 100%;
171
+ flex: 2;
172
+ margin-left: 1rem;
173
+ }
174
+
175
+ .plugin-header {
176
+ padding: 0.75rem 1rem;
177
+ border-bottom: 1px solid var(--border-color);
178
+ background-color: #f9f9f9;
179
+ display: flex;
180
+ justify-content: space-between;
181
+ align-items: center;
182
+ }
183
+
184
+ .plugin-title {
185
+ margin: 0;
186
+ font-size: 1rem;
187
+ font-weight: 500;
188
+ }
189
+
190
+ .plugin-close {
191
+ background: none;
192
+ border: none;
193
+ cursor: pointer;
194
+ font-size: 1rem;
195
+ color: #777;
196
+ }
197
+
198
+ .plugin-content {
199
+ flex: 1;
200
+ overflow-y: auto;
201
+ }
202
+
203
+ /* 代码执行插件样式 */
204
+ .code-plugin .plugin-content {
205
+ padding: 0;
206
+ }
207
+
208
+ /* 可视化插件样式 */
209
+ .visualization-plugin .visualization-form {
210
+ margin-bottom: 1rem;
211
+ padding: 1rem;
212
+ }
213
+
214
+ .visualization-plugin .visualization-result {
215
+ margin-top: 1rem;
216
+ text-align: center;
217
+ padding: 0 1rem;
218
+ }
219
+
220
+ .visualization-plugin .visualization-image {
221
+ max-width: 100%;
222
+ border-radius: var(--border-radius);
223
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
224
+ }
225
+
226
+ /* 思维导图插件样式 */
227
+ .mindmap-plugin .mindmap-form {
228
+ margin-bottom: 1rem;
229
+ padding: 1rem;
230
+ }
231
+
232
+ .mindmap-plugin .mindmap-result {
233
+ margin-top: 1rem;
234
+ text-align: center;
235
+ padding: 0 1rem;
236
+ }
237
+
238
+ .mindmap-plugin .mindmap-image {
239
+ max-width: 100%;
240
+ border-radius: var(--border-radius);
241
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
242
+ }
243
+
244
+ /* 参考资料样式 */
245
+ .reference-container {
246
+ margin-top: 10px;
247
+ border-top: 1px dashed #ddd;
248
+ padding-top: 10px;
249
+ }
250
+
251
+ .reference-toggle {
252
+ color: #666;
253
+ font-size: 0.9rem;
254
+ cursor: pointer;
255
+ display: flex;
256
+ align-items: center;
257
+ }
258
+
259
+ .reference-content {
260
+ display: none;
261
+ margin-top: 10px;
262
+ padding: 10px;
263
+ background-color: #f9f9f9;
264
+ border-radius: 5px;
265
+ font-size: 0.85rem;
266
+ }
267
+
268
+ /* 代码样式 */
269
+ pre {
270
+ background-color: #f8f9fa;
271
+ border-radius: 5px;
272
+ padding: 12px;
273
+ margin: 10px 0;
274
+ overflow-x: auto;
275
+ border: 1px solid #eee;
276
+ }
277
+
278
+ code {
279
+ font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
280
+ font-size: 14px;
281
+ }
282
+
283
+ /* 响应式样式 */
284
+ @media (max-width: 1200px) {
285
+ .main-container.with-plugin .chat-container {
286
+ max-width: 300px;
287
+ }
288
+ }
289
+
290
+ @media (max-width: 992px) {
291
+ .main-container {
292
+ flex-direction: column;
293
+ }
294
+
295
+ .main-container.with-plugin .chat-container {
296
+ max-width: 100%;
297
+ margin-bottom: 1rem;
298
+ height: 40vh;
299
+ }
300
+
301
+ .plugin-container {
302
+ margin-left: 0;
303
+ height: calc(60vh - 32px);
304
+ }
305
+ }
306
+
307
+ @media (max-width: 768px) {
308
+ .main-container {
309
+ padding: 0.5rem;
310
+ }
311
+ }
312
+ </style>
313
+ </head>
314
+ <body>
315
+ <header class="header">
316
+ <div class="header-content">
317
+ <h1>{{ agent_name }}</h1>
318
+ <div>
319
+ <span class="badge bg-primary">AI学习助手</span>
320
+ </div>
321
+ </div>
322
+ </header>
323
+
324
+ <div class="main-container" id="main-container">
325
+ <div class="chat-container">
326
+ <div class="chat-messages" id="chat-messages">
327
+ <div class="message bot-message">
328
+ <p>你好!我是{{ agent_name }},有什么可以帮助你的吗?</p>
329
+ {% if agent_description %}
330
+ <p>{{ agent_description }}</p>
331
+ {% endif %}
332
+ </div>
333
+ </div>
334
+
335
+ <div class="input-container">
336
+ <div class="input-row">
337
+ <textarea class="input-field" id="user-input" placeholder="输入您的问题..." rows="2"></textarea>
338
+ <button class="send-button" id="send-button">
339
+ <i class="bi bi-send"></i>
340
+ </button>
341
+ </div>
342
+ </div>
343
+ </div>
344
+
345
+ <!-- 代码执行插件 -->
346
+ <div class="plugin-container code-plugin" id="code-plugin">
347
+ <div class="plugin-header">
348
+ <h3 class="plugin-title">Python代码执行</h3>
349
+ <button class="plugin-close" id="close-code-plugin">
350
+ <i class="bi bi-x-lg"></i>
351
+ </button>
352
+ </div>
353
+ <div class="plugin-content">
354
+ <iframe id="code-execution-frame" src="" style="width: 100%; height: 100%; border: none;"></iframe>
355
+ </div>
356
+ </div>
357
+
358
+ <!-- 3D可视化插件 -->
359
+ <div class="plugin-container visualization-plugin" id="visualization-plugin">
360
+ <div class="plugin-header">
361
+ <h3 class="plugin-title">3D可视化</h3>
362
+ <button class="plugin-close" id="close-visualization-plugin">
363
+ <i class="bi bi-x-lg"></i>
364
+ </button>
365
+ </div>
366
+ <div class="plugin-content">
367
+ <div class="visualization-result" id="visualization-result">
368
+ <div class="text-center py-4">
369
+ <div class="spinner-border text-primary" role="status">
370
+ <span class="visually-hidden">加载中...</span>
371
+ </div>
372
+ <p class="mt-3">正在准备3D可视化...</p>
373
+ </div>
374
+ </div>
375
+ </div>
376
+ </div>
377
+
378
+ <!-- 思维导图插件 -->
379
+ <div class="plugin-container mindmap-plugin" id="mindmap-plugin">
380
+ <div class="plugin-header">
381
+ <h3 class="plugin-title">思维导图</h3>
382
+ <button class="plugin-close" id="close-mindmap-plugin">
383
+ <i class="bi bi-x-lg"></i>
384
+ </button>
385
+ </div>
386
+ <div class="plugin-content">
387
+ <div class="mindmap-result" id="mindmap-result">
388
+ <div class="text-center py-4">
389
+ <div class="spinner-border text-primary" role="status">
390
+ <span class="visually-hidden">加载中...</span>
391
+ </div>
392
+ <p class="mt-3">正在生成思维导图...</p>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <script>
400
+ // 全局变量
401
+ const agentId = "{{ agent_id }}";
402
+ const token = "{{ token }}";
403
+ let executionContext = null;
404
+ const mainContainer = document.getElementById('main-container');
405
+
406
+ // DOM元素
407
+ const chatMessages = document.getElementById('chat-messages');
408
+ const userInput = document.getElementById('user-input');
409
+ const sendButton = document.getElementById('send-button');
410
+
411
+ // 代码执行插件元素
412
+ const codePlugin = document.getElementById('code-plugin');
413
+ const closeCodePlugin = document.getElementById('close-code-plugin');
414
+ const codeExecutionFrame = document.getElementById('code-execution-frame');
415
+
416
+ // 3D可视化插件元素
417
+ const visualizationPlugin = document.getElementById('visualization-plugin');
418
+ const closeVisualizationPlugin = document.getElementById('close-visualization-plugin');
419
+ const visualizationResult = document.getElementById('visualization-result');
420
+
421
+ // 思维导图插件元素
422
+ const mindmapPlugin = document.getElementById('mindmap-plugin');
423
+ const closeMindmapPlugin = document.getElementById('close-mindmap-plugin');
424
+ const mindmapResult = document.getElementById('mindmap-result');
425
+
426
+ // 页面加载完成后执行
427
+ document.addEventListener('DOMContentLoaded', function() {
428
+ // 初始化发送按钮事件
429
+ sendButton.addEventListener('click', sendMessage);
430
+
431
+ // 初始化输入框回车事件
432
+ userInput.addEventListener('keypress', function(e) {
433
+ if (e.key === 'Enter' && !e.shiftKey) {
434
+ e.preventDefault();
435
+ sendMessage();
436
+ }
437
+ });
438
+
439
+ // 初始化插件关闭事件
440
+ closeCodePlugin.addEventListener('click', () => {
441
+ codePlugin.style.display = 'none';
442
+ // 清空iframe源以停止任何运行中的代码
443
+ codeExecutionFrame.src = '';
444
+ updateMainContainerLayout();
445
+ });
446
+
447
+ closeVisualizationPlugin.addEventListener('click', () => {
448
+ visualizationPlugin.style.display = 'none';
449
+ updateMainContainerLayout();
450
+ });
451
+
452
+ closeMindmapPlugin.addEventListener('click', () => {
453
+ mindmapPlugin.style.display = 'none';
454
+ updateMainContainerLayout();
455
+ });
456
+ });
457
+
458
+ // 更新主容器布局
459
+ function updateMainContainerLayout() {
460
+ // 检查是否有任何插件处于显示状态
461
+ const isAnyPluginVisible =
462
+ codePlugin.style.display === 'flex' ||
463
+ visualizationPlugin.style.display === 'flex' ||
464
+ mindmapPlugin.style.display === 'flex';
465
+
466
+ // 更新主容器类名
467
+ if (isAnyPluginVisible) {
468
+ mainContainer.classList.add('with-plugin');
469
+ } else {
470
+ mainContainer.classList.remove('with-plugin');
471
+ }
472
+ }
473
+
474
+ // 发送消息
475
+ async function sendMessage() {
476
+ const message = userInput.value.trim();
477
+ if (!message) return;
478
+
479
+ // 添加用户消息
480
+ addMessage(message, true);
481
+
482
+ // 清空输入框
483
+ userInput.value = '';
484
+
485
+ // 禁用发送按钮
486
+ sendButton.disabled = true;
487
+
488
+ try {
489
+ // 发送请求
490
+ const response = await fetch(`/api/student/chat/${agentId}`, {
491
+ method: 'POST',
492
+ headers: {
493
+ 'Content-Type': 'application/json'
494
+ },
495
+ body: JSON.stringify({
496
+ message: message,
497
+ token: token
498
+ })
499
+ });
500
+
501
+ const data = await response.json();
502
+
503
+ if (data.success) {
504
+ // 处理回复内容,移除参考来源部分
505
+ const processedContent = processResponseContent(data.message);
506
+
507
+ // 添加机器人回复
508
+ const messageElement = addMessage(processedContent, false);
509
+
510
+ // 添加参考信息(如果有)
511
+ if (data.references && data.references.length > 0) {
512
+ addReferences(messageElement, data.references);
513
+ }
514
+
515
+ // 检查是否需要显示工具
516
+ if (data.tools && data.tools.length > 0) {
517
+ // 隐藏所有插件
518
+ hideAllPlugins();
519
+
520
+ // 使用新的插件激活函数
521
+ activatePlugins(data.message, data.tools);
522
+ }
523
+ } else {
524
+ // 添加错误消息
525
+ addMessage(`错误: ${data.message}`, false);
526
+ }
527
+ } catch (error) {
528
+ console.error('发送请求出错:', error);
529
+ addMessage('发送请求时出错,请重试', false);
530
+ } finally {
531
+ // 恢复发送按钮
532
+ sendButton.disabled = false;
533
+ }
534
+ }
535
+
536
+ // 处理回复内容,移除参考来源部分
537
+ function processResponseContent(content) {
538
+ // 移除 "===参考来源开始===" 到 "===参考来源结束===" 之间的内容
539
+ return content.replace(/===参考来源开始===[\s\S]*?===参考来源结束===/, '');
540
+ }
541
+
542
+ // 添加参考信息显示函数
543
+ function addReferences(messageElement, references) {
544
+ // 创建参考容器
545
+ const referenceContainer = document.createElement('div');
546
+ referenceContainer.className = 'reference-container';
547
+
548
+ // 创建参考切换按钮
549
+ const toggleButton = document.createElement('div');
550
+ toggleButton.className = 'reference-toggle';
551
+ toggleButton.textContent = '参考来源';
552
+ toggleButton.style.color = '#666';
553
+ toggleButton.style.fontSize = '0.9rem';
554
+ toggleButton.style.cursor = 'pointer';
555
+ toggleButton.style.display = 'flex';
556
+ toggleButton.style.alignItems = 'center';
557
+
558
+ // 创建参考内容
559
+ const referenceContent = document.createElement('div');
560
+ referenceContent.className = 'reference-content';
561
+
562
+ // 添加参考项
563
+ references.forEach(ref => {
564
+ const refItem = document.createElement('div');
565
+ refItem.style.marginBottom = '8px';
566
+ refItem.style.paddingBottom = '8px';
567
+ refItem.style.borderBottom = '1px solid #eee';
568
+
569
+ refItem.innerHTML = `
570
+ <div><strong>[${ref.index}]</strong> ${ref.summary}</div>
571
+ <div><small>来源: ${ref.file_name}</small></div>
572
+ `;
573
+
574
+ referenceContent.appendChild(refItem);
575
+ });
576
+
577
+ // 添加切换功能
578
+ toggleButton.addEventListener('click', () => {
579
+ if (referenceContent.style.display === 'none') {
580
+ referenceContent.style.display = 'block';
581
+ toggleButton.textContent = '折叠参考来源';
582
+ } else {
583
+ referenceContent.style.display = 'none';
584
+ toggleButton.textContent = '参考来源';
585
+ }
586
+ });
587
+
588
+ // 组装并添加到消息
589
+ referenceContainer.appendChild(toggleButton);
590
+ referenceContainer.appendChild(referenceContent);
591
+ messageElement.appendChild(referenceContainer);
592
+ }
593
+
594
+ // 添加消息函数
595
+ function addMessage(content, isUser) {
596
+ const messageDiv = document.createElement('div');
597
+ messageDiv.className = isUser ? 'message user-message' : 'message bot-message';
598
+
599
+ // 处理内容中的换行符和代码块
600
+ const processedContent = processMessageContent(content);
601
+ messageDiv.innerHTML = processedContent;
602
+
603
+ chatMessages.appendChild(messageDiv);
604
+
605
+ // 滚动到底部
606
+ chatMessages.scrollTop = chatMessages.scrollHeight;
607
+
608
+ // 对代码块应用高亮
609
+ if (!isUser) {
610
+ applySyntaxHighlighting(messageDiv);
611
+ }
612
+
613
+ return messageDiv;
614
+ }
615
+
616
+ // 处理消息内容中的换行符和代码块
617
+ function processMessageContent(content) {
618
+ // 先替换代码块,保护它们不受其他替换的影响
619
+ const codeBlocks = [];
620
+ let processedContent = content.replace(/```(\w*)\n([\s\S]*?)\n```/g, function(match, language, code) {
621
+ const id = codeBlocks.length;
622
+ codeBlocks.push({ language, code });
623
+ return `__CODE_BLOCK_${id}__`;
624
+ });
625
+
626
+ // 替换普通换行符
627
+ processedContent = processedContent.replace(/\n/g, '<br>');
628
+
629
+ // 恢复代码块
630
+ processedContent = processedContent.replace(/__CODE_BLOCK_(\d+)__/g, function(match, id) {
631
+ const { language, code } = codeBlocks[id];
632
+ const langClass = language ? ` class="language-${language}"` : '';
633
+ return `<pre><code${langClass}>${escapeHtml(code)}</code></pre>`;
634
+ });
635
+
636
+ return processedContent;
637
+ }
638
+
639
+ // 应用语法高亮
640
+ function applySyntaxHighlighting(element) {
641
+ // 检查是否已加载highlight.js
642
+ if (typeof hljs !== 'undefined') {
643
+ // 找出所有代码块并应用高亮
644
+ element.querySelectorAll('pre code').forEach((block) => {
645
+ hljs.highlightBlock(block);
646
+ });
647
+ }
648
+ }
649
+
650
+ // 转义HTML字符
651
+ function escapeHtml(text) {
652
+ return text
653
+ .replace(/&/g, "&amp;")
654
+ .replace(/</g, "&lt;")
655
+ .replace(/>/g, "&gt;")
656
+ .replace(/"/g, "&quot;")
657
+ .replace(/'/g, "&#039;");
658
+ }
659
+
660
+ // 隐藏所有插件
661
+ function hideAllPlugins() {
662
+ codePlugin.style.display = 'none';
663
+ visualizationPlugin.style.display = 'none';
664
+ mindmapPlugin.style.display = 'none';
665
+ updateMainContainerLayout();
666
+ }
667
+
668
+ // 提取代码块
669
+ function extractCodeBlocks(message) {
670
+ const codeBlocks = [];
671
+ const codeRegex = /```python\n([\s\S]*?)\n```/g;
672
+
673
+ let match;
674
+ while ((match = codeRegex.exec(message)) !== null) {
675
+ codeBlocks.push(match[1]);
676
+ }
677
+
678
+ return codeBlocks;
679
+ }
680
+
681
+ // 提取3D可视化函数代码
682
+ function extract3DVisualizationCode(message) {
683
+ // 尝试提取函数代码块
684
+ const codeRegex = /```python\s*(import[\s\S]*?def create_3d_plot\(\):[\s\S]*?return[\s\S]*?})\s*```/i;
685
+ const match = codeRegex.exec(message);
686
+
687
+ if (match) {
688
+ return match[1].trim();
689
+ }
690
+
691
+ return null;
692
+ }
693
+
694
+ // 提取思维导图内容
695
+ function extractMindmapContent(message) {
696
+ // 尝试提取@startmindmap和@endmindmap之间的内容
697
+ const mindmapRegex = /@startmindmap\n([\s\S]*?)@endmindmap/;
698
+ const match = mindmapRegex.exec(message);
699
+
700
+ if (match) {
701
+ return `@startmindmap\n${match[1]}\n@endmindmap`;
702
+ }
703
+
704
+ return null;
705
+ }
706
+
707
+ // 激活代码执行插件
708
+ function activateCodePlugin(message) {
709
+ // 提取代码块
710
+ const codeBlocks = extractCodeBlocks(message);
711
+
712
+ if (codeBlocks.length > 0) {
713
+ const isAlreadyVisible = codePlugin.style.display === 'flex';
714
+
715
+ // 显示插件容器
716
+ codePlugin.style.display = 'flex';
717
+ updateMainContainerLayout();
718
+
719
+ // 获取iframe
720
+ const iframe = document.getElementById('code-execution-frame');
721
+
722
+ // 如果插件已经可见且iframe已加载
723
+ if (isAlreadyVisible && iframe.contentWindow) {
724
+ // 发送新代码到现有iframe
725
+ iframe.contentWindow.postMessage({
726
+ type: 'setCode',
727
+ code: codeBlocks[0]
728
+ }, '*');
729
+ } else {
730
+ // 设置iframe源
731
+ let src = '/code_execution.html';
732
+ if (codeBlocks.length > 0) {
733
+ src += `?code=${encodeURIComponent(codeBlocks[0])}`;
734
+ }
735
+
736
+ iframe.src = src;
737
+
738
+ // 设置iframe加载事件处理程序
739
+ iframe.onload = function() {
740
+ if (codeBlocks.length > 0) {
741
+ iframe.contentWindow.postMessage({
742
+ type: 'setCode',
743
+ code: codeBlocks[0]
744
+ }, '*');
745
+ }
746
+ };
747
+ }
748
+ }
749
+ }
750
+
751
+ // 激活3D可视化插件
752
+ // 激活3D可视化插件
753
+ function activate3DVisualization(message) {
754
+ const code = extract3DVisualizationCode(message);
755
+
756
+ if (code) {
757
+ // 显示插件容器
758
+ visualizationPlugin.style.display = 'flex';
759
+ updateMainContainerLayout();
760
+
761
+ // 显示加载状态
762
+ visualizationResult.innerHTML = `
763
+ <div class="text-center py-4">
764
+ <div class="spinner-border text-primary" role="status">
765
+ <span class="visually-hidden">生成中...</span>
766
+ </div>
767
+ <p class="mt-3">正在生成3D图形,请稍候...</p>
768
+ </div>
769
+ `;
770
+
771
+ // 自动生成可视化
772
+ fetch('https://samlax12-agent.hf.space/api/visualization/3d-surface', {
773
+ method: 'POST',
774
+ headers: {
775
+ 'Content-Type': 'application/json'
776
+ },
777
+ body: JSON.stringify({ code: code })
778
+ })
779
+ .then(response => response.json())
780
+ .then(data => {
781
+ if (data.success) {
782
+ // 直接嵌入HTML
783
+ visualizationResult.innerHTML = `
784
+ <div class="visualization-iframe-container" style="width:100%; height:500px;">
785
+ <iframe src="${data.html_url}" style="width:100%; height:100%; border:none;"></iframe>
786
+ </div>
787
+ <div class="text-center mt-3">
788
+ <p>3D图形生成成功</p>
789
+ <a href="${data.html_url}" class="btn btn-sm btn-outline-primary" target="_blank">
790
+ <i class="bi bi-arrows-fullscreen"></i> 全屏查看
791
+ </a>
792
+ </div>
793
+ `;
794
+ } else {
795
+ visualizationResult.innerHTML = `
796
+ <div class="alert alert-danger">
797
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
798
+ 生成失败: ${data.message}
799
+ </div>
800
+ `;
801
+ }
802
+ })
803
+ .catch(error => {
804
+ console.error('生成3D图形出错:', error);
805
+ visualizationResult.innerHTML = `
806
+ <div class="alert alert-danger">
807
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
808
+ 生成3D图形时发生错误,请重试
809
+ </div>
810
+ `;
811
+ });
812
+ }
813
+ }
814
+ // 激活思维导图插件
815
+ function activateMindmap(message) {
816
+ const content = extractMindmapContent(message);
817
+
818
+ if (content) {
819
+ // 显示插件容器
820
+ mindmapPlugin.style.display = 'flex';
821
+ updateMainContainerLayout();
822
+
823
+ // 显示加载状态
824
+ mindmapResult.innerHTML = `
825
+ <div class="text-center py-4">
826
+ <div class="spinner-border text-primary" role="status">
827
+ <span class="visually-hidden">生成中...</span>
828
+ </div>
829
+ <p class="mt-3">正在生成思维导图,请稍候...</p>
830
+ </div>
831
+ `;
832
+
833
+ // 自动生成思维导图
834
+ fetch('https://samlax12-agent.hf.space/api/visualization/mindmap', {
835
+ method: 'POST',
836
+ headers: {
837
+ 'Content-Type': 'application/json'
838
+ },
839
+ body: JSON.stringify({ content: content })
840
+ })
841
+ .then(response => response.json())
842
+ .then(data => {
843
+ if (data.success) {
844
+ mindmapResult.innerHTML = `
845
+ <div class="text-center">
846
+ <img src="${data.url}" class="mindmap-image" alt="思维导图">
847
+ <p class="mt-3">生成成功!</p>
848
+ </div>
849
+ `;
850
+ }
851
+ else {
852
+ mindmapResult.innerHTML = `
853
+ <div class="alert alert-danger">
854
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
855
+ 生成失败: ${data.message}
856
+ </div>
857
+ `;
858
+ }
859
+ })
860
+ .catch(error => {
861
+ console.error('生成思维导图出错:', error);
862
+ mindmapResult.innerHTML = `
863
+ <div class="alert alert-danger">
864
+ <i class="bi bi-exclamation-triangle-fill me-2"></i>
865
+ 生成思维导图时发生错误,请重试
866
+ </div>
867
+ `;
868
+ });
869
+ }
870
+ }
871
+
872
+ // 根据消息内容激活适当的插件
873
+ function activatePlugins(message, tools) {
874
+ // 检查并激活代码执行插件
875
+ if (tools.includes('code') && message.includes('```python')) {
876
+ activateCodePlugin(message);
877
+ }
878
+
879
+ // 检查并激活3D可视化插件
880
+ if (tools.includes('visualization') &&
881
+ (message.includes('def create_3d_plot') ||
882
+ message.includes('3D') || message.includes('可视化'))) {
883
+ activate3DVisualization(message);
884
+ }
885
+
886
+ // 检查并激活思维导图插件
887
+ if (tools.includes('mindmap') &&
888
+ (message.includes('@startmindmap') ||
889
+ message.includes('思维导图'))) {
890
+ activateMindmap(message);
891
+ }
892
+ }
893
+ </script>
894
+
895
+ <!-- 添加highlight.js用于代码高亮 -->
896
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
897
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script>
898
+ <script>
899
+ // 初始化代码高亮
900
+ document.addEventListener('DOMContentLoaded', function() {
901
+ if (typeof hljs !== 'undefined') {
902
+ hljs.configure({
903
+ languages: ['python']
904
+ });
905
+ }
906
+ });
907
+ </script>
908
+ </body>
909
  </html>