mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-09 16:53:33 +02:00
wip
This commit is contained in:
@@ -268,6 +268,11 @@ export function AIChat(props: AIChatProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
queryClient.invalidateQueries(getAiCourseLimitOptions());
|
queryClient.invalidateQueries(getAiCourseLimitOptions());
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => {
|
||||||
|
return query.queryKey[0] === 'list-chat-history';
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onDetails: (details) => {
|
onDetails: (details) => {
|
||||||
const detailsJson = JSON.parse(details);
|
const detailsJson = JSON.parse(details);
|
||||||
@@ -378,7 +383,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ai-chat relative flex min-h-screen grow flex-col gap-2 bg-gray-100">
|
<div className="ai-chat relative flex grow flex-col gap-2 bg-gray-100">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 overflow-y-auto pb-55"
|
className="absolute inset-0 overflow-y-auto pb-55"
|
||||||
ref={scrollableContainerRef}
|
ref={scrollableContainerRef}
|
||||||
@@ -455,7 +460,7 @@ export function AIChat(props: AIChatProps) {
|
|||||||
onClick={scrollToBottom}
|
onClick={scrollToBottom}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{aiChatHistory.length > 0 && (
|
{aiChatHistory.length > 0 && !isPaidUser && (
|
||||||
<QuickActionButton
|
<QuickActionButton
|
||||||
icon={TrashIcon}
|
icon={TrashIcon}
|
||||||
label="Clear Chat"
|
label="Clear Chat"
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
listChatHistoryOptions,
|
listChatHistoryOptions,
|
||||||
type ChatHistoryDocument,
|
|
||||||
type ChatHistoryWithoutMessages,
|
type ChatHistoryWithoutMessages,
|
||||||
} from '../../queries/chat-history';
|
} from '../../queries/chat-history';
|
||||||
import { queryClient } from '../../stores/query-client';
|
import { queryClient } from '../../stores/query-client';
|
||||||
import { cn } from '../../lib/classname';
|
|
||||||
import { ChatHistoryItem } from './ChatHistoryItem';
|
import { ChatHistoryItem } from './ChatHistoryItem';
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { Loader2Icon, PlusIcon, SearchIcon } from 'lucide-react';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useDebounceValue } from '../../hooks/use-debounce';
|
||||||
|
|
||||||
type ListChatHistoryProps = {
|
type ListChatHistoryProps = {
|
||||||
activeChatHistoryId?: string;
|
activeChatHistoryId?: string;
|
||||||
@@ -20,12 +19,15 @@ type ListChatHistoryProps = {
|
|||||||
export function ListChatHistory(props: ListChatHistoryProps) {
|
export function ListChatHistory(props: ListChatHistoryProps) {
|
||||||
const { activeChatHistoryId, onChatHistoryClick, onDelete } = props;
|
const { activeChatHistoryId, onChatHistoryClick, onDelete } = props;
|
||||||
|
|
||||||
const { data } = useQuery(listChatHistoryOptions(), queryClient);
|
const [query, setQuery] = useState('');
|
||||||
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
|
||||||
|
useInfiniteQuery(listChatHistoryOptions({ query }), queryClient);
|
||||||
|
|
||||||
const groupedChatHistory = useMemo(() => {
|
const groupedChatHistory = useMemo(() => {
|
||||||
const today = DateTime.now().startOf('day');
|
const today = DateTime.now().startOf('day');
|
||||||
|
const allHistories = data?.pages?.flatMap((page) => page.data);
|
||||||
|
|
||||||
return data?.data?.reduce(
|
return allHistories?.reduce(
|
||||||
(acc, chatHistory) => {
|
(acc, chatHistory) => {
|
||||||
const updatedAt = DateTime.fromJSDate(
|
const updatedAt = DateTime.fromJSDate(
|
||||||
new Date(chatHistory.updatedAt),
|
new Date(chatHistory.updatedAt),
|
||||||
@@ -60,7 +62,7 @@ export function ListChatHistory(props: ListChatHistoryProps) {
|
|||||||
{ title: string; histories: ChatHistoryWithoutMessages[] }
|
{ title: string; histories: ChatHistoryWithoutMessages[] }
|
||||||
>,
|
>,
|
||||||
);
|
);
|
||||||
}, [data?.data]);
|
}, [data?.pages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[255px] shrink-0 border-r border-gray-200 bg-white p-2">
|
<div className="w-[255px] shrink-0 border-r border-gray-200 bg-white p-2">
|
||||||
@@ -74,6 +76,8 @@ export function ListChatHistory(props: ListChatHistoryProps) {
|
|||||||
<span className="text-sm">New Chat</span>
|
<span className="text-sm">New Chat</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<SearchInput onSearch={setQuery} isLoading={isLoading} />
|
||||||
|
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
{Object.entries(groupedChatHistory ?? {}).map(([key, value]) => {
|
{Object.entries(groupedChatHistory ?? {}).map(([key, value]) => {
|
||||||
if (value.histories.length === 0) {
|
if (value.histories.length === 0) {
|
||||||
@@ -103,6 +107,71 @@ export function ListChatHistory(props: ListChatHistoryProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasNextPage && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<button
|
||||||
|
className="flex w-full items-center justify-center gap-2 text-sm text-gray-500 hover:text-black"
|
||||||
|
onClick={() => {
|
||||||
|
fetchNextPage();
|
||||||
|
}}
|
||||||
|
disabled={isFetchingNextPage}
|
||||||
|
>
|
||||||
|
{isFetchingNextPage && (
|
||||||
|
<>
|
||||||
|
<Loader2Icon className="h-4 w-4 animate-spin" />
|
||||||
|
Loading more...
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isFetchingNextPage && 'Load More'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchInputProps = {
|
||||||
|
onSearch: (search: string) => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function SearchInput(props: SearchInputProps) {
|
||||||
|
const { onSearch, isLoading } = props;
|
||||||
|
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const debouncedSearch = useDebounceValue(search, 300);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onSearch(debouncedSearch);
|
||||||
|
}, [debouncedSearch, onSearch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="relative mt-2 flex max-w-sm flex-1 grow items-center"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onSearch(search);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search folder by name"
|
||||||
|
className="block h-9 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 pl-8 text-sm outline-none placeholder:text-zinc-500 focus:border-zinc-500"
|
||||||
|
required
|
||||||
|
minLength={3}
|
||||||
|
maxLength={255}
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="absolute top-1/2 left-2.5 -translate-y-1/2">
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader2Icon className="size-4 animate-spin text-gray-500" />
|
||||||
|
) : (
|
||||||
|
<SearchIcon className="size-4 text-gray-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { queryOptions } from '@tanstack/react-query';
|
import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
|
||||||
import { httpGet } from '../lib/query-http';
|
import { httpGet } from '../lib/query-http';
|
||||||
import { isLoggedIn } from '../lib/jwt';
|
import { isLoggedIn } from '../lib/jwt';
|
||||||
import type { RoadmapAIChatHistoryType } from '../components/RoadmapAIChat/RoadmapAIChat';
|
import type { RoadmapAIChatHistoryType } from '../components/RoadmapAIChat/RoadmapAIChat';
|
||||||
@@ -78,20 +78,24 @@ type ListChatHistoryResponse = {
|
|||||||
|
|
||||||
export function listChatHistoryOptions(
|
export function listChatHistoryOptions(
|
||||||
query: ListChatHistoryQuery = {
|
query: ListChatHistoryQuery = {
|
||||||
perPage: '20',
|
|
||||||
currPage: '1',
|
|
||||||
query: '',
|
query: '',
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return queryOptions({
|
return infiniteQueryOptions({
|
||||||
queryKey: ['list-chat-history', query],
|
queryKey: ['list-chat-history', query],
|
||||||
queryFn: () => {
|
queryFn: ({ pageParam }) => {
|
||||||
return httpGet<ListChatHistoryResponse>('/v1-list-chat-history', {
|
return httpGet<ListChatHistoryResponse>('/v1-list-chat-history', {
|
||||||
...(query?.query ? { query: query.query } : {}),
|
...(query?.query ? { query: query.query } : {}),
|
||||||
...(query?.perPage ? { perPage: query.perPage } : {}),
|
...(pageParam ? { currPage: pageParam } : {}),
|
||||||
...(query?.currPage ? { currPage: query.currPage } : {}),
|
perPage: '21',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
enabled: !!isLoggedIn(),
|
enabled: !!isLoggedIn(),
|
||||||
|
getNextPageParam: (lastPage, pages) => {
|
||||||
|
return lastPage.currPage < lastPage.totalPages
|
||||||
|
? lastPage.currPage + 1
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user