Skip to content
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
5 changes: 2 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ android {
repositories {
mavenLocal()
google()
jcenter()
mavenCentral()

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

Expand Down
123 changes: 123 additions & 0 deletions android/src/main/java/land/fx/fula/FulaModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

import androidx.annotation.NonNull;

import android.os.Handler;
import android.os.Looper;


import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
Expand All @@ -15,6 +20,7 @@
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.modules.core.DeviceEventManagerModule;


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


public static final String NAME = "FulaModule";
private final ReactApplicationContext reactContext;
fulamobile.Client fula;

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

public FulaModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
appName = reactContext.getPackageName();
appDir = reactContext.getFilesDir().toString();
fulaStorePath = appDir + "/fula";
Expand Down Expand Up @@ -1865,4 +1873,119 @@ public void updatePlugin(String pluginName, Promise promise) {
});
}

// AI
@ReactMethod
public void chatWithAI(String aiModel, String userMessage, Promise promise) {
ThreadUtils.runOnExecutor(() -> {
Log.d("ReactNative", "chatWithAI: aiModel = " + aiModel + ", userMessage = " + userMessage);
try {
// Call the Go Mobile method, which returns a byte[]
byte[] streamIDBytes = this.fula.chatWithAI(aiModel, userMessage);

// Convert byte[] to String (assuming UTF-8 encoding)
String streamID = new String(streamIDBytes, "UTF-8");

// Resolve the promise with the stream ID
promise.resolve(streamID);
} catch (Exception e) {
Log.d("ReactNative", "ERROR in chatWithAI: " + e.getMessage());
promise.reject(e); // Reject the promise with the error
}
});
}

@ReactMethod
public void getChatChunk(String streamID, Promise promise) {
ThreadUtils.runOnExecutor(() -> {
Log.d("ReactNative", "getChatChunk: streamID = " + streamID);
try {
// Call the Go Mobile method, which returns a String
String chunk = this.fula.getChatChunk(streamID);

// Handle null or empty response
if (chunk == null || chunk.isEmpty()) {
Log.d("ReactNative", "getChatChunk: No data received for streamID = " + streamID);
promise.resolve(""); // Resolve with an empty string
return;
}

// Resolve the promise with the chunk of data
Log.d("ReactNative", "getChatChunk: Successfully received chunk for streamID = " + streamID);
promise.resolve(chunk);
} catch (Exception e) {
// Log and reject the promise with the error
Log.d("ReactNative", "ERROR in getChatChunk: " + e.getMessage());
promise.reject(e);
}
});
}

@ReactMethod
public void streamChunks(String streamID, Promise promise) {
if (streamID == null || streamID.trim().isEmpty()) {
promise.reject("INVALID_ARGUMENT", "streamID cannot be null or empty");
return;
}

ThreadUtils.runOnExecutor(() -> {
try {
fulamobile.StreamIterator iterator = this.fula.getStreamIterator(streamID);

if (iterator == null) {
promise.reject("STREAM_ITERATOR_ERROR", "Failed to create StreamIterator");
return;
}

// Start listening for chunks
new Handler(Looper.getMainLooper()).post(() ->
pollIterator(iterator, promise)
);
} catch (Exception e) {
promise.reject("STREAM_ERROR", e.getMessage(), e);
}
});
}

private void pollIterator(fulamobile.StreamIterator iterator, Promise promise) {
try {
String chunk = iterator.next();
if (chunk != null && !chunk.trim().isEmpty()) {
emitEvent("onChunkReceived", chunk);
}

if (iterator.isComplete()) {
emitEvent("onStreamingCompleted", null);
promise.resolve(null);
} else {
new Handler(Looper.getMainLooper()).postDelayed(() ->
pollIterator(iterator, promise)
, 50); // Reduced delay for better responsiveness
}
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("EOF")) {
emitEvent("onStreamingCompleted", null);
promise.resolve(null);
} else if (e.getMessage() != null && e.getMessage().contains("timeout")) {
// Retry on timeout
new Handler(Looper.getMainLooper()).post(() ->
pollIterator(iterator, promise)
);
} else {
emitEvent("onStreamError", e.getMessage());
promise.reject("STREAM_ERROR", e.getMessage(), e);
}
}
}

