Compare commits

...

2 commits

Author SHA1 Message Date
Oded Shopen
809e6f1c1c Example of using RAG in Vets retreival, as well as refactoring, bug fixes and enhancements 2024-09-23 16:39:16 +01:00
Oded Shopen
290bb439d1 Chatbot for Spring Petclinic.
Supports quering the owners, also guides the user through adding a pet to an owner
2024-09-11 13:09:56 +01:00
39 changed files with 3692 additions and 27 deletions

4
.gitignore vendored
View file

@ -15,3 +15,7 @@ build/*
_site/
*.css
!petclinic.css
**/creds.yaml
.tanzu/
tanzu.yml
**/.DS_Store

View file

@ -0,0 +1,25 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: petclinic-route
annotations:
healthcheck.gslb.tanzu.vmware.com/service: spring-petclinic
healthcheck.gslb.tanzu.vmware.com/path: /
healthcheck.gslb.tanzu.vmware.com/port: "80"
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: default-gateway
sectionName: http-petclinic
rules:
- backendRefs:
- group: ""
kind: Service
name: spring-petclinic
port: 8080
weight: 1
matches:
- path:
type: PathPrefix
value: /

View file

@ -0,0 +1,18 @@
apiVersion: apps.tanzu.vmware.com/v1
kind: ContainerApp
metadata:
creationTimestamp: null
name: spring-petclinic
spec:
nonSecretEnv:
- name: SPRING_PROFILES_ACTIVE
value: openai
build:
buildpacks: {}
nonSecretEnv:
- name: BP_JVM_VERSION
value: "17"
path: ../..
ports:
- name: main
port: 8080

View file

@ -23,12 +23,15 @@ java {
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext.webjarsFontawesomeVersion = "4.7.0"
ext.webjarsBootstrapVersion = "5.3.3"
ext.springAiVersion = "1.0.0-M2"
dependencies {
implementation 'org.springframework.ai:spring-ai-azure-openai-spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
@ -53,6 +56,12 @@ dependencies {
checkstyle 'com.puppycrawl.tools:checkstyle:10.16.0'
}
dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
@ -80,4 +89,4 @@ checkFormatAot.enabled = false
checkFormatAotTest.enabled = false
formatAot.enabled = false
formatAotTest.enabled = false
formatAotTest.enabled = false

20
pom.xml
View file

@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.3</version>
</parent>
<name>petclinic</name>
@ -34,11 +34,15 @@
<maven-checkstyle.version>3.3.1</maven-checkstyle.version>
<nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version>
<spring-format.version>0.0.41</spring-format.version>
<spring-ai.version>1.0.0-M2</spring-ai.version>
</properties>
<dependencies>
<!-- Spring and Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
@ -141,6 +145,18 @@
</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>

View file

@ -75,6 +75,29 @@ or
```bash
docker-compose --profile postgres up
```
## 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. How many vets are there?
3. Is there an owner named Betty?
4. Which owners have dogs?
5. Add a dog for Betty. Its name is Moopsie.
![alt text](spring-ai.png)
By default, The Spring AI Chatbot is disabled and will return the message `Chat is currently unavailable. Please try again later.`.
Spring Petclinic 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 either`pom.xml` or in `build.gradle`, depending on your build tool of choice.
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 your application with the `openai` profile. This profile will work for both LLM providers. You can boot the application with that profile using any of the following:
- For maven: `mvn -Dspring-boot.run.profiles=openai spring-boot:run`
- For Gradle: `./gradlew bootRun --args='--spring.profiles.active=openai'`
- For a standard jar file: `SPRING_PROFILES_ACTIVE=openai java -jar build/libs/spring-petclinic-3.3.0.jar` or `SPRING_PROFILES_ACTIVE=openai java -jar target/spring-petclinic-3.3.0-SNAPSHOT.jar`.
## Test Applications

BIN
spring-ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View file

@ -18,11 +18,7 @@ package org.springframework.samples.petclinic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
/**
* PetClinic Spring Boot Application.

View file

@ -0,0 +1,31 @@
package org.springframework.samples.petclinic.genai;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* A Configuration class for beans used by the Chat Client.
*
* @author Oded Shopen
*/
@Configuration
@Profile("openai")
public class AIBeanConfiguration {
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
@Bean
VectorStore vectorStore(EmbeddingModel embeddingModel) {
return new SimpleVectorStore(embeddingModel);
}
}

View file

@ -0,0 +1,72 @@
package org.springframework.samples.petclinic.genai;
import java.util.List;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.OwnerRepository;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Functions that are invoked by the LLM will use this bean to query the system of record
* for information such as listing owners and vers, or adding pets to an owner.
*
* @author Oded Shopen
*/
@Service
@Profile("openai")
public class AIDataProvider {
private final OwnerRepository ownerRepository;
private final VectorStore vectorStore;
public AIDataProvider(OwnerRepository ownerRepository, VetRepository vetRepository, VectorStore vectorStore) {
this.ownerRepository = ownerRepository;
this.vectorStore = vectorStore;
}
public OwnersResponse getAllOwners() {
Pageable pageable = PageRequest.of(0, 100);
Page<Owner> ownerPage = ownerRepository.findAll(pageable);
return new OwnersResponse(ownerPage.getContent());
}
public VetResponse getVets(VetRequest request) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
String vetAsJson = objectMapper.writeValueAsString(request.vet());
SearchRequest sr = SearchRequest.from(SearchRequest.defaults()).withQuery(vetAsJson).withTopK(20);
if (request.vet() == null) {
// Provide a limit of 50 results when zero parameters are sent
sr = sr.withTopK(50);
}
List<Document> topMatches = this.vectorStore.similaritySearch(sr);
List<String> results = topMatches.stream().map(document -> document.getContent()).toList();
return new VetResponse(results);
}
public AddedPetResponse addPetToOwner(AddPetRequest request) {
Owner owner = ownerRepository.findById(request.ownerId());
owner.addPet(request.pet());
this.ownerRepository.save(owner);
return new AddedPetResponse(owner);
}
public OwnerResponse addOwnerToPetclinic(OwnerRequest ownerRequest) {
ownerRepository.save(ownerRequest.owner());
return new OwnerResponse(ownerRequest.owner());
}
}

View file

@ -0,0 +1,93 @@
package org.springframework.samples.petclinic.genai;
import java.util.List;
import java.util.function.Function;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import org.springframework.context.annotation.Profile;
import org.springframework.samples.petclinic.owner.Owner;
import org.springframework.samples.petclinic.owner.Pet;
import org.springframework.samples.petclinic.vet.Vet;
import com.fasterxml.jackson.core.JsonProcessingException;
/**
* This class defines the @Bean functions that the LLM provider will invoke when it
* requires more Information on a given topic. The currently available functions enable
* the LLM to get the list of owners and their pets, get information about the
* veterinarians, and add a pet to an owner.
*
* @author Oded Shopen
*/
@Configuration
@Profile("openai")
class AIFunctionConfiguration {
// The @Description annotation helps the model understand when to call the function
@Bean
@Description("List the owners that the pet clinic has")
public Function<OwnerRequest, OwnersResponse> listOwners(AIDataProvider petclinicAiProvider) {
return request -> {
return petclinicAiProvider.getAllOwners();
};
}
@Bean
@Description("List the veterinarians that the pet clinic has")
public Function<VetRequest, VetResponse> listVets(AIDataProvider petclinicAiProvider) {
return request -> {
try {
return petclinicAiProvider.getVets(request);
}
catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
};
}
@Bean
@Description("Add a pet with the specified petTypeId, " + "to an owner identified by the ownerId. "
+ "The allowed Pet types IDs are only: " + "1 - cat" + "2 - dog" + "3 - lizard" + "4 - snake" + "5 - bird"
+ "6 - hamster")
public Function<AddPetRequest, AddedPetResponse> addPetToOwner(AIDataProvider petclinicAiProvider) {
return request -> {
return petclinicAiProvider.addPetToOwner(request);
};
}
@Bean
@Description("Add a new pet owner to the pet clinic. "
+ "The Owner must include a first name and a last name as two separate words, "
+ "plus an address and a 10-digit phone number")
public Function<OwnerRequest, OwnerResponse> addOwnerToPetclinic(AIDataProvider petclinicAiDataProvider) {
return request -> {
return petclinicAiDataProvider.addOwnerToPetclinic(request);
};
}
}
record AddPetRequest(Pet pet, Integer ownerId) {
};
record OwnerRequest(Owner owner) {
};
record OwnersResponse(List<Owner> owners) {
};
record OwnerResponse(Owner owner) {
};
record AddedPetResponse(Owner owner) {
};
record VetResponse(List<String> vet) {
};
record VetRequest(Vet vet) {
}

View file

@ -0,0 +1,66 @@
package org.springframework.samples.petclinic.genai;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.DEFAULT_CHAT_MEMORY_CONVERSATION_ID;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* This REST controller is being invoked by the in order to interact with the LLM
*
* @author Oded Shopen
*/
@RestController
@RequestMapping("/")
@Profile("openai")
public class PetclinicChatClient {
// ChatModel is the primary interfaces for interacting with an LLM
// it is a request/response interface that implements the ModelModel
// interface. Make suer to visit the source code of the ChatModel and
// checkout the interfaces in the core spring ai package.
private final ChatClient chatClient;
public PetclinicChatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
// @formatter:off
this.chatClient = builder
.defaultSystem("""
You are a friendly AI assistant designed to help with the management of a veterinarian pet clinic called Spring Petclinic.
Your job is to answer questions about and to perform actions on the user's behalf, mainly around
veterinarians, owners, owners' pets and owners' visits.
You are required to answer an a professional manner. If you don't know the answer, politely tell the user
you don't know the answer, then ask the user a followup question to try and clarify the question they are asking.
If you do know the answer, provide the answer but do not provide any additional followup questions.
When dealing with vets, if the user is unsure about the returned results, explain that there may be additional data that was not returned.
Only if the user is asking about the total number of all vets, answer that there are a lot and ask for some additional criteria.
For owners, pets or visits - provide the correct data.
""")
.defaultAdvisors(
// Chat memory helps us keep context when using the chatbot for up to 10 previous messages.
new MessageChatMemoryAdvisor(chatMemory, DEFAULT_CHAT_MEMORY_CONVERSATION_ID, 10), // CHAT MEMORY
new SimpleLoggerAdvisor()
)
.build();
}
@PostMapping("/chatclient")
public String exchange(@RequestBody String query) {
//All chatbot messages go through this endpoint and are passed to the LLM
return
this.chatClient
.prompt()
.user(
u ->
u.text(query)
)
.call()
.content();
}
}

