Skip to content

Commit 9fa78f0

Browse files
authored
Merge pull request #125 from BuildFire/ai-seeder
AI seeder init commit
2 parents eb5feeb + fec118d commit 9fa78f0

File tree

12 files changed

+343
-21
lines changed

12 files changed

+343
-21
lines changed

control/content/app.services.js

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,291 @@
164164
return deferred.promise;
165165
}
166166
};
167-
}])
167+
}]).factory('StateSeeder', ['Context', 'LoyaltyAPI', '$rootScope', 'Buildfire', '$timeout' ,function(Context, LoyaltyAPI, $rootScope, Buildfire, $timeout) {
168+
let itemsList;
169+
let orderedItems = [];
170+
let currentUser;
171+
let currentContext;
172+
let stateSeederInstance;
173+
let generateJSONTemplate = {
174+
items: [
175+
{
176+
title: "",
177+
pointsToRedeem: 0,
178+
description: "",
179+
listImage: "",
180+
},
181+
],
182+
};
183+
let importJSONTemplate = {
184+
items: [
185+
{
186+
title: "",
187+
pointsToRedeem: 0,
188+
pointsPerItem: 0,
189+
description: "",
190+
listImage: "",
191+
},
192+
],
193+
};
194+
let handleAIReq = function(isImport, err, data) {
195+
if (
196+
err ||
197+
!data ||
198+
typeof data !== "object" ||
199+
!Object.keys(data).length || !data.data || !data.data.items || !data.data.items.length
200+
) {
201+
return buildfire.dialog.toast({
202+
message: "Bad AI request, please try changing your request.",
203+
type: "danger",
204+
});
205+
}
206+
let initPromises = [
207+
new Promise((resolve, reject) => {
208+
getCurrentUser().then((user) => {
209+
currentUser = user;
210+
resolve();
211+
}).catch(err => reject(err))
212+
}),
213+
new Promise((resolve, reject) => {
214+
Context.getContext().then(context => {
215+
currentContext = context;
216+
resolve();
217+
}).catch(err => reject(err))
218+
})
219+
]
220+
Promise.all(initPromises).then(() => {
221+
itemsList = data.data.items;
222+
//Check image URLs
223+
let items = itemsList.map((item, i) => {
224+
return new Promise((resolve, reject) => {
225+
elimanateNotFoundImages(item.listImage).then(res => {
226+
if (res.isValid) {
227+
item.listImage = res.newURL;
228+
item.order = i;
229+
orderedItems.push(item);
230+
resolve(item);
231+
} else {
232+
reject('image URL not valid');
233+
}
234+
})
235+
})
236+
})
237+
238+
Promise.allSettled(items).then(results => {
239+
itemsList = [];
240+
results.forEach(res => {
241+
if(res.status == 'fulfilled') {
242+
const item = res.value;
243+
if (item) {
244+
itemsList.push(item);
245+
}
246+
}
247+
})
248+
if (!itemsList.length) {
249+
stateSeederInstance?.requestResult?.complete();
250+
return buildfire.dialog.toast({
251+
message: isImport ? "Each row must have a valid image URL." : "Bad AI request, please try changing your request.",
252+
type: "danger",
253+
});
254+
}
255+
256+
// reset old data
257+
deleteAll().then(() => {
258+
// save new data
259+
let promises = itemsList.map((item, i) => {
260+
return new Promise((resolve, reject) => {
261+
itemsList[i] = _applyDefaults(item);
262+
LoyaltyAPI.addReward(itemsList[i]).then(result => {
263+
console.info('Saved data result: ', result);
264+
itemsList[i].deepLinkUrl = Buildfire.deeplink.createLink({id: result._id});
265+
itemsList[i] = Object.assign(itemsList[i], result);
266+
resolve();
267+
})
268+
.catch(err => {
269+
console.error('Error while saving data : ', err);
270+
resolve('Error while saving data : ', err);
271+
})
272+
})
273+
})
274+
Promise.allSettled(promises).then(res => {
275+
if (isImport) {
276+
let sortedItems = orderedItems.sort((a,b) => a.order - b.order);
277+
let sortedIds =[];
278+
LoyaltyAPI.getRewards(currentContext.instanceId).then(results => {
279+
sortedItems.forEach(item => {
280+
if (results) {
281+
results.forEach(result => {
282+
if (item.title == result.title && item.listImage == result.listImage ) {
283+
sortedIds.push(result._id);
284+
}
285+
})
286+
}
287+
})
288+
results.forEach(result => {
289+
if (!sortedIds.includes(result._id))
290+
sortedIds.push(result._id);
291+
})
292+
const data = {
293+
appId: currentContext.appId,
294+
loyaltyUnqiueId: currentContext.instanceId,
295+
userToken: currentUser && currentUser.userToken,
296+
auth: currentUser && currentUser.auth,
297+
loyaltyRewardIds: sortedIds
298+
}
299+
LoyaltyAPI.sortRewards(data).finally(() => {
300+
$timeout(() => {
301+
sortedItems = [];
302+
orderedItems = [];
303+
$rootScope.reloadRewards = true;
304+
buildfire.messaging.sendMessageToWidget({
305+
type: 'refresh'
306+
});
307+
stateSeederInstance?.requestResult?.complete();
308+
})
309+
});
310+
});
311+
} else {
312+
$timeout(() => {
313+
$rootScope.reloadRewards = true;
314+
orderedItems = [];
315+
buildfire.messaging.sendMessageToWidget({
316+
type: 'refresh'
317+
});
318+
stateSeederInstance?.requestResult?.complete();
319+
})
320+
}
321+
})
322+
}).catch(err => console.warn('old data delete error', err));
323+
})
324+
}).catch(err => {
325+
console.error(err);
326+
stateSeederInstance?.requestResult?.complete();
327+
})
328+
}
329+
330+
// UTILITIES
331+
let _applyDefaults = function(item) {
332+
if (item.title) {
333+
const points = checkPoints(item.pointsToRedeem, item.pointsPerItem);
334+
return {
335+
title: item.title,
336+
pointsToRedeem: points.pointsToRedeem,
337+
description: item.description || "",
338+
listImage: item.listImage || "",
339+
pointsPerItem: points.pointsPerItem,
340+
appId: currentContext.appId,
341+
loyaltyUnqiueId: currentContext.instanceId,
342+
userToken: currentUser && currentUser.userToken,
343+
auth: currentUser && currentUser.auth,
344+
}
345+
}
346+
return null
347+
}
348+
349+
let checkPoints = function(pointsToRedeem, pointsPerItem) {
350+
let points = {
351+
pointsToRedeem: 0,
352+
pointsPerItem: 0,
353+
};
354+
if (pointsToRedeem && pointsToRedeem > 0) {
355+
points.pointsToRedeem = pointsToRedeem;
356+
} else {
357+
points.pointsToRedeem = 100;
358+
}
359+
360+
if (pointsPerItem && pointsPerItem > 0) {
361+
points.pointsPerItem = pointsPerItem;
362+
} else {
363+
points.pointsPerItem = Math.ceil(points.pointsToRedeem * 0.1);
364+
}
365+
return points;
366+
}
367+
368+
let elimanateNotFoundImages = function(url) {
369+
const optimisedURL = url.replace('1080x720', '100x100');
370+
return new Promise((resolve) => {
371+
if (url.includes("http")){
372+
const xhr = new XMLHttpRequest();
373+
xhr.open("GET", optimisedURL);
374+
xhr.onerror = (error) => {
375+
console.warn('provided URL is not a valid image', error);
376+
resolve({isValid: false, newURL: null});
377+
}
378+
xhr.onload = () => {
379+
if (xhr.responseURL.includes('source-404') || xhr.status == 404) {
380+
return resolve({isValid: false ,newURL: null});
381+
} else {
382+
return resolve({isValid: true, newURL: xhr.responseURL.replace('h=100', 'h=720').replace('w=100', 'w=1080') });
383+
}
384+
};
385+
xhr.send();
386+
} else resolve(false);
387+
});
388+
};
389+
390+
let deleteAll = function() {
391+
const data = {
392+
userToken: currentUser.userToken,
393+
auth: currentUser.auth,
394+
appId: currentContext.appId
395+
};
396+
return new Promise(resolve => {
397+
if (stateSeederInstance.requestResult.resetData){
398+
LoyaltyAPI.getRewards(currentContext.instanceId).then(items => {
399+
const promises = items.map((item) => deleteItem(item._id, data));
400+
resolve(Promise.all(promises));
401+
}).catch(err => console.warn('old data get error', err));
402+
}
403+
else {
404+
resolve();
405+
}
406+
})
407+
}
408+
409+
let deleteItem = function(itemId, data) {
410+
return new Promise((resolve, reject) => {
411+
LoyaltyAPI.removeReward(itemId, data).then(res => {
412+
Deeplink.deleteById(itemId);
413+
resolve(res);
414+
}).catch(err => {
415+
if (err) reject(err);
416+
})
417+
});
418+
}
419+
420+
let getCurrentUser = function() {
421+
return new Promise(resolve => {
422+
buildfire.auth.getCurrentUser(function (err, user) {
423+
if (user && user._cpUser) {
424+
resolve(user._cpUser);
425+
}
426+
});
427+
})
428+
}
429+
430+
return {
431+
initStateSeeder: function() {
432+
stateSeederInstance = new buildfire.components.aiStateSeeder({
433+
generateOptions: {
434+
userMessage: `Generate a sample of redeemable items for a new [business-type].`,
435+
maxRecords: 5,
436+
systemMessage:
437+
'listImage URL related to title and the list type. use source.unsplash.com for image URL, URL should not have premium_photo or source.unsplash.com/random, cost to redeem which is a number greater than zero and less than 100, return description as HTML.',
438+
jsonTemplate: generateJSONTemplate,
439+
callback: handleAIReq.bind(this, false),
440+
hintText: 'Replace values between brackets to match your requirements.',
441+
},
442+
importOptions: {
443+
jsonTemplate: importJSONTemplate,
444+
sampleCSV: "Hotel Voucher, 50, 10, Redeem this voucher for a one-night stay at a luxurious hotel of your choice, https://source.unsplash.com/featured/?hotel\nFlight Upgrade, 30, 15, Upgrade your economy class ticket to business class, https://source.unsplash.com/featured/?flight\nCity Tour, 20, 5, Explore the city with a guided tour that covers all the major attractions and landmarks, https://source.unsplash.com/featured/?city\nAdventure Activity, 80, 30, Embark on an adrenaline-pumping adventure activity such as bungee jumping or skydiving, https://source.unsplash.com/featured/?adventure",
445+
maxRecords: 15,
446+
hintText: 'Each row should start with a loyalty item title, cost to redeem, points earned, description, and image URL.',
447+
systemMessage: ``,
448+
callback: handleAIReq.bind(this, true),
449+
},
450+
}).smartShowEmptyState();
451+
},
452+
}
453+
}])
168454
})(window.angular, window.buildfire);

control/content/assets/css/style.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,4 @@ i {
186186
background-color:white;
187187
bottom:0px;
188188
z-index:1;
189-
}
189+
}

0 commit comments

Comments
 (0)