feat: certificates, landing pages, ai tutor, website update, compliance

This commit is contained in:
Rotimi Best
2026-05-18 03:47:22 +01:00
parent c31ad63955
commit ac3a2c14a7
644 changed files with 56050 additions and 5439 deletions
@@ -4,8 +4,11 @@
import BookOpenIcon from '@lucide/svelte/icons/book-open';
import FileQuestionIcon from '@lucide/svelte/icons/file-question';
import PencilIcon from '@lucide/svelte/icons/pencil';
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up';
import { Button } from '@cio/ui/base/button';
import { Textarea } from '@cio/ui/base/textarea';
import { SvelteSet } from 'svelte/reactivity';
import { t } from '$lib/utils/functions/translations';
import type { CoursePlan } from './utils/course-plan';
/** Token cost estimation heuristics (tokens per item) */
@@ -18,11 +21,31 @@
interface Props {
plan: CoursePlan;
onImplement: (editedPlan: CoursePlan) => void;
onAskChanges: () => void;
onAskChanges: (message: string) => void;
/** When true, disables the inline change-request composer (agent is already working). */
isBusy?: boolean;
remainingTokens?: number;
}
let { plan, onImplement, onAskChanges, remainingTokens }: Props = $props();
let { plan, onImplement, onAskChanges, isBusy = false, remainingTokens }: Props = $props();
let changeRequest = $state('');
const canSendChange = $derived(!isBusy && changeRequest.trim().length > 0);
function submitChangeRequest() {
if (!canSendChange) return;
const message = changeRequest.trim();
changeRequest = '';
onAskChanges(message);
}
function handleChangeKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
submitChangeRequest();
}
}
// CoursePlan is plain JSON; structuredClone fails when the AI SDK part carries
// non-cloneable internals, so JSON round-trip is both safer and sufficient.
@@ -237,9 +260,29 @@
</p>
{/if}
<div class="flex gap-2">
<Button size="sm" onclick={() => onImplement(editablePlan)} class="flex-1">Implement Plan</Button>
<Button size="sm" variant="outline" onclick={onAskChanges} class="flex-1">Ask for Changes</Button>
<div class="flex flex-col gap-2">
<Button size="sm" onclick={() => onImplement(editablePlan)} class="w-full">
{$t('ai_assistant.plan_implement')}
</Button>
<div class="flex items-end gap-2">
<Textarea
bind:value={changeRequest}
placeholder={$t('ai_assistant.plan_ask_changes_placeholder')}
rows={1}
disabled={isBusy}
onkeydown={handleChangeKeydown}
class="ui:min-h-9! ui:resize-none ui:py-2 ui:text-sm"
/>
<Button
size="icon-sm"
variant="default"
disabled={!canSendChange}
onclick={submitChangeRequest}
aria-label={$t('ai_assistant.plan_ask_changes_send_aria')}
>
<ArrowUpIcon class="ui:size-4" />
</Button>
</div>
</div>
</div>
</div>