@eeacms/volto-eea-chatbot 2.0.5 → 3.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.
@@ -0,0 +1,320 @@
1
+ // AI Search Input styles — scoped under .ais-search-input
2
+ // Uses EEA Design System tokens
3
+
4
+ @import '@eeacms/volto-design-tokens/src/colors';
5
+ @import '@eeacms/volto-design-tokens/src/shapes';
6
+ @import '@eeacms/volto-design-tokens/src/borders';
7
+ @import '@eeacms/volto-design-tokens/src/fonts';
8
+
9
+ // Spacing tokens from @eeacms/volto-design-tokens/src/sizes
10
+ // (imported manually to avoid @1px Semantic UI dependency)
11
+ @spacer: 4px;
12
+ @space-025: @spacer * 0.25;
13
+ @space-05: @spacer * 0.5;
14
+ @space-1: @spacer * 1;
15
+ @space-2: @spacer * 2;
16
+ @space-3: @spacer * 3;
17
+ @space-4: @spacer * 4;
18
+ @space-5: @spacer * 5;
19
+ @space-6: @spacer * 6;
20
+ @space-8: @spacer * 8;
21
+
22
+ // Size tokens
23
+ @size-10: 120px;
24
+
25
+ .ais-search-input {
26
+ display: flex;
27
+ flex-direction: column;
28
+ padding: @space-6;
29
+ gap: @space-3;
30
+
31
+ // --- Dark variant (default) ---
32
+ &.ais-dark {
33
+ background-color: @blue-6;
34
+ color: @grey-0;
35
+
36
+ .ais-chip {
37
+ background-color: @blue-5;
38
+ }
39
+
40
+ .ais-search-input-field {
41
+ border-bottom-color: @grey-0;
42
+ }
43
+
44
+ .ais-input-error {
45
+ color: @red-2;
46
+ }
47
+ }
48
+
49
+ // --- Light variant ---
50
+ &.ais-light {
51
+ background-color: @grey-1;
52
+ color: @grey-5;
53
+
54
+ .ais-chip {
55
+ background-color: @grey-2;
56
+ }
57
+
58
+ .ais-search-input-field {
59
+ border-bottom-color: @grey-5;
60
+ }
61
+
62
+ .ais-input-error {
63
+ color: @red-5;
64
+ }
65
+ }
66
+
67
+ // --- Accent variant (EEA green/teal) ---
68
+ &.ais-accent {
69
+ background-color: @green-5;
70
+ color: @grey-0;
71
+
72
+ .ais-chip {
73
+ background-color: @green-4;
74
+ }
75
+
76
+ .ais-search-input-field {
77
+ border-bottom-color: @grey-0;
78
+ }
79
+
80
+ .ais-input-error {
81
+ color: @red-1;
82
+ }
83
+ }
84
+
85
+ // --- Intro header (Row 1) ---
86
+ .ais-intro-header {
87
+ display: flex;
88
+ align-items: flex-start;
89
+ gap: @space-2;
90
+
91
+ .ais-icon {
92
+ flex-shrink: 0;
93
+ margin-top: @space-05;
94
+ }
95
+
96
+ .ais-intro-content {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: @space-05;
100
+ }
101
+
102
+ .ais-block-title {
103
+ font-size: @font-size-1;
104
+ font-weight: @font-weight-6;
105
+ }
106
+
107
+ // Slate-rendered intro text
108
+ :global(.slate-editor) {
109
+ margin: 0;
110
+ font-size: @font-size-0;
111
+ line-height: @font-lineheight-2;
112
+
113
+ p {
114
+ margin: 0;
115
+ }
116
+
117
+ a {
118
+ color: inherit;
119
+ text-decoration: underline;
120
+ }
121
+ }
122
+ }
123
+
124
+ // --- Search input (Row 2) ---
125
+ .ais-search-input-field {
126
+ display: flex;
127
+ align-items: center;
128
+ padding-bottom: @space-1;
129
+ border-bottom: @border-size-2 solid currentColor;
130
+
131
+ input {
132
+ flex: 1;
133
+ padding: @space-1 0;
134
+ border: none;
135
+ background: transparent;
136
+ color: inherit;
137
+ font-family: inherit;
138
+ font-size: @font-size-1;
139
+ outline: none;
140
+
141
+ &::placeholder {
142
+ color: inherit;
143
+ opacity: 0.6;
144
+ }
145
+ }
146
+
147
+ .ais-submit-btn {
148
+ display: flex;
149
+ flex-shrink: 0;
150
+ align-items: center;
151
+ padding: @space-1;
152
+ border: none;
153
+ background: none;
154
+ color: inherit;
155
+ cursor: pointer;
156
+ transition: opacity 0.2s;
157
+
158
+ &:hover {
159
+ opacity: 0.8;
160
+ }
161
+
162
+ &:focus-visible {
163
+ border-radius: @radius-1;
164
+ outline: @border-size-2 solid currentColor;
165
+ outline-offset: @space-05;
166
+ }
167
+ }
168
+ }
169
+
170
+ .ais-input-error {
171
+ width: 100%;
172
+ margin-top: @space-1;
173
+ font-size: @font-size-00;
174
+ }
175
+
176
+ // --- Controls row: chips (left) + deep research toggle (right) ---
177
+ .ais-controls-row {
178
+ display: flex;
179
+ flex-wrap: wrap;
180
+ align-items: center;
181
+ gap: @space-2;
182
+ }
183
+
184
+ // --- Prompt chips ---
185
+ .ais-prompt-chips {
186
+ display: flex;
187
+ flex: 1;
188
+ flex-wrap: wrap;
189
+ gap: @space-2;
190
+
191
+ .ais-chip {
192
+ padding: @space-1 @space-3;
193
+ border: none;
194
+ border-radius: @radius-0;
195
+ color: inherit;
196
+ cursor: pointer;
197
+ font-family: inherit;
198
+ font-size: @font-size-0;
199
+ transition: opacity 0.2s;
200
+
201
+ &:hover {
202
+ opacity: 0.85;
203
+ }
204
+
205
+ &:focus-visible {
206
+ outline: @border-size-2 solid currentColor;
207
+ outline-offset: @space-05;
208
+ }
209
+ }
210
+ }
211
+
212
+ // --- Deep research toggle (right-aligned) ---
213
+ .ais-deep-research {
214
+ display: flex;
215
+ flex-shrink: 0;
216
+ align-items: center;
217
+ margin-left: auto;
218
+ }
219
+
220
+ .ais-deep-research-label {
221
+ font-size: @font-size-00;
222
+ opacity: 0.8;
223
+ }
224
+
225
+ // Custom toggle switch
226
+ .ais-toggle {
227
+ display: inline-flex;
228
+ align-items: center;
229
+ cursor: pointer;
230
+ font-size: @font-size-0;
231
+ gap: @space-2;
232
+
233
+ input {
234
+ display: none;
235
+ }
236
+
237
+ .ais-toggle-slider {
238
+ position: relative;
239
+ display: inline-block;
240
+ width: 28px;
241
+ height: 16px;
242
+ border: @border-size-1 solid currentColor;
243
+ border-radius: @radius-3;
244
+ background-color: rgba(currentColor, 0.15);
245
+ transition: background-color 0.2s;
246
+
247
+ &::before {
248
+ position: absolute;
249
+ top: 1px;
250
+ left: 2px;
251
+ display: block;
252
+ width: 12px;
253
+ height: 12px;
254
+ border-radius: 50%;
255
+ background-color: currentColor;
256
+ content: '';
257
+ transition: left 0.2s;
258
+ }
259
+ }
260
+
261
+ input:checked ~ .ais-toggle-slider {
262
+ border-color: @green-4;
263
+ background-color: @green-4;
264
+
265
+ &::before {
266
+ left: 13px;
267
+ }
268
+ }
269
+ }
270
+
271
+ // --- Disclaimer (Row 5) ---
272
+ .ais-disclaimer {
273
+ margin-top: @space-1;
274
+ font-size: @font-size-00;
275
+ opacity: 0.7;
276
+
277
+ :global(.slate-editor) {
278
+ font-size: inherit;
279
+
280
+ p {
281
+ margin: 0;
282
+ }
283
+
284
+ a {
285
+ color: inherit;
286
+ text-decoration: underline;
287
+ }
288
+ }
289
+ }
290
+
291
+ // --- Responsive ---
292
+ @media (max-width: 768px) {
293
+ padding: @space-4;
294
+ gap: @space-2;
295
+
296
+ .ais-intro-header {
297
+ .ais-icon {
298
+ width: @space-5;
299
+ height: @space-5;
300
+ }
301
+
302
+ .ais-block-title {
303
+ font-size: @font-size-0;
304
+ }
305
+
306
+ :global(.slate-editor) {
307
+ font-size: @font-size-00;
308
+ }
309
+ }
310
+
311
+ .ais-search-input-field input {
312
+ font-size: @font-size-0;
313
+ }
314
+
315
+ .ais-prompt-chips .ais-chip {
316
+ min-width: @size-10;
317
+ flex: 1 1 auto;
318
+ }
319
+ }
320
+ }
@@ -13,6 +13,8 @@ function ChatBlockView(props) {
13
13
  );
