@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
|
+
[](#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
|
+
[](#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.
|
|
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
|
-
|
|
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,
|
|
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
|