mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-25 08:35:42 +02:00
wip
This commit is contained in:
@@ -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();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user