diff --git a/README.md b/README.md index b6c6a8a92..ddc890884 100644 --- a/README.md +++ b/README.md @@ -37,19 +37,15 @@ Extracted from production systems at Juspay and battle-tested at enterprise scal ## What's New (Q1 2026) -| Feature | Version | Description | Guide | -| ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -| **Memory** | v9.12.0 | Per-user condensed memory that persists across conversations. LLM-powered condensation with S3, Redis, or SQLite backends. | [Memory Guide](docs/features/memory.md) | -| **Context Window Management** | v9.2.0 | 4-stage compaction pipeline with auto-detection, budget gate at 80% usage, per-provider token estimation | [Context Compaction Guide](docs/features/context-compaction.md) | -| **Tool Execution Control** | v9.3.0 | `prepareStep` and `toolChoice` support for per-step tool enforcement in multi-step agentic loops. API-level control over tool calls. | [API Reference](docs/api/type-aliases/GenerateOptions.md#preparestep) | -| **File Processor System** | v9.1.0 | 17+ file type processors with ProcessorRegistry, security sanitization, SVG text injection | [File Processors Guide](docs/features/file-processors.md) | -| **RAG with generate()/stream()** | v9.2.0 | Pass `rag: { files }` to generate/stream for automatic document chunking, embedding, and AI-powered search. 10 chunking strategies, hybrid search, reranking. | [RAG Guide](docs/features/rag.md) | -| **External TracerProvider Support** | v8.43.0 | Integrate NeuroLink with existing OpenTelemetry instrumentation. Prevents duplicate registration conflicts. | [Observability Guide](docs/features/observability.md) | -| **Server Adapters** | v8.43.0 | Multi-framework HTTP server with Hono, Express, Fastify, Koa support. Full CLI for server management with foreground/background modes. | [Server Adapters Guide](docs/guides/server-adapters/index.md) | -| **Title Generation Events** | v8.38.0 | Emit `conversation:titleGenerated` event when conversation title is generated. Supports custom title prompts via `NEUROLINK_TITLE_PROMPT`. | [Conversation Memory Guide](docs/conversation-memory.md) | -| **Video Generation with Veo** | v8.32.0 | Video generation using Veo 3.1 (`veo-3.1`). Realistic video generation with many parameter options | [Video Generation Guide](docs/features/video-generation.md) | -| **Image Generation with Gemini** | v8.31.0 | Native image generation using Gemini 2.0 Flash Experimental (`imagen-3.0-generate-002`). High-quality image synthesis directly from Google AI. | [Image Generation Guide](docs/image-generation-streaming.md) | -| **HTTP/Streamable HTTP Transport** | v8.29.0 | Connect to remote MCP servers via HTTP with authentication headers, automatic retry with exponential backoff, and configurable rate limiting. | [HTTP Transport Guide](docs/mcp-http-transport.md) | +| Feature | Version | Description | Guide | +| ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| **External TracerProvider Support** | v8.43.0 | Integrate NeuroLink with existing OpenTelemetry instrumentation. Prevents duplicate registration conflicts. | [Observability Guide](docs/features/observability.md) | +| **Server Adapters** | v8.43.0 | Multi-framework HTTP server with Hono, Express, Fastify, Koa support. Full CLI for server management with foreground/background modes. | [Server Adapters Guide](docs/guides/server-adapters/index.md) | +| **Title Generation Events** | v8.38.0 | Emit `conversation:titleGenerated` event when conversation title is generated. Supports custom title prompts via `NEUROLINK_TITLE_PROMPT`. | [Conversation Memory Guide](docs/conversation-memory.md) | +| **Video Generation with Veo** | v8.32.0 | Video generation using Veo 3.1 (`veo-3.1`). Realistic video generation with many parameter options | [Video Generation Guide](docs/features/video-generation.md) | +| **Image Generation with Gemini** | v8.31.0 | Native image generation using Gemini 2.0 Flash Experimental (`imagen-3.0-generate-002`). High-quality image synthesis directly from Google AI. | [Image Generation Guide](docs/image-generation-streaming.md) | +| **RAG with generate()/stream()** | v9.2.0 | Pass `rag: { files }` to generate/stream for automatic document chunking, embedding, and AI-powered search. 10 chunking strategies, hybrid search, reranking. | [RAG Guide](docs/features/rag.md) | +| **HTTP/Streamable HTTP Transport** | v8.29.0 | Connect to remote MCP servers via HTTP with authentication headers, automatic retry with exponential backoff, and configurable rate limiting. | [HTTP Transport Guide](docs/mcp-http-transport.md) | - **Memory** – Per-user condensed memory that persists across all conversations. Automatically retrieves and stores memory on each `generate()`/`stream()` call. Supports S3, Redis, and SQLite storage with LLM-powered condensation. → [Memory Guide](docs/features/memory.md) - **External TracerProvider Support** – Integrate NeuroLink with applications that already have OpenTelemetry instrumentation. Supports auto-detection and manual configuration. → [Observability Guide](docs/features/observability.md) @@ -57,6 +53,7 @@ Extracted from production systems at Juspay and battle-tested at enterprise scal - **Title Generation Events** – Emit real-time events when conversation titles are auto-generated. Listen to `conversation:titleGenerated` for session tracking. → [Conversation Memory Guide](docs/conversation-memory.md#title-generation-events) - **Custom Title Prompts** – Customize conversation title generation with `NEUROLINK_TITLE_PROMPT` environment variable. Use `${userMessage}` placeholder for dynamic prompts. → [Conversation Memory Guide](docs/conversation-memory.md#customizing-the-title-prompt) - **Video Generation** – Transform images into 8-second videos with synchronized audio using Google Veo 3.1 via Vertex AI. Supports 720p/1080p resolutions, portrait/landscape aspect ratios. → [Video Generation Guide](docs/features/video-generation.md) +- **PPT Generation** – Create professional PowerPoint presentations from text prompts with 35 slide types (title, content, charts, timelines, dashboards, composite layouts), 5 themes, and optional AI-generated images. Works with Vertex AI, OpenAI, Anthropic, Google AI, Azure, and Bedrock. → [PPT Generation Guide](docs/features/ppt-generation.md) - **Image Generation** – Generate images from text prompts using Gemini models via Vertex AI or Google AI Studio. Supports streaming mode with automatic file saving. → [Image Generation Guide](docs/image-generation-streaming.md) - **RAG with generate()/stream()** – Just pass `rag: { files: ["./docs/guide.md"] }` to `generate()` or `stream()`. NeuroLink auto-chunks, embeds, and creates a search tool the AI can invoke. 10 chunking strategies, hybrid search, 5 reranker types. → [RAG Guide](docs/features/rag.md) - **HTTP/Streamable HTTP Transport for MCP** – Connect to remote MCP servers via HTTP with authentication headers, retry logic, and rate limiting. → [HTTP Transport Guide](docs/mcp-http-transport.md) diff --git a/docs-site/config/redirects.ts b/docs-site/config/redirects.ts index 087daf92a..0d6962b68 100644 --- a/docs-site/config/redirects.ts +++ b/docs-site/config/redirects.ts @@ -104,6 +104,9 @@ const sectionReorganizationRedirects: PluginOptions["redirects"] = [ { from: "/image-generation", to: "/docs/features/image-generation" }, { from: "/video-generation", to: "/docs/features/video-generation" }, { from: "/video", to: "/docs/features/video-generation" }, + { from: "/ppt-generation", to: "/docs/features/ppt-generation" }, + { from: "/ppt", to: "/docs/features/ppt-generation" }, + { from: "/powerpoint", to: "/docs/features/ppt-generation" }, // Enterprise features redirects { from: "/hitl", to: "/docs/features/hitl" }, diff --git a/docs-site/scripts/sync-docs.ts b/docs-site/scripts/sync-docs.ts index fce10c837..55848e41c 100644 --- a/docs-site/scripts/sync-docs.ts +++ b/docs-site/scripts/sync-docs.ts @@ -967,6 +967,7 @@ const LINK_MAPPINGS: Record = { "auto-evaluation": "/features/auto-evaluation", interactive: "/demos/interactive", "video-generation": "/features/video-generation", + "ppt-generation": "/features/ppt-generation", "conversation-history": "/features/conversation-history", "mcp-tools-showcase": "/features/mcp-tools-showcase", "provider-orchestration": "/features/provider-orchestration", diff --git a/docs-site/sidebars.ts b/docs-site/sidebars.ts index 1ed124224..105d18f6d 100644 --- a/docs-site/sidebars.ts +++ b/docs-site/sidebars.ts @@ -79,6 +79,7 @@ const sidebars: SidebarsConfig = { "features/tts", "features/audio-input", "features/video-generation", + "features/ppt-generation", "features/pdf-support", "features/csv-support", "features/office-documents", diff --git a/docs/cli-guide.md b/docs/cli-guide.md index 007f376d1..adecdf64b 100644 --- a/docs/cli-guide.md +++ b/docs/cli-guide.md @@ -126,7 +126,7 @@ npx @juspay/neurolink gen "Analyze this problem" --provider google-ai --model ge **Video Generation Options (Veo 3.1):** -- `--outputMode ` - Output mode: 'text' (default) or 'video' +- `--outputMode ` - Output mode: 'text' (default), 'video', or 'ppt' - `--image ` - Path to input image file for image-based video generation (required for video mode, e.g., ./input.jpg) - `--videoOutput ` - Path to save generated video file (e.g., ./output.mp4) - `--videoResolution ` - Video resolution: '720p' or '1080p' (default: 720p) @@ -134,6 +134,17 @@ npx @juspay/neurolink gen "Analyze this problem" --provider google-ai --model ge - `--videoAspectRatio ` - Aspect ratio: '9:16' (portrait) or '16:9' (landscape, default: 16:9) - `--videoAudio ` - Include synchronized audio (default: true) +**PPT Generation Options:** + +- `--outputMode ppt` - Set output mode to PPT generation +- `--pptOutput ` or `--po ` - Path to save generated PPTX file (e.g., ./presentation.pptx) +- `--pptTheme ` - Theme: 'modern', 'corporate', 'creative', 'minimal', or 'dark' (default: AI-selected) +- `--pptAudience ` - Audience: 'business', 'students', 'technical', or 'general' (default: AI-selected) +- `--pptTone ` - Tone: 'professional', 'casual', 'educational', or 'persuasive' (default: AI-selected) +- `--pptPages ` or `--pages ` - Number of slides to generate (5-50, default: 10) +- `--pptAspectRatio ` - Aspect ratio: '16:9' (default) or '4:3' +- `--pptNoImages` - Disable AI image generation for slides (AI images are enabled by default) + **Output Example:** ``` diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 20148822e..3c009e515 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -70,7 +70,7 @@ npx @juspay/neurolink gen "Write code" --provider openai | Flag | Type | Default | Description | | ---------------------- | ------- | ------- | ------------------------------------------------------------------------- | -| `--outputMode` | string | `text` | Output mode: 'text' or 'video' | +| `--outputMode` | string | `text` | Output mode: 'text', 'video', or 'ppt' | | `--image` | string | none | Path to an input image to base the generated video on (e.g., ./input.png) | | `--videoOutput`, `-vo` | string | none | Path to save generated video (e.g., ./output.mp4) | | `--videoResolution` | string | `720p` | Video resolution: '720p' or '1080p' | @@ -78,6 +78,17 @@ npx @juspay/neurolink gen "Write code" --provider openai | `--videoAspectRatio` | string | `16:9` | Aspect ratio: '9:16' (portrait) or '16:9' (landscape) | | `--videoAudio` | boolean | `true` | Include synchronized audio | +### PPT Generation (AI Presentations) + +- `--outputMode` (string, default: `text`) — Output mode: `text`, `video`, or `ppt` +- `--pptPages`, `--pages` (number, default: `10`) — Number of slides to generate (5-50) +- `--pptTheme` (string, default: AI-selected) — Theme: `modern`, `corporate`, `creative`, `minimal`, or `dark` +- `--pptAudience` (string, default: AI-selected) — Audience: `business`, `students`, `technical`, or `general` +- `--pptTone` (string, default: AI-selected) — Tone: `professional`, `casual`, `educational`, or `persuasive` +- `--pptNoImages` (boolean, default: `false`) — Disable AI image generation (AI images are enabled by default in CLI) +- `--pptAspectRatio` (string, default: `16:9`) — Aspect ratio: `16:9` or `4:3` +- `--pptOutput`, `--po` (string, default: auto-generated) — Path to save generated presentation + ## Usage Examples ### Basic Text Generation @@ -309,6 +320,57 @@ npx @juspay/neurolink generate "Vertical scroll animation" \ > **Note:** Video generation requires Vertex AI credentials. See [Video Generation Guide](./features/video-generation.md). +## PPT Generation Examples + +Generate AI-powered PowerPoint presentations: + +```bash +# Basic PPT generation +npx @juspay/neurolink generate "Introduction to Machine Learning" \ + --outputMode ppt \ + --pptPages 10 \ + --pptOutput ./ml-presentation.pptx + +# With theme and audience customization +npx @juspay/neurolink generate "Quarterly Sales Report Q4 2025" \ + --provider vertex \ + --model gemini-2.5-pro \ + --outputMode ppt \ + --pptPages 15 \ + --pptTheme corporate \ + --pptAudience business \ + --pptTone professional \ + --pptOutput ./q4-report.pptx + +# Creative presentation with AI-generated images +npx @juspay/neurolink generate "Future of Space Tourism" \ + --outputMode ppt \ + --pptPages 12 \ + --pptTheme creative \ + --pptOutput ./space-tourism.pptx + +# Technical documentation with dark theme +npx @juspay/neurolink generate "Kubernetes Architecture Deep Dive" \ + --provider anthropic \ + --model claude-3-5-sonnet \ + --outputMode ppt \ + --pptPages 20 \ + --pptTheme dark \ + --pptAudience technical \ + --pptTone educational \ + --pptOutput ./k8s-architecture.pptx + +# Disable AI image generation +npx @juspay/neurolink generate "Company Brand Guidelines" \ + --outputMode ppt \ + --pptPages 8 \ + --pptTheme minimal \ + --pptNoImages \ + --pptOutput ./brand-guidelines.pptx +``` + +> **Note:** PPT generation works with multiple AI providers. See [PPT Generation Guide](./features/ppt-generation.md). + ## Environment Variables See the [Environment Variables](./getting-started/environment-variables.md) documentation for complete configuration options. diff --git a/docs/configuration.md b/docs/configuration.md index 01f873f42..a21c4844b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -214,6 +214,27 @@ export GOOGLE_CLOUD_LOCATION="us-central1" See [Video Generation Guide](features/video-generation.md) for complete setup and usage. +### **PPT Generation (PowerPoint Presentations)** + +Generate professional PowerPoint presentations with supported providers (Vertex AI, Google AI, OpenAI, Anthropic, Azure OpenAI, and Bedrock) using compatible text models. + +```bash +# Use your existing provider credentials (any of these): +export GOOGLE_VERTEX_PROJECT="your-project-id" # For Vertex AI +export OPENAI_API_KEY="sk-..." # For OpenAI +export ANTHROPIC_API_KEY="sk-ant-..." # For Anthropic +export GOOGLE_AI_API_KEY="..." # For Google AI Studio +``` + +**Optional: Enable AI Image Generation for Slides** + +```bash +# Gemini model for slide image generation (optional) +export VERTEX_IMAGE_MODEL="gemini-2.0-flash-exp" +``` + +See [PPT Generation Guide](features/ppt-generation.md) for complete setup and usage. + ### **Regional Routing** Set region-specific variables to control latency and compliance. diff --git a/docs/error-handling.md b/docs/error-handling.md index 695804972..ea56cbf9c 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -33,6 +33,18 @@ Video generation via Veo 3.1 on Vertex AI may encounter specific error condition - **VIDEO_QUOTA_EXCEEDED** - Vertex AI quota or rate limit exceeded - **VIDEO_REGION_UNAVAILABLE** - Veo 3.1 not available in specified region +### PPT Generation Errors + +PPT (PowerPoint) generation may encounter specific error conditions: + +- **PPT_PLANNING_FAILED** - AI content planning process failed +- **PPT_INVALID_AI_RESPONSE** - AI returned invalid or malformed slide data +- **PPT_IMAGE_GENERATION_FAILED** - AI image generation failed for visual slides +- **PPT_ASSEMBLY_FAILED** - PPTX file assembly failed +- **PPT_FILE_WRITE_FAILED** - Could not write presentation to disk +- **PPT_INVALID_INPUT** - Invalid input parameters (prompt length, page count, theme, etc.) +- **PPT_TIMEOUT** - Presentation generation exceeded timeout + ## Error Recovery ### Automatic Retry @@ -101,6 +113,68 @@ try { } ``` +### PPT Generation Error Handling + +**Example: Handling PPT generation errors** + +```typescript +import { NeuroLink, PPTError } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +try { + const result = await neurolink.generate({ + input: { + text: "Quarterly Business Review", + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + pages: 15, + theme: "corporate", + audience: "business", + generateAIImages: true, + }, + }, + timeout: 300, // 5 minutes for PPT generation + }); + + if (result.ppt) { + console.log(`Presentation saved: ${result.ppt.filePath}`); + console.log(`Total slides: ${result.ppt.totalSlides}`); + } +} catch (error) { + // Use your logger for production: logger.error('PPT generation failed', { code: error.code, error }) + if (error.code === "PPT_PLANNING_FAILED") { + console.error( + "Content planning failed. Try a more specific prompt or different model.", + ); + } else if (error.code === "PPT_INVALID_AI_RESPONSE") { + console.error( + "AI returned invalid response. Retry with a different model.", + ); + } else if (error.code === "PPT_IMAGE_GENERATION_FAILED") { + console.error("Image generation failed. Try with generateAIImages: false."); + } else if (error.code === "PPT_ASSEMBLY_FAILED") { + console.error("PPTX assembly failed. Check file system permissions."); + } else if (error.code === "PPT_FILE_WRITE_FAILED") { + console.error( + "Could not write file. Check disk space and output path permissions.", + ); + } else if (error.code === "PPT_INVALID_INPUT") { + console.error( + "Invalid input. Check pages (5-50), theme, and prompt length.", + ); + } else if (error.code === "PPT_TIMEOUT") { + console.error("Generation timed out. Reduce pages or disable AI images."); + } else { + console.error("PPT generation failed:", error.message); + } +} +``` + **CLI Error Handling:** ```bash diff --git a/docs/features/audio-input.md b/docs/features/audio-input.md index 145395bfa..343683054 100644 --- a/docs/features/audio-input.md +++ b/docs/features/audio-input.md @@ -696,6 +696,7 @@ const neurolink = new NeuroLink({ - [TTS Integration Guide](tts.md) - Complete Text-to-Speech documentation - [Video Generation](video-generation.md) - AI-powered video with audio +- [PPT Generation](ppt-generation.md) - AI-powered PowerPoint presentations **Multimodal Capabilities:** diff --git a/docs/features/index.md b/docs/features/index.md index 8005b436b..6819867c8 100644 --- a/docs/features/index.md +++ b/docs/features/index.md @@ -14,6 +14,7 @@ Comprehensive guides for all NeuroLink features organized by category. Each guid | Feature | Description | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| :material-presentation: **[PPT Generation](ppt-generation.md)** | Generate professional PowerPoint presentations from text prompts with 35 slide types, 5 themes, and optional AI images. | | :material-video: **Video Generation** :material-clock-outline:{ .coming-soon title="Coming Soon" } | Generate videos from text prompts using RunwayML (ML5, ML6 Turbo models). _Coming Soon_ | | :material-image-plus: **[Image Generation with Gemini](../image-generation-streaming.md)** | Native image generation using Gemini 2.0 Flash Experimental with imagen-3.0-generate-002 model. | | :material-web: **[HTTP/Streamable HTTP Transport for MCP](../mcp-http-transport.md)** | Connect to remote MCP servers via HTTP with authentication, rate limiting, retry support, and session management. | @@ -26,6 +27,7 @@ Comprehensive guides for all NeuroLink features organized by category. Each guid **Q1 2026 Highlights:** +- **PPT Generation**: Create AI-powered PowerPoint presentations with 35 slide types (title, content, charts, timelines, dashboards, composite layouts), 5 built-in themes, optional AI-generated images, and multi-provider support (Vertex, OpenAI, Anthropic, Google AI, Azure, Bedrock) - **Video Generation** _(Coming Soon)_: Create AI-generated videos with RunwayML integration supporting ML5 and ML6 Turbo models, customizable duration (5-10s), and watermark control - **Gemini Image Generation**: Native support for Google's imagen-3.0-generate-002 model through Gemini 2.0 Flash Experimental for high-quality image synthesis - **Remote MCP Servers**: HTTP/Streamable HTTP transport enables connecting to cloud-hosted MCP servers with Bearer token authentication, configurable rate limits, automatic retry with exponential backoff, and session management via `Mcp-Session-Id` header diff --git a/docs/features/multimodal-chat.md b/docs/features/multimodal-chat.md index bdaf4308a..6c785ec22 100644 --- a/docs/features/multimodal-chat.md +++ b/docs/features/multimodal-chat.md @@ -30,6 +30,35 @@ if (result.video) { **See:** [Video Generation Guide](video-generation.md) for complete documentation. +## PPT Generation {#ppt-generation} + +NeuroLink supports **AI-powered PowerPoint generation** from text prompts. Create professional presentations with 35 slide types, 5 themes, and optional AI-generated images. + +```typescript +const result = await neurolink.generate({ + input: { + text: "Quarterly Business Report: Revenue growth, key metrics, and 2026 outlook", + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + pages: 15, + theme: "corporate", + audience: "business", + generateAIImages: true, + }, + }, +}); + +if (result.ppt) { + console.log(`Presentation saved: ${result.ppt.filePath}`); +} +``` + +**See:** [PPT Generation Guide](ppt-generation.md) for complete documentation. + ## Images {#images} NeuroLink provides comprehensive image support across all vision-capable providers. Images can be provided as local file paths, HTTPS URLs, or Buffer objects, and are automatically converted to the provider's required encoding format. @@ -375,6 +404,11 @@ Set appropriate `maxTokens` for PDF analysis (recommended: 2000-8000 tokens). ## Related Features +**Content Generation:** + +- [PPT Generation](ppt-generation.md) – AI-powered PowerPoint presentations with 35 slide types +- [Video Generation](video-generation.md) – Generate videos from images with Veo 3.1 + **Document Processing:** - [Office Documents](office-documents.md) – DOCX, PPTX, XLSX processing for Bedrock, Vertex, Anthropic diff --git a/docs/features/multimodal.md b/docs/features/multimodal.md index fb362a813..07b575bd7 100644 --- a/docs/features/multimodal.md +++ b/docs/features/multimodal.md @@ -562,6 +562,7 @@ const result = await neurolink.generate({ - [Audio Input](audio-input.md) - Transcription, analysis, and real-time voice - [TTS Integration](tts.md) - Text-to-Speech audio output - [Video Generation](video-generation.md) - AI-powered video creation +- [PPT Generation](ppt-generation.md) - AI-powered PowerPoint presentations **Documentation:** diff --git a/docs/features/ppt-generation.md b/docs/features/ppt-generation.md new file mode 100644 index 000000000..4ffd31438 --- /dev/null +++ b/docs/features/ppt-generation.md @@ -0,0 +1,961 @@ +--- +title: PPT Generation - AI-Powered Presentations +description: Generate professional PowerPoint presentations from text prompts using AI-powered content planning and slide generation +keywords: ppt generation, powerpoint, presentation, slides, ai presentation, neurolink, gemini, claude, gpt-4 +--- + +# PPT Generation - AI-Powered Presentations + +NeuroLink enables AI-powered PowerPoint presentation generation from text prompts. Transform ideas into professional, visually-appealing presentations with intelligent content planning, multiple slide types, and optional AI-generated images. + +## Overview + +PPT generation in NeuroLink uses a multi-stage pipeline powered by any supported AI provider: + +1. **Accepts** a text prompt describing the presentation topic via `input.text` +2. **Plans** structured content using AI-powered content planning +3. **Generates** individual slides with appropriate types, layouts, and content +4. **Creates** optional AI-generated images for visual slides +5. **Assembles** a complete `.pptx` file using pptxgenjs +6. **Returns** a `PPTGenerationResult` containing file path and metadata + +```mermaid +graph LR + A[Text Prompt] --> B[Content Planning AI] + B --> C[Slide Schemas] + C --> D[Slide Generator] + D --> E[Image Generation] + E --> F[PPTX Assembly] + F --> G[PPTGenerationResult] + G --> H[Save to File] +``` + +## What You Get + +- **Professional presentations** – Generate complete PowerPoint files with 5-50 slides +- **35 slide types** – From title and content slides to charts, timelines, dashboards, and composite layouts +- **5 built-in themes** – Modern, Corporate, Creative, Minimal, and Dark +- **AI image generation** – Optional background and decorative images using Gemini +- **User-provided images** – Use your own images instead of AI generation +- **SDK integration** – Use `neurolink.generate()` with `output.mode: "ppt"` +- **CLI support** – Generate presentations directly from command line +- **Multi-provider support** – Works with Vertex AI, OpenAI, Anthropic, Google AI, Azure, and Bedrock + +## Supported Providers & Models + +### Provider Compatibility + +| Provider | Recommended Models | Slide Types | Image Gen | Quality | Notes | +| ----------- | -------------------------------- | ----------- | ----------- | ------- | ------------------------ | +| `vertex` | gemini-2.5-pro, gemini-3 | All 35 | ✅ Native | Highest | Full feature support | +| `google-ai` | gemini-2.5-pro, gemini-3 | All 35 | ✅ Native | Highest | Full feature support | +| `openai` | gpt-4o, gpt-4-turbo | All 35 | ⚠️ External | High | Uses OpenAI for planning | +| `anthropic` | claude-4.5-sonnet, claude-3-opus | All 35 | ⚠️ External | Highest | Advanced reasoning | +| `azure` | gpt-4o, gpt-4-turbo | All 35 | ⚠️ External | High | Enterprise deployment | +| `bedrock` | claude-3-sonnet, titan | All 35 | ⚠️ External | High | AWS integration | + +### Model Tiers + +| Tier | Models | Slide Types | Notes | +| ---------- | ------------------------------------------------------------------------ | ----------- | ----------------------------- | +| `advanced` | claude-4.5-opus, claude-4.5-sonnet, gpt-4o, gemini-2.5-pro, gemini-3-pro | All 35 | Full prompt with all features | +| `basic` | gemini-flash, claude-instant, gpt-3.5 | 10 core | Simplified prompt for speed | + +## Prerequisites + +1. **AI provider credentials** configured for your chosen provider +2. **For AI images**: Vertex AI or Google AI credentials with Gemini access +3. **Sufficient storage**: Output files range from 100KB to 10MB+ depending on images + +## Quick Start + +### SDK Usage + +```typescript +import { NeuroLink } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +// Basic PPT generation +const result = await neurolink.generate({ + input: { + text: "Create a presentation about AI in Healthcare", + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + pages: 10, + theme: "modern", + audience: "business", + tone: "professional", + }, + }, +}); + +// Access result +if (result.ppt) { + console.log(`Presentation saved: ${result.ppt.filePath}`); + console.log(`Total slides: ${result.ppt.totalSlides}`); +} +``` + +#### With Full Options + +```typescript +import { NeuroLink } from "@juspay/neurolink"; +import { readFileSync } from "fs"; + +const neurolink = new NeuroLink(); + +const result = await neurolink.generate({ + input: { + text: "Quarterly Sales Report Q4 2025 - Key achievements, challenges, and outlook", + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + pages: 15, + theme: "corporate", + audience: "business", + tone: "professional", + generateAIImages: true, + aspectRatio: "16:9", + outputPath: "./presentations/q4-report.pptx", + logoPath: readFileSync("./assets/company-logo.png"), + }, + }, +}); + +console.log("Presentation metadata:", { + filePath: result.ppt?.filePath, + slides: result.ppt?.totalSlides, + theme: result.ppt?.metadata?.theme, + fileSize: result.ppt?.metadata?.fileSize, +}); +``` + +#### With User-Provided Images + +```typescript +import { NeuroLink } from "@juspay/neurolink"; +import { readFileSync } from "fs"; + +const neurolink = new NeuroLink(); + +// Use your own images instead of AI generation +const result = await neurolink.generate({ + input: { + text: "Product Launch Presentation for our new smartphone", + images: [ + readFileSync("./product-hero.jpg"), + readFileSync("./product-features.png"), + "./marketing/lifestyle-shot.jpg", // File path also works + ], + }, + provider: "anthropic", + model: "claude-3.5-sonnet", + output: { + mode: "ppt", + ppt: { + pages: 12, + theme: "creative", + generateAIImages: false, // Use provided images only + }, + }, +}); +``` + +### CLI Usage + +```bash +# Basic PPT generation +npx @juspay/neurolink generate "Introduction to Machine Learning" \ + --outputMode ppt \ + --pptPages 10 \ + --pptOutput ./ml-presentation.pptx + +# Full options +npx @juspay/neurolink generate "Company Strategy 2026" \ + --provider vertex \ + --model gemini-2.5-pro \ + --outputMode ppt \ + --pptPages 15 \ + --pptTheme corporate \ + --pptAudience business \ + --pptTone professional \ + --pptAspectRatio 16:9 \ + --pptOutput ./strategy-2026.pptx + +# Disable AI image generation +npx @juspay/neurolink generate "Machine Learning 101" \ + --outputMode ppt \ + --pptTheme minimal \ + --pptTone educational \ + --pptNoImages +``` + +### CLI Arguments + +- `--outputMode` (string, default: `text`) — Output mode: `text`, `video`, or `ppt` +- `--pptPages`, `--pages` (number, default: `10`) — Number of slides (5-50) +- `--pptTheme` (string, default: AI-selected) — Theme: `modern`, `corporate`, `creative`, `minimal`, `dark` +- `--pptAudience` (string, default: AI-selected) — Target audience: `business`, `students`, `technical`, `general` +- `--pptTone` (string, default: AI-selected) — Presentation tone: `professional`, `casual`, `educational`, `persuasive` +- `--pptNoImages` (boolean, default: `false`) — Disable AI images for visual slides (images are enabled by default) +- `--pptAspectRatio` (string, default: `16:9`) — Aspect ratio: `16:9` or `4:3` +- `--pptOutput`, `--po` (string, default: auto-generated) — Output file path + +## Slide Types + +NeuroLink supports **35 distinct slide types** organized by category: + +### Opening/Closing Slides + +| Type | Description | Layout Options | +| ---------------- | -------------------------------- | ---------------------------------- | +| `title` | Opening slide with main title | `title-centered`, `title-bottom` | +| `section-header` | Section divider with large title | `title-centered` | +| `thank-you` | Final slide with contact info | `contact-info`, `title-centered` | +| `closing` | Summary and next steps | `summary-bullets`, `title-content` | + +### Content Slides + +| Type | Description | Layout Options | +| --------------- | ------------------------------ | ------------------------------ | +| `content` | Standard title + bullet points | `title-content`, image layouts | +| `agenda` | Table of contents | `title-content`, `two-column` | +| `bullets` | Enhanced bullet points | `title-content` | +| `numbered-list` | Step-by-step content | `title-content` | + +### Visual Slides + +| Type | Description | Image Required | +| ------------------ | ------------------------- | -------------- | +| `image-focus` | Large centered image | Yes | +| `image-left` | Image left, content right | Yes | +| `image-right` | Content left, image right | Yes | +| `full-bleed-image` | Full background image | Yes | +| `gallery` | Multiple images grid | Yes | + +### Data Slides + +| Type | Description | Data Structure | +| ------------ | ------------------------- | -------------- | +| `table` | Data table with headers | `TableRow[]` | +| `chart-bar` | Bar chart | `ChartSeries` | +| `chart-line` | Line chart for trends | `ChartSeries` | +| `chart-pie` | Pie chart for proportions | `ChartSeries` | +| `chart-area` | Area chart | `ChartSeries` | +| `statistics` | Big numbers display | `Statistic[]` | + +### Layout Slides + +| Type | Description | Columns | +| --------------- | ----------------------- | ------- | +| `two-column` | Two equal columns | 2 | +| `three-column` | Three column layout | 3 | +| `split-content` | Asymmetric 60/40 split | 2 | +| `comparison` | Side-by-side comparison | 2 | + +### Special Slides + +| Type | Description | Key Content | +| -------------- | ----------------------- | ----------------- | +| `quote` | Impactful quote | `quote`, `author` | +| `timeline` | Chronological events | `TimelineItem[]` | +| `process-flow` | Step-by-step process | `ProcessStep[]` | +| `features` | Feature list with icons | `FeatureItem[]` | +| `team` | Team member profiles | `TeamMember[]` | +| `icons` | Icon grid with labels | `icons[]` | +| `conclusion` | Summary with takeaways | `bullets` | + +### Composite/Dashboard Slides + +| Type | Description | Components | +| --------------- | -------------------------- | ------------------------ | +| `dashboard` | Multi-zone flexible grid | charts + stats + bullets | +| `mixed-content` | Left bullets + right chart | bullets + data | +| `stats-grid` | Multiple stat boxes | `Statistic[]` | +| `icon-grid` | Icon boxes in grid | icons | + +## Themes + +### Built-in Themes + +| Theme | Colors | Best For | +| ----------- | ----------------------- | ------------------- | +| `modern` | Blue, Purple, Cyan | Tech, Innovation | +| `corporate` | Dark Blue, Green, Slate | Business, Finance | +| `creative` | Orange, Pink, Yellow | Marketing, Design | +| `minimal` | Black, White, Gray | Clean, Professional | +| `dark` | Cyan, Purple on Dark | Tech, Startups | + +### Theme Structure + +```typescript +interface PresentationTheme { + name: string; + displayName: string; + description: string; + colors: { + primary: string; // Main accent color + secondary: string; // Secondary accent + accent: string; // Highlight color + background: string; // Slide background + text: string; // Body text color + textOnPrimary: string; // Text on primary color + muted: string; // Muted/caption text + }; + fonts: { + heading: string; + body: string; + sizes: { + title: number; + subtitle: number; + heading: number; + body: number; + caption: number; + }; + }; +} +``` + +## Type Definitions + +### PPTOutputOptions + +Options for PPT generation configuration: + +```typescript +type PPTOutputOptions = { + /** Number of slides (5-50) */ + pages: number; + /** Output format (currently only 'pptx') */ + format?: "pptx"; + /** Presentation theme */ + theme?: "modern" | "corporate" | "creative" | "minimal" | "dark"; + /** Target audience */ + audience?: "business" | "students" | "technical" | "general"; + /** Presentation tone */ + tone?: "professional" | "casual" | "educational" | "persuasive"; + /** Generate AI images for visual slides */ + generateAIImages?: boolean; + /** Output file path */ + outputPath?: string; + /** Aspect ratio */ + aspectRatio?: "16:9" | "4:3"; + /** Logo image (Buffer, file path, or ImageWithAltText) */ + logoPath?: Buffer | string | ImageWithAltText; +}; +``` + +### PPTGenerationResult + +Result type for generated presentation: + +```typescript +type PPTGenerationResult = { + /** Path to generated file */ + filePath: string; + /** Total number of slides */ + totalSlides: number; + /** Output format */ + format: "pptx"; + /** Provider used for content planning */ + provider: string; + /** Model used for content planning */ + model: string; + /** Additional metadata */ + metadata?: { + theme?: string; + audience?: string; + tone?: string; + imageModel?: string; + fileSize?: number; + }; +}; +``` + +### Content Structure Types + +```typescript +// Bullet point with optional formatting +type BulletPoint = { + text: string; + subBullets?: string[]; + icon?: string; + emphasis?: boolean; + fontSize?: number; + bulletStyle?: "disc" | "number" | "checkmark" | "arrow" | "dash" | "none"; + color?: string; + bold?: boolean; +}; + +// Statistics for data slides +type Statistic = { + value: string; + label: string; + trend?: "up" | "down" | "neutral"; + change?: string; + icon?: string; +}; + +// Timeline items +type TimelineItem = { + date: string; + title: string; + description?: string; + icon?: string; +}; + +// Process steps +type ProcessStep = { + step: number; + title: string; + description?: string; + icon?: string; +}; + +// Chart data +type ChartSeries = { + name: string; + labels: string[]; + values: number[]; + color?: string; +}; +``` + +### Extended GenerateResult + +The `generate()` function returns an extended result when PPT mode is enabled: + +```typescript +type GenerateResult = { + content: string; + provider?: string; + model?: string; + usage?: TokenUsage; + responseTime?: number; + + // PPT-specific field (present when output.mode === "ppt") + ppt?: PPTGenerationResult; + + // Other optional fields + toolsUsed?: string[]; + analytics?: AnalyticsData; + evaluation?: EvaluationData; +}; +``` + +## Configuration & Best Practices + +### Configuration Options + +| Option | Type | Default | Required | Description | +| ----------------------------- | ------------------ | ---------------- | -------- | --------------------------------- | +| `input.text` | `string` | - | Yes | Topic/description (10-1000 chars) | +| `input.images` | `Array` | - | No | User-provided images | +| `provider` | `string` | `vertex` | No | AI provider for content planning | +| `model` | `string` | provider default | No | Model for content planning | +| `output.mode` | `string` | `text` | Yes | Must be `"ppt"` for PPT output | +| `output.ppt.pages` | `number` | `10` | Yes | Number of slides (5-50) | +| `output.ppt.theme` | `string` | `modern` | No | Presentation theme | +| `output.ppt.audience` | `string` | `general` | No | Target audience | +| `output.ppt.tone` | `string` | `professional` | No | Presentation tone | +| `output.ppt.generateAIImages` | `boolean` | `false` | No | Enable AI image generation | +| `output.ppt.aspectRatio` | `string` | `16:9` | No | Slide aspect ratio | +| `output.ppt.outputPath` | `string` | auto-generated | No | Output file path | +| `output.ppt.logoPath` | `Buffer \| string` | - | No | Logo for slides | + +### Best Practices + +#### 1. Prompt Engineering + +```typescript +// ❌ Vague prompt +const vaguePrompt = "Make a presentation"; + +// ✅ Specific and detailed +const specificPrompt = + "Create a presentation about Machine Learning in Healthcare: covering benefits, use cases (diagnosis, drug discovery, patient care), implementation challenges, and future outlook"; + +// ✅ Include structure hints +const structuredPrompt = `Product Launch Presentation for SmartWatch Pro: +- Start with value proposition +- Cover 5 key features +- Include competitor comparison +- Show pricing tiers +- End with launch timeline and CTA`; +``` + +#### 2. Audience & Tone Matching + +```typescript +// Technical audience +const technicalPpt = await neurolink.generate({ + input: { + text: "Kubernetes Architecture Deep Dive", + }, + output: { + mode: "ppt", + ppt: { + pages: 15, + audience: "technical", + tone: "educational", + theme: "dark", + }, + }, +}); + +// Executive audience +const executivePpt = await neurolink.generate({ + input: { + text: "Digital Transformation ROI Report", + }, + output: { + mode: "ppt", + ppt: { + pages: 10, + audience: "business", + tone: "professional", + theme: "corporate", + }, + }, +}); +``` + +#### 3. Image Strategy + +```typescript +// AI-generated images for creative presentations +const creativePpt = await neurolink.generate({ + input: { text: "Future of Space Tourism" }, + output: { + mode: "ppt", + ppt: { + pages: 12, + theme: "creative", + generateAIImages: true, // AI generates thematic images + }, + }, +}); + +// User-provided images for brand consistency +const brandPpt = await neurolink.generate({ + input: { + text: "Annual Report 2025", + images: [ + readFileSync("./brand/hero.jpg"), + readFileSync("./brand/team.jpg"), + readFileSync("./brand/office.jpg"), + ], + }, + output: { + mode: "ppt", + ppt: { + pages: 20, + generateAIImages: false, + logoPath: "./brand/logo.png", + }, + }, +}); +``` + +## Comprehensive Examples + +### Example 1: Basic Presentation + +```typescript +import { NeuroLink } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +async function generateBasicPresentation() { + const result = await neurolink.generate({ + input: { + text: "Introduction to Cloud Computing: Benefits, Types, and Best Practices", + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + pages: 10, + theme: "modern", + audience: "general", + tone: "educational", + }, + }, + }); + + if (result.ppt) { + console.log({ + filePath: result.ppt.filePath, + totalSlides: result.ppt.totalSlides, + provider: result.ppt.provider, + }); + } +} +``` + +### Example 2: Business Presentation with Analytics + +```typescript +import { NeuroLink } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +async function generateBusinessPresentation() { + const result = await neurolink.generate({ + input: { + text: `Q4 2025 Sales Performance Report: + - Revenue: $12.5M (up 23% YoY) + - New customers: 450 + - Customer retention: 94% + - Top products: Enterprise Suite, Cloud Platform + - Challenges: Supply chain, competition + - 2026 targets: $18M revenue, 600 new customers`, + }, + provider: "anthropic", + model: "claude-3.5-sonnet", + enableAnalytics: true, + output: { + mode: "ppt", + ppt: { + pages: 15, + theme: "corporate", + audience: "business", + tone: "professional", + outputPath: "./reports/q4-sales.pptx", + }, + }, + }); + + console.log("Analytics:", result.analytics); + console.log("Presentation:", result.ppt); +} +``` + +### Example 3: Technical Documentation with Code + +```typescript +import { NeuroLink } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +async function generateTechnicalDocs() { + const result = await neurolink.generate({ + input: { + text: `API Integration Guide for Payment Gateway: + - Authentication (OAuth 2.0, API Keys) + - Endpoints overview + - Request/Response formats + - Error handling + - Code examples in TypeScript + - Testing and sandbox environment + - Security best practices`, + }, + provider: "openai", + model: "gpt-4o", + output: { + mode: "ppt", + ppt: { + pages: 12, + theme: "dark", + audience: "technical", + tone: "educational", + }, + }, + }); + + return result.ppt; +} +``` + +### Example 4: Batch Presentation Generation + +```typescript +import { NeuroLink } from "@juspay/neurolink"; +import pLimit from "p-limit"; + +const neurolink = new NeuroLink(); +const limit = pLimit(2); // Max 2 concurrent generations + +const topics = [ + { topic: "AI in Healthcare", audience: "business" }, + { topic: "Machine Learning Basics", audience: "students" }, + { topic: "Cloud Architecture Patterns", audience: "technical" }, +]; + +async function batchGenerate() { + const results = await Promise.all( + topics.map((item) => + limit(async () => { + const result = await neurolink.generate({ + input: { text: item.topic }, + output: { + mode: "ppt", + ppt: { + pages: 10, + audience: item.audience as any, + outputPath: `./presentations/${item.topic.replace(/\s+/g, "-").toLowerCase()}.pptx`, + }, + }, + }); + return { + topic: item.topic, + filePath: result.ppt?.filePath, + slides: result.ppt?.totalSlides, + }; + }), + ), + ); + + console.table(results); +} +``` + +### Example 5: Error Handling + +```typescript +import { NeuroLink, NeuroLinkError, PPTError } from "@juspay/neurolink"; + +const neurolink = new NeuroLink(); + +async function generateWithErrorHandling(topic: string) { + try { + const result = await neurolink.generate({ + input: { text: topic }, + output: { + mode: "ppt", + ppt: { + pages: 10, + theme: "modern", + }, + }, + timeout: 300000, // 5 minutes for PPT generation + }); + + return result.ppt; + } catch (error) { + if (error instanceof PPTError) { + console.error(`PPT Error [${error.code}]:`, error.message); + + switch (error.code) { + case "PPT_PLANNING_FAILED": + console.error("Content planning failed - try a different prompt"); + break; + case "PPT_INVALID_AI_RESPONSE": + console.error( + "AI returned invalid response - retry with different model", + ); + break; + case "PPT_IMAGE_GENERATION_FAILED": + console.error("Image generation failed - try without AI images"); + break; + case "PPT_ASSEMBLY_FAILED": + console.error("PPTX assembly failed - check file permissions"); + break; + case "PPT_FILE_WRITE_FAILED": + console.error("File write failed - check disk space and permissions"); + break; + case "PPT_TIMEOUT": + console.error("Generation timed out - try fewer slides"); + break; + } + } else if (error instanceof NeuroLinkError) { + console.error(`NeuroLink Error:`, error.message); + } + + throw error; + } +} +``` + +## Error Handling & Validation + +### Validation Rules + +| Parameter | Validation | Error Type | Example Message | +| ------------------------ | ------------------- | ---------- | ------------------------------------------ | +| `input.text` | 10-1000 characters | PPTError | `Prompt must be 10-1000 characters` | +| `output.ppt.pages` | 5-50 slides | PPTError | `Pages must be between 5 and 50` | +| `output.ppt.theme` | Valid theme name | PPTError | `Invalid theme. Use: modern, corporate...` | +| `output.ppt.audience` | Valid audience type | PPTError | `Invalid audience type` | +| `output.ppt.tone` | Valid tone type | PPTError | `Invalid tone type` | +| `output.ppt.aspectRatio` | `16:9` or `4:3` | PPTError | `Invalid aspect ratio` | + +### Error Codes + +```typescript +const PPT_ERROR_CODES = { + PLANNING_FAILED: "PPT_PLANNING_FAILED", + INVALID_AI_RESPONSE: "PPT_INVALID_AI_RESPONSE", + IMAGE_GENERATION_FAILED: "PPT_IMAGE_GENERATION_FAILED", + ASSEMBLY_FAILED: "PPT_ASSEMBLY_FAILED", + FILE_WRITE_FAILED: "PPT_FILE_WRITE_FAILED", + INVALID_INPUT: "PPT_INVALID_INPUT", + TIMEOUT: "PPT_TIMEOUT", +}; +``` + +## Troubleshooting + +| Symptom | Cause | Solution | +| ----------------------- | -------------------------------- | -------------------------------------------- | +| Content planning fails | Invalid/vague prompt | Use more specific, detailed prompts | +| Slides have wrong types | Model not following instructions | Try advanced-tier model (claude-3.5, gpt-4o) | +| Images not generating | `generateAIImages` not enabled | Set `generateAIImages: true` | +| Images fail to generate | Missing Vertex AI credentials | Configure `GOOGLE_APPLICATION_CREDENTIALS` | +| File write fails | Permission denied | Check output directory permissions | +| Generation times out | Too many slides with images | Reduce pages or disable AI images | +| Bullet formatting wrong | AI not following format | Use simplified slide types | +| Charts have no data | AI didn't generate chart data | Provide explicit data in prompt | +| Logo not appearing | Invalid logo path/buffer | Verify logo file exists and is valid image | +| Incorrect aspect ratio | Using wrong dimension | Ensure aspectRatio matches content design | + +### Debug Mode + +```typescript +// Enable verbose logging +const neurolink = new NeuroLink({ + debug: true, + logLevel: "verbose", +}); + +// Or via environment variable +// export NEUROLINK_DEBUG=true +``` + +## Testing + +### Unit Test Example + +```typescript +import { describe, it, expect, vi } from "vitest"; +import { NeuroLink } from "@juspay/neurolink"; + +describe("PPT Generation", () => { + it("should generate presentation with valid options", async () => { + const neurolink = new NeuroLink(); + + const result = await neurolink.generate({ + input: { text: "Test presentation about AI" }, + output: { + mode: "ppt", + ppt: { + pages: 5, + theme: "modern", + audience: "general", + tone: "professional", + }, + }, + }); + + expect(result.ppt).toBeDefined(); + expect(result.ppt?.filePath).toMatch(/\.pptx$/); + expect(result.ppt?.totalSlides).toBeGreaterThanOrEqual(5); + expect(result.ppt?.format).toBe("pptx"); + }); + + it("should throw error for invalid page count", async () => { + const neurolink = new NeuroLink(); + + await expect( + neurolink.generate({ + input: { text: "Test" }, + output: { + mode: "ppt", + ppt: { pages: 100 }, // Invalid: max is 50 + }, + }), + ).rejects.toThrow(); + }); +}); +``` + +### Mock Strategy for CI/CD + +```typescript +import { vi } from "vitest"; + +vi.mock("@juspay/neurolink", () => ({ + NeuroLink: vi.fn().mockImplementation(() => ({ + generate: vi.fn().mockResolvedValue({ + content: "", + provider: "vertex", + model: "gemini-2.5-pro", + ppt: { + filePath: "./test-output.pptx", + totalSlides: 10, + format: "pptx", + provider: "vertex", + model: "gemini-2.5-pro", + metadata: { + theme: "modern", + audience: "general", + tone: "professional", + }, + }, + }), + })), +})); +``` + +## Limitations + +| Limitation | Description | Workaround | +| ---------------- | ------------------------------- | ------------------------------------- | +| Max slides | 50 slides per presentation | Split into multiple presentations | +| Min slides | 5 slides minimum | Use at least 5 pages | +| Output format | Only PPTX supported | Convert with external tools if needed | +| Image generation | Only with Vertex AI / Google AI | Use user-provided images | +| Custom templates | Not supported yet | Use theme customization | +| Animations | Basic transitions only | Edit in PowerPoint after generation | +| Video embedding | Not supported | Add videos manually after generation | + +## Performance Optimization + +### Generation Time Estimates + +| Configuration | Estimated Time | Notes | +| ------------------------- | -------------- | -------------------------- | +| 10 slides, no images | 15-30s | Fast, text-only | +| 10 slides, with AI images | 60-120s | Image generation adds time | +| 20 slides, no images | 30-60s | Linear scaling | +| 20 slides, with AI images | 120-240s | Parallel image generation | +| 50 slides, no images | 60-120s | Large presentation | +| 50 slides, with AI images | 300-600s | Consider splitting | + +### Optimization Tips + +1. **Disable AI images** for faster generation: `generateAIImages: false` +2. **Use basic-tier models** for simple presentations: `gemini-2.5-flash` +3. **Provide user images** instead of AI generation for brand consistency +4. **Limit slide count** to what's actually needed +5. **Use structured prompts** for better AI content planning + +## Related Features + +- [Video Generation](./video-generation.md) – Generate videos from images +- [Multimodal Chat](./multimodal-chat.md) – Image and text processing +- [Office Documents](./office-documents.md) – Process existing PPTX files + +## Implementation Files + +| File | Purpose | +| -------------------------------------------------- | ------------------------------------------------ | +| `src/lib/features/ppt/presentationOrchestrator.ts` | Main orchestration pipeline | +| `src/lib/features/ppt/contentPlanner.ts` | AI-powered content planning | +| `src/lib/features/ppt/slideGenerator.ts` | Individual slide generation | +| `src/lib/features/ppt/slideRenderers.ts` | Slide type rendering functions | +| `src/lib/features/ppt/constants.ts` | Themes, prompts, and configuration | +| `src/lib/features/ppt/types.ts` | Type definitions | +| `src/lib/types/pptTypes.ts` | Public API types | +| `src/lib/utils/parameterValidation.ts` | Input validation: `validatePPTGenerationInput()` | + +**Next:** [Video Generation](./video-generation.md) | [Multimodal Chat](./multimodal-chat.md) diff --git a/docs/features/tts.md b/docs/features/tts.md index b85afe573..ea4dc1b00 100644 --- a/docs/features/tts.md +++ b/docs/features/tts.md @@ -811,6 +811,7 @@ For detailed pricing, see [Google Cloud TTS Pricing](https://cloud.google.com/te - [Multimodal Guide](multimodal.md) - Images, PDFs, CSV inputs - [PDF Support](pdf-support.md) - Document processing - [Video Generation](video-generation.md) - AI-powered video creation +- [PPT Generation](ppt-generation.md) - AI-powered PowerPoint presentations **Advanced Features:** diff --git a/docs/getting-started/provider-setup.md b/docs/getting-started/provider-setup.md index 4c7a4da9d..57119baaa 100644 --- a/docs/getting-started/provider-setup.md +++ b/docs/getting-started/provider-setup.md @@ -481,6 +481,8 @@ const result = await neurolink.generate({ > **Video Generation:** Use `output.mode: "video"` with Veo 3.1 to generate videos. See [Video Generation Guide](../features/video-generation.md). +> **PPT Generation:** Use `output.mode: "ppt"` with supported providers (Vertex AI, Google AI, OpenAI, Anthropic, Azure OpenAI, or Bedrock) and compatible text models to generate PowerPoint presentations. See [PPT Generation Guide](../features/ppt-generation.md). + ### Gemini 3 Extended Thinking Configuration Gemini 3 models support **extended thinking** (also known as "thinking mode"), which allows the model to reason more deeply before providing responses. This is particularly useful for complex reasoning tasks, math problems, and multi-step analysis. diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index be20d51a8..75b86ab76 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -125,6 +125,7 @@ npx @juspay/neurolink generate "Write a poem" --disable-tools **Latest Features:** - [Multimodal Chat](../features/multimodal-chat.md) - Add images to your prompts +- [PPT Generation](../features/ppt-generation.md) - Generate PowerPoint presentations - [Auto Evaluation](../features/auto-evaluation.md) - Quality scoring for responses - [Guardrails](../features/guardrails.md) - Content filtering and safety diff --git a/docs/performance-optimization.md b/docs/performance-optimization.md index 3ae11922c..b9e64da14 100644 --- a/docs/performance-optimization.md +++ b/docs/performance-optimization.md @@ -810,3 +810,4 @@ This comprehensive performance optimization guide provides the tools and strateg - [Troubleshooting](reference/troubleshooting.md) - Common performance issues - [Enterprise Setup](getting-started/provider-setup.md) - Production configuration - [Video Generation Guide](features/video-generation.md) - Complete video generation documentation +- [PPT Generation Guide](features/ppt-generation.md) - PowerPoint presentation generation diff --git a/docs/reference/error-codes.md b/docs/reference/error-codes.md index da80a33dd..24d2a1dc3 100644 --- a/docs/reference/error-codes.md +++ b/docs/reference/error-codes.md @@ -335,6 +335,140 @@ const result = await neurolink.generate({ }); ``` +## PPT Validation Errors + +Errors specific to PPT (PowerPoint) generation validation. + +| Code | Description | Severity | Retriable | Category | +| ---------------------- | --------------------------------- | -------- | --------- | ---------- | +| `INVALID_PPT_PAGES` | Invalid page count (must be 5-50) | MEDIUM | No | VALIDATION | +| `INVALID_PPT_THEME` | Invalid theme specified | MEDIUM | No | VALIDATION | +| `INVALID_PPT_AUDIENCE` | Invalid audience type | MEDIUM | No | VALIDATION | +| `INVALID_PPT_TONE` | Invalid tone specified | MEDIUM | No | VALIDATION | +| `INVALID_PPT_ASPECT` | Invalid aspect ratio | MEDIUM | No | VALIDATION | +| `INVALID_PPT_FORMAT` | Invalid output format | MEDIUM | No | VALIDATION | +| `INVALID_PPT_MODE` | Output mode not set to ppt | MEDIUM | No | VALIDATION | +| `EMPTY_PPT_PROMPT` | Prompt cannot be empty | MEDIUM | No | VALIDATION | +| `PPT_PROMPT_TOO_SHORT` | Prompt must be at least 10 chars | MEDIUM | No | VALIDATION | +| `PPT_PROMPT_TOO_LONG` | Prompt exceeds 1000 characters | MEDIUM | No | VALIDATION | + +### Resolution Guide + +**INVALID_PPT_PAGES** + +```typescript +// Valid page count: 5-50 slides +const result = await neurolink.generate({ + input: { text: "Company presentation" }, + output: { + mode: "ppt", + ppt: { pages: 10 }, // Must be between 5 and 50 + }, +}); +``` + +**INVALID_PPT_THEME** + +```typescript +// Valid themes: 'modern', 'corporate', 'creative', 'minimal', 'dark' +const result = await neurolink.generate({ + input: { text: "Product launch" }, + output: { + mode: "ppt", + ppt: { + pages: 12, + theme: "corporate", // Valid theme name + }, + }, +}); +``` + +**INVALID_PPT_AUDIENCE** + +```typescript +// Valid audiences: 'business', 'students', 'technical', 'general' +const result = await neurolink.generate({ + input: { text: "Technical architecture overview" }, + output: { + mode: "ppt", + ppt: { + pages: 15, + audience: "technical", // Valid audience type + }, + }, +}); +``` + +## PPT Generation Runtime Errors + +Runtime errors during PPT generation (as opposed to validation errors). + +| Code | Description | Severity | Retriable | Category | +| ----------------------------- | -------------------------------- | -------- | --------- | ---------- | +| `PPT_PLANNING_FAILED` | AI content planning failed | HIGH | Yes | EXECUTION | +| `PPT_INVALID_AI_RESPONSE` | AI returned malformed slide data | HIGH | Yes | EXECUTION | +| `PPT_IMAGE_GENERATION_FAILED` | AI image generation failed | MEDIUM | Yes | EXECUTION | +| `PPT_ASSEMBLY_FAILED` | PPTX file assembly failed | HIGH | No | EXECUTION | +| `PPT_FILE_WRITE_FAILED` | Could not write file to disk | HIGH | No | RESOURCE | +| `PPT_TIMEOUT` | Generation exceeded timeout | HIGH | Yes | TIMEOUT | +| `PPT_INVALID_INPUT` | Invalid input during runtime | HIGH | No | VALIDATION | + +### Resolution Guide + +**PPT_PLANNING_FAILED** + +```typescript +// Use a more specific prompt or try a different model +const result = await neurolink.generate({ + input: { + text: `Quarterly Sales Report: + - Revenue growth 23% YoY + - 450 new customers + - Top 3 products by region + - 2026 targets and roadmap`, + }, + provider: "anthropic", + model: "claude-3.5-sonnet", // Try advanced model + output: { + mode: "ppt", + ppt: { pages: 15, theme: "corporate" }, + }, +}); +``` + +**PPT_IMAGE_GENERATION_FAILED** + +```typescript +// Disable AI image generation if it fails +const result = await neurolink.generate({ + input: { text: "Technical architecture" }, + output: { + mode: "ppt", + ppt: { + pages: 10, + generateAIImages: false, // Disable AI images + }, + }, +}); +``` + +**PPT_TIMEOUT** + +```typescript +// Reduce slides or disable images for faster generation +const result = await neurolink.generate({ + input: { text: "Quick summary presentation" }, + output: { + mode: "ppt", + ppt: { + pages: 5, // Fewer slides + generateAIImages: false, // No AI images + }, + }, + timeout: 300, // 5 minute timeout +}); +``` + ## SDK Error Handling Example Complete example demonstrating proper error handling in the SDK: diff --git a/docs/sdk/api-reference.md b/docs/sdk/api-reference.md index fbe32f49e..c5167f210 100644 --- a/docs/sdk/api-reference.md +++ b/docs/sdk/api-reference.md @@ -167,8 +167,9 @@ type GenerateOptions = { // Output configuration output?: { format?: "text" | "structured" | "json"; - mode?: "text" | "video"; // Output mode: 'text' (default) or 'video' + mode?: "text" | "video" | "ppt"; // Output mode: 'text' (default), 'video', or 'ppt' video?: VideoOutputOptions; // Video generation options (when mode is 'video') + ppt?: PPTOutputOptions; // PPT generation options (when mode is 'ppt') }; // Document processing options @@ -219,6 +220,22 @@ type GenerateResult = { }; }; + // PPT generation result (when output.mode is 'ppt') + ppt?: { + filePath: string; // Path to generated PPTX file + totalSlides: number; // Number of slides generated + format: "pptx"; // Output format + provider: string; // Provider used for content planning + model: string; // Model used for content planning + metadata?: { + theme?: string; // Theme applied + audience?: string; // Target audience + tone?: string; // Presentation tone + imageModel?: string; // Model used for image generation + fileSize?: number; // File size in bytes + }; + }; + analytics?: { provider: string; model?: string; diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 71662c246..5cbf90fda 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -70,6 +70,15 @@ This guide helps diagnose and resolve common issues with NeuroLink, including AI | `Project not found` error | Set `GOOGLE_VERTEX_PROJECT` or `GOOGLE_CLOUD_PROJECT` environment variable | | Audio missing from generated video | Set `output.video.audio: true` (enabled by default) and ensure Veo 3.1 model is used | +### PPT Generation (PowerPoint Presentations) + +- `PPT_PLANNING_FAILED` — Check AI provider connection and ensure valid prompt. See [PPT Generation Guide](features/ppt-generation.md#troubleshooting) +- `PPT_INVALID_AI_RESPONSE` during generation — Simplify prompt/topic and retry. See [PPT Generation Guide](features/ppt-generation.md#error-handling) +- `PPT_FILE_WRITE_FAILED` — Check write permissions for output directory and disk space. See [PPT Generation Guide](features/ppt-generation.md#file-output) +- Empty slides in presentation — Ensure content plan has enough detail; try more specific prompts +- Images not generating — Set `generateAIImages: true` in `output.ppt` (SDK) or avoid `--pptNoImages` (CLI), and configure `VERTEX_IMAGE_MODEL`. See [PPT Generation Guide](features/ppt-generation.md#ai-image-generation) +- Theme not applying correctly — Verify theme name: `modern`, `corporate`, `creative`, `minimal`, or `dark` + ## 🎯 **Generate Function Migration Issues** ### **Migration Questions** diff --git a/docs/tutorials.md b/docs/tutorials.md index 46ab50ec7..b444ddf1b 100644 --- a/docs/tutorials.md +++ b/docs/tutorials.md @@ -115,7 +115,53 @@ npx @juspay/neurolink generate "Cinematic camera movement" \ For complete documentation, see the [Video Generation Guide](features/video-generation.md). -## �🌐 Web App Integration +## 📊 PPT Generation Tutorial + +Generate professional PowerPoint presentations using the CLI: + +```bash +# Basic presentation generation +npx @juspay/neurolink generate "Create a 10-slide presentation about AI in healthcare" \ + --outputMode ppt \ + --pptOutput ./healthcare-ai.pptx + +# Full options with custom theme and AI images +npx @juspay/neurolink generate "Create a sales deck for our SaaS product" \ + --provider vertex \ + --model gemini-2.5-pro \ + --outputMode ppt \ + --pptTheme corporate \ + --pptPages 12 \ + --pptOutput ./sales-deck.pptx +``` + +**SDK Usage:** + +```typescript +import { NeuroLink } from "@juspay/neurolink"; +import { writeFile } from "fs/promises"; + +const neurolink = new NeuroLink(); + +const result = await neurolink.generate({ + input: { text: "Create a product launch presentation" }, + output: { + mode: "ppt", + ppt: { + theme: "modern", + pages: 10, + generateAIImages: true, + outputPath: "./launch-deck.pptx", + }, + }, +}); + +console.log(`Generated ${result.ppt?.totalSlides} slides`); +``` + +For complete documentation, see the [PPT Generation Guide](features/ppt-generation.md). + +## 🌐 Web App Integration ### Express.js API diff --git a/docs/use-cases.md b/docs/use-cases.md index 22ead3465..4b4ec49e2 100644 --- a/docs/use-cases.md +++ b/docs/use-cases.md @@ -105,6 +105,58 @@ try { - **Engagement:** 40% higher engagement on video vs. static images - **Scale:** Generate videos for entire product catalog +### Product Presentation Generation + +**Business Challenge:** Create professional product presentations for sales teams, investor meetings, and partner showcases. + +**Solution Implementation:** + +```javascript +import { NeuroLink } from "@juspay/neurolink"; +import { writeFile } from "fs/promises"; + +const neurolink = new NeuroLink(); + +try { + // Generate product presentation + const pptResult = await neurolink.generate({ + input: { + text: `Create a professional product presentation for ${product.name}. + Include: product overview, key features, competitive advantages, + pricing tiers, customer testimonials, and call to action. + Target audience: ${product.targetAudience}`, + }, + provider: "vertex", + model: "gemini-2.5-pro", + output: { + mode: "ppt", + ppt: { + theme: "corporate", + pages: 12, + generateAIImages: true, + outputPath: `./presentations/${product.id}-deck.pptx`, + }, + }, + enableAnalytics: true, + }); + + if (pptResult.ppt) { + // Use your logger instead: logger.info(`Presentation generated: ${pptResult.ppt.totalSlides} slides`) + } +} catch (error) { + // Handle PPT generation errors (timeout, slide validation, file creation, etc.) + // Use your logger instead: logger.error('Presentation generation failed', { error, productId: product.id }) + throw error; +} +``` + +**Business Results:** + +- **Time Savings:** 95% faster than manual deck creation +- **Consistency:** Brand-aligned presentations every time +- **Scale:** Generate presentations for entire product catalog +- **Quality:** Professional 35 slide types with 5 theme options + ### Customer Review Response **CLI Implementation:** diff --git a/src/cli/factories/commandFactory.ts b/src/cli/factories/commandFactory.ts index dfc9bd4bf..b47f1773a 100644 --- a/src/cli/factories/commandFactory.ts +++ b/src/cli/factories/commandFactory.ts @@ -339,10 +339,10 @@ export class CLICommandFactory { // Video Generation options (Veo 3.1) outputMode: { type: "string" as const, - choices: ["text", "video"], + choices: ["text", "video", "ppt"], default: "text", description: - "Output mode: 'text' for standard generation, 'video' for video generation", + "Output mode: 'text' for standard generation, 'video' for video, 'ppt' for presentation", }, videoOutput: { type: "string" as const, @@ -373,6 +373,46 @@ export class CLICommandFactory { description: "Enable/disable audio generation in video", }, + // PPT Generation options + pptPages: { + type: "number" as const, + alias: "pages", + description: + "Number of slides to generate (5-50, default: 10 when PPT mode is enabled)", + }, + pptTheme: { + type: "string" as const, + choices: ["modern", "corporate", "creative", "minimal", "dark"], + description: + "Presentation theme/style (default: AI selects based on topic)", + }, + pptAudience: { + type: "string" as const, + choices: ["business", "students", "technical", "general"], + description: "Target audience (default: AI selects based on topic)", + }, + pptTone: { + type: "string" as const, + choices: ["professional", "casual", "educational", "persuasive"], + description: "Presentation tone (default: AI selects based on topic)", + }, + pptOutput: { + type: "string" as const, + alias: "po", + description: "Path to save generated PPTX file (e.g., ./output.pptx)", + }, + pptAspectRatio: { + type: "string" as const, + choices: ["16:9", "4:3"], + description: + "Slide aspect ratio (default: 16:9 when PPT mode is enabled)", + }, + pptNoImages: { + type: "boolean" as const, + default: false, + description: "Disable AI image generation for slides", + }, + thinking: { alias: "think", type: "boolean" as const, @@ -609,12 +649,36 @@ export class CLICommandFactory { ttsOutput: argv.ttsOutput as string | undefined, ttsPlay: argv.ttsPlay as boolean | undefined, // Video generation options (Veo 3.1) - outputMode: argv.outputMode as "text" | "video" | undefined, + outputMode: argv.outputMode as "text" | "video" | "ppt" | undefined, videoOutput: argv.videoOutput as string | undefined, videoResolution: argv.videoResolution as "720p" | "1080p" | undefined, videoLength: argv.videoLength as 4 | 6 | 8 | undefined, videoAspectRatio: argv.videoAspectRatio as "9:16" | "16:9" | undefined, videoAudio: argv.videoAudio as boolean | undefined, + // PPT generation options + pptPages: argv.pptPages as number | undefined, + pptTheme: argv.pptTheme as + | "modern" + | "corporate" + | "creative" + | "minimal" + | "dark" + | undefined, + pptAudience: argv.pptAudience as + | "business" + | "students" + | "technical" + | "general" + | undefined, + pptTone: argv.pptTone as + | "professional" + | "casual" + | "educational" + | "persuasive" + | undefined, + pptOutput: argv.pptOutput as string | undefined, + pptAspectRatio: argv.pptAspectRatio as "16:9" | "4:3" | undefined, + pptNoImages: argv.pptNoImages as boolean | undefined, // Extended thinking options for Claude and Gemini models thinking: argv.thinking as boolean | undefined, thinkingBudget: argv.thinkingBudget as number | undefined, @@ -928,6 +992,67 @@ export class CLICommandFactory { } } + /** + * Helper method to configure options for PPT generation mode + * Auto-configures provider, model, and tools settings for presentation generation + */ + private static configurePPTMode( + enhancedOptions: BaseCommandArgs & Record, + argv: BaseCommandArgs & Record, + options: BaseCommandArgs & Record, + ): void { + const userEnabledTools = !argv.disableTools; // Tools are enabled by default + enhancedOptions.disableTools = true; + + // Auto-set provider for PPT generation if not explicitly specified + // PPT works best with Vertex or Google AI for content planning + if (!enhancedOptions.provider) { + enhancedOptions.provider = "vertex"; + if (options.debug) { + logger.debug( + "Auto-setting provider to 'vertex' for PPT generation mode", + ); + } + } + + // Auto-set model if not explicitly specified + if (!enhancedOptions.model) { + // Use gemini-2.5-flash for fast, high-quality content planning + const modelAlias = "gemini-2.5-flash"; + const resolvedModel = ModelResolver.resolveModel(modelAlias); + const fullModelId = resolvedModel?.id || "gemini-2.5-flash-001"; + enhancedOptions.model = fullModelId; + if (options.debug) { + logger.debug( + `Auto-setting model to '${fullModelId}' for PPT generation mode`, + ); + } + } + + // Warn user if they explicitly enabled tools + if (userEnabledTools && !options.quiet) { + logger.always( + chalk.yellow( + "⚠️ Note: MCP tools are not supported in PPT generation mode and have been disabled.", + ), + ); + } + + if (options.debug) { + logger.debug("PPT generation mode enabled (tools auto-disabled):", { + provider: enhancedOptions.provider, + model: enhancedOptions.model, + pages: enhancedOptions.pptPages, + theme: enhancedOptions.pptTheme, + audience: enhancedOptions.pptAudience, + tone: enhancedOptions.pptTone, + aspectRatio: enhancedOptions.pptAspectRatio, + noImages: enhancedOptions.pptNoImages, + outputPath: enhancedOptions.pptOutput, + }); + } + } + /** * Helper method to handle video file output * Saves generated video to file when --videoOutput flag is provided @@ -965,18 +1090,15 @@ export class CLICommandFactory { const saveResult = await saveVideoToFile(video, videoOutputPath); if (saveResult.success) { - if (!options.quiet) { - // Format video info output - const sizeInfo = formatVideoFileSize(saveResult.size); - const metadataSummary = getVideoMetadataSummary(video); + const sizeInfo = formatVideoFileSize(saveResult.size); + const metadataSummary = getVideoMetadataSummary(video); - logger.always( - chalk.green(`🎬 Video saved to: ${saveResult.path} (${sizeInfo})`), - ); + logger.always( + chalk.green(`🎬 Video saved to: ${saveResult.path} (${sizeInfo})`), + ); - if (metadataSummary) { - logger.always(chalk.gray(` ${metadataSummary}`)); - } + if (!options.quiet && metadataSummary) { + logger.always(chalk.gray(` ${metadataSummary}`)); } } else { handleError( @@ -989,6 +1111,65 @@ export class CLICommandFactory { } } + /** + * Helper method to handle PPT file output + * Displays PPT generation result info + */ + private static async handlePPTOutput( + result: GenerateResult | unknown, + options: BaseCommandArgs & Record, + ): Promise { + // Extract PPT from result with proper type checking + if (!result || typeof result !== "object") { + return; + } + const generateResult = result as GenerateResult; + const ppt = generateResult.ppt; + + if (!ppt) { + // PPT not in result - either not PPT mode or generation failed + return; + } + + try { + if (options.quiet) { + if (ppt.filePath) { + logger.always( + chalk.green(`📊 Presentation saved to: ${ppt.filePath}`), + ); + } else { + logger.always(chalk.green("📊 Presentation generated successfully.")); + } + if (ppt.totalSlides) { + logger.always(chalk.white(`📄 Slides: ${ppt.totalSlides}`)); + } + return; + } + + logger.always(chalk.green("\n📊 Presentation Generated Successfully!")); + logger.always(chalk.gray("─".repeat(50))); + + if (ppt.filePath) { + logger.always(chalk.white(` 📁 File: ${ppt.filePath}`)); + } + if (ppt.totalSlides) { + logger.always(chalk.white(` 📄 Slides: ${ppt.totalSlides}`)); + } + if (ppt.format) { + logger.always(chalk.white(` 📋 Format: ${ppt.format.toUpperCase()}`)); + } + + logger.always(chalk.gray("─".repeat(50))); + logger.always( + chalk.cyan( + "💡 Tip: Open the file with PowerPoint or Google Slides to view.", + ), + ); + } catch (error) { + handleError(error as Error, "PPT Output"); + } + } + // Helper method to validate token usage data with fallback handling private static isValidTokenUsage(tokens: unknown): tokens is TokenUsage { if (!tokens || typeof tokens !== "object" || tokens === null) { @@ -1178,12 +1359,16 @@ export class CLICommandFactory { "Video generation with full options", ) .example( - '$0 generate "Explain AI" --provider anthropic --subscription-tier pro', - "Use Anthropic with Pro subscription tier", + '$0 generate "AI in Healthcare" --pptPages 10', + "Generate a PowerPoint presentation", + ) + .example( + '$0 generate "Company Q4 Results" --pptPages 15 --pptTheme corporate --pptAudience business', + "Generate presentation with options", ) .example( - '$0 generate "Deep analysis" --provider anthropic-subscription --subscription-tier max --auth-method oauth', - "Use Anthropic with Max subscription and OAuth", + '$0 generate "Machine Learning 101" --pptTheme minimal --pptTone educational --pptNoImages', + "Generate educational slides without AI images", ), ); }, @@ -1960,91 +2145,309 @@ export class CLICommandFactory { } /** - * Execute the generate command + * Handle stdin input for generate command */ - private static async executeGenerate(argv: GenerateCommandArgs) { - // Handle stdin input if no input provided + private static async handleGenerateStdinInput( + argv: GenerateCommandArgs, + ): Promise { if (!argv.input && !process.stdin.isTTY) { let stdinData = ""; process.stdin.setEncoding("utf8"); for await (const chunk of process.stdin) { stdinData += chunk; } - argv.input = stdinData.trim(); - if (!argv.input) { + const trimmedData = stdinData.trim(); + if (!trimmedData) { throw new Error("No input received from stdin"); } + return trimmedData; } else if (!argv.input) { throw new Error( 'Input required. Use: neurolink generate "your prompt" or echo "prompt" | neurolink generate', ); } + return argv.input as string; + } - const options = CLICommandFactory.processOptions(argv); + /** + * Detect output mode (video, ppt, or text) based on CLI arguments + */ + private static detectGenerateOutputMode( + argv: GenerateCommandArgs, + options: BaseCommandArgs & Record, + ): { isVideoMode: boolean; isPPTMode: boolean; spinnerMessage: string } { + const outputMode = (options as Record).outputMode; - // Validate Anthropic subscription options if using Anthropic provider - this.validateAnthropicSubscriptionOptions( - options as Record, - ); + const isVideoMode = outputMode === "video"; + + const hasPPTFlags = + argv.pptPages !== undefined || + argv.pptTheme !== undefined || + argv.pptAudience !== undefined || + argv.pptTone !== undefined || + argv.pptOutput !== undefined || + argv.pptAspectRatio !== undefined || + argv.pptNoImages === true; + + const hasVideoSignals = + outputMode === "video" || argv.videoOutput !== undefined; + const hasPPTSignals = outputMode === "ppt" || hasPPTFlags; + + if (hasVideoSignals && hasPPTSignals) { + throw new Error( + "Conflicting output mode signals detected. Use either video mode (--outputMode video, optionally with --videoOutput) or PPT mode (--outputMode ppt / --ppt* flags), not both.", + ); + } + + const isPPTMode = outputMode === "ppt" || hasPPTFlags; - // Determine if video generation mode is enabled - const isVideoMode = - (options as Record).outputMode === "video"; const spinnerMessage = isVideoMode ? "🎬 Generating video... (this may take 1-2 minutes)" - : "🤖 Generating text..."; - const spinner = argv.quiet ? null : ora(spinnerMessage).start(); + : isPPTMode + ? "📊 Generating presentation... (this may take 2-5 minutes)" + : "🤖 Generating text..."; - try { - // Add delay if specified - if (options.delay) { - await new Promise((resolve) => setTimeout(resolve, options.delay)); + return { isVideoMode, isPPTMode, spinnerMessage }; + } + + /** + * Process context for generation command + */ + private static processGenerateContext( + inputText: string, + options: BaseCommandArgs & Record, + ): { inputText: string; contextMetadata: Partial | undefined } { + let processedInputText = inputText; + let contextMetadata: Partial | undefined; + + if (options.context && options.contextConfig) { + const processedContextResult = ContextFactory.processContext( + options.context as unknown as BaseContext, + options.contextConfig, + ); + + if (processedContextResult.processedContext) { + processedInputText = + processedContextResult.processedContext + processedInputText; + } + + contextMetadata = { + ...ContextFactory.extractAnalyticsContext( + options.context as unknown as BaseContext, + ), + contextMode: processedContextResult.config.mode, + contextTruncated: processedContextResult.metadata.truncated, + }; + + if (options.debug) { + logger.debug("Context processed:", { + mode: processedContextResult.config.mode, + truncated: processedContextResult.metadata.truncated, + processingTime: processedContextResult.metadata.processingTime, + }); } + } - // Process context if provided - let inputText = argv.input as string; - let contextMetadata: Partial | undefined; + return { inputText: processedInputText, contextMetadata }; + } - if (options.context && options.contextConfig) { - const processedContextResult = ContextFactory.processContext( - options.context, - options.contextConfig, + /** + * Build multimodal input from CLI arguments + */ + private static buildGenerateMultimodalInput( + inputText: string, + argv: GenerateCommandArgs, + ): { + text: string; + images?: Array; + csvFiles?: Array; + pdfFiles?: Array; + videoFiles?: Array; + files?: Array; + } { + const imageBuffers = CLICommandFactory.processCliImages( + argv.image as string | string[] | undefined, + ); + const csvFiles = CLICommandFactory.processCliCSVFiles( + argv.csv as string | string[] | undefined, + ); + const pdfFiles = CLICommandFactory.processCliPDFFiles( + argv.pdf as string | string[] | undefined, + ); + const videoFiles = CLICommandFactory.processCliVideoFiles( + argv.video as string | string[] | undefined, + ); + const files = CLICommandFactory.processCliFiles( + argv.file as string | string[] | undefined, + ); + + return { + text: inputText, + ...(imageBuffers && { images: imageBuffers }), + ...(csvFiles && { csvFiles }), + ...(pdfFiles && { pdfFiles }), + ...(videoFiles && { videoFiles }), + ...(files && { files }), + }; + } + + /** + * Build output configuration for generate request + */ + private static buildGenerateOutputConfig( + isVideoMode: boolean, + isPPTMode: boolean, + enhancedOptions: BaseCommandArgs & Record, + ): Record | undefined { + if (isVideoMode) { + return { + mode: "video" as const, + video: { + resolution: enhancedOptions.videoResolution as + | "720p" + | "1080p" + | undefined, + length: enhancedOptions.videoLength as 4 | 6 | 8 | undefined, + aspectRatio: enhancedOptions.videoAspectRatio as + | "9:16" + | "16:9" + | undefined, + audio: enhancedOptions.videoAudio as boolean | undefined, + }, + }; + } + + if (isPPTMode) { + return { + mode: "ppt" as const, + ppt: { + pages: (enhancedOptions.pptPages as number) || 10, + theme: enhancedOptions.pptTheme as + | "modern" + | "corporate" + | "creative" + | "minimal" + | "dark" + | undefined, + audience: enhancedOptions.pptAudience as + | "business" + | "students" + | "technical" + | "general" + | undefined, + tone: enhancedOptions.pptTone as + | "professional" + | "casual" + | "educational" + | "persuasive" + | undefined, + aspectRatio: + (enhancedOptions.pptAspectRatio as "16:9" | "4:3") || "16:9", + generateAIImages: !(enhancedOptions.pptNoImages as boolean), + outputPath: enhancedOptions.pptOutput as string | undefined, + }, + }; + } + + return undefined; + } + + /** + * Handle successful generation result + */ + private static async handleGenerateSuccess( + result: GenerateResult | unknown, + options: BaseCommandArgs & Record, + isVideoMode: boolean, + isPPTMode: boolean, + spinner: ReturnType | null, + ): Promise { + const genResult = result as GenerateResult; + if (spinner) { + if (isVideoMode) { + spinner.succeed(chalk.green("✅ Video generated successfully!")); + } else if (isPPTMode) { + spinner.succeed(chalk.green("✅ Presentation generated successfully!")); + } else { + spinner.succeed(chalk.green("✅ Text generated successfully!")); + } + } + + if (!options.quiet) { + const providerInfo = genResult.provider || "auto"; + const modelInfo = genResult.model || "default"; + logger.always( + chalk.gray(`🔧 Provider: ${providerInfo} | Model: ${modelInfo}`), + ); + } + + if (!isVideoMode && !isPPTMode) { + this.handleOutput(genResult, options); + } + + await this.handleTTSOutput(genResult, options); + await this.handleVideoOutput(genResult, options); + await this.handlePPTOutput(genResult, options); + + if (options.debug) { + logger.debug("\n" + chalk.yellow("Debug Information:")); + logger.debug("Provider:", genResult.provider); + logger.debug("Model:", genResult.model); + if (genResult.analytics) { + logger.debug( + "Analytics:", + JSON.stringify(genResult.analytics, null, 2), ); + } + if (genResult.evaluation) { + logger.debug( + "Evaluation:", + JSON.stringify(genResult.evaluation, null, 2), + ); + } + } - // Integrate context into prompt if configured - if (processedContextResult.processedContext) { - inputText = processedContextResult.processedContext + inputText; - } + if (!globalSession.getCurrentSessionId()) { + await this.flushLangfuseTraces(); + process.exit(0); + } + } - // Add context metadata for analytics - contextMetadata = { - ...ContextFactory.extractAnalyticsContext( - options.context as BaseContext, - ), - contextMode: processedContextResult.config.mode, - contextTruncated: processedContextResult.metadata.truncated, - }; + /** + * Execute the generate command + */ + private static async executeGenerate(argv: GenerateCommandArgs) { + // Handle stdin input + const rawInput = await this.handleGenerateStdinInput(argv); + argv.input = rawInput; - if (options.debug) { - logger.debug("Context processed:", { - mode: processedContextResult.config.mode, - truncated: processedContextResult.metadata.truncated, - processingTime: processedContextResult.metadata.processingTime, - }); - } + const options = this.processOptions(argv); + + // Detect output mode + const { isVideoMode, isPPTMode, spinnerMessage } = + this.detectGenerateOutputMode(argv, options); + + const spinner = argv.quiet ? null : ora(spinnerMessage).start(); + + try { + // Add delay if specified + if (options.delay) { + await new Promise((resolve) => setTimeout(resolve, options.delay)); } + // Process context + const { inputText, contextMetadata } = this.processGenerateContext( + rawInput, + options, + ); + // Handle dry-run mode for testing if (options.dryRun) { const mockResult = { content: "Mock response for testing purposes", provider: options.provider || "auto", model: options.model || "test-model", - usage: { - input: 10, - output: 15, - total: 25, - }, + usage: { input: 10, output: 15, total: 25 }, responseTime: 150, analytics: options.enableAnalytics ? { @@ -2072,22 +2475,21 @@ export class CLICommandFactory { if (spinner) { spinner.succeed(chalk.green("✅ Dry-run completed successfully!")); } - - CLICommandFactory.handleOutput(mockResult, options); - + this.handleOutput(mockResult, options); if (options.debug) { logger.debug("\n" + chalk.yellow("Debug Information (Dry-run):")); logger.debug("Provider:", mockResult.provider); logger.debug("Model:", mockResult.model); logger.debug("Mode: DRY-RUN (no actual API calls made)"); } - if (!globalSession.getCurrentSessionId()) { await CLICommandFactory.flushLangfuseTraces(); process.exit(0); } + return; } + // Initialize SDK and session const sdk = globalSession.getOrCreateNeuroLink(); const sessionVariables = globalSession.getSessionVariables(); const enhancedOptions = { ...options, ...sessionVariables }; @@ -2103,37 +2505,23 @@ export class CLICommandFactory { }); } - // Video generation doesn't support tools, so auto-disable them + // Configure mode-specific options if (isVideoMode) { CLICommandFactory.configureVideoMode(enhancedOptions, argv, options); } + if (isPPTMode) { + this.configurePPTMode(enhancedOptions, argv, options); + } - // Process CLI multimodal inputs - const imageBuffers = CLICommandFactory.processCliImages( - argv.image as string | string[] | undefined, - ); - const csvFiles = CLICommandFactory.processCliCSVFiles( - argv.csv as string | string[] | undefined, - ); - const pdfFiles = CLICommandFactory.processCliPDFFiles( - argv.pdf as string | string[] | undefined, - ); - const videoFiles = CLICommandFactory.processCliVideoFiles( - argv.video as string | string[] | undefined, - ); - const files = CLICommandFactory.processCliFiles( - argv.file as string | string[] | undefined, + // Build multimodal input and output configuration + const generateInput = this.buildGenerateMultimodalInput(inputText, argv); + const outputConfig = this.buildGenerateOutputConfig( + isVideoMode, + isPPTMode, + enhancedOptions, ); - const generateInput = { - text: inputText, - ...(imageBuffers && { images: imageBuffers }), - ...(csvFiles && { csvFiles }), - ...(pdfFiles && { pdfFiles }), - ...(videoFiles && { videoFiles }), - ...(files && { files }), - }; - + // Execute generation const result = await sdk.generate({ input: generateInput, csvOptions: { @@ -2150,24 +2538,7 @@ export class CLICommandFactory { format: argv.videoFormat as "jpeg" | "png" | undefined, transcribeAudio: argv.transcribeAudio as boolean | undefined, }, - // Video generation output configuration - output: isVideoMode - ? { - mode: "video" as const, - video: { - resolution: enhancedOptions.videoResolution as - | "720p" - | "1080p" - | undefined, - length: enhancedOptions.videoLength as 4 | 6 | 8 | undefined, - aspectRatio: enhancedOptions.videoAspectRatio as - | "9:16" - | "16:9" - | undefined, - audio: enhancedOptions.videoAudio as boolean | undefined, - }, - } - : undefined, + output: outputConfig, provider: enhancedOptions.provider, model: enhancedOptions.model, temperature: enhancedOptions.temperature, @@ -2209,73 +2580,16 @@ export class CLICommandFactory { topK: argv.ragTopK as number | undefined, } : undefined, - // TTS configuration - tts: enhancedOptions.tts - ? { - enabled: true, - useAiResponse: true, - voice: enhancedOptions.ttsVoice as string | undefined, - format: - (enhancedOptions.ttsFormat as "mp3" | "wav" | "ogg" | "opus") || - undefined, - speed: enhancedOptions.ttsSpeed as number | undefined, - quality: enhancedOptions.ttsQuality as - | "standard" - | "hd" - | undefined, - output: enhancedOptions.ttsOutput as string | undefined, - play: enhancedOptions.ttsPlay as boolean | undefined, - } - : undefined, }); - if (spinner) { - if (isVideoMode) { - spinner.succeed(chalk.green("✅ Video generated successfully!")); - } else { - spinner.succeed(chalk.green("✅ Text generated successfully!")); - } - } - - // Display provider and model info by default (unless quiet mode) - if (!options.quiet) { - const providerInfo = result.provider || "auto"; - const modelInfo = result.model || "default"; - logger.always( - chalk.gray(`🔧 Provider: ${providerInfo} | Model: ${modelInfo}`), - ); - } - - // Handle output with universal formatting (for text mode) - if (!isVideoMode) { - CLICommandFactory.handleOutput(result, options); - } - - // Handle TTS audio file output if --tts-output is provided - await CLICommandFactory.handleTTSOutput(result, options); - - // Handle video file output if --videoOutput is provided - await CLICommandFactory.handleVideoOutput(result, options); - - if (options.debug) { - logger.debug("\n" + chalk.yellow("Debug Information:")); - logger.debug("Provider:", result.provider); - logger.debug("Model:", result.model); - if (result.analytics) { - logger.debug("Analytics:", JSON.stringify(result.analytics, null, 2)); - } - if (result.evaluation) { - logger.debug( - "Evaluation:", - JSON.stringify(result.evaluation, null, 2), - ); - } - } - - if (!globalSession.getCurrentSessionId()) { - await CLICommandFactory.flushLangfuseTraces(); - process.exit(0); - } + // Handle successful result + await this.handleGenerateSuccess( + result, + options, + isVideoMode, + isPPTMode, + spinner, + ); } catch (error) { if (spinner) { spinner.fail(); diff --git a/src/lib/features/ppt/index.ts b/src/lib/features/ppt/index.ts index d933eb907..f325317ca 100644 --- a/src/lib/features/ppt/index.ts +++ b/src/lib/features/ppt/index.ts @@ -158,6 +158,7 @@ export { toError, isObject, isLogoConfig, + validateImageBuffer, } from "./utils.js"; // Re-export EffectivePPTProviderResult type from types diff --git a/src/lib/types/cli.ts b/src/lib/types/cli.ts index 4b95020b0..3dc2efdc4 100644 --- a/src/lib/types/cli.ts +++ b/src/lib/types/cli.ts @@ -82,8 +82,8 @@ export type GenerateCommandArgs = BaseCommandArgs & { /** Vertex AI region */ region?: string; // Video generation options (Veo 3.1) - /** Output mode - 'text' for standard generation, 'video' for video generation */ - outputMode?: "text" | "video"; + /** Output mode - 'text' for standard generation, 'video' for video generation, 'ppt' for presentation */ + outputMode?: "text" | "video" | "ppt"; /** Path to save generated video file */ videoOutput?: string; /** Video output resolution (720p or 1080p) */ @@ -94,6 +94,21 @@ export type GenerateCommandArgs = BaseCommandArgs & { videoAspectRatio?: "9:16" | "16:9"; /** Enable/disable audio generation in video */ videoAudio?: boolean; + // PPT generation options + /** Number of slides to generate (5-50) */ + pptPages?: number; + /** Presentation theme/style */ + pptTheme?: "modern" | "corporate" | "creative" | "minimal" | "dark"; + /** Target audience */ + pptAudience?: "business" | "students" | "technical" | "general"; + /** Presentation tone/style */ + pptTone?: "professional" | "casual" | "educational" | "persuasive"; + /** Path to save generated PPTX file */ + pptOutput?: string; + /** PPT aspect ratio */ + pptAspectRatio?: "16:9" | "4:3"; + /** Disable AI image generation for PPT slides */ + pptNoImages?: boolean; /** Custom path for generated image output */ imageOutput?: string; }; @@ -410,6 +425,8 @@ export type GenerateResult = CommandResult & { audio?: import("./index.js").TTSResult; /** Video generation result when video mode is enabled */ video?: import("./multimodal.js").VideoGenerationResult; + /** PPT generation result when ppt mode is enabled */ + ppt?: import("./pptTypes.js").PPTGenerationResult; imageOutput?: { base64: string; savedPath?: string; // Local file path where image was saved diff --git a/src/lib/types/generateTypes.ts b/src/lib/types/generateTypes.ts index a09162d73..3acfe1650 100644 --- a/src/lib/types/generateTypes.ts +++ b/src/lib/types/generateTypes.ts @@ -516,8 +516,8 @@ export type GenerateResult = { * }); * * if (result.ppt) { - * console.log(`Generated ${result.ppt.slides.length} slides`); - * console.log(`Title: ${result.ppt.slides[0].title}`); + * console.log(`Generated ${result.ppt.totalSlides} slides`); + * console.log(`Saved at: ${result.ppt.filePath}`); * } * ``` */ diff --git a/test/unit/cli/ppt-flags.test.ts b/test/unit/cli/ppt-flags.test.ts new file mode 100644 index 000000000..f314b2ce4 --- /dev/null +++ b/test/unit/cli/ppt-flags.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect } from "vitest"; +import yargs from "yargs"; +import { CLICommandFactory } from "../../../src/cli/factories/commandFactory.js"; + +const buildGenerateParser = () => { + const command = CLICommandFactory.createGenerateCommand(); + const parser = yargs([]).exitProcess(false).help(false).version(false); + + if (command.builder) { + return command.builder(parser); + } + + return parser; +}; + +describe("PPT CLI Flags Configuration", () => { + it("should expose PPT aliases on generate command", () => { + const parser = buildGenerateParser(); + const options = parser.getOptions(); + const optionNames = Object.keys(options.alias); + + expect(optionNames).toContain("pptPages"); + expect(optionNames).toContain("pptOutput"); + + expect(options.alias.pptPages).toContain("pages"); + expect(options.alias.pptOutput).toContain("po"); + }); + + it("should parse PPT mode options and aliases into normalized argv", () => { + const parser = buildGenerateParser(); + + const argv = parser.parseSync([ + "AI roadmap", + "--outputMode", + "ppt", + "--pages", + "12", + "--pptTheme", + "modern", + "--pptAudience", + "technical", + "--pptTone", + "educational", + "--po", + "./slides.pptx", + "--pptAspectRatio", + "4:3", + "--pptNoImages", + ]); + + expect(argv.outputMode).toBe("ppt"); + expect(argv.pptPages).toBe(12); + expect(argv.pptTheme).toBe("modern"); + expect(argv.pptAudience).toBe("technical"); + expect(argv.pptTone).toBe("educational"); + expect(argv.pptOutput).toBe("./slides.pptx"); + expect(argv.pptAspectRatio).toBe("4:3"); + expect(argv.pptNoImages).toBe(true); + }); + + it("should keep pptNoImages default as false when not provided", () => { + const parser = buildGenerateParser(); + const argv = parser.parseSync(["AI roadmap", "--outputMode", "ppt"]); + + expect(argv.pptNoImages).toBe(false); + }); + + it("should fail fast when video and PPT mode signals are mixed", () => { + const detectGenerateOutputMode = ( + CLICommandFactory as unknown as { + detectGenerateOutputMode: ( + argv: Record, + options: Record, + ) => { + isVideoMode: boolean; + isPPTMode: boolean; + spinnerMessage: string; + }; + } + ).detectGenerateOutputMode; + + expect(() => + detectGenerateOutputMode( + { + pptPages: 10, + pptTheme: undefined, + pptAudience: undefined, + pptTone: undefined, + pptOutput: undefined, + pptAspectRatio: undefined, + pptNoImages: false, + videoOutput: undefined, + }, + { outputMode: "video" }, + ), + ).toThrow(/Conflicting output mode signals detected/); + }); +}); diff --git a/test/unit/ppt-generation.test.ts b/test/unit/ppt-generation.test.ts index f0b16abab..dcf0c82d0 100644 --- a/test/unit/ppt-generation.test.ts +++ b/test/unit/ppt-generation.test.ts @@ -1,6 +1,15 @@ +// ----------------------------------------------------------------------- +// Imports +// ----------------------------------------------------------------------- + import { beforeEach, describe, expect, it, vi } from "vitest"; + +// Constants & Enums import { AIProviderName } from "../../src/lib/constants/enums.js"; + +// PPT Feature - Main exports (use index.ts as primary source) import { + // Constants AUDIENCE_GUIDELINES, buildContentPlanningPrompt, DIAGRAM_SLIDE_TYPES, @@ -17,49 +26,70 @@ import { VALID_AUDIENCES, VALID_THEMES, VALID_TONES, -} from "../../src/lib/features/ppt/constants.js"; -import { + // Slide Generator createSlideGenerator, generateSlidesFromPlan, - type LogoConfig, - type LogoPosition, PptxGenJS, SlideGenerator, - type SlideGeneratorConfig, -} from "../../src/lib/features/ppt/slideGenerator.js"; -import type { - CompleteSlide, - PresentationTheme, -} from "../../src/lib/features/ppt/types.js"; -import { extractPPTContext } from "../../src/lib/features/ppt/utils.js"; -import type { GenerateOptions } from "../../src/lib/types/index.js"; + // Slide Type Inference + inferFromTitle, + inferBulletStyleFromContent, + // Utilities + extractPPTContext, + generateOutputPath, + normalizeLogoConfig, + getLayoutName, + toError, + getFailureStage, + isObject, + isLogoConfig, + validateImageBuffer, + PPT_VALID_PROVIDERS, + // Validation + validatePPTGenerationInput as orchestratorValidateInput, +} from "../../src/lib/features/ppt/index.js"; + +// Types - from pptTypes.ts (canonical source for all PPT types) import { - type BulletPoint, - type ChartOptions, - type ChartSeries, - type ComparisonColumn, - type ContentPlan, - type ImageProps, isValidHexColor, MAX_SLIDES, MIN_SLIDES, normalizeHexColor, - type PositionProps, PPT_ERROR_CODES, PPTError, - type PPTOutputOptions, - type ProcessStep, - type ShadowProps, - type ShapeProps, SLIDE_DIMENSIONS, - type SlideLayout, - type SlideSchema, - type SlideType, - type Statistic, - type TableCell, - type TableOptions, - type TimelineItem, } from "../../src/lib/types/pptTypes.js"; + +import type { + BulletPoint, + ChartOptions, + ChartSeries, + ComparisonColumn, + CompleteSlide, + ContentPlan, + ImageProps, + LogoConfig, + LogoPosition, + PositionProps, + PPTOutputOptions, + PresentationTheme, + ProcessStep, + ShadowProps, + ShapeProps, + SlideGeneratorConfig, + SlideLayout, + SlideSchema, + SlideType, + Statistic, + TableCell, + TableOptions, + TimelineItem, +} from "../../src/lib/types/pptTypes.js"; + +// Types - Core +import type { GenerateOptions } from "../../src/lib/types/index.js"; + +// Validation Utils import { validatePPTGenerationInput, validatePPTOutputOptions, @@ -218,7 +248,7 @@ describe("PPT Validation", () => { // ----------------------------------------------------------------------- describe("PPT Types & Layouts", () => { - it("should have all 31 slide types defined", () => { + it("should have all 35 slide types defined", () => { const allSlideTypes: SlideType[] = [ "title", "section-header", @@ -251,6 +281,10 @@ describe("PPT Types & Layouts", () => { "icons", "conclusion", "blank", + "dashboard", + "mixed-content", + "stats-grid", + "icon-grid", ]; allSlideTypes.forEach((type) => { expect(SLIDE_TYPE_TO_LAYOUT[type]).toBeDefined(); @@ -1230,11 +1264,6 @@ describe("E2E Integration", () => { // Presentation Orchestrator Tests (Stage 4) // ----------------------------------------------------------------------- -import { - validatePPTGenerationInput as orchestratorValidateInput, - type PPTValidationResult, -} from "../../src/lib/features/ppt/index.js"; - describe("Presentation Orchestrator", () => { describe("validatePPTGenerationInput", () => { it("should validate valid PPT generation options", () => { @@ -1545,3 +1574,858 @@ describe("Presentation Orchestrator", () => { }); }); }); + +// ----------------------------------------------------------------------- +// Slide Type Inference Tests +// ----------------------------------------------------------------------- + +describe("Slide Type Inference", () => { + describe("inferFromTitle", () => { + it("should infer agenda slide type from agenda keywords", () => { + const result = inferFromTitle("Agenda"); + expect(result.slideType).toBe("agenda"); + expect(result.bulletStyle).toBe("number"); + }); + + it("should infer agenda from table of contents", () => { + const result = inferFromTitle("Table of Contents"); + expect(result.slideType).toBe("agenda"); + expect(result.bulletStyle).toBe("number"); + }); + + it("should infer conclusion from summary keywords", () => { + const result = inferFromTitle("Key Takeaways"); + expect(result.slideType).toBe("conclusion"); + expect(result.bulletStyle).toBe("checkmark"); + }); + + it("should infer closing from thank you", () => { + const result = inferFromTitle("Thank You"); + expect(result.slideType).toBe("closing"); + expect(result.bulletStyle).toBe("checkmark"); + }); + + it("should infer comparison from vs keywords", () => { + const result = inferFromTitle("Pros and Cons"); + expect(result.slideType).toBe("comparison"); + expect(result.bulletStyle).toBe("arrow"); + }); + + it("should infer numbered-list from process keywords", () => { + const result = inferFromTitle("Implementation Steps"); + expect(result.slideType).toBe("numbered-list"); + expect(result.bulletStyle).toBe("number"); + }); + + it("should infer features from features keywords", () => { + const result = inferFromTitle("Features"); + expect(result.slideType).toBe("features"); + expect(result.bulletStyle).toBe("disc"); + }); + + it("should return null for unrecognized titles", () => { + const result = inferFromTitle("Random Title XYZ 123"); + expect(result.slideType).toBeNull(); + expect(result.bulletStyle).toBeNull(); + }); + + it("should handle case insensitivity", () => { + const result = inferFromTitle("AGENDA"); + expect(result.slideType).toBe("agenda"); + }); + + it("should handle whitespace in title", () => { + const result = inferFromTitle(" Conclusion "); + expect(result.slideType).toBe("conclusion"); + }); + }); + + describe("inferBulletStyleFromContent", () => { + it("should infer number style from numbered bullets", () => { + const bullets: BulletPoint[] = [ + { text: "1. First item", emphasis: false }, + { text: "2. Second item", emphasis: false }, + ]; + expect(inferBulletStyleFromContent(bullets)).toBe("number"); + }); + + it("should infer checkmark from checkmark unicode", () => { + const bullets: BulletPoint[] = [ + { text: "✓ Completed task", emphasis: false }, + { text: "✔ Another done", emphasis: false }, + ]; + expect(inferBulletStyleFromContent(bullets)).toBe("checkmark"); + }); + + it("should infer arrow from arrow unicode", () => { + const bullets: BulletPoint[] = [ + { text: "→ Action item", emphasis: false }, + { text: "➤ Another action", emphasis: false }, + ]; + expect(inferBulletStyleFromContent(bullets)).toBe("arrow"); + }); + + it("should return null for dashed bullets (not in CONTENT_PATTERNS)", () => { + // Note: "- " pattern is not in CONTENT_PATTERNS, so returns null + const bullets: BulletPoint[] = [ + { text: "- Item one", emphasis: false }, + { text: "- Item two", emphasis: false }, + ]; + expect(inferBulletStyleFromContent(bullets)).toBeNull(); + }); + + it("should return null for mixed or undetectable patterns", () => { + const bullets: BulletPoint[] = [ + { text: "Plain text item", emphasis: false }, + { text: "Another plain item", emphasis: false }, + ]; + expect(inferBulletStyleFromContent(bullets)).toBeNull(); + }); + + it("should handle empty bullet array", () => { + expect(inferBulletStyleFromContent([])).toBeNull(); + }); + }); +}); + +// ----------------------------------------------------------------------- +// PPT Utils Tests +// ----------------------------------------------------------------------- + +describe("PPT Utils", () => { + describe("generateOutputPath", () => { + it("should use provided outputPath with .pptx extension", () => { + const context = { + topic: "Test Topic", + pages: 10, + outputPath: "/custom/path/presentation.pptx", + theme: "modern", + audience: "business", + tone: "professional", + generateAIImages: false, + aspectRatio: "16:9" as const, + }; + expect(generateOutputPath(context)).toBe( + "/custom/path/presentation.pptx", + ); + }); + + it("should append filename when outputPath is directory", () => { + const context = { + topic: "My Test Presentation", + pages: 10, + outputPath: "/custom/directory", + theme: "modern", + audience: "business", + tone: "professional", + generateAIImages: false, + aspectRatio: "16:9" as const, + }; + const result = generateOutputPath(context); + expect(result).toContain("/custom/directory/"); + expect(result).toContain("my_test_presentation"); + expect(result.endsWith(".pptx")).toBe(true); + }); + + it("should generate default path when no outputPath provided", () => { + const context = { + topic: "Test", + pages: 10, + theme: "modern", + audience: "general", + tone: "professional", + generateAIImages: false, + aspectRatio: "16:9" as const, + }; + const result = generateOutputPath(context); + expect(result).toContain("output"); + expect(result.endsWith(".pptx")).toBe(true); + }); + + it("should sanitize special characters in topic", () => { + const context = { + topic: "Test@#$%^&* Special/\\Characters", + pages: 10, + theme: "modern", + audience: "general", + tone: "professional", + generateAIImages: false, + aspectRatio: "16:9" as const, + }; + const result = generateOutputPath(context); + expect(result).not.toContain("@"); + expect(result).not.toContain("$"); + expect(result).not.toContain("/\\"); + }); + }); + + describe("normalizeLogoConfig", () => { + it("should return null for null input", () => { + expect(normalizeLogoConfig(null)).toBeNull(); + }); + + it("should normalize Buffer to LogoConfig", () => { + const buffer = Buffer.from("test"); + const result = normalizeLogoConfig(buffer); + expect(result).not.toBeNull(); + expect(result?.data).toBe(buffer); + expect(result?.position).toBe("bottom-right"); + expect(result?.width).toBe(1); + expect(result?.height).toBe(0.4); + }); + + it("should normalize string to LogoConfig", () => { + const dataUrl = "data:image/png;base64,abc123"; + const result = normalizeLogoConfig(dataUrl); + expect(result).not.toBeNull(); + expect(result?.data).toBe(dataUrl); + expect(result?.showOn).toBe("all-slides"); + }); + + it("should pass through valid LogoConfig", () => { + const config = { + data: "data:image/png;base64,test", + position: "top-left" as const, + width: 2, + height: 1, + showOn: "first-slide" as const, + }; + const result = normalizeLogoConfig(config); + expect(result).toEqual(config); + }); + }); + + describe("getLayoutName", () => { + it("should return LAYOUT_16x9 for 16:9", () => { + expect(getLayoutName("16:9")).toBe("LAYOUT_16x9"); + }); + + it("should return LAYOUT_4x3 for 4:3", () => { + expect(getLayoutName("4:3")).toBe("LAYOUT_4x3"); + }); + + it("should default to LAYOUT_16x9 for unknown", () => { + expect(getLayoutName("unknown" as "16:9")).toBe("LAYOUT_16x9"); + }); + }); + + describe("toError", () => { + it("should return Error as-is", () => { + const error = new Error("test"); + expect(toError(error)).toBe(error); + }); + + it("should convert string to Error", () => { + const result = toError("error message"); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("error message"); + }); + + it("should convert object to Error", () => { + // Note: String({ code: 500 }) returns "[object Object]" + const result = toError({ code: 500 }); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe("[object Object]"); + }); + + it("should handle null/undefined", () => { + expect(toError(null).message).toBe("null"); + expect(toError(undefined).message).toBe("undefined"); + }); + }); + + describe("getFailureStage", () => { + it("should return content-planning when contentPlan is null", () => { + expect( + getFailureStage({ contentPlan: null, slides: null, outputPath: null }), + ).toBe("content-planning"); + }); + + it("should return slide-generation when slides is null", () => { + expect( + getFailureStage({ contentPlan: {}, slides: null, outputPath: null }), + ).toBe("slide-generation"); + }); + + it("should return pptx-assembly when outputPath is null", () => { + expect( + getFailureStage({ contentPlan: {}, slides: [], outputPath: null }), + ).toBe("pptx-assembly"); + }); + + it("should return file-output when all stages complete", () => { + expect( + getFailureStage({ + contentPlan: {}, + slides: [], + outputPath: "/path/file.pptx", + }), + ).toBe("file-output"); + }); + }); + + describe("validateImageBuffer", () => { + it("should validate PNG images", () => { + // PNG magic bytes: 89 50 4E 47 + const pngBuffer = Buffer.concat([ + Buffer.from([0x89, 0x50, 0x4e, 0x47]), + Buffer.alloc(100), + ]); + const result = validateImageBuffer(pngBuffer); + expect(result.isValid).toBe(true); + expect(result.format).toBe("PNG"); + expect(result.mimeType).toBe("image/png"); + }); + + it("should validate JPEG images", () => { + // JPEG magic bytes: FF D8 FF + const jpegBuffer = Buffer.concat([ + Buffer.from([0xff, 0xd8, 0xff]), + Buffer.alloc(100), + ]); + const result = validateImageBuffer(jpegBuffer); + expect(result.isValid).toBe(true); + expect(result.format).toBe("JPEG"); + expect(result.mimeType).toBe("image/jpeg"); + }); + + it("should validate GIF images", () => { + // GIF magic bytes: 47 49 46 + const gifBuffer = Buffer.concat([ + Buffer.from([0x47, 0x49, 0x46]), + Buffer.alloc(100), + ]); + const result = validateImageBuffer(gifBuffer); + expect(result.isValid).toBe(true); + expect(result.format).toBe("GIF"); + expect(result.mimeType).toBe("image/gif"); + }); + + it("should reject empty buffer", () => { + const result = validateImageBuffer(Buffer.alloc(0)); + expect(result.isValid).toBe(false); + expect(result.error).toContain("Empty"); + }); + + it("should reject undefined buffer", () => { + const result = validateImageBuffer(undefined); + expect(result.isValid).toBe(false); + }); + + it("should reject too small buffer", () => { + const result = validateImageBuffer(Buffer.alloc(50)); + expect(result.isValid).toBe(false); + expect(result.error).toContain("too small"); + }); + + it("should reject unknown format", () => { + const unknownBuffer = Buffer.concat([ + Buffer.from([0x00, 0x00, 0x00, 0x00]), + Buffer.alloc(100), + ]); + const result = validateImageBuffer(unknownBuffer); + expect(result.isValid).toBe(false); + expect(result.error).toContain("Unknown format"); + }); + }); + + describe("isObject", () => { + it("should return true for plain objects", () => { + expect(isObject({})).toBe(true); + expect(isObject({ key: "value" })).toBe(true); + }); + + it("should return false for null", () => { + expect(isObject(null)).toBe(false); + }); + + it("should return false for arrays", () => { + expect(isObject([])).toBe(false); + expect(isObject([1, 2, 3])).toBe(false); + }); + + it("should return false for primitives", () => { + expect(isObject("string")).toBe(false); + expect(isObject(123)).toBe(false); + expect(isObject(true)).toBe(false); + }); + }); + + describe("isLogoConfig", () => { + it("should return true for valid LogoConfig with Buffer", () => { + expect(isLogoConfig({ data: Buffer.from("test") })).toBe(true); + }); + + it("should return true for valid LogoConfig with string", () => { + expect(isLogoConfig({ data: "data:image/png;base64,test" })).toBe(true); + }); + + it("should return false for invalid structures", () => { + expect(isLogoConfig(null)).toBe(false); + expect(isLogoConfig({})).toBe(false); + expect(isLogoConfig({ data: 123 })).toBe(false); + expect(isLogoConfig("string")).toBe(false); + }); + }); + + describe("PPT_VALID_PROVIDERS", () => { + it("should include all supported providers", () => { + expect(PPT_VALID_PROVIDERS).toContain("vertex"); + expect(PPT_VALID_PROVIDERS).toContain("openai"); + expect(PPT_VALID_PROVIDERS).toContain("anthropic"); + expect(PPT_VALID_PROVIDERS).toContain("google-ai"); + expect(PPT_VALID_PROVIDERS).toContain("azure"); + expect(PPT_VALID_PROVIDERS).toContain("bedrock"); + }); + + it("should have correct length", () => { + expect(PPT_VALID_PROVIDERS.length).toBe(6); + }); + }); +}); + +// ----------------------------------------------------------------------- +// Additional Slide Type Rendering Tests +// ----------------------------------------------------------------------- + +describe("Complete Slide Type Rendering Coverage", () => { + const gen = createSlideGenerator(createMockConfig()); + const ppt = () => { + const p = new PptxGenJS(); + p.layout = "LAYOUT_16x9"; + return p; + }; + const render = (schema: SlideSchema) => + gen.renderSlide( + ppt(), + { slideNumber: 1, schema, generationTime: 10 }, + 1, + 10, + ); + + describe("Opening/Closing slide types", () => { + it("should render section-header", () => { + expect(() => + render( + createMockSlideSchema({ + type: "section-header", + layout: "title-centered", + title: "Section 1", + content: { subtitle: "Introduction" }, + }), + ), + ).not.toThrow(); + }); + + it("should render thank-you", () => { + expect(() => + render( + createMockSlideSchema({ + type: "thank-you", + layout: "contact-info", + title: "Thank You!", + content: { cta: "Questions?", contactInfo: "email@test.com" }, + }), + ), + ).not.toThrow(); + }); + + it("should render closing", () => { + expect(() => + render( + createMockSlideSchema({ + type: "closing", + layout: "thank-you", + title: "The End", + content: { cta: "Let's Connect" }, + }), + ), + ).not.toThrow(); + }); + }); + + describe("Content slide types", () => { + it("should render agenda", () => { + expect(() => + render( + createMockSlideSchema({ + type: "agenda", + layout: "title-content", + title: "Agenda", + content: { + bullets: [ + { text: "Topic 1", emphasis: false }, + { text: "Topic 2", emphasis: false }, + ], + }, + }), + ), + ).not.toThrow(); + }); + + it("should render numbered-list", () => { + expect(() => + render( + createMockSlideSchema({ + type: "numbered-list", + layout: "title-content", + title: "Steps", + content: { + bullets: [ + { text: "Step 1", emphasis: false }, + { text: "Step 2", emphasis: false }, + ], + }, + }), + ), + ).not.toThrow(); + }); + }); + + describe("Visual slide types", () => { + it("should render image-left", () => { + expect(() => + render( + createMockSlideSchema({ + type: "image-left", + layout: "image-left-content-right", + title: "Image Left", + content: { body: "Content on right side" }, + }), + ), + ).not.toThrow(); + }); + + it("should render image-right", () => { + expect(() => + render( + createMockSlideSchema({ + type: "image-right", + layout: "image-right-content-left", + title: "Image Right", + content: { body: "Content on left side" }, + }), + ), + ).not.toThrow(); + }); + + it("should render full-bleed-image", () => { + expect(() => + render( + createMockSlideSchema({ + type: "full-bleed-image", + layout: "image-full-overlay", + title: "Full Image", + content: {}, + }), + ), + ).not.toThrow(); + }); + + it("should render gallery", () => { + expect(() => + render( + createMockSlideSchema({ + type: "gallery", + layout: "image-grid-2x2", + title: "Gallery", + content: {}, + }), + ), + ).not.toThrow(); + }); + }); + + describe("Layout slide types", () => { + it("should render three-column", () => { + expect(() => + render( + createMockSlideSchema({ + type: "three-column", + layout: "three-column-equal", + title: "Three Columns", + content: { + leftColumn: { title: "Col 1", bullets: [] }, + centerColumn: { title: "Col 2", bullets: [] }, + rightColumn: { title: "Col 3", bullets: [] }, + }, + }), + ), + ).not.toThrow(); + }); + + it("should render split-content", () => { + expect(() => + render( + createMockSlideSchema({ + type: "split-content", + layout: "two-column-equal", + title: "Split Content", + content: { + leftColumn: { title: "Left", bullets: [] }, + rightColumn: { title: "Right", bullets: [] }, + }, + }), + ), + ).not.toThrow(); + }); + }); + + describe("Data slide types", () => { + it("should render chart-line", () => { + expect(() => + render( + createMockSlideSchema({ + type: "chart-line", + content: { + chartData: { + type: "line", + title: "Trends", + series: [ + { name: "Data", labels: ["Q1", "Q2"], values: [10, 20] }, + ], + }, + }, + }), + ), + ).not.toThrow(); + }); + + it("should render chart-pie", () => { + expect(() => + render( + createMockSlideSchema({ + type: "chart-pie", + content: { + chartData: { + type: "pie", + title: "Distribution", + series: [ + { name: "Share", labels: ["A", "B"], values: [60, 40] }, + ], + }, + }, + }), + ), + ).not.toThrow(); + }); + + it("should render chart-area", () => { + expect(() => + render( + createMockSlideSchema({ + type: "chart-area", + content: { + chartData: { + type: "area", + title: "Area Chart", + series: [ + { name: "Values", labels: ["1", "2"], values: [5, 15] }, + ], + }, + }, + }), + ), + ).not.toThrow(); + }); + }); + + describe("Special slide types", () => { + it("should render process-flow", () => { + expect(() => + render( + createMockSlideSchema({ + type: "process-flow", + layout: "process-horizontal", + title: "Process", + content: { + processSteps: [ + { step: 1, title: "Start", description: "Begin here" }, + { step: 2, title: "Middle", description: "Continue" }, + { step: 3, title: "End", description: "Finish" }, + ], + }, + }), + ), + ).not.toThrow(); + }); + + it("should render features", () => { + expect(() => + render( + createMockSlideSchema({ + type: "features", + layout: "icon-grid", + title: "Features", + content: { + features: [ + { name: "Feature 1", description: "Description 1" }, + { name: "Feature 2", description: "Description 2" }, + ], + }, + }), + ), + ).not.toThrow(); + }); + + it("should render team", () => { + expect(() => + render( + createMockSlideSchema({ + type: "team", + layout: "team-grid", + title: "Our Team", + content: { + team: [ + { name: "Alice", role: "CEO" }, + { name: "Bob", role: "CTO" }, + ], + }, + }), + ), + ).not.toThrow(); + }); + + it("should render icons", () => { + expect(() => + render( + createMockSlideSchema({ + type: "icons", + layout: "icon-grid", + title: "Icons", + content: { + icons: [ + { icon: "star", label: "Quality" }, + { icon: "rocket", label: "Speed" }, + ], + }, + }), + ), + ).not.toThrow(); + }); + + it("should render conclusion", () => { + expect(() => + render( + createMockSlideSchema({ + type: "conclusion", + layout: "summary-bullets", + title: "Conclusion", + content: { + bullets: [ + { text: "Key point 1", emphasis: true }, + { text: "Key point 2", emphasis: false }, + ], + }, + }), + ), + ).not.toThrow(); + }); + }); +}); + +// ----------------------------------------------------------------------- +// Context Edge Cases Tests +// ----------------------------------------------------------------------- + +describe("PPT Context Edge Cases", () => { + it("should handle logo from input.images[0] as fallback", () => { + const options: GenerateOptions = { + input: { + text: "Test Presentation", + images: [Buffer.from("logo-data")], + }, + output: { mode: "ppt", ppt: { pages: 10 } }, + }; + + const context = extractPPTContext(options); + expect(context.logo).toBeDefined(); + expect(context.images).toHaveLength(1); + }); + + it("should prioritize logoPath over input.images", () => { + const logoBuffer = Buffer.from("explicit-logo"); + const options: GenerateOptions = { + input: { + text: "Test Presentation", + images: [Buffer.from("fallback-logo")], + }, + output: { + mode: "ppt", + ppt: { + pages: 10, + logoPath: logoBuffer, + }, + }, + }; + + const context = extractPPTContext(options); + expect(context.logo).toBe(logoBuffer); + }); + + it("should extract multiple images from input.images", () => { + const options: GenerateOptions = { + input: { + text: "Multi-image presentation", + images: [ + Buffer.from("image1"), + Buffer.from("image2"), + "data:image/png;base64,image3", + ], + }, + output: { mode: "ppt", ppt: { pages: 10 } }, + }; + + const context = extractPPTContext(options); + expect(context.images).toHaveLength(3); + }); + + it("should handle very long topic by truncating in output path", () => { + const longTopic = "A".repeat(100); + const options: GenerateOptions = { + input: { text: longTopic }, + output: { mode: "ppt", ppt: { pages: 10 } }, + }; + + const context = extractPPTContext(options); + expect(context.topic).toBe(longTopic); + + // Output path should truncate + const outputPath = generateOutputPath(context); + const filename = outputPath.split("/").pop() || ""; + expect(filename.length).toBeLessThan(100); + }); +}); + +// ----------------------------------------------------------------------- +// Aspect Ratio Tests +// ----------------------------------------------------------------------- + +describe("Aspect Ratio Handling", () => { + it("should generate slides with 16:9 aspect ratio", async () => { + const gen = createSlideGenerator(createMockConfig({ aspectRatio: "16:9" })); + const result = await gen.generateSlide(createMockSlideSchema()); + expect(result).toBeDefined(); + }); + + it("should generate slides with 4:3 aspect ratio", async () => { + const gen = createSlideGenerator(createMockConfig({ aspectRatio: "4:3" })); + const result = await gen.generateSlide(createMockSlideSchema()); + expect(result).toBeDefined(); + }); + + it("should apply correct layout based on aspect ratio", () => { + const ppt16x9 = new PptxGenJS(); + ppt16x9.layout = getLayoutName("16:9"); + expect(ppt16x9.layout).toBe("LAYOUT_16x9"); + + const ppt4x3 = new PptxGenJS(); + ppt4x3.layout = getLayoutName("4:3"); + expect(ppt4x3.layout).toBe("LAYOUT_4x3"); + }); + + it("should have correct SLIDE_DIMENSIONS for both ratios", () => { + expect(SLIDE_DIMENSIONS["16:9"]).toEqual({ width: 10, height: 5.625 }); + expect(SLIDE_DIMENSIONS["4:3"]).toEqual({ width: 10, height: 7.5 }); + }); +});