This project demonstrates the use of the com.cheroliv.slider plugin with Gradle 9.4.0 and Java 25 to create interactive presentations using AsciidoctorJ Reveal.js.
All slide generation logic is fully encapsulated in the slider-plugin/, keeping the consumer build script minimal.
AI-assisted generation relies on a two-step RAG pipeline:
proposeDeckContext prepares a pedagogical context file reviewed by the author,
generateDeck produces the final AsciiDoc deck enriched by project examples.
@startuml
left to right direction
actor "Developer" as Dev
actor "AI Provider\n(Ollama, Gemini, Mistral, HF)" as AI
actor "pgvector\n(Docker)" as PG
actor "Git Repository" as Git
rectangle "Gradle Slider Plugin" {
Dev --> (Initialize slides project)
Dev --> (Propose a deck context)
Dev --> (Generate a deck)
Dev --> (Reindex RAG store)
Dev --> (Compile slides)
Dev --> (Serve slides locally)
Dev --> (Publish slides)
(Propose a deck context) --> PG
(Propose a deck context) --> AI
(Generate a deck) --> PG
(Generate a deck) --> AI
(Reindex RAG store) --> PG
(Publish slides) --> Git
}
@enduml
@startuml start :Gradle starts; :Plugin applied; :Slides project initialization; if (proposeDeckContext ?) then (yes) :PgVectorService starts Docker; :RAG β retrieve relevant chunks; :LLM β propose DeckContext JSON; :Write <slug>-deck-context.yml; :PgVectorService stopped; endif note right Human review of deck-context.yml end note if (generateDeck ?) then (yes) :PgVectorService starts Docker; :RAG β AsciiDoc syntax examples; :LLM β generate full AsciiDoc; :Write <slug>_<lang>-deck.adoc; :PgVectorService stopped; endif if (build slides ?) then (yes) :Asciidoctor RevealJS; endif if (serve ?) then (yes) :npx serve; endif if (publish ?) then (yes) :Git push; endif stop @enduml
-
JDK 25 (tested with Eclipse Temurin 25.0.2), 23+ supported
-
Gradle Wrapper (version 9.4.0 included)
-
Docker (for the pgvector container used by the RAG pipeline)
-
Node.js / npx (for the
serveSlidestask) -
Internet connection (to download Reveal.js dependencies)
pluginManagement.repositories.gradlePluginPortal()
rootProject.name = "slider-gradle"plugins { alias(libs.plugins.slider) }
slider { configPath = file("slides-context.yml").absolutePath }.
βββ build.gradle.kts # Main Gradle configuration (consumer)
βββ settings.gradle.kts # pluginManagement configuration
βββ gradle/
β βββ libs.versions.toml # Dependency catalog
β βββ wrapper/ # Gradle Wrapper 9.4.0
βββ slides/
β βββ misc/ # AsciiDoc presentation sources
β βββ *_<lang>-deck.adoc # Generated decks (e.g. kotlin-intro_fr-deck.adoc)
β βββ *-deck-context.yml # AI generation contexts (no language code)
β βββ index.html # Presentation dashboard
β βββ images/ # Image resources
βββ slides-context.yml # Git push config + AI API keys
βββ slider-plugin/ # Gradle plugin (plugin source)
βββ README.adoc # This file
βββ README_fr.adoc # French version@startuml
package "Consumer Gradle Project" {
[build.gradle.kts]
[slides-context.yml]
[slides/]
}
package "Slider Gradle Plugin" {
[SliderPlugin]
[SliderExtension]
[SlidesInitializer]
[AssistantManager]
[RagManager]
[PgVectorService]
[SlidesBuilder]
[SlidesPublisher]
[SlidesServer]
}
package "External Systems" {
[AsciidoctorJ Reveal.js]
[Node npx serve]
[Git]
[pgvector\n(Docker)]
[AI Providers\n(Ollama / Gemini / Mistral / HF)]
}
[build.gradle.kts] --> [SliderPlugin]
[SliderPlugin] --> [SliderExtension]
[SliderPlugin] --> [SlidesInitializer]
[SliderPlugin] --> [AssistantManager]
[SliderPlugin] --> [SlidesBuilder]
[SliderPlugin] --> [SlidesPublisher]
[SliderPlugin] --> [SlidesServer]
[AssistantManager] --> [RagManager]
[AssistantManager] --> [PgVectorService]
[RagManager] --> [pgvector\n(Docker)]
[PgVectorService] --> [pgvector\n(Docker)]
[AssistantManager] --> [AI Providers\n(Ollama / Gemini / Mistral / HF)]
[SlidesBuilder] --> [AsciidoctorJ Reveal.js]
[SlidesServer] --> [Node npx serve]
[SlidesPublisher] --> [Git]
@enduml
@startuml actor Developer participant "Gradle" as Gradle participant "SliderPlugin" participant "SlidesInitializer" participant "PgVectorService\n(BuildService)" participant "RagManager" participant "AssistantManager" participant "Tasks" Developer -> Gradle : ./gradlew <task> Gradle -> SliderPlugin : apply() SliderPlugin -> SlidesInitializer : checkStructure() SlidesInitializer --> SliderPlugin : structure OK / resources generated SliderPlugin -> Tasks : register tasks Gradle -> Tasks : execute task opt RAG task (proposeDeckContext / generateDeck / reindexRag) Tasks -> PgVectorService : start() PgVectorService --> Tasks : assigned port Tasks -> RagManager : retrieve() / reindex() RagManager -> Tasks : RAG chunks Tasks -> AssistantManager : resolveModel(provider) AssistantManager --> Tasks : ChatModel Tasks --> Gradle : result Gradle -> PgVectorService : close() end @enduml
On the first execution of any Gradle task, the plugin checks whether the slides/ folder,
the slides-context.yml file and slides/misc/example-deck-context.yml are present and complete.
@startuml
start
:Gradle task execution;
if (slides/ exists ?) then (yes)
if (*-deck.adoc present ?) then (yes)
:continue;
else (no)
:extract slides template;
endif
else (no)
:extract slides.zip from classpath;
endif
if (slides-context.yml exists ?) then (yes)
:load configuration;
else (no)
:generate default SlidesConfiguration;
endif
if (example-deck-context.yml exists ?) then (yes)
:continue;
else (no)
:generate deck-context template;
endif
stop
@enduml
A slides/ folder is considered complete if slides/misc/ contains:
-
index.htmlβ presentation dashboard -
at least one
*-deck.adocsource file
If any of these conditions is not met, the plugin automatically extracts a default slides/
folder from the zip bundled in its classpath.
|
Note
|
deck.properties has been removed β decks are discovered by scanning *-deck.adocfiles directly in slides/misc/, in line with the <slug>_<lang>-deck.adoc convention.
|
If slides-context.yml is missing, the plugin automatically generates one from the
typed SlidesConfiguration model. This file contains the Git push configuration and
AI provider API keys:
srcPath: docs/asciidocRevealJs
pushSlides:
from: build/docs/asciidocRevealJs
to: build/slides-repo
branch: main
message: deploy slides
repo:
name: slides
repository: https://github.com/your-org/your-slides-repo.git
credentials:
username: your-username
password: your-token
ai:
gemini:
- your-gemini-api-key
mistral:
- your-mistral-api-key
huggingface:
- your-huggingface-api-key|
Note
|
slides-context.yml contains sensitive credentials β add it to .gitignore.
|
If slides/misc/example-deck-context.yml is missing, the plugin generates a ready-to-use template:
subject: "Your presentation subject"
audience: "Your target audience"
duration: 45
language: "en"
outputFile: "example_en-deck.adoc"
author:
name: "Your Name"
email: "your.email@example.com"
revealjs:
theme: "sky"
slideNumber: "c/t"
width: 1408
height: 792
notes:
speakerNotes: true
pageNotes: true
pageNotesStyle: "DETAILED"
slides:
- title: "Agenda"
speakerHint: "Present the outline in 2 minutes, ask what the audience already knows."
pageNotesHint: "List prerequisites and suggested readings."|
Note
|
If all three resources already exist, the plugin never modifies existing content. |
slider {
// Path to the YAML configuration file (required)
configPath = file("slides-context.yml").absolutePath
}asciidoctorRevealJs-
Compiles
.adocsources into an HTML Reveal.js presentation.
Slides are generated inbuild/docs/asciidocRevealJs/.
./gradlew asciidoctorRevealJsasciidoctor-
Runs standard Asciidoctor conversion (depends on
asciidoctorRevealJs). cleanSlidesBuild-
Deletes generated presentation artifacts from the
builddirectory.
./gradlew cleanSlidesBuilddashSlidesBuild-
Generates
index.htmlandslides.jsonlisting all available presentations.
serveSlides-
Serves slides via the serve package run by npx.
Ideal for quick local preview.
./gradlew serveSlidesasciidocCapsule-
(TODO) β Video capsule generation from slides. Currently logs a placeholder message.
./gradlew asciidocCapsulepublishSlides-
Deploys generated slides to the remote repository configured in
slides-context.yml.
./gradlew publishSlides@startuml actor Developer participant Gradle participant publishSlides participant "Local build" participant Git Developer -> Gradle : ./gradlew publishSlides Gradle -> publishSlides publishSlides -> "Local build" : collect compiled slides publishSlides -> Git : clone remote repository publishSlides -> Git : copy slides publishSlides -> Git : commit publishSlides -> Git : push (force) @enduml
The plugin integrates a RAG (Retrieval-Augmented Generation) pipeline to generate
complete AsciiDoc/Reveal.js decks. A pgvector store launched via Docker indexes the
projectβs AsciiDoc sources and enriches each LLM call with relevant examples.
Four providers are available: ollama (default), gemini, mistral and huggingface.
Select the provider with -Pai.provider.
@startuml
skinparam componentStyle uml2
package "Gradle Slider Plugin" {
component "ProposeDeckContextTask" as ProposeTask
component "GenerateDeckTask" as GenerateTask
component "ReindexRagTask" as ReindexTask
component "AssistantManager" as AM
component "RagManager" as RM
component "PgVectorService\n(BuildService)" as PGS
component "PromptManager" as PM
}
package "Infrastructure" {
component "pgvector\n(Docker)" as PG
component "AllMiniLmL6V2\n(ONNX in-process)" as Embed
}
package "LLM Providers" {
component "Ollama"
component "Gemini"
component "Mistral"
component "HuggingFace"
}
component "*-deck-context.yml" as CtxFile
component "<slug>_<lang>-deck.adoc" as DeckFile
ProposeTask --> PGS : start()
ProposeTask --> RM : retrieve(subject)
ProposeTask --> AM : resolveModel(provider)
ProposeTask --> PM : contextSystemPrompt / contextUserMessage
ProposeTask --> CtxFile : write
GenerateTask --> PGS : start()
GenerateTask --> RM : retrieve(AsciiDoc syntax)
GenerateTask --> AM : resolveModel(provider)
GenerateTask --> PM : deckSystemPrompt / deckUserMessage
GenerateTask --> DeckFile : write
ReindexTask --> PGS : start()
ReindexTask --> RM : reindex()
RM --> Embed : embedAll()
RM --> PG : search() / addAll() / removeAll()
PGS --> PG : docker start/stop
AM --> Ollama
AM --> Gemini
AM --> Mistral
AM --> HuggingFace
@enduml
@startuml
actor Developer
participant "Gradle\n(proposeDeckContext)" as Propose
participant "PgVectorService" as PGS
participant "RagManager" as RAG
participant "LLM Provider" as LLM
collections "*-deck-context.yml" as CtxFile
participant "Gradle\n(generateDeck)" as Generate
collections "<slug>_<lang>-deck.adoc" as DeckFile
== Step 1: proposeDeckContext ==
Developer -> Propose : ./gradlew proposeDeckContext\n -Psubject="..." -Planguage=en\n -Pai.provider=gemini --no-daemon
Propose -> PGS : start()
activate PGS
Propose -> RAG : retrieve(subject)
RAG --> Propose : RAG chunks
Propose -> LLM : contextSystemPrompt + userMessage
LLM --> Propose : DeckContext JSON
Propose -> CtxFile : write <slug>-deck-context.yml
Propose -> PGS : close() [end of build]
deactivate PGS
note over Developer : Review deck-context.yml
== Step 2: generateDeck ==
Developer -> Generate : ./gradlew generateDeck\n -Pdeck.context=slides/misc/<slug>-deck-context.yml\n -Pai.provider=gemini --no-daemon
Generate -> PGS : start()
activate PGS
Generate -> RAG : retrieve("AsciiDoc Reveal.js syntax <subject>")
RAG --> Generate : AsciiDoc syntax examples
Generate -> LLM : deckSystemPrompt + userMessage
LLM --> Generate : AsciiDoc content
Generate -> DeckFile : write <slug>_en-deck.adoc
Generate -> PGS : close() [end of build]
deactivate PGS
@enduml
Drops and fully rebuilds the pgvector index from project sources.
Run after adding or deleting source files.
./gradlew reindexRag --no-daemonProposes a *-deck-context.yml for a given subject, enriched by RAG context.
Author is resolved automatically from git config user.name / git config user.email.
# Default provider: ollama (local)
./gradlew proposeDeckContext \
-Psubject="Kotlin Coroutines" \
-Planguage=en \
--no-daemon
# Gemini
./gradlew proposeDeckContext \
-Psubject="Kotlin inline functions and reification" \
-Planguage=fr \
-Pai.provider=gemini \
--no-daemon
# Override author
./gradlew proposeDeckContext \
-Psubject="Spring Boot 3" \
-Planguage=en \
-Pai.provider=mistral \
-Pauthor.name="cheroliv" \
-Pauthor.email="cheroliv@example.com" \
--no-daemonOutput file: slides/misc/<slug>-deck-context.yml.
Review and adjust before running generateDeck.
Reads a validated *-deck-context.yml and generates the complete AsciiDoc deck,
enriched with AsciiDoc syntax examples retrieved from the RAG store.
# Gemini
./gradlew generateDeck \
-Pdeck.context=slides/misc/kotlin-inline-functions-and-reification-deck-context.yml \
-Pai.provider=gemini \
--no-daemon
# Mistral AI
./gradlew generateDeck \
-Pdeck.context=slides/misc/kotlin-coroutines-deck-context.yml \
-Pai.provider=mistral \
--no-daemonOutput file: slides/misc/<slug>_<lang>-deck.adoc
where <lang> is the ISO 639-1 code from the language field in deck-context.yml.
|
Note
|
Always use --no-daemon for RAG tasks. The Gradle daemon reuses the JVM processbetween builds, preventing native ONNX library reload (libtokenizers.so) and causing an UnsatisfiedLinkError on the second build.
|
| File | Pattern | Example |
|---|---|---|
Generation context |
|
|
Generated AsciiDoc deck |
|
|
Compiled HTML deck |
|
|
The <slug> is derived from the subject in kebab-case (accents normalized, special characters replaced by -).
The <lang> is the ISO 639-1 code passed via -Planguage (e.g. fr, en, de).
-Pai.provider |
Default model | API key in slides-context.yml |
|---|---|---|
|
|
none β local inference |
|
|
|
|
|
|
|
|
|
If -Pai.provider is absent or set to an unknown value, the task falls back to ollama and logs a warning.
subject: "Kotlin inline functions and reification"
audience: "intermediate Kotlin developers"
duration: 60
language: "en"
outputFile: "kotlin-inline-functions-and-reification_en-deck.adoc"
author:
name: "cheroliv"
email: "cheroliv@example.com"
revealjs:
theme: "sky"
slideNumber: "c/t"
width: 1408
height: 792
notes:
speakerNotes: true # generates [NOTE.speaker] on every slide
pageNotes: true # generates [.notes] on every slide
pageNotesStyle: "DETAILED" # MINIMAL | DETAILED | EXERCISES_ONLY
slides:
- title: "Why inline?"
speakerHint: "Start from the JVM cost of lambdas β object allocation on each call."
pageNotesHint: "JMH benchmarks: inline vs non-inline on a 100k-element collection."
- title: "reified: access the type at runtime"
speakerHint: "Show the type-erasure error, then the reified solution."
pageNotesHint: "Exercise: re-implement a typed version of Gson.fromJson() using reified."slides is optional β if empty, the LLM decides the slide structure freely.
| Style | Content generated in [.notes] |
|---|---|
|
One reference line only |
|
Deep content + references + exercises |
|
Practical exercises only |
# Step 1 β propose the context
./gradlew proposeDeckContext \
-Psubject="Kotlin inline functions and reification" \
-Planguage=en \
-Pai.provider=gemini \
--no-daemon
# β review slides/misc/kotlin-inline-functions-and-reification-deck-context.yml
# Step 2 β generate the deck
./gradlew generateDeck \
-Pdeck.context=slides/misc/kotlin-inline-functions-and-reification-deck-context.yml \
-Pai.provider=gemini \
--no-daemon
# Step 3 β compile and serve
./gradlew asciidoctorRevealJs serveSlides./gradlew serveSlides./gradlew cleanSlidesBuild asciidoctorRevealJs./gradlew asciidoctorRevealJs publishSlides@startuml
actor Developer
rectangle "Developer Environment" {
rectangle "Gradle Build System" {
component "Slider Gradle Plugin"
}
}
rectangle "Slider Internal Components" {
component "Slides Initializer"
component "AssistantManager"
component "RagManager"
component "PgVectorService"
component "Slides Builder"
component "Slides Publisher"
}
rectangle "RAG Pipeline" {
component "AllMiniLmL6V2\n(ONNX in-process)"
component "pgvector\n(Docker)"
component "PromptManager"
}
rectangle "External Systems" {
component "LLM Providers\n(Ollama / Gemini / HF / Mistral)"
component "Reveal.js / Asciidoctor"
component "Git Repository"
}
Developer --> "Gradle Build System"
"Gradle Build System" --> "Slider Gradle Plugin"
"Slider Gradle Plugin" --> "Slides Initializer"
"Slider Gradle Plugin" --> "AssistantManager"
"Slider Gradle Plugin" --> "Slides Builder"
"Slider Gradle Plugin" --> "Slides Publisher"
"AssistantManager" --> "RagManager"
"AssistantManager" --> "PgVectorService"
"AssistantManager" --> "PromptManager"
"AssistantManager" --> "LLM Providers\n(Ollama / Gemini / HF / Mistral)"
"RagManager" --> "AllMiniLmL6V2\n(ONNX in-process)"
"RagManager" --> "pgvector\n(Docker)"
"PgVectorService" --> "pgvector\n(Docker)"
"Slides Builder" --> "Reveal.js / Asciidoctor"
"Slides Publisher" --> "Git Repository"
@enduml
The plugin follows a hexagonal architecture that separates business logic,
external interfaces and infrastructure technologies.
This design guarantees LLM provider independence and high testability.
@startuml
skinparam componentStyle rectangle
package "Domain Core" {
component "Deck Generation Logic"
component "RAG Retrieval"
component "Presentation Context\n(DeckContext)"
}
package "Application Layer" {
component "AssistantManager"
component "RagManager"
component "SlidesInitializer"
component "SlidesPublisher"
}
package "Ports" {
interface "LLM Port"
interface "Embedding Store Port"
interface "Docker Service Port"
interface "Slides Renderer Port"
interface "Git Repository Port"
}
package "Adapters" {
component "Ollama Adapter"
component "Gemini Adapter"
component "Mistral Adapter"
component "HuggingFace Adapter"
component "PgVectorEmbeddingStore"
component "PgVectorService\n(docker-java)"
component "RevealJS Adapter"
component "Git Adapter"
component "Gradle Task Adapter\n(RagTask)"
}
"AssistantManager" --> "LLM Port"
"RagManager" --> "Embedding Store Port"
"RagManager" --> "Docker Service Port"
"SlidesPublisher" --> "Git Repository Port"
"SlidesInitializer" --> "Slides Renderer Port"
"LLM Port" --> "Ollama Adapter"
"LLM Port" --> "Gemini Adapter"
"LLM Port" --> "Mistral Adapter"
"LLM Port" --> "HuggingFace Adapter"
"Embedding Store Port" --> "PgVectorEmbeddingStore"
"Docker Service Port" --> "PgVectorService\n(docker-java)"
"Slides Renderer Port" --> "RevealJS Adapter"
"Git Repository Port" --> "Git Adapter"
"Gradle Task Adapter\n(RagTask)" --> "AssistantManager"
"Gradle Task Adapter\n(RagTask)" --> "RagManager"
"Gradle Task Adapter\n(RagTask)" --> "SlidesInitializer"
@enduml
This architecture delivers:
-
independence from LLM providers
-
swappable RAG store (pgvector today, any backend tomorrow)
-
ability to replace Reveal.js with another rendering engine
-
high testability through dependency injection
-
complete decoupling between Gradle and business logic
-
Configuration Cache support β blocked on
asciidoctor-gradle5.xstable release. -
Cucumber tests for deck generation and the RAG pipeline.
-
Support for additional Reveal.js themes.
-
Extended DSL configuration (theme, transition, source dir).
-
Real-time incremental indexing when editing sources.
|
Note
|
The plugin explicitly declares configurationCache = false on the Gradle Plugin Portal.Do not enable the Gradle Configuration Cache with this plugin β the asciidoctorRevealJs taskruns OUT_OF_PROCESS via JRuby and is not compatible in its current state.
|