feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -7,6 +7,12 @@ import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents';
export type ContentItem = CourseContentItem;
export type ContentSection = CourseContentSection;
export type MentionableContent = {
id: string;
title: string;
type: CourseContentType;
};
export const CONTENT_DEFINITIONS = {
[CourseContentType.Section]: {
label: 'Section',
@@ -28,7 +34,7 @@ const EMPTY_CONTENT = {
items: [] as ContentItem[]
};
export function getContentRoute(courseId: string, item: ContentItem) {
export function getContentRoute(courseId: string, item: { id: string; type: string }) {
if (item.type === CourseContentType.Lesson) {
return `/courses/${courseId}/lessons/${item.id}`;
}
@@ -37,6 +43,10 @@ export function getContentRoute(courseId: string, item: ContentItem) {
return `/courses/${courseId}/exercises/${item.id}`;
}
if (item.type === CourseContentType.Section) {
return `/courses/${courseId}/lessons#section-${item.id}`;
}
return '';
}
@@ -53,3 +63,33 @@ export function getOrderedNavigableContent(course: Course | null): ContentItem[]
NAVIGABLE_CONTENT_TYPES.includes(item.type as (typeof NAVIGABLE_CONTENT_TYPES)[number])
);
}
/**
* Flat list of mentionable content for the AI chat @ menu.
* Includes sections (when grouped) interleaved with their lessons/exercises in display order.
*/
export function getMentionableContent(course: Course | null): MentionableContent[] {
const content = getCourseContent(course);
if (!content.grouped) {
return content.items
.filter((item) => NAVIGABLE_CONTENT_TYPES.includes(item.type as (typeof NAVIGABLE_CONTENT_TYPES)[number]))
.map((item) => ({ id: item.id, title: item.title, type: item.type as CourseContentType }));
}
const result: MentionableContent[] = [];
for (const section of content.sections) {
if (section.id !== 'ungrouped' && section.title) {
result.push({ id: section.id, title: section.title, type: CourseContentType.Section });
}
for (const item of section.items) {
if (NAVIGABLE_CONTENT_TYPES.includes(item.type as (typeof NAVIGABLE_CONTENT_TYPES)[number])) {
result.push({ id: item.id, title: item.title, type: item.type as CourseContentType });
}
}
}
return result;
}