Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9291421
docs: add ai controller docoumentation
ugur-vaadin Apr 13, 2026
d4bd01f
Update articles/flow/ai-support/ai-powered-chart.adoc
ugur-vaadin Apr 22, 2026
4059f84
Update articles/flow/ai-support/ai-powered-chart.adoc
ugur-vaadin Apr 22, 2026
ee50e9a
Update articles/flow/ai-support/ai-powered-grid.adoc
ugur-vaadin Apr 22, 2026
4b8df58
Update articles/flow/ai-support/controllers.adoc
ugur-vaadin Apr 22, 2026
9fe413c
Update articles/flow/ai-support/ai-powered-grid.adoc
ugur-vaadin Apr 22, 2026
bf0ba0d
Update articles/flow/ai-support/controllers.adoc
ugur-vaadin Apr 22, 2026
dc40dc2
docs: remove unnecessary lines
ugur-vaadin Apr 22, 2026
0b65547
docs: move tools to end of controller pages
ugur-vaadin Apr 22, 2026
229d07e
docs: remove unnecessary restore state from examples
ugur-vaadin Apr 22, 2026
bea3331
docs: cleanup chart tools description
ugur-vaadin Apr 22, 2026
99648fc
docs: update articles based on changes
ugur-vaadin Apr 22, 2026
a8064c4
docs: polish based on updates
ugur-vaadin Apr 22, 2026
36a7c80
docs: fix issues with documentation
ugur-vaadin Apr 23, 2026
aa5f71b
docs: update method name
ugur-vaadin Apr 27, 2026
426ae89
docs: link ai features from related components and quickstart
ugur-vaadin May 15, 2026
83ee926
docs: explain components and ai frameworks
ugur-vaadin May 15, 2026
e8836c4
docs: rename provider to llm provider
ugur-vaadin May 15, 2026
329e214
docs: introduce tool calling for newcomers
ugur-vaadin May 15, 2026
969aa49
docs: note schema flexibility on db example
ugur-vaadin May 15, 2026
382e64f
docs: explain sessionstore placeholder
ugur-vaadin May 15, 2026
e3b8f28
docs: describe combining ai chart with dashboard
ugur-vaadin May 15, 2026
17c7c37
docs: simplify basic orchestrator example
ugur-vaadin May 15, 2026
280fbb4
docs: reword to address vale lint warnings
ugur-vaadin May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions articles/building-apps/ai/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ You'll learn how to:

* connect your application to an AI client with popular Java libraries such as Spring AI and LangChain4j,
* use the xref:{articles}/flow/ai-support#[AI support features] to connect LLM providers to Vaadin UI components with minimal boilerplate,
* choose Vaadin components that create intuitive, AI-powered workflows -- such as `MessageInput`, `MessageList`, and `UploadManager`, and
* choose Vaadin components that create intuitive, AI-powered workflows -- such as `MessageInput`, `MessageList`, and `UploadManager`,
* let users explore application data through natural language with <<{articles}/flow/ai-support/ai-powered-grid#,AI-powered Grid>> and <<{articles}/flow/ai-support/ai-powered-chart#,AI-powered Chart>>, and
* deliver real-time updates to users through server push.

[TIP]
The <<{articles}/flow/ai-support#,AI support features>> eliminate the boilerplate of wiring UI components to LLM frameworks. The [classname]`AIOrchestrator` handles streaming, conversation history, file attachments, and tool calling behind a simple builder API. See the <<{articles}/flow/ai-support#,documentation>> for the full API reference.
The <<{articles}/flow/ai-support#,AI support features>> eliminate the boilerplate of wiring UI components to LLM frameworks. The [classname]`AIOrchestrator` handles streaming, conversation history, file attachments, and tool calling behind a simple builder API. Ready-made controllers -- [classname]`GridAIController` and [classname]`ChartAIController` -- bring natural-language data exploration to [classname]`Grid` and [classname]`Chart`. See the <<{articles}/flow/ai-support#,documentation>> for the full API reference.

section_outline::[]

Expand Down
2 changes: 2 additions & 0 deletions articles/building-apps/ai/quickstart-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ Start the application, open the browser, and try your first prompts.
* Add **file attachments** with `UploadManager` via <<{articles}/flow/ai-support/file-attachments#,`withFileReceiver()`>>.
* Support **tool calls** via <<{articles}/flow/ai-support/tool-calling#,`withTools()`>>.
* **Persist conversation history** via <<{articles}/flow/ai-support/conversation-history#,`ResponseCompleteListener`>>.
* Let users **populate a Grid** from your database in natural language with <<{articles}/flow/ai-support/ai-powered-grid#, AI-Powered Grid>>.
* Let users **build and update Charts** from your database in natural language with <<{articles}/flow/ai-support/ai-powered-chart#, AI-Powered Chart>>.
* Log prompts/responses for observability.


Expand Down
37 changes: 37 additions & 0 deletions articles/components/charts/ai-powered.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: AI-Powered Chart
page-title: AI-Powered Chart | Vaadin components
description: Let users build and update charts from your application database using natural language.
meta-description: Use ChartAIController to let an LLM create and update Vaadin Charts visualizations from a database via natural-language prompts.
order: 7
section-nav: badge-flow
---


= [since:com.vaadin:vaadin@V25.2]#AI-Powered Chart# [badge-flow]#Flow#

AI-Powered Chart lets your users build and update Vaadin Charts visualizations by typing in natural language. The [classname]`ChartAIController` from the <<{articles}/flow/ai-support#, AI Support>> module wires an [classname]`AIOrchestrator` to a [classname]`Chart` and a [classname]`DatabaseProvider`, so an LLM can inspect the database schema, write SQL queries, and update the Highcharts configuration on the fly.

[source,java]
----
Chart chart = new Chart();
MessageInput messageInput = new MessageInput();

DatabaseProvider databaseProvider = new JdbcDatabaseProvider(dataSource);
ChartAIController controller = new ChartAIController(chart, databaseProvider);

AIOrchestrator.builder(provider, systemPrompt)
.withInput(messageInput)
.withController(controller)
.build();

add(messageInput, chart);
----

Example prompts:

* "Plot monthly revenue for the last year as a column chart."
* "Show revenue by region as a pie chart."
* "Compare 2025 and 2026 quarterly sales side-by-side with a legend."

For the full guide -- including state persistence, custom data conversion, and combining several charts with the <<{articles}/components/dashboard#, Dashboard>> component -- see <<{articles}/flow/ai-support/ai-powered-chart#, AI-Powered Chart>> in the AI Support section. The same approach is available for tabular data via <<{articles}/flow/ai-support/ai-powered-grid#, AI-Powered Grid>>.
1 change: 1 addition & 0 deletions articles/components/grid/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Some of the more complex topics are described on separate pages:
* <<drag-drop#, Drag & Drop>>
* <<data-binding#, Data Binding [badge-flow]#Flow#>>
* <<inline-editing#, Inline Editing [badge-flow]#Flow#>>
* <<{articles}/flow/ai-support/ai-powered-grid#, AI-Powered Grid [badge-flow]#Flow#>> -- let users populate the grid from your database via natural language.

// Allow "will" in example
pass:[<!-- vale Vaadin.Will = NO -->]
Expand Down
114 changes: 114 additions & 0 deletions articles/flow/ai-support/ai-powered-chart.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
title: AI-Powered Chart
description: Use ChartAIController to let users build and update Highcharts visualizations from the application database using natural language.
meta-description: Learn how to configure the Vaadin ChartAIController to create and update Charts from a database via natural language prompts and persist the resulting state.
order: 70
---


= [since:com.vaadin:vaadin@V25.2]#AI-Powered Chart#


[classname]`ChartAIController` creates and updates a <<{articles}/components/charts#,[classname]`Chart`>> (Vaadin's interactive charting component) visualization based on natural-language requests. Backed by a <<controllers#database-provider,[classname]`DatabaseProvider`>>, the controller lets the LLM inspect the database schema, write SQL queries for one or more series, and update the Highcharts configuration independently of the data.

Data and configuration are kept separate: series data comes from SQL queries, while visual appearance comes from the configuration. Both updates are applied together at the end of the LLM turn, so the user never sees a half-updated chart.

== Basic Usage

Create a [classname]`Chart`, construct a controller, and wire it to the orchestrator:

[source,java]
----
Chart chart = new Chart();
MessageInput messageInput = new MessageInput();

DatabaseProvider databaseProvider = new JdbcDatabaseProvider(dataSource);
ChartAIController controller = new ChartAIController(chart, databaseProvider);

