@learnpack/learnpack 5.0.308 → 5.0.309

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.308",
4
+ "version": "5.0.309",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -1,21 +1,21 @@
1
- import axios from "axios"
2
- import { franc } from "franc"
3
- import { BREATHECODE_HOST, DEV_MODE, RIGOBOT_HOST } from "./constants"
4
- import { Lesson } from "../components/LessonItem"
5
- import { randomUUID } from "./creatorUtils"
6
- import { Syllabus } from "./store"
1
+ import axios from "axios";
2
+ import { franc } from "franc";
3
+ import { BREATHECODE_HOST, DEV_MODE, RIGOBOT_HOST } from "./constants";
4
+ import { Lesson } from "../components/LessonItem";
5
+ import { randomUUID } from "./creatorUtils";
6
+ import { Syllabus } from "./store";
7
7
 
8
8
  export function parseLesson(input: string, previous: Lesson[]): Lesson | null {
9
- const pattern = /^([\d.]+)\s*-\s*(.*?)\s*\[(\w+):\s*(.+)\]$/
10
- const match = input.match(pattern)
9
+ const pattern = /^([\d.]+)\s*-\s*(.*?)\s*\[(\w+):\s*(.+)\]$/;
10
+ const match = input.match(pattern);
11
11
 
12
- if (!match) return null
12
+ if (!match) return null;
13
13
 
14
- const [, index, title, type, description] = match
14
+ const [, index, title, type, description] = match;
15
15
 
16
16
  const alreadyExistsIndex = previous.findIndex(
17
17
  (lesson) => lesson.id === index && lesson.title === title
18
- )
18
+ );
19
19
 
20
20
  if (alreadyExistsIndex !== -1) {
21
21
  return {
@@ -26,7 +26,7 @@ export function parseLesson(input: string, previous: Lesson[]): Lesson | null {
26
26
  description: description.trim(),
27
27
  duration: previous[alreadyExistsIndex].duration,
28
28
  locked: previous[alreadyExistsIndex].locked || false,
29
- }
29
+ };
30
30
  }
31
31
 
32
32
  return {
@@ -37,51 +37,51 @@ export function parseLesson(input: string, previous: Lesson[]): Lesson | null {
37
37
  duration: 2,
38
38
  uid: randomUUID(),
39
39
  locked: false,
40
- }
40
+ };
41
41
  }
42
42
 
43
43
  export const uploadFileToBucket = async (content: string, path: string) => {
44
44
  const response = await axios.post(`/upload`, {
45
45
  content,
46
46
  destination: path,
47
- })
48
- return response.data
49
- }
47
+ });
48
+ return response.data;
49
+ };
50
50
  export const uploadImageToBucket = async (imageUrl: string, path: string) => {
51
51
  const response = await axios.post(`/upload-image`, {
52
52
  image_url: imageUrl,
53
53
  destination: path,
54
- })
55
- return response.data
56
- }
54
+ });
55
+ return response.data;
56
+ };
57
57
 
58
58
  export const checkParams = (paramsToCheck: string[]) => {
59
- const urlParams = new URLSearchParams(window.location.search)
60
- const result: Record<string, string> = {}
59
+ const urlParams = new URLSearchParams(window.location.search);
60
+ const result: Record<string, string> = {};
61
61
 
62
62
  paramsToCheck.forEach((param) => {
63
- const value = urlParams.get(param)
63
+ const value = urlParams.get(param);
64
64
  if (value !== null) {
65
- result[param] = value
65
+ result[param] = value;
66
66
  }
67
- })
67
+ });
68
68
 
69
- return result
70
- }
69
+ return result;
70
+ };
71
71
 
72
72
  export async function getConsumables(token: string): Promise<any> {
73
- const url = `${BREATHECODE_HOST}/v1/payments/me/service/consumable?virtual=true`
73
+ const url = `${BREATHECODE_HOST}/v1/payments/me/service/consumable?virtual=true`;
74
74
 
75
75
  const headers = {
76
76
  Authorization: `Token ${token}`,
77
- }
77
+ };
78
78
 
79
79
  try {
80
- const response = await axios.get(url, { headers })
81
- return response.data
80
+ const response = await axios.get(url, { headers });
81
+ return response.data;
82
82
  } catch (error) {
83
- console.error("Error fetching consumables:", error)
84
- throw error
83
+ console.error("Error fetching consumables:", error);
84
+ throw error;
85
85
  }
