@hed-hog/lms 0.0.353 → 0.0.354

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.
Files changed (135) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts +29 -0
  2. package/dist/course/course-audio-transcription.service.d.ts.map +1 -0
  3. package/dist/course/course-audio-transcription.service.js +291 -0
  4. package/dist/course/course-audio-transcription.service.js.map +1 -0
  5. package/dist/course/course-lesson.controller.d.ts +10 -0
  6. package/dist/course/course-lesson.controller.d.ts.map +1 -0
  7. package/dist/course/course-lesson.controller.js +62 -0
  8. package/dist/course/course-lesson.controller.js.map +1 -0
  9. package/dist/course/course-structure.controller.d.ts +41 -15
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.controller.js +50 -6
  12. package/dist/course/course-structure.controller.js.map +1 -1
  13. package/dist/course/course-structure.service.d.ts +50 -15
  14. package/dist/course/course-structure.service.d.ts.map +1 -1
  15. package/dist/course/course-structure.service.js +238 -73
  16. package/dist/course/course-structure.service.js.map +1 -1
  17. package/dist/course/course-video-conversion.service.d.ts +20 -2
  18. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  19. package/dist/course/course-video-conversion.service.js +730 -10
  20. package/dist/course/course-video-conversion.service.js.map +1 -1
  21. package/dist/course/course.controller.d.ts +24 -8
  22. package/dist/course/course.controller.d.ts.map +1 -1
  23. package/dist/course/course.module.d.ts.map +1 -1
  24. package/dist/course/course.module.js +5 -3
  25. package/dist/course/course.module.js.map +1 -1
  26. package/dist/course/course.service.d.ts +24 -8
  27. package/dist/course/course.service.d.ts.map +1 -1
  28. package/dist/course/course.service.js +112 -176
  29. package/dist/course/course.service.js.map +1 -1
  30. package/dist/course/dto/create-course-lesson-frame.dto.d.ts +5 -0
  31. package/dist/course/dto/create-course-lesson-frame.dto.d.ts.map +1 -0
  32. package/dist/course/dto/create-course-lesson-frame.dto.js +30 -0
  33. package/dist/course/dto/create-course-lesson-frame.dto.js.map +1 -0
  34. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +4 -2
  35. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  36. package/dist/course/dto/create-course-structure-lesson.dto.js +10 -3
  37. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  38. package/dist/course/dto/create-course.dto.d.ts +1 -1
  39. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  40. package/dist/course/dto/create-course.dto.js +6 -6
  41. package/dist/course/dto/create-course.dto.js.map +1 -1
  42. package/dist/course/dto/update-course-lesson-frame.dto.d.ts +5 -0
  43. package/dist/course/dto/update-course-lesson-frame.dto.d.ts.map +1 -0
  44. package/dist/course/dto/update-course-lesson-frame.dto.js +32 -0
  45. package/dist/course/dto/update-course-lesson-frame.dto.js.map +1 -0
  46. package/dist/course/dto/update-course-resources.dto.d.ts +4 -2
  47. package/dist/course/dto/update-course-resources.dto.d.ts.map +1 -1
  48. package/dist/course/dto/update-course-resources.dto.js +10 -3
  49. package/dist/course/dto/update-course-resources.dto.js.map +1 -1
  50. package/dist/course/dto/update-transcription-segments.dto.d.ts +10 -0
  51. package/dist/course/dto/update-transcription-segments.dto.d.ts.map +1 -0
  52. package/dist/course/dto/update-transcription-segments.dto.js +38 -0
  53. package/dist/course/dto/update-transcription-segments.dto.js.map +1 -0
  54. package/dist/course/lms-setting.controller.d.ts +13 -0
  55. package/dist/course/lms-setting.controller.d.ts.map +1 -0
  56. package/dist/course/lms-setting.controller.js +53 -0
  57. package/dist/course/lms-setting.controller.js.map +1 -0
  58. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  59. package/dist/enterprise/training/training-admin.service.js +74 -33
  60. package/dist/enterprise/training/training-admin.service.js.map +1 -1
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +2 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/lms.module.d.ts.map +1 -1
  66. package/dist/lms.module.js +6 -0
  67. package/dist/lms.module.js.map +1 -1
  68. package/hedhog/data/route.yaml +63 -0
  69. package/hedhog/data/setting_group.yaml +76 -0
  70. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +3 -0
  71. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +10 -1
  72. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +7 -2
  73. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +5 -2
  74. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +7 -1
  75. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +3 -0
  76. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +95 -50
  77. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +11 -36
  78. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +19 -5
  79. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  80. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +30 -10
  81. package/hedhog/frontend/app/courses/[id]/structure/_components/CourseInstructorsSummaryCard.tsx.ejs +95 -0
  82. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +29 -11
  83. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +42 -31
  84. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +10 -1
  85. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -9
  86. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +25 -22
  87. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +12 -9
  88. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +315 -229
  89. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +3220 -534
  90. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +20 -15
  91. package/hedhog/frontend/app/courses/[id]/structure/_components/icon-action-tooltip.tsx.ejs +35 -0
  92. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +76 -67
  93. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +13 -10
  94. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +18 -16
  95. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +6 -0
  96. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +1 -1
  97. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +23 -11
  98. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +48 -9
  99. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +11 -2
  100. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +121 -15
  101. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +69 -14
  102. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +11 -0
  103. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +31 -0
  104. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +32 -6
  105. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +57 -3
  106. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +46 -6
  107. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +14 -0
  108. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-audio-files.ts.ejs +23 -0
  109. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +34 -0
  110. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +76 -0
  111. package/hedhog/frontend/messages/en.json +39 -3
  112. package/hedhog/frontend/messages/pt.json +39 -3
  113. package/hedhog/table/course.yaml +8 -0
  114. package/hedhog/table/course_lesson_file.yaml +12 -4
  115. package/hedhog/table/course_lesson_transcription_segment.yaml +22 -0
  116. package/hedhog/table/course_lesson_video_frame.yaml +25 -0
  117. package/package.json +9 -9
  118. package/src/course/course-audio-transcription.service.ts +393 -0
  119. package/src/course/course-lesson.controller.ts +28 -0
  120. package/src/course/course-structure.controller.ts +49 -3
  121. package/src/course/course-structure.service.ts +294 -32
  122. package/src/course/course-video-conversion.service.ts +972 -6
  123. package/src/course/course.module.ts +5 -3
  124. package/src/course/course.service.ts +87 -139
  125. package/src/course/dto/create-course-lesson-frame.dto.ts +14 -0
  126. package/src/course/dto/create-course-structure-lesson.dto.ts +19 -3
  127. package/src/course/dto/create-course.dto.ts +5 -5
  128. package/src/course/dto/update-course-lesson-frame.dto.ts +16 -0
  129. package/src/course/dto/update-course-resources.dto.ts +18 -3
  130. package/src/course/dto/update-transcription-segments.dto.ts +20 -0
  131. package/src/course/lms-setting.controller.ts +30 -0
  132. package/src/enterprise/training/training-admin.service.ts +77 -24
  133. package/src/index.ts +2 -0
  134. package/src/lms.module.ts +6 -0
  135. package/hedhog/table/course_instructor.yaml +0 -27
@@ -22,8 +22,8 @@ export interface ApiLessonResource {
22
22
  nome: string;
23
23
  fileId?: number | null;
24
24
  /** MIME category or custom type label; may be null. */
25
- tipo: string | null;
26
- publico: boolean;
25
+ type?: string | null;
26
+ is_public?: boolean;
27
27
  /**
28
28
  * TODO[BACKEND]: The API does not currently return a URL for the resource.
29
29
  * Add a resolved URL field (e.g. `/file/open/:id`) to the lesson response
@@ -32,13 +32,22 @@ export interface ApiLessonResource {
32
32
  url?: string;
33
33
  }
34
34
 
35
+ /** Frame extracted from the original lesson video. */
36
+ export interface ApiLessonFrame {
37
+ id: number;
38
+ nome: string;
39
+ fileId: number;
40
+ timeSeconds: number;
41
+ url?: string;
42
+ }
43
+
35
44
  /** Resource (arquivo de suporte) attached directly to a course. */
36
45
  export interface ApiCourseResource {
37
46
  id: number;
38
47
  nome: string;
39
48
  fileId?: number | null;
40
- tipo?: string | null;
41
- publico?: boolean;
49
+ type?: string | null;
50
+ is_public?: boolean;
42
51
  }
43
52
 
44
53
  /** Instructor linked to a lesson or available in the course pool. */
@@ -67,6 +76,7 @@ export interface ApiLesson {
67
76
  titulo: string;
68
77
  descricaoPublica: string;
69
78
  descricaoPrivada: string;
79
+ locale_id?: number | null;
70
80
  /** UI-level lesson type resolved by the backend service. */
71
81
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
72
82
  /** Duration in minutes. */
@@ -86,6 +96,7 @@ export interface ApiLesson {
86
96
  exameVinculado?: number;
87
97
  conteudoPost?: string;
88
98
  recursos: ApiLessonResource[];
99
+ frames?: ApiLessonFrame[];
89
100
  instrutores: ApiLessonInstructor[];
90
101
  }
91
102
 
@@ -157,6 +168,7 @@ export interface ApiCreateSessionPayload {
157
168
  export interface ApiUpdateCoursePayload {
158
169
  title?: string;
159
170
  slug?: string;
171
+ code?: string;
160
172
  name?: string;
161
173
  description?: string;
162
174
  /** 'published' = visible to students; 'draft' = hidden. */
@@ -198,8 +210,8 @@ export interface ApiCreateLessonPayload {
198
210
  recursos?: Array<{
199
211
  nome: string;
200
212
  fileId?: number;
201
- tipo?: string;
202
- publico?: boolean;
213
+ type?: string;
214
+ is_public?: boolean;
203
215
  }>;
204
216
  codigo?: string;
205
217
  }
@@ -266,3 +278,31 @@ export interface ApiPasteLessonsPayload {
266
278
  export interface ApiPasteLessonsResponse {
267
279
  lessons: ApiLesson[];
268
280
  }
281
+
282
+ export interface ApiDeleteLessonFrameResponse {
283
+ success: boolean;
284
+ }
285
+
286
+ export interface ApiCreateLessonFramePayload {
287
+ timeSeconds: number;
288
+ fileId: number;
289
+ }
290
+
291
+ export interface ApiCreateLessonFrameResponse {
292
+ success: boolean;
293
+ id: number;
294
+ fileId: number;
295
+ timeSeconds: number;
296
+ }
297
+
298
+ export interface ApiUpdateLessonFramePayload {
299
+ timeSeconds?: number;
300
+ fileId?: number;
301
+ }
302
+
303
+ export interface ApiUpdateLessonFrameResponse {
304
+ success: boolean;
305
+ id: number;
306
+ fileId: number | null;
307
+ timeSeconds: number;
308
+ }
@@ -85,6 +85,7 @@ export function useUpdateCourseMutation() {
85
85
  apiUpdateCourse(request, courseId, {
86
86
  title: formValues.title,
87
87
  slug: formValues.slug.toLowerCase(),
88
+ code: formValues.code,
88
89
  name: formValues.name,
89
90
  description: formValues.description,
90
91
  status: formValues.published ? 'published' : 'draft',
@@ -102,12 +103,19 @@ export function useUpdateCourseMutation() {
102
103
  ...old.course,
103
104
  title: formValues.title,
104
105
  slug: formValues.slug.toLowerCase(),
106
+ code: formValues.code,
105
107
  name: formValues.name,
106
108
  description: formValues.description,
107
109
  published: formValues.published,
108
110
  },
109
111
  };
110
112
  });
113
+ void queryClient.invalidateQueries({
114
+ queryKey: courseStructureQueryKey(courseId),
115
+ });
116
+ void queryClient.invalidateQueries({
117
+ queryKey: ['lms-course-detail', courseId],
118
+ });
111
119
  toast.success(t('mutations.course.saveSuccess'));
112
120
  },
113
121
  onError: () => {
@@ -218,6 +226,9 @@ export function useUpdateSessionMutation() {
218
226
  };
219
227
  }
220
228
  );
229
+ void queryClient.invalidateQueries({
230
+ queryKey: courseStructureQueryKey(courseId),
231
+ });
221
232
  toast.success(t('mutations.session.saveSuccess'));
222
233
  },
223
234
  onError: () => {
@@ -347,6 +358,9 @@ export function useUpdateLessonMutation() {
347
358
  };
348
359
  }
349
360
  );
361
+ void queryClient.invalidateQueries({
362
+ queryKey: courseStructureQueryKey(courseId),
363
+ });
350
364
  toast.success(t('mutations.lesson.saveSuccess'));
351
365
  },
