Skip to content

Commit

Permalink
Merge pull request #274 from amosproj/268-integrate-LLM-dropdown
Browse files Browse the repository at this point in the history
Multiple LLM Answer Queries + Voice Output Mutable and Doesn't Read Links
  • Loading branch information
lukas-varga authored Jul 13, 2024
2 parents 629e192 + 5ecb98c commit 2f80df7
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 25 deletions.
Binary file not shown.
Binary file not shown.
19 changes: 8 additions & 11 deletions src/frontend/components/ChatBubble/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as Speech from 'expo-speech';
import React, { useState } from 'react';
import { ScrollView, Text, TextInput, View } from 'react-native';
import Markdown from 'react-native-markdown-display';
import { ActivityIndicator, Button, IconButton, useTheme } from 'react-native-paper';
import type { MD3Colors } from 'react-native-paper/lib/typescript/types';
import type { conversationMessage } from 'src/frontend/types';
import { SpeakButton } from '../SpeakButton';
import { Style } from './style';

type ChatBubbleProps = {
Expand All @@ -18,8 +18,12 @@ export function ChatBubble({ message }: ChatBubbleProps) {
const isUser = message.type === 'USER';
const AIResponses = !isUser ? Object.entries(message.message) : [];

// take the key of the map as the current LLM
const [llm, setLLM] = useState(AIResponses.length > 0 ? AIResponses[0][0] : 'error');
// set default LLM to first LLM that has a response
const [llm, setLLM] = useState(
AIResponses.length > 0
? AIResponses.find(([key, value]) => value !== 'Model Not Found')?.[0] || AIResponses[0][0]
: 'error'
);

//---------------Functions for buttons----------------
const handleNextResponse = () => {
Expand Down Expand Up @@ -87,14 +91,7 @@ export function ChatBubble({ message }: ChatBubbleProps) {
disabled={AIResponses.length <= 1}
style={Style.chevronButtonRight}
/>
<IconButton
icon='volume-up'
size={16}
onPress={() => {
Speech.speak(response ? response : '', { language: 'en-US', pitch: 1, rate: 1 });
}}
style={Style.speakButton}
/>
<SpeakButton response={response} />
</View>
);
}
Expand Down
5 changes: 0 additions & 5 deletions src/frontend/components/ChatBubble/style.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { StyleSheet } from 'react-native';
import { useTheme } from 'react-native-paper';

export const Style = StyleSheet.create({
chevronButtonLeft: {
Expand Down Expand Up @@ -53,10 +52,6 @@ export const Style = StyleSheet.create({
receivedMessage: {
alignSelf: 'flex-start'
},
speakButton: {
alignSelf: 'center',
margin: 0
},
chatBubble: {
flexDirection: 'row',
alignItems: 'center',
Expand Down
8 changes: 2 additions & 6 deletions src/frontend/components/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,10 @@ export const DropdownMenu = () => {
const activeLLMsNames = Object.values(activeLLMs)
.filter((llm) => llm.active)
.map((llm) => llm.name);
let buttonLabel = activeLLMsCount === 1 ? activeLLMsNames[0] : `${activeLLMsCount} LLMs`;

// If no chatId is selected, set button label to "SELECT"
if (chat === undefined) {
buttonLabel = '';
}
const buttonLabel = activeLLMsCount === 1 ? activeLLMsNames[0] : `${activeLLMsCount} LLMs`;

// Determine if the button should be disabled
// Determine if the button should be disabledHello
const isButtonDisabled = chat === undefined || activeLLMsCount === 0;

return (
Expand Down
49 changes: 49 additions & 0 deletions src/frontend/components/SpeakButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as Speech from 'expo-speech';
import React, { useEffect, useState } from 'react';
import { IconButton } from 'react-native-paper';
import { Style } from './style';

export function SpeakButton(props: { response: string }) {
const [isSpeaking, setIsSpeaking] = useState(false);

// Function to remove links from the text
function extractReadableText(response: string): string {
// Regular expression to match http and https links
const linkRegex = /(https?:\/\/[^\s]+)|(www\.[^\s]+)/g;
// Remove links from the text
return response.replace(linkRegex, '');
}

useEffect(() => {
return () => {
Speech.stop();
};
}, []);

// if button is pressed
const handleSpeech = () => {
if (isSpeaking) {
setIsSpeaking(false);
Speech.stop();
} else {
setIsSpeaking(true);
const readableText = extractReadableText(props.response);
Speech.speak(readableText, {
language: 'en-US',
pitch: 1,
rate: 1,
onDone: () => setIsSpeaking(false),
onError: () => setIsSpeaking(false)
});
}
};

return (
<IconButton
icon={!isSpeaking ? 'volume-up' : 'volume-mute'}
size={16}
onPress={handleSpeech}
style={Style.speakButton}
/>
);
}
8 changes: 8 additions & 0 deletions src/frontend/components/SpeakButton/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { StyleSheet } from 'react-native';

export const Style = StyleSheet.create({
speakButton: {
alignSelf: 'center',
margin: 0
}
});
1 change: 1 addition & 0 deletions src/frontend/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './ChatBubble';
export * from './RenderChat';
export * from './ChatKeyboard';
export * from './VoiceButton';
export * from './SpeakButton';
1 change: 0 additions & 1 deletion src/frontend/hooks/useLLMsTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const LLM_MODELS = [
{ key: 'gpt-4', name: 'OpenAi' },
{ key: 'google', name: 'Gemini' },
{ key: 'mistral', name: 'Mistral' },
{ key: 'claude', name: 'Claude' }
];
40 changes: 38 additions & 2 deletions src/frontend/screens/ChatUI/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
useCreateChat,
useGetChat,
useGetResponse,
useLLMs,
useUpdateChat
} from 'src/frontend/hooks';
import { styles } from './style';
Expand All @@ -31,6 +32,7 @@ export function ChatUI() {
const getResponse = useGetResponse(); // LLM firebase function
const { updateChat } = useUpdateChat(activeChatId); // update chat in firestore
const { createChat } = useCreateChat();
const { activeLLMs } = useLLMs(activeChatId || 'default');
// Flag to wait for LLM answer when a new chat is created
const [waitingForAnswerOnNewChat, setWaitingForAnswerOnNewChat] = useState<{
waiting: boolean;
Expand Down Expand Up @@ -126,11 +128,15 @@ export function ChatUI() {

async function getLLMAnswer(queryText: string) {
// default response
let response: { [key: string]: string } = { 'gpt-4': 'Could not retrieve answer from LLM' };

let response: { [key: string]: string } = initResponses();

// get Response from LLM
try {
const { data } = await getResponse({ query: queryText, llms: ['gpt-4'] });
//get active LLMS in correct format
const llms = extractActiveLLMNames();
console.log('getResponse for llms:', llms);
const { data } = await getResponse({ query: queryText, llms: llms });
response = data as { [key: string]: string };
} catch (error) {
console.error(error as FirebaseError);
Expand All @@ -146,6 +152,35 @@ export function ChatUI() {
}
}

// ------------- Helper functions -------------
function initResponses() {
const response: { [key: string]: string } = Object.keys(activeLLMs).reduce(
(acc, key) => {
if (activeLLMs[key].active) {
acc[key] = 'Could not retrieve answer from LLM';
}
return acc;
},
{} as { [key: string]: string }
);
if (Object.keys(response).length === 0)
response['gpt-4'] = 'Could not retrieve answer from LLM';
return response;
}

// returns currently selected LLMs and 'gpt-4' if no LLM is selected
function extractActiveLLMNames() {
const llms: string[] = [];
for (const llm of Object.keys(activeLLMs)) {
if (activeLLMs[llm].active) {
llms.push(llm);
}
}

if (llms.length === 0) llms.push('gpt-4');
return llms;
}

function extractTitle(queryText: string) {
//TODO: maybe use a more sophisticated method to extract the title later
const arr = queryText.split(' ');
Expand All @@ -155,6 +190,7 @@ export function ChatUI() {
}
return title;
}
// ------------- Render -------------

return (
<View style={styles.container}>
Expand Down

0 comments on commit 2f80df7

Please sign in to comment.