@opentiny/tiny-robot-kit 0.2.0-alpha.2 → 0.2.0-alpha.4

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/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,2 +0,0 @@
1
- export * from './message/useMessage'
2
- export * from './conversation/useConversation'
@@ -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
- }