352
366
  onError: () => {
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
+
5
+ import type { LessonAudioFile } from '../_components/types';
6
+
7
+ export function useLessonAudioFilesQuery(lessonId: string | null) {
8
+ const { request } = useApp();
9
+
10
+ return useQuery<LessonAudioFile[]>({
11
+ queryKey: ['lesson-audio-files', lessonId],
12
+ enabled: Boolean(lessonId),
13
+ queryFn: async () => {
14
+ const res = await request<LessonAudioFile[]>({
15
+ url: `/lms/lessons/${lessonId}/audio-files`,
16
+ method: 'GET',
17
+ });
18
+
19
+ return res.data ?? [];
20
+ },
21
+ initialData: [],
22
+ });
23
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
4
+
5
+ export interface LmsFeatureFlags {
6
+ videoConversionEnabled: boolean;
7
+ imageExtractionEnabled: boolean;
8
+ transcriptionEnabled: boolean;
9
+ youtubeEnabled: boolean;
10
+ vimeoEnabled: boolean;
11
+ }
12
+
13
+ const DEFAULT_FLAGS: LmsFeatureFlags = {
14
+ videoConversionEnabled: true,
15
+ imageExtractionEnabled: true,
16
+ transcriptionEnabled: true,
17
+ youtubeEnabled: true,
18
+ vimeoEnabled: true,
19
+ };
20
+
21
+ export function useLmsSettingsQuery(): LmsFeatureFlags {
22
+ const { request } = useApp();
23
+
24
+ const { data } = useQuery({
25
+ queryKey: ['lms-settings'],
26
+ staleTime: 60_000,
27
+ queryFn: async () => {
28
+ const res = await request<LmsFeatureFlags>({ url: 'lms/settings' });
29
+ return res?.data ?? DEFAULT_FLAGS;
30
+ },
31
+ });
32
+
33
+ return data ?? DEFAULT_FLAGS;
34
+ }
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useState } from 'react';
4
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
5
+
6
+ import type { TranscriptionSegment } from '../_components/types';
7
+
8
+ type ApiTranscriptionSegment = {
9
+ id?: number;
10
+ start_seconds?: number | string;
11
+ end_seconds?: number | string;
12
+ text?: string;
13
+ };
14
+
15
+ export function useTranscriptionSegmentsQuery(lessonId: string | null) {
16
+ const { request } = useApp();
17
+
18
+ return useQuery<TranscriptionSegment[]>({
19
+ queryKey: ['lesson-transcription-segments', lessonId],
20
+ enabled: Boolean(lessonId),
21
+ queryFn: async () => {
22
+ const res = await request<ApiTranscriptionSegment[]>({
23
+ url: `/lms/lessons/${lessonId}/transcription-segments`,
24
+ method: 'GET',
25
+ });
26
+
27
+ return (res.data ?? []).map((segment) => ({
28
+ id: Number(segment.id ?? 0),
29
+ startSeconds: Number(segment.start_seconds ?? 0),
30
+ endSeconds: Number(segment.end_seconds ?? 0),
31
+ text: String(segment.text ?? ''),
32
+ }));
33
+ },
34
+ initialData: [],
35
+ });
36
+ }
37
+
38
+ export function useUpdateTranscriptionSegmentsMutation(
39
+ lessonId: string | null
40
+ ) {
41
+ const { request } = useApp();
42
+ const [isPending, setIsPending] = useState(false);
43
+ const [error, setError] = useState<Error | null>(null);
44
+
45
+ const mutate = useCallback(
46
+ async (
47
+ segments: Array<{
48
+ startSeconds: number;
49
+ endSeconds: number;
50
+ text: string;
51
+ }>,
52
+ options?: { onSuccess?: () => void; onError?: (err: Error) => void }
53
+ ) => {
54
+ setIsPending(true);
55
+ setError(null);
56
+ try {
57
+ await request({
58
+ url: `/lms/lessons/${lessonId}/transcription-segments`,
59
+ method: 'PUT',
60
+ data: { segments },
61
+ });
62
+ options?.onSuccess?.();
63
+ } catch (err) {
64
+ const e = err instanceof Error ? err : new Error(String(err));
65
+ setError(e);
66
+ options?.onError?.(e);
67
+ throw e;
68
+ } finally {
69
+ setIsPending(false);
70
+ }
71
+ },
72
+ [lessonId, request]
73
+ );
74
+
75
+ return { mutate, isPending, error };
76
+ }
@@ -419,7 +419,7 @@
419
419
  "resources": "Resources",
420
420
  "extra": "Extra",
421
421
  "publish": "Publish",
422
- "videoProfiles": "Videos"
422
+ "videoProfiles": "Resolutions"
423
423
  },
