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

Voice output bugfix + Save chat TXT #231

Merged
merged 2 commits into from
Jul 1, 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
16 changes: 11 additions & 5 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,17 @@
],
[
"expo-media-library",
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
"savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.",
"isAccessMediaLocationEnabled": true
}
{
"photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
"savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.",
"isAccessMediaLocationEnabled": true
}
],
[
"expo-file-system",
{
"enableAndroidStorageAccess": true
}
]
],
"extra": {
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@expo/metro-runtime": "~3.2.1",
"@gorhom/bottom-sheet": "^4",
"@react-native-async-storage/async-storage": "1.23.1",
"@react-native-camera-roll/camera-roll": "^7.8.1",
"@react-native-community/netinfo": "^11.3.2",
"@react-native-firebase/app": "^20.1.0",
"@react-native-firebase/auth": "^20.1.0",
Expand All @@ -36,11 +37,13 @@
"@tabler/icons-react-native": "^3.5.0",
"dotenv": "^16.4.5",
"expo": "~51.0.9",
"expo-clipboard": "^6.0.3",
"expo-constants": "~16.0.2",
"expo-dev-client": "~4.0.15",
"expo-file-system": "^17.0.1",
"expo-font": "~12.0.6",
"expo-media-library": "^16.0.4",
"expo-sharing": "^12.0.1",
"expo-speech": "^12.0.2",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
Expand All @@ -51,7 +54,9 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.74.1",
"react-native-blob-util": "^0.19.9",
"react-native-element-dropdown": "^2.12.0",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-paper": "^5.12.3",
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useFonts } from 'expo-font';
import { hideAsync, preventAutoHideAsync } from 'expo-splash-screen';
import { useCallback, useEffect } from 'react';
import { LogBox } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { PaperProvider } from 'react-native-paper';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
Expand All @@ -10,6 +11,12 @@ import { ActiveChatProvider, FirebaseProvider, UpdateApp } from './components';
import { Fonts, LightTheme } from './helpers';
import { AppRoutes } from './routes';

LogBox.ignoreLogs([
'Require cycle:',
'`new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.',
'`new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.'
]);

export function App() {
// load fonts and other assets here
const [isFontLoaded] = useFonts({
Expand Down
1 change: 0 additions & 1 deletion src/frontend/components/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { MainDrawerParams } from 'src/frontend/routes/MainRoutes';
import type { Chat } from 'src/frontend/types';
import { Style } from './style';


export const DropdownMenu = () => {
// get chatID after opening app copilot help
const [isVisible, setIsVisible] = useState(false);
Expand Down
86 changes: 60 additions & 26 deletions src/frontend/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,101 @@
import type { DrawerHeaderProps } from '@react-navigation/drawer';
import { DrawerActions, type RouteProp, useRoute } from '@react-navigation/native';
import * as Clipboard from 'expo-clipboard';
import * as FileSystem from 'expo-file-system';
import * as MediaLibrary from 'expo-media-library';
import * as Sharing from 'expo-sharing';
import React from 'react';
import { Alert, Pressable, View } from 'react-native';
import { IconButton, Surface, Text } from 'react-native-paper';
import { useGetChat } from 'src/frontend/hooks';
import RNFetchBlob from 'react-native-blob-util';
import RNFS from 'react-native-fs';
import { IconButton, Surface, Text, useTheme } from 'react-native-paper';
import { useActiveChatId, useGetChat, useLLMs } from 'src/frontend/hooks';
import { AilixirLogo } from 'src/frontend/icons';
import type { MainDrawerParams } from 'src/frontend/routes/MainRoutes';
import { styles } from 'src/frontend/screens/ChatUI/style';
import { DropdownMenu } from '../DropdownMenu';
import { Style } from './style';

export function Header(props: DrawerHeaderProps) {
const { colors } = useTheme();
const { navigation } = props;
const router = useRoute<RouteProp<MainDrawerParams>>();
const chatId = router.params?.chatId;
const { chat } = useGetChat(chatId || 'default');

const handleDownload = async () => {
const { activeChatId, setActiveChatId } = useActiveChatId();
const { activeLLMs, toggleLLM } = useLLMs(activeChatId || 'default');
const { chat, status, error } = useGetChat(activeChatId);

// 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 to download.');
Alert.alert('No Chat Available', 'There is no chat content available.');
return;
}

const chatContent = chat.conversation.join('\n'); // Join messages with newline character
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`;

const formattedChatContent = chat.conversation
.map((line, index) => {
return index % 2 === 0 ? `Q: '${line}'` : `A: '${line}'\n`;
})
.join('\n');

// Create filename with timestamp
const timestamp = new Date().getTime();
const fileName = `chat_${timestamp}.txt`;
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');

// Get Downloads directory path
const downloadDir = `${FileSystem.documentDirectory}Download/`;
console.log(downloadDir);
const fileName = `chat_${year}-${month}-${day}_${hours}-${minutes}-${seconds}.txt`;

// Ensure Downloads directory exists, create if not
await FileSystem.makeDirectoryAsync(downloadDir, { intermediates: true });
const path = `${RNFS.DownloadDirectoryPath}/${fileName}`;

// Save file to Downloads directory
const fileUri = `${downloadDir}${fileName}`;
await FileSystem.writeAsStringAsync(fileUri, chatContent);
const contentToSave = metadata + formattedChatContent;

Alert.alert('Chat Saved', `Chat saved to ${fileUri}`);
// 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);
Alert.alert('Error Saving Chat', 'An error occurred while saving the chat.');
console.error('Error:', error);
Alert.alert('Error', 'Failed to perform action.');
}
};

return (
<Surface style={Style.container} elevation={1}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={Style.viewContainer}>
<Pressable
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
style={{ marginRight: 16 }}
style={{ marginRight: 12 }}
>
<AilixirLogo height={36} width={36} />
</Pressable>
<Text variant='titleLarge'>AiLixir</Text>
</View>
<View style={{ flexDirection: 'row' }}>
<DropdownMenu />
<Pressable onPress={handleDownload}>
<IconButton icon='file-download' size={20} />
<Pressable onPress={handleAction} style={Style.actionButton}>
<IconButton icon='save' size={24} iconColor={colors.primary} />
</Pressable>
</View>
</Surface>
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/components/Header/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,12 @@ export const Style = StyleSheet.create({
padding: 8,
minHeight: 64,
backgroundColor: '#fff'
},
viewContainer: {
flexDirection: 'row',
alignItems: 'center'
},
actionButton: {
marginVertical: -4
}
});
1 change: 0 additions & 1 deletion src/frontend/hooks/useActiveChatId.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useContext } from 'react';
import { ChatContext } from './../components'; // Create a custom hook to use the ChatContext


export const useActiveChatId = () => {
return useContext(ChatContext);
};
2 changes: 1 addition & 1 deletion src/frontend/hooks/useCreateChat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addDoc, collection, Timestamp, updateDoc } from 'firebase/firestore';
import { Timestamp, addDoc, collection, updateDoc } from 'firebase/firestore';
import { useState } from 'react';
import { useFirestore, useUser } from 'reactfire';
import { FirestoreCollections } from '../helpers';
Expand Down
1 change: 0 additions & 1 deletion src/frontend/hooks/useLLMs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useGetChat } from 'src/frontend/hooks';
import type { LLM } from 'src/frontend/types';
import { LLM_MODELS } from './useLLMsTypes';


export function useLLMs(chatId: string) {
const { chat, status } = useGetChat(chatId);
const { data: users } = useUser();
Expand Down
Loading
Loading