1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-08 16:20:40 +02:00

Changes to ai chat

This commit is contained in:
Kamran Ahmed
2025-05-26 17:48:26 +01:00
parent d3510b5aa6
commit 62f2047db6
6 changed files with 141 additions and 100 deletions

View File

@@ -3,6 +3,6 @@
"enabled": false
},
"_variables": {
"lastUpdateCheck": 1747060270496
"lastUpdateCheck": 1748277554631
}
}

1
.astro/types.d.ts vendored
View File

@@ -1,2 +1 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -56,7 +56,7 @@ export function ChatEditor(props: ChatEditorProps) {
content,
editorProps: {
attributes: {
class: 'focus:outline-none w-full p-2',
class: 'focus:outline-none w-full px-4 py-2 min-h-[40px]',
},
handleKeyDown(_, event) {
if (!editor) {
@@ -115,7 +115,7 @@ export function ChatEditor(props: ChatEditorProps) {
}, [editor, roadmapTreeData, roadmapDetailsData]);
return (
<div className="chat-editor w-full px-2 py-1.5">
<div className="chat-editor w-full py-1.5">
<EditorContent editor={editor} />
</div>
);

View File

@@ -0,0 +1,45 @@
import { SettingsIcon, Trash2, type LucideIcon } from 'lucide-react';
type AIChatActionButtonProps = {
icon: LucideIcon;
label: string;
onClick: () => void;
};
function AIChatActionButton(props: AIChatActionButtonProps) {
const { icon: Icon, label, onClick } = props;
return (
<button
className="flex hover:bg-gray-100 items-center gap-1 rounded-md border border-gray-200 px-2 py-1.5 text-xs"
onClick={onClick}
>
<Icon className="size-3" />
<span>{label}</span>
</button>
);
}
type AIChatActionButtonsProps = {
onTellUsAboutYourSelf: () => void;
onClearChat: () => void;
};
export function AIChatActionButtons(props: AIChatActionButtonsProps) {
const { onTellUsAboutYourSelf, onClearChat } = props;
return (
<div className="flex gap-2 px-4 pt-2">
<AIChatActionButton
icon={SettingsIcon}
label="Tell us about your self"
onClick={onTellUsAboutYourSelf}
/>
<AIChatActionButton
icon={Trash2}
label="Clear chat"
onClick={onClearChat}
/>
</div>
);
}

View File

@@ -45,6 +45,7 @@ import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { billingDetailsOptions } from '../../queries/billing';
import { TopicDetail } from '../TopicDetail/TopicDetail';
import { slugify } from '../../lib/slugger';
import { AIChatActionButtons } from './AIChatActionButtons';
export type RoamdapAIChatHistoryType = {
role: AllowedAIChatRole;
@@ -483,91 +484,102 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
</div>
{!isLoading && (
<div className="relative flex items-start border-t border-gray-200 text-sm">
<ChatEditor
editorRef={editorRef}
roadmapId={roadmapId}
onSubmit={(content) => {
if (
isStreamingMessage ||
abortControllerRef.current ||
!isLoggedIn() ||
isDataLoading ||
isEmptyContent(content)
) {
return;
}
<div className="flex flex-col border-t border-gray-200">
{!isLimitExceeded && (
<AIChatActionButtons
onTellUsAboutYourSelf={() => {}}
onClearChat={() => {
setAiChatHistory([]);
}}
/>
)}
handleChatSubmit(content);
}}
/>
<div className="relative flex items-start text-sm">
<ChatEditor
editorRef={editorRef}
roadmapId={roadmapId}
onSubmit={(content) => {
if (
isStreamingMessage ||
abortControllerRef.current ||
!isLoggedIn() ||
isDataLoading ||
isEmptyContent(content)
) {
return;
}
{isLimitExceeded && isLoggedIn() && (
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
<LockIcon
className="size-4 cursor-not-allowed"
strokeWidth={2.5}
/>
<p className="cursor-not-allowed">
Limit reached for today
{isPaidUser ? '. Please wait until tomorrow.' : ''}
</p>
{!isPaidUser && (
handleChatSubmit(content);
}}
/>
{isLimitExceeded && isLoggedIn() && (
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
<LockIcon
className="size-4 cursor-not-allowed"
strokeWidth={2.5}
/>
<p className="cursor-not-allowed">
Limit reached for today
{isPaidUser ? '. Please wait until tomorrow.' : ''}
</p>
{!isPaidUser && (
<button
onClick={() => {
setShowUpgradeModal(true);
}}
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
>
Upgrade for more
</button>
)}
</div>
)}
{!isLoggedIn() && (
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
<LockIcon
className="size-4 cursor-not-allowed"
strokeWidth={2.5}
/>
<p className="cursor-not-allowed">
Please login to continue
</p>
<button
onClick={() => {
setShowUpgradeModal(true);
showLoginPopup();
}}
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
>
Upgrade for more
Login / Register
</button>
)}
</div>
)}
{!isLoggedIn() && (
<div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
<LockIcon
className="size-4 cursor-not-allowed"
strokeWidth={2.5}
/>
<p className="cursor-not-allowed">
Please login to continue
</p>
<button
onClick={() => {
showLoginPopup();
}}
className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
>
Login / Register
</button>
</div>
)}
<button
className="flex aspect-square size-[36px] items-center justify-center p-2 text-zinc-500 hover:text-black disabled:cursor-not-allowed disabled:opacity-50"
onClick={(e) => {
if (isStreamingMessage || abortControllerRef.current) {
handleAbort();
return;
}
const json = editorRef.current?.getJSON();
if (!json || isEmptyContent(json)) {
toast.error('Please enter a message');
return;
}
handleChatSubmit(json);
}}
>
{isStreamingMessage ? (
<PauseCircleIcon className="size-4 stroke-[2.5]" />
) : (
<SendIcon className="size-4 stroke-[2.5]" />
</div>
)}
</button>
<button
className="flex aspect-square size-[36px] items-center justify-center p-2 text-zinc-500 hover:text-black disabled:cursor-not-allowed disabled:opacity-50"
onClick={(e) => {
if (isStreamingMessage || abortControllerRef.current) {
handleAbort();
return;
}
const json = editorRef.current?.getJSON();
if (!json || isEmptyContent(json)) {
toast.error('Please enter a message');
return;
}
handleChatSubmit(json);
}}
>
{isStreamingMessage ? (
<PauseCircleIcon className="size-4 stroke-[2.5]" />
) : (
<SendIcon className="size-4 stroke-[2.5]" />
)}
</button>
</div>
</div>
)}
</>

View File

@@ -3,10 +3,9 @@ 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, Trash2Icon, XIcon } from 'lucide-react';
import { BookIcon, BotIcon, GiftIcon, XIcon } from 'lucide-react';
import type {
RoadmapAIChatTab,
RoamdapAIChatHistoryType,
RoadmapAIChatTab
} from './RoadmapAIChat';
import { useState } from 'react';
import { useToast } from '../../hooks/use-toast';
@@ -17,9 +16,6 @@ import { cn } from '../../lib/classname';
type RoadmapAIChatHeaderProps = {
isLoading: boolean;
hasChatHistory: boolean;
setAiChatHistory: (history: RoamdapAIChatHistoryType[]) => void;
onLogin: () => void;
onUpgrade: () => void;
@@ -70,8 +66,6 @@ function TabButton(props: TabButtonProps) {
export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
const {
hasChatHistory,
setAiChatHistory,
onLogin,
onUpgrade,
isLoading: isDataLoading,
@@ -127,7 +121,7 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
/>
)}
<div className="flex h-[46px] flex-shrink-0 items-center justify-between border-b border-gray-200 text-sm">
<div className="flex h-[46px] items-center justify-between border-b border-gray-200 text-sm">
<div className="flex h-full items-center">
<TabButton
icon={<BotIcon className="size-4 shrink-0 text-black" />}
@@ -149,24 +143,15 @@ export function RoadmapAIChatHeader(props: RoadmapAIChatHeaderProps) {
{!isDataLoading && (
<div className="flex gap-1.5 pr-4">
{hasChatHistory && (
<button
className="rounded-md bg-white px-2 py-2 text-xs font-medium text-black hover:bg-gray-200"
onClick={() => setAiChatHistory([])}
>
<Trash2Icon className="size-3.5" />
</button>
)}
{!isPaidUser && (
<>
{/* <button
<button
className="hidden rounded-md bg-gray-200 px-2 py-1 text-sm hover:bg-gray-300 sm:block"
onClick={handleCreditsClick}
>
<span className="font-medium">{usagePercentage}%</span>{' '}
credits used
</button> */}
</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}