@ozdao/martyrs 0.2.567 → 0.2.569
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/Feed/Carousel.vue.js +1 -1
- package/dist/martyrs/src/components/Feed/Feed.vue.js +1 -1
- package/dist/martyrs/src/components/FieldBig/FieldBig.vue.js +1 -1
- 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.vue.js → Menu.vue2.js} +2 -2
- package/dist/martyrs/src/components/Menu/Menu.vue2.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/SelectMulti/{SelectMulti.vue2.js → SelectMulti.vue.js} +2 -2
- package/dist/martyrs/src/components/SelectMulti/SelectMulti.vue.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/EnterPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Invite.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/Profile.vue.js +2 -2
- 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 +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/ResetPassword.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignIn.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/pages/SignUp.vue.js +1 -1
- package/dist/martyrs/src/modules/auth/views/components/sections/ProfileEditCredentials.vue.js +1 -1
- package/dist/martyrs/src/modules/backoffice/components/partials/Sidebar.vue.js +1 -1
- 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 +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 +8 -7
- package/dist/martyrs/src/modules/core/views/components/layouts/Client.vue.js.map +1 -1
- 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/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 +3 -3
- package/dist/martyrs/src/modules/events/components/pages/EditEventTickets.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/pages/Event.vue.js +1 -1
- package/dist/martyrs/src/modules/events/components/sections/EditTickets.vue.js +1 -1
- 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 +3 -3
- package/dist/martyrs/src/modules/inventory/components/forms/AdjustmentForm.vue.js +2 -2
- 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 +2 -2
- package/dist/martyrs/src/modules/inventory/components/pages/InventoryEdit.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 +2 -2
- package/dist/martyrs/src/modules/music/components/forms/ArtistForm.vue.js +3 -3
- package/dist/martyrs/src/modules/music/components/forms/PlaylistForm.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/forms/SearchForm.vue.js +1 -1
- package/dist/martyrs/src/modules/music/components/forms/TrackForm.vue.js +2 -2
- package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js +4 -4
- package/dist/martyrs/src/modules/music/components/layouts/MusicBottomPlayer.vue.js.map +1 -1
- 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 +2 -2
- package/dist/martyrs/src/modules/orders/components/forms/FormCustomerDetails.vue.js +2 -2
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/dist/martyrs/src/modules/organizations/components/forms/InviteForm.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/Organization.vue.js +1 -1
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationBackoffice.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/pages/OrganizationEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/organizations/components/sections/Documents.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/sections/MembersAdd.vue.js +2 -2
- package/dist/martyrs/src/modules/organizations/components/sections/Organizations.vue.js +2 -2
- package/dist/martyrs/src/modules/pages/views/components/blocks/CardPage.vue.js +1 -1
- package/dist/martyrs/src/modules/pages/views/components/pages/PageEdit.vue.js +1 -1
- 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 +3 -3
- package/dist/martyrs/src/modules/products/components/pages/Product.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/pages/ProductEdit.vue.js +3 -3
- package/dist/martyrs/src/modules/products/components/sections/EditAttributes.vue.js +1 -1
- package/dist/martyrs/src/modules/products/components/sections/EditDiscounts.vue.js +2 -2
- package/dist/martyrs/src/modules/products/components/sections/EditVariants.vue.js +2 -2
- 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 +1 -1
- package/dist/martyrs/src/modules/reports/components/sections/FormReport.vue.js +2 -2
- package/dist/martyrs/src/modules/spots/components/blocks/SpotMemberModify.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 +2 -2
- package/dist/martyrs/src/modules/spots/components/sections/WorktimeEdit.vue.js +2 -2
- package/dist/martyrs/src/modules/wallet/views/components/blocks/CryptoDeposit.vue.js +1 -1
- 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/components/layouts/Client.vue +12 -6
- package/src/modules/music/components/layouts/MusicBottomPlayer.vue +1 -3
- 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/Loader/Loader.vue.js.map +0 -1
- package/dist/martyrs/src/components/Menu/Menu.vue.js.map +0 -1
- package/dist/martyrs/src/components/Select/Select.vue.js.map +0 -1
- package/dist/martyrs/src/components/SelectMulti/SelectMulti.vue2.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/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/package.json
CHANGED
|
@@ -176,12 +176,6 @@
|
|
|
176
176
|
</router-view>
|
|
177
177
|
</Suspense>
|
|
178
178
|
</div>
|
|
179
|
-
|
|
180
|
-
<component
|
|
181
|
-
v-if="route.meta.player"
|
|
182
|
-
class="z-index-2"
|
|
183
|
-
:is="route.meta.player"
|
|
184
|
-
/>
|
|
185
179
|
|
|
186
180
|
<transition @before-enter="scrollTop" name="scaleTransition" mode="out-in" appear>
|
|
187
181
|
<component
|
|
@@ -193,10 +187,18 @@
|
|
|
193
187
|
:location="route.meta.location"
|
|
194
188
|
/>
|
|
195
189
|
</transition>
|
|
190
|
+
|
|
191
|
+
<component
|
|
192
|
+
v-if="route.meta.player"
|
|
193
|
+
id="player-wrapper"
|
|
194
|
+
class="z-index-2 "
|
|
195
|
+
:is="route.meta.player"
|
|
196
|
+
/>
|
|
196
197
|
</div>
|
|
197
198
|
</div>
|
|
198
199
|
</section>
|
|
199
200
|
|
|
201
|
+
|
|
200
202
|
<router-view
|
|
201
203
|
name="defaultBottom"
|
|
202
204
|
v-slot="{ Component, route }"
|
|
@@ -211,6 +213,8 @@
|
|
|
211
213
|
class="z-index-2"
|
|
212
214
|
:is="route.meta.bottombar"
|
|
213
215
|
/>
|
|
216
|
+
|
|
217
|
+
|
|
214
218
|
</div>
|
|
215
219
|
</template>
|
|
216
220
|
|
|
@@ -530,4 +534,6 @@
|
|
|
530
534
|
transform: scale(0.95);
|
|
531
535
|
> section,div { transform: translateZ(-30px); transition: all 0.5s ease; }
|
|
532
536
|
}
|
|
537
|
+
|
|
538
|
+
|
|
533
539
|
</style>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!-- components/layouts/MusicBottomPlayer.vue -->
|
|
2
2
|
<template>
|
|
3
|
-
|
|
4
|
-
<MusicPlayer v-if="currentTrack && !showFullPlayer" class="pos-absolute pos-b-0 w-100" />
|
|
3
|
+
<MusicPlayer v-if="currentTrack && !showFullPlayer" class="pos-sticky pos-b-0 w-100" />
|
|
5
4
|
|
|
6
5
|
<BottomSheet
|
|
7
6
|
v-if="showFullPlayer"
|
|
@@ -10,7 +9,6 @@
|
|
|
10
9
|
>
|
|
11
10
|
<FullscreenPlayer />
|
|
12
11
|
</BottomSheet>
|
|
13
|
-
</div>
|
|
14
12
|
</template>
|
|
15
13
|
|
|
16
14
|
<script setup>
|
|
@@ -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
|
});
|
|
@@ -12,7 +12,6 @@ import $axios from '@martyrs/src/modules/core/views/utils/axios-instance.js';
|
|
|
12
12
|
const state = reactive({
|
|
13
13
|
all: [],
|
|
14
14
|
current: {
|
|
15
|
-
recommendation: false,
|
|
16
15
|
category: [],
|
|
17
16
|
status: 'draft',
|
|
18
17
|
listing: 'sale',
|
|
@@ -132,20 +131,6 @@ const actions = {
|
|
|
132
131
|
);
|
|
133
132
|
},
|
|
134
133
|
|
|
135
|
-
async submitMood(presetMood = null) {
|
|
136
|
-
return $axios.post('/api/product/recommended', { mood: presetMood }).then(
|
|
137
|
-
response => {
|
|
138
|
-
state.current = response.data.product;
|
|
139
|
-
state.current.recommendation = response.data.recommendationText;
|
|
140
|
-
return Promise.resolve(response.data);
|
|
141
|
-
},
|
|
142
|
-
error => {
|
|
143
|
-
setError(error);
|
|
144
|
-
return Promise.reject(error);
|
|
145
|
-
}
|
|
146
|
-
);
|
|
147
|
-
},
|
|
148
|
-
|
|
149
134
|
async fetchProducts() {
|
|
150
135
|
return await $axios.get(`/api/products/read`).then(
|
|
151
136
|
products => {
|
|
@@ -214,7 +199,6 @@ const mutations = {
|
|
|
214
199
|
resetProduct(product) {
|
|
215
200
|
state.current = {
|
|
216
201
|
included: null,
|
|
217
|
-
recommendation: false,
|
|
218
202
|
category: [],
|
|
219
203
|
status: 'draft',
|
|
220
204
|
attributes: [],
|