@eeacms/volto-eea-chatbot 2.0.1 → 2.0.3

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.
Files changed (84) hide show
  1. package/.eslintrc.js +6 -6
  2. package/CHANGELOG.md +20 -0
  3. package/artifacts/ONYX_V3_INTEGRATION.md +34 -0
  4. package/jest-addon.config.js +2 -1
  5. package/package.json +1 -1
  6. package/src/ChatBlock/ChatBlockEdit.jsx +2 -1
  7. package/src/ChatBlock/chat/AIMessage.tsx +36 -16
  8. package/src/ChatBlock/chat/ChatMessage.tsx +1 -1
  9. package/src/ChatBlock/chat/ChatWindow.tsx +13 -11
  10. package/src/ChatBlock/chat/UserMessage.tsx +4 -4
  11. package/src/ChatBlock/components/AutoResizeTextarea.jsx +1 -1
  12. package/src/ChatBlock/components/ChatMessageFeedback.jsx +2 -2
  13. package/src/ChatBlock/components/EmptyState.jsx +1 -1
  14. package/src/ChatBlock/components/FeedbackModal.jsx +1 -1
  15. package/src/ChatBlock/components/HalloumiFeedback.jsx +2 -2
  16. package/src/ChatBlock/components/Source.jsx +2 -2
  17. package/src/ChatBlock/components/UserActionsToolbar.jsx +3 -3
  18. package/src/ChatBlock/components/WebResultIcon.tsx +2 -2
  19. package/src/ChatBlock/components/markdown/ClaimModal.jsx +3 -3
  20. package/src/ChatBlock/components/markdown/ClaimSegments.jsx +4 -4
  21. package/src/ChatBlock/components/markdown/{index.js → index.jsx} +1 -1
  22. package/src/ChatBlock/hooks/useChatController.ts +67 -14
  23. package/src/ChatBlock/hooks/useChatStreaming.ts +4 -4
  24. package/src/ChatBlock/hooks/useToolDisplayTiming.ts +2 -1
  25. package/src/ChatBlock/packets/MultiToolRenderer.tsx +86 -56
  26. package/src/ChatBlock/packets/RendererComponent.tsx +13 -5
  27. package/src/ChatBlock/packets/renderers/CustomToolRenderer.tsx +3 -3
  28. package/src/ChatBlock/packets/renderers/FetchToolRenderer.tsx +3 -3
  29. package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +3 -3
  30. package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +14 -9
  31. package/src/ChatBlock/packets/renderers/ReasoningRenderer.tsx +6 -5
  32. package/src/ChatBlock/packets/renderers/SearchToolRenderer.tsx +30 -21
  33. package/src/ChatBlock/{schema.js → schema.jsx} +13 -0
  34. package/src/ChatBlock/services/messageProcessor.ts +72 -17
  35. package/src/ChatBlock/services/packetUtils.ts +13 -3
  36. package/src/ChatBlock/services/streamingService.ts +155 -68
  37. package/src/ChatBlock/types/streamingModels.ts +47 -2
  38. package/src/ChatBlock/utils/citations.ts +1 -1
  39. package/src/halloumi/filtering.test.js +199 -1
  40. package/src/middleware.js +18 -1
  41. package/src/middleware.test.js +14 -0
  42. package/src/ChatBlock/tests/AIMessage.test.jsx +0 -95
  43. package/src/ChatBlock/tests/AutoResizeTextarea.test.jsx +0 -49
  44. package/src/ChatBlock/tests/BlinkingDot.test.jsx +0 -71
  45. package/src/ChatBlock/tests/ChatMessage.test.jsx +0 -75
  46. package/src/ChatBlock/tests/ChatMessageFeedback.test.jsx +0 -73
  47. package/src/ChatBlock/tests/Citation.test.jsx +0 -107
  48. package/src/ChatBlock/tests/ClaimModal.test.jsx +0 -136
  49. package/src/ChatBlock/tests/ClaimSegments.test.jsx +0 -206
  50. package/src/ChatBlock/tests/CustomToolRenderer.test.jsx +0 -241
  51. package/src/ChatBlock/tests/EmptyState.test.jsx +0 -137
  52. package/src/ChatBlock/tests/FeedbackModal.test.jsx +0 -138
  53. package/src/ChatBlock/tests/FetchToolRenderer.test.jsx +0 -161
  54. package/src/ChatBlock/tests/HalloumiFeedback.test.jsx +0 -94
  55. package/src/ChatBlock/tests/ImageToolRenderer.test.jsx +0 -178
  56. package/src/ChatBlock/tests/MessageTextRenderer.test.jsx +0 -227
  57. package/src/ChatBlock/tests/MultiToolRenderer.test.jsx +0 -134
  58. package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +0 -105
  59. package/src/ChatBlock/tests/ReasoningRenderer.test.jsx +0 -163
  60. package/src/ChatBlock/tests/RelatedQuestions.test.jsx +0 -215
  61. package/src/ChatBlock/tests/RenderClaimView.test.jsx +0 -191
  62. package/src/ChatBlock/tests/RendererComponent.test.jsx +0 -139
  63. package/src/ChatBlock/tests/SearchToolRenderer.test.jsx +0 -295
  64. package/src/ChatBlock/tests/Source.test.jsx +0 -79
  65. package/src/ChatBlock/tests/SourceChip.test.jsx +0 -108
  66. package/src/ChatBlock/tests/Spinner.test.jsx +0 -18
  67. package/src/ChatBlock/tests/UserActionsToolbar.test.jsx +0 -135
  68. package/src/ChatBlock/tests/UserMessage.test.jsx +0 -83
  69. package/src/ChatBlock/tests/WebResultIcon.test.jsx +0 -61
  70. package/src/ChatBlock/tests/citations.test.js +0 -114
  71. package/src/ChatBlock/tests/index.test.js +0 -51
  72. package/src/ChatBlock/tests/messageProcessor.test.jsx +0 -438
  73. package/src/ChatBlock/tests/packetUtils.test.js +0 -158
  74. package/src/ChatBlock/tests/schema.test.js +0 -166
  75. package/src/ChatBlock/tests/streamingService.test.js +0 -467
  76. package/src/ChatBlock/tests/useChatController.test.jsx +0 -268
  77. package/src/ChatBlock/tests/useChatStreaming.test.jsx +0 -163
  78. package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +0 -107
  79. package/src/ChatBlock/tests/useMarked.test.jsx +0 -107
  80. package/src/ChatBlock/tests/useQualityMarkers.test.jsx +0 -150
  81. package/src/ChatBlock/tests/useScrollonStream.test.jsx +0 -121
  82. package/src/ChatBlock/tests/useToolDisplayTiming.test.jsx +0 -151
  83. package/src/ChatBlock/tests/utils.test.jsx +0 -241
  84. package/src/ChatBlock/tests/withOnyxData.test.jsx +0 -81
