AI provider configuration UI;Setting Page & Component added.

This commit is contained in:
shiva raj badu 2025-05-10 20:55:50 +05:45
parent 2a4d4ce28a
commit 9cca835704
11 changed files with 396 additions and 51 deletions

View file

@ -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 () => {

View file

@ -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"
>
<User className="w-5 h-5 text-gray-500" />
<LayoutDashboard className="w-5 h-5 text-gray-500" />
<span className="text-gray-700 text-sm font-medium font-satoshi">
Dashboard
</span>
</Link>
<Link
href="/profile"
href="/setting"
prefetch={false}
className="flex items-center gap-2 px-4 py-4 hover:bg-gray-50 border-b border-gray-200 transition-colors outline-none focus:bg-gray-50"
role="menuitem"
>
<User className="w-5 h-5 text-gray-500" />
<Settings className="w-5 h-5 text-gray-500" />
<span className="text-gray-700 text-sm font-medium font-satoshi">
Profile
Settings
</span>
</Link>
</nav>

View file

@ -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"
/>
<span className="text-sm h-6 text-[#2E2E2E] overflow-hidden">
{removeUUID(key.split("/").pop() ?? "file.txt")}
{key.split("/").pop() ?? "file.txt"}
</span>
</div>
))}

View file

@ -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: {

View file

@ -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",
}

View file

@ -243,7 +243,7 @@ const UploadPage = () => {
images: [],
research_reports: [],
language: config?.language ?? "",
sources: [],
});
try {

View file

@ -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();

View file

@ -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<string, ProviderConfig> = {
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<ConfigState>({
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 (
<>
<div className="text-4xl">Enter your keys</div>
</>
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white">
<main className="container mx-auto px-4 py-12 max-w-3xl">
{/* Branding Header */}
<div className="text-center mb-12">
<div className="flex items-center justify-center gap-3 mb-4">
<img src="/Logo.png" alt="Presenton Logo" className="" />
</div>
<p className="text-gray-600 text-lg">
Open-source AI presentation generator
</p>
</div>
{/* Main Configuration Card */}
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-8">
{/* Provider Selection */}
<div className="mb-8">
<label className="block text-sm font-medium text-gray-700 mb-3">
Select AI Provider
</label>
<div className="grid grid-cols-2 gap-4">
{Object.keys(PROVIDER_CONFIGS).map((provider) => (
<button
key={provider}
onClick={() => handleProviderChange(provider)}
className={`relative p-4 rounded-lg border-2 transition-all duration-200 ${
config.provider === provider
? "border-blue-500 bg-blue-50"
: "border-gray-200 hover:border-blue-200 hover:bg-gray-50"
}`}
>
<div className="flex items-center justify-center gap-3">
<span
className={`font-medium text-center ${
config.provider === provider
? "text-blue-700"
: "text-gray-700"
}`}
>
{provider.charAt(0).toUpperCase() + provider.slice(1)}
</span>
</div>
</button>
))}
</div>
</div>
{/* API Key Input */}
<div className="mb-8">
<label className="block text-sm font-medium text-gray-700 mb-2">
{config.provider.charAt(0).toUpperCase() +
config.provider.slice(1)}{" "}
API Key
</label>
<div className="relative">
<input
type="text"
value={config.apiKey}
onChange={(e) => 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"
/>
</div>
<p className="mt-2 text-sm text-gray-500 flex items-center gap-2">
<span className="block w-1 h-1 rounded-full bg-gray-400"></span>
Your API key will be stored locally and never shared
</p>
</div>
{/* Model Information */}
<div className="mb-8 p-4 bg-blue-50 rounded-lg border border-blue-100">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-blue-500 mt-0.5" />
<div>
<h3 className="text-sm font-medium text-blue-900 mb-1">
Selected Models
</h3>
<p className="text-sm text-blue-700">
Using {currentProvider.textModels[0].label} for text
generation and {currentProvider.imageModels[0].label} for
images
</p>
<p className="text-sm text-blue-600 mt-2 opacity-75">
We've pre-selected the best models for optimal presentation
generation
</p>
</div>
</div>
</div>
{/* API Guide Section */}
<div className="mb-8 bg-gray-50 rounded-lg p-6 border border-gray-200">
<div className="flex items-start gap-3 mb-4">
<Info className="w-5 h-5 text-blue-600 mt-1" />
<h3 className="text-lg font-medium text-gray-900">
{currentProvider.apiGuide.title}
</h3>
</div>
<div className="space-y-4">
<ol className="list-decimal list-inside space-y-2 text-gray-600">
{currentProvider.apiGuide.steps.map((step, index) => (
<li key={index} className="text-sm">
{step}
</li>
))}
</ol>
<div className="flex flex-col sm:flex-row gap-4 mt-6">
{currentProvider.apiGuide.videoUrl && (
<Link
href={currentProvider.apiGuide.videoUrl}
target="_blank"
className="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-700 transition-colors"
>
<PlayCircle className="w-4 h-4" />
Watch Video Tutorial
<ExternalLink className="w-3 h-3" />
</Link>
)}
<Link
href={currentProvider.apiGuide.docsUrl}
target="_blank"
className="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-700 transition-colors"
>
<span>Official Documentation</span>
<ExternalLink className="w-3 h-3" />
</Link>
</div>
</div>
</div>
{/* Save Button */}
<button
onClick={handleSaveConfig}
className="mt-8 w-full font-semibold bg-gradient-to-r from-blue-600 to-indigo-600 text-white py-3 px-4 rounded-lg hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200 transition-all duration-500"
>
Save Configuration
</button>
</div>
</main>
</div>
);
}

View file

@ -1,26 +0,0 @@
import React from "react";
import Header from "../dashboard/components/Header";
import Wrapper from "@/components/Wrapper";
const ProfilePage = async () => {
return (
<div className="min-h-screen bg-[#E9E8F8]">
<Header />
<Wrapper className="lg:w-[60%]">
<div className="py-8 space-y-6">
{/* Profile Details Section */}
<div className="bg-white rounded-lg p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-medium text-[#101828]">
Profile Detail
</h2>
</div>
</div>
</div>
</Wrapper>
</div>
);
};
export default ProfilePage;

View file

@ -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<Record<string, APIConfig>>({
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 (
<div className="min-h-screen bg-[#E9E8F8]">
<Header />
<Wrapper className="lg:w-[60%]">
<div className="py-8 space-y-6">
{/* Settings Header */}
<div className="flex items-center gap-3 mb-6">
<Settings className="w-8 h-8 text-blue-600" />
<h1 className="text-2xl font-semibold text-gray-900">Settings</h1>
</div>
{/* API Configuration Section */}
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center gap-3 mb-6">
<Key className="w-5 h-5 text-blue-600" />
<h2 className="text-lg font-medium text-gray-900">API Configuration</h2>
</div>
<div className="space-y-6">
{Object.entries(apiConfigs).map(([key, config]) => (
<div key={key} className="border-b border-gray-100 last:border-0 pb-6 last:pb-0">
<label className="block text-sm font-medium text-gray-700 mb-2">
{config.provider} API Key
</label>
<div className="flex gap-3">
<input
type="text"
value={config.apiKey}
onChange={(e) => 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`}
/>
<button
onClick={() => saveApiKey(key)}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Save
</button>
</div>
<p className="mt-2 text-sm text-gray-500">
Required for using {config.provider} services
</p>
</div>
))}
</div>
</div>
</div>
</Wrapper>
</div>
);
};
export default SettingsPage;