@@ -2058,6 +2058,149 @@ func replicateInPool(cidArray: [String], account: String, poolID: String, resolv
20582058 }
20592059}
20602060
2061+ // AI Chat methods
2062+
2063+ @objc ( chatWithAI: withUserMessage: withResolver: withRejecter: )
2064+ func chatWithAI( aiModel: String , userMessage: String , resolve: @escaping RCTPromiseResolveBlock , reject: @escaping RCTPromiseRejectBlock ) -> Void {
2065+ NSLog ( " ReactNative chatWithAI: aiModel = \( aiModel) , userMessage = \( userMessage) " )
2066+ DispatchQueue . global ( qos: . default) . async {
2067+ do {
2068+ guard let fula = self . fula else {
2069+ throw MyError . runtimeError ( " ReactNative Fula client is not initialized " )
2070+ }
2071+
2072+ // Call the Go Mobile method, which returns Data
2073+ let streamIDData = try fula. chatWithAI ( aiModel, userMessage: userMessage)
2074+
2075+ // Convert Data to String (assuming UTF-8 encoding)
2076+ guard let streamID = String ( data: streamIDData, encoding: . utf8) else {
2077+ throw MyError . runtimeError ( " Failed to convert stream ID to string " )
2078+ }
2079+
2080+ // Resolve the promise with the stream ID
2081+ DispatchQueue . main. async {
2082+ resolve ( streamID)
2083+ }
2084+ } catch let error {
2085+ NSLog ( " ReactNative ERROR in chatWithAI: \( error. localizedDescription) " )
2086+ DispatchQueue . main. async {
2087+ reject ( " ERR_CHAT_WITH_AI " , " Chat with AI failed " , error)
2088+ }
2089+ }
2090+ }
2091+ }
2092+
2093+ @objc ( getChatChunk: withResolver: withRejecter: )
2094+ func getChatChunk( streamID: String , resolve: @escaping RCTPromiseResolveBlock , reject: @escaping RCTPromiseRejectBlock ) -> Void {
2095+ NSLog ( " ReactNative getChatChunk: streamID = \( streamID) " )
2096+ DispatchQueue . global ( qos: . default) . async {
2097+ do {
2098+ guard let fula = self . fula else {
2099+ throw MyError . runtimeError ( " ReactNative Fula client is not initialized " )
2100+ }
2101+
2102+ // Call the Go Mobile method, which returns a String
2103+ let chunk = try fula. getChatChunk ( streamID)
2104+
2105+ // Handle null or empty response
2106+ if chunk. isEmpty {
2107+ NSLog ( " ReactNative getChatChunk: No data received for streamID = \( streamID) " )
2108+ DispatchQueue . main. async {
2109+ resolve ( " " ) // Resolve with an empty string
2110+ }
2111+ return
2112+ }
2113+
2114+ // Resolve the promise with the chunk of data
2115+ NSLog ( " ReactNative getChatChunk: Successfully received chunk for streamID = \( streamID) " )
2116+ DispatchQueue . main. async {
2117+ resolve ( chunk)
2118+ }
2119+ } catch let error {
2120+ // Log and reject the promise with the error
2121+ NSLog ( " ReactNative ERROR in getChatChunk: \( error. localizedDescription) " )
2122+ DispatchQueue . main. async {
2123+ reject ( " ERR_GET_CHAT_CHUNK " , " Get chat chunk failed " , error)
2124+ }
2125+ }
2126+ }
2127+ }
2128+
2129+ @objc ( streamChunks: withResolver: withRejecter: )
2130+ func streamChunks( streamID: String , resolve: @escaping RCTPromiseResolveBlock , reject: @escaping RCTPromiseRejectBlock ) -> Void {
2131+ if streamID. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
2132+ reject ( " INVALID_ARGUMENT " , " streamID cannot be null or empty " , nil )
2133+ return
2134+ }
2135+
2136+ DispatchQueue . global ( qos: . default) . async {
2137+ do {
2138+ guard let fula = self . fula else {
2139+ throw MyError . runtimeError ( " ReactNative Fula client is not initialized " )
2140+ }
2141+
2142+ guard let iterator = try fula. getStreamIterator ( streamID) else {
2143+ throw MyError . runtimeError ( " Failed to create StreamIterator " )
2144+ }
2145+
2146+ // Start listening for chunks on the main thread
2147+ DispatchQueue . main. async {
2148+ self . pollIterator ( iterator: iterator, resolve: resolve, reject: reject)
2149+ }
2150+ } catch let error {
2151+ NSLog ( " ReactNative ERROR in streamChunks: \( error. localizedDescription) " )
2152+ DispatchQueue . main. async {
2153+ reject ( " STREAM_ERROR " , " Stream error " , error)
2154+ }
2155+ }
2156+ }
2157+ }
2158+
2159+ private func pollIterator( iterator: FulamobileStreamIterator , resolve: @escaping RCTPromiseResolveBlock , reject: @escaping RCTPromiseRejectBlock ) {
2160+ do {
2161+ let chunk = try iterator. next ( )
2162+ if !chunk. trimmingCharacters ( in: . whitespacesAndNewlines) . isEmpty {
2163+ self . emitEvent ( eventName: " onChunkReceived " , data: chunk)
2164+ }
2165+
2166+ if iterator. isComplete ( ) {
2167+ self . emitEvent ( eventName: " onStreamingCompleted " , data: nil )
2168+ resolve ( nil )
2169+ } else {
2170+ // Schedule another poll with a short delay
2171+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.05 ) { // 50ms delay for better responsiveness
2172+ self . pollIterator ( iterator: iterator, resolve: resolve, reject: reject)
2173+ }
2174+ }
2175+ } catch let error {
2176+ let errorMessage = error. localizedDescription
2177+ if errorMessage. contains ( " EOF " ) {
2178+ self . emitEvent ( eventName: " onStreamingCompleted " , data: nil )
2179+ resolve ( nil )
2180+ } else if errorMessage. contains ( " timeout " ) {
2181+ // Retry on timeout
2182+ DispatchQueue . main. async {
2183+ self . pollIterator ( iterator: iterator, resolve: resolve, reject: reject)
2184+ }
2185+ } else {
2186+ self . emitEvent ( eventName: " onStreamError " , data: errorMessage)
2187+ reject ( " STREAM_ERROR " , errorMessage, error)
2188+ }
2189+ }
2190+ }
2191+
2192+ private func emitEvent( eventName: String , data: String ? ) {
2193+ do {
2194+ guard let eventEmitter = RCTBridge . current ( ) ? . eventDispatcher ( ) else {
2195+ NSLog ( " ReactNative ERROR: Could not get event dispatcher " )
2196+ return
2197+ }
2198+
2199+ eventEmitter. sendAppEvent ( withName: eventName, body: data)
2200+ } catch {
2201+ NSLog ( " ReactNative Error emitting event: \( eventName) , \( error. localizedDescription) " )
2202+ }
2203+ }
20612204
20622205}
20632206
0 commit comments