Skip to content

Commit c26253c

Browse files
chore: example test app for users to run. Initialised VertexAi example (#8471)
Co-authored-by: Mike Hardy <[email protected]>
1 parent a909d1b commit c26253c

File tree

5 files changed

+380
-1
lines changed

5 files changed

+380
-1
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@
5252
"tests:macos:build": "cd tests && yarn build:macos",
5353
"tests:macos:pod:install": "cd tests && rm -f macos/Podfile.lock && cd macos && pod install",
5454
"tests:macos:test-cover": "cd tests && npx jet --target=macos --coverage",
55-
"format:markdown": "prettier --write \"docs/**/*.md\""
55+
"format:markdown": "prettier --write \"docs/**/*.md\"",
56+
"example:app:bundler": "cd tests && yarn react-native start --config ./test-app/metro.config.js --reset-cache",
57+
"example:app:run:ios": "cd tests && SIMCTL_CHILD_GULGeneratedClassDisposeDisabled=1 CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ yarn react-native run-ios",
58+
"example:app:run:android": "cd tests && yarn react-native run-android"
5659
},
5760
"devDependencies": {
5861
"@babel/core": "^7.26.9",

tests/test-app/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# React Native Firebase Example App
2+
3+
## Setup
4+
5+
1. Follow the steps in the [test README](../README.md) up to and including step one (i.e. `Requirements`, `Cleaning dependencies` and `Install test project dependencies`).
6+
7+
2. Choose which example app to run in the [index file](./index.js) by commenting out the apps you don't want to use.
8+
9+
3. In your terminal, run the following to start the test app bundler:
10+
```bash
11+
yarn example:app:bundler
12+
```
13+
14+
4. For vertexAI example, we have enforced Firebase App Check so the APIs will fail, and you will have to update the relevant service files (i.e. `../ios/GoogleService-Info.plist` and `../android/app/google-services.json`) for your Firebase project.
15+
16+
## Run and build app for android
17+
18+
Open a new terminal and run:
19+
20+
```bash
21+
yarn example:app:run:android
22+
```
23+
24+
## Run and build app for iOS
25+
26+
Open a new terminal and run:
27+
28+
```bash
29+
yarn example:app:run:ios
30+
```
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import React from 'react';
2+
import { AppRegistry, Button, View } from 'react-native';
3+
4+
import { getApp } from '@react-native-firebase/app';
5+
import { getVertexAI, getGenerativeModel, Schema } from '@react-native-firebase/vertexai';
6+
7+
function App() {
8+
return (
9+
<View>
10+
<View style={{ height: 90 }} />
11+
<Button
12+
title="Generate Content"
13+
onPress={async () => {
14+
try {
15+
const app = getApp();
16+
const vertexai = getVertexAI(app);
17+
const model = getGenerativeModel(vertexai, { model: 'gemini-1.5-flash' });
18+
19+
const result = await model.generateContent('What is 2 + 2?');
20+
21+
console.log('result', result.response.text());
22+
} catch (e) {
23+
console.error(e);
24+
}
25+
}}
26+
/>
27+
<Button
28+
title="Generate Content Stream"
29+
onPress={async () => {
30+
try {
31+
const app = getApp();
32+
const vertexai = getVertexAI(app);
33+
const model = getGenerativeModel(vertexai, { model: 'gemini-1.5-flash' });
34+
35+
const result = await model.generateContentStream('Write me a short, funny rap');
36+
37+
let text = '';
38+
for await (const chunk of result.stream) {
39+
const chunkText = chunk.text();
40+
console.log(chunkText);
41+
42+
text += chunkText;
43+
}
44+
45+
console.log('result', text);
46+
} catch (e) {
47+
console.error(e);
48+
}
49+
}}
50+
/>
51+
<Button
52+
title="Generate Content Stream multi-modal"
53+
onPress={async () => {
54+
try {
55+
const app = getApp();
56+
const vertexai = getVertexAI(app);
57+
const model = getGenerativeModel(vertexai, { model: 'gemini-1.5-flash' });
58+
const prompt = 'What can you see?';
59+
const base64Emoji =
60+
'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=';
61+
62+
// Call generateContentStream with the text and images
63+
const response = await model.generateContentStream([
64+
prompt,
65+
{ inlineData: { mimeType: 'image/png', data: base64Emoji } },
66+
]);
67+
68+
let text = '';
69+
for await (const chunk of response.stream) {
70+
text += chunk.text();
71+
}
72+
73+
console.log('Generated text:', text);
74+
} catch (e) {
75+
console.error(e);
76+
}
77+
}}
78+
/>
79+
<Button
80+
title="Generate JSON Response"
81+
onPress={async () => {
82+
try {
83+
const app = getApp();
84+
const vertexai = getVertexAI(app);
85+
const jsonSchema = Schema.object({
86+
properties: {
87+
characters: Schema.array({
88+
items: Schema.object({
89+
properties: {
90+
name: Schema.string(),
91+
accessory: Schema.string(),
92+
age: Schema.number(),
93+
species: Schema.string(),
94+
},
95+
optionalProperties: ['accessory'],
96+
}),
97+
}),
98+
},
99+
});
100+
const model = getGenerativeModel(vertexai, {
101+
model: 'gemini-1.5-flash',
102+
generationConfig: {
103+
responseMimeType: 'application/json',
104+
responseSchema: jsonSchema,
105+
},
106+
});
107+
108+
let prompt = "For use in a children's card game, generate 10 animal-based characters.";
109+
110+
let result = await model.generateContent(prompt);
111+
console.log(result.response.text());
112+
} catch (e) {
113+
console.error(e);
114+
}
115+
}}
116+
/>
117+
<Button
118+
title="Start Chat"
119+
onPress={async () => {
120+
try {
121+
const app = getApp();
122+
const vertexai = getVertexAI(app);
123+
const model = getGenerativeModel(vertexai, { model: 'gemini-1.5-flash' });
124+
125+
const chat = model.startChat({
126+
history: [
127+
{
128+
role: 'user',
129+
parts: [{ text: 'Hello, I have 2 dogs in my house.' }],
130+
},
131+
{
132+
role: 'model',
133+
parts: [{ text: 'Great to meet you. What would you like to know?' }],
134+
},
135+
],
136+
generationConfig: {
137+
maxOutputTokens: 100,
138+
},
139+
});
140+
141+
const msg = 'How many paws are in my house?';
142+
const result = await chat.sendMessageStream(msg);
143+
144+
let text = '';
145+
for await (const chunk of result.stream) {
146+
const chunkText = chunk.text();
147+
text += chunkText;
148+
}
149+
console.log(text);
150+
chat.getHistory();
151+
} catch (e) {
152+
console.error(e);
153+
}
154+
}}
155+
/>
156+
<Button
157+
title="Count Tokens"
158+
onPress={async () => {
159+
try {
160+
const app = getApp();
161+
const vertexai = getVertexAI(app);
162+
const model = getGenerativeModel(vertexai, { model: 'gemini-1.5-flash' });
163+
164+
const result = await model.countTokens('What is 2 + 2?');
165+
166+
console.log('totalBillableCharacters', result.totalBillableCharacters);
167+
console.log('totalTokens', result.totalTokens);
168+
} catch (e) {
169+
console.error(e);
170+
}
171+
}}
172+
/>
173+
174+
<Button
175+
title="Function Calling"
176+
onPress={async () => {
177+
// This function calls a hypothetical external API that returns
178+
// a collection of weather information for a given location on a given date.
179+
// `location` is an object of the form { city: string, state: string }
180+
async function fetchWeather({ location, date }) {
181+
// For demo purposes, this hypothetical response is hardcoded here in the expected format.
182+
return {
183+
temperature: 38,
184+
chancePrecipitation: '56%',
185+
cloudConditions: 'partlyCloudy',
186+
};
187+
}
188+
const fetchWeatherTool = {
189+
functionDeclarations: [
190+
{
191+
name: 'fetchWeather',
192+
description: 'Get the weather conditions for a specific city on a specific date',
193+
parameters: Schema.object({
194+
properties: {
195+
location: Schema.object({
196+
description:
197+
'The name of the city and its state for which to get ' +
198+
'the weather. Only cities in the USA are supported.',
199+
properties: {
200+
city: Schema.string({
201+
description: 'The city of the location.',
202+
}),
203+
state: Schema.string({
204+
description: 'The US state of the location.',
205+
}),
206+
},
207+
}),
208+
date: Schema.string({
209+
description:
210+
'The date for which to get the weather. Date must be in the' +
211+
' format: YYYY-MM-DD.',
212+
}),
213+
},
214+
}),
215+
},
216+
],
217+
};
218+
try {
219+
const app = getApp();
220+
const vertexai = getVertexAI(app);
221+
const model = getGenerativeModel(vertexai, {
222+
model: 'gemini-1.5-flash',
223+
tools: fetchWeatherTool,
224+
});
225+
226+
const chat = model.startChat();
227+
const prompt = 'What was the weather in Boston on October 17, 2024?';
228+
229+
// Send the user's question (the prompt) to the model using multi-turn chat.
230+
let result = await chat.sendMessage(prompt);
231+
const functionCalls = result.response.functionCalls();
232+
let functionCall;
233+
let functionResult;
234+
// When the model responds with one or more function calls, invoke the function(s).
235+
if (functionCalls.length > 0) {
236+
for (const call of functionCalls) {
237+
if (call.name === 'fetchWeather') {
238+
// Forward the structured input data prepared by the model
239+
// to the hypothetical external API.
240+
functionResult = await fetchWeather(call.args);
241+
functionCall = call;
242+
}
243+
}
244+
}
245+
result = await chat.sendMessage([
246+
{
247+
functionResponse: {
248+
name: functionCall.name, // "fetchWeather"
249+
response: functionResult,
250+
},
251+
},
252+
]);
253+
console.log(result.response.text());
254+
} catch (e) {
255+
console.error(e);
256+
}
257+
}}
258+
/>
259+
</View>
260+
);
261+
}
262+
263+
AppRegistry.registerComponent('testing', () => App);

tests/test-app/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Add sample apps here
3+
*
4+
* comment out all imports but the one you wish to run
5+
*/
6+
require('./examples/vertexai');

tests/test-app/metro.config.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2016-present Invertase Limited & Contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this library except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
19+
20+
const { resolve, join } = require('path');
21+
const { readdirSync, statSync } = require('fs');
22+
23+
const exclusionList = require('metro-config/src/defaults/exclusionList');
24+
25+
const rootDir = resolve(__dirname, '..', '..');
26+
const packagesDir = resolve(rootDir, 'packages');
27+
28+
const isDirectory = source => statSync(source).isDirectory();
29+
const firebaseModules = readdirSync(packagesDir)
30+
.map(name => join(packagesDir, name))
31+
.filter(isDirectory);
32+
33+
const config = {
34+
projectRoot: __dirname,
35+
resolver: {
36+
useWatchman: !process.env.CI,
37+
blocklist: exclusionList([
38+
/.*\/__fixtures__\/.*/,
39+
/.*\/template\/project\/node_modules\/react-native\/.*/,
40+
new RegExp(`^${escape(resolve(rootDir, 'docs'))}\\/.*$`),
41+
new RegExp(`^${escape(resolve(rootDir, 'tests/ios'))}\\/.*$`),
42+
new RegExp(
43+
`^${escape(resolve(rootDir, 'packages/template/project/node_modules/react-native'))}\\/.*$`,
44+
),
45+
new RegExp(`^${escape(resolve(rootDir, 'tests/e2e'))}\\/.*$`),
46+
new RegExp(`^${escape(resolve(rootDir, 'tests/android'))}\\/.*$`),
47+
new RegExp(`^${escape(resolve(rootDir, 'tests/functions'))}\\/.*$`),
48+
]),
49+
extraNodeModules: new Proxy(
50+
{},
51+
{
52+
get: (target, name) => {
53+
if (typeof name !== 'string') {
54+
return target[name];
55+
}
56+
if (name && name.startsWith && name.startsWith('@react-native-firebase')) {
57+
const packageName = name.replace('@react-native-firebase/', '');
58+
return join(__dirname, `../../packages/${packageName}`);
59+
}
60+
return join(__dirname, `../node_modules/${name}`);
61+
},
62+
},
63+
),
64+
},
65+
transformer: {
66+
unstable_allowRequireContext: true,
67+
getTransformOptions: async () => ({
68+
transform: {
69+
experimentalImportSupport: false,
70+
inlineRequires: true,
71+
},
72+
}),
73+
},
74+
watchFolders: [resolve(__dirname, '..'), ...firebaseModules],
75+
};
76+
77+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

0 commit comments

Comments
 (0)