Class AiChat<T extends AiChatMessage>

java.lang.Object
com.vaadin.flow.component.Component
com.vaadin.flow.component.Composite<com.vaadin.flow.component.html.Div>
io.jmix.aichat.component.chat.AiChat<T>
Type Parameters:
T - the application's message type
All Implemented Interfaces:
com.vaadin.flow.component.AttachNotifier, com.vaadin.flow.component.DetachNotifier, com.vaadin.flow.component.HasElement, com.vaadin.flow.component.HasSize, com.vaadin.flow.component.HasStyle, com.vaadin.flow.component.HasTheme, com.vaadin.flow.component.shared.HasThemeVariant<AiChatVariant>, HasActions, HasSubParts, Serializable, org.springframework.beans.factory.Aware, org.springframework.beans.factory.InitializingBean, org.springframework.context.ApplicationContextAware
Direct Known Subclasses:
BuilderAiChat

public class AiChat<T extends AiChatMessage> extends com.vaadin.flow.component.Composite<com.vaadin.flow.component.html.Div> implements com.vaadin.flow.component.HasSize, com.vaadin.flow.component.shared.HasThemeVariant<AiChatVariant>, org.springframework.context.ApplicationContextAware, org.springframework.beans.factory.InitializingBean, HasActions, HasSubParts
High-level, ready-to-use AI chat component. Composes an AiMessageList and an AiMessageInput, owns the generation lifecycle over an LLMProvider, and binds messages as typed data (T).

This is a facade: the wrapped components are implementation details and are not exposed. Configure the chat through this class.

Responses stream asynchronously via UI.access(...): enable server push (or polling) so tokens reach the browser while the request is in flight; without it, updates arrive only with the next client round trip.

