Merge branch 'feat/chat_editing' into feat/presenton-chat

This commit is contained in:
shiva raj badu 2026-04-24 13:22:38 +05:45
commit 47e16cd786
No known key found for this signature in database
5 changed files with 197 additions and 74 deletions

View file

@ -0,0 +1,122 @@
import { Plus, Send } from 'lucide-react'
import React from 'react'
const suggestions = [
{
id: 1,
icon: <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<g clip-path="url(#clip0_5237_2275)">
<path d="M10.82 1.82039L10.18 1.18039C10.1238 1.12355 10.0568 1.07842 9.98299 1.04763C9.90918 1.01683 9.83 1.00098 9.75002 1.00098C9.67005 1.00098 9.59087 1.01683 9.51706 1.04763C9.44325 1.07842 9.37628 1.12355 9.32002 1.18039L1.18002 9.32039C1.12318 9.37665 1.07806 9.44362 1.04726 9.51743C1.01647 9.59123 1.00061 9.67041 1.00061 9.75039C1.00061 9.83036 1.01647 9.90954 1.04726 9.98335C1.07806 10.0572 1.12318 10.1241 1.18002 10.1804L1.82002 10.8204C1.87593 10.8778 1.94279 10.9235 2.01664 10.9547C2.0905 10.9859 2.16985 11.0019 2.25002 11.0019C2.33019 11.0019 2.40955 10.9859 2.4834 10.9547C2.55726 10.9235 2.62411 10.8778 2.68002 10.8204L10.82 2.68039C10.8775 2.62448 10.9231 2.55762 10.9543 2.48377C10.9855 2.40991 11.0016 2.33056 11.0016 2.25039C11.0016 2.17022 10.9855 2.09087 10.9543 2.01701C10.9231 1.94316 10.8775 1.8763 10.82 1.82039Z" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M7 3.5L8.5 5" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.5 3V5" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9.5 7V9" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5 1V2" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.5 4H1.5" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.5 8H8.5" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.5 1.5H4.5" stroke="#7F22FE" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_5237_2275">
<rect width="12" height="12" fill="white" />
</clipPath>
</defs>
</svg>,
suggestion: 'Generate a full presentation from my topic',
},
{
id: 2,
icon: <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<g clip-path="url(#clip0_5237_2289)">
<path d="M4.96847 7.75012C4.92383 7.57709 4.83364 7.41918 4.70728 7.29282C4.58092 7.16646 4.42301 7.07626 4.24997 7.03162L1.18247 6.24062C1.13014 6.22577 1.08407 6.19425 1.05128 6.15085C1.01848 6.10744 1.00073 6.05453 1.00073 6.00012C1.00073 5.94572 1.01848 5.89281 1.05128 5.8494C1.08407 5.806 1.13014 5.77448 1.18247 5.75962L4.24997 4.96812C4.42294 4.92353 4.58082 4.83341 4.70717 4.70714C4.83353 4.58088 4.92375 4.42307 4.96847 4.25012L5.75947 1.18262C5.77417 1.13008 5.80566 1.0838 5.84913 1.05082C5.8926 1.01785 5.94566 1 6.00022 1C6.05478 1 6.10784 1.01785 6.15131 1.05082C6.19478 1.0838 6.22627 1.13008 6.24097 1.18262L7.03147 4.25012C7.07611 4.42316 7.1663 4.58107 7.29266 4.70743C7.41902 4.83379 7.57693 4.92399 7.74997 4.96862L10.8175 5.75912C10.8702 5.77367 10.9167 5.80513 10.9499 5.84866C10.983 5.8922 11.001 5.94541 11.001 6.00012C11.001 6.05484 10.983 6.10805 10.9499 6.15159C10.9167 6.19512 10.8702 6.22657 10.8175 6.24112L7.74997 7.03162C7.57693 7.07626 7.41902 7.16646 7.29266 7.29282C7.1663 7.41918 7.07611 7.57709 7.03147 7.75012L6.24047 10.8176C6.22577 10.8702 6.19428 10.9165 6.15081 10.9494C6.10734 10.9824 6.05428 11.0002 5.99972 11.0002C5.94516 11.0002 5.8921 10.9824 5.84863 10.9494C5.80516 10.9165 5.77367 10.8702 5.75897 10.8176L4.96847 7.75012Z" stroke="#155DFC" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10 1.5V3.5" stroke="#155DFC" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11 2.5H9" stroke="#155DFC" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2 8.5V9.5" stroke="#155DFC" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.5 9H1.5" stroke="#155DFC" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_5237_2289">
<rect width="12" height="12" fill="white" />
</clipPath>
</defs>
</svg>,
suggestion: 'Improve this slide content',
},
{
id: 3,
icon: <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M6 10H10.5" stroke="#009966" strokeLinecap="round" strokeLinejoin="round" />
<path d="M8.18799 1.81087C8.38703 1.61182 8.657 1.5 8.93849 1.5C9.21998 1.5 9.48994 1.61182 9.68899 1.81087C9.88803 2.00991 9.99986 2.27988 9.99986 2.56137C9.99986 2.84286 9.88803 3.11282 9.68899 3.31187L3.68399 9.31737C3.56504 9.43632 3.418 9.52333 3.25649 9.57037L1.82049 9.98937C1.77746 10.0019 1.73186 10.0027 1.68844 9.99155C1.64503 9.98042 1.6054 9.95783 1.57371 9.92614C1.54202 9.89445 1.51943 9.85483 1.50831 9.81141C1.49719 9.768 1.49794 9.72239 1.51049 9.67937L1.92949 8.24337C1.9766 8.08203 2.06361 7.93518 2.18249 7.81637L8.18799 1.81087Z" stroke="#009966" strokeLinecap="round" strokeLinejoin="round" />
</svg>,
suggestion: 'Rewrite this content professionally',
},
{
id: 4,
icon: <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M1.5 1.5V9.5C1.5 9.76522 1.60536 10.0196 1.79289 10.2071C1.98043 10.3946 2.23478 10.5 2.5 10.5H10.5" stroke="#E17100" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 8.5V4.5" stroke="#E17100" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6.5 8.5V2.5" stroke="#E17100" strokeLinecap="round" strokeLinejoin="round" />
<path d="M4 8.5V7" stroke="#E17100" strokeLinecap="round" strokeLinejoin="round" />
</svg>,
suggestion: 'Add speaker notes to this slide',
}
]
const quickPrompts = ['Expand each section', 'Reorder for storytelling', 'Add missing sections', 'Convert to pitch flow']
const Chat = () => {
return (
<div className='w-full h-full px-4 pt-8 bg-white overflow-y-auto hide-scrollbar'>
<h4 className='flex items-center gap-2 text-sm font-semibold text-[#101828]'><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M19.1407 9.46542C16.5537 9.21616 14.5067 7.17009 14.2577 4.58528L13.8376 0.220703L13.4175 4.58528C13.1685 7.17053 11.1215 9.2166 8.53451 9.46542L4.1731 9.88521L8.53451 10.305C11.1215 10.5543 13.1685 12.6003 13.4175 15.1852L13.8376 19.5497L14.2577 15.1852C14.5067 12.5999 16.5537 10.5538 19.1407 10.305L23.5021 9.88521L19.1407 9.46542Z" fill="#7A5AF8" />
<path d="M9.07681 16.8431C7.62808 16.7035 6.48175 15.5577 6.34232 14.1102L6.10707 11.666L5.87183 14.1102C5.7324 15.5579 4.58606 16.7037 3.13734 16.8431L0.694946 17.0781L3.13734 17.3132C4.58606 17.4528 5.7324 18.5986 5.87183 20.0461L6.10707 22.4903L6.34232 20.0461C6.48175 18.5984 7.62808 17.4526 9.07681 17.3132L11.5192 17.0781L9.07681 16.8431Z" fill="#7A5AF8" />
</svg> AI Assistant</h4>
<div className='pt-8'>
<h4 className='text-[10px] text-[#99A1AF] font-normal leading-[15px] tracking-[0.367px] mb-2 '>SUGGESTIONS</h4>
<div className='flex flex-col gap-1.5'>
{
suggestions.map((suggestion) => (
<div key={suggestion.id} className='flex items-center gap-3 px-3 py-2 rounded-[10px] border border-[#F4F4F4] cursor-pointer'>
{suggestion.icon}
<p className='text-xs text-[#364153] font-normal leading-[15px] tracking-[0.367px] '>{suggestion.suggestion}</p>
</div>
))
}
</div>
</div>
<div className='mt-12'>
<h4 className='text-[10px] text-[#99A1AF] font-normal leading-[15px] tracking-[0.367px] mb-2 '>QUICK PROMPTS</h4>
<div className='flex flex-wrap gap-2'>
{
quickPrompts.map((prompt) => (
<button key={prompt} className='px-2.5 py-1 rounded-[10px] border border-[#F4F4F4] cursor-pointer'>
<p className='text-xs text-[#364153] font-normal leading-[15px] tracking-[0.367px] '>{prompt}</p>
</button>
))
}
</div>
</div>
<div className='relative bg-white border border-[#F4F4F4] rounded-[8px] py-3 px-2.5 mt-[100px]'
style={{
boxShadow: '0 4px 14px 0 rgba(0, 0, 0, 0.04)'
}}
>
<textarea
name='chat-input'
id='chat-input'
className=' h-full w-full bg-transparent focus:outline-none focus:ring-0 '
rows={4}
placeholder='Improve your slides...'
/>
<button className='absolute bottom-3 h-[28px] left-3 bg-white border border-[#EDEEEF] rounded-[64px] px-3 py-1'><Plus className='w-3 h-3 text-black' /></button>
<button className='absolute bottom-3 flex items-center gap-1.5 px-3 py-2 right-3'
style={{
background: 'linear-gradient(270deg, #D5CAFC 2.4%, #E3D2EB 27.88%, #F4DCD3 69.23%, #FDE4C2 100%)',
borderRadius: '34px',
}}
>
<Send className='w-3 h-3 text-[#191919]' /> Send</button>
</div>
</div>
)
}
export default Chat

