@nyris/nyris-webapp 0.3.90 → 0.3.92

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 (106) hide show
  1. package/build/asset-manifest.json +6 -6
  2. package/build/data/related-parts.json +83 -0
  3. package/build/index.html +1 -1
  4. package/build/js/settings.example.js +3 -0
  5. package/build/static/css/main.5ea01690.css +4 -0
  6. package/build/static/css/main.5ea01690.css.map +1 -0
  7. package/build/static/js/main.36b77705.js +3 -0
  8. package/build/static/js/{main.cede3ae1.js.map → main.36b77705.js.map} +1 -1
  9. package/package.json +4 -3
  10. package/public/data/related-parts.json +83 -0
  11. package/public/js/settings.example.js +3 -0
  12. package/src/App.test.tsx +0 -1
  13. package/src/App.tsx +0 -1
  14. package/src/assets/arrow_down_expanded.svg +3 -0
  15. package/src/assets/arrow_enter.svg +3 -0
  16. package/src/assets/camera.svg +3 -0
  17. package/src/assets/close.svg +3 -0
  18. package/src/assets/enter.svg +3 -0
  19. package/src/assets/refresh.svg +3 -0
  20. package/src/assets/vizo_avatar.svg +16 -0
  21. package/src/components/Cadenas/CadenasWebViewer.tsx +1 -1
  22. package/src/components/Cart.tsx +48 -36
  23. package/src/components/ChatAssistant/ChatAssistant.tsx +289 -0
  24. package/src/components/ChatAssistant/MobileChatAssistant.tsx +291 -0
  25. package/src/components/ChatAssistant/OptionChip.tsx +78 -0
  26. package/src/components/ChatAssistant/index.ts +3 -0
  27. package/src/components/ChatAssistant/useChatAssistantLogic.ts +745 -0
  28. package/src/components/CurrentRefinements.tsx +2 -2
  29. package/src/components/CustomCameraDrawer.tsx +56 -13
  30. package/src/components/DragDropFile.tsx +5 -5
  31. package/src/components/ExperienceVisualSearch/ExperienceVisualSearch.tsx +1 -1
  32. package/src/components/Header.tsx +116 -96
  33. package/src/components/Hint.tsx +1 -2
  34. package/src/components/HitsPerPage.tsx +9 -3
  35. package/src/components/ImagePreview.tsx +32 -17
  36. package/src/components/ImageUpload.tsx +16 -8
  37. package/src/components/Inquiry/InquiryBanner.tsx +1 -1
  38. package/src/components/Inquiry/InquiryModal.tsx +35 -29
  39. package/src/components/ItemSpecification.tsx +58 -126
  40. package/src/components/LocationInfoPopup.tsx +33 -33
  41. package/src/components/MatchNotificationBanner.tsx +90 -36
  42. package/src/components/PostFilter/PostFilter.tsx +1 -1
  43. package/src/components/PostFilter/PostFilterComponent.tsx +0 -1
  44. package/src/components/PostFilter/PostFilterFindApi.tsx +0 -1
  45. package/src/components/PoweredBy.tsx +1 -1
  46. package/src/components/PreFilter/PreFilter.tsx +14 -3
  47. package/src/components/PreFilter/PreFilterModal.tsx +0 -1
  48. package/src/components/Product/Product.tsx +15 -11
  49. package/src/components/Product/ProductAttribute.tsx +4 -5
  50. package/src/components/Product/ProductDetailViewModal.tsx +2 -4
  51. package/src/components/Product/ProductList.tsx +26 -13
  52. package/src/components/Rfq/RfqModal.tsx +1 -1
  53. package/src/components/SidePanel.tsx +124 -91
  54. package/src/components/SmartFilter.tsx +320 -0
  55. package/src/components/TextSearch.tsx +134 -70
  56. package/src/components/UploadDisclaimer.tsx +1 -1
  57. package/src/hooks/useBadResultsRecovery.ts +407 -0
  58. package/src/hooks/useEffectiveGroundingResults.ts +54 -0
  59. package/src/hooks/useGoodResultsChat.ts +651 -0
  60. package/src/hooks/useGroundedSearch.ts +88 -0
  61. package/src/hooks/useImageSearch.ts +139 -187
  62. package/src/hooks/useResultEvaluator.ts +417 -0
  63. package/src/index.css +1 -1
  64. package/src/index.tsx +0 -1
  65. package/src/layouts/AppLayout.tsx +53 -2
  66. package/src/pages/Home.tsx +11 -52
  67. package/src/pages/Login.tsx +1 -2
  68. package/src/pages/Logout.tsx +1 -1
  69. package/src/pages/Result.tsx +198 -200
  70. package/src/providers/AuthProvider.tsx +0 -1
  71. package/src/services/Feedback.ts +1 -1
  72. package/src/services/visualSearch.ts +0 -21
  73. package/src/services/vizo.ts +192 -4
  74. package/src/stores/chat/chatStore.ts +150 -0
  75. package/src/stores/chat/conversationStore.ts +300 -0
  76. package/src/stores/request/Misc/misc.slice.ts +2 -2
  77. package/src/stores/request/filter/filter.slice.ts +8 -8
  78. package/src/stores/request/query/query.slice.ts +2 -2
  79. package/src/stores/request/requestImage/requestImage.slice.ts +6 -6
  80. package/src/stores/request/specifications/specifications.slice.ts +10 -7
  81. package/src/stores/result/detectedRegions/detectedRegions.slice.ts +1 -1
  82. package/src/stores/result/prodcuts/products.initialState.ts +12 -0
  83. package/src/stores/result/prodcuts/products.slice.ts +28 -8
  84. package/src/stores/result/session/session.slice.ts +2 -2
  85. package/src/stores/smartFilters/smartFiltersStore.ts +270 -0
  86. package/src/stores/types.ts +41 -0
  87. package/src/stores/ui/ai/ai.initialState.ts +5 -0
  88. package/src/stores/ui/ai/ai.slice.ts +15 -0
  89. package/src/stores/ui/banner/banner.initialState.ts +6 -0
  90. package/src/stores/ui/banner/banner.slice.ts +14 -0
  91. package/src/stores/ui/feedback/feedback.slice.ts +1 -1
  92. package/src/stores/ui/loading/loading.slice.ts +4 -4
  93. package/src/stores/ui/uiStore.ts +7 -1
  94. package/src/styles/product.scss +0 -2
  95. package/src/types.ts +3 -7
  96. package/src/utils/cropImageToBase64.ts +32 -0
  97. package/src/utils/fetchProductImage.ts +109 -0
  98. package/src/utils/imageConverters.ts +124 -0
  99. package/src/utils/relatedParts.ts +35 -0
  100. package/src/utils/specificationFilter.ts +1 -5
  101. package/tailwind.config.js +3 -2
  102. package/build/static/css/main.734b52e1.css +0 -4
  103. package/build/static/css/main.734b52e1.css.map +0 -1
  104. package/build/static/js/main.cede3ae1.js +0 -3
  105. package/src/utils/addAssets.ts +0 -40
  106. /package/build/static/js/{main.cede3ae1.js.LICENSE.txt → main.36b77705.js.LICENSE.txt} +0 -0
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@nyris/nyris-webapp",
3
- "version": "0.3.90",
3
+ "version": "0.3.92",
4
4
  "dependencies": {
5
5
  "@auth0/auth0-react": "^2.2.4",
6
6
  "@emailjs/browser": "^4.3.3",
7
- "@nyris/nyris-api": "^0.3.90",
8
- "@nyris/nyris-react-components": "^0.3.90",
7
+ "@google/genai": "^1.46.0",
8
+ "@nyris/nyris-api": "^0.3.92",
9
+ "@nyris/nyris-react-components": "^0.3.92",
9
10
  "@radix-ui/react-accordion": "^1.2.2",
10
11
  "@radix-ui/react-dialog": "^1.1.4",
11
12
  "@radix-ui/react-popover": "^1.1.4",
@@ -0,0 +1,83 @@
1
+ {
2
+ "GC Gruppe": [
3
+ {
4
+ "keywords": ["faucet", "mixer tap", "basin mixer", "Waschtischarmatur", "Armatur", "Mischer", "Einhandmischer", "Einhandmischbatterie", "Einhebelmischer", "Waschtischbatterie"],
5
+ "relatedParts": [
6
+ { "label": "Filter Cartridge", "description": "Replacement ceramic or carbon filter cartridge for water purification" },
7
+ { "label": "Sealing Tape", "description": "PTFE thread seal tape for leak-proof pipe connections" },
8
+ { "label": "Aerator", "description": "Flow regulator/strainer insert for the faucet spout" },
9
+ { "label": "Flexible Hose", "description": "Braided water supply connection hose" },
10
+ { "label": "Angle Valve", "description": "Shut-off valve for the water supply line beneath the basin" }
11
+ ]
12
+ },
13
+ {
14
+ "keywords": ["shower", "Brause", "Dusche", "Duschkopf", "Duscharmatur", "Brausekopf", "shower head", "shower enclosure"],
15
+ "relatedParts": [
16
+ { "label": "Shower Hose", "description": "Flexible connection hose between mixer and shower head" },
17
+ { "label": "Wall Bracket", "description": "Mounting bracket or slide bar for the shower head" },
18
+ { "label": "Sealing Set", "description": "Gaskets and O-rings for shower connections" },
19
+ { "label": "Shower Filter", "description": "Inline water filter for limescale and chlorine reduction" },
20
+ { "label": "Diverter Valve", "description": "Valve to switch water flow between tub spout and shower" }
21
+ ]
22
+ },
23
+ {
24
+ "keywords": ["radiator", "Heizkoerper", "Heizung", "Heizkörper", "Konvektor", "panel radiator"],
25
+ "relatedParts": [
26
+ { "label": "Thermostat Valve", "description": "Temperature control valve head for the radiator" },
27
+ { "label": "Bleed Valve", "description": "Air release valve for radiator maintenance" },
28
+ { "label": "Wall Bracket", "description": "Mounting bracket set for radiator installation" },
29
+ { "label": "Lockshield Valve", "description": "Return-side balancing valve for flow regulation" },
30
+ { "label": "Radiator Plug", "description": "Blanking plug and seal for unused radiator connections" }
31
+ ]
32
+ },
33
+ {
34
+ "keywords": ["toilet", "WC", "Toilette", "Klosett", "wall-hung WC", "stand-WC"],
35
+ "relatedParts": [
36
+ { "label": "Flush Valve", "description": "Internal flush mechanism and valve for the cistern" },
37
+ { "label": "Toilet Seat", "description": "Replacement seat and lid with hinges" },
38
+ { "label": "Flush Plate", "description": "Actuator plate for concealed cistern systems" },
39
+ { "label": "Concealed Cistern", "description": "In-wall cistern frame for wall-hung installations" },
40
+ { "label": "Connection Set", "description": "Waste pipe connector and seal kit for WC installation" }
41
+ ]
42
+ },
43
+ {
44
+ "keywords": ["pump", "Pumpe", "Umwälzpumpe", "circulation pump", "Heizungspumpe"],
45
+ "relatedParts": [
46
+ { "label": "Check Valve", "description": "Non-return valve to prevent backflow in the system" },
47
+ { "label": "Pump Flange Set", "description": "Connection flanges and gaskets for pump installation" },
48
+ { "label": "Insulation Shell", "description": "Thermal insulation jacket for the pump body" },
49
+ { "label": "Replacement Seal", "description": "Mechanical shaft seal for pump maintenance" },
50
+ { "label": "Speed Controller", "description": "Electronic controller for variable pump speed" }
51
+ ]
52
+ },
53
+ {
54
+ "keywords": ["boiler", "Kessel", "Heizkessel", "Brennwertkessel", "Gaskessel", "water heater", "Warmwasserspeicher", "Durchlauferhitzer"],
55
+ "relatedParts": [
56
+ { "label": "Expansion Vessel", "description": "Pressure compensation vessel for the heating circuit" },
57
+ { "label": "Safety Valve", "description": "Pressure relief valve for overpressure protection" },
58
+ { "label": "Flue Pipe", "description": "Exhaust gas duct for combustion venting" },
59
+ { "label": "Condensate Trap", "description": "Siphon for condensate drainage from condensing boilers" },
60
+ { "label": "Ignition Electrode", "description": "Replacement spark electrode for burner ignition" }
61
+ ]
62
+ },
63
+ {
64
+ "keywords": ["washbasin", "Waschbecken", "basin", "Waschtisch", "Handwaschbecken", "vanity"],
65
+ "relatedParts": [
66
+ { "label": "Pop-up Waste", "description": "Click-clack or pop-up drain plug for the basin" },
67
+ { "label": "Siphon", "description": "Bottle trap or P-trap for basin waste drainage" },
68
+ { "label": "Basin Mixer", "description": "Single-lever or two-handle mixer tap for the washbasin" },
69
+ { "label": "Mounting Kit", "description": "Fixing bolts and brackets for basin installation" },
70
+ { "label": "Overflow Cover", "description": "Decorative cover for the basin overflow hole" }
71
+ ]
72
+ },
73
+ {
74
+ "keywords": ["valve", "Ventil", "Absperrventil", "Regelventil", "ball valve", "Kugelventil", "gate valve"],
75
+ "relatedParts": [
76
+ { "label": "Replacement Seal", "description": "O-ring or flat gasket set for valve maintenance" },
77
+ { "label": "Handle Extension", "description": "Extended lever or wheel handle for hard-to-reach valves" },
78
+ { "label": "Insulation Jacket", "description": "Thermal insulation shell for exposed valve bodies" },
79
+ { "label": "Fitting Adapter", "description": "Thread adapter or compression fitting for pipe connection" }
80
+ ]
81
+ }
82
+ ]
83
+ }
@@ -46,6 +46,8 @@ var settings = {
46
46
  vizo: {
47
47
  groundingEnabled: false,
48
48
  fallbackToElasticSearch: false,
49
+ chat: true,
50
+ findRelatedParts: true,
49
51
  },
50
52
  // UI - theme
51
53
  theme: {
@@ -103,6 +105,7 @@ var settings = {
103
105
  experienceVisualSearchImages: [],
104
106
  simpleCardView: false,
105
107
  noSimilarSearch: false,
108
+ useSmartFilters: false,
106
109
  showFeedback: false,
107
110
  geoLocation: false,
108
111
  geoLocationMessage: '',
package/src/App.test.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { render, screen } from '@testing-library/react';
3
2
  import App from './App';
4
3
 
package/src/App.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import AuthProvider from 'providers/AuthProvider';
3
2
  import AppRouter from 'routes/Router';
4
3
 
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="6" viewBox="0 0 16 6" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M7.46905 5.70262C7.79378 5.90608 8.20622 5.90608 8.53095 5.70262L15.6455 1.2449C16.0406 0.997313 16.0406 0.42165 15.6455 0.174064C15.4403 0.045508 15.1797 0.0455079 14.9745 0.174064L8.53095 4.21139C8.20622 4.41486 7.79378 4.41486 7.46905 4.21139L1.02547 0.174064C0.820298 0.0455083 0.559701 0.0455079 0.354525 0.174064C-0.0406208 0.42165 -0.0406208 0.997313 0.354525 1.2449L7.46905 5.70262Z" fill="#2B2C46"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- License: CC Attribution. Made by iconhub: https://iconhub.io/ -->
3
+ <svg width="16px" height="16px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="up" class="icon glyph"><path d="M19.71,9.29l-7-7h0a1.15,1.15,0,0,0-.33-.21.94.94,0,0,0-.76,0,1.15,1.15,0,0,0-.33.21h0l-7,7a1,1,0,0,0,1.42,1.42L11,5.41V21a1,1,0,0,0,2,0V5.41l5.29,5.3a1,1,0,0,0,1.42,0A1,1,0,0,0,19.71,9.29Z" fill="currentColor"></path></svg>
@@ -0,0 +1,3 @@
1
+ <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M17.3571 16.0714H0.642857C0.287817 16.0714 0 15.7836 0 15.4286V4.50002C0 4.14498 0.287817 3.85716 0.642857 3.85716H4.79571L5.895 2.21787C6.01327 2.03827 6.21352 1.9297 6.42857 1.92859H11.5714C11.7865 1.9297 11.9867 2.03827 12.105 2.21787L13.2043 3.85716H17.3571C17.7122 3.85716 18 4.14498 18 4.50002V15.4286C18 15.7836 17.7122 16.0714 17.3571 16.0714ZM1.28597 14.7857H16.7145V5.14289H12.8574C12.6424 5.14177 12.4421 5.03321 12.3238 4.8536L11.2245 3.21431H6.77597L5.67668 4.8536C5.55841 5.03321 5.35816 5.14177 5.14311 5.14289H1.28597V14.7857ZM9.00013 13.5001C6.86989 13.5001 5.14299 11.7732 5.14299 9.64291C5.14299 7.51267 6.86989 5.78577 9.00013 5.78577C11.1304 5.78577 12.8573 7.51267 12.8573 9.64291C12.8573 11.7732 11.1304 13.5001 9.00013 13.5001ZM9.00042 7.07141C7.58026 7.07141 6.429 8.22268 6.429 9.64284C6.429 11.063 7.58026 12.2143 9.00042 12.2143C10.4206 12.2143 11.5719 11.063 11.5719 9.64284C11.5719 8.22268 10.4206 7.07141 9.00042 7.07141Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.475 3.575C13.7649 3.28505 13.7649 2.81495 13.475 2.525C13.1851 2.23505 12.715 2.23505 12.425 2.525L8.70711 6.24289C8.31658 6.63342 7.68342 6.63342 7.29289 6.24289L3.575 2.525C3.28505 2.23505 2.81495 2.23505 2.525 2.525C2.23505 2.81495 2.23505 3.28505 2.525 3.575L6.24289 7.29289C6.63342 7.68342 6.63342 8.31658 6.24289 8.70711L2.525 12.425C2.23505 12.7149 2.23505 13.1851 2.525 13.475C2.81495 13.7649 3.28505 13.7649 3.575 13.475L7.29289 9.75711C7.68342 9.36658 8.31658 9.36658 8.70711 9.75711L12.425 13.475C12.7149 13.7649 13.1851 13.7649 13.475 13.475C13.7649 13.1851 13.7649 12.715 13.475 12.425L9.75711 8.70711C9.36658 8.31658 9.36658 7.68342 9.75711 7.29289L13.475 3.575Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="13" viewBox="0 0 16 13" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M10.6571 2.43183C10.4497 2.63925 10.4497 2.97899 10.6571 3.18641L13.5539 6.08315C13.7827 6.31202 13.6218 6.70898 13.2964 6.70898H3.50827C3.50827 6.70898 2.42468 6.73759 1.72016 6.06884C1.28744 5.654 1.06571 5.04247 1.06214 4.24497V0.532856C1.06571 0.239607 0.826106 0 0.532857 0C0.239607 0 0 0.239607 0 0.532856V4.24139C0 5.34287 0.336164 6.21547 0.987036 6.83773C1.9097 7.71748 3.17211 7.7747 3.46893 7.7747C3.50827 7.7747 3.52973 7.7747 3.5333 7.7747H13.2964C13.6218 7.7747 13.7863 8.16808 13.5539 8.40054L10.5999 11.3545C10.3925 11.5619 10.3925 11.9017 10.5999 12.1091C10.8073 12.3165 11.1471 12.3165 11.3545 12.1091L15.8426 7.62092C16.0501 7.4135 16.0501 7.07376 15.8426 6.86634L11.4117 2.4354C11.2043 2.22798 10.8646 2.22798 10.6571 2.4354V2.43183Z" fill="#AAABB5"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12.8333 9.37931C12.8333 12.4264 10.2217 14.8966 7 14.8966C3.77834 14.8966 1.16667 12.4264 1.16667 9.37931C1.16667 6.33222 3.77834 3.86207 7 3.86207L10.6062 3.86207L8.51532 5.84022L9.33333 6.62069L12.8333 3.31034L9.33333 0L8.51532 0.780138L10.608 2.75862L7 2.75862C3.13401 2.75862 0 5.72281 0 9.37931C0 13.0358 3.13401 16 7 16C10.866 16 14 13.0358 14 9.37931H12.8333Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1,16 @@
1
+ <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="40" height="40" rx="20" fill="#55566B"/>
3
+ <g clip-path="url(#clip0_8518_62118)">
4
+ <path d="M20 36C28.8366 36 36 28.8366 36 20C36 11.1634 28.8366 4 20 4C11.1634 4 4 11.1634 4 20C4 28.8366 11.1634 36 20 36Z" fill="url(#paint0_radial_8518_62118)"/>
5
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M19.9998 33.3024C12.6632 33.3024 6.69727 27.3365 6.69727 19.9998C6.69727 12.6632 12.6632 6.69727 19.9998 6.69727C27.3365 6.69727 33.3024 12.6676 33.3024 19.9998C33.3024 27.3321 27.3365 33.3024 19.9998 33.3024ZM19.9998 8.42379C13.6165 8.42379 8.42379 13.6165 8.42379 19.9998C8.42379 26.3832 13.6165 31.5759 19.9998 31.5759C26.3832 31.5759 31.5759 26.3832 31.5759 19.9998C31.5759 13.6165 26.3832 8.42379 19.9998 8.42379ZM19.5565 27.9213C19.3149 27.9126 19.0689 27.895 18.8273 27.8598C15.2776 27.4073 12.4615 24.6133 11.9783 21.0636C11.9431 20.8 11.9212 20.5364 11.9124 20.2728C11.9036 19.9916 11.6751 19.7676 11.3984 19.7676H10.88C10.7394 19.7676 10.6032 19.8247 10.5066 19.9257C10.4099 20.0268 10.3572 20.1586 10.3616 20.2992C10.5241 25.2766 14.5527 29.3052 19.5302 29.4677H19.5477C19.6795 29.4677 19.8069 29.415 19.9036 29.3228C20.0046 29.2261 20.0617 29.0899 20.0617 28.9493V28.431C20.0617 28.1498 19.8421 27.9257 19.5565 27.9126V27.9213ZM25.0209 12.2162C25.1615 12.1899 25.2977 12.2206 25.4119 12.3041V12.3085C27.8062 14.0438 29.234 16.7324 29.3306 19.6802C29.335 19.8208 29.2823 19.9526 29.1856 20.0537C29.0846 20.1547 28.9484 20.2118 28.8078 20.2118H28.2894C28.0127 20.2118 27.7842 19.9878 27.7754 19.7066C27.6876 17.2552 26.5014 15.0191 24.5157 13.5737C24.2872 13.4112 24.2301 13.0992 24.3883 12.8664L24.6782 12.4403C24.7573 12.3216 24.8803 12.2426 25.0209 12.2162ZM24.108 26.7081C25.7651 26.7081 27.1085 25.3647 27.1085 23.7076C27.1085 22.0504 25.7651 20.707 24.108 20.707C22.4508 20.707 21.1074 22.0504 21.1074 23.7076C21.1074 25.3647 22.4508 26.7081 24.108 26.7081ZM24.1079 19.2441C21.6433 19.2441 19.6444 21.2431 19.6444 23.7076C19.6444 24.3578 19.785 24.9729 20.0354 25.5308H20.0134C16.9602 25.5308 14.4824 23.053 14.4824 19.9998C14.4824 16.9465 16.9602 14.4688 20.0134 14.4688C22.891 14.4688 25.2501 16.6653 25.5181 19.4726C25.0744 19.3276 24.5999 19.2441 24.1079 19.2441Z" fill="white"/>
6
+ </g>
7
+ <defs>
8
+ <radialGradient id="paint0_radial_8518_62118" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20 20) rotate(90) scale(16)">
9
+ <stop offset="0.3125" stop-color="#655EE3"/>
10
+ <stop offset="1" stop-color="#2B2C46"/>
11
+ </radialGradient>
12
+ <clipPath id="clip0_8518_62118">
13
+ <rect width="32" height="32" fill="white" transform="translate(4 4)"/>
14
+ </clipPath>
15
+ </defs>
16
+ </svg>
@@ -119,7 +119,7 @@ function CadenasWebViewer({
119
119
  .then(() => {
120
120
  setStatus3dView('loaded');
121
121
  })
122
- .catch((err: any) => {
122
+ .catch(() => {
123
123
  setStatus3dView('not-found');
124
124
  });
125
125
  });
@@ -117,41 +117,50 @@ const Cart = () => {
117
117
  content="The basket is currently empty"
118
118
  disabled={!!amountOfParts}
119
119
  >
120
- <button
120
+ <div
121
121
  className={twMerge(
122
- 'relative flex items-center justify-center rounded-2xl border border-solid border-[#DDDEE7]',
123
- 'h-8 w-8',
122
+ 'h-[56px] min-w-[56px]',
123
+ 'desktop:min-w-[68px] desktop:h-[68px] bg-white rounded-2xl flex items-center justify-center ',
124
+ 'border border-solid border-[#DDDEE7]',
125
+ 'shadow-ds-1',
124
126
  )}
125
- style={{
126
- backgroundColor:
127
- amountOfParts > 0
128
- ? window.settings.theme.primaryColor
129
- : '#FAFAFA',
130
- }}
131
- onClick={() => onToggleCart(true)}
132
127
  >
133
- {amountOfParts ? (
134
- <div
135
- className={twMerge(
136
- 'absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full border-2 border-white border-solid text-white',
137
- 'text-[9px]',
138
- )}
139
- style={{
140
- backgroundColor: window.settings.theme.primaryColor,
141
- }}
142
- >
143
- {amountOfParts}
144
- </div>
145
- ) : (
146
- ''
147
- )}
148
- <Icon
149
- name="cart"
128
+ <button
150
129
  className={twMerge(
151
- amountOfParts > 0 ? 'fill-white text-white' : 'text-black',
130
+ 'relative flex items-center justify-center rounded-2xl',
131
+ 'h-10 w-10',
152
132
  )}
153
- />
154
- </button>
133
+ style={{
134
+ backgroundColor:
135
+ amountOfParts > 0
136
+ ? window.settings.theme.primaryColor
137
+ : '#FAFAFA',
138
+ }}
139
+ onClick={() => onToggleCart(true)}
140
+ >
141
+ {amountOfParts ? (
142
+ <div
143
+ className={twMerge(
144
+ 'absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full border-2 border-white border-solid text-white',
145
+ 'text-[9px]',
146
+ )}
147
+ style={{
148
+ backgroundColor: window.settings.theme.primaryColor,
149
+ }}
150
+ >
151
+ {amountOfParts}
152
+ </div>
153
+ ) : (
154
+ ''
155
+ )}
156
+ <Icon
157
+ name="cart"
158
+ className={twMerge(
159
+ amountOfParts > 0 ? 'fill-white text-white' : 'text-[#BBBDCF]',
160
+ )}
161
+ />
162
+ </button>
163
+ </div>
155
164
  </Tooltip>
156
165
  {isCartOpen &&
157
166
  createPortal(
@@ -163,9 +172,10 @@ const Cart = () => {
163
172
  <div
164
173
  className={twMerge(
165
174
  'absolute right-0 flex h-full w-[410px] max-w-full flex-col bg-transparent',
175
+ 'p-4',
166
176
  )}
167
177
  >
168
- <div className="flex h-full max-w-full flex-col bg-white">
178
+ <div className="flex h-full max-w-full flex-col bg-white rounded-2xl">
169
179
  <div className="flex h-[52px] items-center justify-between px-4 py-2 border-b border-solid border-[#DDDEE7]">
170
180
  <div className="text-xl font-semibold">
171
181
  Your cart ({amountOfParts} items)
@@ -224,11 +234,13 @@ const Cart = () => {
224
234
  className="mr-2 h-[50px] w-[50px] bg-white object-contain"
225
235
  />
226
236
  <div>
227
- <div className="max-w-[175px] break-words text-[9px] font-normal">
228
- {part.sku}
229
- </div>
230
- <div className="max-w-[175px] break-words text-xs font-semibold">
231
- {part.title}
237
+ <div>
238
+ <div className="max-w-[175px] break-words text-[9px] font-normal">
239
+ {part.sku}
240
+ </div>
241
+ <div className="max-w-[175px] break-words text-xs font-semibold">
242
+ {part.title}
243
+ </div>
232
244
  </div>
233
245
  </div>
234
246
  </div>
@@ -0,0 +1,289 @@
1
+ import { memo, useRef, useCallback } from 'react';
2
+ import { twMerge } from 'tailwind-merge';
3
+ import { Sparkles, Send, Loader2, Globe } from 'lucide-react';
4
+
5
+ import useConversationStore from 'stores/chat/conversationStore';
6
+ import { Icon } from '@nyris/nyris-react-components';
7
+ import useUiStore from 'stores/ui/uiStore';
8
+ import OptionChip, { ChatOption } from './OptionChip';
9
+ import { useChatAssistantLogic } from './useChatAssistantLogic';
10
+
11
+ function ChatAssistant() {
12
+ const inputRef = useRef<HTMLTextAreaElement>(null);
13
+
14
+ const {
15
+ inputValue,
16
+ setInputValue,
17
+ messages,
18
+ isLoading,
19
+ isEvaluated,
20
+ hasApiKey,
21
+ p7Running,
22
+ p7Done,
23
+ catalogSearching,
24
+ messagesEndRef,
25
+ imageInputRef,
26
+ handleSendMessage,
27
+ handleOptionClick,
28
+ handleImageUpload,
29
+ } = useChatAssistantLogic();
30
+
31
+ const setIsOpen = useConversationStore(s => s.setIsOpen);
32
+
33
+ const toggleAiMode = useUiStore(state => state.toggleAiMode);
34
+
35
+ // Focus input on text_search_prompt option click
36
+ const wrappedHandleOptionClick = useCallback(
37
+ async (option: ChatOption) => {
38
+ if (option.action_type === 'text_search_prompt') {
39
+ inputRef.current?.focus();
40
+ }
41
+ if (option.action_type === 'related_part') {
42
+ inputRef.current?.focus();
43
+ }
44
+ await handleOptionClick(option);
45
+ },
46
+ [handleOptionClick],
47
+ );
48
+
49
+ return (
50
+ <div
51
+ className={twMerge([
52
+ 'w-full h-full rounded-[24px] relative shadow-outer overflow-hidden',
53
+ 'flex flex-col',
54
+
55
+ 'border border-solid border-[#DDDEE7]',
56
+ 'bg-[radial-gradient(ellipse_at_70%_7%,_#F3F4F8_50%,_#E4E3FF_100%)]',
57
+ ])}
58
+ >
59
+ <div
60
+ className="absolute top-3 right-4 bg-[#FAFAFA] w-7 h-7 flex justify-center items-center rounded-lg cursor-pointer"
61
+ onClick={() => {
62
+ toggleAiMode();
63
+ setIsOpen(false);
64
+ }}
65
+ >
66
+ <Icon name="close" className="fill-[#BBBDCF] text-[#BBBDCF]" />
67
+ </div>
68
+
69
+ {/* {error && (
70
+ <div className="flex-shrink-0 mx-3 mt-2 p-2.5 bg-red-50 border border-red-200 rounded-lg flex items-center gap-2">
71
+ <AlertCircle size={12} className="text-red-500 flex-shrink-0" />
72
+ <p className="text-[11px] text-red-700">{error}</p>
73
+ </div>
74
+ )} */}
75
+
76
+ {/* ── Messages ── */}
77
+ <div className="flex-1 min-h-0 overflow-y-auto px-5 py-3 space-y-3">
78
+ {messages.length === 0 && !isLoading ? (
79
+ <div className="flex flex-col items-center justify-center flex-grow text-center px-4 py-10">
80
+ <div className="w-12 h-12 rounded-full bg-[#F0EFFF] flex items-center justify-center mb-3">
81
+ <Sparkles size={20} className="text-[#3E36DC]" />
82
+ </div>
83
+ <p className="text-sm font-semibold text-[#2B2C46] mb-1">
84
+ Vizo Search Assistant
85
+ </p>
86
+ <p className="text-[11px] text-[#999] leading-relaxed max-w-[200px]">
87
+ Upload an image to get started, or search for a product
88
+ </p>
89
+ </div>
90
+ ) : (
91
+ messages.map((msg, i) => {
92
+ return (
93
+ <div
94
+ key={msg.id || i}
95
+ className={twMerge(
96
+ 'flex',
97
+ msg.role === 'user' ? 'justify-end' : 'justify-start',
98
+ )}
99
+ >
100
+ <div className="">
101
+ {/* Bubble */}
102
+ <div
103
+ className={twMerge(
104
+ 'py-3 pr-4 text-sm leading-relaxed',
105
+ msg.role === 'user'
106
+ ? 'bg-[#E7E8F1] text-[#3B3E5F] rounded-tl-[16px] rounded-tr-[8px] rounded-br-[16px] rounded-bl-[16px] px-4 py-3 font-medium'
107
+ : msg?.content?.startsWith('[P7')
108
+ ? 'bg-[#EEF2FF] border border-[#C7D2FE] text-[#2B2C46] rounded-2xl rounded-bl-sm'
109
+ : ' text-[#2B2C46] rounded-2xl rounded-bl-sm',
110
+ i === 0 && 'pr-6',
111
+ )}
112
+ >
113
+ {msg.imageUrl && (
114
+ <img
115
+ src={msg.imageUrl}
116
+ alt="Uploaded"
117
+ className="w-[48px] h-[48px] rounded-lg object-cover mb-2"
118
+ />
119
+ )}
120
+ {msg?.content?.startsWith('[P7') && (
121
+ <div className="flex items-center gap-1 mb-1.5 text-indigo-600 font-semibold text-[10px]">
122
+ <Globe size={10} /> Google Grounding
123
+ </div>
124
+ )}
125
+ <pre className="whitespace-pre-wrap font-sans text-sm leading-4 tracking-[0.16px]">
126
+ {msg?.content?.startsWith('[P7')
127
+ ? msg.content.replace(/^\[P7[^\]]*\]\s*/, '')
128
+ : msg.content}
129
+ </pre>
130
+ </div>
131
+
132
+ {/* Quick reply options */}
133
+ {msg.role === 'assistant' &&
134
+ msg.options &&
135
+ msg.options.length > 0 && (
136
+ <div className="mt-2 space-y-2.5">
137
+ {/* Option chips */}
138
+ <div className="flex flex-wrap gap-1.5">
139
+ {msg.options
140
+ .filter(o => o.action_type !== 'text_search_prompt')
141
+ .map((option, optIdx) => (
142
+ <OptionChip
143
+ key={optIdx}
144
+ option={option}
145
+ onClick={wrappedHandleOptionClick}
146
+ disabled={
147
+ isLoading || i !== messages.length - 1
148
+ }
149
+ />
150
+ ))}
151
+ </div>
152
+ </div>
153
+ )}
154
+ </div>
155
+ </div>
156
+ );
157
+ })
158
+ )}
159
+
160
+ {/* Loading indicator */}
161
+ {isLoading && (
162
+ <div className="flex justify-start">
163
+ <div className="flex items-center gap-2 px-3 py-2.5 bg-[#F3F3F5] rounded-2xl rounded-bl-sm">
164
+ <Loader2 size={12} className="animate-spin text-[#3E36DC]" />
165
+ <span className="text-xs text-[#666]">
166
+ {!isEvaluated
167
+ ? 'Analyzing results...'
168
+ : catalogSearching
169
+ ? 'Refining your search…'
170
+ : 'Thinking...'}
171
+ </span>
172
+ </div>
173
+ </div>
174
+ )}
175
+
176
+ <div ref={messagesEndRef} />
177
+ </div>
178
+
179
+ {/* ── P7 Grounding status strip ── */}
180
+ {(p7Running || p7Done) && (
181
+ <div className="flex-shrink-0 px-3 py-1.5 border-t border-[#E0E0E0] bg-[#F8F8FF]">
182
+ {p7Running ? (
183
+ <span className="flex items-center gap-1.5 text-[10px] text-blue-600">
184
+ <Globe size={10} className="animate-pulse" /> Running Google
185
+ Grounding...
186
+ </span>
187
+ ) : (
188
+ <span className="flex items-center gap-1.5 text-[10px] text-blue-500">
189
+ <Globe size={10} /> Google Grounding complete
190
+ </span>
191
+ )}
192
+ </div>
193
+ )}
194
+
195
+ {/* ── Upload image — fixed above input ── */}
196
+ {isEvaluated && hasApiKey && (
197
+ <div className="px-2">
198
+ <button
199
+ onClick={() => imageInputRef.current?.click()}
200
+ disabled={isLoading}
201
+ className={twMerge(
202
+ 'w-full h-12 rounded-[32px] bg-[#3B3E5F] mb-2',
203
+ 'flex items-center justify-center gap-2 text-white cursor-pointer',
204
+ 'hover:bg-[#333653] transition-colors',
205
+ isLoading && 'opacity-50 cursor-not-allowed',
206
+ )}
207
+ >
208
+ <span>Upload image</span>
209
+ <Icon name="camera_ai" />
210
+ </button>
211
+ </div>
212
+ )}
213
+
214
+ {/* ── Input ── */}
215
+ {/* <div className="flex-shrink-0 px-3 py-3 border-t border-[#E0E0E0] bg-white rounded-3xl mt-2">
216
+ </div> */}
217
+ <form
218
+ onSubmit={e => {
219
+ e.preventDefault();
220
+ handleSendMessage();
221
+ }}
222
+ className="flex gap-2 items-end px-2"
223
+ >
224
+ <textarea
225
+ ref={inputRef}
226
+ value={inputValue}
227
+ onChange={e => setInputValue(e.target.value)}
228
+ onKeyDown={e => {
229
+ if (e.key === 'Enter' && !e.shiftKey && !isLoading) {
230
+ e.preventDefault();
231
+ handleSendMessage();
232
+ }
233
+ }}
234
+ placeholder={
235
+ isLoading ? 'Please wait...' : 'Ask about the results...'
236
+ }
237
+ disabled={isLoading}
238
+ rows={1}
239
+ className={twMerge(
240
+ 'flex-1 resize-none rounded-xl px-3 py-2.5 text-sm pr-14',
241
+ 'border-2 border-[#3E36DC] outline-none',
242
+ 'bg-[#F3F4F8] disabled:opacity-60',
243
+ 'min-h-[56px] max-h-[100px]',
244
+ 'rounded-[32px]',
245
+ 'overflow-y-auto scrollbar-hide',
246
+ )}
247
+ style={{
248
+ fontFamily: 'inherit',
249
+ lineHeight: '1.4',
250
+ scrollbarWidth: 'none',
251
+ msOverflowStyle: 'none',
252
+ }}
253
+ /* Hide scrollbar for Webkit browsers */
254
+ /* Add this style in a global CSS if not already present: .scrollbar-hide::-webkit-scrollbar { display: none; } */
255
+ />
256
+
257
+ <button
258
+ type="submit"
259
+ disabled={isLoading || !inputValue.trim() || !hasApiKey}
260
+ className={twMerge(
261
+ 'p-2.5 rounded-[32px] transition-colors flex-shrink-0 w-10 h-10 flex items-center justify-center',
262
+ inputValue.trim() && !isLoading && hasApiKey
263
+ ? 'bg-[#FAFAFA] text-black hover:bg-blue-100/40'
264
+ : 'bg-[#E0E0E0] text-[#999] cursor-not-allowed',
265
+ 'absolute right-4 bottom-2',
266
+ )}
267
+ >
268
+ <Send size={16} />
269
+ </button>
270
+ {/* Hidden file input — triggered by Upload image button above */}
271
+ <input
272
+ ref={imageInputRef}
273
+ type="file"
274
+ accept="image/*"
275
+ className="hidden"
276
+ onChange={e => {
277
+ const file = e.target.files?.[0];
278
+ if (file) {
279
+ handleImageUpload(file);
280
+ e.target.value = '';
281
+ }
282
+ }}
283
+ />
284
+ </form>
285
+ </div>
286
+ );
287
+ }
288
+
289
+ export default memo(ChatAssistant);