Skip to content

Commit e3c83b4

Browse files
authored
feat: new app (#188)
* new app ui * refactor(expo-example): clean up SetupAdapter and chat settings - Remove ProviderType, use model.provider for grouping instead - Move display info (label, accentColor, icon) into SetupAdapter - Add builtIn flag to indicate if model can be deleted - Implement pending settings for new chats before first message - Refactor addMessage to addMessages for atomic batch updates - Replace Map.groupBy with manual implementation for compatibility * feat(expo-example): add maxSteps setting for tool call iterations - Add maxSteps to ChatSettings type and defaults - Add UI control in settings sheet (1-20 range) - Use maxSteps in streamText stopWhen condition * feat: persist chats/models, hide mic when unavailable, handle unavailable provider - Persist custom models and chats across reloads using atomWithStorage with AsyncStorage - Hide microphone button when transcription is not available (iOS < 26) - Add isAvailable() method to AppleTranscriptionModel - Handle unavailable provider in ChatScreen with friendly message and model picker - Grey out unavailable models in Choose Model sheet - Add systemYellow color for warning states * fix: restore provider name labels in Choose Model sheet * fix: apply code review fixes for PR #188 ## Summary of Fixes Applied | Issue | Description | Status | |-------|-------------|--------| | #1 | Stale messages in handleSend | Not a bug - code already includes user message | | #2 | ModelLifecycleManager cleanup | Fixed - added cleanup on unmount | | #3 | Missing abort for streaming | Fixed - added AbortController + SDK abortSignal | | #4 | Audio listener accumulation | Fixed - recreate recorder each session | | #5 | Unnecessary `!` assertion | Skipped (style) | | #6 | Silent error swallowing | Skipped (per request) | | #7 | stopRecording not awaited | Fixed - made handlePress async | | #8 | State update after delay | Skipped | | #9 | setCurrentChatId inside setChats | Fixed - separated state updates | | #10 | Incorrect date calculation | Fixed - use calendar days | | #11 | No URL validation | Fixed - added `.gguf` check | | #12 | Date serialization | Fixed - store as ISO strings | | #13 | No duplicate check | Skipped | | #14 | stopPropagation non-standard | Fixed - removed | * refactor(expo-example): remove unnecessary useMemo/useCallback for React Compiler React Compiler will auto-memoize these patterns: - groupedChats derived state in CustomDrawerContent - scrollToBottom, handleDelete, renderRightActions callbacks - adaptersByProvider and enabledTools derived computations * up * docs: add PR screenshots * docs: add agents.md style guide (#189) * re-organize and clean-up * refactor: extract ChatScreen into smaller components - Split monolithic ChatScreen (~1300 lines) into focused components: ChatHeader, ChatMessages, ChatInputBar, ModelPickerSheet, SettingsSheet - Add providerStore for centralized adapter/model management - Change isAvailable() from async to sync using expo-file-system - Add Suspense boundaries with fallback components for progressive loading - Move download progress tracking to providerStore * final ui fixes, god damn
1 parent 823cb08 commit e3c83b4

43 files changed

Lines changed: 2898 additions & 1636 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/assets/choose-model.png

293 KB
Loading

.github/assets/new-chat.png

143 KB
Loading

.github/assets/tools-settings.png

219 KB
Loading

LICENSE

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
MIT License
22

3-
Copyright (c) 2024 szymonrybczak
3+
Copyright (c) 2024-2025 Szymon Rybczak
4+
Copyright (c) 2025-present Callstack
5+
46
Permission is hereby granted, free of charge, to any person obtaining a copy
57
of this software and associated documentation files (the "Software"), to deal
68
in the Software without restriction, including without limitation the rights

agents.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Agent Style Guide
2+
3+
## Commit Messages
4+
5+
- Conventional Commits format `type(scope): description`
6+
- Types: `feat`, `fix`, `refactor`, `chore`, `docs`
7+
- Scope optional, use package name when relevant (e.g., `expo-example`, `llama`)
8+
- Lowercase, no period, imperative mood
9+
- Example: `feat(expo-example): add maxSteps setting for tool iterations`
10+
11+
## Branch Naming
12+
13+
- Format: `type/kebab-case-description`
14+
- Types: `feat/`, `fix/`, `refactor/`, `chore/`, `docs/`, `ci/`
15+
- Example: `feat/tool-calling`, `fix/streaming-first-char-missing`
16+
17+
## Stacked PRs
18+
19+
- Create feature branch from `main`
20+
- Atomic commits that can be reviewed independently
21+
- Reference related PRs in commit messages when building on unmerged work
22+
- One logical change per commit
23+
24+
## Code Style
25+
26+
- Package manager: `bun` (not npm/yarn)
27+
- No semicolons
28+
- Single quotes
29+
- Imports auto-sorted (ESLint enforced)
30+
- Run `bun lint` before committing
31+
32+
## Project Structure
33+
34+
- Monorepo with `packages/` (libraries) and `apps/` (example apps)
35+
- Packages: `@react-native-ai/apple`, `@react-native-ai/llama`, `@react-native-ai/mlc`
36+
- Example app: Expo SDK 54 in `apps/expo-example/`
37+
38+
## Tech Stack
39+
40+
- React 19.1 with React Compiler (no manual `useMemo`/`useCallback` needed)
41+
- React Native 0.81
42+
- Expo SDK 54 with native modules
43+
- Vercel AI SDK v6 (`ai` package) for model abstraction
44+
- TypeScript strict mode
45+
46+
## UI & Styling
47+
48+
- `@expo/ui` for SwiftUI-bridged components (Button, Slider, ContextMenu)
49+
- `expo-symbols` for SF Symbols icons
50+
- `expo-blur` and `expo-glass-effect` for glass morphism
51+
- `PlatformColor` for iOS semantic colors (no hardcoded colors)
52+
- `StyleSheet.create` for styles (no inline objects)
53+
- `react-native-reanimated` for animations
54+
55+
## State Management
56+
57+
- `jotai` for global state with atoms
58+
- `atomWithStorage` + AsyncStorage for persistence
59+
- Custom hooks (e.g., `useChatStore`) to expose store API
60+
61+
## Patterns
62+
63+
- Adapter pattern for AI providers (`SetupAdapter<TModel>` interface)
64+
- Prefer composition over configuration
65+
- Keep components focused, extract hooks for logic
66+
- Use `zod` for runtime validation

apps/expo-example/babel.config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
module.exports = function (api) {
22
api.cache(true)
33
return {
4-
presets: [
5-
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
6-
'nativewind/babel',
7-
],
4+
presets: ['babel-preset-expo'],
85
}
96
}

apps/expo-example/metro.config.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Learn more https://docs.expo.io/guides/customizing-metro
22
const { getDefaultConfig } = require('expo/metro-config')
3-
const { withNativeWind } = require('nativewind/metro')
43
const {
54
wrapWithAudioAPIMetroConfig,
65
} = require('react-native-audio-api/metro-config')
@@ -10,6 +9,4 @@ const config = getDefaultConfig(__dirname)
109
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
1110
config.resolver.disableHierarchicalLookup = true
1211

13-
module.exports = wrapWithAudioAPIMetroConfig(
14-
withNativeWind(config, { input: './src/global.css' })
15-
)
12+
module.exports = wrapWithAudioAPIMetroConfig(config)

apps/expo-example/nativewind-env.d.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

apps/expo-example/package.json

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,47 @@
1212
"dependencies": {
1313
"@azure/core-asynciterator-polyfill": "^1.0.2",
1414
"@bottom-tabs/react-navigation": "^0.10.2",
15+
"@expo/ui": "~0.2.0-beta.9",
16+
"@lodev09/react-native-true-sheet": "^3.8.1",
1517
"@react-native-ai/apple": "workspace:*",
1618
"@react-native-ai/llama": "workspace:*",
17-
"@react-native-picker/picker": "^2.11.4",
18-
"@react-native-vector-icons/get-image": "^12.3.0",
19-
"@react-native-vector-icons/material-icons": "^12.4.0",
19+
"@react-native-async-storage/async-storage": "^2.2.0",
20+
"@react-native-picker/picker": "2.11.1",
2021
"@react-navigation/bottom-tabs": "^7.9.0",
22+
"@react-navigation/drawer": "^7.7.13",
2123
"@react-navigation/native": "^7.1.16",
2224
"@react-navigation/native-stack": "^7.3.25",
2325
"@ungap/structured-clone": "^1.3.0",
2426
"ai": "^6.0.0",
25-
"expo": "^54.0.8",
26-
"expo-battery": "~10.0.7",
27-
"expo-build-properties": "~1.0.8",
28-
"expo-calendar": "~15.0.7",
29-
"expo-clipboard": "~8.0.7",
30-
"expo-document-picker": "~14.0.7",
31-
"expo-status-bar": "~3.0.8",
27+
"expo": "~54.0.33",
28+
"expo-battery": "~10.0.8",
29+
"expo-blur": "~15.0.8",
30+
"expo-build-properties": "~1.0.10",
31+
"expo-calendar": "~15.0.8",
32+
"expo-clipboard": "~8.0.8",
33+
"expo-document-picker": "~14.0.8",
34+
"expo-file-system": "~19.0.21",
35+
"expo-glass-effect": "~0.1.8",
36+
"expo-status-bar": "~3.0.9",
37+
"expo-symbols": "~1.0.8",
38+
"jotai": "^2.12.2",
3239
"llama.rn": "^0.10.1",
33-
"nativewind": "^4.1.23",
3440
"react": "19.1.0",
35-
"react-native": "0.81.4",
41+
"react-native": "0.81.5",
3642
"react-native-audio-api": "^0.7.1",
3743
"react-native-bottom-tabs": "^0.11.0",
44+
"react-native-gesture-handler": "~2.28.0",
3845
"react-native-keyboard-controller": "1.18.5",
3946
"react-native-reanimated": "~4.1.0",
4047
"react-native-safe-area-context": "~5.6.0",
4148
"react-native-screens": "~4.16.0",
42-
"react-native-worklets": "^0.6.1",
49+
"react-native-worklets": "0.5.1",
4350
"web-streams-polyfill": "^4.1.0",
4451
"zod": "^4.0.0"
4552
},
4653
"devDependencies": {
4754
"@babel/core": "^7.20.0",
48-
"@types/react": "~19.1.0",
49-
"prettier-plugin-tailwindcss": "^0.5.11",
50-
"tailwindcss": "^3.4.17"
55+
"@types/react": "~19.1.0"
5156
},
5257
"private": true
5358
}

0 commit comments

Comments
 (0)