fix(nextjs): Auto save before slide rendered & some layout issues

This commit is contained in:
shiva raj badu 2025-07-24 14:12:51 +05:45
parent f1480ecd70
commit ba01fe1154
No known key found for this signature in database
13 changed files with 75 additions and 46 deletions

View file

@ -3,7 +3,8 @@ import React, { createContext, useContext, useEffect, useState, ReactNode } from
import dynamic from 'next/dynamic';
import { toast } from "sonner";
import * as z from 'zod';
import { useDispatch } from 'react-redux';
import { setLayoutLoading } from '@/store/slices/presentationGeneration';
export interface LayoutInfo {
id: string;
name?: string;
@ -60,6 +61,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isPreloading, setIsPreloading] = useState(false);
const dispatch = useDispatch();
const buildData = async (groupedLayoutsData: GroupedLayoutsResponse[]) => {
const layouts: LayoutInfo[] = [];
@ -171,6 +173,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
try {
setLoading(true);
setError(null);
dispatch(setLayoutLoading(true));
const layoutResponse = await fetch('/api/layouts');
@ -197,6 +200,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
setError(errorMessage);
console.error('💥 Error loading layouts:', err);
} finally {
dispatch(setLayoutLoading(false));
setLoading(false);
}
};

View file

@ -36,7 +36,6 @@ export const useGroupLayouts = () => {
// Render slide content with group validation, automatic Tiptap text editing, and editable images/icons
const renderSlideContent = useMemo(() => {
return (slide: any, isEditMode: boolean) => {
console.time("renderSlideContent");
const Layout = getGroupLayout(slide.layout, slide.layout_group);
if (!Layout) {
return (
@ -76,7 +75,6 @@ export const useGroupLayouts = () => {
</EditableLayoutWrapper>
);
}
console.timeEnd("renderSlideContent");
return <Layout data={slide.content} />;
};

View file

@ -13,7 +13,7 @@ export const useAutoSave = ({
debounceMs = 2000,
enabled = true,
}: UseAutoSaveOptions = {}) => {
const { presentationData, isStreaming, isLoading } = useSelector(
const { presentationData, isStreaming, isLoading, isLayoutLoading } = useSelector(
(state: RootState) => state.presentationGeneration
);
@ -61,7 +61,7 @@ export const useAutoSave = ({
// Effect to trigger auto-save when presentation data changes
useEffect(() => {
if (!enabled || !presentationData || isStreaming || isLoading) return;
if (!enabled || !presentationData || isStreaming || isLoading || isLayoutLoading) return;
// Trigger debounced save
debouncedSave(presentationData);

View file

@ -87,6 +87,7 @@ export default function RootLayout({
className={`${inter.variable} ${roboto.variable} ${instrument_sans.variable} antialiased`}
>
<Providers>
<LayoutProvider>
{children}
</LayoutProvider>

View file

@ -1,9 +1,9 @@
import Home from '../components/Home'
import Home from "@/components/Home"
const page = () => {
return (
<Home />
)
return (
<Home />
)
}
export default page
export default page

View file

@ -62,9 +62,9 @@ const SettingsPage = () => {
isDisabled: true,
text: "Saving Configuration..."
}));
await handleSaveLLMConfig(llmConfig);
if (llmConfig.LLM === "ollama" && llmConfig.OLLAMA_MODEL) {
const isPulled = await checkIfSelectedOllamaModelIsPulled(llmConfig.OLLAMA_MODEL);
if (!isPulled) {
@ -72,7 +72,7 @@ const SettingsPage = () => {
await handleModelDownload();
}
}
toast.info("Configuration saved successfully");
setIsLoading(false);
setButtonState(prev => ({

View file

@ -112,8 +112,8 @@ interface Type10SlideLayoutProps {
}
const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }) => {
const { title, items, chartData, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const { title, items, data, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const chartData = data || [];
const renderChart = () => {
const commonProps = {
data: chartData,
@ -171,7 +171,7 @@ const Type10SlideLayout: React.FC<Type10SlideLayoutProps> = ({ data: slideData }
case 'pie':
return (
<PieChart margin={{ top: 20, right: 30, left: 40, bottom: 60 }}>
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Pie

View file

@ -59,7 +59,7 @@ const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ dat
key={`timeline-${index}`}
className="relative z-10 w-12 h-12 rounded-full bg-blue-600 px-1 text-white flex items-center justify-center font-bold text-lg"
>
<span> `0${index + 1}`</span>
<span> {index + 1}</span>
</div>
))}
</div>

View file

@ -142,7 +142,7 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
case 'pie':
return (
<PieChart margin={{ top: 20, right: 30, left: 40, bottom: 60 }}>
<PieChart margin={{ top: 10, right: 10, left: 10, bottom: 10 }}>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Pie

View file

@ -101,7 +101,8 @@ interface Type9SlideLayoutProps {
}
const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData }) => {
const { title, items, chartData, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const { title, items, data, chartType = 'line', color = '#3b82f6', dataKey = 'value', categoryKey = 'name', showLegend = false, showTooltip = true } = slideData;
const chartData = data || [];
const renderChart = () => {
const commonProps = {
data: chartData,
@ -159,23 +160,27 @@ const Type9SlideLayout: React.FC<Type9SlideLayoutProps> = ({ data: slideData })
case 'pie':
return (
<PieChart margin={{ top: 20, right: 30, left: 40, bottom: 60 }}>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Pie
data={chartData}
cx="50%"
cy="40%"
outerRadius={70}
fill={color}
dataKey={dataKey}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartData.map((entry: any, index: number) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
</PieChart>
<ResponsiveContainer >
<PieChart margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
<Pie
isAnimationActive={false}
data={chartData}
cx="50%"
cy="40%"
outerRadius={80}
fill={color}
dataKey={dataKey}
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
>
{chartData && chartData.map((entry: any, index: number) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
);
case 'scatter':

View file

@ -1,17 +1,10 @@
import * as z from "zod";
// Note:
// If you want to use Image and Icon Must Use these Schemas.
// Image and icons are only media support for PDF and PPTX.
import { ImageSchema, IconSchema } from "../defaultSchemes";
// Schema definition
export const Schema = z.object({
// Note:
// Schema fields
// Each fields must have a default value (this is important for Layout-preview)
// Each fields must have a meta description
// Each fields must have a min and max length, length must support the layout design.
// Each Array fields must have a min and max length
sectionTitle: z.string()
.min(3)

View file

@ -21,6 +21,16 @@ export const Schema = z.object({
description: "Supporting subtitle that describes the service approach or value proposition",
}),
bulletPoints: z.array(z.string().min(10).max(40)).min(4).max(6)
.default([
"Customized solutions for your business",
"Expert guidance and support",
"Innovative technology solutions",
"Comprehensive service portfolio"
]).meta({
description: "Bullet points to describe the services offered",
}),
serviceHighlight: ImageSchema.default({
__image_url__: "https://images.unsplash.com/photo-1556761175-b413da4baf72?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80",
__image_prompt__: "Professional service delivery or team working on client solutions"
@ -47,7 +57,7 @@ type SchemaType = z.infer<typeof Schema>;
// Component definition
const OurServiceSlide = ({ data }: { data: Partial<SchemaType> }) => {
const { sectionTitle, sectionSubtitle, serviceHighlight, showVisualAccents, showColorBlocks } = data;
const { sectionTitle, sectionSubtitle, serviceHighlight, showVisualAccents, showColorBlocks, bulletPoints } = data;
return (
<div className="aspect-video max-w-[1280px] w-full bg-white relative overflow-hidden">
@ -67,6 +77,18 @@ const OurServiceSlide = ({ data }: { data: Partial<SchemaType> }) => {
</p>
)}
<div className="mt-16 space-y-4">
{bulletPoints && bulletPoints.map((point, index) => (
<div key={index} className="text-base font-semibold text-gray-800 tracking-wide">
<div className="flex gap-3 items-center">
<div className="w-8 h-8 bg-teal-700 rounded-full "></div>
<p className="text-lg font-medium text-gray-800 tracking-wide">{point}</p>
</div>
</div>
))}
</div>
{/* Visual Accents */}
{showVisualAccents && (
<>

View file

@ -30,12 +30,14 @@ interface PresentationGenerationState {
error: string | null;
presentationData: PresentationData | null;
isSlidesRendered: boolean;
isLayoutLoading: boolean;
}
const initialState: PresentationGenerationState = {
presentation_id: null,
outlines: [],
isSlidesRendered: false,
isLayoutLoading: false,
isLoading: false,
isStreaming: null,
error: null,
@ -53,6 +55,9 @@ const presentationGenerationSlice = createSlice({
setLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setLayoutLoading: (state, action: PayloadAction<boolean>) => {
state.isLayoutLoading = action.payload;
},
// Presentation ID
setPresentationId: (state, action: PayloadAction<string>) => {
state.presentation_id = action.payload;
@ -365,6 +370,7 @@ const presentationGenerationSlice = createSlice({
export const {
setStreaming,
setLoading,
setLayoutLoading,
setPresentationId,
setSlidesRendered,
setError,