1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-09 16:53:33 +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 "enabled": false
}, },
"_variables": { "_variables": {
"lastUpdateCheck": 1747060270496 "lastUpdateCheck": 1748277554631
} }
} }

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

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

View File

@@ -56,7 +56,7 @@ export function ChatEditor(props: ChatEditorProps) {
content, content,
editorProps: { editorProps: {
attributes: { attributes: {
class: 'focus:outline-none w-full p-2', class: 'focus:outline-none w-full px-4 py-2 min-h-[40px]',
}, },
handleKeyDown(_, event) { handleKeyDown(_, event) {
if (!editor) { if (!editor) {
@@ -115,7 +115,7 @@ export function ChatEditor(props: ChatEditorProps) {
}, [editor, roadmapTreeData, roadmapDetailsData]); }, [editor, roadmapTreeData, roadmapDetailsData]);
return ( return (
<div className="chat-editor w-full px-2 py-1.5"> <div className="chat-editor w-full py-1.5">
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </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 { billingDetailsOptions } from '../../queries/billing';
import { TopicDetail } from '../TopicDetail/TopicDetail'; import { TopicDetail } from '../TopicDetail/TopicDetail';
import { slugify } from '../../lib/slugger'; import { slugify } from '../../lib/slugger';
import { AIChatActionButtons } from './AIChatActionButtons';
export type RoamdapAIChatHistoryType = { export type RoamdapAIChatHistoryType = {
role: AllowedAIChatRole; role: AllowedAIChatRole;
@@ -483,91 +484,102 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
</div> </div>
{!isLoading && ( {!isLoading && (
<div className="relative flex items-start border-t border-gray-200 text-sm"> <div className="flex flex-col border-t border-gray-200">
<ChatEditor {!isLimitExceeded && (
editorRef={editorRef} <AIChatActionButtons
roadmapId={roadmapId} onTellUsAboutYourSelf={() => {}}
onSubmit={(content) => { onClearChat={() => {
if ( setAiChatHistory([]);
isStreamingMessage || }}
abortControllerRef.current || />
!isLoggedIn() || )}
isDataLoading ||
isEmptyContent(content)
) {
return;
}
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() && ( handleChatSubmit(content);
<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} {isLimitExceeded && isLoggedIn() && (
/> <div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
<p className="cursor-not-allowed"> <LockIcon
Limit reached for today className="size-4 cursor-not-allowed"
{isPaidUser ? '. Please wait until tomorrow.' : ''} strokeWidth={2.5}
</p> />
{!isPaidUser && ( <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 <button
onClick={() => { onClick={() => {
setShowUpgradeModal(true); showLoginPopup();
}} }}
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"
> >
Upgrade for more Login / Register
</button> </button>
)} </div>
</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]" />
)} )}
</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> </div>
)} )}
</> </>

View File

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