diff --git a/.gitignore b/.gitignore
index e282e3b23..77ea2835a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,6 @@ target/
# Branch switching
generated/
+
+**/.DS_Store
+**/creds.yaml
diff --git a/README.md b/README.md
index 0ce9ecbd5..b567465a0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Distributed version of the Spring PetClinic Sample Application built with Spring Cloud
+# Distributed version of the Spring PetClinic Sample Application built with Spring Cloud and Spring AI
[![Build Status](https://github.com/spring-petclinic/spring-petclinic-microservices/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-microservices/actions/workflows/maven-build.yml)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
@@ -17,7 +17,7 @@ If everything goes well, you can access the following services at given location
* Discovery Server - http://localhost:8761
* Config Server - http://localhost:8888
* AngularJS frontend (API Gateway) - http://localhost:8080
-* Customers, Vets and Visits Services - random port, check Eureka Dashboard
+* Customers, Vets, Visits and GenAI Services - random port, check Eureka Dashboard
* Tracing Server (Zipkin) - http://localhost:9411/zipkin/ (we use [openzipkin](https://github.com/openzipkin/zipkin/tree/main/zipkin-server))
* Admin Server (Spring Boot Admin) - http://localhost:9090
* Grafana Dashboards - http://localhost:3000
@@ -46,7 +46,7 @@ For instance, if you target container images for an Apple M2, you could use the
```
Once images are ready, you can start them with a single command
-`docker-compose up` or `podman-compose up`.
+`docker compose up` or `podman-compose up`.
Containers startup order is coordinated with the `service_healthy` condition of the Docker Compose [depends-on](https://github.com/compose-spec/compose-spec/blob/main/spec.md#depends_on) expression
and the [healthcheck](https://github.com/compose-spec/compose-spec/blob/main/spec.md#healthcheck) of the service containers.
@@ -93,7 +93,25 @@ Each service has its own specific role and communicates via REST APIs.
![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram.jpg)
+## Integrating the Spring AI Chatbot
+Spring Petclinic integrates a Chatbot that allows you to interact with the application in a natural language. Here are some examples of what you could ask:
+
+1. Please list the owners that come to the clinic.
+2. Are there any vets that specialize in surgery?
+3. Is there an owner named Betty?
+4. Which owners have dogs?
+5. Add a dog for Betty. Its name is Moopsie.
+6. Create a new owner
+
+![alt text](spring-ai.png)
+
+This Microservice currently supports OpenAI or Azure's OpenAI as the LLM provider.
+In order to enable Spring AI, perform the following steps:
+
+1. Decide which provider you want to use. By default, the `spring-ai-azure-openai-spring-boot-starter` dependency is enabled. You can change it to `spring-ai-openai-spring-boot-starter`in `pom.xml`.
+2. Copy `src/main/resources/creds-template.yaml` into `src/main/resources/creds.yaml`, and edit its contents with your API key and API endpoint. Refer to OpenAI's or Azure's documentation for further information on how to obtain these. You only need to populate the provider you're using - either openai, or azure-openai.
+3. Boot the `spring-petclinic-genai-service` microservice.
## In case you find a bug/suggested improvement for Spring Petclinic Microservices
Our issue tracker is available here: https://github.com/spring-petclinic/spring-petclinic-microservices/issues
diff --git a/docker-compose.yml b/docker-compose.yml
index 49ab25958..8725b5920 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -79,6 +79,23 @@ services:
ports:
- 8083:8083
+
+ genai-service:
+ image: springcommunity/spring-petclinic-genai-service
+ container_name: genai-service
+ deploy:
+ resources:
+ limits:
+ memory: 512M
+ depends_on:
+ config-server:
+ condition: service_healthy
+ discovery-server:
+ condition: service_healthy
+ ports:
+ - 8084:8084
+
+
api-gateway:
image: springcommunity/spring-petclinic-api-gateway
container_name: api-gateway
@@ -131,7 +148,7 @@ services:
limits:
memory: 256M
ports:
- - 3000:3000
+ - 3030:3030
prometheus-server:
build: ./docker/prometheus
diff --git a/pom.xml b/pom.xml
index da8d98809..56c96ea24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,12 +6,12 @@
org.springframework.boot
spring-boot-starter-parent
- 3.2.7
+ 3.3.4
org.springframework.samples
spring-petclinic-microservices
- 3.2.7
+ 3.3.4
${project.artifactId}
pom
@@ -20,6 +20,7 @@
spring-petclinic-customers-service
spring-petclinic-vets-service
spring-petclinic-visits-service
+ spring-petclinic-genai-service
spring-petclinic-config-server
spring-petclinic-discovery-server
spring-petclinic-api-gateway
diff --git a/scripts/run_all.sh b/scripts/run_all.sh
index 8560c917e..18aec7e9b 100755
--- a/scripts/run_all.sh
+++ b/scripts/run_all.sh
@@ -7,10 +7,10 @@ set -o pipefail
pkill -9 -f spring-petclinic || echo "Failed to kill any apps"
-docker-compose kill || echo "No docker containers are running"
+docker compose kill || echo "No docker containers are running"
echo "Running infra"
-docker-compose up -d grafana-server prometheus-server tracing-server
+docker compose up -d grafana-server prometheus-server tracing-server
echo "Running apps"
mkdir -p target
@@ -23,6 +23,7 @@ sleep 20
nohup java -jar spring-petclinic-customers-service/target/*.jar --server.port=8081 --spring.profiles.active=chaos-monkey > target/customers-service.log 2>&1 &
nohup java -jar spring-petclinic-visits-service/target/*.jar --server.port=8082 --spring.profiles.active=chaos-monkey > target/visits-service.log 2>&1 &
nohup java -jar spring-petclinic-vets-service/target/*.jar --server.port=8083 --spring.profiles.active=chaos-monkey > target/vets-service.log 2>&1 &
+nohup java -jar spring-petclinic-genai-service/target/*.jar --server.port=8084 --spring.profiles.active=chaos-monkey > target/genai-service.log 2>&1 &
nohup java -jar spring-petclinic-api-gateway/target/*.jar --server.port=8080 --spring.profiles.active=chaos-monkey > target/gateway-service.log 2>&1 &
nohup java -jar spring-petclinic-admin-server/target/*.jar --server.port=9090 --spring.profiles.active=chaos-monkey > target/admin-server.log 2>&1 &
echo "Waiting for apps to start"
diff --git a/spring-ai.png b/spring-ai.png
new file mode 100644
index 000000000..441de4220
Binary files /dev/null and b/spring-ai.png differ
diff --git a/spring-petclinic-admin-server/pom.xml b/spring-petclinic-admin-server/pom.xml
index 2c537636a..e8faa0f84 100644
--- a/spring-petclinic-admin-server/pom.xml
+++ b/spring-petclinic-admin-server/pom.xml
@@ -12,7 +12,7 @@
org.springframework.samples
spring-petclinic-microservices
- 3.2.7
+ 3.3.4
diff --git a/spring-petclinic-api-gateway/pom.xml b/spring-petclinic-api-gateway/pom.xml
index 2cdb6259b..7d92f68cb 100644
--- a/spring-petclinic-api-gateway/pom.xml
+++ b/spring-petclinic-api-gateway/pom.xml
@@ -11,7 +11,7 @@
org.springframework.samples
spring-petclinic-microservices
- 3.2.7
+ 3.3.4
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java
index df86a6fc2..f872c1c17 100644
--- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/ApiGatewayApplication.java
@@ -84,7 +84,7 @@ RouterFunction> routerFunction() {
public Customizer defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
- .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4)).build())
+ .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build())
.build());
}
}
diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/FallbackController.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/FallbackController.java
new file mode 100644
index 000000000..32853fb33
--- /dev/null
+++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/FallbackController.java
@@ -0,0 +1,16 @@
+package org.springframework.samples.petclinic.api.boundary.web;
+
+import org.apache.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class FallbackController {
+
+ @RequestMapping("/fallback")
+ public ResponseEntity fallback() {
+ return ResponseEntity.status(HttpStatus.SC_SERVICE_UNAVAILABLE)
+ .body("Chat is currently unavailable. Please try again later.");
+ }
+}
diff --git a/spring-petclinic-api-gateway/src/main/resources/application.yml b/spring-petclinic-api-gateway/src/main/resources/application.yml
index 48ead7d0f..f02aa9872 100644
--- a/spring-petclinic-api-gateway/src/main/resources/application.yml
+++ b/spring-petclinic-api-gateway/src/main/resources/application.yml
@@ -5,6 +5,16 @@ spring:
import: optional:configserver:${CONFIG_SERVER_URL:http://localhost:8888/}
cloud:
gateway:
+ default-filters:
+ - name: CircuitBreaker
+ args:
+ name: defaultCircuitBreaker
+ fallbackUri: forward:/fallback
+ - name: Retry
+ args:
+ retries: 1
+ statuses: SERVICE_UNAVAILABLE
+ methods: GET, POST
routes:
- id: vets-service
uri: lb://vets-service
@@ -24,8 +34,13 @@ spring:
- Path=/api/customer/**
filters:
- StripPrefix=2
-
-
+ - id: genai-service
+ uri: lb://genai-service
+ predicates:
+ - Path=/api/genai/**
+ filters:
+ - StripPrefix=2
+ - CircuitBreaker=name=genaiCircuitBreaker,fallbackUri=/fallback
---
spring:
diff --git a/spring-petclinic-api-gateway/src/main/resources/static/css/petclinic.css b/spring-petclinic-api-gateway/src/main/resources/static/css/petclinic.css
index d5fb1c5b9..3928143e7 100644
--- a/spring-petclinic-api-gateway/src/main/resources/static/css/petclinic.css
+++ b/spring-petclinic-api-gateway/src/main/resources/static/css/petclinic.css
@@ -9387,6 +9387,99 @@ table td.action-column {
hr {
border-top: 1px dotted #34302D; }
+/* Chatbox container */
+.chatbox {
+ position: fixed;
+ bottom: 10px;
+ right: 10px;
+ width: 300px;
+ background-color: #f1f1f1;
+ border-radius: 10px;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
+ display: flex;
+ flex-direction: column; }
+ .chatbox.minimized .chatbox-content {
+ height: 40px;
+ /* Height when minimized (header only) */ }
+ .chatbox.minimized .chatbox-messages,
+ .chatbox.minimized .chatbox-footer {
+ display: none; }
+
+/* Header styling */
+.chatbox-header {
+ background-color: #075E54;
+ color: white;
+ padding: 10px;
+ text-align: center;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ cursor: pointer; }
+
+/* Chatbox content styling */
+.chatbox-content {
+ display: flex;
+ flex-direction: column;
+ height: 400px;
+ /* Adjust to desired height */
+ overflow: hidden;
+ /* Hide overflow to make it scrollable */ }
+
+.chatbox-messages {
+ flex-grow: 1;
+ overflow-y: auto;
+ /* Allows vertical scrolling */
+ padding: 10px; }
+
+/* Chat bubbles styling */
+.chat-bubble {
+ max-width: 80%;
+ padding: 10px;
+ border-radius: 20px;
+ margin-bottom: 10px;
+ position: relative;
+ word-wrap: break-word;
+ font-size: 14px; }
+ .chat-bubble strong {
+ font-weight: bold; }
+ .chat-bubble em {
+ font-style: italic; }
+ .chat-bubble.user {
+ background-color: #dcf8c6;
+ /* WhatsApp-style light green */
+ margin-left: auto;
+ text-align: right;
+ border-bottom-right-radius: 0; }
+ .chat-bubble.bot {
+ background-color: #ffffff;
+ margin-right: auto;
+ text-align: left;
+ border-bottom-left-radius: 0;
+ border: 1px solid #e1e1e1; }
+
+/* Input field and button */
+.chatbox-footer {
+ padding: 10px;
+ background-color: #f9f9f9;
+ display: flex; }
+
+.chatbox-footer input {
+ flex-grow: 1;
+ padding: 10px;
+ border-radius: 20px;
+ border: 1px solid #ccc;
+ margin-right: 10px;
+ outline: none; }
+
+.chatbox-footer button {
+ background-color: #075E54;
+ color: white;
+ border: none;
+ padding: 10px;
+ border-radius: 50%;
+ cursor: pointer; }
+ .chatbox-footer button:hover {
+ background-color: #128C7E; }
+
@font-face {
font-family: 'varela_roundregular';
src: url("../fonts/varela_round-webfont.eot");
diff --git a/spring-petclinic-api-gateway/src/main/resources/static/index.html b/spring-petclinic-api-gateway/src/main/resources/static/index.html
index f7e49ef29..70417fc0e 100644
--- a/spring-petclinic-api-gateway/src/main/resources/static/index.html
+++ b/spring-petclinic-api-gateway/src/main/resources/static/index.html
@@ -58,6 +58,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+