86
86
  }
87
87
 
@@ -89,78 +89,78 @@ type ConsumableSlug =
89
89
  | "ai-conversation-message"
90
90
  | "ai-compilation"
91
91
  | "ai-generation"
92
- | "ai-course-generation"
92
+ | "ai-course-generation";
93
93
 
94
94
  export async function useConsumableCall(
95
95
  breathecodeToken: string,
96
96
  consumableSlug: ConsumableSlug = "ai-conversation-message"
97
97
  ): Promise<boolean> {
98
- const url = `${BREATHECODE_HOST}/v1/payments/me/service/${consumableSlug}/consumptionsession`
98
+ const url = `${BREATHECODE_HOST}/v1/payments/me/service/${consumableSlug}/consumptionsession`;
99
99
 
100
100
  const headers = {
101
101
  Authorization: `Token ${breathecodeToken}`,
102
- }
102
+ };
103
103
 
104
104
  try {
105
- const response = await axios.put(url, {}, { headers })
105
+ const response = await axios.put(url, {}, { headers });
106
106
 
107
107
  if (response.status >= 200 && response.status < 300) {
108
- console.log(`Successfully consumed ${consumableSlug}`)
109
- return true
108
+ console.log(`Successfully consumed ${consumableSlug}`);
109
+ return true;
110
110
  } else {
111
- console.error(`Request failed with status code: ${response.status}`)
112
- console.error(`Response: ${response.data}`)
113
- return false
111
+ console.error(`Request failed with status code: ${response.status}`);
112
+ console.error(`Response: ${response.data}`);
113
+ return false;
114
114
  }
115
115
  } catch (error) {
116
- console.error(`Error consuming ${consumableSlug}:`, error)
117
- return false
116
+ console.error(`Error consuming ${consumableSlug}:`, error);
117
+ return false;
118
118
  }
119
119
  }
120
120
 
121
121
  type ConsumableItem = {
122
- id: number
123
- how_many: number
124
- unit_type: string
125
- valid_until: string | null
126
- }
122
+ id: number;
123
+ how_many: number;
124
+ unit_type: string;
125
+ valid_until: string | null;
126
+ };
127
127
 
128
128
  type VoidEntry = {
129
- id: number
130
- slug: string
131
- balance: { unit: number }
132
- items: ConsumableItem[]
133
- }
129
+ id: number;
130
+ slug: string;
131
+ balance: { unit: number };
132
+ items: ConsumableItem[];
133
+ };
134
134
 
135
135
  export const parseConsumables = (
136
136
  voids: VoidEntry[]
137
137
  ): Record<string, number> => {
138
- const result: Record<string, number> = {}
138
+ const result: Record<string, number> = {};
139
139
 
140
140
  voids.forEach((entry) => {
141
141
  const maxHowMany = entry.items.length
142
142
  ? Math.max(...entry.items.map((item) => item.how_many))
143
- : 0
144
- result[entry.slug] = maxHowMany
145
- })
143
+ : 0;
144
+ result[entry.slug] = maxHowMany;
145
+ });
146
146
 
147
- return result
148
- }
147
+ return result;
148
+ };
149
149
 
150
150
  type LoginInfo = {
151
- email: string
152
- password: string
153
- }
151
+ email: string;
152
+ password: string;
153
+ };
154
154
 
155
155
  export const getRigobotJSON = async (breathecodeToken: string) => {
156
- const rigoUrl = `${RIGOBOT_HOST}/v1/auth/me/token?breathecode_token=${breathecodeToken}`
157
- const rigoResp = await fetch(rigoUrl)
156
+ const rigoUrl = `${RIGOBOT_HOST}/v1/auth/me/token?breathecode_token=${breathecodeToken}`;
157
+ const rigoResp = await fetch(rigoUrl);
158
158
  if (!rigoResp.ok) {
159
- throw new Error("Unable to obtain Rigobot token")
159
+ throw new Error("Unable to obtain Rigobot token");
160
160
  }
161
- const rigobotJson = await rigoResp.json()
162
- return rigobotJson
163
- }
161
+ const rigobotJson = await rigoResp.json();
162
+ return rigobotJson;
163
+ };
164
164
  export const validateUser = async (breathecodeToken: string) => {
165
165
  const config = {
166
166
  method: "GET",
@@ -168,30 +168,30 @@ export const validateUser = async (breathecodeToken: string) => {
168
168
  "Content-Type": "application/json",
169
169
  Authorization: `Token ${breathecodeToken}`,
170
170
  },
171
- }
171
+ };
172
172
 
