Skip to content

Commit 76ab788

Browse files
committed
added ios methods for AI
1 parent 63e42ce commit 76ab788

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

ios/Fula.mm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,20 @@ @interface RCT_EXTERN_MODULE(FulaModule, NSObject)
240240
withResolver:(RCTPromiseResolveBlock)resolve
241241
withRejecter:(RCTPromiseRejectBlock)reject)
242242

243+
// AI-related method declarations
244+
RCT_EXTERN_METHOD(chatWithAI:(NSString *)aiModel
245+
withUserMessage:(NSString *)userMessage
246+
withResolver:(RCTPromiseResolveBlock)resolve
247+
withRejecter:(RCTPromiseRejectBlock)reject)
248+
249+
RCT_EXTERN_METHOD(getChatChunk:(NSString *)streamID
250+
withResolver:(RCTPromiseResolveBlock)resolve
251+
withRejecter:(RCTPromiseRejectBlock)reject)
252+
253+
RCT_EXTERN_METHOD(streamChunks:(NSString *)streamID
254+
withResolver:(RCTPromiseResolveBlock)resolve
255+
withRejecter:(RCTPromiseRejectBlock)reject)
256+
243257
+ (BOOL)requiresMainQueueSetup
244258
{
245259
return NO;

ios/Fula.swift

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)