|
4 | 4 |
|
5 | 5 | import androidx.annotation.NonNull;
|
6 | 6 |
|
| 7 | +import android.os.Handler; |
| 8 | +import android.os.Looper; |
| 9 | + |
| 10 | + |
7 | 11 | import com.facebook.react.bridge.Promise;
|
| 12 | +import com.facebook.react.bridge.Callback; |
8 | 13 | import com.facebook.react.bridge.ReactApplicationContext;
|
9 | 14 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
10 | 15 | import com.facebook.react.bridge.ReactMethod;
|
|
15 | 20 | import com.facebook.react.bridge.WritableArray;
|
16 | 21 | import com.facebook.react.bridge.ReadableArray;
|
17 | 22 | import com.facebook.react.bridge.LifecycleEventListener;
|
| 23 | +import com.facebook.react.modules.core.DeviceEventManagerModule; |
18 | 24 |
|
19 | 25 |
|
20 | 26 | import org.apache.commons.io.FileUtils;
|
@@ -63,6 +69,7 @@ public void initialize() {
|
63 | 69 |
|
64 | 70 |
|
65 | 71 | public static final String NAME = "FulaModule";
|
| 72 | + private final ReactApplicationContext reactContext; |
66 | 73 | fulamobile.Client fula;
|
67 | 74 |
|
68 | 75 | Client client;
|
@@ -117,6 +124,7 @@ public byte[] put(@NonNull byte[] cid, byte[] data) {
|
117 | 124 |
|
118 | 125 | public FulaModule(ReactApplicationContext reactContext) {
|
119 | 126 | super(reactContext);
|
| 127 | + this.reactContext = reactContext; |
120 | 128 | appName = reactContext.getPackageName();
|
121 | 129 | appDir = reactContext.getFilesDir().toString();
|
122 | 130 | fulaStorePath = appDir + "/fula";
|
@@ -1865,4 +1873,119 @@ public void updatePlugin(String pluginName, Promise promise) {
|
1865 | 1873 | });
|
1866 | 1874 | }
|
1867 | 1875 |
|
| 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 | + |
1868 | 1991 | }
|
0 commit comments