Merge branch 'pdf-pptx-layout' of https://github.com/presenton/presenton into pdf-pptx-layout

merge
This commit is contained in:
Suraj Jha 2025-08-07 20:33:42 +05:45
commit 2facff5280
12 changed files with 263 additions and 120 deletions

View file

@ -1,9 +1,14 @@
import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Save, X, Eye, Code } from "lucide-react";
import { Save, X, Code } from "lucide-react";
import { ProcessedSlide } from "../../types";
import Editor from 'react-simple-code-editor';
import { highlight, languages } from 'prismjs';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-jsx';
interface HtmlEditorProps {
slide: ProcessedSlide;
@ -19,7 +24,6 @@ export const HtmlEditor: React.FC<HtmlEditorProps> = ({
onCancel,
}) => {
const [htmlContent, setHtmlContent] = useState(slide.html || "");
const [isPreviewMode, setIsPreviewMode] = useState(false);
useEffect(() => {
setHtmlContent(slide.html || "");
@ -45,15 +49,7 @@ export const HtmlEditor: React.FC<HtmlEditorProps> = ({
<span className="text-purple-800">HTML Editor</span>
</div>
<div className="flex gap-2">
<Button
variant={isPreviewMode ? "default" : "outline"}
size="sm"
onClick={() => setIsPreviewMode(!isPreviewMode)}
className="flex items-center gap-1"
>
<Eye size={14} />
{isPreviewMode ? "Code" : "Preview"}
</Button>
<Button
variant="outline"
size="sm"
@ -73,30 +69,29 @@ export const HtmlEditor: React.FC<HtmlEditorProps> = ({
</div>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{isPreviewMode ? (
<div className="border rounded-lg p-4 bg-white">
<div
className="prose max-w-none"
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
</div>
) : (
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">
Edit HTML Content:
</label>
<Textarea
defaultValue={htmlContent}
onBlur={(e) => setHtmlContent(e.target.value)}
className="font-mono text-sm h-96 resize-none"
placeholder="Enter HTML content here..."
/>
<div className="text-xs text-gray-500">
Tip: You can edit the HTML directly. Make sure to maintain proper HTML structure.
</div>
</div>
)}
<CardContent className="space-y-4 ">
<div
dangerouslySetInnerHTML={{
__html: htmlContent,
}}
/>
<p className="text-base text-gray-800">Edit the HTML code to customize the slide layout.</p>
{/* Render code editor */}
<div className="container__content_area">
<Editor
value={htmlContent}
onValueChange={htmlContent => setHtmlContent(htmlContent)}
highlight={htmlContent => highlight(htmlContent, languages.jsx!,'jsx')}
padding={10}
id="html-editor"
name="html-editor"
className="container__editor"
/>
</div>
</CardContent>
</Card>
);

View file

@ -2,6 +2,7 @@ import React from "react";
import SlideContent from "../SlideContent";
import { SlideContentDisplayProps } from "../../types";
import { Repeat2 } from "lucide-react";
export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
slide,
@ -103,7 +104,10 @@ export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
slide.error
)}
</div>
<button onClick={() => retrySlide(slide.slide_number)}>Retry</button>
<div className="flex justify-center">
<button className="bg-red-50 flex gap-2 items-center rounded border border-red-200 px-4 py-2 " onClick={() => retrySlide(slide.slide_number)}><Repeat2 className="w-4 h-4" />Retry</button>
</div>
</div>
);
}

View file

@ -20,6 +20,56 @@ export const useLayoutSaving = (
setIsModalOpen(false);
}, []);
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const convertSlideToReact = async (slide: ProcessedSlide, presentationId: string, FontUrls: string[]) => {
const maxRetries = 3;
let retryCount = 0;
while (retryCount < maxRetries) {
try {
const response = await fetch("/api/v1/ppt/html-to-react/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
html: slide.html,
}),
});
const data = await ApiResponseHandler.handleResponse(
response,
`Failed to convert slide ${slide.slide_number} to React`
);
return {
presentation_id: presentationId,
layout_id: `${slide.slide_number}`,
layout_name: `Slide${slide.slide_number}`,
layout_code: data.react_component || data.component_code,
fonts: FontUrls,
};
} catch (error) {
retryCount++;
console.error(`Error converting slide ${slide.slide_number} (attempt ${retryCount}):`, error);
if (retryCount < maxRetries) {
toast.error(`Failed to convert slide ${slide.slide_number}. Retrying in 2 minutes...`, {
description: `Attempt ${retryCount}/${maxRetries}. Error: ${error instanceof Error ? error.message : "An unexpected error occurred"}`,
});
// Wait for 2 minutes before retrying
await delay(2 * 60 * 1000);
toast.info(`Retrying conversion for slide ${slide.slide_number}...`);
} else {
throw new Error(`Failed to convert slide ${slide.slide_number} after ${maxRetries} attempts: ${error instanceof Error ? error.message : "An unexpected error occurred"}`);
}
}
}
};
const saveLayout = useCallback(async (layoutName: string, description: string) => {
if (!slides.length) {
toast.error("No slides to save");
@ -46,41 +96,23 @@ export const useLayoutSaving = (
}
try {
const response = await fetch("/api/v1/ppt/html-to-react/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
html: slide.html,
}),
});
const data = await ApiResponseHandler.handleResponse(
response,
`Failed to convert slide ${slide.slide_number} to React`
);
reactComponents.push({
presentation_id: presentationId,
layout_id: `${slide.slide_number}`,
layout_name: `Slide${slide.slide_number}`,
layout_code: data.react_component || data.component_code,
fonts: FontUrls,
});
const reactComponent = await convertSlideToReact(slide, presentationId, FontUrls);
reactComponents.push(reactComponent);
// Update progress
toast.info(
toast.success(
`Converted slide ${slide.slide_number} to React component`
);
} catch (error) {
console.error(`Error converting slide ${slide.slide_number}:`, error);
toast.error(`Failed to convert slide ${slide.slide_number}`, {
toast.error(`Failed to convert slide ${slide.slide_number} after all retries`, {
description:
error instanceof Error
? error.message
: "An unexpected error occurred",
});
// Continue with other slides even if one fails
continue;
}
}

View file

@ -173,20 +173,7 @@ export const useSlideEdit = (
processing: false,
error: undefined,
};
// download screenshot
const screenshot = slideOnly.toDataURL("image/png");
const link = document.createElement("a");
link.href = screenshot;
link.download = `slide-${slide.slide_number}-current.png`;
link.click();
// second screenshot
if (sketchImageBlob && slideWithCanvas) {
const screenshot2 = slideWithCanvas.toDataURL("image/png");
const link2 = document.createElement("a");
link2.href = screenshot2;
link2.download = `slide-${slide.slide_number}-sketch.png`;
link2.click();
}
if (onSlideUpdate) {
onSlideUpdate(updatedSlideData);

View file

@ -7,7 +7,7 @@ export const useSlideProcessing = (
selectedFile: File | null,
slides: ProcessedSlide[],
setSlides: React.Dispatch<React.SetStateAction<ProcessedSlide[]>>,
fontsData: FontData | null,
setFontsData: React.Dispatch<React.SetStateAction<FontData | null>>
) => {
const [isProcessingPptx, setIsProcessingPptx] = useState(false);
@ -44,7 +44,6 @@ export const useSlideProcessing = (
);
console.log(`Successfully processed slide ${slide.slide_number}`);
// Update slide with success
setSlides((prev) => {
const newSlides = prev.map((s, i) =>

View file

@ -17,6 +17,7 @@ import { SaveLayoutButton } from "./components/SaveLayoutButton";
import { SaveLayoutModal } from "./components/SaveLayoutModal";
import EachSlide from "./components/EachSlide/NewEachSlide";
const CustomLayoutPage = () => {
const { refetch } = useLayout();
@ -29,7 +30,6 @@ const CustomLayoutPage = () => {
selectedFile,
slides,
setSlides,
fontsData,
setFontsData
);
const { isSavingLayout, isModalOpen, openSaveModal, closeSaveModal, saveLayout } = useLayoutSaving(
@ -81,6 +81,7 @@ const CustomLayoutPage = () => {
interactive HTML layouts
</p>
</div>
{/* File Upload Section */}
<FileUploadSection

View file

@ -110,7 +110,7 @@ const OutlineContent: React.FC<OutlineContentProps> = ({
)}
{/* Empty state */}
{!isLoading && outlines && outlines.length === 0 && (
{!isStreaming && !isLoading && outlines && outlines.length === 0 && (
<div className="text-center py-12 bg-white rounded-lg border-2 border-dashed border-gray-200">
<FileText className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-600 mb-4">No outlines available</p>

View file

@ -424,4 +424,97 @@ thead {
float: left;
height: 0;
pointer-events: none;
}
/* code editor */
.container_editor_area {
tab-size: 4ch;
max-height: 400px;
overflow: auto;
margin: 1.67em 0;
}
.container__content {
width: 440px;
max-width: 100%;
padding: 10px;
text-align: center;
}
.container__content_area {
tab-size: 4ch;
max-height: 400px;
overflow: auto;
margin: 1.67em 0;
}
.container__editor {
font-variant-ligatures: common-ligatures;
background-color: #fafafa;
border-radius: 3px;
}
.container__editor textarea {
outline: 0;
}
/* Syntax highlighting */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #90a4ae;
}
.token.punctuation {
color: #9e9e9e;
}
.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #e91e63;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #4caf50;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #795548;
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #3f51b5;
}
.token.function {
color: #f44336;
}
.token.regex,
.token.important,
.token.variable {
color: #ff9800;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View file

@ -45,12 +45,14 @@
"lucide-react": "^0.447.0",
"marked": "^15.0.11",
"mermaid": "^11.9.0",
"next": "15.4.3",
"next": "15.4.5",
"next-themes": "^0.4.6",
"prismjs": "^1.30.0",
"puppeteer": "^24.13.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-redux": "^9.1.2",
"react-simple-code-editor": "^0.14.1",
"recharts": "^2.15.4",
"sharp": "^0.34.3",
"sonner": "^2.0.6",
@ -63,6 +65,7 @@
"devDependencies": {
"@types/babel__standalone": "^7.1.9",
"@types/node": "^20",
"@types/prismjs": "^1.26.5",
"@types/puppeteer": "^5.4.7",
"@types/react": "^19",
"@types/react-dom": "^19",
@ -1422,15 +1425,15 @@
}
},
"node_modules/@next/env": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.3.tgz",
"integrity": "sha512-lKJ9KJAvaWzqurIsz6NWdQOLj96mdhuDMusLSYHw9HBe2On7BjUwU1WeRvq19x7NrEK3iOgMeSBV5qEhVH1cMw==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.5.tgz",
"integrity": "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.3.tgz",
"integrity": "sha512-YAhZWKeEYY7LHQJiQ8fe3Y6ymfcDcTn7rDC8PDu/pdeIl1Z2LHD4uyPNuQUGCEQT//MSNv6oZCeQzZfTCKZv+A==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.5.tgz",
"integrity": "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA==",
"cpu": [
"arm64"
],
@ -1444,9 +1447,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.3.tgz",
"integrity": "sha512-ZPHRdd51xaxCMpT4viQ6h8TgYM1zPW1JIeksPY9wKlyvBVUQqrWqw8kEh1sa7/x0Ied+U7pYHkAkutrUwxbMcg==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.5.tgz",
"integrity": "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA==",
"cpu": [
"x64"
],
@ -1460,9 +1463,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.3.tgz",
"integrity": "sha512-QUdqftCXC5vw5cowucqi9FeOPQ0vdMxoOHLY0J5jPdercwSJFjdi9CkEO4Xkq1eG4t1TB/BG81n6rmTsWoILnw==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.5.tgz",
"integrity": "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ==",
"cpu": [
"arm64"
],
@ -1476,9 +1479,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.3.tgz",
"integrity": "sha512-HTL31NsmoafX+r5g91Yj3+q34nrn1xKmCWVuNA+fUWO4X0pr+n83uGzLyEOn0kUqbMZ40KmWx+4wsbMoUChkiQ==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.5.tgz",
"integrity": "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw==",
"cpu": [
"arm64"
],
@ -1492,9 +1495,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.3.tgz",
"integrity": "sha512-HRQLWoeFkKXd2YCEEy9GhfwOijRm37x4w5r0MMVHxBKSA6ms3JoPUXvGhfHT6srnGRcEUWNrQ2vzkHir5ZWTSw==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.5.tgz",
"integrity": "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA==",
"cpu": [
"x64"
],
@ -1508,9 +1511,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.3.tgz",
"integrity": "sha512-NyXUx6G7AayaRGUsVPenuwhyAoyxjQuQPaK50AXoaAHPwRuif4WmSrXUs8/Y0HJIZh8E/YXRm9H7uuGfiacpuQ==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.5.tgz",
"integrity": "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ==",
"cpu": [
"x64"
],
@ -1524,9 +1527,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.3.tgz",
"integrity": "sha512-2CUTmpzN/7cL1a7GjdLkDFlfH3nwMwW8a6JiaAUsL9MtKmNNO3fnXqnY0Zk30fii3hVEl4dr7ztrpYt0t2CcGQ==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.5.tgz",
"integrity": "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ==",
"cpu": [
"arm64"
],
@ -1540,9 +1543,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.3.tgz",
"integrity": "sha512-i54YgUhvrUQxQD84SjAbkfWhYkOdm/DNRAVekCHLWxVg3aUbyC6NFQn9TwgCkX5QAS2pXCJo3kFboSFvrsd7dA==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.5.tgz",
"integrity": "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A==",
"cpu": [
"x64"
],
@ -3599,6 +3602,13 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/prismjs": {
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/puppeteer": {
"version": "5.4.7",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.7.tgz",
@ -7133,12 +7143,12 @@
}
},
"node_modules/next": {
"version": "15.4.3",
"resolved": "https://registry.npmjs.org/next/-/next-15.4.3.tgz",
"integrity": "sha512-uW7Qe6poVasNIE1X382nI29oxSdFJzjQzTgJFLD43MxyPfGKKxCMySllhBpvqr48f58Om+tLMivzRwBpXEytvA==",
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/next/-/next-15.4.5.tgz",
"integrity": "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==",
"license": "MIT",
"dependencies": {
"@next/env": "15.4.3",
"@next/env": "15.4.5",
"@swc/helpers": "0.5.15",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
@ -7151,14 +7161,14 @@
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "15.4.3",
"@next/swc-darwin-x64": "15.4.3",
"@next/swc-linux-arm64-gnu": "15.4.3",
"@next/swc-linux-arm64-musl": "15.4.3",
"@next/swc-linux-x64-gnu": "15.4.3",
"@next/swc-linux-x64-musl": "15.4.3",
"@next/swc-win32-arm64-msvc": "15.4.3",
"@next/swc-win32-x64-msvc": "15.4.3",
"@next/swc-darwin-arm64": "15.4.5",
"@next/swc-darwin-x64": "15.4.5",
"@next/swc-linux-arm64-gnu": "15.4.5",
"@next/swc-linux-arm64-musl": "15.4.5",
"@next/swc-linux-x64-gnu": "15.4.5",
"@next/swc-linux-x64-musl": "15.4.5",
"@next/swc-win32-arm64-msvc": "15.4.5",
"@next/swc-win32-x64-msvc": "15.4.5",
"sharp": "^0.34.3"
},
"peerDependencies": {
@ -7634,6 +7644,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -8115,6 +8134,16 @@
}
}
},
"node_modules/react-simple-code-editor": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.14.1.tgz",
"integrity": "sha512-BR5DtNRy+AswWJECyA17qhUDvrrCZ6zXOCfkQY5zSmb96BVUbpVAv03WpcjcwtCwiLbIANx3gebHOcXYn1EHow==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",

View file

@ -49,10 +49,12 @@
"mermaid": "^11.9.0",
"next": "15.4.5",
"next-themes": "^0.4.6",
"prismjs": "^1.30.0",
"puppeteer": "^24.13.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-redux": "^9.1.2",
"react-simple-code-editor": "^0.14.1",
"recharts": "^2.15.4",
"sharp": "^0.34.3",
"sonner": "^2.0.6",
@ -65,6 +67,7 @@
"devDependencies": {
"@types/babel__standalone": "^7.1.9",
"@types/node": "^20",
"@types/prismjs": "^1.26.5",
"@types/puppeteer": "^5.4.7",
"@types/react": "^19",
"@types/react-dom": "^19",

View file

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,