Skip to content

Getting started with AI Support

This guide explains the minimal steps to integrate AI Support in a new application.

Prepare the Application

A new application created on start.vaadin.com can be used as a starting point for this tutorial. Open the site, select an empty project, choose Spring Boot as the framework, and download the generated archive. Extract it, import it into your IDE of choice, and verify the application starts correctly by running the main class.

Add the AppJars Repository

AppJars artifacts are published to the public AppJars Maven repository. Add it to your pom.xml so that Maven can resolve the AppJars dependencies:

<repositories>
    <repository>
        <id>appjars</id>
        <name>AppJars Public Repository</name>
        <url>https://maven.appjars.com</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

Add the Dependencies

Given that it is a monolithic application, the following dependencies representing the three layers of the appjar must be added:

<dependency>
    <groupId>com.appjars</groupId>
    <artifactId>appjars-ai-support-flow</artifactId>
</dependency>
<dependency>
    <groupId>com.appjars</groupId>
    <artifactId>appjars-ai-support-data-impl</artifactId>
</dependency>
<dependency>
    <groupId>com.appjars</groupId>
    <artifactId>appjars-ai-support-business-impl</artifactId>
</dependency>

After adding them, build the application to confirm that the dependencies are resolved correctly.

Modify the Main Application Class

The following annotations must be added to the main application class:

@SpringBootApplication
@EnableAsync
@EnableScheduling
@Push
@ComponentScan(basePackageClasses = {AiSupportConfiguration.class, AppJarsAutoConfiguration.class})
@EnableJpaRepositories(basePackageClasses = AiSupportConfiguration.class)
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
  • @ComponentScan: Registers the AI Support components alongside the shared AppJars infrastructure. Referencing AiSupportConfiguration.class and AppJarsAutoConfiguration.class avoids hard-coding package names.
  • @EnableJpaRepositories: Required. AI Support uses Spring Data repositories for documents, embeddings, participants, and sessions. Without this annotation the application fails to start.
  • @Push: Required for streaming responses. The assistant delivers its answer incrementally, which needs an open server push channel.
  • @EnableAsync: Required for background document processing.
  • @EnableScheduling: Required only if the optional maintenance tasks are used. See Background tasks.

The imports are com.appjars.aisupport.AiSupportConfiguration and com.appjars.AppJarsAutoConfiguration.

Then inject the RouteConfigurer provided by AI Support and configure the router layout in a @PostConstruct method so that the appjar views share the same layout as the rest of the application:

final com.appjars.aisupport.flow.util.RouteConfigurer aiSupportRouteConfigurer;

public Application(final com.appjars.aisupport.flow.util.RouteConfigurer aiSupportRouteConfigurer) {
    this.aiSupportRouteConfigurer = aiSupportRouteConfigurer;
}

@PostConstruct
public void configure() {
    aiSupportRouteConfigurer.setViewsRouterLayout(MainLayout.class);
}

When the views must be wrapped in more than one layout, pass the chain instead, outermost layout last:

@PostConstruct
public void configure() {
    aiSupportRouteConfigurer.setViewsLayoutChain(List.of(ChatAssistantLayout.class, MainLayout.class));
}

Implement AuthenticatedUserProvider

AI Support requires the application to supply the currently authenticated user. Implement the AuthenticatedUserProvider interface and register it as a Spring bean:

@Component
public class AiSupportUserAdapter implements AuthenticatedUserProvider {

    private final AuthenticationContext authenticationContext;
    private final UserDetailsService userDetailsService;

    public AiSupportUserAdapter(AuthenticationContext authenticationContext,
                                UserDetailsService userDetailsService) {
        this.authenticationContext = authenticationContext;
        this.userDetailsService = userDetailsService;
    }

    @Override
    public Optional<com.appjars.aisupport.model.UserDto> getAuthenticatedUser() {
        return authenticationContext
            .getAuthenticatedUser(UserDetails.class)
            .map(userDetails -> {
                com.appjars.aisupport.model.UserDto dto = new com.appjars.aisupport.model.UserDto();
                dto.setUsername(userDetails.getUsername());
                dto.setAdmin(userDetails.getAuthorities().stream()
                    .anyMatch(a -> a.getAuthority().contains("ADMIN")));
                return dto;
            });
    }

