Skip to content

Instantly share code, notes, and snippets.

@hocyadav
Last active June 28, 2024 14:51
Show Gist options
  • Save hocyadav/a2b46d3cbe581ff44c28a5f77ba3bec9 to your computer and use it in GitHub Desktop.
Save hocyadav/a2b46d3cbe581ff44c28a5f77ba3bec9 to your computer and use it in GitHub Desktop.
@hocyadav
Copy link
Author

Sure, I'll show you how to replace the mock service with a real ChatClient that streams responses. This example assumes you have the ChatClient configured and ready to use.

Vaadin Component with ChatClient

Here's the updated Vaadin component that uses ChatClient to stream responses:

import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.reactive.function.client.WebClient;

@Route("")
public class MainView extends VerticalLayout {
    
    private final ChatClient chatClient;

    @Autowired
    public MainView(ChatClient chatClient) {
        this.chatClient = chatClient;
        Text loadingText = new Text("Loading...");
        add(loadingText);

        Button startButton = new Button("Start Loading", event -> startLoading(loadingText));
        add(startButton);
    }

    private void startLoading(Text loadingText) {
        Flux<String> dataStream = chatClient.prompt()
                                             .user("Tell me a joke")
                                             .stream()
                                             .content();

        dataStream.subscribeOn(Schedulers.boundedElastic())
                  .doOnNext(item -> getUI().ifPresent(ui -> ui.access(() -> {
                      loadingText.setText(item);
                  })))
                  .doOnComplete(() -> getUI().ifPresent(ui -> ui.access(() -> {
                      loadingText.setText("Completed!");
                  })))
                  .subscribe();
    }
}

ChatClient Configuration

Ensure your ChatClient is configured correctly. Here’s a simple configuration class for ChatClient:

@Configuration
public class ChatClientConfig {

    @Value("${chat.api.key}")
    private String apiKey;

    @Bean
    public ChatClient chatClient() {
        WebClient webClient = WebClient.builder()
                                       .baseUrl("https://api.openai.com/v1")
                                       .defaultHeader("Authorization", "Bearer " + apiKey)
                                       .build();

        return new OpenAiChatClient(webClient);
    }
}

Explanation

  1. ChatClient Configuration:

    • This configuration class sets up the ChatClient using WebClient. It uses the apiKey from your properties file.
  2. MainView:

    • This is the main Vaadin view. It contains a Text component to display the loading status and a Button to start the loading process.
    • The startLoading method subscribes to the Flux returned by chatClient.prompt(), updating the Text component with each emitted item.
  3. Reactive Data Streaming:

    • The Flux<String> is created by calling chatClient.prompt().user("Tell me a joke").stream().content().
    • The dataStream is processed reactively, updating the UI with each new piece of data emitted by the Flux.

Dependencies

Ensure you have the necessary dependencies in your pom.xml or build.gradle:

Maven (pom.xml)

<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>

Gradle (build.gradle)

implementation 'com.vaadin:vaadin-spring-boot-starter'
implementation 'io.projectreactor:reactor-core'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework:spring-webflux'

This setup will allow your Vaadin application to consume streaming data from a ChatClient and update the UI reactively.

@hocyadav
Copy link
Author

Here is a simple template code to create a Vaadin application that consumes a reactive stream using Project Reactor's Flux. This example includes a mock method returning a Flux<String> and the Vaadin code to display the content with a delay of 1 second.

Mock Method with Flux<String>

import reactor.core.publisher.Flux;

import java.time.Duration;

public class DataService {
    public Flux<String> getDataStream() {
        return Flux.just("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
                   .delayElements(Duration.ofSeconds(1));
    }
}

Vaadin View Code

import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import reactor.core.publisher.Flux;

@Route("")
public class MainView extends VerticalLayout {

    public MainView() {
        DataService dataService = new DataService();
        Div container = new Div();
        container.setText("Loading data...");
        add(container);

        Flux<String> dataStream = dataService.getDataStream();
        dataStream.subscribe(
            item -> getUI().ifPresent(ui -> ui.access(() -> container.add(new Div(new Text(item))))),
            error -> getUI().ifPresent(ui -> ui.access(() -> container.add(new Div(new Text("Error: " + error.getMessage()))))),
            () -> getUI().ifPresent(ui -> ui.access(() -> container.add(new Div(new Text("All items loaded.")))))
        );
    }
}

Explanation

  1. Mock Method (DataService):

    • getDataStream returns a Flux<String> that emits items every 1 second.
  2. Vaadin View (MainView):

    • A VerticalLayout is created with a Div to hold the loading data.
    • The DataService is instantiated, and the Flux<String> from getDataStream is subscribed to.
    • For each emitted item, a new Div with the item text is added to the container Div.
    • If an error occurs, it is displayed in the container.
    • Once all items are loaded, a final message is displayed.

This template code should help you get started with a reactive Vaadin application displaying data with a delay. You can customize it further as per your requirements.

@hocyadav
Copy link
Author

Here's a template code for a chat client using a ChatClient bean from the Spring AI dependency. The focus here is on the ChatClient code part within the Vaadin view.

Vaadin View Code Using ChatClient Bean

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Flux;

@Route("")
public class ChatView extends VerticalLayout {

    private final ChatClient chatClient;

    @Autowired
    public ChatView(ChatClient chatClient) {
        this.chatClient = chatClient;

        TextField questionField = new TextField("Enter your question");
        Button sendButton = new Button("Send");
        Div chatArea = new Div();

        sendButton.addClickListener(event -> {
            String question = questionField.getValue();
            Flux<String> responseStream = chatClient.getResponse(question);

            responseStream.subscribe(
                response -> getUI().ifPresent(ui -> ui.access(() -> chatArea.add(new Div(new Text(response))))),
                error -> getUI().ifPresent(ui -> ui.access(() -> chatArea.add(new Div(new Text("Error: " + error.getMessage()))))),
                () -> getUI().ifPresent(ui -> ui.access(() -> chatArea.add(new Div(new Text("Conversation ended.")))))
            );
        });

        add(questionField, sendButton, chatArea);
    }
}

Explanation

  1. Autowired ChatClient:

    • The ChatClient bean is autowired into the ChatView constructor.
  2. Vaadin Components:

    • A TextField for entering the question.
    • A Button to send the question.
    • A Div to display the chat messages.
  3. Button Click Listener:

    • On button click, the question is sent to the ChatClient's getResponse method.
    • The Flux<String> returned by getResponse is subscribed to.
    • Responses are added to the chatArea Div as they arrive.
    • Errors are displayed if they occur.
    • A final message is shown when the conversation ends.

This template demonstrates how to use a ChatClient bean from the Spring AI dependency within a Vaadin view, making the chat functionality reactive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment