@learnpack/learnpack 5.0.28 → 5.0.29

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.
@@ -279,9 +279,7 @@ throw new Error("File not found: " + filePath)
279
279
  TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
280
280
  }
281
281
 
282
- if (configObject.config?.editor.agent === "os") {
283
- eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
284
- } else {
282
+ if (configObject.config?.editor.agent !== "os") {
285
283
  dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
286
284
  }
287
285
 
@@ -1,147 +1,241 @@
1
- // eslint-disable-next-line
2
- const frontMatter = require("front-matter")
3
- import * as path from "path"
4
-
5
- import * as yaml from "js-yaml"
6
-
7
- type TEstimateReadingTimeReturns = {
8
- minutes: number
9
- words: number
10
- }
11
-
12
- export const estimateReadingTime = (
13
- text: string,
14
- wordsPerMinute = 150
15
- ): TEstimateReadingTimeReturns => {
16
- const words = text.trim().split(/\s+/).length
17
- const minutes = words / wordsPerMinute
18
-
19
- if (minutes < 1) {
20
- if (words === 0)
21
- return {
22
- minutes: 1,
23
- words,
24
- }
25
- } else {
26
- return {
27
- minutes,
28
- words,
29
- }
30
- }
31
-
32
- return {
33
- minutes: 1,
34
- words,
35
- }
36
- }
37
-
38
- export type PackageInfo = {
39
- grading: string
40
- difficulty: string
41
- duration: number
42
- description: {
43
- us: string
44
- }
45
- title: {
46
- us: string
47
- }
48
- }
49
-
50
- export function checkReadingTime(
51
- markdown: string,
52
- wordsPerMinute = 150
53
- ): { newMarkdown: string; exceedsThreshold: boolean } {
54
- const parsed = frontMatter(markdown)
55
- const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
56
- let attributes = parsed.attributes ? parsed.attributes : {}
57
-
58
- if (typeof parsed.attributes !== "object") {
59
- attributes = {}
60
- }
61
-
62
- const updatedAttributes = {
63
- ...attributes,
64
- readingTime,
65
- }
66
-
67
- // Convert the front matter back to a proper YAML string
68
- const yamlFrontMatter = yaml.dump(updatedAttributes).trim()
69
-
70
- // Reconstruct the markdown with the front matter
71
- const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
72
-
73
- return {
74
- newMarkdown,
75
- exceedsThreshold: readingTime.minutes > wordsPerMinute,
76
- }
77
- }
78
-
79
- const slugify = (text: string) => {
80
- return text
81
- .toString()
82
- .normalize("NFD")
83
- .replace(/[\u0300-\u036F]/g, "")
84
- .toLowerCase()
85
- .trim()
86
- .replace(/\s+/g, "-")
87
- .replace(/[^\w-]+/g, "")
88
- }
89
-
90
- export const getExInfo = (title: string) => {
91
- // Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
92
- let [exNumber, exTitle] = title.split(" - ")
93
-
94
- // Extract kind and description
95
- const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
96
- const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
97
- const description = kindMatch ? kindMatch[2].trim() : ""
98
-
99
- exNumber = exNumber.trim()
100
- // Clean title
101
- exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
102
- exTitle = slugify(exTitle)
103
-
104
- return {
105
- exNumber,
106
- kind,
107
- description,
108
- exTitle,
109
- }
110
- }
111
-
112
- export function extractImagesFromMarkdown(markdown: string) {
113
- const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
114
- const images = []
115
- let match
116
-
117
- while ((match = imageRegex.exec(markdown)) !== null) {
118
- const altText = match[1]
119
- const url = match[2]
120
- images.push({ alt: altText, url: url })
121
- }
122
-
123
- return images
124
- }
125
-
126
- export function getFilenameFromUrl(url: string) {
127
- return path.basename(url)
128
- }
129
-
130
- export const makePackageInfo = (choices: any) => {
131
- const packageInfo = {
132
- grading: choices.grading,
133
- difficulty: choices.difficulty,
134
- duration: parseInt(choices.duration),
135
- description: {
136
- us: choices.description,
137
- },
138
- title: {
139
- us: choices.title,
140
- },
141
- slug: choices.title
142
- .toLowerCase()
143
- .replace(/ /g, "-")
144
- .replace(/[^\w-]+/g, ""),
145
- }
146
- return packageInfo
147
- }
1
+ // eslint-disable-next-line
2
+ const frontMatter = require("front-matter")
3
+ import * as path from "path"
4
+ import * as fs from "fs"
5
+ import { homedir } from "os"
6
+ import { join } from "path"
7
+ import { exec } from "child_process"
8
+ import { promisify } from "util"
9
+
10
+ import * as yaml from "js-yaml"
11
+
12
+ type TEstimateReadingTimeReturns = {
13
+ minutes: number
14
+ words: number
15
+ }
16
+
17
+ export const estimateReadingTime = (
18
+ text: string,
19
+ wordsPerMinute = 150
20
+ ): TEstimateReadingTimeReturns => {
21
+ const words = text.trim().split(/\s+/).length
22
+ const minutes = words / wordsPerMinute
23
+
24
+ if (minutes < 1) {
25
+ if (words === 0)
26
+ return {
27
+ minutes: 1,
28
+ words,
29
+ }
30
+ } else {
31
+ return {
32
+ minutes,
33
+ words,
34
+ }
35
+ }
36
+
37
+ return {
38
+ minutes: 1,
39
+ words,
40
+ }
41
+ }
42
+
43
+ export type PackageInfo = {
44
+ grading: string
45
+ difficulty: string
46
+ duration: number
47
+ description: {
48
+ us: string
49
+ }
50
+ title: {
51
+ us: string
52
+ }
53
+ }
54
+
55
+ export function checkReadingTime(
56
+ markdown: string,
57
+ wordsPerMinute = 200,
58
+ maxMinutes = 1
59
+ ): {
60
+ newMarkdown: string
61
+ exceedsThreshold: boolean
62
+ minutes: number
63
+ body: string
64
+ } {
65
+ const parsed = frontMatter(markdown)
66
+
67
+ const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
68
+
69
+ let attributes = parsed.attributes ? parsed.attributes : {}
70
+
71
+ if (typeof parsed.attributes !== "object") {
72
+ attributes = {}
73
+ }
74
+
75
+ const updatedAttributes = {
76
+ ...attributes,
77
+ readingTime,
78
+ }
79
+
80
+ let yamlFrontMatter = ""
81
+ try {
82
+ yamlFrontMatter = yaml.dump(updatedAttributes).trim()
83
+ } catch {
84
+ return {
85
+ newMarkdown: "",
86
+ exceedsThreshold: false,
87
+ minutes: 0,
88
+ body: "",
89
+ }
90
+ }
91
+
92
+ // Reconstruct the markdown with the front matter
93
+ const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
94
+
95
+ return {
96
+ newMarkdown,
97
+ exceedsThreshold: readingTime.minutes > maxMinutes,
98
+ minutes: readingTime.minutes,
99
+ body: parsed.body,
100
+ }
101
+ }
102
+
103
+ const slugify = (text: string) => {
104
+ return text
105
+ .toString()
106
+ .normalize("NFD")
107
+ .replace(/[\u0300-\u036F]/g, "")
108
+ .toLowerCase()
109
+ .trim()
110
+ .replace(/\s+/g, "-")
111
+ .replace(/[^\w-]+/g, "")
112
+ }
113
+
114
+ export const getExInfo = (title: string) => {
115
+ // Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
116
+ let [exNumber, exTitle] = title.split(" - ")
117
+
118
+ // Extract kind and description
119
+ const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
120
+ const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
121
+ const description = kindMatch ? kindMatch[2].trim() : ""
122
+
123
+ exNumber = exNumber.trim()
124
+ // Clean title
125
+ exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
126
+ exTitle = slugify(exTitle)
127
+
128
+ return {
129
+ exNumber,
130
+ kind,
131
+ description,
132
+ exTitle,
133
+ }
134
+ }
135
+
136
+ export function extractImagesFromMarkdown(markdown: string) {
137
+ const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
138
+ const images = []
139
+ let match
140
+
141
+ while ((match = imageRegex.exec(markdown)) !== null) {
142
+ const altText = match[1]
143
+ const url = match[2]
144
+ images.push({ alt: altText, url: url })
145
+ }
146
+
147
+ return images
148
+ }
149
+
150
+ export function getFilenameFromUrl(url: string) {
151
+ return path.basename(url)
152
+ }
153
+
154
+ export const makePackageInfo = (choices: any) => {
155
+ const packageInfo = {
156
+ grading: choices.grading,
157
+ difficulty: "beginner",
158
+ duration: 5,
159
+ description: {
160
+ us: choices.description,
161
+ },
162
+ title: {
163
+ us: choices.title,
164
+ },
165
+ slug: choices.title
166
+ .toLowerCase()
167
+ .replace(/ /g, "-")
168
+ .replace(/[^\w-]+/g, ""),
169
+ }
170
+ return packageInfo
171
+ }
172
+
173
+ export function estimateDuration(listOfSteps: string[]): number {
174
+ let duration = 0
175
+
176
+ for (const step of listOfSteps) {
177
+ if (step.includes("[READ:")) {
178
+ duration += 1
179
+ } else if (step.includes("[QUIZ:")) {
180
+ duration += 2
181
+ } else if (step.includes("[CODE:")) {
182
+ duration += 3
183
+ }
184
+ }
185
+
186
+ return duration
187
+ }
188
+
189
+ const writeFilePromise = promisify(fs.writeFile)
190
+ const execPromise = promisify(exec)
191
+
192
+ const example_content = `
193
+ 00.1 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
194
+ 01.1 - Introduction to Machine Learning [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
195
+ 01.2 - Introduction to Deep Learning [QUIZ: Small introduction to important concepts such as AI, machine learning, and their applications]
196
+ 02.1 - Test your knowledge [CODE: Code problem to solve]
197
+ `
198
+
199
+ export async function createFileOnDesktop() {
200
+ try {
201
+ const desktopPath = join(homedir(), "Desktop")
202
+ const filePath = join(desktopPath, "content_index.txt")
203
+
204
+ const content = example_content.trim()
205
+
206
+ await writeFilePromise(filePath, content)
207
+ console.log(`File created successfully at: ${filePath}`)
208
+
209
+ await openFile(filePath)
210
+ } catch (error) {
211
+ console.error("Error:", error)
212
+ }
213
+ }
214
+
215
+ async function openFile(filePath: string) {
216
+ const platform = process.platform
217
+ let command
218
+
219
+ if (platform === "win32") {
220
+ command = `start "" "${filePath}"`
221
+ } else if (platform === "darwin") {
222
+ command = `open "${filePath}"`
223
+ } else {
224
+ command = `xdg-open "${filePath}"`
225
+ }
226
+
227
+ try {
228
+ await execPromise(command)
229
+ console.log("File opened successfully.")
230
+ } catch (error) {
231
+ console.error("Error opening the file:", error)
232
+ }
233
+ }
234
+
235
+ export function getContentIndex() {
236
+ const desktopPath = join(homedir(), "Desktop")
237
+ const filePath = join(desktopPath, "content_index.txt")
238
+
239
+ const content = fs.readFileSync(filePath, "utf8")
240
+ return content
241
+ }
@@ -293,3 +293,33 @@ export async function createPreviewReadme(
293
293
  })
294
294
  fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer)
295
295
  }
296
+
297
+ // {"lesson": "The text of the lesson", "number_of_words": "Words lenght of the lesson", "expected_number_words": "The expected number of words"}
298
+
299
+ type TReduceReadmeInputs = {
300
+ lesson: string
301
+ number_of_words: string
302
+ expected_number_words: string
303
+ }
304
+ export async function reduceReadme(
305
+ rigoToken: string,
306
+ inputs: TReduceReadmeInputs
307
+ ) {
308
+ try {
309
+ const response = await axios.post(
310
+ `${RIGOBOT_HOST}/v1/prompting/completion/588/`,
311
+ { inputs, include_purpose_objective: false, execute_async: false },
312
+ {
313
+ headers: {
314
+ "Content-Type": "application/json",
315
+ Authorization: "Token " + rigoToken,
316
+ },
317
+ }
318
+ )
319
+
320
+ return response.data
321
+ } catch (error) {
322
+ Console.debug(error)
323
+ return null
324
+ }
325
+ }