Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fb4c0db
fix: disable ScreenWrapper scroll when isKeyboardAvoiding is true, cu…
Jan 27, 2026
6ae9ce7
fix: KnowledgeResponse items transform fix
Jan 27, 2026
67d8026
chore: props simplified
Jan 30, 2026
3f70792
chore: replaced styles for Markdown code block with classnames
Jan 30, 2026
fc6d16c
Merge pull request #41 from RonasIT/PRD-2152-code-block-bg-color-fix
veliseev93 Feb 2, 2026
2bd056d
Merge pull request #39 from RonasIT/PRD-2132-keyboard-controllers-ref…
veliseev93 Feb 2, 2026
33ed906
fix: allow user to scroll up while response is generating
Feb 6, 2026
2b0e6ac
fix: voice mode modal layout fix
Feb 8, 2026
f0d927a
Merge pull request #45 from RonasIT/tfomkin/voice-mode-modal-android-…
veliseev93 Feb 9, 2026
2a9bca9
fix: enable maintainVisibleContentPosition startRenderingFromBottom
Feb 11, 2026
3486abf
fix: disable AI message actions long press while response is generating
Feb 13, 2026
ea1a808
feat: export archived chat
Feb 15, 2026
69bd1b7
Merge pull request #46 from RonasIT/tfomkin/disable-ai-message-action…
veliseev93 Feb 16, 2026
9c6350b
Merge pull request #47 from RonasIT/tfomkin/export-archived-chat-by-id
veliseev93 Feb 16, 2026
e91293d
fix: improved auto scroll behavior while response is generating
Feb 19, 2026
cdde996
Merge pull request #44 from RonasIT/PRD-2153-modify-auto-scroll-behavior
veliseev93 Feb 19, 2026
6bfc86c
fix: show version controls for ai message only if it has a parentId
Feb 20, 2026
dd63704
Merge pull request #48 from RonasIT/PRD-2215-chat-message-version-con…
veliseev93 Feb 23, 2026
384fd19
chore: release 1.3.0
veliseev93 Apr 3, 2026
b3f6c9e
Merge pull request #49 from RonasIT/release-1.3.0
veliseev93 Apr 6, 2026
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
8 changes: 4 additions & 4 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
slug: process.env.EXPO_PUBLIC_APP_SLUG as string,
scheme: process.env.EXPO_PUBLIC_APP_SCHEME as string,
owner: process.env.EXPO_PUBLIC_APP_OWNER as string,
version: '1.2.0',
version: '1.3.0',
orientation: 'portrait',
icon: './assets/icon.png',
runtimeVersion: '1.2.0',
runtimeVersion: '1.3.0',
experiments: {
reactCompiler: true,
},
Expand All @@ -42,7 +42,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
supportsTablet: false,
buildNumber: appEnv.select({
default: '18',
production: '8',
production: '10',
}),
config: {
usesNonExemptEncryption: false,
Expand All @@ -52,7 +52,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
package: appId,
versionCode: appEnv.select({
default: 15,
production: 8,
production: 10,
}),
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/app/(main)/chat/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function CreateChatScreen(): ReactElement {
header: <AppHeader title={translate('TEXT_NEW_CHAT')} onGoBack={handleBackPress} />,
}}
safeAreaProps={{ edges: [] }}
keyBoardAvoidingProps={{ enabled: !isBottomSheetInputFocused }}>
keyBoardAvoidingProps={{ enabled: !isBottomSheetInputFocused, bottomOffset: 60 }}>
<NoConnectionBanner isVisible={isOfflineMode} />
<CreateChat
onChatCreated={handleChatCreated}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface AiMessageActionsProps {
onAddDetails: (messageId: string) => void;
onMoreConcise: (messageId: string) => void;
isLast: boolean;
isResponseGenerating: boolean;
}

