Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExpoConfig } from '@expo/config';
import { EASConfig } from 'expo-constants/build/Constants.types';
import { EASConfig } from 'expo-manifests';
import { AppEnv } from '../../libs/shared/utils/app-env/src/env';
import { AppEnvName } from '../../libs/shared/utils/app-env/src/app-env';
import { compact } from 'lodash-es';
Expand Down
61 changes: 32 additions & 29 deletions apps/mobile/app/(main)/chat/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AppText,
Icon,
FullScreenSearchModal,
AppKeyboardControllerView,
} from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { ChatScreenParams, useInitialNavigation } from '@open-webui-react-native/mobile/shared/utils/navigation';
import { modelsApi } from '@open-webui-react-native/shared/data-access/api';
Expand Down Expand Up @@ -48,34 +49,36 @@ export default function ChatScreen(): ReactElement {
);

return (
<AppScreen
className={cn(isOfflineMode && 'pt-20')}
noOutsideSpacing
header={
<AppHeader
title={
modelId || isLoading ? (
<FullScreenSearchModal
data={models || []}
renderTrigger={renderTrigger}
selectedItemId={modelId}
onSelectItem={onSelectModel}
searchPlaceholder={translate('TEXT_SELECT_A_MODEL')}
/>
) : (
translate('TEXT_LOADING')
)
}
onGoBack={handleGoBackPress}
/>
}
scrollDisabled>
<NoConnectionBanner isVisible={isOfflineMode} />
<Chat
chatId={id}
isNewChat={!!isNewChat}
selectedModelId={modelId}
resetToChatsList={handleResetToChatsList} />
</AppScreen>
<AppKeyboardControllerView className='bg-background-primary'>
<AppScreen
className={cn(isOfflineMode && 'pt-20')}
noOutsideSpacing
header={
<AppHeader
title={
modelId || isLoading ? (
<FullScreenSearchModal
data={models || []}
renderTrigger={renderTrigger}
selectedItemId={modelId}
onSelectItem={onSelectModel}
searchPlaceholder={translate('TEXT_SELECT_A_MODEL')}
/>
) : (
translate('TEXT_LOADING')
)
}
onGoBack={handleGoBackPress}
/>
}
scrollDisabled>
<NoConnectionBanner isVisible={isOfflineMode} />
<Chat
chatId={id}
isNewChat={!!isNewChat}
selectedModelId={modelId}
resetToChatsList={handleResetToChatsList} />
</AppScreen>
</AppKeyboardControllerView>
);
}
2 changes: 1 addition & 1 deletion apps/mobile/app/(main)/chat/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function ChatLayout(): ReactElement {
close: async () => await voiceModeModalRef.current?.close(),
};

const handleChatCreated = (id: string): void => router.replace(navigationConfig.main.chat.view({ id }));
const handleChatCreated = (id: string): void => router.push(navigationConfig.main.chat.view({ id }));

