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:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user