@lobehub/chat 0.161.23 → 0.161.24

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,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.161.24](https://github.com/lobehub/lobe-chat/compare/v0.161.23...v0.161.24)
6
+
7
+ <sup>Released on **2024-05-27**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix the missing user id in chat compeletition and fix remove unstarred topic not working.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix the missing user id in chat compeletition and fix remove unstarred topic not working, closes [#2677](https://github.com/lobehub/lobe-chat/issues/2677) ([c9fb2de](https://github.com/lobehub/lobe-chat/commit/c9fb2de))
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
+
5
30
  ### [Version 0.161.23](https://github.com/lobehub/lobe-chat/compare/v0.161.22...v0.161.23)
6
31
 
7
32
  <sup>Released on **2024-05-27**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.161.23",
3
+ "version": "0.161.24",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot 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",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "dependencies": {
84
84
  "@ant-design/icons": "^5.3.7",
85
- "@anthropic-ai/sdk": "^0.20.9",
85
+ "@anthropic-ai/sdk": "^0.21.0",
86
86
  "@auth/core": "0.28.0",
87
87
  "@aws-sdk/client-bedrock-runtime": "^3.583.0",
88
88
  "@aws-sdk/client-s3": "^3.583.0",
@@ -159,6 +159,7 @@ describe('POST handler', () => {
159
159
  accessCode: 'test-access-code',
160
160
  apiKey: 'test-api-key',
161
161
  azureApiVersion: 'v1',
162
+ userId: 'abc',
162
163
  });
163
164
 
164
165
  const mockParams = { provider: 'test-provider' };
@@ -176,7 +177,7 @@ describe('POST handler', () => {
176
177
  const response = await POST(request as unknown as Request, { params: mockParams });
177
178
 
178
179
  expect(response).toEqual(mockChatResponse);
179
- expect(AgentRuntime.prototype.chat).toHaveBeenCalledWith(mockChatPayload);
180
+ expect(AgentRuntime.prototype.chat).toHaveBeenCalledWith(mockChatPayload, { user: 'abc' });
180
181
  });
181
182
 
182
183
  it('should return an error response when chat completion fails', async () => {
@@ -25,17 +25,16 @@ export const POST = checkAuth(async (req: Request, { params, jwtPayload }) => {
25
25
 
26
26
  const tracePayload = getTracePayload(req);
27
27
 
28
+ let traceOptions = {};
28
29
  // If user enable trace
29
30
  if (tracePayload?.enabled) {
30
- return await agentRuntime.chat(
31
- data,
32
- createTraceOptions(data, {
33
- provider,
34
- trace: tracePayload,
35
- }),
36
- );
31
+ traceOptions = createTraceOptions(data, {
32
+ provider,
33
+ trace: tracePayload,
34
+ });
37
35
  }
38
- return await agentRuntime.chat(data);
36
+
37
+ return await agentRuntime.chat(data, { user: jwtPayload.userId, ...traceOptions });
39
38
  } catch (e) {
40
39
  const {
41
40
  errorType = ChatErrorType.InternalServerError,
package/src/const/auth.ts CHANGED
@@ -35,5 +35,11 @@ export interface JWTPayload {
35
35
  awsAccessKeyId?: string;
36
36
  awsRegion?: string;
37
37
  awsSecretAccessKey?: string;
38
+ /**
39
+ * user id
40
+ * in client db mode it's a uuid
41
+ * in server db mode it's a user id
42
+ */
43
+ userId?: string;
38
44
  }
39
45
  /* eslint-enable */
@@ -95,6 +95,10 @@ export interface ChatCompetitionOptions {
95
95
  callback?: ChatStreamCallbacks;
96
96
  headers?: Record<string, any>;
97
97
  signal?: AbortSignal;
98
+ /**
99
+ * userId for the chat completion
100
+ */
101
+ user?: string;
98
102
  }
99
103
 
100
104
  export interface ChatCompletionFunctions {
@@ -195,18 +195,21 @@ describe('LobeOpenAICompatibleFactory', () => {
195
195
  });
196
196
 
197
197
  describe('handlePayload option', () => {
198
- it('should modify request payload correctly', async () => {
198
+ it('should add user in payload correctly', async () => {
199
199
  const mockCreateMethod = vi.spyOn(instance['client'].chat.completions, 'create');
200
200
 
201
- await instance.chat({
202
- messages: [{ content: 'Hello', role: 'user' }],
203
- model: 'mistralai/mistral-7b-instruct:free',
204
- temperature: 0,
205
- });
201
+ await instance.chat(
202
+ {
203
+ messages: [{ content: 'Hello', role: 'user' }],
204
+ model: 'mistralai/mistral-7b-instruct:free',
205
+ temperature: 0,
206
+ },
207
+ { user: 'abc' },
208
+ );
206
209
 
207
210
  expect(mockCreateMethod).toHaveBeenCalledWith(
208
211
  expect.objectContaining({
209
- // 根据实际的 handlePayload 函数,添加断言
212
+ user: 'abc',
210
213
  }),
211
214
  expect.anything(),
212
215
  );
@@ -79,11 +79,14 @@ export const LobeOpenAICompatibleFactory = ({
79
79
  stream: payload.stream ?? true,
80
80
  } as OpenAI.ChatCompletionCreateParamsStreaming);
81
81
 
82
- const response = await this.client.chat.completions.create(postPayload, {
83
- // https://github.com/lobehub/lobe-chat/pull/318
84
- headers: { Accept: '*/*' },
85
- signal: options?.signal,
86
- });
82
+ const response = await this.client.chat.completions.create(
83
+ { ...postPayload, user: options?.user },
84
+ {
85
+ // https://github.com/lobehub/lobe-chat/pull/318
86
+ headers: { Accept: '*/*' },
87
+ signal: options?.signal,
88
+ },
89
+ );
87
90
 
88
91
  if (postPayload.stream) {
89
92
  const [prod, useForDebug] = response.tee();
@@ -1,7 +1,11 @@
1
1
  import { JWTPayload, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
2
2
  import { ModelProvider } from '@/libs/agent-runtime';
3
3
  import { useUserStore } from '@/store/user';
4
- import { keyVaultsConfigSelectors, settingsSelectors } from '@/store/user/selectors';
4
+ import {
5
+ keyVaultsConfigSelectors,
6
+ settingsSelectors,
7
+ userProfileSelectors,
8
+ } from '@/store/user/selectors';
5
9
  import { GlobalLLMProviderKey } from '@/types/user/settings';
6
10
  import { createJWT } from '@/utils/jwt';
7
11
 
@@ -48,8 +52,9 @@ export const getProviderAuthPayload = (provider: string) => {
48
52
 
49
53
  const createAuthTokenWithPayload = async (payload = {}) => {
50
54
  const accessCode = settingsSelectors.password(useUserStore.getState());
55
+ const userId = userProfileSelectors.userId(useUserStore.getState());
51
56
 
52
- return await createJWT<JWTPayload>({ accessCode, ...payload });
57
+ return await createJWT<JWTPayload>({ accessCode, userId, ...payload });
53
58
  };
54
59
 
55
60
  interface AuthParams {
@@ -384,11 +384,14 @@ describe('topic action', () => {
384
384
  // Set up mock state with unstarred topics
385
385
  await act(async () => {
386
386
  useChatStore.setState({
387
- topics: [
388
- { id: 'topic-1', favorite: false },
389
- { id: 'topic-2', favorite: true },
390
- { id: 'topic-3', favorite: false },
391
- ] as ChatTopic[],
387
+ activeId: 'abc',
388
+ topicMaps: {
389
+ abc: [
390
+ { id: 'topic-1', favorite: false },
391
+ { id: 'topic-2', favorite: true },
392
+ { id: 'topic-3', favorite: false },
393
+ ] as ChatTopic[],
394
+ },
392
395
  });
393
396
  });
394
397
  const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
@@ -431,7 +434,10 @@ describe('topic action', () => {
431
434
  });
432
435
 
433
436
  // Mock the `updateTopicTitleInSummary` and `refreshTopic` for spying
434
- const updateTopicTitleInSummarySpy = vi.spyOn(result.current, 'updateTopicTitleInSummary');
437
+ const updateTopicTitleInSummarySpy = vi.spyOn(
438
+ result.current,
439
+ 'internal_updateTopicTitleInSummary',
440
+ );
435
441
  const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
436
442
 
437
443
  // Mock the `chatService.fetchPresetTaskResult` to simulate the AI response
@@ -3,7 +3,6 @@
3
3
  // DON'T REMOVE THE FIRST LINE
4
4
  import isEqual from 'fast-deep-equal';
5
5
  import { t } from 'i18next';
6
- import { produce } from 'immer';
7
6
  import useSWR, { SWRResponse, mutate } from 'swr';
8
7
  import { StateCreator } from 'zustand/vanilla';
9
8
 
@@ -37,7 +36,7 @@ export interface ChatTopicAction {
37
36
  removeAllTopics: () => Promise<void>;
38
37
  removeSessionTopics: () => Promise<void>;
39
38
  removeTopic: (id: string) => Promise<void>;
40
- removeUnstarredTopic: () => void;
39
+ removeUnstarredTopic: () => Promise<void>;
41
40
  saveToTopic: () => Promise<string | undefined>;
42
41
  createTopic: () => Promise<string | undefined>;
43
42
 
@@ -45,11 +44,11 @@ export interface ChatTopicAction {
45
44
  duplicateTopic: (id: string) => Promise<void>;
46
45
  summaryTopicTitle: (topicId: string, messages: ChatMessage[]) => Promise<void>;
47
46
  switchTopic: (id?: string, skipRefreshMessage?: boolean) => Promise<void>;
48
- updateTopicTitleInSummary: (id: string, title: string) => void;
49
47
  updateTopicTitle: (id: string, title: string) => Promise<void>;
50
48
  useFetchTopics: (sessionId: string) => SWRResponse<ChatTopic[]>;
51
49
  useSearchTopics: (keywords?: string, sessionId?: string) => SWRResponse<ChatTopic[]>;
52
50
 
51
+ internal_updateTopicTitleInSummary: (id: string, title: string) => void;
53
52
  internal_updateTopicLoading: (id: string, loading: boolean) => void;
54
53
  internal_createTopic: (params: CreateTopicParams) => Promise<string>;
55
54
  internal_updateTopic: (id: string, data: Partial<ChatTopic>) => Promise<void>;
@@ -133,18 +132,18 @@ export const chatTopic: StateCreator<
133
132
  },
134
133
  // update
135
134
  summaryTopicTitle: async (topicId, messages) => {
136
- const { updateTopicTitleInSummary, internal_updateTopicLoading } = get();
135
+ const { internal_updateTopicTitleInSummary, internal_updateTopicLoading } = get();
137
136
  const topic = topicSelectors.getTopicById(topicId)(get());
138
137
  if (!topic) return;
139
138
 
140
- updateTopicTitleInSummary(topicId, LOADING_FLAT);
139
+ internal_updateTopicTitleInSummary(topicId, LOADING_FLAT);
141
140
 
142
141
  let output = '';
143
142
 
144
143
  // 自动总结话题标题
145
144
  await chatService.fetchPresetTaskResult({
146
145
  onError: () => {
147
- updateTopicTitleInSummary(topicId, topic.title);
146
+ internal_updateTopicTitleInSummary(topicId, topic.title);
148
147
  },
149
148
  onFinish: async (text) => {
150
149
  await get().internal_updateTopic(topicId, { title: text });
@@ -159,7 +158,7 @@ export const chatTopic: StateCreator<
159
158
  }
160
159
  }
161
160
 
162
- updateTopicTitleInSummary(topicId, output);
161
+ internal_updateTopicTitleInSummary(topicId, output);
163
162
  },
164
163
  params: await chainSummaryTitle(messages),
165
164
  trace: get().getCurrentTracePayload({ traceName: TraceNameMap.SummaryTopicTitle, topicId }),
@@ -264,15 +263,11 @@ export const chatTopic: StateCreator<
264
263
  },
265
264
 
266
265
  // Internal process method of the topics
267
- updateTopicTitleInSummary: (id, title) => {
268
- const topics = produce(get().topics, (draftState) => {
269
- const topic = draftState.find((i) => i.id === id);
270
-
271
- if (!topic) return;
272
- topic.title = title;
273
- });
274
-
275
- set({ topics }, false, n(`updateTopicTitleInSummary`, { id, title }));
266
+ internal_updateTopicTitleInSummary: (id, title) => {
267
+ get().internal_dispatchTopic(
268
+ { type: 'updateTopic', id, value: { title } },
269
+ 'updateTopicTitleInSummary',
270
+ );
276
271
  },
277
272
  refreshTopic: async () => {
278
273
  return mutate([SWR_USE_FETCH_TOPIC, get().activeId]);
@@ -317,8 +312,12 @@ export const chatTopic: StateCreator<
317
312
  },
318
313
 
319
314
  internal_dispatchTopic: (payload, action) => {
320
- const nextTopics = topicReducer(get().topics, payload);
315
+ const nextTopics = topicReducer(topicSelectors.currentTopics(get()), payload);
316
+ const nextMap = { ...get().topicMaps, [get().activeId]: nextTopics };
317
+
318
+ // no need to update map if is the same
319
+ if (isEqual(nextMap, get().topicMaps)) return;
321
320
 
322
- set({ topics: nextTopics }, false, action ?? n(`dispatchTopic/${payload.type}`));
321
+ set({ topicMaps: nextMap }, false, action ?? n(`dispatchTopic/${payload.type}`));
323
322
  },
324
323
  });
@@ -9,7 +9,6 @@ export interface ChatTopicState {
9
9
  topicMaps: Record<string, ChatTopic[]>;
10
10
  topicRenamingId?: string;
11
11
  topicSearchKeywords: string;
12
- topics: ChatTopic[];
13
12
  /**
14
13
  * whether topics have fetched
15
14
  */
@@ -23,6 +22,5 @@ export const initialTopicState: ChatTopicState = {
23
22
  topicLoadingIds: [],
24
23
  topicMaps: {},
25
24
  topicSearchKeywords: '',
26
- topics: [],
27
25
  topicsInit: false,
28
26
  };
@@ -58,7 +58,7 @@ describe('topicSelectors', () => {
58
58
 
59
59
  describe('currentUnFavTopics', () => {
60
60
  it('should return all unfavorited topics', () => {
61
- const state = merge(initialStore, { topics: topicMaps.test });
61
+ const state = merge(initialStore, { topicMaps, activeId: 'test' });
62
62
  const topics = topicSelectors.currentUnFavTopics(state);
63
63
  expect(topics).toEqual([topicMaps.test[1]]);
64
64
  });
@@ -12,7 +12,8 @@ const searchTopics = (s: ChatStore): ChatTopic[] => s.searchTopics;
12
12
  const displayTopics = (s: ChatStore): ChatTopic[] | undefined =>
13
13
  s.isSearchingTopic ? searchTopics(s) : currentTopics(s);
14
14
 
15
- const currentUnFavTopics = (s: ChatStore): ChatTopic[] => s.topics.filter((s) => !s.favorite);
15
+ const currentUnFavTopics = (s: ChatStore): ChatTopic[] =>
16
+ currentTopics(s)?.filter((s) => !s.favorite) || [];
16
17
 
17
18
  const currentTopicLength = (s: ChatStore): number => currentTopics(s)?.length || 0;
18
19