chore: ai, public course and other ui improvements
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import type {
|
||||
PublicCourseCalloutData,
|
||||
PublicCourseOrgData,
|
||||
PublicCourseSidebarSection,
|
||||
PublicExerciseViewData,
|
||||
PublicLessonViewData
|
||||
} from '@cio/ui/custom/public-course';
|
||||
import type { ExerciseQuestionModel } from '@cio/question-types';
|
||||
|
||||
export const CALLOUT_FIXTURE: PublicCourseCalloutData = {
|
||||
title: 'Want to go deeper on this?',
|
||||
description: 'Join the full "AI for Builders" cohort where we ship a production-grade RAG pipeline in 4 weeks.',
|
||||
buttonLabel: 'Read more on my site',
|
||||
buttonUrl: 'https://example.com/ai-for-builders'
|
||||
};
|
||||
|
||||
export const ORG_FIXTURE: PublicCourseOrgData = {
|
||||
id: 'org-1',
|
||||
name: 'ClassroomIO',
|
||||
siteName: 'classroomio',
|
||||
avatarUrl: 'https://avatars.githubusercontent.com/u/119528464?s=200&v=4'
|
||||
};
|
||||
|
||||
export const COURSE_TITLE_FIXTURE = 'AI for Builders — Crash Course';
|
||||
|
||||
export const SIDEBAR_FIXTURE: PublicCourseSidebarSection[] = [
|
||||
{
|
||||
id: 'section-intro',
|
||||
title: 'Getting started',
|
||||
items: [
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-welcome',
|
||||
slug: 'welcome',
|
||||
title: 'Welcome to the course',
|
||||
isUnlocked: true
|
||||
},
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-setup',
|
||||
slug: 'setting-up-your-environment',
|
||||
title: 'Setting up your environment',
|
||||
isUnlocked: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'section-foundations',
|
||||
title: 'Foundations',
|
||||
items: [
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-intro-llms',
|
||||
slug: 'intro-to-large-language-models',
|
||||
title: 'Intro to large language models',
|
||||
isUnlocked: true
|
||||
},
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-hallucinations',
|
||||
slug: 'hallucination-and-limitations',
|
||||
title: 'Hallucination & limitations',
|
||||
isUnlocked: true
|
||||
},
|
||||
{
|
||||
kind: 'exercise',
|
||||
id: 'exercise-foundations-quiz',
|
||||
slug: 'foundations-quiz',
|
||||
title: 'Foundations quiz',
|
||||
isUnlocked: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'section-advanced',
|
||||
title: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-rag',
|
||||
slug: 'retrieval-augmented-generation',
|
||||
title: 'Retrieval-augmented generation',
|
||||
isUnlocked: false
|
||||
},
|
||||
{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-agents',
|
||||
slug: 'building-agents',
|
||||
title: 'Building agents',
|
||||
isUnlocked: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const LESSON_FIXTURE: PublicLessonViewData = {
|
||||
kind: 'lesson',
|
||||
title: 'Hallucination & limitations',
|
||||
sectionTitle: 'Foundations',
|
||||
body: `
|
||||
<p>Large language models are incredibly capable, but they also confidently produce incorrect answers — what the field calls <em>hallucination</em>. Before we design systems around them, it helps to build an intuition for where they fail.</p>
|
||||
<h2>When LLMs hallucinate</h2>
|
||||
<ul>
|
||||
<li>When asked about things outside of their training distribution.</li>
|
||||
<li>When prompted with ambiguous or leading questions.</li>
|
||||
<li>When asked to cite sources without retrieval grounding.</li>
|
||||
</ul>
|
||||
<p>In the next lessons we'll cover concrete mitigations, including retrieval, tool use, and structured outputs.</p>
|
||||
`,
|
||||
video: {
|
||||
type: 'youtube',
|
||||
link: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
||||
},
|
||||
isUnlocked: true
|
||||
};
|
||||
|
||||
export const LESSON_LOCKED_FIXTURE: PublicLessonViewData = {
|
||||
...LESSON_FIXTURE,
|
||||
title: 'Building agents',
|
||||
isUnlocked: false,
|
||||
video: null,
|
||||
body: ''
|
||||
};
|
||||
|
||||
const EXERCISE_QUESTION_FIXTURES: ExerciseQuestionModel[] = [
|
||||
{
|
||||
id: 'q-radio-1',
|
||||
title: 'Which of these is the clearest sign of hallucination?',
|
||||
questionType: 'RADIO',
|
||||
points: 1,
|
||||
options: [
|
||||
{ id: 'a', label: 'The model refuses to answer a question it doesn’t know.', isCorrect: false },
|
||||
{ id: 'b', label: 'The model confidently invents a fact that doesn’t exist.', isCorrect: true },
|
||||
{ id: 'c', label: 'The model cites a source verbatim.', isCorrect: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'q-checkbox-1',
|
||||
title: 'Pick every mitigation that grounds an LLM in real data.',
|
||||
questionType: 'CHECKBOX',
|
||||
points: 2,
|
||||
options: [
|
||||
{ id: 'a', label: 'Retrieval-augmented generation', isCorrect: true },
|
||||
{ id: 'b', label: 'Turning up temperature', isCorrect: false },
|
||||
{ id: 'c', label: 'Calling a tool / function', isCorrect: true },
|
||||
{ id: 'd', label: 'Asking the model to be more confident', isCorrect: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'q-tf-1',
|
||||
title: 'LLMs always know when they’re wrong.',
|
||||
questionType: 'TRUE_FALSE',
|
||||
points: 1,
|
||||
options: [
|
||||
{ id: 'true-option', label: 'True', value: 'true', isCorrect: false },
|
||||
{ id: 'false-option', label: 'False', value: 'false', isCorrect: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'q-short-1',
|
||||
title: 'What’s the shorthand term for "retrieval-augmented generation"?',
|
||||
questionType: 'SHORT_ANSWER',
|
||||
points: 1,
|
||||
options: [
|
||||
{ id: 'rag-1', label: 'rag', isCorrect: true },
|
||||
{ id: 'rag-2', label: 'RAG', isCorrect: true }
|
||||
],
|
||||
settings: { caseSensitive: false }
|
||||
},
|
||||
{
|
||||
id: 'q-numeric-1',
|
||||
title: 'How many mitigations did we cover in this lesson? (A number is fine.)',
|
||||
questionType: 'NUMERIC',
|
||||
points: 1,
|
||||
options: [{ id: 'three', label: '3', value: '3', isCorrect: true }],
|
||||
settings: { tolerance: 0 }
|
||||
}
|
||||
];
|
||||
|
||||
export const EXERCISE_FIXTURE: PublicExerciseViewData = {
|
||||
kind: 'exercise',
|
||||
title: 'Foundations quiz',
|
||||
description: 'A short check-in on the foundations we just covered. Graded instantly in your browser.',
|
||||
sectionTitle: 'Foundations',
|
||||
isUnlocked: true,
|
||||
questions: EXERCISE_QUESTION_FIXTURES
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
<script module lang="ts">
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import { PublicCourse } from '@cio/ui';
|
||||
import type { PublicCourseSidebarItem } from '@cio/ui/custom/public-course';
|
||||
import { QUESTION_LABELS } from '../exercise-question/question-labels';
|
||||
import {
|
||||
CALLOUT_FIXTURE,
|
||||
COURSE_TITLE_FIXTURE,
|
||||
EXERCISE_FIXTURE,
|
||||
LESSON_FIXTURE,
|
||||
LESSON_LOCKED_FIXTURE,
|
||||
ORG_FIXTURE,
|
||||
SIDEBAR_FIXTURE
|
||||
} from './fixtures';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Molecules/PublicCourse',
|
||||
component: PublicCourse.PublicCourseShell,
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
});
|
||||
|
||||
const flatItems = SIDEBAR_FIXTURE.flatMap((section) => section.items);
|
||||
</script>
|
||||
|
||||
<Story name="Lesson · desktop + mobile frames">
|
||||
{#snippet template()}
|
||||
{@const activeSlug = 'hallucination-and-limitations'}
|
||||
{@const activeIndex = flatItems.findIndex((item) => item.slug === activeSlug)}
|
||||
{@const activeItem = flatItems[activeIndex] ?? null}
|
||||
<PublicCourse.PublicCourseShell
|
||||
sections={SIDEBAR_FIXTURE}
|
||||
courseTitle={COURSE_TITLE_FIXTURE}
|
||||
org={ORG_FIXTURE}
|
||||
{activeSlug}
|
||||
{activeItem}
|
||||
activeFlatIndex={activeIndex}
|
||||
totalItems={flatItems.length}
|
||||
hasPrev={activeIndex > 0}
|
||||
hasNext={activeIndex >= 0 && activeIndex < flatItems.length - 1}
|
||||
onItemClick={(item: PublicCourseSidebarItem) => console.log('navigate', item.slug)}
|
||||
onPrev={() => console.log('prev')}
|
||||
onNext={() => console.log('next')}
|
||||
>
|
||||
<PublicCourse.PublicLessonView lesson={LESSON_FIXTURE} callout={CALLOUT_FIXTURE} />
|
||||
</PublicCourse.PublicCourseShell>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Lesson · locked fallback (callout replaces body)">
|
||||
{#snippet template()}
|
||||
<PublicCourse.PublicCourseShell
|
||||
sections={SIDEBAR_FIXTURE}
|
||||
courseTitle={COURSE_TITLE_FIXTURE}
|
||||
org={ORG_FIXTURE}
|
||||
activeSlug="building-agents"
|
||||
activeItem={{
|
||||
kind: 'lesson',
|
||||
id: 'lesson-agents',
|
||||
slug: 'building-agents',
|
||||
title: 'Building agents',
|
||||
isUnlocked: false
|
||||
}}
|
||||
activeFlatIndex={flatItems.length - 1}
|
||||
totalItems={flatItems.length}
|
||||
hasPrev
|
||||
hasNext={false}
|
||||
>
|
||||
<PublicCourse.PublicLessonView lesson={LESSON_LOCKED_FIXTURE} callout={CALLOUT_FIXTURE} />
|
||||
</PublicCourse.PublicCourseShell>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Exercise · client-graded quiz">
|
||||
{#snippet template()}
|
||||
{@const activeSlug = 'foundations-quiz'}
|
||||
{@const activeIndex = flatItems.findIndex((item) => item.slug === activeSlug)}
|
||||
<PublicCourse.PublicCourseShell
|
||||
sections={SIDEBAR_FIXTURE}
|
||||
courseTitle={COURSE_TITLE_FIXTURE}
|
||||
org={ORG_FIXTURE}
|
||||
{activeSlug}
|
||||
activeItem={flatItems[activeIndex] ?? null}
|
||||
activeFlatIndex={activeIndex}
|
||||
totalItems={flatItems.length}
|
||||
hasPrev={activeIndex > 0}
|
||||
hasNext={activeIndex < flatItems.length - 1}
|
||||
>
|
||||
<PublicCourse.PublicExerciseView exercise={EXERCISE_FIXTURE} callout={CALLOUT_FIXTURE} labels={QUESTION_LABELS} />
|
||||
</PublicCourse.PublicCourseShell>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Sidebar · states only">
|
||||
{#snippet template()}
|
||||
<div class="ui:mx-auto ui:max-w-xs ui:border-r ui:border-border">
|
||||
<PublicCourse.PublicCourseSidebar sections={SIDEBAR_FIXTURE} activeSlug="hallucination-and-limitations" />
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Callout · inline + full variants">
|
||||
{#snippet template()}
|
||||
<div class="ui:mx-auto ui:max-w-2xl ui:space-y-8 ui:p-8">
|
||||
<div>
|
||||
<h3 class="ui:mb-2 ui:text-sm ui:font-semibold ui:text-muted-foreground">Inline (at bottom of lessons)</h3>
|
||||
<PublicCourse.PublicCourseCallout callout={CALLOUT_FIXTURE} variant="inline" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="ui:mb-2 ui:text-sm ui:font-semibold ui:text-muted-foreground">Full (locked item replacement)</h3>
|
||||
<PublicCourse.PublicCourseCallout callout={CALLOUT_FIXTURE} variant="full" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="ui:mb-2 ui:text-sm ui:font-semibold ui:text-muted-foreground">Full (no callout configured)</h3>
|
||||
<PublicCourse.PublicCourseCallout callout={null} variant="full" />
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
Reference in New Issue
Block a user