Skip to content

Commit

Permalink
add OpenAI example (#441)
Browse files Browse the repository at this point in the history
* add open-ai example

* update router references

* Update preview.yml
  • Loading branch information
EvanBacon authored Dec 13, 2023
1 parent 9797cd0 commit c58896d
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: 🏗️ Setup Node
uses: actions/setup-node@v1
with:
node-version: 16.x
node-version: 18.x
- name: 🏗️ Setup Expo
uses: expo/expo-github-action@v6
with:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ with-nextjs/out
# Websockets
*/backend/.env

# local env files
*/.env*.local


.DS_Store
report.html

Expand Down
2 changes: 2 additions & 0 deletions with-openai/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# You can get your key from https://platform.openai.com/api-keys
OPENAI_API_KEY=YOUR_KEY
40 changes: 40 additions & 0 deletions with-openai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Open AI Example

Use [Expo API Routes](https://docs.expo.dev/router/reference/api-routes/) to securely interact with the [OpenAI API](https://platform.openai.com/docs/introduction).

## Structure

- `app/api/generate+api.ts`: [Expo API Route](https://docs.expo.dev/router/reference/api-routes/) that interacts with the [OpenAI API](https://platform.openai.com/docs/introduction).
- `app/index.tsx`: Screen that uses the API Route to prompt the user and display results.
- `.env`: The environment variable file with references to your secret [OpenAI API key](https://platform.openai.com/api-keys).

## 🚀 How to use

```sh
npx create-expo-app -e with-openai
```

Replace `OPENAI_API_KEY=YOUR_KEY` in `.env` with your [OpenAI API key](https://platform.openai.com/api-keys).

Replace `origin` in the `app.json` with the URL to your [production API Routes](https://docs.expo.dev/router/reference/api-routes/#deployment) domain. This enables relative fetch requests.

```json
{
"expo": {
"extra": {
"api": {
"origin": "https://my-expo-website.com"
}
}
}
}
```

Ensure you upload your environment variables to wherever you host the web app and API Routes.

## 📝 Notes

- [Expo Router: API Routes](https://docs.expo.dev/router/reference/api-routes/)
- [Expo Router: Server Deployment](https://docs.expo.dev/router/reference/api-routes/#deployment)
- [Expo Router Docs](https://docs.expo.dev/router/introduction/)
- [Open AI Docs](https://platform.openai.com/docs/introduction)
17 changes: 17 additions & 0 deletions with-openai/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"expo": {
"scheme": "acme",
"web": {
"output": "server",
"bundler": "metro"
},
"plugins": [
[
"expo-router",
{
"origin": "https://n"
}
]
]
}
}
72 changes: 72 additions & 0 deletions with-openai/app/api/generate+api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ExpoRequest, ExpoResponse } from "expo-router/server";

const ENDPOINT = "https://api.openai.com/v1/chat/completions";

export async function POST(req: ExpoRequest): Promise<ExpoResponse> {
const { prompt } = await req.json();
console.log("prompt:", prompt);
const content = `Generate 2 app startup ideas that are optimal for Expo Router where you can develop a native app and website simultaneously with automatic universal links and API routes. Format the response as a JSON array with objects containing a "name" and "description" field, both of type string, with no additional explanation above or below the results. Base it on this context: ${prompt}.`;

// const json = FIXTURES.success;

// calling the OpenAI API endpoint
const json = await fetch(ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content }],
temperature: 1.2,
max_tokens: 1100, // You can customize this
}),
}).then((res) => res.json());

// For creating new fixtures.
// console.log("json:", JSON.stringify(json, null, 2));

if (json.choices?.[0]) {
// Assuming the LLM always returns the data in the expected format.
const llmResponse = JSON.parse(json.choices[0].message.content.trim());
return ExpoResponse.json(llmResponse);
}

if (json.error) {
return new ExpoResponse(json.error.message, { status: 400 });
}

return ExpoResponse.json(json);
}

const FIXTURES = {
success: {
id: "chatcmpl-xxx",
object: "chat.completion",
created: 1702423839,
model: "gpt-3.5-turbo-0613",
choices: [
{
index: 0,
message: {
role: "assistant",
content:
'[\n {"name": "BeatsTime", "description": "BeatsTime is a social music platform where users can discover and share their favorite tracks with friends. The app allows users to create personalized playlists, follow their favorite DJs, and explore trending music genres."},\n {"name": "SyncSound", "description": "SyncSound is a collaborative music app that enables users to create synchronized playlists and listen to music together in real-time. Users can invite friends to join their session, vote on the next track, and chat with each other while enjoying a synchronized music experience."}\n]',
},
finish_reason: "stop",
},
],
usage: { prompt_tokens: 81, completion_tokens: 118, total_tokens: 199 },
system_fingerprint: null,
},
error: {
error: {
message:
"You exceeded your current quota, please check your plan and billing details.",
type: "insufficient_quota",
param: null,
code: "insufficient_quota",
},
},
};
122 changes: 122 additions & 0 deletions with-openai/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from "react";
import { Text, View, Button, StyleSheet, TextInput } from "react-native";

interface State {
loading: boolean;
content: { name: string; description: string }[] | null;
}

export default function Page() {
const [{ loading, content }, setState] = React.useReducer(
(state: State, newState: Partial<State>) => ({ ...state, ...newState }),
{
loading: false,
content: null,
}
);

const [input, setInput] = React.useState("");

const generateContent = async () => {
setState({
content: null,
loading: true,
});

try {
const response = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: input,
}),
});

if (!response.ok) {
throw new Error(response.statusText);
}

const content = await response.json();
setState({
content,
loading: false,
});
} catch (error) {
setState({
content: null,
loading: false,
});
throw error;
}
};

