1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-03 14:22:41 +02:00

Improve preferences

This commit is contained in:
Kamran Ahmed
2025-06-24 19:03:06 +01:00
parent ab761c792e
commit aad48d98dc
3 changed files with 76 additions and 35 deletions

View File

@@ -6,14 +6,15 @@ import {
} from '../../queries/user-ai-session'; } from '../../queries/user-ai-session';
import type { AllowedFormat } from './ContentGenerator'; import type { AllowedFormat } from './ContentGenerator';
import { import {
Loader2Icon, RefreshCcwIcon, Loader2Icon,
RotateCwIcon, RefreshCcwIcon,
SendIcon SendIcon, Trash2
} from 'lucide-react'; } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { flushSync } from 'react-dom'; import { flushSync } from 'react-dom';
import { CheckIcon } from '../ReactIcons/CheckIcon'; import { CheckIcon } from '../ReactIcons/CheckIcon';
import { Tooltip } from '../Tooltip';
export type QuestionAnswerChatMessage = export type QuestionAnswerChatMessage =
| { role: 'user'; answer: string } | { role: 'user'; answer: string }
@@ -127,6 +128,30 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
setStatus('answering'); setStatus('answering');
}; };
const handleEditMessage = (messageIndex: number) => {
// Remove the assistant question and user answer pair
// Since user messages are at odd indices, we want to remove both
// the assistant message (at messageIndex - 1) and the user message (at messageIndex)
const assistantMessageIndex = messageIndex - 1;
const newMessages = questionAnswerChatMessages.slice(
0,
assistantMessageIndex,
);
setQuestionAnswerChatMessages(newMessages);
// Calculate which question should be active
// Since we removed both assistant and user messages, the question index
// is simply assistantMessageIndex / 2
const questionIndex = Math.floor(assistantMessageIndex / 2);
setActiveMessageIndex(questionIndex);
setStatus('answering');
setMessage('');
setTimeout(() => {
inputRef.current?.focus();
}, 0);
};
useEffect(() => { useEffect(() => {
scrollToBottom(); scrollToBottom();
}, [defaultQuestions, type]); }, [defaultQuestions, type]);
@@ -152,7 +177,7 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
<div className="absolute inset-0 flex items-center justify-center bg-white"> <div className="absolute inset-0 flex items-center justify-center bg-white">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<CheckIcon additionalClasses="size-12" /> <CheckIcon additionalClasses="size-12" />
<p className="mt-3 font-semibold text-lg">Preferences saved</p> <p className="mt-3 text-lg font-semibold">Preferences saved</p>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
You can now start generating {format} You can now start generating {format}
</p> </p>
@@ -170,18 +195,6 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
{!isLoadingAiQuestionSuggestions && status === 'answering' && ( {!isLoadingAiQuestionSuggestions && status === 'answering' && (
<> <>
{canReset && type === 'create' && (
<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 className="flex h-full w-full flex-col bg-white">
<div <div
ref={scrollAreaRef} ref={scrollAreaRef}
@@ -201,6 +214,11 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
answer={ answer={
message.role === 'user' ? message.answer : undefined message.role === 'user' ? message.answer : undefined
} }
onEdit={
message.role === 'user'
? () => handleEditMessage(index)
: undefined
}
/> />
))} ))}
@@ -219,15 +237,15 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
{!activeMessage && type === 'update' && ( {!activeMessage && type === 'update' && (
<div className="p-2"> <div className="p-2">
<button <button
className="flex w-full items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white p-2" className="flex w-full items-center justify-center gap-2 rounded-lg border border-gray-200 bg-gray-200 p-2 hover:bg-gray-300"
onClick={() => { onClick={() => {
setQuestionAnswerChatMessages([]); setQuestionAnswerChatMessages([]);
setActiveMessageIndex(0); setActiveMessageIndex(0);
setStatus('answering'); setStatus('answering');
}} }}
> >
<RefreshCcwIcon className="size-4" /> <Trash2 className="size-4" />
Reanswer the questions Reanswer all questions
</button> </button>
</div> </div>
)} )}
@@ -281,10 +299,12 @@ type QuestionAnswerChatMessageProps = {
answer?: string; answer?: string;
possibleAnswers?: string[]; possibleAnswers?: string[];
onAnswerSelect?: (answer: string) => void; onAnswerSelect?: (answer: string) => void;
onEdit?: () => void;
}; };
function QuestionAnswerChatMessage(props: QuestionAnswerChatMessageProps) { function QuestionAnswerChatMessage(props: QuestionAnswerChatMessageProps) {
const { role, question, answer, possibleAnswers, onAnswerSelect } = props; const { role, question, answer, possibleAnswers, onAnswerSelect, onEdit } =
props;
const hasAnswers = possibleAnswers && possibleAnswers.length > 0; const hasAnswers = possibleAnswers && possibleAnswers.length > 0;
@@ -319,7 +339,25 @@ function QuestionAnswerChatMessage(props: QuestionAnswerChatMessageProps) {
)} )}
</div> </div>
)} )}
{role === 'user' && <div className="text-sm">{answer}</div>} {role === 'user' && (
<div className="flex items-center gap-2">
<div className="text-sm">{answer}</div>
{onEdit && (
<div className="group relative">
<button
type="button"
className="flex size-6 shrink-0 items-center justify-center rounded-md opacity-70 hover:bg-gray-100 hover:opacity-100 focus:outline-none text-gray-500"
onClick={onEdit}
>
<Trash2 className="size-4" />
</button>
<Tooltip additionalClass="-translate-y-2" position="top-right">
Reanswer after this point
</Tooltip>
</div>
)}
</div>
)}
</div> </div>
); );
} }

