pug-site-core 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ai/aiPanel.js +0 -722
- package/lib/ai/aiRouter.js +586 -258
- package/lib/generate.js +417 -342
- package/lib/utils.js +1 -7
- package/package.json +2 -2
package/lib/ai/aiPanel.js
CHANGED
|
@@ -1,722 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI面板工具模块
|
|
3
|
-
* 为页面注入AI对话功能,允许用户与AI进行实时对话
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* AI面板核心JavaScript代码
|
|
8
|
-
*/
|
|
9
|
-
function createAIPanelScript() {
|
|
10
|
-
/**
|
|
11
|
-
* 公共样式配置
|
|
12
|
-
*/
|
|
13
|
-
const AI_STYLES = {
|
|
14
|
-
// 通用按钮样式
|
|
15
|
-
button: {
|
|
16
|
-
base: `
|
|
17
|
-
border: none;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
border-radius: 6px;
|
|
20
|
-
font-size: 12px;
|
|
21
|
-
padding: 8px 16px;
|
|
22
|
-
transition: all 0.2s;
|
|
23
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
24
|
-
`,
|
|
25
|
-
primary: 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;',
|
|
26
|
-
secondary: 'background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white;',
|
|
27
|
-
success: 'background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white;',
|
|
28
|
-
danger: 'background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); color: white;'
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
// 输入框样式
|
|
32
|
-
input: `
|
|
33
|
-
width: 100%;
|
|
34
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
35
|
-
color: white !important;
|
|
36
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
37
|
-
border-radius: 8px;
|
|
38
|
-
padding: 12px;
|
|
39
|
-
font-size: 14px;
|
|
40
|
-
box-sizing: border-box;
|
|
41
|
-
transition: all 0.2s;
|
|
42
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
43
|
-
`,
|
|
44
|
-
|
|
45
|
-
// 文本域样式
|
|
46
|
-
textarea: `
|
|
47
|
-
width: 100%;
|
|
48
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
49
|
-
color: white !important;
|
|
50
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
51
|
-
border-radius: 8px;
|
|
52
|
-
padding: 12px;
|
|
53
|
-
font-size: 14px;
|
|
54
|
-
box-sizing: border-box;
|
|
55
|
-
resize: vertical;
|
|
56
|
-
min-height: 100px;
|
|
57
|
-
transition: all 0.2s;
|
|
58
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
59
|
-
`,
|
|
60
|
-
|
|
61
|
-
// 消息样式
|
|
62
|
-
message: {
|
|
63
|
-
user: `
|
|
64
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
65
|
-
color: white;
|
|
66
|
-
padding: 12px 16px;
|
|
67
|
-
border-radius: 18px 18px 4px 18px;
|
|
68
|
-
margin: 8px 0;
|
|
69
|
-
margin-left: 20%;
|
|
70
|
-
word-wrap: break-word;
|
|
71
|
-
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
|
72
|
-
`,
|
|
73
|
-
assistant: `
|
|
74
|
-
background: rgba(255, 255, 255, 0.1);
|
|
75
|
-
color: white;
|
|
76
|
-
padding: 12px 16px;
|
|
77
|
-
border-radius: 18px 18px 18px 4px;
|
|
78
|
-
margin: 8px 0;
|
|
79
|
-
margin-right: 20%;
|
|
80
|
-
word-wrap: break-word;
|
|
81
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
82
|
-
backdrop-filter: blur(10px);
|
|
83
|
-
`,
|
|
84
|
-
system: `
|
|
85
|
-
background: rgba(255, 193, 7, 0.2);
|
|
86
|
-
color: #ffc107;
|
|
87
|
-
padding: 8px 12px;
|
|
88
|
-
border-radius: 12px;
|
|
89
|
-
margin: 8px 0;
|
|
90
|
-
text-align: center;
|
|
91
|
-
font-size: 12px;
|
|
92
|
-
border: 1px solid rgba(255, 193, 7, 0.3);
|
|
93
|
-
`
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* UI工具类
|
|
99
|
-
*/
|
|
100
|
-
class AIUIHelper {
|
|
101
|
-
/**
|
|
102
|
-
* 创建带样式的元素
|
|
103
|
-
*/
|
|
104
|
-
static createElement(tag, id = '', className = '', styles = '', innerHTML = '') {
|
|
105
|
-
const element = document.createElement(tag);
|
|
106
|
-
if (id) element.id = id;
|
|
107
|
-
if (className) element.className = className;
|
|
108
|
-
if (styles) element.style.cssText = styles;
|
|
109
|
-
if (innerHTML) element.innerHTML = innerHTML;
|
|
110
|
-
return element;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 创建按钮
|
|
115
|
-
*/
|
|
116
|
-
static createButton(id, text, type = 'primary', onClick = null) {
|
|
117
|
-
const button = this.createElement(
|
|
118
|
-
'button',
|
|
119
|
-
id,
|
|
120
|
-
'ai-panel-element',
|
|
121
|
-
AI_STYLES.button.base + AI_STYLES.button[type],
|
|
122
|
-
text
|
|
123
|
-
);
|
|
124
|
-
if (onClick) button.addEventListener('click', onClick);
|
|
125
|
-
return button;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 显示提示消息
|
|
130
|
-
*/
|
|
131
|
-
static showToast(message, type = 'info') {
|
|
132
|
-
const existing = document.getElementById('ai-panel-toast');
|
|
133
|
-
if (existing) existing.remove();
|
|
134
|
-
|
|
135
|
-
const colors = {
|
|
136
|
-
success: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
|
|
137
|
-
error: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
|
|
138
|
-
info: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const toast = this.createElement('div', 'ai-panel-toast', 'ai-panel-toast', `
|
|
142
|
-
position: fixed;
|
|
143
|
-
top: 20px;
|
|
144
|
-
right: 20px;
|
|
145
|
-
background: ${colors[type]};
|
|
146
|
-
color: white;
|
|
147
|
-
padding: 12px 20px;
|
|
148
|
-
border-radius: 8px;
|
|
149
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
150
|
-
font-size: 14px;
|
|
151
|
-
z-index: 10001;
|
|
152
|
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
153
|
-
opacity: 0;
|
|
154
|
-
transition: all 0.3s ease;
|
|
155
|
-
backdrop-filter: blur(10px);
|
|
156
|
-
`, message);
|
|
157
|
-
|
|
158
|
-
document.body.appendChild(toast);
|
|
159
|
-
|
|
160
|
-
setTimeout(() => toast.style.opacity = '1', 100);
|
|
161
|
-
setTimeout(() => {
|
|
162
|
-
toast.style.opacity = '0';
|
|
163
|
-
setTimeout(() => toast.remove(), 300);
|
|
164
|
-
}, 3000);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 格式化消息内容(支持Markdown基本语法)
|
|
169
|
-
*/
|
|
170
|
-
static formatMessage(content) {
|
|
171
|
-
return content
|
|
172
|
-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
|
173
|
-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
|
174
|
-
.replace(/`(.*?)`/g, '<code style="background: rgba(255,255,255,0.1); padding: 2px 4px; border-radius: 3px;">$1</code>')
|
|
175
|
-
.replace(/\n/g, '<br>');
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* 拖拽功能类
|
|
181
|
-
*/
|
|
182
|
-
class AIDragHandler {
|
|
183
|
-
constructor(element, handle = null) {
|
|
184
|
-
this.element = element;
|
|
185
|
-
this.handle = handle || element;
|
|
186
|
-
this.isDragging = false;
|
|
187
|
-
this.offset = { x: 0, y: 0 };
|
|
188
|
-
|
|
189
|
-
this.boundMouseDown = this.handleMouseDown.bind(this);
|
|
190
|
-
this.boundMouseMove = this.handleMouseMove.bind(this);
|
|
191
|
-
this.boundMouseUp = this.handleMouseUp.bind(this);
|
|
192
|
-
|
|
193
|
-
this.setupDrag();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
setupDrag() {
|
|
197
|
-
this.handle.addEventListener('mousedown', this.boundMouseDown);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
handleMouseDown(e) {
|
|
201
|
-
this.isDragging = true;
|
|
202
|
-
const rect = this.element.getBoundingClientRect();
|
|
203
|
-
this.offset.x = e.clientX - rect.left;
|
|
204
|
-
this.offset.y = e.clientY - rect.top;
|
|
205
|
-
|
|
206
|
-
this.handle.style.cursor = 'grabbing';
|
|
207
|
-
document.addEventListener('mousemove', this.boundMouseMove);
|
|
208
|
-
document.addEventListener('mouseup', this.boundMouseUp);
|
|
209
|
-
e.preventDefault();
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
handleMouseMove(e) {
|
|
213
|
-
if (!this.isDragging) return;
|
|
214
|
-
|
|
215
|
-
const x = e.clientX - this.offset.x;
|
|
216
|
-
const y = e.clientY - this.offset.y;
|
|
217
|
-
const maxX = window.innerWidth - this.element.offsetWidth;
|
|
218
|
-
const maxY = window.innerHeight - this.element.offsetHeight;
|
|
219
|
-
|
|
220
|
-
this.element.style.left = Math.max(0, Math.min(x, maxX)) + 'px';
|
|
221
|
-
this.element.style.top = Math.max(0, Math.min(y, maxY)) + 'px';
|
|
222
|
-
this.element.style.right = 'auto';
|
|
223
|
-
this.element.style.bottom = 'auto';
|
|
224
|
-
this.element.style.transform = 'none';
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
handleMouseUp() {
|
|
228
|
-
this.isDragging = false;
|
|
229
|
-
this.handle.style.cursor = 'move';
|
|
230
|
-
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
231
|
-
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
destroy() {
|
|
235
|
-
this.handle.removeEventListener('mousedown', this.boundMouseDown);
|
|
236
|
-
document.removeEventListener('mousemove', this.boundMouseMove);
|
|
237
|
-
document.removeEventListener('mouseup', this.boundMouseUp);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// 状态变量
|
|
242
|
-
let aiPanelOpen = false;
|
|
243
|
-
let aiPanel = null;
|
|
244
|
-
let conversationHistory = [];
|
|
245
|
-
let isWaitingResponse = false;
|
|
246
|
-
|
|
247
|
-
// 创建AI面板切换按钮
|
|
248
|
-
function createAIToggleButton() {
|
|
249
|
-
// 读取配置
|
|
250
|
-
const config = window.AI_PANEL_CONFIG || { position: 'left', autoOpen: false };
|
|
251
|
-
const position = config.position === 'right' ? 'right' : 'left';
|
|
252
|
-
|
|
253
|
-
const positionStyle = position === 'right' ? 'right: 20px;' : 'left: 20px;';
|
|
254
|
-
|
|
255
|
-
const toggle = AIUIHelper.createElement('div', 'ai-panel-toggle', 'ai-panel-element', `
|
|
256
|
-
position: fixed;
|
|
257
|
-
bottom: 20px;
|
|
258
|
-
${positionStyle}
|
|
259
|
-
width: 60px;
|
|
260
|
-
height: 60px;
|
|
261
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
262
|
-
border-radius: 50%;
|
|
263
|
-
display: flex;
|
|
264
|
-
align-items: center;
|
|
265
|
-
justify-content: center;
|
|
266
|
-
cursor: pointer;
|
|
267
|
-
z-index: 9999;
|
|
268
|
-
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
|
269
|
-
transition: all 0.3s ease;
|
|
270
|
-
backdrop-filter: blur(10px);
|
|
271
|
-
`, '🤖');
|
|
272
|
-
|
|
273
|
-
toggle.style.fontSize = '24px';
|
|
274
|
-
toggle.title = '打开AI助手';
|
|
275
|
-
|
|
276
|
-
toggle.addEventListener('click', toggleAIPanel);
|
|
277
|
-
toggle.addEventListener('mouseenter', () => {
|
|
278
|
-
toggle.style.transform = 'scale(1.1)';
|
|
279
|
-
toggle.style.boxShadow = '0 6px 25px rgba(102, 126, 234, 0.6)';
|
|
280
|
-
});
|
|
281
|
-
toggle.addEventListener('mouseleave', () => {
|
|
282
|
-
toggle.style.transform = 'scale(1)';
|
|
283
|
-
toggle.style.boxShadow = '0 4px 20px rgba(102, 126, 234, 0.4)';
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
document.body.appendChild(toggle);
|
|
287
|
-
|
|
288
|
-
// 如果配置为自动打开,则自动打开面板
|
|
289
|
-
if (config.autoOpen) {
|
|
290
|
-
setTimeout(() => {
|
|
291
|
-
toggleAIPanel();
|
|
292
|
-
}, 1000); // 延迟1秒自动打开
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// 切换AI面板
|
|
297
|
-
function toggleAIPanel() {
|
|
298
|
-
aiPanelOpen = !aiPanelOpen;
|
|
299
|
-
const toggle = document.getElementById('ai-panel-toggle');
|
|
300
|
-
|
|
301
|
-
if (aiPanelOpen) {
|
|
302
|
-
createAIPanel();
|
|
303
|
-
toggle.innerHTML = '✕';
|
|
304
|
-
toggle.title = '关闭AI助手';
|
|
305
|
-
toggle.style.background = 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)';
|
|
306
|
-
} else {
|
|
307
|
-
closeAIPanel();
|
|
308
|
-
toggle.innerHTML = '🤖';
|
|
309
|
-
toggle.title = '打开AI助手';
|
|
310
|
-
toggle.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 创建AI面板
|
|
315
|
-
function createAIPanel() {
|
|
316
|
-
if (aiPanel) return;
|
|
317
|
-
|
|
318
|
-
aiPanel = AIUIHelper.createElement('div', 'ai-panel', 'ai-panel-element', `
|
|
319
|
-
position: fixed;
|
|
320
|
-
top: 50%;
|
|
321
|
-
left: 50%;
|
|
322
|
-
transform: translate(-50%, -50%);
|
|
323
|
-
width: 450px;
|
|
324
|
-
max-width: 90vw;
|
|
325
|
-
height: 600px;
|
|
326
|
-
max-height: 90vh;
|
|
327
|
-
background: linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(30, 30, 60, 0.9) 100%);
|
|
328
|
-
border-radius: 16px;
|
|
329
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
330
|
-
backdrop-filter: blur(20px);
|
|
331
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
332
|
-
z-index: 10000;
|
|
333
|
-
display: flex;
|
|
334
|
-
flex-direction: column;
|
|
335
|
-
overflow: hidden;
|
|
336
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
337
|
-
`);
|
|
338
|
-
|
|
339
|
-
// 创建头部
|
|
340
|
-
const header = AIUIHelper.createElement('div', 'ai-panel-header', 'ai-panel-element', `
|
|
341
|
-
padding: 16px 20px;
|
|
342
|
-
background: rgba(255, 255, 255, 0.05);
|
|
343
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
344
|
-
display: flex;
|
|
345
|
-
justify-content: space-between;
|
|
346
|
-
align-items: center;
|
|
347
|
-
cursor: move;
|
|
348
|
-
`);
|
|
349
|
-
|
|
350
|
-
const titleSection = AIUIHelper.createElement('div', '', '', `
|
|
351
|
-
display: flex;
|
|
352
|
-
align-items: center;
|
|
353
|
-
gap: 12px;
|
|
354
|
-
`, `
|
|
355
|
-
<span style="font-size: 20px;">🤖</span>
|
|
356
|
-
<div>
|
|
357
|
-
<div style="font-weight: 600; color: white; font-size: 16px;">AI助手</div>
|
|
358
|
-
<div style="color: rgba(255, 255, 255, 0.7); font-size: 12px;">智能对话助手</div>
|
|
359
|
-
</div>
|
|
360
|
-
`);
|
|
361
|
-
|
|
362
|
-
const headerButtons = AIUIHelper.createElement('div', '', '', `
|
|
363
|
-
display: flex;
|
|
364
|
-
gap: 8px;
|
|
365
|
-
`);
|
|
366
|
-
|
|
367
|
-
const clearBtn = AIUIHelper.createButton('ai-clear-chat', '清空', 'secondary', clearConversation);
|
|
368
|
-
const closeBtn = AIUIHelper.createButton('ai-close-panel', '✕', 'danger', toggleAIPanel);
|
|
369
|
-
|
|
370
|
-
clearBtn.style.fontSize = '11px';
|
|
371
|
-
clearBtn.style.padding = '6px 12px';
|
|
372
|
-
closeBtn.style.fontSize = '14px';
|
|
373
|
-
closeBtn.style.padding = '6px 12px';
|
|
374
|
-
closeBtn.style.borderRadius = '50%';
|
|
375
|
-
closeBtn.style.width = '32px';
|
|
376
|
-
closeBtn.style.height = '32px';
|
|
377
|
-
closeBtn.style.display = 'flex';
|
|
378
|
-
closeBtn.style.alignItems = 'center';
|
|
379
|
-
closeBtn.style.justifyContent = 'center';
|
|
380
|
-
|
|
381
|
-
headerButtons.appendChild(clearBtn);
|
|
382
|
-
headerButtons.appendChild(closeBtn);
|
|
383
|
-
header.appendChild(titleSection);
|
|
384
|
-
header.appendChild(headerButtons);
|
|
385
|
-
|
|
386
|
-
// 创建消息区域
|
|
387
|
-
const messagesContainer = AIUIHelper.createElement('div', 'ai-messages', 'ai-panel-element', `
|
|
388
|
-
flex: 1;
|
|
389
|
-
padding: 20px;
|
|
390
|
-
overflow-y: auto;
|
|
391
|
-
scrollbar-width: thin;
|
|
392
|
-
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
|
393
|
-
`);
|
|
394
|
-
|
|
395
|
-
// 自定义滚动条样式
|
|
396
|
-
const scrollbarStyle = AIUIHelper.createElement('style', '', '', '', `
|
|
397
|
-
#ai-messages::-webkit-scrollbar {
|
|
398
|
-
width: 6px;
|
|
399
|
-
}
|
|
400
|
-
#ai-messages::-webkit-scrollbar-track {
|
|
401
|
-
background: transparent;
|
|
402
|
-
}
|
|
403
|
-
#ai-messages::-webkit-scrollbar-thumb {
|
|
404
|
-
background: rgba(255, 255, 255, 0.3);
|
|
405
|
-
border-radius: 3px;
|
|
406
|
-
}
|
|
407
|
-
#ai-messages::-webkit-scrollbar-thumb:hover {
|
|
408
|
-
background: rgba(255, 255, 255, 0.5);
|
|
409
|
-
}
|
|
410
|
-
`);
|
|
411
|
-
document.head.appendChild(scrollbarStyle);
|
|
412
|
-
|
|
413
|
-
// 添加欢迎消息
|
|
414
|
-
if (conversationHistory.length === 0) {
|
|
415
|
-
addMessage('assistant', '👋 你好!我是AI助手,有什么可以帮助你的吗?');
|
|
416
|
-
} else {
|
|
417
|
-
// 恢复对话历史
|
|
418
|
-
conversationHistory.forEach(msg => {
|
|
419
|
-
appendMessageToUI(msg.role, msg.content);
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// 创建输入区域
|
|
424
|
-
const inputContainer = AIUIHelper.createElement('div', 'ai-input-container', 'ai-panel-element', `
|
|
425
|
-
padding: 20px;
|
|
426
|
-
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
427
|
-
background: rgba(255, 255, 255, 0.02);
|
|
428
|
-
`);
|
|
429
|
-
|
|
430
|
-
const inputGroup = AIUIHelper.createElement('div', '', '', `
|
|
431
|
-
display: flex;
|
|
432
|
-
gap: 12px;
|
|
433
|
-
`);
|
|
434
|
-
|
|
435
|
-
const messageInput = AIUIHelper.createElement('textarea', 'ai-message-input', 'ai-panel-element', `
|
|
436
|
-
${AI_STYLES.textarea}
|
|
437
|
-
min-height: 50px;
|
|
438
|
-
max-height: 150px;
|
|
439
|
-
resize: none;
|
|
440
|
-
`);
|
|
441
|
-
messageInput.placeholder = '输入你的问题...';
|
|
442
|
-
|
|
443
|
-
const sendBtn = AIUIHelper.createButton('ai-send-btn', '发送', 'primary', sendMessage);
|
|
444
|
-
sendBtn.style.height = '50px';
|
|
445
|
-
sendBtn.style.minWidth = '80px';
|
|
446
|
-
sendBtn.style.alignSelf = 'flex-end';
|
|
447
|
-
|
|
448
|
-
// 输入框事件
|
|
449
|
-
messageInput.addEventListener('keydown', (e) => {
|
|
450
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
451
|
-
e.preventDefault();
|
|
452
|
-
sendMessage();
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
messageInput.addEventListener('input', () => {
|
|
457
|
-
// 自动调整高度
|
|
458
|
-
messageInput.style.height = 'auto';
|
|
459
|
-
messageInput.style.height = Math.min(messageInput.scrollHeight, 150) + 'px';
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
inputGroup.appendChild(messageInput);
|
|
463
|
-
inputGroup.appendChild(sendBtn);
|
|
464
|
-
inputContainer.appendChild(inputGroup);
|
|
465
|
-
|
|
466
|
-
// 组装面板
|
|
467
|
-
aiPanel.appendChild(header);
|
|
468
|
-
aiPanel.appendChild(messagesContainer);
|
|
469
|
-
aiPanel.appendChild(inputContainer);
|
|
470
|
-
|
|
471
|
-
// 设置拖拽
|
|
472
|
-
new AIDragHandler(aiPanel, header);
|
|
473
|
-
|
|
474
|
-
document.body.appendChild(aiPanel);
|
|
475
|
-
|
|
476
|
-
// 聚焦输入框
|
|
477
|
-
setTimeout(() => messageInput.focus(), 100);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// 关闭AI面板
|
|
481
|
-
function closeAIPanel() {
|
|
482
|
-
if (aiPanel) {
|
|
483
|
-
aiPanel.remove();
|
|
484
|
-
aiPanel = null;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// 添加消息到对话历史和UI
|
|
489
|
-
function addMessage(role, content) {
|
|
490
|
-
conversationHistory.push({ role, content, timestamp: new Date().toISOString() });
|
|
491
|
-
appendMessageToUI(role, content);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// 在UI中添加消息
|
|
495
|
-
function appendMessageToUI(role, content) {
|
|
496
|
-
const messagesContainer = document.getElementById('ai-messages');
|
|
497
|
-
if (!messagesContainer) return;
|
|
498
|
-
|
|
499
|
-
const messageDiv = AIUIHelper.createElement('div', '', 'ai-panel-element', `
|
|
500
|
-
${AI_STYLES.message[role]}
|
|
501
|
-
animation: fadeInUp 0.3s ease;
|
|
502
|
-
`);
|
|
503
|
-
|
|
504
|
-
// 添加头像和时间
|
|
505
|
-
const timestamp = new Date().toLocaleTimeString('zh-CN', {
|
|
506
|
-
hour: '2-digit',
|
|
507
|
-
minute: '2-digit'
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
const messageContent = `
|
|
511
|
-
<div style="display: flex; align-items: flex-start; gap: 8px;">
|
|
512
|
-
<div style="flex: 1;">
|
|
513
|
-
${AIUIHelper.formatMessage(content)}
|
|
514
|
-
<div style="font-size: 10px; opacity: 0.7; margin-top: 4px;">
|
|
515
|
-
${role === 'user' ? '你' : 'AI'} · ${timestamp}
|
|
516
|
-
</div>
|
|
517
|
-
</div>
|
|
518
|
-
</div>
|
|
519
|
-
`;
|
|
520
|
-
|
|
521
|
-
messageDiv.innerHTML = messageContent;
|
|
522
|
-
messagesContainer.appendChild(messageDiv);
|
|
523
|
-
|
|
524
|
-
// 滚动到底部
|
|
525
|
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// 发送消息
|
|
529
|
-
async function sendMessage() {
|
|
530
|
-
const input = document.getElementById('ai-message-input');
|
|
531
|
-
const sendBtn = document.getElementById('ai-send-btn');
|
|
532
|
-
|
|
533
|
-
if (!input || !sendBtn || isWaitingResponse) return;
|
|
534
|
-
|
|
535
|
-
const message = input.value.trim();
|
|
536
|
-
if (!message) return;
|
|
537
|
-
|
|
538
|
-
// 添加用户消息
|
|
539
|
-
addMessage('user', message);
|
|
540
|
-
input.value = '';
|
|
541
|
-
input.style.height = '50px';
|
|
542
|
-
|
|
543
|
-
// 设置加载状态
|
|
544
|
-
isWaitingResponse = true;
|
|
545
|
-
sendBtn.disabled = true;
|
|
546
|
-
sendBtn.textContent = '发送中...';
|
|
547
|
-
sendBtn.style.opacity = '0.6';
|
|
548
|
-
|
|
549
|
-
// 添加加载提示
|
|
550
|
-
const loadingDiv = AIUIHelper.createElement('div', 'ai-loading', 'ai-panel-element', `
|
|
551
|
-
${AI_STYLES.message.system}
|
|
552
|
-
animation: pulse 1.5s infinite;
|
|
553
|
-
`, '🤖 AI正在思考...');
|
|
554
|
-
|
|
555
|
-
const messagesContainer = document.getElementById('ai-messages');
|
|
556
|
-
messagesContainer.appendChild(loadingDiv);
|
|
557
|
-
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
558
|
-
|
|
559
|
-
try {
|
|
560
|
-
// 发送到服务器
|
|
561
|
-
const response = await fetch('/api/ai-chat', {
|
|
562
|
-
method: 'POST',
|
|
563
|
-
headers: {
|
|
564
|
-
'Content-Type': 'application/json',
|
|
565
|
-
},
|
|
566
|
-
body: JSON.stringify({
|
|
567
|
-
message: message,
|
|
568
|
-
history: conversationHistory.slice(-10), // 只发送最近10条消息作为上下文
|
|
569
|
-
timestamp: new Date().toISOString(),
|
|
570
|
-
url: window.location.href
|
|
571
|
-
}),
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
const data = await response.json();
|
|
575
|
-
|
|
576
|
-
// 移除加载提示
|
|
577
|
-
loadingDiv.remove();
|
|
578
|
-
|
|
579
|
-
if (data.success) {
|
|
580
|
-
// 添加AI回复
|
|
581
|
-
addMessage('assistant', data.response);
|
|
582
|
-
} else {
|
|
583
|
-
addMessage('system', `❌ 错误: ${data.message || '服务器响应异常'}`);
|
|
584
|
-
AIUIHelper.showToast('AI响应失败,请重试', 'error');
|
|
585
|
-
}
|
|
586
|
-
} catch (error) {
|
|
587
|
-
console.error('AI请求失败:', error);
|
|
588
|
-
loadingDiv.remove();
|
|
589
|
-
addMessage('system', '❌ 网络错误,请检查连接后重试');
|
|
590
|
-
AIUIHelper.showToast('网络连接失败', 'error');
|
|
591
|
-
} finally {
|
|
592
|
-
// 恢复发送按钮状态
|
|
593
|
-
isWaitingResponse = false;
|
|
594
|
-
sendBtn.disabled = false;
|
|
595
|
-
sendBtn.textContent = '发送';
|
|
596
|
-
sendBtn.style.opacity = '1';
|
|
597
|
-
|
|
598
|
-
// 重新聚焦输入框
|
|
599
|
-
input.focus();
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// 清空对话
|
|
604
|
-
function clearConversation() {
|
|
605
|
-
if (confirm('确定要清空所有对话记录吗?')) {
|
|
606
|
-
conversationHistory = [];
|
|
607
|
-
const messagesContainer = document.getElementById('ai-messages');
|
|
608
|
-
if (messagesContainer) {
|
|
609
|
-
messagesContainer.innerHTML = '';
|
|
610
|
-
addMessage('assistant', '👋 对话已清空,有什么可以帮助你的吗?');
|
|
611
|
-
}
|
|
612
|
-
AIUIHelper.showToast('对话记录已清空', 'info');
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// 初始化AI面板
|
|
617
|
-
function initAIPanel() {
|
|
618
|
-
console.log('🤖 AI面板初始化中...');
|
|
619
|
-
|
|
620
|
-
// 添加CSS动画
|
|
621
|
-
const animations = AIUIHelper.createElement('style', '', '', '', `
|
|
622
|
-
@keyframes fadeInUp {
|
|
623
|
-
from {
|
|
624
|
-
opacity: 0;
|
|
625
|
-
transform: translateY(20px);
|
|
626
|
-
}
|
|
627
|
-
to {
|
|
628
|
-
opacity: 1;
|
|
629
|
-
transform: translateY(0);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
@keyframes pulse {
|
|
634
|
-
0%, 100% {
|
|
635
|
-
opacity: 1;
|
|
636
|
-
}
|
|
637
|
-
50% {
|
|
638
|
-
opacity: 0.6;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
.ai-panel-element {
|
|
643
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
644
|
-
}
|
|
645
|
-
`);
|
|
646
|
-
document.head.appendChild(animations);
|
|
647
|
-
|
|
648
|
-
createAIToggleButton();
|
|
649
|
-
|
|
650
|
-
// ESC键关闭面板
|
|
651
|
-
document.addEventListener('keydown', (event) => {
|
|
652
|
-
if (event.key === 'Escape' && aiPanelOpen) {
|
|
653
|
-
toggleAIPanel();
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
console.log('🤖 AI助手已就绪,点击左下角按钮开始对话');
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// DOM加载完成后初始化
|
|
661
|
-
if (document.readyState === 'loading') {
|
|
662
|
-
document.addEventListener('DOMContentLoaded', initAIPanel);
|
|
663
|
-
} else {
|
|
664
|
-
initAIPanel();
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* 生成客户端AI面板脚本
|
|
670
|
-
* @returns {string} 返回AI面板脚本的HTML字符串
|
|
671
|
-
*/
|
|
672
|
-
export function generateAIPanelScript() {
|
|
673
|
-
return `
|
|
674
|
-
<script>
|
|
675
|
-
(${createAIPanelScript.toString()})();
|
|
676
|
-
</script>
|
|
677
|
-
`;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* 为HTML内容注入AI面板脚本
|
|
682
|
-
* @param {string} html - 原始HTML内容
|
|
683
|
-
* @param {boolean} enableAI - 是否启用AI功能,默认true
|
|
684
|
-
* @param {Object} options - 配置选项
|
|
685
|
-
* @returns {string} 注入AI面板脚本后的HTML内容
|
|
686
|
-
*/
|
|
687
|
-
export function injectAIPanelScript(html, enableAI = true, options = {}) {
|
|
688
|
-
if (!enableAI) {
|
|
689
|
-
return html;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// 获取配置(可以从全局配置或选项中获取)
|
|
693
|
-
const position = options.position || 'left'; // 默认左侧
|
|
694
|
-
const autoOpen = options.autoOpen || false;
|
|
695
|
-
|
|
696
|
-
// 将配置注入到脚本中
|
|
697
|
-
const configScript = `
|
|
698
|
-
<script>
|
|
699
|
-
window.AI_PANEL_CONFIG = {
|
|
700
|
-
position: '${position}',
|
|
701
|
-
autoOpen: ${autoOpen}
|
|
702
|
-
};
|
|
703
|
-
</script>
|
|
704
|
-
`;
|
|
705
|
-
|
|
706
|
-
const aiScript = generateAIPanelScript();
|
|
707
|
-
|
|
708
|
-
const fullScript = configScript + aiScript;
|
|
709
|
-
|
|
710
|
-
// 尝试在 </body> 标签前插入AI脚本
|
|
711
|
-
if (html.includes('</body>')) {
|
|
712
|
-
return html.replace('</body>', `${fullScript}\n</body>`);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// 如果没有 </body> 标签,尝试在 </html> 标签前插入
|
|
716
|
-
if (html.includes('</html>')) {
|
|
717
|
-
return html.replace('</html>', `${fullScript}\n</html>`);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// 如果都没有,直接在末尾添加
|
|
721
|
-
return html + fullScript;
|
|
722
|
-
}
|