byt-lingxiao-ai 0.2.8 → 0.3.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.
Files changed (42) hide show
  1. package/components/AiMessage.vue +357 -8
  2. package/components/ChatAvatar.vue +9 -2
  3. package/components/ChatMessageList.vue +29 -5
  4. package/components/ChatWindow.vue +110 -5
  5. package/components/ChatWindowDialog.vue +5 -0
  6. package/components/ChatWindowHeader.vue +1 -0
  7. package/components/assets/arrow.png +0 -0
  8. package/components/assets/empty.png +0 -0
  9. package/components/assets/entering.png +0 -0
  10. package/components/assets/logo.png +0 -0
  11. package/components/assets/normal.png +0 -0
  12. package/components/assets/output.png +0 -0
  13. package/components/assets/speaking.png +0 -0
  14. package/components/assets/think.png +0 -0
  15. package/components/assets/thinking.png +0 -0
  16. package/components/assets/waiting.png +0 -0
  17. package/components/config/index.js +4 -0
  18. package/components/mixins/messageMixin.js +25 -8
  19. package/components/mixins/webSocketMixin.js +3 -1
  20. package/components/utils/StreamParser.js +67 -39
  21. package/dist/img/empty.f36cb82e.png +0 -0
  22. package/dist/img/entering.4ef198fb.png +0 -0
  23. package/dist/img/normal.30197a82.png +0 -0
  24. package/dist/img/output.1dfa94eb.png +0 -0
  25. package/dist/img/speaking.fa87fedb.png +0 -0
  26. package/dist/img/thinking.21ad5ca5.png +0 -0
  27. package/dist/img/waiting.460478ef.png +0 -0
  28. package/dist/index.common.js +29750 -2967
  29. package/dist/index.common.js.map +1 -1
  30. package/dist/index.css +11 -1
  31. package/dist/index.umd.js +29742 -2959
  32. package/dist/index.umd.js.map +1 -1
  33. package/dist/index.umd.min.js +1 -1
  34. package/dist/index.umd.min.js.map +1 -1
  35. package/package.json +5 -3
  36. package/components/assets/byt.mp3 +0 -0
  37. package/dist/img/entering.42f05909.png +0 -0
  38. package/dist/img/normal.13f08ecb.png +0 -0
  39. package/dist/img/output.85c6bd8b.png +0 -0
  40. package/dist/img/speaking.3ce8b666.png +0 -0
  41. package/dist/img/thinking.05f29a84.png +0 -0
  42. package/dist/img/waiting.ac21d76e.png +0 -0
@@ -1,32 +1,222 @@
1
1
  <template>
2
2
  <div class="chat-window-message-ai">
3
3
  <div class="ai-render">
4
- <div class="ai-thinking" @click="$emit('thinking-click')">
5
- <div class="ai-thinking-time">思考用时{{ message.time }}秒</div>
6
- <div class="ai-thinking-content" v-if="thinkStatus">{{ message.thinking }}</div>
4
+ <div class="ai-loading" v-if="isLoading">
5
+ <div class="dot"></div>
6
+ <div class="dot"></div>
7
+ <div class="dot"></div>
7
8
  </div>
8
- <div class="ai-content">{{ message.content }}</div>
9
+ <div class="ai-thinking" @click="$emit('thinking-toggle')" v-if="message.thinking">
10
+ <div class="ai-thinking-time">{{ message.time ? `思考用时${message.time}秒` : '思考中...' }}</div>
11
+ <div class="ai-thinking-content" v-if="thinkingExpanded">{{ message.thinking }}</div>
12
+ </div>
13
+ <div class="ai-content markdown-body" v-html="renderedContent"></div>
9
14
  </div>
10
15
  </div>
11
16
  </template>
12
17
 
13
18
  <script>
