byt-lingxiao-ai 0.3.25 → 0.3.26
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/components/AiMessage.vue +35 -8
- package/components/ChatMessageList.vue +10 -5
- package/components/mixins/messageMixin.js +16 -15
- package/components/utils/StreamParser.js +63 -34
- package/dist/index.common.js +744 -298
- package/dist/index.common.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.umd.js +744 -298
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +1 -1
package/components/AiMessage.vue
CHANGED
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
<div
|
|
15
15
|
v-if="item.type === 'thinking'"
|
|
16
16
|
class="ai-thinking"
|
|
17
|
-
@click="$emit('thinking-toggle')"
|
|
18
17
|
>
|
|
19
|
-
<div class="ai-thinking-time">
|
|
18
|
+
<div class="ai-thinking-time" :class="{'thinking-expanded': !item.thinkingExpanded}" @click="handleThinkingToggle(item)">
|
|
20
19
|
{{
|
|
21
20
|
item.duration
|
|
22
21
|
? `思考用时 ${item.duration} 秒`
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
</div>
|
|
26
25
|
<div
|
|
27
26
|
class="ai-thinking-content"
|
|
28
|
-
v-if="thinkingExpanded"
|
|
27
|
+
v-if="item.thinkingExpanded"
|
|
29
28
|
>
|
|
30
29
|
{{ item.content }}
|
|
31
30
|
</div>
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
v-else-if="item.type === 'tool_call'"
|
|
35
34
|
class="ai-tool-call"
|
|
36
35
|
>
|
|
37
|
-
<div class="tool-title">🔧 {{ item.
|
|
36
|
+
<div class="tool-title">🔧 {{ item.name }}</div>
|
|
38
37
|
</div>
|
|
39
38
|
<div
|
|
40
39
|
v-else-if="item.type === 'tool_result'"
|
|
@@ -57,16 +56,35 @@ import { marked } from 'marked'
|
|
|
57
56
|
import hljs from 'highlight.js'
|
|
58
57
|
import 'highlight.js/styles/github.css'
|
|
59
58
|
|
|
59
|
+
// 创建自定义渲染器
|
|
60
|
+
const renderer = new marked.Renderer()
|
|
61
|
+
|
|
62
|
+
// 自定义链接渲染
|
|
63
|
+
renderer.link = function ({ href, text }) {
|
|
64
|
+
let dataPath = ''
|
|
65
|
+
const url = new URL(href, window.location.origin)
|
|
66
|
+
const pathname = url.pathname
|
|
67
|
+
const search = url.search
|
|
68
|
+
|
|
69
|
+
if (pathname.startsWith('/portal/')) {
|
|
70
|
+
dataPath = pathname.substring('/portal'.length) + search
|
|
71
|
+
return `<a href="javascript:void(0)" data-path="${dataPath}" data-text="${text}">${text}</a>`
|
|
72
|
+
} else {
|
|
73
|
+
dataPath = href
|
|
74
|
+
return `<a href="${dataPath}" target="_blank">${text}</a>`
|
|
75
|
+
}
|
|
76
|
+
}
|
|
60
77
|
marked.setOptions({
|
|
61
|
-
|
|
78
|
+
renderer: renderer,
|
|
79
|
+
highlight: function(code, lang) {
|
|
62
80
|
if (lang && hljs.getLanguage(lang)) {
|
|
63
|
-
return hljs.highlight(code, { language: lang }).value
|
|
81
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
64
82
|
}
|
|
65
|
-
return hljs.highlightAuto(code).value
|
|
83
|
+
return hljs.highlightAuto(code).value;
|
|
66
84
|
},
|
|
67
85
|
breaks: true,
|
|
68
86
|
gfm: true
|
|
69
|
-
})
|
|
87
|
+
});
|
|
70
88
|
|
|
71
89
|
export default {
|
|
72
90
|
props: {
|
|
@@ -81,6 +99,9 @@ export default {
|
|
|
81
99
|
methods: {
|
|
82
100
|
renderMarkdown(text) {
|
|
83
101
|
return marked.parse(text || '')
|
|
102
|
+
},
|
|
103
|
+
handleThinkingToggle(item) {
|
|
104
|
+
this.$set(item, 'thinkingExpanded', !item.thinkingExpanded)
|
|
84
105
|
}
|
|
85
106
|
}
|
|
86
107
|
}
|
|
@@ -187,6 +208,11 @@ export default {
|
|
|
187
208
|
height: 16px;
|
|
188
209
|
background: url('./assets/arrow.png') no-repeat;
|
|
189
210
|
background-size: cover;
|
|
211
|
+
/* 添加过度动画 */
|
|
212
|
+
transition: all 0.2s ease-in-out;
|
|
213
|
+
}
|
|
214
|
+
.thinking-expanded::after {
|
|
215
|
+
transform: translateY(-50%) rotate(180deg);
|
|
190
216
|
}
|
|
191
217
|
|
|
192
218
|
.ai-thinking-content {
|
|
@@ -206,6 +232,7 @@ export default {
|
|
|
206
232
|
font-style: normal;
|
|
207
233
|
font-weight: 400;
|
|
208
234
|
line-height: 24px;
|
|
235
|
+
margin: 10px 0;
|
|
209
236
|
}
|
|
210
237
|
|
|
211
238
|
/* Markdown 样式 */
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
v-else
|
|
15
15
|
:message="message"
|
|
16
16
|
:think-status="thinkStatus"
|
|
17
|
-
@thinking-toggle="handleThinkingToggle(message)"
|
|
18
17
|
/>
|
|
19
18
|
</div>
|
|
20
19
|
</div>
|
|
@@ -33,12 +32,18 @@ export default {
|
|
|
33
32
|
computed: {
|
|
34
33
|
lastMessageObject() {
|
|
35
34
|
return this.messages[this.messages.length - 1] || null
|
|
35
|
+
},
|
|
36
|
+
timelineContents() {
|
|
37
|
+
if (!this.lastMessageObject || !this.lastMessageObject.timeline) {
|
|
38
|
+
return ''
|
|
39
|
+
}
|
|
40
|
+
// 将所有 content 拼接成一个字符串用于监听变化
|
|
41
|
+
return this.lastMessageObject.timeline
|
|
42
|
+
.map(item => item.content || '')
|
|
43
|
+
.join('')
|
|
36
44
|
}
|
|
37
45
|
},
|
|
38
46
|
methods: {
|
|
39
|
-
handleThinkingToggle(message) {
|
|
40
|
-
this.$set(message, 'thinkingExpanded', !message.thinkingExpanded)
|
|
41
|
-
},
|
|
42
47
|
scrollToBottom() {
|
|
43
48
|
this.$nextTick(() => {
|
|
44
49
|
const el = this.$refs.chatArea
|
|
@@ -47,7 +52,7 @@ export default {
|
|
|
47
52
|
}
|
|
48
53
|
},
|
|
49
54
|
watch: {
|
|
50
|
-
|
|
55
|
+
timelineContents: {
|
|
51
56
|
handler() {
|
|
52
57
|
this.scrollToBottom()
|
|
53
58
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StreamParser } from '../utils/StreamParser'
|
|
2
2
|
import { API_URL } from '../config/index.js'
|
|
3
3
|
import { getCookie } from '../utils/Cookie.js'
|
|
4
|
+
import generateUuid from 'components/utils/Uuid'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
|
|
@@ -8,13 +9,7 @@ export default {
|
|
|
8
9
|
data() {
|
|
9
10
|
return {
|
|
10
11
|
streamParser: null,
|
|
11
|
-
|
|
12
|
-
id: Date.now(),
|
|
13
|
-
type: 'ai',
|
|
14
|
-
timeline: [],
|
|
15
|
-
loading: true,
|
|
16
|
-
thinkingExpanded: true
|
|
17
|
-
}
|
|
12
|
+
currentMessage: null
|
|
18
13
|
}
|
|
19
14
|
},
|
|
20
15
|
|
|
@@ -28,14 +23,22 @@ export default {
|
|
|
28
23
|
|
|
29
24
|
methods: {
|
|
30
25
|
createAiMessage() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
const aiMessage = {
|
|
27
|
+
id: generateUuid(),
|
|
28
|
+
type: 'ai',
|
|
29
|
+
sender: 'AI',
|
|
30
|
+
timeline: [],
|
|
31
|
+
loading: true,
|
|
32
|
+
thinkingExpanded: true
|
|
33
|
+
}
|
|
34
|
+
this.messages.push(aiMessage);
|
|
35
|
+
this.currentMessage = aiMessage;
|
|
36
|
+
return aiMessage;
|
|
34
37
|
},
|
|
35
38
|
|
|
36
39
|
createUserMessage(content) {
|
|
37
40
|
const message = {
|
|
38
|
-
id:
|
|
41
|
+
id: generateUuid(),
|
|
39
42
|
type: 'user',
|
|
40
43
|
sender: '用户',
|
|
41
44
|
time: '',
|
|
@@ -83,7 +86,6 @@ export default {
|
|
|
83
86
|
|
|
84
87
|
// 完成解析
|
|
85
88
|
this.streamParser.finish(() => {});
|
|
86
|
-
this.aiMessage.loading = false;
|
|
87
89
|
|
|
88
90
|
// 记录耗时
|
|
89
91
|
const duration = Date.now() - startTime;
|
|
@@ -98,7 +100,6 @@ export default {
|
|
|
98
100
|
console.error('发送消息失败:', error);
|
|
99
101
|
if (this.currentMessage) {
|
|
100
102
|
this.currentMessage.content = '抱歉,发生了错误,请重试。';
|
|
101
|
-
this.$forceUpdate();
|
|
102
103
|
}
|
|
103
104
|
} finally {
|
|
104
105
|
// 确保加载状态关闭
|
|
@@ -125,9 +126,9 @@ export default {
|
|
|
125
126
|
console.log('收到数据块:', chunk);
|
|
126
127
|
// 使用解析器处理数据块,确保this指向正确
|
|
127
128
|
this.streamParser.processChunk(chunk, (e) => {
|
|
128
|
-
|
|
129
|
+
if (!this.currentMessage) return;
|
|
129
130
|
if (e.type === 'create') {
|
|
130
|
-
this.
|
|
131
|
+
this.currentMessage.timeline.push(e.event)
|
|
131
132
|
}
|
|
132
133
|
});
|
|
133
134
|
}
|
|
@@ -60,37 +60,69 @@ export class StreamParser {
|
|
|
60
60
|
this.scheduleFlush(emit)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
const
|
|
63
|
+
parseTag(tag) {
|
|
64
|
+
const isClosing = tag.startsWith('</')
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
66
|
+
const content = tag
|
|
67
|
+
.replace(/^<\/?|>$/g, '')
|
|
68
|
+
.trim()
|
|
69
|
+
|
|
70
|
+
const [tagName, ...attrParts] = content.split(/\s+/)
|
|
71
|
+
|
|
72
|
+
const attrs = {}
|
|
73
|
+
|
|
74
|
+
attrParts.forEach(part => {
|
|
75
|
+
const [key, rawValue] = part.split('=')
|
|
76
|
+
if (!key || !rawValue) return
|
|
77
|
+
|
|
78
|
+
attrs[key] = rawValue.replace(/^['"]|['"]$/g, '')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
tag: tagName.replace('/', ''),
|
|
83
|
+
attrs,
|
|
84
|
+
isClosing
|
|
77
85
|
}
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
handleTag(tag, emit) {
|
|
90
|
+
const { tag: tagName, attrs, isClosing } = this.parseTag(tag.toLowerCase())
|
|
91
|
+
|
|
92
|
+
if (tagName === 'think') {
|
|
93
|
+
if (!isClosing) {
|
|
94
|
+
this.startThinking(attrs, emit)
|
|
95
|
+
} else {
|
|
96
|
+
this.closeThinking()
|
|
97
|
+
this.currentType = 'content'
|
|
98
|
+
this.currentEvent = null
|
|
99
|
+
}
|
|
80
100
|
}
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
|
|
102
|
+
else if (tagName === 'tool_call') {
|
|
103
|
+
if (!isClosing) {
|
|
104
|
+
this.switchType('tool_call', emit, attrs)
|
|
105
|
+
} else {
|
|
106
|
+
this.switchType('content', emit)
|
|
107
|
+
}
|
|
83
108
|
}
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
|
|
110
|
+
else if (tagName === 'tool_result') {
|
|
111
|
+
if (!isClosing) {
|
|
112
|
+
this.switchType('tool_result', emit, attrs)
|
|
113
|
+
} else {
|
|
114
|
+
this.switchType('content', emit)
|
|
115
|
+
}
|
|
86
116
|
}
|
|
117
|
+
|
|
87
118
|
else {
|
|
88
119
|
this.append(tag, emit)
|
|
89
120
|
}
|
|
90
121
|
}
|
|
91
122
|
|
|
92
123
|
|
|
93
|
-
|
|
124
|
+
|
|
125
|
+
startThinking(attrs={}, emit) {
|
|
94
126
|
// 如果上一个 thinking 还没结算,先结算
|
|
95
127
|
this.closeThinking()
|
|
96
128
|
|
|
@@ -98,8 +130,10 @@ export class StreamParser {
|
|
|
98
130
|
this.currentEvent = {
|
|
99
131
|
id: this.uuid(),
|
|
100
132
|
type: 'thinking',
|
|
133
|
+
name: attrs.name || null,
|
|
101
134
|
content: '',
|
|
102
135
|
startTime: Date.now(),
|
|
136
|
+
thinkingExpanded: true,
|
|
103
137
|
endTime: null,
|
|
104
138
|
duration: null
|
|
105
139
|
}
|
|
@@ -121,14 +155,21 @@ export class StreamParser {
|
|
|
121
155
|
}
|
|
122
156
|
}
|
|
123
157
|
|
|
124
|
-
switchType(type) {
|
|
158
|
+
switchType(type, emit, attrs={}) {
|
|
125
159
|
// 如果是从 thinking 切走,结算时间
|
|
126
160
|
if (this.currentEvent?.type === 'thinking') {
|
|
127
161
|
this.closeThinking()
|
|
128
162
|
}
|
|
129
163
|
|
|
130
164
|
this.currentType = type
|
|
131
|
-
this.currentEvent =
|
|
165
|
+
this.currentEvent = {
|
|
166
|
+
id: this.uuid(),
|
|
167
|
+
type,
|
|
168
|
+
name: attrs.name || null,
|
|
169
|
+
content: ''
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
emit({ type: 'create', event: this.currentEvent })
|
|
132
173
|
}
|
|
133
174
|
|
|
134
175
|
append(text, emit) {
|
|
@@ -147,18 +188,6 @@ export class StreamParser {
|
|
|
147
188
|
emit({ type: 'append', event: this.currentEvent })
|
|
148
189
|
}
|
|
149
190
|
|
|
150
|
-
parseTagAttributes(tag) {
|
|
151
|
-
const attrRegex = /(\w+)=['"]([^'"]+)['"]/g
|
|
152
|
-
const attrs = {}
|
|
153
|
-
let match
|
|
154
|
-
|
|
155
|
-
while ((match = attrRegex.exec(tag)) !== null) {
|
|
156
|
-
attrs[match[1]] = match[2]
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return attrs
|
|
160
|
-
}
|
|
161
|
-
|
|
162
191
|
scheduleFlush(emit) {
|
|
163
192
|
if (this.updateTimer) return
|
|
164
193
|
this.updateTimer = setTimeout(() => {
|