diff --git a/package.json b/package.json
index a6575662..e8c924a3 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"prepare": "svelte-kit sync"
},
"dependencies": {
+ "@aws-sdk/client-s3": "^3.386.0",
"@sentry/browser": "^7.61.0",
"@sentry/integrations": "^7.61.0",
"@sentry/tracing": "^7.61.0",
@@ -25,10 +26,12 @@
"@types/pluralize": "0.0.30",
"ai": "^2.1.31",
"autoprefixer": "^10.4.14",
+ "axios": "^1.4.0",
"body-parser": "^1.20.2",
"canvas-confetti": "^1.6.0",
"cookie-parser": "^1.4.6",
"copy-to-clipboard": "^3.3.3",
+ "form-data": "^4.0.0",
"hotkeys-js": "^3.11.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
@@ -47,8 +50,6 @@
"@babel/plugin-transform-runtime": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@babel/runtime": "^7.22.6",
- "@fortawesome/free-brands-svg-icons": "^6.4.0",
- "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@sveltejs/kit": "^1.22.4",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash": "^4.14.196",
diff --git a/src/lib/components/Course/components/Lesson/Materials/Transcript.svelte b/src/lib/components/Course/components/Lesson/Materials/Transcript.svelte
new file mode 100644
index 00000000..74746667
--- /dev/null
+++ b/src/lib/components/Course/components/Lesson/Materials/Transcript.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Natural Language Classifier uses advanced natural language processing and machine learning
+ techniques to create custom classification models. Users train their data and the service
+ predicts the appropriate category for the inputted text.
+
+
+
+
+ Analyze text to extract meta-data from content such as concepts, entities, emotion, relations,
+ sentiment and more.
+
+
+
+
+ Translate text, documents, and websites from one language to another. Create industry or
+ region-specific translations via the service's customization capability.
+
+
+
diff --git a/src/lib/components/Course/components/Lesson/Materials/Video/Index.svelte b/src/lib/components/Course/components/Lesson/Materials/Video/Index.svelte
index 745b81ed..57cc7798 100644
--- a/src/lib/components/Course/components/Lesson/Materials/Video/Index.svelte
+++ b/src/lib/components/Course/components/Lesson/Materials/Video/Index.svelte
@@ -3,6 +3,9 @@
import LocalVideo from './LocalVideo.svelte';
import * as CONSTANTS from './constants';
+ export let lessonId = '';
+ export let saveLesson = () => {};
+
let tabs = CONSTANTS.videoTabs;
let currentTab = tabs[0].value;
@@ -34,7 +37,7 @@
{#if currentTab === 1}
{:else}
-
+
{/if}
diff --git a/src/lib/components/Course/components/Lesson/Materials/Video/LocalVideo.svelte b/src/lib/components/Course/components/Lesson/Materials/Video/LocalVideo.svelte
index ce254b0b..43adb296 100644
--- a/src/lib/components/Course/components/Lesson/Materials/Video/LocalVideo.svelte
+++ b/src/lib/components/Course/components/Lesson/Materials/Video/LocalVideo.svelte
@@ -1,45 +1,95 @@
-{#if isLoaded == false}
+{#if isLoaded === false}
(fileInput && !isLoading ? fileInput.click() : null)}
+ class="w-full h-full"
+ disabled={isLoading}
>
-
-
- Upload video
-
- Select file( Mp4, MOV, AVI) to upload or drag and drop video here. Size must be less than
- 20 MB.
-
-
+
-{:else if isBig && isUploaded == false}
+{:else if isBig && isUploaded === false}
@@ -52,7 +102,7 @@
-{:else if isWrongFormat && isUploaded == false}
+{:else if isWrongFormat && isUploaded === false}
@@ -72,17 +122,19 @@
-
+
+
+
+
+
+
- Video.png
+ {fileInput?.files?.[0]?.name || uploadedFileUrl}
-
{/if}
diff --git a/src/lib/components/Course/components/Lesson/Materials/Video/YoutubeVideo.svelte b/src/lib/components/Course/components/Lesson/Materials/Video/YoutubeVideo.svelte
index 587f83e9..c0b528f0 100644
--- a/src/lib/components/Course/components/Lesson/Materials/Video/YoutubeVideo.svelte
+++ b/src/lib/components/Course/components/Lesson/Materials/Video/YoutubeVideo.svelte
@@ -1,47 +1,49 @@
@@ -58,22 +60,23 @@
- Added : {($lesson?.materials?.video_url && $lesson.materials.video_url.trim().split(',').length) || 0} Videos
+ Videos added:
+ {$lesson?.materials?.videos.filter((v) => v.type === 'youtube' && isValidYouTubeLink(v.link))
+ .length || 0}
+
- {#each $lesson?.materials?.video_url && $lesson.materials.video_url
- .trim()
- .split(',') as videoLink, index}
-
-
- {videoLink}
-
-
- removeVideo(index)}>
-
-
-
+ {#each $lesson?.materials?.videos as video, index}
+ {#if video.type === 'youtube'}
+
+
+ {video.link}
+
+
+ removeVideo(index)}>
+
+
+
+ {/if}
{/each}
diff --git a/src/lib/components/Course/components/Lesson/Materials/index.svelte b/src/lib/components/Course/components/Lesson/Materials/index.svelte
index a9f22bf7..22f0d7b2 100644
--- a/src/lib/components/Course/components/Lesson/Materials/index.svelte
+++ b/src/lib/components/Course/components/Lesson/Materials/index.svelte
@@ -1,11 +1,13 @@
-
+
@@ -236,34 +234,45 @@
- {#if mode === MODES.edit && videoUrls.length}
-
+ {#if mode === MODES.edit && $lesson.materials.videos.length}
+
{/if}
-
- {#if videoUrls.length}
+ {#if $lesson.materials.videos.length}
- {#each videoUrls as url, index}
+ {#each $lesson.materials.videos as video, index}
+ {#if mode === MODES.edit}
+
+ deleteLessonVideo(index)}
+ >
+
+
+
+ {/if}
- {#if url.includes('youtube')}
+ {#key video.link}
-
+ {#if video.type === 'youtube'}
+
+ {:else}
+
+
+
+
+ {/if}
- {:else}
-
-
-
-
-
-
- {/if}
+ {/key}
{/each}
diff --git a/src/lib/components/Course/components/Lesson/store/lessons.ts b/src/lib/components/Course/components/Lesson/store/lessons.ts
index 92a94ccd..385c8006 100644
--- a/src/lib/components/Course/components/Lesson/store/lessons.ts
+++ b/src/lib/components/Course/components/Lesson/store/lessons.ts
@@ -1,21 +1,23 @@
import { writable } from 'svelte/store';
import type { Writable, Updater } from 'svelte/store';
import { createLesson, updateLesson, deleteLesson } from '$lib/utils/services/courses';
-import type { Lesson, Course } from '$lib/utils/types';
+import type { Lesson, Course, LessonPage } from '$lib/utils/types';
export const uploadCourseVideoStore = writable({
- isModalOpen: false
+ isModalOpen: false,
+ formRes: null
});
export const lessons: Writable = writable([]);
-export const lesson = writable({
+
+export const lesson = writable({
id: null,
totalExercises: 0,
is_complete: false,
materials: {
note: '',
slide_url: '',
- video_url: ''
+ videos: []
},
exercises: []
});
@@ -103,14 +105,12 @@ export async function handleUpdateLessonMaterials(lesson: any, lessonId: Lesson[
export const deleteLessonVideo = (index: any) => {
lesson.update((currentLesson) => {
- const links = currentLesson.materials.video_url.split(',').map((link) => link.trim());
- links.splice(index, 1);
- const updatedVideoUrl = links.join(',').trim();
+ const videos = currentLesson.materials.videos || [];
return {
...currentLesson,
materials: {
...currentLesson.materials,
- video_url: updatedVideoUrl
+ videos: videos?.filter((video, i) => i !== index)
}
};
});
diff --git a/src/lib/components/Course/components/Navigation/NavExpandable.svelte b/src/lib/components/Course/components/Navigation/NavExpandable.svelte
index 2ff46564..6f86f643 100644
--- a/src/lib/components/Course/components/Navigation/NavExpandable.svelte
+++ b/src/lib/components/Course/components/Navigation/NavExpandable.svelte
@@ -1,5 +1,5 @@
-
-
-
-
- {#if telegramLink && mailLink}
-
- {/if}
-
-
-
diff --git a/src/lib/utils/functions/genUniqueId.js b/src/lib/utils/functions/genUniqueId.js
new file mode 100644
index 00000000..dd65d622
--- /dev/null
+++ b/src/lib/utils/functions/genUniqueId.js
@@ -0,0 +1 @@
+export default () => Date.now().toString(36) + Math.random().toString(36).substr(2);
diff --git a/src/lib/utils/services/courses/clone.ts b/src/lib/utils/services/courses/clone.ts
index 984323a5..d1c1b6a9 100644
--- a/src/lib/utils/services/courses/clone.ts
+++ b/src/lib/utils/services/courses/clone.ts
@@ -108,7 +108,7 @@ async function cloneLessons(lessons: Lesson[], courseId: Course['id']) {
public: lesson.public,
slide_url: lesson.slide_url,
title: lesson.title,
- video_url: lesson.video_url
+ videos: lesson.videos
}));
const { data } = await supabase.from('lesson').insert(clonedLessons).select();
diff --git a/src/lib/utils/services/courses/index.ts b/src/lib/utils/services/courses/index.ts
index f966473b..9277358a 100644
--- a/src/lib/utils/services/courses/index.ts
+++ b/src/lib/utils/services/courses/index.ts
@@ -34,7 +34,7 @@ export async function fetchCourse(courseId?: Course['id'], slug?: Course['slug']
certificate_theme,
lessons:lesson(
id, title,public, lesson_at, call_url, is_unlocked, is_complete, order, created_at,
- note, video_url, slide_url, call_url, totalExercises:exercise(count),
+ note, videos, slide_url, call_url, totalExercises:exercise(count),
profile:teacher_id(id, avatar_url, fullname)
),
attendance:group_attendance(*)
@@ -150,7 +150,7 @@ export function deleteGroupMember(groupMemberId: Groupmember['id']) {
export function fetchLesson(lessonId: Lesson['id']) {
return supabase
.from('lesson')
- .select(`id, note, video_url, slide_url, call_url, totalExercises:exercise(count)`)
+ .select(`id, note, videos, slide_url, call_url, totalExercises:exercise(count)`)
.eq('id', lessonId)
.single();
}
diff --git a/src/lib/utils/types/index.ts b/src/lib/utils/types/index.ts
index bc6a1a19..2b7ce93d 100644
--- a/src/lib/utils/types/index.ts
+++ b/src/lib/utils/types/index.ts
@@ -136,9 +136,29 @@ export interface Group_attendance {
groupmember?: Groupmember;
}
+enum VideoType {
+ youtube,
+ muse
+}
+export interface LessonPage {
+ id?: string | null;
+ totalExercises: Number;
+ is_complete: Boolean;
+ materials: {
+ note: string;
+ slide_url: string;
+ videos: Array<{
+ type: string;
+ link: string;
+ metadata?: {};
+ }>;
+ };
+ exercises: [];
+}
+
export interface Lesson {
note?: any; // type unknown;
- video_url?: any; // type unknown;
+ videos?: []; // type unknown;
slide_url?: any; // type unknown;
course_id: string /* foreign key to course.id */;
id?: string /* primary key */;
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 0e2fe4c3..5396aeb7 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -39,7 +39,7 @@
import '../app.postcss';
let supabase = getSupabase();
- let path = $page.url.pathname.replace('/', '');
+ let path = $page.url?.pathname?.replace('/', '');
let theme = 'white';
let skipAuth = false;
@@ -261,7 +261,7 @@
};
});
- $: path = $page.url.pathname.replace('/', '');
+ $: path = $page.url?.pathname?.replace('/', '');
$: theme = $appStore.isDark ? 'g100' : 'white';
$: setOrgBasedOnUrl($page.url.host);
diff --git a/src/routes/api/muse/transcript/+server.js b/src/routes/api/muse/transcript/+server.js
new file mode 100644
index 00000000..dd1714fa
--- /dev/null
+++ b/src/routes/api/muse/transcript/+server.js
@@ -0,0 +1,42 @@
+import { json } from '@sveltejs/kit';
+import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
+
+import {
+ CLOUDFLARE_ACCESS_KEY,
+ CLOUDFLARE_SECRET_ACCESS_KEY,
+ CLOUDFLARE_ACCOUNT_ID,
+ CLOUDFLARE_PUBLIC_ACCOUNT_ID,
+ MUSE_API_KEY
+} from '$env/static/private';
+
+const S3 = new S3Client({
+ region: 'auto',
+ endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ credentials: {
+ accessKeyId: CLOUDFLARE_ACCESS_KEY,
+ secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY
+ }
+});
+
+const downloadTranscript = async () => {};
+
+const uploadTranscriptToS3 = async () => {};
+
+export async function GET({ url }) {
+ const museFid = url.searchParams.get('museFid');
+
+ console.log('museFid');
+
+ // check the transcript table and see if we find a record
+ // if we find one
+ // check if downloaded = true
+ // if true
+ // return the transcript
+ // if false
+ // download the transcript from muse
+ // upload it to S3
+ // return the transcript
+ // if we dont
+
+ return json({ success: true, museFid });
+}
diff --git a/src/routes/courses/[id]/+page.svelte b/src/routes/courses/[id]/+page.svelte
index bd29bb97..6904b4de 100644
--- a/src/routes/courses/[id]/+page.svelte
+++ b/src/routes/courses/[id]/+page.svelte
@@ -3,6 +3,7 @@
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
import { setCourse } from '$lib/components/Course/store';
import { fetchCourse } from '$lib/utils/services/courses';
+ import { browser } from '$app/environment';
export let data;
const { courseId } = data;
@@ -10,7 +11,7 @@
let prevCourseId = '';
async function onCourseIdChange(courseId = '') {
- if (!courseId || prevCourseId === courseId) return;
+ if (!courseId || prevCourseId === courseId || !browser) return;
const { data: _data } = await fetchCourse(courseId);
diff --git a/src/routes/courses/[id]/lessons/[...lessonParams]/+page.js b/src/routes/courses/[id]/lessons/[...lessonParams]/+page.js
deleted file mode 100644
index e2bbe9fc..00000000
--- a/src/routes/courses/[id]/lessons/[...lessonParams]/+page.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export const load = ({ params = { id: '' } }) => {
- const { id: courseId, lessonParams = [] } = params;
- const splitparams = lessonParams.split('/');
- const [lessonId, exerciseRouteName, exerciseId] = splitparams;
-
- return {
- courseId,
- lessonId,
- exerciseRouteName,
- exerciseId,
- isMaterialsTabActive: !exerciseRouteName
- };
-};
diff --git a/src/routes/courses/[id]/lessons/[...lessonParams]/+page.server.js b/src/routes/courses/[id]/lessons/[...lessonParams]/+page.server.js
new file mode 100644
index 00000000..4e54c691
--- /dev/null
+++ b/src/routes/courses/[id]/lessons/[...lessonParams]/+page.server.js
@@ -0,0 +1,157 @@
+import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
+import FormData from 'form-data';
+import axios from 'axios';
+import { fail } from '@sveltejs/kit';
+import genUniqueId from '$lib/utils/functions/genUniqueId.js';
+import { getSupabase } from '$lib/utils/functions/supabase';
+
+import {
+ CLOUDFLARE_ACCESS_KEY,
+ CLOUDFLARE_SECRET_ACCESS_KEY,
+ CLOUDFLARE_ACCOUNT_ID,
+ CLOUDFLARE_PUBLIC_ACCOUNT_ID,
+ MUSE_API_KEY
+} from '$env/static/private';
+
+// Set the runtime to edge for best performance
+export const config = {
+ runtime: 'edge'
+};
+
+const S3 = new S3Client({
+ region: 'auto',
+ endpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ credentials: {
+ accessKeyId: CLOUDFLARE_ACCESS_KEY,
+ secretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY
+ }
+});
+
+const supabase = getSupabase();
+
+const uploadToMuse = (s3Data, fileName, lessonId, fileUrl) => {
+ const form = new FormData();
+ form.append('file', s3Data, fileName);
+
+ axios({
+ method: 'POST',
+ url: 'https://api.muse.ai/api/files/upload',
+ data: form,
+ maxContentLength: Infinity,
+ maxBodyLength: Infinity,
+ headers: {
+ ...form.getHeaders(),
+ Key: MUSE_API_KEY
+ }
+ })
+ .then(async function (response) {
+ console.log('Upload to muse complete', response.data);
+
+ const { data: lesson } = await supabase
+ .from('lesson')
+ .select(`*`)
+ .eq('id', lessonId)
+ .single();
+
+ if (lesson) {
+ // Add Muse response to metadata
+ const videos = lesson.videos.map((v) => {
+ if (v.link === fileUrl) {
+ console.log('matches');
+ v.metadata = JSON.parse(JSON.stringify(response.data));
+ }
+ return v;
+ });
+
+ const { error, data } = await supabase
+ .from('lesson')
+ .update({ videos })
+ .eq('id', lessonId)
+ .select();
+ if (error) {
+ console.log('Error updating supabase configs');
+ }
+ console.log('update error', error);
+ console.log('update data', data, data[0].videos);
+ }
+ })
+ .catch(function (error) {
+ console.log('Upload error', error);
+ });
+};
+
+export const load = ({ params = { id: '' } }) => {
+ const { id: courseId, lessonParams = [] } = params;
+ const splitparams = lessonParams.split('/');
+ const [lessonId, exerciseRouteName, exerciseId] = splitparams;
+
+ return {
+ courseId,
+ lessonId,
+ exerciseRouteName,
+ exerciseId,
+ isMaterialsTabActive: !exerciseRouteName
+ };
+};
+
+export const actions = {
+ create: async ({ request }) => {
+ const formData = Object.fromEntries(await request.formData());
+ console.log('formData', formData);
+
+ if (!formData.file?.name || formData.file?.name === 'undefined') {
+ return fail(400, {
+ success: false,
+ message: 'You must provide a file to upload'
+ });
+ }
+
+ const { file, lessonId } = formData;
+ console.log('file', file);
+ console.log('lessonId', lessonId);
+
+ const stream = await file.arrayBuffer();
+ const fileName = `${genUniqueId()}-${file.name}`;
+ const params = {
+ Bucket: 'videos',
+ Key: fileName,
+ Body: stream,
+ ContentLength: stream.length
+ };
+ console.log('params', params);
+
+ let upload;
+
+ const fileUrl = `https://pub-${CLOUDFLARE_PUBLIC_ACCOUNT_ID}.r2.dev/${fileName}`;
+
+ try {
+ upload = await S3.send(new PutObjectCommand(params));
+ // process data.
+ } catch (error) {
+ // error handling.
+ const { requestId, cfId, extendedRequestId } = error.$$metadata;
+ console.error('Error uploading file');
+ console.log({ requestId, cfId, extendedRequestId });
+ } finally {
+ // finally.
+ console.log('File upload complete', upload);
+
+ console.log('\n\nInitilizing Muse Request');
+
+ const data = await S3.send(
+ new GetObjectCommand({
+ Bucket: 'videos',
+ Key: fileName
+ })
+ );
+ // console.log('GetObjectCommand', data);
+ uploadToMuse(data.Body, file.name, lessonId, fileUrl);
+ }
+
+ return {
+ success: true,
+ url: fileUrl,
+ message: 'Uploaded successfully'
+ };
+ }
+};
diff --git a/src/routes/courses/[id]/lessons/[...lessonParams]/+page.svelte b/src/routes/courses/[id]/lessons/[...lessonParams]/+page.svelte
index 6af211cb..3458eb9b 100644
--- a/src/routes/courses/[id]/lessons/[...lessonParams]/+page.svelte
+++ b/src/routes/courses/[id]/lessons/[...lessonParams]/+page.svelte
@@ -12,11 +12,17 @@
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
import MODES from '$lib/utils/constants/mode.js';
import { setCourse, course } from '$lib/components/Course/store';
- import { lesson, handleSaveLesson } from '$lib/components/Course/components/Lesson/store/lessons';
+ import {
+ lesson,
+ handleSaveLesson,
+ uploadCourseVideoStore
+ } from '$lib/components/Course/components/Lesson/store/lessons';
import { browser } from '$app/environment';
export let data;
+ export let form;
+
let path = '';
let mode = MODES.view;
let prevMode = '';
@@ -68,12 +74,14 @@
mode = mode === MODES.edit ? MODES.view : MODES.edit;
}
- $: path = $page.url.pathname.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
+ $: path = $page.url?.pathname?.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
$: if (data.courseId && browser) {
mode = MODES.view;
fetchReqData(data.lessonId);
}
+
+ $: $uploadCourseVideoStore.formRes = form;
diff --git a/src/routes/courses/[id]/submissions/+page.svelte b/src/routes/courses/[id]/submissions/+page.svelte
index e8f0354f..e68bb1ca 100644
--- a/src/routes/courses/[id]/submissions/+page.svelte
+++ b/src/routes/courses/[id]/submissions/+page.svelte
@@ -293,7 +293,7 @@