From bcf8dabbed5fcca88b8f10be5c46364630752811 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Fri, 28 Nov 2025 00:27:54 +0545 Subject: [PATCH] feat: adds option to disable image generation for slides --- Dockerfile.dev | 2 +- README.md | 100 ++--- servers/fastapi/api/middlewares.py | 1 - servers/fastapi/models/user_config.py | 1 + .../services/image_generation_service.py | 10 +- servers/fastapi/utils/get_env.py | 4 + servers/fastapi/utils/image_provider.py | 6 + servers/fastapi/utils/model_availability.py | 9 +- servers/fastapi/utils/set_env.py | 6 +- servers/fastapi/utils/user_config.py | 11 +- servers/nextjs/app/api/user-config/route.ts | 20 +- servers/nextjs/components/LLMSelection.tsx | 349 ++++++++++-------- servers/nextjs/types/llm_config.ts | 1 + servers/nextjs/utils/providerUtils.ts | 1 + servers/nextjs/utils/storeHelpers.ts | 7 +- 15 files changed, 327 insertions(+), 201 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 7f168f4a..89c2b6a8 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -24,7 +24,7 @@ ENV TEMP_DIRECTORY=/tmp/presenton ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium # Install ollama -# RUN curl -fsSL http://ollama.com/install.sh | sh +RUN curl -fsSL http://ollama.com/install.sh | sh # Install dependencies for FastAPI RUN pip install aiohttp aiomysql aiosqlite asyncpg fastapi[standard] \ diff --git a/README.md b/README.md index 78019024..b10665e3 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,13 @@ # Open-Source AI Presentation Generator and API (Gamma, Beautiful AI, Decktopus Alternative) - **Presenton** is an open-source application for generating presentations with AI — all running locally on your device. Stay in control of your data and privacy while using models like OpenAI and Gemini, or use your own hosted models through Ollama. -__✨ Now, generate presentations with your existing PPTX file! Just upload your presentation file to create template design and then use that template to generate on brand and on design presentation on any topic.__ +**✨ Now, generate presentations with your existing PPTX file! Just upload your presentation file to create template design and then use that template to generate on brand and on design presentation on any topic.** ![Demo](readme_assets/demo.gif) - -> [!NOTE] -> **Enterprise Inquiries:** +> [!NOTE] > **Enterprise Inquiries:** > For enterprise use, custom deployments, or partnership opportunities, contact us at **[suraj@presenton.ai](mailto:suraj@presenton.ai)**. > [!IMPORTANT] @@ -32,28 +29,28 @@ __✨ Now, generate presentations with your existing PPTX file! Just upload your > [!TIP] > For detailed setup guides, API documentation, and advanced configuration options, visit our **[Official Documentation](https://docs.presenton.ai)** - ## ✨ More Freedom with AI Presentations Presenton gives you complete control over your AI presentation workflow. Choose your models, customize your experience, and keep your data private. -* ✅ **Custom Templates & Themes** — Create unlimited presentation designs with HTML and Tailwind CSS -* ✅ **AI Template Generation** — Create presentation templates from existing Powerpoint documents. -* ✅ **Flexible Generation** — Build presentations from prompts or uploaded documents -* ✅ **Export Ready** — Save as PowerPoint (PPTX) and PDF with professional formatting -* ✅ **Built-In MCP Server** — Generate presentations over Model Context Protocol -* ✅ **Bring Your Own Key** — Use your own API keys for OpenAI, Google Gemini, Anthropic Claude, or any compatible provider. Only pay for what you use, no hidden fees or subscriptions. -* ✅ **Ollama Integration** — Run open-source models locally with full privacy -* ✅ **OpenAI API Compatible** — Connect to any OpenAI-compatible endpoint with your own models -* ✅ **Multi-Provider Support** — Mix and match text and image generation providers -* ✅ **Versatile Image Generation** — Choose from DALL-E 3, Gemini Flash, Pexels, or Pixabay -* ✅ **Rich Media Support** — Icons, charts, and custom graphics for professional presentations -* ✅ **Runs Locally** — All processing happens on your device, no cloud dependencies -* ✅ **API Deployment** — Host as your own API service for your team -* ✅ **Fully Open-Source** — Apache 2.0 licensed, inspect, modify, and contribute -* ✅ **Docker Ready** — One-command deployment with GPU support for local models +- ✅ **Custom Templates & Themes** — Create unlimited presentation designs with HTML and Tailwind CSS +- ✅ **AI Template Generation** — Create presentation templates from existing Powerpoint documents. +- ✅ **Flexible Generation** — Build presentations from prompts or uploaded documents +- ✅ **Export Ready** — Save as PowerPoint (PPTX) and PDF with professional formatting +- ✅ **Built-In MCP Server** — Generate presentations over Model Context Protocol +- ✅ **Bring Your Own Key** — Use your own API keys for OpenAI, Google Gemini, Anthropic Claude, or any compatible provider. Only pay for what you use, no hidden fees or subscriptions. +- ✅ **Ollama Integration** — Run open-source models locally with full privacy +- ✅ **OpenAI API Compatible** — Connect to any OpenAI-compatible endpoint with your own models +- ✅ **Multi-Provider Support** — Mix and match text and image generation providers +- ✅ **Versatile Image Generation** — Choose from DALL-E 3, Gemini Flash, Pexels, or Pixabay +- ✅ **Rich Media Support** — Icons, charts, and custom graphics for professional presentations +- ✅ **Runs Locally** — All processing happens on your device, no cloud dependencies +- ✅ **API Deployment** — Host as your own API service for your team +- ✅ **Fully Open-Source** — Apache 2.0 licensed, inspect, modify, and contribute +- ✅ **Docker Ready** — One-command deployment with GPU support for local models ## Presenton Cloud + Presenton Logo @@ -64,16 +61,19 @@ Presenton gives you complete control over your AI presentation workflow. Choose #### 1. Start Presenton ##### Linux/MacOS (Bash/Zsh Shell): + ```bash docker run -it --name presenton -p 5000:80 -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` ##### Windows (PowerShell): + ```bash docker run -it --name presenton -p 5000:80 -v "${PWD}\app_data:/app_data" ghcr.io/presenton/presenton:latest ``` #### 2. Open Presenton + Open http://localhost:5000 on browser of your choice to use Presenton. > **Note: You can replace 5000 with any other port number of your choice to run Presenton on a different port number.** @@ -101,7 +101,9 @@ You may want to directly provide your API KEYS as environment variables and keep You can also set the following environment variables to customize the image generation provider and API keys: +- **DISABLE_IMAGE_GENERATION**: If **true**, Image Generation will be disabled for slides. - **IMAGE_PROVIDER=[pexels/pixabay/gemini_flash/dall-e-3]**: Select the image provider of your choice. + - Required if **DISABLE_IMAGE_GENERATION** is not set to **true**. - Defaults to **dall-e-3** for OpenAI models, **gemini_flash** for Google models if not set. - **PEXELS_API_KEY=[Your Pexels API Key]**: Required if using **pexels** as the image provider. - **PIXABAY_API_KEY=[Your Pixabay API Key]**: Required if using **pixabay** as the image provider. @@ -109,32 +111,37 @@ You can also set the following environment variables to customize the image gene - **OPENAI_API_KEY=[Your OpenAI API Key]**: Required if using **dall-e-3** as the image provider. You can disable anonymous telemetry using the following environment variable: -- **DISABLE_ANONYMOUS_TELEMETRY=[true/false]**: Set this to **true** to disable anonymous telemetry. +- **DISABLE_ANONYMOUS_TELEMETRY=[true/false]**: Set this to **true** to disable anonymous telemetry. > **Note:** You can freely choose both the LLM (text generation) and the image provider. Supported image providers: **pexels**, **pixabay**, **gemini_flash** (Google), and **dall-e-3** (OpenAI). ### Using OpenAI + ```bash docker run -it --name presenton -p 5000:80 -e LLM="openai" -e OPENAI_API_KEY="******" -e IMAGE_PROVIDER="dall-e-3" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` ### Using Google + ```bash docker run -it --name presenton -p 5000:80 -e LLM="google" -e GOOGLE_API_KEY="******" -e IMAGE_PROVIDER="gemini_flash" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` ### Using Ollama + ```bash docker run -it --name presenton -p 5000:80 -e LLM="ollama" -e OLLAMA_MODEL="llama3.2:3b" -e IMAGE_PROVIDER="pexels" -e PEXELS_API_KEY="*******" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` ### Using Anthropic + ```bash docker run -it --name presenton -p 5000:80 -e LLM="anthropic" -e ANTHROPIC_API_KEY="******" -e IMAGE_PROVIDER="pexels" -e PEXELS_API_KEY="******" -e CAN_CHANGE_KEYS="false" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` ### Using OpenAI Compatible API + ```bash docker run -it -p 5000:80 -e CAN_CHANGE_KEYS="false" -e LLM="custom" -e CUSTOM_LLM_URL="http://*****" -e CUSTOM_LLM_API_KEY="*****" -e CUSTOM_MODEL="llama3.2:3b" -e IMAGE_PROVIDER="pexels" -e PEXELS_API_KEY="********" -v "./app_data:/app_data" ghcr.io/presenton/presenton:latest ``` @@ -163,29 +170,29 @@ Content-Type: `application/json` #### Request Body -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| content | string | Yes | The content for generating the presentation | -| slides_markdown | string[] \| null | No | The markdown for the slides | -| instructions | string \| null | No | The instruction for generating the presentation | -| tone | string | No | The tone to use for the text (default: "default"). Available options: "default", "casual", "professional", "funny", "educational", "sales_pitch" | -| verbosity | string | No | How verbose the text should be (default: "standard"). Available options: "concise", "standard", "text-heavy" | -| web_search | boolean | No | Whether to enable web search (default: false) | -| n_slides | integer | No | Number of slides to generate (default: 8) | -| language | string | No | Language for the presentation (default: "English") | -| template | string | No | Template to use for the presentation (default: "general") | -| include_table_of_contents | boolean | No | Whether to include a table of contents (default: false) | -| include_title_slide | boolean | No | Whether to include a title slide (default: true) | -| files | string[] \| null | No | Files to use for the presentation. Use /api/v1/ppt/files/upload to upload files | -| export_as | string | No | Export format (default: "pptx"). Available options: "pptx", "pdf" | +| Parameter | Type | Required | Description | +| ------------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| content | string | Yes | The content for generating the presentation | +| slides_markdown | string[] \| null | No | The markdown for the slides | +| instructions | string \| null | No | The instruction for generating the presentation | +| tone | string | No | The tone to use for the text (default: "default"). Available options: "default", "casual", "professional", "funny", "educational", "sales_pitch" | +| verbosity | string | No | How verbose the text should be (default: "standard"). Available options: "concise", "standard", "text-heavy" | +| web_search | boolean | No | Whether to enable web search (default: false) | +| n_slides | integer | No | Number of slides to generate (default: 8) | +| language | string | No | Language for the presentation (default: "English") | +| template | string | No | Template to use for the presentation (default: "general") | +| include_table_of_contents | boolean | No | Whether to include a table of contents (default: false) | +| include_title_slide | boolean | No | Whether to include a title slide (default: true) | +| files | string[] \| null | No | Files to use for the presentation. Use /api/v1/ppt/files/upload to upload files | +| export_as | string | No | Export format (default: "pptx"). Available options: "pptx", "pdf" | #### Response ```json { - "presentation_id": "string", - "path": "string", - "edit_path": "string" + "presentation_id": "string", + "path": "string", + "edit_path": "string" } ``` @@ -218,42 +225,51 @@ curl -X POST http://localhost:5000/api/v1/ppt/presentation/generate \ For detailed info checkout [API documentation](https://docs.presenton.ai/using-presenton-api). ### API Tutorials + - [Generate Presentations via API in 5 minutes](https://docs.presenton.ai/tutorial/generate-presentation-over-api) - [Create Presentations from CSV using AI](https://docs.presenton.ai/tutorial/generate-presentation-from-csv) - [Create Data Reports Using AI](https://docs.presenton.ai/tutorial/create-data-reports-using-ai) ## Roadmap + - [x] Support for custom HTML templates by developers - [x] Support for accessing custom templates over API - [x] Implement MCP server - [ ] Ability for users to change system prompt -- [X] Support external SQL database - +- [x] Support external SQL database ## UI Features ### 1. Add prompt, select number of slides and language + ![Demo](readme_assets/images/prompting.png) ### 2. Select theme + ![Demo](readme_assets/images/select-theme.png) ### 3. Review and edit outline + ![Demo](readme_assets/images/outline.png) ### 4. Select theme + ![Demo](readme_assets/images/select-theme.png) ### 5. Present on app + ![Demo](readme_assets/images/present.png) ### 6. Change theme + ![Demo](readme_assets/images/change-theme.png) ### 7. Export presentation as PDF and PPTX + ![Demo](readme_assets/images/export-presentation.png) ## Community + [Discord](https://discord.gg/9ZsKKxudNE) ## License diff --git a/servers/fastapi/api/middlewares.py b/servers/fastapi/api/middlewares.py index 6c4d83d9..f5c0c3f0 100644 --- a/servers/fastapi/api/middlewares.py +++ b/servers/fastapi/api/middlewares.py @@ -1,6 +1,5 @@ from fastapi import Request from starlette.middleware.base import BaseHTTPMiddleware -from starlette.responses import Response from utils.get_env import get_can_change_keys_env from utils.user_config import update_env_with_user_config diff --git a/servers/fastapi/models/user_config.py b/servers/fastapi/models/user_config.py index c040d22c..99ca7e5e 100644 --- a/servers/fastapi/models/user_config.py +++ b/servers/fastapi/models/user_config.py @@ -27,6 +27,7 @@ class UserConfig(BaseModel): CUSTOM_MODEL: Optional[str] = None # Image Provider + DISABLE_IMAGE_GENERATION: Optional[bool] = None IMAGE_PROVIDER: Optional[str] = None PEXELS_API_KEY: Optional[str] = None PIXABAY_API_KEY: Optional[str] = None diff --git a/servers/fastapi/services/image_generation_service.py b/servers/fastapi/services/image_generation_service.py index 2e9e0f01..a94b8b08 100644 --- a/servers/fastapi/services/image_generation_service.py +++ b/servers/fastapi/services/image_generation_service.py @@ -10,6 +10,7 @@ from utils.download_helpers import download_file from utils.get_env import get_pexels_api_key_env from utils.get_env import get_pixabay_api_key_env from utils.image_provider import ( + is_image_generation_disabled, is_pixels_selected, is_pixabay_selected, is_gemini_flash_selected, @@ -19,12 +20,15 @@ import uuid class ImageGenerationService: - def __init__(self, output_directory: str): self.output_directory = output_directory + self.is_image_generation_disabled = is_image_generation_disabled() self.image_gen_func = self.get_image_gen_func() def get_image_gen_func(self): + if self.is_image_generation_disabled: + return None + if is_pixabay_selected(): return self.get_image_from_pixabay elif is_pixels_selected(): @@ -46,6 +50,10 @@ class ImageGenerationService: otherwise it uses the full image prompt with theme. - Output Directory is used for saving the generated image not the stock provider. """ + if self.is_image_generation_disabled: + print("Image generation is disabled. Using placeholder image.") + return "/static/images/placeholder.jpg" + if not self.image_gen_func: print("No image generation function found. Using placeholder image.") return "/static/images/placeholder.jpg" diff --git a/servers/fastapi/utils/get_env.py b/servers/fastapi/utils/get_env.py index fa80b2a2..0d98e8c5 100644 --- a/servers/fastapi/utils/get_env.py +++ b/servers/fastapi/utils/get_env.py @@ -73,6 +73,10 @@ def get_pexels_api_key_env(): return os.getenv("PEXELS_API_KEY") +def get_disable_image_generation_env(): + return os.getenv("DISABLE_IMAGE_GENERATION") + + def get_image_provider_env(): return os.getenv("IMAGE_PROVIDER") diff --git a/servers/fastapi/utils/image_provider.py b/servers/fastapi/utils/image_provider.py index 84e0677f..42a87519 100644 --- a/servers/fastapi/utils/image_provider.py +++ b/servers/fastapi/utils/image_provider.py @@ -1,11 +1,17 @@ from enums.image_provider import ImageProvider from utils.get_env import ( + get_disable_image_generation_env, get_google_api_key_env, get_image_provider_env, get_openai_api_key_env, get_pexels_api_key_env, get_pixabay_api_key_env, ) +from utils.parsers import parse_bool_or_none + + +def is_image_generation_disabled() -> bool: + return parse_bool_or_none(get_disable_image_generation_env()) or False def is_pixels_selected() -> bool: diff --git a/servers/fastapi/utils/model_availability.py b/servers/fastapi/utils/model_availability.py index 4f942ac7..a238c6c0 100644 --- a/servers/fastapi/utils/model_availability.py +++ b/servers/fastapi/utils/model_availability.py @@ -28,7 +28,10 @@ from utils.llm_provider import ( is_ollama_selected, ) from utils.ollama import pull_ollama_model -from utils.image_provider import get_selected_image_provider +from utils.image_provider import ( + get_selected_image_provider, + is_image_generation_disabled, +) async def check_llm_and_image_provider_api_or_model_availability(): @@ -104,6 +107,10 @@ async def check_llm_and_image_provider_api_or_model_availability(): if custom_model not in available_models: raise Exception(f"Model {custom_model} is not available") + # Skip image provider and API key checks if image generation is disabled + if is_image_generation_disabled(): + return + # Check for Image Provider and API keys selected_image_provider = get_selected_image_provider() if not selected_image_provider: diff --git a/servers/fastapi/utils/set_env.py b/servers/fastapi/utils/set_env.py index ea3758f3..bd94e4c9 100644 --- a/servers/fastapi/utils/set_env.py +++ b/servers/fastapi/utils/set_env.py @@ -69,6 +69,10 @@ def set_pixabay_api_key_env(value): os.environ["PIXABAY_API_KEY"] = value +def set_disable_image_generation_env(value): + os.environ["DISABLE_IMAGE_GENERATION"] = value + + def set_tool_calls_env(value): os.environ["TOOL_CALLS"] = value @@ -82,4 +86,4 @@ def set_extended_reasoning_env(value): def set_web_grounding_env(value): - os.environ["WEB_GROUNDING"] = value \ No newline at end of file + os.environ["WEB_GROUNDING"] = value diff --git a/servers/fastapi/utils/user_config.py b/servers/fastapi/utils/user_config.py index 9321f1d3..ca60d44a 100644 --- a/servers/fastapi/utils/user_config.py +++ b/servers/fastapi/utils/user_config.py @@ -8,6 +8,7 @@ from utils.get_env import ( get_custom_llm_api_key_env, get_custom_llm_url_env, get_custom_model_env, + get_disable_image_generation_env, get_disable_thinking_env, get_google_api_key_env, get_google_model_env, @@ -31,6 +32,7 @@ from utils.set_env import ( set_custom_llm_api_key_env, set_custom_llm_url_env, set_custom_model_env, + set_disable_image_generation_env, set_disable_thinking_env, set_extended_reasoning_env, set_google_api_key_env, @@ -56,7 +58,7 @@ def get_user_config(): if os.path.exists(user_config_path): with open(user_config_path, "r") as f: existing_config = UserConfig(**json.load(f)) - except Exception as e: + except Exception: print("Error while loading user config") pass @@ -76,6 +78,11 @@ def get_user_config(): or get_custom_llm_api_key_env(), CUSTOM_MODEL=existing_config.CUSTOM_MODEL or get_custom_model_env(), IMAGE_PROVIDER=existing_config.IMAGE_PROVIDER or get_image_provider_env(), + DISABLE_IMAGE_GENERATION=( + existing_config.DISABLE_IMAGE_GENERATION + if existing_config.DISABLE_IMAGE_GENERATION is not None + else (parse_bool_or_none(get_disable_image_generation_env()) or False) + ), PIXABAY_API_KEY=existing_config.PIXABAY_API_KEY or get_pixabay_api_key_env(), PEXELS_API_KEY=existing_config.PEXELS_API_KEY or get_pexels_api_key_env(), TOOL_CALLS=( @@ -127,6 +134,8 @@ def update_env_with_user_config(): set_custom_llm_api_key_env(user_config.CUSTOM_LLM_API_KEY) if user_config.CUSTOM_MODEL: set_custom_model_env(user_config.CUSTOM_MODEL) + if user_config.DISABLE_IMAGE_GENERATION is not None: + set_disable_image_generation_env(str(user_config.DISABLE_IMAGE_GENERATION)) if user_config.IMAGE_PROVIDER: set_image_provider_env(user_config.IMAGE_PROVIDER) if user_config.PIXABAY_API_KEY: diff --git a/servers/nextjs/app/api/user-config/route.ts b/servers/nextjs/app/api/user-config/route.ts index 1939175e..828a8ff0 100644 --- a/servers/nextjs/app/api/user-config/route.ts +++ b/servers/nextjs/app/api/user-config/route.ts @@ -46,20 +46,32 @@ export async function POST(request: Request) { OPENAI_MODEL: userConfig.OPENAI_MODEL || existingConfig.OPENAI_MODEL, GOOGLE_API_KEY: userConfig.GOOGLE_API_KEY || existingConfig.GOOGLE_API_KEY, GOOGLE_MODEL: userConfig.GOOGLE_MODEL || existingConfig.GOOGLE_MODEL, - ANTHROPIC_API_KEY: userConfig.ANTHROPIC_API_KEY || existingConfig.ANTHROPIC_API_KEY, - ANTHROPIC_MODEL: userConfig.ANTHROPIC_MODEL || existingConfig.ANTHROPIC_MODEL, + ANTHROPIC_API_KEY: + userConfig.ANTHROPIC_API_KEY || existingConfig.ANTHROPIC_API_KEY, + ANTHROPIC_MODEL: + userConfig.ANTHROPIC_MODEL || existingConfig.ANTHROPIC_MODEL, OLLAMA_URL: userConfig.OLLAMA_URL || existingConfig.OLLAMA_URL, OLLAMA_MODEL: userConfig.OLLAMA_MODEL || existingConfig.OLLAMA_MODEL, CUSTOM_LLM_URL: userConfig.CUSTOM_LLM_URL || existingConfig.CUSTOM_LLM_URL, CUSTOM_LLM_API_KEY: userConfig.CUSTOM_LLM_API_KEY || existingConfig.CUSTOM_LLM_API_KEY, CUSTOM_MODEL: userConfig.CUSTOM_MODEL || existingConfig.CUSTOM_MODEL, + DISABLE_IMAGE_GENERATION: + userConfig.DISABLE_IMAGE_GENERATION === undefined + ? existingConfig.DISABLE_IMAGE_GENERATION + : userConfig.DISABLE_IMAGE_GENERATION, PIXABAY_API_KEY: userConfig.PIXABAY_API_KEY || existingConfig.PIXABAY_API_KEY, IMAGE_PROVIDER: userConfig.IMAGE_PROVIDER || existingConfig.IMAGE_PROVIDER, PEXELS_API_KEY: userConfig.PEXELS_API_KEY || existingConfig.PEXELS_API_KEY, - TOOL_CALLS: userConfig.TOOL_CALLS === undefined ? existingConfig.TOOL_CALLS : userConfig.TOOL_CALLS, - DISABLE_THINKING: userConfig.DISABLE_THINKING === undefined ? existingConfig.DISABLE_THINKING : userConfig.DISABLE_THINKING, + TOOL_CALLS: + userConfig.TOOL_CALLS === undefined + ? existingConfig.TOOL_CALLS + : userConfig.TOOL_CALLS, + DISABLE_THINKING: + userConfig.DISABLE_THINKING === undefined + ? existingConfig.DISABLE_THINKING + : userConfig.DISABLE_THINKING, EXTENDED_REASONING: userConfig.EXTENDED_REASONING === undefined ? existingConfig.EXTENDED_REASONING diff --git a/servers/nextjs/components/LLMSelection.tsx b/servers/nextjs/components/LLMSelection.tsx index ed308226..9bd13ccf 100644 --- a/servers/nextjs/components/LLMSelection.tsx +++ b/servers/nextjs/components/LLMSelection.tsx @@ -3,6 +3,7 @@ import { useState, useEffect } from "react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"; import { Check, ChevronsUpDown, Info } from "lucide-react"; import { Button } from "./ui/button"; +import { Switch } from "./ui/switch"; import { Command, CommandEmpty, @@ -49,6 +50,7 @@ export default function LLMProviderSelection({ }: LLMProviderSelectionProps) { const [llmConfig, setLlmConfig] = useState(initialLLMConfig); const [openImageProviderSelect, setOpenImageProviderSelect] = useState(false); + const isImageGenerationDisabled = llmConfig.DISABLE_IMAGE_GENERATION ?? false; useEffect(() => { onConfigChange(llmConfig); @@ -62,12 +64,21 @@ export default function LLMProviderSelection({ (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) || (llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_MODEL); - const needsApiKey = - ((llmConfig.IMAGE_PROVIDER === "dall-e-3" || llmConfig.LLM === "openai") && !llmConfig.OPENAI_API_KEY) || - ((llmConfig.IMAGE_PROVIDER === "gemini_flash" || llmConfig.LLM === "google") && !llmConfig.GOOGLE_API_KEY) || - (llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_API_KEY) || - (llmConfig.IMAGE_PROVIDER === "pexels" && !llmConfig.PEXELS_API_KEY) || - (llmConfig.IMAGE_PROVIDER === "pixabay" && !llmConfig.PIXABAY_API_KEY); + const needsProviderApiKey = + (llmConfig.LLM === "openai" && !llmConfig.OPENAI_API_KEY) || + (llmConfig.LLM === "google" && !llmConfig.GOOGLE_API_KEY) || + (llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_API_KEY); + + const needsImageProviderApiKey = + !llmConfig.DISABLE_IMAGE_GENERATION && + ( + (llmConfig.IMAGE_PROVIDER === "dall-e-3" && !llmConfig.OPENAI_API_KEY) || + (llmConfig.IMAGE_PROVIDER === "gemini_flash" && !llmConfig.GOOGLE_API_KEY) || + (llmConfig.IMAGE_PROVIDER === "pexels" && !llmConfig.PEXELS_API_KEY) || + (llmConfig.IMAGE_PROVIDER === "pixabay" && !llmConfig.PIXABAY_API_KEY) + ); + + const needsApiKey = needsProviderApiKey || needsImageProviderApiKey; const needsOllamaUrl = (llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_URL); @@ -101,20 +112,29 @@ export default function LLMProviderSelection({ }, [llmConfig.USE_CUSTOM_URL]); useEffect(() => { - let updates: any = {}; - if (!llmConfig.IMAGE_PROVIDER) { - if (llmConfig.LLM === "openai") { - updates.IMAGE_PROVIDER = "dall-e-3"; - } else if (llmConfig.LLM === "google") { - updates.IMAGE_PROVIDER = "gemini_flash"; - } else { - updates.IMAGE_PROVIDER = "pexels"; + setLlmConfig((prevConfig) => { + const updates: Partial = {}; + + if (!prevConfig.DISABLE_IMAGE_GENERATION && !prevConfig.IMAGE_PROVIDER) { + if (prevConfig.LLM === "openai") { + updates.IMAGE_PROVIDER = "dall-e-3"; + } else if (prevConfig.LLM === "google") { + updates.IMAGE_PROVIDER = "gemini_flash"; + } else { + updates.IMAGE_PROVIDER = "pexels"; + } } - } - if (!llmConfig.OLLAMA_URL) { - updates.OLLAMA_URL = "http://localhost:11434"; - } - setLlmConfig({ ...llmConfig, ...updates }); + + if (!prevConfig.OLLAMA_URL) { + updates.OLLAMA_URL = "http://localhost:11434"; + } + + if (Object.keys(updates).length === 0) { + return prevConfig; + } + + return { ...prevConfig, ...updates }; + }); }, []); return ( @@ -198,134 +218,160 @@ export default function LLMProviderSelection({ - {/* Image Provider Selection */} + {/* Image Generation Toggle */}
- -
- - - - - - - - - No provider found. - - {Object.values(IMAGE_PROVIDERS).map( - (provider, index) => ( - { - input_field_changed(value, "image_provider"); - setOpenImageProviderSelect(false); - }} - > - -
-
-
- - {provider.label} - -
- - {provider.description} - -
-
-
- ) - )} -
-
-
-
-
+
+ + { + input_field_changed(checked, "disable_image_generation"); + if (checked) { + setOpenImageProviderSelect(false); + } + }} + />
+

