From f0cc7421fb246a1baf04da133894d172189c9fa8 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Tue, 29 Apr 2025 17:07:13 +0600
Subject: [PATCH] feat: ai course subjects

---
 .../TopicDetail/ResourceListSeparator.tsx     |  2 +-
 src/components/TopicDetail/TopicDetailAI.tsx  | 82 +++++++++++++++----
 src/queries/roadmap-tree.ts                   | 26 ++++++
 3 files changed, 95 insertions(+), 15 deletions(-)
 create mode 100644 src/queries/roadmap-tree.ts

diff --git a/src/components/TopicDetail/ResourceListSeparator.tsx b/src/components/TopicDetail/ResourceListSeparator.tsx
index 1b8d8d60f..ce07255e4 100644
--- a/src/components/TopicDetail/ResourceListSeparator.tsx
+++ b/src/components/TopicDetail/ResourceListSeparator.tsx
@@ -27,7 +27,7 @@ export function ResourceListSeparator(props: ResourceSeparatorProps) {
         {Icon && <Icon className="inline-block h-3 w-3 fill-current" />}
         {text}
       </span>
-      <hr className="absolute inset-x-0 grow border-current" />
+      <span className="absolute inset-x-0 h-px w-full grow bg-current" />
     </p>
   );
 }
diff --git a/src/components/TopicDetail/TopicDetailAI.tsx b/src/components/TopicDetail/TopicDetailAI.tsx
index 7af6b6b49..18817ee76 100644
--- a/src/components/TopicDetail/TopicDetailAI.tsx
+++ b/src/components/TopicDetail/TopicDetailAI.tsx
@@ -26,6 +26,7 @@ import { readStream } from '../../lib/ai';
 import { markdownToHtmlWithHighlighting } from '../../lib/markdown';
 import type { ResourceType } from '../../lib/resource-progress';
 import { getPercentage } from '../../lib/number';
+import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
 
 type TopicDetailAIProps = {
   resourceId: string;
@@ -51,6 +52,10 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
   const textareaRef = useRef<HTMLTextAreaElement>(null);
   const scrollareaRef = useRef<HTMLDivElement>(null);
 
+  const sanitizedTopicId = topicId?.includes('@')
+    ? topicId?.split('@')?.[1]
+    : topicId;
+
   const toast = useToast();
   const [message, setMessage] = useState('');
   const [isStreamingMessage, setIsStreamingMessage] = useState(false);
@@ -64,6 +69,20 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
   const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
     useQuery(billingDetailsOptions(), queryClient);
 
+  const { data: roadmapTreeMapping, isLoading: isRoadmapTreeMappingLoading } =
+    useQuery(
+      {
+        ...roadmapTreeMappingOptions(resourceId),
+        select: (data) => {
+          const node = data.find(
+            (mapping) => mapping.nodeId === sanitizedTopicId,
+          );
+          return node;
+        },
+      },
+      queryClient,
+    );
+
   const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0);
   const isPaidUser = userBillingDetails?.status === 'active';
 
@@ -109,10 +128,6 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
     try {
       setIsStreamingMessage(true);
 
-      const sanitizedTopicId = topicId?.includes('@')
-        ? topicId?.split('@')?.[1]
-        : topicId;
-
       const response = await fetch(
         `${import.meta.env.PUBLIC_API_URL}/v1-topic-detail-chat`,
         {
@@ -194,22 +209,61 @@ export function TopicDetailAI(props: TopicDetailAIProps) {
     scrollToBottom();
   }, []);
 
-  const isDataLoading = isLoading || isBillingDetailsLoading;
+  const isDataLoading =
+    isLoading || isBillingDetailsLoading || isRoadmapTreeMappingLoading;
   const usagePercentage = getPercentage(
     tokenUsage?.used || 0,
     tokenUsage?.limit || 0,
   );
+  const hasSubjects =
+    roadmapTreeMapping?.subjects && roadmapTreeMapping?.subjects?.length > 0;
 
   return (
-    <div className="mt-4 flex grow flex-col overflow-hidden rounded-lg border">
-      <div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm">
-        <h4 className="flex items-center gap-2 text-base font-medium">
-          <BotIcon
-            className="relative -top-[1px] size-5 shrink-0 text-black"
-            strokeWidth={2.5}
-          />
-          AI Tutor
-        </h4>
+    <div className="mt-4 flex grow flex-col overflow-hidden rounded-lg border border-gray-200">
+      {hasSubjects && (
+        <div className="border-b border-gray-200 px-4 py-2">
+          <h4 className="flex items-center gap-2 text-base">
+            Complete the following courses on AI Tutor
+          </h4>
+
+          <div className="mt-2.5 flex flex-wrap gap-1 text-sm">
+            {roadmapTreeMapping?.subjects?.map((subject) => {
+              return (
+                <a
+                  key={subject}
+                  target="_blank"
+                  href={`/ai/search?term=${subject}&difficulty=beginner`}
+                  className="rounded-md border px-1.5"
+                >
+                  {subject}
+                </a>
+              );
+            })}
+          </div>
+        </div>
+      )}
+
+      <div
+        className={cn(
+          'flex items-center justify-between gap-2 border-gray-200 px-4 py-2 text-sm',
+          !hasSubjects && 'border-b',
+        )}
+      >
+        {hasSubjects && (
+          <span className="flex items-center gap-2 text-base">
+            or start chatting with AI
+          </span>
+        )}
+
+        {!hasSubjects && (
+          <h4 className="flex items-center gap-2 text-base font-medium">
+            <BotIcon
+              className="relative -top-[1px] size-5 shrink-0 text-black"
+              strokeWidth={2.5}
+            />
+            AI Tutor
+          </h4>
+        )}
 
         {!isDataLoading && !isPaidUser && (
           <p className="text-sm text-gray-500">
diff --git a/src/queries/roadmap-tree.ts b/src/queries/roadmap-tree.ts
new file mode 100644
index 000000000..a94259ceb
--- /dev/null
+++ b/src/queries/roadmap-tree.ts
@@ -0,0 +1,26 @@
+import { queryOptions } from '@tanstack/react-query';
+import { httpGet } from '../lib/query-http';
+
+export interface RoadmapTreeDocument {
+  _id?: string;
+  roadmapId: string;
+  mapping: {
+    _id?: string;
+    nodeId: string;
+    text: string;
+    subjects: string[];
+  }[];
+  createdAt: Date;
+  updatedAt: Date;
+}
+
+export function roadmapTreeMappingOptions(roadmapId: string) {
+  return queryOptions({
+    queryKey: ['roadmap-tree-mapping', { roadmapId }],
+    queryFn: () => {
+      return httpGet<RoadmapTreeDocument['mapping']>(
+        `${import.meta.env.PUBLIC_API_URL}/v1-roadmap-tree-mapping/${roadmapId}`,
+      );
+    },
+  });
+}