@@ -10,6 +10,9 @@
1010#import <UserNotifications/UserNotifications.h>
1111#import <MapKit/MapKit.h>
1212#import <Quartz/Quartz.h>
13+#import <Vision/Vision.h>
14+#import <Speech/Speech.h>
15+#import <NaturalLanguage/NaturalLanguage.h>
1316#import <objc/message.h>
1417@import RiveRuntime;
1518
@@ -3953,3 +3956,218 @@ RCT_EXPORT_METHOD(hide:(RCTPromiseResolveBlock)resolve
39533956}
39543957
39553958@end
3959+
3960+// ============================================================
3961+// 51. Vision OCR — MacOSOCRModule (imperative)
3962+// ============================================================
3963+
3964+@interface MacOSOCRModule : NSObject <RCTBridgeModule>
3965+@end
3966+
3967+@implementation MacOSOCRModule
3968+
3969+RCT_EXPORT_MODULE()
3970+
3971++ (BOOL)requiresMainQueueSetup { return NO; }
3972+
3973+RCT_EXPORT_METHOD(recognize:(NSString *)imagePath
3974+ resolver:(RCTPromiseResolveBlock)resolve
3975+ rejecter:(RCTPromiseRejectBlock)reject)
3976+{
3977+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
3978+ NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath];
3979+ if (!image) {
3980+ reject(@"NOT_FOUND", [NSString stringWithFormat:@"Image not found: %@", imagePath], nil);
3981+ return;
3982+ }
3983+
3984+ NSBitmapImageRep *rep = nil;
3985+ for (NSImageRep *r in image.representations) {
3986+ if ([r isKindOfClass:[NSBitmapImageRep class]]) {
3987+ rep = (NSBitmapImageRep *)r;
3988+ break;
3989+ }
3990+ }
3991+ if (!rep) {
3992+ // Create bitmap rep from image
3993+ CGImageRef cgRef = [image CGImageForProposedRect:NULL context:nil hints:nil];
3994+ if (!cgRef) {
3995+ reject(@"INVALID", @"Cannot create CGImage from file", nil);
3996+ return;
3997+ }
3998+ rep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
3999+ }
4000+
4001+ CGImageRef cgImage = rep.CGImage;
4002+ if (!cgImage) {
4003+ reject(@"INVALID", @"Cannot get CGImage from bitmap", nil);
4004+ return;
4005+ }
4006+
4007+ VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCGImage:cgImage options:@{}];
4008+
4009+ VNRecognizeTextRequest *request = [[VNRecognizeTextRequest alloc] initWithCompletionHandler:^(VNRequest *req, NSError *error) {
4010+ if (error) {
4011+ reject(@"OCR_FAIL", error.localizedDescription, error);
4012+ return;
4013+ }
4014+ NSMutableArray<NSString *> *lines = [NSMutableArray array];
4015+ for (VNRecognizedTextObservation *obs in req.results) {
4016+ VNRecognizedText *candidate = [[obs topCandidates:1] firstObject];
4017+ if (candidate.string.length > 0) {
4018+ [lines addObject:candidate.string];
4019+ }
4020+ }
4021+ resolve([lines componentsJoinedByString:@"\n"]);
4022+ }];
4023+ request.recognitionLevel = VNRequestTextRecognitionLevelAccurate;
4024+
4025+ NSError *err = nil;
4026+ [handler performRequests:@[request] error:&err];
4027+ if (err) {
4028+ reject(@"OCR_FAIL", err.localizedDescription, err);
4029+ }
4030+ });
4031+}
4032+
4033+@end
4034+
4035+// ============================================================
4036+// 52. SFSpeechRecognizer — MacOSSpeechRecognitionModule (imperative)
4037+// ============================================================
4038+
4039+@interface MacOSSpeechRecognitionModule : NSObject <RCTBridgeModule>
4040+@property (nonatomic, strong) SFSpeechRecognizer *recognizer;
4041+@property (nonatomic, strong) SFSpeechAudioBufferRecognitionRequest *recognitionRequest;
4042+@property (nonatomic, strong) SFSpeechRecognitionTask *recognitionTask;
4043+@property (nonatomic, strong) AVAudioEngine *audioEngine;
4044+@property (nonatomic, copy) NSString *latestTranscript;
4045+@end
4046+
4047+@implementation MacOSSpeechRecognitionModule
4048+
4049+RCT_EXPORT_MODULE()
4050+
4051++ (BOOL)requiresMainQueueSetup { return NO; }
4052+
4053+RCT_EXPORT_METHOD(start:(RCTPromiseResolveBlock)resolve
4054+ rejecter:(RCTPromiseRejectBlock)reject)
4055+{
4056+ [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
4057+ if (status != SFSpeechRecognizerAuthorizationStatusAuthorized) {
4058+ reject(@"DENIED", @"Speech recognition permission denied", nil);
4059+ return;
4060+ }
4061+
4062+ dispatch_async(dispatch_get_main_queue(), ^{
4063+ // Stop any existing session
4064+ if (self.audioEngine.isRunning) {
4065+ [self.audioEngine stop];
4066+ [self.recognitionRequest endAudio];
4067+ }
4068+
4069+ self.latestTranscript = @"";
4070+ self.recognizer = [[SFSpeechRecognizer alloc] initWithLocale:[NSLocale currentLocale]];
4071+ self.audioEngine = [[AVAudioEngine alloc] init];
4072+ self.recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
4073+ self.recognitionRequest.shouldReportPartialResults = YES;
4074+
4075+ AVAudioInputNode *inputNode = self.audioEngine.inputNode;
4076+ AVAudioFormat *format = [inputNode outputFormatForBus:0];
4077+
4078+ [inputNode installTapOnBus:0 bufferSize:1024 format:format block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
4079+ [self.recognitionRequest appendAudioPCMBuffer:buffer];
4080+ }];
4081+
4082+ self.recognitionTask = [self.recognizer recognitionTaskWithRequest:self.recognitionRequest
4083+ resultHandler:^(SFSpeechRecognitionResult *result, NSError *error) {
4084+ if (result) {
4085+ self.latestTranscript = result.bestTranscription.formattedString;
4086+ }
4087+ if (error || result.isFinal) {
4088+ [self.audioEngine stop];
4089+ [inputNode removeTapOnBus:0];
4090+ }
4091+ }];
4092+
4093+ NSError *err = nil;
4094+ [self.audioEngine startAndReturnError:&err];
4095+ if (err) {
4096+ reject(@"ENGINE_FAIL", err.localizedDescription, err);
4097+ return;
4098+ }
4099+ resolve(@"started");
4100+ });
4101+ }];
4102+}
4103+
4104+RCT_EXPORT_METHOD(stop:(RCTPromiseResolveBlock)resolve
4105+ rejecter:(RCTPromiseRejectBlock)reject)
4106+{
4107+ dispatch_async(dispatch_get_main_queue(), ^{
4108+ if (self.audioEngine.isRunning) {
4109+ [self.audioEngine stop];
4110+ [self.recognitionRequest endAudio];
4111+ }
4112+ resolve(self.latestTranscript ?: @"");
4113+ });
4114+}
4115+
4116+RCT_EXPORT_METHOD(getTranscript:(RCTPromiseResolveBlock)resolve
4117+ rejecter:(RCTPromiseRejectBlock)reject)
4118+{
4119+ resolve(self.latestTranscript ?: @"");
4120+}
4121+
4122+@end
4123+
4124+// ============================================================
4125+// 53. NaturalLanguage — MacOSNLModule (imperative)
4126+// ============================================================
4127+
4128+@interface MacOSNLModule : NSObject <RCTBridgeModule>
4129+@end
4130+
4131+@implementation MacOSNLModule
4132+
4133+RCT_EXPORT_MODULE()
4134+
4135++ (BOOL)requiresMainQueueSetup { return NO; }
4136+
4137+RCT_EXPORT_METHOD(detectLanguage:(NSString *)text
4138+ resolver:(RCTPromiseResolveBlock)resolve
4139+ rejecter:(RCTPromiseRejectBlock)reject)
4140+{
4141+ NLLanguageRecognizer *recognizer = [[NLLanguageRecognizer alloc] init];
4142+ [recognizer processString:text ?: @""];
4143+ NLLanguage lang = recognizer.dominantLanguage;
4144+ resolve(lang ?: @"unknown");
4145+}
4146+
4147+RCT_EXPORT_METHOD(sentiment:(NSString *)text
4148+ resolver:(RCTPromiseResolveBlock)resolve
4149+ rejecter:(RCTPromiseRejectBlock)reject)
4150+{
4151+ NLTagger *tagger = [[NLTagger alloc] initWithTagSchemes:@[NLTagSchemeSentimentScore]];
4152+ tagger.string = text ?: @"";
4153+ NSRange tokenRange;
4154+ NLTag tag = [tagger tagAtIndex:0 unit:NLTokenUnitParagraph scheme:NLTagSchemeSentimentScore tokenRange:&tokenRange];
4155+ double score = tag ? tag.doubleValue : 0.0;
4156+ resolve(@(score));
4157+}
4158+
4159+RCT_EXPORT_METHOD(tokenize:(NSString *)text
4160+ resolver:(RCTPromiseResolveBlock)resolve
4161+ rejecter:(RCTPromiseRejectBlock)reject)
4162+{
4163+ NLTokenizer *tokenizer = [[NLTokenizer alloc] initWithUnit:NLTokenUnitWord];
4164+ tokenizer.string = text ?: @"";
4165+ NSMutableArray<NSString *> *tokens = [NSMutableArray array];
4166+ [tokenizer enumerateTokensInRange:NSMakeRange(0, text.length)
4167+ usingBlock:^(NSRange tokenRange, NLTokenizerAttributes attributes, BOOL *stop) {
4168+ [tokens addObject:[text substringWithRange:tokenRange]];
4169+ }];
4170+ resolve(tokens);
4171+}
4172+
4173+@end