@lobehub/lobehub 2.0.0-next.66 → 2.0.0-next.68

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.68](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.67...v2.0.0-next.68)
6
+
7
+ <sup>Released on **2025-11-16**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: The tool to fail execution on ollama when a message contains b….
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: The tool to fail execution on ollama when a message contains b…, closes [#10259](https://github.com/lobehub/lobe-chat/issues/10259) ([1ad8080](https://github.com/lobehub/lobe-chat/commit/1ad8080))
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.67](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.66...v2.0.0-next.67)
31
+
32
+ <sup>Released on **2025-11-16**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor to virtua.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Refactor to virtua, closes [#10151](https://github.com/lobehub/lobe-chat/issues/10151) ([9ffb689](https://github.com/lobehub/lobe-chat/commit/9ffb689))
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.66](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.65...v2.0.0-next.66)
6
56
 
7
57
  <sup>Released on **2025-11-16**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "The tool to fail execution on ollama when a message contains b…."
6
+ ]
7
+ },
8
+ "date": "2025-11-16",
9
+ "version": "2.0.0-next.68"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Refactor to virtua."
15
+ ]
16
+ },
17
+ "date": "2025-11-16",
18
+ "version": "2.0.0-next.67"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.66",
3
+ "version": "2.0.0-next.68",
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",
@@ -122,9 +122,6 @@
122
122
  "eslint --fix"
123
123
  ]
124
124
  },
125
- "overrides": {
126
- "eta": "4.0.1"
127
- },
128
125
  "dependencies": {
129
126
  "@ant-design/icons": "^5.6.1",
130
127
  "@ant-design/pro-components": "^2.8.10",
@@ -265,7 +262,7 @@
265
262
  "react-fast-marquee": "^1.6.5",
266
263
  "react-hotkeys-hook": "^5.2.1",
267
264
  "react-i18next": "^15.7.4",
268
- "react-layout-kit": "^2.0.0",
265
+ "react-layout-kit": "^2.0.1",
269
266
  "react-lazy-load": "^4.0.1",
270
267
  "react-pdf": "^9.2.1",
271
268
  "react-responsive": "^10.0.1",
@@ -295,6 +292,7 @@
295
292
  "url-join": "^5.0.0",
296
293
  "use-merge-value": "^1.2.0",
297
294
  "uuid": "^11.1.0",
295
+ "virtua": "^0.47.0",
298
296
  "word-extractor": "^1.0.4",
299
297
  "ws": "^8.18.3",
300
298
  "yaml": "^2.8.1",
@@ -397,9 +395,6 @@
397
395
  "pnpm": {
398
396
  "onlyBuiltDependencies": [
399
397
  "@vercel/speed-insights"
400
- ],
401
- "overrides": {
402
- "eta": "4.0.1"
403
- }
398
+ ]
404
399
  }
405
400
  }
@@ -235,6 +235,73 @@ describe('OllamaStream', () => {
235
235
  expect(onToolCall).toHaveBeenCalledTimes(1);
236
236
  expect(onCompletionMock).toHaveBeenCalledTimes(1);
237
237
  });
238
+
239
+ it('tools use with a done', async () => {
240
+ vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1').mockReturnValueOnce('abcd1234');
241
+
242
+ const mockOllamaStream = new ReadableStream<ChatResponse>({
243
+ start(controller) {
244
+ controller.enqueue({
245
+ model: 'qwen2.5',
246
+ created_at: new Date('2024-12-01T03:34:55.166692Z'),
247
+ message: {
248
+ role: 'assistant',
249
+ content: '',
250
+ tool_calls: [
251
+ {
252
+ function: {
253
+ name: 'realtime-weather____fetchCurrentWeather',
254
+ arguments: { city: '杭州' },
255
+ },
256
+ },
257
+ ],
258
+ },
259
+ done_reason: 'stop',
260
+ done: true,
261
+ total_duration: 1122415333,
262
+ load_duration: 26178333,
263
+ prompt_eval_count: 221,
264
+ prompt_eval_duration: 507000000,
265
+ eval_count: 26,
266
+ eval_duration: 583000000,
267
+ } as unknown as ChatResponse);
268
+
269
+ controller.close();
270
+ },
271
+ });
272
+ const onStartMock = vi.fn();
273
+ const onTextMock = vi.fn();
274
+ const onToolCall = vi.fn();
275
+ const onCompletionMock = vi.fn();
276
+
277
+ const protocolStream = OllamaStream(mockOllamaStream, {
278
+ onStart: onStartMock,
279
+ onText: onTextMock,
280
+ onCompletion: onCompletionMock,
281
+ onToolsCalling: onToolCall,
282
+ });
283
+
284
+ const decoder = new TextDecoder();
285
+ const chunks = [];
286
+
287
+ // @ts-ignore
288
+ for await (const chunk of protocolStream) {
289
+ chunks.push(decoder.decode(chunk, { stream: true }));
290
+ }
291
+
292
+ expect(chunks).toEqual(
293
+ [
294
+ 'id: chat_1',
295
+ 'event: tool_calls',
296
+ `data: [{"function":{"arguments":"{\\"city\\":\\"杭州\\"}","name":"realtime-weather____fetchCurrentWeather"},"id":"realtime-weather____fetchCurrentWeather_0_abcd1234","index":0,"type":"function"}]\n`,
297
+ ].map((i) => `${i}\n`),
298
+ );
299
+
300
+ expect(onTextMock).toHaveBeenCalledTimes(0);
301
+ expect(onStartMock).toHaveBeenCalledTimes(1);
302
+ expect(onToolCall).toHaveBeenCalledTimes(1);
303
+ expect(onCompletionMock).toHaveBeenCalledTimes(1);
304
+ });
238
305
  });
