mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-03 06:12:53 +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 path="content.d.ts" />
|
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -32,6 +32,9 @@ importers:
|
||||
'@radix-ui/react-dropdown-menu':
|
||||
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)
|
||||
'@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':
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
@@ -270,6 +273,9 @@ importers:
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.6.11
|
||||
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:
|
||||
specifier: ^4.19.4
|
||||
version: 4.19.4
|
||||
@@ -3923,6 +3929,11 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
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:
|
||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4359,6 +4370,12 @@ packages:
|
||||
tailwind-merge@3.3.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-nYtSPfWGDiWgCkwQG/m+aX83XCwf62sBgg3bIlNiiOcggnS1x3uVRDAuyelBFL+vJdOPPCGElxv9DjHJjRHiVA==}
|
||||
|
||||
@@ -8495,6 +8512,12 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
@@ -9154,6 +9177,13 @@ snapshots:
|
||||
|
||||
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.7: {}
|
||||
|
@@ -1,34 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
markdownToHtml,
|
||||
markdownToHtmlWithHighlighting,
|
||||
} from '../../lib/markdown';
|
||||
import './AIDocumentContent.css';
|
||||
import './AIGuideContent.css';
|
||||
|
||||
type AIDocumentContentProps = {
|
||||
document: string;
|
||||
type AIGuideContentProps = {
|
||||
html: string;
|
||||
};
|
||||
|
||||
export function AIDocumentContent(props: AIDocumentContentProps) {
|
||||
const { document } = props;
|
||||
|
||||
const [html, setHtml] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const html = markdownToHtmlWithHighlighting(document)
|
||||
.then((html) => {
|
||||
setHtml(html);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return markdownToHtml(document, false);
|
||||
});
|
||||
}, [document]);
|
||||
export function AIGuideContent(props: AIGuideContentProps) {
|
||||
const { html } = props;
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<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 }}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -4,85 +4,70 @@ import { generateGuide } from '../../helper/generate-ai-guide';
|
||||
import { getCourseFineTuneData } from '../../lib/ai';
|
||||
import { getUrlParams } from '../../lib/browser';
|
||||
import { isLoggedIn } from '../../lib/jwt';
|
||||
import { getAiCourseOptions } from '../../queries/ai-course';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { AIDocumentContent } from './AIGuideContent';
|
||||
import { AIGuideContent } from './AIGuideContent';
|
||||
import { getAiGuideOptions } from '../../queries/ai-guide';
|
||||
|
||||
type GenerateAIGuideProps = {};
|
||||
|
||||
export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
||||
const [term, setTerm] = useState('');
|
||||
const [difficulty, setDifficulty] = useState('');
|
||||
const [sessionId, setSessionId] = useState('');
|
||||
const [goal, setGoal] = useState('');
|
||||
const [about, setAbout] = useState('');
|
||||
const [customInstructions, setCustomInstructions] = useState('');
|
||||
const [depth, setDepth] = useState('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const [creatorId, setCreatorId] = useState('');
|
||||
const [documentId, setDocumentId] = 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
|
||||
// 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
|
||||
const { data: aiCourse } = useQuery(
|
||||
getAiCourseOptions({ aiCourseSlug: documentSlug }),
|
||||
const { data: aiGuide } = useQuery(
|
||||
getAiGuideOptions(documentSlug),
|
||||
queryClient,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (term || difficulty) {
|
||||
if (term || depth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = getUrlParams();
|
||||
const paramsTerm = params?.term;
|
||||
const paramsDifficulty = params?.difficulty;
|
||||
const paramsDepth = params?.depth;
|
||||
const paramsSrc = params?.src || 'search';
|
||||
if (!paramsTerm || !paramsDifficulty) {
|
||||
if (!paramsTerm || !paramsDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTerm(paramsTerm);
|
||||
setDifficulty(paramsDifficulty);
|
||||
|
||||
const sessionId = params?.id;
|
||||
setSessionId(sessionId);
|
||||
|
||||
let paramsGoal = '';
|
||||
let paramsAbout = '';
|
||||
let paramsCustomInstructions = '';
|
||||
|
||||
const sessionId = params?.id;
|
||||
if (sessionId) {
|
||||
const fineTuneData = getCourseFineTuneData(sessionId);
|
||||
if (fineTuneData) {
|
||||
paramsGoal = fineTuneData.goal;
|
||||
paramsAbout = fineTuneData.about;
|
||||
paramsCustomInstructions = fineTuneData.customInstructions;
|
||||
|
||||
setGoal(paramsGoal);
|
||||
setAbout(paramsAbout);
|
||||
setCustomInstructions(paramsCustomInstructions);
|
||||
}
|
||||
}
|
||||
|
||||
handleGenerateDocument({
|
||||
term: paramsTerm,
|
||||
difficulty: paramsDifficulty,
|
||||
depth: paramsDepth,
|
||||
instructions: paramsCustomInstructions,
|
||||
goal: paramsGoal,
|
||||
about: paramsAbout,
|
||||
src: paramsSrc,
|
||||
});
|
||||
}, [term, difficulty]);
|
||||
}, [term, depth]);
|
||||
|
||||
const handleGenerateDocument = async (options: {
|
||||
term: string;
|
||||
difficulty: string;
|
||||
depth: string;
|
||||
instructions?: string;
|
||||
goal?: string;
|
||||
about?: string;
|
||||
@@ -90,30 +75,22 @@ export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
||||
prompt?: string;
|
||||
src?: string;
|
||||
}) => {
|
||||
const {
|
||||
term,
|
||||
difficulty,
|
||||
isForce,
|
||||
prompt,
|
||||
instructions,
|
||||
goal,
|
||||
about,
|
||||
src,
|
||||
} = options;
|
||||
const { term, depth, isForce, prompt, instructions, goal, about, src } =
|
||||
options;
|
||||
|
||||
if (!isLoggedIn()) {
|
||||
window.location.href = '/ai';
|
||||
return;
|
||||
}
|
||||
|
||||
await generateDocument({
|
||||
await generateGuide({
|
||||
term,
|
||||
difficulty,
|
||||
depth,
|
||||
slug: documentSlug,
|
||||
onDocumentIdChange: setDocumentId,
|
||||
onDocumentSlugChange: setDocumentSlug,
|
||||
onCreatorIdChange: setCreatorId,
|
||||
onDocumentChange: setDocument,
|
||||
onGuideSlugChange: (slug) => {
|
||||
setDocumentSlug(slug);
|
||||
window.history.replaceState(null, '', `/ai/guide/${slug}`);
|
||||
},
|
||||
onLoadingChange: setIsLoading,
|
||||
onError: setError,
|
||||
instructions,
|
||||
@@ -122,37 +99,13 @@ export function GenerateAIGuide(props: GenerateAIGuideProps) {
|
||||
isForce,
|
||||
prompt,
|
||||
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) {
|
||||
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 { useEffect, useState } from 'react';
|
||||
import { generateDocument } from '../../helper/generate-ai-guide';
|
||||
import { queryClient } from '../../stores/query-client';
|
||||
import { getAiDocumentOptions } from '../../queries/ai-document';
|
||||
import { AIDocumentContent } from './AIGuideContent';
|
||||
import { AIGuideContent } from './AIGuideContent';
|
||||
import { getAiGuideOptions } from '../../queries/ai-guide';
|
||||
|
||||
type GetAIDocumentProps = {
|
||||
type GetAIGuideProps = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export function GetAIDocument(props: GetAIDocumentProps) {
|
||||
export function GetAIGuide(props: GetAIGuideProps) {
|
||||
const { slug: documentSlug } = props;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRegenerating, setIsRegenerating] = useState(false);
|
||||
|
||||
const [error, setError] = useState('');
|
||||
const { data: aiDocument, error: queryError } = useQuery(
|
||||
const { data: aiGuide, error: queryError } = useQuery(
|
||||
{
|
||||
...getAiDocumentOptions({ documentSlug: documentSlug }),
|
||||
...getAiGuideOptions(documentSlug),
|
||||
enabled: !!documentSlug,
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!aiDocument) {
|
||||
if (!aiGuide) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}, [aiDocument]);
|
||||
}, [aiGuide]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!queryError) {
|
||||
@@ -42,46 +41,44 @@ export function GetAIDocument(props: GetAIDocumentProps) {
|
||||
}, [queryError]);
|
||||
|
||||
const handleRegenerateDocument = async (prompt?: string) => {
|
||||
if (!aiDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryClient.setQueryData(
|
||||
getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||
{
|
||||
...aiDocument,
|
||||
title: '',
|
||||
difficulty: '',
|
||||
modules: [],
|
||||
},
|
||||
);
|
||||
|
||||
await generateDocument({
|
||||
term: aiDocument.keyword,
|
||||
difficulty: aiDocument.difficulty,
|
||||
slug: documentSlug,
|
||||
prompt,
|
||||
onDocumentChange: (document) => {
|
||||
queryClient.setQueryData(
|
||||
getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||
{
|
||||
...aiDocument,
|
||||
title: aiDocument.title,
|
||||
difficulty: aiDocument.difficulty,
|
||||
content: document,
|
||||
},
|
||||
);
|
||||
},
|
||||
onLoadingChange: (isNewLoading) => {
|
||||
setIsRegenerating(isNewLoading);
|
||||
if (!isNewLoading) {
|
||||
// TODO: Update progress
|
||||
}
|
||||
},
|
||||
onError: setError,
|
||||
isForce: true,
|
||||
});
|
||||
// if (!aiDocument) {
|
||||
// return;
|
||||
// }
|
||||
// queryClient.setQueryData(
|
||||
// getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||
// {
|
||||
// ...aiDocument,
|
||||
// title: '',
|
||||
// difficulty: '',
|
||||
// modules: [],
|
||||
// },
|
||||
// );
|
||||
// await generateDocument({
|
||||
// term: aiDocument.keyword,
|
||||
// difficulty: aiDocument.difficulty,
|
||||
// slug: documentSlug,
|
||||
// prompt,
|
||||
// onDocumentChange: (document) => {
|
||||
// queryClient.setQueryData(
|
||||
// getAiDocumentOptions({ documentSlug: documentSlug }).queryKey,
|
||||
// {
|
||||
// ...aiDocument,
|
||||
// title: aiDocument.title,
|
||||
// difficulty: aiDocument.difficulty,
|
||||
// content: document,
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// onLoadingChange: (isNewLoading) => {
|
||||
// setIsRegenerating(isNewLoading);
|
||||
// if (!isNewLoading) {
|
||||
// // TODO: Update progress
|
||||
// }
|
||||
// },
|
||||
// onError: setError,
|
||||
// isForce: true,
|
||||
// });
|
||||
};
|
||||
|
||||
return <AIDocumentContent document={aiDocument?.content || ''} />;
|
||||
return <AIGuideContent html={aiGuide?.html || ''} />;
|
||||
}
|
||||
|
@@ -1,33 +1,36 @@
|
||||
import { readStream } from '../lib/ai';
|
||||
import { queryClient } from '../stores/query-client';
|
||||
import { getAiCourseLimitOptions } from '../queries/ai-course';
|
||||
import { readChatStream } from '../lib/chat';
|
||||
import { markdownToHtmlWithHighlighting } from '../lib/markdown';
|
||||
|
||||
type GenerateGuideOptions = {
|
||||
term: string;
|
||||
difficulty: string;
|
||||
depth: string;
|
||||
slug?: string;
|
||||
isForce?: boolean;
|
||||
prompt?: string;
|
||||
instructions?: string;
|
||||
goal?: string;
|
||||
about?: string;
|
||||
onDocumentIdChange?: (documentId: string) => void;
|
||||
onDocumentSlugChange?: (documentSlug: string) => void;
|
||||
onDocumentChange?: (document: string) => void;
|
||||
onGuideIdChange?: (guideId: string) => void;
|
||||
onGuideSlugChange?: (guideSlug: string) => void;
|
||||
onGuideChange?: (guide: string) => void;
|
||||
onLoadingChange?: (isLoading: boolean) => void;
|
||||
onCreatorIdChange?: (creatorId: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
src?: string;
|
||||
onHtmlChange?: (html: string) => void;
|
||||
};
|
||||
|
||||
export async function generateGuide(options: GenerateGuideOptions) {
|
||||
const {
|
||||
term,
|
||||
slug,
|
||||
difficulty,
|
||||
onDocumentIdChange,
|
||||
onDocumentSlugChange,
|
||||
onDocumentChange,
|
||||
depth,
|
||||
onGuideIdChange,
|
||||
onGuideSlugChange,
|
||||
onGuideChange,
|
||||
onLoadingChange,
|
||||
onError,
|
||||
onCreatorIdChange,
|
||||
@@ -37,10 +40,11 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
goal,
|
||||
about,
|
||||
src = 'search',
|
||||
onHtmlChange,
|
||||
} = options;
|
||||
|
||||
onLoadingChange?.(true);
|
||||
onDocumentChange?.('');
|
||||
onGuideChange?.('');
|
||||
onError?.('');
|
||||
|
||||
try {
|
||||
@@ -48,7 +52,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
|
||||
if (slug && isForce) {
|
||||
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',
|
||||
headers: {
|
||||
@@ -63,7 +67,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
);
|
||||
} else {
|
||||
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',
|
||||
headers: {
|
||||
@@ -71,7 +75,7 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
keyword: term,
|
||||
difficulty,
|
||||
depth,
|
||||
isForce,
|
||||
customPrompt: prompt,
|
||||
instructions,
|
||||
@@ -95,19 +99,6 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
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;
|
||||
if (!stream) {
|
||||
console.error('Failed to get stream from response');
|
||||
@@ -116,55 +107,28 @@ export async function generateGuide(options: GenerateGuideOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
// await readStream(reader, {
|
||||
// onStream: async (result) => {
|
||||
// if (result.includes('@DOCID') || result.includes('@DOCSLUG')) {
|
||||
// const documentIdMatch = result.match(DOCUMENT_ID_REGEX);
|
||||
// const documentSlugMatch = result.match(DOCUMENT_SLUG_REGEX);
|
||||
// const creatorIdMatch = result.match(CREATOR_ID_REGEX);
|
||||
// const extractedDocumentId = documentIdMatch?.[1] || '';
|
||||
// const extractedDocumentSlug = documentSlugMatch?.[1] || '';
|
||||
// const extractedCreatorId = creatorIdMatch?.[1] || '';
|
||||
await readChatStream(stream, {
|
||||
onMessage: async (message) => {
|
||||
onGuideChange?.(message);
|
||||
onHtmlChange?.(await markdownToHtmlWithHighlighting(message));
|
||||
},
|
||||
onMessageEnd: async (message) => {
|
||||
onLoadingChange?.(false);
|
||||
onGuideChange?.(message);
|
||||
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) {
|
||||
// window.history.replaceState(
|
||||
// {
|
||||
// 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());
|
||||
// },
|
||||
// });
|
||||
onGuideIdChange?.(detailsJson?.guideId);
|
||||
onGuideSlugChange?.(detailsJson?.guideSlug);
|
||||
onCreatorIdChange?.(detailsJson?.creatorId);
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
onError?.(error?.message || 'Something went wrong');
|
||||
console.error('Error in course generation:', error);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
||||
import { GetAIDocument } from '../../../components/GenerateGuide/GetAIGuide';
|
||||
import { GetAIGuide } from '../../../components/GenerateGuide/GetAIGuide';
|
||||
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||
|
||||
export const prerender = false;
|
||||
@@ -21,6 +21,6 @@ const { slug } = Astro.params as Params;
|
||||
>
|
||||
<AITutorLayout client:load>
|
||||
<div slot='course-announcement'></div>
|
||||
<GetAIDocument client:load slug={slug} />
|
||||
<GetAIGuide client:load slug={slug} />
|
||||
</AITutorLayout>
|
||||
</SkeletonLayout>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import { AITutorLayout } from '../../../components/AITutor/AITutorLayout';
|
||||
import { CheckSubscriptionVerification } from '../../../components/Billing/CheckSubscriptionVerification';
|
||||
import { GenerateAIDocument } from '../../../components/GenerateGuide/GenerateAIGuide';
|
||||
import { GenerateAIGuide } from '../../../components/GenerateGuide/GenerateAIGuide';
|
||||
import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||
---
|
||||
|
||||
@@ -14,7 +14,7 @@ import SkeletonLayout from '../../../layouts/SkeletonLayout.astro';
|
||||
noIndex={true}
|
||||
>
|
||||
<AITutorLayout client:load>
|
||||
<GenerateAIDocument client:load />
|
||||
<GenerateAIGuide client:load />
|
||||
<CheckSubscriptionVerification client:load />
|
||||
</AITutorLayout>
|
||||
</SkeletonLayout>
|
@@ -1,11 +1,9 @@
|
||||
import { httpGet } from '../lib/query-http';
|
||||
import { isLoggedIn } from '../lib/jwt';
|
||||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { markdownToHtmlWithHighlighting } from '../lib/markdown';
|
||||
|
||||
type GetAIDocumentParams = {
|
||||
documentSlug: string;
|
||||
};
|
||||
|
||||
export interface AIDocumentDocument {
|
||||
export interface AIGuideDocument {
|
||||
_id: string;
|
||||
userId: string;
|
||||
title: string;
|
||||
@@ -18,18 +16,23 @@ export interface AIDocumentDocument {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
type GetAIDocumentResponse = AIDocumentDocument;
|
||||
type GetAIGuideResponse = AIGuideDocument;
|
||||
|
||||
export function getAiDocumentOptions(params: GetAIDocumentParams) {
|
||||
return {
|
||||
queryKey: ['ai-document', params],
|
||||
queryFn: () => {
|
||||
return httpGet<GetAIDocumentResponse>(
|
||||
`/v1-get-ai-document/${params.documentSlug}`,
|
||||
export function getAiGuideOptions(guideSlug: string) {
|
||||
return queryOptions({
|
||||
queryKey: ['ai-guide', guideSlug],
|
||||
queryFn: async () => {
|
||||
const res = await httpGet<GetAIGuideResponse>(
|
||||
`/v1-get-ai-guide/${guideSlug}`,
|
||||
);
|
||||
|
||||
return {
|
||||
...res,
|
||||
html: await markdownToHtmlWithHighlighting(res.content),
|
||||
};
|
||||
},
|
||||
enabled: !!params.documentSlug,
|
||||
};
|
||||
enabled: !!guideSlug,
|
||||
});
|
||||
}
|
||||
|
||||
export type ListUserAiDocumentsQuery = {
|
||||
@@ -39,7 +42,7 @@ export type ListUserAiDocumentsQuery = {
|
||||
};
|
||||
|
||||
type ListUserAiDocumentsResponse = {
|
||||
data: AIDocumentDocument[];
|
||||
data: AIGuideDocument[];
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
currPage: number;
|
Reference in New Issue
Block a user