contentoh-components-library 21.5.78 → 21.5.81

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 (34) hide show
  1. package/dist/ai/utils/compare-strings.js +43 -0
  2. package/dist/components/atoms/GeneralInput/index.js +64 -36
  3. package/dist/components/atoms/GeneralInput/styles.js +1 -1
  4. package/dist/components/atoms/InputFormatter/index.js +45 -21
  5. package/dist/components/atoms/InputFormatter/styles.js +1 -1
  6. package/dist/components/molecules/StatusAsignationInfo/index.js +11 -1
  7. package/dist/components/molecules/TagAndInput/index.js +110 -29
  8. package/dist/components/molecules/TagAndInput/styles.js +2 -2
  9. package/dist/components/organisms/InputGroup/index.js +22 -18
  10. package/dist/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +92 -785
  11. package/dist/components/pages/RetailerProductEdition/context/provider-product-edition.context.js +59 -260
  12. package/dist/components/pages/RetailerProductEdition/context/reducers/product.js +3 -41
  13. package/dist/components/pages/RetailerProductEdition/index.js +191 -123
  14. package/dist/contexts/AiProductEdition.js +179 -155
  15. package/package.json +1 -1
  16. package/src/ai/utils/compare-strings.js +43 -0
  17. package/src/components/atoms/GeneralInput/index.js +63 -42
  18. package/src/components/atoms/GeneralInput/styles.js +7 -0
  19. package/src/components/atoms/InputFormatter/index.js +51 -23
  20. package/src/components/atoms/InputFormatter/styles.js +62 -10
  21. package/src/components/molecules/StatusAsignationInfo/index.js +9 -1
  22. package/src/components/molecules/TagAndInput/index.js +113 -28
  23. package/src/components/molecules/TagAndInput/styles.js +48 -17
  24. package/src/components/organisms/FullTabsMenu/index.js +1 -1
  25. package/src/components/organisms/InputGroup/index.js +4 -0
  26. package/src/components/pages/RetailerProductEdition/RetailerProductEdition.stories.js +111 -832
  27. package/src/components/pages/RetailerProductEdition/context/provider-product-edition.context.jsx +3 -221
  28. package/src/components/pages/RetailerProductEdition/context/reducers/product.js +3 -43
  29. package/src/components/pages/RetailerProductEdition/index.js +17 -15
  30. package/src/contexts/AiProductEdition.jsx +138 -96
  31. package/src/ai/prompts/attribute.prompt.js +0 -46
  32. package/src/ai/prompts/description.prompt.js +0 -52
  33. package/src/ai/schemas/attribute.schema.js +0 -23
  34. package/src/ai/schemas/description.schema.js +0 -19
@@ -11,6 +11,7 @@ import ReloadIcon from "../../../assets/images/Icons/reload.png";
11
11
 
12
12
  import { useAiProductEdition } from "../../../contexts/AiProductEdition";
13
13
  import { InputContainer, BottomContainer, OptionsContainer, ButtonsContainer } from "../InputFormatter/styles";
14
+ import { getTextSimilarityPercentage } from "../../../ai/utils/compare-strings";
14
15
 