return (
<View style={styles.container}>
<View style={styles.main}>
<Text style={styles.title}>Expo App Idea Generator</Text>

<TextInput
value={input}
style={{
minHeight: 120,
borderWidth: 1,
padding: 8,
}}
onChange={(e) => setInput(e.nativeEvent.text)}
rows={4}
placeholderTextColor={"#9CA3AF"}
placeholder="e.g. AI app idea generator."
/>

<Button
disabled={loading}
onPress={() => generateContent()}
title={loading ? "Loading..." : "Generate"}
/>

{content != null && (
<>
<Text style={styles.subtitle}>Generated Ideas:</Text>
{content.map(({ name, description }, index) => (
<View key={String(index)}>
<Text style={styles.title}>{name}</Text>
<Text>{description}</Text>
</View>
))}
</>
)}
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "stretch",
justifyContent: "center",
marginHorizontal: "auto",
},
main: {
flex: 1,
gap: 8,
justifyContent: "center",
alignItems: "stretch",
maxWidth: 640,
paddingHorizontal: 24,
},
title: {
fontSize: 20,
fontWeight: "bold",
},
subtitle: {
fontSize: 24,
},
separator: {
marginVertical: 30,
height: 1,
width: "80%",
},
});
27 changes: 27 additions & 0 deletions with-openai/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "with-openai",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start"
},
"dependencies": {
"expo": "^50.0.0-preview.1",
"expo-constants": "~15.4.0",
"expo-linking": "~6.2.1",
"expo-router": "~3.3.0",
"expo-splash-screen": "~0.26.0",
"expo-status-bar": "~1.11.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.73.0",
"react-native-safe-area-context": "4.7.4",
"react-native-screens": "~3.27.0",
"react-native-web": "~0.19.6"
},
"devDependencies": {
"@types/react": "~18.2.14",
"typescript": "^5.3.0",
"@babel/core": "^7.20.0"
}
}
4 changes: 4 additions & 0 deletions with-openai/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"compilerOptions": {},
"extends": "expo/tsconfig.base"
}
5 changes: 2 additions & 3 deletions with-router/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Expo Router Example

Use [`expo-router`](https://expo.github.io/router) to build native navigation using files in the `app/` directory.
Use [`expo-router`](https://docs.expo.dev/router/introduction/) to build native navigation using files in the `app/` directory.

## 🚀 How to use

Expand All @@ -10,5 +10,4 @@ npx create-expo-app -e with-router

## 📝 Notes

- [Expo Router: Docs](https://expo.github.io/router)
- [Expo Router: Repo](https://github.com/expo/router)
- [Expo Router: Docs](https://docs.expo.dev/router/introduction/)

0 comments on commit c58896d

Please sign in to comment.