feat(Nextjs): Layouts groups and display groups
This commit is contained in:
parent
1b882c34f3
commit
c9f862bf51
5 changed files with 423 additions and 166 deletions
|
|
@ -0,0 +1,105 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { LayoutGroups, LayoutGroup } from "@/components/layouts/layoutGroup";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
|
||||
interface LayoutSelectionProps {
|
||||
selectedLayoutGroup: LayoutGroup | null;
|
||||
onSelectLayoutGroup: (group: LayoutGroup) => void;
|
||||
}
|
||||
|
||||
const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
||||
selectedLayoutGroup,
|
||||
onSelectLayoutGroup
|
||||
}) => {
|
||||
const { getLayout } = useLayout();
|
||||
|
||||
const renderLayoutPreview = (layoutId: string) => {
|
||||
const Layout = getLayout(layoutId);
|
||||
if (!Layout) {
|
||||
return (
|
||||
<div className="w-full h-16 bg-gray-100 rounded flex items-center justify-center">
|
||||
<span className="text-gray-400 text-xs">Preview unavailable</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Sample data for preview
|
||||
const sampleData = {
|
||||
title: "Sample Title",
|
||||
description: "This is a preview of the layout",
|
||||
subtitle: "Sample subtitle",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-16 overflow-hidden rounded bg-white border">
|
||||
<div className="transform scale-[0.12] origin-top-left w-[833%] h-[833%]">
|
||||
<Layout data={sampleData} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="mb-6">
|
||||
<h5 className="text-lg font-medium mb-2">
|
||||
Select Your Presentation Style
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
Choose a layout group that best fits your presentation style and content.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{LayoutGroups.map((group) => (
|
||||
<div
|
||||
key={group.id}
|
||||
onClick={() => onSelectLayoutGroup(group)}
|
||||
className={`relative p-4 rounded-lg border cursor-pointer ${selectedLayoutGroup?.id === group.id
|
||||
? 'border-blue-500 bg-blue-50'
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}
|
||||
>
|
||||
{selectedLayoutGroup?.id === group.id && (
|
||||
<div className="absolute top-3 right-3">
|
||||
<CheckCircle className="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-3">
|
||||
<h6 className="text-base font-medium text-gray-900 mb-1">
|
||||
{group.name}
|
||||
</h6>
|
||||
<p className="text-sm text-gray-600">
|
||||
{group.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Layout previews */}
|
||||
<div className="grid grid-cols-3 gap-2 mb-3">
|
||||
{group.slides.slice(0, 6).map((layoutId, index) => (
|
||||
<div key={index} className="aspect-video">
|
||||
{renderLayoutPreview(layoutId)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<span>{group.slides.length} layouts</span>
|
||||
<span className={`px-2 py-1 rounded text-xs ${group.ordered
|
||||
? 'bg-gray-100 text-gray-700'
|
||||
: 'bg-blue-100 text-blue-700'
|
||||
}`}>
|
||||
{group.ordered ? 'Structured' : 'Flexible'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutSelection;
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { OutlineItem } from "./OutlineItem";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SlideOutline } from "@/store/slices/presentationGeneration";
|
||||
import { FileText } from "lucide-react";
|
||||
|
||||
interface OutlineContentProps {
|
||||
outlines: SlideOutline[] | null;
|
||||
isLoading: boolean;
|
||||
isStreaming: boolean;
|
||||
onDragEnd: (event: any) => void;
|
||||
onAddSlide: () => void;
|
||||
}
|
||||
|
||||
const OutlineContent: React.FC<OutlineContentProps> = ({
|
||||
outlines,
|
||||
isLoading,
|
||||
isStreaming,
|
||||
onDragEnd,
|
||||
onAddSlide
|
||||
}) => {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h5 className="text-lg font-medium">
|
||||
Presentation Outline
|
||||
</h5>
|
||||
{isStreaming && (
|
||||
<div className="flex items-center text-sm text-blue-600">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
|
||||
Generating outlines...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Skeleton loading state */}
|
||||
{isLoading && (
|
||||
<div className="space-y-4">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<div key={index} className="animate-pulse">
|
||||
<div className="flex items-start space-x-3 p-4 border rounded-lg bg-white">
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full flex-shrink-0"></div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="h-5 bg-gray-200 rounded w-3/4"></div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-gray-100 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-100 rounded w-5/6"></div>
|
||||
<div className="h-4 bg-gray-100 rounded w-4/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-5 h-5 bg-gray-200 rounded flex-shrink-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Outlines content */}
|
||||
{outlines && outlines.length > 0 && (
|
||||
<div className="border rounded-lg p-4 bg-white">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={outlines?.map((item, index) => ({ id: item.title || `slide-${index}` })) || []}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{outlines?.map((item, index) => (
|
||||
<OutlineItem
|
||||
key={item.title || `slide-${index}`}
|
||||
index={index + 1}
|
||||
slideOutline={item}
|
||||
isStreaming={isStreaming}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onAddSlide}
|
||||
disabled={isLoading || isStreaming}
|
||||
className="w-full mt-4 text-blue-600 border-blue-200"
|
||||
>
|
||||
+ Add Slide
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{!isLoading && outlines && outlines.length === 0 && (
|
||||
<div className="text-center py-12 bg-white rounded-lg border-2 border-dashed border-gray-200">
|
||||
<FileText className="w-12 h-12 text-gray-400 mx-auto mb-4" />
|
||||
<p className="text-gray-600 mb-4">No outlines available</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onAddSlide}
|
||||
className="text-blue-600 border-blue-200"
|
||||
>
|
||||
+ Add First Slide
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutlineContent;
|
||||
|
|
@ -103,8 +103,8 @@ export function OutlineItem({
|
|||
<div id={`outline-item-${index}`} className="flex flex-col basis-full gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={slideOutline.title || ''}
|
||||
onChange={(e) => handleSlideChange({ ...slideOutline, title: e.target.value })}
|
||||
defaultValue={slideOutline.title || ''}
|
||||
onBlur={(e) => handleSlideChange({ ...slideOutline, title: e.target.value })}
|
||||
className="text-md sm:text-lg flex-1 font-semibold bg-transparent outline-none"
|
||||
placeholder="Title goes here"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,8 @@
|
|||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { OutlineItem } from "./OutlineItem";
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { RootState } from "@/store/store";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
|
@ -29,26 +16,20 @@ import {
|
|||
import { OverlayLoader } from "@/components/ui/overlay-loader";
|
||||
import Wrapper from "@/components/Wrapper";
|
||||
import { jsonrepair } from "jsonrepair";
|
||||
import { LayoutGroup, getDefaultLayoutGroup } from "@/components/layouts/layoutGroup";
|
||||
import OutlineContent from "./OutlineContent";
|
||||
import LayoutSelection from "./LayoutSelection";
|
||||
|
||||
const OutlinePage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
const { presentation_id, outlines } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
);
|
||||
|
||||
const { currentTheme, currentColors } = useSelector(
|
||||
(state: RootState) => state.theme
|
||||
);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string>('outline');
|
||||
const [selectedLayoutGroup, setSelectedLayoutGroup] = useState<LayoutGroup | null>(getDefaultLayoutGroup());
|
||||
const [loadingState, setLoadingState] = useState({
|
||||
message: "",
|
||||
isLoading: false,
|
||||
|
|
@ -182,6 +163,16 @@ const OutlinePage = () => {
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedLayoutGroup) {
|
||||
toast({
|
||||
title: "Select Layout Group",
|
||||
description: "Please select a layout group before generating presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate data
|
||||
setLoadingState({
|
||||
message: "Generating presentation data...",
|
||||
|
|
@ -194,17 +185,11 @@ const OutlinePage = () => {
|
|||
const response = await PresentationGenerationApi.presentationPrepare({
|
||||
presentation_id: presentation_id,
|
||||
outlines: outlines,
|
||||
layoutGroup: selectedLayoutGroup,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
dispatch(setPresentationData(response));
|
||||
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Presentation generated successfully!",
|
||||
variant: "default",
|
||||
});
|
||||
|
||||
router.push(`/presentation?id=${presentation_id}&stream=true`);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -238,7 +223,6 @@ const OutlinePage = () => {
|
|||
dispatch(setOutlines(updatedOutlines));
|
||||
};
|
||||
|
||||
|
||||
if (!presentation_id) {
|
||||
return (
|
||||
<Wrapper>
|
||||
|
|
@ -248,7 +232,7 @@ const OutlinePage = () => {
|
|||
No Presentation ID Found
|
||||
</h4>
|
||||
<p className="text-gray-600 mb-4">Please start a new presentation.</p>
|
||||
<Button onClick={() => router.push("/upload")} className="bg-[#5146E5] w-full rounded-xl text-base sm:text-lg py-4 sm:py-6 transition-all duration-300 font-roboto font-semibold hover:bg-[#5146E5]/80 text-white mt-4 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<Button onClick={() => router.push("/upload")} className="bg-[#5146E5] w-full rounded-xl text-base sm:text-lg py-4 sm:py-6 font-roboto font-semibold hover:bg-[#5146E5]/80 text-white mt-4 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
Start New Presentation
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -266,129 +250,93 @@ const OutlinePage = () => {
|
|||
duration={loadingState.duration}
|
||||
/>
|
||||
|
||||
<div className="max-w-[1000px] mx-auto px-4 sm:px-6 pb-6">
|
||||
<div className="mt-4 sm:mt-8 font-instrument_sans relative">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="text-lg sm:text-xl font-instrument_sans font-medium">
|
||||
Outline
|
||||
<div className="max-w-[1200px] mx-auto px-4 sm:px-6 pb-6">
|
||||
<div className="mt-4 sm:mt-8">
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h4 className="text-2xl font-bold mb-2 text-gray-900">
|
||||
Customize Your Presentation
|
||||
</h4>
|
||||
{isStreaming && (
|
||||
<div className="flex items-center text-sm text-blue-600">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
|
||||
Generating outlines...
|
||||
</div>
|
||||
)}
|
||||
<p className="text-gray-600">
|
||||
Review your outline and select a layout style for your presentation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Skeleton loading state */}
|
||||
{isLoading && (
|
||||
<div className="space-y-4">
|
||||
{[...Array(5)].map((_, index) => (
|
||||
<div key={index} className="animate-pulse">
|
||||
<div className="flex items-start space-x-3 p-3 sm:p-4 border rounded-lg">
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full flex-shrink-0"></div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="h-5 bg-gray-200 rounded w-3/4"></div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-gray-100 rounded w-full"></div>
|
||||
<div className="h-4 bg-gray-100 rounded w-5/6"></div>
|
||||
<div className="h-4 bg-gray-100 rounded w-4/6"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-5 h-5 bg-gray-200 rounded flex-shrink-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="w-full mt-4 h-10 bg-gray-200 rounded-[32px] animate-pulse"></div>
|
||||
</div>
|
||||
)}
|
||||
{/* Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-[50%] mx-auto grid-cols-2">
|
||||
<TabsTrigger value="outline">Outline & Content</TabsTrigger>
|
||||
<TabsTrigger value="layouts">Layout Style</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Outlines content */}
|
||||
{outlines && outlines.length > 0 && (
|
||||
<div className="border p-2 sm:p-4 md:p-6 rounded-lg">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
<TabsContent value="outline" className="mt-6">
|
||||
<OutlineContent
|
||||
outlines={outlines}
|
||||
isLoading={isLoading}
|
||||
isStreaming={isStreaming}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={outlines?.map((item, index) => ({ id: item.title || `slide-${index}` })) || []}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{outlines?.map((item, index) => (
|
||||
<OutlineItem
|
||||
key={item.title || `slide-${index}`}
|
||||
index={index + 1}
|
||||
slideOutline={item}
|
||||
isStreaming={isStreaming}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleAddSlide}
|
||||
disabled={isLoading || isStreaming}
|
||||
className="w-full mt-4 text-[#9034EA] border-[#9034EA] rounded-[32px] hover:bg-[#9034EA]/10"
|
||||
>
|
||||
+ Add Slide
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Empty state */}
|
||||
{!isLoading && outlines && outlines.length === 0 && (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600 mb-4">No outlines available</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleAddSlide}
|
||||
className="text-[#9034EA] border-[#9034EA] rounded-[32px]"
|
||||
>
|
||||
+ Add First Slide
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Generate button */}
|
||||
{!isStreaming && <Button
|
||||
disabled={loadingState.isLoading || isLoading || isStreaming || !outlines || outlines.length === 0}
|
||||
onClick={handleSubmit}
|
||||
className="bg-[#5146E5] w-full rounded-[32px] text-base sm:text-lg py-4 sm:py-6 transition-all duration-300 font-roboto font-semibold hover:bg-[#5146E5]/80 text-white mt-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 25 25"
|
||||
fill="none"
|
||||
>
|
||||
<g clipPath="url(#clip0_1960_939)">
|
||||
<path
|
||||
d="M21.217 9.57008L21.463 9.00408C21.8955 8.0028 22.6876 7.2 23.683 6.75408L24.442 6.41508C24.5341 6.37272 24.6121 6.30485 24.6668 6.21951C24.7214 6.13417 24.7505 6.03494 24.7505 5.93358C24.7505 5.83222 24.7214 5.73299 24.6668 5.64765C24.6121 5.56231 24.5341 5.49444 24.442 5.45208L23.725 5.13308C22.7046 4.67446 21.8989 3.84196 21.474 2.80708L21.221 2.19608C21.1838 2.10144 21.119 2.02018 21.035 1.96291C20.951 1.90563 20.8517 1.875 20.75 1.875C20.6483 1.875 20.549 1.90563 20.465 1.96291C20.381 2.02018 20.3162 2.10144 20.279 2.19608L20.026 2.80608C19.6015 3.84116 18.7962 4.67401 17.776 5.13308L17.058 5.45308C16.9662 5.49556 16.8885 5.56342 16.834 5.64865C16.7795 5.73389 16.7506 5.83293 16.7506 5.93408C16.7506 6.03523 16.7795 6.13428 16.834 6.21951C16.8885 6.30474 16.9662 6.3726 17.058 6.41508L17.818 6.75308C18.8132 7.19945 19.6049 8.00261 20.037 9.00408L20.283 9.57008C20.463 9.98408 21.036 9.98408 21.217 9.57008ZM6.55 16.8761H8.704L9.304 15.3761H12.196L12.796 16.8761H14.95L11.75 8.87608H9.75L6.55 16.8761ZM10.75 11.7611L11.396 13.3761H10.104L10.75 11.7611ZM15.75 16.8761V8.87608H17.75V16.8761H15.75ZM3.75 3.87608C3.48478 3.87608 3.23043 3.98144 3.04289 4.16897C2.85536 4.35651 2.75 4.61086 2.75 4.87608V20.8761C2.75 21.1413 2.85536 21.3957 3.04289 21.5832C3.23043 21.7707 3.48478 21.8761 3.75 21.8761H21.75C22.0152 21.8761 22.2696 21.7707 22.4571 21.5832C22.6446 21.3957 22.75 21.1413 22.75 20.8761V11.8761H20.75V19.8761H4.75V5.87608H14.75V3.87608H3.75Z"
|
||||
fill="white"
|
||||
onAddSlide={handleAddSlide}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1960_939">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.75 0.876953)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
{loadingState.isLoading
|
||||
? loadingState.message
|
||||
: isLoading || isStreaming
|
||||
? "Loading..."
|
||||
: "Generate Presentation"}
|
||||
</Button>}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="layouts" className="mt-6">
|
||||
<LayoutSelection
|
||||
selectedLayoutGroup={selectedLayoutGroup}
|
||||
onSelectLayoutGroup={setSelectedLayoutGroup}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Generate button */}
|
||||
<div className="mt-8 pt-6 border-t border-gray-200">
|
||||
<Button
|
||||
disabled={
|
||||
loadingState.isLoading ||
|
||||
isLoading ||
|
||||
isStreaming ||
|
||||
!outlines ||
|
||||
outlines.length === 0 ||
|
||||
!selectedLayoutGroup
|
||||
}
|
||||
onClick={handleSubmit}
|
||||
className="bg-[#5146E5] w-full rounded-lg text-base sm:text-lg py-4 sm:py-6 font-roboto font-semibold hover:bg-[#5146E5]/80 text-white disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg
|
||||
className="mr-2"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 25 25"
|
||||
fill="none"
|
||||
>
|
||||
<g clipPath="url(#clip0_1960_939)">
|
||||
<path
|
||||
d="M21.217 9.57008L21.463 9.00408C21.8955 8.0028 22.6876 7.2 23.683 6.75408L24.442 6.41508C24.5341 6.37272 24.6121 6.30485 24.6668 6.21951C24.7214 6.13417 24.7505 6.03494 24.7505 5.93358C24.7505 5.83222 24.7214 5.73299 24.6668 5.64765C24.6121 5.56231 24.5341 5.49444 24.442 5.45208L23.725 5.13308C22.7046 4.67446 21.8989 3.84196 21.474 2.80708L21.221 2.19608C21.1838 2.10144 21.119 2.02018 21.035 1.96291C20.951 1.90563 20.8517 1.875 20.75 1.875C20.6483 1.875 20.549 1.90563 20.465 1.96291C20.381 2.02018 20.3162 2.10144 20.279 2.19608L20.026 2.80608C19.6015 3.84116 18.7962 4.67401 17.776 5.13308L17.058 5.45308C16.9662 5.49556 16.8885 5.56342 16.834 5.64865C16.7795 5.73389 16.7506 5.83293 16.7506 5.93408C16.7506 6.03523 16.7795 6.13428 16.834 6.21951C16.8885 6.30474 16.9662 6.3726 17.058 6.41508L17.818 6.75308C18.8132 7.19945 19.6049 8.00261 20.037 9.00408L20.283 9.57008C20.463 9.98408 21.036 9.98408 21.217 9.57008ZM6.55 16.8761H8.704L9.304 15.3761H12.196L12.796 16.8761H14.95L11.75 8.87608H9.75L6.55 16.8761ZM10.75 11.7611L11.396 13.3761H10.104L10.75 11.7611ZM15.75 16.8761V8.87608H17.75V16.8761H15.75ZM3.75 3.87608C3.48478 3.87608 3.23043 3.98144 3.04289 4.16897C2.85536 4.35651 2.75 4.61086 2.75 4.87608V20.8761C2.75 21.1413 2.85536 21.3957 3.04289 21.5832C3.23043 21.7707 3.48478 21.8761 3.75 21.8761H21.75C22.0152 21.8761 22.2696 21.7707 22.4571 21.5832C22.6446 21.3957 22.75 21.1413 22.75 20.8761V11.8761H20.75V19.8761H4.75V5.87608H14.75V3.87608H3.75Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1960_939">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(0.75 0.876953)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
{loadingState.isLoading
|
||||
? loadingState.message
|
||||
: isLoading || isStreaming
|
||||
? "Loading..."
|
||||
: !selectedLayoutGroup
|
||||
? "Select a Layout Style"
|
||||
: "Generate Presentation"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,25 +1,97 @@
|
|||
export const ProfessionalLayoutGroup = {
|
||||
export interface LayoutGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ordered: boolean;
|
||||
isDefault?: boolean;
|
||||
slides: string[];
|
||||
}
|
||||
|
||||
export const ProfessionalLayoutGroup: LayoutGroup = {
|
||||
id: 'professional',
|
||||
name: 'Professional',
|
||||
description: 'Clean, corporate designs perfect for business presentations',
|
||||
ordered: true,
|
||||
slides: ['bullet-point-slide', 'image-slide', 'chart-slide', 'table-slide', 'text-slide', 'title-slide', 'subtitle-slide', 'footer-slide']
|
||||
isDefault: true,
|
||||
slides: [
|
||||
'first-slide',
|
||||
'content-slide',
|
||||
'bullet-point-slide',
|
||||
'comparison-slide',
|
||||
'type4-slide',
|
||||
'statistics-slide',
|
||||
'team-slide',
|
||||
'quote-slide'
|
||||
]
|
||||
}
|
||||
|
||||
export const CasualLayoutGroup = {
|
||||
id: 'casual',
|
||||
ordered: false,
|
||||
slides: ['bullet-point-slide', 'image-slide', 'chart-slide', 'table-slide', 'text-slide', 'title-slide', 'subtitle-slide', 'footer-slide']
|
||||
}
|
||||
|
||||
export const CreativeLayoutGroup = {
|
||||
export const CreativeLayoutGroup: LayoutGroup = {
|
||||
id: 'creative',
|
||||
name: 'Creative',
|
||||
description: 'Vibrant, artistic layouts for innovative and creative presentations',
|
||||
ordered: false,
|
||||
slides: ['bullet-point-slide', 'image-slide', 'chart-slide', 'table-slide', 'text-slide', 'title-slide', 'subtitle-slide', 'footer-slide']
|
||||
slides: [
|
||||
'image-slide',
|
||||
'icon-slide',
|
||||
'card-slide',
|
||||
'type1-slide',
|
||||
'type2-slide',
|
||||
'type3-slide',
|
||||
'process-slide'
|
||||
]
|
||||
}
|
||||
|
||||
export const ModernLayoutGroup = {
|
||||
export const ModernLayoutGroup: LayoutGroup = {
|
||||
id: 'modern',
|
||||
name: 'Modern',
|
||||
description: 'Contemporary designs with clean lines and sophisticated layouts',
|
||||
ordered: true,
|
||||
slides: ['bullet-point-slide', 'image-slide', 'chart-slide', 'table-slide', 'text-slide', 'title-slide', 'subtitle-slide', 'footer-slide']
|
||||
slides: [
|
||||
'type5-slide',
|
||||
'type6-slide',
|
||||
'type7-slide',
|
||||
'type8-slide',
|
||||
'timeline-slide',
|
||||
'type2-timeline-slide',
|
||||
'number-box-slide'
|
||||
]
|
||||
}
|
||||
|
||||
export const MinimalLayoutGroup: LayoutGroup = {
|
||||
id: 'minimal',
|
||||
name: 'Minimal',
|
||||
description: 'Simple, focused layouts that emphasize content over decoration',
|
||||
ordered: false,
|
||||
slides: [
|
||||
'content-slide',
|
||||
'bullet-point-slide',
|
||||
'type2-numbered-slide',
|
||||
'quote-slide',
|
||||
'statistics-slide'
|
||||
]
|
||||
}
|
||||
|
||||
export const LayoutGroups = [
|
||||
ProfessionalLayoutGroup,
|
||||
CreativeLayoutGroup,
|
||||
ModernLayoutGroup,
|
||||
MinimalLayoutGroup
|
||||
];
|
||||
|
||||
export const getDefaultLayoutGroup = (): LayoutGroup => {
|
||||
return LayoutGroups.find(group => group.isDefault) || ProfessionalLayoutGroup;
|
||||
};
|
||||
|
||||
export const getAllLayouts = (): string[] => {
|
||||
const allLayouts = new Set<string>();
|
||||
LayoutGroups.forEach(group => {
|
||||
group.slides.forEach(slide => allLayouts.add(slide));
|
||||
});
|
||||
return Array.from(allLayouts);
|
||||
};
|
||||
|
||||
export const getGroupByLayoutId = (layoutId: string): LayoutGroup | undefined => {
|
||||
return LayoutGroups.find(group => group.slides.includes(layoutId));
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue