@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.
- package/.coverage.babel.config.js +9 -0
- package/.eslintrc.js +68 -0
- package/.husky/pre-commit +2 -0
- package/.release-it.json +17 -0
- package/AGENTS.md +89 -0
- package/CHANGELOG.md +770 -0
- package/DEVELOP.md +124 -0
- package/LICENSE.md +9 -0
- package/README.md +170 -0
- package/RELEASE.md +74 -0
- package/TESTING.md +5 -0
- package/babel.config.js +17 -0
- package/bootstrap +41 -0
- package/cypress.config.js +27 -0
- package/docker-compose.yml +32 -0
- package/jest-addon.config.js +465 -0
- package/jest.setup.js +65 -0
- package/locales/de/LC_MESSAGES/volto.po +14 -0
- package/locales/en/LC_MESSAGES/volto.po +14 -0
- package/locales/it/LC_MESSAGES/volto.po +14 -0
- package/locales/ro/LC_MESSAGES/volto.po +14 -0
- package/locales/volto.pot +16 -0
- package/package.json +98 -0
- package/razzle.extend.js +40 -0
- package/src/ChatBlock/ChatBlockEdit.jsx +46 -0
- package/src/ChatBlock/ChatBlockView.jsx +21 -0
- package/src/ChatBlock/chat/AIMessage.tsx +566 -0
- package/src/ChatBlock/chat/ChatMessage.tsx +35 -0
- package/src/ChatBlock/chat/ChatWindow.tsx +288 -0
- package/src/ChatBlock/chat/UserMessage.tsx +27 -0
- package/src/ChatBlock/chat/index.ts +4 -0
- package/src/ChatBlock/components/AutoResizeTextarea.jsx +67 -0
- package/src/ChatBlock/components/BlinkingDot.tsx +3 -0
- package/src/ChatBlock/components/ChatMessageFeedback.jsx +77 -0
- package/src/ChatBlock/components/EmptyState.jsx +70 -0
- package/src/ChatBlock/components/FeedbackModal.jsx +125 -0
- package/src/ChatBlock/components/HalloumiFeedback.jsx +126 -0
- package/src/ChatBlock/components/Icon.tsx +35 -0
- package/src/ChatBlock/components/QualityCheckToggle.jsx +26 -0
- package/src/ChatBlock/components/RelatedQuestions.jsx +59 -0
- package/src/ChatBlock/components/Source.jsx +93 -0
- package/src/ChatBlock/components/SourceChip.tsx +55 -0
- package/src/ChatBlock/components/Spinner.jsx +3 -0
- package/src/ChatBlock/components/UserActionsToolbar.jsx +44 -0
- package/src/ChatBlock/components/WebResultIcon.tsx +42 -0
- package/src/ChatBlock/components/markdown/Citation.jsx +70 -0
- package/src/ChatBlock/components/markdown/ClaimModal.jsx +98 -0
- package/src/ChatBlock/components/markdown/ClaimSegments.jsx +172 -0
- package/src/ChatBlock/components/markdown/RenderClaimView.jsx +96 -0
- package/src/ChatBlock/components/markdown/colors.js +29 -0
- package/src/ChatBlock/components/markdown/colors.less +52 -0
- package/src/ChatBlock/components/markdown/colors.test.js +69 -0
- package/src/ChatBlock/components/markdown/index.js +115 -0
- package/src/ChatBlock/fonts/DejaVuSans.ttf +0 -0
- package/src/ChatBlock/hocs/withOnyxData.jsx +46 -0
- package/src/ChatBlock/hooks/index.ts +7 -0
- package/src/ChatBlock/hooks/useChatController.ts +333 -0
- package/src/ChatBlock/hooks/useChatStreaming.ts +82 -0
- package/src/ChatBlock/hooks/useDeepCompareMemoize.js +17 -0
- package/src/ChatBlock/hooks/useMarked.js +44 -0
- package/src/ChatBlock/hooks/useQualityMarkers.js +119 -0
- package/src/ChatBlock/hooks/useScrollonStream.ts +131 -0
- package/src/ChatBlock/hooks/useToolDisplayTiming.ts +80 -0
- package/src/ChatBlock/index.js +32 -0
- package/src/ChatBlock/packets/MultiToolRenderer.tsx +235 -0
- package/src/ChatBlock/packets/RendererComponent.tsx +115 -0
- package/src/ChatBlock/packets/index.ts +4 -0
- package/src/ChatBlock/packets/renderers/CustomToolRenderer.tsx +63 -0
- package/src/ChatBlock/packets/renderers/FetchToolRenderer.tsx +59 -0
- package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +62 -0
- package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +172 -0
- package/src/ChatBlock/packets/renderers/ReasoningRenderer.tsx +122 -0
- package/src/ChatBlock/packets/renderers/SearchToolRenderer.tsx +323 -0
- package/src/ChatBlock/packets/renderers/index.ts +6 -0
- package/src/ChatBlock/schema.js +403 -0
- package/src/ChatBlock/services/index.ts +3 -0
- package/src/ChatBlock/services/messageProcessor.ts +348 -0
- package/src/ChatBlock/services/packetUtils.ts +48 -0
- package/src/ChatBlock/services/streamingService.ts +342 -0
- package/src/ChatBlock/style.less +1881 -0
- package/src/ChatBlock/tests/AIMessage.test.jsx +95 -0
- package/src/ChatBlock/tests/AutoResizeTextarea.test.jsx +49 -0
- package/src/ChatBlock/tests/BlinkingDot.test.jsx +71 -0
- package/src/ChatBlock/tests/ChatMessageFeedback.test.jsx +73 -0
- package/src/ChatBlock/tests/Citation.test.jsx +107 -0
- package/src/ChatBlock/tests/EmptyState.test.jsx +137 -0
- package/src/ChatBlock/tests/FeedbackModal.test.jsx +138 -0
- package/src/ChatBlock/tests/HalloumiFeedback.test.jsx +94 -0
- package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +105 -0
- package/src/ChatBlock/tests/RelatedQuestions.test.jsx +215 -0
- package/src/ChatBlock/tests/Source.test.jsx +79 -0
- package/src/ChatBlock/tests/Spinner.test.jsx +18 -0
- package/src/ChatBlock/tests/index.test.js +51 -0
- package/src/ChatBlock/tests/messageProcessor.test.jsx +154 -0
- package/src/ChatBlock/tests/schema.test.js +166 -0
- package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +107 -0
- package/src/ChatBlock/tests/useToolDisplayTiming.test.jsx +151 -0
- package/src/ChatBlock/types/cssmodules.d.ts +7 -0
- package/src/ChatBlock/types/interfaces.ts +154 -0
- package/src/ChatBlock/types/slate.d.ts +3 -0
- package/src/ChatBlock/types/streamingModels.ts +267 -0
- package/src/ChatBlock/types/volto.d.ts +3 -0
- package/src/ChatBlock/utils/citations.ts +25 -0
- package/src/ChatBlock/utils/index.tsx +114 -0
- package/src/halloumi/README.md +1 -0
- package/src/halloumi/generative.js +219 -0
- package/src/halloumi/generative.test.js +88 -0
- package/src/halloumi/middleware.js +70 -0
- package/src/halloumi/postprocessing.js +273 -0
- package/src/halloumi/postprocessing.test.js +441 -0
- package/src/halloumi/preprocessing.js +115 -0
- package/src/halloumi/preprocessing.test.js +245 -0
- package/src/icons/bot.svg +1 -0
- package/src/icons/check.svg +1 -0
- package/src/icons/chevron.svg +3 -0
- package/src/icons/clear.svg +1 -0
- package/src/icons/copy.svg +1 -0
- package/src/icons/done.svg +5 -0
- package/src/icons/external-link.svg +1 -0
- package/src/icons/file.svg +1 -0
- package/src/icons/glasses.svg +1 -0
- package/src/icons/globe.svg +1 -0
- package/src/icons/rotate.svg +1 -0
- package/src/icons/search.svg +5 -0
- package/src/icons/send.svg +1 -0
- package/src/icons/square-pen.svg +1 -0
- package/src/icons/stop.svg +9 -0
- package/src/icons/thumbs-down.svg +1 -0
- package/src/icons/thumbs-up.svg +1 -0
- package/src/icons/user.svg +1 -0
- package/src/index.js +58 -0
- package/src/middleware.js +250 -0
- 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
|
+
}
|