@eeacms/volto-eea-chatbot 1.0.12 → 2.0.0

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 (39) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/DEVELOP.md +28 -26
  3. package/README.md +19 -7
  4. package/docker-compose.yml +1 -1
  5. package/jest-addon.config.js +16 -4
  6. package/package.json +1 -1
  7. package/src/ChatBlock/hocs/withOnyxData.jsx +6 -2
  8. package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +2 -2
  9. package/src/ChatBlock/tests/AIMessage.test.jsx +1 -1
  10. package/src/ChatBlock/tests/BlinkingDot.test.jsx +1 -1
  11. package/src/ChatBlock/tests/ChatMessage.test.jsx +1 -1
  12. package/src/ChatBlock/tests/Citation.test.jsx +1 -1
  13. package/src/ChatBlock/tests/ClaimModal.test.jsx +1 -1
  14. package/src/ChatBlock/tests/ClaimSegments.test.jsx +1 -1
  15. package/src/ChatBlock/tests/CustomToolRenderer.test.jsx +1 -1
  16. package/src/ChatBlock/tests/EmptyState.test.jsx +1 -1
  17. package/src/ChatBlock/tests/FeedbackModal.test.jsx +1 -1
  18. package/src/ChatBlock/tests/FetchToolRenderer.test.jsx +1 -1
  19. package/src/ChatBlock/tests/ImageToolRenderer.test.jsx +1 -1
  20. package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +1 -1
  21. package/src/ChatBlock/tests/RelatedQuestions.test.jsx +1 -1
  22. package/src/ChatBlock/tests/RenderClaimView.test.jsx +1 -1
  23. package/src/ChatBlock/tests/SearchToolRenderer.test.jsx +1 -1
  24. package/src/ChatBlock/tests/Source.test.jsx +1 -1
  25. package/src/ChatBlock/tests/SourceChip.test.jsx +1 -1
  26. package/src/ChatBlock/tests/Spinner.test.jsx +1 -1
  27. package/src/ChatBlock/tests/UserActionsToolbar.test.jsx +1 -1
  28. package/src/ChatBlock/tests/UserMessage.test.jsx +1 -1
  29. package/src/ChatBlock/tests/WebResultIcon.test.jsx +1 -1
  30. package/src/ChatBlock/tests/index.test.js +1 -1
  31. package/src/ChatBlock/tests/schema.test.js +1 -1
  32. package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +1 -1
  33. package/src/ChatBlock/tests/utils.test.jsx +1 -1
  34. package/src/ChatBlock/tests/withOnyxData.test.jsx +1 -1
  35. package/src/halloumi/filtering.js +12 -6
  36. package/src/halloumi/generative.js +12 -7
  37. package/src/halloumi/middleware.js +1 -0
  38. package/src/middleware.js +11 -6
  39. package/src/middleware.test.js +2 -0
