Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add OpenAI example #441

Merged
merged 3 commits into from
Dec 13, 2023
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
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/)
Loading