feat(Nextjs): advance configuration selection
This commit is contained in:
parent
23e06cb77f
commit
ba257cdfa6
5 changed files with 254 additions and 8 deletions
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue