@opentiny/tiny-robot-kit 0.2.0-alpha.0

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.
@@ -0,0 +1,227 @@
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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "declaration": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src",
13
+ "lib": ["ESNext", "DOM"],
14
+ "baseUrl": ".",
15
+ "paths": {
16
+ "*": ["node_modules/*"]
17
+ }
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }