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

Feat/pals #205

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
21 changes: 12 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@ import {KeyboardProvider} from 'react-native-keyboard-controller';
import {useTheme} from './src/hooks';
import {Theme} from './src/utils/types';

import {
SidebarContent,
ModelsHeaderRight,
ChatHeader,
HeaderLeft,
} from './src/components';
import {SidebarContent, ModelsHeaderRight, HeaderLeft} from './src/components';
import {
ChatScreen,
ModelsScreen,
SettingsScreen,
BenchmarkScreen,
} from './src/screens';
import {PalsScreen} from './src/screens/PalsScreen';

const Drawer = createDrawerNavigator();

Expand Down Expand Up @@ -62,7 +58,7 @@ const App = observer(() => {
name="Chat"
component={gestureHandlerRootHOC(ChatScreen)}
options={{
header: () => <ChatHeader />,
headerShown: false,
}}
/>
<Drawer.Screen
Expand All @@ -74,8 +70,8 @@ const App = observer(() => {
}}
/>
<Drawer.Screen
name="Settings"
component={gestureHandlerRootHOC(SettingsScreen)}
name="Pals (experimental)"
component={gestureHandlerRootHOC(PalsScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
Expand All @@ -87,6 +83,13 @@ const App = observer(() => {
headerStyle: styles.headerWithoutDivider,
}}
/>
<Drawer.Screen
name="Settings"
component={gestureHandlerRootHOC(SettingsScreen)}
options={{
headerStyle: styles.headerWithoutDivider,
}}
/>
</Drawer.Navigator>
</NavigationContainer>
</BottomSheetModalProvider>
Expand Down
50 changes: 50 additions & 0 deletions __mocks__/stores/palStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {makeAutoObservable} from 'mobx';
import {v4 as uuidv4} from 'uuid';
import {
AssistantFormData,
RoleplayFormData,
} from '../../src/components/PalsSheets/types';
import {Pal} from '../../src/store/PalStore';

class MockPalStore {
pals: Pal[] = [];

constructor() {
makeAutoObservable(this);
}

addPal = jest.fn((data: AssistantFormData | RoleplayFormData) => {
const newPal = {
id: uuidv4(),
...data,
} as Pal;
this.pals.push(newPal);
});

updatePal = jest.fn(
(id: string, data: Partial<AssistantFormData | RoleplayFormData>) => {
const palIndex = this.pals.findIndex(p => p.id === id);
if (palIndex !== -1) {
const currentPal = this.pals[palIndex];
this.pals[palIndex] = {
...currentPal,
...data,
palType: currentPal.palType,
} as Pal;
}
},
);

deletePal = jest.fn((id: string) => {
const palIndex = this.pals.findIndex(p => p.id === id);
if (palIndex !== -1) {
this.pals.splice(palIndex, 1);
}
});

getPals = jest.fn(() => {
return this.pals;
});
}

export const mockPalStore = new MockPalStore();
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ PODS:
- hermes-engine (0.76.3):
- hermes-engine/Pre-built (= 0.76.3)
- hermes-engine/Pre-built (0.76.3)
- llama-rn (0.4.8-2):
- llama-rn (0.5.2):
- React-Core
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
Expand Down Expand Up @@ -2264,7 +2264,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: ee5c2d2242816773fbf79e5b0563f5355ef1c315
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
hermes-engine: 0555a84ea495e8e3b4bde71b597cd87fbb382888
llama-rn: eb844b9cc4b240409b0411f16836416e567374f8
llama-rn: b6a8a6db8e1d0074aee1ac919f1a5de8e1a243c5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCT-Folly: bf5c0376ffe4dd2cf438dcf86db385df9fdce648
Expand Down
34 changes: 33 additions & 1 deletion jest/fixtures/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CompletionParams} from '@pocketpalai/llama.rn';
import {CompletionParams, NativeLlamaContext} from '@pocketpalai/llama.rn';

import {deviceInfo} from './device-info';

Expand All @@ -10,6 +10,38 @@ import {
ModelOrigin,
} from '../../src/utils/types';

export const mockContextModel: NativeLlamaContext['model'] = {
desc: '',
size: 0,
nEmbd: 0,
nParams: 0,
chatTemplates: {
llamaChat: false,
minja: {
default: false,
defaultCaps: {
tools: false,
toolCalls: false,
toolResponses: false,
systemRole: false,
parallelToolCalls: false,
toolCallId: false,
},
toolUse: false,
toolUseCaps: {
tools: false,
toolCalls: false,
toolResponses: false,
systemRole: false,
parallelToolCalls: false,
toolCallId: false,
},
},
},
isChatTemplateSupported: false,
metadata: {},
};

export const mockDefaultCompletionParams: CompletionParams = {
prompt: '',
n_predict: 400,
Expand Down
2 changes: 2 additions & 0 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {mockHFStore} from '../__mocks__/stores/hfStore';
import {mockModelStore} from '../__mocks__/stores/modelStore';
import {mockChatSessionStore} from '../__mocks__/stores/chatSessionStore';
import {benchmarkStore as mockBenchmarkStore} from '../__mocks__/stores/benchmarkStore';
import {mockPalStore} from '../__mocks__/stores/palStore';

jest.mock('@react-native-clipboard/clipboard', () => mockClipboard);

Expand All @@ -68,6 +69,7 @@ jest.mock('../src/store', () => {
chatSessionStore: mockChatSessionStore,
hfStore: mockHFStore,
benchmarkStore: mockBenchmarkStore,
palStore: mockPalStore,
};
});

Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"@dr.pogodin/react-native-fs": "^2.30.3",
"@flyerhq/react-native-link-preview": "^1.6.0",
"@gorhom/bottom-sheet": "^5.0.6",
"@pocketpalai/llama.rn": "^0.4.8-2",
"@hookform/resolvers": "^3.10.0",
"@pocketpalai/llama.rn": "^0.5.3-1",
"@react-native-async-storage/async-storage": "^2.1.0",
"@react-native-clipboard/clipboard": "^1.15.0",
"@react-native-community/blur": "^4.4.1",
Expand All @@ -47,6 +48,7 @@
"mobx-persist-store": "^1.1.5",
"mobx-react": "^9.1.1",
"react": "18.3.1",
"react-hook-form": "^7.54.2",
"react-native": "0.76.3",
"react-native-config": "^1.5.1",
"react-native-device-info": "^13.1.0",
Expand All @@ -68,7 +70,9 @@
"react-native-screens": "^4.4.0",
"react-native-svg": "^15.11.1",
"react-native-vector-icons": "^10.1.0",
"uuid": "^10.0.0"
"tinycolor2": "^1.6.0",
"uuid": "^10.0.0",
"zod": "^3.24.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/chevron-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/chevron-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/chevron-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ export {default as RefreshIcon} from './refresh.svg';
export {default as ReverseLeftIcon} from './reverse-left.svg';
export {default as CloseIcon} from './close.svg';
export {default as MenuIcon} from './menu.svg';
export {default as ChevronDownIcon} from './chevron-down.svg';
export {default as PlusIcon} from './plus.svg';
export {default as ChevronRightIcon} from './chevron-right.svg';
export {default as ChevronUpIcon} from './chevron-up.svg';
export {default as SendIcon} from './send.svg';
export {default as StopIcon} from './stop.svg';
3 changes: 3 additions & 0 deletions src/assets/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icons/stop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/pocketpal-dark-v2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions src/components/ChatEmptyPlaceholder/ChatEmptyPlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import {Image, View} from 'react-native';
import {Button, Text} from 'react-native-paper';
import {observer} from 'mobx-react';

import {useTheme} from '../../hooks';
import {createStyles} from './styles';
import {modelStore} from '../../store';

interface ChatEmptyPlaceholderProps {
onSelectModel: () => void;
bottomComponentHeight: number;
}

export const ChatEmptyPlaceholder = observer(
({onSelectModel, bottomComponentHeight}: ChatEmptyPlaceholderProps) => {
const theme = useTheme();
const styles = createStyles({theme});

const hasAvailableModels = modelStore.availableModels.length > 0;
const hasActiveModel = modelStore.activeModelId !== undefined;

const getContent = () => {
if (!hasAvailableModels) {
return {
title: 'No Models Available',
description: 'Download a model to start chatting with PocketPal',
buttonText: 'Download Model',
};
}

return {
title: 'Activate Model To Get Started',
description:
'Select the model and download it. After downloading, tap Load next to the model and start chatting.',
buttonText: 'Select Model',
};
};

const {title, description, buttonText} = getContent();

if (hasActiveModel) {
return null;
}
return (
<View
style={[styles.container, {marginBottom: bottomComponentHeight + 100}]}>
<Image
source={require('../../assets/pocketpal-dark-v2.png')}
style={styles.logo}
resizeMode="contain"
/>
<View>
<Text style={styles.title}>{title}</Text>
<Text style={styles.description}>{description}</Text>
</View>
<Button
mode="contained"
onPress={onSelectModel}
style={styles.button}
loading={modelStore.isContextLoading}
disabled={hasActiveModel}>
{modelStore.isContextLoading ? 'Loading...' : buttonText}
</Button>
</View>
);
},
);
1 change: 1 addition & 0 deletions src/components/ChatEmptyPlaceholder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ChatEmptyPlaceholder} from './ChatEmptyPlaceholder';
32 changes: 32 additions & 0 deletions src/components/ChatEmptyPlaceholder/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {StyleSheet} from 'react-native';
import {Theme} from '../../utils/types';

export const createStyles = ({theme}: {theme: Theme}) =>
StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 32,
gap: theme.spacing.default,
},
title: {
color: theme.colors.onSurface,
textAlign: 'center',
marginBottom: 8,
...theme.fonts.titleMedium,
},
description: {
color: theme.colors.onSurfaceVariant,
textAlign: 'center',
...theme.fonts.bodyMedium,
},
button: {
minWidth: 200,
},
logo: {
width: 112,
height: 112,
borderRadius: 30,
},
});
Loading
Loading