@eeacms/volto-eea-chatbot 1.0.9

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 (133) hide show
  1. package/.coverage.babel.config.js +9 -0
  2. package/.eslintrc.js +68 -0
  3. package/.husky/pre-commit +2 -0
  4. package/.release-it.json +17 -0
  5. package/AGENTS.md +89 -0
  6. package/CHANGELOG.md +770 -0
  7. package/DEVELOP.md +124 -0
  8. package/LICENSE.md +9 -0
  9. package/README.md +170 -0
  10. package/RELEASE.md +74 -0
  11. package/TESTING.md +5 -0
  12. package/babel.config.js +17 -0
  13. package/bootstrap +41 -0
  14. package/cypress.config.js +27 -0
  15. package/docker-compose.yml +32 -0
  16. package/jest-addon.config.js +465 -0
  17. package/jest.setup.js +65 -0
  18. package/locales/de/LC_MESSAGES/volto.po +14 -0
  19. package/locales/en/LC_MESSAGES/volto.po +14 -0
  20. package/locales/it/LC_MESSAGES/volto.po +14 -0
  21. package/locales/ro/LC_MESSAGES/volto.po +14 -0
  22. package/locales/volto.pot +16 -0
  23. package/package.json +98 -0
  24. package/razzle.extend.js +40 -0
  25. package/src/ChatBlock/ChatBlockEdit.jsx +46 -0
  26. package/src/ChatBlock/ChatBlockView.jsx +21 -0
  27. package/src/ChatBlock/chat/AIMessage.tsx +566 -0
  28. package/src/ChatBlock/chat/ChatMessage.tsx +35 -0
  29. package/src/ChatBlock/chat/ChatWindow.tsx +288 -0
  30. package/src/ChatBlock/chat/UserMessage.tsx +27 -0
  31. package/src/ChatBlock/chat/index.ts +4 -0
  32. package/src/ChatBlock/components/AutoResizeTextarea.jsx +67 -0
  33. package/src/ChatBlock/components/BlinkingDot.tsx +3 -0
  34. package/src/ChatBlock/components/ChatMessageFeedback.jsx +77 -0
  35. package/src/ChatBlock/components/EmptyState.jsx +70 -0
  36. package/src/ChatBlock/components/FeedbackModal.jsx +125 -0
  37. package/src/ChatBlock/components/HalloumiFeedback.jsx +126 -0
  38. package/src/ChatBlock/components/Icon.tsx +35 -0
  39. package/src/ChatBlock/components/QualityCheckToggle.jsx +26 -0
  40. package/src/ChatBlock/components/RelatedQuestions.jsx +59 -0
  41. package/src/ChatBlock/components/Source.jsx +93 -0
  42. package/src/ChatBlock/components/SourceChip.tsx +55 -0
  43. package/src/ChatBlock/components/Spinner.jsx +3 -0
  44. package/src/ChatBlock/components/UserActionsToolbar.jsx +44 -0
  45. package/src/ChatBlock/components/WebResultIcon.tsx +42 -0
  46. package/src/ChatBlock/components/markdown/Citation.jsx +70 -0
  47. package/src/ChatBlock/components/markdown/ClaimModal.jsx +98 -0
  48. package/src/ChatBlock/components/markdown/ClaimSegments.jsx +172 -0
  49. package/src/ChatBlock/components/markdown/RenderClaimView.jsx +96 -0
  50. package/src/ChatBlock/components/markdown/colors.js +29 -0
  51. package/src/ChatBlock/components/markdown/colors.less +52 -0
  52. package/src/ChatBlock/components/markdown/colors.test.js +69 -0
  53. package/src/ChatBlock/components/markdown/index.js +115 -0
  54. package/src/ChatBlock/fonts/DejaVuSans.ttf +0 -0
  55. package/src/ChatBlock/hocs/withOnyxData.jsx +46 -0
  56. package/src/ChatBlock/hooks/index.ts +7 -0
  57. package/src/ChatBlock/hooks/useChatController.ts +333 -0
  58. package/src/ChatBlock/hooks/useChatStreaming.ts +82 -0
  59. package/src/ChatBlock/hooks/useDeepCompareMemoize.js +17 -0
  60. package/src/ChatBlock/hooks/useMarked.js +44 -0
  61. package/src/ChatBlock/hooks/useQualityMarkers.js +119 -0
  62. package/src/ChatBlock/hooks/useScrollonStream.ts +131 -0
  63. package/src/ChatBlock/hooks/useToolDisplayTiming.ts +80 -0
  64. package/src/ChatBlock/index.js +32 -0
  65. package/src/ChatBlock/packets/MultiToolRenderer.tsx +235 -0
  66. package/src/ChatBlock/packets/RendererComponent.tsx +115 -0
  67. package/src/ChatBlock/packets/index.ts +4 -0
  68. package/src/ChatBlock/packets/renderers/CustomToolRenderer.tsx +63 -0
  69. package/src/ChatBlock/packets/renderers/FetchToolRenderer.tsx +59 -0
  70. package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +62 -0
  71. package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +172 -0
  72. package/src/ChatBlock/packets/renderers/ReasoningRenderer.tsx +122 -0
  73. package/src/ChatBlock/packets/renderers/SearchToolRenderer.tsx +323 -0
  74. package/src/ChatBlock/packets/renderers/index.ts +6 -0
  75. package/src/ChatBlock/schema.js +403 -0
  76. package/src/ChatBlock/services/index.ts +3 -0
  77. package/src/ChatBlock/services/messageProcessor.ts +348 -0
  78. package/src/ChatBlock/services/packetUtils.ts +48 -0
  79. package/src/ChatBlock/services/streamingService.ts +342 -0
  80. package/src/ChatBlock/style.less +1881 -0
  81. package/src/ChatBlock/tests/AIMessage.test.jsx +95 -0
  82. package/src/ChatBlock/tests/AutoResizeTextarea.test.jsx +49 -0
  83. package/src/ChatBlock/tests/BlinkingDot.test.jsx +71 -0
  84. package/src/ChatBlock/tests/ChatMessageFeedback.test.jsx +73 -0
  85. package/src/ChatBlock/tests/Citation.test.jsx +107 -0
  86. package/src/ChatBlock/tests/EmptyState.test.jsx +137 -0
  87. package/src/ChatBlock/tests/FeedbackModal.test.jsx +138 -0
  88. package/src/ChatBlock/tests/HalloumiFeedback.test.jsx +94 -0
  89. package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +105 -0
  90. package/src/ChatBlock/tests/RelatedQuestions.test.jsx +215 -0
  91. package/src/ChatBlock/tests/Source.test.jsx +79 -0
  92. package/src/ChatBlock/tests/Spinner.test.jsx +18 -0
  93. package/src/ChatBlock/tests/index.test.js +51 -0
  94. package/src/ChatBlock/tests/messageProcessor.test.jsx +154 -0
  95. package/src/ChatBlock/tests/schema.test.js +166 -0
  96. package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +107 -0
  97. package/src/ChatBlock/tests/useToolDisplayTiming.test.jsx +151 -0
  98. package/src/ChatBlock/types/cssmodules.d.ts +7 -0
  99. package/src/ChatBlock/types/interfaces.ts +154 -0
  100. package/src/ChatBlock/types/slate.d.ts +3 -0
  101. package/src/ChatBlock/types/streamingModels.ts +267 -0
  102. package/src/ChatBlock/types/volto.d.ts +3 -0
  103. package/src/ChatBlock/utils/citations.ts +25 -0
  104. package/src/ChatBlock/utils/index.tsx +114 -0
  105. package/src/halloumi/README.md +1 -0
  106. package/src/halloumi/generative.js +219 -0
  107. package/src/halloumi/generative.test.js +88 -0
  108. package/src/halloumi/middleware.js +70 -0
  109. package/src/halloumi/postprocessing.js +273 -0
  110. package/src/halloumi/postprocessing.test.js +441 -0
  111. package/src/halloumi/preprocessing.js +115 -0
  112. package/src/halloumi/preprocessing.test.js +245 -0
  113. package/src/icons/bot.svg +1 -0
  114. package/src/icons/check.svg +1 -0
  115. package/src/icons/chevron.svg +3 -0
  116. package/src/icons/clear.svg +1 -0
  117. package/src/icons/copy.svg +1 -0
  118. package/src/icons/done.svg +5 -0
  119. package/src/icons/external-link.svg +1 -0
  120. package/src/icons/file.svg +1 -0
  121. package/src/icons/glasses.svg +1 -0
  122. package/src/icons/globe.svg +1 -0
  123. package/src/icons/rotate.svg +1 -0
  124. package/src/icons/search.svg +5 -0
  125. package/src/icons/send.svg +1 -0
  126. package/src/icons/square-pen.svg +1 -0
  127. package/src/icons/stop.svg +9 -0
  128. package/src/icons/thumbs-down.svg +1 -0
  129. package/src/icons/thumbs-up.svg +1 -0
  130. package/src/icons/user.svg +1 -0
  131. package/src/index.js +58 -0
  132. package/src/middleware.js +250 -0
  133. package/tsconfig.json +40 -0
