Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5f65e57
feat: support newest MLC on iOS
artus9033 Feb 6, 2026
2698748
fix: improve demo app UI on Android (#191)
artus9033 Feb 5, 2026
bb26a8c
fix: chat UI not beginning a new chat in a race condition
artus9033 Feb 9, 2026
70f38c5
feat!: deintegrate constructor tool passing in llama and apple backends
artus9033 Feb 10, 2026
eb418f3
feat: basic generative UI
artus9033 Feb 10, 2026
89bb9b2
fix: ignore nulls in the apple llm provider
artus9033 Feb 10, 2026
27591b5
feat: improved local gen UI by adding guidelines
artus9033 Feb 10, 2026
2a25e0b
feat: track tool calls on the UI
artus9033 Feb 10, 2026
f15fcac
feat: sanitize adding children to root as fallback if targeted parent…
artus9033 Feb 10, 2026
b000bab
Merge branch 'main' into feat/generative-ui
artus9033 Feb 10, 2026
dda2816
feat: add TextInput, remove Container
artus9033 Feb 10, 2026
37af0c1
feat: extract generative UI package
artus9033 Feb 10, 2026
e50492c
fix: race cases that lose data if tool calls happen in a row
artus9033 Feb 10, 2026
68d9d98
feat: show tool results
artus9033 Feb 10, 2026
3a2f728
fix: race condition due to buffered / async React state updates in ch…
artus9033 Feb 10, 2026
c40da1c
docs: document README
artus9033 Feb 10, 2026
570dd53
feat: simplify reorderUINodes
artus9033 Feb 10, 2026
50d0402
feat: properly organize tool proxy to report args & results
artus9033 Feb 11, 2026
1ad8e9e
feat: show full UI JSON
artus9033 Feb 11, 2026
4f7ec18
feat: restore apple adapter and constructor tools arg
artus9033 Feb 11, 2026
7a1cb65
docs: update apple docs
artus9033 Feb 11, 2026
28b068b
feat: customizable rendering of generative UI nodes
artus9033 Feb 11, 2026
db15d23
chore: sort deps
artus9033 Feb 11, 2026
60c045b
chore: rename package to @react-native-ai/json-ui
artus9033 Feb 11, 2026
7a483bf
docs: add docs
artus9033 Feb 11, 2026
e9c7952
chore: comply with linter
artus9033 Feb 11, 2026
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
1 change: 1 addition & 0 deletions apps/expo-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"expo-symbols": "~1.0.8",
"expo-system-ui": "^6.0.9",
"jotai": "^2.12.2",
"@react-native-ai/json-ui": "workspace:*",
"llama.rn": "^0.10.1",
"react": "19.1.0",
"react-native": "0.81.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { LanguageModelV3, SpeechModelV3 } from '@ai-sdk/provider'
import type { Tool } from '@ai-sdk/provider-utils'
import { apple, createAppleProvider } from '@react-native-ai/apple'
import { ToolSet } from 'ai'

import type { SetupAdapter } from '../../config/providers.common'

export const createAppleLanguageSetupAdapter = (
tools: ToolSet = {}
tools: Record<string, Tool>
): SetupAdapter<LanguageModelV3> => {
const apple = createAppleProvider({
availableTools: tools,
})
const apple = createAppleProvider({ availableTools: tools })
const model = apple.languageModel()
return {
model,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { LanguageModelV3 } from '@ai-sdk/provider'
import { llama } from '@react-native-ai/llama'
import { ToolSet } from 'ai'

import {
downloadModel,
Expand All @@ -11,8 +10,7 @@ import type { Availability, SetupAdapter } from '../../config/providers.common'
import { isLlamaModelDownloaded } from '../../utils/llamaStorageUtils'

export const createLlamaLanguageSetupAdapter = (
hfModelId: string,
tools: ToolSet = {}
hfModelId: string
): SetupAdapter<LanguageModelV3> => {
const modelPath = getModelPath(hfModelId)
const model = llama.languageModel(modelPath, {
Expand Down
4 changes: 1 addition & 3 deletions apps/expo-example/src/config/providers.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { LanguageModelV3, SpeechModelV3 } from '@ai-sdk/provider'
import { createLlamaLanguageSetupAdapter } from '../components/adapters/llamaModelSetupAdapter'
import { createLlamaSpeechSetupAdapter } from '../components/adapters/llamaSpeechSetupAdapter'
import { createMLCLanguageSetupAdapter } from '../components/adapters/mlcModelSetupAdapter'
import { toolDefinitions } from '../tools'

export type Availability = 'yes' | 'no' | 'availableForDownload'

Expand Down Expand Up @@ -42,8 +41,7 @@ export const commonLanguageAdapters: SetupAdapter<LanguageModelV3>[] = [
'ggml-org/SmolLM3-3B-GGUF/SmolLM3-Q4_K_M.gguf'
),
createLlamaLanguageSetupAdapter(
'Qwen/Qwen2.5-3B-Instruct-GGUF/qwen2.5-3b-instruct-q3_k_m.gguf',
toolDefinitions
'Qwen/Qwen2.5-3B-Instruct-GGUF/qwen2.5-3b-instruct-q3_k_m.gguf'
),
createMLCLanguageSetupAdapter('Llama-3.2-1B-Instruct'),
createMLCLanguageSetupAdapter('Llama-3.2-3B-Instruct'),
Expand Down
132 changes: 117 additions & 15 deletions apps/expo-example/src/screens/ChatScreen/ChatMessageBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,104 @@
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { Pressable, StyleSheet, Text, View } from 'react-native'

import { colors } from '../../theme/colors'

type ChatMessageBubbleProps = {
content: string
isUser: boolean
messageType?: 'text' | 'toolExecution'
toolExecution?: {
toolName: string
payload: unknown
result?: unknown
}
}

export function ChatMessageBubble({ content, isUser }: ChatMessageBubbleProps) {
export function ChatMessageBubble({
content,
isUser,
messageType = 'text',
toolExecution,
}: ChatMessageBubbleProps) {
const [expanded, setExpanded] = React.useState(false)
const isToolHint = messageType === 'toolExecution'
const toolLabel = toolExecution
? `Tool executed: ${toolExecution.toolName}`
: content
const payload = toolExecution?.payload
? JSON.stringify(toolExecution.payload, null, 2)
: ''
const result =
toolExecution?.result !== undefined
? JSON.stringify(toolExecution.result, null, 2)
: ''
const hasPayload = payload !== '{}' && payload.length > 0
const hasResult = result.length > 0
const hasDetails = hasPayload || hasResult

return (
<View
style={[
styles.messageRow,
isUser ? styles.messageRowUser : styles.messageRowAssistant,
isToolHint
? styles.messageRowAssistant
: isUser
? styles.messageRowUser
: styles.messageRowAssistant,
]}
>
<View
<Pressable
disabled={!isToolHint}
onPress={() => hasDetails && setExpanded((prev) => !prev)}
style={[
styles.messageBubble,
isUser ? styles.messageBubbleUser : styles.messageBubbleAssistant,
isToolHint
? styles.messageBubbleToolHint
: isUser
? styles.messageBubbleUser
: styles.messageBubbleAssistant,
]}
>
<Text
selectable
style={[
styles.messageText,
isUser ? styles.messageTextUser : styles.messageTextAssistant,
]}
>
{content}
</Text>
</View>
{isToolHint ? (
<View style={styles.toolHintHeader}>
<Text
selectable
style={[styles.messageText, styles.messageTextToolHint]}
>
{toolLabel}
</Text>
{hasDetails && (
<Text style={styles.expandIcon}>{expanded ? '[-]' : '[+]'}</Text>
)}
</View>
) : (
<Text
selectable
style={[
styles.messageText,
isUser ? styles.messageTextUser : styles.messageTextAssistant,
]}
>
{content}
</Text>
)}
{isToolHint && expanded && hasPayload ? (
<View style={styles.detailBlock}>
<Text style={styles.detailLabel}>args</Text>
<Text selectable style={styles.payloadText}>
{payload}
</Text>
</View>
) : null}
{isToolHint && expanded && hasResult ? (
<View style={styles.detailBlock}>
<Text style={styles.detailLabel}>result</Text>
<Text selectable style={styles.payloadText}>
{result}
</Text>
</View>
) : null}
</Pressable>
</View>
)
}
Expand Down Expand Up @@ -59,6 +126,10 @@ const styles = StyleSheet.create({
messageBubbleAssistant: {
backgroundColor: colors.secondarySystemBackground as any,
},
messageBubbleToolHint: {
backgroundColor: colors.tertiarySystemFill as any,
borderRadius: 14,
},
messageText: {
fontSize: 16,
lineHeight: 22,
Expand All @@ -69,4 +140,35 @@ const styles = StyleSheet.create({
messageTextAssistant: {
color: colors.label as any,
},
messageTextToolHint: {
color: colors.secondaryLabel as any,
fontSize: 14,
lineHeight: 20,
},
toolHintHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 8,
},
expandIcon: {
color: colors.tertiaryLabel as any,
fontSize: 12,
},
payloadText: {
marginTop: 8,
fontFamily: 'Menlo',
fontSize: 12,
lineHeight: 18,
color: colors.tertiaryLabel as any,
},
detailBlock: {
marginTop: 8,
},
detailLabel: {
fontSize: 11,
color: colors.tertiaryLabel as any,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
})
24 changes: 23 additions & 1 deletion apps/expo-example/src/screens/ChatScreen/ChatMessages.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GenerativeUIView } from '@react-native-ai/json-ui'
import React, { useEffect, useRef } from 'react'
import { ScrollView, StyleSheet, View } from 'react-native'
import { useKeyboardHandler } from 'react-native-keyboard-controller'
Expand All @@ -8,6 +9,7 @@ import Reanimated, {
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { scheduleOnRN } from 'react-native-worklets'

import { getChatUISpecFromChats, useChatStore } from '../../store/chatStore'
import { ChatEmptyState } from './ChatEmptyState'
import { ChatInputBar } from './ChatInputBar'
import { ChatMessageBubble } from './ChatMessageBubble'
Expand All @@ -16,6 +18,12 @@ type ChatMessage = {
id: string
role: string
content: string
type?: 'text' | 'toolExecution'
toolExecution?: {
toolName: string
payload: unknown
result?: unknown
}
}

type ChatMessagesProps = {
Expand Down Expand Up @@ -62,6 +70,8 @@ export function ChatMessages({

useEffect(scrollToBottom, [messages.length])

const { chats, currentChatId } = useChatStore()

return (
<>
<ScrollView
Expand All @@ -72,7 +82,7 @@ export function ChatMessages({
{messages.length === 0 ? (
<ChatEmptyState
title="What can I help you with?"
subtitle={`Start a conversation with ${selectedModelLabel}. Ask questions, get creative, or explore ideas.`}
subtitle={`Start a conversation with ${selectedModelLabel}. Ask questions, ask it to add new UI elements to the screen, get creative, or explore ideas.`}
/>
) : (
<View style={styles.messageList}>
Expand All @@ -81,8 +91,20 @@ export function ChatMessages({
key={message.id}
content={message.content}
isUser={message.role === 'user'}
messageType={message.type}
toolExecution={message.toolExecution}
/>
))}

<GenerativeUIView
spec={
currentChatId
? getChatUISpecFromChats(chats, currentChatId)
: null
}
loading={isGenerating}
showCollapsibleJSON
/>
</View>
)}
</ScrollView>
Expand Down
Loading