19
+ import { marked } from 'marked';
20
+ import hljs from 'highlight.js';
21
+ import 'highlight.js/styles/github.css';
22
+
23
+ marked.setOptions({
24
+ highlight: function(code, lang) {
25
+ if (lang && hljs.getLanguage(lang)) {
26
+ return hljs.highlight(code, { language: lang }).value;
27
+ }
28
+ return hljs.highlightAuto(code).value;
29
+ },
30
+ breaks: true,
31
+ gfm: true
32
+ });
33
+ function parseMarkdown(text) {
34
+ if (!text) return '';
35
+ let html = text;
36
+
37
+ html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
38
+ return `<pre><code class="language-${lang || 'text'}">${escapeHtml(code.trim())}</code></pre>`;
39
+ });
40
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
41
+ html = parseTable(html);
42
+
43
+ html = html.replace(/^#### (.*$)/gm, '<h4>$1</h4>');
44
+ html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
45
+ html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
46
+ html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
47
+
48
+ html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
49
+
50
+ html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
51
+
52
+ html = html.replace(/~~(.*?)~~/g, '<del>$1</del>');
53
+
54
+ html = html.replace(/^\s*[-*+]\s+(.+)$/gm, '<li>$1</li>');
55
+ html = html.replace(/(<li>.*?<\/li>)/gs, '<ul>$1</ul>');
56
+
57
+ html = html.replace(/^\s*\d+\.\s+(.+)$/gm, '<li>$1</li>');
58
+
59
+ html = html.replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>');
60
+
61
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
62
+
63
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
64
+
65
+ html = html.replace(/^-{3,}$/gm, '<hr>');
66
+
67
+ // html = html.replace(/\n/g, '<br>');
68
+
69
+ return html;
70
+ }
71
+ // 解析表格
72
+ function parseTable(text) {
73
+ const lines = text.split('\n');
74
+ let result = [];
75
+ let inTable = false;
76
+ let tableRows = [];
77
+
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const line = lines[i].trim();
80
+
81
+ // 检测表格行
82
+ if (line.includes('|') && line.split('|').length >= 3) {
83
+ if (!inTable) {
84
+ inTable = true;
85
+ tableRows = [];
86
+ }
87
+ tableRows.push(line);
88
+
89
+ // 检查下一行是否还是表格
90
+ if (i === lines.length - 1 || !lines[i + 1].includes('|')) {
91
+ // 表格结束
92
+ result.push(renderTable(tableRows));
93
+ inTable = false;
94
+ tableRows = [];
95
+ }
96
+ } else {
97
+ if (inTable) {
98
+ // 表格意外结束
99
+ result.push(renderTable(tableRows));
100
+ inTable = false;
101
+ tableRows = [];
102
+ }
103
+ result.push(line);
104
+ }
105
+ }
106
+
107
+ return result.join('\n');
108
+ }
109
+ // 渲染表格
110
+ function renderTable(rows) {
111
+ if (rows.length < 2) return rows.join('\n');
112
+
113
+ let html = '<div class="table-wrapper"><table class="markdown-table">';
114
+
115
+ // 表头
116
+ const headerCells = rows[0].split('|').filter(cell => cell.trim());
117
+ html += '<thead><tr>';
118
+ headerCells.forEach(cell => {
119
+ html += `<th>${cell.trim()}</th>`;
120
+ });
121
+ html += '</tr></thead>';
122
+
123
+ // 表体(跳过分隔行)
124
+ html += '<tbody>';
125
+ for (let i = 2; i < rows.length; i++) {
126
+ const cells = rows[i].split('|').filter(cell => cell.trim());
127
+ if (cells.length > 0) {
128
+ html += '<tr>';
129
+ cells.forEach(cell => {
130
+ html += `<td>${cell.trim()}</td>`;
131
+ });
132
+ html += '</tr>';
133
+ }
134
+ }
135
+ html += '</tbody>';
136
+
137
+ html += '</table></div>';
138
+ return html;
139
+ }
140
+ // HTML 转义
141
+ function escapeHtml(text) {
142
+ const map = {
143
+ '&': '&amp;',
144
+ '<': '&lt;',
145
+ '>': '&gt;',
146
+ '"': '&quot;',
147
+ "'": '&#039;'
148
+ };
149
+ return text.replace(/[&<>"']/g, m => map[m]);
150
+ }
151
+
14
152
  export default {
15
153
  name: 'AiMessage',
16
154
  props: {
17
155
  message: {
18
156
  type: Object,
19
157
  required: true
158
+ }
159
+ },
160
+ computed: {
161
+ thinkingExpanded() {
162
+ return this.message.thinkingExpanded !== false;
163
+ },
164
+ renderedContent() {
165
+ return parseMarkdown(this.message.content);
20
166
  },
21
- thinkStatus: {
22
- type: Boolean,
23
- default: true
167
+ isLoading() {
168
+ return this.message.loading === true;
169
+ }
170
+ },
171
+ watch: {
172
+ thinkStatus(newVal, oldVal) {
173
+ console.log('thinkStatus 变化:', newVal, oldVal);
24
174
  }
25
175
  }
26
176
  }
27
177
  </script>
28
178
 
29
179
  <style scoped>
180
+ /* Loading 容器 */
181
+ .ai-loading {
182
+ display: flex;
183
+ align-items: center;
184
+ padding: 12px 0;
185
+ height: 24px;
186
+ }
187
+
188
+ /* 单个圆点样式 */
189
+ .dot {
190
+ width: 8px;
191
+ height: 8px;
192
+ margin-right: 6px;
193
+ background-color: #86909C; /* 与你的思考字体颜色一致 */
194
+ border-radius: 50%;
195
+ animation: dot-bounce 1.4s infinite ease-in-out both;
196
+ }
197
+
198
+ .dot:nth-child(1) {
199
+ animation-delay: -0.32s;
200
+ }
201
+
202
+ .dot:nth-child(2) {
203
+ animation-delay: -0.16s;
204
+ }
205
+ .dot:nth-child(3) {
206
+ animation-delay: 0s;
207
+ }
208
+
209
+ /* 定义跳动动画 */
210
+ @keyframes dot-bounce {
211
+ 0%, 80%, 100% {
212
+ transform: scale(0);
213
+ opacity: 0.5;
214
+ }
215
+ 40% {
216
+ transform: scale(1);
217
+ opacity: 1;
218
+ }
219
+ }
30
220
  .chat-window-message-ai {
31
221
  display: flex;
32
222
  gap: 12px;
@@ -99,11 +289,170 @@ export default {
99
289
 
100
290
  .ai-content {
101
291
  color: #4E5969;
102
- text-align: justify;
103
292
  font-family: "Alibaba PuHuiTi 2.0";
104
293
  font-size: 16px;
105
294
  font-style: normal;
106
295
  font-weight: 400;
107
296
  line-height: 24px;
108
297
  }
298
+
299
+ /* Markdown 样式 */
300
+ .markdown-body {
301
+ word-wrap: break-word;
302
+ }
303
+
304
+ .markdown-body ::v-deep h1,
305
+ .markdown-body ::v-deep h2,
306
+ .markdown-body ::v-deep h3,
307
+ .markdown-body ::v-deep h4 {
308
+ margin: 16px 0 8px 0;
309
+ font-weight: 600;
310
+ line-height: 1.4;
311
+ color: #1f2937;
312
+ }
313
+
314
+ .markdown-body ::v-deep h1 {
315
+ font-size: 24px;
316
+ border-bottom: 2px solid #e5e7eb;
317
+ padding-bottom: 8px;
318
+ }
319
+
320
+ .markdown-body ::v-deep h2 {
321
+ font-size: 20px;
322
+ border-bottom: 1px solid #e5e7eb;
323
+ padding-bottom: 6px;
324
+ }
325
+
326
+ .markdown-body ::v-deep h3 {
327
+ font-size: 18px;
328
+ }
329
+
330
+ .markdown-body ::v-deep h4 {
331
+ font-size: 16px;
332
+ }
333
+
334
+ .markdown-body ::v-deep code {
335
+ background-color: rgba(175, 184, 193, 0.2);
336
+ border-radius: 3px;
337
+ font-size: 85%;
338
+ margin: 0;
339
+ padding: 0.2em 0.4em;
340
+ font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;
341
+ color: #e83e8c;
342
+ }
343
+
344
+ .markdown-body ::v-deep pre {
345
+ background-color: #f6f8fa;
346
+ border-radius: 6px;
347
+ padding: 16px;
348
+ overflow: auto;
349
+ margin: 12px 0;
350
+ border: 1px solid #e1e4e8;
351
+ }
352
+
353
+ .markdown-body ::v-deep pre code {
354
+ background-color: transparent;
355
+ border: 0;
356
+ display: inline;
357
+ line-height: inherit;
358
+ margin: 0;
359
+ overflow: visible;
360
+ padding: 0;
361
+ word-wrap: normal;
362
+ color: #24292e;
363
+ font-size: 14px;
364
+ }
365
+ .markdown-body ::v-deep .table-wrapper{
366
+ overflow-x: auto;
367
+ border: 1px solid #dfe2e5;
368
+ }
369
+ .markdown-body ::v-deep .markdown-table {
370
+ border-collapse: collapse;
371
+ width: 100%;
372
+ margin: 12px 0;
373
+ font-size: 14px;
374
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
375
+ overflow: hidden;
376
+ }
377
+
378
+ .markdown-body ::v-deep .markdown-table th,
379
+ .markdown-body ::v-deep .markdown-table td {
380
+ border: 1px solid #dfe2e5;
381
+ padding: 10px 14px;
382
+ text-align: left;
383
+ }
384
+
385
+ .markdown-body ::v-deep .markdown-table th {
386
+ background-color: #f3f4f6;
387
+ font-weight: 600;
388
+ color: #374151;
389
+ }
390
+
391
+ .markdown-body ::v-deep .markdown-table tr:nth-child(even) {
392
+ background-color: #f9fafb;
393
+ }
394
+
395
+ .markdown-body ::v-deep .markdown-table tr:hover {
396
+ background-color: #f3f4f6;
397
+ transition: background-color 0.2s;
398
+ }
399
+
400
+ .markdown-body ::v-deep ul,
401
+ .markdown-body ::v-deep ol {
402
+ padding-left: 24px;
403
+ margin: 8px 0;
404
+ }
405
+
406
+ .markdown-body ::v-deep li {
407
+ margin: 4px 0;
408
+ line-height: 1.6;
409
+ }
410
+
411
+ .markdown-body ::v-deep strong {
412
+ font-weight: 600;
413
+ color: #1f2937;
414
+ }
415
+
416
+ .markdown-body ::v-deep em {
417
+ font-style: italic;
418
+ }
419
+
420
+ .markdown-body ::v-deep del {
421
+ text-decoration: line-through;
422
+ opacity: 0.7;
423
+ }
424
+
425
+ .markdown-body ::v-deep blockquote {
426
+ border-left: 4px solid #dfe2e5;
427
+ padding-left: 16px;
428
+ margin: 12px 0;
429
+ color: #6b7280;
430
+ font-style: italic;
431
+ }
432
+
433
+ .markdown-body ::v-deep a {
434
+ color: #3b82f6;
435
+ text-decoration: none;
436
+ }
437
+
438
+ .markdown-body ::v-deep a:hover {
439
+ text-decoration: underline;
440
+ color: #2563eb;
441
+ }
442
+
443
+ .markdown-body ::v-deep img {
444
+ max-width: 100%;
445
+ border-radius: 6px;
446
+ margin: 12px 0;
447
+ }
448
+
449
+ .markdown-body ::v-deep hr {
450
+ border: none;
451
+ border-top: 1px solid #e5e7eb;
452
+ margin: 16px 0;
453
+ }
454
+
455
+ .markdown-body ::v-deep br {
456
+ line-height: 0.8em;
457
+ }
109
458
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="chat-ai" @click="$emit('click')">
2
+ <div class="chat-ai" @mousedown="$emit('mousedown')">
3
3
  <div :class="['chat-ai-avater', status]"></div>
4
4
  <div class="chat-ai-text">{{ statusText }}</div>
5
5
  </div>
@@ -20,7 +20,7 @@ export default {
20
20
  const textMap = {
21
21
  'normal': '凌霄AI',
22
22
  'thinking': '思考中',
23
- 'output': '语音中'
23
+ 'output': '输出中'
24
24
  }
25
25
  return textMap[this.status]
26
26
  }
@@ -41,6 +41,13 @@ export default {
41
41
  box-shadow: 0 2px 11.6px 0 rgba(0, 0, 0, 0.1);
42
42
  cursor: pointer;
43
43
  user-select: none;
44
+ transition: box-shadow 0.2s;
45
+ }
46
+ .chat-ai:hover {
47
+ box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
48
+ }
49
+ .chat-ai:active {
50
+ cursor: grabbing;
44
51
  }
45
52
 
46
53
  .chat-ai-avater {
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <div ref="chatArea" class="chat-window-content scrollbar-hide">
3
+ <div v-if="messages.length === 0" class="chat-window-empty"></div>
3
4
  <div
4
5
  class="chat-window-message"
5
6
  v-for="message in messages"
@@ -13,7 +14,7 @@
13
14
  v-else
14
15
  :message="message"
15
16
  :think-status="thinkStatus"
16
- @thinking-click="$emit('thinking-click')"
17
+ @thinking-toggle="handleThinkingToggle(message)"
17
18
  />
18
19
  </div>
19
20
  </div>
@@ -39,7 +40,20 @@ export default {
39
40
  default: true
40
41
  }
41
42
  },
43
+ computed: {
44
+ lastMessageObject() {
45
+ const len = this.messages.length;
46
+ if (len === 0) {
47
+ return null;
48
+ }
49
+ return this.messages[len - 1];
50
+ }
51
+ },
42
52
  methods: {
53
+ handleThinkingToggle(message) {
54
+ console.log('handleThinkingToggle', message)
55
+ this.$set(message, 'thinkingExpanded', !message.thinkingExpanded);
56
+ },
43
57
  scrollToBottom() {
44
58
  this.$nextTick(() => {
45
59
  const chatArea = this.$refs.chatArea
@@ -50,17 +64,27 @@ export default {
50
64
  }
51
65
  },
52
66
  watch: {
53
- messages: {
54
- handler() {
55
- this.scrollToBottom()
67
+ lastMessageObject: {
68
+ handler(newMsg) {
69
+ if (newMsg) {
70
+ this.scrollToBottom();
71
+ }
56
72
  },
57
- deep: true
73
+ deep: true,
74
+ immediate: true
58
75
  }
59
76
  }
60
77
  }
61
78
  </script>
62
79
 
63
80
  <style scoped>
81
+ .chat-window-empty{
82
+ width: 300px;
83
+ height: 300px;
84
+ background: url('./assets/empty.png') no-repeat;
85
+ background-size: cover;
86
+ margin: auto;
87
+ }
64
88
  .chat-window-content {
65
89
  flex: 1;
66
90
  padding: 16px;
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div class="chat">
2
+ <div
3
+ class="chat"
4
+ :style="chatStyle"
5
+ >
3
6
  <!-- 隐藏的音频播放器 -->
4
7
  <audio
5
8
  ref="audioPlayer"
@@ -21,7 +24,7 @@
21
24
  <ChatAvatar
22
25
  v-else
23
26
  :status="avaterStatus"
24
- @click="toggleWindow"
27
+ @mousedown="startDrag"
25
28
  />
26
29
 
27
30
  <!-- 聊天窗口 -->
@@ -45,6 +48,7 @@ import ChatWindowDialog from './ChatWindowDialog.vue'
45
48
  import audioMixin from './mixins/audioMixin'
46
49
  import webSocketMixin from './mixins/webSocketMixin'
47
50
  import messageMixin from './mixins/messageMixin'
51
+ import { AUDIO_URL, TIME_JUMP_POINTS_URL } from './config/index.js'
48
52
 
49
53
  const SAMPLE_RATE = 16000;
50
54
  const FRAME_SIZE = 512;
@@ -65,7 +69,7 @@ export default {
65
69
  },
66
70
  data() {
67
71
  return {
68
- audioSrc: '/minio/lingxiaoai/byt.mp3',
72
+ audioSrc: AUDIO_URL,
69
73
  inputMessage: '',
70
74
  visible: false,
71
75
  messages: [],
@@ -75,7 +79,31 @@ export default {
75
79
  thinkStatus: true,
76
80
  jumpedTimePoints: new Set(),
77
81
  SAMPLE_RATE,
78
- FRAME_SIZE
82
+ FRAME_SIZE,
83
+ dragThreshold: 5, // 拖拽阈值
84
+ isDragging: false,
85
+ dragStartX: 0,
86
+ dragStartY: 0,
87
+ currentX: 10,
88
+ currentY: 20,
89
+ initialX: 10,
90
+ initialY: 20,
91
+ hasMoved: false,
92
+ }
93
+ },
94
+ computed: {
95
+ chatStyle() {
96
+ if (this.robotStatus === 'leaving' && !this.visible) {
97
+ return {
98
+ right: `${this.currentX}px`,
99
+ bottom: `${this.currentY}px`,
100
+ cursor: this.isDragging ? 'grabbing' : 'grab',
101
+ transition: this.isDragging ? 'none' : 'right 0.3s ease, bottom 0.3s ease',
102
+ };
103
+ }
104
+ return {
105
+ cursor: 'pointer',
106
+ };
79
107
  }
80
108
  },
81
109
  mounted() {
@@ -83,6 +111,13 @@ export default {
83
111
  if (this.appendToBody) {
84
112
  this.appendToBodyHandler()
85
113
  }
114
+
115
+ this.$nextTick(() => {
116
+ const chatEl = this.$el;
117
+ const style = window.getComputedStyle(chatEl);
118
+ this.initialX = this.currentX = parseInt(style.right, 10) || 10;
119
+ this.initialY = this.currentY = parseInt(style.bottom, 10) || 20;
120
+ })
86
121
  },
87
122
  beforeDestroy() {
88
123
  if (this.appendToBody && this.$el.parentElement === document.body) {
@@ -90,10 +125,80 @@ export default {
90
125
  }
91
126
  this.closeWebSocket()
92
127
  this.stopRecording()
128
+
129
+ // 移除全局事件监听器
130
+ document.removeEventListener('mousemove', this.onDrag)
131
+ document.removeEventListener('mouseup', this.stopDrag)
93
132
  },
94
133
  methods: {
95
134
  toggleWindow() {
96
135
  this.visible = !this.visible
136
+
137
+ if (this.visible) {
138
+ this.currentX = this.initialX
139
+ this.currentY = this.initialY
140
+ }
141
+ },
142
+ startDrag() {
143
+ console.log('startDrag')
144
+ if (this.robotStatus !== 'leaving' && this.visible) return;
145
+
146
+ this.isDragging = true;
147
+ this.hasMoved = false;
148
+
149
+ // 记录鼠标的初始位置
150
+ this.dragStartX = event.clientX;
151
+ this.dragStartY = event.clientY;
152
+
153
+ // 绑定全局事件监听器
154
+ document.addEventListener('mousemove', this.onDrag);
155
+ document.addEventListener('mouseup', this.stopDrag);
156
+ },
157
+ onDrag(event) {
158
+ if (!this.isDragging) return;
159
+
160
+ // 鼠标位移量
161
+ const deltaX = event.clientX - this.dragStartX;
162
+ const deltaY = event.clientY - this.dragStartY;
163
+
164
+ if (Math.abs(deltaX) > this.dragThreshold || Math.abs(deltaY) > this.dragThreshold) {
165
+ console.log('移动超过阈值')
166
+ this.hasMoved = true; // 只要移动超过阈值,就标记为拖拽
167
+ }
168
+
169
+ // 获取 .chat 容器的尺寸
170
+ const chatEl = this.$el;
171
+ const chatWidth = chatEl.offsetWidth;
172
+ const chatHeight = chatEl.offsetHeight;
173
+
174
+ let newX = this.currentX - deltaX;
175
+ let newY = this.currentY - deltaY;
176
+
177
+ // 视口宽度和高度
178
+ const viewportWidth = window.innerWidth;
179
+ const viewportHeight = window.innerHeight;
180
+
181
+ newX = Math.max(0, newX);
182
+ newX = Math.min(viewportWidth - chatWidth, newX);
183
+
184
+ newY = Math.max(0, newY);
185
+ newY = Math.min(viewportHeight - chatHeight, newY);
186
+
187
+ // 更新位置
188
+ this.currentX = newX;
189
+ this.currentY = newY;
190
+
191
+ // 重新设置新的拖拽起始点,实现平滑拖拽
192
+ this.dragStartX = event.clientX;
193
+ this.dragStartY = event.clientY;
194
+ },
195
+ stopDrag() {
196
+ this.isDragging = false;
197
+ document.removeEventListener('mousemove', this.onDrag);
198
+ document.removeEventListener('mouseup', this.stopDrag);
199
+ if (!this.hasMoved) {
200
+ this.toggleWindow()
201
+ }
97
202
  },
98
203
  handleThinkingClick() {
99
204
  this.thinkStatus = !this.thinkStatus
@@ -117,7 +222,7 @@ export default {
117
222
  this.jumpedTimePoints = new Set()
118
223
  }
119
224
 
120
- fetch('/minio/lingxiaoai/timeJumpPoints.json')
225
+ fetch(TIME_JUMP_POINTS_URL)
121
226
  .then(response => response.json())
122
227
  .then(data => {
123
228
  console.log('时间跳转点:', data)
@@ -9,6 +9,7 @@
9
9
  ref="messageList"
10
10
  :messages="messages"
11
11
  :think-status="thinkStatus"
12
+ :loading="loading"
12
13
  @thinking-click="$emit('thinking-click')"
13
14
  />
14
15
 
@@ -50,6 +51,10 @@ export default {
50
51
  thinkStatus: {
51
52
  type: Boolean,
52
53
  default: true
54
+ },
55
+ loading: {
56
+ type: Boolean,
57
+ default: false
53
58
  }
54
59
  }
55
60
  }
@@ -56,6 +56,7 @@ export default {
56
56
  background: #fff;
57
57
  padding: 16px;
58
58
  flex-shrink: 0;
59
+ user-select: none;
59
60
  }
60
61
 
61
62
  .chat-window-header-title {
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file