@truedat/ai 6.0.2 → 6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/ai",
3
- "version": "6.0.2",
3
+ "version": "6.0.3",
4
4
  "description": "Truedat Web Artificial Intelligence package",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -84,7 +84,7 @@
84
84
  ]
85
85
  },
86
86
  "dependencies": {
87
- "@truedat/core": "6.0.2",
87
+ "@truedat/core": "6.0.3",
88
88
  "prop-types": "^15.8.1",
89
89
  "react-hook-form": "^7.45.4",
90
90
  "react-intl": "^5.20.10",
@@ -97,5 +97,5 @@
97
97
  "react-dom": ">= 16.8.6 < 17",
98
98
  "semantic-ui-react": ">= 2.0.3 < 2.2"
99
99
  },
100
- "gitHead": "dab67cd6f569ca6de2fd8e0b5de260cb5ee261c3"
100
+ "gitHead": "c88ddc501214b8c4e2732654ca0052ebbcc802c9"
101
101
  }
package/src/api.js CHANGED
@@ -2,5 +2,17 @@ const API_RESOURCE_MAPPINGS = "/api/resource_mappings";
2
2
  const API_RESOURCE_MAPPING = "/api/resource_mappings/:id";
3
3
  const API_PROMPTS = "/api/prompts";
4
4
  const API_PROMPT = "/api/prompts/:id";
5
+ const API_PROMPT_SET_ACTIVE = "/api/prompts/:id/set_active";
6
+ const API_SUGGESTIONS_AVAILABILITY_CHECK =
7
+ "/api/suggestions/availability_check";
8
+ const API_SUGGESTIONS_REQUEST = "/api/suggestions/request";
5
9
 
6
- export { API_RESOURCE_MAPPINGS, API_RESOURCE_MAPPING, API_PROMPTS, API_PROMPT };
10
+ export {
11
+ API_RESOURCE_MAPPINGS,
12
+ API_RESOURCE_MAPPING,
13
+ API_PROMPTS,
14
+ API_PROMPT,
15
+ API_PROMPT_SET_ACTIVE,
16
+ API_SUGGESTIONS_AVAILABILITY_CHECK,
17
+ API_SUGGESTIONS_REQUEST,
18
+ };
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash/fp";
2
2
  import { useIntl } from "react-intl";
3
3
 
4
- export const resourceTypes = ["data_structure"];
4
+ export const resourceTypes = ["data_structure", "business_concept"];
5
5
 
6
6
  export const providers = ["openai"];
7
7
 