package/CHANGELOG.md CHANGED
@@ -4,12 +4,38 @@ 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.0](https://github.com/eea/volto-eea-chatbot/compare/1.0.13...2.0.0) - 16 March 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: Volto 18 support - refs #287700 [Alin Voinea - [`e37ee8e`](https://github.com/eea/volto-eea-chatbot/commit/e37ee8e97e43c6108f336a341f31c4411fb22141)]
12
+
13
+ #### :bug: Bug Fixes
14
+
15
+ - fix(withOnyxData): handle API errors gracefully and set proper HTTP status codes [Claudia Ifrim - [`eec4e22`](https://github.com/eea/volto-eea-chatbot/commit/eec4e220649634714165857e5e14a6a444de4f92)]
16
+
17
+ #### :house: Internal changes
18
+
19
+ - style: Automated code fix [eea-jenkins - [`0579cad`](https://github.com/eea/volto-eea-chatbot/commit/0579cadfeb3f6f092f4bc66ffacebe59232e8650)]
20
+
21
+ #### :hammer_and_wrench: Others
22
+
23
+ - tests: Fix Sonar Qube tags - refs #297339 [Alin Voinea - [`958cd4f`](https://github.com/eea/volto-eea-chatbot/commit/958cd4f97fdd6892aa248083fb13bdf9313727bb)]
24
+ ### [1.0.13](https://github.com/eea/volto-eea-chatbot/compare/1.0.12...1.0.13) - 4 March 2026
25
+
26
+ #### :house: Internal changes
27
+
28
+ - style: Automated code fix [eea-jenkins - [`8a8c3c4`](https://github.com/eea/volto-eea-chatbot/commit/8a8c3c4172a4f669661378cf1b5a3569d85609e6)]
29
+
30
+ #### :hammer_and_wrench: Others
31
+
32
+ - update [Miu Razvan - [`04e6c3f`](https://github.com/eea/volto-eea-chatbot/commit/04e6c3f776bb9920b89a014cc97f8e5dbb284a48)]
33
+ - update [Miu Razvan - [`f9e9beb`](https://github.com/eea/volto-eea-chatbot/commit/f9e9beb0676b215a50226a0db8c5be7c540f26ff)]
7
34
  ### [1.0.12](https://github.com/eea/volto-eea-chatbot/compare/1.0.11...1.0.12) - 23 February 2026
8
35
 
9
36
  #### :house: Internal changes
10
37
 
11
38
  - style: Automated code fix [eea-jenkins - [`fdcd884`](https://github.com/eea/volto-eea-chatbot/commit/fdcd8848fd4c3f990ca5ea021f407487aebd6010)]
12
- - chore: [JENKINSFILE] use sonarqube branches [EEA Jenkins - [`3d428d7`](https://github.com/eea/volto-eea-chatbot/commit/3d428d72f32c3d05452b0961c76f5db1c416e05c)]
13
39
 
14
40
  #### :hammer_and_wrench: Others
15
41
 
package/DEVELOP.md CHANGED
@@ -1,12 +1,12 @@
1
- # volto-addon-template
1
+ # volto-eea-chatbot
2
2
 
3
3
  ## Develop
4
4
 
5
5
  1. Make sure you have `docker` and `docker compose` installed and running on your machine:
6
6
 
7
7
  ```Bash
8
- git clone https://github.com/eea/volto-addon-template.git
9
- cd volto-addon-template
8
+ git clone https://github.com/eea/volto-eea-chatbot.git
9
+ cd volto-eea-chatbot
10
10
  git checkout -b bugfix-123456 develop
11
11
  make
12
12
  make start
@@ -24,64 +24,66 @@
24
24
 
25
25
  1. Happy hacking!
26
26
 
27
- ### Or add @eeacms/volto-addon-template to your Volto project
27
+ ### Or add @eeacms/volto-eea-chatbot to your Volto project
28
28
 
29
- Before starting make sure your development environment is properly set. See [Volto Developer Documentation](https://docs.voltocms.com/getting-started/install/)
29
+ Before starting make sure your development environment is properly set. See the official Plone documentation for [creating a project with Cookieplone](https://6.docs.plone.org/install/create-project-cookieplone.html) and [installing an add-on in development mode in Volto 18 and 19](https://6.docs.plone.org/volto/development/add-ons/install-an-add-on-dev-18.html).
30
30
 
31
- 1. Make sure you have installed `yo`, `@plone/generator-volto` and `mrs-developer`
31
+ For new Volto 18+ projects, use Cookieplone. It includes `mrs-developer` by default.
32
32
 
33
- npm install -g yo @plone/generator-volto mrs-developer
33
+ 1. Create a new Volto project with Cookieplone
34
34
 
35
- 1. Create new volto app
36
-
37
- yo @plone/volto my-volto-project --addon @eeacms/volto-addon-template --skip-install
38
- cd my-volto-project
35
+ uvx cookieplone project
36
+ cd project-title
39
37
 
40
38
  1. Add the following to `mrs.developer.json`:
41
39
 
42
40
  {
43
- "volto-addon-template": {
44
- "url": "https://github.com/eea/volto-addon-template.git",
45
- "package": "@eeacms/volto-addon-template",
41
+ "volto-eea-chatbot": {
42
+ "output": "packages",
43
+ "url": "https://github.com/eea/volto-eea-chatbot.git",
44
+ "package": "@eeacms/volto-eea-chatbot",
46
45
  "branch": "develop",
47
46
  "path": "src"
48
47
  }
49
48
  }
50
49
 
51
- 1. Install
50
+ 1. Add `@eeacms/volto-eea-chatbot` to the `addons` key in your project `package.json`
51
+
52
+ 1. Install or refresh the project setup
52
53
 
53
- make develop
54
- yarn
54
+ make install
55
55
 
56
- 1. Start backend
56
+ 1. Start backend in one terminal
57
57
 
58
- docker run --pull always -it --rm --name plone -p 8080:8080 -e SITE=Plone plone/plone-backend
58
+ make backend-start
59
59
 
60
- ...wait for backend to setup and start - `Ready to handle requests`:
60
+ ...wait for backend to setup and start, ending with `Ready to handle requests`
61
61
 
62
62
  ...you can also check http://localhost:8080/Plone
63
63
 
64
- 1. Start frontend
64
+ 1. Start frontend in a second terminal
65
65
 
66
- yarn start
66
+ make frontend-start
67
67
 
68
68
  1. Go to http://localhost:3000
69
69
 
70
70
  1. Happy hacking!
71
71
 
72
- cd src/addons/volto-addon-template/
72
+ cd packages/volto-eea-chatbot
73
+
74
+ For legacy Volto 17 projects, keep using the yarn-based workflow from the Volto 17 documentation.
73
75
 
74
76
  ## Cypress
75
77
 
76
78
  To run cypress locally, first make sure you don't have any Volto/Plone running on ports `8080` and `3000`.
77
79
 
78
80
  You don't have to be in a `clean-volto-project`, you can be in any Volto Frontend
79
- project where you added `volto-addon-template` to `mrs.developer.json`
81
+ project where you added `volto-eea-chatbot` to `mrs.developer.json`
80
82
 
81
83
  Go to:
82
84
 
83
85
  ```BASH
84
- cd src/addons/volto-addon-template/
86
+ cd packages/volto-eea-chatbot/
85
87
  ```
86
88
 
87
89
  Start:
@@ -91,7 +93,7 @@ make
91
93
  make start
92
94
  ```
93
95
 
94
- This will build and start with Docker a clean `Plone backend` and `Volto Frontend` with `volto-addon-template` block installed.
96
+ This will build and start with Docker a clean `Plone backend` and `Volto Frontend` with `volto-eea-chatbot` block installed.
95
97
 
96
98
  Open Cypress Interface:
97
99
 
package/README.md CHANGED
@@ -67,6 +67,11 @@ make start
67
67
 
68
68
  Go to http://localhost:3000
69
69
 
70
+ `make start` now defaults to Volto 18. To run the same setup against Volto 17, use:
71
+
72
+ VOLTO_VERSION=17 make
73
+ VOLTO_VERSION=17 make start
74
+
70
75
  ### Add volto-eea-chatbot to your Volto project
71
76
 
72
77
  1. Make sure you have a [Plone backend](https://plone.org/download) up-and-running at http://localhost:8080/Plone
@@ -89,21 +94,28 @@ Go to http://localhost:3000
89
94
  }
90
95
  ```
91
96
 
92
- - If not, create one:
97
+ - If not, create one with Cookieplone, as recommended by the official Plone documentation for Volto 18+:
93
98
 
94
99
  ```
95
- npm install -g yo @plone/generator-volto
96
- yo @plone/volto my-volto-project --canary --addon @eeacms/volto-eea-chatbot
97
- cd my-volto-project
100
+ uvx cookieplone project
101
+ cd project-title
98
102
  ```
99
103
 
100
- 1. Install new add-ons and restart Volto:
104
+ 1. Install or update dependencies, then start the project:
101
105
 
102
106
  ```
103
- yarn
104
- yarn start
107
+ make install
105
108
  ```
106
109
 
110
+ For a Cookieplone project, start the backend and frontend in separate terminals:
111
+
112
+ ```
113
+ make backend-start
114
+ make frontend-start
115
+ ```
116
+
117
+ For a legacy Volto 17 project, install the package with `yarn` and restart the frontend as usual.
118
+
107
119
  ## Environment Configuration
108
120
 
109
121
  To properly configure the middleware and authenticate with the Onyx service, ensure that the following environment variables are set:
@@ -15,7 +15,7 @@ services:
15
15
  args:
16
16
  ADDON_NAME: "${ADDON_NAME}"
17
17
  ADDON_PATH: "${ADDON_PATH}"
18
- VOLTO_VERSION: ${VOLTO_VERSION:-16}
18
+ VOLTO_VERSION: ${VOLTO_VERSION:-18-yarn}
19
19
  ports:
20
20
  - "3000:3000"
21
21
  - "3001:3001"
@@ -27,6 +27,20 @@ const addonName =
27
27
  ? pathParts[addonsIdx + 1]
28
28
  : path.basename(path.dirname(__filename)); // Fallback to folder name
29
29
  const addonBasePath = `src/addons/${addonName}/src`;
30
+ const voltoSlatePath = fs.existsSync(
31
+ path.join(
32
+ __dirname,
33
+ '..',
34
+ '..',
35
+ '..',
36
+ 'node_modules',
37
+ '@plone',
38
+ 'volto-slate',
39
+ 'src',
40
+ ),
41
+ )
42
+ ? '<rootDir>/node_modules/@plone/volto-slate/src'
43
+ : '<rootDir>/node_modules/@plone/volto/packages/volto-slate/src';
30
44
 
31
45
  // --- Performance caches ---
32
46
  const fileSearchCache = new Map();
@@ -421,10 +435,8 @@ module.exports = {
421
435
  '@eeacms/search/(.*)$': '<rootDir>/src/addons/volto-searchlib/searchlib/$1',
422
436
  '@eeacms/search': '<rootDir>/src/addons/volto-searchlib/searchlib',
423
437
  '@eeacms/(.*?)/(.*)$': '<rootDir>/node_modules/@eeacms/$1/src/$2',
424
- '@plone/volto-slate$':
425
- '<rootDir>/node_modules/@plone/volto/packages/volto-slate/src',
426
- '@plone/volto-slate/(.*)$':
427
- '<rootDir>/node_modules/@plone/volto/packages/volto-slate/src/$1',
438
+ '@plone/volto-slate$': voltoSlatePath,
439
+ '@plone/volto-slate/(.*)$': `${voltoSlatePath}/$1`,
428
440
  '~/(.*)$': '<rootDir>/src/$1',
429
441
  'load-volto-addons':
430
442
  '<rootDir>/node_modules/@plone/volto/jest-addons-loader.js',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "1.0.12",
3
+ "version": "2.0.0",
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",
@@ -25,8 +25,12 @@ export default function withOnyxData(callback) {
25
25
  React.useEffect(() => {
26
26
  async function handler() {
27
27
  if (fetcher) {
28
- const response = await fetcher;
29
- setState({ [name]: response.body });
28
+ try {
29
+ const response = await fetcher;
30
+ setState({ [name]: response.body });
31
+ } catch (error) {
32
+ setState({ [`${name}-error`]: error.message, [name]: null });
33
+ }
30
34
  }
31
35
  }
32
36
  handler();
@@ -45,9 +45,9 @@ export const ImageToolRenderer: MessageRenderer<ImageGenerationToolPacket> = ({
45
45
  alt={image.revised_prompt}
46
46
  className={`image-shape-${image.shape || 'square'}`}
47
47
  />
48
- {image.revised_prompt && (
48
+ {image.revised_prompt ? (
49
49
  <div className="image-prompt">{image.revised_prompt}</div>
50
- )}
50
+ ) : null}
51
51
  </div>
52
52
  ))}
53
53
  </div>
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import { AIMessage } from '../chat/AIMessage';
8
8
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import { BlinkingDot } from '../components/BlinkingDot';
8
8
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import { ChatMessage } from '../chat/ChatMessage';
8
8
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import { Citation } from '../components/markdown/Citation';
8
8
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import { ClaimModal } from '../components/markdown/ClaimModal';
5
5
 
6
6
  // Mock semantic-ui-react Modal
@@ -1,5 +1,5 @@
1
1
  import renderer from 'react-test-renderer';
2
- import '@testing-library/jest-dom/extend-expect';
2
+ import '@testing-library/jest-dom';
3
3
  import { ClaimSegments } from '../components/markdown/ClaimSegments';
4
4
 
5
5
  // Mock semantic-ui-react
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import { CustomToolRenderer } from '../packets/renderers/CustomToolRenderer';
5
5
  import { PacketType } from '../types/streamingModels';
6
6
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import EmptyState from '../components/EmptyState';
8
8
 
@@ -1,5 +1,5 @@
1
1
  import { render, screen, fireEvent, act } from '@testing-library/react';
2
- import '@testing-library/jest-dom/extend-expect';
2
+ import '@testing-library/jest-dom';
3
3
  import FeedbackModal from '../components/FeedbackModal';
4
4
  import * as lib from '../utils';
5
5
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import { FetchToolRenderer } from '../packets/renderers/FetchToolRenderer';
5
5
  import { PacketType } from '../types/streamingModels';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import { ImageToolRenderer } from '../packets/renderers/ImageToolRenderer';
5
5
  import { PacketType } from '../types/streamingModels';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import QualityCheckToggle from '../components/QualityCheckToggle';
5
5
 
6
6
  describe('QualityCheckToggle', () => {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { render, fireEvent } from '@testing-library/react';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import RelatedQuestions from '../components/RelatedQuestions';
5
5
 
6
6
  import { trackEvent } from '@eeacms/volto-matomo/utils';
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
3
  import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom/extend-expect';
4
+ import '@testing-library/jest-dom';
5
5
  import { RenderClaimView } from '../components/markdown/RenderClaimView';
6
6
 
7
7
  describe('RenderClaimView', () => {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import { SearchToolRenderer } from '../packets/renderers/SearchToolRenderer';
5
5
  import { PacketType } from '../types/streamingModels';
6
6
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import SourceDetails from '../components/Source';
8
8
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import renderer, { act } from 'react-test-renderer';
3
3
  import { render, fireEvent, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom/extend-expect';
4
+ import '@testing-library/jest-dom';
5
5
  import { SourceChip } from '../components/SourceChip';
6
6
 
7
7
  describe('SourceChip', () => {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import Spinner from '../components/Spinner';
5
5
 
6
6
  describe('Spinner', () => {
@@ -3,7 +3,7 @@ import { MemoryRouter } from 'react-router-dom';
3
3
  import configureStore from 'redux-mock-store';
4
4
  import renderer from 'react-test-renderer';
5
5
  import { render, fireEvent, screen } from '@testing-library/react';
6
- import '@testing-library/jest-dom/extend-expect';
6
+ import '@testing-library/jest-dom';
7
7
  import { Provider } from 'react-intl-redux';
8
8
  import UserActionsToolbar from '../components/UserActionsToolbar';
9
9
 
@@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import renderer from 'react-test-renderer';
4
4
 
5
- import '@testing-library/jest-dom/extend-expect';
5
+ import '@testing-library/jest-dom';
6
6
  import { Provider } from 'react-intl-redux';
7
7
  import { UserMessage } from '../chat/UserMessage';
8
8
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import renderer from 'react-test-renderer';
3
3
  import { render, fireEvent, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom/extend-expect';
4
+ import '@testing-library/jest-dom';
5
5
  import { WebResultIcon } from '../components/WebResultIcon';
6
6
 
7
7
  describe('WebResultIcon', () => {
@@ -1,4 +1,4 @@
1
- import '@testing-library/jest-dom/extend-expect';
1
+ import '@testing-library/jest-dom';
2
2
 
3
3
  import installChatBlock from '../index';
4
4
 
@@ -1,4 +1,4 @@
1
- import '@testing-library/jest-dom/extend-expect';
1
+ import '@testing-library/jest-dom';
2
2
  import { ChatBlockSchema } from '../schema';
3
3
 
4
4
  describe('ChatBlockSchema', () => {
@@ -1,5 +1,5 @@
1
1
  import { renderHook } from '@testing-library/react-hooks';
2
- import '@testing-library/jest-dom/extend-expect';
2
+ import '@testing-library/jest-dom';
3
3
  import { useDeepCompareMemoize } from '../hooks/useDeepCompareMemoize';
4
4
 
5
5
  describe('useDeepCompareMemoize', () => {
@@ -1,6 +1,6 @@
1
1
  import { act } from '@testing-library/react';
2
2
  import { renderHook } from '@testing-library/react-hooks';
3
- import '@testing-library/jest-dom/extend-expect';
3
+ import '@testing-library/jest-dom';
4
4
  import {
5
5
  EMAIL_REGEX,
6
6
  transformEmailsToLinks,
@@ -1,5 +1,5 @@
1
1
  import { render, screen, waitFor } from '@testing-library/react';
2
- import '@testing-library/jest-dom/extend-expect';
2
+ import '@testing-library/jest-dom';
3
3
  import withOnyxData from '../hocs/withOnyxData';
4
4
 
5
5
  describe('withOnyxData', () => {
@@ -13,7 +13,7 @@ const filterModel = {
13
13
  apiKey: LLMGW_API_KEY,
14
14
  };
15
15
 
16
- export async function callLLM(apiUrl, apiKey, requestBody) {
16
+ export async function callLLM(apiUrl, apiKey, requestBody, { ip } = {}) {
17
17
  const headers = {
18
18
  'Content-Type': 'application/json',
19
19
  accept: 'application/json',
@@ -21,6 +21,9 @@ export async function callLLM(apiUrl, apiKey, requestBody) {
21
21
  if (apiKey) {
22
22
  headers['Authorization'] = `Bearer ${apiKey}`;
23
23
  }
24
+ if (ip) {
25
+ headers['X-Forwarded-For'] = ip;
26
+ }
24
27
 
25
28
  const response = await fetch(apiUrl, {
26
29
  method: 'POST',
@@ -92,24 +95,26 @@ export function parseExcludeIndices(content, maxIndex) {
92
95
  return excludeIndices;
93
96
  }
94
97
 
95
- async function callFilterModel(prompt) {
98
+ async function callFilterModel(prompt, { ip } = {}) {
96
99
  const data = {
97
100
  messages: [{ role: 'user', content: prompt }],
98
101
  temperature: 0.0,
99
102
  model: filterModel.name,
100
103
  };
101
- const jsonData = await callLLM(filterModel.apiUrl, filterModel.apiKey, data);
104
+ const jsonData = await callLLM(filterModel.apiUrl, filterModel.apiKey, data, {
105
+ ip,
106
+ });
102
107
  return jsonData.choices?.[0]?.message?.content || '';
103
108
  }
104
109
 
105
- export async function excludeClaimSentences(sentences) {
110
+ export async function excludeClaimSentences(sentences, { ip } = {}) {
106
111
  if (sentences.length === 0) {
107
112
  return new Set();
108
113
  }
109
114
 
110
115
  try {
111
116
  const prompt = buildClaimFilterPrompt(sentences);
112
- const content = await callFilterModel(prompt);
117
+ const content = await callFilterModel(prompt, { ip });
113
118
  const excludedIndices = parseExcludeIndices(content, sentences.length);
114
119
  log('Claim filter response', excludedIndices.size);
115
120
  return excludedIndices;
@@ -122,6 +127,7 @@ export async function excludeClaimSentences(sentences) {
122
127
  export async function excludeContextSentences(
123
128
  contextSentences,
124
129
  claimSentences,
130
+ { ip } = {},
125
131
  ) {
126
132
  if (contextSentences.length <= MIN_CONTEXT_SENTENCES_FOR_FILTERING) {
127
133
  return new Set();
@@ -129,7 +135,7 @@ export async function excludeContextSentences(
129
135
 
130
136
  try {
131
137
  const prompt = buildContextFilterPrompt(contextSentences, claimSentences);
132
- const content = await callFilterModel(prompt);
138
+ const content = await callFilterModel(prompt, { ip });
133
139
  const excludedIndices = parseExcludeIndices(
134
140
  content,
135
141
  contextSentences.length,
@@ -52,7 +52,12 @@ function mergeChunkClaims(chunkResults) {
52
52
  return Array.from(claimMap.values());
53
53
  }
54
54
 
55
- export async function getVerifyClaimResponse(model, sources, answer) {
55
+ export async function getVerifyClaimResponse(
56
+ model,
57
+ sources,
58
+ answer,
59
+ { ip } = {},
60
+ ) {
56
61
  const emptyResponse = {
57
62
  claims: [],
58
63
  segments: {},
@@ -67,7 +72,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
67
72
 
68
73
  // Filter claims and context in parallel
69
74
  const [excludeResponseIndices] = await Promise.all([
70
- excludeClaimSentences(responseSentences),
75
+ excludeClaimSentences(responseSentences, { ip }),
71
76
  ]);
72
77
 
73
78
  const contextSentences = [];
@@ -109,7 +114,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
109
114
  const chunkResults = await Promise.all(
110
115
  prompts.map((chunkPrompt, i) => {
111
116
  log(`Chunk ${i + 1} request`);
112
- return halloumiGenerativeAPI(model, chunkPrompt);
117
+ return halloumiGenerativeAPI(model, chunkPrompt, { ip });
113
118
  }),
114
119
  );
115
120
 
@@ -166,7 +171,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
166
171
  * - `DUMP_HALLOUMI_REQ_FILE_PATH`: If set, the LLM request (URL and parameters) is dumped to the specified file path.
167
172
  * - `DUMP_HALLOUMI_FILE_PATH`: If set, the LLM response is dumped to the specified file path.
168
173
  */
169
- async function getLLMResponse(model, prompt) {
174
+ async function getLLMResponse(model, prompt, { ip } = {}) {
170
175
  let jsonData;
171
176
 
172
177
  if (process.env.MOCK_HALLOUMI_FILE_PATH) {
@@ -194,7 +199,7 @@ async function getLLMResponse(model, prompt) {
194
199
  log(`Dumped halloumi request: ${filePath}`);
195
200
  }
196
201
 
197
- jsonData = await callLLM(model.apiUrl, model.apiKey, data);
202
+ jsonData = await callLLM(model.apiUrl, model.apiKey, data, { ip });
198
203
 
199
204
  if (process.env.DUMP_HALLOUMI_FILE_PATH) {
200
205
  const filePath = process.env.DUMP_HALLOUMI_FILE_PATH;
@@ -210,8 +215,8 @@ async function getLLMResponse(model, prompt) {
210
215
  * @param response A string containing all claims and their information.
211
216
  * @returns A list of claim objects.
212
217
  */
213
- export async function halloumiGenerativeAPI(model, prompt) {
214
- const jsonData = await getLLMResponse(model, prompt);
218
+ export async function halloumiGenerativeAPI(model, prompt, { ip } = {}) {
219
+ const jsonData = await getLLMResponse(model, prompt, { ip });
215
220
 
216
221
  // Todo: restore log
217
222
  // log('Generative response', jsonData);
@@ -56,6 +56,7 @@ export default async function middleware(req, res, next) {
56
56
  // TODO: map with citation id
57
57
  sources,
58
58
  answer,
59
+ { ip: req.headers['x-forwarded-for'] || req.ip },
59
60
  );
60
61
  // log('Halloumi response', resp);
61
62
  res.send(resp);
package/src/middleware.js CHANGED
@@ -151,6 +151,7 @@ async function send_onyx_request(
151
151
  res,
152
152
  { username, password, api_key, url, is_related_question },
153
153
  ) {
154
+ const forwardedFor = req.headers['x-forwarded-for'] || req.ip;
154
155
  let headers = {};
155
156
  if (!api_key) {
156
157
  await login(username, password);
@@ -166,11 +167,13 @@ async function send_onyx_request(
166
167
  headers = {
167
168
  Cookie: cached_auth_cookie,
168
169
  'Content-Type': 'application/json',
170
+ 'X-Forwarded-For': forwardedFor,
169
171
  };
170
172
  } else {
171
173
  headers = {
172
174
  Authorization: 'Bearer ' + api_key,
173
175
  'Content-Type': 'application/json',
176
+ 'X-Forwarded-For': forwardedFor,
174
177
  };
175
178
  }
176
179
 
@@ -237,9 +240,9 @@ export default async function middleware(req, res, next) {
237
240
 
238
241
  const api_key = process.env.ONYX_API_KEY;
239
242
  if (!api_key) {
240
- res.send({
241
- error: MSG_INVALID_CONFIGURATION,
242
- });
243
+ res.statusCode = 500;
244
+ res.statusMessage = MSG_INVALID_CONFIGURATION;
245
+ res.send({ error: MSG_INVALID_CONFIGURATION });
243
246
  return;
244
247
  }
245
248
 
@@ -251,8 +254,10 @@ export default async function middleware(req, res, next) {
251
254
  });
252
255
  } catch (error) {
253
256
  // eslint-disable-next-line
254
- console.error(MSG_ERROR_REQUEST, error?.response?.text);
255
-
256
- res.send({ error: `Onyx error: ${error?.response?.text || 'error'}` });
257
+ const errorMessage = `Onyx error: ${error?.response?.text || 'error'}`;
258
+ console.error(MSG_ERROR_REQUEST, errorMessage);
259
+ res.statusCode = 500;
260
+ res.statusMessage = errorMessage;
261
+ res.send({ error: errorMessage });
257
262
  }
258
263
  }
@@ -59,6 +59,8 @@ describe('src/middleware', () => {
59
59
  url: '/_da/chat/send-message',
60
60
  method: 'POST',
61
61
  body: { message: 'hello' },
62
+ ip: '127.0.0.1',
63
+ headers: {},
62
64
  };
63
65
  res = {
64
66
  send: jest.fn(),