mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-08 16:20:40 +02:00
wip
This commit is contained in:
1
.astro/types.d.ts
vendored
1
.astro/types.d.ts
vendored
@@ -1,2 +1 @@
|
|||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
/// <reference path="content.d.ts" />
|
|
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -32,6 +32,9 @@ importers:
|
|||||||
'@radix-ui/react-dropdown-menu':
|
'@radix-ui/react-dropdown-menu':
|
||||||
specifier: ^2.1.15
|
specifier: ^2.1.15
|
||||||
version: 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 2.1.15(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
|
'@radix-ui/react-popover':
|
||||||
|
specifier: ^1.1.14
|
||||||
|
version: 1.1.14(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@resvg/resvg-js':
|
'@resvg/resvg-js':
|
||||||
specifier: ^2.6.2
|
specifier: ^2.6.2
|
||||||
version: 2.6.2
|
version: 2.6.2
|
||||||
@@ -270,6 +273,9 @@ importers:
|
|||||||
prettier-plugin-tailwindcss:
|
prettier-plugin-tailwindcss:
|
||||||
specifier: ^0.6.11
|
specifier: ^0.6.11
|
||||||
version: 0.6.11(prettier-plugin-astro@0.14.1)(prettier@3.5.3)
|
version: 0.6.11(prettier-plugin-astro@0.14.1)(prettier@3.5.3)
|
||||||
|
tailwind-scrollbar:
|
||||||
|
specifier: ^4.0.2
|
||||||
|
version: 4.0.2(react@19.1.0)(tailwindcss@4.1.7)
|
||||||
tsx:
|
tsx:
|
||||||
specifier: ^4.19.4
|
specifier: ^4.19.4
|
||||||
version: 4.19.4
|
version: 4.19.4
|
||||||
@@ -3923,6 +3929,11 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
prism-react-renderer@2.4.1:
|
||||||
|
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.0.0'
|
||||||
|
|
||||||
prismjs@1.30.0:
|
prismjs@1.30.0:
|
||||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -4359,6 +4370,12 @@ packages:
|
|||||||
tailwind-merge@3.3.0:
|
tailwind-merge@3.3.0:
|
||||||
resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==}
|
resolution: {integrity: sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==}
|
||||||
|
|
||||||
|
tailwind-scrollbar@4.0.2:
|
||||||
|
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
|
||||||
|
engines: {node: '>=12.13.0'}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: 4.x
|
||||||
|
|
||||||
tailwindcss@4.1.5:
|
tailwindcss@4.1.5:
|
||||||
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
|
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
|
||||||
|
|
||||||
@@ -8495,6 +8512,12 @@ snapshots:
|
|||||||
|
|
||||||
prettier@3.5.3: {}
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
|
prism-react-renderer@2.4.1(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
'@types/prismjs': 1.26.5
|
||||||
|
clsx: 2.1.1
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
prismjs@1.30.0: {}
|
prismjs@1.30.0: {}
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
@@ -9154,6 +9177,13 @@ snapshots:
|
|||||||
|
|
||||||
tailwind-merge@3.3.0: {}
|
tailwind-merge@3.3.0: {}
|
||||||
|
|
||||||
|
tailwind-scrollbar@4.0.2(react@19.1.0)(tailwindcss@4.1.7):
|
||||||
|
dependencies:
|
||||||
|
prism-react-renderer: 2.4.1(react@19.1.0)
|
||||||
|
tailwindcss: 4.1.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- react
|
||||||
|
|
||||||
tailwindcss@4.1.5: {}
|
tailwindcss@4.1.5: {}
|
||||||
|
|
||||||
tailwindcss@4.1.7: {}
|
tailwindcss@4.1.7: {}
|
||||||
|
@@ -1,34 +1,16 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import './AIGuideContent.css';
|
||||||
import {
|
|
||||||
markdownToHtml,
|
|
||||||
markdownToHtmlWithHighlighting,
|
|
||||||
} from '../../lib/markdown';
|
|
||||||
import './AIDocumentContent.css';
|
|
||||||
|
|
||||||
type AIDocumentContentProps = {
|
type AIGuideContentProps = {
|
||||||
document: string;
|
html: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AIDocumentContent(props: AIDocumentContentProps) {
|
export function AIGuideContent(props: AIGuideContentProps) {
|
||||||
const { document } = props;
|
const { html } = props;
|
||||||
|
|
||||||
const [html, setHtml] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const html = markdownToHtmlWithHighlighting(document)
|
|
||||||
.then((html) => {
|
|
||||||
setHtml(html);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
return markdownToHtml(document, false);
|
|
||||||
});
|
|
||||||
}, [document]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-4xl">
|
<div className="mx-auto w-full max-w-4xl">
|
||||||
<div
|
<div
|
||||||
className="course-content [&>h1]:text-balance prose prose-lg prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm mt-8 max-w-full text-black max-lg:mt-4 max-lg:text-base"
|
className="course-content prose prose-lg prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm mt-8 max-w-full text-black max-lg:mt-4 max-lg:text-base [&>h1]:text-balance"
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,85 +4,70 @@ import { generateGuide } from '../../helper/generate-ai-guide';
|
|||||||
import { getCourseFineTuneData } from '../../lib/ai';
|
import { getCourseFineTuneData } from '../../lib/ai';
|
||||||
import { getUrlParams } from '../../lib/browser';
|
import { getUrlParams } from '../../lib/browser';
|
||||||
import { isLoggedIn } from '../../lib/jwt';
|
import { isLoggedIn } from '../../lib/jwt';
|
||||||
import { getAiCourseOptions } from '../../queries/ai-course';
|
|
||||||
import { queryClient } from '../../stores/query-client';
|
import { queryClient } from '../../stores/query-client';
|
||||||
import { AIDocumentContent } from './AIGuideContent';
|
import { AIGuideContent } from './AIGuideContent';
|
||||||
|
import { getAiGuideOptions } from '../../queries/ai-guide';
|
||||||
|
|
||||||
type GenerateAIGuideProps = {};
|
type GenerateAIGuideProps = {};
|
||||||
|
|
||||||
export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
const [difficulty, setDifficulty] = useState('');
|
const [depth, setDepth] = useState('');
|
||||||
const [sessionId, setSessionId] = useState('');
|
|
||||||
const [goal, setGoal] = useState('');
|
|
||||||
const [about, setAbout] = useState('');
|
|
||||||
const [customInstructions, setCustomInstructions] = useState('');
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const [creatorId, setCreatorId] = useState('');
|
|
||||||
const [documentId, setDocumentId] = useState('');
|
|
||||||
const [documentSlug, setDocumentSlug] = useState('');
|
const [documentSlug, setDocumentSlug] = useState('');
|
||||||
const [document, setDocument] = useState<string>('');
|
const [html, setHtml] = useState('');
|
||||||
|
|
||||||
// Once the course is generated, we fetch the course from the database
|
// Once the course is generated, we fetch the course from the database
|
||||||
// so that we get the up-to-date course data and also so that we
|
// so that we get the up-to-date course data and also so that we
|
||||||
// can reload the changes (e.g. progress) etc using queryClient.setQueryData
|
// can reload the changes (e.g. progress) etc using queryClient.setQueryData
|
||||||
const { data: aiCourse } = useQuery(
|
const { data: aiGuide } = useQuery(
|
||||||
getAiCourseOptions({ aiCourseSlug: documentSlug }),
|
getAiGuideOptions(documentSlug),
|
||||||
queryClient,
|
queryClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (term || difficulty) {
|
if (term || depth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = getUrlParams();
|
const params = getUrlParams();
|
||||||
const paramsTerm = params?.term;
|
const paramsTerm = params?.term;
|
||||||
const paramsDifficulty = params?.difficulty;
|
const paramsDepth = params?.depth;
|
||||||
const paramsSrc = params?.src || 'search';
|
const paramsSrc = params?.src || 'search';
|
||||||
if (!paramsTerm || !paramsDifficulty) {
|
if (!paramsTerm || !paramsDepth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTerm(paramsTerm);
|
|
||||||
setDifficulty(paramsDifficulty);
|
|
||||||
|
|
||||||
const sessionId = params?.id;
|
|
||||||
setSessionId(sessionId);
|
|
||||||
|
|
||||||
let paramsGoal = '';
|
let paramsGoal = '';
|
||||||
let paramsAbout = '';
|
let paramsAbout = '';
|
||||||
let paramsCustomInstructions = '';
|
let paramsCustomInstructions = '';
|
||||||
|
|
||||||
|
const sessionId = params?.id;
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
const fineTuneData = getCourseFineTuneData(sessionId);
|
const fineTuneData = getCourseFineTuneData(sessionId);
|
||||||
if (fineTuneData) {
|
if (fineTuneData) {
|
||||||
paramsGoal = fineTuneData.goal;
|
paramsGoal = fineTuneData.goal;
|
||||||
paramsAbout = fineTuneData.about;
|
paramsAbout = fineTuneData.about;
|
||||||
paramsCustomInstructions = fineTuneData.customInstructions;
|
paramsCustomInstructions = fineTuneData.customInstructions;
|
||||||
|
|
||||||
setGoal(paramsGoal);
|
|
||||||
setAbout(paramsAbout);
|
|
||||||
setCustomInstructions(paramsCustomInstructions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGenerateDocument({
|
handleGenerateDocument({
|
||||||
term: paramsTerm,
|
term: paramsTerm,
|
||||||
difficulty: paramsDifficulty,
|
depth: paramsDepth,
|
||||||
instructions: paramsCustomInstructions,
|
instructions: paramsCustomInstructions,
|
||||||
goal: paramsGoal,
|
goal: paramsGoal,
|
||||||
about: paramsAbout,
|
about: paramsAbout,
|
||||||
src: paramsSrc,
|
src: paramsSrc,
|
||||||
});
|
});
|
||||||
}, [term, difficulty]);
|
}, [term, depth]);
|
||||||
|
|
||||||
const handleGenerateDocument = async (options: {
|
const handleGenerateDocument = async (options: {
|
||||||
term: string;
|
term: string;
|
||||||
difficulty: string;
|
depth: string;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
goal?: string;
|
goal?: string;
|
||||||
about?: string;
|
about?: string;
|
||||||
@@ -90,30 +75,22 @@ export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
|||||||
prompt?: string;
|
prompt?: string;
|
||||||
src?: string;
|
src?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { term, depth, isForce, prompt, instructions, goal, about, src } =
|
||||||
term,
|
options;
|
||||||
difficulty,
|
|
||||||
isForce,
|
|
||||||
prompt,
|
|
||||||
instructions,
|
|
||||||
goal,
|
|
||||||
about,
|
|
||||||
src,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
window.location.href = '/ai';
|
window.location.href = '/ai';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await generateDocument({
|
await generateGuide({
|
||||||
term,
|
term,
|
||||||
difficulty,
|
depth,
|
||||||
slug: documentSlug,
|
slug: documentSlug,
|
||||||
onDocumentIdChange: setDocumentId,
|
onGuideSlugChange: (slug) => {
|
||||||
onDocumentSlugChange: setDocumentSlug,
|
setDocumentSlug(slug);
|
||||||
onCreatorIdChange: setCreatorId,
|
window.history.replaceState(null, '', `/ai/guide/${slug}`);
|
||||||
onDocumentChange: setDocument,
|
},
|
||||||
onLoadingChange: setIsLoading,
|
onLoadingChange: setIsLoading,
|
||||||
onError: setError,
|
onError: setError,
|
||||||
instructions,
|
instructions,
|
||||||
@@ -122,37 +99,13 @@ export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
|||||||
isForce,
|
isForce,
|
||||||
prompt,
|
prompt,
|
||||||
src,
|
src,
|
||||||
|
onHtmlChange: setHtml,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handlePopState = (e: PopStateEvent) => {
|
|
||||||
const { documentId, documentSlug, term, difficulty } = e.state || {};
|
|
||||||
if (!documentId || !documentSlug) {
|
|
||||||
window.location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDocumentId(documentId);
|
|
||||||
setDocumentSlug(documentSlug);
|
|
||||||
setTerm(term);
|
|
||||||
setDifficulty(difficulty);
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
handleGenerateDocument({ term, difficulty }).finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('popstate', handlePopState);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('popstate', handlePopState);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="text-red-500">{error}</div>;
|
return <div className="text-red-500">{error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AIDocumentContent document={document} />;
|
return <AIGuideContent html={html} />;
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +1,35 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { generateDocument } from '../../helper/generate-ai-guide';
|
|
||||||
import { queryClient } from '../../stores/query-client';
|
import { queryClient } from '../../stores/query-client';
|
||||||
import { getAiDocumentOptions } from '../../queries/ai-document';
|
import { AIGuideContent } from './AIGuideContent';
|
||||||
import { AIDocumentContent } from './AIGuideContent';
|
import { getAiGuideOptions } from '../../queries/ai-guide';
|
||||||
|
|
||||||
type GetAIDocumentProps = {
|
type GetAIGuideProps = {
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function GetAIDocument(props: GetAIDocumentProps) {
|
export function GetAIGuide(props: GetAIGuideProps) {
|
||||||
const { slug: documentSlug } = props;
|
const { slug: documentSlug } = props;
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isRegenerating, setIsRegenerating] = useState(false);
|
const [isRegenerating, setIsRegenerating] = useState(false);
|
||||||
|
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const { data: aiDocument, error: queryError } = useQuery(
|
const { data: aiGuide, error: queryError } = useQuery(
|
||||||
{
|
{
|
||||||
...getAiDocumentOptions({ documentSlug: documentSlug }),
|
...getAiGuideOptions(documentSlug),
|
||||||
enabled: !!documentSlug,
|
enabled: !!documentSlug,
|
||||||
},
|
},
|
||||||
queryClient,
|
queryClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!aiDocument) {
|
if (!aiGuide) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [aiDocument]);
|
}, [aiGuide]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!queryError) {
|
if (!queryError) {
|
||||||
@@ -42,46 +41,44 @@ export function GetAIDocument(props: GetAIDocumentProps) {
|
|||||||
}, [queryError]);
|
}, [queryError]);
|
||||||
|
|
||||||
const handleRegenerateDocument = async (prompt?: string) => {
|
const handleRegenerateDocument = async (prompt?: string) => {
|
||||||
if (!aiDocument) {
|
// if (!aiDocument) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
// queryClient.setQueryData(
|
||||||
queryClient.setQueryData(
|
// getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||||
getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
// {
|
||||||
{
|
// ...aiDocument,
|
||||||
...aiDocument,
|
// title: '',
|
||||||
title: '',
|
// difficulty: '',
|
||||||
difficulty: '',
|
// modules: [],
|
||||||
modules: [],
|
// },
|
||||||
},
|
// );
|
||||||
);
|
// await generateDocument({
|
||||||
|
// term: aiDocument.keyword,
|
||||||
await generateDocument({
|
// difficulty: aiDocument.difficulty,
|
||||||
term: aiDocument.keyword,
|
// slug: documentSlug,
|
||||||
difficulty: aiDocument.difficulty,
|
// prompt,
|
||||||
slug: documentSlug,
|
// onDocumentChange: (document) => {
|
||||||
prompt,
|
// queryClient.setQueryData(
|
||||||
onDocumentChange: (document) => {
|
// getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||||
queryClient.setQueryData(
|
// {
|
||||||
getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
// ...aiDocument,
|
||||||
{
|
// title: aiDocument.title,
|
||||||
...aiDocument,
|
// difficulty: aiDocument.difficulty,
|
||||||
title: aiDocument.title,
|
// content: document,
|
||||||
difficulty: aiDocument.difficulty,
|
// },
|
||||||
content: document,
|
// );
|
||||||
},
|
// },
|
||||||
);
|
// onLoadingChange: (isNewLoading) => {
|
||||||
},
|
// setIsRegenerating(isNewLoading);
|
||||||
onLoadingChange: (isNewLoading) => {
|
// if (!isNewLoading) {
|
||||||
setIsRegenerating(isNewLoading);
|
// // TODO: Update progress
|
||||||
if (!isNewLoading) {
|
// }
|
||||||
// TODO: Update progress
|
// },
|
||||||
}
|
// onError: setError,
|
||||||
},
|
// isForce: true,
|
||||||
onError: setError,
|
// });
|
||||||
isForce: true,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AIDocumentContent document={aiDocument?.content || ''} />;
|
return <AIGuideContent html={aiGuide?.html || ''} />;
|
||||||
}
|
}
|
||||||
|
@@ -1,33 +1,36 @@
|
|||||||
import { readStream } from '../lib/ai';
|
import { readStream } from '../lib/ai';
|
||||||
import { queryClient } from '../stores/query-client';
|
import { queryClient } from '../stores/query-client';
|
||||||
import { getAiCourseLimitOptions } from '../queries/ai-course';
|
import { getAiCourseLimitOptions } from '../queries/ai-course';
|
||||||
|
import { readChatStream } from '../lib/chat';
|
||||||
|
import { markdownToHtmlWithHighlighting } from '../lib/markdown';
|
||||||
|
|
||||||
type GenerateGuideOptions = {
|
type GenerateGuideOptions = {
|
||||||
term: string;
|
term: string;
|
||||||
difficulty: string;
|
depth: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
isForce?: boolean;
|
isForce?: boolean;
|
||||||
prompt?: string;
|
prompt?: string;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
goal?: string;
|
goal?: string;
|
||||||
about?: string;
|
about?: string;
|
||||||
onDocumentIdChange?: (documentId: string) => void;
|
onGuideIdChange?: (guideId: string) => void;
|
||||||
onDocumentSlugChange?: (documentSlug: string) => void;
|
onGuideSlugChange?: (guideSlug: string) => void;
|
||||||
onDocumentChange?: (document: string) => void;
|
onGuideChange?: (guide: string) => void;
|
||||||
onLoadingChange?: (isLoading: boolean) => void;
|
onLoadingChange?: (isLoading: boolean) => void;
|
||||||
onCreatorIdChange?: (creatorId: string) => void;
|
onCreatorIdChange?: (creatorId: string) => void;
|
||||||
onError?: (error: string) => void;
|
onError?: (error: string) => void;
|
||||||
src?: string;
|
src?: string;
|
||||||
|
onHtmlChange?: (html: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function generateGuide(options: GenerateGuideOptions) {
|
export async function generateGuide(options: GenerateGuideOptions) {
|
||||||
const {
|
const {
|
||||||
term,
|
term,
|
||||||
slug,
|
slug,
|
||||||
difficulty,
|
depth,
|
||||||
onDocumentIdChange,
|
onGuideIdChange,
|
||||||
onDocumentSlugChange,
|
onGuideSlugChange,
|
||||||
onDocumentChange,
|
onGuideChange,
|
||||||
onLoadingChange,
|
onLoadingChange,
|
||||||
onError,
|
onError,
|
||||||
onCreatorIdChange,
|
onCreatorIdChange,
|
||||||
@@ -37,10 +40,11 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
goal,
|
goal,
|
||||||
about,
|
about,
|
||||||
src = 'search',
|
src = 'search',
|
||||||
|
onHtmlChange,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
onLoadingChange?.(true);
|
onLoadingChange?.(true);
|
||||||
onDocumentChange?.('');
|
onGuideChange?.('');
|
||||||
onError?.('');
|
onError?.('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -48,7 +52,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
|
|
||||||
if (slug && isForce) {
|
if (slug && isForce) {
|
||||||
response = await fetch(
|
response = await fetch(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-regenerate-ai-document/${slug}`,
|
`${import.meta.env.PUBLIC_API_URL}/v1-regenerate-ai-guide/${slug}`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -63,7 +67,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
response = await fetch(
|
response = await fetch(
|
||||||
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-document`,
|
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-guide`,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -71,7 +75,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
keyword: term,
|
keyword: term,
|
||||||
difficulty,
|
depth,
|
||||||
isForce,
|
isForce,
|
||||||
customPrompt: prompt,
|
customPrompt: prompt,
|
||||||
instructions,
|
instructions,
|
||||||
@@ -95,19 +99,6 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const reader = response.body?.getReader();
|
|
||||||
|
|
||||||
// if (!reader) {
|
|
||||||
// console.error('Failed to get reader from response');
|
|
||||||
// onError?.('Something went wrong');
|
|
||||||
// onLoadingChange?.(false);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const DOCUMENT_ID_REGEX = new RegExp('@DOCID:(\\w+)@');
|
|
||||||
// const DOCUMENT_SLUG_REGEX = new RegExp(/@DOCSLUG:([\w-]+)@/);
|
|
||||||
// const CREATOR_ID_REGEX = new RegExp('@CREATORID:(\\w+)@');
|
|
||||||
|
|
||||||
const stream = response.body;
|
const stream = response.body;
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
console.error('Failed to get stream from response');
|
console.error('Failed to get stream from response');
|
||||||
@@ -116,55 +107,28 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// await readStream(reader, {
|
await readChatStream(stream, {
|
||||||
// onStream: async (result) => {
|
onMessage: async (message) => {
|
||||||
// if (result.includes('@DOCID') || result.includes('@DOCSLUG')) {
|
onGuideChange?.(message);
|
||||||
// const documentIdMatch = result.match(DOCUMENT_ID_REGEX);
|
onHtmlChange?.(await markdownToHtmlWithHighlighting(message));
|
||||||
// const documentSlugMatch = result.match(DOCUMENT_SLUG_REGEX);
|
},
|
||||||
// const creatorIdMatch = result.match(CREATOR_ID_REGEX);
|
onMessageEnd: async (message) => {
|
||||||
// const extractedDocumentId = documentIdMatch?.[1] || '';
|
onLoadingChange?.(false);
|
||||||
// const extractedDocumentSlug = documentSlugMatch?.[1] || '';
|
onGuideChange?.(message);
|
||||||
// const extractedCreatorId = creatorIdMatch?.[1] || '';
|
onHtmlChange?.(await markdownToHtmlWithHighlighting(message));
|
||||||
|
queryClient.invalidateQueries(getAiCourseLimitOptions());
|
||||||
|
},
|
||||||
|
onDetails: async (details) => {
|
||||||
|
const detailsJson = JSON.parse(details);
|
||||||
|
if (!detailsJson?.guideId || !detailsJson?.guideSlug) {
|
||||||
|
throw new Error('Invalid details');
|
||||||
|
}
|
||||||
|
|
||||||
// if (extractedDocumentSlug) {
|
onGuideIdChange?.(detailsJson?.guideId);
|
||||||
// window.history.replaceState(
|
onGuideSlugChange?.(detailsJson?.guideSlug);
|
||||||
// {
|
onCreatorIdChange?.(detailsJson?.creatorId);
|
||||||
// documentId: extractedDocumentId,
|
},
|
||||||
// documentSlug: extractedDocumentSlug,
|
});
|
||||||
// term,
|
|
||||||
// difficulty,
|
|
||||||
// },
|
|
||||||
// '',
|
|
||||||
// `${origin}/ai/document/${extractedDocumentSlug}`,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// result = result
|
|
||||||
// .replace(DOCUMENT_ID_REGEX, '')
|
|
||||||
// .replace(DOCUMENT_SLUG_REGEX, '')
|
|
||||||
// .replace(CREATOR_ID_REGEX, '');
|
|
||||||
|
|
||||||
// onDocumentIdChange?.(extractedDocumentId);
|
|
||||||
// onDocumentSlugChange?.(extractedDocumentSlug);
|
|
||||||
// onCreatorIdChange?.(extractedCreatorId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// onDocumentChange?.(result);
|
|
||||||
// } catch (e) {
|
|
||||||
// console.error('Error parsing streamed course content:', e);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onStreamEnd: async (result) => {
|
|
||||||
// result = result
|
|
||||||
// .replace(DOCUMENT_ID_REGEX, '')
|
|
||||||
// .replace(DOCUMENT_SLUG_REGEX, '')
|
|
||||||
// .replace(CREATOR_ID_REGEX, '');
|
|
||||||
|
|
||||||
// onLoadingChange?.(false);
|
|
||||||
// queryClient.invalidateQueries(getAiCourseLimitOptions());
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
onError?.(error?.message || 'Something went wrong');
|
onError?.(error?.message || 'Something went wrong');
|
||||||
console.error('Error in course generation:', error);
|
console.error('Error in course generation:', error);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
||||||
import { GetAIDocument } from '../../../components/GenerateGuide/GetAIGuide';
|
import { GetAIGuide } from '../../../components/GenerateGuide/GetAIGuide';
|
||||||
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
@@ -21,6 +21,6 @@ const { slug } = Astro.params as Params;
|
|||||||
>
|
>
|
||||||
<AITutorLayout client:load>
|
<AITutorLayout client:load>
|
||||||
<div slot='course-announcement'></div>
|
<div slot='course-announcement'></div>
|
||||||
<GetAIDocument client:load slug={slug} />
|
<GetAIGuide client:load slug={slug} />
|
||||||
</AITutorLayout>
|
</AITutorLayout>
|
||||||
</SkeletonLayout>
|
</SkeletonLayout>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
||||||
import { CheckSubscriptionVerification } from '../../../components/Billing/CheckSubscriptionVerification';
|
import { CheckSubscriptionVerification } from '../../../components/Billing/CheckSubscriptionVerification';
|
||||||
import { GenerateAIDocument } from '../../../components/GenerateGuide/GenerateAIGuide';
|
import { GenerateAIGuide } from '../../../components/GenerateGuide/GenerateAIGuide';
|
||||||
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
|||||||
noIndex={true}
|
noIndex={true}
|
||||||
>
|
>
|
||||||
<AITutorLayout client:load>
|
<AITutorLayout client:load>
|
||||||
<GenerateAIDocument client:load />
|
<GenerateAIGuide client:load />
|
||||||
<CheckSubscriptionVerification client:load />
|
<CheckSubscriptionVerification client:load />
|
||||||
</AITutorLayout>
|
</AITutorLayout>
|
||||||
</SkeletonLayout>
|
</SkeletonLayout>
|
@@ -1,11 +1,9 @@
|
|||||||
import { httpGet } from '../lib/query-http';
|
import { httpGet } from '../lib/query-http';
|
||||||
import { isLoggedIn } from '../lib/jwt';
|
import { isLoggedIn } from '../lib/jwt';
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
import { markdownToHtmlWithHighlighting } from '../lib/markdown';
|
||||||
|
|
||||||
type GetAIDocumentParams = {
|
export interface AIGuideDocument {
|
||||||
documentSlug: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AIDocumentDocument {
|
|
||||||
_id: string;
|
_id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -18,18 +16,23 @@ export interface AIDocumentDocument {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAIDocumentResponse = AIDocumentDocument;
|
type GetAIGuideResponse = AIGuideDocument;
|
||||||
|
|
||||||
export function getAiDocumentOptions(params: GetAIDocumentParams) {
|
export function getAiGuideOptions(guideSlug: string) {
|
||||||
return {
|
return queryOptions({
|
||||||
queryKey: ['ai-document', params],
|
queryKey: ['ai-guide', guideSlug],
|
||||||
queryFn: () => {
|
queryFn: async () => {
|
||||||
return httpGet<GetAIDocumentResponse>(
|
const res = await httpGet<GetAIGuideResponse>(
|
||||||
`/v1-get-ai-document/${params.documentSlug}`,
|
`/v1-get-ai-guide/${guideSlug}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
html: await markdownToHtmlWithHighlighting(res.content),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
enabled: !!params.documentSlug,
|
enabled: !!guideSlug,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListUserAiDocumentsQuery = {
|
export type ListUserAiDocumentsQuery = {
|
||||||
@@ -39,7 +42,7 @@ export type ListUserAiDocumentsQuery = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ListUserAiDocumentsResponse = {
|
type ListUserAiDocumentsResponse = {
|
||||||
data: AIDocumentDocument[];
|
data: AIGuideDocument[];
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
currPage: number;
|
currPage: number;
|
Reference in New Issue
Block a user