byt-lingxiao-ai 0.3.0 → 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 (38) hide show
  1. package/components/AiMessage.vue +12 -18
  2. package/components/ChatAvatar.vue +8 -1
  3. package/components/ChatMessageList.vue +29 -10
  4. package/components/ChatWindow.vue +107 -4
  5. package/components/ChatWindowHeader.vue +1 -0
  6. package/components/assets/arrow.png +0 -0
  7. package/components/assets/empty.png +0 -0
  8. package/components/assets/entering.png +0 -0
  9. package/components/assets/logo.png +0 -0
  10. package/components/assets/normal.png +0 -0
  11. package/components/assets/output.png +0 -0
  12. package/components/assets/speaking.png +0 -0
  13. package/components/assets/think.png +0 -0
  14. package/components/assets/thinking.png +0 -0
  15. package/components/assets/waiting.png +0 -0
  16. package/components/mixins/messageMixin.js +11 -5
  17. package/dist/img/empty.f36cb82e.png +0 -0
  18. package/dist/img/entering.4ef198fb.png +0 -0
  19. package/dist/img/normal.30197a82.png +0 -0
  20. package/dist/img/output.1dfa94eb.png +0 -0
  21. package/dist/img/speaking.fa87fedb.png +0 -0
  22. package/dist/img/thinking.21ad5ca5.png +0 -0
  23. package/dist/img/waiting.460478ef.png +0 -0
  24. package/dist/index.common.js +189 -79
  25. package/dist/index.common.js.map +1 -1
  26. package/dist/index.css +2 -2
  27. package/dist/index.umd.js +189 -79
  28. package/dist/index.umd.js.map +1 -1
  29. package/dist/index.umd.min.js +1 -1
  30. package/dist/index.umd.min.js.map +1 -1
  31. package/package.json +1 -1
  32. package/components/assets/byt.mp3 +0 -0
  33. package/dist/img/entering.42f05909.png +0 -0
  34. package/dist/img/normal.13f08ecb.png +0 -0
  35. package/dist/img/output.85c6bd8b.png +0 -0
  36. package/dist/img/speaking.3ce8b666.png +0 -0
  37. package/dist/img/thinking.05f29a84.png +0 -0
  38. package/dist/img/waiting.ac21d76e.png +0 -0
@@ -1,14 +1,14 @@
1
1
  <template>
2
2
  <div class="chat-window-message-ai">
3
3
  <div class="ai-render">
4
- <div class="ai-loading" v-if="loading">
4
+ <div class="ai-loading" v-if="isLoading">
5
5
  <div class="dot"></div>
6
6
  <div class="dot"></div>
7
7
  <div class="dot"></div>
8
8
  </div>
9
- <div class="ai-thinking" @click="$emit('thinking-click')" v-if="message.thinking">
10
- <div class="ai-thinking-time">思考用时{{ message.time }}秒</div>
11
- <div class="ai-thinking-content" v-if="thinkStatus">{{ message.thinking }}</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
12
  </div>
13
13
  <div class="ai-content markdown-body" v-html="renderedContent"></div>
14
14
  </div>
@@ -30,7 +30,6 @@ marked.setOptions({
30
30
  breaks: true,
31
31
  gfm: true
32
32
  });
