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
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ This is a monorepo with the following packages:
## Quick Start

1. **Clone and install dependencies:**

```bash
git clone https://github.com/callstackincubator/ai.git
cd ai
bun install
```

2. **Run quality checks:**

```bash
bun run typecheck
bun run lint
Expand Down Expand Up @@ -54,6 +56,7 @@ This is a monorepo with the following packages:
### Commit Messages

Follow [Conventional Commits](https://www.conventionalcommits.org/):

- `feat:` new features
- `fix:` bug fixes
- `docs:` documentation changes
Expand All @@ -78,7 +81,7 @@ Follow [Conventional Commits](https://www.conventionalcommits.org/):

Build all packages

```bash
```bash
bun run --filter='@react-native-ai/*' prepare
```

Expand Down Expand Up @@ -107,6 +110,12 @@ bun run android
- The Apple Intelligence package is a Turbo Module - work directly from the integrated Xcode project
- Native code is in `packages/apple-llm/ios/` but best developed through the example app

## Required Native Builds

The demos require the MLC package to have built the MLC-LLM engine libraries for Android and iOS. We publish the package with prebuilt binaries and this monorepo contains a postinstall script that fetches them on initial setup so you don't need to go through the prerequisites for the MLC package yourself. Set `SKIP_MLC_PREBUILT_FETCH=1` to opt out, or run
`bun run fetch-prebuilt` in `packages/mlc` package to fetch manually. Rebuild from source with
`build:runtime:ios` / `build:runtime:android` when needed.

## Need Help?

- Check existing [issues](https://github.com/callstackincubator/ai/issues)
Expand Down
28 changes: 18 additions & 10 deletions apps/expo-example/src/screens/ChatScreen/ChatMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ type ChatMessagesProps = {
selectedModelLabel: string
onSend: (message: string) => void
isGenerating: boolean
genUiEnabled: boolean
}

export function ChatMessages({
messages,
selectedModelLabel,
onSend,
isGenerating,
genUiEnabled,
}: ChatMessagesProps) {
const ref = useRef<ScrollView>(null)

Expand Down Expand Up @@ -82,7 +84,11 @@ export function ChatMessages({
{messages.length === 0 ? (
<ChatEmptyState
title="What can I help you with?"
subtitle={`Start a conversation with ${selectedModelLabel}. Ask questions, ask it to add new UI elements to the screen, get creative, or explore ideas.`}
subtitle={
genUiEnabled
? `Start a conversation with ${selectedModelLabel}. Ask questions, ask it to add new UI elements to the screen, get creative, or explore ideas.`
: `Start a conversation with ${selectedModelLabel}. Ask questions, get creative, or explore ideas.`
}
/>
) : (
<View style={styles.messageList}>
Expand All @@ -96,15 +102,17 @@ export function ChatMessages({
/>
))}

<GenerativeUIView
spec={
currentChatId
? getChatUISpecFromChats(chats, currentChatId)
: null
}
loading={isGenerating}
showCollapsibleJSON
/>
{genUiEnabled && (
<GenerativeUIView
spec={
currentChatId
? getChatUISpecFromChats(chats, currentChatId)
: null
}
loading={isGenerating}
showCollapsibleJSON
/>
)}
</View>
)}
</ScrollView>
Expand Down
38 changes: 37 additions & 1 deletion apps/expo-example/src/screens/ChatScreen/SettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Pressable,
ScrollView,
StyleSheet,
Switch,
Text,
View,
} from 'react-native'
Expand All @@ -22,7 +23,7 @@ type SettingsSheetProps = {

export function SettingsSheet({ ref }: SettingsSheetProps) {
const { chatSettings, toggleTool, updateChatSettings } = useChatStore()
const { temperature, maxSteps, enabledToolIds } = chatSettings
const { temperature, maxSteps, enabledToolIds, genUiEnabled } = chatSettings

return (
<TrueSheet ref={ref} scrollable style={styles.sheetContainerSlidingWrapper}>
Expand Down Expand Up @@ -80,6 +81,26 @@ export function SettingsSheet({ ref }: SettingsSheetProps) {
})}
</View>

<View style={styles.settingsSection}>
<Text style={styles.sectionLabel}>Features</Text>
<View style={styles.settingsCard}>
<View style={styles.settingRowToggle}>
<View style={styles.settingToggleText}>
<Text style={styles.settingTitle}>JSON UI</Text>
<Text style={styles.settingDescription}>
Let the model build on-screen UI and show the spec JSON
</Text>
</View>
<Switch
value={genUiEnabled}
onValueChange={(value) =>
updateChatSettings({ genUiEnabled: value })
}
/>
</View>
</View>
</View>

<View style={styles.settingsSection}>
<Text style={styles.sectionLabel}>Model Settings</Text>
<View style={styles.settingsCard}>
Expand Down Expand Up @@ -223,6 +244,21 @@ const styles = StyleSheet.create({
backgroundColor: colors.secondarySystemBackground as any,
overflow: 'hidden',
},
settingRowToggle: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingHorizontal: 16,
paddingVertical: 12,
},
settingToggleText: {
flex: 1,
},
settingDescription: {
marginTop: 2,
fontSize: 13,
color: colors.secondaryLabel as any,
},
settingRowSlider: {
paddingHorizontal: 16,
paddingVertical: 12,
Expand Down
30 changes: 18 additions & 12 deletions apps/expo-example/src/screens/ChatScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TrueSheet } from '@lodev09/react-native-true-sheet'
import { type createAppleProvider } from '@react-native-ai/apple'
import type { TrueSheet } from '@lodev09/react-native-true-sheet'
import type { createAppleProvider } from '@react-native-ai/apple'
import {
buildGenUISystemPrompt,
createGenUITools,
Expand Down Expand Up @@ -48,6 +48,7 @@ export default function ChatScreen() {
temperature,
maxSteps,
enabledToolIds,
genUiEnabled,
} = chatSettings

const [isGenerating, setIsGenerating] = useState(false)
Expand Down Expand Up @@ -80,12 +81,14 @@ export default function ChatScreen() {
setIsGenerating(true)

try {
const genUITools = createGenUITools({
contextId: chatId,
getSpec,
updateSpec: updateChatUISpec,
toolWrapper: withToolProxy as any,
})
const genUITools = genUiEnabled
? createGenUITools({
contextId: chatId,
getSpec,
updateSpec: updateChatUISpec,
toolWrapper: withToolProxy as any,
})
: {}
const tools = {
...Object.fromEntries(
enabledToolIds
Expand Down Expand Up @@ -120,10 +123,12 @@ export default function ChatScreen() {
stopWhen: stepCountIs(maxSteps),
abortSignal: signal,
experimental_telemetry: getAiSdkTelemetry('chat-screen-stream-text'),
system: buildGenUISystemPrompt({
additionalInstructions:
'If the user asks, tell who you are (assistant) and what is this (Callstack AI demo app).',
}),
system: genUiEnabled
? buildGenUISystemPrompt({
additionalInstructions:
'If the user asks, tell who you are (assistant) and what is this (Callstack AI demo app).',
})
: 'You are a helpful assistant. If the user asks, tell who you are (assistant) and what is this (Callstack AI demo app).',
})

let accumulated = ''
Expand Down Expand Up @@ -193,6 +198,7 @@ export default function ChatScreen() {
onSend={handleSend}
isGenerating={isGenerating}
selectedModelLabel={selectedAdapter.display.label}
genUiEnabled={genUiEnabled}
/>
)}
</View>
Expand Down
7 changes: 6 additions & 1 deletion apps/expo-example/src/store/chatStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type ChatSettings = {
temperature: number
maxSteps: number
enabledToolIds: string[]
genUiEnabled: boolean
}

/** Single element in the generative UI tree (id is the key in elements). */
Expand Down Expand Up @@ -116,6 +117,7 @@ const DEFAULT_SETTINGS: ChatSettings = {
temperature: 0.7,
maxSteps: 5,
enabledToolIds: Object.keys(toolDefinitions),
genUiEnabled: false,
}

const chatsAtom = atomWithStorage<Chat[]>('chats', [], storage)
Expand Down Expand Up @@ -283,7 +285,10 @@ export function useChatStore() {
})
}

const chatSettings = currentChat?.settings ?? pendingSettings
const chatSettings: ChatSettings = {
...DEFAULT_SETTINGS,
...(currentChat?.settings ?? pendingSettings),
}

const toggleTool = (toolId: string) => {
const tools = chatSettings.enabledToolIds
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"typecheck": "bun run --filter='*' typecheck",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"release": "release-it",
"build": "bun run --filter='@react-native-ai/*' prepare"
"build": "bun run --filter='@react-native-ai/*' prepare",
"postinstall": "bash packages/mlc/scripts/fetch-prebuilt.sh"
},
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/mlc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ await model.prepare()
// Generate
const { text } = await generateText({
model,
prompt: 'What is the meaning of life?'
prompt: 'What is the meaning of life?',
})
```

Expand Down
1 change: 1 addition & 0 deletions packages/mlc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"clean": "del-cli lib",
"typecheck": "tsc --noEmit",
"prepare": "bob build",
"fetch-prebuilt": "./scripts/fetch-prebuilt.sh",
"build:runtime:ios": "./scripts/build-runtime.sh --platform ios",
"build:runtime:android": "./scripts/build-runtime.sh --platform android"
},
Expand Down
75 changes: 75 additions & 0 deletions packages/mlc/scripts/fetch-prebuilt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env bash

# Fetches MLC prebuilt native artifacts from the published @react-native-ai/mlc
# npm package when local prebuilt/ios or prebuilt/android dirs are missing.

set -euo pipefail

if [[ "${SKIP_MLC_PREBUILT_FETCH:-}" == "1" ]]; then
exit 0
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGE_DIR="$(dirname "$SCRIPT_DIR")"
PREBUILT_DIR="$PACKAGE_DIR/prebuilt"

need_ios=false
need_android=false

if [[ ! -d "$PREBUILT_DIR/ios" ]]; then
need_ios=true
fi

if [[ ! -d "$PREBUILT_DIR/android" ]]; then
need_android=true
fi

if [[ "$need_ios" == false && "$need_android" == false ]]; then
exit 0
fi

if ! command -v npm >/dev/null 2>&1; then
echo "fetch-prebuilt: npm is required to download MLC prebuilt artifacts" >&2
exit 1
fi

VERSION="$(node -pe "require('${PACKAGE_DIR}/package.json').version")"
PKG="@react-native-ai/mlc@${VERSION}"

echo "MLC prebuilt artifacts missing — fetching ${PKG} from npm..."

TMPDIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMPDIR"
}
trap cleanup EXIT

(
cd "$TMPDIR"
npm pack "$PKG" --silent >/dev/null
)

TARBALL="$(ls "$TMPDIR"/react-native-ai-mlc-*.tgz)"
tar -xzf "$TARBALL" -C "$TMPDIR"

mkdir -p "$PREBUILT_DIR"

if [[ "$need_ios" == true ]]; then
if [[ ! -d "$TMPDIR/package/prebuilt/ios" ]]; then
echo "fetch-prebuilt: ios prebuilt directory not found in ${PKG}" >&2
exit 1
fi
cp -R "$TMPDIR/package/prebuilt/ios" "$PREBUILT_DIR/"
echo " installed prebuilt/ios"
fi

if [[ "$need_android" == true ]]; then
if [[ ! -d "$TMPDIR/package/prebuilt/android" ]]; then
echo "fetch-prebuilt: android prebuilt directory not found in ${PKG}" >&2
exit 1
fi
cp -R "$TMPDIR/package/prebuilt/android" "$PREBUILT_DIR/"
echo " installed prebuilt/android"
fi

echo "MLC prebuilt artifacts ready at ${PREBUILT_DIR}"
Loading