Skip to content

Commit

Permalink
Announcement Debug Logs. Fixes #50
Browse files Browse the repository at this point in the history
  • Loading branch information
brian-mckeown committed Jan 27, 2024
1 parent a297f63 commit e54b362
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import io.github.cdimascio.dotenv.Dotenv;

import java.io.File;
import java.util.*;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;


@RestController
@RequestMapping("/api/v1/announcements")
Expand All @@ -28,6 +33,12 @@ public class AnnouncementController {
}
weatherbitApiToken = token;
}

private String getCurrentUtcDateTime() {
ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSS 'UTC'");
return utcNow.format(formatter);
}


@PostMapping("/universal")
Expand Down Expand Up @@ -96,15 +107,22 @@ public ResponseEntity<?> fetchLandingAnnouncement(@RequestBody Map<String, Objec
}

StringBuilder apiCallStatus = new StringBuilder();

RestTemplate restTemplate = new RestTemplate();


ResponseEntity<Map> weatherResponse = restTemplate.getForEntity(arrivalWeatherUrl, Map.class);
apiCallStatus.append("Weather API Call - Status: ")
.append(weatherResponse.getStatusCode())
ResponseEntity<Map> weatherResponse = null; // Declare before try block
try {
weatherResponse = restTemplate.getForEntity(arrivalWeatherUrl, Map.class);
apiCallStatus.append(getCurrentUtcDateTime()).append(" - Weather API Call - Status: ").append(weatherResponse.getStatusCode()).append("\n");
} catch (HttpClientErrorException e) {
// This will catch client errors like 401 Unauthorized and provide more details
apiCallStatus.append(getCurrentUtcDateTime()).append(" - Weather API Call - HttpClient Error: ")
.append(e.getStatusCode())
.append(" - ")
.append(e.getResponseBodyAsString())
.append("\n");

} catch (Exception e) {
apiCallStatus.append(getCurrentUtcDateTime()).append(" - Weather API Call - Error: ").append(e.getMessage()).append("\n");
}
// Initialize response variables
String weatherDescription = null;
Number celsiusTemp = null;
Expand Down Expand Up @@ -150,7 +168,7 @@ public ResponseEntity<?> fetchLandingAnnouncement(@RequestBody Map<String, Objec
}