239
306
 
240
307
  it('should handle empty stream', async () => {
@@ -11,11 +11,6 @@ import {
11
11
  } from './protocol';
12
12
 
13
13
  const transformOllamaStream = (chunk: ChatResponse, stack: StreamContext): StreamProtocolChunk => {
14
- // maybe need another structure to add support for multiple choices
15
- if (chunk.done && !chunk.message.content) {
16
- return { data: 'finished', id: stack.id, type: 'stop' };
17
- }
18
-
19
14
  if (chunk.message.thinking) {
20
15
  return { data: chunk.message.thinking, id: stack.id, type: 'reasoning' };
21
16
  }
@@ -36,6 +31,11 @@ const transformOllamaStream = (chunk: ChatResponse, stack: StreamContext): Strea
36
31
  };
37
32
  }
38
33
 
34
+ // maybe need another structure to add support for multiple choices
35
+ if (chunk.done && !chunk.message.content) {
36
+ return { data: 'finished', id: stack.id, type: 'stop' };
37
+ }
38
+
39
39
  // 判断是否有 <think> 或 </think> 标签,更新 thinkingInContent 状态
40
40
  if (chunk.message.content.includes('<think>')) {
41
41
  stack.thinkingInContent = true;
@@ -75,6 +75,7 @@ const MainChatItem = memo<ThreadChatItemProps>(({ id, index }) => {
75
75
  endRender={showThread && <Thread id={id} placement={placement} />}
76
76
  id={id}
77
77
  index={index}
78
+ isLatestItem={isLatestItem}
78
79
  />
79
80
  {isLatestItem && <SupervisorThinkingTag />}
80
81
  </>
@@ -4,6 +4,7 @@ import { Icon } from '@lobehub/ui';
4
4
  import { Popover, Tooltip } from 'antd';
5
5
  import { createStyles, useTheme } from 'antd-style';
6
6
  import debug from 'debug';
7
+ import isEqual from 'fast-deep-equal';
7
8
  import { ChevronDown, ChevronUp } from 'lucide-react';
8
9
  import { markdownToTxt } from 'markdown-to-txt';
9
10
  import { memo, useCallback, useMemo, useState, useSyncExternalStore } from 'react';
@@ -11,13 +12,13 @@ import { useTranslation } from 'react-i18next';
11
12
  import { Flexbox } from 'react-layout-kit';
12
13
 
13
14
  import {
14
- getVirtuosoActiveIndex,
15
- getVirtuosoGlobalRef,
16
- subscribeVirtuosoActiveIndex,
17
- subscribeVirtuosoGlobalRef,
15
+ getVirtuaActiveIndex,
16
+ getVirtuaGlobalRef,
17
+ subscribeVirtuaActiveIndex,
18
+ subscribeVirtuaGlobalRef,
18
19
  } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
19
20
  import { useChatStore } from '@/store/chat';
20
- import { chatSelectors } from '@/store/chat/selectors';
21
+ import { displayMessageSelectors } from '@/store/chat/selectors';
21
22
 
22
23
  const log = debug('lobe-react:chat-minimap');
23
24
 
@@ -194,21 +195,17 @@ interface MinimapIndicator {
194
195
  width: number;
195
196
  }
196
197
 
197
- const ChatMinimap = () => {
198
+ const ChatMinimap = memo(() => {
198
199
  const { t } = useTranslation('chat');
199
200
  const { styles, cx } = useStyles();
200
201
  const [isHovered, setIsHovered] = useState(false);
201
- const virtuosoRef = useSyncExternalStore(
202
- subscribeVirtuosoGlobalRef,
203
- getVirtuosoGlobalRef,
204
- () => null,
205
- );
202
+ const virtuaRef = useSyncExternalStore(subscribeVirtuaGlobalRef, getVirtuaGlobalRef, () => null);
206
203
  const activeIndex = useSyncExternalStore(
207
- subscribeVirtuosoActiveIndex,
208
- getVirtuosoActiveIndex,
204
+ subscribeVirtuaActiveIndex,
205
+ getVirtuaActiveIndex,
209
206
  () => null,
210
207
  );
211
- const messages = useChatStore(chatSelectors.mainDisplayChats);
208
+ const messages = useChatStore(displayMessageSelectors.mainAIChats, isEqual);
212
209
 
213
210
  const theme = useTheme();
214
211
 
@@ -247,20 +244,19 @@ const ChatMinimap = () => {
247
244
 
248
245
  const handleJump = useCallback(
249
246
  (virtIndex: number) => {
250
- virtuosoRef?.current?.scrollToIndex({
247
+ virtuaRef?.current?.scrollToIndex(virtIndex, {
251
248
  align: 'start',
252
- behavior: 'smooth',
253
- index: virtIndex,
254
249
  // The current index detection will be off by 1, so we need to add 1 here
255
250
  offset: 6,
251
+ smooth: true,
256
252
  });
257
253
  },
258
- [virtuosoRef],
254
+ [virtuaRef],
259
255
  );
260
256
 
261
257
  const handleStep = useCallback(
262
258
  (direction: 'prev' | 'next') => {
263
- const ref = virtuosoRef?.current;
259
+ const ref = virtuaRef?.current;
264
260
  if (!ref || indicators.length === 0) return;
265
261
 
266
262
  let targetPosition: number;
@@ -303,14 +299,13 @@ const ChatMinimap = () => {
303
299
 
304
300
  if (!targetIndicator) return;
305
301
 
306
- ref.scrollToIndex({
302
+ ref.scrollToIndex(targetIndicator.virtuosoIndex, {
307
303
  align: 'start',
308
- behavior: 'smooth',
309
- index: targetIndicator.virtuosoIndex,
310
304
  offset: 6,
305
+ smooth: true,
311
306
  });
312
307
  },
313
- [activeIndex, activeIndicatorPosition, indicators, virtuosoRef],
308
+ [activeIndex, activeIndicatorPosition, indicators, virtuaRef],
314
309
  );
315
310
 
316
311
  if (indicators.length <= MIN_MESSAGES) return null;
@@ -353,9 +348,7 @@ const ChatMinimap = () => {
353
348
  aria-label={t('minimap.jumpToMessage', { index: position + 1 })}
354
349
  className={styles.indicator}
355
350
  onClick={() => handleJump(virtuosoIndex)}
356
- style={{
357
- width,
358
- }}
351
+ style={{ width }}
359
352
  type={'button'}
360
353
  >
361
354
  <div
@@ -382,6 +375,6 @@ const ChatMinimap = () => {
382
375
  </Flexbox>
383
376
  </Flexbox>
384
377
  );
385
- };
378
+ });
386
379
 
387
- export default memo(ChatMinimap);
380
+ export default ChatMinimap;
@@ -86,6 +86,10 @@ export const useStyles = createStyles(
86
86
  padding-block: 24px 12px;
87
87
  padding-inline: 12px;
88
88
 
89
+ @supports (content-visibility: auto) {
90
+ contain-intrinsic-size: auto 100lvh;
91
+ }
92
+
89
93
  time {
90
94
  display: inline-block;
91
95
  white-space: nowrap;
@@ -6,7 +6,7 @@ import { memo, use, useCallback, useContext, useMemo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import ShareMessageModal from '@/features/Conversation/components/ShareMessageModal';
9
- import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
9
+ import { VirtuaContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
10
10
  import { useChatStore } from '@/store/chat';
11
11
  import { messageStateSelectors, threadSelectors } from '@/store/chat/selectors';
12
12
  import { useSessionStore } from '@/store/session';
@@ -90,7 +90,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
90
90
  s.toggleMessageCollapsed,
91
91
  ]);
92
92
  const { message } = App.useApp();
93
- const virtuosoRef = use(VirtuosoContext);
93
+ const virtuaRef = use(VirtuaContext);
94
94
 
95
95
  const onActionClick = useCallback(
96
96
  async (action: ActionIconGroupEvent) => {
@@ -98,7 +98,7 @@ export const AssistantActionsBar = memo<AssistantActionsProps>(({ id, data, inde
98
98
  case 'edit': {
99
99
  toggleMessageEditing(id, true);
100
100
 
101
- virtuosoRef?.current?.scrollIntoView({ align: 'start', behavior: 'auto', index });
101
+ virtuaRef?.current?.scrollToIndex(index, { align: 'start' });
102
102
  }
103
103
  }
104
104
  if (!data) return;