diff --git a/app/main.ts b/app/main.ts index 653f4a79..2cc0e4eb 100644 --- a/app/main.ts +++ b/app/main.ts @@ -74,7 +74,7 @@ app.whenReady().then(async () => { console.log(`FastAPI port: ${fastApiPort}, NextJS port: ${nextjsPort}`); await startServers(fastApiPort, nextjsPort); - win?.loadURL(`${localhost}:${nextjsPort}/upload`); + win?.loadURL(`${localhost}:${nextjsPort}`); }); app.on("window-all-closed", async () => { diff --git a/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx b/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx index c5ad78bf..28929174 100644 --- a/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/UserAccount.tsx @@ -1,5 +1,5 @@ "use client"; -import { ChevronDown, User } from "lucide-react"; +import { ChevronDown, LayoutDashboard, Settings, User } from "lucide-react"; import { AvatarFallback } from "@/components/ui/avatar"; import { Avatar } from "@/components/ui/avatar"; import { @@ -37,20 +37,20 @@ const UserAccount = () => { className="flex items-center gap-2 px-4 py-4 hover:bg-gray-50 border-b border-gray-300 transition-colors outline-none focus:bg-gray-50" role="menuitem" > - + Dashboard - + - Profile + Settings diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx index b8838f4f..c0432c5b 100644 --- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx @@ -139,15 +139,15 @@ const DocumentsPreviewPage: React.FC = () => { }); const documentPaths = documentKeys.map(key => documents[key]); - + const researchReportPath = reports['research_report_content']; const createResponse = await PresentationGenerationApi.getQuestions({ prompt: config?.prompt ?? "", n_slides: config?.slides ? parseInt(config.slides) : null, documents: documentPaths, images: imageKeys, - research_reports: [reports['research_report_content']], + research_reports: researchReportPath ? [researchReportPath] : [], language: config?.language ?? "", - sources: allSources, + }); try { @@ -298,7 +298,7 @@ const DocumentsPreviewPage: React.FC = () => { alt="Document icon" /> - {removeUUID(key.split("/").pop() ?? "file.txt")} + {key.split("/").pop() ?? "file.txt"} ))} diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 38f988e0..bdc3638c 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -6,10 +6,8 @@ import { SquareArrowOutUpRight, Play, Loader2, - Copy, - Zap, } from "lucide-react"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import Image from "next/image"; import Wrapper from "@/components/Wrapper"; import { usePathname, useRouter } from "next/navigation"; @@ -129,7 +127,7 @@ const Header = ({ const getSlideMetadata = async () => { try { const response = await fetch( - "http://localhost:3000/api/slide-metadata", + "http://localhost:40001/api/slide-metadata", { method: "POST", headers: { diff --git a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts index d84513cd..5c5a4bc6 100644 --- a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts +++ b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts @@ -452,7 +452,7 @@ export class PresentationGenerationApi { images, research_reports, language, - sources, + }: { prompt: string; n_slides: number | null; @@ -460,7 +460,7 @@ export class PresentationGenerationApi { images?: string[]; research_reports?: string[]; language: string | null; - sources?: string[]; + }) { try { const response = await fetch( @@ -475,7 +475,7 @@ export class PresentationGenerationApi { documents, research_reports, images, - sources, + }), cache: "no-cache", } diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index b04a350e..44699a94 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -243,7 +243,7 @@ const UploadPage = () => { images: [], research_reports: [], language: config?.language ?? "", - sources: [], + }); try { diff --git a/servers/nextjs/app/api/slide-metadata/route.ts b/servers/nextjs/app/api/slide-metadata/route.ts index a0f4b351..8a7d44f3 100644 --- a/servers/nextjs/app/api/slide-metadata/route.ts +++ b/servers/nextjs/app/api/slide-metadata/route.ts @@ -111,12 +111,12 @@ export async function POST(request: NextRequest) { }); const page = await browser.newPage(); - await page.setViewport({ width: 1920, height: 1024, deviceScaleFactor: 3 }); + await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 }); try { await page.goto(url, { waitUntil: "networkidle0", - timeout: 90000, + timeout: 60000, }); } catch (error) { await browser.close(); diff --git a/servers/nextjs/app/page.tsx b/servers/nextjs/app/page.tsx index bc866f5a..3f6e1d79 100644 --- a/servers/nextjs/app/page.tsx +++ b/servers/nextjs/app/page.tsx @@ -1,10 +1,278 @@ -import Header from "@/components/homePage/header"; -import Footer from "@/components/homePage/Footer"; +"use client"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "@/hooks/use-toast"; +import { Info, ExternalLink, PlayCircle } from "lucide-react"; +import Link from "next/link"; + +interface ModelOption { + value: string; + label: string; + description?: string; +} + +interface ProviderConfig { + textModels: ModelOption[]; + imageModels: ModelOption[]; + apiGuide: { + title: string; + steps: string[]; + videoUrl?: string; + docsUrl: string; + }; +} + +const PROVIDER_CONFIGS: Record = { + openai: { + textModels: [ + { + value: "gpt-4", + label: "GPT-4", + description: "Most capable model, best for complex tasks", + }, + ], + imageModels: [ + { + value: "dall-e-3", + label: "DALL-E 3", + description: "Latest version with highest quality", + }, + ], + apiGuide: { + title: "How to get your OpenAI API Key", + steps: [ + "Go to platform.openai.com and sign in or create an account", + 'Click on your profile icon and select "View API keys"', + 'Click "Create new secret key" and give it a name', + "Copy your API key immediately (you won't be able to see it again)", + "Make sure you have sufficient credits in your account", + ], + videoUrl: "https://www.youtube.com/watch?v=OB99E7Y1cMA", + docsUrl: "https://platform.openai.com/docs/api-reference/authentication", + }, + }, + google: { + textModels: [ + { + value: "gemini-pro", + label: "Gemini Pro", + description: "Balanced model for most tasks", + }, + ], + imageModels: [ + { + value: "imagen", + label: "Imagen", + description: "Google's primary image generation model", + }, + ], + apiGuide: { + title: "How to get your Google AI Studio API Key", + steps: [ + "Visit aistudio.google.com", + 'Click on "Get API key" in the top navigation', + 'Click "Create API key" on the next page', + 'Choose either "Create API Key in new Project" or select an existing project', + "Copy your API key - you're ready to go!", + ], + videoUrl: "https://www.youtube.com/watch?v=o8iyrtQyrZM&t=66s", + docsUrl: "https://aistudio.google.com/app/apikey", + }, + }, +}; + +interface ConfigState { + provider: string; + apiKey: string; + textModel: string; + imageModel: string; +} export default function Home() { + const router = useRouter(); + const [config, setConfig] = useState({ + provider: "openai", + apiKey: "", + textModel: PROVIDER_CONFIGS.openai.textModels[0].value, + imageModel: PROVIDER_CONFIGS.openai.imageModels[0].value, + }); + + const handleProviderChange = (provider: string) => { + setConfig((prev) => ({ + ...prev, + provider, + textModel: PROVIDER_CONFIGS[provider].textModels[0].value, + imageModel: PROVIDER_CONFIGS[provider].imageModels[0].value, + })); + }; + + const handleConfigChange = ( + field: keyof ConfigState, + value: string | number + ) => { + setConfig((prev) => ({ + ...prev, + [field]: value, + })); + }; + + const currentProvider = PROVIDER_CONFIGS[config.provider]; + const handleSaveConfig = () => { + console.log("cllee"); + if (!config.apiKey) { + toast({ + title: "Error", + description: "Please enter an API key", + }); + return; + } + toast({ + title: "Configuration saved", + description: "You can now upload your presentation", + }); + router.push("/upload"); + }; + return ( - <> -
Enter your keys
- +
+
+ {/* Branding Header */} +
+
+ Presenton Logo +
+

+ Open-source AI presentation generator +

+
+ + {/* Main Configuration Card */} +
+ {/* Provider Selection */} +
+ +
+ {Object.keys(PROVIDER_CONFIGS).map((provider) => ( + + ))} +
+
+ + {/* API Key Input */} +
+ +
+ handleConfigChange("apiKey", e.target.value)} + className="w-full px-4 py-2.5 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" + placeholder="Enter your API key" + /> +
+

+ + Your API key will be stored locally and never shared +

+
+ + {/* Model Information */} +
+
+ +
+

+ Selected Models +

+

+ Using {currentProvider.textModels[0].label} for text + generation and {currentProvider.imageModels[0].label} for + images +

+

+ We've pre-selected the best models for optimal presentation + generation +

+
+
+
+ {/* API Guide Section */} +
+
+ +

+ {currentProvider.apiGuide.title} +

+
+ +
+
    + {currentProvider.apiGuide.steps.map((step, index) => ( +
  1. + {step} +
  2. + ))} +
+ +
+ {currentProvider.apiGuide.videoUrl && ( + + + Watch Video Tutorial + + + )} + + Official Documentation + + +
+
+
+ + {/* Save Button */} + +
+
+
); } diff --git a/servers/nextjs/app/profile/page.tsx b/servers/nextjs/app/profile/page.tsx deleted file mode 100644 index 24d65e3a..00000000 --- a/servers/nextjs/app/profile/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import Header from "../dashboard/components/Header"; -import Wrapper from "@/components/Wrapper"; - -const ProfilePage = async () => { - return ( -
-
- - -
- {/* Profile Details Section */} -
-
-

- Profile Detail -

-
-
-
-
-
- ); -}; - -export default ProfilePage; diff --git a/servers/nextjs/app/profile/loading.tsx b/servers/nextjs/app/setting/loading.tsx similarity index 100% rename from servers/nextjs/app/profile/loading.tsx rename to servers/nextjs/app/setting/loading.tsx diff --git a/servers/nextjs/app/setting/page.tsx b/servers/nextjs/app/setting/page.tsx new file mode 100644 index 00000000..c8c07d53 --- /dev/null +++ b/servers/nextjs/app/setting/page.tsx @@ -0,0 +1,105 @@ +'use client'; + +import React, { useState } from "react"; +import Header from "../dashboard/components/Header"; +import Wrapper from "@/components/Wrapper"; +import { Settings, Key, Palette, Bell } from 'lucide-react'; +import { toast } from '@/hooks/use-toast'; + +interface APIConfig { + provider: string; + apiKey: string; +} + +const SettingsPage = () => { + const [apiConfigs, setApiConfigs] = useState>({ + openai: { + provider: 'OpenAI', + apiKey: localStorage.getItem('openai_api_key') || '', + }, + google: { + provider: 'Google', + apiKey: localStorage.getItem('google_api_key') || '', + } + }); + + const handleApiKeyChange = (provider: string, value: string) => { + setApiConfigs(prev => ({ + ...prev, + [provider]: { + ...prev[provider], + apiKey: value + } + })); + }; + + const saveApiKey = (provider: string) => { + const config = apiConfigs[provider]; + if (!config.apiKey) { + toast({ + title: 'Error', + description: `Please enter a valid ${config.provider} API key`, + }); + return; + } + localStorage.setItem(`${provider}_api_key`, config.apiKey); + toast({ + title: 'Success', + description: `${config.provider} API key saved successfully`, + }); + }; + + return ( +
+
+ + +
+ {/* Settings Header */} +
+ +

Settings

+
+ + {/* API Configuration Section */} +
+
+ +

API Configuration

+
+ +
+ {Object.entries(apiConfigs).map(([key, config]) => ( +
+ +
+ handleApiKeyChange(key, e.target.value)} + className="flex-1 px-4 py-2.5 border border-gray-300 outline-none rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" + placeholder={`Enter your ${config.provider} API key`} + /> + +
+

+ Required for using {config.provider} services +

+
+ ))} +
+
+
+
+
+ ); +}; + +export default SettingsPage;