@learnpack/learnpack 5.0.15 → 5.0.16
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 +19 -11
- package/lib/commands/init.d.ts +1 -0
- package/lib/commands/init.js +122 -102
- package/lib/utils/BaseCommand.d.ts +3 -0
- package/lib/utils/BaseCommand.js +7 -0
- package/lib/utils/api.d.ts +3 -0
- package/lib/utils/api.js +25 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +144 -106
- package/src/utils/BaseCommand.ts +56 -48
- package/src/utils/api.ts +42 -0
package/README.md
CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
|
|
21
21
|
$ learnpack COMMAND
|
22
22
|
running command...
|
23
23
|
$ learnpack (-v|--version|version)
|
24
|
-
@learnpack/learnpack/5.0.
|
24
|
+
@learnpack/learnpack/5.0.16 win32-x64 node-v20.16.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -75,7 +75,7 @@ DESCRIPTION
|
|
75
75
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
76
76
|
```
|
77
77
|
|
78
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
78
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\audit.ts)_
|
79
79
|
|
80
80
|
## `learnpack clean`
|
81
81
|
|
@@ -90,7 +90,7 @@ DESCRIPTION
|
|
90
90
|
Extra documentation goes here
|
91
91
|
```
|
92
92
|
|
93
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
93
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\clean.ts)_
|
94
94
|
|
95
95
|
## `learnpack download [PACKAGE]`
|
96
96
|
|
@@ -108,7 +108,7 @@ DESCRIPTION
|
|
108
108
|
Extra documentation goes here
|
109
109
|
```
|
110
110
|
|
111
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
111
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\download.ts)_
|
112
112
|
|
113
113
|
## `learnpack help [COMMAND]`
|
114
114
|
|
@@ -137,9 +137,10 @@ USAGE
|
|
137
137
|
|
138
138
|
OPTIONS
|
139
139
|
-h, --grading show CLI help
|
140
|
+
-y, --yes Skip all prompts and initialize an empty project
|
140
141
|
```
|
141
142
|
|
142
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
143
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\init.ts)_
|
143
144
|
|
144
145
|
## `learnpack login [PACKAGE]`
|
145
146
|
|
@@ -157,7 +158,7 @@ DESCRIPTION
|
|
157
158
|
Extra documentation goes here
|
158
159
|
```
|
159
160
|
|
160
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
161
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\login.ts)_
|
161
162
|
|
162
163
|
## `learnpack logout [PACKAGE]`
|
163
164
|
|
@@ -175,7 +176,7 @@ DESCRIPTION
|
|
175
176
|
Extra documentation goes here
|
176
177
|
```
|
177
178
|
|
178
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
179
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\logout.ts)_
|
179
180
|
|
180
181
|
## `learnpack plugins`
|
181
182
|
|
@@ -306,7 +307,7 @@ OPTIONS
|
|
306
307
|
-h, --help show CLI help
|
307
308
|
```
|
308
309
|
|
309
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
310
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\publish.ts)_
|
310
311
|
|
311
312
|
## `learnpack start`
|
312
313
|
|
@@ -325,9 +326,10 @@ OPTIONS
|
|
325
326
|
-p, --port=port server port
|
326
327
|
-v, --version=version E.g: 1.0.1
|
327
328
|
-w, --watch Watch for file changes
|
329
|
+
-y, --yes Skip all prompts and initialize an empty project
|
328
330
|
```
|
329
331
|
|
330
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
332
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\start.ts)_
|
331
333
|
|
332
334
|
## `learnpack test [EXERCISESLUG]`
|
333
335
|
|
@@ -339,9 +341,12 @@ USAGE
|
|
339
341
|
|
340
342
|
ARGUMENTS
|
341
343
|
EXERCISESLUG The name of the exercise to test
|
344
|
+
|
345
|
+
OPTIONS
|
346
|
+
-y, --yes Skip all prompts and initialize an empty project
|
342
347
|
```
|
343
348
|
|
344
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
349
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\test.ts)_
|
345
350
|
|
346
351
|
## `learnpack translate`
|
347
352
|
|
@@ -350,9 +355,12 @@ List all the lessons, the user is able of select many of them to translate to th
|
|
350
355
|
```
|
351
356
|
USAGE
|
352
357
|
$ learnpack translate
|
358
|
+
|
359
|
+
OPTIONS
|
360
|
+
-y, --yes Skip all prompts and initialize an empty project
|
353
361
|
```
|
354
362
|
|
355
|
-
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
363
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\translate.ts)_
|
356
364
|
<!-- commandsstop -->
|
357
365
|
|
358
366
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
package/lib/commands/init.d.ts
CHANGED
package/lib/commands/init.js
CHANGED
@@ -6,11 +6,13 @@ const BaseCommand_1 = require("../utils/BaseCommand");
|
|
6
6
|
const fs = require("fs-extra");
|
7
7
|
const prompts = require("prompts");
|
8
8
|
const cli_ux_1 = require("cli-ux");
|
9
|
+
const eta = require("eta");
|
9
10
|
const api_1 = require("../utils/api");
|
10
11
|
const console_1 = require("../utils/console");
|
11
12
|
const errors_1 = require("../utils/errors");
|
12
13
|
const path = require("path");
|
13
14
|
const rigoActions_1 = require("../utils/rigoActions");
|
15
|
+
const api_2 = require("../utils/api");
|
14
16
|
const slugify = (text) => {
|
15
17
|
return text
|
16
18
|
.toString()
|
@@ -38,6 +40,24 @@ function extractImagesFromMarkdown(markdown) {
|
|
38
40
|
function getFilenameFromUrl(url) {
|
39
41
|
return path.basename(url);
|
40
42
|
}
|
43
|
+
const makePackageInfo = (choices) => {
|
44
|
+
const packageInfo = {
|
45
|
+
grading: choices.grading,
|
46
|
+
difficulty: choices.difficulty,
|
47
|
+
duration: parseInt(choices.duration),
|
48
|
+
description: {
|
49
|
+
us: choices.description,
|
50
|
+
},
|
51
|
+
title: {
|
52
|
+
us: choices.title,
|
53
|
+
},
|
54
|
+
slug: choices.title
|
55
|
+
.toLowerCase()
|
56
|
+
.replace(/ /g, "-")
|
57
|
+
.replace(/[^\w-]+/g, ""),
|
58
|
+
};
|
59
|
+
return packageInfo;
|
60
|
+
};
|
41
61
|
async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents) {
|
42
62
|
const readmeFilename = `README.md`;
|
43
63
|
const readmeContent = await (0, rigoActions_1.generateCourseIntroduction)(rigoToken, {
|
@@ -47,7 +67,7 @@ async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeCo
|
|
47
67
|
fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer);
|
48
68
|
}
|
49
69
|
const handleAILogic = async (tutorialDir, packageInfo) => {
|
50
|
-
console_1.default.info("Almost there! First you need to login to use
|
70
|
+
console_1.default.info("Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/creators");
|
51
71
|
fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"));
|
52
72
|
const loginPrompts = await prompts([
|
53
73
|
{
|
@@ -77,10 +97,17 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
77
97
|
return;
|
78
98
|
}
|
79
99
|
const rigoToken = sessionPayload.rigobot.key;
|
100
|
+
const consumables = await (0, api_2.getConsumables)(sessionPayload.token);
|
101
|
+
if (consumables.ai_tutorial_generation === 0) {
|
102
|
+
console_1.default.error("It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions?token=" +
|
103
|
+
sessionPayload.token +
|
104
|
+
". If you believe there is an issue you can always contact support@4geeks.com");
|
105
|
+
process.exit(1);
|
106
|
+
}
|
80
107
|
const isCreator = await (0, rigoActions_1.hasCreatorPermission)(rigoToken);
|
81
108
|
if (!isCreator) {
|
82
109
|
console_1.default.error("👀 Oops! You need to be a creator to use our specialized AI. Please contact support");
|
83
|
-
|
110
|
+
process.exit(1);
|
84
111
|
}
|
85
112
|
console_1.default.success("🎉 Let's begin this learning journey!");
|
86
113
|
const aiChoices = await prompts([
|
@@ -161,91 +188,92 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
161
188
|
await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents);
|
162
189
|
return true;
|
163
190
|
};
|
191
|
+
const getChoices = async (empty) => {
|
192
|
+
const defaultChoices = {
|
193
|
+
title: "My Interactive Tutorial",
|
194
|
+
description: "",
|
195
|
+
difficulty: "beginner",
|
196
|
+
duration: 1,
|
197
|
+
useAI: "no",
|
198
|
+
grading: "isolated",
|
199
|
+
};
|
200
|
+
if (empty) {
|
201
|
+
return defaultChoices;
|
202
|
+
}
|
203
|
+
const choices = await prompts([
|
204
|
+
{
|
205
|
+
type: "select",
|
206
|
+
name: "grading",
|
207
|
+
message: "Is the auto-grading going to be isolated or incremental?",
|
208
|
+
choices: [
|
209
|
+
{
|
210
|
+
title: "Incremental: Build on top of each other like a tutorial",
|
211
|
+
value: "incremental",
|
212
|
+
},
|
213
|
+
{ title: "Isolated: Small separated exercises", value: "isolated" },
|
214
|
+
{
|
215
|
+
title: "No grading: No feedback or testing whatsoever",
|
216
|
+
value: null,
|
217
|
+
},
|
218
|
+
],
|
219
|
+
},
|
220
|
+
{
|
221
|
+
type: "text",
|
222
|
+
name: "title",
|
223
|
+
initial: "My Interactive Tutorial",
|
224
|
+
message: "Title for your tutorial? Press enter to leave as it is",
|
225
|
+
},
|
226
|
+
{
|
227
|
+
type: "text",
|
228
|
+
name: "description",
|
229
|
+
initial: "",
|
230
|
+
message: "Description for your tutorial? Press enter to leave blank",
|
231
|
+
},
|
232
|
+
{
|
233
|
+
type: "select",
|
234
|
+
name: "difficulty",
|
235
|
+
message: "How difficulty will be to complete the tutorial?",
|
236
|
+
choices: [
|
237
|
+
{ title: "Begginer (no previous experience)", value: "beginner" },
|
238
|
+
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
239
|
+
{
|
240
|
+
title: "Intermediate (you need experience)",
|
241
|
+
value: "intermediate",
|
242
|
+
},
|
243
|
+
{ title: "Hard (master the topic)", value: "hard" },
|
244
|
+
],
|
245
|
+
},
|
246
|
+
{
|
247
|
+
type: "text",
|
248
|
+
name: "duration",
|
249
|
+
initial: "1",
|
250
|
+
message: "How many hours avg it takes to complete (number)?",
|
251
|
+
validate: (value) => {
|
252
|
+
const n = Math.floor(Number(value));
|
253
|
+
return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0;
|
254
|
+
},
|
255
|
+
},
|
256
|
+
{
|
257
|
+
type: "select",
|
258
|
+
name: "useAI",
|
259
|
+
message: "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
|
260
|
+
choices: [
|
261
|
+
{
|
262
|
+
title: "Yes, please help me",
|
263
|
+
value: "yes",
|
264
|
+
},
|
265
|
+
{ title: "No, thanks, I prefer to do it manually", value: "no" },
|
266
|
+
],
|
267
|
+
},
|
268
|
+
]);
|
269
|
+
return choices;
|
270
|
+
};
|
164
271
|
class InitComand extends BaseCommand_1.default {
|
165
272
|
async run() {
|
166
273
|
const { flags } = this.parse(InitComand);
|
167
274
|
await alreadyInitialized();
|
168
|
-
const choices = await
|
169
|
-
|
170
|
-
type: "select",
|
171
|
-
name: "grading",
|
172
|
-
message: "Is the auto-grading going to be isolated or incremental?",
|
173
|
-
choices: [
|
174
|
-
{
|
175
|
-
title: "Incremental: Build on top of each other like a tutorial",
|
176
|
-
value: "incremental",
|
177
|
-
},
|
178
|
-
{ title: "Isolated: Small separated exercises", value: "isolated" },
|
179
|
-
{
|
180
|
-
title: "No grading: No feedback or testing whatsoever",
|
181
|
-
value: null,
|
182
|
-
},
|
183
|
-
],
|
184
|
-
},
|
185
|
-
{
|
186
|
-
type: "text",
|
187
|
-
name: "title",
|
188
|
-
initial: "My Interactive Tutorial",
|
189
|
-
message: "Title for your tutorial? Press enter to leave as it is",
|
190
|
-
},
|
191
|
-
{
|
192
|
-
type: "text",
|
193
|
-
name: "description",
|
194
|
-
initial: "",
|
195
|
-
message: "Description for your tutorial? Press enter to leave blank",
|
196
|
-
},
|
197
|
-
{
|
198
|
-
type: "select",
|
199
|
-
name: "difficulty",
|
200
|
-
message: "How difficulty will be to complete the tutorial?",
|
201
|
-
choices: [
|
202
|
-
{ title: "Begginer (no previous experience)", value: "beginner" },
|
203
|
-
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
204
|
-
{
|
205
|
-
title: "Intermediate (you need experience)",
|
206
|
-
value: "intermediate",
|
207
|
-
},
|
208
|
-
{ title: "Hard (master the topic)", value: "hard" },
|
209
|
-
],
|
210
|
-
},
|
211
|
-
{
|
212
|
-
type: "text",
|
213
|
-
name: "duration",
|
214
|
-
initial: "1",
|
215
|
-
message: "How many hours avg it takes to complete (number)?",
|
216
|
-
validate: (value) => {
|
217
|
-
const n = Math.floor(Number(value));
|
218
|
-
return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0;
|
219
|
-
},
|
220
|
-
},
|
221
|
-
{
|
222
|
-
type: "select",
|
223
|
-
name: "useAI",
|
224
|
-
message: "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
|
225
|
-
choices: [
|
226
|
-
{
|
227
|
-
title: "Yes, please help me",
|
228
|
-
value: "yes",
|
229
|
-
},
|
230
|
-
{ title: "No, thanks, I prefer to do it manually", value: "no" },
|
231
|
-
],
|
232
|
-
},
|
233
|
-
]);
|
234
|
-
const packageInfo = {
|
235
|
-
grading: choices.grading,
|
236
|
-
difficulty: choices.difficulty,
|
237
|
-
duration: parseInt(choices.duration),
|
238
|
-
description: {
|
239
|
-
us: choices.description,
|
240
|
-
},
|
241
|
-
title: {
|
242
|
-
us: choices.title,
|
243
|
-
},
|
244
|
-
slug: choices.title
|
245
|
-
.toLowerCase()
|
246
|
-
.replace(/ /g, "-")
|
247
|
-
.replace(/[^\w-]+/g, ""),
|
248
|
-
};
|
275
|
+
const choices = await getChoices(flags.yes);
|
276
|
+
const packageInfo = makePackageInfo(choices);
|
249
277
|
const tutorialDir = `./${packageInfo.slug}`;
|
250
278
|
fs.ensureDirSync(tutorialDir);
|
251
279
|
const templatesDir = path.resolve(__dirname, "../../src/utils/templates/" + (choices.grading || "no-grading"));
|
@@ -259,25 +287,16 @@ class InitComand extends BaseCommand_1.default {
|
|
259
287
|
// Creating README files
|
260
288
|
for (const language of languages) {
|
261
289
|
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`;
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
// }
|
273
|
-
// const readmeContent = eta.render(
|
274
|
-
// fs.readFileSync(readmeTemplatePath, "utf-8"),
|
275
|
-
// readmeObject
|
276
|
-
// )
|
277
|
-
// fs.writeFileSync(
|
278
|
-
// path.join(tutorialDir, `${readmeFilename}.md`),
|
279
|
-
// readmeContent
|
280
|
-
// )
|
290
|
+
const readmeTemplatePath = path.resolve(templatesDir, `${readmeFilename}.ejs`);
|
291
|
+
const readmeObject = {
|
292
|
+
title: packageInfo.title.us,
|
293
|
+
description: packageInfo.description.us,
|
294
|
+
grading: packageInfo.grading,
|
295
|
+
difficulty: packageInfo.difficulty,
|
296
|
+
duration: packageInfo.duration,
|
297
|
+
};
|
298
|
+
const readmeContent = eta.render(fs.readFileSync(readmeTemplatePath, "utf-8"), readmeObject);
|
299
|
+
fs.writeFileSync(path.join(tutorialDir, `${readmeFilename}.md`), readmeContent);
|
281
300
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
282
301
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`));
|
283
302
|
}
|
@@ -289,6 +308,7 @@ class InitComand extends BaseCommand_1.default {
|
|
289
308
|
console_1.default.success(`😋 Package initialized successfully in ${tutorialDir}`);
|
290
309
|
console_1.default.help(`Get inside the tutorial with the command: $ cd ${tutorialDir}`);
|
291
310
|
console_1.default.help(`Start the exercises by running the following command on your terminal: $ learnpack start`);
|
311
|
+
process.exit(0);
|
292
312
|
}
|
293
313
|
}
|
294
314
|
InitComand.description = "Create a new learning package: Book, Tutorial or Exercise";
|
@@ -3,6 +3,9 @@ declare class BaseCommand extends Command {
|
|
3
3
|
catch(err: any): Promise<void>;
|
4
4
|
init(): Promise<void>;
|
5
5
|
finally(): Promise<void>;
|
6
|
+
static flags: {
|
7
|
+
yes: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
|
8
|
+
};
|
6
9
|
run(): Promise<void>;
|
7
10
|
}
|
8
11
|
export default BaseCommand;
|
package/lib/utils/BaseCommand.js
CHANGED
@@ -38,4 +38,11 @@ class BaseCommand extends command_1.Command {
|
|
38
38
|
// console.log('running my command')
|
39
39
|
}
|
40
40
|
}
|
41
|
+
BaseCommand.flags = {
|
42
|
+
yes: command_1.flags.boolean({
|
43
|
+
char: "y",
|
44
|
+
description: "Skip all prompts and initialize an empty project",
|
45
|
+
default: false,
|
46
|
+
}),
|
47
|
+
};
|
41
48
|
exports.default = BaseCommand;
|
package/lib/utils/api.d.ts
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
type TConsumableSlug = "ai-conversation-message" | "ai-compilation" | "ai-tutorial-generation";
|
2
|
+
export declare const countConsumables: (consumables: any, consumableSlug?: TConsumableSlug) => any;
|
3
|
+
export declare const getConsumables: (token: string) => Promise<any>;
|
1
4
|
declare const _default: {
|
2
5
|
login: (identification: string, password: string) => Promise<any>;
|
3
6
|
publish: (config: any) => Promise<any>;
|
package/lib/utils/api.js
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getConsumables = exports.countConsumables = void 0;
|
3
4
|
const console_1 = require("../utils/console");
|
4
5
|
const storage = require("node-persist");
|
5
6
|
const cli_ux_1 = require("cli-ux");
|
7
|
+
const axios_1 = require("axios");
|
6
8
|
const HOST = "https://breathecode.herokuapp.com";
|
7
9
|
const RIGOBOT_HOST = "https://rigobot.herokuapp.com";
|
8
10
|
// const RIGOBOT_HOST = "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
@@ -249,6 +251,29 @@ const sendStreamTelemetry = async function (url, body) {
|
|
249
251
|
console_1.default.debug("Error while sending stream Telemetry", error);
|
250
252
|
});
|
251
253
|
};
|
254
|
+
const countConsumables = (consumables, consumableSlug = "ai-tutorial-generation") => {
|
255
|
+
// Find the void that matches the consumableSlug
|
256
|
+
const consumable = consumables.voids.find((voidItem) => voidItem.slug === consumableSlug);
|
257
|
+
// Return the available units or 0 if not found
|
258
|
+
return consumable ? consumable.balance.unit : 0;
|
259
|
+
};
|
260
|
+
exports.countConsumables = countConsumables;
|
261
|
+
const getConsumables = async (token) => {
|
262
|
+
const url = `${HOST}/v1/payments/me/service/consumable?virtual=true`;
|
263
|
+
const headers = {
|
264
|
+
Authorization: `Token ${token}`,
|
265
|
+
};
|
266
|
+
try {
|
267
|
+
const response = await axios_1.default.get(url, { headers });
|
268
|
+
const ai_tutorial_generation = (0, exports.countConsumables)(response.data, "ai-tutorial-generation");
|
269
|
+
return { ai_tutorial_generation };
|
270
|
+
}
|
271
|
+
catch (error) {
|
272
|
+
console.error("Error fetching consumables:", error);
|
273
|
+
throw error;
|
274
|
+
}
|
275
|
+
};
|
276
|
+
exports.getConsumables = getConsumables;
|
252
277
|
exports.default = {
|
253
278
|
login,
|
254
279
|
publish,
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.16","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|
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.
|
4
|
+
"version": "5.0.16",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/init.ts
CHANGED
@@ -19,7 +19,7 @@ import {
|
|
19
19
|
downloadImage,
|
20
20
|
generateCourseIntroduction,
|
21
21
|
} from "../utils/rigoActions"
|
22
|
-
|
22
|
+
import { getConsumables } from "../utils/api"
|
23
23
|
const slugify = (text: string) => {
|
24
24
|
return text
|
25
25
|
.toString()
|
@@ -53,6 +53,25 @@ function getFilenameFromUrl(url: string) {
|
|
53
53
|
return path.basename(url)
|
54
54
|
}
|
55
55
|
|
56
|
+
const makePackageInfo = (choices: any) => {
|
57
|
+
const packageInfo = {
|
58
|
+
grading: choices.grading,
|
59
|
+
difficulty: choices.difficulty,
|
60
|
+
duration: parseInt(choices.duration),
|
61
|
+
description: {
|
62
|
+
us: choices.description,
|
63
|
+
},
|
64
|
+
title: {
|
65
|
+
us: choices.title,
|
66
|
+
},
|
67
|
+
slug: choices.title
|
68
|
+
.toLowerCase()
|
69
|
+
.replace(/ /g, "-")
|
70
|
+
.replace(/[^\w-]+/g, ""),
|
71
|
+
}
|
72
|
+
return packageInfo
|
73
|
+
}
|
74
|
+
|
56
75
|
async function createPreviewReadme(
|
57
76
|
tutorialDir: string,
|
58
77
|
packageInfo: PackageInfo,
|
@@ -81,7 +100,9 @@ type PackageInfo = {
|
|
81
100
|
}
|
82
101
|
|
83
102
|
const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
84
|
-
Console.info(
|
103
|
+
Console.info(
|
104
|
+
"Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/creators"
|
105
|
+
)
|
85
106
|
|
86
107
|
fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"))
|
87
108
|
|
@@ -115,12 +136,23 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
115
136
|
|
116
137
|
const rigoToken = sessionPayload.rigobot.key
|
117
138
|
|
139
|
+
const consumables = await getConsumables(sessionPayload.token)
|
140
|
+
|
141
|
+
if (consumables.ai_tutorial_generation === 0) {
|
142
|
+
Console.error(
|
143
|
+
"It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions?token=" +
|
144
|
+
sessionPayload.token +
|
145
|
+
". If you believe there is an issue you can always contact support@4geeks.com"
|
146
|
+
)
|
147
|
+
process.exit(1)
|
148
|
+
}
|
149
|
+
|
118
150
|
const isCreator = await hasCreatorPermission(rigoToken)
|
119
151
|
if (!isCreator) {
|
120
152
|
Console.error(
|
121
153
|
"👀 Oops! You need to be a creator to use our specialized AI. Please contact support"
|
122
154
|
)
|
123
|
-
|
155
|
+
process.exit(1)
|
124
156
|
}
|
125
157
|
|
126
158
|
Console.success("🎉 Let's begin this learning journey!")
|
@@ -236,6 +268,91 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
236
268
|
return true
|
237
269
|
}
|
238
270
|
|
271
|
+
const getChoices = async (empty: boolean) => {
|
272
|
+
const defaultChoices = {
|
273
|
+
title: "My Interactive Tutorial",
|
274
|
+
description: "",
|
275
|
+
difficulty: "beginner",
|
276
|
+
duration: 1,
|
277
|
+
useAI: "no",
|
278
|
+
grading: "isolated",
|
279
|
+
}
|
280
|
+
|
281
|
+
if (empty) {
|
282
|
+
return defaultChoices
|
283
|
+
}
|
284
|
+
|
285
|
+
const choices = await prompts([
|
286
|
+
{
|
287
|
+
type: "select",
|
288
|
+
name: "grading",
|
289
|
+
message: "Is the auto-grading going to be isolated or incremental?",
|
290
|
+
choices: [
|
291
|
+
{
|
292
|
+
title: "Incremental: Build on top of each other like a tutorial",
|
293
|
+
value: "incremental",
|
294
|
+
},
|
295
|
+
{ title: "Isolated: Small separated exercises", value: "isolated" },
|
296
|
+
{
|
297
|
+
title: "No grading: No feedback or testing whatsoever",
|
298
|
+
value: null,
|
299
|
+
},
|
300
|
+
],
|
301
|
+
},
|
302
|
+
{
|
303
|
+
type: "text",
|
304
|
+
name: "title",
|
305
|
+
initial: "My Interactive Tutorial",
|
306
|
+
message: "Title for your tutorial? Press enter to leave as it is",
|
307
|
+
},
|
308
|
+
{
|
309
|
+
type: "text",
|
310
|
+
name: "description",
|
311
|
+
initial: "",
|
312
|
+
message: "Description for your tutorial? Press enter to leave blank",
|
313
|
+
},
|
314
|
+
{
|
315
|
+
type: "select",
|
316
|
+
name: "difficulty",
|
317
|
+
message: "How difficulty will be to complete the tutorial?",
|
318
|
+
choices: [
|
319
|
+
{ title: "Begginer (no previous experience)", value: "beginner" },
|
320
|
+
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
321
|
+
{
|
322
|
+
title: "Intermediate (you need experience)",
|
323
|
+
value: "intermediate",
|
324
|
+
},
|
325
|
+
{ title: "Hard (master the topic)", value: "hard" },
|
326
|
+
],
|
327
|
+
},
|
328
|
+
{
|
329
|
+
type: "text",
|
330
|
+
name: "duration",
|
331
|
+
initial: "1",
|
332
|
+
message: "How many hours avg it takes to complete (number)?",
|
333
|
+
validate: (value: string) => {
|
334
|
+
const n = Math.floor(Number(value))
|
335
|
+
return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
|
336
|
+
},
|
337
|
+
},
|
338
|
+
{
|
339
|
+
type: "select",
|
340
|
+
name: "useAI",
|
341
|
+
message:
|
342
|
+
"Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
|
343
|
+
choices: [
|
344
|
+
{
|
345
|
+
title: "Yes, please help me",
|
346
|
+
value: "yes",
|
347
|
+
},
|
348
|
+
{ title: "No, thanks, I prefer to do it manually", value: "no" },
|
349
|
+
],
|
350
|
+
},
|
351
|
+
])
|
352
|
+
|
353
|
+
return choices
|
354
|
+
}
|
355
|
+
|
239
356
|
class InitComand extends BaseCommand {
|
240
357
|
static description =
|
241
358
|
"Create a new learning package: Book, Tutorial or Exercise"
|
@@ -250,89 +367,9 @@ class InitComand extends BaseCommand {
|
|
250
367
|
|
251
368
|
await alreadyInitialized()
|
252
369
|
|
253
|
-
const choices = await
|
254
|
-
|
255
|
-
|
256
|
-
name: "grading",
|
257
|
-
message: "Is the auto-grading going to be isolated or incremental?",
|
258
|
-
choices: [
|
259
|
-
{
|
260
|
-
title: "Incremental: Build on top of each other like a tutorial",
|
261
|
-
value: "incremental",
|
262
|
-
},
|
263
|
-
{ title: "Isolated: Small separated exercises", value: "isolated" },
|
264
|
-
{
|
265
|
-
title: "No grading: No feedback or testing whatsoever",
|
266
|
-
value: null,
|
267
|
-
},
|
268
|
-
],
|
269
|
-
},
|
270
|
-
{
|
271
|
-
type: "text",
|
272
|
-
name: "title",
|
273
|
-
initial: "My Interactive Tutorial",
|
274
|
-
message: "Title for your tutorial? Press enter to leave as it is",
|
275
|
-
},
|
276
|
-
{
|
277
|
-
type: "text",
|
278
|
-
name: "description",
|
279
|
-
initial: "",
|
280
|
-
message: "Description for your tutorial? Press enter to leave blank",
|
281
|
-
},
|
282
|
-
{
|
283
|
-
type: "select",
|
284
|
-
name: "difficulty",
|
285
|
-
message: "How difficulty will be to complete the tutorial?",
|
286
|
-
choices: [
|
287
|
-
{ title: "Begginer (no previous experience)", value: "beginner" },
|
288
|
-
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
289
|
-
{
|
290
|
-
title: "Intermediate (you need experience)",
|
291
|
-
value: "intermediate",
|
292
|
-
},
|
293
|
-
{ title: "Hard (master the topic)", value: "hard" },
|
294
|
-
],
|
295
|
-
},
|
296
|
-
{
|
297
|
-
type: "text",
|
298
|
-
name: "duration",
|
299
|
-
initial: "1",
|
300
|
-
message: "How many hours avg it takes to complete (number)?",
|
301
|
-
validate: (value: string) => {
|
302
|
-
const n = Math.floor(Number(value))
|
303
|
-
return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
|
304
|
-
},
|
305
|
-
},
|
306
|
-
{
|
307
|
-
type: "select",
|
308
|
-
name: "useAI",
|
309
|
-
message:
|
310
|
-
"Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
|
311
|
-
choices: [
|
312
|
-
{
|
313
|
-
title: "Yes, please help me",
|
314
|
-
value: "yes",
|
315
|
-
},
|
316
|
-
{ title: "No, thanks, I prefer to do it manually", value: "no" },
|
317
|
-
],
|
318
|
-
},
|
319
|
-
])
|
320
|
-
|
321
|
-
const packageInfo = {
|
322
|
-
grading: choices.grading,
|
323
|
-
difficulty: choices.difficulty,
|
324
|
-
duration: parseInt(choices.duration),
|
325
|
-
description: {
|
326
|
-
us: choices.description,
|
327
|
-
},
|
328
|
-
title: {
|
329
|
-
us: choices.title,
|
330
|
-
},
|
331
|
-
slug: choices.title
|
332
|
-
.toLowerCase()
|
333
|
-
.replace(/ /g, "-")
|
334
|
-
.replace(/[^\w-]+/g, ""),
|
335
|
-
}
|
370
|
+
const choices = await getChoices(flags.yes)
|
371
|
+
|
372
|
+
const packageInfo = makePackageInfo(choices)
|
336
373
|
|
337
374
|
const tutorialDir = `./${packageInfo.slug}`
|
338
375
|
fs.ensureDirSync(tutorialDir)
|
@@ -354,26 +391,26 @@ class InitComand extends BaseCommand {
|
|
354
391
|
// Creating README files
|
355
392
|
for (const language of languages) {
|
356
393
|
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
394
|
+
const readmeTemplatePath = path.resolve(
|
395
|
+
templatesDir,
|
396
|
+
`${readmeFilename}.ejs`
|
397
|
+
)
|
398
|
+
|
399
|
+
const readmeObject = {
|
400
|
+
title: packageInfo.title.us,
|
401
|
+
description: packageInfo.description.us,
|
402
|
+
grading: packageInfo.grading,
|
403
|
+
difficulty: packageInfo.difficulty,
|
404
|
+
duration: packageInfo.duration,
|
405
|
+
}
|
406
|
+
const readmeContent = eta.render(
|
407
|
+
fs.readFileSync(readmeTemplatePath, "utf-8"),
|
408
|
+
readmeObject
|
409
|
+
)
|
410
|
+
fs.writeFileSync(
|
411
|
+
path.join(tutorialDir, `${readmeFilename}.md`),
|
412
|
+
readmeContent
|
413
|
+
)
|
377
414
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
378
415
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`))
|
379
416
|
}
|
@@ -398,6 +435,7 @@ class InitComand extends BaseCommand {
|
|
398
435
|
Console.help(
|
399
436
|
`Start the exercises by running the following command on your terminal: $ learnpack start`
|
400
437
|
)
|
438
|
+
process.exit(0)
|
401
439
|
}
|
402
440
|
}
|
403
441
|
|
package/src/utils/BaseCommand.ts
CHANGED
@@ -1,48 +1,56 @@
|
|
1
|
-
import { Command } from "@oclif/command"
|
2
|
-
import Console from "./console"
|
3
|
-
import { createInterface } from "readline"
|
4
|
-
// import SessionManager from '../managers/session'
|
5
|
-
|
6
|
-
class BaseCommand extends Command {
|
7
|
-
async catch(err: any) {
|
8
|
-
Console.debug("COMMAND CATCH", err)
|
9
|
-
|
10
|
-
throw err
|
11
|
-
}
|
12
|
-
|
13
|
-
async init() {
|
14
|
-
const { flags, args } = this.parse(BaseCommand)
|
15
|
-
Console.debug("COMMAND INIT")
|
16
|
-
Console.debug("These are your flags: ", flags)
|
17
|
-
Console.debug("These are your args: ", args)
|
18
|
-
|
19
|
-
// quick fix for listening to the process termination on windows
|
20
|
-
if (process.platform === "win32") {
|
21
|
-
const rl = createInterface({
|
22
|
-
input: process.stdin,
|
23
|
-
output: process.stdout,
|
24
|
-
})
|
25
|
-
|
26
|
-
rl.on("SIGINT", function () {
|
27
|
-
// process.emit('SIGINT')
|
28
|
-
// process.emit('SIGINT')
|
29
|
-
})
|
30
|
-
}
|
31
|
-
|
32
|
-
process.on("SIGINT", function () {
|
33
|
-
Console.debug("Terminated (SIGINT)")
|
34
|
-
process.exit()
|
35
|
-
})
|
36
|
-
}
|
37
|
-
|
38
|
-
async finally() {
|
39
|
-
Console.debug("COMMAND FINALLY")
|
40
|
-
// called after run and catch regardless of whether or not the command errored
|
41
|
-
}
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
1
|
+
import { Command, flags } from "@oclif/command"
|
2
|
+
import Console from "./console"
|
3
|
+
import { createInterface } from "readline"
|
4
|
+
// import SessionManager from '../managers/session'
|
5
|
+
|
6
|
+
class BaseCommand extends Command {
|
7
|
+
async catch(err: any) {
|
8
|
+
Console.debug("COMMAND CATCH", err)
|
9
|
+
|
10
|
+
throw err
|
11
|
+
}
|
12
|
+
|
13
|
+
async init() {
|
14
|
+
const { flags, args } = this.parse(BaseCommand)
|
15
|
+
Console.debug("COMMAND INIT")
|
16
|
+
Console.debug("These are your flags: ", flags)
|
17
|
+
Console.debug("These are your args: ", args)
|
18
|
+
|
19
|
+
// quick fix for listening to the process termination on windows
|
20
|
+
if (process.platform === "win32") {
|
21
|
+
const rl = createInterface({
|
22
|
+
input: process.stdin,
|
23
|
+
output: process.stdout,
|
24
|
+
})
|
25
|
+
|
26
|
+
rl.on("SIGINT", function () {
|
27
|
+
// process.emit('SIGINT')
|
28
|
+
// process.emit('SIGINT')
|
29
|
+
})
|
30
|
+
}
|
31
|
+
|
32
|
+
process.on("SIGINT", function () {
|
33
|
+
Console.debug("Terminated (SIGINT)")
|
34
|
+
process.exit()
|
35
|
+
})
|
36
|
+
}
|
37
|
+
|
38
|
+
async finally() {
|
39
|
+
Console.debug("COMMAND FINALLY")
|
40
|
+
// called after run and catch regardless of whether or not the command errored
|
41
|
+
}
|
42
|
+
|
43
|
+
static flags = {
|
44
|
+
yes: flags.boolean({
|
45
|
+
char: "y",
|
46
|
+
description: "Skip all prompts and initialize an empty project",
|
47
|
+
default: false,
|
48
|
+
}),
|
49
|
+
}
|
50
|
+
|
51
|
+
async run() {
|
52
|
+
// console.log('running my command')
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
export default BaseCommand
|
package/src/utils/api.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import Console from "../utils/console"
|
2
2
|
import * as storage from "node-persist"
|
3
3
|
import cli from "cli-ux"
|
4
|
+
import axios from "axios"
|
4
5
|
const HOST = "https://breathecode.herokuapp.com"
|
5
6
|
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
6
7
|
// const RIGOBOT_HOST = "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
@@ -299,6 +300,47 @@ const sendStreamTelemetry = async function (url: string, body: object) {
|
|
299
300
|
})
|
300
301
|
}
|
301
302
|
|
303
|
+
type TConsumableSlug =
|
304
|
+
| "ai-conversation-message"
|
305
|
+
| "ai-compilation"
|
306
|
+
| "ai-tutorial-generation"
|
307
|
+
|
308
|
+
export const countConsumables = (
|
309
|
+
consumables: any,
|
310
|
+
consumableSlug: TConsumableSlug = "ai-tutorial-generation"
|
311
|
+
) => {
|
312
|
+
// Find the void that matches the consumableSlug
|
313
|
+
|
314
|
+
const consumable = consumables.voids.find(
|
315
|
+
(voidItem: any) => voidItem.slug === consumableSlug
|
316
|
+
)
|
317
|
+
|
318
|
+
// Return the available units or 0 if not found
|
319
|
+
return consumable ? consumable.balance.unit : 0
|
320
|
+
}
|
321
|
+
|
322
|
+
export const getConsumables = async (token: string): Promise<any> => {
|
323
|
+
const url = `${HOST}/v1/payments/me/service/consumable?virtual=true`
|
324
|
+
|
325
|
+
const headers = {
|
326
|
+
Authorization: `Token ${token}`,
|
327
|
+
}
|
328
|
+
|
329
|
+
try {
|
330
|
+
const response = await axios.get(url, { headers })
|
331
|
+
|
332
|
+
const ai_tutorial_generation = countConsumables(
|
333
|
+
response.data,
|
334
|
+
"ai-tutorial-generation"
|
335
|
+
)
|
336
|
+
|
337
|
+
return { ai_tutorial_generation }
|
338
|
+
} catch (error) {
|
339
|
+
console.error("Error fetching consumables:", error)
|
340
|
+
throw error
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
302
344
|
export default {
|
303
345
|
login,
|
304
346
|
publish,
|