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:
@@ -3,6 +3,6 @@
|
|||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
"_variables": {
|
"_variables": {
|
||||||
"lastUpdateCheck": 1747060270496
|
"lastUpdateCheck": 1748277554631
|
||||||
}
|
}
|
||||||
}
|
}
|
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1,2 +1 @@
|
|||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
/// <reference path="content.d.ts" />
|
|
@@ -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>
|
||||||
);
|
);
|
||||||
|
45
src/components/RoadmapAIChat/AIChatActionButtons.tsx
Normal file
45
src/components/RoadmapAIChat/AIChatActionButtons.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@@ -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}
|
||||||
|
Reference in New Issue
Block a user