View file

@ -416,12 +416,18 @@ const PresentationHeader = ({
return (
<>
<div className="py-7 sticky top-0 bg-white z-50 mb-[17px] font-syne flex justify-between items-center gap-4">
{presentationData && !isStreaming && !isEditingTitle ? (
<ToolTip content="Rename presentation">{titleBlock}</ToolTip>
) : (
titleBlock
)}
<div className="py-[18px] px-4 sticky top-0 bg-white z-50 mb-[17px] font-syne flex justify-between items-center gap-4">
<div className="flex items-center gap-3">
<img onClick={() => {
router.push("/dashboard");
}} src="/logo-with-bg.png" alt="" className="w-10 h-10 cursor-pointer object-contain" />
{presentationData && !isStreaming && !isEditingTitle ? (
<ToolTip content="Rename presentation">{titleBlock}</ToolTip>
) : (
titleBlock
)}
</div>
<div className="flex items-center gap-2.5">

View file

@ -23,6 +23,7 @@ import { applyPresentationThemeToElement } from "../utils/applyPresentationTheme
import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo";
import PresentationHeader from "./PresentationHeader";
import Chat from "./Chat";
const PresentationPage: React.FC<PresentationPageProps> = ({
presentation_id,
@ -141,66 +142,63 @@ const PresentationPage: React.FC<PresentationPageProps> = ({
}
return (
<div className="h-screen overflow-hidden font-syne ">
<div className="h-screen overflow-hidden font-syne">
<div
style={{
background: "#ffffff",
background: "#EDEEEF",
}}
id="presentation-slides-wrapper"
className="flex gap-6 relative "
className="relative flex h-full flex-col overflow-hidden"
>
<div className="w-[200px]">
<SidePanel
selectedSlide={selectedSlide}
onSlideClick={handleSlideClick}
presentationId={presentation_id}
loading={loading}
/>
</div>
<div className=" w-full h-[calc(100vh-20px)] pr-[25px] overflow-y-auto">
<PresentationHeader presentation_id={presentation_id} isPresentationSaving={isSaving} currentSlide={selectedSlide} />
<div
style={{
background: "rgba(255, 255, 255, 0.10)",
boxShadow: "0 0 20.01px 0 rgba(122, 90, 248, 0.16) inset",
}}
className="p-6 rounded-[20px] font-inter flex flex-col items-center overflow-hidden justify-center border border-[#EDECEC] "
>
<div className="w-full max-w-[1280px] h-full">
{!presentationData ||
loading ||
!presentationData?.slides ||
presentationData?.slides.length === 0 ? (
<div className="relative w-full h-[calc(100vh-120px)] mx-auto">
<div className="">
{Array.from({ length: 2 }).map((_, index) => (
<Skeleton
key={index}
className="aspect-video bg-gray-400 my-4 w-full mx-auto "
/>
))}
<PresentationHeader presentation_id={presentation_id} isPresentationSaving={isSaving} currentSlide={selectedSlide} />
<div className="flex flex-1 min-h-0 gap-6">
<div className="w-[120px] h-full shrink-0 self-start sticky top-0">
<SidePanel
selectedSlide={selectedSlide}
onSlideClick={handleSlideClick}
presentationId={presentation_id}
loading={loading}
/>
</div>
<div className="w-full min-w-0 h-full flex-1 ">
<div className="font-inter h-full overflow-y-auto hide-scrollbar">
<div className="w-full max-w-[1280px] min-h-full mx-auto flex flex-col items-center pb-8">
{!presentationData ||
loading ||
!presentationData?.slides ||
presentationData?.slides.length === 0 ? (
<div className="relative w-full h-[calc(100vh-120px)] mx-auto hide-scrollbar">
<div className="">
{Array.from({ length: 2 }).map((_, index) => (
<Skeleton
key={index}
className="aspect-video bg-gray-400 my-4 w-full mx-auto "
/>
))}
</div>
{stream && <LoadingState />}
</div>
{stream && <LoadingState />}
</div>
) : (
<>
{presentationData &&
presentationData.slides &&
presentationData.slides.length > 0 &&
presentationData.slides.map((slide: any, index: number) => (
<SlideContent
key={`${slide.type}-${index}-${slide.index}`}
slide={slide}
index={index}
presentationId={presentation_id}
/>
))}
</>
)}
) : (
<>
{presentationData &&
presentationData.slides &&
presentationData.slides.length > 0 &&
presentationData.slides.map((slide: any, index: number) => (
<SlideContent
key={`${slide.type}-${index}-${slide.index}`}
slide={slide}
index={index}
presentationId={presentation_id}
/>
))}
</>
)}
</div>
</div>
</div>
<div className="w-full max-w-[370px] h-full shrink-0 self-start sticky top-0">
<Chat />
</div>
</div>
</div>
</div>

View file

@ -21,7 +21,7 @@ import { setPresentationData } from "@/store/slices/presentationGeneration";
import { SortableSlide } from "./SortableSlide";
import SlideScale from "../../components/PresentationRender";
import { Separator } from "@/components/ui/separator";
import { usePathname, useRouter } from "next/navigation";
import { usePathname } from "next/navigation";
import NewSlide from "./NewSlide";
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
@ -40,8 +40,6 @@ const SidePanel = ({
loading,
}: SidePanelProps) => {
const router = useRouter();
const pathname = usePathname();
const [showNewSlideSelection, setShowNewSlideSelection] = useState(false);
@ -132,32 +130,26 @@ const SidePanel = ({
}
return (
<div className="bg-[#F6F6F9] pt-8 px-4 w-[200px]">
<div className="px-4 w-[120px] h-full">
<img onClick={() => {
router.push("/dashboard");
}} src="/logo-with-bg.png" alt="" className="w-10 h-10 cursor-pointer object-contain" />
<Separator orientation="horizontal" className="my-6 " />
<div
className={`
relative bg-[#F6F6F9] h-full z-50 xl:z-auto
relative h-full z-50 xl:z-auto
transition-all duration-300 ease-in-out
`}
>
<div
className="w-full h-[calc(100vh-120px)] hide-scrollbar overflow-hidden slide-theme "
className="w-full h-full hide-scrollbar overflow-hidden slide-theme flex flex-col"
>
<p className="text-xl font-normal font-syne pb-3.5 text-[#000000]">Slides ({presentationData?.slides?.length})</p>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<div className=" overflow-y-auto w-full hide-scrollbar h-[calc(100%-140px)] space-y-3.5">
<div className="overflow-y-auto w-full hide-scrollbar min-h-0 flex-1 space-y-3.5">
{isStreaming ? (
presentationData &&
presentationData?.slides.map((slide: any, index: number) => (
@ -203,7 +195,7 @@ const SidePanel = ({
<button
type="button"
onClick={handleAddSlideClick}
className="pt-6 gap-2 flex flex-col py-2 duration-300 items-center justify-center rounded-lg cursor-pointer mx-auto"
className="py-4 gap-2 flex flex-col duration-300 items-center justify-center rounded-lg cursor-pointer mx-auto"
>
<Plus className="w-3.5 h-3.5" />
<span className="text-[11px] font-normal text-[#000000]">Add Slide</span>

View file

@ -3,14 +3,13 @@ import { CSS } from '@dnd-kit/utilities';
import { Slide } from '../../types/slide';
import { useRef } from 'react';
import { V1ContentRender } from '../../components/V1ContentRender';
import { useSearchParams } from 'next/navigation';
interface SortableSlideProps {
slide: Slide;
index: number;
selectedSlide: number;
onSlideClick: (index: any) => void;
}
const SCALE = 0.125;
const SCALE = 0.0625;
export function SortableSlide({ slide, index, selectedSlide, onSlideClick }: SortableSlideProps) {
const lastClickTime = useRef(0);
@ -56,11 +55,17 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick }: Sor
className={` cursor-pointer border relative p-1 rounded-[12px] transition-all duration-200 ${selectedSlide === index ? ' border-[#BDB4FE]' : 'border-[#EDEEEF]'
}`}
>
<p className='absolute top-1/2 translate-y-1/2 -left-3 bg-white border border-[#EDEEEF] rounded-[40px] text-[#191919] text-[10px] font-medium px-1 z-50
'>
{index + 1}
</p>
<div
className="relative"
style={{ height: `${720 * SCALE}px`, overflow: "hidden" }}
>
<div
className="absolute top-0 left-0 pointer-events-none"
style={{