fix: export issue in chromium
This commit is contained in:
parent
3dc5a22a62
commit
73c097bd31
8 changed files with 671 additions and 562 deletions
|
|
@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y \
|
|||
curl \
|
||||
libreoffice \
|
||||
fontconfig \
|
||||
chromium \
|
||||
imagemagick
|
||||
|
||||
RUN sed -i 's/rights="none" pattern="PDF"/rights="read|write" pattern="PDF"/' /etc/ImageMagick-6/policy.xml
|
||||
|
|
@ -22,7 +23,7 @@ WORKDIR /app
|
|||
# Set environment variables
|
||||
ENV APP_DATA_DIRECTORY=/app_data
|
||||
ENV TEMP_DIRECTORY=/tmp/presenton
|
||||
# ENV PYTHONPATH="${PYTHONPATH}:/app/servers/fastapi"
|
||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
|
||||
|
||||
# Install ollama
|
||||
|
|
@ -39,8 +40,6 @@ WORKDIR /app/servers/nextjs
|
|||
COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Install chrome for puppeteer
|
||||
RUN npx puppeteer browsers install chrome@136.0.7103.92 --install-deps
|
||||
|
||||
# Copy Next.js app
|
||||
COPY servers/nextjs/ /app/servers/nextjs/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ RUN apt-get update && apt-get install -y \
|
|||
curl \
|
||||
libreoffice \
|
||||
fontconfig \
|
||||
chromium \
|
||||
imagemagick
|
||||
|
||||
RUN sed -i 's/rights="none" pattern="PDF"/rights="read|write" pattern="PDF"/' /etc/ImageMagick-6/policy.xml
|
||||
|
|
@ -24,7 +25,7 @@ RUN ls -a
|
|||
# Set environment variables
|
||||
ENV APP_DATA_DIRECTORY=/app_data
|
||||
ENV TEMP_DIRECTORY=/tmp/presenton
|
||||
# ENV PYTHONPATH="${PYTHONPATH}:/app/servers/fastapi"
|
||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
|
||||
# Install ollama
|
||||
RUN curl -fsSL http://ollama.com/install.sh | sh
|
||||
|
|
@ -40,9 +41,6 @@ WORKDIR /node_dependencies
|
|||
COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./
|
||||
RUN npm install --verbose
|
||||
|
||||
# Install chrome for puppeteer
|
||||
RUN npx puppeteer browsers install chrome@138.0.7204.94 --install-deps
|
||||
|
||||
RUN chmod -R 777 /node_dependencies
|
||||
|
||||
# Copy nginx configuration
|
||||
|
|
|
|||
|
|
@ -5,200 +5,241 @@ import GroupLayouts from "./GroupLayouts";
|
|||
|
||||
import { LayoutGroup } from "../types/index";
|
||||
interface LayoutSelectionProps {
|
||||
selectedLayoutGroup: LayoutGroup | null;
|
||||
onSelectLayoutGroup: (group: LayoutGroup) => void;
|
||||
selectedLayoutGroup: LayoutGroup | null;
|
||||
onSelectLayoutGroup: (group: LayoutGroup) => void;
|
||||
}
|
||||
|
||||
const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
||||
selectedLayoutGroup,
|
||||
onSelectLayoutGroup
|
||||
selectedLayoutGroup,
|
||||
onSelectLayoutGroup,
|
||||
}) => {
|
||||
const {
|
||||
getLayoutsByGroup,
|
||||
getGroupSetting,
|
||||
getAllGroups,
|
||||
getFullDataByGroup,
|
||||
loading
|
||||
} = useLayout();
|
||||
const {
|
||||
getLayoutsByGroup,
|
||||
getGroupSetting,
|
||||
getAllGroups,
|
||||
getFullDataByGroup,
|
||||
loading,
|
||||
} = useLayout();
|
||||
|
||||
const [summaryMap, setSummaryMap] = React.useState<Record<string, { lastUpdatedAt?: number; name?: string; description?: string }>>({});
|
||||
const [summaryMap, setSummaryMap] = React.useState<
|
||||
Record<
|
||||
string,
|
||||
{ lastUpdatedAt?: number; name?: string; description?: string }
|
||||
>
|
||||
>({});
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch custom templates summary to get last_updated_at and template meta for sorting and display
|
||||
fetch("/api/v1/ppt/template-management/summary")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const map: Record<string, { lastUpdatedAt?: number; name?: string; description?: string }> = {};
|
||||
if (data && Array.isArray(data.presentations)) {
|
||||
for (const p of data.presentations) {
|
||||
const slug = `custom-${p.presentation_id}`;
|
||||
map[slug] = {
|
||||
lastUpdatedAt: p.last_updated_at ? new Date(p.last_updated_at).getTime() : 0,
|
||||
name: p.template?.name,
|
||||
description: p.template?.description,
|
||||
};
|
||||
}
|
||||
}
|
||||
setSummaryMap(map);
|
||||
})
|
||||
.catch(() => setSummaryMap({}));
|
||||
}, []);
|
||||
|
||||
const layoutGroups: LayoutGroup[] = React.useMemo(() => {
|
||||
const groups = getAllGroups();
|
||||
if (groups.length === 0) return [];
|
||||
|
||||
const Groups: LayoutGroup[] = groups
|
||||
.filter(groupName => {
|
||||
// Filter out groups that contain any errored layouts (from custom templates compile/parse errors)
|
||||
const fullData = getFullDataByGroup(groupName);
|
||||
const hasErroredLayouts = fullData.some(fd => (fd as any)?.component?.displayName === "CustomTemplateErrorSlide");
|
||||
return !hasErroredLayouts;
|
||||
})
|
||||
.map(groupName => {
|
||||
const settings = getGroupSetting(groupName);
|
||||
const customMeta = summaryMap[groupName];
|
||||
const isCustom = groupName.toLowerCase().startsWith("custom-");
|
||||
return {
|
||||
id: groupName,
|
||||
name: isCustom && customMeta?.name ? customMeta.name : groupName,
|
||||
description: (isCustom && customMeta?.description) ? customMeta.description : (settings?.description || `${groupName} presentation templates`),
|
||||
ordered: settings?.ordered || false,
|
||||
default: settings?.default || false,
|
||||
useEffect(() => {
|
||||
// Fetch custom templates summary to get last_updated_at and template meta for sorting and display
|
||||
fetch("/api/v1/ppt/template-management/summary")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const map: Record<
|
||||
string,
|
||||
{ lastUpdatedAt?: number; name?: string; description?: string }
|
||||
> = {};
|
||||
if (data && Array.isArray(data.presentations)) {
|
||||
for (const p of data.presentations) {
|
||||
const slug = `custom-${p.presentation_id}`;
|
||||
map[slug] = {
|
||||
lastUpdatedAt: p.last_updated_at
|
||||
? new Date(p.last_updated_at).getTime()
|
||||
: 0,
|
||||
name: p.template?.name,
|
||||
description: p.template?.description,
|
||||
};
|
||||
});
|
||||
|
||||
// Sort groups to put default first, then by name
|
||||
return Groups.sort((a, b) => {
|
||||
if (a.default && !b.default) return -1;
|
||||
if (!a.default && b.default) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}, [getAllGroups, getLayoutsByGroup, getGroupSetting, getFullDataByGroup, summaryMap]);
|
||||
|
||||
const inBuiltGroups = React.useMemo(
|
||||
() => layoutGroups.filter(g => !g.id.toLowerCase().startsWith("custom-")),
|
||||
[layoutGroups]
|
||||
);
|
||||
const customGroups = React.useMemo(() => {
|
||||
const unsorted = layoutGroups.filter(g => g.id.toLowerCase().startsWith("custom-"));
|
||||
// Sort by last_updated_at desc using summaryMap keyed by slug id
|
||||
return unsorted.sort((a, b) => (summaryMap[b.id]?.lastUpdatedAt || 0) - (summaryMap[a.id]?.lastUpdatedAt || 0));
|
||||
}, [layoutGroups, summaryMap]);
|
||||
|
||||
// Auto-select first group when groups are loaded
|
||||
useEffect(() => {
|
||||
if (layoutGroups.length > 0 && !selectedLayoutGroup) {
|
||||
const defaultGroup = layoutGroups.find(g => g.default) || layoutGroups[0];
|
||||
const slides = getLayoutsByGroup(defaultGroup.id);
|
||||
|
||||
onSelectLayoutGroup({
|
||||
...defaultGroup,
|
||||
slides: slides,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [layoutGroups, selectedLayoutGroup, onSelectLayoutGroup]);
|
||||
useEffect(() => {
|
||||
setSummaryMap(map);
|
||||
})
|
||||
.catch(() => setSummaryMap({}));
|
||||
}, []);
|
||||
|
||||
const layoutGroups: LayoutGroup[] = React.useMemo(() => {
|
||||
const groups = getAllGroups();
|
||||
|
||||
console.log("All groups: ", groups);
|
||||
|
||||
if (groups.length === 0) return [];
|
||||
|
||||
const Groups: LayoutGroup[] = groups
|
||||
.filter((groupName) => {
|
||||
// Filter out groups that contain any errored layouts (from custom templates compile/parse errors)
|
||||
const fullData = getFullDataByGroup(groupName);
|
||||
const hasErroredLayouts = fullData.some(
|
||||
(fd) =>
|
||||
(fd as any)?.component?.displayName === "CustomTemplateErrorSlide"
|
||||
);
|
||||
return !hasErroredLayouts;
|
||||
})
|
||||
.map((groupName) => {
|
||||
const settings = getGroupSetting(groupName);
|
||||
const customMeta = summaryMap[groupName];
|
||||
const isCustom = groupName.toLowerCase().startsWith("custom-");
|
||||
return {
|
||||
id: groupName,
|
||||
name: isCustom && customMeta?.name ? customMeta.name : groupName,
|
||||
description:
|
||||
isCustom && customMeta?.description
|
||||
? customMeta.description
|
||||
: settings?.description || `${groupName} presentation templates`,
|
||||
ordered: settings?.ordered || false,
|
||||
default: settings?.default || false,
|
||||
};
|
||||
});
|
||||
|
||||
// Sort groups to put default first, then by name
|
||||
return Groups.sort((a, b) => {
|
||||
if (a.default && !b.default) return -1;
|
||||
if (!a.default && b.default) return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
}, [
|
||||
getAllGroups,
|
||||
getLayoutsByGroup,
|
||||
getGroupSetting,
|
||||
getFullDataByGroup,
|
||||
summaryMap,
|
||||
]);
|
||||
|
||||
const inBuiltGroups = React.useMemo(
|
||||
() => layoutGroups.filter((g) => !g.id.toLowerCase().startsWith("custom-")),
|
||||
[layoutGroups]
|
||||
);
|
||||
const customGroups = React.useMemo(() => {
|
||||
const unsorted = layoutGroups.filter((g) =>
|
||||
g.id.toLowerCase().startsWith("custom-")
|
||||
);
|
||||
// Sort by last_updated_at desc using summaryMap keyed by slug id
|
||||
return unsorted.sort(
|
||||
(a, b) =>
|
||||
(summaryMap[b.id]?.lastUpdatedAt || 0) -
|
||||
(summaryMap[a.id]?.lastUpdatedAt || 0)
|
||||
);
|
||||
}, [layoutGroups, summaryMap]);
|
||||
|
||||
// Auto-select first group when groups are loaded
|
||||
useEffect(() => {
|
||||
if (layoutGroups.length > 0 && !selectedLayoutGroup) {
|
||||
const defaultGroup =
|
||||
layoutGroups.find((g) => g.default) || layoutGroups[0];
|
||||
const slides = getLayoutsByGroup(defaultGroup.id);
|
||||
|
||||
onSelectLayoutGroup({
|
||||
...defaultGroup,
|
||||
slides: slides,
|
||||
});
|
||||
}
|
||||
}, [layoutGroups, selectedLayoutGroup, onSelectLayoutGroup]);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
const existingScript = document.querySelector(
|
||||
'script[src*="tailwindcss.com"]'
|
||||
);
|
||||
if (!existingScript) {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://cdn.tailwindcss.com";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
}, []);
|
||||
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="p-4 rounded-lg border border-gray-200 bg-gray-50 animate-pulse">
|
||||
<div className="h-4 bg-gray-200 rounded mb-2"></div>
|
||||
<div className="h-3 bg-gray-200 rounded mb-3"></div>
|
||||
<div className="grid grid-cols-3 gap-2 mb-3">
|
||||
{[1, 2, 3].map((j) => (
|
||||
<div key={j} className="aspect-video bg-gray-200 rounded"></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (layoutGroups.length === 0) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="text-center py-8">
|
||||
<h5 className="text-lg font-medium mb-2 text-gray-700">
|
||||
No Templates Available
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
No presentation templates could be loaded. Please try refreshing the page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLayoutGroupSelection = (group: LayoutGroup) => {
|
||||
const slides = getLayoutsByGroup(group.id);
|
||||
onSelectLayoutGroup({
|
||||
...group,
|
||||
slides: slides,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8 mb-4">
|
||||
{/* In Built Templates */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">In Built Templates</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{inBuiltGroups.map((group) => (
|
||||
<GroupLayouts
|
||||
key={group.id}
|
||||
group={group}
|
||||
onSelectLayoutGroup={handleLayoutGroupSelection}
|
||||
selectedLayoutGroup={selectedLayoutGroup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom AI Templates */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-lg font-semibold text-gray-900">Custom AI Templates</h3>
|
||||
</div>
|
||||
{customGroups.length === 0 ? (
|
||||
<div className="text-sm text-gray-600 py-2">
|
||||
No custom templates. Create one from "Create Template" menu.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{customGroups.map((group) => (
|
||||
<GroupLayouts
|
||||
key={group.id}
|
||||
group={group}
|
||||
onSelectLayoutGroup={handleLayoutGroupSelection}
|
||||
selectedLayoutGroup={selectedLayoutGroup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
const existingScript = document.querySelector(
|
||||
'script[src*="tailwindcss.com"]'
|
||||
);
|
||||
if (!existingScript) {
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://cdn.tailwindcss.com";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="p-4 rounded-lg border border-gray-200 bg-gray-50 animate-pulse"
|
||||
>
|
||||
<div className="h-4 bg-gray-200 rounded mb-2"></div>
|
||||
<div className="h-3 bg-gray-200 rounded mb-3"></div>
|
||||
<div className="grid grid-cols-3 gap-2 mb-3">
|
||||
{[1, 2, 3].map((j) => (
|
||||
<div
|
||||
key={j}
|
||||
className="aspect-video bg-gray-200 rounded"
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (layoutGroups.length === 0) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="text-center py-8">
|
||||
<h5 className="text-lg font-medium mb-2 text-gray-700">
|
||||
No Templates Available
|
||||
</h5>
|
||||
<p className="text-gray-600 text-sm">
|
||||
No presentation templates could be loaded. Please try refreshing the
|
||||
page.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLayoutGroupSelection = (group: LayoutGroup) => {
|
||||
const slides = getLayoutsByGroup(group.id);
|
||||
onSelectLayoutGroup({
|
||||
...group,
|
||||
slides: slides,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8 mb-4">
|
||||
{/* In Built Templates */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">
|
||||
In Built Templates
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{inBuiltGroups.map((group) => (
|
||||
<GroupLayouts
|
||||
key={group.id}
|
||||
group={group}
|
||||
onSelectLayoutGroup={handleLayoutGroupSelection}
|
||||
selectedLayoutGroup={selectedLayoutGroup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom AI Templates */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Custom AI Templates
|
||||
</h3>
|
||||
</div>
|
||||
{customGroups.length === 0 ? (
|
||||
<div className="text-sm text-gray-600 py-2">
|
||||
No custom templates. Create one from "Create Template" menu.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{customGroups.map((group) => (
|
||||
<GroupLayouts
|
||||
key={group.id}
|
||||
group={group}
|
||||
onSelectLayoutGroup={handleLayoutGroupSelection}
|
||||
selectedLayoutGroup={selectedLayoutGroup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutSelection;
|
||||
export default LayoutSelection;
|
||||
|
|
|
|||
|
|
@ -1,31 +1,45 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
import { sanitizeFilename } from '@/app/(presentation-generator)/utils/others';
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
import { sanitizeFilename } from "@/app/(presentation-generator)/utils/others";
|
||||
import { NextResponse, NextRequest } from "next/server";
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const { id, title } = await req.json();
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: "Missing Presentation ID" }, { status: 400 });
|
||||
return NextResponse.json(
|
||||
{ error: "Missing Presentation ID" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||
headless: true,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-web-security',
|
||||
]
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-gpu",
|
||||
"--disable-web-security",
|
||||
"--disable-background-timer-throttling",
|
||||
"--disable-backgrounding-occluded-windows",
|
||||
"--disable-renderer-backgrounding",
|
||||
"--disable-features=TranslateUI",
|
||||
"--disable-ipc-flooding-protection",
|
||||
],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1280, height: 720 });
|
||||
page.setDefaultNavigationTimeout(300000);
|
||||
page.setDefaultTimeout(300000);
|
||||
|
||||
await page.goto(`http://localhost/pdf-maker?id=${id}`, { waitUntil: 'networkidle0', timeout: 300000 });
|
||||
await page.goto(`http://localhost/pdf-maker?id=${id}`, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
await page.waitForFunction('() => document.readyState === "complete"')
|
||||
await page.waitForFunction('() => document.readyState === "complete"');
|
||||
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
|
|
@ -52,13 +66,11 @@ export async function POST(req: NextRequest) {
|
|||
{ timeout: 300000 }
|
||||
);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} catch (error) {
|
||||
console.log("Warning: Some content may not have loaded completely:", error);
|
||||
}
|
||||
|
||||
|
||||
const pdfBuffer = await page.pdf({
|
||||
width: "1280px",
|
||||
height: "720px",
|
||||
|
|
@ -68,13 +80,17 @@ export async function POST(req: NextRequest) {
|
|||
|
||||
browser.close();
|
||||
|
||||
const sanitizedTitle = sanitizeFilename(title ?? 'presentation');
|
||||
const destinationPath = path.join(process.env.APP_DATA_DIRECTORY!, 'exports', `${sanitizedTitle}.pdf`);
|
||||
const sanitizedTitle = sanitizeFilename(title ?? "presentation");
|
||||
const destinationPath = path.join(
|
||||
process.env.APP_DATA_DIRECTORY!,
|
||||
"exports",
|
||||
`${sanitizedTitle}.pdf`
|
||||
);
|
||||
await fs.promises.mkdir(path.dirname(destinationPath), { recursive: true });
|
||||
await fs.promises.writeFile(destinationPath, pdfBuffer);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
path: destinationPath
|
||||
path: destinationPath,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import puppeteer from "puppeteer";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const groupName = searchParams.get("group");
|
||||
console.log("API called with group:", groupName);
|
||||
|
||||
if (!groupName) {
|
||||
console.warn("No group name provided in query params");
|
||||
return NextResponse.json({ error: "Missing group name" }, { status: 400 });
|
||||
}
|
||||
|
||||
const schemaPageUrl = `http://localhost/schema?group=${encodeURIComponent(groupName)}`;
|
||||
console.log("Fetching client page:", schemaPageUrl);
|
||||
|
||||
let browser;
|
||||
try {
|
||||
browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-web-security"],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1280, height: 720 });
|
||||
page.setDefaultNavigationTimeout(300000);
|
||||
page.setDefaultTimeout(300000);
|
||||
await page.goto(schemaPageUrl, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
await page.waitForSelector("[data-layouts]", { timeout: 300000 });
|
||||
|
||||
// Extract both data-layouts and data-group-settings attributes
|
||||
const { dataLayouts, dataGroupSettings } = await page.$eval(
|
||||
"[data-layouts]",
|
||||
(el) => ({
|
||||
dataLayouts: el.getAttribute("data-layouts"),
|
||||
dataGroupSettings: el.getAttribute("data-group-settings"),
|
||||
}),
|
||||
);
|
||||
|
||||
let slides, groupSettings;
|
||||
try {
|
||||
slides = JSON.parse(dataLayouts || "[]");
|
||||
} catch (e) {
|
||||
console.error("Failed to parse data-layouts JSON:", e);
|
||||
slides = [];
|
||||
}
|
||||
try {
|
||||
groupSettings = JSON.parse(dataGroupSettings || "null");
|
||||
} catch (e) {
|
||||
console.error("Failed to parse data-group-settings JSON:", e);
|
||||
groupSettings = null;
|
||||
}
|
||||
|
||||
// Compose the response to match PresentationLayoutModel
|
||||
const response = {
|
||||
name: groupName,
|
||||
ordered: groupSettings?.ordered ?? false,
|
||||
slides: slides.map((slide: any) => ({
|
||||
id: slide.id,
|
||||
name: slide.name,
|
||||
description: slide.description,
|
||||
json_schema: slide.json_schema,
|
||||
})),
|
||||
};
|
||||
|
||||
return NextResponse.json(response);
|
||||
} catch (err) {
|
||||
console.error("Error fetching or parsing client page:", err);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch or parse client page" },
|
||||
{ status: 500 },
|
||||
);
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { GroupSetting } from '@/app/(presentation-generator)/template-preview/types'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get the path to the presentation-templates directory
|
||||
const layoutsDirectory = path.join(process.cwd(), 'presentation-templates')
|
||||
|
||||
// Read all directories in the presentation-templates directory
|
||||
const items = await fs.readdir(layoutsDirectory, { withFileTypes: true })
|
||||
|
||||
// Filter for directories (layout groups) and exclude files
|
||||
const groupDirectories = items
|
||||
.filter(item => item.isDirectory())
|
||||
.map(dir => dir.name)
|
||||
|
||||
const allLayouts: { groupName: string; files: string[]; settings: GroupSetting | null }[] = []
|
||||
|
||||
// Scan each group directory for layout files and settings
|
||||
for (const groupName of groupDirectories) {
|
||||
try {
|
||||
const groupPath = path.join(layoutsDirectory, groupName)
|
||||
const groupFiles = await fs.readdir(groupPath)
|
||||
|
||||
// Filter for .tsx files and exclude any non-layout files
|
||||
const layoutFiles = groupFiles.filter(file =>
|
||||
file.endsWith('.tsx') &&
|
||||
!file.startsWith('.') &&
|
||||
!file.includes('.test.') &&
|
||||
!file.includes('.spec.') &&
|
||||
file !== 'settings.json'
|
||||
)
|
||||
|
||||
// Read settings.json if it exists
|
||||
let settings: GroupSetting | null = null
|
||||
const settingsPath = path.join(groupPath, 'settings.json')
|
||||
try {
|
||||
const settingsContent = await fs.readFile(settingsPath, 'utf-8')
|
||||
settings = JSON.parse(settingsContent) as GroupSetting
|
||||
} catch (settingsError) {
|
||||
|
||||
console.warn(`No settings.json found for group ${groupName} or invalid JSON`)
|
||||
// Provide default settings if settings.json is missing or invalid
|
||||
settings = {
|
||||
description: `${groupName} presentation layouts`,
|
||||
ordered: false,
|
||||
default: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (layoutFiles.length > 0) {
|
||||
allLayouts.push({
|
||||
groupName: groupName,
|
||||
files: layoutFiles,
|
||||
settings: settings
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading group directory ${groupName}:`, error)
|
||||
// Continue with other groups even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NextResponse.json(allLayouts)
|
||||
} catch (error) {
|
||||
console.error('Error reading presentation-templates directory:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to read presentation-templates directory' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -9,16 +9,36 @@ export async function GET(request: Request) {
|
|||
return NextResponse.json({ error: "Missing group name" }, { status: 400 });
|
||||
}
|
||||
|
||||
const schemaPageUrl = `http://localhost/schema?group=${encodeURIComponent(groupName)}`;
|
||||
const schemaPageUrl = `http://localhost/schema?group=${encodeURIComponent(
|
||||
groupName
|
||||
)}`;
|
||||
|
||||
let browser;
|
||||
try {
|
||||
browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox", "--disable-web-security"] });
|
||||
browser = await puppeteer.launch({
|
||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||
headless: true,
|
||||
args: [
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-gpu",
|
||||
"--disable-web-security",
|
||||
"--disable-background-timer-throttling",
|
||||
"--disable-backgrounding-occluded-windows",
|
||||
"--disable-renderer-backgrounding",
|
||||
"--disable-features=TranslateUI",
|
||||
"--disable-ipc-flooding-protection",
|
||||
],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1280, height: 720 });
|
||||
page.setDefaultNavigationTimeout(300000);
|
||||
page.setDefaultTimeout(300000);
|
||||
await page.goto(schemaPageUrl, { waitUntil: "networkidle0", timeout: 300000 });
|
||||
await page.goto(schemaPageUrl, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 300000,
|
||||
});
|
||||
|
||||
await page.waitForSelector("[data-layouts]", { timeout: 300000 });
|
||||
|
||||
|
|
@ -55,8 +75,11 @@ export async function GET(request: Request) {
|
|||
|
||||
return NextResponse.json(response);
|
||||
} catch (err) {
|
||||
return NextResponse.json({ error: "Failed to fetch or parse client page" }, { status: 500 });
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch or parse client page" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue