1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 00:21:28 +02:00

Chat history improvements

This commit is contained in:
Kamran Ahmed
2025-06-13 00:59:18 +01:00
parent 0d2a6ed9e6
commit 5cc96b2dd0
3 changed files with 57 additions and 55 deletions

View File

@@ -6,6 +6,7 @@ import {
MessageCirclePlus,
PauseCircleIcon,
PersonStanding,
Plus,
SendIcon,
SquareArrowOutUpRight,
Trash2,
@@ -18,24 +19,23 @@ import { useKeydown } from '../../hooks/use-keydown';
import {
roadmapAIChatRenderer,
useRoadmapAIChat,
type RoadmapAIChatHistoryType,
} from '../../hooks/use-roadmap-ai-chat';
import { cn } from '../../lib/classname';
import { lockBodyScroll } from '../../lib/dom';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
import { slugify } from '../../lib/slugger';
import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { billingDetailsOptions } from '../../queries/billing';
import { chatHistoryOptions } from '../../queries/chat-history';
import { roadmapJSONOptions } from '../../queries/roadmap';
import { roadmapQuestionsOptions } from '../../queries/roadmap-questions';
import { queryClient } from '../../stores/query-client';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { RoadmapAIChatCard } from '../RoadmapAIChat/RoadmapAIChatCard';
import { RoadmapAIChatHistory } from '../RoadmapAIChatHistory/RoadmapAIChatHistory';
import { CLOSE_TOPIC_DETAIL_EVENT } from '../TopicDetail/TopicDetail';
import { UpdatePersonaModal } from '../UserPersona/UpdatePersonaModal';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
import { chatHistoryOptions } from '../../queries/chat-history';
import { RoadmapAIChatHistory } from '../RoadmapAIChatHistory/RoadmapAIChatHistory';
type ChatHeaderButtonProps = {
onClick?: () => void;
@@ -381,21 +381,22 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
<div className="flex">
<ChatHeaderButton
icon={<BookOpen className="h-3.5 w-3.5" />}
className="text-sm"
className="pointer-events-none text-sm"
>
AI Tutor
{chatHistory?.title || 'AI Tutor'}
</ChatHeaderButton>
</div>
<div className="flex gap-1.5">
<ChatHeaderButton
href={newTabUrl}
target="_blank"
icon={<SquareArrowOutUpRight className="h-3.5 w-3.5" />}
className="hidden rounded-md py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300 sm:flex"
>
Open in new tab
</ChatHeaderButton>
{isPaidUser && activeChatHistoryId && (
<ChatHeaderButton
onClick={() => {
setActiveChatHistoryId(undefined);
}}
icon={<Plus className="h-3.5 w-3.5" />}
className="justify-center rounded-md bg-gray-200 px-2 py-1 text-xs text-black hover:bg-gray-300"
/>
)}
<RoadmapAIChatHistory
roadmapId={roadmapId}
@@ -403,20 +404,25 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
onChatHistoryClick={(chatHistoryId) => {
setIsChatHistoryLoading(true);
setActiveChatHistoryId(chatHistoryId);
setShowScrollToBottom(false);
}}
onDelete={(chatHistoryId) => {
if (activeChatHistoryId === chatHistoryId) {
setActiveChatHistoryId(undefined);
}
}}
onNewChat={() => {
setActiveChatHistoryId(undefined);
}}
onUpgrade={() => {
setShowUpgradeModal(true);
}}
/>
<ChatHeaderButton
href={newTabUrl}
target="_blank"
icon={<SquareArrowOutUpRight className="h-3.5 w-3.5" />}
className="hidden justify-center rounded-md bg-gray-200 px-1 py-1 text-gray-500 hover:bg-gray-300 sm:flex"
/>
<ChatHeaderButton
onClick={() => setIsOpen(false)}
icon={<X className="h-3.5 w-3.5" />}

View File

@@ -3,7 +3,7 @@ import { getAiCourseLimitOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { billingDetailsOptions } from '../../queries/billing';
import { isLoggedIn } from '../../lib/jwt';
import { BookIcon, BotIcon, GiftIcon, XIcon } from 'lucide-react';
import { BookIcon, BotIcon, GiftIcon, PlusIcon, XIcon } from 'lucide-react';
import type { RoadmapAIChatTab } from './RoadmapAIChat';
import { useState } from 'react';
import { getPercentage } from '../../lib/number';
@@ -158,15 +158,18 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
{!isDataLoading && isLoggedIn() && (
<div className="flex gap-1.5 pr-4">
{isPaidUser && (
<button
className="flex items-center gap-1 rounded-md bg-gray-200 px-2 py-1 text-xs text-black hover:bg-gray-300"
onClick={onNewChat}
>
<PlusIcon className="size-4" />
New Chat
</button>
)}
{!isPaidUser && (
<>
<button
className="hidden rounded-md bg-gray-200 px-2 py-1 text-sm hover:bg-gray-300 2xl:block"
onClick={handleCreditsClick}
>
<span className="font-medium">{usagePercentage}%</span> limit
used
</button>
<button
className="flex items-center gap-1 rounded-md bg-yellow-400 px-2 py-1 text-sm text-black hover:bg-yellow-500"
onClick={handleUpgradeClick}
@@ -174,23 +177,21 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
<GiftIcon className="size-4" />
Upgrade
</button>
<button
className="hidden items-center gap-1 rounded-md bg-gray-200 px-2 py-1 text-sm text-black hover:bg-gray-300 max-xl:flex"
onClick={onCloseChat}
>
<XIcon className="size-3.5" strokeWidth={2.5} />
</button>
</>
)}
<RoadmapAIChatHistory
roadmapId={roadmapId}
onChatHistoryClick={onChatHistoryClick}
activeChatHistoryId={activeChatHistoryId}
onNewChat={onNewChat}
onDelete={onDeleteChatHistory}
onUpgrade={onUpgrade}
/>
<button
className="hidden items-center gap-1 rounded-md bg-gray-200 px-2 py-1 text-sm text-black hover:bg-gray-300 max-xl:flex"
onClick={onCloseChat}
>
<XIcon className="size-3.5" strokeWidth={2.5} />
</button>
</div>
)}
</div>

View File

@@ -1,4 +1,4 @@
import { HistoryIcon, Loader2Icon, PlusIcon } from 'lucide-react';
import { HistoryIcon, Loader2Icon } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '../Popover';
import { useEffect, useMemo, useState } from 'react';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
@@ -15,9 +15,9 @@ import { showLoginPopup } from '../../lib/popup';
type RoadmapAIChatHistoryProps = {
roadmapId: string;
activeChatHistoryId?: string;
activeChatHistoryTitle?: string;
onChatHistoryClick: (id: string) => void;
onDelete?: (id: string) => void;
onNewChat?: () => void;
onUpgrade?: () => void;
};
@@ -25,9 +25,9 @@ export function RoadmapAIChatHistory(props: RoadmapAIChatHistoryProps) {
const {
roadmapId,
activeChatHistoryId,
activeChatHistoryTitle,
onChatHistoryClick,
onDelete,
onNewChat,
onUpgrade,
} = props;
@@ -50,11 +50,19 @@ export function RoadmapAIChatHistory(props: RoadmapAIChatHistoryProps) {
roadmapId,
query,
}),
enabled: !!roadmapId && isLoggedIn() && isOpen,
enabled: !!roadmapId && isLoggedIn() && isOpen && isPaidUser,
},
queryClient,
);
// no initial spinner if not paid user
// because we won't fetch the data
useEffect(() => {
if (!isPaidUser) {
setIsLoading(false);
}
}, [isPaidUser]);
useEffect(() => {
if (!chatHistory || isBillingDetailsLoading) {
return;
@@ -83,18 +91,18 @@ export function RoadmapAIChatHistory(props: RoadmapAIChatHistoryProps) {
setIsOpen(open);
}}
>
<PopoverTrigger className="flex h-8 items-center justify-center gap-2 rounded-md px-2 text-xs text-gray-500 hover:bg-gray-200 hover:text-black">
<PopoverTrigger className="flex items-center justify-center gap-2 rounded-md bg-gray-200 px-3 py-1.5 text-xs text-gray-900 hover:bg-gray-300 hover:text-black">
<HistoryIcon className="size-3.5" />
Chat History
{activeChatHistoryTitle || 'Chat History'}
</PopoverTrigger>
<PopoverContent
className="z-[999] flex max-h-[400px] w-80 flex-col overflow-hidden p-0"
className="z-[999] flex max-h-[400px] w-80 flex-col overflow-hidden p-0 shadow-lg"
align="end"
sideOffset={4}
>
{isLoading && (
<div className="flex items-center justify-center py-10">
<Loader2Icon className="size-6 animate-spin stroke-[2.5]" />
<Loader2Icon className="size-6 animate-spin stroke-[2.5] text-gray-400" />
</div>
)}
@@ -167,19 +175,6 @@ export function RoadmapAIChatHistory(props: RoadmapAIChatHistoryProps) {
</div>
)}
</div>
<div className="flex items-center justify-center border-t border-gray-200">
<button
className="flex w-full items-center justify-center gap-2 p-2 text-sm text-gray-500 hover:bg-gray-200 hover:text-black"
onClick={() => {
setIsOpen(false);
onNewChat?.();
}}
>
<PlusIcon className="size-4" />
New Chat
</button>
</div>
</>
)}
</PopoverContent>