mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-03 06:12:53 +02:00
wip
This commit is contained in:
@@ -16,6 +16,7 @@ import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
import { useKeydown } from '../../hooks/use-keydown';
|
import { useKeydown } from '../../hooks/use-keydown';
|
||||||
import {
|
import {
|
||||||
|
roadmapAIChatRenderer,
|
||||||
useRoadmapAIChat,
|
useRoadmapAIChat,
|
||||||
type RoadmapAIChatHistoryType,
|
type RoadmapAIChatHistoryType,
|
||||||
} from '../../hooks/use-roadmap-ai-chat';
|
} from '../../hooks/use-roadmap-ai-chat';
|
||||||
@@ -33,6 +34,8 @@ import { CLOSE_TOPIC_DETAIL_EVENT } from '../TopicDetail/TopicDetail';
|
|||||||
import { UpdatePersonaModal } from '../UserPersona/UpdatePersonaModal';
|
import { UpdatePersonaModal } from '../UserPersona/UpdatePersonaModal';
|
||||||
import { isLoggedIn } from '../../lib/jwt';
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
import { showLoginPopup } from '../../lib/popup';
|
import { showLoginPopup } from '../../lib/popup';
|
||||||
|
import { chatHistoryOptions } from '../../queries/chat-history';
|
||||||
|
import { RoadmapAIChatHistory } from '../RoadmapAIChatHistory/RoadmapAIChatHistory';
|
||||||
|
|
||||||
type ChatHeaderButtonProps = {
|
type ChatHeaderButtonProps = {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
@@ -47,7 +50,7 @@ function ChatHeaderButton(props: ChatHeaderButtonProps) {
|
|||||||
const { onClick, href, icon, children, className, target } = props;
|
const { onClick, href, icon, children, className, target } = props;
|
||||||
|
|
||||||
const classNames = cn(
|
const classNames = cn(
|
||||||
'flex items-center gap-1.5 text-xs text-gray-600 transition-colors hover:text-gray-900',
|
'flex shrink-0 items-center gap-1.5 text-xs text-gray-600 transition-colors hover:text-gray-900 min-w-8',
|
||||||
className,
|
className,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -227,6 +230,22 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [isChatHistoryLoading, setIsChatHistoryLoading] = useState(true);
|
||||||
|
const [activeChatHistoryId, setActiveChatHistoryId] = useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
|
const { data: chatHistory } = useQuery(
|
||||||
|
chatHistoryOptions(
|
||||||
|
activeChatHistoryId,
|
||||||
|
roadmapAIChatRenderer({
|
||||||
|
roadmapId,
|
||||||
|
totalTopicCount,
|
||||||
|
onSelectTopic,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
queryClient,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
aiChatHistory,
|
aiChatHistory,
|
||||||
isStreamingMessage,
|
isStreamingMessage,
|
||||||
@@ -237,13 +256,39 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
handleAbort,
|
handleAbort,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
clearChat,
|
clearChat,
|
||||||
|
setAiChatHistory,
|
||||||
} = useRoadmapAIChat({
|
} = useRoadmapAIChat({
|
||||||
|
activeChatHistoryId,
|
||||||
roadmapId,
|
roadmapId,
|
||||||
totalTopicCount,
|
totalTopicCount,
|
||||||
scrollareaRef,
|
scrollareaRef,
|
||||||
onSelectTopic,
|
onSelectTopic,
|
||||||
|
onChatHistoryIdChange: (chatHistoryId) => {
|
||||||
|
setActiveChatHistoryId(chatHistoryId);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!chatHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAiChatHistory(chatHistory?.messages ?? []);
|
||||||
|
setIsChatHistoryLoading(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollToBottom('instant');
|
||||||
|
}, 0);
|
||||||
|
}, [chatHistory]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeChatHistoryId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAiChatHistory([]);
|
||||||
|
setIsChatHistoryLoading(false);
|
||||||
|
}, [activeChatHistoryId, setAiChatHistory, setIsChatHistoryLoading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lockBodyScroll(isOpen);
|
lockBodyScroll(isOpen);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
@@ -293,6 +338,7 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasMessages = aiChatHistory.length > 0;
|
const hasMessages = aiChatHistory.length > 0;
|
||||||
|
const newTabUrl = `/${roadmapId}/ai${activeChatHistoryId ? `?chatId=${activeChatHistoryId}` : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -331,7 +377,6 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden rounded-lg bg-white shadow-lg">
|
<div className="flex h-full w-full flex-col overflow-hidden rounded-lg bg-white shadow-lg">
|
||||||
{/* Messages area */}
|
|
||||||
<div className="flex items-center justify-between px-3 py-2">
|
<div className="flex items-center justify-between px-3 py-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<ChatHeaderButton
|
<ChatHeaderButton
|
||||||
@@ -344,7 +389,7 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
|
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
<ChatHeaderButton
|
<ChatHeaderButton
|
||||||
href={`/${roadmapId}/ai`}
|
href={newTabUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
icon={<SquareArrowOutUpRight className="h-3.5 w-3.5" />}
|
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"
|
className="hidden rounded-md py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300 sm:flex"
|
||||||
@@ -352,10 +397,27 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
Open in new tab
|
Open in new tab
|
||||||
</ChatHeaderButton>
|
</ChatHeaderButton>
|
||||||
|
|
||||||
|
<RoadmapAIChatHistory
|
||||||
|
roadmapId={roadmapId}
|
||||||
|
activeChatHistoryId={activeChatHistoryId}
|
||||||
|
onChatHistoryClick={(chatHistoryId) => {
|
||||||
|
setIsChatHistoryLoading(true);
|
||||||
|
setActiveChatHistoryId(chatHistoryId);
|
||||||
|
}}
|
||||||
|
onDelete={(chatHistoryId) => {
|
||||||
|
if (activeChatHistoryId === chatHistoryId) {
|
||||||
|
setActiveChatHistoryId(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onNewChat={() => {
|
||||||
|
setActiveChatHistoryId(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<ChatHeaderButton
|
<ChatHeaderButton
|
||||||
onClick={() => setIsOpen(false)}
|
onClick={() => setIsOpen(false)}
|
||||||
icon={<X className="h-3.5 w-3.5" />}
|
icon={<X className="h-3.5 w-3.5" />}
|
||||||
className="rounded-md bg-red-100 px-1 py-1 text-red-500 hover:bg-red-200"
|
className="flex items-center justify-center rounded-md bg-red-100 px-1 py-1 text-red-500 hover:bg-red-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -412,13 +474,11 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{aiChatHistory.map(
|
{aiChatHistory.map((chat, index) => (
|
||||||
(chat: RoadmapAIChatHistoryType, index: number) => (
|
<Fragment key={`chat-${index}`}>
|
||||||
<Fragment key={`chat-${index}`}>
|
<RoadmapAIChatCard {...chat} />
|
||||||
<RoadmapAIChatCard {...chat} />
|
</Fragment>
|
||||||
</Fragment>
|
))}
|
||||||
),
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isStreamingMessage && !streamedMessage && (
|
{isStreamingMessage && !streamedMessage && (
|
||||||
<RoadmapAIChatCard role="assistant" html="Thinking..." />
|
<RoadmapAIChatCard role="assistant" html="Thinking..." />
|
||||||
@@ -444,7 +504,6 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Input area */}
|
|
||||||
{isLimitExceeded && (
|
{isLimitExceeded && (
|
||||||
<UpgradeMessage
|
<UpgradeMessage
|
||||||
onUpgradeClick={() => {
|
onUpgradeClick={() => {
|
||||||
@@ -482,7 +541,7 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{hasMessages && (
|
{hasMessages && !isPaidUser && (
|
||||||
<ChatHeaderButton
|
<ChatHeaderButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
@@ -550,7 +609,7 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
{!isOpen && (
|
{!isOpen && (
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative mx-auto flex flex-shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full bg-stone-900 py-2.5 pr-8 pl-6 text-center text-white shadow-2xl transition-all duration-300 hover:scale-101 hover:bg-stone-800 w-max',
|
'relative mx-auto flex w-max flex-shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full bg-stone-900 py-2.5 pr-8 pl-6 text-center text-white shadow-2xl transition-all duration-300 hover:scale-101 hover:bg-stone-800',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
@@ -566,10 +625,10 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
|
|||||||
<span className="mr-1 text-sm font-semibold text-yellow-400">
|
<span className="mr-1 text-sm font-semibold text-yellow-400">
|
||||||
AI Tutor
|
AI Tutor
|
||||||
</span>
|
</span>
|
||||||
<span className={'text-white hidden sm:block'}>
|
<span className={'hidden text-white sm:block'}>
|
||||||
Have a question? Type here
|
Have a question? Type here
|
||||||
</span>
|
</span>
|
||||||
<span className={'text-white block sm:hidden'}>
|
<span className={'block text-white sm:hidden'}>
|
||||||
Ask anything
|
Ask anything
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
@@ -65,11 +65,11 @@ export function RoadmapAIChatHistory(props: RoadmapAIChatHistoryProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<PopoverTrigger className="flex size-8 items-center justify-center rounded-lg hover:bg-gray-200">
|
<PopoverTrigger className="flex size-8 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-200 hover:text-black">
|
||||||
<HistoryIcon className="size-4" />
|
<HistoryIcon className="size-4" />
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="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"
|
||||||
align="end"
|
align="end"
|
||||||
sideOffset={4}
|
sideOffset={4}
|
||||||
>
|
>
|
||||||
|
Reference in New Issue
Block a user