@@ -18,6 +18,7 @@ import { useResourceTypeOptions, useProvidersOptions } from "../constants";
18
18
  export default function PromptEditor({
19
19
  selectedPrompt,
20
20
  onSubmit,
21
+ onSetActive,
21
22
  onCancel,
22
23
  onDelete,
23
24
  isSubmitting,
@@ -303,6 +304,15 @@ export default function PromptEditor({
303
304
  disabled={!isValid || !isDirty}
304
305
  content={formatMessage({ id: "actions.save" })}
305
306
  />
307
+ {selectedPrompt.id ? (
308
+ <Button
309
+ onClick={onSetActive}
310
+ primary
311
+ loading={isSubmitting}
312
+ disabled={isDirty || selectedPrompt.active}
313
+ content={formatMessage({ id: "actions.set_active" })}
314
+ />
315
+ ) : null}
306
316
  {isDirty ? (
307
317
  <ConfirmModal
308
318
  trigger={
@@ -9,6 +9,7 @@ import {
9
9
  Icon,
10
10
  List,
11
11
  Segment,
12
+ Label,
12
13
  } from "semantic-ui-react";
13
14
 
14
15
  import {
@@ -16,6 +17,7 @@ import {
16
17
  usePromptCreate,
17
18
  usePromptDelete,
18
19
  usePromptUpdate,
20
+ usePromptSetActive,
19
21
  } from "@truedat/ai/hooks/usePrompts";
20
22
  import PromptEditor from "./PromptEditor";
21
23
 
@@ -48,16 +50,27 @@ export default function Prompts() {
48
50
  const { trigger: updatePrompt, isMutating: isUpdating } =
49
51
  usePromptUpdate(selectedPrompt);
50
52
 
53
+ const { trigger: setPromptActive, isMutating: isSettingActive } =
54
+ usePromptSetActive(selectedPrompt);
55
+
51
56
  const { trigger: deletePrompt, isMutating: isDeleting } =
52
57
  usePromptDelete(selectedPrompt);
53
58
 
54
- const isSubmitting = isCreating || isUpdating || isDeleting;
59
+ const isSubmitting =
60
+ isCreating || isUpdating || isDeleting || isSettingActive;
55
61
  const onPromptCreate = async (prompt) => {
56
62
  const mutatePrompt = selectedPrompt?.id ? updatePrompt : createPrompt;
57
63
  await mutatePrompt({ prompt });
58
64
  clearForm();
59
65
  mutate();
60
66
  };
67
+
68
+ const onSetActive = async () => {
69
+ await setPromptActive();
70
+ clearForm();
71
+ mutate();
72
+ };
73
+
61
74
  const onPromptDelete = async (prompt) => {
62
75
  await deletePrompt({ prompt });
63
76
  clearForm();
@@ -90,6 +103,11 @@ export default function Prompts() {
90
103
  >
91
104
  <List.Content>
92
105
  <List.Header>
106
+ <Label
107
+ circular
108
+ color={prompt.active ? "green" : "grey"}
109
+ empty
110
+ />{" "}
93
111
  {prompt.name ||
94
112
  formatMessage({
95
113
  id: "prompts.form.name.new",
@@ -117,6 +135,7 @@ export default function Prompts() {
117
135
  prompts={prompts}
118
136
  onCancel={clearForm}
119
137
  onSubmit={onPromptCreate}
138
+ onSetActive={onSetActive}
120
139
  onDelete={selectedPrompt?.id ? onPromptDelete : null}
121
140
  isSubmitting={isSubmitting}
122
141
  setDirty={setDirty}
@@ -47,6 +47,9 @@ const renderOpts = {
47
47
  "prompts.provider.openai": "prompts.provider.openai",
48
48
  "resourceMappings.resourceType.data_structure":
49
49
  "resourceMappings.resourceType.data_structure",
50
+ "actions.set_active": "actions.set_active",
51
+ "resourceMappings.resourceType.business_concept":
52
+ "resourceMappings.resourceType.business_concept",
50
53
  },
51
54
  },
52
55
  fallback: "lazy",
@@ -68,6 +68,19 @@ exports[`<PromptEditor /> matches snapshot without onDelete 1`] = `
68
68
  resourceMappings.resourceType.data_structure
69
69
  </span>
70
70
  </div>
71
+ <div
72
+ aria-checked="false"
73
+ aria-selected="false"
74
+ class="item"
75
+ role="option"
76
+ style="pointer-events: all;"
77
+ >
78
+ <span
79
+ class="text"
80
+ >
81
+ resourceMappings.resourceType.business_concept
82
+ </span>
83
+ </div>
71
84
  </div>
72
85
  </div>
73
86
  </div>
@@ -210,6 +223,11 @@ exports[`<PromptEditor /> matches snapshot without onDelete 1`] = `
210
223
  >
211
224
  actions.save
212
225
  </button>
226
+ <button
227
+ class="ui primary button"
228
+ >
229
+ actions.set_active
230
+ </button>
213
231
  <button
214
232
  class="ui button"
215
233
  >
@@ -290,6 +308,19 @@ exports[`<PromptEditor /> matches the latest snapshot 1`] = `
290
308
  resourceMappings.resourceType.data_structure
291
309
  </span>
292
310
  </div>
311
+ <div
312
+ aria-checked="false"
313
+ aria-selected="false"
314
+ class="item"
315
+ role="option"
316
+ style="pointer-events: all;"
317
+ >
318
+ <span
319
+ class="text"
320
+ >
321
+ resourceMappings.resourceType.business_concept
322
+ </span>
323
+ </div>
293
324
  </div>
294
325
  </div>
295
326
  </div>
@@ -432,6 +463,11 @@ exports[`<PromptEditor /> matches the latest snapshot 1`] = `
432
463
  >
433
464
  actions.save
434
465
  </button>
466
+ <button
467
+ class="ui primary button"
468
+ >
469
+ actions.set_active
470
+ </button>
435
471
  <button
436
472
  class="ui button"
437
473
  >
@@ -515,6 +551,19 @@ exports[`<PromptEditor /> test cancel button with confirm 1`] = `
515
551
  resourceMappings.resourceType.data_structure
516
552
  </span>
517
553
  </div>
554
+ <div
555
+ aria-checked="false"
556
+ aria-selected="false"
557
+ class="item"
558
+ role="option"
559
+ style="pointer-events: all;"
560
+ >
561
+ <span
562
+ class="text"
563
+ >
564
+ resourceMappings.resourceType.business_concept
565
+ </span>
566
+ </div>
518
567
  </div>
519
568
  </div>
520
569
  </div>
@@ -655,6 +704,13 @@ exports[`<PromptEditor /> test cancel button with confirm 1`] = `
655
704
  >
656
705
  actions.save
657
706
  </button>
707
+ <button
708
+ class="ui primary disabled button"
709
+ disabled=""
710
+ tabindex="-1"
711
+ >
712
+ actions.set_active
713
+ </button>
658
714
  <button
659
715
  class="ui button"
660
716
  >
@@ -738,6 +794,19 @@ exports[`<PromptEditor /> test delete button 1`] = `
738
794
  resourceMappings.resourceType.data_structure
739
795
  </span>
740
796
  </div>
797
+ <div
798
+ aria-checked="false"
799
+ aria-selected="false"
800
+ class="item"
801
+ role="option"
802
+ style="pointer-events: all;"
803
+ >
804
+ <span
805
+ class="text"
806
+ >
807
+ resourceMappings.resourceType.business_concept
808
+ </span>
809
+ </div>
741
810
  </div>
742
811
  </div>
743
812
  </div>
@@ -880,6 +949,11 @@ exports[`<PromptEditor /> test delete button 1`] = `
880
949
  >
881
950
  actions.save
882
951
  </button>
952
+ <button
953
+ class="ui primary button"
954
+ >
955
+ actions.set_active
956
+ </button>
883
957
  <button
884
958
  class="ui button"
885
959
  >
@@ -48,6 +48,10 @@ exports[`<Prompts /> matches the latest snapshot 1`] = `
48
48
  <div
49
49
  class="header"
50
50
  >
51
+ <div
52
+ class="ui grey circular empty label"
53
+ />
54
+
51
55
  rm1
52
56
  </div>
53
57
  </div>
@@ -36,6 +36,7 @@ const renderOpts = {
36
36
  "actions.discard.confirmation.header",
37
37
  "confirmation.yes": "confirmation.yes",
38
38
  "confirmation.no": "confirmation.no",
39
+ "resourceMappings.resourceType.business_concept": "business concept",
39
40
  },
40
41
  },
41
42
  fallback: "lazy",
@@ -66,6 +66,7 @@ describe("<ResourceMappings />", () => {
66
66
  "actions.discard.confirmation.header": "confirmation_header",
67
67
  "actions.discard.confirmation.content": "confirmation_content",
68
68
  "resourceMappings.action.new": "resourceMappings.action.new",
69
+ "resourceMappings.resourceType.business_concept": "business concept",
69
70
  },
70
71
  },
71
72
  fallback: "lazy",
@@ -60,6 +60,19 @@ exports[`<ResourceMappingEditor /> matches snapshot without onDelete 1`] = `
60
60
  resourceMappings.resourceType.data_structure
61
61
  </span>
62
62
  </div>
63
+ <div
64
+ aria-checked="false"
65
+ aria-selected="false"
66
+ class="item"
67
+ role="option"
68
+ style="pointer-events: all;"
69
+ >
70
+ <span
71
+ class="text"
72
+ >
73
+ business concept
74
+ </span>
75
+ </div>
63
76
  </div>
64
77
  </div>
65
78
  </div>
@@ -245,6 +258,19 @@ exports[`<ResourceMappingEditor /> matches the latest snapshot 1`] = `
245
258
  resourceMappings.resourceType.data_structure
246
259
  </span>
247
260
  </div>
261
+ <div
262
+ aria-checked="false"
263
+ aria-selected="false"
264
+ class="item"
265
+ role="option"
266
+ style="pointer-events: all;"
267
+ >
268
+ <span
269
+ class="text"
270
+ >
271
+ business concept
272
+ </span>
273
+ </div>
248
274
  </div>
249
275
  </div>
250
276
  </div>
@@ -433,6 +459,19 @@ exports[`<ResourceMappingEditor /> test cancel button with confirm 1`] = `
433
459
  resourceMappings.resourceType.data_structure
434
460
  </span>
435
461
  </div>
462
+ <div
463
+ aria-checked="false"
464
+ aria-selected="false"
465
+ class="item"
466
+ role="option"
467
+ style="pointer-events: all;"
468
+ >
469
+ <span
470
+ class="text"
471
+ >
472
+ business concept
473
+ </span>
474
+ </div>
436
475
  </div>
437
476
  </div>
438
477
  </div>
@@ -619,6 +658,19 @@ exports[`<ResourceMappingEditor /> test delete button 1`] = `
619
658
  resourceMappings.resourceType.data_structure
620
659
  </span>
621
660
  </div>
661
+ <div
662
+ aria-checked="false"
663
+ aria-selected="false"
664
+ class="item"
665
+ role="option"
666
+ style="pointer-events: all;"
667
+ >
668
+ <span
669
+ class="text"
670
+ >
671
+ business concept
672
+ </span>
673
+ </div>
622
674
  </div>
623
675
  </div>
624
676
  </div>
@@ -0,0 +1,192 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ Container,
8
+ Divider,
9
+ Icon,
10
+ List,
11
+ Message,
12
+ Segment,
13
+ } from "semantic-ui-react";
14
+ import { FormattedMessage } from "react-intl";
15
+ import { toEnrichedTextFormat } from "@truedat/core/services/format";
16
+
17
+ export default function SuggestionsWidget({
18
+ requestAiSuggestion,
19
+ applySuggestions,
20
+ template,
21
+ isModification,
22
+ }) {
23
+ const [loadingSuggestion, setLoadingSuggestion] = useState();
24
+ const [suggestions, setSuggestions] = useState();
25
+ const [suggestionsError, setSuggestionsError] = useState();
26
+ const [selectedSuggestions, setSelectedSuggestions] = useState([]);
27
+
28
+ const suggestionKeys = _.keys(suggestions);
29
+ const fieldInSuggestions = ({ name }) => _.includes(name)(suggestionKeys);
30
+ const templateFields = _.flow(
31
+ _.prop("content"),
32
+ _.filter(({ fields }) => _.any(fieldInSuggestions)(fields)),
33
+ _.map(({ name, fields }) => ({
34
+ name,
35
+ fields: _.filter(fieldInSuggestions)(fields),
36
+ }))
37
+ )(template);
38
+ const fieldTypes = _.flow(
39
+ _.prop("content"),
40
+ _.flatMap(({ fields }) =>
41
+ _.flow(
42
+ _.filter(({ editable = true }) => editable || !isModification),
43
+ _.map(({ name, type }) => [name, type])
44
+ )(fields)
45
+ ),
46
+ _.fromPairs
47
+ )(template);
48
+
49
+ const validFields = _.keys(fieldTypes);
50
+
51
+ const handleRequestSuggestions = () => {
52
+ setLoadingSuggestion(true);
53
+ setSuggestionsError(null);
54
+ setSelectedSuggestions([]);
55
+ setSuggestions(null);
56
+
57
+ requestAiSuggestion(({ data: { data: suggestions } }) => {
58
+ if (_.prop("[0]")(suggestions) === "error") {
59
+ setSuggestionsError(
60
+ _.prop("[1].error.message")(suggestions) || _.prop("[1]")(suggestions)
61
+ );
62
+ } else {
63
+ setSuggestions(suggestions);
64
+ const selectedFields = _.flow(
65
+ _.keys,
66
+ _.filter(
67
+ (key) =>
68
+ _.includes(key)(validFields) && !_.isEmpty(suggestions[key])
69
+ )
70
+ )(suggestions);
71
+ setSelectedSuggestions(selectedFields);
72
+ }
73
+ setLoadingSuggestion(false);
74
+ });
75
+ };
76
+ const handleApplySuggestions = () => {
77
+ const parsedSuggestions = _.flow(
78
+ _.pick(selectedSuggestions),
79
+ _.toPairs,
80
+ _.map(([key, value]) => [
81
+ key,
82
+ fieldTypes[key] === "enriched_text"
83
+ ? toEnrichedTextFormat(value)
84
+ : value,
85
+ ]),
86
+ _.fromPairs
87
+ )(suggestions);
88
+
89
+ applySuggestions(parsedSuggestions);
90
+ setSelectedSuggestions([]);
91
+ setSuggestions(null);
92
+ };
93
+
94
+ const toggleSelectedSuggestion = (name) => {
95
+ setSelectedSuggestions(
96
+ _.includes(name)(selectedSuggestions)
97
+ ? _.reject((v) => v == name)(selectedSuggestions)
98
+ : [...selectedSuggestions, name]
99
+ );
100
+ };
101
+
102
+ return (
103
+ <Segment loading={loadingSuggestion}>
104
+ <Container style={{ display: "flex", justifyContent: "space-between" }}>
105
+ <h4>
106
+ {suggestions ? (
107
+ <Icon name="lightbulb outline" />
108
+ ) : (
109
+ <Icon name="lightbulb" />
110
+ )}
111
+ <FormattedMessage id="structure_note.ai_suggestion.header" />
112
+ </h4>
113
+ <Divider hidden />
114
+ {suggestions ? null : (
115
+ <Button onClick={handleRequestSuggestions}>
116
+ <FormattedMessage id="actions.ai_suggestion" />
117
+ </Button>
118
+ )}
119
+ </Container>
120
+ {suggestionsError ? (
121
+ <Message negative>
122
+ <Message.Header>
123
+ <FormattedMessage id="structure_note.ai_suggestion.error" />
124
+ </Message.Header>
125
+ <p>{suggestionsError}</p>
126
+ </Message>
127
+ ) : null}
128
+ {suggestions ? (
129
+ <>
130
+ {_.map(({ name: groupName, fields }) => (
131
+ <Container key={`group-${groupName}`}>
132
+ <>
133
+ <h5>{groupName ? groupName : ""}</h5>
134
+ <List>
135
+ {_.map(({ name, label, editable = true }) => (
136
+ <List.Item
137
+ key={name}
138
+ style={{
139
+ display: "flex",
140
+ alignItems: "center",
141
+ gap: "12px",
142
+ }}
143
+ >
144
+ <Checkbox
145
+ id={name}
146
+ disabled={
147
+ (isModification && !editable) ||
148
+ _.isEmpty(suggestions[name])
149
+ }
150
+ checked={_.includes(name)(selectedSuggestions)}
151
+ onChange={() => toggleSelectedSuggestion(name)}
152
+ />
153
+ <label
154
+ htmlFor={name}
155
+ style={{ cursor: "pointer" }}
156
+ disabled
157
+ >
158
+ <List.Header>{label}</List.Header>
159
+ <List.Description>
160
+ {_.isEmpty(suggestions[name])
161
+ ? "-"
162
+ : suggestions[name]}
163
+ </List.Description>
164
+ </label>
165
+ </List.Item>
166
+ ))(fields)}
167
+ </List>
168
+ <Divider hidden />
169
+ </>
170
+ </Container>
171
+ ))(templateFields)}
172
+ <Container textAlign="right">
173
+ <Button
174
+ primary
175
+ onClick={handleApplySuggestions}
176
+ disabled={_.isEmpty(selectedSuggestions)}
177
+ >
178
+ <FormattedMessage id="actions.apply_ai_suggestion" />
179
+ </Button>
180
+ </Container>
181
+ </>
182
+ ) : null}
183
+ </Segment>
184
+ );
185
+ }
186
+
187
+ SuggestionsWidget.propTypes = {
188
+ aiSuggestionsUrl: PropTypes.object,
189
+ applySuggestions: PropTypes.func,
190
+ template: PropTypes.object,
191
+ isModification: PropTypes.bool,
192
+ };
@@ -0,0 +1,151 @@
1
+ import React, { Suspense } from "react";
2
+ import { waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { render } from "@truedat/test/render";
5
+ import SuggestionsWidget from "../SuggestionsWidget";
6
+
7
+ const mockSuggestions = {
8
+ editable_field: "editable_field_value",
9
+ non_editable_field: "non_editable_field_value",
10
+ unselect_field: "unselect_field_value",
11
+ };
12
+
13
+ const messages = {
14
+ en: {
15
+ "actions.cancel": "cancel",
16
+ "actions.save": "save",
17
+ "structure_note.ai_suggestion.header": "header",
18
+ "actions.apply_ai_suggestion": "actions.apply_ai_suggestion",
19
+ "actions.ai_suggestion": "actions.ai_suggestion",
20
+ "structure_note.ai_suggestion.error": "error",
21
+ },
22
+ };
23
+
24
+ const requestAiSuggestion = (mock) => (callback) => {
25
+ callback({
26
+ data: {
27
+ data:
28
+ mock === "suggestions"
29
+ ? mockSuggestions
30
+ : mock === "error_message"
31
+ ? ["error", { error: { message: "ERROR MESSAGE" } }]
32
+ : ["error", "ERROR TEXT"],
33
+ },
34
+ });
35
+ };
36
+
37
+ describe("<SuggestionsWidget />", () => {
38
+ it("matches the latest snapshot", () => {
39
+ const props = {
40
+ template: {},
41
+ requestAiSuggestion: requestAiSuggestion("suggestions"),
42
+ };
43
+ const { container } = render(
44
+ <Suspense fallback={null}>
45
+ <SuggestionsWidget {...props} />
46
+ </Suspense>,
47
+ { messages }
48
+ );
49
+ expect(container).toMatchSnapshot();
50
+ });
51
+
52
+ it("component lifecycle", async () => {
53
+ const applySuggestions = jest.fn();
54
+ const props = {
55
+ requestAiSuggestion: requestAiSuggestion("suggestions"),
56
+ applySuggestions,
57
+ template: {
58
+ content: [
59
+ {
60
+ fields: [
61
+ {
62
+ name: "editable_field",
63
+ label: "EditableField",
64
+ editable: true,
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ name: "Group2",
70
+ fields: [
71
+ {
72
+ name: "non_editable_field",
73
+ label: "NonEditableField",
74
+ editable: false,
75
+ },
76
+ {
77
+ name: "unselect_field",
78
+ label: "UnselectedField",
79
+ editable: true,
80
+ },
81
+ ],
82
+ },
83
+ ],
84
+ },
85
+ isModification: true,
86
+ };
87
+ const { container, findByText, queryByText } = render(
88
+ <Suspense fallback={null}>
89
+ <SuggestionsWidget {...props} />
90
+ </Suspense>,
91
+ { messages }
92
+ );
93
+
94
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
95
+ await waitFor(() =>
96
+ expect(queryByText(/actions.ai_suggestion/i)).not.toBeInTheDocument()
97
+ );
98
+
99
+ expect(container).toMatchSnapshot();
100
+
101
+ userEvent.click(await findByText(/UnselectedField/i));
102
+ userEvent.click(await findByText(/UnselectedField/i));
103
+ userEvent.click(await findByText(/UnselectedField/i));
104
+
105
+ userEvent.click(await findByText(/actions.apply_ai_suggestion/i));
106
+ await waitFor(() =>
107
+ expect(
108
+ queryByText(/actions.apply_ai_suggestion/i)
109
+ ).not.toBeInTheDocument()
110
+ );
111
+ expect(applySuggestions).toHaveBeenCalledWith(
112
+ expect.objectContaining({ editable_field: "editable_field_value" })
113
+ );
114
+
115
+ expect(container).toMatchSnapshot();
116
+ });
117
+
118
+ it("renders error with message", async () => {
119
+ const props = {
120
+ requestAiSuggestion: requestAiSuggestion("error_message"),
121
+ };
122
+ const { container, findByText, queryByText } = render(
123
+ <Suspense fallback={null}>
124
+ <SuggestionsWidget {...props} />
125
+ </Suspense>,
126
+ { messages }
127
+ );
128
+
129
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
130
+ await waitFor(() =>
131
+ expect(queryByText(/error message/i)).toBeInTheDocument()
132
+ );
133
+ expect(container).toMatchSnapshot();
134
+ });
135
+
136
+ it("renders error with text", async () => {
137
+ const props = {
138
+ requestAiSuggestion: requestAiSuggestion("error_text"),
139
+ };
140
+ const { container, findByText, queryByText } = render(
141
+ <Suspense fallback={null}>
142
+ <SuggestionsWidget {...props} />
143
+ </Suspense>,
144
+ { messages }
145
+ );
146
+
147
+ userEvent.click(await findByText(/actions.ai_suggestion/i));
148
+ await waitFor(() => expect(queryByText(/error text/i)).toBeInTheDocument());
149
+ expect(container).toMatchSnapshot();
150
+ });
151
+ });
@@ -0,0 +1,316 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<SuggestionsWidget /> component lifecycle 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui segment"
7
+ >
8
+ <div
9
+ class="ui container"
10
+ style="display: flex; justify-content: space-between;"
11
+ >
12
+ <h4>
13
+ <i
14
+ aria-hidden="true"
15
+ class="lightbulb outline icon"
16
+ />
17
+ header
18
+ </h4>
19
+ <div
20
+ class="ui hidden divider"
21
+ />
22
+ </div>
23
+ <div
24
+ class="ui container"
25
+ >
26
+ <h5 />
27
+ <div
28
+ class="ui list"
29
+ role="list"
30
+ >
31
+ <div
32
+ class="item"
33
+ role="listitem"
34
+ style="display: flex; align-items: center; gap: 12px;"
35
+ >
36
+ <div
37
+ class="ui checked fitted checkbox"
38
+ >
39
+ <input
40
+ checked=""
41
+ class="hidden"
42
+ id="editable_field"
43
+ readonly=""
44
+ tabindex="0"
45
+ type="checkbox"
46
+ value=""
47
+ />
48
+ <label
49
+ for="editable_field"
50
+ />
51
+ </div>
52
+ <label
53
+ disabled=""
54
+ for="editable_field"
55
+ style="cursor: pointer;"
56
+ >
57
+ <div
58
+ class="header"
59
+ >
60
+ EditableField
61
+ </div>
62
+ <div
63
+ class="description"
64
+ >
65
+ editable_field_value
66
+ </div>
67
+ </label>
68
+ </div>
69
+ </div>
70
+ <div
71
+ class="ui hidden divider"
72
+ />
73
+ </div>
74
+ <div
75
+ class="ui container"
76
+ >
77
+ <h5>
78
+ Group2
79
+ </h5>
80
+ <div
81
+ class="ui list"
82
+ role="list"
83
+ >
84
+ <div
85
+ class="item"
86
+ role="listitem"
87
+ style="display: flex; align-items: center; gap: 12px;"
88
+ >
89
+ <div
90
+ class="ui disabled fitted checkbox"
91
+ >
92
+ <input
93
+ class="hidden"
94
+ disabled=""
95
+ id="non_editable_field"
96
+ readonly=""
97
+ tabindex="-1"
98
+ type="checkbox"
99
+ value=""
100
+ />
101
+ <label
102
+ for="non_editable_field"
103
+ />
104
+ </div>
105
+ <label
106
+ disabled=""
107
+ for="non_editable_field"
108
+ style="cursor: pointer;"
109
+ >
110
+ <div
111
+ class="header"
112
+ >
113
+ NonEditableField
114
+ </div>
115
+ <div
116
+ class="description"
117
+ >
118
+ non_editable_field_value
119
+ </div>
120
+ </label>
121
+ </div>
122
+ <div
123
+ class="item"
124
+ role="listitem"
125
+ style="display: flex; align-items: center; gap: 12px;"
126
+ >
127
+ <div
128
+ class="ui checked fitted checkbox"
129
+ >
130
+ <input
131
+ checked=""
132
+ class="hidden"
133
+ id="unselect_field"
134
+ readonly=""
135
+ tabindex="0"
136
+ type="checkbox"
137
+ value=""
138
+ />
139
+ <label
140
+ for="unselect_field"
141
+ />
142
+ </div>
143
+ <label
144
+ disabled=""
145
+ for="unselect_field"
146
+ style="cursor: pointer;"
147
+ >
148
+ <div
149
+ class="header"
150
+ >
151
+ UnselectedField
152
+ </div>
153
+ <div
154
+ class="description"
155
+ >
156
+ unselect_field_value
157
+ </div>
158
+ </label>
159
+ </div>
160
+ </div>
161
+ <div
162
+ class="ui hidden divider"
163
+ />
164
+ </div>
165
+ <div
166
+ class="ui right aligned container"
167
+ >
168
+ <button
169
+ class="ui primary button"
170
+ >
171
+ actions.apply_ai_suggestion
172
+ </button>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ `;
177
+
178
+ exports[`<SuggestionsWidget /> component lifecycle 2`] = `
179
+ <div>
180
+ <div
181
+ class="ui segment"
182
+ >
183
+ <div
184
+ class="ui container"
185
+ style="display: flex; justify-content: space-between;"
186
+ >
187
+ <h4>
188
+ <i
189
+ aria-hidden="true"
190
+ class="lightbulb icon"
191
+ />
192
+ header
193
+ </h4>
194
+ <div
195
+ class="ui hidden divider"
196
+ />
197
+ <button
198
+ class="ui button"
199
+ >
200
+ actions.ai_suggestion
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ `;
206
+
207
+ exports[`<SuggestionsWidget /> matches the latest snapshot 1`] = `
208
+ <div>
209
+ <div
210
+ class="ui segment"
211
+ >
212
+ <div
213
+ class="ui container"
214
+ style="display: flex; justify-content: space-between;"
215
+ >
216
+ <h4>
217
+ <i
218
+ aria-hidden="true"
219
+ class="lightbulb icon"
220
+ />
221
+ header
222
+ </h4>
223
+ <div
224
+ class="ui hidden divider"
225
+ />
226
+ <button
227
+ class="ui button"
228
+ >
229
+ actions.ai_suggestion
230
+ </button>
231
+ </div>
232
+ </div>
233
+ </div>
234
+ `;
235
+
236
+ exports[`<SuggestionsWidget /> renders error with message 1`] = `
237
+ <div>
238
+ <div
239
+ class="ui segment"
240
+ >
241
+ <div
242
+ class="ui container"
243
+ style="display: flex; justify-content: space-between;"
244
+ >
245
+ <h4>
246
+ <i
247
+ aria-hidden="true"
248
+ class="lightbulb icon"
249
+ />
250
+ header
251
+ </h4>
252
+ <div
253
+ class="ui hidden divider"
254
+ />
255
+ <button
256
+ class="ui button"
257
+ >
258
+ actions.ai_suggestion
259
+ </button>
260
+ </div>
261
+ <div
262
+ class="ui negative message"
263
+ >
264
+ <div
265
+ class="header"
266
+ >
267
+ error
268
+ </div>
269
+ <p>
270
+ ERROR MESSAGE
271
+ </p>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ `;
276
+
277
+ exports[`<SuggestionsWidget /> renders error with text 1`] = `
278
+ <div>
279
+ <div
280
+ class="ui segment"
281
+ >
282
+ <div
283
+ class="ui container"
284
+ style="display: flex; justify-content: space-between;"
285
+ >
286
+ <h4>
287
+ <i
288
+ aria-hidden="true"
289
+ class="lightbulb icon"
290
+ />
291
+ header
292
+ </h4>
293
+ <div
294
+ class="ui hidden divider"
295
+ />
296
+ <button
297
+ class="ui button"
298
+ >
299
+ actions.ai_suggestion
300
+ </button>
301
+ </div>
302
+ <div
303
+ class="ui negative message"
304
+ >
305
+ <div
306
+ class="header"
307
+ >
308
+ error
309
+ </div>
310
+ <p>
311
+ ERROR TEXT
312
+ </p>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ `;
@@ -7,7 +7,7 @@ import {
7
7
  apiJsonPatch,
8
8
  apiJsonDelete,
9
9
  } from "@truedat/core/services/api";
10
- import { API_PROMPTS, API_PROMPT } from "../api";
10
+ import { API_PROMPTS, API_PROMPT, API_PROMPT_SET_ACTIVE } from "../api";
11
11
 
12
12
  export const usePrompts = () => {
13
13
  const { data, error, mutate } = useSWR(API_PROMPTS, apiJson);
@@ -24,6 +24,12 @@ export const usePromptUpdate = (func) => {
24
24
  return useSWRMutations(url, (url, { arg }) => apiJsonPatch(url, arg));
25
25
  };
26
26
 
27
+ export const usePromptSetActive = (func) => {
28
+ const id = func?.id || 0;
29
+ const url = compile(API_PROMPT_SET_ACTIVE)({ id });
30
+ return useSWRMutations(url, (url) => apiJsonPatch(url));
31
+ };
32
+
27
33
  export const usePromptDelete = (func) => {
28
34
  const id = func?.id || 0;
29
35
  const url = compile(API_PROMPT)({ id });
@@ -0,0 +1,9 @@
1
+ import useSWRMutations from "swr/mutation";
2
+ import { apiJsonPost } from "@truedat/core/services/api";
3
+ import { API_SUGGESTIONS_AVAILABILITY_CHECK } from "../api";
4
+
5
+ export const useAvailabilityCheck = () => {
6
+ return useSWRMutations(API_SUGGESTIONS_AVAILABILITY_CHECK, (url, { arg }) =>
7
+ apiJsonPost(url, arg)
8
+ );
9
+ };
package/src/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import * as components from "./components";
2
+ import * as api from "./api";
2
3
 
3
- export { components };
4
+ export { components, api };