+ + When enabled, slides will not include automatically generated images. +

- {/* Dynamic API Key Input for Image Provider */} - {llmConfig.IMAGE_PROVIDER && - IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] && - (() => { - const provider = IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER]; - - // Show info message when using same API key as main provider - if (provider.value === "dall-e-3" && llmConfig.LLM === "openai") { - return <>; - } - - if (provider.value === "gemini_flash" && llmConfig.LLM === "google") { - return <>; - } - - // Show API key input for other providers - return ( -
- -
- { - if (provider.apiKeyField === "PEXELS_API_KEY") { - input_field_changed(e.target.value, "pexels_api_key"); - } else if (provider.apiKeyField === "PIXABAY_API_KEY") { - input_field_changed(e.target.value, "pixabay_api_key"); - } - }} - /> -
-

- - API key for {provider.label} image generation -

+ {!isImageGenerationDisabled && ( + <> + {/* Image Provider Selection */} +
+ +
+ + + + + + + + + No provider found. + + {Object.values(IMAGE_PROVIDERS).map( + (provider, index) => ( + { + input_field_changed(value, "image_provider"); + setOpenImageProviderSelect(false); + }} + > + +
+
+
+ + {provider.label} + +
+ + {provider.description} + +
+
+
+ ) + )} +
+
+
+
+
- ); - })()} +
+ + {/* Dynamic API Key Input for Image Provider */} + {llmConfig.IMAGE_PROVIDER && + IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] && + (() => { + const provider = IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER]; + + // Show info message when using same API key as main provider + if (provider.value === "dall-e-3" && llmConfig.LLM === "openai") { + return <>; + } + + if (provider.value === "gemini_flash" && llmConfig.LLM === "google") { + return <>; + } + + // Show API key input for other providers + return ( +
+ +
+ { + if (provider.apiKeyField === "PEXELS_API_KEY") { + input_field_changed(e.target.value, "pexels_api_key"); + } else if (provider.apiKeyField === "PIXABAY_API_KEY") { + input_field_changed(e.target.value, "pixabay_api_key"); + } + }} + /> +
+

