feat(Nextjs): advance configuration selection

This commit is contained in:
shiva raj badu 2025-09-09 00:01:17 +05:45
parent 23e06cb77f
commit ba257cdfa6
No known key found for this signature in database
5 changed files with 254 additions and 8 deletions

View file

@ -147,12 +147,19 @@ const DocumentsPreviewPage: React.FC = () => {
(fileItem: FileItem) => fileItem.file_path
);
trackEvent(MixpanelEvent.DocumentsPreview_Create_Presentation_API_Call);
const createResponse = await PresentationGenerationApi.createPresentation(
const createResponse = await PresentationGenerationApi.createPresentation(
{
content: config?.prompt ?? "",
n_slides: config?.slides ? parseInt(config.slides) : null,
file_paths: documentPaths,
language: config?.language ?? "",
tone: config?.tone,
verbosity: config?.verbosity,
instructions: config?.instructions || null,
include_table_of_contents: !!config?.includeTableOfContents,
include_title_slide: !!config?.includeTitleSlide,
web_search: !!config?.webSearch,
image_type: config?.imageType,
}
);

View file

@ -49,16 +49,30 @@ export class PresentationGenerationApi {
}
}
static async createPresentation({
static async createPresentation({
content,
n_slides,
file_paths,
language,
tone,
verbosity,
instructions,
include_table_of_contents,
include_title_slide,
web_search,
image_type,
}: {
content: string;
n_slides: number | null;
file_paths?: string[];
language: string | null;
tone?: string | null;
verbosity?: string | null;
instructions?: string | null;
include_table_of_contents?: boolean;
include_title_slide?: boolean;
web_search?: boolean;
image_type?: string | null;
}) {
try {
const response = await fetch(
@ -71,6 +85,13 @@ export class PresentationGenerationApi {
n_slides,
file_paths,
language,
tone,
verbosity,
instructions,
include_table_of_contents,
include_title_slide,
web_search,
image_type,
}),
cache: "no-cache",
}

View file

@ -5,9 +5,9 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { LanguageType, PresentationConfig } from "../type";
import { ImageType, LanguageType, PresentationConfig, ToneType, VerbosityType } from "../type";
import { useState } from "react";
import { Check, ChevronsUpDown } from "lucide-react";
import { Check, ChevronsUpDown, SlidersHorizontal } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Command,
@ -24,11 +24,15 @@ import {
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import ToolTip from "@/components/ToolTip";
// Types
interface ConfigurationSelectsProps {
config: PresentationConfig;
onConfigChange: (key: keyof PresentationConfig, value: string) => void;
onConfigChange: (key: keyof PresentationConfig, value: any) => void;
}
type SlideOption = "5" | "8" | "9" | "10" | "11" | "12" | "13" | "14" | "15" | "16" | "17" | "18" | "19" | "20";
@ -192,9 +196,46 @@ export function ConfigurationSelects({
onConfigChange,
}: ConfigurationSelectsProps) {
const [openLanguage, setOpenLanguage] = useState(false);
const [openAdvanced, setOpenAdvanced] = useState(false);
const [advancedDraft, setAdvancedDraft] = useState({
tone: config.tone,
verbosity: config.verbosity,
imageType: config.imageType,
instructions: config.instructions,
includeTableOfContents: config.includeTableOfContents,
includeTitleSlide: config.includeTitleSlide,
webSearch: config.webSearch,
});
const handleOpenAdvancedChange = (open: boolean) => {
if (open) {
setAdvancedDraft({
tone: config.tone,
verbosity: config.verbosity,
imageType: config.imageType,
instructions: config.instructions,
includeTableOfContents: config.includeTableOfContents,
includeTitleSlide: config.includeTitleSlide,
webSearch: config.webSearch,
});
}
setOpenAdvanced(open);
};
const handleSaveAdvanced = () => {
onConfigChange("tone", advancedDraft.tone);
onConfigChange("verbosity", advancedDraft.verbosity);
onConfigChange("imageType", advancedDraft.imageType);
onConfigChange("instructions", advancedDraft.instructions);
onConfigChange("includeTableOfContents", advancedDraft.includeTableOfContents);
onConfigChange("includeTitleSlide", advancedDraft.includeTitleSlide);
onConfigChange("webSearch", advancedDraft.webSearch);
setOpenAdvanced(false);
};
return (
<div className="flex flex-wrap order-1 gap-4">
<div className="flex flex-wrap order-1 gap-4 items-center">
<SlideCountSelect
value={config.slides}
onValueChange={(value) => onConfigChange("slides", value)}
@ -205,6 +246,141 @@ export function ConfigurationSelects({
open={openLanguage}
onOpenChange={setOpenLanguage}
/>
<ToolTip content="Advanced settings">
<button
aria-label="Advanced settings"
title="Advanced settings"
type="button"
onClick={() => handleOpenAdvancedChange(true)}
className="ml-auto flex items-center gap-2 text-sm underline underline-offset-4 bg-blue-100 hover:bg-blue-100 border-blue-200 focus-visible:ring-blue-300 border-none p-2 rounded-md font-instrument_sans font-medium"
>
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
</button>
</ToolTip>
<Dialog open={openAdvanced} onOpenChange={handleOpenAdvancedChange}>
<DialogContent className="max-w-2xl font-instrument_sans">
<DialogHeader>
<DialogTitle>Advanced settings</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
{/* Tone */}
<div className="w-full flex flex-col gap-2">
<label className="text-sm font-semibold text-gray-700">Tone</label>
<p className="text-xs text-gray-500">Controls the writing style (e.g., casual, professional, funny).</p>
<Select
value={advancedDraft.tone}
onValueChange={(value) => setAdvancedDraft((prev) => ({ ...prev, tone: value as ToneType }))}
>
<SelectTrigger className="w-full font-instrument_sans capitalize font-medium bg-blue-100 border-blue-200 focus-visible:ring-blue-300">
<SelectValue placeholder="Select tone" />
</SelectTrigger>
<SelectContent className="font-instrument_sans">
{Object.values(ToneType).map((tone) => (
<SelectItem key={tone} value={tone} className="text-sm font-medium capitalize">
{tone}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Verbosity */}
<div className="w-full flex flex-col gap-2">
<label className="text-sm font-semibold text-gray-700">Verbosity</label>
<p className="text-xs text-gray-500">Controls how detailed slide descriptions are: concise, standard, or text-heavy.</p>
<Select
value={advancedDraft.verbosity}
onValueChange={(value) => setAdvancedDraft((prev) => ({ ...prev, verbosity: value as VerbosityType }))}
>
<SelectTrigger className="w-full font-instrument_sans capitalize font-medium bg-blue-100 border-blue-200 focus-visible:ring-blue-300">
<SelectValue placeholder="Select verbosity" />
</SelectTrigger>
<SelectContent className="font-instrument_sans">
{Object.values(VerbosityType).map((verbosity) => (
<SelectItem key={verbosity} value={verbosity} className="text-sm font-medium capitalize">
{verbosity}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Image Type */}
<div className="w-full flex flex-col gap-2">
<label className="text-sm font-semibold text-gray-700">Image type</label>
<p className="text-xs text-gray-500">Choose whether images should be stock photos or AI-generated.</p>
<Select
value={advancedDraft.imageType}
onValueChange={(value) => setAdvancedDraft((prev) => ({ ...prev, imageType: value as ImageType }))}
>
<SelectTrigger className="w-full font-instrument_sans capitalize font-medium bg-blue-100 border-blue-200 focus-visible:ring-blue-300">
<SelectValue placeholder="Select image type" />
</SelectTrigger>
<SelectContent className="font-instrument_sans">
{Object.values(ImageType).map((imageType) => (
<SelectItem key={imageType} value={imageType} className="text-sm font-medium capitalize ">
{imageType}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Toggles */}
<div className="w-full flex flex-col gap-2 p-3 rounded-md bg-blue-100 border-blue-200">
<div className="flex items-center justify-between">
<label className="text-sm font-semibold text-gray-700">Include table of contents</label>
<Switch
checked={advancedDraft.includeTableOfContents}
onCheckedChange={(checked) => setAdvancedDraft((prev) => ({ ...prev, includeTableOfContents: checked }))}
/>
</div>
<p className="text-xs text-gray-600">Add an index slide summarizing sections (requires 3+ slides).</p>
</div>
<div className="w-full flex flex-col gap-2 p-3 rounded-md bg-blue-100 border-blue-200">
<div className="flex items-center justify-between">
<label className="text-sm font-semibold text-gray-700">Title slide</label>
<Switch
checked={advancedDraft.includeTitleSlide}
onCheckedChange={(checked) => setAdvancedDraft((prev) => ({ ...prev, includeTitleSlide: checked }))}
/>
</div>
<p className="text-xs text-gray-600">Include a title slide as the first slide.</p>
</div>
<div className="w-full flex flex-col gap-2 p-3 rounded-md bg-blue-100 border-blue-200">
<div className="flex items-center justify-between">
<label className="text-sm font-semibold text-gray-700">Web search</label>
<Switch
checked={advancedDraft.webSearch}
onCheckedChange={(checked) => setAdvancedDraft((prev) => ({ ...prev, webSearch: checked }))}
/>
</div>
<p className="text-xs text-gray-600">Allow the model to consult the web for fresher facts.</p>
</div>
{/* Instructions */}
<div className="w-full sm:col-span-2 flex flex-col gap-2">
<label className="text-sm font-semibold text-gray-700">Instructions</label>
<p className="text-xs text-gray-500">Optional guidance for the AI. These override defaults except format constraints.</p>
<Textarea
value={advancedDraft.instructions}
rows={4}
onChange={(e) => setAdvancedDraft((prev) => ({ ...prev, instructions: e.target.value }))}
placeholder="Example: Focus on enterprise buyers, emphasize ROI and security compliance. Keep slides data-driven, avoid jargon, and include a short call-to-action on the final slide."
className="py-2 px-3 border-2 font-medium text-sm min-h-[100px] max-h-[200px] border-blue-200 focus-visible:ring-offset-0 focus-visible:ring-blue-300"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => handleOpenAdvancedChange(false)}>Cancel</Button>
<Button onClick={handleSaveAdvanced} className="bg-[#5141e5] text-white hover:bg-[#5141e5]/90">Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View file

@ -16,7 +16,7 @@ import { useDispatch } from "react-redux";
import { clearOutlines, setPresentationId } from "@/store/slices/presentationGeneration";
import { ConfigurationSelects } from "./ConfigurationSelects";
import { PromptInput } from "./PromptInput";
import { LanguageType, PresentationConfig } from "../type";
import { ImageType, LanguageType, PresentationConfig, ToneType, VerbosityType } from "../type";
import SupportingDoc from "./SupportingDoc";
import { Button } from "@/components/ui/button";
import { ChevronRight } from "lucide-react";
@ -47,6 +47,13 @@ const UploadPage = () => {
slides: "8",
language: LanguageType.English,
prompt: "",
tone: ToneType.Default,
verbosity: VerbosityType.Standard,
imageType: ImageType.Stock,
instructions: "",
includeTableOfContents: false,
includeTitleSlide: false,
webSearch: false,
});
const [loadingState, setLoadingState] = useState<LoadingState>({
@ -156,8 +163,16 @@ const UploadPage = () => {
n_slides: config?.slides ? parseInt(config.slides) : null,
file_paths: [],
language: config?.language ?? "",
tone: config?.tone,
verbosity: config?.verbosity,
instructions: config?.instructions || null,
include_table_of_contents: !!config?.includeTableOfContents,
include_title_slide: !!config?.includeTitleSlide,
web_search: !!config?.webSearch,
image_type: config?.imageType,
});
dispatch(setPresentationId(createResponse.id));
dispatch(clearOutlines())
trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/outline" });

View file

@ -119,7 +119,34 @@ export enum LanguageType {
}
export interface PresentationConfig {
slides: "5" | "8" | "10" | "12" | "15" | null;
slides: string | null;
language: LanguageType | null;
prompt: string;
tone: ToneType;
verbosity: VerbosityType;
imageType: ImageType;
instructions: string;
includeTableOfContents: boolean;
includeTitleSlide: boolean;
webSearch: boolean;
}
export enum ToneType {
Default = "default",
Casual = "casual",
Professional = "professional",
Funny = "funny",
Educational = "educational",
Sales_Pitch = "sales_pitch",
}
export enum VerbosityType {
Concise = "concise",
Standard = "standard",
Text_Heavy = "text-heavy",
}
export enum ImageType {
Stock = "stock",
AIGenerated = "ai-generated",
}