See Also:
  • Field Details

    • messageList

      protected AiMessageList messageList
    • messageInput

      protected AiMessageInput messageInput
    • emptyStateHeader

      protected com.vaadin.flow.component.html.Div emptyStateHeader
    • emptyStateFooter

      protected com.vaadin.flow.component.html.Div emptyStateFooter
    • liveRegion

      protected com.vaadin.flow.component.html.Div liveRegion
    • applicationContext

      protected org.springframework.context.ApplicationContext applicationContext
    • fileStorageLocator

      protected FileStorageLocator fileStorageLocator
    • llmProvider

      protected transient @Nullable com.vaadin.flow.component.ai.provider.LLMProvider llmProvider
    • systemPrompt

      protected @Nullable String systemPrompt
    • items

      protected AiChatItems<T extends AiChatMessage> items
    • itemsChangeRegistration

      protected @Nullable com.vaadin.flow.shared.Registration itemsChangeRegistration
    • sourceOrderWarned

      protected boolean sourceOrderWarned
    • messageFactory

      protected @Nullable AiChatMessageFactory<T extends AiChatMessage> messageFactory
    • defaultMessageFactory

      protected @Nullable AiChatMessageFactory<SimpleAiChatMessage> defaultMessageFactory
    • saveDelegate

      protected @Nullable Function<T extends AiChatMessage,T extends AiChatMessage> saveDelegate
    • removeDelegate

      protected @Nullable Consumer<T extends AiChatMessage> removeDelegate
    • i18n

      protected AiChatI18n i18n
    • defaultI18n

      protected AiChatI18n defaultI18n
    • actionsSupport

      protected @Nullable AiChatActionsSupport actionsSupport
    • attachmentResources

      protected Map<FileRef,com.vaadin.flow.server.StreamRegistration> attachmentResources
    • attachmentsEnabledListeners

      protected List<Consumer<Boolean>> attachmentsEnabledListeners
    • state

      protected GenerationState state
    • currentStream

      protected transient @Nullable reactor.core.Disposable currentStream
    • activeBuffer

      protected transient @Nullable StringBuilder activeBuffer
    • activeAssistantItem

      protected @Nullable AiMessageListItem activeAssistantItem
    • lastUserMessage

      protected @Nullable String lastUserMessage
    • lastUserAttachments

      protected @Nullable List<com.vaadin.flow.component.ai.common.AIAttachment> lastUserAttachments
    • lastAssistantMessage

      protected @Nullable T extends AiChatMessage lastAssistantMessage
  • Constructor Details

  • Method Details

    • currentHttpSession

      protected static @Nullable com.vaadin.flow.server.WrappedSession currentHttpSession()
      The current Vaadin session's underlying HTTP session, or null when there is none to read — no current session (so nothing installed a view of it), or one already invalidated.
      Returns:
      the HTTP session, or null if none is available
    • initComponent

      protected void initComponent()
      Wires the inner input/list and the message source. Runs from afterPropertiesSet(), after the application context is injected — not from the constructor.
    • initContent

      protected com.vaadin.flow.component.html.Div initContent()
      Overrides:
      initContent in class com.vaadin.flow.component.Composite<com.vaadin.flow.component.html.Div>
    • createMessageList

      protected AiMessageList createMessageList()
      Creates the inner message list. Override to supply a customized instance.
      Returns:
      a new message list
    • createMessageInput

      protected AiMessageInput createMessageInput()
      Creates the inner message input. Override to supply a customized instance.
      Returns:
      a new message input
    • setApplicationContext

      public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext) throws org.springframework.beans.BeansException
      Specified by:
      setApplicationContext in interface org.springframework.context.ApplicationContextAware
      Throws:
      org.springframework.beans.BeansException
    • afterPropertiesSet

      public void afterPropertiesSet()
      Specified by:
      afterPropertiesSet in interface org.springframework.beans.factory.InitializingBean
    • initI18n

      protected void initI18n()
    • checkLlmProviderConfigured

      protected void checkLlmProviderConfigured()
    • checkMessageFactoryConfigured

      protected void checkMessageFactoryConfigured()
      Fails fast when messages cannot be created for the bound entity type: the default factory produces SimpleAiChatMessage, so an entity-backed source needs an application-supplied factory.
    • setLlmProvider

      public void setLlmProvider(@Nullable com.vaadin.flow.component.ai.provider.LLMProvider llmProvider)
      Sets the LLM provider used to generate responses.
      Parameters:
      llmProvider - the provider, or null to unset
    • getLlmProvider

      public @Nullable com.vaadin.flow.component.ai.provider.LLMProvider getLlmProvider()
      Returns:
      the LLM provider, or null if none is set
    • setSystemPrompt

      public void setSystemPrompt(@Nullable String systemPrompt)
      Sets the system prompt injected into every request. Whitespace is stripped; a null or blank value defers to the provider's own default.
      Parameters:
      systemPrompt - the system prompt, or null
    • getSystemPrompt

      public @Nullable String getSystemPrompt()
      Returns:
      the system prompt, or null
    • setMessageFactory

      public void setMessageFactory(@Nullable AiChatMessageFactory<T> messageFactory)
      Sets the factory creating the T instances for the user echo and the assistant response.
      Parameters:
      messageFactory - the factory, or null to use the default one
    • getMessageFactory

      public @Nullable AiChatMessageFactory<T> getMessageFactory()
      Returns:
      the message factory, or null if none is set
    • getSaveDelegate

      public @Nullable Function<T,T> getSaveDelegate()
      Returns:
      the delegate that persists a committed message, or null if none is set
    • setSaveDelegate

      public void setSaveDelegate(Function<T,T> saveDelegate)
      Sets the delegate that persists a message the component has just created, before it enters the bound source.

      The delegate returns the instance the component keeps — a persistence layer routinely answers with a different object than it was handed (a tracked or merged copy); returning the argument unchanged is valid for a layer that saves in place.

      Throwing abandons the commit, nothing having entered the source yet: on a user message the exception propagates out of the send, on an assistant message the generation ends in GenerationState#ERROR with a GenerationFailedEvent.

      Unset, the component persists nothing. The autoSave descriptor attribute installs a DataContext-backed default.

      Parameters:
      saveDelegate - the delegate to set, not null
    • getRemoveDelegate

      public @Nullable Consumer<T> getRemoveDelegate()
      Returns:
      the delegate that deletes a removed message, or null if none is set
    • setRemoveDelegate

      public void setRemoveDelegate(Consumer<T> removeDelegate)
      Sets the delegate that deletes a message the component is removing from the bound source, before it leaves that source.

      Called on the rewind paths — regenerate() and regenerateFrom(T), once per message, newest first. A delegate that throws stops the truncation where it failed; the deletions already made cannot be undone. Unset, the component deletes nothing.

      Parameters:
      removeDelegate - the delegate to set, not null
    • setMaxRows

      public void setMaxRows(int maxRows)
      Sets the maximum visible textarea rows before it scrolls.
      Parameters:
      maxRows - the maximum rows
    • getMaxRows

      public int getMaxRows()
      Returns:
      the maximum visible textarea rows
    • setEnterAction

      public void setEnterAction(EnterAction enterAction)
      Sets the Enter-key behavior.
      Parameters:
      enterAction - the behavior
    • getEnterAction

      public EnterAction getEnterAction()
      Returns:
      the Enter-key behavior
    • getMessageInputInternal

      protected AiMessageInput getMessageInputInternal()
    • addAttachmentsEnabledChangeListenerInternal

      protected com.vaadin.flow.shared.Registration addAttachmentsEnabledChangeListenerInternal(Consumer<Boolean> listener)
    • isAttachmentsEnabled

      public boolean isAttachmentsEnabled()
      Returns:
      true if the attach affordance is shown
    • setAttachmentsEnabled

      public void setAttachmentsEnabled(boolean attachmentsEnabled)
      Sets whether the attach affordance (paperclip, drag-and-drop, chips) is shown. Off by default.
      Parameters:
      attachmentsEnabled - true to enable attachments
    • getMaxFiles

      public int getMaxFiles()
      Returns:
      the maximum number of files that can be attached to one message
    • setMaxFiles

      public void setMaxFiles(int maxFiles)
      Sets the maximum number of files that can be attached to one message.
      Parameters:
      maxFiles - the maximum file count
    • getMaxFileSize

      public long getMaxFileSize()
      Returns:
      the maximum size of a single attachment, in bytes
    • setMaxFileSize

      public void setMaxFileSize(long maxFileSize)
      Sets the maximum size of a single attachment.
      Parameters:
      maxFileSize - the maximum size, in bytes
    • getAcceptedMimeTypes

      public List<String> getAcceptedMimeTypes()
      Returns:
      an unmodifiable view of the accepted MIME types, empty when any type is accepted
    • setAcceptedMimeTypes

      public void setAcceptedMimeTypes(String... mimeTypes)
      Restricts attachments to the given MIME types. Passing none accepts any type.
      Parameters:
      mimeTypes - the accepted MIME types, e.g. "image/*"
    • getAcceptedFileExtensions

      public List<String> getAcceptedFileExtensions()
      Returns:
      an unmodifiable view of the accepted file extensions, empty when any is accepted
    • setAcceptedFileExtensions

      public void setAcceptedFileExtensions(String... extensions)
      Restricts attachments to the given file extensions. Passing none accepts any extension.
      Parameters:
      extensions - the accepted extensions, e.g. ".png"
    • isUploadDropZoneEnabled

      public boolean isUploadDropZoneEnabled()
      Returns:
      true if files may be attached by dropping them on the composer
    • setUploadDropZoneEnabled

      public void setUploadDropZoneEnabled(boolean uploadDropZoneEnabled)
      Sets whether files may be attached by dropping them on the composer. Enabled by default; the paperclip stays available either way.
      Parameters:
      uploadDropZoneEnabled - true to accept dropped files
    • getThemeNames

      public com.vaadin.flow.dom.ThemeList getThemeNames()
      Returns a theme list projecting every change onto the inner message list and message input as well as this chat's own root element. Every HasTheme and HasThemeVariant mutator funnels through here, so a variant set either way reaches both inner components.
      Specified by:
      getThemeNames in interface com.vaadin.flow.component.HasTheme
    • setInputPrefixComponent

      public void setInputPrefixComponent(@Nullable com.vaadin.flow.component.Component component)
      Sets a component into the input's prefix slot.
      Parameters:
      component - the component, or null to clear
    • getInputPrefixComponent

      public @Nullable com.vaadin.flow.component.Component getInputPrefixComponent()
      Returns:
      the input's prefix component, or null
    • setInputSuffixComponent

      public void setInputSuffixComponent(@Nullable com.vaadin.flow.component.Component component)
      Sets a component into the input's suffix slot.
      Parameters:
      component - the component, or null to clear
    • getInputSuffixComponent

      public @Nullable com.vaadin.flow.component.Component getInputSuffixComponent()
      Returns:
      the input's suffix component, or null
    • setInputHeaderComponent

      public void setInputHeaderComponent(@Nullable com.vaadin.flow.component.Component component)
      Sets a component into the input's header slot — the band above the composer's input box. Hidden by default while the chat is empty, since the empty state shows the chat's own bands; AiChatVariant.INPUT_HEADER_COMPONENT_ALWAYS_VISIBLE keeps it shown.
      Parameters:
      component - the component, or null to clear
    • getInputHeaderComponent

      public @Nullable com.vaadin.flow.component.Component getInputHeaderComponent()
      Returns:
      the input's header component, or null
    • setInputFooterComponent

      public void setInputFooterComponent(@Nullable com.vaadin.flow.component.Component component)
      Sets a component into the input's footer slot — the band below the composer's input box. Hidden by default while the chat is empty, since the empty state shows the chat's own bands; AiChatVariant.INPUT_FOOTER_COMPONENT_ALWAYS_VISIBLE keeps it shown.
      Parameters:
      component - the component, or null to clear
    • getInputFooterComponent

      public @Nullable com.vaadin.flow.component.Component getInputFooterComponent()
      Returns:
      the input's footer component, or null
    • setAnnounceMessages

      public void setAnnounceMessages(boolean announceMessages)
      Announces new messages through the message list's ARIA live region. false by default. Independent of the chat's own live region, which announces the generation lifecycle and is always on.
      Parameters:
      announceMessages - true to announce new messages
    • isAnnounceMessages

      public boolean isAnnounceMessages()
      Returns:
      true if new messages are announced to assistive technologies
    • setThinkingIndicator

      public void setThinkingIndicator(@Nullable com.vaadin.flow.component.Component indicator)
      Sets a custom thinking indicator shown while the assistant is generating, or null for the built-in default.
      Parameters:
      indicator - the indicator component, or null for the default
    • getThinkingIndicator

      public @Nullable com.vaadin.flow.component.Component getThinkingIndicator()
      Returns:
      the custom thinking indicator, or null when the built-in default is used
    • setThinkingStages

      public void setThinkingStages(List<ThinkingStage> stages)
      Sets the escalating thinking stages.
      Parameters:
      stages - the stages
    • getThinkingStages

      public List<ThinkingStage> getThinkingStages()
      Gets the escalating thinking stages.
      Returns:
      an unmodifiable view of the stages
    • getThinkingStatusPublisher

      public ThinkingStatusPublisher getThinkingStatusPublisher()
      The thinking-status feed of the inner message list: the ordered account of what the assistant is doing, rendered while generating. Ephemeral — cleared at the start of every generation and again when it finishes, fails or is stopped.
      Returns:
      the feed, never null
    • setUserActions

      public void setUserActions(List<? extends AiMessageAction<?>> actions)
      Sets the per-role action set for user messages.
      Parameters:
      actions - the actions
    • getUserActions

      public List<AiMessageAction<?>> getUserActions()
      Returns:
      an unmodifiable view of the per-role action set for user messages
    • addUserAction

      public void addUserAction(AiMessageAction<?> action)
      Appends an action to the per-role action set for user messages, keeping the ones already there — including any declared in a view descriptor.
      Parameters:
      action - the action to append
      See Also:
    • setAssistantActions

      public void setAssistantActions(List<? extends AiMessageAction<?>> actions)
      Sets the per-role action set for assistant messages.
      Parameters:
      actions - the actions
    • getAssistantActions

      public List<AiMessageAction<?>> getAssistantActions()
      Returns:
      an unmodifiable view of the per-role action set for assistant messages
    • addAssistantAction

      public void addAssistantAction(AiMessageAction<?> action)
      Appends an action to the per-role action set for assistant messages, keeping the ones already there — including any declared in a view descriptor.
      Parameters:
      action - the action to append
      See Also:
    • targetMessageAction

      protected void targetMessageAction(AiMessageAction<?> action)
      Hands the chat to a message action being assigned to a role, if it declares itself a TargetAction — the contract by which a per-message action reaches the message source. An action targeting anything else must not be declared a TargetAction.

      Assigning is the only wiring point: the role API has no remove operation, so there is no detach to clear the target on.

      Parameters:
      action - the action to wire
    • setI18n

      public void setI18n(AiChatI18n i18n)
      Applies the localization, handing each nested holder to the component it belongs to. Replace semantics: the given holder becomes the effective one, so a field left null falls back to the client's hardcoded English rather than to the message bundle. A null nested holder leaves that component's localization alone.

      To re-word one label, start from getI18n() rather than from a fresh new AiChatI18n().

      Parameters:
      i18n - the localization bean, not null
    • getI18n

      public AiChatI18n getI18n()
      Gets the localization currently in effect — resolved from the message bundle when the chat was created, and replaced by every setI18n(AiChatI18n) since.
      Returns:
      the i18n bean
    • getState

      public GenerationState getState()
      Returns:
      the current generation state
    • prompt

      public void prompt(String userMessage)
      Sends a prompt programmatically, as if typed and submitted. Silently ignored while a generation is in progress and for blank input.
      Parameters:
      userMessage - the prompt text
      Throws:
      IllegalStateException - if no LLM provider is configured or the component is not attached to a UI
    • regenerate

      public void regenerate()
      Re-runs the last user prompt and streams a fresh answer. A committed assistant message is removed first (firing REMOVED); an errored or stopped one leaves the data untouched, only dropping the transient partial answer.
      Throws:
      IllegalStateException - if no LLM provider is configured or the component is not attached to a UI
    • regenerateFrom

      public void regenerateFrom(T message)
      Re-runs the user prompt the given message belongs to and streams a fresh answer, dropping the message and everything after it (each firing REMOVED).

      The prompt is the nearest AiMessageRole.USER message at or before the given one, so an assistant answer rewinds to the prompt it answers.

      Silently ignored while a generation is in progress, for a message that is not in this chat's source, and when no user prompt precedes it.

      Parameters:
      message - the message to regenerate from, not null
      Throws:
      IllegalStateException - if no LLM provider is configured or the component is not attached to a UI
    • regenerate

      public void regenerate(AiMessageListItem item)
      Resolves the rendered message to the domain message it was rendered from and delegates to regenerateFrom(AiChatMessage).

      Silently ignored for an item that is not rendered by this chat. An item with no source message — the transient bubble a stopped or failed generation leaves behind — rewinds to the conversation's last prompt, behaving like regenerate().

      Parameters:
      item - the rendered message to regenerate from, not null
      Throws:
      IllegalStateException - if no LLM provider is configured or the component is not attached to a UI
    • regenerateAt

      protected void regenerateAt(int index)
      Rewinds the conversation to the user prompt at or before the given index in the message source and streams a fresh answer.
      Parameters:
      index - the index in the message source to regenerate from
    • findPromptIndex

      protected int findPromptIndex(List<T> data, int index)
      Finds the message that carries the prompt to re-run: the nearest user message at or before the given index.
      Parameters:
      data - the messages in source order
      index - the index to search back from; clamped to the last message, defensively — every caller now passes an in-range index
      Returns:
      the prompt's index, or -1 if none precedes it
    • promptAttachments

      protected List<com.vaadin.flow.component.ai.common.AIAttachment> promptAttachments(T message)
      Reads a stored prompt's attachments back into the form the provider takes. Only a FileRef survives in the data, so the bytes are re-read from the storage the reference names.
      Parameters:
      message - the prompt to re-send
      Returns:
      its attachments, empty if it has none
    • removeMessage

      protected void removeMessage(T message)
    • stop

      public void stop()
      Cancels the in-flight generation. No-op unless a generation is in progress. Partially streamed text stays visible until the next rebuild and is not committed to the data; a stop before the first token leaves nothing behind.
    • cancelGeneration

      protected void cancelGeneration()
      Cancels the in-flight stream and resets to idle without announcing anything. Shared by stop(), detach cleanup, and message source replacement.
    • onAttach

      protected void onAttach(com.vaadin.flow.component.AttachEvent attachEvent)
      Overrides:
      onAttach in class com.vaadin.flow.component.Component
    • onDetach

      protected void onDetach(com.vaadin.flow.component.DetachEvent detachEvent)
      Overrides:
      onDetach in class com.vaadin.flow.component.Component
    • send

      protected void send(String userMessage, List<com.vaadin.flow.component.ai.common.AIAttachment> attachments)
      Commits the user message and streams an answer for it. A message needs text or at least one attachment: a blank prompt carrying attachments is sent (an attachment alone is an ordinary chat message), a blank one carrying none is silently ignored, as is any prompt arriving while a generation is in progress.

      The text is committed and sent to the provider unchanged, blank or empty — the component substitutes no wording of its own.

      Parameters:
      userMessage - the prompt text, not null; may be empty or blank when attachments is not empty
      attachments - the files attached to the message, not null
    • checkAttached

      protected void checkAttached()
    • stream

      protected void stream(String userMessage, List<com.vaadin.flow.component.ai.common.AIAttachment> attachments)
      Subscribes to the provider and routes its signals onto the UI thread. An override that re-implements the subscription must wrap the terminal callbacks in runWithGenerationContext(com.vaadin.flow.server.VaadinRequest, java.lang.Runnable), or application listeners lose the request-bound context there; overriding onComplete(java.lang.StringBuilder) or onError(java.lang.StringBuilder, java.lang.Throwable) alone is safe, since the wrapping sits outside them.
      Parameters:
      userMessage - the prompt text
      attachments - the prompt's attachments
    • runWithGenerationContext

      protected void runWithGenerationContext(@Nullable com.vaadin.flow.server.VaadinRequest request, Runnable body)
      Runs a generation callback with the two pieces of request-bound context an application listener needs: the HTTP session's attributes and the VaadinRequest captured when the generation started. The terminal callbacks run on a provider thread, where otherwise {session_*} query parameters resolve to null behind a warning and a view access check throws. Mirrors what Jmix does for the same hop in InMemoryUploadHandler and BackgroundWorkerImpl; authentication needs none.

      This installs two thread-locals, not Spring's session scope: a session-scoped bean such as SessionData still fails here with ScopeNotActiveException.

      Nothing is installed over a context the thread already carries. Session attributes are a live view rather than a snapshot, so a re-entrant session.access(...) on another user's session reads its own attributes rather than this generation's.

      Parameters:
      request - the request captured when the generation started, or null
      body - the callback to run
    • onToken

      protected void onToken(StringBuilder buffer, String token)
    • onComplete

      protected void onComplete(StringBuilder buffer)
    • onError

      protected void onError(StringBuilder buffer, Throwable error)
    • failGeneration

      protected void failGeneration(StringBuilder buffer, Throwable error)
      Ends the current generation in GenerationState.ERROR: clears the stream state, drops an empty transient placeholder, and fires GenerationFailedEvent with whatever text had arrived. Called for a failing stream and for a failure to persist an otherwise complete answer, which is the same outcome for the reader — the answer is on screen and is not stored.
      Parameters:
      buffer - the accumulated answer text
      error - the failure
    • finishGeneration

      protected void finishGeneration()
    • commitMessage

      protected T commitMessage(AiMessageRole role, String content, List<com.vaadin.flow.component.ai.common.AIAttachment> attachments)
    • removeLastAssistant

      protected void removeLastAssistant()
    • requireFactory

      protected AiChatMessageFactory<T> requireFactory()
    • getDefaultMessageFactory

      protected AiChatMessageFactory<SimpleAiChatMessage> getDefaultMessageFactory()
      Returns the factory used when the application supplied none. Resolved lazily and only from the fallback path, so a chat given its own factory never asks the context for this bean. Override to supply a different fallback; to replace it application-wide, declare a @Primary DefaultAiChatMessageFactory subclass instead.
      Returns:
      the fallback message factory
    • setState

      protected void setState(GenerationState newState)
    • resolveAnnouncement

      protected String resolveAnnouncement(Function<AiChatI18n,@Nullable String> getter, String fallback)
      Resolves an aria-live announcement: the user-set value, else the bundle-localized default, else the hardcoded fallback.
      Parameters:
      getter - the accessor of the label to resolve
      fallback - the hardcoded fallback
      Returns:
      the announcement text
    • announce

      protected void announce(String message)
    • setItems

      public void setItems(AiChatItems<T> items)
      Binds the chat to the given message source. Replacing the source cancels an in-flight generation and clears the regenerate() target, so a restored conversation does not inherit the previous one's last prompt.
      Parameters:
      items - the message source, not null
    • setItems

      public void setItems(Collection<T> items)
      Sets the messages from a Collection, wrapping them in a ListAiChatItems that copies them, so generation works even for an immutable collection.
      Parameters:
      items - the messages to display, not null
      See Also:
    • setItems

      public void setItems(CollectionContainer<T> container)
      Binds the chat to a Jmix data container, which supplies the history and receives the generated messages, so the chat takes part in the view's ordinary load and save cycle. Messages render in container order, so the loader must sort them chronologically ascending (see warnIfSourceOutOfOrder()).

      The container's entity type must implement AiChatMessage, and a AiChatMessageFactory must be supplied — the component does not create entities itself.

      Parameters:
      container - the container to bind to, not null
      See Also:
    • getItems

      public AiChatItems<T> getItems()
      Returns:
      the bound message source; never null — an unbound chat holds an empty in-memory source
    • bindItems

      protected void bindItems(AiChatItems<T> items)
      Makes the given source the bound one, re-subscribing and re-rendering. The single place that both listens and renders, so the two never diverge.
      Parameters:
      items - the message source to bind, not null
    • onItemsChanged

      protected void onItemsChanged(AiChatItems.ItemsChangeEvent<T> event)
      Handles a change in the bound message source: every AiChatItems.ItemsChangeType rebuilds the rendered list, and a REFRESH additionally re-checks the order. A REFRESH is deliberately not read as a conversation swap — a container fires it on every in-place reload, and cancelling generation there would break the common commit-then-reload pattern. Swaps are recognized only in setItems(AiChatItems).
      Parameters:
      event - the change event
    • warnIfSourceOutOfOrder

      protected void warnIfSourceOutOfOrder()
      Warns — once per binding — when the messages are not in ascending chronological order. The component renders in source order and never re-sorts, so an unsorted loader is a silent defect: the history reads the wrong way round, and regenerateAt(int) rewinds by position, truncating the wrong messages.

      Nothing is re-ordered and nothing throws: AiChatMessage.getTime() is too weak a sort key to carry the component — nullable by contract, and unable to order a turn's own two messages stamped in one commit. So a null is skipped rather than read as a reset, and equal times pass unjudged.

      An ADD is not checked, the chat's own appends being chronological by construction. Override as a no-op to silence the warning for a source whose order is deliberately not chronological.

    • rebuild

      protected void rebuild()
    • toListItem

      protected AiMessageListItem toListItem(T message)
      Maps a data message to a rendered list item, rejecting a null role as a contract violation.

      Carries the source message onto the item, which is what lets a per-message action and an attachment click resolve back to the domain message. The streaming placeholder is built outside this method and deliberately carries none.

      Parameters:
      message - the data message
      Returns:
      the rendered item
    • toRenderRole

      protected MessageRole toRenderRole(AiMessageRole role)
      Projects the contract's role onto the message list's render discriminator.
      Parameters:
      role - the contract role
      Returns:
      the render role
      Throws:
      IllegalArgumentException - if the role has no rendering
    • describe

      protected String describe(T message)
      Names a message in a failure message. Prefers the entity id, since a message entity's toString() rarely identifies the row.
      Parameters:
      message - the message to describe
      Returns:
      a short description for a failure message
    • toDisplayAttachment

      protected AiMessageListItem.Attachment toDisplayAttachment(FileRef ref)
      Maps a stored file reference to a display attachment. Images get a session download-handler URL for an inline thumbnail; other types get a null URL and render as a file chip (download runs through the click event).
      Parameters:
      ref - the stored file reference
      Returns:
      the display attachment
    • imageUrl

      protected String imageUrl(FileRef ref)
      Registers — once per FileRef — a session download handler that streams the file lazily from storage, and returns its URL. Mirrors UiComponentUtils.createResource's FileRef branch, but also registers the handler to obtain a URL string.
      Parameters:
      ref - the stored file reference
      Returns:
      the download URL
    • updateEmptyState

      protected void updateEmptyState()
    • setEmptyStateHeader

      public void setEmptyStateHeader(@Nullable com.vaadin.flow.component.Component component)
      Sets the component shown above the centered input in the empty state.
      Parameters:
      component - the header component, or null to clear
    • getEmptyStateHeader

      public @Nullable com.vaadin.flow.component.Component getEmptyStateHeader()
      Returns:
      the empty-state header component, or null if none
    • setEmptyStateFooter

      public void setEmptyStateFooter(@Nullable com.vaadin.flow.component.Component component)
      Sets the component shown below the centered input in the empty state.
      Parameters:
      component - the footer component, or null to clear
    • getEmptyStateFooter

      public @Nullable com.vaadin.flow.component.Component getEmptyStateFooter()
      Returns:
      the empty-state footer component, or null if none
    • getSubPart

      public @Nullable Object getSubPart(String name)
      Exposes the slot components and the message actions to the framework's id-based component walk. A facade's slot children are reachable neither as own components nor through HasPrefix/HasSuffix, so without this an id declared in a slot could not be injected with @ViewComponent.

      Only the slots nobody else can answer for are resolved here — the input's prefix / suffix and the chat's own empty-state bands; the composed components answer for their own children.

      Specified by:
      getSubPart in interface HasSubParts
      Parameters:
      name - the component id to find
      Returns:
      the matching action, slot component or descendant, or null if none matches
    • findSlotSubPart

      protected Optional<com.vaadin.flow.component.Component> findSlotSubPart(com.vaadin.flow.component.Component slotComponent, String name)
      Resolves an id against a single slot component: the component itself when its own id matches, otherwise a descendant of it.
      Parameters:
      slotComponent - the slot component to search
      name - the component id to find
      Returns:
      the matching component, or an empty Optional
    • addMessageChangeListener

      public com.vaadin.flow.shared.Registration addMessageChangeListener(com.vaadin.flow.component.ComponentEventListener<MessageChangeEvent<T>> listener)
      Adds a listener for message add/remove changes (the persistence hook).
      Parameters:
      listener - the listener
      Returns:
      a registration for removing the listener
    • addGenerationStateChangeListener

      public com.vaadin.flow.shared.Registration addGenerationStateChangeListener(com.vaadin.flow.component.ComponentEventListener<GenerationStateChangeEvent<T>> listener)
      Adds a listener for generation state transitions.
      Parameters:
      listener - the listener
      Returns:
      a registration for removing the listener
    • addGenerationFailedListener

      public com.vaadin.flow.shared.Registration addGenerationFailedListener(com.vaadin.flow.component.ComponentEventListener<GenerationFailedEvent<T>> listener)
      Adds a listener for generation failures.
      Parameters:
      listener - the listener
      Returns:
      a registration for removing the listener
    • addAttachmentClickListener

      public com.vaadin.flow.shared.Registration addAttachmentClickListener(com.vaadin.flow.component.ComponentEventListener<AttachmentClickEvent<T>> listener)
      Adds a listener notified when the user clicks a message attachment.
      Parameters:
      listener - the listener
      Returns:
      a registration for removing the listener
    • addAttachmentRejectedListener

      public com.vaadin.flow.shared.Registration addAttachmentRejectedListener(com.vaadin.flow.component.ComponentEventListener<AttachmentRejectedEvent<T>> listener)
      Adds a listener notified when an attached file is rejected (too many, too large, or wrong type).
      Parameters:
      listener - the listener
      Returns:
      a registration for removing the listener
    • addAction

      public void addAction(Action action, int index)
      Description copied from interface: HasActions
      Add an action to the component at the specified index.
      Specified by:
      addAction in interface HasActions
      Parameters:
      action - action to add
      index - index at which the specified action is to be added
    • removeAction

      public void removeAction(Action action)
      Description copied from interface: HasActions
      Removes the action from the component.
      Specified by:
      removeAction in interface HasActions
      Parameters:
      action - action to remove
    • getActions

      public Collection<Action> getActions()
      Specified by:
      getActions in interface HasActions
      Returns:
      unmodifiable collection of actions
    • getAction

      public @Nullable Action getAction(String id)
      Description copied from interface: HasActions
      Returns an action with passed id.
      Specified by:
      getAction in interface HasActions
      Parameters:
      id - id of the action to find
      Returns:
      an action by its id, or null if not found
    • getActionsSupport

      protected AiChatActionsSupport getActionsSupport()
    • createActionsSupport

      protected AiChatActionsSupport createActionsSupport()
    • fireMessageChange

      protected void fireMessageChange(T message, MessageChangeEvent.ChangeType changeType)
    • buildRequest

      protected com.vaadin.flow.component.ai.provider.LLMProvider.LLMRequest buildRequest(String userMessage, List<com.vaadin.flow.component.ai.common.AIAttachment> attachments)
      Builds an LLMProvider.LLMRequest for the given user message and attachments. Tools are empty in this iteration. Subclasses may override to customize the request (e.g. future tools).
      Parameters:
      userMessage - the user's prompt text
      attachments - the files attached to the message (may be empty)
      Returns:
      the request