14
14
 
15
15
  const isPlaywrightTest = query.playwright === 'yes';
16
+ const initialQuery = query.query || null;
17
+ const initialDeepResearch = query.deepResearch || null;
16
18
 
17
19
  useEffect(() => {
18
20
  if (isPlaywrightTest) {
@@ -29,6 +31,8 @@ function ChatBlockView(props) {
29
31
  isEditMode={isEditMode}
30
32
  isPlaywrightTest={isPlaywrightTest}
31
33
  block_id={id}
34
+ initialQuery={initialQuery}
35
+ initialDeepResearch={initialDeepResearch}
32
36
  {...data}
33
37
  />
34
38
  ) : (
@@ -5,6 +5,7 @@ import React, {
5
5
  useMemo,
6
6
  useCallback,
7
7
  } from 'react';
8
+ import { useHistory } from 'react-router-dom';
8
9
  import type { Persona } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
9
10
  import { Button, Form, Segment, Checkbox } from 'semantic-ui-react';
10
11
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable';
@@ -50,6 +51,8 @@ interface ChatWindowProps {
50
51
  maxContextSegments?: number;
51
52
  onyxVersion?: '2' | '3';
52
53
  isPlaywrightTest?: boolean;
54
+ initialQuery?: string | null;
55
+ initialDeepResearch?: string | null;
53
56
  [key: string]: any;
54
57
  }
55
58
 
@@ -61,6 +64,8 @@ function ChatWindow({
61
64
  placeholderPrompt = 'Ask a question',
62
65
  isEditMode,
63
66
  isPlaywrightTest,
67
+ initialQuery,
68
+ initialDeepResearch,
64
69
  ...data
65
70
  }: ChatWindowProps) {
66
71
  const {
@@ -124,6 +129,7 @@ function ChatWindow({
124
129
 
125
130
  const [showLandingPage, setShowLandingPage] = useState(true);
126
131
 
132
+ const history = useHistory();
127
133
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
128
134
  const chatWindowRef = useRef(null);
129
135
  const chatWindowEndRef = useRef(null);
@@ -132,6 +138,23 @@ function ChatWindow({
132
138
  setShowLandingPage(messages.length === 0);
133
139
  }, [messages]);
134
140
 
141
+ // Set deep research state from URL parameter
142
+ useEffect(() => {
143
+ if (initialDeepResearch !== null && initialDeepResearch !== undefined) {
144
+ const enabled = initialDeepResearch.toLowerCase() === 'true';
145
+ setIsDeepResearchEnabled(enabled);
146
+ }
147
+ }, []);
148
+
149
+ // Auto-submit query from URL parameter on mount
150
+ useEffect(() => {
151
+ if (initialQuery && messages.length === 0 && !isStreaming) {
152
+ onSubmit({ message: initialQuery });
153
+ setShowLandingPage(false);
154
+ history.replace(window.location.pathname);
155
+ }
156
+ }, []);
157
+
135
158
  const handleStarterPromptChoice = useCallback(
136
159
  (message: string) => {
137
160
  if (enableMatomoTracking) {
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/>
3
+ </svg>
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import installChatBlock from './ChatBlock';
2
+ import installAISearchInputBlock from './AISearchInput';
2
3
  import loadable from '@loadable/component';
3
4
 
4
5
  const applyConfig = (config) => {
@@ -40,6 +41,7 @@ const applyConfig = (config) => {
40
41
  };
41
42
 
42
43
  installChatBlock(config);
44
+ installAISearchInputBlock(config);
43
45
 
44
46
  return config;
45
47
  };