mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-06 23:30:42 +02:00
wip
This commit is contained in:
@@ -2,8 +2,9 @@ import type { FormEvent } from 'react';
|
||||
import { useId, useState } from 'react';
|
||||
import { httpPost } from '../../lib/http';
|
||||
import {
|
||||
COURSE_PURCHASE_PARAM, FIRST_LOGIN_PARAM,
|
||||
setAuthToken
|
||||
COURSE_PURCHASE_PARAM,
|
||||
FIRST_LOGIN_PARAM,
|
||||
setAuthToken,
|
||||
} from '../../lib/jwt';
|
||||
|
||||
type EmailLoginFormProps = {
|
||||
|
@@ -6,7 +6,7 @@ import { AuthenticationForm } from './AuthenticationForm';
|
||||
|
||||
<Popup id='login-popup' title='' subtitle=''>
|
||||
<div class='mb-7 text-center'>
|
||||
<p class='mb-3 text-2xl font-semibold leading-5 text-slate-900'>
|
||||
<p class='mb-3 text-2xl leading-5 font-semibold text-slate-900'>
|
||||
Login or Signup
|
||||
</p>
|
||||
<p class='mt-2 text-sm leading-4 text-slate-600'>
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import './RoadmapAIChat.css';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
roadmapJSONOptions
|
||||
} from '../../queries/roadmap';
|
||||
import { roadmapJSONOptions } from '../../queries/roadmap';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import {
|
||||
Fragment,
|
||||
@@ -13,7 +11,13 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { BotIcon, Frown, Loader2Icon, PauseCircleIcon, SendIcon } from 'lucide-react';
|
||||
import {
|
||||
BotIcon,
|
||||
Frown,
|
||||
Loader2Icon,
|
||||
PauseCircleIcon,
|
||||
SendIcon,
|
||||
} from 'lucide-react';
|
||||
import { ChatEditor } from '../ChatEditor/ChatEditor';
|
||||
import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
|
||||
import { type AllowedAIChatRole } from '../GenerateCourse/AICourseLessonChat';
|
||||
@@ -352,24 +356,17 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form
|
||||
className="relative flex items-start border-t border-gray-200 text-sm"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (isStreamingMessage && abortControllerRef.current) {
|
||||
handleAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
handleChatSubmit(editorRef.current?.getJSON() || {});
|
||||
}}
|
||||
>
|
||||
<div className="relative flex items-start border-t border-gray-200 text-sm">
|
||||
<ChatEditor
|
||||
editorRef={editorRef}
|
||||
roadmapId={roadmapId}
|
||||
onSubmit={(content) => {
|
||||
if (isStreamingMessage && abortControllerRef.current) {
|
||||
handleAbort();
|
||||
if (isStreamingMessage || abortControllerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEmptyContent(content)) {
|
||||
toast.error('Please enter a message');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -378,8 +375,15 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="flex aspect-square size-[36px] items-center justify-center p-2 text-zinc-500 hover:text-black disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={(e) => {
|
||||
if (isStreamingMessage || abortControllerRef.current) {
|
||||
handleAbort();
|
||||
return;
|
||||
}
|
||||
|
||||
handleChatSubmit(editorRef.current?.getJSON() || {});
|
||||
}}
|
||||
>
|
||||
{isStreamingMessage ? (
|
||||
<PauseCircleIcon className="size-4 stroke-[2.5]" />
|
||||
@@ -387,12 +391,29 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
|
||||
<SendIcon className="size-4 stroke-[2.5]" />
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function isEmptyContent(content: JSONContent) {
|
||||
if (!content) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// because they wrap the content in type doc
|
||||
const firstContent = content.content?.[0];
|
||||
if (!firstContent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
firstContent.type === 'paragraph' &&
|
||||
(!firstContent?.content || firstContent?.content?.length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
export function htmlFromTiptapJSON(json: JSONContent) {
|
||||
const content = json.content;
|
||||
|
||||
|
@@ -136,6 +136,8 @@ export function UserProgressActionList(props: UserProgressActionListProps) {
|
||||
? progressItemWithText
|
||||
: progressItemWithText.slice(0, itemCountToShow);
|
||||
|
||||
const hasMoreItemsToShow = progressItemWithText.length > itemCountToShow;
|
||||
|
||||
return (
|
||||
<div className="relative my-6 overflow-hidden rounded-lg border border-gray-200 bg-white p-2 first:mt-0 last:mb-0">
|
||||
<div className="relative flex flex-col gap-2">
|
||||
@@ -151,7 +153,7 @@ export function UserProgressActionList(props: UserProgressActionListProps) {
|
||||
/>
|
||||
))}
|
||||
|
||||
{progressItemWithText.length > itemCountToShow && (
|
||||
{hasMoreItemsToShow && (
|
||||
<div className="absolute inset-x-0 right-0 bottom-0.5 translate-y-1/2">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<button
|
||||
@@ -167,37 +169,41 @@ export function UserProgressActionList(props: UserProgressActionListProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="absolute top-0 right-0">
|
||||
<button
|
||||
className="flex items-center gap-1 rounded-b-md bg-green-100 px-2 py-1 text-[10px] leading-none font-medium text-green-600"
|
||||
disabled={isBulkUpdating}
|
||||
onClick={() => {
|
||||
const done = updateUserProgress
|
||||
.filter((item) => item.action === 'done')
|
||||
.map((item) => item.id);
|
||||
const learning = updateUserProgress
|
||||
.filter((item) => item.action === 'learning')
|
||||
.map((item) => item.id);
|
||||
const skipped = updateUserProgress
|
||||
.filter((item) => item.action === 'skipped')
|
||||
.map((item) => item.id);
|
||||
const pending = updateUserProgress
|
||||
.filter((item) => item.action === 'pending')
|
||||
.map((item) => item.id);
|
||||
{hasMoreItemsToShow && (
|
||||
<div className="absolute top-0 right-0">
|
||||
<button
|
||||
className="flex items-center gap-1 rounded-b-md bg-green-100 px-2 py-1 text-[10px] leading-none font-medium text-green-600"
|
||||
disabled={isBulkUpdating}
|
||||
onClick={() => {
|
||||
const done = updateUserProgress
|
||||
.filter((item) => item.action === 'done')
|
||||
.map((item) => item.id);
|
||||
const learning = updateUserProgress
|
||||
.filter((item) => item.action === 'learning')
|
||||
.map((item) => item.id);
|
||||
const skipped = updateUserProgress
|
||||
.filter((item) => item.action === 'skipped')
|
||||
.map((item) => item.id);
|
||||
const pending = updateUserProgress
|
||||
.filter((item) => item.action === 'pending')
|
||||
.map((item) => item.id);
|
||||
|
||||
bulkUpdateResourceProgress({
|
||||
done,
|
||||
learning,
|
||||
skipped,
|
||||
pending,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isBulkUpdating && <Loader2Icon className="size-2.5 animate-spin" />}
|
||||
{!isBulkUpdating && <CheckIcon additionalClasses="size-2.5" />}
|
||||
Apply All
|
||||
</button>
|
||||
</div>
|
||||
bulkUpdateResourceProgress({
|
||||
done,
|
||||
learning,
|
||||
skipped,
|
||||
pending,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isBulkUpdating && (
|
||||
<Loader2Icon className="size-2.5 animate-spin" />
|
||||
)}
|
||||
{!isBulkUpdating && <CheckIcon additionalClasses="size-2.5" />}
|
||||
Apply All
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
27
src/pages/ai/chat/index.astro
Normal file
27
src/pages/ai/chat/index.astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import { CheckSubscriptionVerification } from '../../../components/Billing/CheckSubscriptionVerification';
|
||||
import { RoadmapAIChat } from '../../../components/RoadmapAIChat/RoadmapAIChat';
|
||||
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
||||
|
||||
type Props = {
|
||||
roadmapId: string;
|
||||
};
|
||||
|
||||
const { roadmapId } = Astro.params as Props;
|
||||
---
|
||||
|
||||
<SkeletonLayout
|
||||
title='Roadmap AI Chat'
|
||||
noIndex={true}
|
||||
description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
|
||||
>
|
||||
<AITutorLayout
|
||||
activeTab='chat'
|
||||
wrapperClassName='flex-row p-0 lg:p-0 overflow-hidden'
|
||||
client:load
|
||||
>
|
||||
<h1>Roadmap AI Chat</h1>
|
||||
<CheckSubscriptionVerification client:load />
|
||||
</AITutorLayout>
|
||||
</SkeletonLayout>
|
Reference in New Issue
Block a user