From ee6c823c3551d1811b7572d4cc3f6b8578afd1fe Mon Sep 17 00:00:00 2001 From: evanbacon Date: Tue, 12 Dec 2023 17:45:32 -0600 Subject: [PATCH 1/3] add open-ai example --- .gitignore | 4 + with-openai/.env | 1 + with-openai/README.md | 40 +++++++++ with-openai/app.json | 17 ++++ with-openai/app/api/generate+api.ts | 72 ++++++++++++++++ with-openai/app/index.tsx | 122 ++++++++++++++++++++++++++++ with-openai/package.json | 27 ++++++ with-openai/tsconfig.json | 4 + 8 files changed, 287 insertions(+) create mode 100644 with-openai/.env create mode 100644 with-openai/README.md create mode 100644 with-openai/app.json create mode 100644 with-openai/app/api/generate+api.ts create mode 100644 with-openai/app/index.tsx create mode 100644 with-openai/package.json create mode 100644 with-openai/tsconfig.json diff --git a/.gitignore b/.gitignore index 90e2b675..cc8f7969 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ with-nextjs/out # Websockets */backend/.env +# local env files +*/.env*.local + + .DS_Store report.html diff --git a/with-openai/.env b/with-openai/.env new file mode 100644 index 00000000..e6c43748 --- /dev/null +++ b/with-openai/.env @@ -0,0 +1 @@ +OPENAI_API_KEY=YOUR_KEY \ No newline at end of file diff --git a/with-openai/README.md b/with-openai/README.md new file mode 100644 index 00000000..0ba949b7 --- /dev/null +++ b/with-openai/README.md @@ -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) diff --git a/with-openai/app.json b/with-openai/app.json new file mode 100644 index 00000000..349a6c62 --- /dev/null +++ b/with-openai/app.json @@ -0,0 +1,17 @@ +{ + "expo": { + "scheme": "acme", + "web": { + "output": "server", + "bundler": "metro" + }, + "plugins": [ + [ + "expo-router", + { + "origin": "https://n" + } + ] + ] + } +} diff --git a/with-openai/app/api/generate+api.ts b/with-openai/app/api/generate+api.ts new file mode 100644 index 00000000..1a0b98f3 --- /dev/null +++ b/with-openai/app/api/generate+api.ts @@ -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 { + 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", + }, + }, +}; diff --git a/with-openai/app/index.tsx b/with-openai/app/index.tsx new file mode 100644 index 00000000..a02b1bc3 --- /dev/null +++ b/with-openai/app/index.tsx @@ -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, ...newState }), + { + loading: false, + content: null, + } + ); + + const [input, setInput] = React.useState(""); + + const generateBio = 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 ( + + + Expo App Idea Generator + + setInput(e.nativeEvent.text)} + rows={4} + placeholderTextColor={"#9CA3AF"} + placeholder="e.g. AI app idea generator." + /> + +