@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.
- package/README.md +164 -0
- package/dist/index.d.mts +351 -0
- package/dist/index.d.ts +351 -0
- package/dist/index.js +561 -0
- package/dist/index.mjs +524 -0
- package/package.json +26 -0
- package/src/client.ts +101 -0
- package/src/error.ts +100 -0
- package/src/index.ts +10 -0
- package/src/providers/base.ts +62 -0
- package/src/providers/openai.ts +134 -0
- package/src/types.ts +163 -0
- package/src/utils.ts +125 -0
- package/src/vue/index.ts +1 -0
- package/src/vue/useMessage.ts +227 -0
- package/tsconfig.json +21 -0
package/src/error.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误处理模块
|
|
3
|
+
* 用于统一处理各种错误情况并提供标准化的错误格式
|
|
4
|
+
*/
|
|
5
|
+
import { AIAdapterError, ErrorType } from './types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 创建标准化的AI适配器错误
|
|
9
|
+
* @param error 错误信息
|
|
10
|
+
* @returns 标准化的AI适配器错误
|
|
11
|
+
*/
|
|
12
|
+
export function createError(error: Partial<AIAdapterError>): AIAdapterError {
|
|
13
|
+
return {
|
|
14
|
+
type: error.type || ErrorType.UNKNOWN_ERROR,
|
|
15
|
+
message: error.message || '未知错误',
|
|
16
|
+
statusCode: error.statusCode,
|
|
17
|
+
originalError: error.originalError,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Error {
|
|
22
|
+
response?: object
|
|
23
|
+
code?: string
|
|
24
|
+
message?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 处理API请求错误
|
|
29
|
+
* @param error 原始错误
|
|
30
|
+
* @returns 标准化的AI适配器错误
|
|
31
|
+
*/
|
|
32
|
+
export function handleRequestError(error: Error): AIAdapterError {
|
|
33
|
+
// 网络错误
|
|
34
|
+
if (!error.response) {
|
|
35
|
+
return createError({
|
|
36
|
+
type: ErrorType.NETWORK_ERROR,
|
|
37
|
+
message: '网络连接错误,请检查您的网络连接',
|
|
38
|
+
originalError: error,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 服务器返回的错误
|
|
43
|
+
if (error.response) {
|
|
44
|
+
const { status, data } = error.response
|
|
45
|
+
|
|
46
|
+
// 身份验证错误
|
|
47
|
+
if (status === 401 || status === 403) {
|
|
48
|
+
return createError({
|
|
49
|
+
type: ErrorType.AUTHENTICATION_ERROR,
|
|
50
|
+
message: '身份验证失败,请检查您的API密钥',
|
|
51
|
+
statusCode: status,
|
|
52
|
+
originalError: error,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 速率限制错误
|
|
57
|
+
if (status === 429) {
|
|
58
|
+
return createError({
|
|
59
|
+
type: ErrorType.RATE_LIMIT_ERROR,
|
|
60
|
+
message: '超出API调用限制,请稍后再试',
|
|
61
|
+
statusCode: status,
|
|
62
|
+
originalError: error,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 服务器错误
|
|
67
|
+
if (status >= 500) {
|
|
68
|
+
return createError({
|
|
69
|
+
type: ErrorType.SERVER_ERROR,
|
|
70
|
+
message: '服务器错误,请稍后再试',
|
|
71
|
+
statusCode: status,
|
|
72
|
+
originalError: error,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 其他HTTP错误
|
|
77
|
+
return createError({
|
|
78
|
+
type: ErrorType.UNKNOWN_ERROR,
|
|
79
|
+
message: data?.error?.message || `请求失败,状态码: ${status}`,
|
|
80
|
+
statusCode: status,
|
|
81
|
+
originalError: error,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 超时错误
|
|
86
|
+
if (error.code === 'ECONNABORTED') {
|
|
87
|
+
return createError({
|
|
88
|
+
type: ErrorType.TIMEOUT_ERROR,
|
|
89
|
+
message: '请求超时,请稍后再试',
|
|
90
|
+
originalError: error,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 默认错误
|
|
95
|
+
return createError({
|
|
96
|
+
type: ErrorType.UNKNOWN_ERROR,
|
|
97
|
+
message: error.message || '发生未知错误',
|
|
98
|
+
originalError: error,
|
|
99
|
+
})
|
|
100
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { AIClient } from './client'
|
|
2
|
+
|
|
3
|
+
export { BaseModelProvider } from './providers/base'
|
|
4
|
+
export { OpenAIProvider } from './providers/openai'
|
|
5
|
+
|
|
6
|
+
export { formatMessages, extractTextFromResponse } from './utils'
|
|
7
|
+
|
|
8
|
+
export * from './vue'
|
|
9
|
+
|
|
10
|
+
export * from './types'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AIModelConfig, ChatCompletionRequest, ChatCompletionResponse, StreamHandler } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 模型Provider基类
|
|
5
|
+
*/
|
|
6
|
+
export abstract class BaseModelProvider {
|
|
7
|
+
protected config: AIModelConfig
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param config AI模型配置
|
|
11
|
+
*/
|
|
12
|
+
constructor(config: AIModelConfig) {
|
|
13
|
+
this.config = config
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 发送聊天请求并获取响应
|
|
18
|
+
* @param request 聊天请求参数
|
|
19
|
+
* @returns 聊天响应
|
|
20
|
+
*/
|
|
21
|
+
abstract chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 发送流式聊天请求并通过处理器处理响应
|
|
25
|
+
* @param request 聊天请求参数
|
|
26
|
+
* @param handler 流式响应处理器
|
|
27
|
+
*/
|
|
28
|
+
abstract chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 更新配置
|
|
32
|
+
* @param config 新的AI模型配置
|
|
33
|
+
*/
|
|
34
|
+
updateConfig(config: AIModelConfig): void {
|
|
35
|
+
this.config = { ...this.config, ...config }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 获取当前配置
|
|
40
|
+
* @returns AI模型配置
|
|
41
|
+
*/
|
|
42
|
+
getConfig(): AIModelConfig {
|
|
43
|
+
return { ...this.config }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 验证请求参数
|
|
48
|
+
* @param request 聊天请求参数
|
|
49
|
+
*/
|
|
50
|
+
protected validateRequest(request: ChatCompletionRequest): void {
|
|
51
|
+
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
|
|
52
|
+
throw new Error('请求必须包含至少一条消息')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 验证每条消息的格式
|
|
56
|
+
for (const message of request.messages) {
|
|
57
|
+
if (!message.role || !message.content) {
|
|
58
|
+
throw new Error('每条消息必须包含角色和内容')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI提供商
|
|
3
|
+
* 用于与OpenAI API进行交互
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseModelProvider } from './base'
|
|
7
|
+
import type { AIModelConfig, ChatCompletionRequest, ChatCompletionResponse, StreamHandler } from '../types'
|
|
8
|
+
import { handleRequestError } from '../error'
|
|
9
|
+
import { handleSSEStream } from '../utils'
|
|
10
|
+
|
|
11
|
+
export class OpenAIProvider extends BaseModelProvider {
|
|
12
|
+
private baseURL: string
|
|
13
|
+
private apiKey: string
|
|
14
|
+
private defaultModel: string = 'gpt-3.5-turbo'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param config AI模型配置
|
|
18
|
+
*/
|
|
19
|
+
constructor(config: AIModelConfig) {
|
|
20
|
+
super(config)
|
|
21
|
+
|
|
22
|
+
this.baseURL = config.apiUrl || 'https://api.openai.com/v1'
|
|
23
|
+
this.apiKey = config.apiKey || ''
|
|
24
|
+
|
|
25
|
+
// 设置默认模型
|
|
26
|
+
if (config.defaultModel) {
|
|
27
|
+
this.defaultModel = config.defaultModel
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!this.apiKey) {
|
|
31
|
+
console.warn('API key is not provided. Authentication will likely fail.')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 发送聊天请求并获取响应
|
|
37
|
+
* @param request 聊天请求参数
|
|
38
|
+
* @returns 聊天响应
|
|
39
|
+
*/
|
|
40
|
+
async chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
|
|
41
|
+
try {
|
|
42
|
+
this.validateRequest(request)
|
|
43
|
+
|
|
44
|
+
const requestData = {
|
|
45
|
+
model: request.options?.model || this.config.defaultModel || this.defaultModel,
|
|
46
|
+
messages: request.messages,
|
|
47
|
+
...request.options,
|
|
48
|
+
stream: false,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(requestData),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const errorText = await response.text()
|
|
62
|
+
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return await response.json()
|
|
66
|
+
} catch (error: unknown) {
|
|
67
|
+
// 处理错误
|
|
68
|
+
throw handleRequestError(error as { response: object })
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 发送流式聊天请求并通过处理器处理响应
|
|
74
|
+
* @param request 聊天请求参数
|
|
75
|
+
* @param handler 流式响应处理器
|
|
76
|
+
*/
|
|
77
|
+
async chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void> {
|
|
78
|
+
const { signal, ...options } = request.options || {}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// 验证请求参数
|
|
82
|
+
this.validateRequest(request)
|
|
83
|
+
|
|
84
|
+
const requestData = {
|
|
85
|
+
model: request.options?.model || this.config.defaultModel || this.defaultModel,
|
|
86
|
+
messages: request.messages,
|
|
87
|
+
...options,
|
|
88
|
+
stream: true,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
96
|
+
Accept: 'text/event-stream',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(requestData),
|
|
99
|
+
signal,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const errorText = await response.text()
|
|
104
|
+
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await handleSSEStream(response, handler, signal)
|
|
108
|
+
} catch (error: unknown) {
|
|
109
|
+
if (signal?.aborted) return
|
|
110
|
+
handler.onError(handleRequestError(error as { response: object }))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 更新配置
|
|
116
|
+
* @param config 新的AI模型配置
|
|
117
|
+
*/
|
|
118
|
+
updateConfig(config: AIModelConfig): void {
|
|
119
|
+
super.updateConfig(config)
|
|
120
|
+
|
|
121
|
+
// 更新配置
|
|
122
|
+
if (config.apiUrl) {
|
|
123
|
+
this.baseURL = config.apiUrl
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (config.apiKey) {
|
|
127
|
+
this.apiKey = config.apiKey
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (config.defaultModel) {
|
|
131
|
+
this.defaultModel = config.defaultModel
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息角色类型
|
|
3
|
+
*/
|
|
4
|
+
export type MessageRole = 'system' | 'user' | 'assistant'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 聊天消息接口
|
|
8
|
+
*/
|
|
9
|
+
export interface ChatMessage {
|
|
10
|
+
role: MessageRole
|
|
11
|
+
content: string
|
|
12
|
+
name?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 聊天历史记录
|
|
17
|
+
*/
|
|
18
|
+
export type ChatHistory = ChatMessage[]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 聊天完成请求选项
|
|
22
|
+
*/
|
|
23
|
+
export interface ChatCompletionOptions {
|
|
24
|
+
model?: string
|
|
25
|
+
temperature?: number
|
|
26
|
+
top_p?: number
|
|
27
|
+
n?: number
|
|
28
|
+
stream?: boolean
|
|
29
|
+
max_tokens?: number
|
|
30
|
+
signal?: AbortSignal
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 聊天完成请求参数
|
|
35
|
+
*/
|
|
36
|
+
export interface ChatCompletionRequest {
|
|
37
|
+
messages: ChatMessage[]
|
|
38
|
+
options?: ChatCompletionOptions
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 聊天完成响应消息
|
|
43
|
+
*/
|
|
44
|
+
export interface ChatCompletionResponseMessage {
|
|
45
|
+
role: MessageRole
|
|
46
|
+
content: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 聊天完成响应选择
|
|
51
|
+
*/
|
|
52
|
+
export interface ChatCompletionResponseChoice {
|
|
53
|
+
index: number
|
|
54
|
+
message: ChatCompletionResponseMessage
|
|
55
|
+
finish_reason: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 聊天完成响应使用情况
|
|
60
|
+
*/
|
|
61
|
+
export interface ChatCompletionResponseUsage {
|
|
62
|
+
prompt_tokens: number
|
|
63
|
+
completion_tokens: number
|
|
64
|
+
total_tokens: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 聊天完成响应
|
|
69
|
+
*/
|
|
70
|
+
export interface ChatCompletionResponse {
|
|
71
|
+
id: string
|
|
72
|
+
object: string
|
|
73
|
+
created: number
|
|
74
|
+
model: string
|
|
75
|
+
choices: ChatCompletionResponseChoice[]
|
|
76
|
+
usage: ChatCompletionResponseUsage
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 流式聊天完成响应增量
|
|
81
|
+
*/
|
|
82
|
+
export interface ChatCompletionStreamResponseDelta {
|
|
83
|
+
content?: string
|
|
84
|
+
role?: MessageRole
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 流式聊天完成响应选择
|
|
89
|
+
*/
|
|
90
|
+
export interface ChatCompletionStreamResponseChoice {
|
|
91
|
+
index: number
|
|
92
|
+
delta: ChatCompletionStreamResponseDelta
|
|
93
|
+
finish_reason: string | null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 流式聊天完成响应
|
|
98
|
+
*/
|
|
99
|
+
export interface ChatCompletionStreamResponse {
|
|
100
|
+
id: string
|
|
101
|
+
object: string
|
|
102
|
+
created: number
|
|
103
|
+
model: string
|
|
104
|
+
choices: ChatCompletionStreamResponseChoice[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* AI模型提供商类型
|
|
109
|
+
*/
|
|
110
|
+
export type AIProvider = 'openai' | 'deepseek' | 'custom'
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* AI模型配置接口
|
|
114
|
+
*/
|
|
115
|
+
export interface AIModelConfig {
|
|
116
|
+
provider: AIProvider
|
|
117
|
+
apiKey?: string
|
|
118
|
+
apiUrl?: string
|
|
119
|
+
apiVersion?: string
|
|
120
|
+
defaultModel?: string
|
|
121
|
+
defaultOptions?: ChatCompletionOptions
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 错误类型
|
|
126
|
+
*/
|
|
127
|
+
export enum ErrorType {
|
|
128
|
+
NETWORK_ERROR = 'network_error',
|
|
129
|
+
AUTHENTICATION_ERROR = 'authentication_error',
|
|
130
|
+
RATE_LIMIT_ERROR = 'rate_limit_error',
|
|
131
|
+
SERVER_ERROR = 'server_error',
|
|
132
|
+
MODEL_ERROR = 'model_error',
|
|
133
|
+
TIMEOUT_ERROR = 'timeout_error',
|
|
134
|
+
UNKNOWN_ERROR = 'unknown_error',
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* AI适配器错误
|
|
139
|
+
*/
|
|
140
|
+
export interface AIAdapterError {
|
|
141
|
+
type: ErrorType
|
|
142
|
+
message: string
|
|
143
|
+
statusCode?: number
|
|
144
|
+
originalError?: object
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 流式响应事件类型
|
|
149
|
+
*/
|
|
150
|
+
export enum StreamEventType {
|
|
151
|
+
DATA = 'data',
|
|
152
|
+
ERROR = 'error',
|
|
153
|
+
DONE = 'done',
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 流式响应处理器
|
|
158
|
+
*/
|
|
159
|
+
export interface StreamHandler {
|
|
160
|
+
onData: (data: ChatCompletionStreamResponse) => void
|
|
161
|
+
onError: (error: AIAdapterError) => void
|
|
162
|
+
onDone: () => void
|
|
163
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
}
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useMessage'
|