@learnpack/learnpack 5.0.146 → 5.0.150
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/README.md +13 -13
- package/lib/commands/serve.js +11 -6
- package/lib/creatorDist/assets/{index-CzMCewx6.js → index-CkzbY9Qa.js} +32902 -36358
- package/lib/creatorDist/assets/{index-DSOj0E0h.css → index-CztA582_.css} +25 -20
- package/lib/creatorDist/index.html +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +15 -6
- package/src/creator/src/App.tsx +31 -16
- package/src/creator/src/components/ConsumablesManager.tsx +1 -0
- package/src/creator/src/components/ContentCard.tsx +28 -7
- package/src/creator/src/components/FileUploader.tsx +44 -17
- package/src/creator/src/components/Loader.tsx +1 -1
- package/src/creator/src/components/PreviewGenerator.tsx +13 -11
- package/src/creator/src/components/TurnstileChallenge.tsx +2 -9
- package/src/creator/src/components/syllabus/ContentIndex.tsx +2 -2
- package/src/creator/src/components/syllabus/Sidebar.tsx +23 -23
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +4 -1
- package/src/creator/src/index.css +0 -5
- package/src/creator/src/utils/creatorUtils.ts +1 -141
- package/src/creator/src/utils/rigo.ts +22 -377
- package/src/creatorDist/assets/{index-CzMCewx6.js → index-CkzbY9Qa.js} +32902 -36358
- package/src/creatorDist/assets/{index-DSOj0E0h.css → index-CztA582_.css} +25 -20
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +245 -245
- package/src/ui/app.tar.gz +0 -0
@@ -39,34 +39,34 @@ export const Sidebar = ({
|
|
39
39
|
return (
|
40
40
|
<>
|
41
41
|
{!isOpen && (
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
className={`flex flex-row gap-3 cloudy bg-white rounded-md p-2 shadow-md bg-learnpack-blue duration-500 border-2 border-blue-600`}
|
47
|
-
>
|
48
|
-
<span>Chat with me to update the course content</span>
|
49
|
-
<button
|
50
|
-
className=" text-red-500 cursor-pointer bg-learnpack-blue p-2 rounded-md"
|
51
|
-
onClick={() => setShowBubble(false)}
|
52
|
-
>
|
53
|
-
{SVGS.redClose}
|
54
|
-
</button>
|
55
|
-
</div>
|
56
|
-
)}
|
57
|
-
<button
|
58
|
-
className="p-1 shadow-md cursor-pointer p-2 w-15 h-15 flex items-center justify-center bg-blue-600 rounded-[50%] fluid-svg"
|
59
|
-
onClick={() => setIsOpen(true)}
|
42
|
+
<div className="fixed bottom-5 left-2 z-30 lg:hidden">
|
43
|
+
{showBubble && (
|
44
|
+
<div
|
45
|
+
className={`flex flex-row gap-3 cloudy bg-white rounded-md p-2 shadow-md bg-learnpack-blue duration-500 border-2 border-blue-600 mb-4`}
|
60
46
|
>
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
47
|
+
<span className="w-[280px]">
|
48
|
+
Chat with me to update the course content
|
49
|
+
</span>
|
50
|
+
<button
|
51
|
+
className=" text-red-500 cursor-pointer bg-learnpack-blue p-2 rounded-md"
|
52
|
+
onClick={() => setShowBubble(false)}
|
53
|
+
>
|
54
|
+
{SVGS.redClose}
|
55
|
+
</button>
|
56
|
+
</div>
|
57
|
+
)}
|
58
|
+
<button
|
59
|
+
className="p-1 shadow-md cursor-pointer p-2 w-15 h-15 flex items-center justify-center bg-blue-600 rounded-[50%] fluid-svg"
|
60
|
+
onClick={() => setIsOpen(true)}
|
61
|
+
>
|
62
|
+
{SVGS.rigoSoftBlue}
|
63
|
+
</button>
|
64
|
+
</div>
|
65
65
|
)}
|
66
66
|
|
67
67
|
<div
|
68
68
|
ref={sidebarRef}
|
69
|
-
className={`fixed z-
|
69
|
+
className={`fixed z-30 top-0 left-0 h-full w-4/5 max-w-sm bg-learnpack-blue text-sm text-gray-700 border-r border-C8DBFC overflow-y-auto scrollbar-hide p-6 transition-transform duration-300 ease-in-out lg:relative lg:transform-none lg:w-1/3 ${
|
70
70
|
isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
71
71
|
}`}
|
72
72
|
>
|
@@ -146,7 +146,10 @@ const SyllabusEditor: React.FC = () => {
|
|
146
146
|
setShowLoginModal(true)
|
147
147
|
return
|
148
148
|
}
|
149
|
-
const success = await useConsumableCall(
|
149
|
+
const success = await useConsumableCall(
|
150
|
+
auth.bcToken,
|
151
|
+
"ai-course-generation"
|
152
|
+
)
|
150
153
|
if (!success) {
|
151
154
|
toast.error("You don't have enough credits to generate a course!")
|
152
155
|
return
|
@@ -1,18 +1,5 @@
|
|
1
|
-
import { Lesson } from "../components/LessonItem"
|
2
1
|
import { eventBus } from "./eventBus"
|
3
|
-
import {
|
4
|
-
generateImage,
|
5
|
-
getFilenameFromUrl,
|
6
|
-
uploadFileToBucket,
|
7
|
-
uploadImageToBucket,
|
8
|
-
} from "./lib"
|
9
|
-
import {
|
10
|
-
makeReadmeReadable,
|
11
|
-
readmeCreator,
|
12
|
-
checkReadability,
|
13
|
-
createCodeFile,
|
14
|
-
} from "./rigo"
|
15
|
-
import { FormState } from "./store"
|
2
|
+
import { generateImage, getFilenameFromUrl, uploadImageToBucket } from "./lib"
|
16
3
|
|
17
4
|
export const slugify = (text: string) => {
|
18
5
|
return text
|
@@ -21,133 +8,6 @@ export const slugify = (text: string) => {
|
|
21
8
|
.replace(/[^\w.-]+/g, "")
|
22
9
|
}
|
23
10
|
|
24
|
-
export const createLearnJson = (courseInfo: FormState) => {
|
25
|
-
console.log("courseInfo to create learn json", courseInfo)
|
26
|
-
|
27
|
-
const learnJson = {
|
28
|
-
slug: slugify(courseInfo.title as string),
|
29
|
-
title: {
|
30
|
-
us: courseInfo.title,
|
31
|
-
},
|
32
|
-
technologies: courseInfo.technologies || [],
|
33
|
-
difficulty: "beginner",
|
34
|
-
description: {
|
35
|
-
us: courseInfo.description,
|
36
|
-
},
|
37
|
-
grading: "isolated",
|
38
|
-
telemetry: {
|
39
|
-
batch: "https://breathecode.herokuapp.com/v1/assignment/me/telemetry",
|
40
|
-
},
|
41
|
-
preview: "preview.png",
|
42
|
-
}
|
43
|
-
return learnJson
|
44
|
-
}
|
45
|
-
|
46
|
-
const PARAMS = {
|
47
|
-
expected_grade_level: "6",
|
48
|
-
max_fkgl: 8,
|
49
|
-
max_words: 200,
|
50
|
-
max_rewrite_attempts: 3,
|
51
|
-
max_title_length: 50,
|
52
|
-
}
|
53
|
-
export async function processExercise(
|
54
|
-
rigoToken: string,
|
55
|
-
steps: Lesson[],
|
56
|
-
packageContext: string,
|
57
|
-
exercise: Lesson,
|
58
|
-
exercisesDir: string
|
59
|
-
): Promise<string> {
|
60
|
-
// const tid = toast.loading("Generating lesson...")
|
61
|
-
setTimeout(() => {
|
62
|
-
eventBus.emit("course-generation", {
|
63
|
-
message: `✍🏻 Generating lesson ${exercise.id} - ${exercise.title}...`,
|
64
|
-
})
|
65
|
-
}, 500)
|
66
|
-
const readme = await readmeCreator(rigoToken, {
|
67
|
-
title: `${exercise.id} - ${exercise.title}`,
|
68
|
-
output_lang: "en",
|
69
|
-
list_of_exercises: JSON.stringify(steps),
|
70
|
-
tutorial_description: packageContext,
|
71
|
-
lesson_description: exercise.description,
|
72
|
-
kind: exercise.type.toLowerCase(),
|
73
|
-
})
|
74
|
-
|
75
|
-
const duration = exercise.duration
|
76
|
-
let attempts = 0
|
77
|
-
let readability = checkReadability(readme.parsed.content, 200, duration || 1)
|
78
|
-
|
79
|
-
while (
|
80
|
-
readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
81
|
-
attempts < PARAMS.max_rewrite_attempts
|
82
|
-
) {
|
83
|
-
setTimeout(() => {
|
84
|
-
eventBus.emit("course-generation", {
|
85
|
-
message: `🔄 The lesson ${exercise.id} - ${
|
86
|
-
exercise.title
|
87
|
-
} has a readability score of ${
|
88
|
-
readability.fkglResult.fkgl
|
89
|
-
}. Rewriting it... (Attempt ${attempts + 1})`,
|
90
|
-
})
|
91
|
-
}, 500)
|
92
|
-
// eslint-disable-next-line
|
93
|
-
const reducedReadme = await makeReadmeReadable(rigoToken, {
|
94
|
-
lesson: readability.body,
|
95
|
-
number_of_words: readability.minutes.toString(),
|
96
|
-
expected_number_words: PARAMS.max_words.toString(),
|
97
|
-
fkgl_results: JSON.stringify(readability.fkglResult),
|
98
|
-
expected_grade_level: PARAMS.expected_grade_level,
|
99
|
-
})
|
100
|
-
|
101
|
-
if (!reducedReadme) break
|
102
|
-
|
103
|
-
readability = checkReadability(
|
104
|
-
reducedReadme.parsed.content,
|
105
|
-
PARAMS.max_words,
|
106
|
-
duration || 1
|
107
|
-
)
|
108
|
-
|
109
|
-
attempts++
|
110
|
-
}
|
111
|
-
|
112
|
-
setTimeout(() => {
|
113
|
-
eventBus.emit("course-generation", {
|
114
|
-
message: `✅ After ${attempts} attempts, the lesson ${
|
115
|
-
exercise.title
|
116
|
-
} has a readability score of ${
|
117
|
-
readability.fkglResult.fkgl
|
118
|
-
} using FKGL. And it has ${readability.minutes.toFixed(
|
119
|
-
2
|
120
|
-
)} minutes of reading time.`,
|
121
|
-
})
|
122
|
-
}, 500)
|
123
|
-
|
124
|
-
const readmeFilename = "README.md"
|
125
|
-
|
126
|
-
const targetDir = `${exercisesDir}/${slugify(
|
127
|
-
exercise.id + "-" + exercise.title
|
128
|
-
)}`
|
129
|
-
await uploadFileToBucket(
|
130
|
-
readability.newMarkdown,
|
131
|
-
`${targetDir}/${readmeFilename}`
|
132
|
-
)
|
133
|
-
|
134
|
-
if (exercise.type.toLowerCase() === "code") {
|
135
|
-
eventBus.emit("course-generation", {
|
136
|
-
message: `🔍 Creating code file for ${exercise.title}`,
|
137
|
-
})
|
138
|
-
const codeFile = await createCodeFile(rigoToken, {
|
139
|
-
readme: readability.newMarkdown,
|
140
|
-
tutorial_info: packageContext,
|
141
|
-
})
|
142
|
-
await uploadFileToBucket(
|
143
|
-
codeFile.parsed.content,
|
144
|
-
`${targetDir}/index.${codeFile.parsed.extension.replace(".", "")}`
|
145
|
-
)
|
146
|
-
}
|
147
|
-
|
148
|
-
return readability.newMarkdown
|
149
|
-
}
|
150
|
-
|
151
11
|
export const randomUUID = () => {
|
152
12
|
return Math.random().toString(36).substring(2, 15)
|
153
13
|
}
|
@@ -1,418 +1,63 @@
|
|
1
|
+
import toast from "react-hot-toast"
|
1
2
|
import { RIGOBOT_HOST } from "./constants"
|
2
|
-
import axios from "axios"
|
3
|
-
import frontMatter from "front-matter"
|
4
|
-
import { syllable } from "syllable"
|
5
|
-
|
6
|
-
import * as yaml from "js-yaml"
|
3
|
+
import axios, { AxiosError } from "axios"
|
7
4
|
|
8
5
|
type TInteractiveCreationInputs = {
|
9
6
|
courseInfo: string
|
10
7
|
prevInteractions: string
|
11
8
|
}
|
9
|
+
|
12
10
|
export const interactiveCreation = async (
|
13
|
-
// token: string,
|
14
11
|
inputs: TInteractiveCreationInputs
|
15
|
-
) => {
|
16
|
-
const response = await axios.post(
|
17
|
-
`${RIGOBOT_HOST}/v1/prompting/public/completion/390/`,
|
18
|
-
{
|
19
|
-
inputs: inputs,
|
20
|
-
include_purpose_objective: false,
|
21
|
-
execute_async: false,
|
22
|
-
},
|
23
|
-
{
|
24
|
-
headers: {
|
25
|
-
"Content-Type": "application/json",
|
26
|
-
// Authorization: "Token " + token,
|
27
|
-
},
|
28
|
-
}
|
29
|
-
)
|
30
|
-
|
31
|
-
return response.data
|
32
|
-
}
|
33
|
-
|
34
|
-
type TCreateReadmeInputs = {
|
35
|
-
title: string
|
36
|
-
output_lang: string
|
37
|
-
list_of_exercises: string
|
38
|
-
tutorial_description: string
|
39
|
-
include_quiz: string
|
40
|
-
lesson_description: string
|
41
|
-
}
|
42
|
-
|
43
|
-
export const createReadme = async (
|
44
|
-
token: string,
|
45
|
-
inputs: TCreateReadmeInputs
|
46
|
-
) => {
|
12
|
+
): Promise<any | null> => {
|
47
13
|
try {
|
48
14
|
const response = await axios.post(
|
49
|
-
`${RIGOBOT_HOST}/v1/prompting/completion/
|
15
|
+
`${RIGOBOT_HOST}/v1/prompting/public/completion/390/`,
|
50
16
|
{
|
51
|
-
inputs,
|
17
|
+
inputs: inputs,
|
52
18
|
include_purpose_objective: false,
|
53
19
|
execute_async: false,
|
54
20
|
},
|
55
21
|
{
|
22
|
+
withCredentials: true,
|
56
23
|
headers: {
|
57
24
|
"Content-Type": "application/json",
|
58
|
-
Authorization: "Token " + token,
|
59
25
|
},
|
60
26
|
}
|
61
27
|
)
|
62
|
-
return response.data
|
63
|
-
} catch (error) {
|
64
|
-
console.error(error)
|
65
|
-
return null
|
66
|
-
}
|
67
|
-
}
|
68
|
-
|
69
|
-
type TCreateCodingReadmeInputs = {
|
70
|
-
tutorial_description: string
|
71
|
-
list_of_exercises: string
|
72
|
-
output_lang: string
|
73
|
-
title: string
|
74
|
-
lesson_description: string
|
75
|
-
}
|
76
|
-
export const createCodingReadme = async (
|
77
|
-
token: string,
|
78
|
-
inputs: TCreateCodingReadmeInputs
|
79
|
-
) => {
|
80
|
-
const response = await axios.post(
|
81
|
-
`${RIGOBOT_HOST}/v1/prompting/completion/489/`,
|
82
|
-
{ inputs, include_purpose_objective: false, execute_async: false },
|
83
|
-
{
|
84
|
-
headers: {
|
85
|
-
"Content-Type": "application/json",
|
86
|
-
Authorization: "Token " + token,
|
87
|
-
},
|
88
|
-
}
|
89
|
-
)
|
90
|
-
|
91
|
-
return response.data
|
92
|
-
}
|
93
|
-
|
94
|
-
type TReadmeCreatorInputs = {
|
95
|
-
tutorial_description: string
|
96
|
-
list_of_exercises: string
|
97
|
-
output_lang: string
|
98
|
-
title: string
|
99
|
-
lesson_description: string
|
100
|
-
kind: string
|
101
|
-
}
|
102
|
-
|
103
|
-
export const readmeCreator = async (
|
104
|
-
token: string,
|
105
|
-
inputs: TReadmeCreatorInputs
|
106
|
-
) => {
|
107
|
-
if (inputs.kind === "quiz" || inputs.kind === "read") {
|
108
|
-
const createReadmeInputs: TCreateReadmeInputs = {
|
109
|
-
title: inputs.title,
|
110
|
-
output_lang: inputs.output_lang,
|
111
|
-
list_of_exercises: inputs.list_of_exercises,
|
112
|
-
tutorial_description: inputs.tutorial_description,
|
113
|
-
include_quiz: inputs.kind === "quiz" ? "true" : "false",
|
114
|
-
lesson_description: inputs.lesson_description,
|
115
|
-
}
|
116
|
-
return createReadme(token, createReadmeInputs)
|
117
|
-
}
|
118
28
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
output_lang: inputs.output_lang,
|
123
|
-
list_of_exercises: inputs.list_of_exercises,
|
124
|
-
tutorial_description: inputs.tutorial_description,
|
125
|
-
lesson_description: inputs.lesson_description,
|
126
|
-
})
|
127
|
-
}
|
128
|
-
|
129
|
-
throw new Error("Invalid kind of lesson")
|
130
|
-
}
|
131
|
-
|
132
|
-
type TEstimateReadingTimeReturns = {
|
133
|
-
minutes: number
|
134
|
-
words: number
|
135
|
-
}
|
136
|
-
|
137
|
-
export const estimateReadingTime = (
|
138
|
-
text: string,
|
139
|
-
wordsPerMinute = 150
|
140
|
-
): TEstimateReadingTimeReturns => {
|
141
|
-
const words = text.trim().split(/\s+/).length
|
142
|
-
const minutes = words / wordsPerMinute
|
29
|
+
return response.data
|
30
|
+
} catch (error: unknown) {
|
31
|
+
const err = error as AxiosError
|
143
32
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
words,
|
149
|
-
}
|
150
|
-
} else {
|
151
|
-
return {
|
152
|
-
minutes,
|
153
|
-
words,
|
33
|
+
if (err.response?.status === 403) {
|
34
|
+
toast.error("You've reached the limit. Please log in to continue.")
|
35
|
+
} else {
|
36
|
+
toast.error("Something went wrong while generating the course.")
|
154
37
|
}
|
155
|
-
}
|
156
38
|
|
157
|
-
|
158
|
-
minutes: 1,
|
159
|
-
words,
|
39
|
+
return null
|
160
40
|
}
|
161
41
|
}
|
162
42
|
|
163
|
-
|
164
|
-
text: string
|
165
|
-
fkgl: number
|
166
|
-
}
|
167
|
-
|
168
|
-
export function checkReadability(
|
169
|
-
markdown: string,
|
170
|
-
wordsPerMinute = 200,
|
171
|
-
maxMinutes = 1
|
172
|
-
): {
|
173
|
-
newMarkdown: string
|
174
|
-
exceedsThreshold: boolean
|
175
|
-
minutes: number
|
176
|
-
body: string
|
177
|
-
fkglResult: TFKGLResult
|
178
|
-
// readingEase: number
|
179
|
-
} {
|
180
|
-
const parsed = frontMatter(markdown)
|
181
|
-
|
182
|
-
const fkglResult = fleschKincaidGrade(parsed.body)
|
183
|
-
|
184
|
-
const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
|
185
|
-
|
186
|
-
// const readingEase = estimateReadingEase(parsed.body)
|
187
|
-
let attributes = parsed.attributes ? parsed.attributes : {}
|
188
|
-
|
189
|
-
if (typeof parsed.attributes !== "object") {
|
190
|
-
attributes = {}
|
191
|
-
}
|
192
|
-
|
193
|
-
const updatedAttributes = {
|
194
|
-
...attributes,
|
195
|
-
readingTime,
|
196
|
-
fkglResult: fkglResult.fkgl,
|
197
|
-
}
|
198
|
-
|
199
|
-
let yamlFrontMatter = ""
|
43
|
+
export const isHuman = async (token: string) => {
|
200
44
|
try {
|
201
|
-
|
202
|
-
|
203
|
-
// Console.error("Error dumping YAML front matter")
|
204
|
-
return {
|
205
|
-
newMarkdown: "",
|
206
|
-
exceedsThreshold: false,
|
207
|
-
minutes: 0,
|
208
|
-
body: "",
|
209
|
-
fkglResult,
|
210
|
-
// readingEase: 0,
|
45
|
+
const body = {
|
46
|
+
access_token: token,
|
211
47
|
}
|
212
|
-
}
|
213
|
-
|
214
|
-
const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
|
215
|
-
|
216
|
-
return {
|
217
|
-
newMarkdown,
|
218
|
-
exceedsThreshold: readingTime.minutes > maxMinutes,
|
219
|
-
minutes: readingTime.minutes,
|
220
|
-
body: parsed.body,
|
221
|
-
fkglResult,
|
222
|
-
}
|
223
|
-
}
|
224
|
-
|
225
|
-
export function extractImagesFromMarkdown(markdown: string) {
|
226
|
-
const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
|
227
|
-
const images = []
|
228
|
-
let match
|
229
|
-
|
230
|
-
while ((match = imageRegex.exec(markdown)) !== null) {
|
231
|
-
const altText = match[1]
|
232
|
-
const url = match[2]
|
233
|
-
images.push({ alt: altText, url: url })
|
234
|
-
}
|
235
|
-
|
236
|
-
return images
|
237
|
-
}
|
238
|
-
|
239
|
-
export function estimateDuration(listOfSteps: string[]): number {
|
240
|
-
let duration = 0
|
241
|
-
|
242
|
-
for (const step of listOfSteps) {
|
243
|
-
if (step.includes("[READ:")) {
|
244
|
-
duration += 2
|
245
|
-
} else if (step.includes("[QUIZ:")) {
|
246
|
-
duration += 3
|
247
|
-
} else if (step.includes("[CODE:")) {
|
248
|
-
duration += 5
|
249
|
-
}
|
250
|
-
}
|
251
|
-
|
252
|
-
return duration
|
253
|
-
}
|
254
|
-
|
255
|
-
export function extractTextFromMarkdown(mdContent: string): string {
|
256
|
-
return mdContent
|
257
|
-
.replace(/!\[.*?]\(.*?\)/g, "") // Remove images
|
258
|
-
.replace(/\[.*?]\(.*?\)/g, "") // Remove links
|
259
|
-
.replace(/(```[\S\s]*?```|`.*?`)/g, "") // Remove inline & block code
|
260
|
-
.replace(/^#.*$/gm, "") // Remove headings
|
261
|
-
.replace(/(\*{1,2}|_{1,2})/g, "") // Remove bold/italic markers
|
262
|
-
.replace(/>\s?/g, "") // Remove blockquotes
|
263
|
-
.replace(/[*-]\s+/g, "") // Remove bullets from lists
|
264
|
-
.trim()
|
265
|
-
}
|
266
|
-
|
267
|
-
/**
|
268
|
-
* Splits a paragraph into words and separates each word into syllables.
|
269
|
-
* @param paragraph The input paragraph.
|
270
|
-
* @returns An array of words, each split into syllables.
|
271
|
-
*/
|
272
|
-
export function splitIntoSyllables(paragraph: string): string[] {
|
273
|
-
const words = paragraph.split(/\s+/)
|
274
|
-
const syllables: string[] = []
|
275
|
-
|
276
|
-
for (const word of words) {
|
277
|
-
syllables.push(...splitWordIntoSyllables(word))
|
278
|
-
}
|
279
|
-
|
280
|
-
return syllables
|
281
|
-
}
|
282
|
-
|
283
|
-
/**
|
284
|
-
* Splits a word into its syllables using a basic estimation.
|
285
|
-
* @param word The word to split.
|
286
|
-
* @returns An array of syllables.
|
287
|
-
*/
|
288
|
-
export function splitWordIntoSyllables(word: string): string[] {
|
289
|
-
const syllableCount = syllable(word)
|
290
|
-
|
291
|
-
// Simple heuristic: Split word into equal parts (not perfect, better with a dictionary-based approach)
|
292
|
-
if (syllableCount <= 1) return [word]
|
293
|
-
|
294
|
-
const approxLength = Math.ceil(word.length / syllableCount)
|
295
|
-
const syllables: string[] = []
|
296
|
-
for (let i = 0; i < word.length; i += approxLength) {
|
297
|
-
// eslint-disable-next-line
|
298
|
-
syllables.push(word.substring(i, i + approxLength))
|
299
|
-
}
|
300
|
-
|
301
|
-
return syllables
|
302
|
-
}
|
303
|
-
|
304
|
-
/**
|
305
|
-
* Extracts words from a given paragraph.
|
306
|
-
* @param paragraph The input text.
|
307
|
-
* @returns An array of words.
|
308
|
-
*/
|
309
|
-
export function extractWords(paragraph: string): string[] {
|
310
|
-
const words = paragraph.match(/\b\w+\b/g) // Match words using regex
|
311
|
-
return words ? words : [] // Return words or an empty array if none found
|
312
|
-
}
|
313
|
-
|
314
|
-
/**
|
315
|
-
* Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
|
316
|
-
* @param text The input paragraph.
|
317
|
-
* @returns The FKGL score.
|
318
|
-
*/
|
319
|
-
export function fleschKincaidGrade(text: string): TFKGLResult {
|
320
|
-
const processableText = extractTextFromMarkdown(text)
|
321
|
-
const words = extractWords(processableText)
|
322
|
-
const numWords = words.length
|
323
|
-
const numSentences = countSentences(processableText)
|
324
|
-
const numSyllables = words.reduce((total, word) => total + syllable(word), 0)
|
325
|
-
|
326
|
-
if (numWords === 0 || numSentences === 0) {
|
327
|
-
return {
|
328
|
-
text,
|
329
|
-
fkgl: 0,
|
330
|
-
}
|
331
|
-
}
|
332
|
-
|
333
|
-
const fkgl =
|
334
|
-
// eslint-disable-next-line
|
335
|
-
0.39 * (numWords / numSentences) + 11.8 * (numSyllables / numWords) - 15.59
|
336
|
-
|
337
|
-
return {
|
338
|
-
text,
|
339
|
-
fkgl: parseFloat(fkgl.toFixed(2)),
|
340
|
-
}
|
341
|
-
}
|
342
|
-
|
343
|
-
/**
|
344
|
-
* Counts the number of sentences in a given text.
|
345
|
-
* @param text The input paragraph.
|
346
|
-
* @returns The total number of sentences.
|
347
|
-
*/
|
348
|
-
export function countSentences(text: string): number {
|
349
|
-
const sentences = text
|
350
|
-
.split(/[!.?]+/)
|
351
|
-
.filter((sentence) => sentence.trim().length > 0)
|
352
|
-
return sentences.length
|
353
|
-
}
|
354
|
-
|
355
|
-
export function howManyDifficultParagraphs(
|
356
|
-
paragraphs: TFKGLResult[],
|
357
|
-
maxFKGL: number
|
358
|
-
): number {
|
359
|
-
return paragraphs.filter((paragraph) => paragraph.fkgl > maxFKGL).length
|
360
|
-
}
|
361
|
-
|
362
|
-
type TReduceReadmeInputs = {
|
363
|
-
lesson: string
|
364
|
-
number_of_words: string
|
365
|
-
expected_number_words: string
|
366
|
-
fkgl_results: string
|
367
|
-
expected_grade_level: string
|
368
|
-
}
|
369
|
-
export async function makeReadmeReadable(
|
370
|
-
rigoToken: string,
|
371
|
-
inputs: TReduceReadmeInputs
|
372
|
-
) {
|
373
|
-
try {
|
374
48
|
const response = await axios.post(
|
375
|
-
`${RIGOBOT_HOST}/v1/
|
376
|
-
|
49
|
+
`${RIGOBOT_HOST}/v1/auth/verify/humanity`,
|
50
|
+
body,
|
377
51
|
{
|
378
52
|
headers: {
|
379
53
|
"Content-Type": "application/json",
|
380
|
-
Authorization: "Token " + rigoToken,
|
381
54
|
},
|
382
55
|
}
|
383
56
|
)
|
384
57
|
|
385
|
-
return response.
|
58
|
+
return response.status === 200
|
386
59
|
} catch (error) {
|
387
60
|
console.error(error)
|
388
|
-
|
389
|
-
return null
|
61
|
+
return false
|
390
62
|
}
|
391
63
|
}
|
392
|
-
|
393
|
-
type TCreateCodeFileInputs = {
|
394
|
-
readme: string
|
395
|
-
tutorial_info: string
|
396
|
-
}
|
397
|
-
|
398
|
-
export const createCodeFile = async (
|
399
|
-
token: string,
|
400
|
-
inputs: TCreateCodeFileInputs
|
401
|
-
) => {
|
402
|
-
const response = await axios.post(
|
403
|
-
`${RIGOBOT_HOST}/v1/prompting/completion/456/`,
|
404
|
-
{
|
405
|
-
inputs: inputs,
|
406
|
-
include_purpose_objective: false,
|
407
|
-
execute_async: false,
|
408
|
-
},
|
409
|
-
{
|
410
|
-
headers: {
|
411
|
-
"Content-Type": "application/json",
|
412
|
-
Authorization: "Token " + token,
|
413
|
-
},
|
414
|
-
}
|
415
|
-
)
|
416
|
-
|
417
|
-
return response.data
|
418
|
-
}
|