15
16
  export const GeneralInput = ({
16
17
  inputType,
@@ -43,11 +44,14 @@ export const GeneralInput = ({
43
44
  isAiGenerationLoading = false,
44
45
  isAiRegenerationLoading=false,
45
46
  isAiActive = false,
46
- // aiSuggestions = null,
47
+ isAiAvailable = false,
48
+ aiGenerated = false,
49
+
47
50
  setIsAiActive = () => {},
48
51
  handlerAiGeneration = () => {},
49
52
  handlerRegenerateSuggestions = () => {},
50
53
  handleChangeSuggestion = () => {}
54
+
51
55
  }) => {
52
56
 
53
57
  const {
@@ -64,40 +68,34 @@ export const GeneralInput = ({
64
68
  const [aiSuggestionAccepted, setAiSuggestionAccepted] = useState(false);
65
69
  const [valueAccepted, setValueAccepted] = useState(textValue);
66
70
 
71
+ const updateParentData = (generalValue, isAiAccepted) => {
72
+ setTextValue({ value: generalValue });
67
73
 
68
- const onHandleChange = (evt) => {
69
- if (validateInput) {
70
- setTextValue({ value: validateInput(evt, position, inputsArray) });
71
- } else if (
72
- updatedDatasheets ||
73
- updatedDescriptions ||
74
- inputType === "textarea"
75
- ) {
76
- let generalValue;
77
- if (optionList?.length > 0) {
78
- let valueSelected = evt.target.value;
79
- generalValue = valueSelected;
80
- setTextValue({ value: generalValue });
81
- } else {
82
- generalValue =
83
- inputType === "checkbox" ? evt.target.checked : evt.target.value;
84
- setTextValue({
85
- value: generalValue,
86
- });
87
- }
88
- let dataSave = updatedDatasheets?.slice();
74
+ const similarity = getTextSimilarityPercentage(valueAccepted, inputValue);
75
+
76
+ const generatedWithAi = (isAiAccepted) || aiGenerated && similarity >= 50;
77
+
78
+ if (updatedDatasheets || updatedDescriptions || inputType === "textarea") {
79
+ let dataSave = updatedDatasheets?.slice() || [];
89
80
 
90
81
  if (dataSave?.length > 0) {
91
82
  const index = dataSave.findIndex((e) => e.attributeId === inputId);
92
83
  if (index !== -1) {
93
- if (generalValue !== inputValue) dataSave[index].value = generalValue;
94
- else dataSave.splice(index, 1);
84
+ if (generalValue !== inputValue) {
85
+ dataSave[index].value = generalValue;
86
+ dataSave[index].aiSuggestionAccepted = generatedWithAi;
87
+ } else {
88
+ dataSave.splice(index, 1);
89
+ }
95
90
  } else {
96
- dataSave.push({
97
- articleId: articleId,
98
- attributeId: inputId,
99
- value: generalValue,
100
- });
91
+ if (generalValue !== inputValue) {
92
+ dataSave.push({
93
+ articleId: articleId,
94
+ attributeId: inputId,
95
+ value: generalValue,
96
+ aiSuggestionAccepted: generatedWithAi,
97
+ });
98
+ }
101
99
  }
102
100
  } else {
103
101
  if (generalValue !== inputValue) {
@@ -105,10 +103,32 @@ export const GeneralInput = ({
105
103
  articleId: articleId,
106
104
  attributeId: inputId,
107
105
  value: generalValue,
106
+ aiSuggestionAccepted: generatedWithAi,
108
107
  });
109
108
  }
110
109
  }
111
110
  setUpdatedDatasheets(dataSave);
111
+ }
112
+ };
113
+
114
+ const onHandleChange = (evt) => {
115
+ if (validateInput) {
116
+ setTextValue({ value: validateInput(evt, position, inputsArray) });
117
+ } else if (
118
+ updatedDatasheets ||
119
+ updatedDescriptions ||
120
+ inputType === "textarea"
121
+ ) {
122
+ let generalValue;
123
+ if (optionList?.length > 0) {
124
+ generalValue = evt.target.value;
125
+ } else {
126
+ generalValue =
127
+ inputType === "checkbox" ? evt.target.checked : evt.target.value;
128
+ }
129
+
130
+ updateParentData(generalValue, aiSuggestionAccepted);
131
+
112
132
  } else {
113
133
  setTextValue({ value: evt.target.value });
114
134
  inputOnChange && inputOnChange(evt);
@@ -136,12 +156,11 @@ export const GeneralInput = ({
136
156
 
137
157
  }, [suggestions]);
138
158
 
139
- useEffect(() => {
140
-
141
- // Comprueba cuando la función de IA se cierra y no se acepto una sugerencia
142
- // Devuelve el valor inicial
143
- if(!isAiActive && !aiSuggestionAccepted) return setTextValue(valueAccepted);
144
-
159
+ useEffect(() => {
160
+ if(!isAiActive && !aiSuggestionAccepted) {
161
+ const restoredValue = typeof valueAccepted === 'object' ? valueAccepted.value : valueAccepted;
162
+ setTextValue({ value: restoredValue });
163
+ }
145
164
  }, [suggestions, isAiActive]);
146
165
 
147
166
  useEffect(() => {
@@ -163,13 +182,13 @@ export const GeneralInput = ({
163
182
  //AI Generation
164
183
 
165
184
  const handleAcceptSuggestion = (suggestionValue) => {
166
-
167
185
  if(!suggestionValue) return;
168
186
 
169
- setTextValue({ value: suggestionValue })
170
- setValueAccepted(suggestionValue);
187
+ setValueAccepted({ value: suggestionValue });
171
188
  setAiSuggestionAccepted(true);
172
189
  setIsAiActive(false);
190
+
191
+ updateParentData(suggestionValue, true);
173
192
  }
174
193
 
175
194
  return (
@@ -218,10 +237,10 @@ export const GeneralInput = ({
218
237
  {
219
238
 
220
239
  hasAiGeneration && isBenefitInput && (
221
- <div className="icon_container" onClick={() => {
240
+ <div className={`icon_container ${isAiAvailable ? "ai-available" : ''} ${isAiActive ? 'ai-active' : ''}`} title={!isAiAvailable ? 'Debes de completar ficha técnica e imágenes para desbloquear la generación con IA' : ''} onClick={() => {
222
241
  handlerAiGeneration({ type: "attribute" })
223
242
  }}>
224
- <img className={isAiGenerationLoading ? "loading" : ""} src={isAiGenerationLoading ? LoadingIcon : isAiActive ? CancelIcon : AiGenerationIcon} />
243
+ <img className={`${isAiGenerationLoading ? 'loading' : ''}`} src={isAiGenerationLoading ? LoadingIcon : isAiActive ? CancelIcon : AiGenerationIcon} />
225
244
  </div>
226
245
  )
227
246
 
@@ -290,7 +309,9 @@ export const GeneralInput = ({
290
309
  maxChar={maxChar}
291
310
  isRequired={isRequired}
292
311
  disabled={disabled}
293
- hasAiGeneration={hasAiGeneration}
312
+ hasAiGeneration={hasAiGeneration && inputId != 'commentary-box'}
313
+ isAiAvailable={isAiAvailable}
314
+ aiGenerated={aiGenerated}
294
315
  handlerAiGeneration={() => {
295
316
  handlerAiGeneration({ type: "description" })
296
317
  }}
@@ -301,7 +322,7 @@ export const GeneralInput = ({
301
322
  isAiGenerationLoading={isAiGenerationLoading}
302
323
  isAiRegenerationLoading={isAiRegenerationLoading}
303
324
  isAiActive={isAiActive}
304
- setIsAiActive={setIsAiActive}
325
+ setIsAiActive={setIsAiActive}
305
326
  />
306
327
  )}
307
328
  {/* <p>{description}</p> */}
@@ -132,6 +132,13 @@ export const InputContainer = styled.div`
132
132
  z-index: -1;
133
133
  }
134
134
 
135
+ &.ai-available::before{
136
+
137
+ background: gray;
138
+ background-size: 200% 200%;
139
+
140
+ }
141
+
135
142
  img {
136
143
  width: 100%;
137
144
  height: 100%;
@@ -9,9 +9,10 @@ import LoadingIcon from "../../../assets/images/Icons/loading.svg";
9
9
  import CancelIcon from "../../../assets/images/Icons/cancel.png";
10
10
  import ReloadIcon from "../../../assets/images/Icons/reload.png";
11
11
 
12
-
13
12
  import { useAiProductEdition } from "../../../contexts/AiProductEdition";
14
13
 
14
+ import { getTextSimilarityPercentage } from "../../../ai/utils/compare-strings";
15
+
15
16
  export const InputFormatter = ({
16
17
  mainValue = "",
17
18
  inputId,
@@ -27,6 +28,9 @@ export const InputFormatter = ({
27
28
  isAiGenerationLoading = false,
28
29
  isAiRegenerationLoading=false,
29
30
  isAiActive = false,
31
+ isAiAvailable = false,
32
+ aiGenerated = false,
33
+
30
34
  setIsAiActive = () => {},
31
35
  handlerAiGeneration = () => {},
32
36
  handlerRegenerateSuggestions = () => {},
@@ -72,7 +76,6 @@ export const InputFormatter = ({
72
76
  };
73
77
 
74
78
  const onChange = (valueInput, h) => {
75
-
76
79
  let value = "";
77
80
  try {
78
81
  if (h.getLength() - 1 <= maxLength) {
@@ -96,32 +99,49 @@ export const InputFormatter = ({
96
99
  setCharsCounter(h.getLength() - 1);
97
100
  value = valueFormater(value);
98
101
 
99
- if (updatedDescriptions) {
100
- let idInput = inputId;
101
- let dataSave = updatedDescriptions?.slice();
102
- if (dataSave?.length > 0) {
103
- const index = dataSave.findIndex((e) => e.attributeId === idInput);
104
- if (index !== -1) {
105
- if (value !== mainValue) dataSave[index].value = value;
106
- else dataSave.splice(index, 1);
102
+ updateParentDescriptions(value, aiSuggestionAccepted);
103
+ };
104
+
105
+ const updateParentDescriptions = (finalValue, isAiAccepted) => {
106
+ if (!updatedDescriptions) return;
107
+
108
+ let idInput = inputId;
109
+ let dataSave = updatedDescriptions?.slice();
110
+
111
+ const similarity = getTextSimilarityPercentage(valueAccepted, inputValue);
112
+
113
+ const generatedWithAi = (isAiAccepted) || aiGenerated && similarity >= 50;
114
+
115
+ if (dataSave?.length > 0) {
116
+ const index = dataSave.findIndex((e) => e.attributeId === idInput);
117
+ if (index !== -1) {
118
+ if (finalValue !== mainValue) {
119
+ dataSave[index].value = finalValue;
120
+ dataSave[index].aiSuggestionAccepted = generatedWithAi;
107
121
  } else {
108
- dataSave.push({
109
- articleId: articleId,
110
- attributeId: idInput,
111
- value: value,
112
- });
122
+ dataSave.splice(index, 1);
113
123
  }
114
124
  } else {
115
- if (value !== mainValue) {
125
+ if (finalValue !== mainValue) {
116
126
  dataSave.push({
117
127
  articleId: articleId,
118
128
  attributeId: idInput,
119
- value: value,
129
+ value: finalValue,
130
+ aiSuggestionAccepted: generatedWithAi,
120
131
  });
121
132
  }
122
133
  }
123
- setUpdatedDescriptions(dataSave);
134
+ } else {
135
+ if (finalValue !== mainValue) {
136
+ dataSave.push({
137
+ articleId: articleId,
138
+ attributeId: idInput,
139
+ value: finalValue,
140
+ aiSuggestionAccepted: generatedWithAi,
141
+ });
142
+ }
124
143
  }
144
+ setUpdatedDescriptions(dataSave);
125
145
  };
126
146
 
127
147
  const getSelection = (range, a, b) => {
@@ -136,14 +156,19 @@ export const InputFormatter = ({
136
156
 
137
157
  //AI Generation
138
158
  const handleAcceptSuggestion = (suggestionValue) => {
139
-
140
- if(!suggestionValue) return;
159
+ if (!suggestionValue) return;
141
160
 
142
161
  setInputValue(suggestionValue);
143
162
  setValueAccepted(suggestionValue);
144
163
  setAiSuggestionAccepted(true);
145
164
  setIsAiActive(false);
146
- }
165
+
166
+ // Formateamos el valor sugerido igual que en el onChange
167
+ const formattedSuggestion = valueFormater(suggestionValue);
168
+
169
+ // Disparamos la actualización enviando "true" directamente
170
+ updateParentDescriptions(formattedSuggestion, true);
171
+ };
147
172
 
148
173
  useEffect(() => {
149
174
 
@@ -161,6 +186,7 @@ export const InputFormatter = ({
161
186
  setAiSuggestionAccepted(false);
162
187
 
163
188
  }, [isAiActive]);
189
+
164
190
  //End Ai Generation
165
191
 
166
192
  return (
@@ -181,7 +207,9 @@ export const InputFormatter = ({
181
207
  currentSuggestion?.[inputId]?.value
182
208
  ) : getValue(inputValue)}
183
209
  readOnly={isAiActive || disabled}
184
- modules={{ toolbar: ["bold"] }}
210
+ modules={{
211
+ toolbar: false,
212
+ }}
185
213
  onKeyPress={(e) => {
186
214
  if (charsCounter >= maxLength && e.key !== "Backspace") {
187
215
  e.preventDefault();
@@ -210,7 +238,7 @@ export const InputFormatter = ({
210
238
  }
211
239
  {
212
240
  hasAiGeneration && (
213
- <div className={`icon_container ${isAiActive && "ai-active"}`} onClick={handlerAiGeneration}>
241
+ <div className={`icon_container ${isAiActive ? "ai-active" : ''} ${isAiAvailable ? "ai-available" : ''}`} title={!isAiAvailable ? 'Debes de completar ficha técnica e imágenes para desbloquear la generación con IA' : ''} onClick={handlerAiGeneration}>
214
242
  <img className={isAiGenerationLoading ? "loading" : ""} src={isAiGenerationLoading ? LoadingIcon : isAiActive ? CancelIcon : AiGenerationIcon} />
215
243
  </div>
216
244
  )
@@ -62,13 +62,11 @@ export const Container = styled.div`
62
62
  margin-top: 5px;
63
63
  }
64
64
  `;
65
-
66
65
  export const InputContainer = styled.div`
67
66
 
68
67
  &.ai-generation {
69
- position: relative;
70
68
 
71
- cursor: pointer;
69
+ position: relative;
72
70
 
73
71
  > input[type=text] {
74
72
  padding-right: 2.5rem;
@@ -87,6 +85,8 @@ export const InputContainer = styled.div`
87
85
  height: 22.5px;
88
86
  padding: 4px;
89
87
 
88
+ cursor: not-allowed;
89
+
90
90
  border-radius: 50%;
91
91
  overflow: hidden;
92
92
  display: flex;
@@ -94,10 +94,6 @@ export const InputContainer = styled.div`
94
94
  justify-content: center;
95
95
  z-index: 1;
96
96
 
97
- &.ai-active::before {
98
- background: #FB4141;
99
- }
100
-
101
97
  &::before {
102
98
  content: '';
103
99
  position: absolute;
@@ -106,6 +102,18 @@ export const InputContainer = styled.div`
106
102
  width: 100%;
107
103
  height: 100%;
108
104
 
105
+ background: gray;
106
+ background-size: 200% 200%;
107
+
108
+ animation: ai-shimmer 3s ease-in-out infinite alternate;
109
+ z-index: -1;
110
+ }
111
+
112
+ &.ai-available {
113
+ cursor: pointer;
114
+ }
115
+
116
+ &.ai-available::before{
109
117
  background: linear-gradient(
110
118
  120deg,
111
119
  #4285F4 20%,
@@ -113,9 +121,40 @@ export const InputContainer = styled.div`
113
121
  #4285F4 80%
114
122
  );
115
123
  background-size: 200% 200%;
124
+ }
116
125
 
117
- animation: ai-shimmer 3s ease-in-out infinite alternate;
118
- z-index: -1;
126
+ &.ai-available::after {
127
+ content: "";
128
+ position: absolute;
129
+ top: 0;
130
+ left: 0;
131
+ width: 100%;
132
+ height: 100%;
133
+ opacity: 0.75;
134
+
135
+ background: linear-gradient(
136
+ 135deg,
137
+ rgba(255, 255, 255, 0) 0%,
138
+ rgba(255, 255, 255, 0) 40%,
139
+ rgba(255, 255, 255, 0.8) 50%,
140
+ rgba(255, 255, 255, 0) 60%,
141
+ rgba(255, 255, 255, 0) 100%
142
+ );
143
+
144
+ transform: scale(2) translate(-100%, -100%);
145
+ z-index: 3;
146
+
147
+ animation: ai-glint 3s infinite ease-in-out;
148
+ }
149
+
150
+ &.ai-available img.loading ~ ::after {
151
+ animation: none;
152
+ display: none;
153
+ }
154
+
155
+ &.ai-active::after {
156
+ animation: none;
157
+ display: none;
119
158
  }
120
159
 
121
160
  img {
@@ -132,6 +171,11 @@ export const InputContainer = styled.div`
132
171
  height: 22.5px;
133
172
  }
134
173
 
174
+ &.ai-active::before {
175
+ background: oklch(57.7% 0.245 27.325);
176
+ }
177
+
178
+
135
179
  }
136
180
  }
137
181
 
@@ -144,8 +188,16 @@ export const InputContainer = styled.div`
144
188
  }
145
189
  }
146
190
 
147
- `;
191
+ @keyframes ai-glint {
192
+ 0% {
193
+ transform: scale(2) translate(-100%, -100%);
194
+ }
195
+ 70%, 100% {
196
+ transform: scale(2) translate(100%, 100%);
197
+ }
198
+ }
148
199
 
200
+ `;
149
201
  export const BottomContainer = styled.div`
150
202
 
151
203
  display: flex;
@@ -13,6 +13,7 @@ import Slide2 from "../../../assets/images/sliderToolTip/slide22.svg";
13
13
  import Slide3 from "../../../assets/images/sliderToolTip/slide23.svg";
14
14
  import { FinancedCompanies } from "./FinancedCompanies";
15
15
  import { GlobalColors } from "../../../global-files/variables";
16
+ import { useAiProductEdition } from "../../../contexts/AiProductEdition";
16
17
 
17
18
  export const StatusAsignationInfo = ({
18
19
  status = "-",
@@ -37,6 +38,10 @@ export const StatusAsignationInfo = ({
37
38
  const [assignationType, setAssignationType] = useState("facilitator");
38
39
  const isFinanced = FinancedCompanies.includes(user?.id_company);
39
40
 
41
+ const {
42
+ inputsUsingAi
43
+ } = useAiProductEdition();
44
+
40
45
  const closeAsignations = (e) => {
41
46
  if (!e.target.closest("#default-id") && showAsignationPanel) {
42
47
  document.removeEventListener("click", closeAsignations, false);
@@ -93,7 +98,10 @@ export const StatusAsignationInfo = ({
93
98
  {showSaveButton && !isFinanced && (
94
99
  <Button
95
100
  buttonType={"circular-button save-button"}
96
- onClick={onClickSave}
101
+ onClick={() => {
102
+ if(Object.values(inputsUsingAi).some(input => !!input?.using)) return console.warn("Para guardar se debe de dejar de usar la generación con IA");
103
+ onClickSave();
104
+ }}
97
105
  />
98
106
  )}
99
107
  {imagesSection && (