CLASSROOMIO-T-141: Reusable AI trigger all around the app

CLASSROOM-T-141: Dark mode improvements

CLASSROOMIO-T-141: Add ai widget to landing page settings

CLASSROOMIO-T-141: Enable AI button to text area in the landingpage settings

CLASSROOMIO-T-141: Complete AI flow

Merge branch 'main' into CLASSROOMIO-T-141

CLASSROOMIO-T-141: AI Button test


Co-authored-by: Rotimi Best <rotimiibitoyeemma@gmail.com>
Co-authored-by: Tunji Adeyemi <tunji@digdippa.com>


Merge-request: CLASSROOMIO-MR-74
Merged-by: Best Admin <best@digdippa.com>
This commit is contained in:
tunny17
2023-09-24 07:08:37 +00:00
committed by Space Cloud
co-authored by Rotimi Best
parent 5e77f9ca46
commit a2c83c7909
17 changed files with 525 additions and 271 deletions
@@ -0,0 +1,135 @@
<script lang="ts">
import { useCompletion } from 'ai/svelte';
import { Popover } from 'carbon-components-svelte';
import type { PopoverProps } from 'carbon-components-svelte/types/Popover/Popover.svelte';
import MagicWandFilled from 'carbon-icons-svelte/lib/MagicWandFilled.svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { Loading } from 'carbon-components-svelte';
export let className = '';
export let handleInsert = (v: string) => {};
export let defaultPrompt = '';
export let alignPopover: PopoverProps['align'] = 'left';
export let isHTML = false;
let openPopover = false;
let plainText = '';
let textRef: HTMLParagraphElement;
let prompt: string = defaultPrompt;
let isSubmitted = false;
const { input, handleSubmit, completion, isLoading } = useCompletion({
api: '/api/completion/customprompt'
});
function restart() {
isSubmitted = false;
plainText = '';
}
function onCompletion(completion: string) {
plainText = completion;
if (textRef) {
textRef.scrollTop = textRef.scrollHeight;
}
}
function callAI(prompt: string, rephrase: boolean) {
isSubmitted = true;
$input = rephrase ? `Can you please rephrase this: ${plainText}` : prompt;
setTimeout(() => {
handleSubmit({ preventDefault: () => {} });
}, 500);
}
$: onCompletion($completion);
</script>
<div class="{className} relative z-[100]">
<IconButton
contained={true}
color="text-primary-700 dark:bg-neutral-800"
size="large"
onClick={() => (openPopover = !openPopover)}
>
<MagicWandFilled size={16} class="carbon-icon" />
<Popover caret align={alignPopover} bind:open={openPopover}>
<button
class="p-2 w-[300px] h-[220px] text-start"
type="button"
on:click={(e) => e.stopPropagation()}
>
{#if isSubmitted}
<div class="h-[200px]">
<div
bind:this={textRef}
class="h-[82%] bg-white dark:text-black p-2 overflow-y-auto text-sm text-start w-full my-1"
>
{#if isHTML}
{@html plainText}
{:else}
<p class="font-normal">
{plainText}
</p>
{/if}
</div>
<div class="flex gap-5 ml-2">
<button
class="text-xs px-4 py-2 border-[1px] cursor-pointer rounded-md font-medium"
on:click={() => {
handleInsert(plainText);
openPopover = false;
}}
type="button"
disabled={$isLoading}
>
{#if $isLoading}
<Loading withOverlay={false} small />
{:else}
Insert
{/if}
</button>
<button
class="text-xs px-4 py-2 border-[1px] cursor-pointer rounded-md font-medium {$isLoading &&
'opacity-25 cursor-not-allowed'}"
on:click|preventDefault={() => callAI(prompt, true)}
type="button"
disabled={$isLoading}
>
Rephrase
</button>
{#if !$isLoading}
<button
class="text-xs px-4 py-2 border-[1px] cursor-pointer rounded-md font-medium"
on:click|preventDefault={restart}
type="button"
>
Reset
</button>
{/if}
</div>
</div>
{:else}
<div class="h-full">
<p class="text-sm font-medium mb-2 ml-1">Help me write</p>
<textarea
bind:value={prompt}
class="w-full border-0 rounded-lg h-[65%] text-sm m-0 dark:text-black"
placeholder="Help me write a short description"
/>
<button
class="flex text-xs px-4 py-2 m-0 border rounded-md font-medium"
on:click|preventDefault={() => callAI(prompt, false)}
type="button"
>
Start typing...
</button>
</div>
{/if}
</button>
</Popover>
</IconButton>
</div>
@@ -19,7 +19,7 @@
let errors = {}; let errors = {};
let lesson = { let lesson = {
id: null, id: null,
title: 'Untitled lesson', title: '',
profile: undefined, profile: undefined,
call_url: undefined, call_url: undefined,
lesson_at: new Date(), lesson_at: new Date(),
@@ -11,6 +11,7 @@
import ListIcon from 'carbon-icons-svelte/lib/List.svelte'; import ListIcon from 'carbon-icons-svelte/lib/List.svelte';
import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte'; import IbmWatsonKnowledgeStudioIcon from 'carbon-icons-svelte/lib/IbmWatsonKnowledgeStudio.svelte';
import MachineLearningModel from 'carbon-icons-svelte/lib/MachineLearningModel.svelte'; import MachineLearningModel from 'carbon-icons-svelte/lib/MachineLearningModel.svelte';
import MagicWandFilled from 'carbon-icons-svelte/lib/MagicWandFilled.svelte';
import Tabs from '$lib/components/Tabs/index.svelte'; import Tabs from '$lib/components/Tabs/index.svelte';
import TabContent from '$lib/components/TabContent/index.svelte'; import TabContent from '$lib/components/TabContent/index.svelte';
import Box from '$lib/components/Box/index.svelte'; import Box from '$lib/components/Box/index.svelte';
@@ -221,7 +222,7 @@
isDisabled={$isLoading} isDisabled={$isLoading}
variant={VARIANTS.OUTLINED} variant={VARIANTS.OUTLINED}
> >
<MachineLearningModel size={20} class="carbon-icon mr-3" /> <MagicWandFilled size={20} class="carbon-icon mr-3" />
AI AI
<Popover <Popover
caret caret
@@ -1,18 +1,9 @@
<script> <script>
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '$lib/components/TextEditor/index.svelte'; import TextEditor from '$lib/components/TextEditor/index.svelte';
export let course; export let value = '';
function handleChange(html) {
const _course = cloneDeep(course);
set(_course, 'metadata.description', html);
course = _course;
}
</script> </script>
<div class="h-2/5"> <div class="h-2/5">
<TextEditor value={get(course, 'metadata.description')} onChange={handleChange} /> <TextEditor bind:value />
</div> </div>
@@ -1,18 +1,9 @@
<script> <script>
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '$lib/components/TextEditor/index.svelte'; import TextEditor from '$lib/components/TextEditor/index.svelte';
export let course; export let value = '';
function handleChange(html) {
const _course = cloneDeep(course);
set(_course, 'metadata.goals', html);
course = _course;
}
</script> </script>
<div class="h-2/5"> <div class="h-2/5">
<TextEditor value={get(course, 'metadata.goals')} onChange={handleChange} /> <TextEditor bind:value />
</div> </div>
@@ -1,18 +1,9 @@
<script> <script lang="ts">
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import TextEditor from '$lib/components/TextEditor/index.svelte'; import TextEditor from '$lib/components/TextEditor/index.svelte';
export let course; export let value: string;
function handleChange(html) {
const _course = cloneDeep(course);
set(_course, 'metadata.requirements', html);
course = _course;
}
</script> </script>
<div class="h-2/5"> <div class="h-2/5">
<TextEditor value={get(course, 'metadata.requirements')} onChange={handleChange} /> <TextEditor bind:value />
</div> </div>
@@ -58,6 +58,9 @@
placeholder="" placeholder=""
bind:value={review.description} bind:value={review.description}
errorMessage={errors.description} errorMessage={errors.description}
isAIEnabled={true}
initAIPrompt="Generate a 20 word review from a student saying good things about me teaching"
aiAlignPopover="top-right"
/> />
<div class="flex justify-between w-full items-center mt-2"> <div class="flex justify-between w-full items-center mt-2">
@@ -1,10 +1,11 @@
<script> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte'; import ChevronRightIcon from 'carbon-icons-svelte/lib/ChevronRight.svelte';
import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte'; import ArrowLeftIcon from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
import ArrowUpRightIcon from 'carbon-icons-svelte/lib/ArrowUpRight.svelte'; import ArrowUpRightIcon from 'carbon-icons-svelte/lib/ArrowUpRight.svelte';
import Container from './Container.svelte';
import IconButton from '$lib/components/IconButton/index.svelte'; import IconButton from '$lib/components/IconButton/index.svelte';
import CloseButton from '$lib/components/Buttons/Close/index.svelte'; import CloseButton from '$lib/components/Buttons/Close/index.svelte';
import HeaderForm from './HeaderForm.svelte'; import HeaderForm from './HeaderForm.svelte';
@@ -21,47 +22,70 @@
import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte'; import ChevronLeftIcon from 'carbon-icons-svelte/lib/ChevronLeft.svelte';
import { isMobile } from '$lib/utils/store/useMobile'; import { isMobile } from '$lib/utils/store/useMobile';
import CustomPromptBtn from '$lib/components/AI/AIButton/CustomPromptBtn.svelte';
import type { Course } from '$lib/utils/types';
export let course; export let course: Course;
export let courseId; export let courseId: string;
let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300'; let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300';
let loading = false; let loading = false;
let show = false; let show = false;
const sections = [
interface Section {
key: number;
path: string;
title: string;
enableAIWriter?: boolean;
initPrompt?: string;
}
const sections: Section[] = [
{ {
key: 1, key: 1,
path: '',
title: 'Header' title: 'Header'
}, },
{ {
key: 2, key: 2,
title: 'Requirement' path: 'metadata.requirements',
title: 'Requirement',
enableAIWriter: true,
initPrompt: 'Generate a course requirement for this given course:'
}, },
{ {
key: 3, key: 3,
title: 'Description' path: 'metadata.description',
title: 'Description',
enableAIWriter: true,
initPrompt: 'Generate a course description for this given course:'
}, },
{ {
key: 4, key: 4,
title: 'Goals' path: 'metadata.goals',
title: 'Goals',
enableAIWriter: true,
initPrompt: 'What should a student expect to learn from this given course:'
}, },
{ {
key: 5, key: 5,
path: '',
title: 'Reviews' title: 'Reviews'
}, },
{ {
key: 6, key: 6,
path: '',
title: 'Instructor' title: 'Instructor'
}, },
{ {
key: 7, key: 7,
path: '',
title: 'Pricing' title: 'Pricing'
} }
]; ];
let selectedSection = null; let selectedSection: Section | null = null;
function handleClose() { function handleClose() {
console.log('closing');
if (!selectedSection) { if (!selectedSection) {
goto(`/courses/${courseId}`); goto(`/courses/${courseId}`);
} }
@@ -69,11 +93,13 @@
selectedSection = null; selectedSection = null;
} }
function handleSectionSelect(sectionKey) { function handleSectionSelect(sectionKey: number) {
return () => { return () => {
selectedSection = sections.find((section) => section.key === sectionKey); selectedSection = sections.find((section) => section.key === sectionKey) || null;
if (selectedSection) { if (selectedSection) {
const sectionId = selectedSection.title.toLowerCase(); const sectionId = selectedSection.title.toLowerCase();
const sectionEl = document.getElementById(sectionId); const sectionEl = document.getElementById(sectionId);
if (sectionEl) { if (sectionEl) {
sectionEl.scrollIntoView({ sectionEl.scrollIntoView({
@@ -120,8 +146,8 @@
<aside <aside
class={`${ class={`${
show ? '-translate-x-[100%] fixed z-[9999]' : 'translate-x-0 fixed z-[9999]' show ? '-translate-x-[100%] fixed z-[50]' : 'translate-x-0 fixed z-[50]'
} left-0 transition w-[280px] min-w-[280px] bg-gray-100 dark:bg-black h-full border border-l-0 border-t-0 border-b-0 border-r-1`} } left-0 transition w-[350px] min-w-[300px] bg-gray-100 dark:bg-black h-full border border-l-0 border-t-0 border-b-0 border-r-1`}
> >
<div class="toggler rounded-full shadow-lg absolute"> <div class="toggler rounded-full shadow-lg absolute">
<IconButton <IconButton
@@ -185,18 +211,36 @@
<IconButton onClick={handleClose}> <IconButton onClick={handleClose}>
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" title="Go back" /> <ArrowLeftIcon size={24} class="carbon-icon dark:text-white" title="Go back" />
</IconButton> </IconButton>
<h3 class="dark:text-white">{selectedSection.title}</h3> <div class=" flex items-center">
<h3 class="dark:text-white">
{selectedSection.title}
</h3>
{#if selectedSection.enableAIWriter}
<CustomPromptBtn
className="w-fit ml-2"
alignPopover="bottom-left"
defaultPrompt={`${selectedSection.initPrompt} ${course.title}. Please format in html`}
isHTML={true}
handleInsert={(v) => {
if (!selectedSection) return;
const _course = cloneDeep(course);
set(_course, selectedSection.path, v);
course = _course;
}}
/>
{/if}
</div>
</div> </div>
<div class="title-content p-2 overflow-y-auto"> <div class="title-content p-2 overflow-y-auto">
{#if selectedSection.key === 1} {#if selectedSection.key === 1}
<HeaderForm bind:course /> <HeaderForm bind:course />
{:else if selectedSection.key === 2} {:else if selectedSection.key === 2}
<RequirementForm bind:course /> <RequirementForm bind:value={course.metadata.requirements} />
{:else if selectedSection.key === 3} {:else if selectedSection.key === 3}
<DescriptionForm bind:course /> <DescriptionForm bind:value={course.metadata.description} />
{:else if selectedSection.key === 4} {:else if selectedSection.key === 4}
<GoalsForm bind:course /> <GoalsForm bind:value={course.metadata.goals} />
{:else if selectedSection.key === 5} {:else if selectedSection.key === 5}
<ReviewsForm bind:course /> <ReviewsForm bind:course />
{:else if selectedSection.key === 6} {:else if selectedSection.key === 6}
@@ -63,7 +63,11 @@
/> />
<!-- Pricing Details --> <!-- Pricing Details -->
<aside class="{className} price-container lg:sticky lg:top-10 lg:shadow-2xl lg:rounded-lg m-h-fit"> <aside
class="{className} price-container lg:sticky {editMode
? 'lg:top-0'
: 'lg:top-10'} lg:shadow-2xl lg:rounded-lg m-h-fit bg-neutral-800"
>
<div class="p-10"> <div class="p-10">
<!-- Pricing --> <!-- Pricing -->
<div class="mb-6"> <div class="mb-6">
@@ -167,12 +167,14 @@
<!-- Course Details --> <!-- Course Details -->
<div class="course-content w-full p-3 lg:w-10/12 lg:mr-14"> <div class="course-content w-full p-3 lg:w-10/12 lg:mr-14">
<!-- Navigation --> <!-- Navigation -->
<nav class="flex items-center border-b border-gray-300 py-3 sticky top-11 bg-white"> <nav
class="flex items-center border-b border-gray-300 py-3 sticky top-11 bg-white dark:bg-neutral-800"
>
{#each NAV_ITEMS as navItem} {#each NAV_ITEMS as navItem}
<a <a
href="{$page.url.pathname}{navItem.key}" href="{$page.url.pathname}{navItem.key}"
class="{navItem.key === activeNav && class="{navItem.key === activeNav &&
'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-slate-100 hover:text-slate-900 dark:text-white" 'active text-primary-700'} rounded-lg px-2 mr-6 text-slate-700 font-normal hover:bg-neutral-600 hover:text-slate-900 dark:text-white"
> >
{navItem.label} {navItem.label}
</a> </a>
@@ -16,6 +16,19 @@
description: '' description: ''
}; };
let userPrompt = '';
function onClose() {
createCourseModal.update(() => ({
open: false,
title: '',
description: '',
emails: '',
tutors: '',
students: ''
}));
}
async function createCourse(e) { async function createCourse(e) {
const { hasError, fieldErrors } = validateForm($createCourseModal); const { hasError, fieldErrors } = validateForm($createCourseModal);
@@ -32,6 +45,9 @@
console.log(`newGroup`, newGroup); console.log(`newGroup`, newGroup);
const { id: group_id } = newGroup[0]; const { id: group_id } = newGroup[0];
// Set userPrompt to the description
userPrompt = description;
// 2. Create course with group_id // 2. Create course with group_id
const { data: newCourse } = await supabase const { data: newCourse } = await supabase
.from('course') .from('course')
@@ -55,7 +71,7 @@
role_id: ROLE.TUTOR role_id: ROLE.TUTOR
}); });
$createCourseModal.open = false; onClose();
} }
</script> </script>
@@ -64,7 +80,7 @@
</svelte:head> </svelte:head>
<Modal <Modal
onClose={() => ($createCourseModal.open = false)} {onClose}
bind:open={$createCourseModal.open} bind:open={$createCourseModal.open}
width="w-4/5 md:w-2/5" width="w-4/5 md:w-2/5"
modalHeading="Create a Course" modalHeading="Create a Course"
@@ -73,13 +89,13 @@
<TextField <TextField
label="Course name" label="Course name"
bind:value={$createCourseModal.title} bind:value={$createCourseModal.title}
autoFocus={true}
placeholder="Your course name" placeholder="Your course name"
className="mb-4" className="mb-4"
isRequired={true} isRequired={true}
errorMessage={errors.title} errorMessage={errors.title}
autoComplete={false} autoComplete={false}
/> />
<TextArea <TextArea
label="Short description" label="Short description"
bind:value={$createCourseModal.description} bind:value={$createCourseModal.description}
@@ -89,6 +105,8 @@
className="mb-4" className="mb-4"
isRequired={true} isRequired={true}
errorMessage={errors.description} errorMessage={errors.description}
isAIEnabled={true}
initAIPrompt="Write a 30 word description for a course titled: {$createCourseModal.title}"
/> />
<div class="mt-5 flex items-center"> <div class="mt-5 flex items-center">
+26 -9
View File
@@ -1,10 +1,13 @@
<script> <script lang="ts">
import CustomPromptBtn from '$lib/components/AI/AIButton/CustomPromptBtn.svelte';
import type { PopoverProps } from 'carbon-components-svelte/types/Popover/Popover.svelte';
export let label = ''; export let label = '';
export let disabled = false; export let disabled = false;
export let placeholder = 'Write your answer...'; export let placeholder = 'Write your answer...';
export let value = ''; export let value = '';
export let rows = '3'; export let rows = 3;
export let maxRows = 3; // export let maxRows = 3;
export let className = ''; export let className = '';
export let labelClassName = ''; export let labelClassName = '';
export let bgColor = 'bg-gray-50 focus:bg-primary-50 dark:bg-white'; export let bgColor = 'bg-gray-50 focus:bg-primary-50 dark:bg-white';
@@ -12,20 +15,34 @@
export let errorMessage = ''; export let errorMessage = '';
export let isRequired = false; export let isRequired = false;
export let onChange = () => {}; // This is to know if element is 'dirty' export let onChange = () => {}; // This is to know if element is 'dirty'
export let ref; export let ref = null;
export let isAIEnabled = false;
export let initAIPrompt = '';
export let aiAlignPopover: PopoverProps['align'] = 'left';
$: minHeight = `${1 + parseInt(rows, 10) * 1.2}em`; // $: minHeight = `${1 + parseInt(rows, 10) * 1.2}em`;
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`; // $: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
</script> </script>
<label class="block w-full relative {className}"> <label for="text-field" class="block w-full relative {className}">
{#if label} {#if label}
<p for="text-field" class="dark:text-white text-left {labelClassName}"> <p class="dark:text-white text-left w-full flex items-center justify-between {labelClassName}">
<span>
{label} {label}
{#if isRequired} {#if isRequired}
<span class="text-red-700">*</span> <span class="text-red-700">*</span>
{/if} {/if}
</span>
{#if isAIEnabled}
<CustomPromptBtn
defaultPrompt={initAIPrompt}
alignPopover={aiAlignPopover}
handleInsert={(v) => {
value = v;
}}
/>
{/if}
</p> </p>
{/if} {/if}
@@ -164,6 +164,7 @@
placeholder="Write your subtitle here" placeholder="Write your subtitle here"
className="w-full mb-5" className="w-full mb-5"
bind:value={$landingPageSettings.header.subtitle} bind:value={$landingPageSettings.header.subtitle}
isAIEnabled={true}
/> />
<SectionTitle>Actions</SectionTitle> <SectionTitle>Actions</SectionTitle>
@@ -221,6 +222,7 @@
placeholder="Write your Content here" placeholder="Write your Content here"
className="mb-5" className="mb-5"
bind:value={$landingPageSettings.aboutUs.content} bind:value={$landingPageSettings.aboutUs.content}
isAIEnabled={true}
/> />
<div> <div>
@@ -268,6 +270,7 @@
labelClassName="font-light" labelClassName="font-light"
placeholder="Write your subtitle here" placeholder="Write your subtitle here"
bind:value={$landingPageSettings.courses.subtitle} bind:value={$landingPageSettings.courses.subtitle}
isAIEnabled={true}
/> />
</Column> </Column>
</Row> </Row>
@@ -301,6 +304,7 @@
placeholder="Write your answer here" placeholder="Write your answer here"
bind:value={item.content} bind:value={item.content}
className="mb-5" className="mb-5"
isAIEnabled={true}
/> />
<IconButton onClick={() => deleteFaq(item.id)}> <IconButton onClick={() => deleteFaq(item.id)}>
<TrashCan size={24} class="fill-red-700" /> <TrashCan size={24} class="fill-red-700" />
@@ -320,6 +324,7 @@
placeholder="Write your answer here" placeholder="Write your answer here"
bind:value={newQuestion.content} bind:value={newQuestion.content}
className="mb-5" className="mb-5"
isAIEnabled={true}
/> />
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<PrimaryButton variant={VARIANTS.OUTLINED} label="Save" onClick={saveNewFAQ} /> <PrimaryButton variant={VARIANTS.OUTLINED} label="Save" onClick={saveNewFAQ} />
@@ -359,6 +364,7 @@
placeholder="Write your subtitle here" placeholder="Write your subtitle here"
className="mt-3 mb-5" className="mt-3 mb-5"
bind:value={$landingPageSettings.contact.subtitle} bind:value={$landingPageSettings.contact.subtitle}
isAIEnabled={true}
/> />
<TextField <TextField
label="Phone number" label="Phone number"
@@ -396,6 +402,7 @@
placeholder="Write your subtitle here" placeholder="Write your subtitle here"
className="mb-5" className="mb-5"
bind:value={$landingPageSettings.mailinglist.subtitle} bind:value={$landingPageSettings.mailinglist.subtitle}
isAIEnabled={true}
/> />
<TextField <TextField
+1 -1
View File
@@ -45,5 +45,5 @@
</script> </script>
<div> <div>
<Editor {value} {apiKey} {conf} /> <Editor bind:value {apiKey} {conf} />
</div> </div>
+4 -4
View File
@@ -2,9 +2,9 @@ export * from './config';
export * from './dashboard'; export * from './dashboard';
interface CourseMetadata { interface CourseMetadata {
requirements?: string; requirements: string;
description?: string; description: string;
goals?: string; goals: string;
videoUrl?: ''; videoUrl?: '';
showDiscount: false; showDiscount: false;
discount: 0; discount: 0;
@@ -95,7 +95,7 @@ export interface Course {
organization_id?: string /* foreign key to organization.id */; organization_id?: string /* foreign key to organization.id */;
logo?: string; logo?: string;
slug?: any; // type unknown; slug?: any; // type unknown;
metadata?: CourseMetadata; metadata: CourseMetadata;
cost: number; cost: number;
currency?: string; currency?: string;
group?: Group; group?: Group;
@@ -0,0 +1,37 @@
import { Configuration, OpenAIApi } from 'openai-edge';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { OPENAI_API_KEY } from '$env/static/private';
// Create an OpenAI API client (that's edge friendly!)
const openAIConfig = new Configuration({
apiKey: OPENAI_API_KEY
});
const openai = new OpenAIApi(openAIConfig);
// Set the runtime to edge for best performance
export const config = {
runtime: 'edge'
};
export async function POST({ request }) {
const { prompt } = await request.json();
const response = await openai.createChatCompletion({
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: 'Act like an assistant of a lecturer or an educator'
},
{
role: 'user',
content: prompt
}
],
stream: true
});
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}
+15 -2
View File
@@ -117,6 +117,7 @@
</PageNav> </PageNav>
<PageBody width="max-w-6xl" padding="p-0"> <PageBody width="max-w-6xl" padding="p-0">
{#if $lessons.length}
<section <section
class="w-full lg:w-11/12 p-3 lg:px-4 m-auto" class="w-full lg:w-11/12 p-3 lg:px-4 m-auto"
use:dndzone={{ use:dndzone={{
@@ -325,13 +326,25 @@
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" /> <img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No lessons yet</h2> <h2 class="text-xl my-1.5 font-normal">No lessons yet</h2>
<p class="text-sm text-center text-slate-500"> <p class="text-sm text-center text-slate-500">
Share your knowledge with the world by creating engaging lessons. Start by clicking on Share your knowledge with the world by creating engaging lessons. Start by clicking
the Add button. on the Add button.
</p> </p>
</div> </div>
</Box> </Box>
{/each} {/each}
</section> </section>
{:else}
<Box className="w-full lg:w-11/12 lg:px-4 m-auto">
<div class="flex justify-between flex-col items-center">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No lessons yet</h2>
<p class="text-sm text-center text-slate-500">
Share your knowledge with the world by creating engaging lessons. Start by clicking on
the Add button.
</p>
</div>
</Box>
{/if}
</PageBody> </PageBody>
</CourseContainer> </CourseContainer>