Merge pull request #10 from presenton/user_config_puppeteer_env_injection

User config puppeteer env injection
This commit is contained in:
Shiva Raj Badu 2025-05-11 22:17:22 +05:45 committed by GitHub
commit a756f6998d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 302 additions and 86 deletions

View file

@ -37,7 +37,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import ToolTip from "@/components/ToolTip";
import { BASE_URL } from "@/utils/constant";
import { getEnv } from "@/utils/constant";
interface ImageEditorProps {
initialImage: string | null;
@ -310,6 +310,8 @@ const ImageEditor = ({
if (!src) return "";
return src.startsWith("user") ? `file://${src}` : `file://${src}`;
};
const urls = getEnv();
const BASE_URL = urls.BASE_URL;
return (
<>

View file

@ -129,23 +129,28 @@ const Header = ({
const getSlideMetadata = async () => {
try {
// Get the current URL without any query parameters
const baseUrl = window.location.origin + window.location.pathname;
const urls = getEnv();
const response = await fetch(
`${urls.NEXT_PUBLIC_URL}/api/slide-metadata`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify({
url: window.location.href,
url: baseUrl,
theme: currentTheme,
customColors: currentColors,
tempDirectory: urls.TEMP_DIRECTORY,
baseUrl: urls.BASE_URL,
}),
}
);
if (!response.ok) {
throw new Error("Failed to fetch metadata");
const errorData = await response.json();
throw new Error(errorData.error || "Failed to fetch metadata");
}
const metadata = await response.json();
console.log("metadata", metadata);
@ -153,7 +158,11 @@ const Header = ({
} catch (error) {
setShowLoader(false);
console.error("Error fetching metadata:", error);
// You might want to show an error toast/notification here
toast({
title: "Error fetching slide metadata",
description: error instanceof Error ? error.message : "Failed to fetch metadata",
variant: "destructive",
});
throw error;
}
};

View file

@ -54,6 +54,8 @@ function useDebounce<T extends (...args: any[]) => void>(
}
const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
const urls = getEnv();
const BASE_URL = urls.BASE_URL;
const dispatch = useDispatch();
const [loading, setLoading] = useState(true);
const [selectedSlide, setSelectedSlide] = useState(0);

View file

@ -0,0 +1,30 @@
import { NextRequest, NextResponse } from "next/server";
import fs from "fs";
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const path = searchParams.get('path');
if (!path) {
return NextResponse.json(
{ error: "Path parameter is required" },
{ status: 400 }
);
}
let config = {};
if (fs.existsSync(path)) {
const configData = fs.readFileSync(path, 'utf-8');
config = JSON.parse(configData);
}
return NextResponse.json({ config });
} catch (error) {
console.error("Error reading config:", error);
return NextResponse.json(
{ error: "Failed to read configuration" },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,51 @@
import { NextRequest, NextResponse } from "next/server";
import fs from "fs";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { provider, apiKey, userConfigPath } = body;
if (!userConfigPath) {
return NextResponse.json(
{ error: "User config path not found" },
{ status: 500 }
);
}
// Create config object based on provider
const config = {
LLM: provider === "google" ? "gemini-pro" : "gpt-4",
OPENAI_API_KEY: provider === "openai" ? apiKey : undefined,
GOOGLE_API_KEY: provider === "google" ? apiKey : undefined,
};
// Read existing config if it exists
let existingConfig = {};
if (fs.existsSync(userConfigPath)) {
try {
const existingData = fs.readFileSync(userConfigPath, 'utf-8');
existingConfig = JSON.parse(existingData);
} catch (error) {
console.error("Error reading existing config:", error);
}
}
// Merge with existing config
const mergedConfig = {
...existingConfig,
...config,
};
// Write to file
fs.writeFileSync(userConfigPath, JSON.stringify(mergedConfig, null, 2));
return NextResponse.json({ success: true, config: mergedConfig });
} catch (error) {
console.error("Error saving user config:", error);
return NextResponse.json(
{ error: "Failed to save configuration" },
{ status: 500 }
);
}
}

View file

@ -102,7 +102,7 @@ export async function POST(request: NextRequest) {
let browser;
try {
const body = await request.json();
const { url, theme, customColors,tempDirectory } = body;
const { url, theme, customColors, tempDirectory, baseUrl } = body;
if (!url) {
return NextResponse.json({ error: "Missing URL" }, { status: 400 });
@ -116,21 +116,31 @@ export async function POST(request: NextRequest) {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
// Inject the environment variables before loading the page
await page.evaluateOnNewDocument(`
window.env = {
NEXT_PUBLIC_FAST_API: "${baseUrl || 'http://localhost:8000'}",
NEXT_PUBLIC_URL: "${url}",
};
`);
try {
await page.goto(url, {
waitUntil: "networkidle0",
timeout: 60000,
});
} catch (error) {
console.error("Navigation error:", error);
await browser.close();
return NextResponse.json({ error: "Failed to Navigate to provided URL" }, { status: 500 });
}
try {
await page.waitForSelector('[data-element-type="slide-container"]', {
timeout: 60000,
timeout: 80000,
});
await page.evaluate(
async (params: ThemeParams) => {
const { theme, customColors } = params;

View file

@ -24,7 +24,6 @@ export interface PresentationResponse {
}
export class DashboardApi {
// static BASE_URL = "http://localhost:48388";
static async getPresentations(): Promise<PresentationResponse[]> {
try {
@ -32,7 +31,6 @@ export class DashboardApi {
`${BASE_URL}/ppt/user_presentations`,
{
method: "GET",
headers: getHeader(),
}
);
if (response.status === 200) {
@ -54,7 +52,7 @@ export class DashboardApi {
`${BASE_URL}/ppt/presentation?presentation_id=${id}`,
{
method: "GET",
headers: getHeader(),
}
);
if (response.status === 200) {

View file

@ -4,6 +4,7 @@ import { useRouter } from "next/navigation";
import { toast } from "@/hooks/use-toast";
import { Info, ExternalLink, PlayCircle } from "lucide-react";
import Link from "next/link";
import { getEnv } from "@/utils/constant";
interface ModelOption {
value: string;
@ -88,7 +89,9 @@ interface ConfigState {
imageModel: string;
}
export default function Home() {
const urls = getEnv();
const router = useRouter();
const [config, setConfig] = useState<ConfigState>({
provider: "openai",
@ -117,8 +120,7 @@ export default function Home() {
};
const currentProvider = PROVIDER_CONFIGS[config.provider];
const handleSaveConfig = () => {
console.log("cllee");
const handleSaveConfig = async () => {
if (!config.apiKey) {
toast({
title: "Error",
@ -126,11 +128,47 @@ export default function Home() {
});
return;
}
toast({
title: "Configuration saved",
description: "You can now upload your presentation",
});
router.push("/upload");
try {
const userConfigPath = urls.USER_CONFIG_PATH;
if (!userConfigPath) {
toast({
title: "Error",
description: "Configuration path not found",
});
return;
}
const response = await fetch('/api/save-user-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: config.provider,
apiKey: config.apiKey,
userConfigPath,
}),
});
if (!response.ok) {
throw new Error('Failed to save configuration');
}
toast({
title: "Configuration saved",
description: "You can now upload your presentation",
});
router.push("/upload");
} catch (error) {
console.error('Error saving configuration:', error);
toast({
title: "Error",
description: "Failed to save configuration",
});
}
};
return (
@ -158,19 +196,17 @@ export default function Home() {
<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"
}`}
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"
}`}
className={`font-medium text-center ${config.provider === provider
? "text-blue-700"
: "text-gray-700"
}`}
>
{provider.charAt(0).toUpperCase() + provider.slice(1)}
</span>

View file

@ -1,58 +1,116 @@
'use client';
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import Header from "../dashboard/components/Header";
import Wrapper from "@/components/Wrapper";
import { Settings, Key, Palette, Bell } from 'lucide-react';
import { Settings, Key } from 'lucide-react';
import { toast } from '@/hooks/use-toast';
import { getEnv } from "@/utils/constant";
interface APIConfig {
provider: string;
apiKey: string;
interface UserConfig {
LLM?: string;
OPENAI_API_KEY?: string;
GOOGLE_API_KEY?: string;
}
const SettingsPage = () => {
const [apiConfigs, setApiConfigs] = useState<Record<string, APIConfig>>({
openai: {
provider: 'OpenAI',
apiKey: '',
},
google: {
provider: 'Google',
apiKey: '',
}
});
const [config, setConfig] = useState<UserConfig>({});
const [isLoading, setIsLoading] = useState(true);
const handleApiKeyChange = (provider: string, value: string) => {
setApiConfigs(prev => ({
...prev,
[provider]: {
...prev[provider],
apiKey: value
useEffect(() => {
const loadConfig = async () => {
try {
const urls = getEnv();
const userConfigPath = urls.USER_CONFIG_PATH;
if (!userConfigPath) {
console.error("User config path not found");
return;
}
// Use the Node.js fs API through an API route
const response = await fetch(`/api/read-config?path=${encodeURIComponent(userConfigPath)}`);
if (!response.ok) {
throw new Error('Failed to load configuration');
}
const { config } = await response.json();
setConfig(config);
} catch (error) {
console.error("Error loading config:", error);
toast({
title: 'Error',
description: 'Failed to load configuration',
});
} finally {
setIsLoading(false);
}
}));
};
};
const saveApiKey = (provider: string) => {
const config = apiConfigs[provider];
if (!config.apiKey) {
loadConfig();
}, []);
const handleSaveConfig = async (provider: string, apiKey: string) => {
try {
const urls = getEnv();
const userConfigPath = urls.USER_CONFIG_PATH;
if (!userConfigPath) {
toast({
title: 'Error',
description: 'Configuration path not found',
});
return;
}
const response = await fetch('/api/save-user-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider,
apiKey,
userConfigPath,
}),
});
if (!response.ok) {
throw new Error('Failed to save configuration');
}
const { config: newConfig } = await response.json();
setConfig(newConfig);
toast({
title: 'Success',
description: 'Configuration saved successfully',
});
} catch (error) {
console.error('Error:', error);
toast({
title: 'Error',
description: `Please enter a valid ${config.provider} API key`,
description: 'Failed to save configuration',
});
return;
}
localStorage.setItem(`${provider}_api_key`, config.apiKey);
toast({
title: 'Success',
description: `${config.provider} API key saved successfully`,
});
};
if (isLoading) {
return (
<div className="min-h-screen bg-[#E9E8F8]">
<Header />
<Wrapper className="lg:w-[60%]">
<div className="py-8">
<div className="text-center">Loading configuration...</div>
</div>
</Wrapper>
</div>
);
}
return (
<div className="min-h-screen bg-[#E9E8F8]">
<Header />
<Wrapper className="lg:w-[60%]">
<div className="py-8 space-y-6">
{/* Settings Header */}
@ -69,31 +127,51 @@ const SettingsPage = () => {
</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>
{/* OpenAI Configuration */}
<div className="border-b border-gray-100 pb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
OpenAI API Key
</label>
<div className="flex gap-3">
<input
type="text"
value={config.OPENAI_API_KEY || ''}
onChange={(e) => setConfig(prev => ({ ...prev, OPENAI_API_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 OpenAI API key"
/>
<button
onClick={() => handleSaveConfig('openai', config.OPENAI_API_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 OpenAI services</p>
</div>
{/* Google Configuration */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Google API Key
</label>
<div className="flex gap-3">
<input
type="password"
value={config.GOOGLE_API_KEY || ''}
onChange={(e) => setConfig(prev => ({ ...prev, GOOGLE_API_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 Google API key"
/>
<button
onClick={() => handleSaveConfig('google', config.GOOGLE_API_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 Google services</p>
</div>
</div>
</div>
</div>