feat(nextjs): Timer added in Each layout convert
This commit is contained in:
parent
d96ac341e1
commit
bb93648a61
21 changed files with 580 additions and 208 deletions
|
|
@ -38,7 +38,7 @@ RUN pip install docling --extra-index-url https://download.pytorch.org/whl/cpu
|
|||
# Install dependencies for Next.js
|
||||
WORKDIR /node_dependencies
|
||||
COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./
|
||||
RUN npm install
|
||||
RUN npm install --verbose
|
||||
|
||||
# Install chrome for puppeteer
|
||||
RUN npx puppeteer browsers install chrome@138.0.7204.94 --install-deps
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,18 +1,15 @@
|
|||
import React from "react";
|
||||
import Header from "@/components/Header";
|
||||
|
||||
export const AnthropicKeyWarning: React.FC = () => {
|
||||
export const APIKeyWarning: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen font-roboto bg-gradient-to-br from-slate-50 to-slate-100">
|
||||
<Header />
|
||||
<div className="flex items-center justify-center aspect-video mx-auto px-6">
|
||||
<div className="text-center space-y-2 my-6 bg-white p-10 rounded-lg shadow-lg">
|
||||
<h1 className="text-xl font-bold text-gray-900">
|
||||
Please put Anthropic Key To Process The Layout
|
||||
Please add "GOOGLE_API_KEY" to enable template creation via AI.
|
||||
</h1>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
It Only works on Anthropic(Claude-4).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
'use client'
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
'use client'
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useDrawingCanvas } from "../../hooks/useDrawingCanvas";
|
||||
|
||||
|
|
@ -155,36 +156,6 @@ const EachSlide: React.FC<EachSlideProps> = ({
|
|||
onTouchEnd={handleTouchEnd}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="p-4 pt-0 flex gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const newWindow = window.open("", "_blank");
|
||||
if (newWindow) {
|
||||
newWindow.document.write(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Slide ${slide.slide_number} - HTML Preview</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="slide-container">
|
||||
${slide.html}
|
||||
</div>
|
||||
</body>
|
||||
</html>`);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Open in new tab
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
'use client'
|
||||
import React from "react";
|
||||
import { AlertCircle, CheckCircle, Edit, Loader2, Repeat2, Trash, Code } from "lucide-react";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
'use client'
|
||||
|
||||
import React from "react";
|
||||
|
||||
import SlideContent from "../SlideContent";
|
||||
import { SlideContentDisplayProps } from "../../types";
|
||||
import { Repeat2 } from "lucide-react";
|
||||
import Timer from "../Timer";
|
||||
|
||||
export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
|
||||
slide,
|
||||
|
|
@ -33,16 +36,15 @@ export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
|
|||
if (slide.processing) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-base text-blue-600 font-medium">
|
||||
🔄 Converting to HTML...
|
||||
</p>
|
||||
<div className="animate-pulse space-y-3">
|
||||
<div className="h-6 bg-gray-200 rounded w-2/3"></div>
|
||||
<div className="h-6 bg-gray-200 rounded w-1/2"></div>
|
||||
<div className="h-64 bg-gray-200 rounded"></div>
|
||||
<div className="h-64 bg-gray-200 rounded"></div>
|
||||
|
||||
<p className="text-base text-blue-600 font-medium">🔄 Converting to HTML...</p>
|
||||
<div className="space-y-3">
|
||||
<Timer duration={160} />
|
||||
</div>
|
||||
<div className="animate-pulse space-y-3">
|
||||
<div className="h-6 bg-gray-200 rounded w-2/3"></div>
|
||||
<div className="h-6 bg-gray-200 rounded w-1/2"></div>
|
||||
<div className="h-64 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -50,6 +52,12 @@ export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
|
|||
if (slide.processed && slide.html) {
|
||||
return (
|
||||
<div className="relative">
|
||||
{slide.convertingToReact && (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-purple-700 font-medium mb-1">⚙️ Converting HTML to React...</p>
|
||||
<Timer duration={90} />
|
||||
</div>
|
||||
)}
|
||||
<div ref={slideDisplayRef} className="relative mx-auto w-full">
|
||||
<div ref={slideContentRef}>
|
||||
<SlideContent slide={slide} />
|
||||
|
|
@ -86,27 +94,21 @@ export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
|
|||
if (slide.error) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-base text-red-600 font-medium">
|
||||
✗ Conversion failed
|
||||
</p>
|
||||
<p className="text-base text-red-600 font-medium">✗ Conversion failed</p>
|
||||
<div className="text-sm text-gray-700 p-4 bg-red-50 rounded border border-red-200">
|
||||
{slide.error.includes("image exceeds 5 MB maximum") ? (
|
||||
<div>
|
||||
<p className="font-medium text-red-700 mb-2">
|
||||
Image too large for processing
|
||||
</p>
|
||||
<p>
|
||||
This slide's image exceeds the 5MB limit. Try using a
|
||||
smaller resolution PPTX file.
|
||||
</p>
|
||||
<p className="font-medium text-red-700 mb-2">Image too large for processing</p>
|
||||
<p>This slide's image exceeds the 5MB limit. Try using a smaller resolution PPTX file.</p>
|
||||
</div>
|
||||
) : (
|
||||
slide.error
|
||||
)}
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
|
|
@ -114,9 +116,7 @@ export const SlideContentDisplay: React.FC<SlideContentDisplayProps> = ({
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-base text-gray-500">
|
||||
⏳ Waiting in queue to process...
|
||||
</p>
|
||||
<p className="text-base text-gray-500">⏳ Waiting in queue to process...</p>
|
||||
<div className="animate-pulse space-y-3">
|
||||
<div className="h-6 bg-gray-200 rounded w-2/3"></div>
|
||||
<div className="h-6 bg-gray-200 rounded w-1/2"></div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { Label } from "@/components/ui/label";
|
||||
import { Upload, FileText, X, Loader2 } from "lucide-react";
|
||||
import { ProcessedSlide } from "../types";
|
||||
import Timer from "./Timer";
|
||||
|
||||
interface FileUploadSectionProps {
|
||||
selectedFile: File | null;
|
||||
|
|
@ -96,7 +97,7 @@ export const FileUploadSection: React.FC<FileUploadSectionProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col gap-1 ">
|
||||
<Button
|
||||
onClick={processFile}
|
||||
disabled={isProcessingPptx || slides.some((s) => s.processing)}
|
||||
|
|
@ -108,6 +109,7 @@ export const FileUploadSection: React.FC<FileUploadSectionProps> = ({
|
|||
? "Select a PPTX file"
|
||||
: "Process File"}
|
||||
</Button>
|
||||
{isProcessingPptx && <Timer duration={90} />}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
'use client'
|
||||
|
||||
import React, { memo } from "react";
|
||||
|
||||
const SlideContent = memo(({ slide }: { slide: any }) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
'use client'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface TimerProps {
|
||||
duration: number // seconds
|
||||
}
|
||||
|
||||
const Timer = ({ duration }: TimerProps) => {
|
||||
const [progress, setProgress] = useState<number>(0)
|
||||
const rafIdRef = useRef<number | null>(null)
|
||||
const startTimeRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Guard against invalid durations
|
||||
const totalMs = Math.max(0, duration * 1000)
|
||||
|
||||
const easeOutCubic = (x: number) => 1 - Math.pow(1 - x, 3)
|
||||
const easeOutSine = (x: number) => Math.sin((x * Math.PI) / 2)
|
||||
|
||||
const tick = (now: number) => {
|
||||
if (startTimeRef.current === null) startTimeRef.current = now
|
||||
const elapsed = now - startTimeRef.current
|
||||
const t = totalMs === 0 ? 1 : Math.min(elapsed / totalMs, 1)
|
||||
|
||||
// Piecewise progression:
|
||||
// - Reach ~75% around 60% of the total duration (faster start)
|
||||
// - Then ease slowly towards 99% for the remainder
|
||||
let nextProgress: number
|
||||
if (t <= 0.6) {
|
||||
nextProgress = 75 * easeOutCubic(t / 0.6)
|
||||
} else {
|
||||
nextProgress = 75 + 24 * easeOutSine((t - 0.6) / 0.4)
|
||||
}
|
||||
|
||||
// Clamp and ensure we never hit 100
|
||||
nextProgress = Math.min(99, nextProgress)
|
||||
|
||||
setProgress(prev => (nextProgress < prev ? prev : nextProgress))
|
||||
|
||||
if (t < 1 && nextProgress < 99) {
|
||||
rafIdRef.current = requestAnimationFrame(tick)
|
||||
} else {
|
||||
// End at 99 and stop
|
||||
setProgress(99)
|
||||
if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current)
|
||||
rafIdRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize animation
|
||||
setProgress(0)
|
||||
startTimeRef.current = null
|
||||
rafIdRef.current = requestAnimationFrame(tick)
|
||||
|
||||
return () => {
|
||||
if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current)
|
||||
rafIdRef.current = null
|
||||
startTimeRef.current = null
|
||||
}
|
||||
}, [duration])
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-2">
|
||||
<div className="flex justify-end items-center text-gray-800 text-sm">
|
||||
<span className="font-inter text-end font-semibold text-xs">{Math.round(progress)}%</span>
|
||||
</div>
|
||||
<div
|
||||
className="w-full rounded-full h-3 overflow-hidden shadow-inner"
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={Math.round(progress)}
|
||||
>
|
||||
<div className="relative h-full rounded-full" style={{
|
||||
width: `${progress}%`,
|
||||
backgroundImage: 'linear-gradient(90deg, #9034EA, #5146E5, #9034EA)',
|
||||
backgroundSize: '200% 100%',
|
||||
animation: 'gradient 2s linear infinite'
|
||||
}}>
|
||||
<div className="absolute inset-0 opacity-25" style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(45deg, rgba(255,255,255,.8) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.8) 50%, rgba(255,255,255,.8) 75%, transparent 75%, transparent)',
|
||||
backgroundSize: '16px 16px',
|
||||
animation: 'stripes 1s linear infinite'
|
||||
}} />
|
||||
</div>
|
||||
<div className="absolute inset-0" />
|
||||
</div>
|
||||
<style jsx>{`
|
||||
@keyframes gradient {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
@keyframes stripes {
|
||||
to { background-position: 16px 0; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Timer
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
export const useAPIKeyCheck = () => {
|
||||
const [hasRequiredKey, setHasAnthropicKey] = useState(false);
|
||||
const [isRequiredKeyLoading, setIsAnthropicKeyLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/has-required-key")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setHasAnthropicKey(data.hasKey);
|
||||
setIsAnthropicKeyLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { hasRequiredKey, isRequiredKeyLoading };
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import { useState, useEffect } from "react";
|
||||
|
||||
export const useAnthropicKeyCheck = () => {
|
||||
const [hasAnthropicKey, setHasAnthropicKey] = useState(false);
|
||||
const [isAnthropicKeyLoading, setIsAnthropicKeyLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/has-anthropic-key")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setHasAnthropicKey(data.hasKey);
|
||||
setIsAnthropicKeyLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { hasAnthropicKey, isAnthropicKeyLoading };
|
||||
};
|
||||
|
|
@ -7,7 +7,8 @@ import { ProcessedSlide, UploadedFont } from "../types";
|
|||
export const useLayoutSaving = (
|
||||
slides: ProcessedSlide[],
|
||||
UploadedFonts: UploadedFont[],
|
||||
refetch: () => void
|
||||
refetch: () => void,
|
||||
setSlides: React.Dispatch<React.SetStateAction<ProcessedSlide[]>>
|
||||
) => {
|
||||
const [isSavingLayout, setIsSavingLayout] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
|
@ -80,7 +81,7 @@ export const useLayoutSaving = (
|
|||
|
||||
try {
|
||||
// Convert each slide HTML to React component
|
||||
const reactComponents = [];
|
||||
const reactComponents: any[] = [];
|
||||
const presentationId = uuidv4();
|
||||
|
||||
// Get all uploaded font URLs
|
||||
|
|
@ -95,6 +96,9 @@ export const useLayoutSaving = (
|
|||
continue;
|
||||
}
|
||||
|
||||
// Mark current slide as converting to React
|
||||
setSlides(prev => prev.map((s, idx) => idx === i ? { ...s, convertingToReact: true } : s));
|
||||
|
||||
try {
|
||||
const reactComponent = await convertSlideToReact(slide, presentationId, FontUrls);
|
||||
reactComponents.push(reactComponent);
|
||||
|
|
@ -112,7 +116,9 @@ export const useLayoutSaving = (
|
|||
: "An unexpected error occurred",
|
||||
});
|
||||
// Continue with other slides even if one fails
|
||||
continue;
|
||||
} finally {
|
||||
// Clear converting flag for this slide
|
||||
setSlides(prev => prev.map((s, idx) => idx === i ? { ...s, convertingToReact: false } : s));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +175,7 @@ export const useLayoutSaving = (
|
|||
} finally {
|
||||
setIsSavingLayout(false);
|
||||
}
|
||||
}, [slides, UploadedFonts, refetch, closeSaveModal]);
|
||||
}, [slides, UploadedFonts, refetch, closeSaveModal, setSlides]);
|
||||
|
||||
return {
|
||||
isSavingLayout,
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ import { useFontManagement } from "./hooks/useFontManagement";
|
|||
import { useFileUpload } from "./hooks/useFileUpload";
|
||||
import { useSlideProcessing } from "./hooks/useSlideProcessing";
|
||||
import { useLayoutSaving } from "./hooks/useLayoutSaving";
|
||||
import { useAnthropicKeyCheck } from "./hooks/useAnthropicKeyCheck";
|
||||
import { LoadingSpinner } from "./components/LoadingSpinner";
|
||||
import { AnthropicKeyWarning } from "./components/AnthropicKeyWarning";
|
||||
import { FileUploadSection } from "./components/FileUploadSection";
|
||||
import { SaveLayoutButton } from "./components/SaveLayoutButton";
|
||||
import { SaveLayoutModal } from "./components/SaveLayoutModal";
|
||||
import EachSlide from "./components/EachSlide/NewEachSlide";
|
||||
import { APIKeyWarning } from "./components/APIKeyWarning";
|
||||
import { useAPIKeyCheck } from "./hooks/useAPIKeyCheck";
|
||||
|
||||
|
||||
const CustomLayoutPage = () => {
|
||||
const { refetch } = useLayout();
|
||||
|
||||
// Custom hooks for different concerns
|
||||
const { hasAnthropicKey, isAnthropicKeyLoading } = useAnthropicKeyCheck();
|
||||
const { hasRequiredKey, isRequiredKeyLoading } = useAPIKeyCheck();
|
||||
const { selectedFile, handleFileSelect, removeFile } = useFileUpload();
|
||||
const { slides, setSlides, completedSlides } = useCustomLayout();
|
||||
const { fontsData, UploadedFonts, uploadFont, removeFont, getAllUnsupportedFonts, setFontsData } = useFontManagement();
|
||||
|
|
@ -35,7 +35,8 @@ const CustomLayoutPage = () => {
|
|||
const { isSavingLayout, isModalOpen, openSaveModal, closeSaveModal, saveLayout } = useLayoutSaving(
|
||||
slides,
|
||||
UploadedFonts,
|
||||
refetch
|
||||
refetch,
|
||||
setSlides
|
||||
);
|
||||
|
||||
const handleProcessSlideToHtml = (slide: any) => {
|
||||
|
|
@ -58,13 +59,13 @@ const CustomLayoutPage = () => {
|
|||
};
|
||||
|
||||
// Loading state
|
||||
if (isAnthropicKeyLoading) {
|
||||
return <LoadingSpinner message="Checking Anthropic Key..." />;
|
||||
if (isRequiredKeyLoading) {
|
||||
return <LoadingSpinner message="Checking API Key..." />;
|
||||
}
|
||||
|
||||
// Anthropic key warning
|
||||
if (!hasAnthropicKey) {
|
||||
return <AnthropicKeyWarning />;
|
||||
if (!hasRequiredKey) {
|
||||
return <APIKeyWarning />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface ProcessedSlide extends SlideData {
|
|||
processed?: boolean;
|
||||
error?: string;
|
||||
modified?: boolean;
|
||||
convertingToReact?: boolean; // indicates HTML-to-React conversion in progress
|
||||
}
|
||||
|
||||
export interface FontData {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,54 @@
|
|||
"use client";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
// import { useGroupLayoutLoader } from '../hooks/useGroupLayoutLoader'
|
||||
import LoadingStates from "../components/LoadingStates";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft, Home, Trash2 } from "lucide-react";
|
||||
import { ArrowLeft, Edit, Home, Trash2 } from "lucide-react";
|
||||
import { useLayout } from "@/app/(presentation-generator)/context/LayoutContext";
|
||||
import { useDrawingCanvas } from "../../custom-layout/hooks/useDrawingCanvas";
|
||||
import { EditControls } from "../../custom-layout/components/EachSlide/EditControls";
|
||||
import html2canvas from "html2canvas";
|
||||
|
||||
const GroupLayoutPreview = () => {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const slug = params.slug as string;
|
||||
// const isCustom = slug.includes("custom-");
|
||||
const isCustom = true;
|
||||
// Custom hooks
|
||||
const {
|
||||
canvasRef,
|
||||
slideDisplayRef,
|
||||
strokeWidth,
|
||||
strokeColor,
|
||||
eraserMode,
|
||||
isDrawing,
|
||||
canvasDimensions,
|
||||
setCanvasDimensions,
|
||||
didYourDraw,
|
||||
handleMouseDown,
|
||||
handleMouseMove,
|
||||
handleMouseUp,
|
||||
handleTouchStart,
|
||||
handleTouchMove,
|
||||
handleTouchEnd,
|
||||
handleClearCanvas,
|
||||
handleEraserModeChange,
|
||||
handleStrokeColorChange,
|
||||
handleStrokeWidthChange,
|
||||
} = useDrawingCanvas();
|
||||
|
||||
const slideContentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const { getFullDataByGroup, loading,refetch } = useLayout();
|
||||
const layoutGroup = getFullDataByGroup(slug);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const existingScript = document.querySelector(
|
||||
|
|
@ -27,6 +62,17 @@ const GroupLayoutPreview = () => {
|
|||
}
|
||||
}, [slug]);
|
||||
|
||||
// Size canvas to content when entering edit mode
|
||||
useEffect(() => {
|
||||
if (isEditMode && slideContentRef.current) {
|
||||
const rect = slideContentRef.current.getBoundingClientRect();
|
||||
setCanvasDimensions({
|
||||
width: Math.max(rect.width, 800),
|
||||
height: Math.max(rect.height, 600),
|
||||
});
|
||||
}
|
||||
}, [isEditMode, setCanvasDimensions]);
|
||||
|
||||
// Handle loading state
|
||||
if (loading) {
|
||||
return <LoadingStates type="loading" />;
|
||||
|
|
@ -47,6 +93,130 @@ const GroupLayoutPreview = () => {
|
|||
router.push("/layout-preview");
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async (
|
||||
slideDisplayRef: React.RefObject<HTMLDivElement |null>,
|
||||
didYourDraw: boolean
|
||||
) => {
|
||||
if (
|
||||
!slideContentRef.current ||
|
||||
!slideDisplayRef.current
|
||||
)
|
||||
return;
|
||||
|
||||
if (!prompt.trim()) {
|
||||
alert("Please enter a prompt before saving.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
// Take screenshot of the slide display area (slide only)
|
||||
const slideOnly = await html2canvas(slideDisplayRef.current, {
|
||||
backgroundColor: "#ffffff",
|
||||
scale: 1,
|
||||
logging: false,
|
||||
useCORS: true,
|
||||
ignoreElements: (element) => {
|
||||
return element.tagName === "CANVAS";
|
||||
},
|
||||
});
|
||||
let slideWithCanvas;
|
||||
if (didYourDraw) {
|
||||
// Take screenshot of the entire slide display area including canvas
|
||||
slideWithCanvas = await html2canvas(slideDisplayRef.current, {
|
||||
backgroundColor: "#ffffff",
|
||||
scale: 1,
|
||||
logging: false,
|
||||
useCORS: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const currentUiImageBlob = dataURLToBlob(
|
||||
slideOnly.toDataURL("image/png")
|
||||
);
|
||||
let sketchImageBlob;
|
||||
if (didYourDraw && slideWithCanvas) {
|
||||
sketchImageBlob = dataURLToBlob(slideWithCanvas.toDataURL("image/png"));
|
||||
}
|
||||
|
||||
// download the images
|
||||
|
||||
const currentUiImageUrl = URL.createObjectURL(currentUiImageBlob);
|
||||
const sketchImageUrl = didYourDraw ? URL.createObjectURL(sketchImageBlob) : null;
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = currentUiImageUrl;
|
||||
a.download = `slide-current.png`;
|
||||
a.click();
|
||||
|
||||
if (sketchImageUrl) {
|
||||
const b = document.createElement("a");
|
||||
b.href = sketchImageUrl;
|
||||
b.download = `slide-sketch.png`;
|
||||
b.click();
|
||||
}
|
||||
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append(
|
||||
// "current_ui_image",
|
||||
// currentUiImageBlob,
|
||||
// `slide--current.png`
|
||||
// );
|
||||
// if (didYourDraw && slideWithCanvas && sketchImageBlob) {
|
||||
// formData.append(
|
||||
// "sketch_image",
|
||||
// sketchImageBlob,
|
||||
// `slide-sketch.png`
|
||||
// );
|
||||
// }
|
||||
// formData.append("html", '');
|
||||
// formData.append("prompt", prompt);
|
||||
|
||||
// const response = await fetch("/api/v1/ppt/html-edit/", {
|
||||
// method: "POST",
|
||||
// body: formData,
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`API call failed: ${response.statusText}`);
|
||||
// }
|
||||
|
||||
// const data = await response.json();
|
||||
|
||||
|
||||
// Exit edit mode
|
||||
setIsEditMode(false);
|
||||
setPrompt("");
|
||||
} catch (error) {
|
||||
console.error("Error updating slide:", error);
|
||||
alert(
|
||||
`Error updating slide: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`
|
||||
);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
};
|
||||
const dataURLToBlob = (dataURL: string): Blob => {
|
||||
const parts = dataURL.split(",");
|
||||
const contentType = parts[0].match(/:(.*?);/)?.[1] || "image/png";
|
||||
const raw = window.atob(parts[1]);
|
||||
const rawLength = raw.length;
|
||||
const uInt8Array = new Uint8Array(rawLength);
|
||||
|
||||
for (let i = 0; i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i);
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], { type: contentType });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
|
|
@ -72,7 +242,7 @@ const GroupLayoutPreview = () => {
|
|||
<Home className="w-4 h-4" />
|
||||
All Groups
|
||||
</Button>
|
||||
{slug.includes('custom-') && <button className=" border border-red-200 flex justify-center items-center gap-2 text-red-700 px-4 py-1 rounded-md" onClick={() => {
|
||||
{isCustom && <button className=" border border-red-200 flex justify-center items-center gap-2 text-red-700 px-4 py-1 rounded-md" onClick={() => {
|
||||
deleteLayouts();
|
||||
}}><Trash2 className="w-4 h-4" />Delete</button>}
|
||||
</div>
|
||||
|
|
@ -90,8 +260,37 @@ const GroupLayoutPreview = () => {
|
|||
</div>
|
||||
</header>
|
||||
|
||||
{/* Layout Grid */}
|
||||
|
||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||
{/* Edit Controls (no HTML editor) */}
|
||||
{isCustom && (
|
||||
<EditControls
|
||||
isEditMode={isEditMode}
|
||||
prompt={prompt}
|
||||
isUpdating={isUpdating}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeColor={strokeColor}
|
||||
eraserMode={eraserMode}
|
||||
onPromptChange={setPrompt}
|
||||
onSave={() => {
|
||||
setIsUpdating(true);
|
||||
setTimeout(() => {
|
||||
setIsUpdating(false);
|
||||
setIsEditMode(false);
|
||||
setSelectedIndex(null);
|
||||
}, 300);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsEditMode(false);
|
||||
setSelectedIndex(null);
|
||||
handleClearCanvas();
|
||||
}}
|
||||
onStrokeWidthChange={handleStrokeWidthChange}
|
||||
onStrokeColorChange={handleStrokeColorChange}
|
||||
onEraserModeChange={handleEraserModeChange}
|
||||
onClearCanvas={handleClearCanvas}
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-8">
|
||||
{layoutGroup.map((layout: any, index: number) => {
|
||||
const {
|
||||
|
|
@ -101,6 +300,8 @@ const GroupLayoutPreview = () => {
|
|||
fileName,
|
||||
} = layout;
|
||||
|
||||
const isSelected = isCustom && isEditMode && selectedIndex === index;
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={`${layoutGroup[0].groupName}-${index}`}
|
||||
|
|
@ -123,16 +324,53 @@ const GroupLayoutPreview = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-700">
|
||||
Layout #{index + 1}
|
||||
</div>
|
||||
{isCustom && (
|
||||
<button
|
||||
className="border flex items-center gap-2 border-blue-400 bg-blue-50 px-4 py-1 rounded-md text-blue-700"
|
||||
onClick={() => {
|
||||
setIsEditMode(true);
|
||||
setSelectedIndex(index);
|
||||
}}
|
||||
>
|
||||
<Edit className="w-4 h-4" />Edit
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Layout Content */}
|
||||
<div className="bg-gray-50 aspect-video max-w-[1280px] w-full">
|
||||
<LayoutComponent data={sampleData} />
|
||||
<div ref={isSelected ? slideDisplayRef : undefined} className="relative mx-auto w-full">
|
||||
<div
|
||||
ref={isSelected ? slideContentRef : undefined}
|
||||
className="bg-gray-50 aspect-video max-w-[1280px] w-full"
|
||||
>
|
||||
<LayoutComponent data={sampleData} />
|
||||
{isSelected && (
|
||||
<canvas
|
||||
ref={canvasRef!}
|
||||
width={canvasDimensions.width}
|
||||
height={canvasDimensions.height}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 30,
|
||||
cursor: eraserMode ? "grab" : "crosshair",
|
||||
pointerEvents: "auto",
|
||||
touchAction: "none",
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ import { NextResponse } from "next/server";
|
|||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const hasKey = process.env.ANTHROPIC_API_KEY !== "";
|
||||
const hasKey = process.env.GOOGLE_API_KEY !== "";
|
||||
return NextResponse.json({ hasKey });
|
||||
}
|
||||
2
servers/nextjs/next-env.d.ts
vendored
2
servers/nextjs/next-env.d.ts
vendored
|
|
@ -2,4 +2,4 @@
|
|||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
|
|
|
|||
251
servers/nextjs/package-lock.json
generated
251
servers/nextjs/package-lock.json
generated
|
|
@ -45,7 +45,7 @@
|
|||
"lucide-react": "^0.447.0",
|
||||
"marked": "^15.0.11",
|
||||
"mermaid": "^11.9.0",
|
||||
"next": "15.4.5",
|
||||
"next": "^14.2.14",
|
||||
"next-themes": "^0.4.6",
|
||||
"prismjs": "^1.30.0",
|
||||
"puppeteer": "^24.13.0",
|
||||
|
|
@ -67,8 +67,8 @@
|
|||
"@types/node": "^20",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/puppeteer": "^5.4.7",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"cypress": "^14.3.3",
|
||||
|
|
@ -1425,15 +1425,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.5.tgz",
|
||||
"integrity": "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.31.tgz",
|
||||
"integrity": "sha512-X8VxxYL6VuezrG82h0pUA1V+DuTSJp7Nv15bxq3ivrFqZLjx81rfeHMWOE9T0jm1n3DtHGv8gdn6B0T0kr0D3Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.5.tgz",
|
||||
"integrity": "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.31.tgz",
|
||||
"integrity": "sha512-dTHKfaFO/xMJ3kzhXYgf64VtV6MMwDs2viedDOdP+ezd0zWMOQZkxcwOfdcQeQCpouTr9b+xOqMCUXxgLizl8Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1447,9 +1447,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.31.tgz",
|
||||
"integrity": "sha512-iSavebQgeMukUAfjfW8Fi2Iz01t95yxRl2w2wCzjD91h5In9la99QIDKcKSYPfqLjCgwz3JpIWxLG6LM/sxL4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1463,9 +1463,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.31.tgz",
|
||||
"integrity": "sha512-XJb3/LURg1u1SdQoopG6jDL2otxGKChH2UYnUTcby4izjM0il7ylBY5TIA7myhvHj9lG5pn9F2nR2s3i8X9awQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1479,9 +1479,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.31.tgz",
|
||||
"integrity": "sha512-IInDAcchNCu3BzocdqdCv1bKCmUVO/bKJHnBFTeq3svfaWpOPewaLJ2Lu3GL4yV76c/86ZvpBbG/JJ1lVIs5MA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1495,9 +1495,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.31.tgz",
|
||||
"integrity": "sha512-YTChJL5/9e4NXPKW+OJzsQa42RiWUNbE+k+ReHvA+lwXk+bvzTsVQboNcezWOuCD+p/J+ntxKOB/81o0MenBhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1511,9 +1511,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.31.tgz",
|
||||
"integrity": "sha512-A0JmD1y4q/9ufOGEAhoa60Sof++X10PEoiWOH0gZ2isufWZeV03NnyRlRmJpRQWGIbRkJUmBo9I3Qz5C10vx4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1527,9 +1527,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.31.tgz",
|
||||
"integrity": "sha512-nowJ5GbMeDOMzbTm29YqrdrD6lTM8qn2wnZfGpYMY7SZODYYpaJHH1FJXE1l1zWICHR+WfIMytlTDBHu10jb8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1542,10 +1542,26 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.31.tgz",
|
||||
"integrity": "sha512-pk9Bu4K0015anTS1OS9d/SpS0UtRObC+xe93fwnm7Gvqbv/W1ZbzhK4nvc96RURIQOux3P/bBH316xz8wjGSsA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"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==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.31.tgz",
|
||||
"integrity": "sha512-LwFZd4JFnMHGceItR9+jtlMm8lGLU/IPkgjBBgYmdYSfalbHCiDpjMYtgDQ2wtwiAOSJOCyFI4m8PikrsDyA6Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -2823,13 +2839,20 @@
|
|||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
|
||||
"integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
"@swc/counter": "^0.1.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
|
|
@ -3593,9 +3616,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
|
||||
"integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==",
|
||||
"version": "20.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.10.tgz",
|
||||
"integrity": "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -3609,6 +3632,13 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/puppeteer": {
|
||||
"version": "5.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.7.tgz",
|
||||
|
|
@ -3620,23 +3650,24 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
|
||||
"integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==",
|
||||
"version": "18.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
|
||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sinonjs__fake-timers": {
|
||||
|
|
@ -3951,9 +3982,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bare-events": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.0.tgz",
|
||||
"integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz",
|
||||
"integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true
|
||||
},
|
||||
|
|
@ -4152,6 +4183,17 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cachedir": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz",
|
||||
|
|
@ -4212,9 +4254,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001731",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz",
|
||||
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==",
|
||||
"version": "1.0.30001733",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz",
|
||||
"integrity": "sha512-e4QKw/O2Kavj2VQTKZWrwzkt3IxOmIlU6ajRb6LP64LHpBo1J67k2Hi4Vu/TgJWsNtynurfS0uK3MaUTCPfu5Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -4679,9 +4721,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "14.5.3",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.3.tgz",
|
||||
"integrity": "sha512-syLwKjDeMg77FRRx68bytLdlqHXDT4yBVh0/PPkcgesChYDjUZbwxLqMXuryYKzAyJsPsQHUDW1YU74/IYEUIA==",
|
||||
"version": "14.5.4",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.4.tgz",
|
||||
"integrity": "sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
|
|
@ -5346,9 +5388,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1464554",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz",
|
||||
"integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==",
|
||||
"version": "0.0.1475386",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz",
|
||||
"integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
|
|
@ -6107,7 +6149,6 @@
|
|||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hachure-fill": {
|
||||
|
|
@ -7143,40 +7184,41 @@
|
|||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.4.5",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.4.5.tgz",
|
||||
"integrity": "sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==",
|
||||
"version": "14.2.31",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-14.2.31.tgz",
|
||||
"integrity": "sha512-Wyw1m4t8PhqG+or5a1U/Deb888YApC4rAez9bGhHkTsfwAy4SWKVro0GhEx4sox1856IbLhvhce2hAA6o8vkog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.4.5",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"@next/env": "14.2.31",
|
||||
"@swc/helpers": "0.5.5",
|
||||
"busboy": "1.6.0",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.6"
|
||||
"styled-jsx": "5.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"next": "dist/bin/next"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
"node": ">=18.17.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@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"
|
||||
"@next/swc-darwin-arm64": "14.2.31",
|
||||
"@next/swc-darwin-x64": "14.2.31",
|
||||
"@next/swc-linux-arm64-gnu": "14.2.31",
|
||||
"@next/swc-linux-arm64-musl": "14.2.31",
|
||||
"@next/swc-linux-x64-gnu": "14.2.31",
|
||||
"@next/swc-linux-x64-musl": "14.2.31",
|
||||
"@next/swc-win32-arm64-msvc": "14.2.31",
|
||||
"@next/swc-win32-ia32-msvc": "14.2.31",
|
||||
"@next/swc-win32-x64-msvc": "14.2.31"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"babel-plugin-react-compiler": "*",
|
||||
"react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
|
||||
"@playwright/test": "^1.41.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.3.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
|
@ -7186,9 +7228,6 @@
|
|||
"@playwright/test": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
}
|
||||
|
|
@ -7797,9 +7836,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prosemirror-model": {
|
||||
"version": "1.25.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.2.tgz",
|
||||
"integrity": "sha512-BVypCAJ4SL6jOiTsDffP3Wp6wD69lRhI4zg/iT8JXjp3ccZFiq5WyguxvMKmdKFC3prhaig7wSr8dneDToHE1Q==",
|
||||
"version": "1.25.3",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.3.tgz",
|
||||
"integrity": "sha512-dY2HdaNXlARknJbrManZ1WyUtos+AP97AmvqdOQtWtrrC5g4mohVX5DTi9rXNFSk09eczLq9GuNTtq3EfMeMGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"orderedmap": "^2.0.0"
|
||||
|
|
@ -7948,17 +7987,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/puppeteer": {
|
||||
"version": "24.15.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.15.0.tgz",
|
||||
"integrity": "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ==",
|
||||
"version": "24.16.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.16.0.tgz",
|
||||
"integrity": "sha512-5qxFGOpdAzYexoPwKPEF4L/IYKYOFE1MxWsqcp7K33HySM8N8S/yZwSQCaV0rzmJsTLX5LxU4zt65+ceNiVDgQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.10.6",
|
||||
"chromium-bidi": "7.2.0",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"devtools-protocol": "0.0.1464554",
|
||||
"puppeteer-core": "24.15.0",
|
||||
"devtools-protocol": "0.0.1475386",
|
||||
"puppeteer-core": "24.16.0",
|
||||
"typed-query-selector": "^2.12.0"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -7969,15 +8008,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/puppeteer-core": {
|
||||
"version": "24.15.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.15.0.tgz",
|
||||
"integrity": "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg==",
|
||||
"version": "24.16.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.16.0.tgz",
|
||||
"integrity": "sha512-tZ0tJiOYaDGTRzzr2giDpf8O/55JsoqkrafS1Xu4H6S8oP4eeL6RbZzY9OzjShSf5EQvx/zAc55QKpDqzXos/Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.10.6",
|
||||
"chromium-bidi": "7.2.0",
|
||||
"debug": "^4.4.1",
|
||||
"devtools-protocol": "0.0.1464554",
|
||||
"devtools-protocol": "0.0.1475386",
|
||||
"typed-query-selector": "^2.12.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
|
|
@ -8747,6 +8786,14 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/streamx": {
|
||||
"version": "2.22.1",
|
||||
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz",
|
||||
|
|
@ -8825,9 +8872,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
"integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
||||
"integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"client-only": "0.0.1"
|
||||
|
|
@ -8836,7 +8883,7 @@
|
|||
"node": ">= 12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
|
||||
"react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
|
|
@ -9207,9 +9254,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
|
||||
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -9669,9 +9716,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.15.tgz",
|
||||
"integrity": "sha512-2IVHb9h4Mt6+UXkyMs0XbfICUh1eUrlJJAOupBHUhLRnKkruawyDddYRCs0Eizt900ntIMk9/4RksYl+FgSpcQ==",
|
||||
"version": "4.0.16",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.16.tgz",
|
||||
"integrity": "sha512-Djo/cM339grjI7/HmN+ixYO2FzEMcWr/On50UlQ/RjrWK1I/hPpWhpC76heCptnRFpH0LMwrEbUY50HDc0V8wg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"dev": "next dev ",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
"lucide-react": "^0.447.0",
|
||||
"marked": "^15.0.11",
|
||||
"mermaid": "^11.9.0",
|
||||
"next": "15.4.5",
|
||||
"next": "^14.2.14",
|
||||
"next-themes": "^0.4.6",
|
||||
"prismjs": "^1.30.0",
|
||||
"puppeteer": "^24.13.0",
|
||||
|
|
@ -69,8 +69,8 @@
|
|||
"@types/node": "^20",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/puppeteer": "^5.4.7",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"cypress": "^14.3.3",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue