-
{selectedFile.name}
+
+
{selectedFile.name}
Presentation ( {(selectedFile.size / (1024 * 1024)).toFixed(2)} MB)
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/api-error-handler.ts b/servers/nextjs/app/(presentation-generator)/services/api/api-error-handler.ts
index eae66bf0..b7949572 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/api-error-handler.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/api-error-handler.ts
@@ -1,12 +1,49 @@
// API Error Response Interface
interface ApiErrorResponse {
- detail?: string;
+ detail?: unknown;
message?: string;
error?: string;
}
// API Response Handler Utility
export class ApiResponseHandler {
+ private static normalizeErrorDetail(detail: unknown): string | null {
+ if (!detail) return null;
+
+ if (typeof detail === "string") {
+ return detail;
+ }
+
+ if (Array.isArray(detail)) {
+ const parts = detail
+ .map((item) => {
+ if (typeof item === "string") return item;
+ if (item && typeof item === "object") {
+ const maybeMsg = (item as { msg?: unknown }).msg;
+ const maybeLoc = (item as { loc?: unknown }).loc;
+ const locPath = Array.isArray(maybeLoc)
+ ? maybeLoc
+ .filter((v) => typeof v === "string" || typeof v === "number")
+ .join(".")
+ : "";
+ if (typeof maybeMsg === "string") {
+ return locPath ? `${locPath}: ${maybeMsg}` : maybeMsg;
+ }
+ }
+ return null;
+ })
+ .filter((v): v is string => Boolean(v));
+
+ return parts.length ? parts.join("; ") : JSON.stringify(detail);
+ }
+
+ if (typeof detail === "object") {
+ return JSON.stringify(detail);
+ }
+
+ return String(detail);
+ }
+
static async handleResponse(response: Response, defaultErrorMessage: string): Promise
{
// Handle successful responses
@@ -32,8 +69,9 @@ export class ApiResponseHandler {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
- if (errorData.detail) {
- errorMessage = errorData.detail;
+ const normalizedDetail = this.normalizeErrorDetail(errorData.detail);
+ if (normalizedDetail) {
+ errorMessage = normalizedDetail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
@@ -63,8 +101,9 @@ export class ApiResponseHandler {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
- if (errorData.detail) {
- errorMessage = errorData.detail;
+ const normalizedDetail = this.normalizeErrorDetail(errorData.detail);
+ if (normalizedDetail) {
+ errorMessage = normalizedDetail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/dashboard.ts b/servers/nextjs/app/(presentation-generator)/services/api/dashboard.ts
index dcabc907..8f287afb 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/dashboard.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/dashboard.ts
@@ -1,8 +1,8 @@
-import { getApiUrl } from "@/utils/api";
import {
getHeader,
} from "@/app/(presentation-generator)/services/api/header";
import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler";
+import { getApiUrl } from "@/utils/api";
export interface PresentationResponse {
id: string;
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/images.ts b/servers/nextjs/app/(presentation-generator)/services/api/images.ts
index be56cc97..59adbd52 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/images.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/images.ts
@@ -1,7 +1,13 @@
-import { getApiUrl } from "@/utils/api";
import { getHeaderForFormData } from "./header";
import { ApiResponseHandler } from "./api-error-handler";
import { ImageAssetResponse } from "./types";
+import { getApiUrl } from "@/utils/api";
+
+interface StockSearchOptions {
+ provider?: string;
+ apiKey?: string;
+ strictApiKey?: boolean;
+}
export class ImagesApi {
@@ -43,6 +49,41 @@ export class ImagesApi {
throw error;
}
}
+
+ static async searchStockImages(
+ query: string,
+ limit: number = 12,
+ options: StockSearchOptions = {}
+ ): Promise {
+ try {
+ const params = new URLSearchParams({
+ query,
+ limit: String(limit),
+ });
+ const normalizedProvider = (options.provider || "").trim().toLowerCase();
+ if (normalizedProvider) {
+ params.set("provider", normalizedProvider);
+ }
+ if (options.strictApiKey) {
+ params.set("strict_api_key", "true");
+ }
+
+ const headers: Record = {};
+ const trimmedApiKey = (options.apiKey || "").trim();
+ if (trimmedApiKey) {
+ headers["X-Provider-Api-Key"] = trimmedApiKey;
+ }
+
+ const response = await fetch(getApiUrl(`/api/v1/ppt/images/search?${params.toString()}`), {
+ method: "GET",
+ headers,
+ });
+ return await ApiResponseHandler.handleResponse(response, "Failed to search stock images") as string[];
+ } catch (error:any) {
+ console.log("Stock image search error:", error);
+ throw error;
+ }
+ }
}
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 24349a9e..675c1fcf 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts
@@ -1,11 +1,9 @@
-import { getApiUrl } from "@/utils/api";
import { getHeader, getHeaderForFormData } from "./header";
import { IconSearch, ImageGenerate, ImageSearch, PreviousGeneratedImagesResponse } from "./params";
import { ApiResponseHandler } from "./api-error-handler";
+import { getApiUrl } from "@/utils/api";
export class PresentationGenerationApi {
- private static readonly DECOMPOSE_TIMEOUT_MS = 10 * 60 * 1000;
-
static async uploadDoc(documents: File[]) {
const formData = new FormData();
@@ -31,10 +29,10 @@ export class PresentationGenerationApi {
}
}
- static async decomposeDocuments(documentKeys: string[]) {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), this.DECOMPOSE_TIMEOUT_MS);
-
+ static async decomposeDocuments(
+ documentKeys: string[],
+ language?: string | null
+ ) {
try {
const response = await fetch(
getApiUrl(`/api/v1/ppt/files/decompose`),
@@ -43,21 +41,16 @@ export class PresentationGenerationApi {
headers: getHeader(),
body: JSON.stringify({
file_paths: documentKeys,
+ language: language ?? null,
}),
cache: "no-cache",
- signal: controller.signal,
}
);
return await ApiResponseHandler.handleResponse(response, "Failed to decompose documents");
} catch (error) {
- if (error instanceof DOMException && error.name === "AbortError") {
- throw new Error("File decomposition timed out after 10 minutes");
- }
console.error("Error in Decompose Files", error);
throw error;
- } finally {
- clearTimeout(timeoutId);
}
}
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/template.ts b/servers/nextjs/app/(presentation-generator)/services/api/template.ts
index 4a79a2f5..e7b8b341 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/template.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/template.ts
@@ -1,11 +1,24 @@
import { getApiUrl } from "@/utils/api";
import { ApiResponseHandler } from "./api-error-handler";
+import { getHeader } from "./header";
+
+export interface CloneTemplatePayload {
+ id: string;
+ name?: string;
+ description?: string;
+}
+
+export interface CloneLayoutPayload {
+ template_id: string;
+ layout_id: string;
+ layout_name?: string;
+}
class TemplateService {
static async getCustomTemplateSummaries() {
try {
- const response = await fetch(getApiUrl(`/api/v1/ppt/template-management/summary`));
+ const response = await fetch(getApiUrl(`/api/v1/ppt/template/all`),);
return await ApiResponseHandler.handleResponse(response, "Failed to get custom template summaries");
} catch (error) {
console.error("Failed to get custom template summaries", error);
@@ -15,7 +28,7 @@ class TemplateService {
static async getCustomTemplateDetails(templateId: string) {
try {
- const response = await fetch(getApiUrl(`/api/v1/ppt/template-management/get-templates/${templateId}`));
+ const response = await fetch(getApiUrl(`/api/v1/ppt/template/${templateId}/layouts`),);
return await ApiResponseHandler.handleResponse(response, "Failed to get custom template details");
} catch (error) {
console.error("Failed to get custom template details", error);
@@ -25,13 +38,41 @@ class TemplateService {
static async deleteCustomTemplate(presentationId: string) {
try {
- const response = await fetch(getApiUrl(`/api/v1/ppt/template-management/delete-templates/${presentationId}`), { method: "DELETE" });
+ const response = await fetch(getApiUrl(`/api/v1/ppt/template-management/delete-templates/${presentationId}`), { method: "DELETE", headers: getHeader() });
return await ApiResponseHandler.handleResponseWithResult(response, "Failed to delete custom template");
} catch (error) {
console.error("Failed to delete custom template", error);
throw error;
}
}
+
+ static async cloneCustomTemplate(payload: CloneTemplatePayload) {
+ try {
+ const response = await fetch(getApiUrl(`/api/v1/ppt/template/clone`), {
+ method: "POST",
+ headers: getHeader(),
+ body: JSON.stringify(payload),
+ });
+ return await ApiResponseHandler.handleResponse(response, "Failed to clone template");
+ } catch (error) {
+ console.error("Failed to clone template", error);
+ throw error;
+ }
+ }
+
+ static async cloneTemplateLayout(payload: CloneLayoutPayload) {
+ try {
+ const response = await fetch(getApiUrl(`/api/v1/ppt/template/slide-layout/clone`), {
+ method: "POST",
+ headers: getHeader(),
+ body: JSON.stringify(payload),
+ });
+ return await ApiResponseHandler.handleResponse(response, "Failed to clone layout");
+ } catch (error) {
+ console.error("Failed to clone layout", error);
+ throw error;
+ }
+ }
}
export default TemplateService;
\ No newline at end of file
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/theme.ts b/servers/nextjs/app/(presentation-generator)/services/api/theme.ts
index b7a0184f..6d80d35d 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/theme.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/theme.ts
@@ -1,7 +1,7 @@
-import { getApiUrl } from "@/utils/api"
import { ApiResponseHandler } from "./api-error-handler"
import { getHeader, getHeaderForFormData } from "./header"
import { Theme, ThemeParams } from "./types"
+import { getApiUrl } from "@/utils/api"
@@ -90,8 +90,8 @@ class ThemeApi {
static async uploadFont(font: File) {
try {
const formData = new FormData();
- formData.append("file", font);
- const response = await fetch(getApiUrl(`/api/v1/ppt/fonts/upload`), {
+ formData.append("font_file", font);
+ const response: any = await fetch(getApiUrl(`/api/v1/ppt/fonts/upload`), {
method: "POST",
headers: getHeaderForFormData(),
body: formData,
diff --git a/servers/nextjs/app/(presentation-generator)/services/api/types.ts b/servers/nextjs/app/(presentation-generator)/services/api/types.ts
index f1f45afd..ac5b6108 100644
--- a/servers/nextjs/app/(presentation-generator)/services/api/types.ts
+++ b/servers/nextjs/app/(presentation-generator)/services/api/types.ts
@@ -25,10 +25,10 @@ export interface DeplotResponse {
}
export interface ImageAssetResponse {
- message: string;
- path: string;
- id: string;
- file_url?: string;
+ message: string;
+ path: string;
+ id: string;
+ file_url?: string;
}