Skip to content

Commit 7d8fce8

Browse files
committed
feat: react-native-ai-kit initial implementation
1 parent febb03b commit 7d8fce8

18 files changed

Lines changed: 15055 additions & 10 deletions

example/src/App.tsx

Lines changed: 302 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,316 @@
1-
import { Text, View, StyleSheet } from 'react-native';
2-
import { multiply } from 'react-native-ai-kit';
1+
import { useRef, useState } from 'react';
2+
import {
3+
KeyboardAvoidingView,
4+
Platform,
5+
View,
6+
TextInput,
7+
TouchableOpacity,
8+
Text,
9+
ActivityIndicator,
10+
StyleSheet,
11+
} from 'react-native';
12+
import { useChat, ChatList, ChatBubble } from 'react-native-ai-kit';
313

4-
const result = multiply(3, 7);
14+
const API_URL = 'https://api.openai.com/v1/chat/completions';
15+
const API_KEY = '';
516

617
export default function App() {
18+
const [input, setInput] = useState('');
19+
const [apiKey, setApiKey] = useState(API_KEY);
20+
const cumulativeTokens = useRef(0);
21+
const lastTotalRef = useRef(0);
22+
23+
const { messages, sendMessage, isStreaming, tokenUsage, error, stop } =
24+
useChat({
25+
apiUrl: API_URL,
26+
systemPrompt: 'You are a helpful assistant. Keep responses concise.',
27+
headers: {
28+
'Content-Type': 'application/json',
29+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
30+
},
31+
});
32+
33+
// Accumulate totalTokens from each completed response
34+
if (tokenUsage && tokenUsage.totalTokens !== lastTotalRef.current) {
35+
cumulativeTokens.current += tokenUsage.totalTokens;
36+
lastTotalRef.current = tokenUsage.totalTokens;
37+
}
38+
39+
const handleSend = () => {
40+
const text = input.trim();
41+
if (!text || isStreaming) return;
42+
setInput('');
43+
sendMessage(text);
44+
};
45+
746
return (
8-
<View style={styles.container}>
9-
<Text>Result: {result}</Text>
10-
</View>
47+
<KeyboardAvoidingView
48+
style={styles.container}
49+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
50+
>
51+
{/* Header */}
52+
<View style={styles.header}>
53+
<Text style={styles.headerTitle}>react-native-ai-kit</Text>
54+
{!apiKey && (
55+
<TextInput
56+
style={styles.apiKeyInput}
57+
placeholder="Enter OpenAI API key..."
58+
value={apiKey}
59+
onChangeText={setApiKey}
60+
autoCapitalize="none"
61+
autoCorrect={false}
62+
secureTextEntry
63+
/>
64+
)}
65+
</View>
66+
67+
{/* Token stats bar */}
68+
<View style={styles.statsBar}>
69+
<View style={styles.statItem}>
70+
<Text style={styles.statLabel}>Req</Text>
71+
<Text style={styles.statValue}>
72+
{messages.filter((m) => m.role === 'user').length}
73+
</Text>
74+
</View>
75+
<View style={styles.statDivider} />
76+
<View style={styles.statItem}>
77+
<Text style={styles.statLabel}>Resp</Text>
78+
<Text style={styles.statValue}>
79+
{messages.filter((m) => m.role === 'assistant').length}
80+
</Text>
81+
</View>
82+
<View style={styles.statDivider} />
83+
<View style={styles.statItem}>
84+
<Text style={styles.statLabel}>Prompt</Text>
85+
<Text style={styles.statValue}>
86+
{tokenUsage?.promptTokens ?? '—'}
87+
</Text>
88+
</View>
89+
<View style={styles.statDivider} />
90+
<View style={styles.statItem}>
91+
<Text style={styles.statLabel}>Complet.</Text>
92+
<Text style={styles.statValue}>
93+
{tokenUsage?.completionTokens ?? '—'}
94+
</Text>
95+
</View>
96+
<View style={styles.statDivider} />
97+
<View style={styles.statItem}>
98+
<Text style={styles.statLabel}>Total</Text>
99+
<Text style={[styles.statValue, styles.statTotal]}>
100+
{tokenUsage?.totalTokens ?? '—'}
101+
</Text>
102+
</View>
103+
<View style={styles.statDivider} />
104+
<View style={styles.statItem}>
105+
<Text style={styles.statLabel}>All</Text>
106+
<Text style={[styles.statValue, styles.statAll]}>
107+
{cumulativeTokens.current || '—'}
108+
</Text>
109+
</View>
110+
</View>
111+
112+
{/* Messages */}
113+
<ChatList
114+
messages={messages}
115+
style={styles.chatList}
116+
renderStreamingIndicator={() =>
117+
isStreaming && !messages[messages.length - 1]?.content ? (
118+
<View style={styles.streamingRow}>
119+
<Text style={styles.streamingText}>AI is typing</Text>
120+
<ActivityIndicator size="small" color="#007AFF" />
121+
</View>
122+
) : null
123+
}
124+
renderMessage={(message) =>
125+
message.content ? (
126+
<ChatBubble
127+
message={message}
128+
variant={message.role === 'user' ? 'user' : 'assistant'}
129+
/>
130+
) : null
131+
}
132+
/>
133+
134+
{/* Error */}
135+
{error && (
136+
<View style={styles.errorRow}>
137+
<Text style={styles.errorText}>{error.message}</Text>
138+
</View>
139+
)}
140+
141+
{/* Input */}
142+
<View style={styles.inputRow}>
143+
<TextInput
144+
style={styles.input}
145+
placeholder="Type a message..."
146+
value={input}
147+
onChangeText={setInput}
148+
editable={!isStreaming}
149+
multiline
150+
/>
151+
{isStreaming ? (
152+
<TouchableOpacity style={styles.stopButton} onPress={stop}>
153+
<Text style={styles.stopButtonText}>Stop</Text>
154+
</TouchableOpacity>
155+
) : (
156+
<TouchableOpacity
157+
style={styles.sendButton}
158+
onPress={handleSend}
159+
disabled={!input.trim()}
160+
>
161+
<Text
162+
style={[
163+
styles.sendButtonText,
164+
!input.trim() && styles.sendButtonDisabled,
165+
]}
166+
>
167+
Send
168+
</Text>
169+
</TouchableOpacity>
170+
)}
171+
</View>
172+
</KeyboardAvoidingView>
11173
);
12174
}
13175

