@lobehub/lobehub 2.0.0-next.181 → 2.0.0-next.183

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/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.183](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.182...v2.0.0-next.183)
6
+
7
+ <sup>Released on **2025-12-31**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **store**: Clear new key data when switchTopic to new state.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **store**: Clear new key data when switchTopic to new state, closes [#11078](https://github.com/lobehub/lobe-chat/issues/11078) ([180ea14](https://github.com/lobehub/lobe-chat/commit/180ea14))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.182](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.181...v2.0.0-next.182)
31
+
32
+ <sup>Released on **2025-12-31**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: Brand new 2.0 ui for next.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **misc**: Brand new 2.0 ui for next ([e5d6d3d](https://github.com/lobehub/lobe-chat/commit/e5d6d3d))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.181](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.180...v2.0.0-next.181)
6
56
 
7
57
  <sup>Released on **2025-12-31**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-12-31",
5
+ "version": "2.0.0-next.183"
6
+ },
7
+ {
8
+ "children": {
9
+ "features": [
10
+ "Brand new 2.0 ui for next."
11
+ ]
12
+ },
13
+ "date": "2025-12-31",
14
+ "version": "2.0.0-next.182"
15
+ },
2
16
  {
3
17
  "children": {
4
18
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.181",
3
+ "version": "2.0.0-next.183",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -345,6 +345,160 @@ describe('topic action', () => {
345
345
  // Verify that the refreshMessages was called to update the messages
346
346
  expect(refreshMessagesSpy).toHaveBeenCalled();
347
347
  });
348
+
349
+ it('should support boolean as second parameter for backward compatibility', async () => {
350
+ const topicId = 'topic-id';
351
+ const { result } = renderHook(() => useChatStore());
352
+
353
+ const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages');
354
+
355
+ // Call with boolean (old API)
356
+ await act(async () => {
357
+ await result.current.switchTopic(topicId, true);
358
+ });
359
+
360
+ expect(useChatStore.getState().activeTopicId).toBe(topicId);
361
+ // Should not call refreshMessages when skipRefreshMessage is true
362
+ expect(refreshMessagesSpy).not.toHaveBeenCalled();
363
+ });
364
+
365
+ it('should support options object as second parameter', async () => {
366
+ const topicId = 'topic-id';
367
+ const { result } = renderHook(() => useChatStore());
368
+
369
+ const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages');
370
+
371
+ // Call with options object (new API)
372
+ await act(async () => {
373
+ await result.current.switchTopic(topicId, { skipRefreshMessage: true });
374
+ });
375
+
376
+ expect(useChatStore.getState().activeTopicId).toBe(topicId);
377
+ expect(refreshMessagesSpy).not.toHaveBeenCalled();
378
+ });
379
+
380
+ it('should clear new key data when switching to new state (main scope)', async () => {
381
+ const { result } = renderHook(() => useChatStore());
382
+ const activeAgentId = 'test-agent-id';
383
+ const newKey = messageMapKey({ agentId: activeAgentId, topicId: null });
384
+
385
+ // Setup initial state with some messages in the new key
386
+ await act(async () => {
387
+ useChatStore.setState({
388
+ activeAgentId,
389
+ activeTopicId: 'existing-topic',
390
+ dbMessagesMap: {
391
+ [newKey]: [{ id: 'msg-1' }, { id: 'msg-2' }] as any,
392
+ },
393
+ messagesMap: {
394
+ [newKey]: [{ id: 'msg-1' }, { id: 'msg-2' }] as any,
395
+ },
396
+ });
397
+ });
398
+
399
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
400
+
401
+ // Switch to new state (id = undefined)
402
+ await act(async () => {
403
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true });
404
+ });
405
+
406
+ // Verify replaceMessages was called to clear the new key
407
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
408
+ context: {
409
+ agentId: activeAgentId,
410
+ groupId: undefined,
411
+ scope: 'main',
412
+ topicId: null,
413
+ },
414
+ action: expect.any(String),
415
+ });
416
+
417
+ // Verify activeTopicId is now null
418
+ expect(useChatStore.getState().activeTopicId).toBeNull();
419
+ });
420
+
421
+ it('should clear new key data when switching to new state (group scope)', async () => {
422
+ const { result } = renderHook(() => useChatStore());
423
+ const activeAgentId = 'test-agent-id';
424
+ const activeGroupId = 'test-group-id';
425
+
426
+ // Setup initial state with group context
427
+ await act(async () => {
428
+ useChatStore.setState({
429
+ activeAgentId,
430
+ activeGroupId,
431
+ activeTopicId: 'existing-topic',
432
+ });
433
+ });
434
+
435
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
436
+
437
+ // Switch to new state
438
+ await act(async () => {
439
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true });
440
+ });
441
+
442
+ // Verify replaceMessages was called with group scope
443
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
444
+ context: {
445
+ agentId: activeAgentId,
446
+ groupId: activeGroupId,
447
+ scope: 'group',
448
+ topicId: null,
449
+ },
450
+ action: expect.any(String),
451
+ });
452
+ });
453
+
454
+ it('should use explicit scope from options when provided', async () => {
455
+ const { result } = renderHook(() => useChatStore());
456
+ const activeAgentId = 'test-agent-id';
457
+
458
+ await act(async () => {
459
+ useChatStore.setState({
460
+ activeAgentId,
461
+ activeTopicId: 'existing-topic',
462
+ });
463
+ });
464
+
465
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
466
+
467
+ // Switch to new state with explicit scope
468
+ await act(async () => {
469
+ await result.current.switchTopic(undefined, { skipRefreshMessage: true, scope: 'group' });
470
+ });
471
+
472
+ // Verify replaceMessages was called with explicit scope
473
+ expect(replaceMessagesSpy).toHaveBeenCalledWith([], {
474
+ context: expect.objectContaining({
475
+ scope: 'group',
476
+ }),
477
+ action: expect.any(String),
478
+ });
479
+ });
480
+
481
+ it('should not clear new key data when switching to an existing topic', async () => {
482
+ const { result } = renderHook(() => useChatStore());
483
+ const activeAgentId = 'test-agent-id';
484
+
485
+ await act(async () => {
486
+ useChatStore.setState({
487
+ activeAgentId,
488
+ activeTopicId: undefined,
489
+ });
490
+ });
491
+
492
+ const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
493
+
494
+ // Switch to an existing topic (not new state)
495
+ await act(async () => {
496
+ await result.current.switchTopic('existing-topic-id', { skipRefreshMessage: true });
497
+ });
498
+
499
+ // replaceMessages should not be called when switching to existing topic
500
+ expect(replaceMessagesSpy).not.toHaveBeenCalled();
501
+ });
348
502
  });
