@ozdao/martyrs 0.2.568 → 0.2.570
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/dist/martyrs/src/components/Button/{Button.vue2.js → Button.vue.js} +3 -3
- package/dist/martyrs/src/components/Button/Button.vue.js.map +1 -0
- package/dist/martyrs/src/components/EditImages/{EditImages.vue.js → EditImages.vue2.js} +2 -2
- package/dist/martyrs/src/components/EditImages/EditImages.vue2.js.map +1 -0
- package/dist/martyrs/src/components/Feed/Carousel.vue.js +1 -1
- package/dist/martyrs/src/components/Feed/Feed.vue.js +2 -2
- package/dist/martyrs/src/components/Field/{Field.vue.js → Field.vue2.js} +2 -2
- package/dist/martyrs/src/components/Field/Field.vue2.js.map +1 -0
- package/dist/martyrs/src/components/FieldBig/FieldBig.vue.js +2 -2
- package/dist/martyrs/src/components/Loader/{Loader.vue.js → Loader.vue2.js} +2 -2
- package/dist/martyrs/src/components/Loader/Loader.vue2.js.map +1 -0
- package/dist/martyrs/src/components/LocationMarker/LocationMarker.vue.js +1 -1
- package/dist/martyrs/src/components/Media/Media.vue.js +1 -1
- package/dist/martyrs/src/components/Menu/{Menu.vue2.js → Menu.vue.js} +2 -2
- package/dist/martyrs/src/components/Menu/Menu.vue.js.map +1 -0
- package/dist/martyrs/src/components/Select/{Select.vue.js → Select.vue2.js} +2 -2
- package/dist/martyrs/src/components/Select/Select.vue2.js.map +1 -0
- package/dist/martyrs/src/components/UploadImage/UploadImage.vue.js +1 -1
- package/dist/martyrs/src/components/UploadImageMultiple/UploadImageMultiple.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/EnterCode.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/EnterPassword.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/pages/Invite.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +3 -3
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileEdit.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileEditAccount.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ProfileEditProfile.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/pages/ResetPassword.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/pages/SignUp.vue.js +2 -2
- package/dist/martyrs/src/modules/auth/views/components/sections/ProfileEditCredentials.vue.js +2 -2
- package/dist/martyrs/src/modules/community/components/layouts/Community.vue.js +1 -1
- package/dist/martyrs/src/modules/community/components/pages/BlogPost.vue.js +2 -2
- package/dist/martyrs/src/modules/community/components/pages/CreateBlogPost.vue.js +2 -2
- package/dist/martyrs/src/modules/constructor/components/elements/Card.vue.js +1 -1
- package/dist/martyrs/src/modules/constructor/components/elements/Embed.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/blocks/BlockSearch.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/blocks/CardHeader.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/blocks/PopupAuth.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/blocks/PopupDateSelector.vue.js +2 -2
- package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js +2 -2
- package/dist/martyrs/src/modules/core/views/components/partials/Header.vue.js +2 -2
- package/dist/martyrs/src/modules/core/views/components/partials/NavigationBar.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/components/sections/{Filters.vue.js → Filters.vue2.js} +2 -2
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue2.js.map +1 -0
- package/dist/martyrs/src/modules/core/views/components/sections/filters/FilterRange.vue.js +1 -1
- package/dist/martyrs/src/modules/core/views/router/scrollBehavior.js +27 -25
- package/dist/martyrs/src/modules/core/views/router/scrollBehavior.js.map +1 -1
- package/dist/martyrs/src/modules/events/components/elements/ButtonCheck.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/elements/ButtonJoin.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/EditEvent.vue.js +4 -4
- package/dist/martyrs/src/modules/events/components/pages/EditEventTickets.vue.js +2 -2
- package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +3 -3
- package/dist/martyrs/src/modules/events/components/sections/EditTickets.vue.js +2 -2
- package/dist/martyrs/src/modules/events/components/sections/Feed.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/sections/List.vue.js +1 -1
- package/dist/martyrs/src/modules/gallery/components/sections/BackofficeGallery.vue.js +4 -4
- package/dist/martyrs/src/modules/inventory/components/forms/AdjustmentForm.vue.js +3 -3
- package/dist/martyrs/src/modules/inventory/components/forms/ColumnSettingsMenu.vue.js +1 -1
- package/dist/martyrs/src/modules/inventory/components/forms/HistoryView.vue.js +1 -1
- package/dist/martyrs/src/modules/inventory/components/forms/StockAlertsForm.vue.js +3 -3
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/marketplace/views/components/pages/Marketplace.vue.js +2 -2
- package/dist/martyrs/src/modules/marketplace/views/components/sections/SectionMenu.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/blocks/ActionButtons.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/cards/AlbumCard.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/cards/ArtistCardSmall.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/cards/PlaylistCard.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/cards/TrackListCard.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/forms/AlbumForm.vue.js +3 -3
- package/dist/martyrs/src/modules/music/components/forms/ArtistForm.vue.js +4 -4
- package/dist/martyrs/src/modules/music/components/forms/PlaylistForm.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/forms/SearchForm.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/forms/TrackForm.vue.js +3 -3
- package/dist/martyrs/src/modules/music/components/pages/Album.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/pages/Artist.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/pages/MusicLibrary.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/pages/Playlist.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/pages/SearchResults.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/pages/Track.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/pages/TrackCreate.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/player/MusicPlayer.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/player/PlayerControls.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/player/VolumeControl.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/forms/FormApplicationDetails.vue.js +3 -3
- package/dist/martyrs/src/modules/orders/components/forms/FormCustomerDetails.vue.js +3 -3
- package/dist/martyrs/src/modules/orders/components/forms/FormSelectCustomer.vue.js +2 -2
- package/dist/martyrs/src/modules/orders/components/pages/OrderBackoffice.vue.js +2 -2
- package/dist/martyrs/src/modules/orders/components/pages/OrderCreate.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/pages/OrderCreateBackoffice.vue.js +2 -2
- package/dist/martyrs/src/modules/orders/components/sections/ApplicationDetails.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/sections/CustomerDetails.vue.js +1 -1
- package/dist/martyrs/src/modules/orders/components/sections/FormDelivery.vue.js +3 -3
- package/dist/martyrs/src/modules/orders/components/sections/FormPayment.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/elements/ButtonToggleMembership.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/forms/AddExistingMembersForm.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/forms/DepartmentForm.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/forms/InviteForm.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/organizations/components/sections/Documents.vue.js +3 -3
- package/dist/martyrs/src/modules/organizations/components/sections/MembersAdd.vue.js +3 -3
- package/dist/martyrs/src/modules/organizations/components/sections/Organizations.vue.js +3 -3
- package/dist/martyrs/src/modules/pages/views/components/blocks/CardPage.vue.js +2 -2
- package/dist/martyrs/src/modules/pages/views/components/pages/PageEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/pages/views/components/partials/SidebarPages.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/elements/Image360.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/Categories.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/CategoryEdit.vue.js +5 -5
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +4 -4
- package/dist/martyrs/src/modules/products/components/pages/Products.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/sections/EditAttributes.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/sections/EditDiscounts.vue.js +3 -3
- package/dist/martyrs/src/modules/products/components/sections/EditVariants.vue.js +4 -4
- package/dist/martyrs/src/modules/products/components/sections/ProductConfigurator.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/sections/ProductsRecommended.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js +25 -38
- package/dist/martyrs/src/modules/products/components/sections/SectionProduct.vue.js.map +1 -1
- package/dist/martyrs/src/modules/products/products.client.js +15 -21
- package/dist/martyrs/src/modules/products/products.client.js.map +1 -1
- package/dist/martyrs/src/modules/products/router/products.router.js +0 -34
- package/dist/martyrs/src/modules/products/router/products.router.js.map +1 -1
- package/dist/martyrs/src/modules/products/store/products.js +0 -15
- package/dist/martyrs/src/modules/products/store/products.js.map +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/Gant/GanttToolbar.vue.js +1 -1
- package/dist/martyrs/src/modules/rents/views/components/pages/RentsEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/reports/components/sections/FormReport.vue.js +2 -2
- package/dist/martyrs/src/modules/spots/components/blocks/SpotMemberModify.vue.js +2 -2
- package/dist/martyrs/src/modules/spots/components/layouts/Spots.vue.js +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/Map.vue.js +1 -1
- package/dist/martyrs/src/modules/spots/components/pages/SpotEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/spots/components/sections/WorktimeEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/wallet/views/components/blocks/CardDeposit.vue.js +1 -1
- package/dist/martyrs/src/modules/wallet/views/components/blocks/CryptoDeposit.vue.js +2 -2
- package/dist/martyrs/src/modules/wallet/views/components/pages/Wallet.vue.js +2 -2
- package/dist/products.server.js +1 -130
- package/dist/style.css +0 -23
- package/package.json +1 -1
- package/src/modules/core/views/router/scrollBehavior.js +30 -28
- package/src/modules/products/components/sections/SectionProduct.vue +2 -9
- package/src/modules/products/controllers/products.controller.js +0 -51
- package/src/modules/products/experiments/product-recommendation/README.md +156 -0
- package/src/modules/products/experiments/product-recommendation/controllers/recommendation.controller.js +59 -0
- package/src/modules/products/experiments/product-recommendation/router/recommendation.router.js +43 -0
- package/src/modules/products/experiments/product-recommendation/routes/recommendation.routes.js +10 -0
- package/src/modules/products/experiments/product-recommendation/store/recommendation.store.js +38 -0
- package/src/modules/products/products.client.js +0 -6
- package/src/modules/products/products.router.js +0 -29
- package/src/modules/products/router/products.router.js +0 -28
- package/src/modules/products/routes/products.routes.js +0 -2
- package/src/modules/products/store/products.js +0 -16
- package/dist/martyrs/src/components/Button/Button.vue2.js.map +0 -1
- package/dist/martyrs/src/components/EditImages/EditImages.vue.js.map +0 -1
- package/dist/martyrs/src/components/Field/Field.vue.js.map +0 -1
- package/dist/martyrs/src/components/Loader/Loader.vue.js.map +0 -1
- package/dist/martyrs/src/components/Menu/Menu.vue2.js.map +0 -1
- package/dist/martyrs/src/components/Select/Select.vue.js.map +0 -1
- package/dist/martyrs/src/components/Shader/Shader.vue.js +0 -2
- package/dist/martyrs/src/components/Shader/Shader.vue.js.map +0 -1
- package/dist/martyrs/src/modules/core/views/components/sections/Filters.vue.js.map +0 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductRecommmendation.vue.js +0 -106
- package/dist/martyrs/src/modules/products/components/pages/ProductRecommmendation.vue.js.map +0 -1
- package/dist/martyrs/src/modules/products/components/sections/HeroRecommendation.vue.js +0 -120
- package/dist/martyrs/src/modules/products/components/sections/HeroRecommendation.vue.js.map +0 -1
- /package/src/modules/products/{components/sections → experiments/product-recommendation/components}/HeroRecommendation.vue +0 -0
- /package/src/modules/products/{components/pages → experiments/product-recommendation/components}/ProductRecommmendation.vue +0 -0
package/dist/style.css
CHANGED
|
@@ -2604,27 +2604,6 @@ to { opacity: 1; transform: translateY(0);
|
|
|
2604
2604
|
.thumbnail.active[data-v-eddb4b2b] {
|
|
2605
2605
|
border: 1px solid rgb(var(--second));
|
|
2606
2606
|
}
|
|
2607
|
-
|
|
2608
|
-
.spiral {
|
|
2609
|
-
object-fit: cover;
|
|
2610
|
-
width: 100rem;
|
|
2611
|
-
height: 100rem;
|
|
2612
|
-
position: absolute;
|
|
2613
|
-
top: 50%;
|
|
2614
|
-
left: 50%;
|
|
2615
|
-
opacity: 0.066;
|
|
2616
|
-
transform: translate(-50%, -50%) rotate(0deg);
|
|
2617
|
-
transform-origin: center center;
|
|
2618
|
-
animation: spin 5s linear infinite;
|
|
2619
|
-
}
|
|
2620
|
-
@keyframes spin {
|
|
2621
|
-
0% {
|
|
2622
|
-
transform: translate(-50%, -50%) rotate(0deg);
|
|
2623
|
-
}
|
|
2624
|
-
100% {
|
|
2625
|
-
transform: translate(-50%, -50%) rotate(360deg);
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
2607
|
.popupar_products .carousel__slide {
|
|
2629
2608
|
flex: 0 0 25%;
|
|
2630
2609
|
min-width: 0;
|
|
@@ -2651,8 +2630,6 @@ to { opacity: 1; transform: translateY(0);
|
|
|
2651
2630
|
.text-muted[data-v-a5eb77c9] {
|
|
2652
2631
|
color: #6c757d;
|
|
2653
2632
|
}
|
|
2654
|
-
|
|
2655
|
-
/* Add your styles here */
|
|
2656
2633
|
.custom-table[data-v-b85cde7e] {
|
|
2657
2634
|
border-collapse: collapse;
|
|
2658
2635
|
}
|
package/package.json
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
export default
|
|
2
|
-
|
|
3
|
-
const findEl = async (hash, x) => {
|
|
4
|
-
return (
|
|
5
|
-
document.querySelector(hash) ||
|
|
6
|
-
new Promise((resolve, reject) => {
|
|
7
|
-
if (x > 50) {
|
|
8
|
-
return resolve();
|
|
9
|
-
}
|
|
10
|
-
setTimeout(() => {
|
|
11
|
-
resolve(findEl(hash, ++x || 1));
|
|
12
|
-
}, 1000);
|
|
13
|
-
})
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
1
|
+
export default function scrollBehavior(selector = '#scrollview') {
|
|
2
|
+
return async function(to, from, savedPosition) {
|
|
17
3
|
if (to.hash) {
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
const findEl = async (hash, x) => {
|
|
5
|
+
return (
|
|
6
|
+
document.querySelector(hash) ||
|
|
7
|
+
new Promise((resolve, reject) => {
|
|
8
|
+
if (x > 50) {
|
|
9
|
+
return resolve();
|
|
10
|
+
}
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
resolve(findEl(hash, ++x || 1));
|
|
13
|
+
}, 1000);
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (to.hash) {
|
|
19
|
+
let el = await findEl(to.hash);
|
|
20
|
+
let screen = await findEl(selector);
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
if ('scrollBehavior' in document.documentElement.style) {
|
|
23
|
+
return screen.scrollTo({ top: el.offsetTop, behavior: 'smooth' });
|
|
24
|
+
} else {
|
|
25
|
+
return screen.scrollTo(0, el.offsetTop);
|
|
26
|
+
}
|
|
25
27
|
}
|
|
28
|
+
} else if (to.matched.some(m => m.meta.scrollTo)) {
|
|
29
|
+
return { top: m.meta.scrollTo.top, left: m.meta.scrollTo.left };
|
|
30
|
+
} else if (savedPosition) {
|
|
31
|
+
return { savedPosition };
|
|
32
|
+
} else {
|
|
33
|
+
return { top: 0 };
|
|
26
34
|
}
|
|
27
|
-
} else if (to.matched.some(m => m.meta.scrollTo)) {
|
|
28
|
-
return { top: m.meta.scrollTo.top, left: m.meta.scrollTo.left };
|
|
29
|
-
} else if (savedPosition) {
|
|
30
|
-
return { savedPosition };
|
|
31
|
-
} else {
|
|
32
|
-
return { top: 0 };
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
class="pos-absolute pos-t-regular pos-r-regular i-medium t-transp"
|
|
42
42
|
/>
|
|
43
43
|
|
|
44
|
-
<h2 v-if="recommendation" class="t-main t-semi p-medium">{{t('airecommend')}}</h2>
|
|
45
44
|
<!-- Name -->
|
|
46
45
|
<h1 class="w-100 h1-product mn-b-small">{{ product.name }}</h1>
|
|
47
46
|
<!-- Price -->
|
|
@@ -51,9 +50,6 @@
|
|
|
51
50
|
<SelectElement v-if="sizes2.length > 0" :elements="sizes2" :selected="product.selectedSize" class="mn-r-medium" />
|
|
52
51
|
</div> -->
|
|
53
52
|
<!-- Description -->
|
|
54
|
-
<h3 v-if="recommendation" class="mn-b-semi">
|
|
55
|
-
{{ recommendation }}
|
|
56
|
-
</h3>
|
|
57
53
|
|
|
58
54
|
<Tab
|
|
59
55
|
v-model:selected="tabProduct"
|
|
@@ -69,11 +65,11 @@
|
|
|
69
65
|
<transition name="slide-fade">
|
|
70
66
|
|
|
71
67
|
<div v-if="tabProduct === 'description'" class="pd-medium radius-medium bg-light ">
|
|
72
|
-
<p v-if="product.description && !product.translations < 1
|
|
68
|
+
<p v-if="product.description && !product.translations < 1" class="w-100 t-transp">
|
|
73
69
|
{{ product.description }}
|
|
74
70
|
</p>
|
|
75
71
|
|
|
76
|
-
<p v-if="product.translations && product.translations.length > 1
|
|
72
|
+
<p v-if="product.translations && product.translations.length > 1" class="w-100 t-transp">
|
|
77
73
|
{{ t('description') }}
|
|
78
74
|
</p>
|
|
79
75
|
</div>
|
|
@@ -161,9 +157,6 @@ const props = defineProps({
|
|
|
161
157
|
accesses: {
|
|
162
158
|
type: Object,
|
|
163
159
|
default: null
|
|
164
|
-
},
|
|
165
|
-
recommendation: {
|
|
166
|
-
type: String
|
|
167
160
|
}
|
|
168
161
|
})
|
|
169
162
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import ChatGPT from '@martyrs/src/modules/integrations/openai/openai.globals.js';
|
|
2
|
-
|
|
3
1
|
import queryProcessorCore from '@martyrs/src/modules/core/controllers/utils/queryProcessor.js';
|
|
4
2
|
import queryProcessorProducts from '@martyrs/src/modules/products/controllers/queries/products.queries.js';
|
|
5
3
|
|
|
@@ -158,61 +156,12 @@ const controllerFactory = db => {
|
|
|
158
156
|
res.status(500).send({ message: err.message });
|
|
159
157
|
}
|
|
160
158
|
};
|
|
161
|
-
|
|
162
|
-
const getProductRecommendation = async (req, res) => {
|
|
163
|
-
const { mood } = req.body;
|
|
164
|
-
try {
|
|
165
|
-
const products = await Product.find({
|
|
166
|
-
status: 'published',
|
|
167
|
-
}).limit(40);
|
|
168
|
-
if (!products) {
|
|
169
|
-
console.log('no products');
|
|
170
|
-
return res.status(404).send({ message: 'Products not found.' });
|
|
171
|
-
}
|
|
172
|
-
const productsList = products
|
|
173
|
-
.map(p => {
|
|
174
|
-
const info = p.attributes || [];
|
|
175
|
-
const value0 = info[0] ? info[0].value : '';
|
|
176
|
-
const value1 = info[1] ? `(${info[1].value}%)` : '';
|
|
177
|
-
const value2 = info[2] ? `(${info[2].value})` : '';
|
|
178
|
-
return `${p._id}: ${p.name} (${value0}) ${value1} ${value2}`;
|
|
179
|
-
})
|
|
180
|
-
.join(', ');
|
|
181
|
-
|
|
182
|
-
const prompt = `
|
|
183
|
-
1. When asked how the client wants to feel, they responded "${mood}".
|
|
184
|
-
2. Here is a list of products in our store: ${productsList}.
|
|
185
|
-
3. Based on the attributes about the products (strain, THC content) and the user's desires, choose 1 product to recommend to the user.
|
|
186
|
-
4. The response should be in the language that the user used in mood (${mood}).
|
|
187
|
-
5. Please format your response as a JSON object '{"_id": "ID of the recommended product (it must correspond to one of the product IDs I sent)", "recommendationText": "Text explaining why this particular product"'. Write only the JSON object without any other text outside of it.
|
|
188
|
-
`;
|
|
189
|
-
|
|
190
|
-
// Specify a model explicitly
|
|
191
|
-
const result = await ChatGPT.createChatCompletion(prompt, {
|
|
192
|
-
model: 'gpt-4',
|
|
193
|
-
temperature: 0.8,
|
|
194
|
-
systemPrompt: 'You are a product recommendation specialist with expertise in matching customer needs to product attributes.'
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const recommendedProduct = await Product.findById(result._id);
|
|
198
|
-
|
|
199
|
-
if (!recommendedProduct) {
|
|
200
|
-
console.log(`No product found with _id: ${result._id}`);
|
|
201
|
-
return res.status(404).send({ message: 'Recommended product not found.' });
|
|
202
|
-
}
|
|
203
|
-
res.status(200).json({ product: recommendedProduct, recommendationText: result.recommendationText });
|
|
204
|
-
} catch (err) {
|
|
205
|
-
console.log(err);
|
|
206
|
-
res.status(500).send({ message: err });
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
159
|
|
|
210
160
|
return {
|
|
211
161
|
Create,
|
|
212
162
|
Read,
|
|
213
163
|
Update,
|
|
214
164
|
Delete,
|
|
215
|
-
getProductRecommendation,
|
|
216
165
|
};
|
|
217
166
|
};
|
|
218
167
|
export default controllerFactory;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Product Recommendation (Experiment)
|
|
2
|
+
|
|
3
|
+
AI-powered product recommendation feature using OpenAI GPT-4.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This experiment provides personalized product recommendations based on user mood/preferences using ChatGPT.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- AI-powered product selection based on user mood
|
|
12
|
+
- Integration with OpenAI GPT-4
|
|
13
|
+
- Custom recommendation page with mood input
|
|
14
|
+
- Recommendation display in product details
|
|
15
|
+
|
|
16
|
+
## Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
experiments/product-recommendation/
|
|
20
|
+
├── components/
|
|
21
|
+
│ ├── ProductRecommmendation.vue # Main recommendation page
|
|
22
|
+
│ └── HeroRecommendation.vue # Mood input component
|
|
23
|
+
├── store/
|
|
24
|
+
│ └── recommendation.store.js # Store for recommendation state
|
|
25
|
+
├── controllers/
|
|
26
|
+
│ └── recommendation.controller.js # Backend recommendation logic
|
|
27
|
+
├── routes/
|
|
28
|
+
│ └── recommendation.routes.js # API endpoint
|
|
29
|
+
├── router/
|
|
30
|
+
│ └── recommendation.router.js # Frontend routes
|
|
31
|
+
└── README.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
To use this experiment, you need:
|
|
37
|
+
|
|
38
|
+
1. **OpenAI API Key** in your `.env` file:
|
|
39
|
+
```
|
|
40
|
+
OPENAI_API_KEY=your-api-key-here
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
2. **OpenAI integration** at `/martyrs/src/modules/integrations/openai/openai.globals.js`
|
|
44
|
+
|
|
45
|
+
## How to Enable
|
|
46
|
+
|
|
47
|
+
### 1. Backend Setup
|
|
48
|
+
|
|
49
|
+
Add to your `products.server.js`:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
import recommendationRoutes from './experiments/product-recommendation/routes/recommendation.routes.js';
|
|
53
|
+
|
|
54
|
+
// In initialize function:
|
|
55
|
+
recommendationRoutes(app, db);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Frontend Store
|
|
59
|
+
|
|
60
|
+
Add to your `products.client.js`:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import * as storeRecommendation from './experiments/product-recommendation/store/recommendation.store.js';
|
|
64
|
+
|
|
65
|
+
// In initialize function:
|
|
66
|
+
store.addStore('recommendation', storeRecommendation);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Frontend Routes
|
|
70
|
+
|
|
71
|
+
Import and add routes from `./experiments/product-recommendation/router/recommendation.router.js`:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { recommendationRoutes } from './experiments/product-recommendation/router/recommendation.router.js';
|
|
75
|
+
|
|
76
|
+
// Add to your routes:
|
|
77
|
+
...recommendationRoutes.homeRoutes,
|
|
78
|
+
...recommendationRoutes.organizationRoutes,
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4. Component Exports
|
|
82
|
+
|
|
83
|
+
Add to `products.client.js` exports:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import HeroRecommendation from './experiments/product-recommendation/components/HeroRecommendation.vue';
|
|
87
|
+
import ProductRecommendation from './experiments/product-recommendation/components/ProductRecommmendation.vue';
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
// ... other exports
|
|
91
|
+
HeroRecommendation,
|
|
92
|
+
ProductRecommendation,
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 5. SectionProduct Integration (Optional)
|
|
97
|
+
|
|
98
|
+
To show recommendations in product details, add to `SectionProduct.vue`:
|
|
99
|
+
|
|
100
|
+
```vue
|
|
101
|
+
<template>
|
|
102
|
+
<SectionProduct
|
|
103
|
+
:product="product"
|
|
104
|
+
:recommendation="recommendation.state.current.recommendation"
|
|
105
|
+
/>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<script>
|
|
109
|
+
import * as recommendation from '../experiments/product-recommendation/store/recommendation.store.js';
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
And add prop to `SectionProduct.vue`:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
defineProps({
|
|
117
|
+
// ... other props
|
|
118
|
+
recommendation: {
|
|
119
|
+
type: String
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
1. Navigate to `/products/recommendation`
|
|
127
|
+
2. Enter your mood/preference
|
|
128
|
+
3. Get AI-powered product recommendation
|
|
129
|
+
4. View recommended product details
|
|
130
|
+
|
|
131
|
+
## API Endpoint
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
POST /api/product/recommended
|
|
135
|
+
Body: { mood: "your mood here" }
|
|
136
|
+
Response: { product: {...}, recommendationText: "..." }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- Requires OpenAI API key and valid subscription
|
|
142
|
+
- Uses GPT-4 model for better recommendations
|
|
143
|
+
- Customize prompt in `recommendation.controller.js`
|
|
144
|
+
- Product attributes (strain, THC content) are used for matching
|
|
145
|
+
|
|
146
|
+
## Removed from Core
|
|
147
|
+
|
|
148
|
+
This feature was moved to experiments to:
|
|
149
|
+
- Remove OpenAI dependency from core products module
|
|
150
|
+
- Make it optional for projects that don't need AI recommendations
|
|
151
|
+
- Allow easier customization and experimentation
|
|
152
|
+
- Reduce bundle size for projects without AI features
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
Part of @ozdao/martyrs framework.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import ChatGPT from '@martyrs/src/modules/integrations/openai/openai.globals.js';
|
|
2
|
+
|
|
3
|
+
const controllerFactory = db => {
|
|
4
|
+
const Product = db.product;
|
|
5
|
+
|
|
6
|
+
const getProductRecommendation = async (req, res) => {
|
|
7
|
+
const { mood } = req.body;
|
|
8
|
+
try {
|
|
9
|
+
const products = await Product.find({
|
|
10
|
+
status: 'published',
|
|
11
|
+
}).limit(40);
|
|
12
|
+
if (!products) {
|
|
13
|
+
console.log('no products');
|
|
14
|
+
return res.status(404).send({ message: 'Products not found.' });
|
|
15
|
+
}
|
|
16
|
+
const productsList = products
|
|
17
|
+
.map(p => {
|
|
18
|
+
const info = p.attributes || [];
|
|
19
|
+
const value0 = info[0] ? info[0].value : '';
|
|
20
|
+
const value1 = info[1] ? `(${info[1].value}%)` : '';
|
|
21
|
+
const value2 = info[2] ? `(${info[2].value})` : '';
|
|
22
|
+
return `${p._id}: ${p.name} (${value0}) ${value1} ${value2}`;
|
|
23
|
+
})
|
|
24
|
+
.join(', ');
|
|
25
|
+
|
|
26
|
+
const prompt = `
|
|
27
|
+
1. When asked how the client wants to feel, they responded "${mood}".
|
|
28
|
+
2. Here is a list of products in our store: ${productsList}.
|
|
29
|
+
3. Based on the attributes about the products (strain, THC content) and the user's desires, choose 1 product to recommend to the user.
|
|
30
|
+
4. The response should be in the language that the user used in mood (${mood}).
|
|
31
|
+
5. Please format your response as a JSON object '{"_id": "ID of the recommended product (it must correspond to one of the product IDs I sent)", "recommendationText": "Text explaining why this particular product"'. Write only the JSON object without any other text outside of it.
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// Specify a model explicitly
|
|
35
|
+
const result = await ChatGPT.createChatCompletion(prompt, {
|
|
36
|
+
model: 'gpt-4',
|
|
37
|
+
temperature: 0.8,
|
|
38
|
+
systemPrompt: 'You are a product recommendation specialist with expertise in matching customer needs to product attributes.'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const recommendedProduct = await Product.findById(result._id);
|
|
42
|
+
|
|
43
|
+
if (!recommendedProduct) {
|
|
44
|
+
console.log(`No product found with _id: ${result._id}`);
|
|
45
|
+
return res.status(404).send({ message: 'Recommended product not found.' });
|
|
46
|
+
}
|
|
47
|
+
res.status(200).json({ product: recommendedProduct, recommendationText: result.recommendationText });
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.log(err);
|
|
50
|
+
res.status(500).send({ message: err });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
getProductRecommendation,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default controllerFactory;
|
package/src/modules/products/experiments/product-recommendation/router/recommendation.router.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Product Recommendation Routes
|
|
2
|
+
// These routes can be added to products router if needed
|
|
3
|
+
|
|
4
|
+
export const recommendationRoutes = {
|
|
5
|
+
// Home context routes
|
|
6
|
+
homeRoutes: [
|
|
7
|
+
{
|
|
8
|
+
path: 'recommendation',
|
|
9
|
+
name: 'ProductRecommmendation',
|
|
10
|
+
meta: {
|
|
11
|
+
title: {
|
|
12
|
+
en: 'Product Recommmendation',
|
|
13
|
+
ru: 'Рекомендации продукта',
|
|
14
|
+
},
|
|
15
|
+
header_theme: 'dark',
|
|
16
|
+
footer_theme: 'dark',
|
|
17
|
+
},
|
|
18
|
+
component: () => import(/* webpackChunkName: 'products-recommendation' */ '../components/ProductRecommmendation.vue'),
|
|
19
|
+
props: route => ({ mood: route.query.mood }),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
// Organization context routes
|
|
24
|
+
organizationRoutes: [
|
|
25
|
+
{
|
|
26
|
+
path: 'recommendation',
|
|
27
|
+
name: 'Organization_ProductRecommmendation',
|
|
28
|
+
meta: {
|
|
29
|
+
title: {
|
|
30
|
+
en: 'Product Recommmendation',
|
|
31
|
+
ru: 'Рекомендации продукта',
|
|
32
|
+
},
|
|
33
|
+
header_theme: 'dark',
|
|
34
|
+
footer_theme: 'dark',
|
|
35
|
+
context: 'organization',
|
|
36
|
+
},
|
|
37
|
+
component: () => import(/* webpackChunkName: 'products-recommendation' */ '../components/ProductRecommmendation.vue'),
|
|
38
|
+
props: route => ({ mood: route.query.mood }),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default recommendationRoutes;
|
package/src/modules/products/experiments/product-recommendation/routes/recommendation.routes.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import controllerFactory from '../controllers/recommendation.controller.js';
|
|
2
|
+
|
|
3
|
+
const routesFactory = (app, db) => {
|
|
4
|
+
const controller = controllerFactory(db);
|
|
5
|
+
|
|
6
|
+
// Product recommendation endpoint
|
|
7
|
+
app.post('/api/product/recommended', controller.getProductRecommendation);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default routesFactory;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { reactive } from 'vue';
|
|
2
|
+
import $axios from '@martyrs/src/modules/core/plugins/axios.js';
|
|
3
|
+
|
|
4
|
+
const state = reactive({
|
|
5
|
+
current: {
|
|
6
|
+
recommendation: false,
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const actions = {
|
|
11
|
+
async submitMood(presetMood = null) {
|
|
12
|
+
return $axios.post('/api/product/recommended', { mood: presetMood }).then(
|
|
13
|
+
response => {
|
|
14
|
+
state.current = response.data.product;
|
|
15
|
+
state.current.recommendation = response.data.recommendationText;
|
|
16
|
+
return Promise.resolve(response.data);
|
|
17
|
+
},
|
|
18
|
+
error => {
|
|
19
|
+
console.error('Recommendation error:', error);
|
|
20
|
+
return Promise.reject(error);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const mutations = {
|
|
27
|
+
resetRecommendation() {
|
|
28
|
+
state.current = {
|
|
29
|
+
recommendation: false,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
state,
|
|
36
|
+
actions,
|
|
37
|
+
mutations,
|
|
38
|
+
};
|
|
@@ -19,7 +19,6 @@ import ProductImages from './components/blocks/ProductImages.vue';
|
|
|
19
19
|
// Sections
|
|
20
20
|
import EditVariants from './components/sections/EditVariants.vue';
|
|
21
21
|
import FilterProducts from './components/sections/FilterProducts.vue';
|
|
22
|
-
import HeroRecommendation from './components/sections/HeroRecommendation.vue';
|
|
23
22
|
import ProductsPopular from './components/sections/ProductsPopular.vue';
|
|
24
23
|
import SectionProduct from './components/sections/SectionProduct.vue';
|
|
25
24
|
|
|
@@ -28,7 +27,6 @@ import Price from './components/elements/Price.vue';
|
|
|
28
27
|
// Pages
|
|
29
28
|
import Product from './components/pages/Product.vue';
|
|
30
29
|
import ProductEdit from './components/pages/ProductEdit.vue';
|
|
31
|
-
import ProductRecommendation from './components/pages/ProductRecommmendation.vue';
|
|
32
30
|
import Products from './components/pages/Products.vue';
|
|
33
31
|
|
|
34
32
|
// Пример функции инициализации для модуля продуктов
|
|
@@ -69,14 +67,12 @@ const ModuleProducts = {
|
|
|
69
67
|
CardPosition,
|
|
70
68
|
// Sections
|
|
71
69
|
SectionProduct,
|
|
72
|
-
HeroRecommendation,
|
|
73
70
|
FilterProducts,
|
|
74
71
|
EditVariants,
|
|
75
72
|
ProductsPopular,
|
|
76
73
|
// Pages
|
|
77
74
|
Product,
|
|
78
75
|
ProductEdit,
|
|
79
|
-
ProductRecommendation,
|
|
80
76
|
Products,
|
|
81
77
|
// Layouts
|
|
82
78
|
},
|
|
@@ -89,7 +85,6 @@ export {
|
|
|
89
85
|
CardProduct,
|
|
90
86
|
EditVariants,
|
|
91
87
|
FilterProducts,
|
|
92
|
-
HeroRecommendation,
|
|
93
88
|
Image360,
|
|
94
89
|
// Blocks
|
|
95
90
|
ProductImages,
|
|
@@ -99,7 +94,6 @@ export {
|
|
|
99
94
|
// Pages
|
|
100
95
|
Product,
|
|
101
96
|
ProductEdit,
|
|
102
|
-
ProductRecommendation,
|
|
103
97
|
Products,
|
|
104
98
|
// Sections
|
|
105
99
|
SectionProduct,
|
|
@@ -74,20 +74,6 @@ export function getRoutes(options = {}) {
|
|
|
74
74
|
],
|
|
75
75
|
component: () => import(/* webpackChunkName: "products-edit" */ './components/pages/ProductEdit.vue'),
|
|
76
76
|
},
|
|
77
|
-
{
|
|
78
|
-
path: 'recommendation',
|
|
79
|
-
name: 'ProductRecommmendation',
|
|
80
|
-
meta: {
|
|
81
|
-
title: {
|
|
82
|
-
en: 'Product Recommmendation',
|
|
83
|
-
ru: 'Рекомендации продукта',
|
|
84
|
-
},
|
|
85
|
-
header_theme: 'dark',
|
|
86
|
-
footer_theme: 'dark',
|
|
87
|
-
},
|
|
88
|
-
component: () => import(/* webpackChunkName: 'products-recommendation' */ './components/pages/ProductRecommmendation.vue'),
|
|
89
|
-
props: route => ({ mood: route.query.mood }),
|
|
90
|
-
},
|
|
91
77
|
],
|
|
92
78
|
}
|
|
93
79
|
});
|
|
@@ -162,21 +148,6 @@ export function getRoutes(options = {}) {
|
|
|
162
148
|
],
|
|
163
149
|
component: () => import(/* webpackChunkName: "products-edit" */ './components/pages/ProductEdit.vue'),
|
|
164
150
|
},
|
|
165
|
-
{
|
|
166
|
-
path: 'recommendation',
|
|
167
|
-
name: 'Organization_ProductRecommmendation',
|
|
168
|
-
meta: {
|
|
169
|
-
title: {
|
|
170
|
-
en: 'Product Recommmendation',
|
|
171
|
-
ru: 'Рекомендации продукта',
|
|
172
|
-
},
|
|
173
|
-
header_theme: 'dark',
|
|
174
|
-
footer_theme: 'dark',
|
|
175
|
-
context: 'organization',
|
|
176
|
-
},
|
|
177
|
-
component: () => import(/* webpackChunkName: 'products-recommendation' */ './components/pages/ProductRecommmendation.vue'),
|
|
178
|
-
props: route => ({ mood: route.query.mood }),
|
|
179
|
-
},
|
|
180
151
|
],
|
|
181
152
|
}
|
|
182
153
|
});
|
|
@@ -81,20 +81,6 @@ export function getRoutes(options = {}) {
|
|
|
81
81
|
beforeEnter: [validationAuth.requiresAuth],
|
|
82
82
|
component: () => import(/* webpackChunkName: "products-edit" */ '../components/pages/ProductEdit.vue'),
|
|
83
83
|
},
|
|
84
|
-
{
|
|
85
|
-
path: 'recommendation',
|
|
86
|
-
name: 'ProductRecommmendation',
|
|
87
|
-
meta: {
|
|
88
|
-
title: {
|
|
89
|
-
en: 'Product Recommmendation',
|
|
90
|
-
ru: 'Рекомендации продукта',
|
|
91
|
-
},
|
|
92
|
-
header_theme: 'dark',
|
|
93
|
-
footer_theme: 'dark',
|
|
94
|
-
},
|
|
95
|
-
component: () => import(/* webpackChunkName: 'products-recommendation' */ '../components/pages/ProductRecommmendation.vue'),
|
|
96
|
-
props: route => ({ mood: route.query.mood }),
|
|
97
|
-
},
|
|
98
84
|
],
|
|
99
85
|
},
|
|
100
86
|
});
|
|
@@ -275,20 +261,6 @@ export function getRoutes(options = {}) {
|
|
|
275
261
|
],
|
|
276
262
|
component: () => import(/* webpackChunkName: "products-edit" */ '../components/pages/ProductEdit.vue'),
|
|
277
263
|
},
|
|
278
|
-
{
|
|
279
|
-
path: 'recommendation',
|
|
280
|
-
name: 'ProductRecommmendation',
|
|
281
|
-
meta: {
|
|
282
|
-
title: {
|
|
283
|
-
en: 'Product Recommmendation',
|
|
284
|
-
ru: 'Рекомендации продукта',
|
|
285
|
-
},
|
|
286
|
-
header_theme: 'dark',
|
|
287
|
-
footer_theme: 'dark',
|
|
288
|
-
},
|
|
289
|
-
component: () => import(/* webpackChunkName: 'products-recommendation' */ '../components/pages/ProductRecommmendation.vue'),
|
|
290
|
-
props: route => ({ mood: route.query.mood }),
|
|
291
|
-
},
|
|
292
264
|
],
|
|
293
265
|
},
|
|
294
266
|
});
|
|
@@ -9,6 +9,4 @@ export default (function (app, db, allowedOrigins) {
|
|
|
9
9
|
app.post('/api/products/:_id', controller.Update);
|
|
10
10
|
// (D) Delete product
|
|
11
11
|
app.delete('/api/products/:_id', controller.Delete);
|
|
12
|
-
|
|
13
|
-
app.post('/api/product/recommended', controller.getProductRecommendation);
|
|
14
12
|
});
|