@@ -1,13 +1,13 @@
1
- import type { Packet } from '../types/streamingModels';
2
- import type { Message } from '../types/interfaces';
1
+ import type { Packet } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
2
+ import type { Message } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
3
3
  import React, { useState, useEffect, useMemo } from 'react';
4
4
  import cx from 'classnames';
5
- import { PacketType } from '../types/streamingModels';
5
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
6
6
  import { RendererComponent } from './RendererComponent';
7
- import { useToolDisplayTiming } from '../hooks/useToolDisplayTiming';
8
- import SVGIcon from '../components/Icon';
9
- import DoneIcon from '../../icons/done.svg';
10
- import ChevronIcon from '../../icons/chevron.svg';
7
+ import { useToolDisplayTiming } from '@eeacms/volto-eea-chatbot/ChatBlock/hooks/useToolDisplayTiming';
8
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
9
+ import DoneIcon from '@eeacms/volto-eea-chatbot/icons/done.svg';
10
+ import ChevronIcon from '@eeacms/volto-eea-chatbot/icons/chevron.svg';
11
11
 
12
12
  interface MultiToolRendererProps {
13
13
  toolGroups: { ind: number; packets: Packet[] }[];
@@ -19,7 +19,7 @@ interface MultiToolRendererProps {
19
19
 
20
20
  export function MultiToolRenderer({
21
21
  toolGroups,
22
- showTools = [PacketType.SEARCH_TOOL_START],
22
+ showTools = [PacketType.SEARCH_TOOL_START, PacketType.REASONING_START],
23
23
  message,
24
24
  libs,
25
25
  onAllToolsDisplayed,
@@ -28,23 +28,33 @@ export function MultiToolRenderer({
28
28
  const { isFinalMessageComing = false, isComplete = false } = message;
29
29
 
30
30
  // Filter tool groups based on allowed tool types
31
- const filteredToolGroups = useMemo(
32
- () =>
33
- toolGroups.filter(
34
- (group) =>
35
- group.packets?.some(
36
- (packet) => showTools?.includes(packet.obj.type as PacketType),
37
- ),
31
+ const filteredToolGroups = useMemo(() => {
32
+ const expandedShowTools = [...(showTools || [])];
33
+ if (showTools?.includes(PacketType.SEARCH_TOOL_START)) {
34
+ expandedShowTools.push(
35
+ PacketType.SEARCH_TOOL_START_V3,
36
+ PacketType.SEARCH_TOOL_QUERIES_DELTA,
37
+ PacketType.SEARCH_TOOL_DOCUMENTS_DELTA,
38
+ PacketType.SEARCH_TOOL_DELTA,
39
+ );
40
+ }
41
+ if (showTools?.includes(PacketType.REASONING_START)) {
42
+ expandedShowTools.push(
43
+ PacketType.REASONING_DELTA,
44
+ PacketType.REASONING_DONE,
45
+ PacketType.REASONING_END as any,
46
+ );
47
+ }
48
+ return toolGroups.filter((group) =>
49
+ group.packets?.some((packet) =>
50
+ expandedShowTools.includes(packet.obj.type as PacketType),
38
51
  ),
39
- [toolGroups, showTools],
40
- );
52
+ );
53
+ }, [toolGroups, showTools]);
41
54
 
42
55
  // Manage tool display timing
43
- const { allToolsDisplayed, handleToolComplete } = useToolDisplayTiming(
44
- filteredToolGroups,
45
- isFinalMessageComing,
46
- isComplete,
47
- );
56
+ const { allToolsDisplayed, handleToolComplete, toolStates } =
57
+ useToolDisplayTiming(filteredToolGroups, isFinalMessageComing, isComplete);
48
58
 
49
59
  // Notify parent when all tools are displayed
50
60
  useEffect(() => {
@@ -65,27 +75,29 @@ export function MultiToolRenderer({
65
75
 
66
76
  if (filteredToolGroups.length === 0) return null;
67
77
 
68
- const isStreaming = !allToolsDisplayed;
78
+ const isOverallStreaming = !allToolsDisplayed;
69
79
 
70
80
  const count = filteredToolGroups.length;
71
81
 
72
- const ariaLabel = `${count} ${isStreaming ? 'processing' : 'completed'} ${
73
- count === 1 ? 'step' : 'steps'
74
- }, ${isExpanded ? 'expanded' : 'collapsed'}`;
82
+ const ariaLabel = `${count} ${
83
+ isOverallStreaming ? 'processing' : 'completed'
84
+ } ${count === 1 ? 'step' : 'steps'}, ${isExpanded ? 'expanded' : 'collapsed'}`;
75
85
 
76
86
  return (
77
87
  <div
78
88
  className={cx('multi-tool-renderer', {
79
- streaming: isStreaming,
80
- complete: !isStreaming,
89
+ streaming: isOverallStreaming,
90
+ complete: !isOverallStreaming,
81
91
  })}
82
92
  >
83
93
  {/* Header */}
84
- <div className={cx({ 'tools-container collapsed-view': isStreaming })}>
94
+ <div
95
+ className={cx({ 'tools-container collapsed-view': isOverallStreaming })}
96
+ >
85
97
  <div
86
98
  className={cx({
87
- 'tools-collapsed-header': isStreaming,
88
- 'tools-summary-header': !isStreaming,
99
+ 'tools-collapsed-header': isOverallStreaming,
100
+ 'tools-summary-header': !isOverallStreaming,
89
101
  })}
90
102
  onClick={toggleExpanded}
91
103
  role="button"
@@ -110,21 +122,25 @@ export function MultiToolRenderer({
110
122
  {/* Tools List */}
111
123
  <div
112
124
  className={cx({
113
- 'tools-collapsed-list': isStreaming,
114
- 'tools-expanded-content': !isStreaming,
115
- expanded: isExpanded && isStreaming,
116
- visible: isExpanded && !isStreaming,
125
+ 'tools-collapsed-list': isOverallStreaming,
126
+ 'tools-expanded-content': !isOverallStreaming,
127
+ expanded: isExpanded && isOverallStreaming,
128
+ visible: isExpanded && !isOverallStreaming,
117
129
  })}
118
130
  >
119
- <div className={cx({ 'tools-list': isStreaming })}>
131
+ <div className={cx({ 'tools-list': isOverallStreaming })}>
120
132
  <div>
121
133
  {filteredToolGroups.map((toolGroup, index) => {
122
134
  const isLastItem = index === filteredToolGroups.length - 1;
135
+ const toolState = toolStates.get(toolGroup.ind);
136
+ const isToolCompleted = toolState?.isCompleted;
123
137
 
124
138
  return (
125
139
  <div
126
140
  key={toolGroup.ind}
127
- className={cx({ 'tool-collaps ed-wrapper': isStreaming })}
141
+ className={cx({
142
+ 'tool-collapsed-wrapper': isOverallStreaming,
143
+ })}
128
144
  >
129
145
  <RendererComponent
130
146
  packets={toolGroup.packets}
@@ -144,32 +160,46 @@ export function MultiToolRenderer({
144
160
  ) : (
145
161
  <span
146
162
  className={cx({
147
- 'tool-icon-dot': isStreaming,
148
- 'tool-icon-default': !isStreaming,
163
+ 'tool-icon-dot': isOverallStreaming,
164
+ 'tool-icon-default': !isOverallStreaming,
149
165
  })}
150
166
  />
151
167
  );
152
168
 
153
- // Streaming: collapsed view (status only)
154
- if (isStreaming) {
155
- return (
156
- <div
157
- className={cx('tool-item-collapsed', {
158
- active: isLastItem,
159
- completed: !isLastItem,
160
- })}
161
- >
162
- <div className="tool-collapsed-icon">
163
- {finalIcon}
164
- </div>
165
- <span className="tool-collapsed-status">
166
- {status}
167
- </span>
168
- </div>
169
+ // If tool is not completed and we are overall streaming, show collapsed view
170
+ // EXCEPT for reasoning and search which we want to see while they stream/progress
171
+ if (isOverallStreaming && !isToolCompleted) {
172
+ const isDetailedTool = toolGroup.packets.some(
173
+ (p) =>
174
+ p.obj.type === PacketType.REASONING_START ||
175
+ p.obj.type === PacketType.REASONING_DELTA ||
176
+ p.obj.type === PacketType.SEARCH_TOOL_START ||
177
+ p.obj.type === PacketType.SEARCH_TOOL_START_V3 ||
178
+ p.obj.type === PacketType.SEARCH_TOOL_DELTA ||
179
+ p.obj.type === PacketType.SEARCH_TOOL_QUERIES_DELTA ||
180
+ p.obj.type === PacketType.SEARCH_TOOL_DOCUMENTS_DELTA,
169
181
  );
182
+
183
+ if (!isDetailedTool || !content) {
184
+ return (
185
+ <div
186
+ className={cx('tool-item-collapsed', {
187
+ active: isLastItem,
188
+ completed: isToolCompleted,
189
+ })}
190
+ >
191
+ <div className="tool-collapsed-icon">
192
+ {finalIcon}
193
+ </div>
194
+ <span className="tool-collapsed-status">
195
+ {status}
196
+ </span>
197
+ </div>
198
+ );
199
+ }
170
200
  }
171
201
 
172
- // Complete: expanded view (full content)
202
+ // Expanded view (full content) - used for completed tools or when overall complete
173
203
  return (
174
204
  <div className="tool-item-expanded">
175
205
  <div className="tool-connector-line" />
@@ -1,6 +1,9 @@
1
- import type { Packet } from '../types/streamingModels';
2
- import type { RendererResult, Message } from '../types/interfaces';
3
- import { PacketType } from '../types/streamingModels';
1
+ import type { Packet } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
2
+ import type {
3
+ RendererResult,
4
+ Message,
5
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
6
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
4
7
  import {
5
8
  MessageTextRenderer,
6
9
  SearchToolRenderer,
@@ -23,7 +26,10 @@ function isChatPacket(packet: Packet): boolean {
23
26
  }
24
27
 
25
28
  function isSearchToolPacket(packet: Packet): boolean {
26
- return packet.obj.type === PacketType.SEARCH_TOOL_START;
29
+ return (
30
+ packet.obj.type === PacketType.SEARCH_TOOL_START ||
31
+ packet.obj.type === PacketType.SEARCH_TOOL_START_V3
32
+ );
27
33
  }
28
34
 
29
35
  function isImageToolPacket(packet: Packet): boolean {
@@ -41,7 +47,9 @@ function isFetchToolPacket(packet: Packet): boolean {
41
47
  function isReasoningPacket(packet: Packet): boolean {
42
48
  return (
43
49
  packet.obj.type === PacketType.REASONING_START ||
44
- packet.obj.type === PacketType.REASONING_DELTA
50
+ packet.obj.type === PacketType.REASONING_DELTA ||
51
+ packet.obj.type === PacketType.REASONING_DONE ||
52
+ packet.obj.type === PacketType.REASONING_END
45
53
  );
46
54
  }
47
55
 
@@ -2,10 +2,10 @@ import type {
2
2
  CustomToolPacket,
3
3
  CustomToolStart,
4
4
  CustomToolDelta,
5
- } from '../../types/streamingModels';
6
- import type { MessageRenderer } from '../../types/interfaces';
5
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
6
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
7
7
  import { useEffect } from 'react';
8
- import { PacketType } from '../../types/streamingModels';
8
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
9
9
 
10
10
  export const CustomToolRenderer: MessageRenderer<CustomToolPacket> = ({
11
11
  packets,
@@ -1,10 +1,10 @@
1
1
  import type {
2
2
  FetchToolPacket,
3
3
  FetchToolStart,
4
- } from '../../types/streamingModels';
5
- import type { MessageRenderer } from '../../types/interfaces';
4
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
5
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
6
6
  import { useEffect } from 'react';
7
- import { PacketType } from '../../types/streamingModels';
7
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
8
8
 
9
9
  export const FetchToolRenderer: MessageRenderer<FetchToolPacket> = ({
10
10
  packets,
@@ -3,9 +3,9 @@ import type {
3
3
  ImageGenerationToolPacket,
4
4
  ImageGenerationToolDelta,
5
5
  GeneratedImage,
6
- } from '../../types/streamingModels';
7
- import type { MessageRenderer } from '../../types/interfaces';
8
- import { PacketType } from '../../types/streamingModels';
6
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
7
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
8
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
9
9
 
10
10
  export const ImageToolRenderer: MessageRenderer<ImageGenerationToolPacket> = ({
11
11
  packets,
@@ -1,11 +1,11 @@
1
- import type { ChatPacket } from '../../types/streamingModels';
2
- import type { MessageRenderer } from '../../types/interfaces';
1
+ import type { ChatPacket } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
2
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
3
3
  import { useEffect, useMemo, useState } from 'react';
4
4
  import loadable from '@loadable/component';
5
- import { components } from '../../components/markdown';
6
- import { isFinalAnswerComplete } from '../../services/packetUtils';
7
- import { PacketType } from '../../types/streamingModels';
8
- import { BlinkingDot } from '../../components/BlinkingDot';
5
+ import { components } from '@eeacms/volto-eea-chatbot/ChatBlock/components/markdown';
6
+ import { isFinalAnswerComplete } from '@eeacms/volto-eea-chatbot/ChatBlock/services/packetUtils';
7
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
8
+ import { BlinkingDot } from '@eeacms/volto-eea-chatbot/ChatBlock/components/BlinkingDot';
9
9
 
10
10
  const Markdown: any = loadable(() => import('react-markdown'));
11
11
 
@@ -90,7 +90,11 @@ export const MessageTextRenderer: MessageRenderer<ChatPacket> = ({
90
90
  // If we're far behind, catch up faster
91
91
  const increment =
92
92
  remaining > CATCH_UP_THRESHOLD ? PACKETS_PER_TICK : 1;
93
- return Math.min(prev + increment, packets.length);
93
+ const next = Math.min(prev + increment, packets.length);
94
+ if (isStreamFinished && next === packets.length) {
95
+ console.log(`[MessageTextRenderer] Animation finished: ${next}/${packets.length}`);
96
+ }
97
+ return next;
94
98
  });
95
99
  }, PACKET_DELAY_MS);
96
100
 
@@ -104,8 +108,8 @@ export const MessageTextRenderer: MessageRenderer<ChatPacket> = ({
104
108
  const resetCount = isStreamFinished
105
109
  ? packets.length // Show all if stream is finished
106
110
  : packets.length > 0
107
- ? 1
108
- : 0;
111
+ ? 1
112
+ : 0;
109
113
  setDisplayedPacketCount(resetCount);
110
114
  }
111
115
  }, [animate, packets.length, displayedPacketCount, isStreamFinished]);
@@ -121,6 +125,7 @@ export const MessageTextRenderer: MessageRenderer<ChatPacket> = ({
121
125
  ) {
122
126
  return;
123
127
  }
128
+ console.log(`[MessageTextRenderer] Calling onComplete: packets=${packets.length}, finished=${isStreamFinished}`);
124
129
  onComplete();
125
130
  }
126
131
  }, [
@@ -1,13 +1,13 @@
1
1
  import type {
2
2
  ReasoningPacket,
3
3
  ReasoningDelta,
4
- } from '../../types/streamingModels';
5
- import type { MessageRenderer } from '../../types/interfaces';
4
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
5
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
6
6
  import { useEffect, useState, useRef, useMemo } from 'react';
7
7
  import loadable from '@loadable/component';
8
- import { PacketType } from '../../types/streamingModels';
9
- import { components } from '../../components/markdown';
10
- import { addCitations } from '../../utils/citations';
8
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
9
+ import { components } from '@eeacms/volto-eea-chatbot/ChatBlock/components/markdown';
10
+ import { addCitations } from '@eeacms/volto-eea-chatbot/ChatBlock/utils/citations';
11
11
 
12
12
  const Markdown: any = loadable(() => import('react-markdown'));
13
13
 
@@ -21,6 +21,7 @@ function constructCurrentReasoningState(packets: ReasoningPacket[]) {
21
21
  const hasEnd = packets.some(
22
22
  (p) =>
23
23
  p.obj.type === PacketType.SECTION_END ||
24
+ p.obj.type === PacketType.REASONING_DONE ||
24
25
  // Support either convention for reasoning completion
25
26
  (p.obj as any).type === PacketType.REASONING_END,
26
27
  );
@@ -4,17 +4,17 @@ import type {
4
4
  SearchToolDelta,
5
5
  SectionEnd,
6
6
  OnyxDocument,
7
- } from '../../types/streamingModels';
8
- import type { MessageRenderer } from '../../types/interfaces';
7
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
8
+ import type { MessageRenderer } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
9
9
  import { useEffect, useState, useRef, useMemo } from 'react';
10
- import { PacketType } from '../../types/streamingModels';
11
- import { SourceChip } from '../../components/SourceChip';
12
- import { BlinkingDot } from '../../components/BlinkingDot';
13
- import SVGIcon from '../../components/Icon';
14
- import { WebResultIcon } from '../../components/WebResultIcon';
15
- import SearchIcon from '../../../icons/search.svg';
16
- import GlobeIcon from '../../../icons/globe.svg';
17
- import FileIcon from '../../../icons/file.svg';
10
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
11
+ import { SourceChip } from '@eeacms/volto-eea-chatbot/ChatBlock/components/SourceChip';
12
+ import { BlinkingDot } from '@eeacms/volto-eea-chatbot/ChatBlock/components/BlinkingDot';
13
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
14
+ import { WebResultIcon } from '@eeacms/volto-eea-chatbot/ChatBlock/components/WebResultIcon';
15
+ import SearchIcon from '@eeacms/volto-eea-chatbot/icons/search.svg';
16
+ import GlobeIcon from '@eeacms/volto-eea-chatbot/icons/globe.svg';
17
+ import FileIcon from '@eeacms/volto-eea-chatbot/icons/file.svg';
18
18
 
19
19
  const INITIAL_RESULTS_TO_SHOW = 3;
20
20
  const RESULTS_PER_EXPANSION = 10;
@@ -55,26 +55,33 @@ const constructCurrentSearchState = (
55
55
  isInternetSearch: boolean;
56
56
  } => {
57
57
  const searchStart = packets.find(
58
- (packet) => packet.obj.type === PacketType.SEARCH_TOOL_START,
58
+ (packet) =>
59
+ packet.obj.type === PacketType.SEARCH_TOOL_START ||
60
+ packet.obj.type === PacketType.SEARCH_TOOL_START_V3,
59
61
  )?.obj as SearchToolStart | null;
60
62
 
61
63
  const searchDeltas = packets
62
- .filter((packet) => packet.obj.type === PacketType.SEARCH_TOOL_DELTA)
63
- .map((packet) => packet.obj as SearchToolDelta);
64
+ .filter(
65
+ (packet) =>
66
+ packet.obj.type === PacketType.SEARCH_TOOL_DELTA ||
67
+ packet.obj.type === PacketType.SEARCH_TOOL_QUERIES_DELTA ||
68
+ packet.obj.type === PacketType.SEARCH_TOOL_DOCUMENTS_DELTA,
69
+ )
70
+ .map((packet) => packet.obj);
64
71
 
65
72
  const searchEnd = packets.find(
66
73
  (packet) => packet.obj.type === PacketType.SECTION_END,
67
74
  )?.obj as SectionEnd | null;
68
75
 
69
- // Extract queries from ToolDelta packets
76
+ // Extract queries from various delta packets
70
77
  const queries = searchDeltas
71
- .flatMap((delta) => delta?.queries || [])
78
+ .flatMap((delta: any) => delta?.queries || [])
72
79
  .filter((query, index, arr) => arr.indexOf(query) === index); // Remove duplicates
73
80
 
74
81
  const seenDocIds = new Set<string>();
75
82
  const results = searchDeltas
76
- .flatMap((delta) => delta?.documents || [])
77
- .filter((doc) => {
83
+ .flatMap((delta: any) => delta?.documents || [])
84
+ .filter((doc: OnyxDocument) => {
78
85
  if (!doc || !doc.document_id) return false;
79
86
  if (seenDocIds.has(doc.document_id)) return false;
80
87
  seenDocIds.add(doc.document_id);
@@ -202,8 +209,8 @@ export const SearchToolRenderer: MessageRenderer<SearchToolPacket> = ({
202
209
  <SVGIcon name={isInternetSearch ? GlobeIcon : SearchIcon} size={size} />
203
210
  );
204
211
 
205
- // Don't render anything if search hasn't started
206
- if (queries.length === 0) {
212
+ // Don't render anything if search hasn't started or has no data yet
213
+ if (queries.length === 0 && results.length === 0) {
207
214
  return children({
208
215
  icon: IconComponent,
209
216
  status: status,
@@ -218,7 +225,9 @@ export const SearchToolRenderer: MessageRenderer<SearchToolPacket> = ({
218
225
  <div className="search-tool-renderer">
219
226
  <div className="queries-section">
220
227
  <div className="queries-header">
221
- <strong>Queries</strong>
228
+ <strong>
229
+ {isInternetSearch ? 'Web Queries' : 'Internal Search Queries'}
230
+ </strong>
222
231
  </div>
223
232
  <div className="queries-list">
224
233
  {queries.slice(0, queriesToShow).map((query, index) => (
@@ -262,7 +271,7 @@ export const SearchToolRenderer: MessageRenderer<SearchToolPacket> = ({
262
271
 
263
272
  <div className="results-section">
264
273
  <div className="results-header">
265
- <strong>{isInternetSearch ? 'Results' : 'Documents'}</strong>
274
+ <strong>{isInternetSearch ? 'Web Results' : 'Documents'}</strong>
266
275
  </div>
267
276
 
268
277
  <div className="results-list">
@@ -104,6 +104,7 @@ export function ChatBlockSchema({ assistants, data }) {
104
104
  'scrollToInput',
105
105
  'showAssistantTitle',
106
106
  'showAssistantDescription',
107
+ 'onyxVersion',
107
108
  ],
108
109
  },
109
110
  ],
@@ -368,6 +369,18 @@ range is from 0 to 100`,
368
369
  type: 'boolean',
369
370
  default: true,
370
371
  },
372
+ onyxVersion: {
373
+ title: 'Onyx API version',
374
+ choices: [
375
+ ['2', 'Onyx 2.x'],
376
+ ['3', 'Onyx 3.x'],
377
+ ],
378
+ default: '2',
379
+ description:
380
+ 'Select which Onyx API version the backend is running. ' +
381
+ 'Onyx 2.x uses the legacy send-message payload; ' +
382
+ 'Onyx 3.x uses the new send-message payload with placement-based streaming.',
383
+ },
371
384
  showAssistantPrompts: {
372
385
  title: 'Show predefined prompts',
373
386
  type: 'boolean',
@@ -5,14 +5,17 @@ import type {
5
5
  OnyxDocument,
6
6
  Packet,
7
7
  StreamingCitation,
8
- } from '../types/streamingModels';
9
- import type { Message, ToolCallMetadata } from '../types/interfaces';
8
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
9
+ import type {
10
+ Message,
11
+ ToolCallMetadata,
12
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
10
13
  import {
11
14
  getSynteticPacket,
12
15
  isToolPacket,
13
16
  isDisplayPacket,
14
17
  } from './packetUtils';
15
- import { PacketType } from '../types/streamingModels';
18
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
16
19
 
17
20
  /**
18
21
  * Process streaming packets into a message object
@@ -108,17 +111,26 @@ export class MessageProcessor {
108
111
  this.indicesStarted.push(packet.ind);
109
112
  }
110
113
 
111
- // Send synthetic SECTION_END when needed
112
- if (
113
- packet.obj.type === PacketType.SECTION_END &&
114
- this.indicesStarted.length > 0
115
- ) {
116
- processedPacket = getSynteticPacket(
117
- this.indicesStarted.shift()!,
118
- PacketType.SECTION_END,
119
- );
120
- } else if (packet.obj.type === PacketType.SECTION_END) {
121
- return;
114
+ // Handle SECTION_END (v2/v3 compatible)
115
+ if (packet.obj.type === PacketType.SECTION_END) {
116
+ let targetInd = packet.ind;
117
+
118
+ // If ind is -1 (common in v2), use synthetic logic
119
+ if (targetInd === -1 && this.indicesStarted.length > 0) {
120
+ targetInd = this.indicesStarted.shift()!;
121
+ } else if (targetInd !== -1) {
122
+ // If ind is provided (v3), remove it from indicesStarted if present
123
+ const startIdx = this.indicesStarted.indexOf(targetInd);
124
+ if (startIdx > -1) {
125
+ this.indicesStarted.splice(startIdx, 1);
126
+ }
127
+ }
128
+
129
+ if (targetInd === -1) {
130
+ return; // Skip if we can't map it to a turn
131
+ }
132
+
133
+ processedPacket = getSynteticPacket(targetInd, PacketType.SECTION_END);
122
134
  }
123
135
 
124
136
  const { ind } = processedPacket;
@@ -126,6 +138,10 @@ export class MessageProcessor {
126
138
  // Store processed packet for later aggregation
127
139
  this.packets.push(processedPacket);
128
140
 
141
+ if (processedPacket.obj.type === PacketType.MESSAGE_START || processedPacket.obj.type === PacketType.SECTION_END) {
142
+ console.log(`[MessageProcessor] Processed ${processedPacket.obj.type} for ind=${processedPacket.ind}`);
143
+ }
144
+
129
145
  // Group packets by index for later processing
130
146
  if (!this.groupedPackets.has(ind)) {
131
147
  this.groupedPackets.set(ind, []);
@@ -171,6 +187,7 @@ export class MessageProcessor {
171
187
  PacketType.MESSAGE_START,
172
188
  PacketType.SEARCH_TOOL_DELTA,
173
189
  PacketType.FETCH_TOOL_START,
190
+ PacketType.SEARCH_TOOL_DOCUMENTS_DELTA,
174
191
  ].includes(packet.obj.type)
175
192
  ) {
176
193
  return;
@@ -178,7 +195,8 @@ export class MessageProcessor {
178
195
  let newDocuments = false;
179
196
  const data = packet.obj as any;
180
197
  const documents = data.final_documents || data.documents;
181
- if (documents) {
198
+
199
+ if (documents && Array.isArray(documents)) {
182
200
  documents.forEach((doc: OnyxDocument) => {
183
201
  const docId = doc.document_id;
184
202
  if (docId && !this.documentMap.has(docId)) {
@@ -187,8 +205,23 @@ export class MessageProcessor {
187
205
  }
188
206
  });
189
207
  }
208
+
190
209
  if (newDocuments) {
191
210
  this._documents = Array.from(this.documentMap.values());
211
+
212
+ // If we have final_documents and no citations yet, create a fallback mapping
213
+ // This ensures the Sources tab shows up in v3 when citation_info is missing
214
+ if (
215
+ packet.obj.type === PacketType.MESSAGE_START &&
216
+ data.final_documents &&
217
+ this._citations.size === 0
218
+ ) {
219
+ data.final_documents.forEach((doc: OnyxDocument, index: number) => {
220
+ if (doc.document_id) {
221
+ this._citations.set(index + 1, doc.document_id);
222
+ }
223
+ });
224
+ }
192
225
  }
193
226
  }
194
227
 
@@ -197,6 +230,16 @@ export class MessageProcessor {
197
230
  * Updates the internal citation collection and notifies when new citations are added
198
231
  */
199
232
  private processCitations(packet: Packet) {
233
+ if (packet.obj.type === PacketType.CITATION_INFO) {
234
+ const citationInfo = packet.obj as any;
235
+ if (citationInfo.citation_number && citationInfo.document_id) {
236
+ this._citations.set(
237
+ citationInfo.citation_number,
238
+ citationInfo.document_id,
239
+ );
240
+ }
241
+ return;
242
+ }
200
243
  if (packet.obj.type !== PacketType.CITATION_DELTA) {
201
244
  return;
202
245
  }
@@ -234,6 +277,13 @@ export class MessageProcessor {
234
277
  */
235
278
  private processStreamEnd(packet: Packet) {
236
279
  if ([PacketType.STOP, PacketType.ERROR].includes(packet.obj.type)) {
280
+ // Close any remaining open sections (especially the last one)
281
+ while (this.indicesStarted.length > 0) {
282
+ const ind = this.indicesStarted.shift()!;
283
+ console.log(`[MessageProcessor] Stream ended. Synthesizing section_end for ind=${ind}`);
284
+ const synthetic = getSynteticPacket(ind, PacketType.SECTION_END);
285
+ this.processPacket(synthetic);
286
+ }
237
287
  this._isComplete = true;
238
288
  }
239
289
  }
@@ -245,7 +295,9 @@ export class MessageProcessor {
245
295
  private extractToolCall(packets: Packet[]): ToolCallMetadata | null {
246
296
  // Look for search tool packets
247
297
  const searchToolStart = packets.find(
248
- (p) => p.obj.type === PacketType.SEARCH_TOOL_START,
298
+ (p) =>
299
+ p.obj.type === PacketType.SEARCH_TOOL_START ||
300
+ p.obj.type === PacketType.SEARCH_TOOL_START_V3,
249
301
  );
250
302
 
251
303
  if (!searchToolStart) {
@@ -257,7 +309,10 @@ export class MessageProcessor {
257
309
  const processedDocs: Record<string, any> = {};
258
310
 
259
311
  for (const packet of packets) {
260
- if (packet.obj.type === PacketType.SEARCH_TOOL_DELTA) {
312
+ if (
313
+ packet.obj.type === PacketType.SEARCH_TOOL_DELTA ||
314
+ packet.obj.type === PacketType.SEARCH_TOOL_DOCUMENTS_DELTA
315
+ ) {
261
316
  const delta = packet.obj as any;
262
317
  if (delta.documents && Array.isArray(delta.documents)) {
263
318
  delta.documents.forEach((doc: any) => {