Skip to content

Commit

Permalink
Merge pull request #275 from amosproj/frontend-format-cleanup-comments
Browse files Browse the repository at this point in the history
Frontend format cleanup comments
  • Loading branch information
lukas-varga authored Jul 13, 2024
2 parents 2f80df7 + 00805e5 commit a9418f5
Show file tree
Hide file tree
Showing 37 changed files with 335 additions and 112 deletions.
Binary file not shown.
3 changes: 3 additions & 0 deletions src/frontend/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ActiveChatProvider, FirebaseProvider, UpdateApp } from './components';
import { Fonts, LightTheme } from './helpers';
import { AppRoutes } from './routes';

// Suppress warnings
LogBox.ignoreLogs([
'Require cycle:',
'`new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.',
Expand All @@ -36,12 +37,14 @@ export function App() {
prepare();
}, []);

// hide splash screen after fonts are loaded
const onLayoutRootView = useCallback(async () => {
if (isFontLoaded) await hideAsync();
}, [isFontLoaded]);

if (!isFontLoaded) return null;

// Display the app
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }} onLayout={onLayoutRootView}>
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/components/ActiveChatProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import React, {
type SetStateAction
} from 'react';

/**
* This file is a context provider for the active chat id.
*/

// Define the shape of the context value
interface ChatContextValue {
activeChatId: string;
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/components/ChatBubble/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import type { conversationMessage } from 'src/frontend/types';
import { SpeakButton } from '../SpeakButton';
import { Style } from './style';

/**
* This file renders a chat bubble in the chat UI.
*
* Depending on whether the message is from the user or the AI,
* the chat bubble will be styled differently.
*/

type ChatBubbleProps = {
message: conversationMessage;
};
Expand Down
10 changes: 7 additions & 3 deletions src/frontend/components/ChatItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { Screens } from 'src/frontend/helpers';
import { useActiveChatId, useDeleteChat, useGetChat } from 'src/frontend/hooks';
import type { AppRoutesParams } from 'src/frontend/routes';

/**
* This file renders a ChatItem in the Drawer
*
* When the ChatItem is pressed, the chat is opened in the main screen.
* When the ChatItem is long pressed, a menu is opened to delete the chat.
*/

export type ChatItemProps = {
id: string;
title: string;
Expand All @@ -29,8 +36,6 @@ export function ChatItem(props: ChatItemProps) {
}
}, [drawerStatus]);

const handleArchive = async () => {};

