1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 08:35:42 +02:00
This commit is contained in:
Arik Chakma
2025-06-20 18:32:22 +06:00
parent 2364eb9725
commit ea1df049a5
2 changed files with 103 additions and 67 deletions

View File

@@ -4,7 +4,7 @@ import {
SparklesIcon,
type LucideIcon,
} from 'lucide-react';
import { useEffect, useId, useState, type FormEvent } from 'react';
import { useEffect, useId, useState } from 'react';
import { FormatItem } from './FormatItem';
import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup';
@@ -57,8 +57,7 @@ export function ContentGenerator() {
},
];
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const handleSubmit = () => {
if (!isLoggedIn()) {
showLoginPopup();
return;
@@ -113,7 +112,13 @@ export function ContentGenerator() {
</p>
</div>
<form className="mt-10 space-y-4" onSubmit={handleSubmit}>
<form
className="mt-10 space-y-4"
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
<div className="flex flex-col gap-2">
<label htmlFor={titleFieldId} className="inline-block text-gray-500">
What can I help you learn?
@@ -184,6 +189,9 @@ export function ContentGenerator() {
format={selectedFormat}
questionAnswerChatMessages={questionAnswerChatMessages}
setQuestionAnswerChatMessages={setQuestionAnswerChatMessages}
onGenerateNow={() => {
handleSubmit();
}}
/>
)}

View File

@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { queryClient } from '../../stores/query-client';
import { aiQuestionSuggestionsOptions } from '../../queries/user-ai-session';
import type { AllowedFormat } from './ContentGenerator';
import { Loader2Icon, SendIcon } from 'lucide-react';
import { Loader2Icon, RotateCwIcon, SendIcon } from 'lucide-react';
import { useRef, useState } from 'react';
import { cn } from '../../lib/classname';
import { flushSync } from 'react-dom';
@@ -23,6 +23,7 @@ type QuestionAnswerChatProps = {
setQuestionAnswerChatMessages: (
messages: QuestionAnswerChatMessage[],
) => void;
onGenerateNow: () => void;
};
export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
@@ -31,6 +32,7 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
format,
questionAnswerChatMessages,
setQuestionAnswerChatMessages,
onGenerateNow,
} = props;
const [activeMessageIndex, setActiveMessageIndex] = useState(0);
@@ -94,11 +96,18 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
const canGenerateNow =
// user can generate after answering 5 questions -> 5 * 2 messages (user and assistant)
!isLoadingAiQuestionSuggestions && questionAnswerChatMessages.length > 10;
!isLoadingAiQuestionSuggestions && questionAnswerChatMessages.length >= 10;
const canReset = questionAnswerChatMessages.length >= 2;
const handleReset = () => {
setQuestionAnswerChatMessages([]);
setActiveMessageIndex(0);
setStatus('answering');
};
return (
<>
<div className="relative h-[300px] w-full overflow-hidden rounded-xl border border-gray-200 bg-white">
<div className="relative h-[420px] w-full overflow-hidden rounded-xl border border-gray-200 bg-white">
{isLoadingAiQuestionSuggestions && (
<div className="absolute inset-0 flex items-center justify-center bg-white">
<div className="flex animate-pulse items-center gap-2 rounded-full border border-gray-200 bg-gray-50 p-2 px-4 text-sm">
@@ -121,35 +130,53 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
)}
{!isLoadingAiQuestionSuggestions && status === 'answering' && (
<div className="flex h-full w-full flex-col bg-white">
<div
ref={scrollAreaRef}
className="relative h-full w-full grow overflow-y-auto"
>
<div className="absolute inset-0 flex flex-col">
<div className="flex w-full grow flex-col justify-end gap-2 p-2">
{questionAnswerChatMessages.map((message, index) => (
<>
{canReset && (
<div className="absolute top-2 left-2 z-10">
<button
className="flex cursor-pointer items-center gap-1.5 rounded-lg bg-gray-50 px-2 py-1 text-xs text-gray-500 hover:bg-gray-200 focus:outline-none"
onClick={handleReset}
>
<RotateCwIcon className="size-3" />
Reset and Restart Asking
</button>
</div>
)}
<div className="flex h-full w-full flex-col bg-white">
<div
ref={scrollAreaRef}
className="relative h-full w-full grow overflow-y-auto"
>
<div className="absolute inset-0 flex flex-col">
<div className="flex w-full grow flex-col justify-end gap-2 p-2">
{questionAnswerChatMessages.map((message, index) => (
<QuestionAnswerChatMessage
key={index}
role={message.role}
question={
message.role === 'assistant'
? message.question
: undefined
}
answer={
message.role === 'user' ? message.answer : undefined
}
/>
))}
<QuestionAnswerChatMessage
key={index}
role={message.role}
question={
message.role === 'assistant'
? message.question
: undefined
}
answer={
message.role === 'user' ? message.answer : undefined
}
role="assistant"
question={activeMessage?.question ?? ''}
/>
))}
<QuestionAnswerChatMessage
role="assistant"
question={activeMessage?.question ?? ''}
/>
</div>
</div>
</div>
<div className="p-2">
<div className="rounded-lg border border-gray-200 bg-white">
{activeMessage && (
<div>
<div className="border-b border-gray-200 p-2">
<p className="text-sm text-gray-500">
Pick an answer from these or write it below
</p>
@@ -158,7 +185,7 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
<button
type="button"
key={answer}
className="cursor-pointer rounded-lg bg-gray-100 p-1 px-2 hover:bg-gray-200"
className="cursor-pointer rounded-lg bg-gray-100 p-1 px-2 hover:bg-gray-200 focus:outline-none"
onClick={() => {
handleAnswerSelect(answer);
}}
@@ -169,42 +196,40 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
</div>
</div>
)}
<div
className="flex w-full items-center justify-between gap-2 p-2"
onSubmit={(e) => {
e.preventDefault();
handleAnswerSelect(message);
}}
>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full bg-transparent text-sm focus:outline-none"
placeholder="Write your answer here..."
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleAnswerSelect(message);
setMessage('');
}
}}
/>
<button
type="button"
className="flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-gray-100 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
>
<SendIcon className="size-4" />
</button>
</div>
</div>
</div>
</div>
<div className="p-2">
<div
className="flex w-full items-center justify-between gap-2 rounded-lg border border-gray-200 bg-white p-2"
onSubmit={(e) => {
e.preventDefault();
handleAnswerSelect(message);
}}
>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full bg-transparent text-sm focus:outline-none"
placeholder="Write your answer here..."
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleAnswerSelect(message);
setMessage('');
}
}}
/>
<button
type="button"
className="flex size-7 shrink-0 items-center justify-center rounded-md hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
>
<SendIcon className="size-4" />
</button>
</div>
</div>
</div>
</>
)}
</div>
@@ -212,7 +237,10 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
<div className="flex w-full items-center rounded-lg border border-gray-200 bg-white p-2">
<p className="text-sm">
Keep answering for better output or{' '}
<button className="text-blue-500 underline underline-offset-2 hover:no-underline focus:outline-none">
<button
className="text-blue-500 underline underline-offset-2 hover:no-underline focus:outline-none"
onClick={onGenerateNow}
>
Generate now.
</button>
</p>