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

Frontend format cleanup comments #275

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.
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