AIOrchestrator.builder(provider, systemPrompt)
.withInput(messageInput)
.withController(controller)
.build();

add(messageInput, chart);
----

Example prompts:

* "Plot monthly revenue for the last year as a column chart."
* "Show revenue by region as a pie chart."
* "Compare 2025 and 2026 quarterly sales side-by-side with a legend."
* "Turn this into an area chart and add a 'Revenue (USD)' y-axis title."

.Built-In Workflow Instructions
[TIP]
The controller already informs the LLM of the workflow it needs. You can focus your own system prompt on application-specific behavior.

.Provider Compatibility
[NOTE]
[classname]`ChartAIController` does not support OpenAI's strict tool-calling mode. Strict mode is off by default in both LangChain4j and Spring AI; only users who explicitly opt in are affected.


== Persisting Chart State

[classname]`ChartState` captures both the SQL queries and the Highcharts configuration. Register a state change listener to persist the state automatically after each successful AI request:

[source,java]
----
controller.addStateChangeListener(state ->
sessionStore.save(sessionId, state));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for Grid: can we assume everybody knows sessionStore?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a follow-up sentence here too.


// Restore on a new session
ChartState saved = sessionStore.load(sessionId);
if (saved != null) {
controller.restoreState(saved);
}
----

`sessionStore` here is a placeholder for your own storage -- a database table, a file, a [classname]`VaadinSession` attribute, or whatever fits your application.

Listeners do not fire when [methodname]`restoreState()` is called. The current state is also automatically included in session serialization, so no extra save/restore code is needed for in-session persistence.


== Reconnecting After Deserialization

Check warning on line 73 in articles/flow/ai-support/ai-powered-chart.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.HeadingCase] 'Reconnecting After Deserialization' should be in title case. Raw Output: {"message": "[Vaadin.HeadingCase] 'Reconnecting After Deserialization' should be in title case.", "location": {"path": "articles/flow/ai-support/ai-powered-chart.adoc", "range": {"start": {"line": 73, "column": 4}}}, "severity": "WARNING"}

[classname]`ChartAIController` is not serializable. After session restore, create a new controller, pass it to [methodname]`reconnect()` together with the new provider, and optionally re-apply the saved state:

[source,java]
----
ChartAIController controller = new ChartAIController(chart, databaseProvider);
orchestrator.reconnect(provider)
.withController(controller)
.apply();
----


== Custom Data Conversion

[classname]`DefaultDataConverter` maps SQL result rows to Highcharts series automatically. To take full control -- for example, to post-process rows, merge data from multiple queries into a single series, or apply custom formatting -- implement [classname]`DataConverter` and register it with [methodname]`setDataConverter()`:

[source,java]
----
public class CurrencyDataConverter implements DataConverter {

@Override
public List<Series> convertToSeries(List<Map<String, Object>> data) {
DataSeries series = new DataSeries();
for (Map<String, Object> row : data) {
String name = (String) row.get("category");
Number value = (Number) row.get("value");
series.add(new DataSeriesItem(name, roundToCents(value)));
}
return List.of(series);
}
}

controller.setDataConverter(new CurrencyDataConverter());
----

The converter receives the raw rows from [methodname]`DatabaseProvider.executeQuery()` and returns one or more [classname]`Series` instances.


== Combining with Dashboard

[classname]`ChartAIController` manages a single chart, but you can combine several of them with the <<{articles}/components/dashboard#,[classname]`Dashboard`>> component to build an AI-generated dashboard that users arrange themselves and whose layout and chart state can be saved across sessions. Each dashboard widget hosts its own [classname]`Chart` and [classname]`ChartAIController` instance; persist each controller's state alongside the dashboard's own layout state to let users return to the same chart set and layout in a later session.
84 changes: 84 additions & 0 deletions articles/flow/ai-support/ai-powered-grid.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
Comment thread
tomivirkki marked this conversation as resolved.
title: AI-Powered Grid
description: Use GridAIController to let users populate a Grid from the application database using natural language.
meta-description: Learn how to configure the Vaadin GridAIController to populate a Grid from a database via natural language prompts and persist the resulting state.
order: 60
---


= [since:com.vaadin:vaadin@V25.2]#AI-Powered Grid#