    @Override
    public Set<com.appjars.aisupport.model.UserDto> findUsersByStartWith(String match,
                                                                          boolean toLowercase) {
        // Return an empty set or look up users from your user store
        return Set.of();
    }
}

The import for AuthenticatedUserProvider is com.appjars.aisupport.business.service.AuthenticatedUserProvider.

If the application already uses User Manager, the existing AuthenticatedUser component can implement this interface directly by adding the two methods, rather than creating a separate adapter class.

Implement UserProfilePictureProvider (Optional)

AI Support can display user profile pictures in the chat interface. To enable this, implement the UserProfilePictureProvider interface and register it as a Spring bean:

@Component
public class ProfilePictureAdapter implements UserProfilePictureProvider {

    @Override
    public Optional<byte[]> getProfilePictureByUsername(String username) {
        // Return the profile picture bytes for the given username, or Optional.empty()
        return Optional.empty();
    }
}

The import for UserProfilePictureProvider is com.appjars.aisupport.business.service.UserProfilePictureProvider.

If the application uses User Profile, the existing UserProfileService can be used here to retrieve the user avatar.

Add the Views to the Navigation

AI Support provides a user-facing chat view and several administrator views. Add them to the application's navigation menu by inserting the following snippet at the end of the method createNavigation() in MainLayout, before returning nav:

SideNavItem aiSupportItem = new SideNavItem("AI Support");
aiSupportItem.setPrefixComponent(LineAwesomeIcon.ROBOT_SOLID.create());

if (accessChecker.hasAccess(ChatView.class)) {
    aiSupportItem.addItem(new SideNavItem("Chat", ChatView.class, LineAwesomeIcon.COMMENTS_SOLID.create()));
}

SideNavItem aiAdminItem = new SideNavItem("Administration");
aiAdminItem.setPrefixComponent(LineAwesomeIcon.TOOLS_SOLID.create());

if (accessChecker.hasAccess(AssistantView.class)) {
    aiAdminItem.addItem(new SideNavItem("Assistants", AssistantView.class, LineAwesomeIcon.USER_TIE_SOLID.create()));
}
if (accessChecker.hasAccess(DocumentsView.class)) {
    aiAdminItem.addItem(new SideNavItem("Documents", DocumentsView.class, LineAwesomeIcon.FILE_ALT_SOLID.create()));
}
if (accessChecker.hasAccess(LlmsView.class)) {
    aiAdminItem.addItem(new SideNavItem("LLMs", LlmsView.class, LineAwesomeIcon.BRAIN_SOLID.create()));
}
if (accessChecker.hasAccess(PromptsView.class)) {
    aiAdminItem.addItem(new SideNavItem("Prompts", PromptsView.class, LineAwesomeIcon.COMMENT_ALT_SOLID.create()));
}
if (accessChecker.hasAccess(CategoriesView.class)) {
    aiAdminItem.addItem(new SideNavItem("Categories", CategoriesView.class, LineAwesomeIcon.TAGS_SOLID.create()));
}
if (accessChecker.hasAccess(LlmInspectorView.class)) {
    aiAdminItem.addItem(new SideNavItem("LLM Inspector", LlmInspectorView.class, LineAwesomeIcon.SEARCH_SOLID.create()));
}
if (accessChecker.hasAccess(ChannelsView.class)) {
    aiAdminItem.addItem(new SideNavItem("Channels", ChannelsView.class, LineAwesomeIcon.PLUG_SOLID.create()));
}

if (aiAdminItem.getChildren().findAny().isPresent()) {
    aiSupportItem.addItem(aiAdminItem);
}

nav.addItem(aiSupportItem);

The imports for the view classes are:

  • com.appjars.aisupport.flow.view.ChatView
  • com.appjars.aisupport.flow.view.AssistantView
  • com.appjars.aisupport.flow.view.DocumentsView
  • com.appjars.aisupport.flow.view.LlmsView
  • com.appjars.aisupport.flow.view.PromptsView
  • com.appjars.aisupport.flow.view.CategoriesView
  • com.appjars.aisupport.flow.view.LlmInspectorView
  • com.appjars.aisupport.flow.view.ChannelsView

Add the Chat Bubble (Optional)

AI Support provides AISupportChatAssistant, the floating chat window described in The Chat Bubble. It gives users a conversation on any page of the application without navigating to the Chat view.

The component must remain attached across navigation, so add it to a router layout that wraps the views rather than to an individual view. A dedicated layout keeps the concern in one place:

@ParentLayout(MainLayout.class)
@PreserveOnRefresh
@PermitAll
public class ChatAssistantLayout extends Div implements RouterLayout, AfterNavigationObserver {

    private final AISupportChatAssistant chatAssistant;
    private final AuthenticatedUser authenticatedUser;

    @Value("${com.appjars.aisupport.url.views.chat}")
    private String chatViewPath;

    public ChatAssistantLayout(AISupportChatAssistant chatAssistant,
                               AuthenticatedUser authenticatedUser) {
        this.chatAssistant = chatAssistant;
        this.authenticatedUser = authenticatedUser;
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        setSizeFull();
        chatAssistant.getStyle()
            .setPosition(Style.Position.RELATIVE)
            .setZIndex(Integer.MAX_VALUE);

        // Only authenticated users get the bubble.
        if (authenticatedUser.isAuthenticated()) {
            if (getChildren().noneMatch(AISupportChatAssistant.class::isInstance)) {
                add(chatAssistant);
            }
        } else {
            getChildren()
                .filter(AISupportChatAssistant.class::isInstance)
                .findFirst()
                .ifPresent(this::remove);
        }
    }

    @Override
    public void afterNavigation(AfterNavigationEvent event) {
        // Hide the bubble on the full Chat view to avoid two chats on one page.
        chatAssistant.setVisible(!event.getLocation().getPath().equals(chatViewPath));
    }
}

Then register the layout as the innermost link of the chain, so the AI Support views are wrapped by it and it in turn by the application's main layout:

@PostConstruct
public void configure() {
    aiSupportRouteConfigurer.setViewsLayoutChain(
        List.of(ChatAssistantLayout.class, MainLayout.class));
}

Three details matter:

  • @PreserveOnRefresh keeps the same layout instance across a page refresh, so an open conversation is not lost.
  • The z-index must place the bubble above the application content; otherwise the floating button ends up behind views that establish their own stacking context.
  • Hiding it on the Chat view avoids presenting two chats on the same page. The path is read from the same property that defines the view's URL, so a customised URL keeps working.

The import for AISupportChatAssistant is com.appjars.aisupport.flow.component.AISupportChatAssistant. See Chat Bubble Component for the appearance and sizing API.

Configure Application Properties

AI Support requires a PostgreSQL database with the pgvector extension enabled. The extension is used to store and query document embeddings for the RAG (Retrieval-Augmented Generation) pipeline. Add the datasource configuration to application.properties:

appjars.database.host=localhost
appjars.database.port=5432
appjars.database.name=myapp
spring.datasource.url=jdbc:postgresql://${appjars.database.host}:${appjars.database.port}/${appjars.database.name}
spring.datasource.username=myapp
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.defer-datasource-initialization=true

The appjars.database.* properties are read separately from the JDBC URL, because the embedding store connects to PostgreSQL directly rather than through the Spring datasource. Declare them even when the JDBC URL is written out in full.

Next, declare the embedding store dimensions and table name. The dimension must match the output size of the embedding model; the built-in model produces 384-dimensional vectors:

spring.ai.vectorstore.pgvector.dimensions=384
spring.ai.vectorstore.pgvector.table-name=as_embedding

Server push must be enabled so that the assistant can stream its responses:

vaadin.pushMode=AUTOMATIC

AI Support supports file attachments on chat messages. Two limits apply: the servlet multipart limits, and the appjar's own per-message limits. Raise both to allow meaningful file sizes:

spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
appjars.aisupport.chat.attachments.max-size=10
appjars.aisupport.chat.attachments.max-files=3

max-size is expressed in megabytes per file and defaults to 10; max-files defaults to 3. The servlet limits must be at least as large as the appjar limits.

Testing the Application

Start the application by running the main Spring Boot class. AI Support creates its database tables on first run using the JPA schema generation.

After the application starts, log in as an administrator and navigate to AI Support > Administration > LLMs to register the first LLM provider. Optionally mark it as the system LLM with the Set as system action; when no model is marked, the internal summarisation and moderation tasks use the model of the assistant handling the conversation.

Then write a Prompt in Administration > Prompts, and create an Assistant in Administration > Assistants that combines the registered LLM with that prompt. An assistant can only be used once its LLM and its prompt are both enabled.

Once an assistant exists, navigate to AI Support > Chat to open a conversation and verify that the assistant responds correctly.