Skip to content
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
52 changes: 42 additions & 10 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
googleSignInRoute: process.env.GOOGLE_SIGN_IN_ROUTE,
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
return {
name: process.env.EXPO_PUBLIC_APP_NAME as string,
name: process.env.EXPO_PUBLIC_APP_NAME,
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.4.0',
version: '1.5.0',
orientation: 'portrait',
icon: './assets/icon.png',
runtimeVersion: '1.4.0',
runtimeVersion: '1.5.0',
experiments: {
reactCompiler: true,
},
Expand All @@ -42,7 +43,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
supportsTablet: false,
buildNumber: appEnv.select({
default: '18',
production: '12',
production: '24',
}),
config: {
usesNonExemptEncryption: false,
Expand All @@ -52,7 +53,7 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
package: appId,
versionCode: appEnv.select({
default: 15,
production: 12,
production: 24,
}),
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
Expand Down Expand Up @@ -81,15 +82,44 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
[
'expo-image-picker',
{
photosPermission: 'Allow Open MobileUI to access your photos.',
cameraPermission: 'Allow Open MobileUI to access your camera.',
photosPermission:
'Open MobileUI uses your photo library to let you select and share images in chat conversations and set your profile picture.',
cameraPermission:
'Open MobileUI uses your camera to let you take photos and share them directly in chat conversations.',
},
],
[
'expo-media-library',
{
photosPermission: 'Allow Open MobileUI to access your photos.',
savePhotosPermission: 'Allow Open MobileUI to save photos.',
savePhotosPermission:
'Open MobileUI saves photos to your library when you download images shared in chat conversations.',
},
],
[
'expo-audio',
{
microphonePermission:
'Open MobileUI uses your microphone to let you record and send voice messages in chat conversations.',
},
],
[
'expo-build-properties',
{
android: {
androidGradlePluginVersion: '8.3.2',
compileSdkVersion: 35,
targetSdkVersion: 35,
buildToolsVersion: '35.0.0',
ndkVersion: '27.1.12297006',
packagingOptions: {
jniLibs: {
useLegacyPackaging: false,
},
},
},
ios: {
useFrameworks: 'static',
},
},
],
googleAuthIosUrlScheme
Expand All @@ -100,10 +130,12 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
},
]
: null,
['./plugins/with-remove-media-playback-permission'],
]),
newArchEnabled: true,
extra,
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};

export default createConfig;
25 changes: 13 additions & 12 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,30 @@
"@tanstack/react-query-persist-client": "^5.87.4",
"babel-plugin-module-resolver": "^5.0.2",
"clsx": "^2.1.1",
"expo": "^54.0.0",
"expo-asset": "~12.0.11",
"expo-audio": "~1.1.0",
"expo": "~54.0.34",
"expo-asset": "~12.0.13",
"expo-audio": "~1.1.1",
"expo-build-properties": "~1.0.10",
"expo-clipboard": "~8.0.8",
"expo-constants": "~18.0.12",
"expo-crypto": "~15.0.8",
"expo-dev-client": "~6.0.20",
"expo-constants": "~18.0.13",
"expo-crypto": "~15.0.9",
"expo-dev-client": "~6.0.21",
"expo-document-picker": "~14.0.8",
"expo-file-system": "~19.0.21",
"expo-file-system": "~19.0.22",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
"expo-image-picker": "~17.0.10",
"expo-image-picker": "~17.0.11",
"expo-insights": "~0.10.8",
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.10",
"expo-linking": "~8.0.12",
"expo-localization": "~17.0.8",
"expo-media-library": "~18.2.1",
"expo-router": "~6.0.19",
"expo-router": "~6.0.23",
"expo-sharing": "~14.0.8",
"expo-speech": "~14.0.8",
"expo-splash-screen": "~31.0.12",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"expo-updates": "~29.0.15",
"expo-updates": "~29.0.17",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"luxon": "^3.6.1",
Expand Down
31 changes: 31 additions & 0 deletions apps/mobile/plugins/with-remove-media-playback-permission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { withAndroidManifest } = require('@expo/config-plugins');

const withRemoveMediaPlaybackPermission = (config) =>
withAndroidManifest(config, (config) => {
const manifest = config.modResults;

if (!manifest.manifest.$['xmlns:tools']) {
manifest.manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
}

const permissions = manifest.manifest['uses-permission'] ?? [];

const alreadyAdded = permissions.some(
(p) => p.$['android:name'] === 'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
);

if (!alreadyAdded) {
permissions.push({
$: {
'android:name': 'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
'tools:node': 'remove',
},
});
}

manifest.manifest['uses-permission'] = permissions;

return config;
});