//TODO Extend with more actions - https://www.figma.com/design/YPCZjyVlD86psDwUxvMVBc/OpenWebUI-Redesign-React-Native?node-id=27540-25291&t=kg2yUIDp3UQDStLf-0
Expand All @@ -31,6 +32,7 @@ export function AiMessageActions({
onAddDetails,
onMoreConcise,
isLast,
isResponseGenerating,
children,
}: PropsWithChildren<AiMessageActionsProps>): ReactElement {
const translate = useTranslation('CHAT.AI_MESSAGE_ACTIONS');
Expand Down Expand Up @@ -134,7 +136,10 @@ export function AiMessageActions({

return (
<View>
<MessageActionsSheetWrapper actions={actions} sheetRef={actionsSheetRef}>
<MessageActionsSheetWrapper
actions={actions}
sheetRef={actionsSheetRef}
isResponseGenerating={isResponseGenerating}>
{children}
</MessageActionsSheetWrapper>
<ActionsBottomSheet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { useTranslation } from '@ronas-it/react-native-common-modules/i18n';
import { compact } from 'lodash-es';
import { Fragment, ReactElement, useRef } from 'react';
import { DownloadChatOptionsSheet } from '@open-webui-react-native/mobile/shared/features/download-chat-options-sheet';
import { ChatListRow, ChatListRowProps } from '@open-webui-react-native/mobile/shared/ui/chat-list-row';
import { ActionsBottomSheet, ActionSheetItemProps } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { chatApi, ChatListItem } from '@open-webui-react-native/shared/data-access/api';
import { withOfflineGuard } from '@open-webui-react-native/shared/features/network';
import { alertService } from '@open-webui-react-native/shared/utils/alert-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';

interface ArchivedChatItemProps extends Partial<ChatListRowProps> {
onItemPress: (id: string) => void;
Expand All @@ -19,6 +19,7 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh
const translate = useTranslation('CHAT.ARCHIVED_CHATS_LIST.CHAT_ITEM');

const actionsSheetRef = useRef<BottomSheetModal>(null);
const downloadOptionsModalRef = useRef<BottomSheetModal>(null);

const { mutateAsync: deleteChat, isPending: isDeleting } = chatApi.useDelete();
const { mutateAsync: unarchiveChat, isPending: isUnarchiving } = chatApi.useUnarchiveChat();
Expand Down Expand Up @@ -46,7 +47,8 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh
await unarchiveChat(item.id);
actionsSheetRef.current?.close();
};
const handleExportChatPress = (): void => ToastService.showFeatureNotImplemented();

const handleExportChatPress = (): void => downloadOptionsModalRef.current?.present();

const actions: Array<ActionSheetItemProps> = compact([
{
Expand Down Expand Up @@ -80,6 +82,7 @@ export function ArchivedChatItem({ item, onItemPress, ...restProps }: ArchivedCh
{...restProps}
/>
<ActionsBottomSheet actions={actions} ref={actionsSheetRef} />
<DownloadChatOptionsSheet ref={downloadOptionsModalRef} chatId={item.id} />
</Fragment>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface ArchivedChatsActionsSheetProps {
export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsActionsSheetProps): ReactElement {
const translate = useTranslation('CHAT.ARCHIVED_CHATS_LIST.ARCHIVED_CHATS_ACTIONS_SHEET');

const actionsHSeetRef = useRef<BottomSheetModal>(null);
const actionsSheetRef = useRef<BottomSheetModal>(null);

const { unarchiveAllChats, isUnarchiving: isUnarchivingAllChats } = useUnarchiveChats();

Expand All @@ -35,12 +35,12 @@ export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsAction

const handleConfirmUnarchiveAll = async (): Promise<void> => {
await unarchiveAllChats();
actionsHSeetRef.current?.close();
actionsSheetRef.current?.close();
};

const handleExportArchivedChats = async (): Promise<void> => {
await exportArchivedChats();
actionsHSeetRef.current?.close();
actionsSheetRef.current?.close();
};

const actions: Array<ActionSheetItemProps> = compact([
Expand All @@ -61,5 +61,5 @@ export function ArchivedChatsActionsSheet({ renderTrigger }: ArchivedChatsAction
return <ActionsBottomSheet
actions={actions}
renderTrigger={renderTrigger}
ref={actionsHSeetRef} />;
ref={actionsSheetRef} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,14 @@ export function ChatAiMessage({
visible={isPreviewVisible}
onClosePress={handleCloseImagePress}
/>
<MessageVersionControls
message={message}
onNextSibling={onNextSibling}
onPreviousSibling={onPreviousSibling}
getSiblingsInfo={getSiblingsInfo}
/>
{message.parentId && (
<MessageVersionControls
message={message}
onNextSibling={onNextSibling}
onPreviousSibling={onPreviousSibling}
getSiblingsInfo={getSiblingsInfo}
/>
)}
</Fragment>
) : (
<SkeletonMessage />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FlashList } from '@shopify/flash-list';
import { useLocalSearchParams } from 'expo-router';
import { delay } from 'lodash-es';
import React, { ReactElement, useCallback, useRef, useState } from 'react';
import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import React, { ReactElement, useCallback, useRef } from 'react';
import { GestureResponderEvent, 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';
import { useManageMessageSiblings } from '@open-webui-react-native/mobile/chat/features/use-manage-messages-siblings';
Expand Down Expand Up @@ -62,7 +62,8 @@ export default function ChatMessagesList({
const isScrollToBottomAvailableTimeout = useRef<NodeJS.Timeout | null | number>(null); //NOTE: number needs to fix pipeline lint error
const isScrollToBottomVisible = useSharedValue(0);
const previousScrollY = useRef(0);
const [autoscrollToBottomThreshold, setAutoscrollToBottomThreshold] = useState<number | undefined>(1);
const shouldAutoscrollToBottomRef = useRef(true);
const previousTouchY = useRef(0);

const { showPreviousSibling, showNextSibling, getSiblingsInfo } = useManageMessageSiblings(chatId, history);
const { mutate: completeChat } = chatApi.useCompleteChat();
Expand All @@ -80,6 +81,12 @@ export default function ChatMessagesList({
isScrollToBottomAvailable.current = true;
}, 500);

if (shouldAutoscrollToBottomRef.current) {
requestAnimationFrame(() => {
listRef.current?.scrollToEnd({ animated: true });
});
}

if (!isMessagesListLoaded && listRef.current && messages?.length > 0) {
delay(() => {
listRef.current?.scrollToIndex({
Expand Down Expand Up @@ -120,6 +127,7 @@ export default function ChatMessagesList({
//NOTE: Needs to hide scroll to bottom button to avoid its jumping while scrolling to bottom
animateScrollToBottom(0);
isScrollToBottomAvailable.current = false;

delay(() => {
isScrollToBottomAvailable.current = true;
}, 1000);
Expand All @@ -128,7 +136,6 @@ export default function ChatMessagesList({
};

const handleEditPress = (index: number, messageId: string, content: string): void => {
setAutoscrollToBottomThreshold(undefined);
onEditPress(messageId, content);
delay(() => {
listRef.current?.scrollToIndex({
Expand All @@ -137,9 +144,6 @@ export default function ChatMessagesList({
animated: true,
});
}, 500);
delay(() => {
setAutoscrollToBottomThreshold(1);
}, 1000);
};

const handleContinueResponsePress = (messageId: string): void => {
Expand Down Expand Up @@ -171,6 +175,23 @@ export default function ChatMessagesList({
onFollowUpPress(text);
};

const handleTouchStart = (e: GestureResponderEvent): void => {
if (!isResponseGenerating) return;

shouldAutoscrollToBottomRef.current = false;
previousTouchY.current = e.nativeEvent.pageY;
};

const handleTouchMove = (e: GestureResponderEvent): void => {
if (!isResponseGenerating) return;

const { pageY } = e.nativeEvent;
const deltaY = pageY - previousTouchY.current;

previousTouchY.current = pageY;
shouldAutoscrollToBottomRef.current = deltaY < 0;
};

const renderItem = useCallback(
({ item, index }: { item: Message; index: number }) => {
const message = history?.messages[item.id];
Expand All @@ -191,6 +212,7 @@ export default function ChatMessagesList({
onTryAgain={onTryAgain}
onAddDetails={onAddDetails}
onMoreConcise={onMoreConcise}
isResponseGenerating={isResponseGenerating}
isLast={isLast}>
<ChatAiMessage
message={message}
Expand Down Expand Up @@ -243,14 +265,13 @@ export default function ChatMessagesList({
ItemSeparatorComponent={() => <View className='h-20' />}
data={messages}
renderItem={renderItem}
// TODO: Add autoscrollToBottom logic when it implemented in lib
maintainVisibleContentPosition={{
startRenderingFromBottom: true,
animateAutoScrollToBottom: true,
autoscrollToBottomThreshold,
}}
onContentSizeChange={handleContentSizeChange}
onScroll={handleScroll}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
scrollEventThrottle={16}
/>
<ChatBottomButton isVisible={isScrollToBottomVisible} onPress={scrollToBottom} />
Expand Down
50 changes: 26 additions & 24 deletions libs/mobile/chat/features/voice-mode-modal/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,31 +184,33 @@ export function VoiceModeModal({ onChatCreated, ref, ...props }: VoiceModeModalP
backdropTransitionOutTiming={1}
animationOutTiming={1}
animationIn='fadeIn'
style={{ overflow: 'hidden' }}
style={{ overflow: 'hidden', margin: 0 }}
{...props}>
<AppSafeAreaView edges={['bottom']} className='flex-1'>
<View className='flex-1 items-center justify-center'>
{isThinking || isAiSpeaking ? <Loader /> : <SpeechListener metering={metering} />}
</View>
<View className='flex-row justify-between items-center p-12'>
<IconButton
iconName='camera'
onPress={showUnderConstruction}
className='w-40 h-40 bg-background-secondary rounded-full'
/>
<AppText className='text-sm-sm sm:text-sm'>
{isAiSpeaking
? translate('TEXT_TALKING')
: isThinking
? translate('TEXT_THINKING')
: translate('TEXT_LISTENING')}
</AppText>
<IconButton
iconName='close'
onPress={close}
className='w-40 h-40 bg-background-secondary rounded-full' />
</View>
</AppSafeAreaView>
<View className='flex-1 bg-background-primary'>
<AppSafeAreaView edges={['bottom']} className='flex-1'>
<View className='flex-1 items-center justify-center'>
{isThinking || isAiSpeaking ? <Loader /> : <SpeechListener metering={metering} />}
</View>
<View className='flex-row justify-between items-center p-24'>
<IconButton
iconName='camera'
onPress={showUnderConstruction}
className='w-40 h-40 bg-background-secondary rounded-full'
/>
<AppText className='text-sm-sm sm:text-sm'>
{isAiSpeaking
? translate('TEXT_TALKING')
: isThinking
? translate('TEXT_THINKING')
: translate('TEXT_LISTENING')}
</AppText>
<IconButton
iconName='close'
onPress={close}
className='w-40 h-40 bg-background-secondary rounded-full' />
</View>
</AppSafeAreaView>
</View>
<AppToast />
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import {
import { useAnimateMessage } from './hooks/use-animate-message';

interface MessageActionsSheetWrapperProps {
sheetRef?: React.RefObject<BottomSheetModal | null>;
actions: Array<ActionSheetItemProps>;
isResponseGenerating?: boolean;
sheetRef?: React.RefObject<BottomSheetModal | null>;
}

export function MessageActionsSheetWrapper({
sheetRef,
actions,
isResponseGenerating,
children,
}: PropsWithChildren<MessageActionsSheetWrapperProps>): ReactElement {
const actionsSheetRef = useRef<BottomSheetModal>(null);
const { animatedStyle, startAnimation, stopAnimation } = useAnimateMessage();

const handleLongPress = (): void => {
if (isResponseGenerating) return;

startAnimation();
actionsSheetRef.current?.present();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UpsertFolderSheet,
UpsertFolderSheetMethods,
} from '@open-webui-react-native/mobile/folder/features/upsert-folder-sheet';
import { DownloadChatOptionsSheet } from '@open-webui-react-native/mobile/shared/features/download-chat-options-sheet';
import {
ActionButtonsModal,
ActionButtonsModalMethods,
Expand All @@ -31,7 +32,6 @@ import {
import { withOfflineGuard } from '@open-webui-react-native/shared/features/network';
import { alertService } from '@open-webui-react-native/shared/utils/alert-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { DownloadChatOptionsSheet } from './components';
import { ChatAction } from './enums';

export type ChatActionsMenuSheetMethods = {
Expand Down

This file was deleted.

12 changes: 12 additions & 0 deletions libs/mobile/shared/features/download-chat-options-sheet/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# mobile/shared/features/download-chat-options-sheet

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test mobile/shared/features/download-chat-options-sheet` to execute the unit tests via [Jest](https://jestjs.io).
Loading
Loading