View file

@ -0,0 +1,25 @@
package org.springframework.samples.petclinic.genai;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* This REST controller implements a default behavior for the chat client when AI profile
* is not in use. It will return a default message that chat is not available.
*
* @author Oded Shopen
*/
@RestController
@RequestMapping("/")
@Profile("!openai")
public class PetclinicDisabledChatClient {
@PostMapping("/chatclient")
public String exchange(@RequestBody String query) {
return "Chat is currently unavailable. Please try again later.";
}
}

View file

@ -0,0 +1,105 @@
package org.springframework.samples.petclinic.genai;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.DocumentReader;
import org.springframework.ai.reader.JsonReader;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.annotation.Profile;
import org.springframework.context.event.EventListener;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.samples.petclinic.vet.Vet;
import org.springframework.samples.petclinic.vet.VetRepository;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Loads the veterinarians data into a vector store for the purpose of RAG functionality.
*
* @author Oded Shopen
*/
@Component
@Profile("openai")
public class VectorStoreController {
private final Logger logger = LoggerFactory.getLogger(VectorStoreController.class);
private final VectorStore vectorStore;
private final VetRepository vetRepository;
public VectorStoreController(VectorStore vectorStore, VetRepository vetRepository) throws IOException {
this.vectorStore = vectorStore;
this.vetRepository = vetRepository;
}
@EventListener
public void loadVetDataToVectorStoreOnStartup(ApplicationStartedEvent event) throws IOException {
Resource resource = new ClassPathResource("vectorstore.json");
// Check if file exists
if (resource.exists()) {
// In order to save on AI credits, use a pre-embedded database that was saved
// to
// disk based on the current data in the h2 data.sql file
File file = resource.getFile();
((SimpleVectorStore) this.vectorStore).load(file);
logger.info("vector store loaded from existing vectorstore.json file in the classpath");
return;
}
// If vectorstore.json is deleted, the data will be loaded on startup every time.
// Warning - this can be costly in terms of credits used with the AI provider.
// Fetches all Vet entites and creates a document per vet
Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE);
Page<Vet> vetsPage = vetRepository.findAll(pageable);
Resource vetsAsJson = convertListToJsonResource(vetsPage.getContent());
DocumentReader reader = new JsonReader(vetsAsJson);
List<Document> documents = reader.get();
// add the documents to the vector store
this.vectorStore.add(documents);
if (vectorStore instanceof SimpleVectorStore) {
var file = File.createTempFile("vectorstore", ".json");
((SimpleVectorStore) this.vectorStore).save(file);
logger.info("vector store contents written to {}", file.getAbsolutePath());
}
logger.info("vector store loaded with {} documents", documents.size());
}
public Resource convertListToJsonResource(List<Vet> vets) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// Convert List<Vet> to JSON string
String json = objectMapper.writeValueAsString(vets);
// Convert JSON string to byte array
byte[] jsonBytes = json.getBytes();
// Create a ByteArrayResource from the byte array
return new ByteArrayResource(jsonBytes);
}
catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}

View file

