@opentiny/tiny-robot-kit 0.2.0-alpha.2 → 0.2.0-alpha.3
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/dist/index.js +2 -761
- package/dist/index.mjs +2 -722
- package/package.json +7 -3
- package/src/client.ts +0 -101
- package/src/error.ts +0 -100
- package/src/index.ts +0 -10
- package/src/providers/base.ts +0 -62
- package/src/providers/openai.ts +0 -139
- package/src/types.ts +0 -163
- package/src/utils.ts +0 -125
- package/src/vue/conversation/useConversation.ts +0 -365
- package/src/vue/index.ts +0 -2
- package/src/vue/message/useMessage.ts +0 -227
- package/tsconfig.json +0 -21
package/src/utils.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 工具函数模块
|
|
3
|
-
* 提供一些实用的辅助函数
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ChatMessage, ChatCompletionResponse, ChatCompletionStreamResponse, StreamHandler } from './types'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 处理SSE流式响应
|
|
10
|
-
* @param response fetch响应对象
|
|
11
|
-
* @param handler 流处理器
|
|
12
|
-
*/
|
|
13
|
-
export async function handleSSEStream(response: Response, handler: StreamHandler, signal?: AbortSignal): Promise<void> {
|
|
14
|
-
// 获取ReadableStream
|
|
15
|
-
const reader = response.body?.getReader()
|
|
16
|
-
if (!reader) {
|
|
17
|
-
throw new Error('Response body is null')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 处理流式数据
|
|
21
|
-
const decoder = new TextDecoder()
|
|
22
|
-
let buffer = ''
|
|
23
|
-
|
|
24
|
-
if (signal) {
|
|
25
|
-
signal.addEventListener(
|
|
26
|
-
'abort',
|
|
27
|
-
() => {
|
|
28
|
-
reader.cancel().catch((err) => console.error('Error cancelling reader:', err))
|
|
29
|
-
},
|
|
30
|
-
{ once: true },
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
while (true) {
|
|
36
|
-
if (signal?.aborted) {
|
|
37
|
-
await reader.cancel()
|
|
38
|
-
break
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const { done, value } = await reader.read()
|
|
42
|
-
if (done) break
|
|
43
|
-
|
|
44
|
-
// 解码二进制数据
|
|
45
|
-
const chunk = decoder.decode(value, { stream: true })
|
|
46
|
-
buffer += chunk
|
|
47
|
-
|
|
48
|
-
// 处理完整的SSE消息
|
|
49
|
-
const lines = buffer.split('\n\n')
|
|
50
|
-
buffer = lines.pop() || ''
|
|
51
|
-
|
|
52
|
-
for (const line of lines) {
|
|
53
|
-
if (line.trim() === '') continue
|
|
54
|
-
if (line.trim() === 'data: [DONE]') {
|
|
55
|
-
handler.onDone()
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// 解析SSE消息
|
|
61
|
-
const dataMatch = line.match(/^data: (.+)$/m)
|
|
62
|
-
if (!dataMatch) continue
|
|
63
|
-
|
|
64
|
-
const data = JSON.parse(dataMatch[1]) as ChatCompletionStreamResponse
|
|
65
|
-
handler.onData(data)
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.error('Error parsing SSE message:', error)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (buffer.trim() === 'data: [DONE]' || signal?.aborted) {
|
|
73
|
-
handler.onDone()
|
|
74
|
-
}
|
|
75
|
-
} catch (error) {
|
|
76
|
-
if (signal?.aborted) return
|
|
77
|
-
throw error
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 格式化消息
|
|
83
|
-
* 将各种格式的消息转换为标准的ChatMessage格式
|
|
84
|
-
* @param messages 消息数组
|
|
85
|
-
* @returns 标准格式的消息数组
|
|
86
|
-
*/
|
|
87
|
-
export function formatMessages(messages: Array<ChatMessage | string>): ChatMessage[] {
|
|
88
|
-
return messages.map((msg) => {
|
|
89
|
-
// 如果已经是标准格式,直接返回
|
|
90
|
-
if (typeof msg === 'object' && 'role' in msg && 'content' in msg) {
|
|
91
|
-
return {
|
|
92
|
-
role: msg.role,
|
|
93
|
-
content: String(msg.content),
|
|
94
|
-
...(msg.name ? { name: msg.name } : {}),
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 如果是字符串,默认为用户消息
|
|
99
|
-
if (typeof msg === 'string') {
|
|
100
|
-
return {
|
|
101
|
-
role: 'user',
|
|
102
|
-
content: msg,
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 其他情况,尝试转换为字符串
|
|
107
|
-
return {
|
|
108
|
-
role: 'user',
|
|
109
|
-
content: String(msg),
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 从响应中提取文本内容
|
|
116
|
-
* @param response 聊天完成响应
|
|
117
|
-
* @returns 文本内容
|
|
118
|
-
*/
|
|
119
|
-
export function extractTextFromResponse(response: ChatCompletionResponse): string {
|
|
120
|
-
if (!response.choices || !response.choices.length) {
|
|
121
|
-
return ''
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return response.choices[0].message?.content || ''
|
|
125
|
-
}
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useConversation composable
|
|
3
|
-
* 提供会话管理和持久化功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { reactive, watch } from 'vue'
|
|
7
|
-
import type { ChatMessage } from '../../types'
|
|
8
|
-
import { useMessage, type UseMessageReturn } from '../message/useMessage'
|
|
9
|
-
import type { AIClient } from '../../client'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 会话接口
|
|
13
|
-
*/
|
|
14
|
-
export interface Conversation {
|
|
15
|
-
/** 会话ID */
|
|
16
|
-
id: string
|
|
17
|
-
/** 会话标题 */
|
|
18
|
-
title: string
|
|
19
|
-
/** 创建时间 */
|
|
20
|
-
createdAt: number
|
|
21
|
-
/** 更新时间 */
|
|
22
|
-
updatedAt: number
|
|
23
|
-
/** 自定义元数据 */
|
|
24
|
-
metadata?: Record<string, unknown>
|
|
25
|
-
/** 会话消息 */
|
|
26
|
-
messages: ChatMessage[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* 存储策略接口
|
|
31
|
-
*/
|
|
32
|
-
export interface ConversationStorageStrategy {
|
|
33
|
-
/** 保存会话列表 */
|
|
34
|
-
saveConversations: (conversations: Conversation[]) => Promise<void> | void
|
|
35
|
-
/** 加载会话列表 */
|
|
36
|
-
loadConversations: () => Promise<Conversation[]> | Conversation[]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 本地存储策略
|
|
41
|
-
*/
|
|
42
|
-
export class LocalStorageStrategy implements ConversationStorageStrategy {
|
|
43
|
-
private storageKey: string
|
|
44
|
-
|
|
45
|
-
constructor(storageKey: string = 'tiny-robot-ai-conversations') {
|
|
46
|
-
this.storageKey = storageKey
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
saveConversations(conversations: Conversation[]): void {
|
|
50
|
-
try {
|
|
51
|
-
localStorage.setItem(this.storageKey, JSON.stringify(conversations))
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('保存会话失败:', error)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
loadConversations(): Conversation[] {
|
|
58
|
-
try {
|
|
59
|
-
const data = localStorage.getItem(this.storageKey)
|
|
60
|
-
return data ? JSON.parse(data) : []
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error('加载会话失败:', error)
|
|
63
|
-
return []
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 会话状态接口
|
|
70
|
-
*/
|
|
71
|
-
export interface ConversationState {
|
|
72
|
-
/** 会话列表 */
|
|
73
|
-
conversations: Conversation[]
|
|
74
|
-
/** 当前会话ID */
|
|
75
|
-
currentId: string | null
|
|
76
|
-
/** 是否正在加载 */
|
|
77
|
-
loading: boolean
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* useConversation选项接口
|
|
82
|
-
*/
|
|
83
|
-
export interface UseConversationOptions {
|
|
84
|
-
/** AI客户端实例 */
|
|
85
|
-
client: AIClient
|
|
86
|
-
/** 存储策略 */
|
|
87
|
-
storage?: ConversationStorageStrategy
|
|
88
|
-
/** 是否自动保存 */
|
|
89
|
-
autoSave?: boolean
|
|
90
|
-
/** 是否默认使用流式响应 */
|
|
91
|
-
useStreamByDefault?: boolean
|
|
92
|
-
/** 错误消息模板 */
|
|
93
|
-
errorMessage?: string
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* useConversation返回值接口
|
|
98
|
-
*/
|
|
99
|
-
export interface UseConversationReturn {
|
|
100
|
-
/** 会话状态 */
|
|
101
|
-
state: ConversationState
|
|
102
|
-
/** 消息管理 */
|
|
103
|
-
messageManager: UseMessageReturn
|
|
104
|
-
/** 创建新会话 */
|
|
105
|
-
createConversation: (title?: string, metadata?: Record<string, unknown>) => string
|
|
106
|
-
/** 切换会话 */
|
|
107
|
-
switchConversation: (id: string) => void
|
|
108
|
-
/** 删除会话 */
|
|
109
|
-
deleteConversation: (id: string) => void
|
|
110
|
-
/** 更新会话标题 */
|
|
111
|
-
updateTitle: (id: string, title: string) => void
|
|
112
|
-
/** 更新会话元数据 */
|
|
113
|
-
updateMetadata: (id: string, metadata: Record<string, unknown>) => void
|
|
114
|
-
/** 保存会话 */
|
|
115
|
-
saveConversations: () => Promise<void>
|
|
116
|
-
/** 加载会话 */
|
|
117
|
-
loadConversations: () => Promise<void>
|
|
118
|
-
/** 生成会话标题 */
|
|
119
|
-
generateTitle: (id: string) => Promise<string>
|
|
120
|
-
/** 获取当前会话 */
|
|
121
|
-
getCurrentConversation: () => Conversation | null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 生成唯一ID
|
|
126
|
-
*/
|
|
127
|
-
function generateId(): string {
|
|
128
|
-
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* useConversation composable
|
|
133
|
-
* 提供会话管理和持久化功能
|
|
134
|
-
*
|
|
135
|
-
* @param options useConversation选项
|
|
136
|
-
* @returns UseConversationReturn
|
|
137
|
-
*/
|
|
138
|
-
export function useConversation(options: UseConversationOptions): UseConversationReturn {
|
|
139
|
-
const {
|
|
140
|
-
client,
|
|
141
|
-
storage = new LocalStorageStrategy(),
|
|
142
|
-
autoSave = true,
|
|
143
|
-
useStreamByDefault = true,
|
|
144
|
-
errorMessage = '请求失败,请稍后重试',
|
|
145
|
-
} = options
|
|
146
|
-
|
|
147
|
-
// 会话状态
|
|
148
|
-
const state = reactive<ConversationState>({
|
|
149
|
-
conversations: [],
|
|
150
|
-
currentId: null,
|
|
151
|
-
loading: false,
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
// 消息管理
|
|
155
|
-
const messageManager = useMessage({
|
|
156
|
-
client,
|
|
157
|
-
useStreamByDefault,
|
|
158
|
-
errorMessage,
|
|
159
|
-
initialMessages: [],
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
// 监听消息变化,自动更新会话
|
|
163
|
-
watch(
|
|
164
|
-
() => messageManager.messages.value,
|
|
165
|
-
(messages: ChatMessage[]) => {
|
|
166
|
-
if (state.currentId && messages.length > 0) {
|
|
167
|
-
const index = state.conversations.findIndex((row: Conversation) => row.id === state.currentId)
|
|
168
|
-
if (index !== -1) {
|
|
169
|
-
state.conversations[index].messages = [...messages]
|
|
170
|
-
state.conversations[index].updatedAt = Date.now()
|
|
171
|
-
if (autoSave) {
|
|
172
|
-
saveConversations()
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
{ deep: true },
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* 创建新会话
|
|
182
|
-
*/
|
|
183
|
-
const createConversation = (title: string = '新会话', metadata: Record<string, unknown> = {}): string => {
|
|
184
|
-
const id = generateId()
|
|
185
|
-
const newConversation: Conversation = {
|
|
186
|
-
id,
|
|
187
|
-
title,
|
|
188
|
-
createdAt: Date.now(),
|
|
189
|
-
updatedAt: Date.now(),
|
|
190
|
-
messages: [],
|
|
191
|
-
metadata,
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
state.conversations.unshift(newConversation)
|
|
195
|
-
switchConversation(id)
|
|
196
|
-
|
|
197
|
-
if (autoSave) {
|
|
198
|
-
saveConversations()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return id
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* 切换会话
|
|
206
|
-
*/
|
|
207
|
-
const switchConversation = (id: string): void => {
|
|
208
|
-
const conversation = state.conversations.find((conv: Conversation) => conv.id === id)
|
|
209
|
-
if (conversation) {
|
|
210
|
-
state.currentId = id
|
|
211
|
-
messageManager.clearMessages()
|
|
212
|
-
if (conversation.messages.length > 0) {
|
|
213
|
-
conversation.messages.forEach((msg: ChatMessage) => messageManager.addMessage(msg))
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 删除会话
|
|
220
|
-
*/
|
|
221
|
-
const deleteConversation = (id: string): void => {
|
|
222
|
-
const index = state.conversations.findIndex((conv: Conversation) => conv.id === id)
|
|
223
|
-
if (index !== -1) {
|
|
224
|
-
state.conversations.splice(index, 1)
|
|
225
|
-
|
|
226
|
-
// 如果删除的是当前会话,切换到第一个会话或清空
|
|
227
|
-
if (state.currentId === id) {
|
|
228
|
-
if (state.conversations.length > 0) {
|
|
229
|
-
switchConversation(state.conversations[0].id)
|
|
230
|
-
} else {
|
|
231
|
-
state.currentId = null
|
|
232
|
-
messageManager.clearMessages()
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (autoSave) {
|
|
237
|
-
saveConversations()
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 更新会话标题
|
|
244
|
-
*/
|
|
245
|
-
const updateTitle = (id: string, title: string): void => {
|
|
246
|
-
const conversation = state.conversations.find((conv: Conversation) => conv.id === id)
|
|
247
|
-
if (conversation) {
|
|
248
|
-
conversation.title = title
|
|
249
|
-
conversation.updatedAt = Date.now()
|
|
250
|
-
|
|
251
|
-
if (autoSave) {
|
|
252
|
-
saveConversations()
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* 更新会话元数据
|
|
259
|
-
*/
|
|
260
|
-
const updateMetadata = (id: string, metadata: Record<string, unknown>): void => {
|
|
261
|
-
const conversation = state.conversations.find((conv: Conversation) => conv.id === id)
|
|
262
|
-
if (conversation) {
|
|
263
|
-
conversation.metadata = { ...conversation.metadata, ...metadata }
|
|
264
|
-
conversation.updatedAt = Date.now()
|
|
265
|
-
|
|
266
|
-
if (autoSave) {
|
|
267
|
-
saveConversations()
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 保存会话
|
|
274
|
-
*/
|
|
275
|
-
const saveConversations = async (): Promise<void> => {
|
|
276
|
-
try {
|
|
277
|
-
await storage.saveConversations(state.conversations)
|
|
278
|
-
} catch (error) {
|
|
279
|
-
console.error('保存会话失败:', error)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* 加载会话
|
|
285
|
-
*/
|
|
286
|
-
const loadConversations = async (): Promise<void> => {
|
|
287
|
-
state.loading = true
|
|
288
|
-
try {
|
|
289
|
-
const conversations = await storage.loadConversations()
|
|
290
|
-
state.conversations = conversations
|
|
291
|
-
|
|
292
|
-
// 如果有会话,默认选中第一个
|
|
293
|
-
if (conversations.length > 0 && !state.currentId) {
|
|
294
|
-
switchConversation(conversations[0].id)
|
|
295
|
-
}
|
|
296
|
-
} catch (error) {
|
|
297
|
-
console.error('加载会话失败:', error)
|
|
298
|
-
} finally {
|
|
299
|
-
state.loading = false
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* 生成会话标题
|
|
305
|
-
* 基于会话内容自动生成标题
|
|
306
|
-
*/
|
|
307
|
-
const generateTitle = async (id: string): Promise<string> => {
|
|
308
|
-
const conversation = state.conversations.find((conv: Conversation) => conv.id === id)
|
|
309
|
-
if (!conversation || conversation.messages.length < 2) {
|
|
310
|
-
return conversation?.title || '新会话'
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
// 构建生成标题的提示
|
|
315
|
-
const prompt: ChatMessage = {
|
|
316
|
-
role: 'system',
|
|
317
|
-
content:
|
|
318
|
-
'请根据以下对话内容,生成一个简短的标题(不超过20个字符)。只需要返回标题文本,不需要任何解释或额外内容。',
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// 获取前几条消息用于生成标题
|
|
322
|
-
const contextMessages = conversation.messages.slice(0, Math.min(4, conversation.messages.length))
|
|
323
|
-
|
|
324
|
-
const response = await client.chat({
|
|
325
|
-
messages: [prompt, ...contextMessages],
|
|
326
|
-
options: {
|
|
327
|
-
stream: false,
|
|
328
|
-
max_tokens: 30,
|
|
329
|
-
},
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
const title = response.choices[0].message.content.trim()
|
|
333
|
-
updateTitle(id, title)
|
|
334
|
-
return title
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.error('生成标题失败:', error)
|
|
337
|
-
return conversation.title
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* 获取当前会话
|
|
343
|
-
*/
|
|
344
|
-
const getCurrentConversation = (): Conversation | null => {
|
|
345
|
-
if (!state.currentId) return null
|
|
346
|
-
return state.conversations.find((conv: Conversation) => conv.id === state.currentId) || null
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// 初始加载会话
|
|
350
|
-
loadConversations()
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
state,
|
|
354
|
-
messageManager,
|
|
355
|
-
createConversation,
|
|
356
|
-
switchConversation,
|
|
357
|
-
deleteConversation,
|
|
358
|
-
updateTitle,
|
|
359
|
-
updateMetadata,
|
|
360
|
-
saveConversations,
|
|
361
|
-
loadConversations,
|
|
362
|
-
generateTitle,
|
|
363
|
-
getCurrentConversation,
|
|
364
|
-
}
|
|
365
|
-
}
|
package/src/vue/index.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useMessage composable
|
|
3
|
-
* 提供消息管理和状态控制功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { reactive, Reactive, ref, toRaw, type Ref } from 'vue'
|
|
7
|
-
import type { ChatMessage } from '../../types'
|
|
8
|
-
import type { AIClient } from '../../client'
|
|
9
|
-
|
|
10
|
-
export enum STATUS {
|
|
11
|
-
INIT = 'init', // 初始状态
|
|
12
|
-
PROCESSING = 'processing', // AI请求正在处理中, 还未响应,显示加载动画
|
|
13
|
-
STREAMING = 'streaming', // 流式响应中分块数据返回中
|
|
14
|
-
FINISHED = 'finished', // AI请求已完成
|
|
15
|
-
ABORTED = 'aborted', // 用户中止请求
|
|
16
|
-
ERROR = 'error', // AI请求发生错误
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const GeneratingStatus = [STATUS.PROCESSING, STATUS.STREAMING]
|
|
20
|
-
export const FinalStatus = [STATUS.FINISHED, STATUS.ABORTED, STATUS.ERROR]
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 消息状态接口
|
|
24
|
-
*/
|
|
25
|
-
export interface MessageState {
|
|
26
|
-
status: STATUS
|
|
27
|
-
errorMsg: string | null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* useMessage选项接口
|
|
32
|
-
*/
|
|
33
|
-
export interface UseMessageOptions {
|
|
34
|
-
/** AI客户端实例 */
|
|
35
|
-
client: AIClient
|
|
36
|
-
/** 是否默认使用流式响应 */
|
|
37
|
-
useStreamByDefault?: boolean
|
|
38
|
-
/** 错误消息模板 */
|
|
39
|
-
errorMessage?: string
|
|
40
|
-
/** 初始消息列表 */
|
|
41
|
-
initialMessages?: ChatMessage[]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* useMessage返回值接口
|
|
46
|
-
*/
|
|
47
|
-
export interface UseMessageReturn {
|
|
48
|
-
messages: Ref<ChatMessage[]>
|
|
49
|
-
/** 消息状态 */
|
|
50
|
-
messageState: Reactive<MessageState>
|
|
51
|
-
/** 输入消息 */
|
|
52
|
-
inputMessage: Ref<string>
|
|
53
|
-
/** 是否使用流式响应 */
|
|
54
|
-
useStream: Ref<boolean>
|
|
55
|
-
/** 发送消息 */
|
|
56
|
-
sendMessage: (content?: string, clearInput?: boolean) => Promise<void>
|
|
57
|
-
/** 清空消息 */
|
|
58
|
-
clearMessages: () => void
|
|
59
|
-
/** 添加消息 */
|
|
60
|
-
addMessage: (message: ChatMessage) => void
|
|
61
|
-
/** 中止请求 */
|
|
62
|
-
abortRequest: () => void
|
|
63
|
-
/** 重试请求 */
|
|
64
|
-
retryRequest: (msgIndex: number) => Promise<void>
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* useMessage composable
|
|
69
|
-
* 提供消息管理和状态控制功能
|
|
70
|
-
*
|
|
71
|
-
* @param options useMessage选项
|
|
72
|
-
* @returns UseMessageReturn
|
|
73
|
-
*/
|
|
74
|
-
export function useMessage(options: UseMessageOptions): UseMessageReturn {
|
|
75
|
-
const { client, useStreamByDefault = true, errorMessage = '请求失败,请稍后重试', initialMessages = [] } = options
|
|
76
|
-
|
|
77
|
-
// 消息列表
|
|
78
|
-
const messages = ref<ChatMessage[]>([...initialMessages])
|
|
79
|
-
|
|
80
|
-
// 输入消息
|
|
81
|
-
const inputMessage = ref('')
|
|
82
|
-
|
|
83
|
-
// 是否使用流式响应
|
|
84
|
-
const useStream = ref(useStreamByDefault)
|
|
85
|
-
|
|
86
|
-
// 请求控制器
|
|
87
|
-
let abortController: AbortController | null = null
|
|
88
|
-
|
|
89
|
-
// 消息状态
|
|
90
|
-
const messageState = reactive<MessageState>({
|
|
91
|
-
status: STATUS.INIT,
|
|
92
|
-
errorMsg: null,
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// 普通请求
|
|
96
|
-
const chat = async (abortController: AbortController) => {
|
|
97
|
-
const response = await client.chat({
|
|
98
|
-
messages: toRaw(messages.value),
|
|
99
|
-
options: {
|
|
100
|
-
stream: false,
|
|
101
|
-
signal: abortController.signal,
|
|
102
|
-
},
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const assistantMessage: ChatMessage = {
|
|
106
|
-
role: 'assistant',
|
|
107
|
-
content: response.choices[0].message.content,
|
|
108
|
-
}
|
|
109
|
-
messages.value.push(assistantMessage)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 流式请求
|
|
113
|
-
const streamChat = async (abortController: AbortController) => {
|
|
114
|
-
await client.chatStream(
|
|
115
|
-
{
|
|
116
|
-
messages: toRaw(messages.value),
|
|
117
|
-
options: {
|
|
118
|
-
stream: true,
|
|
119
|
-
signal: abortController.signal,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
onData: (data) => {
|
|
124
|
-
messageState.status = STATUS.STREAMING
|
|
125
|
-
if (messages.value[messages.value.length - 1].role === 'user') {
|
|
126
|
-
messages.value.push({ role: 'assistant', content: '' })
|
|
127
|
-
}
|
|
128
|
-
const choice = data.choices?.[0]
|
|
129
|
-
if (choice && choice.delta.content) {
|
|
130
|
-
messages.value[messages.value.length - 1].content += choice.delta.content
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
onError: (error) => {
|
|
134
|
-
messageState.status = STATUS.ERROR
|
|
135
|
-
messageState.errorMsg = errorMessage
|
|
136
|
-
console.error('Stream request error:', error)
|
|
137
|
-
},
|
|
138
|
-
onDone: () => {
|
|
139
|
-
messageState.status = STATUS.FINISHED
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const chatRequest = async () => {
|
|
146
|
-
// 更新状态
|
|
147
|
-
messageState.status = STATUS.PROCESSING
|
|
148
|
-
messageState.errorMsg = null
|
|
149
|
-
|
|
150
|
-
// 创建中止控制器
|
|
151
|
-
abortController = new AbortController()
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
if (useStream.value) {
|
|
155
|
-
await streamChat(abortController)
|
|
156
|
-
} else {
|
|
157
|
-
await chat(abortController)
|
|
158
|
-
}
|
|
159
|
-
messageState.status = STATUS.FINISHED
|
|
160
|
-
} catch (error) {
|
|
161
|
-
messageState.errorMsg = errorMessage
|
|
162
|
-
messageState.status = STATUS.ERROR
|
|
163
|
-
console.error('Send message error:', error)
|
|
164
|
-
} finally {
|
|
165
|
-
abortController = null
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// 发送消息
|
|
170
|
-
const sendMessage = async (content: string = inputMessage.value, clearInput: boolean = true) => {
|
|
171
|
-
if (!content?.trim() || GeneratingStatus.includes(messageState.status)) {
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const userMessage: ChatMessage = {
|
|
176
|
-
role: 'user',
|
|
177
|
-
content,
|
|
178
|
-
}
|
|
179
|
-
messages.value.push(userMessage)
|
|
180
|
-
if (clearInput) {
|
|
181
|
-
inputMessage.value = ''
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
await chatRequest()
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 中止请求
|
|
188
|
-
const abortRequest = () => {
|
|
189
|
-
if (abortController) {
|
|
190
|
-
abortController.abort()
|
|
191
|
-
abortController = null
|
|
192
|
-
messageState.status = STATUS.ABORTED
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// 重试请求
|
|
197
|
-
const retryRequest = async (msgIndex: number) => {
|
|
198
|
-
if (msgIndex === 0 || !messages.value[msgIndex] || messages.value[msgIndex].role === 'user') {
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
messages.value.splice(msgIndex)
|
|
202
|
-
await chatRequest()
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
//清空消息
|
|
206
|
-
const clearMessages = () => {
|
|
207
|
-
messages.value = []
|
|
208
|
-
messageState.errorMsg = null
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// 添加消息
|
|
212
|
-
const addMessage = (message: ChatMessage) => {
|
|
213
|
-
messages.value.push(message)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
messages,
|
|
218
|
-
messageState,
|
|
219
|
-
inputMessage,
|
|
220
|
-
useStream,
|
|
221
|
-
sendMessage,
|
|
222
|
-
clearMessages,
|
|
223
|
-
addMessage,
|
|
224
|
-
abortRequest,
|
|
225
|
-
retryRequest,
|
|
226
|
-
}
|
|
227
|
-
}
|