349
503
  describe('removeSessionTopics', () => {
350
504
  it('should remove all topics from the current session and refresh the topic list', async () => {
@@ -2,7 +2,7 @@
2
2
  // Note: To make the code more logic and readable, we just disable the auto sort key eslint rule
3
3
  // DON'T REMOVE THE FIRST LINE
4
4
  import { chainSummaryTitle } from '@lobechat/prompts';
5
- import { TraceNameMap, type UIChatMessage } from '@lobechat/types';
5
+ import { type MessageMapScope, TraceNameMap, type UIChatMessage } from '@lobechat/types';
6
6
  import isEqual from 'fast-deep-equal';
7
7
  import { t } from 'i18next';
8
8
  import useSWR, { type SWRResponse } from 'swr';
@@ -34,6 +34,22 @@ const n = setNamespace('t');
34
34
  const SWR_USE_FETCH_TOPIC = 'SWR_USE_FETCH_TOPIC';
35
35
  const SWR_USE_SEARCH_TOPIC = 'SWR_USE_SEARCH_TOPIC';
36
36
 
37
+ /**
38
+ * Options for switchTopic action
39
+ */
40
+ export interface SwitchTopicOptions {
41
+ /**
42
+ * Explicit scope for clearing new key data
43
+ * If not provided, will be inferred from store state (activeGroupId)
44
+ */
45
+ scope?: MessageMapScope;
46
+ /**
47
+ * Skip refreshing messages after switching topic
48
+ * @default false
49
+ */
50
+ skipRefreshMessage?: boolean;
51
+ }
52
+
37
53
  export interface ChatTopicAction {
38
54
  closeAllTopicsDrawer: () => void;
39
55
  favoriteTopic: (id: string, favState: boolean) => Promise<void>;
@@ -53,7 +69,12 @@ export interface ChatTopicAction {
53
69
  autoRenameTopicTitle: (id: string) => Promise<void>;
54
70
  duplicateTopic: (id: string) => Promise<void>;
55
71
  summaryTopicTitle: (topicId: string, messages: UIChatMessage[]) => Promise<void>;
56
- switchTopic: (id?: string, skipRefreshMessage?: boolean) => Promise<void>;
72
+ /**
73
+ * Switch to a topic or create new topic state
74
+ * @param id - Topic ID to switch to, or undefined/null to switch to "new" state
75
+ * @param options - Options object or boolean for backward compatibility (skipRefreshMessage)
76
+ */
77
+ switchTopic: (id?: string, options?: boolean | SwitchTopicOptions) => Promise<void>;
57
78
  updateTopicTitle: (id: string, title: string) => Promise<void>;
58
79
  useFetchTopics: (
59
80
  enable: boolean,
@@ -423,14 +444,37 @@ export const chatTopic: StateCreator<
423
444
  },
424
445
  ),
425
446
 
426
- switchTopic: async (id, skipRefreshMessage) => {
447
+ switchTopic: async (id, options) => {
448
+ // Backward compatibility: support both boolean and options object
449
+ const opts: SwitchTopicOptions =
450
+ typeof options === 'boolean' ? { skipRefreshMessage: options } : (options ?? {});
451
+
452
+ const { activeAgentId, activeGroupId } = get();
453
+
454
+ // When switching to "new" state (id is undefined/null), clear the new key data
455
+ // This prevents stale data from previous conversations showing up
456
+ if (!id && activeAgentId) {
457
+ // Determine scope: use explicit scope from options, or infer from activeGroupId
458
+ const scope = opts.scope ?? (activeGroupId ? 'group' : 'main');
459
+
460
+ get().replaceMessages([], {
461
+ context: {
462
+ agentId: activeAgentId,
463
+ groupId: activeGroupId,
464
+ scope,
465
+ topicId: null,
466
+ },
467
+ action: n('clearNewKeyData'),
468
+ });
469
+ }
470
+
427
471
  set(
428
472
  { activeTopicId: !id ? (null as any) : id, activeThreadId: undefined },
429
473
  false,
430
474
  n('toggleTopic'),
431
475
  );
432
476
 
433
- if (skipRefreshMessage) return;
477
+ if (opts.skipRefreshMessage) return;
434
478
  await get().refreshMessages();
435
479
  },
436
480
  // delete
@@ -1,4 +1,3 @@
1
-
2
1
  export interface LoadI18nNamespaceModuleParams {
3
2
  defaultLang: string;
4
3
  lng: string;