Skip to content

Commit

Permalink
Add naive converse UI implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
pacphi committed Dec 20, 2024
1 parent c2133ac commit 0570bc1
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ config/
logs/
package*.json
src/main/bundles
src/main/frontend
src/main/frontend/generated
src/main/frontend/index.html
tsconfig.json
types.d.ts
node_modules
Expand Down
63 changes: 63 additions & 0 deletions src/main/frontend/flow/audio-recorder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
window.audioRecorder = {
mediaRecorder: null,
audioChunks: [],

startRecording: async function() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.mediaRecorder = new MediaRecorder(stream);
this.audioChunks = [];

this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.audioChunks.push(event.data);
}
};

this.mediaRecorder.start();
} catch (error) {
console.error('Error accessing microphone:', error);
}
},

stopRecording: async function() {
return new Promise((resolve, reject) => {
if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
this.mediaRecorder.stop();
this.mediaRecorder.onstop = async () => {
try {
const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
const base64Data = await this.blobToBase64(audioBlob);

// Stop all tracks
this.mediaRecorder.stream.getTracks().forEach(track => track.stop());

resolve({ audioData: base64Data });
} catch (error) {
reject(error);
}
};
} else {
reject(new Error('No recording in progress'));
}
});
},

blobToBase64: function(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const base64String = reader.result
.replace('data:audio/webm;base64,', '');
resolve(base64String);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
},

playAudioResponse: function(base64Audio) {
const audio = new Audio(`data:audio/wav;base64,${base64Audio}`);
audio.play();
}
};
55 changes: 55 additions & 0 deletions src/main/java/org/cftoolsuite/client/ProfilesClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.cftoolsuite.client;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;


@Component
public class ProfilesClient {

private static final Logger log = LoggerFactory.getLogger(ProfilesClient.class);

private final RestClient client;

public ProfilesClient(@Value("${document.service.url}") String sanfordUrl) {
client = RestClient.builder()
.baseUrl(sanfordUrl)
.build();
}

public Set<String> getProfiles() {
Set<String> result = new HashSet<>();
ResponseEntity<Map<String, Object>> response =
client
.get()
.uri("/actuator/info")
.retrieve()
.toEntity(new ParameterizedTypeReference<Map<String, Object>>() {});
if (response.getStatusCode().is2xxSuccessful()) {
Map<String, Object> body = response.getBody();
if (body != null && body.containsKey("active-profiles")) {
String profiles = (String) body.get("active-profiles");
result = Stream.of(profiles.trim().split(","))
.collect(Collectors.toSet());
} else {
log.warn("Could not determine active profiles.");
}
}
return result;
}

public boolean contains(String profile) {
return getProfiles().contains(profile);
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/cftoolsuite/client/SanfordClient.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.cftoolsuite.client;

import java.io.IOException;
import java.util.List;

import org.cftoolsuite.domain.FileMetadata;
import org.cftoolsuite.domain.chat.AudioResponse;
import org.cftoolsuite.domain.chat.Inquiry;
import org.cftoolsuite.domain.crawl.CrawlRequest;
import org.cftoolsuite.domain.crawl.CrawlResponse;
Expand Down Expand Up @@ -33,6 +35,9 @@ public interface SanfordClient {
@PostMapping("/api/fetch")
public ResponseEntity<FetchResponse> fetchUrls(@RequestBody FetchRequest request);

@PostMapping("/api/converse")
public ResponseEntity<AudioResponse> converse(@RequestParam("file") MultipartFile file) throws IOException;

@PostMapping("/api/chat")
public ResponseEntity<String> chat(@RequestBody Inquiry inquiry);

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/cftoolsuite/domain/chat/AudioResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.cftoolsuite.domain.chat;

import java.util.Base64;

public record AudioResponse(String text, String audioBase64) {
public AudioResponse(String text, byte[] audio) {
this(text, Base64.getEncoder().encodeToString(audio));
}
}
28 changes: 11 additions & 17 deletions src/main/java/org/cftoolsuite/ui/MainLayout.java
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
package org.cftoolsuite.ui;

import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.server.StreamResource;
import org.cftoolsuite.ui.view.ChatView;
import org.cftoolsuite.ui.view.CrawlView;
import org.cftoolsuite.ui.view.DeleteView;
import org.cftoolsuite.ui.view.DownloadView;
import org.cftoolsuite.ui.view.FetchView;
import org.cftoolsuite.ui.view.HomeView;
import org.cftoolsuite.ui.view.ListView;
import org.cftoolsuite.ui.view.SearchView;
import org.cftoolsuite.ui.view.SummarizeView;
import org.cftoolsuite.ui.view.UploadView;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.accordion.Accordion;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.details.DetailsVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.server.StreamResource;
import org.cftoolsuite.client.ProfilesClient;
import org.cftoolsuite.ui.view.*;


public class MainLayout extends AppLayout {

private static final long serialVersionUID = 1L;

public MainLayout() {
public MainLayout(ProfilesClient modeClient) {
Tab homeTab = createTab(VaadinIcon.HOME.create(), "Home", HomeView.class);

Tabs actionTabs = createTabs();

Tab uploadTab = createTab(VaadinIcon.UPLOAD.create(), "Upload documents", UploadView.class);
Tab crawlTab = createTab(VaadinIcon.SITEMAP.create(), "Crawl websites for documents", CrawlView.class);
Tab fetchTab = createTab(VaadinIcon.CROSSHAIRS.create(), "Fetch documents", FetchView.class);
Tab converseTab = createTab(VaadinIcon.MEGAPHONE.create(), "Converse with AI bot about documents", ConverseView.class);
Tab chatTab = createTab(VaadinIcon.CHAT.create(), "Chat with AI bot about documents", ChatView.class);
Tab listTab = createTab(VaadinIcon.LIST.create(), "List document metadata", ListView.class);
Tab searchTab = createTab(VaadinIcon.SEARCH.create(), "Search for document metadata", SearchView.class);
Tab summaryTab = createTab(VaadinIcon.BULLETS.create(), "Summarize a document", SummarizeView.class);
Tab downloadTab = createTab(VaadinIcon.DOWNLOAD.create(), "Download a document", DownloadView.class);
Tab deleteTab = createTab(VaadinIcon.TRASH.create(), "Delete a document", DeleteView.class);
actionTabs.add(uploadTab, crawlTab, fetchTab, chatTab, listTab, searchTab, summaryTab, downloadTab, deleteTab);
actionTabs.add(uploadTab, crawlTab, fetchTab);
if (modeClient.contains("openai")) {
actionTabs.add(converseTab);
}
actionTabs.add(chatTab, listTab, searchTab, summaryTab, downloadTab, deleteTab);
addToNavbar(true, homeTab, new DrawerToggle());
addToDrawer(getLogoImage(), actionTabs);
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/cftoolsuite/ui/view/BaseView.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public BaseView(SanfordClient sanfordClient, AppProperties appProperties) {

protected abstract void setupUI();

protected abstract void clearAllFields();
protected void clearAllFields() {}

protected void showNotification(String message, NotificationVariant variant) {
Notification notification = new Notification(message, 5000, Position.TOP_STRETCH);
Expand Down
Loading

0 comments on commit 0570bc1

Please sign in to comment.