feat: implement PDF/PPTX export functionality with dedicated routes and components
- Added new middleware to handle session authentication for presentation retrieval. - Introduced new layout and page components for PDF export, ensuring no loading state is shown during headless rendering. - Enhanced ConfigurationInitializer to manage loading state based on route. - Updated DashboardApi to include credentials in requests for presentation data. - Refactored hasValidLLMConfig function for cleaner validation logic.
This commit is contained in:
parent
11904c6cb0
commit
2e7e31db8c
7 changed files with 39 additions and 19 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import re
|
||||
|
||||
from fastapi import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
|
@ -23,6 +25,12 @@ class SessionAuthMiddleware(BaseHTTPMiddleware):
|
|||
_EXEMPT_PREFIXES = (
|
||||
"/api/v1/auth/",
|
||||
)
|
||||
# PPTX/PDF export loads /pdf-maker in a headless browser with no session cookie; it
|
||||
# only needs a single-deck read by id. (UUID is not a secret; this matches prior behavior
|
||||
# when auth middleware did not protect these routes during export.)
|
||||
_PRESENTATION_GET_BY_ID = re.compile(
|
||||
r"^/api/v1/ppt/presentation/[0-9a-fA-F-]{36}/?$"
|
||||
)
|
||||
_PROTECTED_NON_API_PATHS = {
|
||||
"/docs",
|
||||
"/openapi.json",
|
||||
|
|
@ -32,6 +40,11 @@ class SessionAuthMiddleware(BaseHTTPMiddleware):
|
|||
def _is_exempt(self, path: str) -> bool:
|
||||
return any(path.startswith(prefix) for prefix in self._EXEMPT_PREFIXES)
|
||||
|
||||
def _is_presentation_get_by_id(self, request: Request, path: str) -> bool:
|
||||
if request.method != "GET":
|
||||
return False
|
||||
return bool(self._PRESENTATION_GET_BY_ID.match(path))
|
||||
|
||||
def _requires_auth(self, path: str) -> bool:
|
||||
if path.startswith("/api/"):
|
||||
return True
|
||||
|
|
@ -46,6 +59,7 @@ class SessionAuthMiddleware(BaseHTTPMiddleware):
|
|||
request.method == "OPTIONS"
|
||||
or not self._requires_auth(path)
|
||||
or self._is_exempt(path)
|
||||
or self._is_presentation_get_by_id(request, path)
|
||||
):
|
||||
return await call_next(request)
|
||||
|
||||
|
|
|
|||
10
servers/nextjs/app/(export)/layout.tsx
Normal file
10
servers/nextjs/app/(export)/layout.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
|
||||
/**
|
||||
* Do not wrap with ConfigurationInitializer: it always mounts with isLoading=true
|
||||
* and only clears after useEffect, so headless PDF/PPTX export captures the
|
||||
* "Initializing Application" screen. Export only needs the slide renderer; no LLM check.
|
||||
*/
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import "../utils/prism-languages";
|
||||
import "@/app/(presentation-generator)/utils/prism-languages";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -10,13 +10,12 @@ import { usePathname } from "next/navigation";
|
|||
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { setPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { DashboardApi } from "../services/api/dashboard";
|
||||
import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard";
|
||||
import { setupImageUrlConverter } from "@/utils/image-url-converter";
|
||||
|
||||
|
||||
import { V1ContentRender } from "../components/V1ContentRender";
|
||||
import { useFontLoader } from "../hooks/useFontLoad";
|
||||
import { Theme } from "../services/api/types";
|
||||
import { V1ContentRender } from "@/app/(presentation-generator)/components/V1ContentRender";
|
||||
import { useFontLoader } from "@/app/(presentation-generator)/hooks/useFontLoad";
|
||||
import { Theme } from "@/app/(presentation-generator)/services/api/types";
|
||||
|
||||
|
||||
|
||||
|
|
@ -53,6 +53,7 @@ export class DashboardApi {
|
|||
getApiUrl(`/api/v1/ppt/presentation/${id}`),
|
||||
{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ import { getApiUrl } from '@/utils/api';
|
|||
export function ConfigurationInitializer({ children }: { children: React.ReactNode }) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const route = usePathname();
|
||||
const [isLoading, setIsLoading] = useState(
|
||||
() => !route?.startsWith("/pdf-maker")
|
||||
);
|
||||
const router = useRouter();
|
||||
|
||||
// Fetch user config state
|
||||
useEffect(() => {
|
||||
|
|
@ -31,13 +33,13 @@ export function ConfigurationInitializer({ children }: { children: React.ReactNo
|
|||
}
|
||||
|
||||
const fetchUserConfigState = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
if (route.startsWith('/pdf-maker')) {
|
||||
if (route.startsWith("/pdf-maker")) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
let canChangeKeys = false;
|
||||
try {
|
||||
const res = await fetch('/api/can-change-keys');
|
||||
|
|
@ -64,7 +66,6 @@ export function ConfigurationInitializer({ children }: { children: React.ReactNo
|
|||
|
||||
dispatch(setLLMConfig(llmConfig));
|
||||
const isValid = hasValidLLMConfig(llmConfig);
|
||||
console.log('isValid', isValid);
|
||||
if (route.startsWith('/pdf-maker')) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -128,10 +128,5 @@ export const handleSaveLLMConfig = async (llmConfig: LLMConfig) => {
|
|||
store.dispatch(setLLMConfig(llmConfig));
|
||||
};
|
||||
|
||||
export const hasValidLLMConfig = (llmConfig: LLMConfig) => {
|
||||
console.log('llmConfig', llmConfig);
|
||||
|
||||
const validationError = getLLMConfigValidationError(llmConfig);
|
||||
console.log('validationError', validationError);
|
||||
return validationError === null;
|
||||
}
|
||||
export const hasValidLLMConfig = (llmConfig: LLMConfig) =>
|
||||
getLLMConfigValidationError(llmConfig) === null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue