diff --git a/src/components/Activity/ProjectProgress.tsx b/src/components/Activity/ProjectProgress.tsx
index 6ac20de28..3e91c25f3 100644
--- a/src/components/Activity/ProjectProgress.tsx
+++ b/src/components/Activity/ProjectProgress.tsx
@@ -1,5 +1,4 @@
 import { getUser } from '../../lib/jwt';
-import { getPercentage } from '../../helper/number';
 import { ProjectProgressActions } from './ProjectProgressActions';
 import { cn } from '../../lib/classname';
 import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
diff --git a/src/components/Activity/ResourceProgress.tsx b/src/components/Activity/ResourceProgress.tsx
index 28c88ec9f..00b38aba3 100644
--- a/src/components/Activity/ResourceProgress.tsx
+++ b/src/components/Activity/ResourceProgress.tsx
@@ -1,7 +1,7 @@
 import { getUser } from '../../lib/jwt';
-import { getPercentage } from '../../helper/number';
 import { ResourceProgressActions } from './ResourceProgressActions';
 import { cn } from '../../lib/classname';
+import { getPercentage } from '../../lib/number';
 
 type ResourceProgressType = {
   resourceType: 'roadmap' | 'best-practice';
diff --git a/src/components/Billing/BillingPage.tsx b/src/components/Billing/BillingPage.tsx
index fdf8287b0..92748a844 100644
--- a/src/components/Billing/BillingPage.tsx
+++ b/src/components/Billing/BillingPage.tsx
@@ -16,10 +16,11 @@ import {
   Calendar,
   RefreshCw,
   Loader2,
-  AlertTriangle,
   CreditCard,
   ArrowRightLeft,
+  CircleX,
 } from 'lucide-react';
+import { BillingWarning } from './BillingWarning';
 
 export type CreateCustomerPortalBody = {};
 
@@ -38,6 +39,10 @@ export function BillingPage() {
     queryClient,
   );
 
+  const isCanceled =
+    billingDetails?.status === 'canceled' || billingDetails?.cancelAtPeriodEnd;
+  const isPastDue = billingDetails?.status === 'past_due';
+
   const {
     mutate: createCustomerPortal,
     isSuccess: isCreatingCustomerPortalSuccess,
@@ -80,9 +85,6 @@ export function BillingPage() {
   const selectedPlanDetails = USER_SUBSCRIPTION_PLAN_PRICES.find(
     (plan) => plan.priceId === billingDetails?.priceId,
   );
-
-  const shouldHideDeleteButton =
-    billingDetails?.status === 'canceled' || billingDetails?.cancelAtPeriodEnd;
   const priceDetails = selectedPlanDetails;
 
   const formattedNextBillDate = new Date(
@@ -115,25 +117,30 @@ export function BillingPage() {
         !isLoadingBillingDetails &&
         priceDetails && (
           <div className="mt-1">
-            {billingDetails?.status === 'past_due' && (
-              <div className="mb-6 flex items-center gap-2 rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-600">
-                <AlertTriangle className="h-5 w-5" />
-                <span>
-                  We were not able to charge your card.{' '}
-                  <button
-                    disabled={
-                      isCreatingCustomerPortal ||
-                      isCreatingCustomerPortalSuccess
-                    }
-                    onClick={() => {
-                      createCustomerPortal({});
-                    }}
-                    className="font-semibold underline underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50"
-                  >
-                    Update payment information.
-                  </button>
-                </span>
-              </div>
+            {isCanceled && (
+              <BillingWarning
+                icon={CircleX}
+                message="Your subscription has been canceled."
+                buttonText="Reactivate?"
+                onButtonClick={() => {
+                  createCustomerPortal({});
+                }}
+                isLoading={
+                  isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+                }
+              />
+            )}
+            {isPastDue && (
+              <BillingWarning
+                message="We were not able to charge your card."
+                buttonText="Update payment information."
+                onButtonClick={() => {
+                  createCustomerPortal({});
+                }}
+                isLoading={
+                  isCreatingCustomerPortal || isCreatingCustomerPortalSuccess
+                }
+              />
             )}
 
             <h2 className="mb-2 text-xl font-semibold text-black">
@@ -181,7 +188,7 @@ export function BillingPage() {
               </div>
 
               <div className="mt-8 flex gap-3 max-sm:flex-col">
-                {!shouldHideDeleteButton && (
+                {!isCanceled && (
                   <button
                     className="inline-flex items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 max-sm:flex-grow"
                     onClick={() => {
diff --git a/src/components/Billing/BillingWarning.tsx b/src/components/Billing/BillingWarning.tsx
new file mode 100644
index 000000000..f71cce994
--- /dev/null
+++ b/src/components/Billing/BillingWarning.tsx
@@ -0,0 +1,39 @@
+import { AlertTriangle, type LucideIcon } from 'lucide-react';
+
+export type BillingWarningProps = {
+  icon?: LucideIcon;
+  message: string;
+  onButtonClick?: () => void;
+  buttonText?: string;
+  isLoading?: boolean;
+};
+
+export function BillingWarning(props: BillingWarningProps) {
+  const {
+    message,
+    onButtonClick,
+    buttonText,
+    isLoading,
+    icon: Icon = AlertTriangle,
+  } = props;
+
+  return (
+    <div className="mb-6 flex items-center gap-2 rounded-lg border border-red-300 bg-red-50 p-4 text-sm text-red-600">
+      <Icon className="h-5 w-5" />
+      <span>
+        {message}
+        {buttonText && (
+          <button
+            disabled={isLoading}
+            onClick={() => {
+              onButtonClick?.();
+            }}
+            className="font-semibold underline underline-offset-4 disabled:cursor-not-allowed disabled:opacity-50 ml-0.5"
+          >
+            {buttonText}
+          </button>
+        )}
+      </span>
+    </div>
+  );
+}
diff --git a/src/components/Dashboard/DashboardCustomProgressCard.tsx b/src/components/Dashboard/DashboardCustomProgressCard.tsx
index 9464d5a73..9262614bc 100644
--- a/src/components/Dashboard/DashboardCustomProgressCard.tsx
+++ b/src/components/Dashboard/DashboardCustomProgressCard.tsx
@@ -1,5 +1,5 @@
-import { getPercentage } from '../../helper/number';
 import { getRelativeTimeString } from '../../lib/date';
+import { getPercentage } from '../../lib/number';
 import type { UserProgress } from '../TeamProgress/TeamProgressPage';
 
 type DashboardCustomProgressCardProps = {
diff --git a/src/components/Dashboard/DashboardProgressCard.tsx b/src/components/Dashboard/DashboardProgressCard.tsx
index d243051b0..467cffb6c 100644
--- a/src/components/Dashboard/DashboardProgressCard.tsx
+++ b/src/components/Dashboard/DashboardProgressCard.tsx
@@ -1,6 +1,5 @@
-import { getPercentage } from '../../helper/number';
+import { getPercentage } from '../../lib/number';
 import type { UserProgress } from '../TeamProgress/TeamProgressPage';
-import { ArrowUpRight, ExternalLink } from 'lucide-react';
 
 type DashboardProgressCardProps = {
   progress: UserProgress;
diff --git a/src/components/GenerateCourse/AICourseCard.tsx b/src/components/GenerateCourse/AICourseCard.tsx
index 3a4a6bd6c..4048dd73f 100644
--- a/src/components/GenerateCourse/AICourseCard.tsx
+++ b/src/components/GenerateCourse/AICourseCard.tsx
@@ -27,7 +27,7 @@ export function AICourseCard(props: AICourseCardProps) {
 
   // Calculate progress percentage
   const totalTopics = course.lessonCount || 0;
-  const completedTopics = course.progress?.done?.length || 0;
+  const completedTopics = course.done?.length || 0;
   const progressPercentage =
     totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0;
 
diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index ce3ad71fb..78b6d304e 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -20,16 +20,19 @@ import { AICourseModuleList } from './AICourseModuleList';
 import { AICourseModuleView } from './AICourseModuleView';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 import { AILimitsPopup } from './AILimitsPopup';
+import { RegenerateOutline } from './RegenerateOutline';
+import { useIsPaidUser } from '../../queries/billing';
 
 type AICourseContentProps = {
   courseSlug?: string;
   course: AiCourse;
   isLoading: boolean;
   error?: string;
+  onRegenerateOutline: (prompt?: string) => void;
 };
 
 export function AICourseContent(props: AICourseContentProps) {
-  const { course, courseSlug, isLoading, error } = props;
+  const { course, courseSlug, isLoading, error, onRegenerateOutline } = props;
 
   const [showUpgradeModal, setShowUpgradeModal] = useState(false);
   const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
@@ -39,6 +42,8 @@ export function AICourseContent(props: AICourseContentProps) {
   const [sidebarOpen, setSidebarOpen] = useState(false);
   const [viewMode, setViewMode] = useState<'module' | 'full'>('full');
 
+  const { isPaidUser } = useIsPaidUser();
+
   const { data: aiCourseProgress } = useQuery(
     getAiCourseProgressOptions({ aiCourseSlug: courseSlug || '' }),
     queryClient,
@@ -49,21 +54,23 @@ export function AICourseContent(props: AICourseContentProps) {
   >({});
 
   const goToNextModule = () => {
-    if (activeModuleIndex < course.modules.length - 1) {
-      const nextModuleIndex = activeModuleIndex + 1;
-      setActiveModuleIndex(nextModuleIndex);
-      setActiveLessonIndex(0);
-
-      setExpandedModules((prev) => {
-        const newState: Record<number, boolean> = {};
-        course.modules.forEach((_, idx) => {
-          newState[idx] = false;
-        });
-
-        newState[nextModuleIndex] = true;
-        return newState;
-      });
+    if (activeModuleIndex >= course.modules.length) {
+      return;
     }
+
+    const nextModuleIndex = activeModuleIndex + 1;
+    setActiveModuleIndex(nextModuleIndex);
+    setActiveLessonIndex(0);
+
+    setExpandedModules((prev) => {
+      const newState: Record<number, boolean> = {};
+      course.modules.forEach((_, idx) => {
+        newState[idx] = false;
+      });
+
+      newState[nextModuleIndex] = true;
+      return newState;
+    });
   };
 
   const goToNextLesson = () => {
@@ -78,26 +85,29 @@ export function AICourseContent(props: AICourseContentProps) {
   const goToPrevLesson = () => {
     if (activeLessonIndex > 0) {
       setActiveLessonIndex(activeLessonIndex - 1);
-    } else {
-      const prevModule = course.modules[activeModuleIndex - 1];
-      if (prevModule) {
-        const prevModuleIndex = activeModuleIndex - 1;
-        setActiveModuleIndex(prevModuleIndex);
-        setActiveLessonIndex(prevModule.lessons.length - 1);
-
-        // Expand the previous module in the sidebar
-        setExpandedModules((prev) => {
-          const newState: Record<number, boolean> = {};
-          // Set all modules to collapsed
-          course.modules.forEach((_, idx) => {
-            newState[idx] = false;
-          });
-          // Expand only the previous module
-          newState[prevModuleIndex] = true;
-          return newState;
-        });
-      }
+      return;
     }
+
+    const prevModule = course.modules[activeModuleIndex - 1];
+    if (!prevModule) {
+      return;
+    }
+
+    const prevModuleIndex = activeModuleIndex - 1;
+    setActiveModuleIndex(prevModuleIndex);
+    setActiveLessonIndex(prevModule.lessons.length - 1);
+
+    // Expand the previous module in the sidebar
+    setExpandedModules((prev) => {
+      const newState: Record<number, boolean> = {};
+      // Set all modules to collapsed
+      course.modules.forEach((_, idx) => {
+        newState[idx] = false;
+      });
+      // Expand only the previous module
+      newState[prevModuleIndex] = true;
+      return newState;
+    });
   };
 
   const currentModule = course.modules[activeModuleIndex];
@@ -109,6 +119,7 @@ export function AICourseContent(props: AICourseContentProps) {
     (total, module) => total + module.lessons.length,
     0,
   );
+
   const totalDoneLessons = aiCourseProgress?.done?.length || 0;
   const finishedPercentage = Math.round(
     (totalDoneLessons / totalCourseLessons) * 100,
@@ -154,12 +165,14 @@ export function AICourseContent(props: AICourseContentProps) {
 
           {isLimitReached && (
             <div className="mt-4">
-              <button
-                onClick={() => setShowUpgradeModal(true)}
-                className="rounded-md bg-yellow-400 px-6 py-2 text-sm font-medium text-black hover:bg-yellow-500"
-              >
-                Upgrade to remove Limits
-              </button>
+              {!isPaidUser && (
+                <button
+                  onClick={() => setShowUpgradeModal(true)}
+                  className="rounded-md bg-yellow-400 px-6 py-2 text-sm font-medium text-black hover:bg-yellow-500"
+                >
+                  Upgrade to remove Limits
+                </button>
+              )}
 
               <p className="mt-4 text-sm text-black">
                 <a href="/ai-tutor" className="underline underline-offset-2">
@@ -173,6 +186,8 @@ export function AICourseContent(props: AICourseContentProps) {
     );
   }
 
+  const isViewingLesson = viewMode === 'module';
+
   return (
     <section className="flex h-screen flex-grow flex-col overflow-hidden bg-gray-50">
       {modals}
@@ -181,11 +196,17 @@ export function AICourseContent(props: AICourseContentProps) {
         <div className="flex items-center justify-between px-4 py-2">
           <a
             href="/ai-tutor"
+            onClick={(e) => {
+              if (isViewingLesson) {
+                e.preventDefault();
+                setViewMode('full');
+              }
+            }}
             className="flex flex-row items-center gap-1.5 text-sm font-medium text-gray-700 hover:text-gray-900"
             aria-label="Back to generator"
           >
             <ChevronLeft className="size-4" strokeWidth={2.5} />
-            Back<span className="hidden lg:inline"> to AI Tutor</span>
+            Back {isViewingLesson ? 'to Outline' : 'to AI Tutor'}
           </a>
           <div className="flex items-center gap-2">
             <div className="flex flex-row lg:hidden">
@@ -351,7 +372,7 @@ export function AICourseContent(props: AICourseContentProps) {
             <div className="mx-auto rounded-xl border border-gray-200 bg-white shadow-sm lg:max-w-3xl">
               <div
                 className={cn(
-                  'mb-1 flex items-start justify-between border-b border-gray-100 p-6 max-lg:hidden',
+                  'relative mb-1 flex items-start justify-between border-b border-gray-100 p-6 max-lg:hidden',
                   isLoading && 'striped-loader',
                 )}
               >
@@ -363,6 +384,12 @@ export function AICourseContent(props: AICourseContentProps) {
                     {course.title ? course.difficulty : 'Please wait ..'}
                   </p>
                 </div>
+
+                {!isLoading && (
+                  <RegenerateOutline
+                    onRegenerateOutline={onRegenerateOutline}
+                  />
+                )}
               </div>
               {course.title ? (
                 <div className="flex flex-col p-6 max-lg:mt-0.5 max-lg:p-4">
diff --git a/src/components/GenerateCourse/AICourseFollowUp.tsx b/src/components/GenerateCourse/AICourseFollowUp.tsx
index 9241ccdb2..917d1d6f9 100644
--- a/src/components/GenerateCourse/AICourseFollowUp.tsx
+++ b/src/components/GenerateCourse/AICourseFollowUp.tsx
@@ -36,10 +36,7 @@ export function AICourseFollowUp(props: AICourseFollowUpProps) {
         onClick={() => setIsOpen(true)}
       >
         <BotIcon className="h-4 w-4" />
-        <span>
-          <span className="max-sm:hidden">Still confused?&nbsp;</span>
-          Ask AI some follow up questions
-        </span>
+        <span>Ask AI some follow up questions</span>
 
         <ArrowRightIcon className="ml-auto h-4 w-4 max-sm:hidden" />
       </button>
diff --git a/src/components/GenerateCourse/AICourseFollowUpPopover.tsx b/src/components/GenerateCourse/AICourseFollowUpPopover.tsx
index d1e84976f..4a03fdf1c 100644
--- a/src/components/GenerateCourse/AICourseFollowUpPopover.tsx
+++ b/src/components/GenerateCourse/AICourseFollowUpPopover.tsx
@@ -2,18 +2,18 @@ import { useQuery } from '@tanstack/react-query';
 import { BookOpen, Bot, Code, HelpCircle, LockIcon, Send } from 'lucide-react';
 import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
 import { flushSync } from 'react-dom';
+import TextareaAutosize from 'react-textarea-autosize';
 import { useOutsideClick } from '../../hooks/use-outside-click';
-import { readAICourseLessonStream } from '../../helper/read-stream';
-import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
 import { useToast } from '../../hooks/use-toast';
+import { readStream } from '../../lib/ai';
+import { cn } from '../../lib/classname';
+import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
 import {
   markdownToHtml,
   markdownToHtmlWithHighlighting,
 } from '../../lib/markdown';
-import { cn } from '../../lib/classname';
 import { getAiCourseLimitOptions } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
-import TextareaAutosize from 'react-textarea-autosize';
 
 export type AllowedAIChatRole = 'user' | 'assistant';
 export type AIChatHistoryType = {
@@ -142,7 +142,7 @@ export function AICourseFollowUpPopover(props: AICourseFollowUpPopoverProps) {
       return;
     }
 
-    await readAICourseLessonStream(reader, {
+    await readStream(reader, {
       onStream: async (content) => {
         flushSync(() => {
           setStreamedMessage(content);
diff --git a/src/components/GenerateCourse/AICourseLimit.tsx b/src/components/GenerateCourse/AICourseLimit.tsx
index db57beda3..efba35c2b 100644
--- a/src/components/GenerateCourse/AICourseLimit.tsx
+++ b/src/components/GenerateCourse/AICourseLimit.tsx
@@ -1,9 +1,9 @@
 import { useQuery } from '@tanstack/react-query';
+import { Gift, Info } from 'lucide-react';
+import { getPercentage } from '../../lib/number';
 import { getAiCourseLimitOptions } from '../../queries/ai-course';
-import { queryClient } from '../../stores/query-client';
 import { billingDetailsOptions } from '../../queries/billing';
-import { getPercentage } from '../../helper/number';
-import { Gift, Info } from 'lucide-react';
+import { queryClient } from '../../stores/query-client';
 
 type AICourseLimitProps = {
   onUpgrade: () => void;
@@ -31,19 +31,22 @@ export function AICourseLimit(props: AICourseLimitProps) {
 
   const totalPercentage = getPercentage(used, limit);
 
-  // has consumed 80% of the limit
-  const isNearLimit = used >= limit * 0.8;
-  const isPaidUser = userBillingDetails.status !== 'none';
+  // has consumed 85% of the limit
+  const isNearLimit = used >= limit * 0.85;
+  const isPaidUser = userBillingDetails.status === 'active';
 
   return (
     <>
-      <button
-        className="mr-1 flex items-center gap-1 text-sm font-medium underline underline-offset-2 lg:hidden"
-        onClick={() => onShowLimits()}
-      >
-        <Info className="size-4" />
-        {totalPercentage}% limit used
-      </button>
+      {!isPaidUser ||
+        (isNearLimit && (
+          <button
+            className="mr-1 flex items-center gap-1 text-sm font-medium underline underline-offset-2 lg:hidden"
+            onClick={() => onShowLimits()}
+          >
+            <Info className="size-4" />
+            {totalPercentage}% limit used
+          </button>
+        ))}
 
       {(!isPaidUser || isNearLimit) && (
         <button
diff --git a/src/components/GenerateCourse/AICourseModuleView.tsx b/src/components/GenerateCourse/AICourseModuleView.tsx
index 34e07a91b..975ffa293 100644
--- a/src/components/GenerateCourse/AICourseModuleView.tsx
+++ b/src/components/GenerateCourse/AICourseModuleView.tsx
@@ -8,7 +8,7 @@ import {
   XIcon,
 } from 'lucide-react';
 import { useEffect, useMemo, useState } from 'react';
-import { readAICourseLessonStream } from '../../helper/read-stream';
+import { readStream } from '../../lib/ai';
 import { cn } from '../../lib/classname';
 import { isLoggedIn, removeAuthToken } from '../../lib/jwt';
 import {
@@ -25,6 +25,7 @@ import {
 import { queryClient } from '../../stores/query-client';
 import { AICourseFollowUp } from './AICourseFollowUp';
 import './AICourseFollowUp.css';
+import { useIsPaidUser } from '../../queries/billing';
 
 type AICourseModuleViewProps = {
   courseSlug: string;
@@ -72,6 +73,8 @@ export function AICourseModuleView(props: AICourseModuleViewProps) {
   const lessonId = `${slugify(currentModuleTitle)}__${slugify(currentLessonTitle)}`;
   const isLessonDone = aiCourseProgress?.done.includes(lessonId);
 
+  const { isPaidUser } = useIsPaidUser();
+
   const abortController = useMemo(
     () => new AbortController(),
     [activeModuleIndex, activeLessonIndex],
@@ -124,36 +127,41 @@ export function AICourseModuleView(props: AICourseModuleViewProps) {
         removeAuthToken();
         window.location.reload();
       }
+      return;
     }
 
-    const reader = response.body?.getReader();
-
-    if (!reader) {
+    if (!response.body) {
       setIsLoading(false);
-      setError('Something went wrong');
+      setError('No response body received');
       return;
     }
 
-    setIsLoading(false);
-    setIsGenerating(true);
-    await readAICourseLessonStream(reader, {
-      onStream: async (result) => {
-        if (abortController.signal.aborted) {
-          return;
-        }
-
-        setLessonHtml(markdownToHtml(result, false));
-      },
-      onStreamEnd: async (result) => {
-        if (abortController.signal.aborted) {
-          return;
-        }
-
-        setLessonHtml(await markdownToHtmlWithHighlighting(result));
-        queryClient.invalidateQueries(getAiCourseLimitOptions());
-        setIsGenerating(false);
-      },
-    });
+    try {
+      const reader = response.body.getReader();
+      setIsLoading(false);
+      setIsGenerating(true);
+      await readStream(reader, {
+        onStream: async (result) => {
+          if (abortController.signal.aborted) {
+            return;
+          }
+
+          setLessonHtml(markdownToHtml(result, false));
+        },
+        onStreamEnd: async (result) => {
+          if (abortController.signal.aborted) {
+            return;
+          }
+
+          setLessonHtml(await markdownToHtmlWithHighlighting(result));
+          queryClient.invalidateQueries(getAiCourseLimitOptions());
+          setIsGenerating(false);
+        },
+      });
+    } catch (e) {
+      setError(e instanceof Error ? e.message : 'Something went wrong');
+      setIsLoading(false);
+    }
   };
 
   const { mutate: toggleDone, isPending: isTogglingDone } = useMutation(
@@ -273,18 +281,21 @@ export function AICourseModuleView(props: AICourseModuleViewProps) {
                   Limit reached
                 </h2>
                 <p className="my-3 text-red-600">
-                  You have reached the AI usage limit for today. Please upgrade
-                  your account to continue.
+                  You have reached the AI usage limit for today.
+                  {!isPaidUser && <>Please upgrade your account to continue.</>}
+                  {isPaidUser && <>Please wait until tomorrow to continue.</>}
                 </p>
 
-                <button
-                  onClick={() => {
-                    onUpgrade();
-                  }}
-                  className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700"
-                >
-                  Upgrade Account
-                </button>
+                {!isPaidUser && (
+                  <button
+                    onClick={() => {
+                      onUpgrade();
+                    }}
+                    className="rounded-full bg-red-600 px-4 py-1 text-white hover:bg-red-700"
+                  >
+                    Upgrade Account
+                  </button>
+                )}
               </div>
             ) : (
               <p className="text-red-600">{error}</p>
diff --git a/src/components/GenerateCourse/AILimitsPopup.tsx b/src/components/GenerateCourse/AILimitsPopup.tsx
index 7c87c6009..79244940c 100644
--- a/src/components/GenerateCourse/AILimitsPopup.tsx
+++ b/src/components/GenerateCourse/AILimitsPopup.tsx
@@ -24,7 +24,7 @@ export function AILimitsPopup(props: AILimitsPopupProps) {
   const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
     useQuery(billingDetailsOptions(), queryClient);
 
-  const isPaidUser = userBillingDetails?.status !== 'none';
+  const isPaidUser = userBillingDetails?.status === 'active';
 
   return (
     <Modal
diff --git a/src/components/GenerateCourse/GenerateAICourse.tsx b/src/components/GenerateCourse/GenerateAICourse.tsx
index 5822a7855..7a054a3cb 100644
--- a/src/components/GenerateCourse/GenerateAICourse.tsx
+++ b/src/components/GenerateCourse/GenerateAICourse.tsx
@@ -1,11 +1,9 @@
 import { useEffect, useState } from 'react';
 import { getUrlParams } from '../../lib/browser';
 import { isLoggedIn } from '../../lib/jwt';
-import { generateAiCourseStructure, type AiCourse } from '../../lib/ai';
-import { readAICourseStream } from '../../helper/read-stream';
+import { type AiCourse } from '../../lib/ai';
 import { AICourseContent } from './AICourseContent';
-import { queryClient } from '../../stores/query-client';
-import { getAiCourseLimitOptions } from '../../queries/ai-course';
+import { generateCourse } from '../../helper/generate-ai-course';
 
 type GenerateAICourseProps = {};
 
@@ -38,119 +36,34 @@ export function GenerateAICourse(props: GenerateAICourseProps) {
 
     setTerm(paramsTerm);
     setDifficulty(paramsDifficulty);
-    generateCourse({ term: paramsTerm, difficulty: paramsDifficulty });
+    handleGenerateCourse({ term: paramsTerm, difficulty: paramsDifficulty });
   }, [term, difficulty]);
 
-  const generateCourse = async (options: {
+  const handleGenerateCourse = async (options: {
     term: string;
     difficulty: string;
+    isForce?: boolean;
+    prompt?: string;
   }) => {
-    const { term, difficulty } = options;
+    const { term, difficulty, isForce, prompt } = options;
 
     if (!isLoggedIn()) {
       window.location.href = '/ai-tutor';
       return;
     }
 
-    setIsLoading(true);
-    setCourse({
-      title: '',
-      modules: [],
-      difficulty: '',
+    await generateCourse({
+      term,
+      difficulty,
+      slug: courseSlug,
+      onCourseIdChange: setCourseId,
+      onCourseSlugChange: setCourseSlug,
+      onCourseChange: setCourse,
+      onLoadingChange: setIsLoading,
+      onError: setError,
+      isForce,
+      prompt,
     });
-    setError('');
-
-    try {
-      const response = await fetch(
-        `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course`,
-        {
-          method: 'POST',
-          headers: {
-            'Content-Type': 'application/json',
-          },
-          body: JSON.stringify({
-            keyword: term,
-            difficulty,
-          }),
-          credentials: 'include',
-        },
-      );
-
-      if (!response.ok) {
-        const data = await response.json();
-        console.error(
-          'Error generating course:',
-          data?.message || 'Something went wrong',
-        );
-        setIsLoading(false);
-        setError(data?.message || 'Something went wrong');
-        return;
-      }
-
-      const reader = response.body?.getReader();
-
-      if (!reader) {
-        console.error('Failed to get reader from response');
-        setError('Something went wrong');
-        setIsLoading(false);
-        return;
-      }
-
-      const COURSE_ID_REGEX = new RegExp('@COURSEID:(\\w+)@');
-      const COURSE_SLUG_REGEX = new RegExp(/@COURSESLUG:([\w-]+)@/);
-
-      await readAICourseStream(reader, {
-        onStream: (result) => {
-          if (result.includes('@COURSEID') || result.includes('@COURSESLUG')) {
-            const courseIdMatch = result.match(COURSE_ID_REGEX);
-            const courseSlugMatch = result.match(COURSE_SLUG_REGEX);
-            const extractedCourseId = courseIdMatch?.[1] || '';
-            const extractedCourseSlug = courseSlugMatch?.[1] || '';
-
-            if (extractedCourseSlug) {
-              window.history.replaceState(
-                {
-                  courseId,
-                  courseSlug: extractedCourseSlug,
-                  term,
-                  difficulty,
-                },
-                '',
-                `${origin}/ai-tutor/${extractedCourseSlug}`,
-              );
-            }
-
-            result = result
-              .replace(COURSE_ID_REGEX, '')
-              .replace(COURSE_SLUG_REGEX, '');
-
-            setCourseId(extractedCourseId);
-            setCourseSlug(extractedCourseSlug);
-          }
-
-          try {
-            const aiCourse = generateAiCourseStructure(result);
-            setCourse({
-              ...aiCourse,
-              difficulty: difficulty || '',
-            });
-          } catch (e) {
-            console.error('Error parsing streamed course content:', e);
-          }
-        },
-        onStreamEnd: (result) => {
-          result = result
-            .replace(COURSE_ID_REGEX, '')
-            .replace(COURSE_SLUG_REGEX, '');
-          setIsLoading(false);
-          queryClient.invalidateQueries(getAiCourseLimitOptions());
-        },
-      });
-    } catch (error: any) {
-      setError(error?.message || 'Something went wrong');
-      console.error('Error in course generation:', error);
-      setIsLoading(false);
-    }
   };
 
   useEffect(() => {
@@ -167,7 +80,7 @@ export function GenerateAICourse(props: GenerateAICourseProps) {
       setDifficulty(difficulty);
 
       setIsLoading(true);
-      generateCourse({ term, difficulty }).finally(() => {
+      handleGenerateCourse({ term, difficulty }).finally(() => {
         setIsLoading(false);
       });
     };
@@ -184,6 +97,14 @@ export function GenerateAICourse(props: GenerateAICourseProps) {
       course={course}
       isLoading={isLoading}
       error={error}
+      onRegenerateOutline={(prompt) => {
+        handleGenerateCourse({
+          term,
+          difficulty,
+          isForce: true,
+          prompt,
+        });
+      }}
     />
   );
 }
diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx
index d26f613c8..b3970d3fd 100644
--- a/src/components/GenerateCourse/GetAICourse.tsx
+++ b/src/components/GenerateCourse/GetAICourse.tsx
@@ -1,10 +1,14 @@
 import { useQuery } from '@tanstack/react-query';
-import { getAiCourseOptions } from '../../queries/ai-course';
+import {
+  getAiCourseOptions,
+  getAiCourseProgressOptions,
+} from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
 import { useEffect, useState } from 'react';
 import { AICourseContent } from './AICourseContent';
 import { generateAiCourseStructure } from '../../lib/ai';
 import { isLoggedIn } from '../../lib/jwt';
+import { generateCourse } from '../../helper/generate-ai-course';
 
 type GetAICourseProps = {
   courseSlug: string;
@@ -14,7 +18,10 @@ export function GetAICourse(props: GetAICourseProps) {
   const { courseSlug } = props;
 
   const [isLoading, setIsLoading] = useState(true);
-  const { data: aiCourse, error } = useQuery(
+  const [isRegenerating, setIsRegenerating] = useState(false);
+
+  const [error, setError] = useState('');
+  const { data: aiCourse, error: queryError } = useQuery(
     {
       ...getAiCourseOptions({ aiCourseSlug: courseSlug }),
       select: (data) => {
@@ -43,12 +50,49 @@ export function GetAICourse(props: GetAICourseProps) {
   }, [aiCourse]);
 
   useEffect(() => {
-    if (!error) {
+    if (!queryError) {
       return;
     }
 
     setIsLoading(false);
-  }, [error]);
+    setError(queryError.message);
+  }, [queryError]);
+
+  const handleRegenerateCourse = async (prompt?: string) => {
+    if (!aiCourse) {
+      return;
+    }
+
+    await generateCourse({
+      term: aiCourse.keyword,
+      difficulty: aiCourse.difficulty,
+      slug: courseSlug,
+      prompt,
+      onCourseChange: (course, rawData) => {
+        queryClient.setQueryData(
+          getAiCourseOptions({ aiCourseSlug: courseSlug }).queryKey,
+          {
+            ...aiCourse,
+            title: course.title,
+            difficulty: course.difficulty,
+            data: rawData,
+          },
+        );
+      },
+      onLoadingChange: (isNewLoading) => {
+        setIsRegenerating(isNewLoading);
+        if (!isNewLoading) {
+          queryClient.invalidateQueries({
+            queryKey: getAiCourseProgressOptions({
+              aiCourseSlug: courseSlug,
+            }).queryKey,
+          });
+        }
+      },
+      onError: setError,
+      isForce: true,
+    });
+  };
 
   return (
     <AICourseContent
@@ -57,9 +101,10 @@ export function GetAICourse(props: GetAICourseProps) {
         modules: aiCourse?.course.modules || [],
         difficulty: aiCourse?.difficulty || 'Easy',
       }}
-      isLoading={isLoading}
+      isLoading={isLoading || isRegenerating}
       courseSlug={courseSlug}
-      error={error?.message}
+      error={error}
+      onRegenerateOutline={handleRegenerateCourse}
     />
   );
 }
diff --git a/src/components/GenerateCourse/ModifyCoursePrompt.tsx b/src/components/GenerateCourse/ModifyCoursePrompt.tsx
new file mode 100644
index 000000000..35583635d
--- /dev/null
+++ b/src/components/GenerateCourse/ModifyCoursePrompt.tsx
@@ -0,0 +1,69 @@
+import { useState } from 'react';
+import { Modal } from '../Modal';
+
+export type ModifyCoursePromptProps = {
+  onClose: () => void;
+  onSubmit: (prompt: string) => void;
+};
+
+export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
+  const { onClose, onSubmit } = props;
+
+  const [prompt, setPrompt] = useState('');
+
+  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
+    e.preventDefault();
+    onSubmit(prompt);
+  };
+
+  return (
+    <Modal
+      onClose={onClose}
+      wrapperClassName="rounded-xl max-w-xl w-full h-auto"
+      bodyClassName="p-6"
+      overlayClassName="items-start md:items-center"
+    >
+      <div className="flex flex-col gap-4">
+        <div>
+          <h2 className="mb-2 text-left text-xl font-semibold">
+            Give AI more context
+          </h2>
+          <p className="text-sm text-gray-500">
+            Pass additional information to the AI to generate a course outline.
+          </p>
+        </div>
+        <form className="flex flex-col gap-2" onSubmit={handleSubmit}>
+          <textarea
+            id="prompt"
+            autoFocus
+            rows={3}
+            value={prompt}
+            onChange={(e) => setPrompt(e.target.value)}
+            className="w-full rounded-md border border-gray-200 p-2 placeholder:text-sm focus:outline-black"
+            placeholder="e.g. make sure to add a section on React hooks"
+          />
+
+          <p className="text-sm text-gray-500">
+            Complete the sentence: "I want AI to..."
+          </p>
+
+          <div className="flex justify-end gap-2">
+            <button
+              className="rounded-md bg-gray-200 px-4 py-2.5 text-sm text-black hover:opacity-80"
+              onClick={onClose}
+            >
+              Cancel
+            </button>
+            <button
+              type="submit"
+              disabled={!prompt.trim()}
+              className="rounded-md bg-black px-4 py-2.5 text-sm text-white hover:opacity-80 disabled:opacity-50"
+            >
+              Modify Prompt
+            </button>
+          </div>
+        </form>
+      </div>
+    </Modal>
+  );
+}
diff --git a/src/components/GenerateCourse/RegenerateOutline.tsx b/src/components/GenerateCourse/RegenerateOutline.tsx
new file mode 100644
index 000000000..9aa4cb894
--- /dev/null
+++ b/src/components/GenerateCourse/RegenerateOutline.tsx
@@ -0,0 +1,98 @@
+import { PenSquare, RefreshCcw } from 'lucide-react';
+import { useRef, useState } from 'react';
+import { useOutsideClick } from '../../hooks/use-outside-click';
+import { cn } from '../../lib/classname';
+import { useIsPaidUser } from '../../queries/billing';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { ModifyCoursePrompt } from './ModifyCoursePrompt';
+
+type RegenerateOutlineProps = {
+  onRegenerateOutline: (prompt?: string) => void;
+};
+
+export function RegenerateOutline(props: RegenerateOutlineProps) {
+  const { onRegenerateOutline } = props;
+
+  const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
+  const [showPromptModal, setShowPromptModal] = useState(false);
+
+  const ref = useRef<HTMLDivElement>(null);
+
+  const { isPaidUser } = useIsPaidUser();
+
+  useOutsideClick(ref, () => setIsDropdownVisible(false));
+
+  return (
+    <>
+      {showUpgradeModal && (
+        <UpgradeAccountModal
+          onClose={() => {
+            setShowUpgradeModal(false);
+          }}
+        />
+      )}
+
+      {showPromptModal && (
+        <ModifyCoursePrompt
+          onClose={() => setShowPromptModal(false)}
+          onSubmit={(prompt) => {
+            setShowPromptModal(false);
+            onRegenerateOutline(prompt);
+          }}
+        />
+      )}
+
+      <div className="absolute right-3 top-3" ref={ref}>
+        <button
+          className={cn('text-gray-400 hover:text-black', {
+            'text-black': isDropdownVisible,
+          })}
+          onClick={() => setIsDropdownVisible(!isDropdownVisible)}
+        >
+          <PenSquare className="text-current" size={16} strokeWidth={2.5} />
+        </button>
+        {isDropdownVisible && (
+          <div className="absolute right-0 top-full min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
+            <button
+              onClick={() => {
+                if (!isPaidUser) {
+                  setIsDropdownVisible(false);
+                  setShowUpgradeModal(true);
+                } else {
+                  onRegenerateOutline();
+                }
+              }}
+              className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
+            >
+              <RefreshCcw
+                size={16}
+                className="text-gray-400"
+                strokeWidth={2.5}
+              />
+              Regenerate
+            </button>
+            <button
+              onClick={() => {
+                setIsDropdownVisible(false);
+                if (!isPaidUser) {
+                  setShowUpgradeModal(true);
+                } else {
+                  setShowPromptModal(true);
+                }
+              }}
+              className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
+            >
+              <PenSquare
+                size={16}
+                className="text-gray-400"
+                strokeWidth={2.5}
+              />
+              Modify Prompt
+            </button>
+          </div>
+        )}
+      </div>
+    </>
+  );
+}
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index e6e10a50d..92ecab6f7 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -10,7 +10,7 @@ import { Gift, Loader2, Search, User2 } from 'lucide-react';
 import { isLoggedIn } from '../../lib/jwt';
 import { showLoginPopup } from '../../lib/popup';
 import { cn } from '../../lib/classname';
-import { billingDetailsOptions } from '../../queries/billing';
+import { useIsPaidUser } from '../../queries/billing';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 
 type UserCoursesListProps = {};
@@ -20,17 +20,13 @@ export function UserCoursesList(props: UserCoursesListProps) {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
   const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
-  const { data: limits, isLoading } = useQuery(
+  const { data: limits, isLoading: isLimitsLoading } = useQuery(
     getAiCourseLimitOptions(),
     queryClient,
   );
 
   const { used, limit } = limits ?? { used: 0, limit: 0 };
-
-  const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
-    useQuery(billingDetailsOptions(), queryClient);
-
-  const isPaidUser = userBillingDetails?.status !== 'none';
+  const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
 
   const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
     listUserAiCoursesOptions(),
@@ -55,13 +51,6 @@ export function UserCoursesList(props: UserCoursesListProps) {
   });
 
   const isAuthenticated = isLoggedIn();
-
-  const canSearch =
-    !isInitialLoading &&
-    !isUserAiCoursesLoading &&
-    isAuthenticated &&
-    userAiCourses?.length !== 0;
-
   const limitUsedPercentage = Math.round((used / limit) * 100);
 
   return (
@@ -72,37 +61,39 @@ export function UserCoursesList(props: UserCoursesListProps) {
       <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
         <div className="flex items-center gap-2">
           <h2 className="text-lg font-semibold">
-            <span className='max-md:hidden'>Your </span>Courses
+            <span className="max-md:hidden">Your </span>Courses
           </h2>
         </div>
 
         <div className="flex items-center gap-2">
-          <div
-            className={cn(
-              'flex items-center gap-2 opacity-0 transition-opacity',
-              {
-                'opacity-100': !isPaidUser,
-              },
-            )}
-          >
-            <p className="flex items-center text-sm text-yellow-600">
-              <span className="max-md:hidden">
-                {limitUsedPercentage}% of daily limit used{' '}
-              </span>
-              <span className="inline md:hidden">
-                {limitUsedPercentage}% used
-              </span>
-              <button
-                onClick={() => {
-                  setShowUpgradePopup(true);
-                }}
-                className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pl-1.5 pr-2 text-xs text-white"
-              >
-                <Gift className="size-4" />
-                Upgrade
-              </button>
-            </p>
-          </div>
+          {used > 0 && limit > 0 && !isPaidUserLoading && (
+            <div
+              className={cn(
+                'flex items-center gap-2 opacity-0 transition-opacity',
+                {
+                  'opacity-100': !isPaidUser,
+                },
+              )}
+            >
+              <p className="flex items-center text-sm text-yellow-600">
+                <span className="max-md:hidden">
+                  {limitUsedPercentage}% of daily limit used{' '}
+                </span>
+                <span className="inline md:hidden">
+                  {limitUsedPercentage}% used
+                </span>
+                <button
+                  onClick={() => {
+                    setShowUpgradePopup(true);
+                  }}
+                  className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pl-1.5 pr-2 text-xs text-white"
+                >
+                  <Gift className="size-4" />
+                  Upgrade
+                </button>
+              </p>
+            </div>
+          )}
 
           <div className={cn('relative w-64 max-sm:hidden', {})}>
             <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx
index ccb2cf16f..945fc2a35 100644
--- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx
+++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx
@@ -11,7 +11,6 @@ import { useToast } from '../../hooks/use-toast';
 import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
 import { renderFlowJSON } from '../../../editor/renderer/renderer';
 import { replaceChildren } from '../../lib/dom';
-import { readAIRoadmapStream } from '../../helper/read-stream';
 import {
   getOpenAIKey,
   isLoggedIn,
@@ -31,7 +30,7 @@ import { showLoginPopup } from '../../lib/popup.ts';
 import { cn } from '../../lib/classname.ts';
 import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx';
 import { AIRoadmapAlert } from './AIRoadmapAlert.tsx';
-import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts';
+import { IS_KEY_ONLY_ROADMAP_GENERATION, readAIRoadmapStream } from '../../lib/ai.ts';
 import { AITermSuggestionInput } from './AITermSuggestionInput.tsx';
 import { IncreaseRoadmapLimit } from './IncreaseRoadmapLimit.tsx';
 import { AuthenticationForm } from '../AuthenticationFlow/AuthenticationForm.tsx';
diff --git a/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx b/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx
index 560d9e940..33938800a 100644
--- a/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx
+++ b/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx
@@ -3,13 +3,13 @@ import { useEffect, useMemo, useRef, useState } from 'react';
 import { useKeydown } from '../../hooks/use-keydown';
 import { useOutsideClick } from '../../hooks/use-outside-click';
 import { markdownToHtml } from '../../lib/markdown';
-import { Ban, Cog, Contact, FileText, User, UserRound, X } from 'lucide-react';
+import { Ban, Cog, Contact, FileText, X } from 'lucide-react';
 import { Spinner } from '../ReactIcons/Spinner';
 import type { RoadmapNodeDetails } from './GenerateRoadmap';
 import { getOpenAIKey, isLoggedIn, removeAuthToken } from '../../lib/jwt';
-import { readAIRoadmapContentStream } from '../../helper/read-stream';
 import { cn } from '../../lib/classname';
 import { showLoginPopup } from '../../lib/popup';
+import { readAIRoadmapContentStream } from '../../lib/ai';
 
 type RoadmapTopicDetailProps = RoadmapNodeDetails & {
   onClose?: () => void;
diff --git a/src/components/Navigation/Navigation.astro b/src/components/Navigation/Navigation.astro
index 1b87eefd4..bebe897f9 100644
--- a/src/components/Navigation/Navigation.astro
+++ b/src/components/Navigation/Navigation.astro
@@ -49,7 +49,10 @@ import { CourseAnnouncement } from '../SQLCourse/CourseAnnouncement';
             </span>
           </span>
         </a>
-        <a href='/teams' class='group hidden xl:block relative text-gray-400 hover:text-white'>
+        <a
+          href='/teams'
+          class='group relative hidden text-gray-400 hover:text-white xl:block'
+        >
           Teams
         </a>
       </div>
diff --git a/src/components/UserPublicProfile/UserProfileRoadmap.tsx b/src/components/UserPublicProfile/UserProfileRoadmap.tsx
index 0d42ba79d..5dfb69ab4 100644
--- a/src/components/UserPublicProfile/UserProfileRoadmap.tsx
+++ b/src/components/UserPublicProfile/UserProfileRoadmap.tsx
@@ -2,7 +2,7 @@ import type {
   GetUserProfileRoadmapResponse,
   GetPublicProfileResponse,
 } from '../../api/user';
-import { getPercentage } from '../../helper/number';
+import { getPercentage } from '../../lib/number';
 import { PrivateProfileBanner } from './PrivateProfileBanner';
 import { UserProfileRoadmapRenderer } from './UserProfileRoadmapRenderer';
 
diff --git a/src/components/UserPublicProfile/UserPublicProgressStats.tsx b/src/components/UserPublicProfile/UserPublicProgressStats.tsx
index 9b8fc85f5..c9eef634c 100644
--- a/src/components/UserPublicProfile/UserPublicProgressStats.tsx
+++ b/src/components/UserPublicProfile/UserPublicProgressStats.tsx
@@ -1,5 +1,5 @@
-import { getPercentage } from '../../helper/number';
 import { getRelativeTimeString } from '../../lib/date';
+import { getPercentage } from '../../lib/number';
 
 type UserPublicProgressStats = {
   resourceType: 'roadmap';
diff --git a/src/components/UserPublicProfile/UserPublicProgresses.tsx b/src/components/UserPublicProfile/UserPublicProgresses.tsx
index 1eac8e296..cb5fb2fb1 100644
--- a/src/components/UserPublicProfile/UserPublicProgresses.tsx
+++ b/src/components/UserPublicProfile/UserPublicProgresses.tsx
@@ -1,6 +1,5 @@
 import type { GetPublicProfileResponse } from '../../api/user';
-import { UserPublicProgressStats } from './UserPublicProgressStats';
-import { getPercentage } from '../../helper/number.ts';
+import { getPercentage } from '../../lib/number';
 
 type UserPublicProgressesProps = {
   userId: string;
@@ -73,15 +72,15 @@ export function UserPublicProgresses(props: UserPublicProgressesProps) {
                   target="_blank"
                   key={roadmap.id + counter}
                   href={`/${roadmap.id}?s=${userId}`}
-                  className="relative group border-gray-300 flex items-center justify-between rounded-md border bg-white px-3 py-2 text-left text-sm transition-all hover:border-gray-400 overflow-hidden"
+                  className="group relative flex items-center justify-between overflow-hidden rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm transition-all hover:border-gray-400"
                 >
                   <span className="flex-grow truncate">{roadmap.title}</span>
                   <span className="text-xs text-gray-400">
-                    {parseInt(percentageDone, 10)}%
+                    {percentageDone}%
                   </span>
 
                   <span
-                    className="absolute transition-colors left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 group-hover:bg-black/10"
+                    className="absolute left-0 top-0 block h-full cursor-pointer rounded-tl-md bg-black/5 transition-colors group-hover:bg-black/10"
                     style={{
                       width: `${percentageDone}%`,
                     }}
diff --git a/src/helper/generate-ai-course.ts b/src/helper/generate-ai-course.ts
new file mode 100644
index 000000000..9aa03ad56
--- /dev/null
+++ b/src/helper/generate-ai-course.ts
@@ -0,0 +1,162 @@
+import {
+  generateAiCourseStructure,
+  readStream,
+  type AiCourse,
+} from '../lib/ai';
+import { queryClient } from '../stores/query-client';
+import { getAiCourseLimitOptions } from '../queries/ai-course';
+
+type GenerateCourseOptions = {
+  term: string;
+  difficulty: string;
+  slug?: string;
+  isForce?: boolean;
+  prompt?: string;
+  onCourseIdChange?: (courseId: string) => void;
+  onCourseSlugChange?: (courseSlug: string) => void;
+  onCourseChange?: (course: AiCourse, rawData: string) => void;
+  onLoadingChange?: (isLoading: boolean) => void;
+  onError?: (error: string) => void;
+};
+
+export async function generateCourse(options: GenerateCourseOptions) {
+  const {
+    term,
+    slug,
+    difficulty,
+    onCourseIdChange,
+    onCourseSlugChange,
+    onCourseChange,
+    onLoadingChange,
+    onError,
+    isForce = false,
+    prompt,
+  } = options;
+
+  onLoadingChange?.(true);
+  onCourseChange?.(
+    {
+      title: '',
+      modules: [],
+      difficulty: '',
+    },
+    '',
+  );
+  onError?.('');
+
+  try {
+    let response = null;
+
+    if (slug && isForce) {
+      response = await fetch(
+        `${import.meta.env.PUBLIC_API_URL}/v1-regenerate-ai-course/${slug}`,
+        {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          credentials: 'include',
+          body: JSON.stringify({
+            isForce,
+            customPrompt: prompt,
+          }),
+        },
+      );
+    } else {
+      response = await fetch(
+        `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course`,
+        {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({
+            keyword: term,
+            difficulty,
+            isForce,
+            customPrompt: prompt,
+          }),
+          credentials: 'include',
+        },
+      );
+    }
+
+    if (!response.ok) {
+      const data = await response.json();
+      console.error(
+        'Error generating course:',
+        data?.message || 'Something went wrong',
+      );
+      onLoadingChange?.(false);
+      onError?.(data?.message || 'Something went wrong');
+      return;
+    }
+
+    const reader = response.body?.getReader();
+
+    if (!reader) {
+      console.error('Failed to get reader from response');
+      onError?.('Something went wrong');
+      onLoadingChange?.(false);
+      return;
+    }
+
+    const COURSE_ID_REGEX = new RegExp('@COURSEID:(\\w+)@');
+    const COURSE_SLUG_REGEX = new RegExp(/@COURSESLUG:([\w-]+)@/);
+
+    await readStream(reader, {
+      onStream: (result) => {
+        if (result.includes('@COURSEID') || result.includes('@COURSESLUG')) {
+          const courseIdMatch = result.match(COURSE_ID_REGEX);
+          const courseSlugMatch = result.match(COURSE_SLUG_REGEX);
+          const extractedCourseId = courseIdMatch?.[1] || '';
+          const extractedCourseSlug = courseSlugMatch?.[1] || '';
+
+          if (extractedCourseSlug) {
+            window.history.replaceState(
+              {
+                courseId: extractedCourseId,
+                courseSlug: extractedCourseSlug,
+                term,
+                difficulty,
+              },
+              '',
+              `${origin}/ai-tutor/${extractedCourseSlug}`,
+            );
+          }
+
+          result = result
+            .replace(COURSE_ID_REGEX, '')
+            .replace(COURSE_SLUG_REGEX, '');
+
+          onCourseIdChange?.(extractedCourseId);
+          onCourseSlugChange?.(extractedCourseSlug);
+        }
+
+        try {
+          const aiCourse = generateAiCourseStructure(result);
+          onCourseChange?.(
+            {
+              ...aiCourse,
+              difficulty: difficulty || '',
+            },
+            result,
+          );
+        } catch (e) {
+          console.error('Error parsing streamed course content:', e);
+        }
+      },
+      onStreamEnd: (result) => {
+        result = result
+          .replace(COURSE_ID_REGEX, '')
+          .replace(COURSE_SLUG_REGEX, '');
+        onLoadingChange?.(false);
+        queryClient.invalidateQueries(getAiCourseLimitOptions());
+      },
+    });
+  } catch (error: any) {
+    onError?.(error?.message || 'Something went wrong');
+    console.error('Error in course generation:', error);
+    onLoadingChange?.(false);
+  }
+}
diff --git a/src/helper/number.ts b/src/helper/number.ts
deleted file mode 100644
index 5e1309319..000000000
--- a/src/helper/number.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export function getPercentage(portion: number, total: number): number {
-  if (portion <= 0 || total <= 0) {
-    return 0;
-  }
-
-  if (portion >= total) {
-    return 100;
-  }
-
-  const percentage = (portion / total) * 100;
-  return Math.round(percentage);
-}
diff --git a/src/helper/read-stream.ts b/src/helper/read-stream.ts
deleted file mode 100644
index 2d65de068..000000000
--- a/src/helper/read-stream.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-const NEW_LINE = '\n'.charCodeAt(0);
-
-export async function readAIRoadmapStream(
-  reader: ReadableStreamDefaultReader<Uint8Array>,
-  {
-    onStream,
-    onStreamEnd,
-  }: {
-    onStream?: (roadmap: string) => void;
-    onStreamEnd?: (roadmap: string) => void;
-  },
-) {
-  const decoder = new TextDecoder('utf-8');
-  let result = '';
-
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) {
-      break;
-    }
-
-    // We will call the renderRoadmap callback whenever we encounter
-    // a new line with the result until the new line
-    // otherwise, we will keep appending the result to the previous result
-    if (value) {
-      let start = 0;
-      for (let i = 0; i < value.length; i++) {
-        if (value[i] === NEW_LINE) {
-          result += decoder.decode(value.slice(start, i + 1));
-          onStream?.(result);
-          start = i + 1;
-        }
-      }
-      if (start < value.length) {
-        result += decoder.decode(value.slice(start));
-      }
-    }
-  }
-
-  onStream?.(result);
-  onStreamEnd?.(result);
-  reader.releaseLock();
-}
-
-export async function readAIRoadmapContentStream(
-  reader: ReadableStreamDefaultReader<Uint8Array>,
-  {
-    onStream,
-    onStreamEnd,
-  }: {
-    onStream?: (roadmap: string) => void;
-    onStreamEnd?: (roadmap: string) => void;
-  },
-) {
-  const decoder = new TextDecoder('utf-8');
-  let result = '';
-
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) {
-      break;
-    }
-
-    if (value) {
-      result += decoder.decode(value);
-      onStream?.(result);
-    }
-  }
-
-  onStream?.(result);
-  onStreamEnd?.(result);
-  reader.releaseLock();
-}
-
-export async function readAICourseStream(
-  reader: ReadableStreamDefaultReader<Uint8Array>,
-  {
-    onStream,
-    onStreamEnd,
-  }: {
-    onStream?: (course: string) => void;
-    onStreamEnd?: (course: string) => void;
-  },
-) {
-  const decoder = new TextDecoder('utf-8');
-  let result = '';
-
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) {
-      break;
-    }
-
-    // Process the stream data as it comes in
-    if (value) {
-      let start = 0;
-      for (let i = 0; i < value.length; i++) {
-        if (value[i] === NEW_LINE) {
-          result += decoder.decode(value.slice(start, i + 1));
-          onStream?.(result);
-          start = i + 1;
-        }
-      }
-      if (start < value.length) {
-        result += decoder.decode(value.slice(start));
-      }
-    }
-  }
-
-  onStream?.(result);
-  onStreamEnd?.(result);
-  reader.releaseLock();
-}
-
-export async function readAICourseLessonStream(
-  reader: ReadableStreamDefaultReader<Uint8Array>,
-  {
-    onStream,
-    onStreamEnd,
-  }: {
-    onStream?: (lesson: string) => void;
-    onStreamEnd?: (lesson: string) => void;
-  },
-) {
-  const decoder = new TextDecoder('utf-8');
-  let result = '';
-
-  while (true) {
-    const { value, done } = await reader.read();
-    if (done) {
-      break;
-    }
-
-    result += decoder.decode(value);
-    onStream?.(result);
-  }
-
-  onStream?.(result);
-  onStreamEnd?.(result);
-  reader.releaseLock();
-}
diff --git a/src/lib/ai.ts b/src/lib/ai.ts
index 75307d36f..b7cec3f57 100644
--- a/src/lib/ai.ts
+++ b/src/lib/ai.ts
@@ -53,3 +53,117 @@ export function generateAiCourseStructure(
     modules,
   };
 }
+
+const NEW_LINE = '\n'.charCodeAt(0);
+
+export async function readAIRoadmapStream(
+  reader: ReadableStreamDefaultReader<Uint8Array>,
+  {
+    onStream,
+    onStreamEnd,
+  }: {
+    onStream?: (roadmap: string) => void;
+    onStreamEnd?: (roadmap: string) => void;
+  },
+) {
+  const decoder = new TextDecoder('utf-8');
+  let result = '';
+
+  while (true) {
+    const { value, done } = await reader.read();
+    if (done) {
+      break;
+    }
+
+    // We will call the renderRoadmap callback whenever we encounter
+    // a new line with the result until the new line
+    // otherwise, we will keep appending the result to the previous result
+    if (value) {
+      let start = 0;
+      for (let i = 0; i < value.length; i++) {
+        if (value[i] === NEW_LINE) {
+          result += decoder.decode(value.slice(start, i + 1));
+          onStream?.(result);
+          start = i + 1;
+        }
+      }
+      if (start < value.length) {
+        result += decoder.decode(value.slice(start));
+      }
+    }
+  }
+
+  onStream?.(result);
+  onStreamEnd?.(result);
+  reader.releaseLock();
+}
+
+export async function readAIRoadmapContentStream(
+  reader: ReadableStreamDefaultReader<Uint8Array>,
+  {
+    onStream,
+    onStreamEnd,
+  }: {
+    onStream?: (roadmap: string) => void;
+    onStreamEnd?: (roadmap: string) => void;
+  },
+) {
+  const decoder = new TextDecoder('utf-8');
+  let result = '';
+
+  while (true) {
+    const { value, done } = await reader.read();
+    if (done) {
+      break;
+    }
+
+    if (value) {
+      result += decoder.decode(value);
+      onStream?.(result);
+    }
+  }
+
+  onStream?.(result);
+  onStreamEnd?.(result);
+  reader.releaseLock();
+}
+
+export async function readStream(
+  reader: ReadableStreamDefaultReader<Uint8Array>,
+  {
+    onStream,
+    onStreamEnd,
+  }: {
+    onStream?: (course: string) => void;
+    onStreamEnd?: (course: string) => void;
+  },
+) {
+  const decoder = new TextDecoder('utf-8');
+  let result = '';
+
+  while (true) {
+    const { value, done } = await reader.read();
+    if (done) {
+      break;
+    }
+
+    // Process the stream data as it comes in
+    if (value) {
+      let start = 0;
+      for (let i = 0; i < value.length; i++) {
+        if (value[i] === NEW_LINE) {
+          result += decoder.decode(value.slice(start, i + 1));
+          onStream?.(result);
+          start = i + 1;
+        }
+      }
+      if (start < value.length) {
+        result += decoder.decode(value.slice(start));
+      }
+    }
+  }
+
+  onStream?.(result);
+  onStreamEnd?.(result);
+  reader.releaseLock();
+}
diff --git a/src/lib/number.ts b/src/lib/number.ts
index d686a5934..feadfbaf2 100644
--- a/src/lib/number.ts
+++ b/src/lib/number.ts
@@ -21,3 +21,16 @@ export function humanizeNumber(number: number): string {
 
   return `${decimalIfNeeded(number / 1000000)}m`;
 }
+
+export function getPercentage(portion: number, total: number): number {
+  if (portion <= 0 || total <= 0) {
+    return 0;
+  }
+
+  if (portion >= total) {
+    return 100;
+  }
+
+  const percentage = (portion / total) * 100;
+  return Math.round(percentage);
+}
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index f7a947579..c95c77154 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -39,6 +39,7 @@ export interface AICourseDocument {
   title: string;
   slug?: string;
   keyword: string;
+  done: string[];
   difficulty: string;
   data: string;
   viewCount: number;
@@ -75,7 +76,6 @@ export function getAiCourseLimitOptions() {
 }
 
 export type AICourseListItem = AICourseDocument & {
-  progress: AICourseProgressDocument;
   lessonCount: number;
 };
 
diff --git a/src/queries/billing.ts b/src/queries/billing.ts
index f7250e62a..6425b4048 100644
--- a/src/queries/billing.ts
+++ b/src/queries/billing.ts
@@ -1,6 +1,7 @@
-import { queryOptions } from '@tanstack/react-query';
+import { queryOptions, useQuery } from '@tanstack/react-query';
 import { httpGet } from '../lib/query-http';
 import { isLoggedIn } from '../lib/jwt';
+import { queryClient } from '../stores/query-client';
 
 export const allowedSubscriptionStatus = [
   'active',
@@ -53,6 +54,25 @@ export function billingDetailsOptions() {
   });
 }
 
+export function useIsPaidUser() {
+  const { data, isLoading } = useQuery(
+    {
+      queryKey: ['billing-details'],
+      queryFn: async () => {
+        return httpGet<BillingDetailsResponse>('/v1-billing-details');
+      },
+      enabled: !!isLoggedIn(),
+      select: (data) => data.status === 'active',
+    },
+    queryClient,
+  );
+
+  return {
+    isPaidUser: data ?? false,
+    isLoading,
+  };
+}
+
 type CoursePriceParams = {
   courseSlug: string;
 };