Skip to content

Commit

Permalink
Integrate chat agent web interaction to api-gateway UI (#80)
Browse files Browse the repository at this point in the history
## Purpose

+ Update gateway frontend to add chat-agent part.
+ Reactive web socket server in api-gateway.
+ Call by name for chat agent.
+ Spring cloud gateway route config for chat agent.
+ Make chat agent as Eureka client, because it will be called by spring
cloud gateway, which underlying uses load balancing.


![image](https://github.com/user-attachments/assets/324242bd-ef2d-4ecc-b11c-49a0659f3288)

## Does this introduce a breaking change?
<!-- Mark one with an "x". -->
```
[ ] Yes
[ ] No
```

## Pull Request Type
What kind of change does this Pull Request introduce?

<!-- Please check the one that applies to this PR using "x". -->
```
[ ] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Documentation content changes
[ ] Other... Please describe:
```

## How to Test
*  Get the code

```
git clone [repo-address]
cd [repo-name]
git checkout [branch-name]
npm install
```

* Test the code
<!-- Add steps to run the tests suite and/or manually test -->
```
```

## What to Check
Verify that the following are valid
* ...

## Other Information
<!-- Add any other helpful information that may be needed here. -->

---------

Co-authored-by: Hao Zhang <[email protected]>
  • Loading branch information
haoozhang and Hao Zhang authored Oct 12, 2024
1 parent 02eaf71 commit 984f525
Show file tree
Hide file tree
Showing 17 changed files with 551 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/spring-petclinic-api-gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<!-- Webjars -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.api.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Component
@RequiredArgsConstructor
public class ChatServiceClient {

private final WebClient.Builder webClientBuilder;

public Mono<String> chat(String sender, String message) {
return webClientBuilder.build().get()
.uri("http://chat-agent/chat/{sender}/{message}", sender, message)
.retrieve()
.bodyToMono(String.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.api.dto;

import lombok.*;

/**
* Represents a chat message in the chat application.
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChatMessage {

private String content;

private String sender;

private MessageType type;

/**
* Enum representing the type of the chat message.
*/
public enum MessageType {

CHAT, LEAVE, JOIN

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.api.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.samples.petclinic.api.application.ChatServiceClient;
import org.springframework.samples.petclinic.api.dto.ChatMessage;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;

@Component
public class ChatWebSocketHandler implements WebSocketHandler {

private final Logger logger = LoggerFactory.getLogger(ChatWebSocketHandler.class);

@Autowired
private ChatServiceClient chatServiceClient;

@Value("${petclinic.agent.name:petclinic agent}")
private String agentName;

@Override
public Mono<Void> handle(WebSocketSession session) {
return session.send(
session.receive()
.map(WebSocketMessage::getPayloadAsText)
.flatMap(
payload -> processMessage(session, payload)
)
);
}

private Mono<WebSocketMessage> processMessage(WebSocketSession session, String payload) {
logger.info("received payload: {}", payload);

var objectMapper = new ObjectMapper();
ChatMessage receivedMessage = null;
try {
receivedMessage = objectMapper.readValue(payload, ChatMessage.class);
} catch (Exception e) {
logger.error("Exception thrown when deserializing client chat message: {}", payload);
return Mono.empty();
}

if (receivedMessage.getType() == ChatMessage.MessageType.JOIN) {
return Mono.just(session.textMessage(payload));
} else if (receivedMessage.getType() == ChatMessage.MessageType.CHAT) {
return chatServiceClient.chat(receivedMessage.getSender(), receivedMessage.getContent())
.flatMap(responseContent -> {
ChatMessage responseMessage = new ChatMessage();
responseMessage.setContent(responseContent);
responseMessage.setSender(agentName);

String responsePayload = null;
try {
responsePayload = objectMapper.writeValueAsString(responseMessage);
} catch (Exception e) {
logger.error("Exception thrown when serializing CHAT message: {}", responseMessage);
return Mono.empty();
}

logger.info("response payload: {}", responsePayload);
return Mono.just(session.textMessage(responsePayload));
});
}

return Mono.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.api.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class WebSocketConfig {

@Bean
public HandlerMapping webSocketMapping(ChatWebSocketHandler webSocketHandler) {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/websocket", webSocketHandler);

SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // give it higher precedence
return mapping;
}

// This bean is necessary for WebSocket support in WebFlux
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
68 changes: 68 additions & 0 deletions src/spring-petclinic-api-gateway/src/main/less/petclinic.less
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,74 @@
}
}

.form-control {
display: block;
width: 100%;
padding: 0.375rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
appearance: none;
background-clip: padding-box;
}

.no-marker {
list-style-type: none;
}

#chat-page {
.event-message {
width: 100%;
text-align: center;
clear: both;

p {
font-size: 14px;
word-wrap: break-word;
}
}

.own-message {
text-align: right;

i {
visibility: hidden !important;
}
}
}

.input-group {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;

> .form-control,
> .form-select,
> .form-floating {
position: relative;
flex: 1 1 auto;
width: 1%;
min-width: 0;
}

> .form-control:focus,
> .form-select:focus,
> .form-floating:focus-within {
z-index: 5;
}

.btn {
position: relative;
z-index: 2;

&:focus {
z-index: 5;
}
}
}


.container .text-muted {
margin: 20px 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ spring:
- Path=/api/customer/**
filters:
- StripPrefix=2
- id: chat-agent
uri: lb://chat-agent
predicates:
- Path=/api/chat/**
filters:
- StripPrefix=2



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@
<script src="/scripts/vet-list/vet-list.controller.js"></script>
<script src="/scripts/vet-list/vet-list.component.js"></script>

<script src="/scripts/agent/agent.js"></script>
<script src="/scripts/agent/agent.component.js"></script>

<script src="/scripts/infrastructure/infrastructure.js"></script>
<script src="/scripts/infrastructure/httpErrorHandlingInterceptor.js"></script>
</head>

<body class="container">
<layout-nav></layout-nav>
<div class="container-fluid">
<div class="container xd-container">
<div ui-view=""></div>
</div>
<div class="row" style="height: 30px;"></div>
<div ui-view=""></div>
</div>
<layout-footer></layout-footer>
</body>
Expand Down
Loading

0 comments on commit 984f525

Please sign in to comment.