[classname]`GridAIController` populates a <<{articles}/components/grid#,[classname]`Grid`>> (Vaadin's component for displaying tabular data) with data from the application database based on natural-language requests. Backed by a <<controllers#database-provider,[classname]`DatabaseProvider`>>, the controller lets the LLM inspect the database schema and run SQL queries that drive the grid.

When an LLM request completes, the queued query is executed and the grid re-renders with dynamically generated columns, type-appropriate renderers, right-aligned numeric columns, lazy loading via SQL `LIMIT`/`OFFSET`, and optional column grouping.


== Basic Usage

Create a grid typed as [classname]`Grid<AIDataRow>`, construct a controller with the grid and a [classname]`DatabaseProvider`, and attach it to an orchestrator. [classname]`AIDataRow` is a framework-owned row type -- you never construct instances yourself.

[source,java]
----
Grid<AIDataRow> grid = new Grid<>();
MessageInput messageInput = new MessageInput();

DatabaseProvider databaseProvider = new JdbcDatabaseProvider(dataSource);
GridAIController controller = new GridAIController(grid, databaseProvider);

AIOrchestrator.builder(provider, systemPrompt)
.withInput(messageInput)
.withController(controller)
.build();

add(messageInput, grid);
----

A message list is not required -- the grid itself is the output surface. Example prompts:

* "Show product name and category grouped under Product, and monthly revenue."
* "List the top 10 highest-paid employees with name, salary, and hire date."
* "Show me all employees in the Sales department."

Check warning on line 41 in articles/flow/ai-support/ai-powered-grid.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.FirstPerson] Use first person (such as 'me') sparingly. Raw Output: {"message": "[Vaadin.FirstPerson] Use first person (such as 'me') sparingly.", "location": {"path": "articles/flow/ai-support/ai-powered-grid.adoc", "range": {"start": {"line": 41, "column": 9}}}, "severity": "WARNING"}

.Built-In Workflow Instructions
[TIP]
The controller already informs the LLM of the workflow it needs. You can focus your own system prompt on application-specific behavior.


== Query Validation

Before queueing an update, the controller runs a lightweight probe against the database to validate the LLM's query. If the probe fails, the error is returned to the LLM so that it can correct the query on the next turn. Invalid queries never reach the grid.

Check failure on line 50 in articles/flow/ai-support/ai-powered-grid.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'LLM's'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'LLM's'?", "location": {"path": "articles/flow/ai-support/ai-powered-grid.adoc", "range": {"start": {"line": 50, "column": 105}}}, "severity": "ERROR"}


== Persisting Grid State

Grid state is a single SQL query represented by the [classname]`GridState` record. Capture it with [methodname]`getState()` and restore it later with [methodname]`restoreState()`. Register a state change listener to persist the state automatically after each successful AI request:

[source,java]
----
controller.addStateChangeListener(state ->
sessionStore.save(sessionId, state));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assume everyone is familiar with sessionStore? If not, should briefly explain and mention other alternatives.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a follow-up sentence after the listener snippet noting that it is a placeholder for the developer's own storage.


// Restore on a new session
GridState saved = sessionStore.load(sessionId);
if (saved != null) {
controller.restoreState(saved);
}
----

`sessionStore` here is a placeholder for your own storage -- a database table, a file, a [classname]`VaadinSession` attribute, or whatever fits your application.

[methodname]`addStateChangeListener()` fires only when the grid is updated by the LLM, not when [methodname]`restoreState()` is called. The current state is also automatically included in session serialization, so no extra save/restore code is needed for in-session persistence.


== Reconnecting After Deserialization

Check warning on line 74 in articles/flow/ai-support/ai-powered-grid.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.HeadingCase] 'Reconnecting After Deserialization' should be in title case. Raw Output: {"message": "[Vaadin.HeadingCase] 'Reconnecting After Deserialization' should be in title case.", "location": {"path": "articles/flow/ai-support/ai-powered-grid.adoc", "range": {"start": {"line": 74, "column": 4}}}, "severity": "WARNING"}

[classname]`GridAIController` is not serializable. After session restore, create a new controller, pass it to [methodname]`reconnect()` together with the new provider, and optionally re-apply the saved state:

[source,java]
----
GridAIController controller = new GridAIController(grid, databaseProvider);
orchestrator.reconnect(provider)
.withController(controller)
.apply();
----
Loading
Loading