wip: default questions

feat/chat
Arik Chakma 4 weeks ago
parent af0a3a5057
commit 53359bbd87
  1. 34
      src/components/GenerateCourse/AICourseLesson.tsx
  2. 42
      src/components/GenerateCourse/AICourseLessonChat.tsx

@ -37,6 +37,18 @@ import {
ResizablePanelGroup,
} from './Resizeable';
function getQuestionsFromResult(result: string) {
const matchedQuestions = result.match(
/=START_QUESTIONS=(.*?)=END_QUESTIONS=/,
);
if (matchedQuestions) {
return matchedQuestions[1].split('@@');
}
return [];
}
type AICourseLessonProps = {
courseSlug: string;
progress: string[];
@ -82,6 +94,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
const [isLoading, setIsLoading] = useState(true);
const [isGenerating, setIsGenerating] = useState(false);
const [error, setError] = useState('');
const [defaultQuestions, setDefaultQuestions] = useState<string[]>([]);
const [lessonHtml, setLessonHtml] = useState('');
@ -163,14 +176,29 @@ export function AICourseLesson(props: AICourseLessonProps) {
return;
}
setLessonHtml(markdownToHtml(result, false));
const questions = getQuestionsFromResult(result);
setDefaultQuestions(questions);
const newResult = result.replace(
/=START_QUESTIONS=.*?=END_QUESTIONS=/,
'',
);
setLessonHtml(markdownToHtml(newResult, false));
},
onStreamEnd: async (result) => {
if (abortController.signal.aborted) {
return;
}
setLessonHtml(await markdownToHtmlWithHighlighting(result));
const questions = getQuestionsFromResult(result);
setDefaultQuestions(questions);
const newResult = result.replace(
/=START_QUESTIONS=.*?=END_QUESTIONS=/,
'',
);
setLessonHtml(await markdownToHtmlWithHighlighting(newResult));
queryClient.invalidateQueries(getAiCourseLimitOptions());
setIsGenerating(false);
},
@ -445,6 +473,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
lessonTitle={currentLessonTitle}
onUpgradeClick={onUpgrade}
isDisabled={isGenerating || isLoading || isTogglingDone}
defaultQuestions={defaultQuestions}
/>
</ResizablePanel>
</>
@ -462,6 +491,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
lessonTitle={currentLessonTitle}
onUpgradeClick={onUpgrade}
isDisabled={isGenerating || isLoading || isTogglingDone}
defaultQuestions={defaultQuestions}
/>
<button

@ -6,7 +6,6 @@ import {
HelpCircle,
LockIcon,
Send,
XIcon,
} from 'lucide-react';
import {
Fragment,
@ -19,7 +18,6 @@ import {
} from 'react';
import { flushSync } from 'react-dom';
import TextareaAutosize from 'react-textarea-autosize';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { useToast } from '../../hooks/use-toast';
import { readStream } from '../../lib/ai';
import { cn } from '../../lib/classname';
@ -46,14 +44,23 @@ type AICourseLessonChatProps = {
lessonTitle: string;
onUpgradeClick: () => void;
isDisabled?: boolean;
defaultQuestions?: string[];
};
export function AICourseLessonChat(props: AICourseLessonChatProps) {
const { courseSlug, moduleTitle, lessonTitle, onUpgradeClick, isDisabled } =
props;
const {
courseSlug,
moduleTitle,
lessonTitle,
onUpgradeClick,
isDisabled,
defaultQuestions = [],
} = props;
const toast = useToast();
const scrollareaRef = useRef<HTMLDivElement | null>(null);
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const [courseAIChatHistory, setCourseAIChatHistory] = useState<
AIChatHistoryType[]
@ -197,7 +204,7 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
return (
<>
<div className="relative h-full border-l border-gray-200">
<div className="relative h-full">
<div className="absolute inset-y-0 right-0 z-20 flex w-full flex-col overflow-hidden bg-white">
<div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm">
<h4 className="text-base font-medium">Course AI</h4>
@ -219,7 +226,7 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
html={chat.html}
/>
{chat.isDefault && (
{chat.isDefault && !defaultQuestions?.length && (
<div className="mb-1 mt-0.5">
<div className="grid grid-cols-2 gap-2">
{capabilities.map((capability, index) => (
@ -231,6 +238,28 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
</div>
</div>
)}
{chat.isDefault && defaultQuestions?.length > 1 && (
<div className="mb-1 mt-0.5">
<div className="grid grid-cols-2 items-stretch gap-2">
{defaultQuestions.map((question, index) => (
<button
key={`default-question-${index}`}
className="rounded-md bg-yellow-500/10 p-2 text-left text-sm text-black"
onClick={() => {
flushSync(() => {
setMessage(question);
});
textareaRef.current?.focus();
}}
>
{question}
</button>
))}
</div>
</div>
)}
</Fragment>
);
})}
@ -287,6 +316,7 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
handleChatSubmit(e as unknown as FormEvent<HTMLFormElement>);
}
}}
ref={textareaRef}
/>
<button
type="submit"

Loading…
Cancel
Save