├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── codeql-analysis.yml │ ├── pr.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore ├── pre-commit └── prepare-commit-msg ├── .prettierrc.js ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── assets └── example.gif ├── eslint.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── hooks │ └── use-chat.tsx ├── index.ts └── openai │ ├── chat-completion.ts │ ├── constants.ts │ ├── openai-api.ts │ ├── openai.ts │ └── utils.ts ├── test └── index.spec.ts ├── tsconfig.build.json └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | # Local env vars for debugging 2 | TS_NODE_IGNORE="false" 3 | TS_NODE_FILES="true" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/types/global.d.ts -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'node', 'prettier'], 5 | parserOptions: { 6 | tsconfigRootDir: __dirname, 7 | project: ['./tsconfig.json'], 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:node/recommended', 12 | 'plugin:@typescript-eslint/eslint-recommended', 13 | 'plugin:@typescript-eslint/recommended', 14 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 15 | 'plugin:prettier/recommended', 16 | ], 17 | rules: { 18 | 'prettier/prettier': 'warn', 19 | 'node/no-missing-import': 'off', 20 | 'node/no-empty-function': 'off', 21 | 'node/no-unsupported-features/es-syntax': 'off', 22 | '@typescript-eslint/ban-types': 'off', 23 | 'node/no-missing-require': 'off', 24 | 'node/shebang': 'off', 25 | '@typescript-eslint/no-use-before-define': 'off', 26 | quotes: ['warn', 'single', { avoidEscape: true }], 27 | 'node/no-unpublished-import': 'off', 28 | '@typescript-eslint/no-unsafe-assignment': 'off', 29 | '@typescript-eslint/no-var-requires': 'off', 30 | '@typescript-eslint/ban-ts-comment': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the repository to show as TypeScript rather than JS in GitHub 2 | *.js linguist-detectable=false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: Report a reproducible bug or regression. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Current Behavior 11 | 12 | 13 | 14 | ## Expected Behavior 15 | 16 | 17 | 18 | ## Steps to Reproduce the Problem 19 | 20 | 1. 21 | 1. 22 | 1. 23 | 24 | ## Environment 25 | 26 | - Version: 27 | - Platform: 28 | - Node.js Version: 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🌈 Feature request 3 | about: Suggest an amazing new idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Feature Request 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | 15 | **Describe the solution you'd like** 16 | 17 | 18 | **Describe alternatives you've considered** 19 | 20 | 21 | ## Are you willing to resolve this issue by submitting a Pull Request? 22 | 23 | 26 | 27 | - [ ] Yes, I have the time, and I know how to start. 28 | - [ ] Yes, I have the time, but I don't know how to start. I would need guidance. 29 | - [ ] No, I don't have the time, although I believe I could do it if I had the time... 30 | - [ ] No, I don't have the time and I wouldn't even know how to start. 31 | 32 | 35 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ### Description of change 9 | 10 | 23 | 24 | ### Pull-Request Checklist 25 | 26 | 31 | 32 | - [ ] Code is up-to-date with the `master` branch 33 | - [ ] `npm run lint` passes with this change 34 | - [ ] `npm run test` passes with this change 35 | - [ ] This pull request links relevant issues as `Fixes #0000` 36 | - [ ] There are new or updated unit tests validating the change 37 | - [ ] Documentation has been updated to reflect this change 38 | - [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/) 39 | 40 | 43 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # Reusable workflow for code analysis; to eject, you can replace this file with 2 | # https://github.com/ryansonshine/ryansonshine/blob/main/.github/workflows/codeql-analysis.yml 3 | name: "CodeQL" 4 | 5 | permissions: 6 | security-events: write 7 | actions: read 8 | contents: read 9 | 10 | on: 11 | push: 12 | branches: [master] 13 | pull_request: 14 | # The branches below must be a subset of the branches above 15 | branches: [master] 16 | schedule: 17 | - cron: "36 7 * * 6" 18 | 19 | jobs: 20 | analyze: 21 | uses: ryansonshine/ryansonshine/.github/workflows/codeql-analysis.yml@main 22 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | # Reusable workflow for PRs; to eject, you can replace this file with 2 | # https://github.com/ryansonshine/ryansonshine/blob/main/.github/workflows/pr.yml 3 | name: Pull Request 4 | 5 | on: [pull_request] 6 | 7 | jobs: 8 | build: 9 | uses: ryansonshine/ryansonshine/.github/workflows/pr.yml@main 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Reusable workflow for releases; to eject, you can replace this file with 2 | # https://github.com/ryansonshine/ryansonshine/blob/main/.github/workflows/release.yml 3 | name: Release 4 | on: 5 | push: 6 | branches: 7 | - master 8 | jobs: 9 | release: 10 | permissions: 11 | contents: write 12 | issues: write 13 | pull-requests: write 14 | uses: ryansonshine/ryansonshine/.github/workflows/release.yml@main 15 | secrets: 16 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env.test 73 | .env.local 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # Compiled code 119 | lib/ 120 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec ', 19 | '', 20 | '', 21 | '', 22 | '^@/lib/(.*)$', 23 | '^@/hooks/(.*)$', 24 | '^@/components/(.*)$', 25 | '^@/app/(.*)$', 26 | '', 27 | '^(?!.*[.]css$)[./].*$', 28 | '.css$', 29 | '', 30 | '^[./]', 31 | ], 32 | importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'], 33 | importOrderTypeScriptVersion: '5.0.0', 34 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Current TS File", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeExecutable": "node", 12 | "args": ["${relativeFile}"], 13 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], 14 | "envFile": "${workspaceFolder}/.env", 15 | "cwd": "${workspaceRoot}", 16 | "internalConsoleOptions": "openOnSessionStart", 17 | "skipFiles": ["/**", "node_modules/**"] 18 | }, 19 | { 20 | "name": "Debug Jest Tests", 21 | "type": "node", 22 | "request": "launch", 23 | "runtimeArgs": [ 24 | "--inspect-brk", 25 | "${workspaceRoot}/node_modules/.bin/jest", 26 | "--runInBand" 27 | ], 28 | "envFile": "${workspaceFolder}/.env", 29 | "console": "integratedTerminal", 30 | "internalConsoleOptions": "neverOpen", 31 | "port": 9229, 32 | "disableOptimisticBPs": true, 33 | "windows": { 34 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 35 | } 36 | }, 37 | { 38 | "name": "Debug Jest Current File", 39 | "type": "node", 40 | "request": "launch", 41 | "program": "${workspaceFolder}/node_modules/.bin/jest", 42 | "args": ["${relativeFile}", "--config", "jest.config.js"], 43 | "console": "integratedTerminal", 44 | "internalConsoleOptions": "neverOpen", 45 | "port": 9229, 46 | "disableOptimisticBPs": true, 47 | "windows": { 48 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[typescript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 zerodays d.o.o. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Generative UI Library 2 | 3 | Inspired by Vercel's [Generative UI](https://sdk.vercel.ai/docs/concepts/ai-rsc) for React Server Components. 4 | 5 | Offers a seamless integration of OpenAI's advanced AI capabilities within React Native applications. Library provides components and helpers for building AI-powered streaming text and chat UIs. 6 | 7 | ![Example Gif](assets/example.gif) 8 | 9 | If you are interested in learning more, take a look at this [blog post](https://dev.to/zerodays/generative-ui-in-react-native-180f). It discusses how to build UIs with this package and what is behind the scenes. 10 | 11 | ## Features 12 | 13 | - React Native (with Expo) type-safe helpers for streaming text responses + components for building chat UIs 14 | - First-class support for [Function calling](https://platform.openai.com/docs/guides/function-calling) with component support that LLM decides to render for interactive user interfaces 15 | - Easy UI implementation with powerful `useChat` hook 16 | - Support for [OpenAI models](https://platform.openai.com/docs/guides/text-generation) 17 | - Streaming responses (only streaming is supported ATM). 18 | - Supports OpenAI's [Chat completions](https://platform.openai.com/docs/guides/text-generation/chat-completions-api) API. 19 | 20 | ## Installation :rocket: 21 | 22 | It's easy to get started - just install package with your favorite package manager: 23 | 24 | ### Yarn 25 | 26 | ```bash 27 | yarn add react-native-gen-ui 28 | ``` 29 | 30 | ### NPM 31 | 32 | ```bash 33 | npm install react-native-gen-ui 34 | ``` 35 | 36 | ## Basic usage :tada: 37 | 38 | ### Import 39 | 40 | To get started, import `useChat` hook in any React component: 41 | 42 | ```ts 43 | import { OpenAI, isReactElement, useChat } from 'react-native-gen-ui'; 44 | ``` 45 | 46 | ### Initialize the OpenAI instance 47 | 48 | ```ts 49 | const openAi = new OpenAI({ 50 | apiKey: process.env.EXPO_PUBLIC_OPENAI_API_KEY!, 51 | model: 'gpt-4', 52 | // You can even set a custom basePath of your SSE server 53 | }); 54 | ``` 55 | 56 | Ensure you have the OpenAI API key and the desired model environment variables set up in your project. These are stored as environment variables (in Expo): 57 | 58 | ``` 59 | EXPO_PUBLIC_OPENAI_API_KEY=sk.... # Required, you can get one in OpenAi dashboard 60 | EXPO_PUBLIC_OPENAI_MODEL=model_name_here # Optional, model name from OpenAI (defaults to 'gpt-4') 61 | ``` 62 | 63 | **🚨 Note:** This kind of implementation where you access OpenAI directly from the client device exposes your OpenAI API key to the public. The documentation here is just an example, for production use make sure to point `basePath` to your proxy server that forwards server sent events from OpenAI back to the client. 64 | 65 | ### Use the hook 66 | 67 | Initialize the `useChat` hook inside your component. You can optionally pass **initial messages**, **success** and **error handlers**, and any tools the model will have access to. 68 | 69 | ```ts 70 | const { input, onInputChange, messages, isLoading, handleSubmit } = useChat({ 71 | openAi, 72 | // Optional initial messages 73 | initialMessages: [ 74 | { content: 'Hi! How can I help you today?', role: 'system' }, 75 | ], 76 | // Optional success handler 77 | onSuccess: (messages) => console.log('Chat success:', messages), 78 | // Optional error handler 79 | onError: (error) => console.error('Chat error:', error), 80 | }); 81 | ``` 82 | 83 | Create the UI for your chat interface that includes input, submit button and a view to display the chat messages. 84 | 85 | ```tsx 86 | return ( 87 | 88 | {messages.map((msg, index) => { 89 | // Message can be react component or string (see function calling section for more details) 90 | if (isReactElement(msg)) { 91 | return msg; 92 | } 93 | switch (msg.role) { 94 | case 'user': 95 | return ( 96 | 101 | {msg.content?.toString()} 102 | 103 | ); 104 | case 'assistant': 105 | return {msg.content?.toString()}; 106 | default: 107 | // This includes tool calls, tool results and system messages 108 | // Those are visible to the model, but here we hide them to the user 109 | return null; 110 | } 111 | })} 112 | 113 |