mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2025-07-20 06:45:50 +00:00
exampe of using RAG when searching of vets
This commit is contained in:
parent
290bb439d1
commit
3083556289
12 changed files with 2412 additions and 100 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -16,3 +16,6 @@ _site/
|
|||
*.css
|
||||
!petclinic.css
|
||||
**/creds.yaml
|
||||
**/.DS_Store
|
||||
.tanzu/
|
||||
tanzu.yml
|
||||
|
|
|
@ -2,10 +2,12 @@ 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;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* A Configuration class for beans used by the Chat Client.
|
||||
|
@ -14,16 +16,16 @@ import org.springframework.web.client.RestClient;
|
|||
*/
|
||||
@Configuration
|
||||
@Profile("openai")
|
||||
public class PetclinicAIConfiguration {
|
||||
|
||||
@Bean
|
||||
public RestClient restClient() {
|
||||
return RestClient.create();
|
||||
}
|
||||
public class AIBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
public ChatMemory chatMemory() {
|
||||
return new InMemoryChatMemory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
VectorStore vectorStore(EmbeddingModel embeddingModel) {
|
||||
return new SimpleVectorStore(embeddingModel);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,8 @@ 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
|
||||
|
@ -26,7 +28,7 @@ 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(PetclinicAIProvider petclinicAiProvider) {
|
||||
public Function<OwnerRequest, OwnersResponse> listOwners(AIDataProvider petclinicAiProvider) {
|
||||
return request -> {
|
||||
return petclinicAiProvider.getAllOwners();
|
||||
};
|
||||
|
@ -34,25 +36,39 @@ class AIFunctionConfiguration {
|
|||
|
||||
@Bean
|
||||
@Description("List the veterinarians that the pet clinic has")
|
||||
public Function<VetRequest, VetResponse> listVets(PetclinicAIProvider petclinicAiProvider) {
|
||||
public Function<VetRequest, VetResponse> listVets(AIDataProvider petclinicAiProvider) {
|
||||
return request -> {
|
||||
return petclinicAiProvider.getAllVets();
|
||||
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(PetclinicAIProvider petclinicAiProvider) {
|
||||
@Description("Add a pet with the specified petTypeId, to an owner identified by the ownerId.")
|
||||
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, String petType, Integer ownerId) {
|
||||
}
|
||||
|
||||
record AddPetRequest(Pet pet, Integer ownerId) {
|
||||
};
|
||||
|
||||
record OwnerRequest(Owner owner) {
|
||||
|
@ -61,10 +77,13 @@ record OwnerRequest(Owner owner) {
|
|||
record OwnersResponse(List<Owner> owners) {
|
||||
};
|
||||
|
||||
record OwnerResponse(Owner owner) {
|
||||
};
|
||||
|
||||
record AddedPetResponse(Owner owner) {
|
||||
};
|
||||
|
||||
record VetResponse(List<Vet> vet) {
|
||||
record VetResponse(List<String> vet) {
|
||||
};
|
||||
|
||||
record VetRequest(Vet vet) {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package org.springframework.samples.petclinic.genai;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ai.chat.client.AdvisedRequest;
|
||||
import org.springframework.ai.chat.client.RequestResponseAdvisor;
|
||||
|
||||
/**
|
||||
* A ChatClient Advisor that adds logs on the requests being sent to the LLM.
|
||||
*
|
||||
* @author Oded Shopen
|
||||
*/
|
||||
public class LoggingAdvisor implements RequestResponseAdvisor {
|
||||
|
||||
private static final Log log = LogFactory.getLog(LoggingAdvisor.class);
|
||||
|
||||
@Override
|
||||
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
|
||||
log.info("Received Spring AI Request: " + request);
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package org.springframework.samples.petclinic.genai;
|
||||
|
||||
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.Vet;
|
||||
import org.springframework.samples.petclinic.vet.VetRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 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 PetclinicAIProvider {
|
||||
|
||||
OwnerRepository ownerRepository;
|
||||
|
||||
VetRepository vetRepository;
|
||||
|
||||
public PetclinicAIProvider(OwnerRepository ownerRepository, VetRepository vetRepository) {
|
||||
this.ownerRepository = ownerRepository;
|
||||
this.vetRepository = vetRepository;
|
||||
}
|
||||
|
||||
public OwnersResponse getAllOwners() {
|
||||
Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE);
|
||||
Page<Owner> ownerPage = ownerRepository.findAll(pageable);
|
||||
return new OwnersResponse(ownerPage.getContent());
|
||||
}
|
||||
|
||||
public VetResponse getAllVets() {
|
||||
Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE);
|
||||
Page<Vet> vetsPage = vetRepository.findAll(pageable);
|
||||
return new VetResponse(vetsPage.getContent());
|
||||
}
|
||||
|
||||
public AddedPetResponse addPetToOwner(AddPetRequest request) {
|
||||
Owner owner = ownerRepository.findById(request.ownerId());
|
||||
owner.addPet(request.pet());
|
||||
this.ownerRepository.save(owner);
|
||||
return new AddedPetResponse(owner);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvis
|
|||
|
||||
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;
|
||||
|
@ -32,16 +33,18 @@ public class PetclinicChatClient {
|
|||
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 the existing veterinarians and to perform actions on the customer's behalf, mainly around
|
||||
pet owners, their pets and their visits.
|
||||
You are required to answer an a professional manner. If you don't know the answer, politely tell the customer
|
||||
you don't know the answer, then ask the customer a followup qusetion to try and clarify the question they are asking.
|
||||
Your job is to answer questions about the existing veterinarians and to perform actions on the user's behalf, mainly around
|
||||
veterinarians, pet owners, their pets and their owner's 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 qusetion to try and clarify the question they are asking.
|
||||
If you do know the answer, provide the answer but do not provide any additional helpful 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 - answer 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 LoggingAdvisor()
|
||||
new SimpleLoggerAdvisor()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +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
|
||||
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=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
|
||||
|
||||
|
||||
|
||||
|
|
262
src/main/resources/db/h2/generate_records.sql
Normal file
262
src/main/resources/db/h2/generate_records.sql
Normal 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
|
1794
src/main/resources/vectorstore.json
Normal file
1794
src/main/resources/vectorstore.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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,117 @@ 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";
|
||||
|
|
Loading…
Reference in a new issue