---
title: "Generating AI-Powered speaker notes and story slides"
date: "`r Sys.Date()`"
output:
    rmarkdown::html_document:
        theme: "spacelab"
        highlight: "kate"
        toc: true
        toc_float: true
author: 
  - Yolanda Zhou ([`yolandazzz13`])

vignette: >
  %\VignetteIndexEntry{Deploy LLM}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
editor_options:
    markdown:
        wrap: 72
---


```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```


## Overview
This guide explains how to enrich your slides with AI in two complementary ways:

- **Speaker notes** with `get_ai_notes()` — attaches an AI-written analysis to each
  slide as PowerPoint *speaker notes* (hidden from the audience, visible to the
  presenter).
- **Story slides** with `add_ai_story()` — a post-processing step that reads a
  generated deck, asks an LLM to *tell the story of the data*, and inserts **real
  content slides** (a summary section at the front and a conclusions section at the
  end) that show in presentation mode.

Both features talk to an LLM through the [`ellmer`](https://ellmer.tidyverse.org)
package, so they work against a local model (Ollama) or a hosted provider
(Anthropic/Claude, OpenAI, DeepSeek).

> **Note — two different AI roles, don't conflate them.**
> `autoslider.core` ships an **MCP server** (`inst/mcp/autoslider_mcp_server.R`).
> When you drive it from an MCP client — **Claude Desktop / Claude Code**, or
> **CodeBuddy** — that client is helping with the *coding and programming* part:
> it orchestrates the pipeline tools (`list_programs`, `load_spec`,
> `run_pipeline`, `generate_slides`, `add_ai_notes`, `add_ai_story`)
> conversationally so you don't have to write the R by hand.
>
> The *insight generation* part — actually reading the tables and writing the
> narrative — is **not** done by the MCP client. It is performed by whatever LLM
> you configure through `ellmer`, and that call **requires an API key** (for a
> hosted provider) or a running local endpoint (for Ollama). In short: the MCP
> client writes and runs the code; `ellmer` (with your key) produces the analytical
> content. These can even be different models — e.g. Claude Code orchestrating a
> run whose notes are generated by DeepSeek.

## Prerequisite

Before you begin, ensure you have the following ready:

- A Prompt File (`prompt.yml`) containing the instructions for the AI model
  (needed for `get_ai_notes()`; `add_ai_story()` builds its own prompt from the
  tables and needs no prompt file).

- Access to an LLM: either a local instance (Ollama) or an API key for a hosted
  provider. API keys are read from the usual environment variables
  (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`) or passed directly.

Please see `autoslideR.Rmd` for some readily available spec and filter examples and detailed instructions on how to use them. If you do not have an LLM model installed yet, see appendix on how to deploy your own local LLM.

## Speaker notes workflow
The process involves three main steps: generating the initial slide data, adding the AI footnotes, and then creating the final PowerPoint file.

### Step 1: Initial Slide Generation
First, generate the core slide outputs from your specification file. This process reads your spec, filters it for the desired programs, and generates the basic table and plot objects.

```{r, eval = FALSE}
spec <- read_spec("path/to/your/spec.yml")
filters::load_filters("path/to/your/filter.yml")
prompt_list <- get_prompt_list("path/to/your/prompt.yml")

# Generate the initial outputs
outputs <- spec |>
  filter_spec(program %in% c("t_dm_slide")) |>
  generate_outputs(datasets = my_datasets) |>
  decorate_outputs()
```


### Step 2: Adding AI Footnotes
Next, pass the outputs object to the `get_ai_notes()` function. This function iterates through your outputs, and for each one that has a corresponding prompt, it communicates with the specified LLM model to generate a response in the form of a speaker note.

You can configure the function to point to different AI platforms.

#### Example: Using a Local LLM 

This is ideal for local development. It assumes you have Ollama on your local machine and is developing in a Docker container.

```{r, eval = FALSE}
outputs_ai <- get_ai_notes(
  outputs = outputs,
  prompt_list = prompt_list,
  platform = "ollama",
  base_url = "http://host.docker.internal:11434", # URL for Ollama if R in a Docker container
  model = "deepseek-r1:1.5b" # The LLM model name
)
```
The parameters you pass in might depend on your specific situation. In general:

- `platform`: Set to `ollama`.

- `base_url`: Points to where your `Ollama` instance is running.

- `model`: The name of a model you have pulled in `Ollama.`


### Step 3: Create the Final PowerPoint File
Finally, take the modified `outputs_ai` object and pass it to `generate_slides()` to create the presentation. The footnotes will be automatically included on the relevant slides.


```{r, eval = FALSE}
outputs_ai |>
  generate_slides(outfile = "My_AI_Presentation.pptx")
```

This will produce a PowerPoint file with your tables and plots, and enhanced with AI generated analysis.


## Story slides workflow

Speaker notes are easy to miss — they never appear in presentation mode. When you
want the narrative *on the slides themselves*, use `add_ai_story()`. It is a
**post-processing** step: you first generate a normal deck, then hand the `.pptx`
back to `add_ai_story()`, which reads the underlying tables, asks an LLM to tell
the story, and inserts real content slides:

- a **summary** section at the **front** of the deck (the key takeaways, before the
  detailed TLGs), and
- a **conclusions** section at the **end** (the main conclusions and caveats).

Each inserted slide is a title plus concise bullets, on an AI-chosen layout drawn
from a safe text-only subset (`Section Header`, `Title and Content`, `Title Only`)
intersected with the layouts your template actually ships — so the narrative always
renders.

### Step 1: Generate the content deck
Build and write a deck as usual.

```{r, eval = FALSE}
outputs <- spec |>
  filter_spec(program %in% c("t_dm_slide", "t_ae_slide")) |>
  generate_outputs(datasets = my_datasets) |>
  decorate_outputs()

generate_slides(outputs, outfile = "deck.pptx")
```

### Step 2: Add the AI story
Pass the same `outputs` (the data the story is told from) and the deck you just
wrote. The result is a new deck with the summary and conclusions sections inserted.

```{r, eval = FALSE}
# Using Anthropic / Claude (reads ANTHROPIC_API_KEY from the environment)
add_ai_story(
  outputs,
  infile   = "deck.pptx",
  outfile  = "deck_story.pptx",
  platform = "anthropic",
  model    = "claude-haiku-4-5",
  max_slides = 3        # cap on slides per section
)
```

The same call works against other providers — for example DeepSeek:

```{r, eval = FALSE}
add_ai_story(
  outputs,
  infile   = "deck.pptx",
  outfile  = "deck_story.pptx",
  platform = "deepseek",
  base_url = "https://api.deepseek.com",
  model    = "deepseek-chat"
)
```

`add_ai_story()` prefers the provider's native structured output; for providers
that do not support it (e.g. DeepSeek) it automatically falls back to a plain-text
JSON mode and parses the response, so all supported providers work.

> **Reminder:** as with speaker notes, the storytelling is produced by `ellmer`
> and therefore needs an API key (hosted providers) or a local Ollama endpoint —
> even when you are orchestrating the run through an MCP client such as Claude Code
> or CodeBuddy. The MCP client handles the *how* (running the pipeline); the LLM you
> point `ellmer` at handles the *what* (the analysis).

### Under the hood
`add_ai_story()` is a thin wrapper around two pieces you can also call directly:

- `get_ai_story(outputs, allowed_layouts, platform, ...)` — the LLM call. Returns a
  structured `list(summary = ..., conclusions = ...)` of slides.
- `add_story_slides(ppt, story)` — pure `officer` slide insertion (no network), so
  it is easy to test or to feed a hand-written story.

### From the MCP server
The same capability is exposed as the `add_ai_story` MCP tool. A typical
conversational flow from an MCP client (Claude Desktop/Code, CodeBuddy) is:
`load_spec` → `run_pipeline` → `generate_slides` → `add_ai_story`. Remember that the
tool still calls out to `ellmer` for the narrative, so the provider key must be
available to the R session running the MCP server.


## Appendix
### Running Your Local LLM

First of all, we need to download the Ollama tool at https://ollama.com/download. Once Ollama is installed, you can run any model from its library with a single command. For this example, we'll use deepseek-r1:1.5b.

Open your command-line tool (e.g., Terminal, Windows PowerShell).

Type the following command and press Enter. Ollama will automatically download the model, which may take a few minutes.

```{bash, eval=FALSE}
$ ollama run deepseek-r1:1.5b
```

After the process completes, you will see a success message and a new prompt, like this:
```{bash, eval=FALSE}
$ >>> Send a message (/? for help)
```
This means you have successfully installed and are now running a local LLM. Feel free to start a conversation and play around with some prompts!