private void emitEvent(String eventName, String data) {
try {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
} catch (Exception e) {
Log.e("ReactNative", "Error emitting event: " + eventName, e);
}
}


}
50 changes: 46 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect } from 'react';
import { StyleSheet, ScrollView, View, Button } from 'react-native';
import { updatePlugin, installPlugin, listPlugins, getInstallOutput, getInstallStatus } from '../../src/protocols/fxblox';

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

const bloxAddr = '/dns/relay.dev.fx.land/tcp/4001/p2p/12D3KooWDRrBaAfPwsGJivBoUw5fE7ZpDiyfUjqgiURq2DEcL835/p2p-circuit/p2p/' + bloxPeerId;
// const bloxAddr = '/ip4/192.168.2.14/tcp/40001/p2p/' + bloxPeerId; // /ip4/192.168.2.14/tcp/40001/p2p/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK78Q
//const bloxAddr = '/ip4/192.168.2.139/tcp/40001/p2p/' + bloxPeerId; // /ip4/192.168.2.14/tcp/40001/p2p/12D3KooWRTzN7HfmjoUBHokyRZuKdyohVVSGqKBMF24ZC3tGK78Q
//const bloxAddr = '/dns4/1.pools.test.fula.network/tcp/40001/p2p/12D3KooWHb38UxY8akVGWZBuFtS3NJ7rJUwd36t3cfkoY7EbgNt9';
const initFula = async () => {
try {
Expand Down Expand Up @@ -1266,6 +1266,48 @@ const App = () => {
color={inprogress ? 'green' : 'blue'}
/>
</View>

<View style={styles.section}>
<Button
title={inprogress ? 'Getting...' : 'Test Chat with AI'}
onPress={async () => {
try {
if (initComplete) {
// Step 1: Check connection to Blox
const isConnected = await fula.checkConnection();
console.log('Connection check:', isConnected);

if (isConnected) {
console.log('Initialization is completed. Starting ChatWithAI...');

// Step 2: Start Chat with AI
try {
const streamID = await fxAi.chatWithAI('deepseek-chart', 'Hello AI!');
console.log('ChatWithAI started, Stream ID:', streamID);

// Step 3: Fetch streamed responses using iterator
const fullResponse = await fxAi.fetchChunksUsingIterator(streamID);

console.log('Full Response:', fullResponse); // Log the full response after all chunks are received
console.log('All chunks received.');
} catch (startError) {
console.error('Error starting ChatWithAI:', startError);
}
} else {
console.log('Connection to Blox failed. Please check your connection.');
}
} else {
console.log('Wait for initialization to complete.');
}
} catch (e) {
console.error('Unexpected error:', e);
}
}}
color={inprogress ? 'green' : 'blue'}
/>
</View>


</ScrollView>
);
};
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@functionland/react-native-fula",
"version": "1.55.0",
"version": "1.55.1",
"description": "This package is a bridge to use the Fula libp2p protocols in the react-native which is using wnfs",
"type": "module",
"main": "lib/commonjs/index",
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * as fula from './protocols/fula';
export * as blockchain from './protocols/blockchain';
export * as chainApi from './protocols/chain-api';
export * as fxblox from './protocols/fxblox';
export * as fxAi from './protocols/fx-ai';
7 changes: 7 additions & 0 deletions src/interfaces/fulaNativeModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ interface FulaNativeModule {
getInstallStatus: (pluginName: string) => Promise<string>;
updatePlugin: (pluginName: string) => Promise<string>;
deleteDsLock: () => Promise<void>;

//AI
chatWithAI: (aiModel: string, userMessage: string) => Promise<string>;
getChatChunk: (streamID: string) => Promise<string>;
streamChunks: (streamID: string) => Promise<void>;


}

const LINKING_ERROR =
Expand Down
Loading