Merge pull request #32 from presenton/nextjs_logs_save

Nextjs Logs saved in [APP_DIRECTORY]/logs/nextjs.log
This commit is contained in:
Shiva Raj Badu 2025-05-20 00:33:28 +05:45 committed by GitHub
commit 88935fc7db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 238 additions and 74 deletions

View file

@ -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();
}

50
app/ipc/log_handler.ts Normal file
View file

@ -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 };
}
});
}

View file

@ -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"),
});

4
package-lock.json generated
View file

@ -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",

View file

@ -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<HTMLInputElement>
) => {
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
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 {

View file

@ -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);
}
};

View file

@ -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));
};

View file

@ -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.",

View file

@ -1,18 +0,0 @@
import React from 'react'
import { Skeleton } from '@/components/ui/skeleton'
const loading = () => {
return (
<div>
<Skeleton className='h-24 w-full' />
<div className="flex flex-col max-w-7xl mx-auto gap-6 my-10 justify-center items-center ">
{
Array.from({ length: 10 }).map((_, index) => (
<Skeleton key={index} className="animate-pulse aspect-video w-full" />
))
}
</div>
</div>
)
}
export default loading

View file

@ -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!",

View file

@ -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,

View file

@ -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({

View file

@ -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`,

View file

@ -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<ThemeType | null>(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");
};

View file

@ -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<File[]>([]);
const [images, setImages] = useState<File[]>([]);
@ -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<any>[] = [];
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,

View file

@ -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();
};

View file

@ -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<PresentationResponse[]> {
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;
}