mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-03 06:12:53 +02:00
Merge branch 'feat/chat-history' into feat/roadmap-chat-history
This commit is contained in:
@@ -32,7 +32,6 @@ import {
|
|||||||
import { RoadmapRecommendations } from '../RoadmapAIChat/RoadmapRecommendations';
|
import { RoadmapRecommendations } from '../RoadmapAIChat/RoadmapRecommendations';
|
||||||
import { AIChatCourse } from './AIChatCouse';
|
import { AIChatCourse } from './AIChatCouse';
|
||||||
import { showLoginPopup } from '../../lib/popup';
|
import { showLoginPopup } from '../../lib/popup';
|
||||||
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
|
|
||||||
import { readChatStream } from '../../lib/chat';
|
import { readChatStream } from '../../lib/chat';
|
||||||
import { chatHistoryOptions } from '../../queries/chat-history';
|
import { chatHistoryOptions } from '../../queries/chat-history';
|
||||||
import { cn } from '../../lib/classname';
|
import { cn } from '../../lib/classname';
|
||||||
@@ -51,6 +50,7 @@ type AIChatProps = {
|
|||||||
messages?: RoadmapAIChatHistoryType[];
|
messages?: RoadmapAIChatHistoryType[];
|
||||||
chatHistoryId?: string;
|
chatHistoryId?: string;
|
||||||
setChatHistoryId?: (chatHistoryId: string) => void;
|
setChatHistoryId?: (chatHistoryId: string) => void;
|
||||||
|
onUpgrade?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AIChat(props: AIChatProps) {
|
export function AIChat(props: AIChatProps) {
|
||||||
@@ -58,6 +58,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
messages: defaultMessages,
|
messages: defaultMessages,
|
||||||
chatHistoryId: defaultChatHistoryId,
|
chatHistoryId: defaultChatHistoryId,
|
||||||
setChatHistoryId: setDefaultChatHistoryId,
|
setChatHistoryId: setDefaultChatHistoryId,
|
||||||
|
onUpgrade,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
@@ -70,7 +71,6 @@ export function AIChat(props: AIChatProps) {
|
|||||||
RoadmapAIChatHistoryType[]
|
RoadmapAIChatHistoryType[]
|
||||||
>(defaultMessages ?? []);
|
>(defaultMessages ?? []);
|
||||||
|
|
||||||
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
|
||||||
const [isPersonalizedResponseFormOpen, setIsPersonalizedResponseFormOpen] =
|
const [isPersonalizedResponseFormOpen, setIsPersonalizedResponseFormOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [isUploadResumeModalOpen, setIsUploadResumeModalOpen] = useState(false);
|
const [isUploadResumeModalOpen, setIsUploadResumeModalOpen] = useState(false);
|
||||||
@@ -128,7 +128,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
|
|
||||||
if (isLimitExceeded) {
|
if (isLimitExceeded) {
|
||||||
if (!isPaidUser) {
|
if (!isPaidUser) {
|
||||||
setShowUpgradeModal(true);
|
onUpgrade?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.error('Limit reached for today. Please wait until tomorrow.');
|
toast.error('Limit reached for today. Please wait until tomorrow.');
|
||||||
@@ -342,7 +342,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
(index: number) => {
|
(index: number) => {
|
||||||
if (isLimitExceeded) {
|
if (isLimitExceeded) {
|
||||||
if (!isPaidUser) {
|
if (!isPaidUser) {
|
||||||
setShowUpgradeModal(true);
|
onUpgrade?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.error('Limit reached for today. Please wait until tomorrow.');
|
toast.error('Limit reached for today. Please wait until tomorrow.');
|
||||||
@@ -429,10 +429,6 @@ export function AIChat(props: AIChatProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showUpgradeModal && (
|
|
||||||
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="pointer-events-none absolute right-0 bottom-0 left-0 mx-auto w-full max-w-3xl px-4"
|
className="pointer-events-none absolute right-0 bottom-0 left-0 mx-auto w-full max-w-3xl px-4"
|
||||||
ref={chatContainerRef}
|
ref={chatContainerRef}
|
||||||
@@ -446,7 +442,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowUpgradeModal(true)}
|
onClick={() => onUpgrade?.()}
|
||||||
className="shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2 py-1 text-xs font-medium text-yellow-800 hover:bg-yellow-200"
|
className="shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2 py-1 text-xs font-medium text-yellow-800 hover:bg-yellow-200"
|
||||||
>
|
>
|
||||||
Upgrade to Pro
|
Upgrade to Pro
|
||||||
@@ -540,7 +536,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowUpgradeModal(true);
|
onUpgrade?.();
|
||||||
}}
|
}}
|
||||||
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
|
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
|
||||||
>
|
>
|
||||||
|
@@ -9,6 +9,7 @@ import { ListChatHistory } from './ListChatHistory';
|
|||||||
import { billingDetailsOptions } from '../../queries/billing';
|
import { billingDetailsOptions } from '../../queries/billing';
|
||||||
import { ChatHistoryError } from './ChatHistoryError';
|
import { ChatHistoryError } from './ChatHistoryError';
|
||||||
import { useClientMount } from '../../hooks/use-client-mount';
|
import { useClientMount } from '../../hooks/use-client-mount';
|
||||||
|
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
|
||||||
|
|
||||||
type AIChatHistoryProps = {
|
type AIChatHistoryProps = {
|
||||||
chatHistoryId?: string;
|
chatHistoryId?: string;
|
||||||
@@ -19,6 +20,7 @@ export function AIChatHistory(props: AIChatHistoryProps) {
|
|||||||
|
|
||||||
const isClientMounted = useClientMount();
|
const isClientMounted = useClientMount();
|
||||||
const [keyTrigger, setKeyTrigger] = useState(0);
|
const [keyTrigger, setKeyTrigger] = useState(0);
|
||||||
|
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
||||||
const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true);
|
const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true);
|
||||||
const [chatHistoryId, setChatHistoryId] = useState<string | undefined>(
|
const [chatHistoryId, setChatHistoryId] = useState<string | undefined>(
|
||||||
defaultChatHistoryId || undefined,
|
defaultChatHistoryId || undefined,
|
||||||
@@ -116,13 +118,15 @@ export function AIChatHistory(props: AIChatHistoryProps) {
|
|||||||
return (
|
return (
|
||||||
<AIChatLayout>
|
<AIChatLayout>
|
||||||
<div className="relative flex grow">
|
<div className="relative flex grow">
|
||||||
{isPaidUser && (
|
<ListChatHistory
|
||||||
<ListChatHistory
|
activeChatHistoryId={chatHistoryId}
|
||||||
activeChatHistoryId={chatHistoryId}
|
onChatHistoryClick={handleChatHistoryClick}
|
||||||
onChatHistoryClick={handleChatHistoryClick}
|
onDelete={handleDelete}
|
||||||
onDelete={handleDelete}
|
isPaidUser={isPaidUser}
|
||||||
/>
|
onUpgrade={() => {
|
||||||
)}
|
setShowUpgradeModal(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="relative flex grow">
|
<div className="relative flex grow">
|
||||||
{showLoader && (
|
{showLoader && (
|
||||||
@@ -151,10 +155,17 @@ export function AIChatHistory(props: AIChatHistoryProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
onUpgrade={() => {
|
||||||
|
setShowUpgradeModal(true);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showUpgradeModal && (
|
||||||
|
<UpgradeAccountModal onClose={() => setShowUpgradeModal(false)} />
|
||||||
|
)}
|
||||||
</AIChatLayout>
|
</AIChatLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import { queryClient } from '../../stores/query-client';
|
|||||||
import { ChatHistoryItem } from './ChatHistoryItem';
|
import { ChatHistoryItem } from './ChatHistoryItem';
|
||||||
import {
|
import {
|
||||||
Loader2Icon,
|
Loader2Icon,
|
||||||
|
LockIcon,
|
||||||
PanelLeftCloseIcon,
|
PanelLeftCloseIcon,
|
||||||
PanelLeftIcon,
|
PanelLeftIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
@@ -24,10 +25,18 @@ type ListChatHistoryProps = {
|
|||||||
activeChatHistoryId?: string;
|
activeChatHistoryId?: string;
|
||||||
onChatHistoryClick: (chatHistoryId: string | null) => void;
|
onChatHistoryClick: (chatHistoryId: string | null) => void;
|
||||||
onDelete?: (chatHistoryId: string) => void;
|
onDelete?: (chatHistoryId: string) => void;
|
||||||
|
isPaidUser?: boolean;
|
||||||
|
onUpgrade?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ListChatHistory(props: ListChatHistoryProps) {
|
export function ListChatHistory(props: ListChatHistoryProps) {
|
||||||
const { activeChatHistoryId, onChatHistoryClick, onDelete } = props;
|
const {
|
||||||
|
activeChatHistoryId,
|
||||||
|
onChatHistoryClick,
|
||||||
|
onDelete,
|
||||||
|
isPaidUser,
|
||||||
|
onUpgrade,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@@ -85,14 +94,49 @@ export function ListChatHistory(props: ListChatHistoryProps) {
|
|||||||
(group) => group.histories.length === 0,
|
(group) => group.histories.length === 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const classNames = cn(
|
||||||
<div
|
'flex w-[255px] shrink-0 flex-col justify-start border-r border-gray-200 bg-white p-2',
|
||||||
className={cn(
|
'max-md:absolute max-md:inset-0 max-md:z-20 max-md:w-full',
|
||||||
'flex w-[255px] shrink-0 flex-col justify-start border-r border-gray-200 bg-white p-2',
|
!isOpen && 'hidden',
|
||||||
'max-md:absolute max-md:inset-0 max-md:z-20 max-md:w-full',
|
);
|
||||||
!isOpen && 'hidden',
|
|
||||||
)}
|
const closeButton = (
|
||||||
|
<button
|
||||||
|
className="flex size-8 items-center justify-center rounded-lg p-1 text-gray-500 hover:bg-gray-100 hover:text-black"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<PanelLeftCloseIcon className="h-4.5 w-4.5" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isPaidUser) {
|
||||||
|
return (
|
||||||
|
<div className={cn(classNames, 'relative')}>
|
||||||
|
<div className="absolute top-2 right-2">{closeButton}</div>
|
||||||
|
|
||||||
|
<div className="flex grow flex-col items-center justify-center">
|
||||||
|
<LockIcon className="size-8 text-gray-500" />
|
||||||
|
<p className="mt-4 text-center text-sm text-balance text-gray-500">
|
||||||
|
Upgrade to Pro to keep your chat history.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mt-2 shrink-0 cursor-pointer rounded-md bg-yellow-200 px-2.5 py-1.5 text-sm font-medium text-yellow-800 hover:bg-yellow-200"
|
||||||
|
onClick={() => {
|
||||||
|
onUpgrade?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Upgrade to Pro
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames}>
|
||||||
{isLoading && <ListChatHistorySkeleton />}
|
{isLoading && <ListChatHistorySkeleton />}
|
||||||
{!isLoading && isError && <ChatHistoryError error={error} />}
|
{!isLoading && isError && <ChatHistoryError error={error} />}
|
||||||
|
|
||||||
@@ -101,18 +145,11 @@ export function ListChatHistory(props: ListChatHistoryProps) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h1 className="font-medium text-gray-900">Chat History</h1>
|
<h1 className="font-medium text-gray-900">Chat History</h1>
|
||||||
<button
|
{closeButton}
|
||||||
className="flex size-8 items-center justify-center rounded-lg p-1 hover:bg-gray-100"
|
|
||||||
onClick={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PanelLeftCloseIcon className="h-4.5 w-4.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="flex w-full items-center hover:opacity-80 justify-center gap-2 rounded-lg bg-black p-2 text-sm text-white"
|
className="flex w-full items-center justify-center gap-2 rounded-lg bg-black p-2 text-sm text-white hover:opacity-80"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
Reference in New Issue
Block a user