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

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 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
- 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, ErrorType, FinalStatus, GeneratingStatus, type MessageRole, type MessageState, OpenAIProvider, STATUS, StreamEventType, type StreamHandler, type UseMessageOptions, type UseMessageReturn, extractTextFromResponse, formatMessages, useMessage };
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
- 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, ErrorType, FinalStatus, GeneratingStatus, type MessageRole, type MessageState, OpenAIProvider, STATUS, StreamEventType, type StreamHandler, type UseMessageOptions, type UseMessageReturn, extractTextFromResponse, formatMessages, useMessage };
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 response = await fetch(`${this.baseURL}/chat/completions`, {
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 response = await fetch(`${this.baseURL}/chat/completions`, {
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 response = await fetch(`${this.baseURL}/chat/completions`, {
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 response = await fetch(`${this.baseURL}/chat/completions`, {
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.0",
3
+ "version": "0.2.0-alpha.2",
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": "3517724651afbbb05b1be20487a9f3341c06133c"
25
+ "gitHead": "f334d2ba1772fe0e2859d5f859980257e70f36e1"
26
26
  }
@@ -48,14 +48,15 @@ export class OpenAIProvider extends BaseModelProvider {
48
48
  stream: false,
49
49
  }
50
50
 
51
- const response = await fetch(`${this.baseURL}/chat/completions`, {
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 response = await fetch(`${this.baseURL}/chat/completions`, {
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 '../types'
8
- import type { AIClient } from '../client'
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
  }