424
424
  "structureTab": {
425
425
  "summary": {
@@ -947,6 +947,8 @@
947
947
  "videoLinked": "Linked video",
948
948
  "resources": "{count} downloadable resource(s)",
949
949
  "resourcesAria": "{count} resources",
950
+ "uploadedVideos": "{count} uploaded video(s)",
951
+ "uploadedVideosAria": "{count} uploaded videos",
950
952
  "hasTranscription": "Has transcription"
951
953
  },
952
954
  "sessionRow": {
@@ -954,7 +956,11 @@
954
956
  "expand": "Expand session",
955
957
  "rename": "Rename session",
956
958
  "draft": "Draft",
957
- "published": "Published"
959
+ "published": "Published",
960
+ "resources": "{count} attached resource(s)",
961
+ "resourcesAria": "{count} attached resources",
962
+ "uploadedVideos": "{count} uploaded video(s)",
963
+ "uploadedVideosAria": "{count} uploaded videos"
958
964
  },
959
965
  "sessionForm": {
960
966
  "titleCreate": "New Session",
@@ -1041,6 +1047,7 @@
1041
1047
  "save": "Save lesson",
1042
1048
  "tabData": "Data",
1043
1049
  "tabVideos": "Videos",
1050
+ "tabAudios": "Audios",
1044
1051
  "tabTranscription": "Transcription",
1045
1052
  "tabResources": "Resources",
1046
1053
  "videoProvider": "Video provider",
@@ -1067,20 +1074,28 @@
1067
1074
  "loadingVideoProfiles": "Loading course video profiles...",
1068
1075
  "videoProfilesLoadError": "Could not load the course video profiles.",
1069
1076
  "retryLoadVideoProfiles": "Try loading again",
1070
- "noVideoProfilesConfigured": "Configure video profiles in the course Videos tab before attaching File Storage videos.",
1077
+ "noVideoProfilesConfigured": "Configure video profiles in the course Resolutions tab before attaching File Storage videos.",
1071
1078
  "originalVideoTitle": "Original high-resolution video",
1072
1079
  "originalVideoHint": "Upload an original video to automatically generate the course profile videos.",
1073
1080
  "originalVideoPurpose": "This original file is only used to generate the course video variants.",
1074
1081
  "fileStorageVideoHint": "Use the original video below to automatically generate the course profile variants.",
1075
1082
  "uploadOriginalForConversion": "Upload original for conversion",
1083
+ "replaceOriginalForConversion": "Replace original video",
1084
+ "retryConversionWithSavedOriginal": "Start a new job with the saved original",
1076
1085
  "videoUploadBlockedWhileProcessing": "Original uploads stay blocked while the current conversion is still running.",
1086
+ "videoJobStateLoading": "Waiting for the latest job status before allowing new actions.",
1087
+ "videoProviderSavePending": "Wait for the provider to be saved before uploading the original video.",
1077
1088
  "videoUploadMaxSizeError": "The video exceeds the {size} limit.",
1078
1089
  "videoConversionQueued": "Video conversion queued as job #{id}.",
1079
1090
  "videoConversionFailed": "Could not queue the video conversion.",
1091
+ "videoConversionRetryFailed": "Could not start a new job with the saved original video.",
1080
1092
  "videoConversionJob": "Conversion queued as job #{id}",
1081
1093
  "videoJobFeedbackTitle": "Conversion tracking",
1094
+ "videoJobToggleDetails": "Toggle conversion details",
1095
+ "videoJobCollapsedSummary": "Conversion completed successfully. Processed videos are listed in the resolution section above.",
1082
1096
  "videoJobLoading": "Loading conversion progress...",
1083
1097
  "videoJobPendingLoad": "Preparing conversion job tracking...",
1098
+ "awaitingConversion": "Awaiting conversion...",
1084
1099
  "videoJobLoadError": "Could not load the conversion progress.",
1085
1100
  "retryLoadVideoJob": "Try again",
1086
1101
  "videoJobIdLabel": "Job",
@@ -1089,10 +1104,13 @@
1089
1104
  "videoJobCreatedAt": "Queued at",
1090
1105
  "videoJobStartedAt": "Started at",
1091
1106
  "videoJobFinishedAt": "Finished at",
1107
+ "videoJobRunningTime": "Running time",
1108
+ "videoJobTotalTime": "Total time",
1092
1109
  "videoJobLatestAttempt": "Latest attempt",
1093
1110
  "videoJobAttemptValue": "Attempt {count}",
1094
1111
  "videoJobLastError": "Last error",
1095
1112
  "videoJobRecentEvents": "Recent events",
1113
+ "videoJobTranscriptionEvents": "Recent transcription events",
1096
1114
  "videoJobNoEvents": "No events recorded yet.",
1097
1115
  "videoProfilesLockedWhileProcessing": "Profile video uploads stay locked while the original conversion job is active.",
1098
1116
  "videoJobStatuses": {
@@ -1124,6 +1142,23 @@
1124
1142
  "requeued": "Requeued",
1125
1143
  "unlocked": "Unlocked"
1126
1144
  },
1145
+ "videoJobProgress": {
1146
+ "download_original": "Downloading original video...",
1147
+ "probe_duration": "Reading video duration...",
1148
+ "convert_profile": "Converting video for the {profileName} profile...",
1149
+ "extract_frames": "Extracting video frames...",
1150
+ "extract_frames_done": "Frames extracted: {count}.",
1151
+ "extract_audio": "Extracting video audio...",
1152
+ "queue_transcription": "Scheduling AI transcription...",
1153
+ "queue_transcription_done": "AI transcription is in progress...",
1154
+ "queue_transcription_skipped": "Audio is unavailable. Transcription will be skipped.",
1155
+ "transcription_download_audio": "Downloading audio for transcription...",
1156
+ "transcription_split_audio": "Splitting audio into chunks...",
1157
+ "transcription_split_done": "Audio split into {count} chunk(s).",
1158
+ "transcription_ai_chunk": "Transcribing chunk {current} of {total} with AI...",
1159
+ "transcription_save": "Saving generated transcription...",
1160
+ "transcription_done": "Transcription completed with {count} segment(s)."
1161
+ },
1127
1162
  "videoProfileMissing": "No video uploaded for this profile.",
1128
1163
  "playVideoAria": "Play video {name}",
1129
1164
  "videoPreviewTitle": "Video preview: {name}",
@@ -1867,6 +1902,7 @@
1867
1902
  "courseForm": {
1868
1903
  "title": "Create Course",
1869
1904
  "description": "Create a course quickly to link it to this training.",
1905
+ "locale": "Course Language",
1870
1906
  "fields": {
1871
1907
  "nome": {
1872
1908
  "label": "Name",
@@ -428,7 +428,7 @@
428
428
  "resources": "Recursos",
429
429
  "extra": "Extra",
430
430
  "publish": "Publicação",
431
- "videoProfiles": "Vídeos"
431
+ "videoProfiles": "Resoluções"
432
432
  },
433
433
  "structureTab": {
434
434
  "summary": {
@@ -956,6 +956,8 @@
956
956
  "videoLinked": "Vídeo vinculado",
957
957
  "resources": "{count} recurso(s) para download",
958
958
  "resourcesAria": "{count} recursos",
959
+ "uploadedVideos": "{count} vídeo(s) enviado(s)",
960
+ "uploadedVideosAria": "{count} vídeos enviados",
959
961
  "hasTranscription": "Possui transcrição"
960
962
  },
961
963
  "sessionRow": {
@@ -963,7 +965,11 @@
963
965
  "expand": "Expandir sessão",
964
966
  "rename": "Renomear sessão",
965
967
  "draft": "Rascunho",
966
- "published": "Publicada"
968
+ "published": "Publicada",
969
+ "resources": "{count} recurso(s) anexado(s)",
970
+ "resourcesAria": "{count} recursos anexados",
971
+ "uploadedVideos": "{count} vídeo(s) enviado(s)",
972
+ "uploadedVideosAria": "{count} vídeos enviados"
967
973
  },
968
974
  "sessionForm": {
969
975
  "titleCreate": "Nova Sessão",
@@ -1050,6 +1056,7 @@
1050
1056
  "save": "Salvar aula",
1051
1057
  "tabData": "Dados",
1052
1058
  "tabVideos": "Vídeos",
1059
+ "tabAudios": "Áudios",
1053
1060
  "tabTranscription": "Transcrição",
1054
1061
  "tabResources": "Recursos",
1055
1062
  "videoProvider": "Provedor de vídeo",
@@ -1076,20 +1083,28 @@
1076
1083
  "loadingVideoProfiles": "Carregando perfis de vídeo do curso...",
1077
1084
  "videoProfilesLoadError": "Não foi possível carregar os perfis de vídeo do curso.",
1078
1085
  "retryLoadVideoProfiles": "Tentar carregar novamente",
1079
- "noVideoProfilesConfigured": "Configure os perfis de vídeo na aba Vídeos do curso antes de anexar vídeos File Storage.",
1086
+ "noVideoProfilesConfigured": "Configure os perfis de vídeo na aba Resoluções do curso antes de anexar vídeos File Storage.",
1080
1087
  "originalVideoTitle": "Vídeo original em alta resolução",
1081
1088
  "originalVideoHint": "Envie um original para gerar automaticamente os vídeos dos perfis do curso.",
1082
1089
  "originalVideoPurpose": "Este arquivo original é usado apenas para gerar as versões de vídeo do curso.",
1083
1090
  "fileStorageVideoHint": "Use o vídeo original abaixo para gerar automaticamente as versões por perfil do curso.",
1084
1091
  "uploadOriginalForConversion": "Enviar original para conversão",
1092
+ "replaceOriginalForConversion": "Substituir vídeo original",
1093
+ "retryConversionWithSavedOriginal": "Iniciar novo job com o original salvo",
1085
1094
  "videoUploadBlockedWhileProcessing": "O upload de original fica bloqueado enquanto a conversão atual estiver em andamento.",
1095
+ "videoJobStateLoading": "Aguardando o status mais recente do job atual para liberar novas ações.",
1096
+ "videoProviderSavePending": "Aguarde o provedor ser salvo antes de enviar o vídeo original.",
1086
1097
  "videoUploadMaxSizeError": "O vídeo excede o limite de {size}.",
1087
1098
  "videoConversionQueued": "Conversão de vídeo enviada para a fila #{id}.",
1088
1099
  "videoConversionFailed": "Não foi possível enviar o vídeo para conversão.",
1100
+ "videoConversionRetryFailed": "Não foi possível iniciar um novo job com o vídeo original salvo.",
1089
1101
  "videoConversionJob": "Conversão na fila #{id}",
1090
1102
  "videoJobFeedbackTitle": "Acompanhamento da conversão",
1103
+ "videoJobToggleDetails": "Alternar detalhes da conversão",
1104
+ "videoJobCollapsedSummary": "Conversão concluída com sucesso. Os vídeos processados aparecem na lista de resoluções acima.",
1091
1105
  "videoJobLoading": "Carregando andamento da conversão...",
1092
1106
  "videoJobPendingLoad": "Preparando acompanhamento do job de conversão...",
1107
+ "awaitingConversion": "Aguardando conversão...",
1093
1108
  "videoJobLoadError": "Não foi possível carregar o andamento da conversão.",
1094
1109
  "retryLoadVideoJob": "Tentar novamente",
1095
1110
  "videoJobIdLabel": "Job",
@@ -1098,10 +1113,13 @@
1098
1113
  "videoJobCreatedAt": "Enviado em",
1099
1114
  "videoJobStartedAt": "Iniciado em",
1100
1115
  "videoJobFinishedAt": "Finalizado em",
1116
+ "videoJobRunningTime": "Tempo em execução",
1117
+ "videoJobTotalTime": "Tempo total",
1101
1118
  "videoJobLatestAttempt": "Última tentativa",
1102
1119
  "videoJobAttemptValue": "Tentativa {count}",
1103
1120
  "videoJobLastError": "Último erro",
1104
1121
  "videoJobRecentEvents": "Eventos recentes",
1122
+ "videoJobTranscriptionEvents": "Eventos recentes da transcrição",
1105
1123
  "videoJobNoEvents": "Nenhum evento registrado ainda.",
1106
1124
  "videoProfilesLockedWhileProcessing": "Os vídeos por perfil ficam bloqueados enquanto a conversão do original está ativa.",
1107
1125
  "videoJobStatuses": {
@@ -1133,6 +1151,23 @@
1133
1151
  "requeued": "Reenfileirado",
1134
1152
  "unlocked": "Liberado"
1135
1153
  },
1154
+ "videoJobProgress": {
1155
+ "download_original": "Baixando vídeo original...",
1156
+ "probe_duration": "Lendo duração do vídeo...",
1157
+ "convert_profile": "Convertendo vídeo para o perfil {profileName}...",
1158
+ "extract_frames": "Extraindo imagens do vídeo...",
1159
+ "extract_frames_done": "Imagens extraídas: {count}.",
1160
+ "extract_audio": "Extraindo áudio do vídeo...",
1161
+ "queue_transcription": "Agendando transcrição com IA...",
1162
+ "queue_transcription_done": "Transcrição com IA em andamento...",
1163
+ "queue_transcription_skipped": "Áudio indisponível. Transcrição não será executada.",
1164
+ "transcription_download_audio": "Baixando áudio para transcrição...",
1165
+ "transcription_split_audio": "Dividindo áudio em partes...",
1166
+ "transcription_split_done": "Áudio dividido em {count} parte(s).",
1167
+ "transcription_ai_chunk": "Transcrevendo com IA a parte {current} de {total}...",
1168
+ "transcription_save": "Salvando a transcrição gerada...",
1169
+ "transcription_done": "Transcrição concluída com {count} segmento(s)."
1170
+ },
1136
1171
  "videoProfileMissing": "Nenhum vídeo enviado para este perfil.",
1137
1172
  "playVideoAria": "Reproduzir vídeo {name}",
1138
1173
  "videoPreviewTitle": "Pré-visualização do vídeo: {name}",
@@ -1891,6 +1926,7 @@
1891
1926
  "courseForm": {
1892
1927
  "title": "Cadastrar Curso",
1893
1928
  "description": "Cadastre um curso rapidamente para vincular nesta formação.",
1929
+ "locale": "Idioma do Curso",
1894
1930
  "fields": {
1895
1931
  "nome": {
1896
1932
  "label": "Nome",
@@ -83,6 +83,13 @@ columns:
83
83
  type: varchar
84
84
  length: 32
85
85
  isNullable: true
86
+ - name: locale_id
87
+ type: fk
88
+ isNullable: true
89
+ references:
90
+ table: locale
91
+ column: id
92
+ onDelete: SET NULL
86
93
  - type: created_at
87
94
  - type: updated_at
88
95
 
@@ -93,3 +100,4 @@ indices:
93
100
  - columns: [status]
94
101
  - columns: [offering_type]
95
102
  - columns: [operations_project_id]
103
+ - columns: [locale_id]
@@ -16,16 +16,24 @@ columns:
16
16
  - name: title
17
17
  type: varchar
18
18
  length: 255
19
- - name: tipo
19
+ - name: type
20
20
  type: varchar
21
- length: 80
21
+ length: 64
22
22
  isNullable: true
23
- - name: publico
23
+ - name: is_public
24
24
  type: boolean
25
25
  default: true
26
+ - name: locale_id
27
+ type: fk
28
+ isNullable: true
29
+ references:
30
+ table: locale
31
+ column: id
32
+ onDelete: SET NULL
26
33
  - type: created_at
27
34
  - type: updated_at
28
35
 
29
36
  indices:
30
37
  - columns: [course_lesson_id]
31
- - columns: [course_lesson_id, tipo]
38
+ - columns: [course_lesson_id, type]
39
+ - columns: [locale_id]
@@ -0,0 +1,22 @@
1
+ columns:
2
+ - type: pk
3
+ - name: course_lesson_id
4
+ type: fk
5
+ references:
6
+ table: course_lesson
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: start_seconds
10
+ type: NUMERIC(10,3)
11
+ default: 0
12
+ - name: end_seconds
13
+ type: NUMERIC(10,3)
14
+ default: 0
15
+ - name: text
16
+ type: text
17
+ - type: created_at
18
+ - type: updated_at
19
+
20
+ indices:
21
+ - columns: [course_lesson_id]
22
+ - columns: [course_lesson_id, start_seconds]
@@ -0,0 +1,25 @@
1
+ columns:
2
+ - type: pk
3
+ - name: course_lesson_id
4
+ type: fk
5
+ references:
6
+ table: course_lesson
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: file_id
10
+ type: fk
11
+ isNullable: true
12
+ references:
13
+ table: file
14
+ column: id
15
+ onDelete: SET NULL
16
+ - name: time_seconds
17
+ type: int
18
+ default: 0
19
+ - type: created_at
20
+ - type: updated_at
21
+
22
+ indices:
23
+ - columns: [course_lesson_id]
24
+ - columns: [file_id]
25
+ - columns: [course_lesson_id, time_seconds]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.353",
3
+ "version": "0.0.354",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,16 +9,16 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-prisma": "0.0.6",
13
- "@hed-hog/api-locale": "0.0.14",
12
+ "@hed-hog/api": "0.0.8",
14
13
  "@hed-hog/api-types": "0.0.1",
14
+ "@hed-hog/api-prisma": "0.0.6",
15
+ "@hed-hog/crm": "0.0.354",
15
16
  "@hed-hog/api-pagination": "0.0.7",
16
- "@hed-hog/category": "0.0.353",
17
- "@hed-hog/crm": "0.0.353",
18
- "@hed-hog/finance": "0.0.353",
19
- "@hed-hog/core": "0.0.353",
20
- "@hed-hog/queue": "0.0.353",
21
- "@hed-hog/api": "0.0.8"
17
+ "@hed-hog/api-locale": "0.0.14",
18
+ "@hed-hog/category": "0.0.354",
19
+ "@hed-hog/core": "0.0.354",
20
+ "@hed-hog/finance": "0.0.354",
21
+ "@hed-hog/queue": "0.0.354"
22
22
  },
23
23
  "exports": {
24
24
  ".": {