14176
const styles = StyleSheet.create({
15177
container: {
16178
flex: 1,
179+
paddingTop: Platform.OS === 'ios' ? 50 : 20,
180+
backgroundColor: '#FFFFFF',
181+
},
182+
header: {
183+
paddingHorizontal: 16,
184+
paddingTop: 4,
185+
paddingBottom: 8,
186+
borderBottomWidth: StyleSheet.hairlineWidth,
187+
borderBottomColor: '#C7C7CC',
188+
},
189+
headerTitle: {
190+
fontSize: 20,
191+
fontWeight: '700',
192+
color: '#000000',
193+
},
194+
apiKeyInput: {
195+
marginTop: 8,
196+
height: 36,
197+
borderRadius: 8,
198+
borderWidth: 1,
199+
borderColor: '#C7C7CC',
200+
paddingHorizontal: 10,
201+
fontSize: 14,
202+
},
203+
statsBar: {
204+
flexDirection: 'row',
205+
alignItems: 'center',
206+
justifyContent: 'center',
207+
paddingHorizontal: 16,
208+
paddingVertical: 6,
209+
backgroundColor: '#F9F9F9',
210+
borderBottomWidth: StyleSheet.hairlineWidth,
211+
borderBottomColor: '#E5E5EA',
212+
},
213+
statItem: {
214+
alignItems: 'center',
215+
paddingHorizontal: 10,
216+
},
217+
statLabel: {
218+
fontSize: 10,
219+
color: '#8E8E93',
220+
textTransform: 'uppercase',
221+
fontWeight: '600',
222+
},
223+
statValue: {
224+
fontSize: 16,
225+
fontWeight: '700',
226+
color: '#000000',
227+
},
228+
statTotal: {
229+
color: '#007AFF',
230+
},
231+
statAll: {
232+
color: '#34C759',
233+
},
234+
statDivider: {
235+
width: 1,
236+
height: 24,
237+
backgroundColor: '#E5E5EA',
238+
},
239+
chatList: {
240+
flex: 1,
241+
backgroundColor: '#F2F2F7',
242+
},
243+
streamingRow: {
244+
flexDirection: 'row',
17245
alignItems: 'center',
246+
paddingHorizontal: 16,
247+
paddingVertical: 8,
248+
},
249+
streamingText: {
250+
fontSize: 13,
251+
color: '#8E8E93',
252+
fontStyle: 'italic',
253+
marginRight: 8,
254+
},
255+
errorRow: {
256+
paddingHorizontal: 16,
257+
paddingVertical: 6,
258+
backgroundColor: '#FF3B3020',
259+
},
260+
errorText: {
261+
color: '#FF3B30',
262+
fontSize: 13,
263+
},
264+
inputRow: {
265+
flexDirection: 'row',
266+
alignItems: 'flex-end',
267+
paddingHorizontal: 12,
268+
paddingVertical: 8,
269+
paddingBottom: Platform.OS === 'ios' ? 28 : 8,
270+
borderTopWidth: StyleSheet.hairlineWidth,
271+
borderTopColor: '#C7C7CC',
272+
backgroundColor: '#FFFFFF',
273+
},
274+
input: {
275+
flex: 1,
276+
minHeight: 36,
277+
maxHeight: 100,
278+
borderRadius: 18,
279+
borderWidth: 1,
280+
borderColor: '#C7C7CC',
281+
paddingHorizontal: 14,
282+
paddingVertical: 8,
283+
fontSize: 15,
284+
},
285+
sendButton: {
286+
marginLeft: 8,
287+
height: 36,
288+
paddingHorizontal: 16,
289+
borderRadius: 18,
290+
backgroundColor: '#007AFF',
18291
justifyContent: 'center',
292+
alignItems: 'center',
293+
},
294+
sendButtonText: {
295+
color: '#FFFFFF',
296+
fontSize: 15,
297+
fontWeight: '600',
298+
},
299+
sendButtonDisabled: {
300+
opacity: 0.4,
301+
},
302+
stopButton: {
303+
marginLeft: 8,
304+
height: 36,
305+
paddingHorizontal: 16,
306+
borderRadius: 18,
307+
backgroundColor: '#FF3B30',
308+
justifyContent: 'center',
309+
alignItems: 'center',
310+
},
311+
stopButtonText: {
312+
color: '#FFFFFF',
313+
fontSize: 15,
314+
fontWeight: '600',
19315
},
20316
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-native-ai-kit",
33
"version": "0.1.0",
4-
"description": "test",
4+
"description": "AI chat toolkit for React Native — SSE streaming, chat hooks, and UI components for any LLM backend",
55
"main": "./lib/module/index.js",
66
"types": "./lib/typescript/src/index.d.ts",
77
"exports": {

0 commit comments

Comments
 (0)