@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,342 @@
1
+ import { PacketType, type Packet } from '../types/streamingModels';
2
+ import type { FileDescriptor, Filters } from '../types/interfaces';
3
+
4
+ export interface SendMessageParams {
5
+ regenerate: boolean;
6
+ message: string;
7
+ fileDescriptors?: FileDescriptor[];
8
+ parentMessageId: number | null;
9
+ chatSessionId: string;
10
+ filters: Filters | null;
11
+ selectedDocumentIds: number[] | null;
12
+ queryOverride?: string;
13
+ forceSearch?: boolean;
14
+ modelProvider?: string;
15
+ modelVersion?: string;
16
+ temperature?: number;
17
+ systemPromptOverride?: string;
18
+ taskPromptOverride?: string;
19
+ useExistingUserMessage?: boolean;
20
+ alternateAssistantId?: number;
21
+ signal?: AbortSignal;
22
+ currentMessageFiles?: FileDescriptor[];
23
+ useAgentSearch?: boolean;
24
+ enabledToolIds?: number[];
25
+ forcedToolIds?: number[];
26
+ retrieval_options?: any;
27
+ }
28
+
29
+ export interface StreamResponse {
30
+ packets: Packet[];
31
+ error?: string;
32
+ isComplete: boolean;
33
+ }
34
+
35
+ /**
36
+ * Process a single chunk from the stream
37
+ */
38
+ const processSingleChunk = (
39
+ chunk: string,
40
+ currPartialChunk: string | null,
41
+ ): [any | null, string | null] => {
42
+ const completeChunk = (currPartialChunk || '') + chunk;
43
+ try {
44
+ // Every complete chunk should be valid JSON
45
+ const chunkJson = JSON.parse(completeChunk);
46
+ return [chunkJson, null];
47
+ } catch (err) {
48
+ // If it's not valid JSON, then it's probably an incomplete chunk
49
+ return [null, completeChunk];
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Process raw chunk string that may contain multiple packets
55
+ */
56
+ export const processRawChunkString = (
57
+ rawChunkString: string,
58
+ previousPartialChunk: string | null,
59
+ ): [any[], string | null] => {
60
+ if (!rawChunkString) {
61
+ return [[], null];
62
+ }
63
+
64
+ const chunkSections = rawChunkString
65
+ .split('\n')
66
+ .filter((chunk) => chunk.length > 0);
67
+
68
+ const parsedChunkSections: any[] = [];
69
+ let currPartialChunk = previousPartialChunk;
70
+
71
+ chunkSections.forEach((chunk) => {
72
+ const [processedChunk, partialChunk] = processSingleChunk(
73
+ chunk,
74
+ currPartialChunk,
75
+ );
76
+ if (processedChunk) {
77
+ parsedChunkSections.push(processedChunk);
78
+ currPartialChunk = null;
79
+ } else {
80
+ currPartialChunk = partialChunk;
81
+ }
82
+ });
83
+
84
+ return [parsedChunkSections, currPartialChunk];
85
+ };
86
+
87
+ /**
88
+ * Handle streaming response from the backend
89
+ */
90
+ export async function* handleStream(
91
+ streamingResponse: Response,
92
+ ): AsyncGenerator<Packet[], void, unknown> {
93
+ const reader = streamingResponse.body?.getReader();
94
+ if (!reader) {
95
+ throw new Error('No reader available from response');
96
+ }
97
+
98
+ const decoder = new TextDecoder('utf-8');
99
+ let previousPartialChunk: string | null = null;
100
+
101
+ while (true) {
102
+ const rawChunk = await reader.read();
103
+ if (!rawChunk) {
104
+ throw new Error('Unable to process chunk');
105
+ }
106
+
107
+ const { done, value } = rawChunk;
108
+ if (done) {
109
+ break;
110
+ }
111
+
112
+ const [completedChunks, partialChunk] = processRawChunkString(
113
+ decoder.decode(value, { stream: true }),
114
+ previousPartialChunk,
115
+ );
116
+
117
+ if (!completedChunks.length && !partialChunk) {
118
+ break;
119
+ }
120
+
121
+ previousPartialChunk = partialChunk;
122
+
123
+ // Convert chunks to packets
124
+ const packets: Packet[] = completedChunks
125
+ .filter((chunk) => chunk && typeof chunk === 'object')
126
+ .map((chunk) => {
127
+ // Onyx v2 format: { ind: number, obj: { type: string, ... } }
128
+ if ('ind' in chunk && 'obj' in chunk) {
129
+ return chunk as Packet;
130
+ }
131
+
132
+ // Handle MessageResponseIDInfo (special case without ind/obj)
133
+ if (
134
+ 'user_message_id' in chunk &&
135
+ 'reserved_assistant_message_id' in chunk
136
+ ) {
137
+ // Just pass it through as is - MessageProcessor will handle it
138
+ return {
139
+ ind: -1,
140
+ obj: {
141
+ type: PacketType.MESSAGE_END_ID_INFO,
142
+ user_message_id: chunk.user_message_id,
143
+ reserved_assistant_message_id:
144
+ chunk.reserved_assistant_message_id,
145
+ },
146
+ };
147
+ }
148
+
149
+ if ('error' in chunk) {
150
+ return {
151
+ ind: -1,
152
+ obj: { type: PacketType.ERROR, error: chunk.error },
153
+ };
154
+ }
155
+ // Handle legacy format if needed
156
+ return null;
157
+ })
158
+ .filter((p): p is Packet => p !== null);
159
+
160
+ if (packets.length > 0) {
161
+ yield packets;
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Send a message and stream the response
168
+ */
169
+ export async function* sendMessage({
170
+ regenerate,
171
+ retrieval_options,
172
+ message,
173
+ fileDescriptors,
174
+ currentMessageFiles,
175
+ parentMessageId,
176
+ chatSessionId,
177
+ filters,
178
+ selectedDocumentIds,
179
+ queryOverride,
180
+ forceSearch,
181
+ modelProvider,
182
+ modelVersion,
183
+ temperature,
184
+ systemPromptOverride,
185
+ taskPromptOverride,
186
+ useExistingUserMessage,
187
+ alternateAssistantId,
188
+ signal,
189
+ useAgentSearch,
190
+ enabledToolIds,
191
+ forcedToolIds,
192
+ }: SendMessageParams): AsyncGenerator<Packet[], void, unknown> {
193
+ const documentsAreSelected =
194
+ selectedDocumentIds && selectedDocumentIds.length > 0;
195
+
196
+ const payload = {
197
+ alternate_assistant_id: alternateAssistantId,
198
+ chat_session_id: chatSessionId,
199
+ parent_message_id: parentMessageId,
200
+ message,
201
+ prompt_id: null,
202
+ search_doc_ids: documentsAreSelected ? selectedDocumentIds : null,
203
+ file_descriptors: fileDescriptors,
204
+ current_message_files: currentMessageFiles,
205
+ regenerate,
206
+ retrieval_options:
207
+ retrieval_options ??
208
+ (!documentsAreSelected
209
+ ? {
210
+ run_search: queryOverride || forceSearch ? 'always' : 'auto',
211
+ real_time: true,
212
+ filters: filters,
213
+ }
214
+ : null),
215
+ query_override: queryOverride,
216
+ prompt_override: {
217
+ ...(systemPromptOverride ? { system_prompt: systemPromptOverride } : {}),
218
+ ...(taskPromptOverride ? { task_prompt: taskPromptOverride } : {}),
219
+ },
220
+ llm_override:
221
+ temperature || modelVersion
222
+ ? {
223
+ temperature,
224
+ model_provider: modelProvider,
225
+ model_version: modelVersion,
226
+ }
227
+ : null,
228
+ use_existing_user_message: useExistingUserMessage,
229
+ use_agentic_search: useAgentSearch ?? false,
230
+ allowed_tool_ids: enabledToolIds,
231
+ forced_tool_ids: forcedToolIds,
232
+ };
233
+
234
+ const body = JSON.stringify(payload);
235
+
236
+ const sendMessageResponse = await fetch('/_da/chat/send-message', {
237
+ method: 'POST',
238
+ headers: {
239
+ 'Content-Type': 'application/json',
240
+ },
241
+ body,
242
+ signal,
243
+ });
244
+
245
+ if (!sendMessageResponse.ok) {
246
+ const errorJson = await sendMessageResponse.json();
247
+ const errorMsg = errorJson.message || errorJson.detail || '';
248
+ throw new Error(`Failed to send message - ${errorMsg}`);
249
+ }
250
+
251
+ yield* handleStream(sendMessageResponse);
252
+ }
253
+
254
+ /**
255
+ * Create a new chat session
256
+ */
257
+ export async function createChatSession(
258
+ personaId: number,
259
+ description?: string,
260
+ ): Promise<string> {
261
+ const response = await fetch('/_da/chat/create-chat-session', {
262
+ method: 'POST',
263
+ headers: {
264
+ 'Content-Type': 'application/json',
265
+ },
266
+ body: JSON.stringify({
267
+ persona_id: personaId,
268
+ description,
269
+ }),
270
+ });
271
+
272
+ if (!response.ok) {
273
+ throw new Error('Failed to create chat session');
274
+ }
275
+
276
+ const data = await response.json();
277
+ return data.chat_session_id;
278
+ }
279
+
280
+ /**
281
+ * Submit feedback for a message
282
+ */
283
+ export async function submitFeedback(params: {
284
+ chatMessageId: number;
285
+ feedbackText?: string;
286
+ isPositive: boolean;
287
+ predefinedFeedback?: string;
288
+ }): Promise<void> {
289
+ const {
290
+ chatMessageId,
291
+ feedbackText = '',
292
+ isPositive,
293
+ predefinedFeedback = '',
294
+ } = params;
295
+
296
+ const payload: any = {
297
+ chat_message_id: chatMessageId,
298
+ feedback_text: feedbackText,
299
+ is_positive: isPositive,
300
+ };
301
+
302
+ if (!isPositive) {
303
+ payload.predefined_feedback = predefinedFeedback;
304
+ }
305
+
306
+ const response = await fetch('/_da/chat/create-chat-message-feedback', {
307
+ method: 'POST',
308
+ headers: {
309
+ 'Content-Type': 'application/json',
310
+ },
311
+ body: JSON.stringify(payload),
312
+ });
313
+
314
+ if (!response.ok) {
315
+ throw new Error('Failed to submit feedback');
316
+ }
317
+
318
+ await response.json();
319
+ }
320
+
321
+ /**
322
+ * Regenerate a message
323
+ */
324
+ export async function regenerateMessage(
325
+ messageId: number,
326
+ chatSessionId: number,
327
+ ): Promise<void> {
328
+ const response = await fetch('/_da/chat/regenerate-message', {
329
+ method: 'POST',
330
+ headers: {
331
+ 'Content-Type': 'application/json',
332
+ },
333
+ body: JSON.stringify({
334
+ message_id: messageId,
335
+ chat_session_id: chatSessionId,
336
+ }),
337
+ });
338
+
339
+ if (!response.ok) {
340
+ throw new Error('Failed to regenerate message');
341
+ }
342
+ }