@@ -0,0 +1,348 @@
1
+ import type {
2
+ CitationDelta,
3
+ MessageDelta,
4
+ MessageStart,
5
+ OnyxDocument,
6
+ Packet,
7
+ StreamingCitation,
8
+ } from '../types/streamingModels';
9
+ import type { Message, ToolCallMetadata } from '../types/interfaces';
10
+ import {
11
+ getSynteticPacket,
12
+ isToolPacket,
13
+ isDisplayPacket,
14
+ } from './packetUtils';
15
+ import { PacketType } from '../types/streamingModels';
16
+
17
+ /**
18
+ * Process streaming packets into a message object
19
+ */
20
+ export class MessageProcessor {
21
+ private packets: Packet[] = [];
22
+ private groupedPackets = new Map<number, Packet[]>();
23
+ private toolPackets: number[] = [];
24
+ private displayPackets: number[] = [];
25
+ private userMessageId: number | null = null;
26
+ private assistantMessageId: number | null = null;
27
+ private documentMap = new Map<string, OnyxDocument>();
28
+ private indicesStarted: number[] = [];
29
+ private _textContent: string = '';
30
+ private _errorContent: string = '';
31
+ private _documents: OnyxDocument[] = [];
32
+ private _citations = new Map<number, string>();
33
+ private _isComplete: boolean = false;
34
+ private _isFinalMessageComing: boolean = false;
35
+
36
+ constructor(
37
+ private nodeId: number,
38
+ private parentNodeId: number | null,
39
+ ) {}
40
+
41
+ /**
42
+ * Add new packets to the processor
43
+ */
44
+ addPackets(newPackets: Packet[]): void {
45
+ for (const packet of newPackets) {
46
+ this.processPacket(packet);
47
+ this.processMessageIdsInfo(packet);
48
+ this.processTextContent(packet);
49
+ this.processDocuments(packet);
50
+ this.processCitations(packet);
51
+ this.processFinalMessageComming(packet);
52
+ this.processError(packet);
53
+ this.processStreamEnd(packet);
54
+ }
55
+ }
56
+
57
+ // Getters
58
+ /**
59
+ * Indicates if the streaming has completed
60
+ */
61
+ get isComplete(): boolean {
62
+ return this._isComplete;
63
+ }
64
+
65
+ /**
66
+ * Indicates if the final message is about to come
67
+ */
68
+ get isFinalMessageComing(): boolean {
69
+ return this._isFinalMessageComing;
70
+ }
71
+
72
+ /**
73
+ * Get the real database message IDs from backend
74
+ */
75
+ get messageIds(): {
76
+ userMessageId: number | null;
77
+ assistantMessageId: number | null;
78
+ } {
79
+ return {
80
+ userMessageId: this.userMessageId,
81
+ assistantMessageId: this.assistantMessageId,
82
+ };
83
+ }
84
+
85
+ // Packet processing
86
+ /**
87
+ * Process a single packet and track its lifecycle
88
+ */
89
+ private processPacket(packet: Packet) {
90
+ let processedPacket: Packet = packet;
91
+
92
+ // Store tool packets indices
93
+ if (isToolPacket(packet) && !this.toolPackets.includes(packet.ind)) {
94
+ this.toolPackets.push(packet.ind);
95
+ }
96
+
97
+ // Store display packets indices
98
+ if (isDisplayPacket(packet) && !this.displayPackets.includes(packet.ind)) {
99
+ this.displayPackets.push(packet.ind);
100
+ }
101
+
102
+ // Keep track of all started indices to know when to send SECTION_END
103
+ if (
104
+ packet.ind > -1 &&
105
+ packet.obj.type !== PacketType.SECTION_END &&
106
+ !this.indicesStarted.includes(packet.ind)
107
+ ) {
108
+ this.indicesStarted.push(packet.ind);
109
+ }
110
+
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;
122
+ }
123
+
124
+ const { ind } = processedPacket;
125
+
126
+ // Store processed packet for later aggregation
127
+ this.packets.push(processedPacket);
128
+
129
+ // Group packets by index for later processing
130
+ if (!this.groupedPackets.has(ind)) {
131
+ this.groupedPackets.set(ind, []);
132
+ }
133
+ this.groupedPackets.get(ind)!.push(processedPacket);
134
+ }
135
+
136
+ /**
137
+ * Process MESSAGE_END_ID_INFO packets to extract message IDs
138
+ * These packets contain the actual database message IDs assigned by the backend
139
+ */
140
+ private processMessageIdsInfo(packet: Packet) {
141
+ if (packet.obj.type !== PacketType.MESSAGE_END_ID_INFO) {
142
+ return;
143
+ }
144
+ const idInfo = packet.obj;
145
+ this.userMessageId = idInfo.user_message_id;
146
+ this.assistantMessageId = idInfo.reserved_assistant_message_id;
147
+ }
148
+
149
+ /**
150
+ * Process text content from MESSAGE_START and MESSAGE_DELTA packets
151
+ * Accumulates text content across multiple packets
152
+ */
153
+ private processTextContent(packet: Packet) {
154
+ if (
155
+ [PacketType.MESSAGE_START, PacketType.MESSAGE_DELTA].includes(
156
+ packet.obj.type,
157
+ )
158
+ ) {
159
+ const content = (packet.obj as MessageStart | MessageDelta).content || '';
160
+ this._textContent += content;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Process document information from various tool packets
166
+ * Updates the internal document collection and notifies when new documents are added
167
+ */
168
+ private processDocuments(packet: Packet) {
169
+ if (
170
+ ![
171
+ PacketType.MESSAGE_START,
172
+ PacketType.SEARCH_TOOL_DELTA,
173
+ PacketType.FETCH_TOOL_START,
174
+ ].includes(packet.obj.type)
175
+ ) {
176
+ return;
177
+ }
178
+ let newDocuments = false;
179
+ const data = packet.obj as any;
180
+ const documents = data.final_documents || data.documents;
181
+ if (documents) {
182
+ documents.forEach((doc: OnyxDocument) => {
183
+ const docId = doc.document_id;
184
+ if (docId && !this.documentMap.has(docId)) {
185
+ this.documentMap.set(docId, doc);
186
+ newDocuments = true;
187
+ }
188
+ });
189
+ }
190
+ if (newDocuments) {
191
+ this._documents = Array.from(this.documentMap.values());
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Process citation information from CITATION_DELTA packets
197
+ * Updates the internal citation collection and notifies when new citations are added
198
+ */
199
+ private processCitations(packet: Packet) {
200
+ if (packet.obj.type !== PacketType.CITATION_DELTA) {
201
+ return;
202
+ }
203
+ const citationDelta = packet.obj as CitationDelta;
204
+ citationDelta.citations?.forEach((citation: StreamingCitation) => {
205
+ if (!this._citations.has(citation.citation_num)) {
206
+ this._citations.set(citation.citation_num, citation.document_id);
207
+ }
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Check if a packet indicates the final message is about to be sent
213
+ * Sets the _isFinalMessageComing flag when appropriate
214
+ */
215
+ private processFinalMessageComming(packet: Packet) {
216
+ if (
217
+ [
218
+ PacketType.MESSAGE_START,
219
+ PacketType.IMAGE_GENERATION_TOOL_START,
220
+ ].includes(packet.obj.type)
221
+ ) {
222
+ this._isFinalMessageComing = true;
223
+ }
224
+ }
225
+
226
+ private processError(packet: Packet) {
227
+ if (packet.obj.type === PacketType.ERROR) {
228
+ this._errorContent = packet.obj.error;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Handle STOP packets to mark streaming as complete
234
+ */
235
+ private processStreamEnd(packet: Packet) {
236
+ if ([PacketType.STOP, PacketType.ERROR].includes(packet.obj.type)) {
237
+ this._isComplete = true;
238
+ }
239
+ }
240
+
241
+ // Utility methods
242
+ /**
243
+ * Extract tool call information from packets
244
+ */
245
+ private extractToolCall(packets: Packet[]): ToolCallMetadata | null {
246
+ // Look for search tool packets
247
+ const searchToolStart = packets.find(
248
+ (p) => p.obj.type === PacketType.SEARCH_TOOL_START,
249
+ );
250
+
251
+ if (!searchToolStart) {
252
+ return null;
253
+ }
254
+
255
+ // Collect all documents from SEARCH_TOOL_DELTA packets
256
+ const toolDocuments: any[] = [];
257
+ const processedDocs: Record<string, any> = {};
258
+
259
+ for (const packet of packets) {
260
+ if (packet.obj.type === PacketType.SEARCH_TOOL_DELTA) {
261
+ const delta = packet.obj as any;
262
+ if (delta.documents && Array.isArray(delta.documents)) {
263
+ delta.documents.forEach((doc: any) => {
264
+ if (!processedDocs[doc.document_id]) {
265
+ processedDocs[doc.document_id] = doc;
266
+ toolDocuments.push(doc);
267
+ }
268
+ });
269
+ }
270
+ }
271
+ }
272
+
273
+ // If we have documents, create the tool call metadata
274
+ if (toolDocuments.length > 0) {
275
+ return {
276
+ tool_name: 'run_search',
277
+ tool_args: {
278
+ query: '', // Query info might be in SEARCH_TOOL_DELTA.queries
279
+ },
280
+ tool_result: toolDocuments.map((doc) => ({
281
+ ...doc,
282
+ content: doc.content || doc.blurb,
283
+ })),
284
+ };
285
+ }
286
+
287
+ return null;
288
+ }
289
+
290
+ /**
291
+ * Get the current message state
292
+ */
293
+ getMessage(): Message {
294
+ let toolCall = null;
295
+
296
+ if (this._isComplete) {
297
+ // Extract tool call information
298
+ toolCall = this._isComplete ? this.extractToolCall(this.packets) : null;
299
+ }
300
+
301
+ return {
302
+ messageId: this.assistantMessageId,
303
+ nodeId: this.nodeId,
304
+ message: this._textContent,
305
+ error: this._errorContent,
306
+ type: 'assistant',
307
+ parentNodeId: this.parentNodeId,
308
+ packets: [...this.packets],
309
+ groupedPackets: Array.from(this.groupedPackets.entries())
310
+ .map(([ind, packets]) => ({
311
+ ind,
312
+ packets: [...packets],
313
+ }))
314
+ .sort((a, b) => a.ind - b.ind),
315
+ toolPackets: [...this.toolPackets],
316
+ displayPackets: [...this.displayPackets],
317
+ documents: this._documents.length > 0 ? [...this._documents] : null,
318
+ citations:
319
+ this._citations.size > 0
320
+ ? Object.fromEntries(this._citations)
321
+ : undefined,
322
+ files: [],
323
+ isComplete: this._isComplete,
324
+ isFinalMessageComing: this._isFinalMessageComing,
325
+ toolCall,
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Reset the processor
331
+ */
332
+ reset(): void {
333
+ this.packets = [];
334
+ this.groupedPackets.clear();
335
+ this.toolPackets = [];
336
+ this.displayPackets = [];
337
+ this.userMessageId = null;
338
+ this.assistantMessageId = null;
339
+ this.documentMap.clear();
340
+ this.indicesStarted = [];
341
+ this._textContent = '';
342
+ this._errorContent = '';
343
+ this._documents = [];
344
+ this._citations.clear();
345
+ this._isComplete = false;
346
+ this._isFinalMessageComing = false;
347
+ }
348
+ }
@@ -0,0 +1,48 @@
1
+ import type { Packet } from '../types/streamingModels';
2
+ import { PacketType } from '../types/streamingModels';
3
+
4
+ export function getSynteticPacket(ind: number, type: PacketType): Packet {
5
+ return {
6
+ ind,
7
+ obj: { type } as any,
8
+ };
9
+ }
10
+
11
+ export function isToolPacket(packet: Packet): boolean {
12
+ const toolPacketTypes = [
13
+ PacketType.SEARCH_TOOL_START,
14
+ PacketType.SEARCH_TOOL_DELTA,
15
+ PacketType.CUSTOM_TOOL_START,
16
+ PacketType.CUSTOM_TOOL_DELTA,
17
+ PacketType.REASONING_START,
18
+ PacketType.REASONING_DELTA,
19
+ PacketType.FETCH_TOOL_START,
20
+ ];
21
+
22
+ return toolPacketTypes.includes(packet.obj.type as PacketType);
23
+ }
24
+
25
+ export function isDisplayPacket(packet: Packet): boolean {
26
+ return [
27
+ PacketType.MESSAGE_START,
28
+ PacketType.IMAGE_GENERATION_TOOL_START,
29
+ ].includes(packet.obj.type as PacketType);
30
+ }
31
+
32
+ export function isFinalAnswerComplete(packets: Packet[]): boolean {
33
+ const messageStartPacket = packets.find(
34
+ (packet) =>
35
+ packet.obj.type === PacketType.MESSAGE_START ||
36
+ packet.obj.type === PacketType.IMAGE_GENERATION_TOOL_START,
37
+ );
38
+
39
+ if (!messageStartPacket) {
40
+ return false;
41
+ }
42
+
43
+ return packets.some(
44
+ (packet) =>
45
+ packet.obj.type === PacketType.SECTION_END &&
46
+ packet.ind === messageStartPacket.ind,
47
+ );
48
+ }