173
- const res = await fetch(`${BREATHECODE_HOST}/v1/auth/user/me`, config)
173
+ const res = await fetch(`${BREATHECODE_HOST}/v1/auth/user/me`, config);
174
174
  if (!res.ok) {
175
- console.log("ERROR", res)
176
- return null
175
+ console.log("ERROR", res);
176
+ return null;
177
177
  }
178
- const json = await res.json()
178
+ const json = await res.json();
179
179
 
180
180
  if ("roles" in json) {
181
- delete json.roles
181
+ delete json.roles;
182
182
  }
183
183
  if ("permissions" in json) {
184
- delete json.permissions
184
+ delete json.permissions;
185
185
  }
186
186
  if ("settings" in json) {
187
- delete json.settings
187
+ delete json.settings;
188
188
  }
189
189
 
190
- return json
191
- }
190
+ return json;
191
+ };
192
192
 
193
193
  export const login4Geeks = async (loginInfo: LoginInfo) => {
194
- const url = `${BREATHECODE_HOST}/v1/auth/login/`
194
+ const url = `${BREATHECODE_HOST}/v1/auth/login/`;
195
195
 
196
196
  const res = await fetch(url, {
197
197
  body: JSON.stringify(loginInfo),
@@ -199,87 +199,87 @@ export const login4Geeks = async (loginInfo: LoginInfo) => {
199
199
  headers: {
200
200
  "Content-Type": "application/json",
201
201
  },
202
- })
202
+ });
203
203
 
204
204
  if (!res.ok) {
205
- throw Error("Unable to login with provided credentials")
205
+ throw Error("Unable to login with provided credentials");
206
206
  }
207
207
 
208
- const json = await res.json()
208
+ const json = await res.json();
209
209
 
210
- const rigoJson = await getRigobotJSON(json.token)
210
+ const rigoJson = await getRigobotJSON(json.token);
211
211
 
212
- const user = await validateUser(json.token)
213
- const returns = { ...json, rigobot: { ...rigoJson }, user }
212
+ const user = await validateUser(json.token);
213
+ const returns = { ...json, rigobot: { ...rigoJson }, user };
214
214
 
215
- return returns
216
- }
215
+ return returns;
216
+ };
217
217
 
218
218
  export const loginWithToken = async (token: string) => {
219
- const rigoJson = await getRigobotJSON(token)
219
+ const rigoJson = await getRigobotJSON(token);
220
220
 
221
- const user = await validateUser(token)
221
+ const user = await validateUser(token);
222
222
 
223
- const returns = { rigobot: { ...rigoJson }, ...user }
223
+ const returns = { rigobot: { ...rigoJson }, ...user };
224
224
 
225
- return returns
226
- }
225
+ return returns;
226
+ };
227
227
 
228
228
  export const validateTokens = async (
229
229
  breathecodeToken: string,
230
230
  onValidRigoToken: (token: string) => void
231
231
  ) => {
232
- const user = await validateUser(breathecodeToken)
233
- console.log("USER", user)
232
+ const user = await validateUser(breathecodeToken);
233
+ console.log("USER", user);
234
234
  if (!user) {
235
- return false
235
+ return false;
236
236
  }
237
237
 
238
- const rigobotJson = await getRigobotJSON(breathecodeToken)
238
+ const rigobotJson = await getRigobotJSON(breathecodeToken);
239
239
  if (!rigobotJson) {
240
- return false
240
+ return false;
241
241
  }
242
- onValidRigoToken(rigobotJson.key)
243
- return true
244
- }
242
+ onValidRigoToken(rigobotJson.key);
243
+ return true;
244
+ };
245
245
 
246
246
  export function extractImagesFromMarkdown(markdown: string) {
247
- const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
248
- const images = []
249
- let match
247
+ const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g;
248
+ const images = [];
249
+ let match;
250
250
 
251
251
  while ((match = imageRegex.exec(markdown)) !== null) {
252
- const altText = match[1]
253
- const url = match[2]
254
- images.push({ alt: altText, url: url })
252
+ const altText = match[1];
253
+ const url = match[2];
254
+ images.push({ alt: altText, url: url });
255
255
  }
256
256
 
257
- return images
257
+ return images;
258
258
  }
