@eeacms/volto-eea-chatbot 2.0.5 → 3.0.1

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 (36) hide show
  1. package/.github/workflows/betterleaks.yml +133 -0
  2. package/.gitleaks.toml +89 -0
  3. package/AGENTS.md +230 -0
  4. package/CHANGELOG.md +40 -0
  5. package/README.md +59 -0
  6. package/RELEASE.md +1 -1
  7. package/locales/de/LC_MESSAGES/volto.po +44 -0
  8. package/locales/en/LC_MESSAGES/volto.po +44 -0
  9. package/locales/it/LC_MESSAGES/volto.po +44 -0
  10. package/locales/ro/LC_MESSAGES/volto.po +44 -0
  11. package/locales/volto.pot +46 -2
  12. package/package.json +2 -1
  13. package/src/AISearchInput/AISearchInputEdit.jsx +37 -0
  14. package/src/AISearchInput/AISearchInputView.jsx +130 -0
  15. package/src/AISearchInput/components/DeepResearchToggle.jsx +35 -0
  16. package/src/AISearchInput/components/DisclaimerText.jsx +10 -0
  17. package/src/AISearchInput/components/IntroHeader.jsx +24 -0
  18. package/src/AISearchInput/components/PromptChips.jsx +22 -0
  19. package/src/AISearchInput/components/SearchInput.jsx +53 -0
  20. package/src/AISearchInput/index.js +32 -0
  21. package/src/AISearchInput/messages.js +38 -0
  22. package/src/AISearchInput/schema.jsx +215 -0
  23. package/src/AISearchInput/styles.less +320 -0
  24. package/src/ChatBlock/ChatBlockView.jsx +4 -0
  25. package/src/ChatBlock/chat/ChatWindow.tsx +23 -0
  26. package/src/ChatBlock/packets/MultiToolRenderer.tsx +20 -11
  27. package/src/ChatBlock/services/messageProcessor.ts +32 -14
  28. package/src/ChatBlock/style.less +41 -51
  29. package/src/halloumi/filtering.js +2 -2
  30. package/src/halloumi/generative.test.js +1 -1
  31. package/src/halloumi/middleware.js +2 -2
  32. package/src/halloumi/middleware.test.js +5 -5
  33. package/src/icons/sparkle.svg +3 -0
  34. package/src/index.js +2 -0
  35. package/src/middleware.js +2 -2
  36. package/src/middleware.test.js +11 -11
@@ -11,4 +11,48 @@ msgstr ""
11
11
  "Content-Transfer-Encoding: \n"
12
12
  "Plural-Forms: \n"
13
13
 
14
+ #. Default: "AI Search Input"
15
+ #: AISearchInput/messages
16
+ msgid "AI Search Input"
17
+ msgstr ""
18
+
19
+ #. Default: "Ask your question..."
20
+ #: AISearchInput/components/SearchInput
21
+ #: AISearchInput/messages
22
+ msgid "Ask your question..."
23
+ msgstr ""
24
+
25
+ #. Default: "Assistant page is not configured. Please contact the site administrator."
26
+ #: AISearchInput/AISearchInputView
27
+ #: AISearchInput/messages
28
+ msgid "Assistant page is not configured. Please contact the site administrator."
29
+ msgstr ""
30
+
31
+ #. Default: "Deep research"
32
+ #: AISearchInput/components/DeepResearchToggle
33
+ #: AISearchInput/messages
34
+ msgid "Deep research"
35
+ msgstr ""
14
36
 
37
+ #. Default: "Deep research on"
38
+ #: AISearchInput/components/DeepResearchToggle
39
+ #: AISearchInput/messages
40
+ msgid "Deep research on"
41
+ msgstr ""
42
+
43
+ #. Default: "Invalid assistant page URL."
44
+ #: AISearchInput/AISearchInputView
45
+ #: AISearchInput/messages
46
+ msgid "Invalid assistant page URL."
47
+ msgstr ""
48
+
49
+ #. Default: "Submit question"
50
+ #: AISearchInput/components/SearchInput
51
+ #: AISearchInput/messages
52
+ msgid "Submit question"
53
+ msgstr ""
54
+
55
+ #. Default: "This assistant uses AI. Responses may contain inaccuracies."
56
+ #: AISearchInput/messages
57
+ msgid "This assistant uses AI. Responses may contain inaccuracies."
58
+ msgstr ""
@@ -11,4 +11,48 @@ msgstr ""
11
11
  "Content-Transfer-Encoding: \n"
12
12
  "Plural-Forms: \n"
13
13
 
14
+ #. Default: "AI Search Input"
15
+ #: AISearchInput/messages
16
+ msgid "AI Search Input"
17
+ msgstr ""
18
+
19
+ #. Default: "Ask your question..."
20
+ #: AISearchInput/components/SearchInput
21
+ #: AISearchInput/messages
22
+ msgid "Ask your question..."
23
+ msgstr ""
24
+
25
+ #. Default: "Assistant page is not configured. Please contact the site administrator."
26
+ #: AISearchInput/AISearchInputView
27
+ #: AISearchInput/messages
28
+ msgid "Assistant page is not configured. Please contact the site administrator."
29
+ msgstr ""
30
+
31
+ #. Default: "Deep research"
32
+ #: AISearchInput/components/DeepResearchToggle
33
+ #: AISearchInput/messages
34
+ msgid "Deep research"
35
+ msgstr ""
14
36
 
37
+ #. Default: "Deep research on"
38
+ #: AISearchInput/components/DeepResearchToggle
39
+ #: AISearchInput/messages
40
+ msgid "Deep research on"
41
+ msgstr ""
42
+
43
+ #. Default: "Invalid assistant page URL."
44
+ #: AISearchInput/AISearchInputView
45
+ #: AISearchInput/messages
46
+ msgid "Invalid assistant page URL."
47
+ msgstr ""
48
+
49
+ #. Default: "Submit question"
50
+ #: AISearchInput/components/SearchInput
51
+ #: AISearchInput/messages
52
+ msgid "Submit question"
53
+ msgstr ""
54
+
55
+ #. Default: "This assistant uses AI. Responses may contain inaccuracies."
56
+ #: AISearchInput/messages
57
+ msgid "This assistant uses AI. Responses may contain inaccuracies."
58
+ msgstr ""
package/locales/volto.pot CHANGED
@@ -1,16 +1,60 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2023-06-28T10:48:22.678Z\n"
4
+ "POT-Creation-Date: 2026-07-07T08:22:35.802Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
- "MIME-Version: 1.0\n"
8
7
  "Content-Type: text/plain; charset=utf-8\n"
9
8
  "Content-Transfer-Encoding: 8bit\n"
10
9
  "Plural-Forms: nplurals=1; plural=0;\n"
10
+ "MIME-Version: 1.0\n"
11
11
  "Language-Code: en\n"
12
12
  "Language-Name: English\n"
13
13
  "Preferred-Encodings: utf-8\n"
14
14
  "Domain: volto\n"
15
15
 
16
+ #. Default: "AI Search Input"
17
+ #: AISearchInput/messages
18
+ msgid "AI Search Input"
19
+ msgstr ""
20
+
21
+ #. Default: "Ask your question..."
22
+ #: AISearchInput/components/SearchInput
23
+ #: AISearchInput/messages
24
+ msgid "Ask your question..."
25
+ msgstr ""
26
+
27
+ #. Default: "Assistant page is not configured. Please contact the site administrator."
28
+ #: AISearchInput/AISearchInputView
29
+ #: AISearchInput/messages
30
+ msgid "Assistant page is not configured. Please contact the site administrator."
31
+ msgstr ""
32
+
33
+ #. Default: "Deep research"
34
+ #: AISearchInput/components/DeepResearchToggle
35
+ #: AISearchInput/messages
36
+ msgid "Deep research"
37
+ msgstr ""
38
+
39
+ #. Default: "Deep research on"
40
+ #: AISearchInput/components/DeepResearchToggle
41
+ #: AISearchInput/messages
42
+ msgid "Deep research on"
43
+ msgstr ""
44
+
45
+ #. Default: "Invalid assistant page URL."
46
+ #: AISearchInput/AISearchInputView
47
+ #: AISearchInput/messages
48
+ msgid "Invalid assistant page URL."
49
+ msgstr ""
50
+
51
+ #. Default: "Submit question"
52
+ #: AISearchInput/components/SearchInput
53
+ #: AISearchInput/messages
54
+ msgid "Submit question"
55
+ msgstr ""
16
56
 
57
+ #. Default: "This assistant uses AI. Responses may contain inaccuracies."
58
+ #: AISearchInput/messages
59
+ msgid "This assistant uses AI. Responses may contain inaccuracies."
60
+ msgstr ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "2.0.5",
3
+ "version": "3.0.1",
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",
@@ -40,6 +40,7 @@
40
40
  "typescript": "^5.7.3"
41
41
  },
42
42
  "dependencies": {
43
+ "@eeacms/volto-design-tokens": "*",
43
44
  "@eeacms/volto-matomo": "*",
44
45
  "@microsoft/fetch-event-source": "2.0.1",
45
46
  "@plone-collective/volto-sentry": "*",
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { useIntl } from 'react-intl';
3
+ import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal';
4
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
5
+
6
+ import AISearchInputView from './AISearchInputView';
7
+ import { AISearchInputSchema } from './schema';
8
+
9
+ const AISearchInputEdit = (props) => {
10
+ const { onChangeBlock, block, data } = props;
11
+ const intl = useIntl();
12
+
13
+ const schema = React.useMemo(
14
+ () => AISearchInputSchema({ intl, data }),
15
+ [data, intl],
16
+ );
17
+
18
+ return (
19
+ <div>
20
+ <AISearchInputView {...props} isEditMode />
21
+ <SidebarPortal selected={props.selected}>
22
+ <BlockDataForm
23
+ schema={schema}
24
+ title={schema.title}
25
+ block={block}
26
+ onChangeBlock={onChangeBlock}
27
+ onChangeField={(id, value) => {
28
+ onChangeBlock(block, { ...data, [id]: value });
29
+ }}
30
+ formData={data}
31
+ />
32
+ </SidebarPortal>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default AISearchInputEdit;
@@ -0,0 +1,130 @@
1
+ import React, { useState } from 'react';
2
+ import { useHistory } from 'react-router-dom';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { trackEvent } from '@eeacms/volto-matomo/utils';
5
+
6
+ import IntroHeader from './components/IntroHeader';
7
+ import SearchInput from './components/SearchInput';
8
+ import PromptChips from './components/PromptChips';
9
+ import DeepResearchToggle from './components/DeepResearchToggle';
10
+ import DisclaimerText from './components/DisclaimerText';
11
+
12
+ import './styles.less';
13
+
14
+ const AISearchInputView = ({ data }) => {
15
+ const history = useHistory();
16
+ const [query, setQuery] = useState('');
17
+ const [error, setError] = useState(null);
18
+ const [deepResearchEnabled, setDeepResearchEnabled] = useState(
19
+ data.deepResearch === 'always_on' || data.deepResearch === 'user_on',
20
+ );
21
+
22
+ const variant = data.stylingVariant || 'dark';
23
+
24
+ const handleSubmit = (message) => {
25
+ setError(null);
26
+ const q = (message || query).trim();
27
+
28
+ if (!q) return;
29
+
30
+ if (!data.assistantEndpoint) {
31
+ setError(
32
+ <FormattedMessage
33
+ id="Assistant page is not configured. Please contact the site administrator."
34
+ defaultMessage="Assistant page is not configured. Please contact the site administrator."
35
+ />,
36
+ );
37
+ return;
38
+ }
39
+
40
+ // Build target URL
41
+ let targetUrl;
42
+ try {
43
+ targetUrl = new URL(data.assistantEndpoint, window.location.origin);
44
+ } catch {
45
+ setError(
46
+ <FormattedMessage
47
+ id="Invalid assistant page URL."
48
+ defaultMessage="Invalid assistant page URL."
49
+ />,
50
+ );
51
+ return;
52
+ }
53
+
54
+ targetUrl.searchParams.set('query', q);
55
+ if (data.deepResearch !== 'unavailable') {
56
+ targetUrl.searchParams.set(
57
+ 'deepResearch',
58
+ deepResearchEnabled ? 'true' : 'false',
59
+ );
60
+ }
61
+
62
+ const targetString = targetUrl.pathname + targetUrl.search;
63
+
64
+ // Matomo tracking
65
+ trackEvent({
66
+ category: 'Chatbot',
67
+ action: 'AI Search Input: Question submitted',
68
+ name: q.substring(0, 100),
69
+ });
70
+
71
+ // Navigate: client-side for same-origin, full for external
72
+ if (targetUrl.origin === window.location.origin) {
73
+ history.push(targetString);
74
+ } else {
75
+ window.location.href = targetUrl.toString();
76
+ }
77
+ };
78
+
79
+ const handleChipSelect = (message) => {
80
+ handleSubmit(message);
81
+ };
82
+
83
+ return (
84
+ <div className={`ais-search-input ais-${variant}`}>
85
+ <IntroHeader
86
+ showIcon={data.showIcon}
87
+ blockTitle={data.blockTitle}
88
+ introText={data.introText}
89
+ />
90
+
91
+ <SearchInput
92
+ placeholderText={data.placeholderText || 'Ask your question...'}
93
+ onSubmit={() => handleSubmit()}
94
+ value={query}
95
+ onChange={(value) => {
96
+ setQuery(value);
97
+ setError(null);
98
+ }}
99
+ error={error}
100
+ />
101
+
102
+ {(data.examplePromptsEnabled && data.examplePrompts) ||
103
+ data.deepResearch !== 'unavailable' ? (
104
+ <div className="ais-controls-row">
105
+ {data.examplePromptsEnabled && data.examplePrompts && (
106
+ <PromptChips
107
+ prompts={data.examplePrompts}
108
+ onSelect={handleChipSelect}
109
+ />
110
+ )}
111
+ <DeepResearchToggle
112
+ mode={data.deepResearch}
113
+ enabled={deepResearchEnabled}
114
+ onChange={setDeepResearchEnabled}
115
+ />
116
+ </div>
117
+ ) : (
118
+ <DeepResearchToggle
119
+ mode={data.deepResearch}
120
+ enabled={deepResearchEnabled}
121
+ onChange={setDeepResearchEnabled}
122
+ />
123
+ )}
124
+
125
+ <DisclaimerText disclaimerText={data.disclaimerText} />
126
+ </div>
127
+ );
128
+ };
129
+
130
+ export default AISearchInputView;
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+
4
+ const DeepResearchToggle = ({ mode, enabled, onChange }) => {
5
+ if (mode === 'unavailable' || !mode) return null;
6
+
7
+ if (mode === 'always_on') {
8
+ return (
9
+ <div className="ais-deep-research">
10
+ <small className="ais-deep-research-label">
11
+ <FormattedMessage
12
+ id="Deep research on"
13
+ defaultMessage="Deep research on"
14
+ />
15
+ </small>
16
+ </div>
17
+ );
18
+ }
19
+
20
+ return (
21
+ <div className="ais-deep-research">
22
+ <label className="ais-toggle">
23
+ <input
24
+ type="checkbox"
25
+ checked={enabled}
26
+ onChange={(e) => onChange(e.target.checked)}
27
+ />
28
+ <span className="ais-toggle-slider" />
29
+ <FormattedMessage id="Deep research" defaultMessage="Deep research" />
30
+ </label>
31
+ </div>
32
+ );
33
+ };
34
+
35
+ export default DeepResearchToggle;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { serializeNodes } from '@plone/volto-slate/editor/render';
3
+
4
+ const DisclaimerText = ({ disclaimerText }) => {
5
+ if (!disclaimerText) return null;
6
+
7
+ return <div className="ais-disclaimer">{serializeNodes(disclaimerText)}</div>;
8
+ };
9
+
10
+ export default DisclaimerText;
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
3
+ import SparkleIcon from '@eeacms/volto-eea-chatbot/icons/sparkle.svg';
4
+ import { serializeNodes } from '@plone/volto-slate/editor/render';
5
+
6
+ const IntroHeader = ({ showIcon, blockTitle, introText }) => {
7
+ if (!showIcon && !blockTitle && !introText) return null;
8
+
9
+ return (
10
+ <div className="ais-intro-header">
11
+ {showIcon && (
12
+ <SVGIcon name={SparkleIcon} size={20} className="ais-icon" />
13
+ )}
14
+ <div className="ais-intro-content">
15
+ {blockTitle && <span className="ais-block-title">{blockTitle}</span>}
16
+ {introText && (
17
+ <div className="ais-intro-slate">{serializeNodes(introText)}</div>
18
+ )}
19
+ </div>
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export default IntroHeader;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+
3
+ const PromptChips = ({ prompts, onSelect }) => {
4
+ if (!prompts || prompts.length === 0) return null;
5
+
6
+ return (
7
+ <div className="ais-prompt-chips">
8
+ {prompts.map((prompt, index) => (
9
+ <button
10
+ key={index}
11
+ type="button"
12
+ className="ais-chip"
13
+ onClick={() => onSelect(prompt.message || prompt.label)}
14
+ >
15
+ {prompt.label}
16
+ </button>
17
+ ))}
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default PromptChips;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
4
+ import SendIcon from '@eeacms/volto-eea-chatbot/icons/send.svg';
5
+
6
+ const SearchInput = ({ placeholderText, onSubmit, value, onChange, error }) => {
7
+ const handleKeyDown = (e) => {
8
+ if (e.key === 'Enter' && !e.shiftKey) {
9
+ e.preventDefault();
10
+ onSubmit();
11
+ }
12
+ };
13
+
14
+ return (
15
+ <div className="ais-search-input-field">
16
+ <input
17
+ type="text"
18
+ value={value}
19
+ onChange={(e) => onChange(e.target.value)}
20
+ onKeyDown={handleKeyDown}
21
+ placeholder={placeholderText}
22
+ aria-label={
23
+ <FormattedMessage
24
+ id="Ask your question..."
25
+ defaultMessage="Ask your question..."
26
+ />
27
+ }
28
+ aria-invalid={!!error}
29
+ aria-describedby={error ? 'ais-input-error' : undefined}
30
+ />
31
+ <button
32
+ type="button"
33
+ onClick={onSubmit}
34
+ className="ais-submit-btn"
35
+ aria-label={
36
+ <FormattedMessage
37
+ id="Submit question"
38
+ defaultMessage="Submit question"
39
+ />
40
+ }
41
+ >
42
+ <SVGIcon name={SendIcon} size={24} />
43
+ </button>
44
+ {error && (
45
+ <div id="ais-input-error" className="ais-input-error" role="alert">
46
+ {error}
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ };
52
+
53
+ export default SearchInput;
@@ -0,0 +1,32 @@
1
+ import sparkleSVG from '@eeacms/volto-eea-chatbot/icons/sparkle.svg';
2
+ import AISearchInputView from './AISearchInputView';
3
+ import AISearchInputEdit from './AISearchInputEdit';
4
+ import { AISearchInputSchema } from './schema';
5
+
6
+ export default function installAISearchInputBlock(config) {
7
+ config.blocks.blocksConfig.eeaAISearchInput = {
8
+ id: 'eeaAISearchInput',
9
+ title: 'AI Search Input',
10
+ icon: sparkleSVG,
11
+ group: 'common',
12
+ view: AISearchInputView,
13
+ edit: AISearchInputEdit,
14
+ restricted: ({ user }) => {
15
+ if (user?.roles) {
16
+ return !user.roles.find((role) => role === 'Manager');
17
+ }
18
+ return false;
19
+ },
20
+ mostUsed: false,
21
+ blockHasOwnFocusManagement: false,
22
+ sidebarTab: 1,
23
+ schema: AISearchInputSchema,
24
+ security: {
25
+ addPermission: [],
26
+ view: [],
27
+ },
28
+ variations: [],
29
+ };
30
+
31
+ return config;
32
+ }
@@ -0,0 +1,38 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ aiSearchInput: {
5
+ id: 'AI Search Input',
6
+ defaultMessage: 'AI Search Input',
7
+ },
8
+ askYourQuestion: {
9
+ id: 'Ask your question...',
10
+ defaultMessage: 'Ask your question...',
11
+ },
12
+ submit: {
13
+ id: 'Submit question',
14
+ defaultMessage: 'Submit question',
15
+ },
16
+ deepResearch: {
17
+ id: 'Deep research',
18
+ defaultMessage: 'Deep research',
19
+ },
20
+ deepResearchOn: {
21
+ id: 'Deep research on',
22
+ defaultMessage: 'Deep research on',
23
+ },
24
+ defaultDisclaimer: {
25
+ id: 'This assistant uses AI. Responses may contain inaccuracies.',
26
+ defaultMessage:
27
+ 'This assistant uses AI. Responses may contain inaccuracies.',
28
+ },
29
+ noEndpointError: {
30
+ id: 'Assistant page is not configured. Please contact the site administrator.',
31
+ defaultMessage:
32
+ 'Assistant page is not configured. Please contact the site administrator.',
33
+ },
34
+ invalidEndpointError: {
35
+ id: 'Invalid assistant page URL.',
36
+ defaultMessage: 'Invalid assistant page URL.',
37
+ },
38
+ });