-
Notifications
You must be signed in to change notification settings - Fork 218
docs: add ai controller documentation #5473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9291421
d4bd01f
4059f84
ee50e9a
4b8df58
9fe413c
bf0ba0d
dc40dc2
0b65547
229d07e
bea3331
99648fc
a8064c4
36a7c80
aa5f71b
426ae89
83ee926
e8836c4
329e214
969aa49
382e64f
e3b8f28
17c7c37
280fbb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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>>. |
| 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)); | ||
|
|
||
| // 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
|
||
|
|
||
| [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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| --- | ||
|
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
|
||
|
|
||
| .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
|
||
|
|
||
|
|
||
| == 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)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we assume everyone is familiar with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||
|
|
||
| [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(); | ||
| ---- | ||
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.