259
259
 
260
260
  export function getFilenameFromUrl(url: string): string {
261
261
  try {
262
262
  // 1) Use the URL constructor to strip off protocol/host/search/hash
263
- const pathname = new URL(url, location.href).pathname
263
+ const pathname = new URL(url, location.href).pathname;
264
264
  // 2) Grab everything after the last “/”
265
- return pathname.substring(pathname.lastIndexOf("/") + 1)
265
+ return pathname.substring(pathname.lastIndexOf("/") + 1);
266
266
  } catch {
267
267
  // Fallback for non-absolute URLs or invalid inputs
268
- const clean = url.split("?")[0].split("#")[0]
269
- return clean.substring(clean.lastIndexOf("/") + 1)
268
+ const clean = url.split("?")[0].split("#")[0];
269
+ return clean.substring(clean.lastIndexOf("/") + 1);
270
270
  }
271
271
  }
272
272
 
273
273
  export const makeCallbackUrl = (slug: string) => {
274
274
  if (DEV_MODE) {
275
- return `https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us120.gitpod.io/v1/learnpack/tools/images/callback?slug=${slug}`
275
+ return `https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us120.gitpod.io/v1/learnpack/tools/images/callback?slug=${slug}`;
276
276
  }
277
- return `${window.location.origin}/api/v1/webhooks/images`
278
- }
277
+ return `${window.location.origin}/api/v1/webhooks/images`;
278
+ };
279
279
 
280
280
  type TGenerateImageParams = {
281
- prompt: string
282
- }
281
+ prompt: string;
282
+ };
283
283
 
284
284
  export const generateImage = async (
285
285
  token: string,
@@ -298,14 +298,14 @@ export const generateImage = async (
298
298
  Authorization: "Token " + token,
299
299
  },
300
300
  }
301
- )
301
+ );
302
302
 
303
- return response.data
303
+ return response.data;
304
304
  } catch (error) {
305
- console.error("Error generating image:", error)
306
- return null
305
+ console.error("Error generating image:", error);
306
+ return null;
307
307
  }
308
- }
308
+ };
309
309
 
310
310
  export const createCourse = async (
311
311
  syllabus: Syllabus,
@@ -323,27 +323,27 @@ export const createCourse = async (
323
323
  "x-rigo-token": token,
324
324
  },
325
325
  }
326
- )
327
- return response.data
328
- }
326
+ );
327
+ return response.data;
328
+ };
329
329
 
330
330
  interface SlugAvailabilityResponse {
331
- slug: string
332
- available: boolean
331
+ slug: string;
332
+ available: boolean;
333
333
  }
334
334
 
