Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple LLM Answer Queries + Voice Output Mutable and Doesn't Read Links #274

Merged
merged 5 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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