Skip to content
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

Added compose example for spring ai and reactjs #516

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ application with a Rust backend and a Postgres database.
with Spring framework and a Postgres database.
- [`WasmEdge / MySQL / Nginx`](wasmedge-mysql-nginx) - Sample Wasm-based web application with a static HTML frontend, using a MySQL (MariaDB) database. The frontend connects to a Wasm microservice written in Rust, that runs using the WasmEdge runtime.&nbsp;<a href="wasmedge-mysql-nginx"><img src="icon_wasm.svg" alt="Compatible with Docker+wasm" height="30" align="top"/></a>
- [`WasmEdge / Kafka / MySQL`](wasmedge-kafka-mysql) - Sample Wasm-based microservice that subscribes to a Kafka (Redpanda) queue topic, and transforms and saves any incoming message into a MySQL (MariaDB) database.&nbsp;<a href="wasmedge-kafka-mysql"><img src="icon_wasm.svg" alt="Compatible with Docker+wasm" height="30" align="top"/></a>
- [`Spring AI / Reactjs`](spring-ai-react-chatbot) - Sample to get started with building a chatbot application using Spring AI and Reactjs using OpenAPI as a sample. The project acts as a good starter for developing AI apps using Spring AI and React and supports almost all popular LLMs.

## Single service samples

Expand Down
82 changes: 82 additions & 0 deletions spring-ai-react-chatbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
## Compose sample application
### Java application with Spring AI and Reactjs

Project structure:
```
├── backend
│   ├── Dockerfile
├── frontend
│   ├── ...
│   └── Dockerfile
|── README.md
├── compose.yaml
```

[_compose.yaml_](compose.yaml)
```
services:
springboot-app:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- SPRING_AI_OPENAI_API_KEY="<OPENAI_API_KEY>"
networks:
- app-network

react-ui:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80" # Serving on port 3000 (Nginx default port 80)
networks:
- app-network
networks:
app-network:
driver: bridge
```
The compose file defines an application with two services `backend` and `frontend`.
When deploying the application, docker compose maps port 8080 of the backend service container to port 8080 of the host as specified in the file. And maps port 3000 of the frontend service container to port 3000 of th hostas specified in the file.
Make sure port 8080 and 3000 on the host is not already being in use.

## Deploy with docker compose

```
$ docker compose up -d
[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/0) docker:defaultr reading preface from client //./pipe/docker_engine: file has already been closed
[+] Building 174.2s (27/27) FINISHED
...
[+] Running 3/3
✔ Network spring-ai-react-chatbot_app-network Created
✔ Container spring-ai-react-chatbot-springboot-app-1 Started
✔ Container spring-ai-react-chatbot-react-ui-1 Started
```

## Expected result

Listing containers must show two containers running and the port mapping as below:
```
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
792d11abca1c spring-ai-react-chatbot-springboot-app "sh -c 'java -jar ap…" 4 minutes ago Up 4 minutes 0.0.0.0:8080->8080/tcp spring-ai-react-chatbot-springboot-app-1
b311be4f9ae3 spring-ai-react-chatbot-react-ui "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:3000->80/tcp spring-ai-react-chatbot-react-ui-1
```

After the application starts, navigate to `http://localhost:3000` in your web browse:

![Chatbot Video](chatbot-ui-screenshot.png)

![Chatbot Image](chatbot.gif)

Stop and remove the containers
```
$ docker compose down
[+] Running 3/3
✔ Container spring-ai-react-chatbot-springboot-app-1 Removed
✔ Container spring-ai-react-chatbot-react-ui-1 Removed
✔ Network spring-ai-react-chatbot_app-network Removed
```
45 changes: 45 additions & 0 deletions spring-ai-react-chatbot/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# # Use an official OpenJDK runtime as a parent image
# FROM openjdk:17-jdk-alpine

# # Set the working directory in the container
# WORKDIR /app

# # Copy the jar file from the target directory (Maven) or build/libs (Gradle)
# ARG JAR_FILE=target/*.jar

# COPY ${JAR_FILE} app.jar

# # Expose the port on which the Spring Boot application will run
# EXPOSE 8080

# # Command to run the jar file
# ENTRYPOINT ["java", "-jar", "app.jar"]


# Use an official Maven image from Docker Hub as the build environment
FROM maven:3.8.4-openjdk-17 AS build

# Set the working directory inside the container
WORKDIR /app

# Copy the pom.xml and project files to the container
COPY pom.xml .
COPY src ./src

# Build the project and package it as a JAR file
RUN mvn clean install

# Use an OpenJDK runtime image for running the application
FROM openjdk:17-jdk-slim

# Set environment variable for the port
ENV SERVER_PORT=8080

# Copy the built JAR file from the build image
COPY --from=build /app/target/*.jar app.jar

# Expose the port defined in the environment variable
EXPOSE ${SERVER_PORT}

# Run the Spring Boot application using java -jar and the environment variable for the port
ENTRYPOINT ["sh", "-c", "java -jar app.jar"]
80 changes: 80 additions & 0 deletions spring-ai-react-chatbot/backend/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.company.ai</groupId>
<artifactId>chatbot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring Boot ChatBot</name>
<description>Your friendly personal assistant powered by OpenAI</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M1</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.company.ai.chatbot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootChatBotApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootChatBotApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.company.ai.chatbot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // Apply CORS to all endpoints
.allowedOrigins("http://localhost:3000") // Allowed origin (replace with your frontend URL)
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") // Allowed methods
.allowedHeaders("*") // Allow all headers
.allowCredentials(true); // Allow credentials (e.g., cookies)
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.company.ai.chatbot.controller;

import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;

@RestController
public class ChatController {

private final OpenAiChatModel chatModel;

@Autowired
public ChatController(OpenAiChatModel chatModel) {
this.chatModel = chatModel;
}

@GetMapping("/ai/chat/prompt")
public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return chatModel.stream(prompt);
}

@GetMapping("/ai/chat")
public Flux<String> generateString(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return chatModel.stream(message).onErrorReturn("Got error while communicating to openai. Please check your application logs for further information");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
spring:
application:
name: "Spring Boot ChatBot"
ai:
openai:
chat:
options:
model: "gpt-3.5-turbo"
temperature: "0.7"
Binary file added spring-ai-react-chatbot/chatbot-ui-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spring-ai-react-chatbot/chatbot.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions spring-ai-react-chatbot/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3.8'

services:
springboot-app:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- SPRING_AI_OPENAI_API_KEY="<OPENAI_API_KEY>"
networks:
- app-network

react-ui:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80" # Serving on port 3000 (Nginx default port 80)
networks:
- app-network

networks:
app-network:
driver: bridge
23 changes: 23 additions & 0 deletions spring-ai-react-chatbot/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
27 changes: 27 additions & 0 deletions spring-ai-react-chatbot/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Stage 1: Build the React app
FROM node:16 as build

# Set working directory
WORKDIR /app

# Copy the package.json and install dependencies
COPY package.json ./
RUN npm install

# Copy the rest of the application code
COPY . ./

# Build the React app
RUN npm run build

# Stage 2: Serve the React app using nginx
FROM nginx:alpine

# Copy the built React app from the previous stage
COPY --from=build /app/build /usr/share/nginx/html

# Expose port 80 to serve the application
EXPOSE 80

# Start nginx
CMD ["nginx", "-g", "daemon off;"]
Loading