33
-
34
33
  function parseMarkdown(text) {
35
34
  if (!text) return '';
36
35
  let html = text;
@@ -65,11 +64,10 @@ function parseMarkdown(text) {
65
64
 
66
65
  html = html.replace(/^-{3,}$/gm, '<hr>');
67
66
 
68
- html = html.replace(/\n/g, '<br>');
67
+ // html = html.replace(/\n/g, '<br>');
69
68
 
70
69
  return html;
71
70
  }
72
-
73
71
  // 解析表格
74
72
  function parseTable(text) {
75
73
  const lines = text.split('\n');
@@ -108,7 +106,6 @@ function parseTable(text) {
108
106
 
109
107
  return result.join('\n');
110
108
  }
111
-
112
109
  // 渲染表格
113
110
  function renderTable(rows) {
114
111
  if (rows.length < 2) return rows.join('\n');
@@ -140,7 +137,6 @@ function renderTable(rows) {
140
137
  html += '</table></div>';
141
138
  return html;
142
139
  }
143
-
144
140
  // HTML 转义
145
141
  function escapeHtml(text) {
146
142
  const map = {
@@ -159,19 +155,17 @@ export default {
159
155
  message: {
160
156
  type: Object,
161
157
  required: true
162
- },
163
- thinkStatus: {
164
- type: Boolean,
165
- default: true
166
- },
167
- loading: {
168
- type: Boolean,
169
- default: false
170
158
  }
171
159
  },
172
160
  computed: {
161
+ thinkingExpanded() {
162
+ return this.message.thinkingExpanded !== false;
163
+ },
173
164
  renderedContent() {
174
165
  return parseMarkdown(this.message.content);
166
+ },
167
+ isLoading() {
168
+ return this.message.loading === true;
175
169
  }
176
170
  },
177
171
  watch: {
@@ -459,6 +453,6 @@ export default {
459
453
  }
460
454
 
461
455
  .markdown-body ::v-deep br {
462
- line-height: 1.6;
456
+ line-height: 0.8em;
463
457
  }
464
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>
@@ -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,8 +14,7 @@
13
14
  v-else
14
15
  :message="message"
15
16
  :think-status="thinkStatus"
16
- :loading="isLoading"
17
- @thinking-click="$emit('thinking-click')"
17
+ @thinking-toggle="handleThinkingToggle(message)"
18
18
  />
19
19
  </div>
20
20
  </div>
@@ -38,13 +38,22 @@ export default {
38
38
  thinkStatus: {
39
39
  type: Boolean,
40
40
  default: true
41
- },
42
- isLoading: {
43
- type: Boolean,
44
- default: false
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];
45
50
  }
46
51
  },
47
52
  methods: {
53
+ handleThinkingToggle(message) {
54
+ console.log('handleThinkingToggle', message)
55
+ this.$set(message, 'thinkingExpanded', !message.thinkingExpanded);
56
+ },
48
57
  scrollToBottom() {
49
58
  this.$nextTick(() => {
50
59
  const chatArea = this.$refs.chatArea
@@ -55,17 +64,27 @@ export default {
55
64
  }
56
65
  },
57
66
  watch: {
58
- messages: {
59
- handler() {
60
- this.scrollToBottom()
67
+ lastMessageObject: {
68
+ handler(newMsg) {
69
+ if (newMsg) {
70
+ this.scrollToBottom();
71
+ }
61
72
  },
62
- deep: true
73
+ deep: true,
74
+ immediate: true
63
75
  }
64
76
  }
65
77
  }
66
78
  </script>
67
79
 
68
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
+ }
69
88
  .chat-window-content {
70
89
  flex: 1;
71
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
  <!-- 聊天窗口 -->
@@ -30,7 +33,6 @@
30
33
  :messages="messages"
31
34
  :input-message="inputMessage"
32
35
  :think-status="thinkStatus"
33
- :loading="isLoading"
34
36
  @update:inputMessage="inputMessage = $event"
35
37
  @send="handleSend"
36
38
  @thinking-click="handleThinkingClick"
@@ -77,7 +79,31 @@ export default {
77
79
  thinkStatus: true,
78
80
  jumpedTimePoints: new Set(),
79
81
  SAMPLE_RATE,
80
- 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
+ };
81
107
  }
82
108
  },
83
109
  mounted() {
@@ -85,6 +111,13 @@ export default {
85
111
  if (this.appendToBody) {
86
112
  this.appendToBodyHandler()
87
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
+ })
88
121
  },
89
122
  beforeDestroy() {
90
123
  if (this.appendToBody && this.$el.parentElement === document.body) {
@@ -92,10 +125,80 @@ export default {
92
125
  }
93
126
  this.closeWebSocket()
94
127
  this.stopRecording()
128
+
129
+ // 移除全局事件监听器
130
+ document.removeEventListener('mousemove', this.onDrag)
131
+ document.removeEventListener('mouseup', this.stopDrag)
95
132
  },
96
133
  methods: {
97
134
  toggleWindow() {
98
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
+ }
99
202
  },
100
203
  handleThinkingClick() {
101
204
  this.thinkStatus = !this.thinkStatus
@@ -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
Binary file
Binary file
Binary file
@@ -4,8 +4,7 @@ import { API_URL } from '../config/index.js'
4
4
  export default {
5
5
  data() {
6
6
  return {
7
- streamParser: null,
8
- isLoading: false
7
+ streamParser: null
9
8
  }
10
9
  },
11
10
 
@@ -15,7 +14,6 @@ export default {
15
14
  updateInterval: 16, // 约60fps
16
15
  debug: process.env.NODE_ENV === 'development'
17
16
  });
18
- this.isLoading = true;
19
17
  },
20
18
 
21
19
  methods: {
@@ -28,6 +26,8 @@ export default {
28
26
  thinking: '',
29
27
  charts: [],
30
28
  content: '',
29
+ loading: true,
30
+ thinkingExpanded: true
31
31
  };
32
32
  this.messages.push(message);
33
33
  this.currentMessage = message;
@@ -95,7 +95,6 @@ export default {
95
95
 
96
96
  console.log(`流处理完成,总耗时: ${duration}ms`);
97
97
  this.avaterStatus = 'normal';
98
- this.isLoading = false;
99
98
 
100
99
  } catch (error) {
101
100
  console.error('发送消息失败:', error);
@@ -103,7 +102,11 @@ export default {
103
102
  this.currentMessage.content = '抱歉,发生了错误,请重试。';
104
103
  this.$forceUpdate();
105
104
  }
106
- this.isLoading = false;
105
+ } finally {
106
+ // 确保加载状态关闭
107
+ if (this.currentMessage) {
108
+ this.currentMessage.loading = false;
109
+ }
107
110
  }
108
111
  },
109
112
 
@@ -141,6 +144,9 @@ export default {
141
144
 
142
145
  console.log('收到更新:', result);
143
146
 
147
+ if (this.currentMessage.loading) {
148
+ this.currentMessage.loading = false;
149
+ }
144
150
  // 更新思考内容
145
151
  if (result.thinking) {
146
152
  this.currentMessage.thinking += result.thinking;
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file