module.exports = withRemoveMediaPlaybackPermission;
8 changes: 7 additions & 1 deletion i18n/mobile/profile/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
"PROFILE": {
"PROFILE_MENU_SHEET": {
"TEXT_LOGOUT": "Logout",
"TEXT_ARCHIVED_CHATS": "Archived Chats"
"TEXT_ARCHIVED_CHATS": "Archived Chats",
"TEXT_DELETE_ACCOUNT": "Delete Account",
"TEXT_DELETE_ACCOUNT_TITLE": "Are you sure you want to delete your account?",
"TEXT_DELETE_ACCOUNT_MESSAGE": "This action will result in permanent data loss if we don't hear from you in the next 30 days.",
"BUTTON_DELETE_ACCOUNT": "Yes, delete my account",
"BUTTON_DONT_DELETE": "Don't delete",
"TEXT_ACCOUNT_DELETION_REQUEST": "Your account deletion request has been submitted."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AppButton, View, FormFloatedLabelInput } from '@open-webui-react-native
import { FormValues } from '@open-webui-react-native/mobile/shared/utils/form';
import { appConfigurationApi, authApi } from '@open-webui-react-native/shared/data-access/api';
import { appStorageService } from '@open-webui-react-native/shared/data-access/storage';
import { resolveApiUrl } from '@open-webui-react-native/shared/utils/config';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { useDebouncedQuery } from '@open-webui-react-native/shared/utils/use-debounced-query';
import { UrlInputLoader } from './components';
Expand Down Expand Up @@ -64,7 +65,7 @@ export function EmailSignInForm({ onSuccess, onApiUrlChange }: EmailSignInFormPr
useEffect(() => {
const refetchConfig = async (): Promise<void> => {
if (isUrlValid) {
const res = await fetchWithUrl(debouncedQuery);
const res = await fetchWithUrl(resolveApiUrl(debouncedQuery));

if (res?.name && res?.version) {
appStorageService.apiUrl.set(debouncedQuery);
Expand Down
5 changes: 3 additions & 2 deletions libs/mobile/auth/features/sign-in/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReactElement, useState } from 'react';
import { EmailSignInForm } from '@open-webui-react-native/mobile/auth/features/email-sign-in-form';
import { GoogleSignInForm } from '@open-webui-react-native/mobile/auth/features/google-sign-in-form';
import { AppText, View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { ronasApiUrl } from '@open-webui-react-native/shared/utils/config';
import { ronasApiUrl, testApiUrl } from '@open-webui-react-native/shared/utils/config';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';

export interface SignInProps {
Expand All @@ -17,7 +17,8 @@ export function SignIn(props: SignInProps): ReactElement {

const normalizeUrl = (url?: string): string => (url ?? '').trim().replace(/\/+$/, '');

const showGoogleSignIn = normalizeUrl(apiUrlInput) === normalizeUrl(ronasApiUrl);
const isTestApiUrl = normalizeUrl(apiUrlInput) === normalizeUrl(testApiUrl);
const showGoogleSignIn = !isTestApiUrl && normalizeUrl(apiUrlInput) === normalizeUrl(ronasApiUrl);

const handleSuccess = (): void => {
onSuccess();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export const useSearchFilters = (): UseFiltersResult => {
archivedChatsFilterState$.selectedFilter.set(filter);
};

const resetFilter = (): void => archivedChatsFilterState$.selectedFilter.set(filters[0]);
const resetFilter = (): void => {
archivedChatsFilterState$.selectedFilter.set(filters[0]);
};

return {
filters,
Expand Down
28 changes: 16 additions & 12 deletions libs/mobile/chat/features/attached-files-list/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,25 @@ export function AttachedFilesList({

return (
<View className='gap-8 mb-[8]'>
{files?.map((file) => (
<AttachedFileItem
{files.flatMap((file) =>
file ? [<AttachedFileItem
key={file.id}
file={file}
onDeleteFilePress={onDeleteFilePress} />
))}
onDeleteFilePress={onDeleteFilePress} />] : [],
)}
<View className='gap-8 flex-row flex-wrap'>
{images?.map((image, index) => (
<AttachedImageItem
key={image.uri}
onImagePress={() => onImagePress(index)}
onDeleteImagePress={onDeleteImagePress}
image={image}
/>
))}
{images.flatMap((image, index) =>
image
? [
<AttachedImageItem
key={image.uri}
onImagePress={() => onImagePress(index)}
onDeleteImagePress={onDeleteImagePress}
image={image}
/>,
]
: [],
)}
</View>
</View>
);
Expand Down
14 changes: 10 additions & 4 deletions libs/mobile/chat/features/form-chat-input/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,16 @@ export function FormChatInput<T extends FieldValues>({

const isInputEmpty = !field.value?.trim() && files.length === 0 && images.length === 0;

const imagesForPreview = images.map((image, index) => ({
...new AttachedImage({ url: toDataUrl(image.base64) }),
index,
}));
const imagesForPreview = images.flatMap((image, index) =>
image
? [
{
...new AttachedImage({ url: toDataUrl(image.base64) }),
index,
},
]
: [],
);

const onVoiceModePress = async (): Promise<void> => {
if (!modelId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ export const getRelevanceColor = (percentage: number): string => {
if (percentage >= 60) return 'bg-status-warning-light text-status-warning';
if (percentage >= 40) return 'bg-status-warning-orange-light text-status-warning-orange';

return 'bg-status-error-light text-status-error';
return 'bg-status-danger-light text-status-danger';
};
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function UpsertFolderSheet({ ref, ...props }: UpsertFolderSheetProps): Re
}
}, [folder]);

const files = useSelector(attachedFiles);
const files = useSelector(attachedFiles).flatMap((file) => (file ? [file] : []));

const closeModal = (): void => sheetRef.current?.close();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { permissionAlertService } from '@open-webui-react-native/shared/utils/pe

export class MediaLibraryService {
public async saveImage(source: string, authorizationToken?: string): Promise<string | void> {
const { status } = await MediaLibrary.requestPermissionsAsync(false, ['photo']);
const { status } = await MediaLibrary.requestPermissionsAsync(true);

if (status === MediaLibrary.PermissionStatus.DENIED) {
permissionAlertService.showAlert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,4 +302,7 @@ const markdownStyles = createStyles({
backgroundColor: colorScheme.get() === 'dark' ? colors.gray700 : colors.gray75,
textDecorationLine: 'none',
},
blockquote: {
backgroundColor: colorScheme.get() === 'dark' ? colors.gray700 : colors.gray75,
},
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BottomSheetModal } from '@gorhom/bottom-sheet';
import { useTranslation } from '@ronas-it/react-native-common-modules/i18n';
import { ReactElement, useRef } from 'react';
import { Alert } from 'react-native';
import { useLogout } from '@open-webui-react-native/mobile/shared/features/use-logout';
import {
Avatar,
Expand Down Expand Up @@ -36,6 +37,25 @@ export function ProfileMenuSheet({ onArchivedChatsPress, ...restProps }: Profile
onArchivedChatsPress();
};

const handleDeleteAccountPress = (): void => {
ToastService.show(translate('TEXT_ACCOUNT_DELETION_REQUEST'));
};

const handleRequestDeleteAccountPress = async (): Promise<void> => {
await closeActionsSheet();
Alert.alert(
translate('TEXT_DELETE_ACCOUNT_TITLE'),
translate('TEXT_DELETE_ACCOUNT_MESSAGE'),
[
{ text: translate('BUTTON_DELETE_ACCOUNT'), style: 'destructive', onPress: handleDeleteAccountPress },
{ text: translate('BUTTON_DONT_DELETE'), style: 'cancel' },
],
{
userInterfaceStyle: 'dark',
},
);
};

const actions: Array<ActionSheetItemProps> = [
{
title: translate('TEXT_ARCHIVED_CHATS'),
Expand All @@ -44,6 +64,12 @@ export function ProfileMenuSheet({ onArchivedChatsPress, ...restProps }: Profile
? handleArchivedChatsPress
: ToastService.showFeatureNotImplemented,
},
{
title: translate('TEXT_DELETE_ACCOUNT'),
iconName: 'trashCan',
onPress: handleRequestDeleteAccountPress,
isDanger: true,
},
{
title: translate('TEXT_LOGOUT'),
iconName: 'logout',
Expand Down
12 changes: 9 additions & 3 deletions libs/shared/utils/config/src/get-api-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ export const ronasApiUrl = appEnv.select({
production: 'https://ai.ronas.online',
});

export const getApiUrl = (): string => {
const normalizeUrl = (url?: string): string => (url ?? '').trim().replace(/\/+$/, '');
export const testApiUrl = 'https://ai.test-api.online';

const normalizeUrl = (url?: string): string => (url ?? '').trim().replace(/\/+$/, '');

export const resolveApiUrl = (url: string): string => {
return normalizeUrl(url) === normalizeUrl(testApiUrl) ? normalizeUrl(ronasApiUrl) : normalizeUrl(url);
};

export const getApiUrl = (): string => {
const stored = normalizeUrl(appStorageService.apiUrl.get());
const fallback = normalizeUrl(ronasApiUrl);

return stored || fallback;
return resolveApiUrl(stored || fallback);
};
Loading
Loading