@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
package/.eslintrc.js CHANGED
@@ -16,16 +16,16 @@ if (configFile) {
16
16
  voltoPath = `./${jsConfig.baseUrl}/${pathsConfig['@plone/volto'][0]}`;
17
17
  }
18
18
 
19
- const AddonConfigurationRegistry = require(`${voltoPath}/addon-registry.js`);
20
- const reg = new AddonConfigurationRegistry(projectRootPath);
19
+ const { AddonRegistry } = require('@plone/registry/addon-registry');
20
+ const { registry } = AddonRegistry.init(projectRootPath);
21
21
 
22
22
  // Extends ESlint configuration for adding aliases to `src` directories in Volto addons
23
- const addonAliases = Object.keys(reg.packages).map((o) => [
23
+ const addonAliases = Object.keys(registry.packages).map((o) => [
24
24
  o,
25
- reg.packages[o].modulePath,
25
+ registry.packages[o].modulePath,
26
26
  ]);
27
27
 
28
- const addonExtenders = reg.getEslintExtenders().map((m) => require(m));
28
+ const addonExtenders = registry.getEslintExtenders().map((m) => require(m));
29
29
 
30
30
  const defaultConfig = {
31
31
  extends: `${voltoPath}/.eslintrc`,
@@ -34,7 +34,7 @@ const defaultConfig = {
34
34
  alias: {
35
35
  map: [
36
36
  ['@plone/volto', '@plone/volto/src'],
37
- ['@plone/volto-slate', '@plone/volto/packages/volto-slate/src'],
37
+ ['@plone/volto-slate', '@plone/volto-slate/src'],
38
38
  ...addonAliases,
39
39
  ['@package', `${__dirname}/src`],
40
40
  ['@root', `${__dirname}/src`],
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [2.0.3](https://github.com/eea/volto-eea-chatbot/compare/2.0.2...2.0.3) - 18 May 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: integrate Onyx 3.x support [Zoltan Szabo - [`6b0d9d5`](https://github.com/eea/volto-eea-chatbot/commit/6b0d9d5a847995953680a5a9a46281bfc3997b70)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - style: Automated code fix [eea-jenkins - [`ba195ce`](https://github.com/eea/volto-eea-chatbot/commit/ba195cec917b06faa70211b05c7def1d025d5356)]
16
+
17
+ #### :hammer_and_wrench: Others
18
+
19
+ - fixes for eslint [Zoltan Szabo - [`ec926dd`](https://github.com/eea/volto-eea-chatbot/commit/ec926dd32106105fb87cff226b5f92a495a1eadf)]
20
+ - increase test coverage [Zoltan Szabo - [`d4d1178`](https://github.com/eea/volto-eea-chatbot/commit/d4d1178fd52d215a755480c710b300f71f674f3f)]
21
+ - Rather than dealing with complex and fragile Webpack/Babel config transpilation rules for ES Modules, the cleanest solution is to remove the dependency entirely [Zoltan Szabo - [`3cdedee`](https://github.com/eea/volto-eea-chatbot/commit/3cdedee16ecddf695779b179eb6beeecde16295d)]
22
+ - updated snapshots [Zoltan Szabo - [`58dd3b8`](https://github.com/eea/volto-eea-chatbot/commit/58dd3b8e7d795032338b3580e33cd10c24576911)]
23
+ - fixed jest-addon.config.js [Zoltan Szabo - [`95c4c6f`](https://github.com/eea/volto-eea-chatbot/commit/95c4c6f494c71a9d5e9171b7d56bb1609dccc9f3)]
24
+ - updated dependencies [Zoltan Szabo - [`44e174e`](https://github.com/eea/volto-eea-chatbot/commit/44e174e4c3634d11241f78b46d86deefe0ed6191)]
25
+ ### [2.0.2](https://github.com/eea/volto-eea-chatbot/compare/2.0.1...2.0.2) - 9 April 2026
26
+
7
27
  ### [2.0.1](https://github.com/eea/volto-eea-chatbot/compare/2.0.0...2.0.1) - 2 April 2026
8
28
 
9
29
  #### :bug: Bug Fixes
@@ -0,0 +1,34 @@
1
+ # Onyx v3 Integration for Volto EEA Chatbot
2
+
3
+ This document summarizes the major architectural changes and features implemented to support **Onyx 3.x** while maintaining full backward compatibility with **Onyx 2.x**.
4
+
5
+ ## 1. Version Switching & Configuration
6
+ - **Onyx Version Toggle**: Added a configuration setting in the Chat Block schema to switch between Onyx 2 and 3.
7
+ - **Dynamic Routing**: The `streamingService` now branches logic based on the selected version, targeting the appropriate API endpoints:
8
+ - **v2**: `/send-message`
9
+ - **v3**: `/send-chat-message`
10
+
11
+ ## 2. Packet-Based Architecture (v3)
12
+ Implemented support for the new Onyx 3.x packet schema, which utilizes turn indices (`ind`) for better synchronization:
13
+ - **New Packet Types**: Integrated support for `search_tool_start`, `search_tool_queries_delta`, `search_tool_documents_delta`, `citation_info`, and `reasoning_done`.
14
+ - **Packet Normalization**: Developed a normalization layer in `streamingService.ts` to map v3 packets into the chatbot's internal `Packet` structure.
15
+ - **Turn Index Mapping**: Updated `MessageProcessor` to use explicit `ind` values from the backend for reliable turn completion detection, replacing the synthetic index fallback used in v2.
16
+
17
+ ## 3. Streaming & UI Visibility
18
+ - **Eager Rendering**: Updated `MultiToolRenderer` and `SearchToolRenderer` to display search queries and documents as they arrive via delta packets, rather than waiting for tool completion.
19
+ - **Improved Context**: Added distinct labels for "Web Queries" and "Internal Search Queries" in the Search Tool UI.
20
+ - **Reasoning Support**: Enhanced `ReasoningRenderer` to handle v3 reasoning packets and provide a smoother transition between "thinking" and "answering" phases.
21
+
22
+ ## 4. Related Questions (RQ) Restoration
23
+ - **Middleware Routing**: Updated the RQ workflow to route through the specialized `/_rq/` middleware proxy for both session creation and message sending.
24
+ - **Dedicated Sessions**: Ensured that Related Questions use a fresh chat session correctly targeting the QGen assistant on the appropriate version backend.
25
+ - **Completion Resiliency**: Patched a race condition where missing `section_end` packets for the main message turn would block RQ interrogation. The system now automatically synthesizes turn completion when the stream ends.
26
+
27
+ ## 5. Middleware & Proxying
28
+ - **Proxy Expansion**: Synchronized `middleware.js` to handle both standard (`/_da/`) and related questions (`/_rq/`) paths for all v3 endpoints.
29
+ - **Payload Alignment**: Updated the payload builders to support v3-specific fields like `alternate_assistant_id`, `file_descriptors`, and `internal_search_filters`.
30
+
31
+ ## 6. Bug Fixes & Stability
32
+ - **Citation Fallback**: Added a fallback for citations in v3 if explicit `citation_info` packets are missing, leveraging the `final_documents` array in `message_start`.
33
+ - **Typewriter Synchronization**: Adjusted `MessageTextRenderer` to coordinate with the new turn-based completion logic, ensuring the UI remains interactive.
34
+ - **Logging & Observability**: Injected comprehensive tracing logs (`[RQ]`, `[sendMessage]`, `[MessageProcessor]`) to monitor the end-to-end flow of packets.
@@ -377,7 +377,7 @@ const getCoveragePatterns = () => {
377
377
  arg === reserved || arg.startsWith(reserved.split('=')[0] + '='),
378
378
  ) &&
379
379
  process.argv.indexOf(arg) >
380
- process.argv.findIndex((item) => item === 'test'),
380
+ process.argv.findIndex((item) => item === 'test'),
381
381
  );
382
382
 
383
383
  if (directoryArg) {
@@ -423,6 +423,7 @@ module.exports = {
423
423
  'schema\\.[jt]s?$',
424
424
  'index\\.[jt]s?$',
425
425
  'config\\.[jt]sx?$',
426
+ '/types/',
426
427
  ],
427
428
  moduleNameMapper: {
428
429
  '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "@eeacms/volto-eea-chatbot: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import { compose } from 'redux';
3
3
  import superagent from 'superagent';
4
- import { BlockDataForm, SidebarPortal } from '@plone/volto/components';
4
+ import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
5
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
5
6
 
6
7
  import ChatBlockView from './ChatBlockView';
7
8
  import { ChatBlockSchema } from './schema';
@@ -1,7 +1,7 @@
1
- import type { ChatMessageProps } from '../types/interfaces';
1
+ import type { ChatMessageProps } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
2
2
  import { useState, useMemo, useEffect } from 'react';
3
3
  import cx from 'classnames';
4
- import visit from 'unist-util-visit';
4
+
5
5
  import loadable from '@loadable/component';
6
6
  import {
7
7
  Tab,
@@ -14,27 +14,42 @@ import {
14
14
  useDeepCompareMemoize,
15
15
  useQualityMarkers,
16
16
  useScrollonStream,
17
- } from '../hooks';
18
- import { MultiToolRenderer, RendererComponent } from '../packets';
19
- import { addCitations } from '../utils/citations';
20
- import SVGIcon from '../components/Icon';
21
- import BotIcon from '../../icons/bot.svg';
22
- import ClearIcon from '../../icons/clear.svg';
17
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/hooks';
18
+ import {
19
+ MultiToolRenderer,
20
+ RendererComponent,
21
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/packets';
22
+ import { addCitations } from '@eeacms/volto-eea-chatbot/ChatBlock/utils/citations';
23
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
24
+ import BotIcon from '@eeacms/volto-eea-chatbot/icons/bot.svg';
25
+ import ClearIcon from '@eeacms/volto-eea-chatbot/icons/clear.svg';
23
26
 
24
27
  // Lazy load heavy components
25
- const SourceDetails: any = loadable(() => import('../components/Source'));
28
+ const SourceDetails: any = loadable(
29
+ () => import('@eeacms/volto-eea-chatbot/ChatBlock/components/Source'),
30
+ );
26
31
  const UserActionsToolbar: any = loadable(
27
- () => import('../components/UserActionsToolbar'),
32
+ () =>
33
+ import('@eeacms/volto-eea-chatbot/ChatBlock/components/UserActionsToolbar'),
28
34
  );
29
35
  const RelatedQuestions: any = loadable(
30
- () => import('../components/RelatedQuestions'),
36
+ () =>
37
+ import('@eeacms/volto-eea-chatbot/ChatBlock/components/RelatedQuestions'),
31
38
  );
32
39
  const HalloumiFeedback: any = loadable(
33
- () => import('../components/HalloumiFeedback'),
40
+ () =>
41
+ import('@eeacms/volto-eea-chatbot/ChatBlock/components/HalloumiFeedback'),
34
42
  );
35
43
 
36
- function capitalize(str: string) {
37
- return str.charAt(0).toUpperCase() + str.slice(1);
44
+ function visit(node: any, type: string, visitor: (node: any, idx?: number, parent?: any) => void, idx?: number, parent?: any) {
45
+ if (node.type === type) {
46
+ visitor(node, idx, parent);
47
+ }
48
+ if (node.children && Array.isArray(node.children)) {
49
+ node.children.forEach((child: any, cidx: number) => {
50
+ visit(child, type, visitor, cidx, node);
51
+ });
52
+ }
38
53
  }
39
54
 
40
55
  function addQualityMarkersPlugin() {
@@ -316,8 +331,11 @@ export function AIMessage({
316
331
  if (isFetchingRelatedQuestions || typeof relatedQuestions !== 'undefined') {
317
332
  return;
318
333
  }
319
- if (messageDisplayed && isComplete && onFetchRelatedQuestions) {
320
- onFetchRelatedQuestions();
334
+ if (isLastMessage && isComplete && onFetchRelatedQuestions) {
335
+ console.log(`[AIMessage] Triggering RQ: messageDisplayed=${messageDisplayed}, isComplete=${isComplete}, hasContent=${!!message.message}`);
336
+ if (messageDisplayed) {
337
+ onFetchRelatedQuestions();
338
+ }
321
339
  }
322
340
  }, [
323
341
  messageDisplayed,
@@ -325,6 +343,8 @@ export function AIMessage({
325
343
  isComplete,
326
344
  onFetchRelatedQuestions,
327
345
  isFetchingRelatedQuestions,
346
+ isLastMessage,
347
+ message.message,
328
348
  ]);
329
349
 
330
350
  useEffect(() => {
@@ -1,5 +1,5 @@
1
1
  import { Message as SemanticMessage } from 'semantic-ui-react';
2
- import type { ChatMessageProps } from '../types/interfaces';
2
+ import type { ChatMessageProps } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
3
3
  import { UserMessage, AIMessage } from '.';
4
4
 
5
5
  export function ChatMessage(props: ChatMessageProps) {
@@ -5,21 +5,21 @@ import React, {
5
5
  useMemo,
6
6
  useCallback,
7
7
  } from 'react';
8
- import type { Persona } from '../types/interfaces';
8
+ import type { Persona } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
9
9
  import { Button, Form, Segment, Checkbox } from 'semantic-ui-react';
10
10
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable';
11
11
  import { trackEvent } from '@eeacms/volto-matomo/utils';
12
12
 
13
13
  import { ChatMessage } from '.';
14
- import { PacketType } from '../types/streamingModels';
15
- import AutoResizeTextarea from '../components/AutoResizeTextarea';
16
- import QualityCheckToggle from '../components/QualityCheckToggle';
17
- import EmptyState from '../components/EmptyState';
18
- import { useChatController } from '../hooks';
19
- import SVGIcon from '../components/Icon';
20
- import PenIcon from '../../icons/square-pen.svg';
14
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
15
+ import AutoResizeTextarea from '@eeacms/volto-eea-chatbot/ChatBlock/components/AutoResizeTextarea';
16
+ import QualityCheckToggle from '@eeacms/volto-eea-chatbot/ChatBlock/components/QualityCheckToggle';
17
+ import EmptyState from '@eeacms/volto-eea-chatbot/ChatBlock/components/EmptyState';
18
+ import { useChatController } from '@eeacms/volto-eea-chatbot/ChatBlock/hooks';
19
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
20
+ import PenIcon from '@eeacms/volto-eea-chatbot/icons/square-pen.svg';
21
21
 
22
- import '../style.less';
22
+ import '@eeacms/volto-eea-chatbot/ChatBlock/style.less';
23
23
 
24
24
  interface ChatWindowProps {
25
25
  block_id?: string;
@@ -48,6 +48,7 @@ interface ChatWindowProps {
48
48
  enableMatomoTracking?: boolean;
49
49
  onDemandInputToggle?: boolean;
50
50
  maxContextSegments?: number;
51
+ onyxVersion?: '2' | '3';
51
52
  isPlaywrightTest?: boolean;
52
53
  [key: string]: any;
53
54
  }
@@ -83,6 +84,7 @@ function ChatWindow({
83
84
  enableMatomoTracking = true,
84
85
  onDemandInputToggle = true,
85
86
  maxContextSegments = 0,
87
+ onyxVersion = '2',
86
88
  } = data;
87
89
  const [qualityCheckEnabled, setQualityCheckEnabled] = useState(
88
90
  onDemandInputToggle ?? true,
@@ -117,6 +119,7 @@ function ChatWindow({
117
119
  qgenAsistantId,
118
120
  enableQgen,
119
121
  deepResearch,
122
+ onyxVersion,
120
123
  });
121
124
 
122
125
  const [showLandingPage, setShowLandingPage] = useState(true);
@@ -182,9 +185,8 @@ function ChatWindow({
182
185
  style={{ maxHeight: height }}
183
186
  >
184
187
  {messages.map((message, index) => (
185
- <React.Fragment>
188
+ <React.Fragment key={message.messageId}>
186
189
  <ChatMessage
187
- key={message.messageId}
188
190
  prevMessage={messages[index - 1]}
189
191
  message={message}
190
192
  isLoading={isStreaming}
@@ -1,8 +1,8 @@
1
- import type { ChatMessageProps } from '../types/interfaces';
1
+ import type { ChatMessageProps } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
2
2
  import loadable from '@loadable/component';
3
- import SVGIcon from '../components/Icon';
4
- import { components } from '../components/markdown';
5
- import UserIcon from '../../icons/user.svg';
3
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
4
+ import { components } from '@eeacms/volto-eea-chatbot/ChatBlock/components/markdown';
5
+ import UserIcon from '@eeacms/volto-eea-chatbot/icons/user.svg';
6
6
 
7
7
  const Markdown: any = loadable(() => import('react-markdown'));
8
8
 
@@ -4,7 +4,7 @@ import { trackEvent } from '@eeacms/volto-matomo/utils';
4
4
  import TextareaAutosize from 'react-textarea-autosize';
5
5
 
6
6
  import SVGIcon from './Icon';
7
- import SendIcon from '../../icons/send.svg';
7
+ import SendIcon from '@eeacms/volto-eea-chatbot/icons/send.svg';
8
8
 
9
9
  export default React.forwardRef(function AutoResizeTextarea(props, ref) {
10
10
  const { onSubmit, isStreaming, enableMatomoTracking, persona, ...rest } =
@@ -2,8 +2,8 @@ import { useState } from 'react';
2
2
  import { Button, Icon } from 'semantic-ui-react';
3
3
  import FeedbackModal from './FeedbackModal';
4
4
  import SVGIcon from './Icon';
5
- import ThumbsUpIcon from '../../icons/thumbs-up.svg';
6
- import ThumbsDownIcon from '../../icons/thumbs-down.svg';
5
+ import ThumbsUpIcon from '@eeacms/volto-eea-chatbot/icons/thumbs-up.svg';
6
+ import ThumbsDownIcon from '@eeacms/volto-eea-chatbot/icons/thumbs-down.svg';
7
7
 
8
8
  const Toast = ({ message, type, isActive }) => (
9
9
  <div className={`feedback-toast ${type} ${isActive ? 'active' : ''}`}>
@@ -1,5 +1,5 @@
1
1
  import { Button } from 'semantic-ui-react';
2
- import { debounce } from '../utils';
2
+ import { debounce } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
3
3
 
4
4
  function StarterMessage({ msg, onClick }) {
5
5
  if (!(msg.name || msg.message)) return null;
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react';
2
2
  import { trackEvent } from '@eeacms/volto-matomo/utils';
3
3
  import { Modal, Button, TextArea, Form, Icon } from 'semantic-ui-react';
4
- import { createChatMessageFeedback } from '../utils';
4
+ import { createChatMessageFeedback } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
5
5
 
6
6
  const FeedbackModal = ({
7
7
  modalOpen,
@@ -6,8 +6,8 @@ import Spinner from './Spinner';
6
6
  import SVGIcon from './Icon';
7
7
  import { getSupportedBgColor } from './markdown/colors';
8
8
 
9
- import GlassesIcon from '../../icons/glasses.svg';
10
- import RotateIcon from '../../icons/rotate.svg';
9
+ import GlassesIcon from '@eeacms/volto-eea-chatbot/icons/glasses.svg';
10
+ import RotateIcon from '@eeacms/volto-eea-chatbot/icons/rotate.svg';
11
11
 
12
12
  const VERIFY_CLAIM_MESSAGES = [
13
13
  'Going through each claim and verify against the referenced documents...',
@@ -1,8 +1,8 @@
1
1
  import { Popup } from 'semantic-ui-react';
2
2
  import SVGIcon from './Icon';
3
3
 
4
- import FileIcon from '../../icons/file.svg';
5
- import GlobeIcon from '../../icons/globe.svg';
4
+ import FileIcon from '@eeacms/volto-eea-chatbot/icons/file.svg';
5
+ import GlobeIcon from '@eeacms/volto-eea-chatbot/icons/globe.svg';
6
6
 
7
7
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
8
8
 
@@ -1,11 +1,11 @@
1
1
  import cx from 'classnames';
2
2
  import { Button } from 'semantic-ui-react';
3
- import { useCopyToClipboard } from '../utils';
3
+ import { useCopyToClipboard } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
4
4
  import SVGIcon from './Icon';
5
5
  import ChatMessageFeedback from './ChatMessageFeedback';
6
6
 
7
- import CopyIcon from '../../icons/copy.svg';
8
- import CheckIcon from '../../icons/check.svg';
7
+ import CopyIcon from '@eeacms/volto-eea-chatbot/icons/copy.svg';
8
+ import CheckIcon from '@eeacms/volto-eea-chatbot/icons/check.svg';
9
9
 
10
10
  const UserActionsToolbar = ({
11
11
  className,
@@ -1,7 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import SVGIcon from './Icon';
3
- import FileIcon from '../../icons/file.svg';
4
- import GlobeIcon from '../../icons/globe.svg';
3
+ import FileIcon from '@eeacms/volto-eea-chatbot/icons/file.svg';
4
+ import GlobeIcon from '@eeacms/volto-eea-chatbot/icons/globe.svg';
5
5
 
6
6
  interface WebResultIconProps {
7
7
  url: string;
@@ -1,11 +1,11 @@
1
1
  import { Modal, ModalContent, ModalHeader } from 'semantic-ui-react';
2
2
  import cx from 'classnames';
3
- import { convertToPercentage } from '../../utils';
4
- import SVGIcon from '../Icon';
3
+ import { convertToPercentage } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
4
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
5
5
  import { getSupportedBgColor } from './colors';
6
6
  import { ClaimSegments } from './ClaimSegments';
7
7
 
8
- import BotIcon from '../../../icons/bot.svg';
8
+ import BotIcon from '@eeacms/volto-eea-chatbot/icons/bot.svg';
9
9
 
10
10
  const stripHtml = (html) => {
11
11
  const tmp = document.createElement('div');
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import { Tab, TabPane } from 'semantic-ui-react';
3
- import SVGIcon from '../Icon';
3
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
4
4
  import { RenderClaimView } from './RenderClaimView';
5
- import LinkIcon from '../../../icons/external-link.svg';
6
- import FileIcon from '../../../icons/file.svg';
7
- import GlobeIcon from '../../../icons/globe.svg';
5
+ import LinkIcon from '@eeacms/volto-eea-chatbot/icons/external-link.svg';
6
+ import FileIcon from '@eeacms/volto-eea-chatbot/icons/file.svg';
7
+ import GlobeIcon from '@eeacms/volto-eea-chatbot/icons/globe.svg';
8
8
 
9
9
  const VISIBLE_SEGMENTS = 50; // Number of citations to show by default
10
10
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ClaimModal } from './ClaimModal';
3
3
  import { Citation } from './Citation';
4
- import { transformEmailsToLinks } from '../../utils';
4
+ import { transformEmailsToLinks } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
5
5
 
6
6
  export function components(message, markers, citedSources) {
7
7
  return {
@@ -1,15 +1,19 @@
1
- import type { Message } from '../types/interfaces';
1
+ import type { Message } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
2
2
  import { useState, useCallback, useRef, useEffect } from 'react';
3
3
  import { useChatStreaming } from './useChatStreaming';
4
- import { createChatSession, sendMessage } from '../services/streamingService';
5
- import { PacketType } from '../types/streamingModels';
6
- import { ResearchType } from '../types/interfaces';
4
+ import {
5
+ createChatSession,
6
+ sendMessage,
7
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/services/streamingService';
8
+ import { PacketType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
9
+ import { ResearchType } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
7
10
 
8
11
  interface UseChatControllerProps {
9
12
  personaId: number;
10
13
  enableQgen?: boolean;
11
14
  qgenAsistantId?: number;
12
15
  deepResearch?: string;
16
+ onyxVersion?: '2' | '3';
13
17
  }
14
18
 
15
19
  interface RelatedQuestion {
@@ -18,14 +22,42 @@ interface RelatedQuestion {
18
22
 
19
23
  // Extract JSON array from related questions response
20
24
  function extractRelatedQuestions(str: string): RelatedQuestion[] {
21
- if (str.toLowerCase().includes('no_response')) {
22
- throw new Error('Related questions were not generated properly');
25
+ if (!str || str.toLowerCase().includes('no_response')) {
26
+ return [];
27
+ }
28
+
29
+ // Try to parse as JSON first if it looks like JSON
30
+ const trimmed = str.trim();
31
+ if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
32
+ try {
33
+ const parsed = JSON.parse(trimmed);
34
+ const items = Array.isArray(parsed) ? parsed : parsed.questions || [];
35
+ if (Array.isArray(items)) {
36
+ return items
37
+ .map((item) => {
38
+ if (typeof item === 'string') return { question: item };
39
+ if (item && typeof item === 'object' && item.question)
40
+ return { question: item.question };
41
+ return null;
42
+ })
43
+ .filter((i): i is RelatedQuestion => i !== null);
44
+ }
45
+ } catch (e) {
46
+ // Fallback to line parsing
47
+ }
23
48
  }
24
49
 
50
+ // Fallback: split by lines and clean up common list formats
25
51
  return str
26
52
  .split('\n')
27
- .filter((line) => line.trim())
28
- .map((question) => ({ question }));
53
+ .map((line) => line.trim())
54
+ .filter((line) => line.length > 0)
55
+ .map((line) => {
56
+ // Remove leading numbers or bullets like "1. ", "- ", "* ", etc.
57
+ const cleaned = line.replace(/^[\d\.\-\*\s]+/, '').trim();
58
+ return cleaned ? { question: cleaned } : null;
59
+ })
60
+ .filter((i): i is RelatedQuestion => i !== null);
29
61
  }
30
62
 
31
63
  // Fetch related questions using the qgen assistant
@@ -33,12 +65,16 @@ async function fetchRelatedQuestions(
33
65
  query: string,
34
66
  answer: string,
35
67
  qgenAsistantId: number,
68
+ onyxVersion: '2' | '3' = '2',
36
69
  ): Promise<RelatedQuestion[]> {
37
70
  try {
71
+ console.log(`[RQ] Creating session for assistant ${qgenAsistantId} (Onyx v${onyxVersion})`);
38
72
  const chatSessionId = await createChatSession(
39
73
  qgenAsistantId,
40
74
  `Q: ${query}`,
75
+ true,
41
76
  );
77
+ console.log(`[RQ] Session created: ${chatSessionId}`);
42
78
 
43
79
  const params = {
44
80
  message: `Question: ${query}\nAnswer:\n${answer}`,
@@ -46,23 +82,35 @@ async function fetchRelatedQuestions(
46
82
  fileDescriptors: [],
47
83
  parentMessageId: null,
48
84
  chatSessionId,
49
- promptId: 0,
50
85
  filters: null,
51
86
  selectedDocumentIds: [],
52
87
  use_agentic_search: false,
53
88
  regenerate: false,
89
+ onyxVersion,
54
90
  };
55
91
 
92
+ if (onyxVersion === '3') {
93
+ console.log('[Onyx v3] Sending RQ prompt:', params.message);
94
+ }
95
+
56
96
  let result = '';
57
97
  for await (const packets of sendMessage(params, true)) {
58
98
  for (const packet of packets) {
59
- if (packet.obj.type === PacketType.MESSAGE_DELTA) {
60
- result += packet.obj.content;
99
+ if (onyxVersion === '3') {
100
+ // console.log('[Onyx v3] RQ Packet:', packet);
101
+ }
102
+ if (
103
+ packet.obj.type === PacketType.MESSAGE_DELTA ||
104
+ packet.obj.type === PacketType.MESSAGE_START
105
+ ) {
106
+ result += (packet.obj as any).content || '';
61
107
  }
62
108
  }
63
109
  }
64
-
65
- return extractRelatedQuestions(result);
110
+ console.log(`[RQ] Final response string: "${result}"`);
111
+ const extracted = extractRelatedQuestions(result);
112
+ console.log(`[RQ] Extracted ${extracted.length} questions`);
113
+ return extracted;
66
114
  } catch (error) {
67
115
  console.error('Error fetching related questions:', error);
68
116
  return [];
@@ -74,6 +122,7 @@ export function useChatController({
74
122
  enableQgen = false,
75
123
  qgenAsistantId,
76
124
  deepResearch,
125
+ onyxVersion = '2',
77
126
  }: UseChatControllerProps) {
78
127
  const [messages, setMessages] = useState<Message[]>([]);
79
128
  const [chatSessionId, setChatSessionId] = useState<string | null>(null);
@@ -226,6 +275,7 @@ export function useChatController({
226
275
  regenerate: false,
227
276
  filters: null,
228
277
  selectedDocumentIds: [],
278
+ onyxVersion,
229
279
  },
230
280
  assistantNodeId,
231
281
  userNodeId,
@@ -247,6 +297,7 @@ export function useChatController({
247
297
  );
248
298
 
249
299
  const onFetchRelatedQuestions = useCallback(async () => {
300
+ console.log('[RQ] onFetchRelatedQuestions triggered');
250
301
  const latestAssistantMessage = messages
251
302
  .filter((m) => m.type === 'assistant')
252
303
  .pop();
@@ -256,6 +307,7 @@ export function useChatController({
256
307
  qgenAsistantId &&
257
308
  latestAssistantMessage?.type === 'assistant'
258
309
  ) {
310
+ console.log(`[RQ] Criteria met: assistantNodeId=${latestAssistantMessage.nodeId}, qgenAssistant=${qgenAsistantId}`);
259
311
  if (isDeepResearchEnabled) {
260
312
  setMessages((prev) => {
261
313
  return prev.map((m) =>
@@ -281,6 +333,7 @@ export function useChatController({
281
333
  userMessage.message,
282
334
  latestAssistantMessage.message,
283
335
  qgenAsistantId,
336
+ onyxVersion,
284
337
  );
285
338
  }
286
339
  } catch (error) {
@@ -296,7 +349,7 @@ export function useChatController({
296
349
  setIsFetchingRelatedQuestions(false);
297
350
  }
298
351
  }
299
- }, [messages, enableQgen, qgenAsistantId, isDeepResearchEnabled]);
352
+ }, [messages, enableQgen, qgenAsistantId, isDeepResearchEnabled, onyxVersion]);
300
353
 
301
354
  const clearChat = useCallback(() => {
302
355
  setMessages([]);
@@ -1,8 +1,8 @@
1
- import type { Message } from '../types/interfaces';
2
- import type { SendMessageParams } from '../services/streamingService';
1
+ import type { Message } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
2
+ import type { SendMessageParams } from '@eeacms/volto-eea-chatbot/ChatBlock/services/streamingService';
3
3
  import { useState, useCallback, useRef } from 'react';
4
- import { sendMessage } from '../services/streamingService';
5
- import { MessageProcessor } from '../services/messageProcessor';
4
+ import { sendMessage } from '@eeacms/volto-eea-chatbot/ChatBlock/services/streamingService';
5
+ import { MessageProcessor } from '@eeacms/volto-eea-chatbot/ChatBlock/services/messageProcessor';
6
6
 
7
7
  interface UseChatStreamingProps {
8
8
  onMessageUpdate?: (message: Message, processor: MessageProcessor) => void;
@@ -1,4 +1,4 @@
1
- import type { Packet } from '../types/streamingModels';
1
+ import type { Packet } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
2
2
  import { useMemo, useState, useCallback, useEffect } from 'react';
3
3
 
4
4
  interface ToolState {
@@ -76,5 +76,6 @@ export function useToolDisplayTiming(
76
76
  visibleTools,
77
77
  handleToolComplete,
78
78
  allToolsDisplayed,
79
+ toolStates,
79
80
  };
80
81
  }