335
335
  export const isSlugAvailable = async (slug: string): Promise<boolean> => {
336
336
  try {
337
337
  const url = `${RIGOBOT_HOST}/v1/learnpack/check-slug-availability?slug=${encodeURIComponent(
338
338
  slug
339
- )}`
340
- const response = await axios.get<SlugAvailabilityResponse>(url)
341
- return response.data.available
339
+ )}`;
340
+ const response = await axios.get<SlugAvailabilityResponse>(url);
341
+ return response.data.available;
342
342
  } catch (error) {
343
- console.error("Error checking slug availability:", error)
344
- throw error
343
+ console.error("Error checking slug availability:", error);
344
+ throw error;
345
345
  }
346
- }
346
+ };
347
347
 
348
348
  export const reWriteTitle = async (title: string, token: string) => {
349
349
  // We hav in the parsed the newTitle
@@ -363,25 +363,25 @@ export const reWriteTitle = async (title: string, token: string) => {
363
363
  Authorization: "Token " + token,
364
364
  },
365
365
  }
366
- )
367
- console.log("RESPONSE", response.data)
368
- return response.data.parsed.newTitle
366
+ );
367
+ console.log("RESPONSE", response.data);
368
+ return response.data.parsed.newTitle;
369
369
  } catch (error) {
370
- console.error("Error rewriting title:", error)
370
+ console.error("Error rewriting title:", error);
371
371
  // Return the title as it is with a random number of 4 characters
372
- return `${title} ${Math.random().toString(36).substring(2, 6)}`
372
+ return `${title} ${Math.random().toString(36).substring(2, 6)}`;
373
373
  }
374
- }
374
+ };
375
375
 
376
376
  export async function registerUserWithFormData(
377
377
  firstName: string,
378
378
  lastName: string,
379
379
  email: string
380
380
  ): Promise<any> {
381
- const formData = new FormData()
382
- formData.append("1_first_name", firstName)
383
- formData.append("1_last_name", lastName)
384
- formData.append("1_email", email)
381
+ const formData = new FormData();
382
+ formData.append("1_first_name", firstName);
383
+ formData.append("1_last_name", lastName);
384
+ formData.append("1_email", email);
385
385
 
386
386
  // Puedes modificar este objeto para agregar info real de tracking si la tienes.
387
387
  const conversionArray = [
@@ -402,9 +402,9 @@ export async function registerUserWithFormData(
402
402
  internal_cta_content: "$undefined",
403
403
  internal_cta_campaign: "$undefined",
404
404
  },
405
- ]
405
+ ];
406
406
 
407
- formData.append("0", JSON.stringify(conversionArray))
407
+ formData.append("0", JSON.stringify(conversionArray));
408
408
 
409
409
  try {
410
410
  const response = await axios.post(
@@ -415,57 +415,57 @@ export async function registerUserWithFormData(
415
415
  "Content-Type": "multipart/form-data",
416
416
  },
417
417
  }
418
- )
419
- return response.data
418
+ );
419
+ return response.data;
420
420
  } catch (error: any) {
421
421
  // Manejo de errores básico
422
- console.error(error, "ERROR REGISTERING IN LEARNPACK")
422
+ console.error(error, "ERROR REGISTERING IN LEARNPACK");
423
423
  return {
424
424
  success: false,
425
425
  message: error?.response?.data?.detail || "Registration error",
426
426
  data: error?.response?.data || null,
427
- }
427
+ };
428
428
  }
429
429
  }
430
430
 
431
431
  export const isValidRigoToken = async (rigobotToken: string) => {
432
- const rigoUrl = `${RIGOBOT_HOST}/v1/auth/token/${rigobotToken}`
433
- const rigoResp = await fetch(rigoUrl)
432
+ const rigoUrl = `${RIGOBOT_HOST}/v1/auth/token/${rigobotToken}`;
433
+ const rigoResp = await fetch(rigoUrl);
434
434
  if (!rigoResp.ok) {
435
- return false
435
+ return false;
436
436
  }
437
437
 
438
- return true
439
- }
438
+ return true;
439
+ };
440
440
  export const isValidPublicToken = async (publicToken: string) => {
441
441
  const headers = {
442
442
  "Content-Type": "application/json",
443
443
  Authorization: "Token " + publicToken,
444
- }
445
- const rigoUrl = `${RIGOBOT_HOST}/v1/auth/public/token/validate`
446
- const rigoResp = await fetch(rigoUrl, { headers })
444
+ };
445
+ const rigoUrl = `${RIGOBOT_HOST}/v1/auth/public/token/validate`;
446
+ const rigoResp = await fetch(rigoUrl, { headers });
447
447
  if (!rigoResp.ok) {
448
- return false
448
+ return false;
449
449
  }
450
450
 
451
- return true
452
- }
451
+ return true;
452
+ };
453
453
 
454
454
  export const fixTitleLength = (title: string) => {
455
- const MAX_LENGTH = 49
456
- let fixed = title.slice(0, MAX_LENGTH)
457
- fixed = fixed.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "")
458
- return fixed
459
- }
455
+ const MAX_LENGTH = 49;
456
+ let fixed = title.slice(0, MAX_LENGTH);
457
+ fixed = fixed.replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "");
458
+ return fixed;
459
+ };
460
460
 
461
461
  export const getTechnologies = async () => {
462
- const response = await axios.get(`/technologies`)
463
- return response.data
464
- }
462
+ const response = await axios.get(`/technologies`);
463
+ return response.data;
464
+ };
465
465
 
466
466
  export const detectLanguage = (text: string) => {
467
- const lang = franc(text)
468
- if (lang === "spa") return "es"
469
- if (lang === "eng") return "en"
470
- return "en" // fallback to English
471
- }
467
+ const lang = franc(text);
468
+ if (lang === "spa") return "es";
469
+ if (lang === "eng") return "en";
470
+ return "en"; // fallback to English
471
+ };