1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-02 22:02:39 +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';
import type { AllowedFormat } from './ContentGenerator';
import {
Loader2Icon, RefreshCcwIcon,
RotateCwIcon,
SendIcon
Loader2Icon,
RefreshCcwIcon,
SendIcon, Trash2
} from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '../../lib/classname';
import { flushSync } from 'react-dom';
import { CheckIcon } from '../ReactIcons/CheckIcon';
import { Tooltip } from '../Tooltip';
export type QuestionAnswerChatMessage =
| { role: 'user'; answer: string }
@@ -127,6 +128,30 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
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(() => {
scrollToBottom();
}, [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="flex flex-col items-center">
<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">
You can now start generating {format}
</p>
@@ -170,18 +195,6 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
{!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
ref={scrollAreaRef}
@@ -201,6 +214,11 @@ export function QuestionAnswerChat(props: QuestionAnswerChatProps) {
answer={
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' && (
<div className="p-2">
<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={() => {
setQuestionAnswerChatMessages([]);
setActiveMessageIndex(0);
setStatus('answering');
}}
>
<RefreshCcwIcon className="size-4" />
Reanswer the questions
<Trash2 className="size-4" />
Reanswer all questions
</button>
</div>
)}
@@ -281,10 +299,12 @@ type QuestionAnswerChatMessageProps = {
answer?: string;
possibleAnswers?: string[];
onAnswerSelect?: (answer: string) => void;
onEdit?: () => void;
};
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;
@@ -319,7 +339,25 @@ function QuestionAnswerChatMessage(props: QuestionAnswerChatMessageProps) {
)}
</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>
);
}

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 { useOutsideClick } from '../../hooks/use-outside-click';
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 { getAiCourseOptions } from '../../queries/ai-course';
import { queryClient } from '../../stores/query-client';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import type { QuestionAnswerChatMessage } from '../ContentGenerator/QuestionAnswerChat';
import { getAiGuideOptions } from '../../queries/ai-guide';
import { UpdatePreferences } from '../GenerateGuide/UpdatePreferences';
import { ModifyCoursePrompt } from './ModifyCoursePrompt';
type RegenerateOutlineProps = {
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"
>
<SettingsIcon
<Settings2Icon
size={16}
className="text-gray-400"
strokeWidth={2.5}
/>
Update Preferences
Preferences
</button>
<button

View File

@@ -1,5 +1,4 @@
import { useMemo, useState } from 'react';
import type { AIQuestionSuggestionsResponse } from '../../queries/user-ai-session';
import type { AllowedFormat } from '../ContentGenerator/ContentGenerator';
import {
QuestionAnswerChat,
@@ -46,16 +45,20 @@ export function UpdatePreferences(props: UpdatePreferencesProps) {
);
}, [questionAnswerChatMessages, defaultQuestionAndAnswers]);
console.log(questionAnswerChatMessages);
console.log(defaultQuestionAndAnswers);
return (
<Modal
onClose={onClose}
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">
<h2 className="text-lg font-medium">Update Preferences</h2>
<p className="text-sm text-gray-500">
Update the preferences for the AI to generate a guide.
Update your preferences for better content
</p>
</div>
@@ -68,18 +71,19 @@ export function UpdatePreferences(props: UpdatePreferencesProps) {
onGenerateNow={() => {
onUpdatePreferences(questionAnswerChatMessages);
}}
className="-mx-2 h-[400px] border-none p-0"
type="update"
/>
{hasChangedQuestionAndAnswers && (
<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}
onClick={() => {
onUpdatePreferences(questionAnswerChatMessages);
}}
>
{isUpdating ? 'Updating...' : 'Update Preferences'}
{isUpdating ? 'Updating...' : 'Apply preferences'}
</button>
)}
</Modal>