return (
<VoiceModeModalContext.Provider value={contextValue}>
Expand Down
9 changes: 7 additions & 2 deletions apps/mobile/app/(main)/chat/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ import { appState$ } from '@open-webui-react-native/shared/data-access/app-state
import { useTranslation } from '@ronas-it/react-native-common-modules/i18n';
import { router } from 'expo-router';
import { ReactElement, useRef } from 'react';
import { Keyboard, InteractionManager } from 'react-native';

export default function CreateChatScreen(): ReactElement {
const translate = useTranslation('CHAT.CREATE_CHAT');
const upsertFolderSheetRef = useRef<UpsertFolderSheetMethods>(null);

const handleChatCreated = (id: string): void =>
router.replace(navigationConfig.main.chat.view({ id, isNewChat: 'true' }));
const handleChatCreated = (id: string): void => {
Keyboard.dismiss();
InteractionManager.runAfterInteractions(() => {
router.push(navigationConfig.main.chat.view({ id, isNewChat: 'true' }));
});
};

const openCreateFolderModal = (): void => upsertFolderSheetRef.current?.present();

Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(main)/chat/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function ChatListScreen(): ReactElement {

return (
<ScreenWrapper safeAreaProps={{ edges: [] }} screenProps={{ noOutsideSpacing: true, scrollDisabled: true }}>
<View className='flex-1 bg-background-primary mt-safe'>
<View className='flex-1 bg-background-primary ios:mt-safe'>
<AppHeader
className='mt-0'
title={translate('TEXT_CHATS')}
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function App(): ReactElement | null {

return (
<View className='bg-background-primary flex-1'>
<StatusBar className='bg-background-primary' />
<StatusBar className='bg-background-primary' translucent />
<Stack>
<Stack.Screen name='index' options={{ headerShown: false, contentStyle: { backgroundColor: 'transparent' } }} />
<Stack.Screen name={navigationConfig.auth.root} options={{ headerShown: false }} />
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ module.exports = function (api) {
'nativewind/babel',
],
plugins: [
'react-native-reanimated/plugin',
[
'module-resolver',
{
Expand All @@ -23,6 +22,7 @@ module.exports = function (api) {
},
},
],
'react-native-worklets/plugin',
],
};
};
71 changes: 34 additions & 37 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,58 +29,55 @@
"@react-native-community/netinfo": "11.4.1",
"@react-native-cookies/cookies": "^6.2.1",
"@react-native-google-signin/google-signin": "^15.0.0",
"@react-navigation/drawer": "^7.4.1",
"@react-navigation/drawer": "^7.3.9",
"@ronas-it/react-native-common-modules": "^1.1.0",
"@ronas-it/rtkq-entity-api": "^0.4.10",
"@shopify/flash-list": "^2.0.0-rc.11",
"@shopify/flash-list": "2.0.2",
"@tanstack/react-query": "^5.80.6",
"@tanstack/react-query-persist-client": "^5.87.4",
"@testing-library/jest-native": "*",
"@testing-library/react-native": "*",
"babel-plugin-module-resolver": "^5.0.2",
"clsx": "^2.1.1",
"expo": "53.0.11",
"expo-asset": "~11.1.7",
"expo-audio": "~0.4.9",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.6",
"expo-crypto": "~14.1.5",
"expo-dev-client": "^5.0.6",
"expo-document-picker": "~13.1.6",
"expo-file-system": "~18.1.11",
"expo-haptics": "~14.1.4",
"expo-image": "~2.4.0",
"expo-image-picker": "~16.1.4",
"expo-insights": "~0.9.3",
"expo-linear-gradient": "~14.1.5",
"expo-linking": "~7.1.5",
"expo-localization": "~16.1.5",
"expo-media-library": "~17.1.7",
"expo-router": "~5.0.7",
"expo-sharing": "~13.1.5",
"expo-speech": "~13.1.7",
"expo-splash-screen": "~0.30.10",
"expo-status-bar": "^2.0.0",
"expo-updates": "~0.28.13",
"expo": "^54.0.0",
"expo-asset": "~12.0.11",
"expo-audio": "~1.1.0",
"expo-clipboard": "~8.0.8",
"expo-constants": "~18.0.12",
"expo-crypto": "~15.0.8",
"expo-dev-client": "~6.0.20",
"expo-document-picker": "~14.0.8",
"expo-file-system": "~19.0.21",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-image-picker": "~17.0.10",
"expo-insights": "~0.10.8",
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.10",
"expo-localization": "~17.0.8",
"expo-media-library": "~18.2.1",
"expo-router": "~6.0.19",
"expo-sharing": "~14.0.8",
"expo-speech": "~14.0.8",
"expo-splash-screen": "~31.0.12",
"expo-status-bar": "~3.0.9",
"expo-updates": "~29.0.15",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"luxon": "^3.6.1",
"metro-config": "*",
"nativewind": "^4.1.23",
"react": "19.0.0",
"react": "19.1.0",
"react-hook-form": "^7.57.0",
"react-native": "*",
"react-native": "0.81.5",
"react-native-compressor": "^1.12.0",
"react-native-extended-stylesheet": "^0.12.0",
"react-native-gesture-handler": "~2.24.0",
"react-native-keyboard-controller": "^1.17.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-keyboard-controller": "1.18.5",
"react-native-mmkv": "^3.2.0",
"react-native-modal": "^14.0.0-rc.1",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.10.0",
"react-native-svg": "*",
"react-native-web": "*",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-svg": "15.12.1",
"react-native-web": "^0.21.0",
"reflect-metadata": "^0.2.2",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^3.4.17",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
PressableSearchInput,
View,
} from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { useBottomInset } from '@open-webui-react-native/mobile/shared/utils/use-bottom-inset';
import { chatApi, ChatListItem } from '@open-webui-react-native/shared/data-access/api';
import { formatDateTime } from '@open-webui-react-native/shared/utils/date';
import { ArchivedChatsActionsSheet } from './components';
Expand All @@ -33,6 +34,7 @@ export function ArchivedChatsList({
onSearchPress,
}: ArchivedChatsListProps): ReactElement {
const translate = useTranslation('CHAT.ARCHIVED_CHATS_LIST');
const bottomInset = useBottomInset();

const { filters, selectedFilter, handleFilterPress, resetFilter } = useSearchFilters();

Expand Down Expand Up @@ -103,15 +105,14 @@ export function ArchivedChatsList({
}
transformSectionTitle={transformSectionTitle}
keyboardShouldPersistTaps='handled'
estimatedItemSize={32}
showsVerticalScrollIndicator={false}
ItemSeparatorComponent={renderSeparator}
renderItem={renderItem}
ListFooterComponent={isFetchingNextPage ? <AppSpinner /> : null}
ListEmptyComponent={
<ListEmptyComponent containerClassName='mt-16' description={translate('TEXT_NO_CHATS')} />
}
contentContainerClassName='pb-safe android:pb-16'
contentContainerStyle={{ paddingBottom: bottomInset }}
/>
)}
</View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as FileSystem from 'expo-file-system';
import * as FileSystem from 'expo-file-system/legacy';
import { useState } from 'react';
import {
fileSystemService,
Expand Down
16 changes: 11 additions & 5 deletions libs/mobile/chat/features/chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useSelector } from '@legendapp/state/react';
import { useKeyboard } from '@react-native-community/hooks';
import { useTranslation } from '@ronas-it/react-native-common-modules/i18n';
import dayjs from 'dayjs';
import { delay } from 'lodash-es';
import React, { ReactElement, useEffect, useState } from 'react';
import React, { Fragment, ReactElement, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { InteractionManager } from 'react-native';
import { EditMessageInput } from '@open-webui-react-native/mobile/chat/features/edit-message-input';
Expand All @@ -13,8 +14,9 @@ import { useSendMessage } from '@open-webui-react-native/mobile/chat/features/us
import { useSuggestChange } from '@open-webui-react-native/mobile/chat/features/use-suggest-change';
import { useAttachedFiles } from '@open-webui-react-native/mobile/shared/features/use-attached-files';
import { cn } from '@open-webui-react-native/mobile/shared/ui/styles';
import { AppKeyboardControllerView, AppSpinner, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { AppSpinner, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { FormValues } from '@open-webui-react-native/mobile/shared/utils/form';
import { useBottomInset } from '@open-webui-react-native/mobile/shared/utils/use-bottom-inset';
import { chatApi, ChatGenerationOption, chatQueriesKeys } from '@open-webui-react-native/shared/data-access/api';
import { Role } from '@open-webui-react-native/shared/data-access/common';
import { useSubscribeToQueryCache } from '@open-webui-react-native/shared/data-access/query-client';
Expand All @@ -36,6 +38,8 @@ interface ChatProps {
export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: ChatProps): ReactElement {
const translate = useTranslation('CHAT.CHAT');
const translateRegeneratePrompt = useTranslation('CHAT.AI_MESSAGE_ACTIONS.REGENERATE_MESSAGE_ACTION_SHEET');
const bottomInset = useBottomInset();
const { keyboardShown } = useKeyboard();

const [isInputFocusing, setIsInputFocusing] = useState(false); //NOTE: Needs to avoid ChatBottomButton jumping when auto-scrolling after focus

Expand Down Expand Up @@ -181,7 +185,7 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
}, [isNewChat, isSuccess, chatId]);

return (
<AppKeyboardControllerView>
<Fragment>
{shouldHideContent && (
<View className='absolute w-full h-full z-50 bg-background-primary items-center justify-center'>
<AppSpinner />
Expand All @@ -207,7 +211,9 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
/>
</React.Suspense>
)}
<View className={cn('pb-safe android:pb-16 pt-8 px-16', shouldHideContent && 'opacity-0')}>
<View
style={!keyboardShown && { paddingBottom: bottomInset }}
className={cn('pt-8 px-16', shouldHideContent && 'opacity-0')}>
{activeInputMode === ActiveInputMode.EDIT && editingMessageId ? (
<EditMessageInput
control={editMessageControl}
Expand Down Expand Up @@ -246,6 +252,6 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
/>
)}
</View>
</AppKeyboardControllerView>
</Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FlashList } from '@shopify/flash-list';
import { useLocalSearchParams } from 'expo-router';
import { delay } from 'lodash-es';
import { ReactElement, useCallback, useRef, useState } from 'react';
import React, { ReactElement, useCallback, useRef, useState } from 'react';
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import { useSharedValue, withTiming } from 'react-native-reanimated';
import { AiMessageActions } from '@open-webui-react-native/mobile/chat/features/ai-message-actions';
Expand Down Expand Up @@ -53,7 +53,7 @@ export default function ChatMessagesList({
onMoreConcise,
editingMessageId,
}: ChatMessagesListProps): ReactElement {
const listRef = useRef<FlashList<Message>>(null);
const listRef = useRef<React.ComponentRef<typeof FlashList<Message>>>(null);
const isScrollToBottomAvailable = useRef(false);
const isScrollToBottomAvailableTimeout = useRef<NodeJS.Timeout | null | number>(null); //NOTE: number needs to fix pipeline lint error
const isScrollToBottomVisible = useSharedValue(0);
Expand Down
6 changes: 4 additions & 2 deletions libs/mobile/chat/features/menu-list/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
PressableSearchInput,
View,
} from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { useBottomInset } from '@open-webui-react-native/mobile/shared/utils/use-bottom-inset';
import { chatApi, ChatListItem, FolderListItem, foldersApi } from '@open-webui-react-native/shared/data-access/api';
import { formatDateTime } from '@open-webui-react-native/shared/utils/date';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
Expand All @@ -39,6 +40,7 @@ export function ChatMenuList({
const translate = useTranslation('CHAT.CHAT_MENU_LIST');
const chatActionsSheetRef = useRef<ChatActionsMenuSheetMethods>(null);
const isFocused = useIsFocused();
const bottomInset = useBottomInset();

const [isFirstLoading, setIsFirstLoading] = useState<boolean>(true);

Expand Down Expand Up @@ -98,7 +100,6 @@ export function ChatMenuList({
) : (
<DateSectionList
data={chats || []}
estimatedItemSize={52}
renderItem={renderItem}
transformSectionTitle={transformSectionTitle}
onEndReached={fetchNextPage}
Expand All @@ -122,7 +123,8 @@ export function ChatMenuList({
/>
}
ListFooterComponent={isFetchingNextPage ? <AppSpinner /> : null}
contentContainerClassName='mt-12 pb-safe android:pb-24'
contentContainerClassName='mt-12'
contentContainerStyle={{ paddingBottom: bottomInset }}
showsVerticalScrollIndicator={false}
/>
)}
Expand Down
Loading
Loading