View File

@@ -1,16 +1,15 @@
import { PenSquare, RefreshCcw, SettingsIcon } from 'lucide-react'; import { useMutation, useQuery } from '@tanstack/react-query';
import { PenSquare, RefreshCcw, Settings2Icon } from 'lucide-react';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { useOutsideClick } from '../../hooks/use-outside-click'; import { useOutsideClick } from '../../hooks/use-outside-click';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
import { queryClient } from '../../stores/query-client';
import { getAiCourseOptions } from '../../queries/ai-course';
import { useMutation, useQuery } from '@tanstack/react-query';
import { httpPost } from '../../lib/query-http'; import { httpPost } from '../../lib/query-http';
import { getAiCourseOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat'; import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
import { getAiGuideOptions } from '../../queries/ai-guide';
import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences'; import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
type RegenerateOutlineProps = { type RegenerateOutlineProps = {
onRegenerateOutline: (prompt?: string) => void; onRegenerateOutline: (prompt?: string) => void;
@@ -121,12 +120,12 @@ export function RegenerateOutline(props: RegenerateOutlineProps) {
}} }}
className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100" className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
> >
<SettingsIcon <Settings2Icon
size={16} size={16}
className="text-gray-400" className="text-gray-400"
strokeWidth={2.5} strokeWidth={2.5}
/> />
Update Preferences Preferences
</button> </button>
<button <button

View File

@@ -1,5 +1,4 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import type { AIQuestionSuggestionsResponse } from '../../queries/user-ai-session';
import type { AllowedFormat } from '../ContentGenerator/ContentGenerator'; import type { AllowedFormat } from '../ContentGenerator/ContentGenerator';
import { import {
QuestionAnswerChat, QuestionAnswerChat,
@@ -46,16 +45,20 @@ export function UpdatePreferences(props: UpdatePreferencesProps) {
); );
}, [questionAnswerChatMessages, defaultQuestionAndAnswers]); }, [questionAnswerChatMessages, defaultQuestionAndAnswers]);
console.log(questionAnswerChatMessages);
console.log(defaultQuestionAndAnswers);
return ( return (
<Modal <Modal
onClose={onClose} onClose={onClose}
bodyClassName="p-4 flex flex-col gap-4" bodyClassName="p-4 flex flex-col gap-4"
wrapperClassName="max-w-xl" wrapperClassName="max-w-xl h-auto"
overlayClassName="items-start md:items-center"
> >
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h2 className="text-lg font-medium">Update Preferences</h2> <h2 className="text-lg font-medium">Update Preferences</h2>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Update the preferences for the AI to generate a guide. Update your preferences for better content
</p> </p>
</div> </div>
@@ -68,18 +71,19 @@ export function UpdatePreferences(props: UpdatePreferencesProps) {
onGenerateNow={() => { onGenerateNow={() => {
onUpdatePreferences(questionAnswerChatMessages); onUpdatePreferences(questionAnswerChatMessages);
}} }}
className="-mx-2 h-[400px] border-none p-0"
type="update" type="update"
/> />
{hasChangedQuestionAndAnswers && ( {hasChangedQuestionAndAnswers && (
<button <button
className="rounded-lg bg-black px-4 py-2 text-white disabled:opacity-50" className="rounded-lg bg-black px-4 py-2 text-white hover:opacity-80 disabled:opacity-50"
disabled={isUpdating || !hasChangedQuestionAndAnswers} disabled={isUpdating || !hasChangedQuestionAndAnswers}
onClick={() => { onClick={() => {
onUpdatePreferences(questionAnswerChatMessages); onUpdatePreferences(questionAnswerChatMessages);
}} }}
> >
{isUpdating ? 'Updating...' : 'Update Preferences'} {isUpdating ? 'Updating...' : 'Apply preferences'}
</button> </button>
)} )}
</Modal> </Modal>