Skip to content

Commit

Permalink
feat: add android
Browse files Browse the repository at this point in the history
  • Loading branch information
gtokman committed Oct 17, 2023
1 parent ce0388a commit f581c24
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 45 deletions.
52 changes: 33 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ Currently the project only supports iOS using URLSession for faster performance.
## Features

- [x] [Chat](https://platform.openai.com/docs/api-reference/chat)
- [x] [Models](https://beta.openai.com/docs/api-reference/models)
- [x] [Completions](https://beta.openai.com/docs/api-reference/completions)
- [x] [Edits](https://beta.openai.com/docs/api-reference/edits)
- [x] [Images](https://beta.openai.com/docs/api-reference/images)
- [x] [Embeddings](https://beta.openai.com/docs/api-reference/embeddings)
- [x] [Files](https://beta.openai.com/docs/api-reference/files)
- [x] [Moderations](https://beta.openai.com/docs/api-reference/moderations)
- [ ] [Models](https://beta.openai.com/docs/api-reference/models)
- [ ] [Completions](https://beta.openai.com/docs/api-reference/completions)
- [ ] [Edits](https://beta.openai.com/docs/api-reference/edits)
- [ ] [Images](https://beta.openai.com/docs/api-reference/images)
- [ ] [Embeddings](https://beta.openai.com/docs/api-reference/embeddings)
- [ ] [Files](https://beta.openai.com/docs/api-reference/files)
- [ ] [Moderations](https://beta.openai.com/docs/api-reference/moderations)
- [ ] [Fine-tunes](https://beta.openai.com/docs/api-reference/fine-tunes)
- [x] [Speech to text](https://platform.openai.com/docs/guides/speech-to-text)
- [ ] [Speech to text](https://platform.openai.com/docs/guides/speech-to-text)
- [ ] [Function calling](https://platform.openai.com/docs/guides/gpt/function-calling)

## Installation
Expand All @@ -45,7 +45,7 @@ yarn add react-native-openai
## Basic Usage

1. Create a new OpenAI instance with your API key and organization ID.
2. Call `createCompletion` with your prompt to generate a streaming completion.
2. Call `stream` with your messages to generate a streaming completion.
3. Check out the documentation for more information on the available methods.

```js
Expand All @@ -56,24 +56,38 @@ const openAI = new OpenAI('API_KEY', 'ORG_ID');
const [result, setResult] = React.useState('');

React.useEffect(() => {
openAI.addListener('onMessageReceived', (event) => {
setResult((message) => message + event.payload.message);
});

return () => {
openAI.removeListener('onMessageReceived');
};
}, []);
openAI.chat.addListener('onChatMessageReceived', (payload) => {
setResult((message) => {
const newMessage = payload.choices[0]?.delta.content;
if (newMessage) {
return message + newMessage;
}
return message;
});
});

return () => {
openAI.chat.removeListener('onChatMessageReceived');
};
}, [openAI]);

// Create a new completion
func ask(question: string) {
openAI.createCompletion(question);
openAI.chat.stream({
messages: [
{
role: 'user',
content: question,
},
],
model: 'gpt-3.5-turbo',
});
}
```
## Contributing
Join our [Discord](https://discord.gg/qnAgjxhg6n) and ask questions in the **#dev** channel.
Join our [Discord](https://discord.gg/qnAgjxhg6n) and ask questions in the **#oss** channel.
## License
Expand Down
9 changes: 5 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ReactNativeOpenai_kotlinVersion"]
def kotlin_version = '1.9.0'

repositories {
google()
Expand All @@ -21,7 +21,6 @@ def isNewArchitectureEnabled() {
apply plugin: "com.android.library"
apply plugin: "kotlin-android"


def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }

if (isNewArchitectureEnabled()) {
Expand Down Expand Up @@ -78,8 +77,8 @@ android {
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

}
Expand All @@ -97,6 +96,8 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.aallam.openai:openai-client:3.5.0"
implementation 'io.ktor:ktor-client-android:+'
}

if (isNewArchitectureEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
package com.candlefinance.reactnativeopenai

import android.provider.ContactsContract.CommonDataKinds.Organization
import com.aallam.openai.api.chat.ChatCompletion
import com.aallam.openai.api.chat.ChatCompletionChunk
import com.aallam.openai.api.chat.ChatCompletionRequest
import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.api.chat.ChatRole
import com.aallam.openai.api.http.Timeout
import com.aallam.openai.api.model.ModelId
import com.aallam.openai.client.OpenAI
import com.aallam.openai.client.OpenAIConfig
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JsonNull.content
import org.json.JSONObject
import java.util.HashMap
import kotlin.time.Duration.Companion.seconds

class ReactNativeOpenaiModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
Expand All @@ -12,14 +34,104 @@ class ReactNativeOpenaiModule(reactContext: ReactApplicationContext) :
return NAME
}

// Example method
// See https://reactnative.dev/docs/native-modules-android
companion object {
const val onChatMessageReceived = "onChatMessageReceived"
const val NAME = "ReactNativeOpenai"
}

override fun getConstants() = mapOf(
"onChatMessageReceived" to onChatMessageReceived
)

@ReactMethod
fun multiply(a: Double, b: Double, promise: Promise) {
promise.resolve(a * b)
fun addListener(eventName: String?) {
// Keep: Required for RN built in Event Emitter Calls.
println("Event is called for $eventName")
}

companion object {
const val NAME = "ReactNativeOpenai"
@ReactMethod
fun removeListeners(count: Int?) {
// Keep: Required for RN built in Event Emitter Calls.
println("Remove event is called for $count")
}

private var openAIClient: OpenAI? = null

@ReactMethod
fun initialize(apiKey: String, organization: String) {
println("Initializing client with $apiKey and org $organization")
val config = OpenAIConfig(
token = apiKey,
organization = organization,
timeout = Timeout(socket = 60.seconds)
)
this.openAIClient = OpenAI(config)
}

@ReactMethod
fun stream(input: ReadableMap) {
val model = input.getString("model")
val messages = input.getArray("messages")
val temperature = if (input.hasKey("temperature")) input.getDouble("temperature") else null
val topP = if (input.hasKey("topP")) input.getDouble("topP") else null
val n = if (input.hasKey("n")) input.getInt("n") else null
val stops = if (input.hasKey("stops")) input.getArray("stops") else null
val maxTokens = if (input.hasKey("maxTokens")) input.getInt("maxTokens") else null
val presencePenalty = if (input.hasKey("presencePenalty")) input.getDouble("presencePenalty") else null
val frequencyPenalty = if (input.hasKey("frequencyPenalty")) input.getDouble("frequencyPenalty") else null
val logitBias = if (input.hasKey("logitBias")) input.getMap("logitBias") else null
val user = if (input.hasKey("user")) input.getString("user") else null
val m = messages?.toArrayList()?.map { it ->
val role: String = (it as HashMap<String, String>).get("role") ?: "user"
val content: String = it.get("content") as String
ChatMessage(
role = ChatRole(role),
content = content
)
} ?: emptyList()

val chatCompletionRequest = ChatCompletionRequest(
model = ModelId(model ?: "gpt-3.5-turbo"),
messages = m,
maxTokens = maxTokens,
temperature = temperature,
topP = topP,
n = n,
presencePenalty = presencePenalty,
frequencyPenalty = frequencyPenalty,
user = user,
)
runBlocking {
val completion: Flow<ChatCompletionChunk>? = openAIClient?.chatCompletions(chatCompletionRequest)
completion?.map { it }?.collect {
println("completion ${it.choices}")
val map = mapOf(
"id" to it.id,
"created" to it.created,
"model" to (it.model?.id ?: "$model"),
"choices" to (it.choices?.map {
mapOf(
"delta" to mapOf(
"content" to it.delta.content,
"role" to it.delta.role.toString()
),
"index" to it.index,
"finishReason" to (it.finishReason?.value ?: "stop")
)
} ?: {}),
)
dispatch(onChatMessageReceived, map)
}
}
}

fun dispatch(action: String, payload: Map<String, Any?>) {
val map = mapOf(
"type" to action,
"payload" to JSONObject(payload).toString()
)
val event: WritableMap = Arguments.makeNativeMap(map)
reactApplicationContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(onChatMessageReceived, event)
}
}
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 33
compileSdkVersion = 34
targetSdkVersion = 33

// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
Expand Down
2 changes: 1 addition & 1 deletion example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ newArchEnabled=false

# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
hermesEnabled=false
57 changes: 45 additions & 12 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,67 @@
import * as React from 'react';

import {
StyleSheet,
Text,
TextInput,
View,
useColorScheme,
} from 'react-native';
import OpenAI from 'react-native-openai';
import { StyleSheet, Text, TextInput, View } from 'react-native';

export default function App() {
const scheme = useColorScheme();
const [result, setResult] = React.useState<string>('');
const ai = React.useMemo(() => new OpenAI('', ''), []);
const openAI = React.useMemo(() => new OpenAI('', ''), []);

React.useEffect(() => {
ai.addListener('onMessageReceived', (event) => {
console.log(event);
setResult((message) => message + event.payload?.message);
openAI.chat.addListener('onChatMessageReceived', (payload) => {
setResult((message) => {
const newMessage = payload.choices[0]?.delta.content;
if (newMessage) {
return message + newMessage;
}
return message;
});
});

return () => {
ai.removeListener('onMessageReceived');
openAI.chat.removeListener('onChatMessageReceived');
};
}, [ai]);
}, [openAI]);

return (
<View style={styles.container}>
<View
style={[
styles.container,
{ backgroundColor: scheme === 'dark' ? 'black' : 'white' },
]}
>
<TextInput
placeholder="Ask me a question."
onEndEditing={(e) => {
onEndEditing={async (e) => {
console.log(e.nativeEvent.text);
ai.createCompletion(e.nativeEvent.text);
openAI.chat.stream({
messages: [
{
role: 'user',
content: e.nativeEvent.text,
},
],
model: 'gpt-3.5-turbo',
});
}}
style={styles.input}
style={[
styles.input,
{
color: scheme === 'dark' ? 'white' : 'black',
backgroundColor: scheme === 'dark' ? 'black' : 'white',
},
]}
/>
<Text>Result: {result}</Text>
<Text style={{ color: scheme === 'dark' ? 'white' : 'black' }}>
Result: {result}
</Text>
</View>
);
}
Expand Down
3 changes: 1 addition & 2 deletions ios/ReactNativeOpenai.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,5 @@ extension ReactNativeOpenai {
reject("error", "error", error)
}
}
}

}
}

0 comments on commit f581c24

Please sign in to comment.