feat: improve New Slide and Presentation Mode components, enhance settings page with image and text provider summaries
This commit is contained in:
parent
3cdbf246ab
commit
6d8c34fd5e
9 changed files with 89 additions and 20 deletions
|
|
@ -7,11 +7,12 @@ import { Switch } from '@/components/ui/switch'
|
|||
import { cn } from '@/lib/utils'
|
||||
import { LLMConfig } from '@/types/llm_config'
|
||||
import { DALLE_3_QUALITY_OPTIONS, GPT_IMAGE_1_5_QUALITY_OPTIONS, IMAGE_PROVIDERS } from '@/utils/providerConstants'
|
||||
import { Check, ChevronsUpDown } from 'lucide-react'
|
||||
import { Check, ChevronUp, Eye, EyeOff } from 'lucide-react'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setLlmConfig: (config: any) => void }) => {
|
||||
const [openImageProviderSelect, setOpenImageProviderSelect] = useState(false);
|
||||
const [showApiKey, setShowApiKey] = useState(false);
|
||||
const isImageGenerationDisabled = llmConfig.DISABLE_IMAGE_GENERATION ?? false;
|
||||
const handleChangeImageGenerationDisabled = (value: boolean) => {
|
||||
setLlmConfig((prev: any) => ({
|
||||
|
|
@ -173,7 +174,7 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL
|
|||
: "Select image provider"}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="w-4 h-4 text-gray-500" />
|
||||
<ChevronUp className="w-4 h-4 text-gray-500" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
|
@ -277,7 +278,7 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL
|
|||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
type={showApiKey ? 'text' : 'password'}
|
||||
placeholder={`Enter your ${provider.apiKeyFieldLabel}`}
|
||||
className="w-full px-4 py-2.5 h-12 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors"
|
||||
value={getFieldValue(provider.apiKeyField)}
|
||||
|
|
@ -288,6 +289,13 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL
|
|||
)
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowApiKey((prev) => !prev)}
|
||||
className='absolute right-2 top-1/2 -translate-y-1/2 bg-white px-2 py-1 cursor-pointer'
|
||||
>
|
||||
{showApiKey ? <Eye className='w-4 h-4 text-gray-500' /> : <EyeOff className='w-4 h-4 text-gray-500' />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
|||
import SettingSideBar from "./SettingSideBar";
|
||||
import TextProvider from "./TextProvider";
|
||||
import ImageProvider from "./ImageProvider";
|
||||
import { IMAGE_PROVIDERS, LLM_PROVIDERS } from "@/utils/providerConstants";
|
||||
|
||||
// Button state interface
|
||||
interface ButtonState {
|
||||
|
|
@ -156,6 +157,31 @@ const SettingsPage = () => {
|
|||
}
|
||||
|
||||
|
||||
const textProviderKey = llmConfig.LLM || "openai";
|
||||
const textProviderLabel =
|
||||
LLM_PROVIDERS[textProviderKey]?.label || textProviderKey;
|
||||
const selectedTextModel =
|
||||
textProviderKey === "openai"
|
||||
? llmConfig.OPENAI_MODEL
|
||||
: textProviderKey === "google"
|
||||
? llmConfig.GOOGLE_MODEL
|
||||
: textProviderKey === "anthropic"
|
||||
? llmConfig.ANTHROPIC_MODEL
|
||||
: textProviderKey === "ollama"
|
||||
? llmConfig.OLLAMA_MODEL
|
||||
: textProviderKey === "custom"
|
||||
? llmConfig.CUSTOM_MODEL
|
||||
: "";
|
||||
const textSummary = selectedTextModel
|
||||
? `${textProviderLabel} (${selectedTextModel})`
|
||||
: textProviderLabel;
|
||||
|
||||
const imageSummary = llmConfig.DISABLE_IMAGE_GENERATION
|
||||
? "Image generation disabled"
|
||||
: llmConfig.IMAGE_PROVIDER
|
||||
? IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER]?.label || llmConfig.IMAGE_PROVIDER
|
||||
: "No image provider";
|
||||
|
||||
return (
|
||||
<div className="h-screen font-instrument_sans flex flex-col overflow-hidden relative">
|
||||
<div
|
||||
|
|
@ -171,10 +197,13 @@ const SettingsPage = () => {
|
|||
<SettingSideBar mode={mode} setMode={setMode} selectedProvider={selectedProvider} setSelectedProvider={setSelectedProvider} />
|
||||
<div className="w-full">
|
||||
<div className="sticky top-0 right-0 z-50 py-[28px] backdrop-blur mb-4 ">
|
||||
<div className="flex xl:flex-row flex-col gap-6 xl:gap-0 items-center justify-between">
|
||||
<div className="flex gap-3 items-center ">
|
||||
<h3 className=" text-[28px] tracking-[-0.84px] font-unbounded font-normal text-black flex items-center gap-2">
|
||||
Settings
|
||||
</h3>
|
||||
<p className="text-[10px] px-2.5 py-0.5 rounded-[50px] text-[#7A5AF8] border border-[#EDEEEF] font-medium ">
|
||||
{textSummary} · {imageSummary}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Switch } from '@/components/ui/switch';
|
|||
import { cn } from '@/lib/utils';
|
||||
import { LLMConfig } from '@/types/llm_config';
|
||||
import { LLM_PROVIDERS } from '@/utils/providerConstants';
|
||||
import { Check, ChevronsUpDown, Loader2, Eye, EyeOff } from 'lucide-react';
|
||||
import { Check, ChevronsUpDown, Loader2, Eye, EyeOff, ChevronUp } from 'lucide-react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner';
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ const TextProvider = ({
|
|||
: "Select text provider"}
|
||||
</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="w-4 h-4 text-gray-500" />
|
||||
<ChevronUp className="w-4 h-4 text-gray-500" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
|
@ -393,7 +393,7 @@ const TextProvider = ({
|
|||
: "Select a model"}
|
||||
</span>
|
||||
|
||||
<ChevronsUpDown className="w-4 h-4 text-gray-500" />
|
||||
<ChevronUp className="w-4 h-4 text-gray-500" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const OutlinePage: React.FC = () => {
|
|||
/>
|
||||
|
||||
<Wrapper className="h-full flex flex-col w-full relative">
|
||||
<div className="flex-grow w-full overflow-y-hidden mx-auto mt-6">
|
||||
<div className="flex-grow w-full overflow-y-hidden mx-auto ">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
|
||||
<TabsList className="my-4 h-auto w-fit rounded-full border border-[#DFDFE1] bg-[#F8F8F9] p-1.5">
|
||||
<TabsTrigger
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
import React, { useEffect, useState, memo, useCallback } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { addNewSlide } from "@/store/slices/presentationGeneration";
|
||||
import { Loader2, Trash } from "lucide-react";
|
||||
import { Loader2, X } from "lucide-react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { getCustomTemplateDetails } from "@/app/hooks/useCustomTemplates";
|
||||
import { getTemplatesByTemplateName } from "@/app/presentation-templates";
|
||||
|
|
@ -95,7 +94,7 @@ const NewSlideV1 = ({
|
|||
<div className="my-6 w-full bg-gray-50 p-8 max-w-[1280px]">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h2 className="text-2xl font-semibold">Select a Slide Layout</h2>
|
||||
<Trash
|
||||
<X
|
||||
onClick={() => setShowNewSlideSelection(false)}
|
||||
className="text-gray-500 text-2xl cursor-pointer"
|
||||
/>
|
||||
|
|
@ -111,7 +110,7 @@ const NewSlideV1 = ({
|
|||
<div className="my-6 w-full bg-gray-50 p-8 max-w-[1280px]">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<h2 className="text-2xl font-semibold">Select a Slide Layout</h2>
|
||||
<Trash2
|
||||
<X
|
||||
onClick={() => setShowNewSlideSelection(false)}
|
||||
className="text-gray-500 text-2xl cursor-pointer"
|
||||
/>
|
||||
|
|
@ -10,8 +10,9 @@ import {
|
|||
EyeOff,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Slide } from "../types/slide";
|
||||
import { V1ContentRender } from "./V1ContentRender";
|
||||
import { Slide } from "../../types/slide";
|
||||
import { V1ContentRender } from "../../components/V1ContentRender";
|
||||
|
||||
|
||||
|
||||
interface PresentationModeProps {
|
||||
|
|
@ -3,13 +3,13 @@ import React, { useState } from "react";
|
|||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import PresentationMode from "../../components/PresentationMode";
|
||||
import PresentationMode from "./PresentationMode";
|
||||
import SidePanel from "./SidePanel";
|
||||
import SlideContent from "./SlideContent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
||||
import { AlertCircle, Loader2 } from "lucide-react";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import {
|
||||
usePresentationStreaming,
|
||||
usePresentationData,
|
||||
|
|
@ -79,7 +79,6 @@ const PresentationPage: React.FC<PresentationPageProps> = ({
|
|||
handleSlideChange(newSlide, presentationData);
|
||||
};
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if(!loading && !isStreaming && presentationData?.slides && presentationData?.slides.length > 0){
|
||||
// const presentation_id = presentationData?.slides[0].layout.split(":")[0].split("custom-")[1];
|
||||
|
|
@ -132,6 +131,7 @@ const PresentationPage: React.FC<PresentationPageProps> = ({
|
|||
<SidePanel
|
||||
selectedSlide={selectedSlide}
|
||||
onSlideClick={handleSlideClick}
|
||||
presentationId={presentation_id}
|
||||
loading={loading}
|
||||
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import React, { } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
|
|
@ -22,10 +22,12 @@ import { SortableSlide } from "./SortableSlide";
|
|||
import SlideScale from "../../components/PresentationRender";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useRouter } from "next/navigation";
|
||||
import NewSlide from "./NewSlide";
|
||||
|
||||
interface SidePanelProps {
|
||||
selectedSlide: number;
|
||||
onSlideClick: (index: number) => void;
|
||||
presentationId: string;
|
||||
|
||||
loading: boolean;
|
||||
}
|
||||
|
|
@ -33,11 +35,13 @@ interface SidePanelProps {
|
|||
const SidePanel = ({
|
||||
selectedSlide,
|
||||
onSlideClick,
|
||||
presentationId,
|
||||
|
||||
loading,
|
||||
}: SidePanelProps) => {
|
||||
|
||||
const router = useRouter();
|
||||
const [showNewSlideSelection, setShowNewSlideSelection] = useState(false);
|
||||
|
||||
const { presentationData, isStreaming } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
|
|
@ -45,6 +49,18 @@ const SidePanel = ({
|
|||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const lastSlideIndex = presentationData?.slides?.length
|
||||
? presentationData.slides.length - 1
|
||||
: 0;
|
||||
const lastSlideTemplateId = presentationData?.slides?.[lastSlideIndex]?.layout
|
||||
? presentationData.slides[lastSlideIndex].layout.split(":")[0]
|
||||
: "";
|
||||
|
||||
const handleAddSlideClick = () => {
|
||||
if (!presentationData?.slides?.length || isStreaming) return;
|
||||
setShowNewSlideSelection(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -174,12 +190,28 @@ const SidePanel = ({
|
|||
|
||||
</DndContext>
|
||||
|
||||
<button className=" pt-6 gap-2 flex flex-col py-2 duration-300 items-center justify-center rounded-lg cursor-pointer mx-auto">
|
||||
<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"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
<span className="text-[11px] font-normal text-[#000000]">Add Slide</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{showNewSlideSelection && lastSlideTemplateId && (
|
||||
<div className="fixed inset-0 z-[60] bg-black/50 overflow-y-auto p-4">
|
||||
<div className="min-h-full flex items-start justify-center py-8">
|
||||
<NewSlide
|
||||
index={lastSlideIndex}
|
||||
templateID={lastSlideTemplateId}
|
||||
setShowNewSlideSelection={setShowNewSlideSelection}
|
||||
presentationId={presentationId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import {
|
|||
} from "@/store/slices/presentationGeneration";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
||||
import NewSlide from "../../components/NewSlide";
|
||||
import { addToHistory } from "@/store/slices/undoRedoSlice";
|
||||
import { V1ContentRender } from "../../components/V1ContentRender";
|
||||
import NewSlide from "./NewSlide";
|
||||
|
||||
interface SlideContentProps {
|
||||
slide: any;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue