diff --git a/app/ipc/index.ts b/app/ipc/index.ts index 137b3dab..aedb1154 100644 --- a/app/ipc/index.ts +++ b/app/ipc/index.ts @@ -5,6 +5,7 @@ import { setupReadFile } from "./read_file"; import { setupFooterHandlers } from "./footer_handlers"; import { setupThemeHandlers } from "./theme_handlers"; import { setupUploadImage } from "./upload_image"; +import { setupLogHandler } from "./log_handler"; export function setupIpcHandlers() { setupExportHandlers(); setupUserConfigHandlers(); @@ -13,4 +14,5 @@ export function setupIpcHandlers() { setupFooterHandlers(); setupThemeHandlers(); setupUploadImage(); + setupLogHandler(); } \ No newline at end of file diff --git a/app/ipc/log_handler.ts b/app/ipc/log_handler.ts new file mode 100644 index 00000000..213c0b00 --- /dev/null +++ b/app/ipc/log_handler.ts @@ -0,0 +1,50 @@ +import { ipcMain } from 'electron'; +import * as fs from 'fs'; +import * as path from 'path'; +import { userDataDir } from '../utils/constants'; + +export function setupLogHandler() { + // Ensure logs directory exists + const logsDir = path.join(userDataDir, 'logs'); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + + const logFilePath = path.join(logsDir, 'nextjs.log'); + + // Handle log writing through IPC - non-blocking + ipcMain.handle('write-nextjs-log', (_, logData: string) => { + try { + const timestamp = new Date().toISOString(); + const logEntry = `[${timestamp}] ${logData}\n`; + + // Use non-blocking write + fs.appendFile(logFilePath, logEntry, (err) => { + if (err) { + console.error('Error writing to log file:', err); + } + }); + + return { success: true }; + } catch (error) { + console.error('Error in log handler:', error); + return { success: false, error: (error as Error).message }; + } + }); + + // Handle log clearing + ipcMain.handle('clear-nextjs-logs', () => { + try { + // Create a new empty file, effectively clearing the old one + fs.writeFile(logFilePath, '', (err) => { + if (err) { + console.error('Error clearing log file:', err); + } + }); + return { success: true }; + } catch (error) { + console.error('Error in clear logs handler:', error); + return { success: false, error: (error as Error).message }; + } + }); +} diff --git a/app/preloads/index.ts b/app/preloads/index.ts index fa589931..c5b3e49a 100644 --- a/app/preloads/index.ts +++ b/app/preloads/index.ts @@ -21,4 +21,6 @@ contextBridge.exposeInMainWorld('electron', { getTheme: (userId: string) => ipcRenderer.invoke("get-theme", userId), setTheme: (userId: string, themeData: any) => ipcRenderer.invoke("set-theme", userId, themeData), uploadImage: (file: Buffer) => ipcRenderer.invoke("upload-image", file), + writeNextjsLog: (logData: string) => ipcRenderer.invoke("write-nextjs-log", logData), + clearNextjsLogs: () => ipcRenderer.invoke("clear-nextjs-logs"), }); diff --git a/package-lock.json b/package-lock.json index e7417ad9..9b96fa25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "presenton", - "version": "0.1.0-beta", + "version": "0.2.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "presenton", - "version": "0.1.0-beta", + "version": "0.2.0-beta", "dependencies": { "@tailwindcss/cli": "^4.1.5", "dotenv": "^16.5.0", diff --git a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx index 062eb78f..87b7c643 100644 --- a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx @@ -35,6 +35,7 @@ import { } from "@/components/ui/popover"; import ToolTip from "@/components/ToolTip"; import { getEnv } from "@/utils/constant"; +import { clearLogs, logOperation } from "../utils/log"; interface ImageEditorProps { initialImage: string | null; @@ -96,6 +97,7 @@ const ImageEditor = ({ useEffect(() => { setImage(initialImage); setPreviewImages([initialImage]); + }, [initialImage]); // Close toolbar when clicking outside @@ -110,7 +112,7 @@ const ImageEditor = ({ ) { setIsToolbarOpen(false); if (isFocusPointMode) { - // saveFocusPoint(); // Save focus point before closing + logOperation(`Saving focus point for slide ${slideIndex}, element ${elementId}: x=${focusPoint.x}, y=${focusPoint.y}`); saveImageProperties(objectFit, focusPoint); } setIsFocusPointMode(false); @@ -125,16 +127,19 @@ const ImageEditor = ({ const handleImageClick = () => { if (!isFocusPointMode) { + logOperation(`Opening toolbar for slide ${slideIndex}, element ${elementId}`); setIsToolbarOpen(true); } }; const handleOpenEditor = () => { + logOperation(`Opening image editor for slide ${slideIndex}, element ${elementId}`); setIsToolbarOpen(false); setIsEditorOpen(true); }; const handleImageChange = (newImage: string) => { + logOperation(`Changing image for slide ${slideIndex}, element ${elementId}`); setImage(newImage); dispatch( updateSlideImage({ @@ -159,31 +164,28 @@ const ImageEditor = ({ Math.min(100, ((e.clientY - rect.top) / rect.height) * 100) ); + logOperation(`Setting focus point for slide ${slideIndex}, element ${elementId}: x=${x}, y=${y}`); setFocusPoint({ x, y }); saveImageProperties(objectFit, { x, y }); - // Apply the focus point in real-time if (imageRef.current) { imageRef.current.style.objectPosition = `${x}% ${y}%`; } }; const toggleFocusPointMode = () => { - if (isFocusPointMode) { - // If turning off focus point mode, save the current focus point - // saveFocusPoint(); - } + logOperation(`Toggling focus point mode for slide ${slideIndex}, element ${elementId}: ${!isFocusPointMode}`); setIsFocusPointMode(!isFocusPointMode); }; const handleFitChange = (fit: "cover" | "contain" | "fill") => { + logOperation(`Changing image fit for slide ${slideIndex}, element ${elementId}: ${fit}`); setObjectFit(fit); if (imageRef.current) { imageRef.current.style.objectFit = fit; } - // Save the fit change to your state saveImageProperties(fit, focusPoint); }; @@ -191,6 +193,7 @@ const ImageEditor = ({ fit: "cover" | "contain" | "fill", focusPoint: { x: number; y: number } ) => { + logOperation(`Saving image properties for slide ${slideIndex}, element ${elementId}: fit=${fit}, focusPoint=(${focusPoint.x},${focusPoint.y})`); const propertiesData = { initialObjectFit: fit, initialFocusPoint: focusPoint, @@ -207,6 +210,7 @@ const ImageEditor = ({ const handleGenerateImage = async () => { try { + logOperation(`Generating image for slide ${slideIndex}, element ${elementId} with prompt: ${prompt}`); setIsGenerating(true); setError(null); @@ -221,33 +225,34 @@ const ImageEditor = ({ }, }); + logOperation(`Image generation successful for slide ${slideIndex}, element ${elementId}`); setPreviewImages(response.paths); } catch (err) { - setError("Failed to generate image. Please try again."); + const errorMessage = "Failed to generate image. Please try again."; + logOperation(`Image generation failed for slide ${slideIndex}, element ${elementId}: ${err}`); + setError(errorMessage); } finally { setIsGenerating(false); } }; - const handleFileUpload = async ( - event: React.ChangeEvent - ) => { + const handleFileUpload = async (event: React.ChangeEvent) => { const presentation_id = searchParams.get("id"); const file = event.target.files?.[0]; if (!file) return; - // Check file size (e.g., 5MB limit) + logOperation(`Attempting to upload file for slide ${slideIndex}, element ${elementId}: ${file.name}`); + if (file.size > 5 * 1024 * 1024) { const error_message = "File size should be less than 5MB"; - + logOperation(`File upload failed for slide ${slideIndex}, element ${elementId}: File too large`); setUploadError(error_message); return; } - // Check file type if (!file.type.startsWith("image/")) { const error_message = "Please upload an image file"; - + logOperation(`File upload failed for slide ${slideIndex}, element ${elementId}: Invalid file type`); setUploadError(error_message); return; } @@ -256,18 +261,15 @@ const ImageEditor = ({ setIsUploading(true); setUploadError(null); - // Convert file to buffer const buffer = await file.arrayBuffer(); - - // Send to electron main process // @ts-ignore const relativePath = await window.electron.uploadImage(Buffer.from(buffer)); - // Update state with the returned path + logOperation(`File upload successful for slide ${slideIndex}, element ${elementId}: ${relativePath}`); setUploadedImageUrl(relativePath); } catch (err) { const error_message = "Failed to upload image. Please try again."; - + logOperation(`File upload failed for slide ${slideIndex}, element ${elementId}: ${err}`); setUploadError(error_message); console.error("Upload error:", err); } finally { diff --git a/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx b/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx index 08447af4..3f89a784 100644 --- a/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx +++ b/servers/nextjs/app/(presentation-generator)/context/footerContext.tsx @@ -5,6 +5,7 @@ import { FooterProperties, useFooterService, } from "../services/footerService"; +import { clearLogs, logOperation } from "../utils/log"; // Default footer properties export const defaultFooterProperties: FooterProperties = { @@ -57,11 +58,16 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ useEffect(() => { const loadFooterProperties = async () => { try { + logOperation('Loading footer properties'); const properties = await footerService.getFooterProperties(); if (properties) { + logOperation('Footer properties loaded successfully'); setFooterProperties(properties); + } else { + logOperation('No footer properties found, using defaults'); } } catch (error) { + logOperation(`Error loading footer properties: ${error}`); console.error("Failed to load footer properties:", error); } }; @@ -71,22 +77,32 @@ export const FooterProvider: React.FC<{ children: React.ReactNode }> = ({ const resetFooterProperties = async () => { try { + logOperation('Resetting footer properties to defaults'); const success = await footerService.resetFooterProperties(defaultFooterProperties); if (success) { + logOperation('Footer properties reset successfully'); setFooterProperties(defaultFooterProperties); + } else { + logOperation('Failed to reset footer properties'); } } catch (error) { + logOperation(`Error resetting footer properties: ${error}`); console.error("Failed to reset footer properties:", error); } }; const saveFooterProperties = async (newProperties: FooterProperties) => { try { + logOperation('Saving new footer properties'); const success = await footerService.saveFooterProperties(newProperties); if (success) { + logOperation('Footer properties saved successfully'); setFooterProperties(newProperties); + } else { + logOperation('Failed to save footer properties'); } } catch (error) { + logOperation(`Error saving footer properties: ${error}`); console.error("Failed to save footer properties:", error); } }; diff --git a/servers/nextjs/app/(presentation-generator)/create/components/CreatePage.tsx b/servers/nextjs/app/(presentation-generator)/create/components/CreatePage.tsx index 15a6aae5..682ba822 100644 --- a/servers/nextjs/app/(presentation-generator)/create/components/CreatePage.tsx +++ b/servers/nextjs/app/(presentation-generator)/create/components/CreatePage.tsx @@ -27,6 +27,7 @@ import { } from "@/store/slices/presentationGeneration"; import { OverlayLoader } from "@/components/ui/overlay-loader"; import Wrapper from "@/components/Wrapper"; +import { clearLogs, logOperation } from "../../utils/log"; const CreatePage = () => { const dispatch = useDispatch(); @@ -71,12 +72,11 @@ const CreatePage = () => { if (!active || !over || !titles) return; if (active.id !== over.id) { + logOperation(`Reordering slides: ${active.id} -> ${over.id}`); // Find the indices of the dragged and target items const oldIndex = titles.findIndex((item) => item === active.id); const newIndex = titles.findIndex((item) => item === over.id); - // Create new array with reordered items and updated indices - // Reorder the array const reorderedArray = arrayMove(titles, oldIndex, newIndex); @@ -86,6 +86,7 @@ const CreatePage = () => { }; const handleSubmit = async () => { + logOperation('Starting presentation generation'); // Generate data setLoadingState({ message: "Generating data...", @@ -94,8 +95,6 @@ const CreatePage = () => { duration: 10, }); try { - - const response = await PresentationGenerationApi.generateData({ presentation_id: presentation_id, theme: { @@ -105,17 +104,17 @@ const CreatePage = () => { watermark: false, images: images, titles: titles, - }); if (response) { + logOperation('Presentation data generated successfully'); dispatch(setPresentationData(response)); - router.push( `/presentation?id=${presentation_id}&session=${response.session}` ); } } catch (error) { + logOperation(`Error in presentation generation: ${error}`); console.error("error in data generation", error); toast({ title: "Error Adding Charts", @@ -134,6 +133,7 @@ const CreatePage = () => { const handleAddSlide = () => { if (!titles) { + logOperation('Error: Cannot add slide - titles not available'); toast({ title: "Error", description: "Cannot add slide at this time", @@ -143,6 +143,7 @@ const CreatePage = () => { } if (titles.length >= initialSlideCount) { + logOperation('Error: Cannot add more slides - reached maximum limit'); toast({ title: "Cannot add more slides", description: @@ -152,8 +153,8 @@ const CreatePage = () => { return; } + logOperation('Adding new slide to presentation'); const newTitleWithCharts = [...titles, "New Slide"]; - dispatch(setTitles(newTitleWithCharts)); }; diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx index a4c1ffc2..1068bdb3 100644 --- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx @@ -30,6 +30,7 @@ import { getIconFromFile, removeUUID } from "../../utils/others"; import { ChevronRight, PanelRightOpen, X } from "lucide-react"; import ToolTip from "@/components/ToolTip"; import Header from "@/app/dashboard/components/Header"; +import { clearLogs, logOperation } from "../../utils/log"; // Types interface LoadingState { @@ -132,6 +133,7 @@ const DocumentsPreviewPage: React.FC = () => { const handleCreatePresentation = async () => { try { + logOperation('Starting document preview presentation generation'); setShowLoading({ message: "Generating presentation outline...", show: true, @@ -146,16 +148,17 @@ const DocumentsPreviewPage: React.FC = () => { documents: documentPaths, images: imageKeys, language: config?.language ?? "", - }); try { + logOperation('Generating presentation titles'); const titlePromise = await PresentationGenerationApi.titleGeneration({ presentation_id: createResponse.id, }); dispatch(setPresentationId(titlePromise.id)); dispatch(setTitles(titlePromise.titles)); + logOperation('Presentation titles generated successfully'); setShowLoading({ message: "", @@ -166,6 +169,7 @@ const DocumentsPreviewPage: React.FC = () => { router.push("/theme"); } catch (error) { + logOperation(`Error in title generation: ${error}`); console.error("Error in title generation:", error); toast({ title: "Error in title generation.", @@ -174,6 +178,7 @@ const DocumentsPreviewPage: React.FC = () => { }); } } catch (error) { + logOperation(`Error in presentation creation: ${error}`); console.error("Error in presentation creation:", error); toast({ title: "Error in presentation creation.", diff --git a/servers/nextjs/app/(presentation-generator)/presentation/[id]/loading.tsx b/servers/nextjs/app/(presentation-generator)/presentation/[id]/loading.tsx deleted file mode 100644 index 33989b55..00000000 --- a/servers/nextjs/app/(presentation-generator)/presentation/[id]/loading.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { Skeleton } from '@/components/ui/skeleton' -const loading = () => { - return ( -
- -
- { - Array.from({ length: 10 }).map((_, index) => ( - - )) - } -
-
- ) -} - -export default loading diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 0971d7fb..91368270 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -47,6 +47,7 @@ import Modal from "./Modal"; import Announcement from "@/components/Announcement"; import { getFontLink } from "../../utils/others"; +import { clearLogs, logOperation } from "../../utils/log"; const Header = ({ presentation_id, @@ -70,6 +71,7 @@ const Header = ({ const handleThemeSelect = async (value: string) => { if (isStreaming) return; if (value === "custom") { + logOperation('Opening custom theme modal'); setShowCustomThemeModal(true); return; } else { @@ -78,6 +80,7 @@ const Header = ({ if (themeColors) { try { + logOperation(`Changing theme to: ${themeType}`); // Update UI dispatch(setTheme(themeType)); dispatch(setThemeColors({ ...themeColors, theme: themeType })); @@ -111,7 +114,9 @@ const Header = ({ ...themeColors, }, }); + logOperation(`Theme ${themeType} applied successfully`); } catch (error) { + logOperation(`Error updating theme: ${error}`); console.error("Failed to update theme:", error); toast({ title: "Error updating theme", @@ -126,8 +131,7 @@ const Header = ({ const getSlideMetadata = async () => { try { - // Get the current URL without any query parameters - // const baseUrl = `${window.location.origin}/pdf-maker?id=${presentation_id}`; + logOperation('Fetching slide metadata'); const baseUrl = window.location.href; // @ts-ignore const metadata = await window.electron.getSlideMetadata( @@ -135,10 +139,10 @@ const Header = ({ currentTheme, currentColors, ); - - console.log("metadata", metadata); + logOperation('Slide metadata fetched successfully'); return metadata; } catch (error) { + logOperation(`Error fetching metadata: ${error}`); setShowLoader(false); console.error("Error fetching metadata:", error); toast({ @@ -183,23 +187,27 @@ const Header = ({ if (isStreaming) return; try { + logOperation('Starting PPTX export'); setOpen(false); setShowLoader(true); const apiBody = await metaData(); - const response = await PresentationGenerationApi.exportAsPPTX(apiBody); + if (response.path) { + logOperation('PPTX export completed, initiating download'); setShowLoader(false); // @ts-ignore const ipcResponse = await window.electron.fileDownloaded(response.path); if (!ipcResponse.success) { throw new Error("Failed to download file"); } + logOperation('PPTX download completed successfully'); } else { throw new Error("No URL returned from export"); } } catch (error) { + logOperation(`Error in PPTX export: ${error}`); console.error("Export failed:", error); setShowLoader(false); toast({ @@ -218,6 +226,7 @@ const Header = ({ setOpen(false); try { + logOperation('Starting PDF export'); toast({ title: "Exporting presentation...", description: "Please wait while we export your presentation.", @@ -229,8 +238,10 @@ const Header = ({ if (!ipcResponse.success) { throw new Error("Failed to export as PDF"); } + logOperation('PDF export completed successfully'); } catch (err) { + logOperation(`Error in PDF export: ${err}`); console.error(err); toast({ title: "Having trouble exporting!", diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index 643625a3..a7747b0e 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -28,6 +28,7 @@ import { Button } from "@/components/ui/button"; import { AlertCircle } from "lucide-react"; import Help from "./Help"; import { getEnv } from "@/utils/constant"; +import { clearLogs, logOperation } from "../../utils/log"; const urls = getEnv(); const BASE_URL = urls.BASE_URL; @@ -87,14 +88,17 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { const autoSave = useCallback( (data: { presentation_id: string; slides: any[] }) => { setAutoSaveLoading(true); + logOperation('Auto-saving presentation changes'); // Fire and forget - no await PresentationGenerationApi.updatePresentationContent(data) - .then(() => { }) + .then(() => { + logOperation('Auto-save completed successfully'); + }) .catch((error) => { + logOperation(`Auto-save error: ${error}`); console.error("Error AAYO", error); }) .finally(() => { - console.log("Auto finished"); setAutoSaveLoading(false); }); }, @@ -128,11 +132,11 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { // Function to fetch the slides useEffect(() => { - let evtSource: EventSource; let accumulatedChunks = ""; const fetchSlides = async () => { + logOperation('Starting slide streaming'); dispatch(setStreaming(true)); evtSource = new EventSource( @@ -140,6 +144,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { ); evtSource.onopen = () => { + logOperation('Stream connection opened'); setColorsVariables(currentColors, currentTheme); }; @@ -153,7 +158,6 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { const repairedJson = jsonrepair(accumulatedChunks); const partialData = JSON.parse(repairedJson); if (partialData.slides) { - // Check if the length of slides has changed if ( partialData.slides.length !== previousSlidesLength.current && partialData.slides.length > 1 @@ -165,17 +169,16 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { slides: partialData.slides, }) ); - previousSlidesLength.current = partialData.slides.length + 1; // Update the previous length + previousSlidesLength.current = partialData.slides.length + 1; setLoading(false); } } } catch (error) { - // console.error('error while repairing json', error) // It's okay if this fails, it just means the JSON isn't complete yet } } else if (data.type === "complete") { try { - console.log("evtsource completer", data); + logOperation('Stream completed successfully'); dispatch(setPresentationData(data.presentation)); dispatch(setStreaming(false)); if (data.presentation.theme) { @@ -198,11 +201,13 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { newUrl.searchParams.delete("session"); window.history.replaceState({}, "", newUrl.toString()); } catch (error) { + logOperation(`Error processing stream completion: ${error}`); evtSource.close(); console.error("Error parsing accumulated chunks:", error); } accumulatedChunks = ""; } else if (data.type === "closing") { + logOperation('Stream closing normally'); dispatch(setPresentationData(data.presentation)); if (data.presentation.theme) { dispatch( @@ -226,6 +231,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { } }); evtSource.onerror = (error) => { + logOperation(`Stream error: ${error}`); console.error("EventSource failed:", error); setLoading(false); @@ -262,8 +268,10 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { // Function to fetch the user slides const fetchUserSlides = async () => { try { + logOperation('Fetching user slides'); const data = await DashboardApi.getPresentation(presentation_id); if (data) { + logOperation('User slides fetched successfully'); if (data.presentation.theme) { dispatch( setThemeColors({ @@ -280,6 +288,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { setLoading(false); } } catch (error) { + logOperation(`Error fetching user slides: ${error}`); setError(true); toast({ title: "Error", @@ -316,6 +325,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { // Function to handle slide change for presentation mode const handleSlideChange = (newSlide: number) => { if (newSlide >= 0 && newSlide < presentationData?.slides.length!) { + logOperation(`Changing to slide ${newSlide}`); setSelectedSlide(newSlide); router.push( `/presentation?id=${presentation_id}&mode=present&slide=${newSlide}`, @@ -325,6 +335,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { }; const handleDeleteSlide = async (index: number) => { + logOperation(`Deleting slide at index ${index}`); dispatch(deletePresentationSlide(index)); const response = PresentationGenerationApi.deleteSlide( presentation_id, diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index 1477e18c..394b075c 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -17,6 +17,7 @@ import { useDispatch, useSelector } from "react-redux"; import { addSlide, updateSlide } from "@/store/slices/presentationGeneration"; import NewSlide from "../../components/slide_layouts/NewSlide"; import { getEmptySlideContent } from "../../utils/NewSlideContent"; +import { clearLogs, logOperation } from "../../utils/log"; interface SlideContentProps { slide: Slide; @@ -47,6 +48,7 @@ const SlideContent = ({ ) as HTMLInputElement; const value = element?.value; if (!value?.trim()) { + logOperation(`Error: Empty prompt for slide ${slide.index}`); toast({ title: "Error", description: "Please enter a prompt before submitting", @@ -55,6 +57,7 @@ const SlideContent = ({ return; } setIsUpdating(true); + logOperation(`Updating slide ${slide.index} with prompt: ${value}`); try { const response = await PresentationGenerationApi.editSlide( @@ -64,7 +67,7 @@ const SlideContent = ({ ); if (response) { - console.log("response", response); + logOperation(`Slide ${slide.index} updated successfully`); dispatch(updateSlide({ index: slide.index, slide: response })); toast({ title: "Success", @@ -72,6 +75,7 @@ const SlideContent = ({ }); } } catch (error) { + logOperation(`Error updating slide ${slide.index}: ${error}`); console.error("Error updating slide:", error); toast({ title: "Error", @@ -84,6 +88,7 @@ const SlideContent = ({ }; const handleNewSlide = (type: number, index: number) => { + logOperation(`Adding new slide of type ${type} after slide ${index}`); const newSlide: Slide = getEmptySlideContent( type, index + 1, @@ -101,6 +106,7 @@ const SlideContent = ({ presentationData.slides.length > 1 && isStreaming ) { + const slideElement = document.getElementById(`slide-${index}`); if (slideElement) { slideElement.scrollIntoView({ diff --git a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts index 738c1140..b4246524 100644 --- a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts +++ b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts @@ -1,6 +1,7 @@ import { getEnv } from "@/utils/constant"; import { getHeader, getHeaderForFormData } from "./header"; import { IconSearch, ImageGenerate, ImageSearch } from "./params"; +import { clearLogs, logOperation } from "../../utils/log"; const urls = getEnv(); const BASE_URL = urls.BASE_URL; @@ -11,6 +12,7 @@ export class PresentationGenerationApi { static async getChapterDetails() { try { + logOperation('Fetching chapter details'); const response = await fetch( `${BASE_URL}/ppt/chapter-details`, { @@ -21,15 +23,18 @@ export class PresentationGenerationApi { ); if (response.status === 200) { const data = await response.json(); + logOperation('Successfully fetched chapter details'); return data; } } catch (error) { + logOperation(`Error fetching chapter details: ${error}`); console.error("Error getting chapter details:", error); throw error; } } static async uploadDoc(documents: File[], images: File[]) { + logOperation(`Uploading documents: ${documents.length} files, images: ${images.length} files`); const formData = new FormData(); documents.forEach((document) => { @@ -53,12 +58,15 @@ export class PresentationGenerationApi { ); if (!response.ok) { + logOperation(`Upload failed with status: ${response.status}`); throw new Error(`Upload failed: ${response.statusText}`); } const data = await response.json(); + logOperation('Successfully uploaded documents and images'); return data; } catch (error) { + logOperation(`Upload error: ${error}`); console.error("Upload error:", error); throw error; } @@ -67,6 +75,7 @@ export class PresentationGenerationApi { static async decomposeDocuments(documentKeys: string[], imageKeys: string[]) { + logOperation(`Decomposing documents: ${documentKeys.length} files, images: ${imageKeys.length} files`); try { const response = await fetch( `${BASE_URL}/ppt/files/decompose`, @@ -82,12 +91,14 @@ export class PresentationGenerationApi { ); if (response.status === 200) { const data = await response.json(); - + logOperation('Successfully decomposed documents'); return data; } else { + logOperation(`Failed to decompose files: ${response.statusText}`); throw new Error(`Failed to decompose files: ${response.statusText}`); } } catch (error) { + logOperation(`Error in Decompose Files: ${error}`); console.error("Error in Decompose Files", error); throw error; } @@ -124,6 +135,7 @@ export class PresentationGenerationApi { } static async generatePresentation(presentationData: any) { + logOperation('Generating presentation'); try { const response = await fetch( `${BASE_URL}/ppt/generate`, @@ -136,14 +148,14 @@ export class PresentationGenerationApi { ); if (response.status === 200) { const data = await response.json(); - + logOperation('Successfully generated presentation'); return data; } else { - throw new Error( - `Failed to generate presentation: ${response.statusText}` - ); + logOperation(`Failed to generate presentation: ${response.statusText}`); + throw new Error(`Failed to generate presentation: ${response.statusText}`); } } catch (error) { + logOperation(`Error in presentation generation: ${error}`); console.error("error in presentation generation", error); throw error; } @@ -153,6 +165,7 @@ export class PresentationGenerationApi { index: number, prompt: string ) { + logOperation(`Editing slide ${index} in presentation ${presentation_id}`); try { const response = await fetch( `${BASE_URL}/ppt/edit`, @@ -170,12 +183,15 @@ export class PresentationGenerationApi { ); if (!response.ok) { + logOperation(`Failed to update slide ${index}: ${response.statusText}`); throw new Error("Failed to update slides"); } const data = await response.json(); + logOperation(`Successfully updated slide ${index}`); return data; } catch (error) { + logOperation(`Error in slide update: ${error}`); console.error("error in slide update", error); throw error; } @@ -254,6 +270,7 @@ export class PresentationGenerationApi { } } static async generateImage(imageGenerate: ImageGenerate) { + logOperation(`Generating image with prompt: ${imageGenerate.prompt.image_prompt}`); try { const response = await fetch( `${BASE_URL}/ppt/image/generate`, @@ -266,12 +283,14 @@ export class PresentationGenerationApi { ); if (response.ok) { const data = await response.json(); - + logOperation('Successfully generated image'); return data; } else { + logOperation(`Failed to generate images: ${response.statusText}`); throw new Error(`Failed to generate images: ${response.statusText}`); } } catch (error) { + logOperation(`Error in image generation: ${error}`); console.error("error in image generation", error); throw error; } @@ -325,6 +344,7 @@ export class PresentationGenerationApi { // EXPORT PRESENTATION static async exportAsPPTX(presentationData: any) { + logOperation('Exporting presentation as PPTX'); try { const response = await fetch( `${BASE_URL}/ppt/presentation/export_as_pptx`, @@ -337,20 +357,23 @@ export class PresentationGenerationApi { ); if (response.ok) { const data = await response.json(); - + logOperation('Successfully exported presentation as PPTX'); return { ...data, url: `${BASE_URL}${data.url}`, }; } else { + logOperation(`Failed to export as pptx: ${response.statusText}`); throw new Error(`Failed to export as pptx: ${response.statusText}`); } } catch (error) { + logOperation(`Error in pptx export: ${error}`); console.error("error in pptx export", error); throw error; } } static async exportAsPDF(presentationData: any) { + logOperation('Exporting presentation as PDF'); try { const response = await fetch( `${BASE_URL}/ppt/presentation/export_as_pdf`, @@ -362,17 +385,20 @@ export class PresentationGenerationApi { ); if (response.ok) { const data = await response.json(); - + logOperation('Successfully exported presentation as PDF'); return data; } else { + logOperation(`Failed to export as pdf: ${response.statusText}`); throw new Error(`Failed to export as pdf: ${response.statusText}`); } } catch (error) { + logOperation(`Error in pdf export: ${error}`); console.error("error in pdf export", error); throw error; } } static async deleteSlide(presentation_id: string, slide_id: string) { + logOperation(`Deleting slide ${slide_id} from presentation ${presentation_id}`); try { const response = await fetch( `${BASE_URL}/ppt/slide/delete?presentation_id=${presentation_id}&slide_id=${slide_id}`, @@ -383,17 +409,21 @@ export class PresentationGenerationApi { } ); if (response.status === 204) { + logOperation(`Successfully deleted slide ${slide_id}`); return true; } else { + logOperation(`Failed to delete slide: ${response.statusText}`); throw new Error(`Failed to delete slide: ${response.statusText}`); } } catch (error) { + logOperation(`Error in slide deletion: ${error}`); console.error("error in slide deletion", error); throw error; } } // SET THEME COLORS static async setThemeColors(presentation_id: string, theme: any) { + logOperation(`Setting theme colors for presentation ${presentation_id}`); try { const response = await fetch( `${BASE_URL}/ppt/presentation/theme`, diff --git a/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx b/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx index 4fa6a039..c95faafb 100644 --- a/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx +++ b/servers/nextjs/app/(presentation-generator)/theme/ThemePage.tsx @@ -8,8 +8,8 @@ import { useDispatch } from "react-redux"; import { useRouter } from "next/navigation"; import { ThemeType } from "../upload/type"; import { Button } from "@/components/ui/button"; - import { toast } from "@/hooks/use-toast"; +import { clearLogs, logOperation } from "../utils/log"; interface ThemeCardProps { name: string; @@ -114,18 +114,20 @@ const ThemePage = () => { const router = useRouter(); const [selectedTheme, setSelectedTheme] = useState(null); const handleThemeClick = async (theme: ThemeColors, type: string) => { + logOperation(`Theme selected: ${type}`); setSelectedTheme(type as ThemeType); }; const handleSubmit = () => { if (!selectedTheme) { + logOperation('Error: No theme selected'); toast({ title: "Please select a theme", variant: "destructive", }); return; } + logOperation(`Proceeding with theme: ${selectedTheme}`); dispatch(setTheme(selectedTheme as ThemeType)); - router.push("/create"); }; diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index 8d8dfd3f..95be5661 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -30,6 +30,7 @@ import { PresentationGenerationApi } from "../../services/api/presentation-gener import { OverlayLoader } from "@/components/ui/overlay-loader"; import Wrapper from "@/components/Wrapper"; import { setPptGenUploadState } from "@/store/slices/presentationGenUpload"; +import { clearLogs, logOperation } from "../../utils/log"; // Types for loading state interface LoadingState { @@ -63,6 +64,8 @@ const UploadPage = () => { const dispatch = useDispatch(); const { toast } = useToast(); + + // State management const [documents, setDocuments] = useState([]); const [images, setImages] = useState([]); @@ -138,7 +141,15 @@ const UploadPage = () => { if (!validateConfiguration()) return; try { + // Clear previous logs before starting new presentation + clearLogs(); + logOperation(`----New Presentation Generation----`); + const hasUploadedAssets = documents.length > 0 || images.length > 0; + // Log the configuration + logOperation(`Config: ${JSON.stringify(config)}`); + // Log the files updated + logOperation(`Files updated: ${documents.length} documents, ${images.length} images`); if (hasUploadedAssets) { await handleDocumentProcessing(); @@ -154,6 +165,7 @@ const UploadPage = () => { * Handles document processing */ const handleDocumentProcessing = async () => { + logOperation('Starting document processing'); setLoadingState({ isLoading: true, message: "Processing documents...", @@ -166,6 +178,7 @@ const UploadPage = () => { let imageKeys = []; if (documents.length > 0 || images.length > 0) { + logOperation(`Uploading ${documents.length} documents and ${images.length} images`); const uploadResponse = await PresentationGenerationApi.uploadDoc(documents, images); documentKeys = uploadResponse["documents"]; imageKeys = uploadResponse["images"]; @@ -174,15 +187,16 @@ const UploadPage = () => { const promises: Promise[] = []; if (documents.length > 0 || images.length > 0) { + logOperation('Decomposing documents'); promises.push( PresentationGenerationApi.decomposeDocuments(documentKeys, imageKeys) ); } const responses = await Promise.all(promises); - const processedData = processApiResponses(responses); + logOperation('Document processing completed'); dispatch(setPptGenUploadState(processedData)); router.push("/documents-preview"); }; @@ -217,6 +231,7 @@ const UploadPage = () => { * Handles direct presentation generation without documents */ const handleDirectPresentationGeneration = async () => { + logOperation('Starting direct presentation generation'); setLoadingState({ isLoading: true, message: "Generating outlines...", @@ -229,20 +244,21 @@ const UploadPage = () => { n_slides: config?.slides ? parseInt(config.slides) : null, documents: [], images: [], - language: config?.language ?? "", - }); try { + logOperation('Generating presentation titles'); const titlePromise = await PresentationGenerationApi.titleGeneration({ presentation_id: createResponse.id, }); dispatch(setPresentationId(titlePromise.id)); dispatch(setTitles(titlePromise.titles)); + logOperation('Presentation generation completed successfully'); router.push("/theme"); } catch (error) { console.error("Error in title generation:", error); + logOperation(`Error in title generation: ${error}`); toast({ title: "Error in title generation.", description: "Please try again.", @@ -256,6 +272,7 @@ const UploadPage = () => { */ const handleGenerationError = (error: any) => { console.error("Error in presentation generation:", error); + logOperation(`Presentation generation error: ${error}`); dispatch(setError("Failed to generate presentation")); setLoadingState({ isLoading: false, diff --git a/servers/nextjs/app/(presentation-generator)/utils/log.ts b/servers/nextjs/app/(presentation-generator)/utils/log.ts new file mode 100644 index 00000000..553e2564 --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/utils/log.ts @@ -0,0 +1,11 @@ + // Add logging function +export const logOperation = (message: string) => { + // @ts-ignore + window.electron.writeNextjsLog(message) +}; + + // Add clear logs function +export const clearLogs = () => { + // @ts-ignore + window.electron.clearNextjsLogs(); +}; \ No newline at end of file diff --git a/servers/nextjs/app/dashboard/api/dashboard.ts b/servers/nextjs/app/dashboard/api/dashboard.ts index e8cf4f64..c6e14424 100644 --- a/servers/nextjs/app/dashboard/api/dashboard.ts +++ b/servers/nextjs/app/dashboard/api/dashboard.ts @@ -3,6 +3,7 @@ import { getHeaderForFormData, } from "@/app/(presentation-generator)/services/api/header"; import { getEnv } from "@/utils/constant"; +import { clearLogs, logOperation } from "@/app/(presentation-generator)/utils/log"; const urls = getEnv(); const BASE_URL = urls.BASE_URL; @@ -27,6 +28,7 @@ export class DashboardApi { static async getPresentations(): Promise { try { + logOperation('Fetching user presentations'); const response = await fetch( `${BASE_URL}/ppt/user_presentations`, { @@ -35,19 +37,23 @@ export class DashboardApi { ); if (response.status === 200) { const data = await response.json(); + logOperation(`Successfully fetched ${data.length} presentations`); return data; } else if (response.status === 404) { + logOperation('No presentations found'); console.log("No presentations found"); return []; } return []; } catch (error) { + logOperation(`Error fetching presentations: ${error}`); console.error("Error fetching presentations:", error); throw error; } } static async getPresentation(id: string) { try { + logOperation(`Fetching presentation with ID: ${id}`); const response = await fetch( `${BASE_URL}/ppt/presentation?presentation_id=${id}`, { @@ -57,16 +63,20 @@ export class DashboardApi { ); if (response.status === 200) { const data = await response.json(); + logOperation(`Successfully fetched presentation ${id}`); return data; } + logOperation(`Presentation ${id} not found`); throw new Error("Presentation not found"); } catch (error) { + logOperation(`Error fetching presentation ${id}: ${error}`); console.error("Error fetching presentations:", error); throw error; } } static async deletePresentation(presentation_id: string) { try { + logOperation(`Deleting presentation ${presentation_id}`); const response = await fetch( `${BASE_URL}/ppt/delete?presentation_id=${presentation_id}`, { @@ -76,15 +86,19 @@ export class DashboardApi { ); if (response.status === 204) { + logOperation(`Successfully deleted presentation ${presentation_id}`); return true; } + logOperation(`Failed to delete presentation ${presentation_id}`); return false; } catch (error) { + logOperation(`Error deleting presentation ${presentation_id}: ${error}`); console.error("Error deleting presentation:", error); throw error; } } static async setSlideThumbnail(presentation_id: string, file: any) { + logOperation(`Setting thumbnail for presentation ${presentation_id}`); const formData = new FormData(); formData.append("presentation_id", presentation_id); @@ -99,8 +113,10 @@ export class DashboardApi { } ); const data = await response.json(); + logOperation(`Successfully set thumbnail for presentation ${presentation_id}`); return data; } catch (error) { + logOperation(`Error setting slide thumbnail for presentation ${presentation_id}: ${error}`); console.error("Error setting slide thumbnail:", error); throw error; }