@ -32,6 +32,8 @@ import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
public class BaseEntity implements Serializable {
private static final long serialVersionUID = -3856744164839586177L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

View file

@ -28,6 +28,8 @@ import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
public class NamedEntity extends BaseEntity {
private static final long serialVersionUID = -1827620691768236760L;
@Column(name = "name")
private String name;

View file

@ -27,6 +27,8 @@ import jakarta.validation.constraints.NotBlank;
@MappedSuperclass
public class Person extends BaseEntity {
private static final long serialVersionUID = -5934070342233945557L;
@Column(name = "first_name")
@NotBlank
private String firstName;

View file

@ -46,6 +46,8 @@ import jakarta.validation.constraints.NotBlank;
@Table(name = "owners")
public class Owner extends Person {
private static final long serialVersionUID = 7676019169107660494L;
@Column(name = "address")
@NotBlank
private String address;

View file

@ -17,7 +17,7 @@ package org.springframework.samples.petclinic.owner;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@ -32,9 +32,9 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import jakarta.validation.Valid;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
* @author Juergen Hoeller

View file

@ -44,6 +44,8 @@ import jakarta.persistence.Table;
@Table(name = "pets")
public class Pet extends NamedEntity {
private static final long serialVersionUID = 622048308893169889L;
@Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;

View file

@ -27,4 +27,6 @@ import jakarta.persistence.Table;
@Table(name = "types")
public class PetType extends NamedEntity {
private static final long serialVersionUID = -7611995145056548231L;
}

View file

@ -35,6 +35,8 @@ import jakarta.validation.constraints.NotBlank;
@Table(name = "visits")
public class Visit extends BaseEntity {
private static final long serialVersionUID = -8061148591973721283L;
@Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;

View file

@ -29,4 +29,6 @@ import jakarta.persistence.Table;
@Table(name = "specialties")
public class Specialty extends NamedEntity {
private static final long serialVersionUID = 5551869401872945493L;
}

View file

@ -25,13 +25,16 @@ import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.samples.petclinic.model.Person;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import jakarta.xml.bind.annotation.XmlElement;
/**
* Simple JavaBean domain object representing a veterinarian.
@ -45,6 +48,8 @@ import jakarta.xml.bind.annotation.XmlElement;
@Table(name = "vets")
public class Vet extends Person {
private static final long serialVersionUID = 2216866745632621103L;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
@ -61,19 +66,22 @@ public class Vet extends Person {
this.specialties = specialties;
}
@XmlElement
@JsonProperty("specialties")
@JsonSerialize(as = ArrayList.class)
public List<Specialty> getSpecialties() {
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedSpecs);
}
@JsonIgnore
public int getNrOfSpecialties() {
return getSpecialtiesInternal().size();
}
@JsonIgnore
public void addSpecialty(Specialty specialty) {
getSpecialtiesInternal().add(specialty);
}
}
}

View file

@ -61,7 +61,7 @@ class VetController {
}
private Page<Vet> findPaginated(int page) {
int pageSize = 5;
int pageSize = 20;
Pageable pageable = PageRequest.of(page - 1, pageSize);
return vetRepository.findAll(pageable);
}

View file

@ -19,6 +19,7 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional;
@ -45,6 +46,13 @@ public interface VetRepository extends Repository<Vet, Integer> {
@Cacheable("vets")
Collection<Vet> findAll() throws DataAccessException;
/**
* Count the number of <code>Vet</code>s in the data store.
*/
@Transactional(readOnly = true)
@Query("SELECT COUNT(v) FROM Vet v")
Integer countVets() throws DataAccessException;
/**
* Retrieve all <code>Vet</code>s from data store in Pages
* @param pageable

View file

@ -0,0 +1,18 @@
spring.config.import=optional:classpath:/creds.yaml
#These apply when using spring-ai-azure-openai-spring-boot-starter
spring.ai.azure.openai.chat.options.functions=listOwners,listVets,addPetToOwner,addOwnerToPetclinic
spring.ai.azure.openai.chat.options.temperature: 0.7
#These apply when using spring-ai-openai-spring-boot-starter
#spring.ai.openai.chat.options.functions=listOwners,listVets,addPetToOwner
#spring.ai.openai.chat.options.functions=addOwnerToPetclinic
spring.ai.openai.chat.options.temperature: 0.7
#Enable Spring AI by default
spring.ai.chat.client.enabled=true
logging.level.org.springframework.ai.chat.client.advisor=DEBUG

View file

@ -1,5 +1,6 @@
# database init, supports mysql too
database=h2
spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql
spring.sql.init.data-locations=classpath*:db/${database}/data.sql
@ -23,3 +24,9 @@ logging.level.org.springframework=INFO
# Maximum time static resources should be cached
spring.web.resources.cache.cachecontrol.max-age=12h
#Disable Spring AI by default
spring.ai.chat.client.enabled=false
#Currently these properties require dummy values when a spring AI is in the classpath, even when chat is disabled.
spring.ai.azure.openai.api-key=dummy
spring.ai.azure.openai.endpoint=dummy

View file

@ -0,0 +1,17 @@
spring:
ai:
#These parameters only apply when using the spring-ai-azure-openai-spring-boot-starter dependency:
azure:
openai:
api-key: ""
endpoint: ""
chat:
options:
deployment-name: "gpt-4o"
#These parameters only apply when using the spring-ai-openai-spring-boot-starter dependency:
openai:
api-key: ""
endpoint: ""
chat:
options:
deployment-name: "gpt-4o"

View file

@ -4,16 +4,523 @@ INSERT INTO vets VALUES (default, 'Linda', 'Douglas');
INSERT INTO vets VALUES (default, 'Rafael', 'Ortega');
INSERT INTO vets VALUES (default, 'Henry', 'Stevens');
INSERT INTO vets VALUES (default, 'Sharon', 'Jenkins');
INSERT INTO vets VALUES (default, 'Matthew', 'Alexander');
INSERT INTO vets VALUES (default, 'Alice', 'Anderson');
INSERT INTO vets VALUES (default, 'James', 'Rogers');
INSERT INTO vets VALUES (default, 'Lauren', 'Butler');
INSERT INTO vets VALUES (default, 'Cheryl', 'Rodriguez');
INSERT INTO vets VALUES (default, 'Laura', 'Martin');
INSERT INTO vets VALUES (default, 'Ashley', 'Henderson');
INSERT INTO vets VALUES (default, 'Walter', 'Moore');
INSERT INTO vets VALUES (default, 'Benjamin', 'Hill');
INSERT INTO vets VALUES (default, 'Matthew', 'Myers');
INSERT INTO vets VALUES (default, 'Jean', 'Henderson');
INSERT INTO vets VALUES (default, 'David', 'Phillips');
INSERT INTO vets VALUES (default, 'Jacqueline', 'Ross');
INSERT INTO vets VALUES (default, 'Jacqueline', 'Perry');
INSERT INTO vets VALUES (default, 'William', 'Walker');
INSERT INTO vets VALUES (default, 'Christine', 'Garcia');
INSERT INTO vets VALUES (default, 'Patricia', 'Myers');
INSERT INTO vets VALUES (default, 'Michael', 'Gonzalez');
INSERT INTO vets VALUES (default, 'Joseph', 'Ross');
INSERT INTO vets VALUES (default, 'Paul', 'Walker');
INSERT INTO vets VALUES (default, 'Tyler', 'Reed');
INSERT INTO vets VALUES (default, 'Doris', 'Allen');
INSERT INTO vets VALUES (default, 'Julia', 'Allen');
INSERT INTO vets VALUES (default, 'Walter', 'Cox');
INSERT INTO vets VALUES (default, 'Samantha', 'Walker');
INSERT INTO vets VALUES (default, 'Marie', 'Rodriguez');
INSERT INTO vets VALUES (default, 'Andrew', 'Taylor');
INSERT INTO vets VALUES (default, 'Dorothy', 'Turner');
INSERT INTO vets VALUES (default, 'Aaron', 'Bryant');
INSERT INTO vets VALUES (default, 'Rose', 'Sanders');
INSERT INTO vets VALUES (default, 'Patrick', 'Bell');
INSERT INTO vets VALUES (default, 'Melissa', 'Hall');
INSERT INTO vets VALUES (default, 'Joshua', 'Stewart');
INSERT INTO vets VALUES (default, 'Teresa', 'Walker');
INSERT INTO vets VALUES (default, 'Doris', 'Taylor');
INSERT INTO vets VALUES (default, 'Thomas', 'Perez');
INSERT INTO vets VALUES (default, 'Evelyn', 'Ward');
INSERT INTO vets VALUES (default, 'Lauren', 'Smith');
INSERT INTO vets VALUES (default, 'Ashley', 'Morgan');
INSERT INTO vets VALUES (default, 'Helen', 'Wood');
INSERT INTO vets VALUES (default, 'Deborah', 'Russell');
INSERT INTO vets VALUES (default, 'Alice', 'Bennett');
INSERT INTO vets VALUES (default, 'Timothy', 'Jackson');
INSERT INTO vets VALUES (default, 'Marie', 'Williams');
INSERT INTO vets VALUES (default, 'Aaron', 'Richardson');
INSERT INTO vets VALUES (default, 'Donna', 'Davis');
INSERT INTO vets VALUES (default, 'Daniel', 'Richardson');
INSERT INTO vets VALUES (default, 'Julia', 'Butler');
INSERT INTO vets VALUES (default, 'Doris', 'Russell');
INSERT INTO vets VALUES (default, 'Paul', 'Baker');
INSERT INTO vets VALUES (default, 'Mary', 'Jones');
INSERT INTO vets VALUES (default, 'Alice', 'Peterson');
INSERT INTO vets VALUES (default, 'Eric', 'Robinson');
INSERT INTO vets VALUES (default, 'Henry', 'Smith');
INSERT INTO vets VALUES (default, 'Scott', 'Ford');
INSERT INTO vets VALUES (default, 'Jeffrey', 'King');
INSERT INTO vets VALUES (default, 'Laura', 'Baker');
INSERT INTO vets VALUES (default, 'Alice', 'Ross');
INSERT INTO vets VALUES (default, 'Patrick', 'Howard');
INSERT INTO vets VALUES (default, 'Hannah', 'Price');
INSERT INTO vets VALUES (default, 'Dennis', 'Price');
INSERT INTO vets VALUES (default, 'Joshua', 'Jackson');
INSERT INTO vets VALUES (default, 'Kenneth', 'White');
INSERT INTO vets VALUES (default, 'Mildred', 'Jones');
INSERT INTO vets VALUES (default, 'Evelyn', 'Stewart');
INSERT INTO vets VALUES (default, 'Jacob', 'Stewart');
INSERT INTO vets VALUES (default, 'Douglas', 'Bryant');
INSERT INTO vets VALUES (default, 'Walter', 'Bailey');
INSERT INTO vets VALUES (default, 'Linda', 'Bennett');
INSERT INTO vets VALUES (default, 'Alice', 'Thomas');
INSERT INTO vets VALUES (default, 'Rose', 'Allen');
INSERT INTO vets VALUES (default, 'Daniel', 'White');
INSERT INTO vets VALUES (default, 'Rebecca', 'Rogers');
INSERT INTO vets VALUES (default, 'Melissa', 'Garcia');
INSERT INTO vets VALUES (default, 'Jennifer', 'Phillips');
INSERT INTO vets VALUES (default, 'Tyler', 'Bailey');
INSERT INTO vets VALUES (default, 'Doris', 'Thomas');
INSERT INTO vets VALUES (default, 'Alice', 'Gonzalez');
INSERT INTO vets VALUES (default, 'Sandra', 'Scott');
INSERT INTO vets VALUES (default, 'Helen', 'Ford');
INSERT INTO vets VALUES (default, 'Helen', 'Jackson');
INSERT INTO vets VALUES (default, 'Richard', 'Miller');
INSERT INTO vets VALUES (default, 'Jacob', 'Brooks');
INSERT INTO vets VALUES (default, 'Richard', 'Hill');
INSERT INTO vets VALUES (default, 'Eric', 'Bailey');
INSERT INTO vets VALUES (default, 'Jason', 'Morgan');
INSERT INTO vets VALUES (default, 'Laura', 'Young');
INSERT INTO vets VALUES (default, 'Susan', 'Cooper');
INSERT INTO vets VALUES (default, 'Gary', 'Torres');
INSERT INTO vets VALUES (default, 'Julia', 'James');
INSERT INTO vets VALUES (default, 'Stephanie', 'Young');
INSERT INTO vets VALUES (default, 'Patrick', 'Hayes');
INSERT INTO vets VALUES (default, 'Megan', 'Roberts');
INSERT INTO vets VALUES (default, 'Stephen', 'Rivera');
INSERT INTO vets VALUES (default, 'David', 'Thompson');
INSERT INTO vets VALUES (default, 'Lauren', 'Adams');
INSERT INTO vets VALUES (default, 'Samuel', 'Wilson');
INSERT INTO vets VALUES (default, 'Rose', 'Edwards');
INSERT INTO vets VALUES (default, 'Janet', 'Jones');
INSERT INTO vets VALUES (default, 'Cheryl', 'Smith');
INSERT INTO vets VALUES (default, 'Alice', 'Roberts');
INSERT INTO vets VALUES (default, 'Nicholas', 'Walker');
INSERT INTO vets VALUES (default, 'Nicholas', 'Rodriguez');
INSERT INTO vets VALUES (default, 'Carol', 'Ford');
INSERT INTO vets VALUES (default, 'Thomas', 'Hughes');
INSERT INTO vets VALUES (default, 'Dennis', 'Brooks');
INSERT INTO vets VALUES (default, 'Doris', 'Phillips');
INSERT INTO vets VALUES (default, 'Timothy', 'Ford');
INSERT INTO vets VALUES (default, 'Susan', 'Howard');
INSERT INTO vets VALUES (default, 'Janet', 'Stewart');
INSERT INTO vets VALUES (default, 'Helen', 'Martin');
INSERT INTO vets VALUES (default, 'Rose', 'Jenkins');
INSERT INTO vets VALUES (default, 'Rebecca', 'Parker');
INSERT INTO vets VALUES (default, 'Ryan', 'Barnes');
INSERT INTO vets VALUES (default, 'Ruth', 'Nguyen');
INSERT INTO vets VALUES (default, 'Samantha', 'Rivera');
INSERT INTO vets VALUES (default, 'Alice', 'Roberts');
INSERT INTO vets VALUES (default, 'Keith', 'Howard');
INSERT INTO vets VALUES (default, 'Charles', 'Simmons');
INSERT INTO vets VALUES (default, 'Ryan', 'Kelly');
INSERT INTO vets VALUES (default, 'Martha', 'Campbell');
INSERT INTO vets VALUES (default, 'Mary', 'Thompson');
INSERT INTO vets VALUES (default, 'Eric', 'Wilson');
INSERT INTO vets VALUES (default, 'Charles', 'Russell');
INSERT INTO vets VALUES (default, 'David', 'Rodriguez');
INSERT INTO vets VALUES (default, 'Alice', 'Watson');
INSERT INTO vets VALUES (default, 'Margaret', 'Wright');
INSERT INTO vets VALUES (default, 'Dennis', 'Robinson');
INSERT INTO vets VALUES (default, 'Margaret', 'Turner');
INSERT INTO vets VALUES (default, 'Cheryl', 'Garcia');
INSERT INTO vets VALUES (default, 'Scott', 'Alexander');
INSERT INTO vets VALUES (default, 'Aaron', 'Price');
INSERT INTO vets VALUES (default, 'Patrick', 'Anderson');
INSERT INTO vets VALUES (default, 'Justin', 'Bell');
INSERT INTO vets VALUES (default, 'Melissa', 'Ward');
INSERT INTO vets VALUES (default, 'Paul', 'Perry');
INSERT INTO vets VALUES (default, 'David', 'Clark');
INSERT INTO vets VALUES (default, 'Marie', 'Robinson');
INSERT INTO vets VALUES (default, 'Sandra', 'Cooper');
INSERT INTO vets VALUES (default, 'Lauren', 'Price');
INSERT INTO vets VALUES (default, 'Ashley', 'Martin');
INSERT INTO vets VALUES (default, 'Ruth', 'Jenkins');
INSERT INTO vets VALUES (default, 'Daniel', 'Morris');
INSERT INTO vets VALUES (default, 'Lauren', 'Nguyen');
INSERT INTO vets VALUES (default, 'Charles', 'Torres');
INSERT INTO vets VALUES (default, 'Justin', 'Griffin');
INSERT INTO vets VALUES (default, 'Douglas', 'Jackson');
INSERT INTO vets VALUES (default, 'Gloria', 'Henderson');
INSERT INTO vets VALUES (default, 'Martha', 'Parker');
INSERT INTO vets VALUES (default, 'Jean', 'Martin');
INSERT INTO vets VALUES (default, 'Eric', 'Griffin');
INSERT INTO vets VALUES (default, 'Lauren', 'Wood');
INSERT INTO vets VALUES (default, 'Gary', 'Coleman');
INSERT INTO vets VALUES (default, 'Larry', 'Robinson');
INSERT INTO vets VALUES (default, 'William', 'Cook');
INSERT INTO vets VALUES (default, 'Jacob', 'Jackson');
INSERT INTO vets VALUES (default, 'George', 'Kelly');
INSERT INTO vets VALUES (default, 'Lauren', 'Perez');
INSERT INTO vets VALUES (default, 'Margaret', 'Hall');
INSERT INTO vets VALUES (default, 'Doris', 'Ross');
INSERT INTO vets VALUES (default, 'Adam', 'Miller');
INSERT INTO vets VALUES (default, 'Lauren', 'Richardson');
INSERT INTO vets VALUES (default, 'John', 'Thomas');
INSERT INTO vets VALUES (default, 'Robert', 'Ross');
INSERT INTO vets VALUES (default, 'William', 'Martin');
INSERT INTO vets VALUES (default, 'Maria', 'Sanchez');
INSERT INTO vets VALUES (default, 'Teresa', 'Morgan');
INSERT INTO vets VALUES (default, 'Janet', 'Perry');
INSERT INTO vets VALUES (default, 'Ruby', 'Rogers');
INSERT INTO vets VALUES (default, 'Rose', 'Patterson');
INSERT INTO vets VALUES (default, 'Alexander', 'Ramirez');
INSERT INTO vets VALUES (default, 'Ruth', 'Ross');
INSERT INTO vets VALUES (default, 'Doris', 'Campbell');
INSERT INTO vets VALUES (default, 'Patrick', 'Alexander');
INSERT INTO vets VALUES (default, 'Eric', 'Cox');
INSERT INTO vets VALUES (default, 'Rebecca', 'Myers');
INSERT INTO vets VALUES (default, 'Mildred', 'Long');
INSERT INTO vets VALUES (default, 'Rebecca', 'Ramirez');
INSERT INTO vets VALUES (default, 'Jeffrey', 'Butler');
INSERT INTO vets VALUES (default, 'James', 'Walker');
INSERT INTO vets VALUES (default, 'Melissa', 'Rodriguez');
INSERT INTO vets VALUES (default, 'David', 'Williams');
INSERT INTO vets VALUES (default, 'Megan', 'Henderson');
INSERT INTO vets VALUES (default, 'Patricia', 'Phillips');
INSERT INTO vets VALUES (default, 'Eric', 'Jackson');
INSERT INTO vets VALUES (default, 'Carol', 'Wood');
INSERT INTO vets VALUES (default, 'Andrew', 'Wright');
INSERT INTO vets VALUES (default, 'Anthony', 'Gonzalez');
INSERT INTO vets VALUES (default, 'Shirley', 'Martinez');
INSERT INTO vets VALUES (default, 'Janet', 'Foster');
INSERT INTO vets VALUES (default, 'Justin', 'Watson');
INSERT INTO vets VALUES (default, 'Janet', 'Thomas');
INSERT INTO vets VALUES (default, 'Melissa', 'Taylor');
INSERT INTO vets VALUES (default, 'Angela', 'Cook');
INSERT INTO vets VALUES (default, 'Jeffrey', 'Perez');
INSERT INTO vets VALUES (default, 'Matthew', 'Thomas');
INSERT INTO vets VALUES (default, 'Joshua', 'Adams');
INSERT INTO vets VALUES (default, 'Walter', 'Wright');
INSERT INTO vets VALUES (default, 'Henry', 'Diaz');
INSERT INTO vets VALUES (default, 'Melissa', 'Simmons');
INSERT INTO vets VALUES (default, 'Mary', 'Perez');
INSERT INTO vets VALUES (default, 'Carol', 'Collins');
INSERT INTO vets VALUES (default, 'Alice', 'Henderson');
INSERT INTO vets VALUES (default, 'Marie', 'Allen');
INSERT INTO vets VALUES (default, 'Linda', 'Robinson');
INSERT INTO vets VALUES (default, 'Samantha', 'Simmons');
INSERT INTO vets VALUES (default, 'Jennifer', 'Peterson');
INSERT INTO vets VALUES (default, 'Henry', 'Wright');
INSERT INTO vets VALUES (default, 'Angela', 'Russell');
INSERT INTO vets VALUES (default, 'Jean', 'Wilson');
INSERT INTO vets VALUES (default, 'George', 'Clark');
INSERT INTO vets VALUES (default, 'Lauren', 'Johnson');
INSERT INTO vets VALUES (default, 'Alice', 'James');
INSERT INTO vets VALUES (default, 'Dorothy', 'Lewis');
INSERT INTO vets VALUES (default, 'Carolyn', 'Hayes');
INSERT INTO vets VALUES (default, 'Lauren', 'Coleman');
INSERT INTO vets VALUES (default, 'Evelyn', 'Ramirez');
INSERT INTO vets VALUES (default, 'Alexander', 'Stewart');
INSERT INTO vets VALUES (default, 'John', 'Peterson');
INSERT INTO vets VALUES (default, 'Patrick', 'Edwards');
INSERT INTO vets VALUES (default, 'Evelyn', 'Collins');
INSERT INTO vets VALUES (default, 'Anthony', 'Perez');
INSERT INTO vets VALUES (default, 'Andrew', 'Lewis');
INSERT INTO vets VALUES (default, 'Richard', 'James');
INSERT INTO vets VALUES (default, 'William', 'Garcia');
INSERT INTO vets VALUES (default, 'Helen', 'Patterson');
INSERT INTO vets VALUES (default, 'Walter', 'Lewis');
INSERT INTO vets VALUES (default, 'Doris', 'Hill');
INSERT INTO vets VALUES (default, 'Scott', 'Phillips');
INSERT INTO vets VALUES (default, 'Elizabeth', 'Wood');
INSERT INTO vets VALUES (default, 'Paul', 'Hayes');
INSERT INTO vets VALUES (default, 'Mark', 'Howard');
INSERT INTO vets VALUES (default, 'Barbara', 'Coleman');
INSERT INTO vets VALUES (default, 'Andrew', 'Wood');
INSERT INTO vets VALUES (default, 'Ruth', 'Moore');
INSERT INTO vets VALUES (default, 'Sandra', 'Brooks');
INSERT INTO vets VALUES (default, 'Eric', 'Garcia');
INSERT INTO vets VALUES (default, 'Deborah', 'Ward');
INSERT INTO vets VALUES (default, 'James', 'Davis');
INSERT INTO vets VALUES (default, 'Samantha', 'Adams');
INSERT INTO vets VALUES (default, 'Nicholas', 'Johnson');
INSERT INTO vets VALUES (default, 'Joshua', 'Murphy');
INSERT INTO vets VALUES (default, 'Rebecca', 'Flores');
INSERT INTO vets VALUES (default, 'Emma', 'Bell');
INSERT INTO vets VALUES (default, 'Jerry', 'Nelson');
INSERT INTO vets VALUES (default, 'Ruth', 'Cox');
INSERT INTO vets VALUES (default, 'Gloria', 'Powell');
INSERT INTO vets VALUES (default, 'Shirley', 'Clark');
INSERT INTO vets VALUES (default, 'Larry', 'Bryant');
INSERT INTO vets VALUES (default, 'George', 'Brown');
INSERT INTO specialties VALUES (default, 'radiology');
INSERT INTO specialties VALUES (default, 'surgery');
INSERT INTO specialties VALUES (default, 'dentistry');
INSERT INTO vet_specialties VALUES (2, 1);
INSERT INTO vet_specialties VALUES (3, 2);
INSERT INTO vet_specialties VALUES (3, 3);
INSERT INTO vet_specialties VALUES (4, 2);
INSERT INTO vet_specialties VALUES (5, 1);
-- First, let's make sure we have 5 specialties
INSERT INTO specialties (name) VALUES ('radiology');
INSERT INTO specialties (name) VALUES ('surgery');
INSERT INTO specialties (name) VALUES ('dentistry');
INSERT INTO specialties (name) VALUES ('cardiology');
INSERT INTO specialties (name) VALUES ('anesthesia');
INSERT INTO vet_specialties VALUES ('220', '2');
INSERT INTO vet_specialties VALUES ('131', '1');
INSERT INTO vet_specialties VALUES ('58', '3');
INSERT INTO vet_specialties VALUES ('43', '4');
INSERT INTO vet_specialties VALUES ('110', '3');
INSERT INTO vet_specialties VALUES ('63', '5');
INSERT INTO vet_specialties VALUES ('206', '4');
INSERT INTO vet_specialties VALUES ('29', '3');
INSERT INTO vet_specialties VALUES ('189', '3');
INSERT INTO vet_specialties VALUES ('202', '4');
INSERT INTO vet_specialties VALUES ('75', '4');
INSERT INTO vet_specialties VALUES ('156', '3');
INSERT INTO vet_specialties VALUES ('218', '5');
INSERT INTO vet_specialties VALUES ('152', '4');
INSERT INTO vet_specialties VALUES ('173', '2');
INSERT INTO vet_specialties VALUES ('251', '2');
INSERT INTO vet_specialties VALUES ('99', '3');
INSERT INTO vet_specialties VALUES ('157', '2');
INSERT INTO vet_specialties VALUES ('250', '3');
INSERT INTO vet_specialties VALUES ('37', '1');
INSERT INTO vet_specialties VALUES ('197', '3');
INSERT INTO vet_specialties VALUES ('121', '3');
INSERT INTO vet_specialties VALUES ('176', '1');
INSERT INTO vet_specialties VALUES ('72', '3');
INSERT INTO vet_specialties VALUES ('160', '1');
INSERT INTO vet_specialties VALUES ('56', '5');
INSERT INTO vet_specialties VALUES ('248', '4');
INSERT INTO vet_specialties VALUES ('49', '5');
INSERT INTO vet_specialties VALUES ('61', '5');
INSERT INTO vet_specialties VALUES ('29', '4');
INSERT INTO vet_specialties VALUES ('238', '2');
INSERT INTO vet_specialties VALUES ('176', '4');
INSERT INTO vet_specialties VALUES ('46', '4');
INSERT INTO vet_specialties VALUES ('189', '5');
INSERT INTO vet_specialties VALUES ('56', '2');
INSERT INTO vet_specialties VALUES ('200', '5');
INSERT INTO vet_specialties VALUES ('70', '3');
INSERT INTO vet_specialties VALUES ('213', '5');
INSERT INTO vet_specialties VALUES ('106', '2');
INSERT INTO vet_specialties VALUES ('219', '2');
INSERT INTO vet_specialties VALUES ('81', '3');
INSERT INTO vet_specialties VALUES ('126', '5');
INSERT INTO vet_specialties VALUES ('255', '2');
INSERT INTO vet_specialties VALUES ('175', '4');
INSERT INTO vet_specialties VALUES ('71', '2');
INSERT INTO vet_specialties VALUES ('206', '1');
INSERT INTO vet_specialties VALUES ('85', '2');
INSERT INTO vet_specialties VALUES ('177', '1');
INSERT INTO vet_specialties VALUES ('118', '2');
INSERT INTO vet_specialties VALUES ('256', '4');
INSERT INTO vet_specialties VALUES ('136', '3');
INSERT INTO vet_specialties VALUES ('44', '5');
INSERT INTO vet_specialties VALUES ('236', '4');
INSERT INTO vet_specialties VALUES ('2', '5');
INSERT INTO vet_specialties VALUES ('88', '1');
INSERT INTO vet_specialties VALUES ('86', '1');
INSERT INTO vet_specialties VALUES ('155', '2');
INSERT INTO vet_specialties VALUES ('122', '5');
INSERT INTO vet_specialties VALUES ('61', '3');
INSERT INTO vet_specialties VALUES ('24', '5');
INSERT INTO vet_specialties VALUES ('14', '4');
INSERT INTO vet_specialties VALUES ('159', '2');
INSERT INTO vet_specialties VALUES ('67', '4');
INSERT INTO vet_specialties VALUES ('182', '2');
INSERT INTO vet_specialties VALUES ('10', '1');
INSERT INTO vet_specialties VALUES ('174', '4');
INSERT INTO vet_specialties VALUES ('166', '5');
INSERT INTO vet_specialties VALUES ('91', '3');
INSERT INTO vet_specialties VALUES ('244', '5');
INSERT INTO vet_specialties VALUES ('135', '2');
INSERT INTO vet_specialties VALUES ('222', '1');
INSERT INTO vet_specialties VALUES ('151', '3');
INSERT INTO vet_specialties VALUES ('79', '4');
INSERT INTO vet_specialties VALUES ('182', '3');
INSERT INTO vet_specialties VALUES ('249', '1');
INSERT INTO vet_specialties VALUES ('133', '1');
INSERT INTO vet_specialties VALUES ('127', '4');
INSERT INTO vet_specialties VALUES ('123', '5');
INSERT INTO vet_specialties VALUES ('248', '5');
INSERT INTO vet_specialties VALUES ('99', '1');
INSERT INTO vet_specialties VALUES ('252', '5');
INSERT INTO vet_specialties VALUES ('221', '1');
INSERT INTO vet_specialties VALUES ('123', '2');
INSERT INTO vet_specialties VALUES ('16', '3');
INSERT INTO vet_specialties VALUES ('180', '2');
INSERT INTO vet_specialties VALUES ('198', '3');
INSERT INTO vet_specialties VALUES ('214', '3');
INSERT INTO vet_specialties VALUES ('144', '2');
INSERT INTO vet_specialties VALUES ('140', '4');
INSERT INTO vet_specialties VALUES ('233', '2');
INSERT INTO vet_specialties VALUES ('87', '5');
INSERT INTO vet_specialties VALUES ('12', '4');
INSERT INTO vet_specialties VALUES ('2', '3');
INSERT INTO vet_specialties VALUES ('156', '4');
INSERT INTO vet_specialties VALUES ('105', '4');
INSERT INTO vet_specialties VALUES ('144', '3');
INSERT INTO vet_specialties VALUES ('31', '2');
INSERT INTO vet_specialties VALUES ('228', '5');
INSERT INTO vet_specialties VALUES ('232', '3');
INSERT INTO vet_specialties VALUES ('33', '1');
INSERT INTO vet_specialties VALUES ('195', '2');
INSERT INTO vet_specialties VALUES ('12', '1');
INSERT INTO vet_specialties VALUES ('180', '1');
INSERT INTO vet_specialties VALUES ('122', '2');
INSERT INTO vet_specialties VALUES ('101', '1');
INSERT INTO vet_specialties VALUES ('211', '2');
INSERT INTO vet_specialties VALUES ('175', '2');
INSERT INTO vet_specialties VALUES ('246', '1');
INSERT INTO vet_specialties VALUES ('251', '4');
INSERT INTO vet_specialties VALUES ('13', '4');
INSERT INTO vet_specialties VALUES ('217', '2');
INSERT INTO vet_specialties VALUES ('171', '3');
INSERT INTO vet_specialties VALUES ('125', '5');
INSERT INTO vet_specialties VALUES ('129', '3');
INSERT INTO vet_specialties VALUES ('217', '3');
INSERT INTO vet_specialties VALUES ('68', '4');
INSERT INTO vet_specialties VALUES ('13', '2');
INSERT INTO vet_specialties VALUES ('214', '2');
INSERT INTO vet_specialties VALUES ('128', '1');
INSERT INTO vet_specialties VALUES ('253', '2');
INSERT INTO vet_specialties VALUES ('36', '1');
INSERT INTO vet_specialties VALUES ('210', '2');
INSERT INTO vet_specialties VALUES ('81', '5');
INSERT INTO vet_specialties VALUES ('237', '5');
INSERT INTO vet_specialties VALUES ('78', '3');
INSERT INTO vet_specialties VALUES ('244', '1');
INSERT INTO vet_specialties VALUES ('37', '4');
INSERT INTO vet_specialties VALUES ('230', '2');
INSERT INTO vet_specialties VALUES ('9', '3');
INSERT INTO vet_specialties VALUES ('249', '5');
INSERT INTO vet_specialties VALUES ('210', '5');
INSERT INTO vet_specialties VALUES ('33', '5');
INSERT INTO vet_specialties VALUES ('177', '2');
INSERT INTO vet_specialties VALUES ('92', '3');
INSERT INTO vet_specialties VALUES ('18', '5');
INSERT INTO vet_specialties VALUES ('82', '4');
INSERT INTO vet_specialties VALUES ('185', '1');
INSERT INTO vet_specialties VALUES ('70', '1');
INSERT INTO vet_specialties VALUES ('146', '2');
INSERT INTO vet_specialties VALUES ('60', '2');
INSERT INTO vet_specialties VALUES ('157', '4');
INSERT INTO vet_specialties VALUES ('43', '1');
INSERT INTO vet_specialties VALUES ('124', '4');
INSERT INTO vet_specialties VALUES ('185', '2');
INSERT INTO vet_specialties VALUES ('92', '2');
INSERT INTO vet_specialties VALUES ('152', '5');
INSERT INTO vet_specialties VALUES ('161', '3');
INSERT INTO vet_specialties VALUES ('178', '3');
INSERT INTO vet_specialties VALUES ('53', '2');
INSERT INTO vet_specialties VALUES ('93', '5');
INSERT INTO vet_specialties VALUES ('168', '2');
INSERT INTO vet_specialties VALUES ('19', '4');
INSERT INTO vet_specialties VALUES ('158', '2');
INSERT INTO vet_specialties VALUES ('240', '4');
INSERT INTO vet_specialties VALUES ('246', '5');
INSERT INTO vet_specialties VALUES ('166', '4');
INSERT INTO vet_specialties VALUES ('134', '5');
INSERT INTO vet_specialties VALUES ('151', '4');
INSERT INTO vet_specialties VALUES ('113', '1');
INSERT INTO vet_specialties VALUES ('114', '4');
INSERT INTO vet_specialties VALUES ('107', '3');
INSERT INTO vet_specialties VALUES ('200', '2');
INSERT INTO vet_specialties VALUES ('17', '4');
INSERT INTO vet_specialties VALUES ('140', '5');
INSERT INTO vet_specialties VALUES ('196', '2');
INSERT INTO vet_specialties VALUES ('108', '1');
INSERT INTO vet_specialties VALUES ('102', '3');
INSERT INTO vet_specialties VALUES ('83', '4');
INSERT INTO vet_specialties VALUES ('79', '1');
INSERT INTO vet_specialties VALUES ('91', '4');
INSERT INTO vet_specialties VALUES ('30', '4');
INSERT INTO vet_specialties VALUES ('165', '3');
INSERT INTO vet_specialties VALUES ('34', '1');
INSERT INTO vet_specialties VALUES ('204', '4');
INSERT INTO vet_specialties VALUES ('30', '5');
INSERT INTO vet_specialties VALUES ('84', '5');
INSERT INTO vet_specialties VALUES ('187', '5');
INSERT INTO vet_specialties VALUES ('127', '1');
INSERT INTO vet_specialties VALUES ('229', '5');
INSERT INTO vet_specialties VALUES ('71', '1');
INSERT INTO vet_specialties VALUES ('253', '3');
INSERT INTO vet_specialties VALUES ('102', '1');
INSERT INTO vet_specialties VALUES ('195', '1');
INSERT INTO vet_specialties VALUES ('149', '3');
INSERT INTO vet_specialties VALUES ('238', '5');
INSERT INTO vet_specialties VALUES ('113', '3');
INSERT INTO vet_specialties VALUES ('105', '1');
INSERT INTO vet_specialties VALUES ('111', '2');
INSERT INTO vet_specialties VALUES ('20', '1');
INSERT INTO vet_specialties VALUES ('52', '4');
INSERT INTO vet_specialties VALUES ('226', '5');
INSERT INTO vet_specialties VALUES ('216', '1');
INSERT INTO vet_specialties VALUES ('136', '5');
INSERT INTO vet_specialties VALUES ('250', '2');
INSERT INTO vet_specialties VALUES ('229', '2');
INSERT INTO vet_specialties VALUES ('198', '4');
INSERT INTO vet_specialties VALUES ('73', '1');
INSERT INTO vet_specialties VALUES ('128', '4');
INSERT INTO vet_specialties VALUES ('161', '4');
INSERT INTO vet_specialties VALUES ('111', '1');
INSERT INTO vet_specialties VALUES ('146', '1');
INSERT INTO vet_specialties VALUES ('60', '5');
INSERT INTO vet_specialties VALUES ('235', '5');
INSERT INTO vet_specialties VALUES ('174', '2');
INSERT INTO vet_specialties VALUES ('225', '5');
INSERT INTO vet_specialties VALUES ('224', '1');
INSERT INTO vet_specialties VALUES ('256', '1');
INSERT INTO vet_specialties VALUES ('132', '3');
INSERT INTO vet_specialties VALUES ('211', '4');
INSERT INTO vet_specialties VALUES ('104', '4');
INSERT INTO vet_specialties VALUES ('112', '3');
INSERT INTO vet_specialties VALUES ('213', '1');
INSERT INTO vet_specialties VALUES ('108', '5');
INSERT INTO vet_specialties VALUES ('143', '2');
INSERT INTO vet_specialties VALUES ('141', '5');
INSERT INTO vet_specialties VALUES ('159', '1');
INSERT INTO vet_specialties VALUES ('228', '3');
INSERT INTO vet_specialties VALUES ('75', '5');
INSERT INTO vet_specialties VALUES ('36', '5');
INSERT INTO vet_specialties VALUES ('237', '1');
INSERT INTO vet_specialties VALUES ('83', '5');
INSERT INTO vet_specialties VALUES ('124', '1');
INSERT INTO vet_specialties VALUES ('133', '2');
INSERT INTO vet_specialties VALUES ('121', '5');
INSERT INTO vet_specialties VALUES ('240', '5');
INSERT INTO vet_specialties VALUES ('142', '3');
INSERT INTO vet_specialties VALUES ('193', '3');
INSERT INTO vet_specialties VALUES ('252', '3');
INSERT INTO vet_specialties VALUES ('118', '4');
INSERT INTO vet_specialties VALUES ('26', '3');
INSERT INTO vet_specialties VALUES ('197', '2');
INSERT INTO vet_specialties VALUES ('17', '2');
INSERT INTO vet_specialties VALUES ('78', '2');
INSERT INTO vet_specialties VALUES ('26', '5');
INSERT INTO vet_specialties VALUES ('196', '1');
INSERT INTO vet_specialties VALUES ('141', '2');
INSERT INTO vet_specialties VALUES ('224', '2');
INSERT INTO vet_specialties VALUES ('223', '5');
INSERT INTO vet_specialties VALUES ('170', '3');
INSERT INTO vet_specialties VALUES ('9', '2');
INSERT INTO vet_specialties VALUES ('94', '1');
INSERT INTO vet_specialties VALUES ('230', '3');
INSERT INTO vet_specialties VALUES ('131', '5');
INSERT INTO vet_specialties VALUES ('117', '3');
INSERT INTO vet_specialties VALUES ('223', '4');
INSERT INTO vet_specialties VALUES ('236', '2');
INSERT INTO vet_specialties VALUES ('97', '2');
INSERT INTO vet_specialties VALUES ('143', '1');
INSERT INTO vet_specialties VALUES ('173', '1');
INSERT INTO vet_specialties VALUES ('132', '5');
INSERT INTO vet_specialties VALUES ('138', '1');
INSERT INTO vet_specialties VALUES ('80', '5');
INSERT INTO vet_specialties VALUES ('6', '5');
INSERT INTO vet_specialties VALUES ('216', '3');
INSERT INTO vet_specialties VALUES ('135', '5');
INSERT INTO vet_specialties VALUES ('160', '3');
INSERT INTO vet_specialties VALUES ('97', '4');
INSERT INTO types VALUES (default, 'cat');
INSERT INTO types VALUES (default, 'dog');
@ -51,3 +558,5 @@ INSERT INTO visits VALUES (default, 7, '2013-01-01', 'rabies shot');
INSERT INTO visits VALUES (default, 8, '2013-01-02', 'rabies shot');
INSERT INTO visits VALUES (default, 8, '2013-01-03', 'neutered');
INSERT INTO visits VALUES (default, 7, '2013-01-04', 'spayed');

View file

@ -0,0 +1,262 @@
-- Create a list of first names and last names
WITH first_names AS (
SELECT 'James' AS name UNION ALL
SELECT 'Mary' UNION ALL
SELECT 'John' UNION ALL
SELECT 'Patricia' UNION ALL
SELECT 'Robert' UNION ALL
SELECT 'Linda' UNION ALL
SELECT 'Michael' UNION ALL
SELECT 'Barbara' UNION ALL
SELECT 'William' UNION ALL
SELECT 'Elizabeth' UNION ALL
SELECT 'David' UNION ALL
SELECT 'Jennifer' UNION ALL
SELECT 'Richard' UNION ALL
SELECT 'Maria' UNION ALL
SELECT 'Charles' UNION ALL
SELECT 'Susan' UNION ALL
SELECT 'Joseph' UNION ALL
SELECT 'Margaret' UNION ALL
SELECT 'Thomas' UNION ALL
SELECT 'Dorothy' UNION ALL
SELECT 'Daniel' UNION ALL
SELECT 'Helen' UNION ALL
SELECT 'Matthew' UNION ALL
SELECT 'Sandra' UNION ALL
SELECT 'Anthony' UNION ALL
SELECT 'Ashley' UNION ALL
SELECT 'Mark' UNION ALL
SELECT 'Donna' UNION ALL
SELECT 'Paul' UNION ALL
SELECT 'Carol' UNION ALL
SELECT 'Andrew' UNION ALL
SELECT 'Ruth' UNION ALL
SELECT 'Joshua' UNION ALL
SELECT 'Shirley' UNION ALL
SELECT 'Kenneth' UNION ALL
SELECT 'Angela' UNION ALL
SELECT 'Kevin' UNION ALL
SELECT 'Melissa' UNION ALL
SELECT 'Brian' UNION ALL
SELECT 'Deborah' UNION ALL
SELECT 'George' UNION ALL
SELECT 'Stephanie' UNION ALL
SELECT 'Edward' UNION ALL
SELECT 'Rebecca' UNION ALL
SELECT 'Ronald' UNION ALL
SELECT 'Laura' UNION ALL
SELECT 'Timothy' UNION ALL
SELECT 'Helen' UNION ALL
SELECT 'Jason' UNION ALL
SELECT 'Alice' UNION ALL
SELECT 'Jeffrey' UNION ALL
SELECT 'Judith' UNION ALL
SELECT 'Ryan' UNION ALL
SELECT 'Jacqueline' UNION ALL
SELECT 'Jacob' UNION ALL
SELECT 'Frances' UNION ALL
SELECT 'Gary' UNION ALL
SELECT 'Martha' UNION ALL
SELECT 'Nicholas' UNION ALL
SELECT 'Teresa' UNION ALL
SELECT 'Eric' UNION ALL
SELECT 'Doris' UNION ALL
SELECT 'Stephen' UNION ALL
SELECT 'Gloria' UNION ALL
SELECT 'Larry' UNION ALL
SELECT 'Evelyn' UNION ALL
SELECT 'Justin' UNION ALL
SELECT 'Jean' UNION ALL
SELECT 'Scott' UNION ALL
SELECT 'Cheryl' UNION ALL
SELECT 'Brandon' UNION ALL
SELECT 'Mildred' UNION ALL
SELECT 'Benjamin' UNION ALL
SELECT 'Katherine' UNION ALL
SELECT 'Adam' UNION ALL
SELECT 'Samantha' UNION ALL
SELECT 'Samuel' UNION ALL
SELECT 'Janet' UNION ALL
SELECT 'Alexander' UNION ALL
SELECT 'Megan' UNION ALL
SELECT 'Patrick' UNION ALL
SELECT 'Carolyn' UNION ALL
SELECT 'Jack' UNION ALL
SELECT 'Hannah' UNION ALL
SELECT 'Dennis' UNION ALL
SELECT 'Christine' UNION ALL
SELECT 'Jerry' UNION ALL
SELECT 'Emma' UNION ALL
SELECT 'Tyler' UNION ALL
SELECT 'Lauren' UNION ALL
SELECT 'Aaron' UNION ALL
SELECT 'Alice' UNION ALL
SELECT 'Henry' UNION ALL
SELECT 'Julia' UNION ALL
SELECT 'Douglas' UNION ALL
SELECT 'Marie' UNION ALL
SELECT 'Keith' UNION ALL
SELECT 'Ruby' UNION ALL
SELECT 'Walter' UNION ALL
SELECT 'Rose'
),
last_names AS (
SELECT 'Smith' AS name UNION ALL
SELECT 'Johnson' UNION ALL
SELECT 'Williams' UNION ALL
SELECT 'Jones' UNION ALL
SELECT 'Brown' UNION ALL
SELECT 'Davis' UNION ALL
SELECT 'Miller' UNION ALL
SELECT 'Wilson' UNION ALL
SELECT 'Moore' UNION ALL
SELECT 'Taylor' UNION ALL
SELECT 'Anderson' UNION ALL
SELECT 'Thomas' UNION ALL
SELECT 'Jackson' UNION ALL
SELECT 'White' UNION ALL
SELECT 'Harris' UNION ALL
SELECT 'Martin' UNION ALL
SELECT 'Thompson' UNION ALL
SELECT 'Garcia' UNION ALL
SELECT 'Martinez' UNION ALL
SELECT 'Robinson' UNION ALL
SELECT 'Clark' UNION ALL
SELECT 'Rodriguez' UNION ALL
SELECT 'Lewis' UNION ALL
SELECT 'Lee' UNION ALL
SELECT 'Walker' UNION ALL
SELECT 'Hall' UNION ALL
SELECT 'Allen' UNION ALL
SELECT 'Young' UNION ALL
SELECT 'King' UNION ALL
SELECT 'Wright' UNION ALL
SELECT 'Scott' UNION ALL
SELECT 'Torres' UNION ALL
SELECT 'Nguyen' UNION ALL
SELECT 'Hill' UNION ALL
SELECT 'Adams' UNION ALL
SELECT 'Baker' UNION ALL
SELECT 'Nelson' UNION ALL
SELECT 'Carter' UNION ALL
SELECT 'Mitchell' UNION ALL
SELECT 'Perez' UNION ALL
SELECT 'Roberts' UNION ALL
SELECT 'Turner' UNION ALL
SELECT 'Phillips' UNION ALL
SELECT 'Campbell' UNION ALL
SELECT 'Parker' UNION ALL
SELECT 'Evans' UNION ALL
SELECT 'Edwards' UNION ALL
SELECT 'Collins' UNION ALL
SELECT 'Stewart' UNION ALL
SELECT 'Sanchez' UNION ALL
SELECT 'Morris' UNION ALL
SELECT 'Rogers' UNION ALL
SELECT 'Reed' UNION ALL
SELECT 'Cook' UNION ALL
SELECT 'Morgan' UNION ALL
SELECT 'Bell' UNION ALL
SELECT 'Murphy' UNION ALL
SELECT 'Bailey' UNION ALL
SELECT 'Rivera' UNION ALL
SELECT 'Cooper' UNION ALL
SELECT 'Richardson' UNION ALL
SELECT 'Cox' UNION ALL
SELECT 'Howard' UNION ALL
SELECT 'Ward' UNION ALL
SELECT 'Torres' UNION ALL
SELECT 'Peterson' UNION ALL
SELECT 'Gray' UNION ALL
SELECT 'Ramirez' UNION ALL
SELECT 'James' UNION ALL
SELECT 'Watson' UNION ALL
SELECT 'Brooks' UNION ALL
SELECT 'Kelly' UNION ALL
SELECT 'Sanders' UNION ALL
SELECT 'Price' UNION ALL
SELECT 'Bennett' UNION ALL
SELECT 'Wood' UNION ALL
SELECT 'Barnes' UNION ALL
SELECT 'Ross' UNION ALL
SELECT 'Henderson' UNION ALL
SELECT 'Coleman' UNION ALL
SELECT 'Jenkins' UNION ALL
SELECT 'Perry' UNION ALL
SELECT 'Powell' UNION ALL
SELECT 'Long' UNION ALL
SELECT 'Patterson' UNION ALL
SELECT 'Hughes' UNION ALL
SELECT 'Flores' UNION ALL
SELECT 'Washington' UNION ALL
SELECT 'Butler' UNION ALL
SELECT 'Simmons' UNION ALL
SELECT 'Foster' UNION ALL
SELECT 'Gonzalez' UNION ALL
SELECT 'Bryant' UNION ALL
SELECT 'Alexander' UNION ALL
SELECT 'Russell' UNION ALL
SELECT 'Griffin' UNION ALL
SELECT 'Diaz' UNION ALL
SELECT 'Hayes' UNION ALL
SELECT 'Myers' UNION ALL
SELECT 'Ford'
),
random_names AS (
SELECT
first_names.name AS first_name,
last_names.name AS last_name
FROM
first_names
CROSS JOIN
last_names
ORDER BY
RAND()
LIMIT 250
)
INSERT INTO vets (first_name, last_name)
SELECT first_name, last_name FROM random_names;
-- Add specialties for 80% of the vets
WITH vet_ids AS (
SELECT id
FROM vets
ORDER BY RAND()
LIMIT 200 -- 80% of 1000
),
specialties AS (
SELECT id
FROM specialties
),
random_specialties AS (
SELECT
vet_ids.id AS vet_id,
specialties.id AS specialty_id
FROM
vet_ids
CROSS JOIN
specialties
ORDER BY
RAND()
LIMIT 300 -- 2 specialties per vet on average
)
INSERT INTO vet_specialties (vet_id, specialty_id)
SELECT
vet_id,
specialty_id
FROM (
SELECT
vet_id,
specialty_id,
ROW_NUMBER() OVER (PARTITION BY vet_id ORDER BY RAND()) AS rn
FROM
random_specialties
) tmp
WHERE
rn <= 2; -- Assign at most 2 specialties per vet
-- The remaining 20% of vets will have no specialties, so no need for additional insertion commands

View file

@ -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");
@ -9517,4 +9610,118 @@ strong {
margin-top: 10px;
margin-bottom: 30px; } }
/*# sourceMappingURL=../../../../../../target/petclinic.css.map */
/*# sourceMappingURL=../../../../../../target/petclinic.css.map */
/* 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;
}
/* 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 */
}
/* Minimize style */
.chatbox.minimized .chatbox-content {
height: 40px; /* Height when minimized (header only) */
}
.chatbox.minimized .chatbox-messages,
.chatbox.minimized .chatbox-footer {
display: none;
}
.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;
}
/* Ensure bold and italic styles are handled */
.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;
}

View file

@ -70,6 +70,7 @@
</div>
</div>
</nav>
<div class="container-fluid">
<div class="container xd-container">
@ -87,7 +88,118 @@
</div>
</div>
<script th:src="@{/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js}"></script>
<div class="chatbox" id="chatbox">
<div class="chatbox-header" onclick="toggleChatbox()">
Chat with Us!
</div>
<div class="chatbox-content" id="chatbox-content">
<div class="chatbox-messages" id="chatbox-messages">
<!-- Chat messages will be dynamically inserted here -->
</div>
<div class="chatbox-footer">
<input type="text" id="chatbox-input" placeholder="Type a message..." onkeydown="handleKeyPress(event)" />
<button onclick="sendMessage()">Send</button>
</div>
</div>
</div>
<!-- JavaScript for handling chatbox interaction -->
<script>
function appendMessage(message, type) {
const chatMessages = document.getElementById('chatbox-messages');
const messageElement = document.createElement('div');
messageElement.classList.add('chat-bubble', type);
// Convert Markdown to HTML
const htmlContent = marked.parse(message); // Use marked.parse() for newer versions
messageElement.innerHTML = htmlContent;
chatMessages.appendChild(messageElement);
// Scroll to the bottom of the chatbox to show the latest message
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function toggleChatbox() {
const chatbox = document.getElementById('chatbox');
const chatboxContent = document.getElementById('chatbox-content');
if (chatbox.classList.contains('minimized')) {
chatbox.classList.remove('minimized');
chatboxContent.style.height = '400px'; // Set to initial height when expanded
} else {
chatbox.classList.add('minimized');
chatboxContent.style.height = '40px'; // Set to minimized height
}
}
function sendMessage() {
const query = document.getElementById('chatbox-input').value;
// Only send if there's a message
if (!query.trim()) return;
// Clear the input field after sending the message
document.getElementById('chatbox-input').value = '';
// Display user message in the chatbox
appendMessage(query, 'user');
// Send the message to the backend
fetch('/chatclient', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(query),
})
.then(response => response.text())
.then(responseText => {
// Display the response from the server in the chatbox
appendMessage(responseText, 'bot');
})
.catch(error => console.error('Error:', error));
}
function handleKeyPress(event) {
if (event.key === "Enter") {
event.preventDefault(); // Prevents adding a newline
sendMessage(); // Send the message when Enter is pressed
}
}
// Save chat messages to localStorage
function saveChatMessages() {
const messages = document.getElementById('chatbox-messages').innerHTML;
localStorage.setItem('chatMessages', messages);
}
// Load chat messages from localStorage
function loadChatMessages() {
const messages = localStorage.getItem('chatMessages');
if (messages) {
document.getElementById('chatbox-messages').innerHTML = messages;
document.getElementById('chatbox-messages').scrollTop = document.getElementById('chatbox-messages').scrollHeight;
}
}
// Call loadChatMessages when the page loads
window.onload = loadChatMessages;
// Ensure messages are saved when navigating away
window.onbeforeunload = saveChatMessages;
</script>
<script th:src="@{/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js}"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</body>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,88 @@
.chatbox {
position: fixed;
bottom: 0;
right: 0;
width: 300px;
border-radius: 10px 10px 0 0;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
font-family: Arial, sans-serif;
background-color: #f0f0f0;
overflow: hidden;
&.minimized {
height: 40px;
}
&-header {
background-color: $spring-green;
color: white;
padding: 10px;
cursor: pointer;
font-weight: bold;
text-align: center;
}
&-content {
height: 400px;
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: white;
transition: height 0.3s ease;
}
&-messages {
padding: 10px;
overflow-y: auto;
height: 90%;
display: flex;
flex-direction: column;
gap: 10px;
.chat-bubble {
padding: 10px;
border-radius: 15px;
max-width: 70%;
word-wrap: break-word;
&.user {
background-color: $spring-brown;
color: white;
align-self: flex-end;
}
&.bot {
background-color: $spring-grey;
color: black;
align-self: flex-start;
}
}
}
&-footer {
display: flex;
padding: 10px;
background-color: $spring-light-grey;
input {
flex: 1;
padding: 10px;
border-radius: 5px;
border: 1px solid $spring-brown;
}
button {
margin-left: 10px;
padding: 10px 20px;
background-color: $spring-green;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
&:hover {
background-color: darken($spring-green, 10%);
}
}
}
}

View file

@ -21,6 +21,20 @@ $spring-brown: #34302D;
$spring-grey: #838789;
$spring-light-grey: #f1f1f1;
$chatbox-bg-color: #f1f1f1;
$chatbox-header-bg-color: #075E54;
$chatbox-header-text-color: white;
$chatbox-height: 400px;
$chatbox-border-radius: 10px;
$chatbox-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
$chatbox-bubble-user-bg-color: #dcf8c6;
$chatbox-bubble-bot-bg-color: #ffffff;
$chatbox-bubble-border-color: #e1e1e1;
$chatbox-footer-bg-color: #f9f9f9;
$chatbox-input-border-color: #ccc;
$chatbox-button-bg-color: #075E54;
$chatbox-button-hover-bg-color: #128C7E;
$body-bg: $spring-light-grey;
$text-color: $spring-brown;
$link-color: $spring-dark-green;
@ -209,6 +223,118 @@ hr {
border-top: 1px dotted $spring-brown;
}
/* Chatbox container */
.chatbox {
position: fixed;
bottom: 10px;
right: 10px;
width: 300px;
background-color: $chatbox-bg-color;
border-radius: $chatbox-border-radius;
box-shadow: $chatbox-box-shadow;
display: flex;
flex-direction: column;
&.minimized {
.chatbox-content {
height: 40px; /* Height when minimized (header only) */
}
.chatbox-messages,
.chatbox-footer {
display: none;
}
}
}
/* Header styling */
.chatbox-header {
background-color: $chatbox-header-bg-color;
color: $chatbox-header-text-color;
padding: 10px;
text-align: center;
border-top-left-radius: $chatbox-border-radius;
border-top-right-radius: $chatbox-border-radius;
cursor: pointer;
}
/* Chatbox content styling */
.chatbox-content {
display: flex;
flex-direction: column;
height: $chatbox-height; /* 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;
strong {
font-weight: bold;
}
em {
font-style: italic;
}
&.user {
background-color: $chatbox-bubble-user-bg-color; /* WhatsApp-style light green */
margin-left: auto;
text-align: right;
border-bottom-right-radius: 0;
}
&.bot {
background-color: $chatbox-bubble-bot-bg-color;
margin-right: auto;
text-align: left;
border-bottom-left-radius: 0;
border: 1px solid $chatbox-bubble-border-color;
}
}
/* Input field and button */
.chatbox-footer {
padding: 10px;
background-color: $chatbox-footer-bg-color;
display: flex;
}
.chatbox-footer input {
flex-grow: 1;
padding: 10px;
border-radius: 20px;
border: 1px solid $chatbox-input-border-color;
margin-right: 10px;
outline: none;
}
.chatbox-footer button {
background-color: $chatbox-button-bg-color;
color: white;
border: none;
padding: 10px;
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: $chatbox-button-hover-bg-color;
}
}
@import "typography.scss";
@import "header.scss";
@import "responsive.scss";
@import "chatbot.scss";

View file

@ -82,11 +82,15 @@ class PetTypeFormatterTests {
private List<PetType> makePetTypes() {
List<PetType> petTypes = new ArrayList<>();
petTypes.add(new PetType() {
private static final long serialVersionUID = 4182992965923515553L;
{
setName("Dog");
}
});
petTypes.add(new PetType() {
private static final long serialVersionUID = 1823182409934678856L;
{
setName("Bird");
}

View file

@ -186,11 +186,11 @@ class ClinicServiceTests {
void shouldFindVets() {
Collection<Vet> vets = this.vets.findAll();
Vet vet = EntityUtils.getById(vets, Vet.class, 3);
assertThat(vet.getLastName()).isEqualTo("Douglas");
Vet vet = EntityUtils.getById(vets, Vet.class, 2);
assertThat(vet.getLastName()).isEqualTo("Leary");
assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("anesthesia");
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("dentistry");
}
@Test

6
tanzu.yml Normal file
View file

@ -0,0 +1,6 @@
apiVersion: config.tanzu.vmware.com/v1
configuration:
dev:
paths:
- .tanzu/config/
kind: TanzuConfig