@opentiny/tiny-robot-kit 0.2.0-alpha.0 → 0.2.0-alpha.1
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 +125 -0
- package/dist/index.d.mts +103 -1
- package/dist/index.d.ts +103 -1
- package/dist/index.js +211 -10
- package/dist/index.mjs +209 -10
- package/package.json +2 -2
- package/src/providers/openai.ts +13 -8
- package/src/vue/conversation/useConversation.ts +365 -0
- package/src/vue/index.ts +2 -1
- package/src/vue/{useMessage.ts → message/useMessage.ts} +3 -3
package/README.md
CHANGED
|
@@ -162,3 +162,128 @@ enum STATUS {
|
|
|
162
162
|
ERROR = 'error', // AI请求发生错误
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
|
+
|
|
166
|
+
### useConversation
|
|
167
|
+
|
|
168
|
+
对话管理与数据持久化
|
|
169
|
+
|
|
170
|
+
#### 基本用法
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { useConversation, AIClient } from '@opentiny/tiny-robot-kit'
|
|
174
|
+
|
|
175
|
+
const client = new AIClient({
|
|
176
|
+
provider: 'openai',
|
|
177
|
+
apiKey: 'your-api-key',
|
|
178
|
+
defaultModel: 'gpt-3.5-turbo'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const {
|
|
182
|
+
state,
|
|
183
|
+
messageManager, // 与 useMessage 返回一致,具体查看useMessage
|
|
184
|
+
createConversation,
|
|
185
|
+
switchConversation,
|
|
186
|
+
deleteConversation,
|
|
187
|
+
// ...
|
|
188
|
+
} = useConversation({ client })
|
|
189
|
+
|
|
190
|
+
const conversationId = createConversation('新对话')
|
|
191
|
+
|
|
192
|
+
messageManager.sendMessage('你好,请介绍一下自己')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### 返回值
|
|
196
|
+
|
|
197
|
+
`useConversation` 返回以下内容:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
interface UseConversationReturn {
|
|
201
|
+
/** 会话状态 */
|
|
202
|
+
state: ConversationState;
|
|
203
|
+
/** 消息管理 */
|
|
204
|
+
messageManager: UseMessageReturn;
|
|
205
|
+
/** 创建新会话 */
|
|
206
|
+
createConversation: (title?: string, metadata?: Record<string, any>) => string;
|
|
207
|
+
/** 切换会话 */
|
|
208
|
+
switchConversation: (id: string) => void;
|
|
209
|
+
/** 删除会话 */
|
|
210
|
+
deleteConversation: (id: string) => void;
|
|
211
|
+
/** 更新会话标题 */
|
|
212
|
+
updateTitle: (id: string, title: string) => void;
|
|
213
|
+
/** 更新会话元数据 */
|
|
214
|
+
updateMetadata: (id: string, metadata: Record<string, any>) => void;
|
|
215
|
+
/** 保存会话 */
|
|
216
|
+
saveConversations: () => Promise<void>;
|
|
217
|
+
/** 加载会话 */
|
|
218
|
+
loadConversations: () => Promise<void>;
|
|
219
|
+
/** 生成会话标题 */
|
|
220
|
+
generateTitle: (id: string) => Promise<string>;
|
|
221
|
+
/** 获取当前会话 */
|
|
222
|
+
getCurrentConversation: () => Conversation | null;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### 会话状态
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
interface ConversationState {
|
|
230
|
+
/** 会话列表 */
|
|
231
|
+
conversations: Conversation[];
|
|
232
|
+
/** 当前会话ID */
|
|
233
|
+
currentId: string | null;
|
|
234
|
+
/** 是否正在加载 */
|
|
235
|
+
loading: boolean;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### 会话接口
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
|
|
243
|
+
interface Conversation {
|
|
244
|
+
/** 会话ID */
|
|
245
|
+
id: string;
|
|
246
|
+
/** 会话标题 */
|
|
247
|
+
title: string;
|
|
248
|
+
/** 创建时间 */
|
|
249
|
+
createdAt: number;
|
|
250
|
+
/** 更新时间 */
|
|
251
|
+
updatedAt: number;
|
|
252
|
+
/** 自定义元数据 */
|
|
253
|
+
metadata?: Record<string, any>;
|
|
254
|
+
/** 消息 */
|
|
255
|
+
messages: ChatMessage[];
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### 自定义存储策略
|
|
260
|
+
|
|
261
|
+
默认使用 LocalStorage 存储会话数据,你也可以实现自定义的存储策略:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
interface ConversationStorageStrategy {
|
|
265
|
+
/** 保存会话列表 */
|
|
266
|
+
saveConversations: (conversations: Conversation[]) => Promise<void> | void;
|
|
267
|
+
/** 加载会话列表 */
|
|
268
|
+
loadConversations: () => Promise<Conversation[]> | Conversation[];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 自定义存储策略示例
|
|
272
|
+
class CustomStorageStrategy implements ConversationStorageStrategy {
|
|
273
|
+
async saveConversations(conversations: Conversation[]) {
|
|
274
|
+
// 实现自定义存储逻辑
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async loadConversations(): Promise<Conversation[]> {
|
|
278
|
+
// 实现自定义加载逻辑
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 使用自定义存储策略
|
|
284
|
+
const conversationManager = useConversation({
|
|
285
|
+
client,
|
|
286
|
+
storage: new CustomStorageStrategy(),
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
package/dist/index.d.mts
CHANGED
|
@@ -348,4 +348,106 @@ interface UseMessageReturn {
|
|
|
348
348
|
*/
|
|
349
349
|
declare function useMessage(options: UseMessageOptions): UseMessageReturn;
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
/**
|
|
352
|
+
* useConversation composable
|
|
353
|
+
* 提供会话管理和持久化功能
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 会话接口
|
|
358
|
+
*/
|
|
359
|
+
interface Conversation {
|
|
360
|
+
/** 会话ID */
|
|
361
|
+
id: string;
|
|
362
|
+
/** 会话标题 */
|
|
363
|
+
title: string;
|
|
364
|
+
/** 创建时间 */
|
|
365
|
+
createdAt: number;
|
|
366
|
+
/** 更新时间 */
|
|
367
|
+
updatedAt: number;
|
|
368
|
+
/** 自定义元数据 */
|
|
369
|
+
metadata?: Record<string, unknown>;
|
|
370
|
+
/** 会话消息 */
|
|
371
|
+
messages: ChatMessage[];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 存储策略接口
|
|
375
|
+
*/
|
|
376
|
+
interface ConversationStorageStrategy {
|
|
377
|
+
/** 保存会话列表 */
|
|
378
|
+
saveConversations: (conversations: Conversation[]) => Promise<void> | void;
|
|
379
|
+
/** 加载会话列表 */
|
|
380
|
+
loadConversations: () => Promise<Conversation[]> | Conversation[];
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* 本地存储策略
|
|
384
|
+
*/
|
|
385
|
+
declare class LocalStorageStrategy implements ConversationStorageStrategy {
|
|
386
|
+
private storageKey;
|
|
387
|
+
constructor(storageKey?: string);
|
|
388
|
+
saveConversations(conversations: Conversation[]): void;
|
|
389
|
+
loadConversations(): Conversation[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 会话状态接口
|
|
393
|
+
*/
|
|
394
|
+
interface ConversationState {
|
|
395
|
+
/** 会话列表 */
|
|
396
|
+
conversations: Conversation[];
|
|
397
|
+
/** 当前会话ID */
|
|
398
|
+
currentId: string | null;
|
|
399
|
+
/** 是否正在加载 */
|
|
400
|
+
loading: boolean;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* useConversation选项接口
|
|
404
|
+
*/
|
|
405
|
+
interface UseConversationOptions {
|
|
406
|
+
/** AI客户端实例 */
|
|
407
|
+
client: AIClient;
|
|
408
|
+
/** 存储策略 */
|
|
409
|
+
storage?: ConversationStorageStrategy;
|
|
410
|
+
/** 是否自动保存 */
|
|
411
|
+
autoSave?: boolean;
|
|
412
|
+
/** 是否默认使用流式响应 */
|
|
413
|
+
useStreamByDefault?: boolean;
|
|
414
|
+
/** 错误消息模板 */
|
|
415
|
+
errorMessage?: string;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* useConversation返回值接口
|
|
419
|
+
*/
|
|
420
|
+
interface UseConversationReturn {
|
|
421
|
+
/** 会话状态 */
|
|
422
|
+
state: ConversationState;
|
|
423
|
+
/** 消息管理 */
|
|
424
|
+
messageManager: UseMessageReturn;
|
|
425
|
+
/** 创建新会话 */
|
|
426
|
+
createConversation: (title?: string, metadata?: Record<string, unknown>) => string;
|
|
427
|
+
/** 切换会话 */
|
|
428
|
+
switchConversation: (id: string) => void;
|
|
429
|
+
/** 删除会话 */
|
|
430
|
+
deleteConversation: (id: string) => void;
|
|
431
|
+
/** 更新会话标题 */
|
|
432
|
+
updateTitle: (id: string, title: string) => void;
|
|
433
|
+
/** 更新会话元数据 */
|
|
434
|
+
updateMetadata: (id: string, metadata: Record<string, unknown>) => void;
|
|
435
|
+
/** 保存会话 */
|
|
436
|
+
saveConversations: () => Promise<void>;
|
|
437
|
+
/** 加载会话 */
|
|
438
|
+
loadConversations: () => Promise<void>;
|
|
439
|
+
/** 生成会话标题 */
|
|
440
|
+
generateTitle: (id: string) => Promise<string>;
|
|
441
|
+
/** 获取当前会话 */
|
|
442
|
+
getCurrentConversation: () => Conversation | null;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* useConversation composable
|
|
446
|
+
* 提供会话管理和持久化功能
|
|
447
|
+
*
|
|
448
|
+
* @param options useConversation选项
|
|
449
|
+
* @returns UseConversationReturn
|
|
450
|
+
*/
|
|
451
|
+
declare function useConversation(options: UseConversationOptions): UseConversationReturn;
|
|
452
|
+
|
|
453
|
+
export { type AIAdapterError, AIClient, type AIModelConfig, type AIProvider, BaseModelProvider, type ChatCompletionOptions, type ChatCompletionRequest, type ChatCompletionResponse, type ChatCompletionResponseChoice, type ChatCompletionResponseMessage, type ChatCompletionResponseUsage, type ChatCompletionStreamResponse, type ChatCompletionStreamResponseChoice, type ChatCompletionStreamResponseDelta, type ChatHistory, type ChatMessage, type Conversation, type ConversationState, type ConversationStorageStrategy, ErrorType, FinalStatus, GeneratingStatus, LocalStorageStrategy, type MessageRole, type MessageState, OpenAIProvider, STATUS, StreamEventType, type StreamHandler, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessageReturn, extractTextFromResponse, formatMessages, useConversation, useMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -348,4 +348,106 @@ interface UseMessageReturn {
|
|
|
348
348
|
*/
|
|
349
349
|
declare function useMessage(options: UseMessageOptions): UseMessageReturn;
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
/**
|
|
352
|
+
* useConversation composable
|
|
353
|
+
* 提供会话管理和持久化功能
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 会话接口
|
|
358
|
+
*/
|
|
359
|
+
interface Conversation {
|
|
360
|
+
/** 会话ID */
|
|
361
|
+
id: string;
|
|
362
|
+
/** 会话标题 */
|
|
363
|
+
title: string;
|
|
364
|
+
/** 创建时间 */
|
|
365
|
+
createdAt: number;
|
|
366
|
+
/** 更新时间 */
|
|
367
|
+
updatedAt: number;
|
|
368
|
+
/** 自定义元数据 */
|
|
369
|
+
metadata?: Record<string, unknown>;
|
|
370
|
+
/** 会话消息 */
|
|
371
|
+
messages: ChatMessage[];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 存储策略接口
|
|
375
|
+
*/
|
|
376
|
+
interface ConversationStorageStrategy {
|
|
377
|
+
/** 保存会话列表 */
|
|
378
|
+
saveConversations: (conversations: Conversation[]) => Promise<void> | void;
|
|
379
|
+
/** 加载会话列表 */
|
|
380
|
+
loadConversations: () => Promise<Conversation[]> | Conversation[];
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* 本地存储策略
|
|
384
|
+
*/
|
|
385
|
+
declare class LocalStorageStrategy implements ConversationStorageStrategy {
|
|
386
|
+
private storageKey;
|
|
387
|
+
constructor(storageKey?: string);
|
|
388
|
+
saveConversations(conversations: Conversation[]): void;
|
|
389
|
+
loadConversations(): Conversation[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 会话状态接口
|
|
393
|
+
*/
|
|
394
|
+
interface ConversationState {
|
|
395
|
+
/** 会话列表 */
|
|
396
|
+
conversations: Conversation[];
|
|
397
|
+
/** 当前会话ID */
|
|
398
|
+
currentId: string | null;
|
|
399
|
+
/** 是否正在加载 */
|
|
400
|
+
loading: boolean;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* useConversation选项接口
|
|
404
|
+
*/
|
|
405
|
+
interface UseConversationOptions {
|
|
406
|
+
/** AI客户端实例 */
|
|
407
|
+
client: AIClient;
|
|
408
|
+
/** 存储策略 */
|
|
409
|
+
storage?: ConversationStorageStrategy;
|
|
410
|
+
/** 是否自动保存 */
|
|
411
|
+
autoSave?: boolean;
|
|
412
|
+
/** 是否默认使用流式响应 */
|
|
413
|
+
useStreamByDefault?: boolean;
|
|
414
|
+
/** 错误消息模板 */
|
|
415
|
+
errorMessage?: string;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* useConversation返回值接口
|
|
419
|
+
*/
|
|
420
|
+
interface UseConversationReturn {
|
|
421
|
+
/** 会话状态 */
|
|
422
|
+
state: ConversationState;
|
|
423
|
+
/** 消息管理 */
|
|
424
|
+
messageManager: UseMessageReturn;
|
|
425
|
+
/** 创建新会话 */
|
|
426
|
+
createConversation: (title?: string, metadata?: Record<string, unknown>) => string;
|
|
427
|
+
/** 切换会话 */
|
|
428
|
+
switchConversation: (id: string) => void;
|
|
429
|
+
/** 删除会话 */
|
|
430
|
+
deleteConversation: (id: string) => void;
|
|
431
|
+
/** 更新会话标题 */
|
|
432
|
+
updateTitle: (id: string, title: string) => void;
|
|
433
|
+
/** 更新会话元数据 */
|
|
434
|
+
updateMetadata: (id: string, metadata: Record<string, unknown>) => void;
|
|
435
|
+
/** 保存会话 */
|
|
436
|
+
saveConversations: () => Promise<void>;
|
|
437
|
+
/** 加载会话 */
|
|
438
|
+
loadConversations: () => Promise<void>;
|
|
439
|
+
/** 生成会话标题 */
|
|
440
|
+
generateTitle: (id: string) => Promise<string>;
|
|
441
|
+
/** 获取当前会话 */
|
|
442
|
+
getCurrentConversation: () => Conversation | null;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* useConversation composable
|
|
446
|
+
* 提供会话管理和持久化功能
|
|
447
|
+
*
|
|
448
|
+
* @param options useConversation选项
|
|
449
|
+
* @returns UseConversationReturn
|
|
450
|
+
*/
|
|
451
|
+
declare function useConversation(options: UseConversationOptions): UseConversationReturn;
|
|
452
|
+
|
|
453
|
+
export { type AIAdapterError, AIClient, type AIModelConfig, type AIProvider, BaseModelProvider, type ChatCompletionOptions, type ChatCompletionRequest, type ChatCompletionResponse, type ChatCompletionResponseChoice, type ChatCompletionResponseMessage, type ChatCompletionResponseUsage, type ChatCompletionStreamResponse, type ChatCompletionStreamResponseChoice, type ChatCompletionStreamResponseDelta, type ChatHistory, type ChatMessage, type Conversation, type ConversationState, type ConversationStorageStrategy, ErrorType, FinalStatus, GeneratingStatus, LocalStorageStrategy, type MessageRole, type MessageState, OpenAIProvider, STATUS, StreamEventType, type StreamHandler, type UseConversationOptions, type UseConversationReturn, type UseMessageOptions, type UseMessageReturn, extractTextFromResponse, formatMessages, useConversation, useMessage };
|
package/dist/index.js
CHANGED
|
@@ -25,11 +25,13 @@ __export(index_exports, {
|
|
|
25
25
|
ErrorType: () => ErrorType,
|
|
26
26
|
FinalStatus: () => FinalStatus,
|
|
27
27
|
GeneratingStatus: () => GeneratingStatus,
|
|
28
|
+
LocalStorageStrategy: () => LocalStorageStrategy,
|
|
28
29
|
OpenAIProvider: () => OpenAIProvider,
|
|
29
30
|
STATUS: () => STATUS,
|
|
30
31
|
StreamEventType: () => StreamEventType,
|
|
31
32
|
extractTextFromResponse: () => extractTextFromResponse,
|
|
32
33
|
formatMessages: () => formatMessages,
|
|
34
|
+
useConversation: () => useConversation,
|
|
33
35
|
useMessage: () => useMessage
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -266,14 +268,15 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
266
268
|
...request.options,
|
|
267
269
|
stream: false
|
|
268
270
|
};
|
|
269
|
-
const
|
|
271
|
+
const options = {
|
|
270
272
|
method: "POST",
|
|
271
|
-
headers: {
|
|
272
|
-
"Content-Type": "application/json",
|
|
273
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
274
|
-
},
|
|
273
|
+
headers: { "Content-Type": "application/json" },
|
|
275
274
|
body: JSON.stringify(requestData)
|
|
276
|
-
}
|
|
275
|
+
};
|
|
276
|
+
if (this.apiKey) {
|
|
277
|
+
Object.assign(options.headers, { Authorization: `Bearer ${this.apiKey}` });
|
|
278
|
+
}
|
|
279
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, options);
|
|
277
280
|
if (!response.ok) {
|
|
278
281
|
const errorText = await response.text();
|
|
279
282
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
|
|
@@ -298,7 +301,7 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
298
301
|
...options,
|
|
299
302
|
stream: true
|
|
300
303
|
};
|
|
301
|
-
const
|
|
304
|
+
const requestOptions = {
|
|
302
305
|
method: "POST",
|
|
303
306
|
headers: {
|
|
304
307
|
"Content-Type": "application/json",
|
|
@@ -307,7 +310,11 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
307
310
|
},
|
|
308
311
|
body: JSON.stringify(requestData),
|
|
309
312
|
signal
|
|
310
|
-
}
|
|
313
|
+
};
|
|
314
|
+
if (this.apiKey) {
|
|
315
|
+
Object.assign(requestOptions.headers, { Authorization: `Bearer ${this.apiKey}` });
|
|
316
|
+
}
|
|
317
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, requestOptions);
|
|
311
318
|
if (!response.ok) {
|
|
312
319
|
const errorText = await response.text();
|
|
313
320
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
|
|
@@ -411,7 +418,7 @@ var AIClient = class {
|
|
|
411
418
|
}
|
|
412
419
|
};
|
|
413
420
|
|
|
414
|
-
// src/vue/useMessage.ts
|
|
421
|
+
// src/vue/message/useMessage.ts
|
|
415
422
|
var import_vue = require("vue");
|
|
416
423
|
var STATUS = /* @__PURE__ */ ((STATUS2) => {
|
|
417
424
|
STATUS2["INIT"] = "init";
|
|
@@ -463,7 +470,7 @@ function useMessage(options) {
|
|
|
463
470
|
if (messages.value[messages.value.length - 1].role === "user") {
|
|
464
471
|
messages.value.push({ role: "assistant", content: "" });
|
|
465
472
|
}
|
|
466
|
-
const choice = data.choices[0];
|
|
473
|
+
const choice = data.choices?.[0];
|
|
467
474
|
if (choice && choice.delta.content) {
|
|
468
475
|
messages.value[messages.value.length - 1].content += choice.delta.content;
|
|
469
476
|
}
|
|
@@ -545,6 +552,198 @@ function useMessage(options) {
|
|
|
545
552
|
retryRequest
|
|
546
553
|
};
|
|
547
554
|
}
|
|
555
|
+
|
|
556
|
+
// src/vue/conversation/useConversation.ts
|
|
557
|
+
var import_vue2 = require("vue");
|
|
558
|
+
var LocalStorageStrategy = class {
|
|
559
|
+
constructor(storageKey = "tiny-robot-ai-conversations") {
|
|
560
|
+
this.storageKey = storageKey;
|
|
561
|
+
}
|
|
562
|
+
saveConversations(conversations) {
|
|
563
|
+
try {
|
|
564
|
+
localStorage.setItem(this.storageKey, JSON.stringify(conversations));
|
|
565
|
+
} catch (error) {
|
|
566
|
+
console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
loadConversations() {
|
|
570
|
+
try {
|
|
571
|
+
const data = localStorage.getItem(this.storageKey);
|
|
572
|
+
return data ? JSON.parse(data) : [];
|
|
573
|
+
} catch (error) {
|
|
574
|
+
console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
575
|
+
return [];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
function generateId() {
|
|
580
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
|
|
581
|
+
}
|
|
582
|
+
function useConversation(options) {
|
|
583
|
+
const {
|
|
584
|
+
client,
|
|
585
|
+
storage = new LocalStorageStrategy(),
|
|
586
|
+
autoSave = true,
|
|
587
|
+
useStreamByDefault = true,
|
|
588
|
+
errorMessage = "\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
|
|
589
|
+
} = options;
|
|
590
|
+
const state = (0, import_vue2.reactive)({
|
|
591
|
+
conversations: [],
|
|
592
|
+
currentId: null,
|
|
593
|
+
loading: false
|
|
594
|
+
});
|
|
595
|
+
const messageManager = useMessage({
|
|
596
|
+
client,
|
|
597
|
+
useStreamByDefault,
|
|
598
|
+
errorMessage,
|
|
599
|
+
initialMessages: []
|
|
600
|
+
});
|
|
601
|
+
(0, import_vue2.watch)(
|
|
602
|
+
() => messageManager.messages.value,
|
|
603
|
+
(messages) => {
|
|
604
|
+
if (state.currentId && messages.length > 0) {
|
|
605
|
+
const index = state.conversations.findIndex((row) => row.id === state.currentId);
|
|
606
|
+
if (index !== -1) {
|
|
607
|
+
state.conversations[index].messages = [...messages];
|
|
608
|
+
state.conversations[index].updatedAt = Date.now();
|
|
609
|
+
if (autoSave) {
|
|
610
|
+
saveConversations();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
{ deep: true }
|
|
616
|
+
);
|
|
617
|
+
const createConversation = (title = "\u65B0\u4F1A\u8BDD", metadata = {}) => {
|
|
618
|
+
const id = generateId();
|
|
619
|
+
const newConversation = {
|
|
620
|
+
id,
|
|
621
|
+
title,
|
|
622
|
+
createdAt: Date.now(),
|
|
623
|
+
updatedAt: Date.now(),
|
|
624
|
+
messages: [],
|
|
625
|
+
metadata
|
|
626
|
+
};
|
|
627
|
+
state.conversations.unshift(newConversation);
|
|
628
|
+
switchConversation(id);
|
|
629
|
+
if (autoSave) {
|
|
630
|
+
saveConversations();
|
|
631
|
+
}
|
|
632
|
+
return id;
|
|
633
|
+
};
|
|
634
|
+
const switchConversation = (id) => {
|
|
635
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
636
|
+
if (conversation) {
|
|
637
|
+
state.currentId = id;
|
|
638
|
+
messageManager.clearMessages();
|
|
639
|
+
if (conversation.messages.length > 0) {
|
|
640
|
+
conversation.messages.forEach((msg) => messageManager.addMessage(msg));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
const deleteConversation = (id) => {
|
|
645
|
+
const index = state.conversations.findIndex((conv) => conv.id === id);
|
|
646
|
+
if (index !== -1) {
|
|
647
|
+
state.conversations.splice(index, 1);
|
|
648
|
+
if (state.currentId === id) {
|
|
649
|
+
if (state.conversations.length > 0) {
|
|
650
|
+
switchConversation(state.conversations[0].id);
|
|
651
|
+
} else {
|
|
652
|
+
state.currentId = null;
|
|
653
|
+
messageManager.clearMessages();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (autoSave) {
|
|
657
|
+
saveConversations();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const updateTitle = (id, title) => {
|
|
662
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
663
|
+
if (conversation) {
|
|
664
|
+
conversation.title = title;
|
|
665
|
+
conversation.updatedAt = Date.now();
|
|
666
|
+
if (autoSave) {
|
|
667
|
+
saveConversations();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
const updateMetadata = (id, metadata) => {
|
|
672
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
673
|
+
if (conversation) {
|
|
674
|
+
conversation.metadata = { ...conversation.metadata, ...metadata };
|
|
675
|
+
conversation.updatedAt = Date.now();
|
|
676
|
+
if (autoSave) {
|
|
677
|
+
saveConversations();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
const saveConversations = async () => {
|
|
682
|
+
try {
|
|
683
|
+
await storage.saveConversations(state.conversations);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
const loadConversations = async () => {
|
|
689
|
+
state.loading = true;
|
|
690
|
+
try {
|
|
691
|
+
const conversations = await storage.loadConversations();
|
|
692
|
+
state.conversations = conversations;
|
|
693
|
+
if (conversations.length > 0 && !state.currentId) {
|
|
694
|
+
switchConversation(conversations[0].id);
|
|
695
|
+
}
|
|
696
|
+
} catch (error) {
|
|
697
|
+
console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
698
|
+
} finally {
|
|
699
|
+
state.loading = false;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
const generateTitle = async (id) => {
|
|
703
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
704
|
+
if (!conversation || conversation.messages.length < 2) {
|
|
705
|
+
return conversation?.title || "\u65B0\u4F1A\u8BDD";
|
|
706
|
+
}
|
|
707
|
+
try {
|
|
708
|
+
const prompt = {
|
|
709
|
+
role: "system",
|
|
710
|
+
content: "\u8BF7\u6839\u636E\u4EE5\u4E0B\u5BF9\u8BDD\u5185\u5BB9\uFF0C\u751F\u6210\u4E00\u4E2A\u7B80\u77ED\u7684\u6807\u9898\uFF08\u4E0D\u8D85\u8FC720\u4E2A\u5B57\u7B26\uFF09\u3002\u53EA\u9700\u8981\u8FD4\u56DE\u6807\u9898\u6587\u672C\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u89E3\u91CA\u6216\u989D\u5916\u5185\u5BB9\u3002"
|
|
711
|
+
};
|
|
712
|
+
const contextMessages = conversation.messages.slice(0, Math.min(4, conversation.messages.length));
|
|
713
|
+
const response = await client.chat({
|
|
714
|
+
messages: [prompt, ...contextMessages],
|
|
715
|
+
options: {
|
|
716
|
+
stream: false,
|
|
717
|
+
max_tokens: 30
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
const title = response.choices[0].message.content.trim();
|
|
721
|
+
updateTitle(id, title);
|
|
722
|
+
return title;
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.error("\u751F\u6210\u6807\u9898\u5931\u8D25:", error);
|
|
725
|
+
return conversation.title;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const getCurrentConversation = () => {
|
|
729
|
+
if (!state.currentId) return null;
|
|
730
|
+
return state.conversations.find((conv) => conv.id === state.currentId) || null;
|
|
731
|
+
};
|
|
732
|
+
loadConversations();
|
|
733
|
+
return {
|
|
734
|
+
state,
|
|
735
|
+
messageManager,
|
|
736
|
+
createConversation,
|
|
737
|
+
switchConversation,
|
|
738
|
+
deleteConversation,
|
|
739
|
+
updateTitle,
|
|
740
|
+
updateMetadata,
|
|
741
|
+
saveConversations,
|
|
742
|
+
loadConversations,
|
|
743
|
+
generateTitle,
|
|
744
|
+
getCurrentConversation
|
|
745
|
+
};
|
|
746
|
+
}
|
|
548
747
|
// Annotate the CommonJS export names for ESM import in node:
|
|
549
748
|
0 && (module.exports = {
|
|
550
749
|
AIClient,
|
|
@@ -552,10 +751,12 @@ function useMessage(options) {
|
|
|
552
751
|
ErrorType,
|
|
553
752
|
FinalStatus,
|
|
554
753
|
GeneratingStatus,
|
|
754
|
+
LocalStorageStrategy,
|
|
555
755
|
OpenAIProvider,
|
|
556
756
|
STATUS,
|
|
557
757
|
StreamEventType,
|
|
558
758
|
extractTextFromResponse,
|
|
559
759
|
formatMessages,
|
|
760
|
+
useConversation,
|
|
560
761
|
useMessage
|
|
561
762
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -230,14 +230,15 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
230
230
|
...request.options,
|
|
231
231
|
stream: false
|
|
232
232
|
};
|
|
233
|
-
const
|
|
233
|
+
const options = {
|
|
234
234
|
method: "POST",
|
|
235
|
-
headers: {
|
|
236
|
-
"Content-Type": "application/json",
|
|
237
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
238
|
-
},
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
239
236
|
body: JSON.stringify(requestData)
|
|
240
|
-
}
|
|
237
|
+
};
|
|
238
|
+
if (this.apiKey) {
|
|
239
|
+
Object.assign(options.headers, { Authorization: `Bearer ${this.apiKey}` });
|
|
240
|
+
}
|
|
241
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, options);
|
|
241
242
|
if (!response.ok) {
|
|
242
243
|
const errorText = await response.text();
|
|
243
244
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
|
|
@@ -262,7 +263,7 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
262
263
|
...options,
|
|
263
264
|
stream: true
|
|
264
265
|
};
|
|
265
|
-
const
|
|
266
|
+
const requestOptions = {
|
|
266
267
|
method: "POST",
|
|
267
268
|
headers: {
|
|
268
269
|
"Content-Type": "application/json",
|
|
@@ -271,7 +272,11 @@ var OpenAIProvider = class extends BaseModelProvider {
|
|
|
271
272
|
},
|
|
272
273
|
body: JSON.stringify(requestData),
|
|
273
274
|
signal
|
|
274
|
-
}
|
|
275
|
+
};
|
|
276
|
+
if (this.apiKey) {
|
|
277
|
+
Object.assign(requestOptions.headers, { Authorization: `Bearer ${this.apiKey}` });
|
|
278
|
+
}
|
|
279
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, requestOptions);
|
|
275
280
|
if (!response.ok) {
|
|
276
281
|
const errorText = await response.text();
|
|
277
282
|
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
|
|
@@ -375,7 +380,7 @@ var AIClient = class {
|
|
|
375
380
|
}
|
|
376
381
|
};
|
|
377
382
|
|
|
378
|
-
// src/vue/useMessage.ts
|
|
383
|
+
// src/vue/message/useMessage.ts
|
|
379
384
|
import { reactive, ref, toRaw } from "vue";
|
|
380
385
|
var STATUS = /* @__PURE__ */ ((STATUS2) => {
|
|
381
386
|
STATUS2["INIT"] = "init";
|
|
@@ -427,7 +432,7 @@ function useMessage(options) {
|
|
|
427
432
|
if (messages.value[messages.value.length - 1].role === "user") {
|
|
428
433
|
messages.value.push({ role: "assistant", content: "" });
|
|
429
434
|
}
|
|
430
|
-
const choice = data.choices[0];
|
|
435
|
+
const choice = data.choices?.[0];
|
|
431
436
|
if (choice && choice.delta.content) {
|
|
432
437
|
messages.value[messages.value.length - 1].content += choice.delta.content;
|
|
433
438
|
}
|
|
@@ -509,16 +514,210 @@ function useMessage(options) {
|
|
|
509
514
|
retryRequest
|
|
510
515
|
};
|
|
511
516
|
}
|
|
517
|
+
|
|
518
|
+
// src/vue/conversation/useConversation.ts
|
|
519
|
+
import { reactive as reactive2, watch } from "vue";
|
|
520
|
+
var LocalStorageStrategy = class {
|
|
521
|
+
constructor(storageKey = "tiny-robot-ai-conversations") {
|
|
522
|
+
this.storageKey = storageKey;
|
|
523
|
+
}
|
|
524
|
+
saveConversations(conversations) {
|
|
525
|
+
try {
|
|
526
|
+
localStorage.setItem(this.storageKey, JSON.stringify(conversations));
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
loadConversations() {
|
|
532
|
+
try {
|
|
533
|
+
const data = localStorage.getItem(this.storageKey);
|
|
534
|
+
return data ? JSON.parse(data) : [];
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
function generateId() {
|
|
542
|
+
return Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
|
|
543
|
+
}
|
|
544
|
+
function useConversation(options) {
|
|
545
|
+
const {
|
|
546
|
+
client,
|
|
547
|
+
storage = new LocalStorageStrategy(),
|
|
548
|
+
autoSave = true,
|
|
549
|
+
useStreamByDefault = true,
|
|
550
|
+
errorMessage = "\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
|
|
551
|
+
} = options;
|
|
552
|
+
const state = reactive2({
|
|
553
|
+
conversations: [],
|
|
554
|
+
currentId: null,
|
|
555
|
+
loading: false
|
|
556
|
+
});
|
|
557
|
+
const messageManager = useMessage({
|
|
558
|
+
client,
|
|
559
|
+
useStreamByDefault,
|
|
560
|
+
errorMessage,
|
|
561
|
+
initialMessages: []
|
|
562
|
+
});
|
|
563
|
+
watch(
|
|
564
|
+
() => messageManager.messages.value,
|
|
565
|
+
(messages) => {
|
|
566
|
+
if (state.currentId && messages.length > 0) {
|
|
567
|
+
const index = state.conversations.findIndex((row) => row.id === state.currentId);
|
|
568
|
+
if (index !== -1) {
|
|
569
|
+
state.conversations[index].messages = [...messages];
|
|
570
|
+
state.conversations[index].updatedAt = Date.now();
|
|
571
|
+
if (autoSave) {
|
|
572
|
+
saveConversations();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
{ deep: true }
|
|
578
|
+
);
|
|
579
|
+
const createConversation = (title = "\u65B0\u4F1A\u8BDD", metadata = {}) => {
|
|
580
|
+
const id = generateId();
|
|
581
|
+
const newConversation = {
|
|
582
|
+
id,
|
|
583
|
+
title,
|
|
584
|
+
createdAt: Date.now(),
|
|
585
|
+
updatedAt: Date.now(),
|
|
586
|
+
messages: [],
|
|
587
|
+
metadata
|
|
588
|
+
};
|
|
589
|
+
state.conversations.unshift(newConversation);
|
|
590
|
+
switchConversation(id);
|
|
591
|
+
if (autoSave) {
|
|
592
|
+
saveConversations();
|
|
593
|
+
}
|
|
594
|
+
return id;
|
|
595
|
+
};
|
|
596
|
+
const switchConversation = (id) => {
|
|
597
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
598
|
+
if (conversation) {
|
|
599
|
+
state.currentId = id;
|
|
600
|
+
messageManager.clearMessages();
|
|
601
|
+
if (conversation.messages.length > 0) {
|
|
602
|
+
conversation.messages.forEach((msg) => messageManager.addMessage(msg));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const deleteConversation = (id) => {
|
|
607
|
+
const index = state.conversations.findIndex((conv) => conv.id === id);
|
|
608
|
+
if (index !== -1) {
|
|
609
|
+
state.conversations.splice(index, 1);
|
|
610
|
+
if (state.currentId === id) {
|
|
611
|
+
if (state.conversations.length > 0) {
|
|
612
|
+
switchConversation(state.conversations[0].id);
|
|
613
|
+
} else {
|
|
614
|
+
state.currentId = null;
|
|
615
|
+
messageManager.clearMessages();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (autoSave) {
|
|
619
|
+
saveConversations();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
const updateTitle = (id, title) => {
|
|
624
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
625
|
+
if (conversation) {
|
|
626
|
+
conversation.title = title;
|
|
627
|
+
conversation.updatedAt = Date.now();
|
|
628
|
+
if (autoSave) {
|
|
629
|
+
saveConversations();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const updateMetadata = (id, metadata) => {
|
|
634
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
635
|
+
if (conversation) {
|
|
636
|
+
conversation.metadata = { ...conversation.metadata, ...metadata };
|
|
637
|
+
conversation.updatedAt = Date.now();
|
|
638
|
+
if (autoSave) {
|
|
639
|
+
saveConversations();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
const saveConversations = async () => {
|
|
644
|
+
try {
|
|
645
|
+
await storage.saveConversations(state.conversations);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
const loadConversations = async () => {
|
|
651
|
+
state.loading = true;
|
|
652
|
+
try {
|
|
653
|
+
const conversations = await storage.loadConversations();
|
|
654
|
+
state.conversations = conversations;
|
|
655
|
+
if (conversations.length > 0 && !state.currentId) {
|
|
656
|
+
switchConversation(conversations[0].id);
|
|
657
|
+
}
|
|
658
|
+
} catch (error) {
|
|
659
|
+
console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
|
|
660
|
+
} finally {
|
|
661
|
+
state.loading = false;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const generateTitle = async (id) => {
|
|
665
|
+
const conversation = state.conversations.find((conv) => conv.id === id);
|
|
666
|
+
if (!conversation || conversation.messages.length < 2) {
|
|
667
|
+
return conversation?.title || "\u65B0\u4F1A\u8BDD";
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const prompt = {
|
|
671
|
+
role: "system",
|
|
672
|
+
content: "\u8BF7\u6839\u636E\u4EE5\u4E0B\u5BF9\u8BDD\u5185\u5BB9\uFF0C\u751F\u6210\u4E00\u4E2A\u7B80\u77ED\u7684\u6807\u9898\uFF08\u4E0D\u8D85\u8FC720\u4E2A\u5B57\u7B26\uFF09\u3002\u53EA\u9700\u8981\u8FD4\u56DE\u6807\u9898\u6587\u672C\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u89E3\u91CA\u6216\u989D\u5916\u5185\u5BB9\u3002"
|
|
673
|
+
};
|
|
674
|
+
const contextMessages = conversation.messages.slice(0, Math.min(4, conversation.messages.length));
|
|
675
|
+
const response = await client.chat({
|
|
676
|
+
messages: [prompt, ...contextMessages],
|
|
677
|
+
options: {
|
|
678
|
+
stream: false,
|
|
679
|
+
max_tokens: 30
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
const title = response.choices[0].message.content.trim();
|
|
683
|
+
updateTitle(id, title);
|
|
684
|
+
return title;
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error("\u751F\u6210\u6807\u9898\u5931\u8D25:", error);
|
|
687
|
+
return conversation.title;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
const getCurrentConversation = () => {
|
|
691
|
+
if (!state.currentId) return null;
|
|
692
|
+
return state.conversations.find((conv) => conv.id === state.currentId) || null;
|
|
693
|
+
};
|
|
694
|
+
loadConversations();
|
|
695
|
+
return {
|
|
696
|
+
state,
|
|
697
|
+
messageManager,
|
|
698
|
+
createConversation,
|
|
699
|
+
switchConversation,
|
|
700
|
+
deleteConversation,
|
|
701
|
+
updateTitle,
|
|
702
|
+
updateMetadata,
|
|
703
|
+
saveConversations,
|
|
704
|
+
loadConversations,
|
|
705
|
+
generateTitle,
|
|
706
|
+
getCurrentConversation
|
|
707
|
+
};
|
|
708
|
+
}
|
|
512
709
|
export {
|
|
513
710
|
AIClient,
|
|
514
711
|
BaseModelProvider,
|
|
515
712
|
ErrorType,
|
|
516
713
|
FinalStatus,
|
|
517
714
|
GeneratingStatus,
|
|
715
|
+
LocalStorageStrategy,
|
|
518
716
|
OpenAIProvider,
|
|
519
717
|
STATUS,
|
|
520
718
|
StreamEventType,
|
|
521
719
|
extractTextFromResponse,
|
|
522
720
|
formatMessages,
|
|
721
|
+
useConversation,
|
|
523
722
|
useMessage
|
|
524
723
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opentiny/tiny-robot-kit",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"vue": ">=3.0.0"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "3000951723ed8b1caa1361ebac131229bec9c237"
|
|
26
26
|
}
|
package/src/providers/openai.ts
CHANGED
|
@@ -48,14 +48,15 @@ export class OpenAIProvider extends BaseModelProvider {
|
|
|
48
48
|
stream: false,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const options = {
|
|
52
52
|
method: 'POST',
|
|
53
|
-
headers: {
|
|
54
|
-
'Content-Type': 'application/json',
|
|
55
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
56
|
-
},
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
54
|
body: JSON.stringify(requestData),
|
|
58
|
-
}
|
|
55
|
+
}
|
|
56
|
+
if (this.apiKey) {
|
|
57
|
+
Object.assign(options.headers, { Authorization: `Bearer ${this.apiKey}` })
|
|
58
|
+
}
|
|
59
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, options)
|
|
59
60
|
|
|
60
61
|
if (!response.ok) {
|
|
61
62
|
const errorText = await response.text()
|
|
@@ -88,7 +89,7 @@ export class OpenAIProvider extends BaseModelProvider {
|
|
|
88
89
|
stream: true,
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
const
|
|
92
|
+
const requestOptions = {
|
|
92
93
|
method: 'POST',
|
|
93
94
|
headers: {
|
|
94
95
|
'Content-Type': 'application/json',
|
|
@@ -97,7 +98,11 @@ export class OpenAIProvider extends BaseModelProvider {
|
|
|
97
98
|
},
|
|
98
99
|
body: JSON.stringify(requestData),
|
|
99
100
|
signal,
|
|
100
|
-
}
|
|
101
|
+
}
|
|
102
|
+
if (this.apiKey) {
|
|
103
|
+
Object.assign(requestOptions.headers, { Authorization: `Bearer ${this.apiKey}` })
|
|
104
|
+
}
|
|
105
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, requestOptions)
|
|
101
106
|
|
|
102
107
|
if (!response.ok) {
|
|
103
108
|
const errorText = await response.text()
|
|
@@ -0,0 +1,365 @@
|
|
|
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
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export * from './useMessage'
|
|
1
|
+
export * from './message/useMessage'
|
|
2
|
+
export * from './conversation/useConversation'
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { reactive, Reactive, ref, toRaw, type Ref } from 'vue'
|
|
7
|
-
import type { ChatMessage } from '
|
|
8
|
-
import type { AIClient } from '
|
|
7
|
+
import type { ChatMessage } from '../../types'
|
|
8
|
+
import type { AIClient } from '../../client'
|
|
9
9
|
|
|
10
10
|
export enum STATUS {
|
|
11
11
|
INIT = 'init', // 初始状态
|
|
@@ -125,7 +125,7 @@ export function useMessage(options: UseMessageOptions): UseMessageReturn {
|
|
|
125
125
|
if (messages.value[messages.value.length - 1].role === 'user') {
|
|
126
126
|
messages.value.push({ role: 'assistant', content: '' })
|
|
127
127
|
}
|
|
128
|
-
const choice = data.choices[0]
|
|
128
|
+
const choice = data.choices?.[0]
|
|
129
129
|
if (choice && choice.delta.content) {
|
|
130
130
|
messages.value[messages.value.length - 1].content += choice.delta.content
|
|
131
131
|
}
|