Skip to content
Open
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
8 changes: 5 additions & 3 deletions apps/mobile/.env.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
EXPO_PUBLIC_PRIJECT_ID=
EXPO_PUBLIC_PROJECT_ID=

EXPO_PUBLIC_APP_ID=
EXPO_PUBLIC_APP_OWNER=
EXPO_PIBLIC_APP_SCHEME=
EXPO_PUBLIC_APP_SCHEME=
EXPO_PUBLIC_APP_NAME=
EXPO_PUBLIC_APP_SLUG=
EXPO_PUBLIC_IS_INTERNAL_RELEASE=
SENTRY_DSN=
SENTRY_DSN=
AMPLITUDE_API_KEY_DEV=
AMPLITUDE_API_KEY_PROD=
4 changes: 4 additions & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const createConfig = (): Omit<ExpoConfig, 'extra'> & { extra: { eas: EASConfig }
sentry: {
dsn: process.env.SENTRY_DSN,
},
amplitude: {
apiKeyDev: process.env.AMPLITUDE_API_KEY_DEV,
apiKeyProd: process.env.AMPLITUDE_API_KEY_PROD,
},
env: appEnv.current,
googleIosClientId: appEnv.select({
default: process.env.GOOGLE_IOS_CLIENT_ID_DEV,
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { queryClient } from '@open-webui-react-native/shared/data-access/query-client';
import { useSocket } from '@open-webui-react-native/shared/data-access/websocket';
import { useNetworkConnection } from '@open-webui-react-native/shared/features/network';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { constants } from '@open-webui-react-native/shared/utils/config';
import { setupReactotron } from '@open-webui-react-native/shared/utils/reactotron';
import { setLanguage } from '@ronas-it/react-native-common-modules/i18n';
Expand Down Expand Up @@ -56,6 +57,7 @@ const translations = {
const useLanguage = setLanguage(translations, constants.defaultLocale);

setupReactotron('open-web-ui');
analyticsService.init();

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/nativewind-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// <reference types="nativewind/types" />

// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
declare module '*.css';
2 changes: 2 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"ios": "expo run:ios"
},
"dependencies": {
"@amplitude/analytics-react-native": "^1.6.8",
"@expo/metro-config": "~57.0.0",
"@expo/metro-runtime": "~57.0.7",
"@expo/ui": "~57.0.7",
Expand All @@ -33,6 +34,7 @@
"@likashefqet/react-native-image-zoom": "^4.3.0",
"@lukemorales/query-key-factory": "^1.3.4",
"@pusher/pusher-websocket-react-native": "^1.3.1",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-native-community/hooks": "^100.1.0",
"@react-native-community/netinfo": "12.0.1",
"@ronas-it/axios-api-client": "^0.1.2",
Expand Down
6 changes: 6 additions & 0 deletions libs/mobile/chat/features/chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { Role } from '@open-webui-react-native/shared/data-access/common';
import { useSubscribeToQueryCache } from '@open-webui-react-native/shared/data-access/query-client';
import { webSocketConfig, webSocketState$ } from '@open-webui-react-native/shared/data-access/websocket';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
import { useAppStateChange } from '@open-webui-react-native/shared/utils/use-app-state-change';
import { ActiveInputMode } from './enums';
Expand Down Expand Up @@ -169,7 +170,12 @@ export function Chat({ chatId, selectedModelId, isNewChat, resetToChatsList }: C
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
}

if (options.includes(ChatGenerationOption.IMAGE_GENERATION)) {
analyticsService.trackEvent(AnalyticsEvent.GENERATE_IMAGE_USED);
}

sendMessage(inputValue, selectedModelId, options, attachedFiles.get(), attachedImages.get());
analyticsService.trackEvent(AnalyticsEvent.MESSAGE_SENT, { modelId: selectedModelId });
reset();
resetAttachments();
// NOTE: Forces input rerender to reset it to its initial height after submit
Expand Down
7 changes: 7 additions & 0 deletions libs/mobile/chat/features/create-chat/src/lib/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { View } from '@open-webui-react-native/mobile/shared/ui/ui-kit';
import { FormValues } from '@open-webui-react-native/mobile/shared/utils/form';
import { ChatGenerationOption } from '@open-webui-react-native/shared/data-access/api';
import { webSocketState$ } from '@open-webui-react-native/shared/data-access/websocket';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
import { SearchFolderView, SearchModelView } from './components';

Expand Down Expand Up @@ -43,6 +44,7 @@ export function CreateChat({
reset();
resetAttachments();
onChatCreated(id);
analyticsService.trackEvent(AnalyticsEvent.NEW_CHAT_CREATED);
};

const { startChatCreation, isLoading: isCreating } = useCreateNewChat({ onSuccess: handleChatCreated });
Expand All @@ -62,7 +64,12 @@ export function CreateChat({
return ToastService.showError(translate('TEXT_MODEL_NOT_SELECTED'));
}

if (options.includes(ChatGenerationOption.IMAGE_GENERATION)) {
analyticsService.trackEvent(AnalyticsEvent.GENERATE_IMAGE_USED);
}

startChatCreation(inputValue, modelId, options, attachedFiles.get(), attachedImages.get(), folderId);
analyticsService.trackEvent(AnalyticsEvent.MESSAGE_SENT, { modelId });
})();

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@open-webui-react-native/shared/data-access/api';
import { AttachedImage, FileData, ImageData } from '@open-webui-react-native/shared/data-access/common';
import { withOfflineGuard } from '@open-webui-react-native/shared/features/network';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { toDataUrl } from '@open-webui-react-native/shared/utils/files';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
Expand Down Expand Up @@ -111,6 +112,7 @@ export function FormChatInput<T extends FieldValues>({

const handleSuggestionPress = (suggestion: string): void => {
field.onChange(suggestion);
analyticsService.trackEvent(AnalyticsEvent.SUGGESTION_USED);
};

const onGenerationOptionPress = (option: ChatGenerationOption): void => setOptions((state) => xor(state, [option]));
Expand All @@ -125,6 +127,7 @@ export function FormChatInput<T extends FieldValues>({
const onCompleteRecording = (text: string): void => {
setIsDictateMode(false);
field.onChange(text);
analyticsService.trackEvent(AnalyticsEvent.DICTATION_MODE_USED);
};

const onStopGenerationPress = async (): Promise<void> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
prepareCreateFolderPayload,
} from '@open-webui-react-native/shared/data-access/api';
import { AttachedFile, FileData, FileType } from '@open-webui-react-native/shared/data-access/common';
import { AnalyticsEvent, analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';
import { FeatureID, isFeatureEnabled } from '@open-webui-react-native/shared/utils/feature-flag';
import { getDocumentFormData } from '@open-webui-react-native/shared/utils/files';
import { ToastService } from '@open-webui-react-native/shared/utils/toast-service';
Expand Down Expand Up @@ -135,6 +136,7 @@ export function UpsertFolderSheet({ ref, ...props }: UpsertFolderSheetProps): Re
});
} else {
await createFolder(payload);
analyticsService.trackEvent(AnalyticsEvent.NEW_FOLDER_CREATED);
}
closeModal();
resetKnowledge();
Expand Down
2 changes: 2 additions & 0 deletions libs/mobile/shared/features/use-logout/src/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { authApi } from '@open-webui-react-native/shared/data-access/api';
import { authState$ } from '@open-webui-react-native/shared/data-access/auth';
import { cookieService } from '@open-webui-react-native/shared/data-access/cookie';
import { queryPersister } from '@open-webui-react-native/shared/data-access/persist-query-storage';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';

export const useLogout = (): { logout: () => Promise<void>; isLoading: boolean } => {
const queryClient = useQueryClient();
Expand All @@ -17,6 +18,7 @@ export const useLogout = (): { logout: () => Promise<void>; isLoading: boolean }
authState$.logout();
queryClient.removeQueries();
cookieService.clearAll();
analyticsService.resetUser();
await queryPersister.removeClient();
};

Expand Down
10 changes: 9 additions & 1 deletion libs/shared/features/use-init-requests/src/hook.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { useEffect } from 'react';
import { appConfigurationApi, authApi, modelsApi, usersApi } from '@open-webui-react-native/shared/data-access/api';
import { analyticsService } from '@open-webui-react-native/shared/utils/analytics-service';

interface UseInitRequestsResult {
isLoading: boolean;
}

export const useInitRequests = (): UseInitRequestsResult => {
const { isLoading: isModelsLoading } = modelsApi.useGetModels();
const { isLoading: isProfileLoading } = authApi.useGetProfile();
const { data: profile, isLoading: isProfileLoading } = authApi.useGetProfile();
const { isLoading: isUserSettingsLoading } = usersApi.useGetUserSettings();
const { isLoading: isConfigurationLoading } = appConfigurationApi.useGetAppConfiguration();

const isLoading = isModelsLoading || isProfileLoading || isUserSettingsLoading || isConfigurationLoading;

useEffect(() => {
if (profile?.id) {
analyticsService.setUser(profile.id);
}
}, [profile]);

return { isLoading };
};
12 changes: 12 additions & 0 deletions libs/shared/utils/analytics-service/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
7 changes: 7 additions & 0 deletions libs/shared/utils/analytics-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# shared/utils/analytics-service

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

## Running unit tests

Run `nx test shared/utils/analytics-service` to execute the unit tests via [Jest](https://jestjs.io).
12 changes: 12 additions & 0 deletions libs/shared/utils/analytics-service/eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const nx = require('@nx/eslint-plugin');
const baseConfig = require('../../../../eslint.config.cjs');

module.exports = [
...baseConfig,
...nx.configs['flat/react'],
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
},
];
9 changes: 9 additions & 0 deletions libs/shared/utils/analytics-service/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "shared/utils/analytics-service",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/utils/analytics-service/src",
"projectType": "library",
"tags": ["app:shared", "scope:shared", "type:utils"],
"// targets": "to see all targets run: nx show project shared/utils/analytics-service --web",
"targets": {}
}
10 changes: 10 additions & 0 deletions libs/shared/utils/analytics-service/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Constants from 'expo-constants';
import { appEnv } from '@open-webui-react-native/shared/utils/app-env';

const amplitudeConfig = Constants.expoConfig?.extra?.amplitude;

export const amplitudeApiKey = appEnv.select({
development: amplitudeConfig?.apiKeyDev,
staging: amplitudeConfig?.apiKeyDev,
production: amplitudeConfig?.apiKeyProd,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum AnalyticsEvent {
NEW_CHAT_CREATED = 'new_chat_created',
NEW_FOLDER_CREATED = 'new_folder_created',
GENERATE_IMAGE_USED = 'generate_image_used',
DICTATION_MODE_USED = 'dictation_mode_used',
SUGGESTION_USED = 'suggestion_used',
MESSAGE_SENT = 'message_sent',
}
1 change: 1 addition & 0 deletions libs/shared/utils/analytics-service/src/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './analytics-event';
2 changes: 2 additions & 0 deletions libs/shared/utils/analytics-service/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './service';
export * from './enums';
39 changes: 39 additions & 0 deletions libs/shared/utils/analytics-service/src/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { init, setUserId, reset, Types, track } from '@amplitude/analytics-react-native';
import Constants from 'expo-constants';
import { amplitudeApiKey } from './config';

class AnalyticsService {
public init(): void {
init(amplitudeApiKey as string, undefined, {
trackingOptions: {
adid: false,
appSetId: false,
carrier: false,
deviceManufacturer: false,
deviceModel: true,
ipAddress: true,
idfv: false,
language: true,
osName: true,
osVersion: true,
platform: true,
},
logLevel: Types.LogLevel.Debug,
appVersion: Constants.expoConfig?.version,
});
}

public setUser(userId: string): void {
setUserId(userId);
}

public resetUser(): void {
reset();
}

public trackEvent(eventName: string, eventProperties?: Record<string, unknown>): void {
track(eventName, eventProperties);
}
}

export const analyticsService = new AnalyticsService();
17 changes: 17 additions & 0 deletions libs/shared/utils/analytics-service/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../../../../tsconfig.base.json"
}
19 changes: 19 additions & 0 deletions libs/shared/utils/analytics-service/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"types": ["node", "@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
Loading
Loading