return (
<View>
<Menu
Expand All @@ -55,7 +60,6 @@ export function ChatItem(props: ChatItemProps) {
</Button>
}
>
<Menu.Item leadingIcon='archive' onPress={handleArchive} title='Archive' />
<Menu.Item leadingIcon='trash' onPress={handleDelete} title='Delete' />
</Menu>
</View>
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/components/ChatKeyboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import { Keyboard } from 'react-native';
import { useTheme } from 'react-native-paper';
import { styles } from './style';

/**
* This file renders the chat keyboard in the chat UI below the chat
*
* The Keyboard disappears when we click somewhere else on the screen
* or when we close the component where the keyboard was used.
*/

type ChatKeyboardProps = {
text: string;
setText: (text: string) => void;
Expand Down
9 changes: 8 additions & 1 deletion src/frontend/components/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ import type { MainDrawerParams } from 'src/frontend/routes/MainRoutes';
import type { Chat } from 'src/frontend/types';
import { Style } from './style';

/**
* This file renders a dropdown menu in the chat UI Header.
*
* You can select which LLM should be used for the current chat to generate answers.
* The Menu is based on the current active chat given by @activeChatId.
* Otherwise if no chat is active it will be disabled.
*/

export const DropdownMenu = () => {
// get chatID after opening app copilot help
const [isVisible, setIsVisible] = useState(false);
const { activeChatId, setActiveChatId } = useActiveChatId();
const { activeLLMs, toggleLLM } = useLLMs(activeChatId || 'default');
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/components/FirebaseProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { getFunctions } from 'firebase/functions';
import { type ReactNode, useEffect, useMemo } from 'react';
import { AuthProvider, FirebaseAppProvider, FirestoreProvider, FunctionsProvider } from 'reactfire';

/**
* This file provides global variables for using firebase hooks
*/

type FirebaseProviderProps = {
children: ReactNode;
};
Expand Down
111 changes: 16 additions & 95 deletions src/frontend/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,39 @@
import type { DrawerHeaderProps } from '@react-navigation/drawer';
import { DrawerActions } from '@react-navigation/native';
import * as Clipboard from 'expo-clipboard';
import * as MediaLibrary from 'expo-media-library';
import React, { useEffect } from 'react';
import { Alert, Platform, Pressable, View } from 'react-native';
import RNFS from 'react-native-fs';
import { IconButton, Surface, Text, useTheme } from 'react-native-paper';
import { useActiveChatId, useGetChat } from 'src/frontend/hooks';
import React from 'react';
import { Pressable, View } from 'react-native';
import { Surface, Text } from 'react-native-paper';
import { AilixirLogo } from 'src/frontend/icons';
import { DropdownMenu } from '../DropdownMenu';
import { SavingChat } from '../SavingChat';
import { Style } from './style';

/**
* This file holds the code for rendering the header of the Chat UI.
*
* It contains the logic for ...
* - Opening the Drawer
* - Selecting the LLM model
* - Saving the chat to the device and clipboard.
*/

export function Header(props: DrawerHeaderProps) {
const { colors } = useTheme();
const { navigation } = props;
const { activeChatId } = useActiveChatId();
const { chat } = useGetChat(activeChatId);

// Determine if the button should be disabled
const isButtonDisabled = chat === undefined;

useEffect(() => {
const requestPermissions = async () => {
if (Platform.OS === 'android') {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission Denied', 'Media library permissions are required.');
}
}
};

requestPermissions();
}, []);

// Saving to download and clipboard
const handleAction = async () => {
try {
if (!chat || !chat.conversation || chat.conversation.length === 0) {
Alert.alert('No Chat Available', 'There is no chat content available.');
return;
}

const createdAtDate = new Date(chat.createdAt.toDate());
const formattedCreatedAt = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
}).format(createdAtDate);

const metadata = `title: ${
chat.title
}\ncreated: ${formattedCreatedAt}\nmodels: ${chat.model.toString()}\n\n\n`;

const formattedChatContent = chat.conversation
.map((line) => {
return Object.entries(line)
.map(([key, value]) => `${key}: '${value}'`)
.join('\n');
})
.join('\n\n');

const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');

const fileName = `chat_${year}-${month}-${day}_${hours}-${minutes}-${seconds}.txt`;

const path = `${RNFS.DownloadDirectoryPath}/${fileName}`;

const contentToSave = metadata + formattedChatContent;

// Copy to clipboard
await Clipboard.setStringAsync(contentToSave);
// Save to file
await RNFS.writeFile(path, contentToSave, 'utf8');
Alert.alert(
'Chat Saved',
`Chat saved to Downloads folder as ${fileName} and also to clipboard.`
);
} catch (error) {
console.error('Error:', error);
Alert.alert('Error', 'Failed to perform action.');
}
};

return (
<Surface style={Style.container} elevation={1}>
<View style={Style.viewContainer}>
<Pressable
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
style={{ marginRight: 12 }}
style={Style.drawer}
>
<AilixirLogo height={36} width={36} />
</Pressable>
<Text variant='titleLarge'>AiLixir</Text>
</View>
<View style={{ flexDirection: 'row' }}>
<View style={Style.buttons}>
<DropdownMenu />
<Pressable onPress={handleAction} style={Style.actionButton} disabled={isButtonDisabled}>
<IconButton
icon='save'
size={24}
iconColor={colors.primary}
disabled={isButtonDisabled}
/>
</Pressable>
<SavingChat />
</View>
</Surface>
);
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/components/Header/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ export const Style = StyleSheet.create({
minHeight: 64,
backgroundColor: '#fff'
},
drawer: {
marginRight: 12
},
viewContainer: {
flexDirection: 'row',
alignItems: 'center'
},
actionButton: {
marginVertical: -4
buttons: {
flexDirection: 'row'
}
});
7 changes: 7 additions & 0 deletions src/frontend/components/PersonalInfoForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import uuid from 'react-native-uuid';
import type { UserProfile } from 'src/frontend/types';
import { Style } from './style';

/**
* This file renders a form to input personal information.
*
* This form allows the user to input their name, style instructions, and personalized instructions.
* These informations should be used by the bot to generate personalized responses.
*/

const PersonalInfoForm = () => {
const [profiles, setProfiles] = useState<UserProfile[]>([]);
const [currentProfile, setCurrentProfile] = useState<UserProfile | null>(null);
Expand Down
6 changes: 6 additions & 0 deletions src/frontend/components/RenderChat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import type { conversationMessage } from 'src/frontend/types';
import { ChatBubble } from '../ChatBubble';
import { Style } from './style';

/**
* This file handles rendering all the different chat bubbles from a saved chat in firestore
*
* There is case distinction between AI and user messages because the storage format is different
*/

export function RenderChat() {
const { activeChatId } = useActiveChatId();
const { chat, status } = useGetChat(activeChatId);
Expand Down
Loading

0 comments on commit a9418f5

Please sign in to comment.