Skip to content

Commit 7347bc1

Browse files
authored
Merge pull request #87 from functionland/ChatWithAI
Chat with ai
2 parents 4c79658 + fc16974 commit 7347bc1

File tree

9 files changed

+294
-11
lines changed

9 files changed

+294
-11
lines changed

android/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ android {
8686
repositories {
8787
mavenLocal()
8888
google()
89-
jcenter()
9089
mavenCentral()
9190

9291
// Add this repo to get go-fula package
@@ -99,10 +98,10 @@ dependencies {
9998
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
10099
//noinspection GradleDynamicVersion
101100
implementation "com.facebook.react:react-android:+"
102-
implementation 'com.github.functionland:fula-build-aar:v1.55.0' // From jitpack.io
101+
implementation 'com.github.functionland:fula-build-aar:v1.55.9' // From jitpack.io
103102
implementation 'com.github.functionland:wnfs-android:v1.8.2' // From jitpack.io
104103
implementation 'commons-io:commons-io:20030203.000550'
105-
implementation 'commons-codec:commons-codec:1.15'
104+
implementation 'commons-codec:commons-codec:1.16.0'
106105
// implementation files('mobile.aar')
107106
}
108107

android/src/main/java/land/fx/fula/FulaModule.java

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
import androidx.annotation.NonNull;
66

7+
import android.os.Handler;
8+
import android.os.Looper;
9+
10+
711
import com.facebook.react.bridge.Promise;
12+
import com.facebook.react.bridge.Callback;
813
import com.facebook.react.bridge.ReactApplicationContext;
914
import com.facebook.react.bridge.ReactContextBaseJavaModule;
1015
import com.facebook.react.bridge.ReactMethod;
@@ -15,6 +20,7 @@
1520
import com.facebook.react.bridge.WritableArray;
1621
import com.facebook.react.bridge.ReadableArray;
1722
import com.facebook.react.bridge.LifecycleEventListener;
23+
import com.facebook.react.modules.core.DeviceEventManagerModule;
1824

1925

2026
import org.apache.commons.io.FileUtils;
@@ -63,6 +69,7 @@ public void initialize() {
6369

6470

6571
public static final String NAME = "FulaModule";
72+
private final ReactApplicationContext reactContext;
6673
fulamobile.Client fula;
6774

6875
Client client;
@@ -117,6 +124,7 @@ public byte[] put(@NonNull byte[] cid, byte[] data) {
117124

118125
public FulaModule(ReactApplicationContext reactContext) {
119126
super(reactContext);
127+
this.reactContext = reactContext;
120128
appName = reactContext.getPackageName();
121129
appDir = reactContext.getFilesDir().toString();
122130
fulaStorePath = appDir + "/fula";
@@ -1865,4 +1873,119 @@ public void updatePlugin(String pluginName, Promise promise) {
18651873
});
18661874
}
18671875

1876+
// AI
1877+
@ReactMethod
1878+
public void chatWithAI(String aiModel, String userMessage, Promise promise) {
1879+
ThreadUtils.runOnExecutor(() -> {
1880+
Log.d("ReactNative", "chatWithAI: aiModel = " + aiModel + ", userMessage = " + userMessage);
1881+
try {
1882+
// Call the Go Mobile method, which returns a byte[]
1883+
byte[] streamIDBytes = this.fula.chatWithAI(aiModel, userMessage);
1884+
1885+
// Convert byte[] to String (assuming UTF-8 encoding)
1886+
String streamID = new String(streamIDBytes, "UTF-8");
1887+
1888+
// Resolve the promise with the stream ID
1889+
promise.resolve(streamID);
1890+
} catch (Exception e) {
1891+
Log.d("ReactNative", "ERROR in chatWithAI: " + e.getMessage());
1892+
promise.reject(e); // Reject the promise with the error
1893+
}
1894+
});
1895+
}
1896+
1897+
@ReactMethod
1898+
public void getChatChunk(String streamID, Promise promise) {
1899+
ThreadUtils.runOnExecutor(() -> {
1900+
Log.d("ReactNative", "getChatChunk: streamID = " + streamID);
1901+
try {
1902+
// Call the Go Mobile method, which returns a String
1903+
String chunk = this.fula.getChatChunk(streamID);
1904+
1905+
// Handle null or empty response
1906+
if (chunk == null || chunk.isEmpty()) {
1907+
Log.d("ReactNative", "getChatChunk: No data received for streamID = " + streamID);
1908+
promise.resolve(""); // Resolve with an empty string
1909+
return;
1910+
}
1911+
1912+
// Resolve the promise with the chunk of data
1913+
Log.d("ReactNative", "getChatChunk: Successfully received chunk for streamID = " + streamID);
1914+
promise.resolve(chunk);
1915+
} catch (Exception e) {
1916+
// Log and reject the promise with the error
1917+
Log.d("ReactNative", "ERROR in getChatChunk: " + e.getMessage());
1918+
promise.reject(e);
1919+
}
1920+
});
1921+
}
1922+
1923+
@ReactMethod
1924+
public void streamChunks(String streamID, Promise promise) {
1925+
if (streamID == null || streamID.trim().isEmpty()) {
1926+
promise.reject("INVALID_ARGUMENT", "streamID cannot be null or empty");
1927+
return;
1928+
}
1929+
1930+
ThreadUtils.runOnExecutor(() -> {
1931+
try {
1932+
fulamobile.StreamIterator iterator = this.fula.getStreamIterator(streamID);
1933+
1934+
if (iterator == null) {
1935+
promise.reject("STREAM_ITERATOR_ERROR", "Failed to create StreamIterator");
1936+
return;
1937+
}
1938+
1939+
// Start listening for chunks
1940+
new Handler(Looper.getMainLooper()).post(() ->
1941+
pollIterator(iterator, promise)
1942+
);
1943+
} catch (Exception e) {
1944+
promise.reject("STREAM_ERROR", e.getMessage(), e);
1945+
}
1946+
});
1947+
}
1948+
1949+
private void pollIterator(fulamobile.StreamIterator iterator, Promise promise) {
1950+
try {
1951+
String chunk = iterator.next();
1952+
if (chunk != null && !chunk.trim().isEmpty()) {
1953+
emitEvent("onChunkReceived", chunk);
1954+
}
1955+
1956+
if (iterator.isComplete()) {
1957+
emitEvent("onStreamingCompleted", null);
1958+
promise.resolve(null);
1959+
} else {
1960+
new Handler(Looper.getMainLooper()).postDelayed(() ->
1961+
pollIterator(iterator, promise)
1962+
, 50); // Reduced delay for better responsiveness
1963+
}
1964+
} catch (Exception e) {
1965+
if (e.getMessage() != null && e.getMessage().contains("EOF")) {
1966+
emitEvent("onStreamingCompleted", null);
1967+
promise.resolve(null);
1968+
} else if (e.getMessage() != null && e.getMessage().contains("timeout")) {
1969+
// Retry on timeout
1970+
new Handler(Looper.getMainLooper()).post(() ->
1971+
pollIterator(iterator, promise)
1972+
);
1973+
} else {
1974+
emitEvent("onStreamError", e.getMessage());
1975+
promise.reject("STREAM_ERROR", e.getMessage(), e);
1976+
}
1977+
}
1978+
}
1979+
1980+
private void emitEvent(String eventName, String data) {
1981+
try {
1982+
getReactApplicationContext()
1983+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
1984+
.emit(eventName, data);
1985+
} catch (Exception e) {
1986+
Log.e("ReactNative", "Error emitting event: " + eventName, e);
1987+
}
1988+
}
1989+
1990+
18681991
}

example/src/App.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React, { useEffect } from 'react';
22
import { StyleSheet, ScrollView, View, Button } from 'react-native';
3-
import { updatePlugin, installPlugin, listPlugins, getInstallOutput, getInstallStatus } from '../../src/protocols/fxblox';
43

54
import {
65
fula,
76
blockchain,
87
chainApi,
98
fxblox,
9+
fxAi,
1010
} from '@functionland/react-native-fula';
1111
const App = () => {
1212
const inprogress = false;
@@ -65,13 +65,13 @@ const App = () => {
6565
153, 106, 217, 201, 106, 9, 66, 33, 214, 195, 255, 234, 178, 244, 203, 112,
6666
62, 91, 140, 55, 179, 10, 208, 210, 177, 111, 61, 46, 73, 148, 14, 62,
6767
];
68-
// const bloxPeerId = '12D3KooWACVcVsQh18jM9UudRQzeYEjxCJQJgFUaAgs41tayjxC4'; //tower
69-
const bloxPeerId = '12D3KooWDaT8gS2zGMLGBKmW1mKhQSHxYeEX3Fr3VSjuPzmjyfZC'; //laptop
68+
const bloxPeerId = '12D3KooWRadeHPBedP633MMVZjbVni5XDxzhGDXXMpDgC29vuhLB'; //tower
69+
// const bloxPeerId = '12D3KooWDaT8gS2zGMLGBKmW1mKhQSHxYeEX3Fr3VSjuPzmjyfZC'; //laptop
7070
// const bloxPeerId = '12D3KooWQZBdE5zNUVTE2Aayajyy9cJDmK4bJwMZG52ieHt2f6nb'; //laptop2
7171
//const bloxPeerId = '12D3KooWAN5FaAnC4d1GhAvoYxyUXdrkCGqux1NB6Pr4cZXn813E'; //test aws server
7272

7373
const bloxAddr = '/dns/relay.dev.fx.land/tcp/4001/p2p/12D3KooWDRrBaAfPwsGJivBoUw5fE7ZpDiyfUjqgiURq2DEcL835/p2p-circuit/p2p/' + bloxPeerId;
74-
// const bloxAddr = '/ip4/192.168.2.14/tcp/40001/p2p/' + bloxPeerId; // /ip4/192.168.2.14/tcp/40001/p2p/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK78Q
74+
//const bloxAddr = '/ip4/192.168.2.139/tcp/40001/p2p/' + bloxPeerId; // /ip4/192.168.2.14/tcp/40001/p2p/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK78Q
7575
//const bloxAddr = '/dns4/1.pools.test.fula.network/tcp/40001/p2p/12D3KooWHb38UxY8akVGWZBuFtS3NJ7rJUwd36t3cfkoY7EbgNt9';
7676
const initFula = async () => {
7777
try {
@@ -1266,6 +1266,48 @@ const App = () => {
12661266
color={inprogress ? 'green' : 'blue'}
12671267
/>
12681268
</View>
1269+
1270+
<View style={styles.section}>
1271+
<Button
1272+
title={inprogress ? 'Getting...' : 'Test Chat with AI'}
1273+
onPress={async () => {
1274+
try {
1275+
if (initComplete) {
1276+
// Step 1: Check connection to Blox
1277+
const isConnected = await fula.checkConnection();
1278+
console.log('Connection check:', isConnected);
1279+
1280+
if (isConnected) {
1281+
console.log('Initialization is completed. Starting ChatWithAI...');
1282+
1283+
// Step 2: Start Chat with AI
1284+
try {
1285+
const streamID = await fxAi.chatWithAI('deepseek-chart', 'Hello AI!');
1286+
console.log('ChatWithAI started, Stream ID:', streamID);
1287+
1288+
// Step 3: Fetch streamed responses using iterator
1289+
const fullResponse = await fxAi.fetchChunksUsingIterator(streamID);
1290+
1291+
console.log('Full Response:', fullResponse); // Log the full response after all chunks are received
1292+
console.log('All chunks received.');
1293+
} catch (startError) {
1294+
console.error('Error starting ChatWithAI:', startError);
1295+
}
1296+
} else {
1297+
console.log('Connection to Blox failed. Please check your connection.');
1298+
}
1299+
} else {
1300+
console.log('Wait for initialization to complete.');
1301+
}
1302+
} catch (e) {
1303+
console.error('Unexpected error:', e);
1304+
}
1305+
}}
1306+
color={inprogress ? 'green' : 'blue'}
1307+
/>
1308+
</View>
1309+
1310+
12691311
</ScrollView>
12701312
);
12711313
};

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@functionland/react-native-fula",
3-
"version": "1.55.0",
3+
"version": "1.55.1",
44
"description": "This package is a bridge to use the Fula libp2p protocols in the react-native which is using wnfs",
55
"type": "module",
66
"main": "lib/commonjs/index",

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * as fula from './protocols/fula';
22
export * as blockchain from './protocols/blockchain';
33
export * as chainApi from './protocols/chain-api';
44
export * as fxblox from './protocols/fxblox';
5+
export * as fxAi from './protocols/fx-ai';

src/interfaces/fulaNativeModule.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ interface FulaNativeModule {
146146
getInstallStatus: (pluginName: string) => Promise<string>;
147147
updatePlugin: (pluginName: string) => Promise<string>;
148148
deleteDsLock: () => Promise<void>;
149+
150+
//AI
151+
chatWithAI: (aiModel: string, userMessage: string) => Promise<string>;
152+
getChatChunk: (streamID: string) => Promise<string>;
153+
streamChunks: (streamID: string) => Promise<void>;
154+
155+
149156
}
150157

151158
const LINKING_ERROR =

0 commit comments

Comments
 (0)