+ + API key for {provider.label} image generation +

+
+ ); + })()} + + )} {/* Model Information */}
@@ -348,12 +394,19 @@ export default function LLMProviderSelection({ : llmConfig.LLM === "openai" ? llmConfig.OPENAI_MODEL ?? "xxxxx" : "xxxxx"}{" "} - for text generation and{" "} - {llmConfig.IMAGE_PROVIDER && - IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] - ? IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER].label - : "xxxxx"}{" "} - for images + for text generation{" "} + {isImageGenerationDisabled ? ( + "and image generation is disabled." + ) : ( + <> + and{" "} + {llmConfig.IMAGE_PROVIDER && + IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] + ? IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER].label + : "xxxxx"}{" "} + for images + + )}

diff --git a/servers/nextjs/types/llm_config.ts b/servers/nextjs/types/llm_config.ts index 5b73b215..7250e74f 100644 --- a/servers/nextjs/types/llm_config.ts +++ b/servers/nextjs/types/llm_config.ts @@ -23,6 +23,7 @@ export interface LLMConfig { CUSTOM_MODEL?: string; // Image providers + DISABLE_IMAGE_GENERATION?: boolean; IMAGE_PROVIDER?: string; PEXELS_API_KEY?: string; PIXABAY_API_KEY?: string; diff --git a/servers/nextjs/utils/providerUtils.ts b/servers/nextjs/utils/providerUtils.ts index 5e4dea0e..15a44180 100644 --- a/servers/nextjs/utils/providerUtils.ts +++ b/servers/nextjs/utils/providerUtils.ts @@ -42,6 +42,7 @@ export const updateLLMConfig = ( pexels_api_key: "PEXELS_API_KEY", pixabay_api_key: "PIXABAY_API_KEY", image_provider: "IMAGE_PROVIDER", + disable_image_generation: "DISABLE_IMAGE_GENERATION", use_custom_url: "USE_CUSTOM_URL", tool_calls: "TOOL_CALLS", disable_thinking: "DISABLE_THINKING", diff --git a/servers/nextjs/utils/storeHelpers.ts b/servers/nextjs/utils/storeHelpers.ts index ebc9b3be..71c7abbf 100644 --- a/servers/nextjs/utils/storeHelpers.ts +++ b/servers/nextjs/utils/storeHelpers.ts @@ -16,7 +16,7 @@ export const handleSaveLLMConfig = async (llmConfig: LLMConfig) => { export const hasValidLLMConfig = (llmConfig: LLMConfig) => { if (!llmConfig.LLM) return false; - if (!llmConfig.IMAGE_PROVIDER) return false; + if (!llmConfig.DISABLE_IMAGE_GENERATION && !llmConfig.IMAGE_PROVIDER) return false; const isOpenAIConfigValid = llmConfig.OPENAI_MODEL !== "" && @@ -58,7 +58,12 @@ export const hasValidLLMConfig = (llmConfig: LLMConfig) => { llmConfig.CUSTOM_MODEL !== null && llmConfig.CUSTOM_MODEL !== undefined; + const shouldValidateImages = !llmConfig.DISABLE_IMAGE_GENERATION; + const isImageConfigValid = () => { + if (!shouldValidateImages) { + return true; + } switch (llmConfig.IMAGE_PROVIDER) { case "pexels": return llmConfig.PEXELS_API_KEY && llmConfig.PEXELS_API_KEY !== "";