// Check for successful response and extract data
if (weatherResponse.getStatusCode().is2xxSuccessful() && weatherResponse.getBody() != null) {
if (weatherResponse != null && weatherResponse.getStatusCode().is2xxSuccessful() && weatherResponse.getBody() != null) {
List<Map<String, Object>> data = (List<Map<String, Object>>) weatherResponse.getBody().get("data");
if (data != null && !data.isEmpty()) {
Map<String, Object> weatherData = data.get(0);
Expand Down Expand Up @@ -444,8 +462,8 @@ else if (announcementType.equals("boarding6")) {

// Make the POST request to OpenAI
String openAiUrl = "https://api.openai.com/v1/chat/completions";
ResponseEntity<Map> openAiResponse = restTemplate.postForEntity(openAiUrl, entity, Map.class);
apiCallStatus.append("OpenAI GPT API Call - Status: ")
try { ResponseEntity<Map> openAiResponse = restTemplate.postForEntity(openAiUrl, entity, Map.class);
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI GPT API Call - Status: ")
.append(openAiResponse.getStatusCode())
.append("\n");

Expand All @@ -460,6 +478,18 @@ else if (announcementType.equals("boarding6")) {
}
}
}
} catch (HttpClientErrorException e) {
// This will catch client errors like 401 Unauthorized and provide more details
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI GPT API Call - HttpClient Error: ")
.append(e.getStatusCode())
.append(" - ")
.append(e.getResponseBodyAsString())
.append("\n");
} catch (Exception e) {
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI GPT API Call - Error: ")
.append(e.getMessage())
.append("\n");
}

Map<String, Object> ttsRequestBody = new HashMap<>();
ttsRequestBody.put("model", ttsModel);
Expand All @@ -468,30 +498,47 @@ else if (announcementType.equals("boarding6")) {

HttpEntity<Map<String, Object>> ttsEntity = new HttpEntity<>(ttsRequestBody, headers);
String ttsUrl = "https://api.openai.com/v1/audio/speech";
try {
ResponseEntity<byte[]> ttsResponse = restTemplate.postForEntity(ttsUrl, ttsEntity, byte[].class);
// Construct the response object
if (ttsResponse.getStatusCode().is2xxSuccessful() && ttsResponse.getBody() != null) {
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI TTS API Call - Status: ")
.append(ttsResponse.getStatusCode())
.append("\n");
if (ttsResponse.getStatusCode().is2xxSuccessful() && ttsResponse.getBody() != null) {
byte[] audioData = ttsResponse.getBody();

// Encode audio data to Base64 string
String base64Audio = Base64.getEncoder().encodeToString(audioData);

apiCallStatus.append("OpenAI TTS API Call - Success\n");
// The response should be the binary data of the mp3 file
byte[] audioData = ttsResponse.getBody();

// Set up headers for audio response
HttpHeaders audioHeaders = new HttpHeaders();
audioHeaders.setContentType(MediaType.valueOf("audio/mpeg"));
audioHeaders.setContentDisposition(ContentDisposition.builder("inline").filename("announcement.mp3").build());
// Create a response map
Map<String, Object> responseBody = new HashMap<>();
responseBody.put("base64Audio", base64Audio); // Encoded audio
responseBody.put("apiCallLog", apiCallStatus.toString()); // API Call Log

return ResponseEntity.ok()
.headers(audioHeaders)
.contentLength(audioData.length)
.body(new ByteArrayResource(audioData));
return ResponseEntity.ok(responseBody); // Send back as JSON
} else {
// Handle error case
// Since it's an error condition, construct a JSON response
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", apiCallStatus + "\nOpenAI TTS Call: Failed to generate audio from the text");
return ResponseEntity.status(ttsResponse.getStatusCode()).body(new MapResource(errorResponse));
}
} catch (HttpClientErrorException e) {
// This will catch client errors like 401 Unauthorized and provide more details
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI TTS API Call - HttpClient Error: ")
.append(e.getStatusCode())
.append(" - ")
.append(e.getResponseBodyAsString())
.append("\n");
}catch (Exception e) {
apiCallStatus.append(getCurrentUtcDateTime()).append(" - OpenAI TTS API Call - Error: ")
.append(e.getMessage())
.append("\n");
}
} else {
// Handle the case where weather API call was not successful or body is null
apiCallStatus.append(getCurrentUtcDateTime()).append(" - Weather API Call - Failed or no data received\n");
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(apiCallStatus + "\ninternal server error");

Expand Down
87 changes: 60 additions & 27 deletions src/main/resources/static/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var app = angular.module('checklistApp', []);
app.controller('ChecklistController', ['$scope', '$sce', '$timeout', '$http', '$document', '$interval', function($scope, $sce, $timeout, $http, $document, $interval) {


$scope.versionNumber = '1.5.2';
$scope.versionNumber = '1.5.3';

$scope.state = 'Idle';
$scope.messages = [];
Expand Down Expand Up @@ -735,7 +735,6 @@ $scope.generateFlightCrew = function() {
// Add the crew member to the array
$scope.flightCrewArray.push(crewMember);
});
console.log($scope.flightCrewArray);
}


Expand Down Expand Up @@ -1351,7 +1350,6 @@ $scope.deBoardPassengersAndBags = function() {
$scope.departureIcao = $scope.flightPlanJSONData.origin.icao_code;
$scope.arrivalIcao = $scope.flightPlanJSONData.destination.icao_code;
$scope.flightLevelString = $scope.flightPlanJSONData.general.stepclimb_string;
console.log("STEP FLIGHT LEVEL STRING: " + $scope.flightLevelString);

$scope.numberOfPassengers = Number($scope.flightPlanJSONData.weights.pax_count);

Expand Down Expand Up @@ -1406,9 +1404,6 @@ $scope.deBoardPassengersAndBags = function() {

$scope.runways = $scope.airportData.runways;
$scope.frequencies = $scope.airportData.freqs;
console.log($scope.airportData);
console.log($scope.airportData.freqs);
console.log($scope.airportData.runways);

$scope.parseMetar($scope.airportInfo.metar);
$scope.sortRunwaysByHeadwind();
Expand Down Expand Up @@ -1624,26 +1619,39 @@ $scope.deBoardPassengersAndBags = function() {
aircraftName: $scope.aircraftName
};

// Define the headers for your POST request
var config = {
headers : {
'Content-Type': 'application/json'
}
};
// Create a string representation of requestData with masked API key
var requestDataString = JSON.stringify({
...requestData,
openAiApiKey: 'xxxxxxxxx'
}, null, 2); // Format with 2-space indentation for readability

$http({
method: 'POST',
url: '/api/v1/announcements/universal',
data: requestData,
responseType: 'blob', // Correct way to set the expected response type for binary data
headers: {
'Content-Type': 'application/json'
},
})
.then(function(response) {
var blob = new Blob([response.data], { type: 'audio/mpeg' });
$scope.audioSrc = $sce.trustAsResourceUrl(URL.createObjectURL(blob));
transformResponse: function(data, headersGetter) {
try {
// Try to parse the response as JSON
return angular.fromJson(data);
} catch (e) {
// If parsing fails, return the raw response
return data;
}
}
}).then(function(response) {
// Decode the Base64 audio string to a Blob
var audioBlob = base64ToBlob(response.data.base64Audio, 'audio/mpeg');
$scope.audioSrc = $sce.trustAsResourceUrl(URL.createObjectURL(audioBlob));

// Use the reusable function
// Assign the API call log
$scope.announcementApiReport = response.data.apiCallLog;
// Append requestDataString to announcementApiReport
$scope.announcementApiReport += "\n\nRequest Data:\n" + requestDataString;

// Play the audio
$scope.playDingThenCallback(function() {
var announcementAudio = document.getElementById('announcementAudio');
if (announcementAudio) {
Expand All @@ -1653,16 +1661,41 @@ $scope.deBoardPassengersAndBags = function() {
console.error('Announcement audio element not found');
}
});
})
.catch(function(error) {
// Handle errors here, such as displaying a message to the user
console.error('Error fetching announcement:', error);
$scope.announcementApiReport = error;
})
.finally(function() {
$scope.isAnnouncementLoading = false; // Hide spinner
});
}).catch(function(error) {
console.error('Error fetching announcement:', error);

// Check if error.data is a string and not empty
if (typeof error.data === 'string' && error.data.trim()) {
$scope.announcementApiReport = error.data;
$scope.announcementApiReport += "\n\nRequest Data:\n" + requestDataString;
} else {
$scope.announcementApiReport = "Unknown error";
$scope.announcementApiReport += "\n\nRequest Data:\n" + requestDataString;
}
}).finally(function() {
$scope.isAnnouncementLoading = false;
});
};

// Helper function to convert base64 string to Blob
function base64ToBlob(base64, contentType) {
var byteCharacters = atob(base64);
var byteArrays = [];

for (var offset = 0; offset < byteCharacters.length; offset += 512) {
var slice = byteCharacters.slice(offset, offset + 512);

var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}

var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}

return new Blob(byteArrays, {type: contentType});
}

$scope.startPreBoardingAnnouncement = function() {

Expand Down
15 changes: 12 additions & 3 deletions src/main/resources/templates/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -1258,9 +1258,18 @@ <h3>Announcements (Open Beta)</h3>
report the issue via <a href="/#support-section" target="_blank">Support</a>
</div>
<div>
<p ng-model="announcementApiReport">

</p>
<div class="form-check mb-3">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" ng-model="showDebugLog"> Debug Log
</label>
</div>

<div ng-show="showDebugLog">
<label for="debugLog">Debug Log:</label>
<textarea class="form-control" rows="5" id="debugLog" name="text"
ng-model="announcementApiReport" ng-disabled="true"
style="overflow-y: scroll;"></textarea>
</div>
</div>
</div>
</div>
Expand Down

0 comments on commit e54b362

Please sign in to comment.