Skip to content

Commit

Permalink
Add email subject line auto-gen
Browse files Browse the repository at this point in the history
  • Loading branch information
scosman committed Mar 25, 2024
1 parent d093974 commit 41979c1
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 43 deletions.
55 changes: 38 additions & 17 deletions ios/voicebox/UI/VBShareViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

#import "VBShareViewController.h"
#import "../Util/VBMagicEnhancer.h"
#import "../Util/VBStringUtils.h"
#import "Constants.h"
#import "VBButton.h"
Expand Down Expand Up @@ -121,32 +122,52 @@ - (void)stringCopy:(UIButton*)sender

- (void)email:(UIButton*)sender
{
NSString* body = self.content;
UIAlertController* generatingSubjectAlert = [UIAlertController alertControllerWithTitle:@"Generating Subject Line" message:@"\nThis will only take a few seconds....\n" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:generatingSubjectAlert animated:YES completion:nil];

__block NSString* body = self.content;
VBMagicEnhancer* enhancer = [[VBMagicEnhancer alloc] init];
[enhancer generateSubjectForEmail:body
onComplete:^(NSString* _Nullable subject, NSError* _Nullable err) {
dispatch_sync(dispatch_get_main_queue(), ^{
[generatingSubjectAlert dismissViewControllerAnimated:NO completion:nil];
});

// Send even if error generating subject
[self showSendEmail:body withSubject:subject];
}];
}

- (void)showSendEmail:(NSString*)body withSubject:(NSString*)subject
{
// Mailto required /r/n
body = [body stringByReplacingOccurrencesOfString:@"\n" withString:@"\r\n"];

NSURLComponents* urlComps = [NSURLComponents componentsWithString:@"mailto:"];
urlComps.queryItems = @[
[NSURLQueryItem queryItemWithName:@"body"
value:body]
// TODO: smart ML @"subject"
value:body],
[NSURLQueryItem queryItemWithName:@"subject"
value:subject]
];
NSURL* url = urlComps.URL;

[[UIApplication sharedApplication] openURL:url
options:@{}
completionHandler:^(BOOL success) {
if (success) {
[self dismissViewControllerAnimated:YES completion:nil];
} else {
UIAlertController* errVC = [UIAlertController alertControllerWithTitle:@"Error Launching Email" message:@"We could not launch your default email application." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:nil];
[errVC addAction:defaultAction];
[self presentViewController:errVC animated:YES completion:nil];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] openURL:url
options:@{}
completionHandler:^(BOOL success) {
if (success) {
[self dismissViewControllerAnimated:YES completion:nil];
} else {
UIAlertController* errVC = [UIAlertController alertControllerWithTitle:@"Error Launching Email" message:@"We could not launch your default email application." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:nil];
[errVC addAction:defaultAction];
[self presentViewController:errVC animated:YES completion:nil];
}
}];
});
}

- (void)otherShare:(UIButton*)sender
Expand Down
50 changes: 24 additions & 26 deletions ios/voicebox/Util/OpenAiApiRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ - (NSMutableDictionary*)buildBodyForChatCompletion:(ChatGptRequest*)request
strangely it's been rock solid, so using for prototyping. Try the "n" param of open API endpoint,
and structured response instead of parsing json from plaintext
*/
- (NSMutableArray<ResponseOption*>*)sendSynchronousRequest:(NSError**)error
- (NSString*)sendSynchronousRequestRaw:(NSError**)error
{
NSData* bodyPayloadJsonData = [NSJSONSerialization dataWithJSONObject:self.bodyPayload options:NSJSONWritingPrettyPrinted error:error];
if (*error) {
Expand All @@ -231,11 +231,22 @@ - (NSMutableDictionary*)buildBodyForChatCompletion:(ChatGptRequest*)request
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

NSURLSession* session = [NSURLSession sharedSession];
__block NSMutableArray<ResponseOption*>* options;
__block NSData* responseData;
__block NSError* requestBlockError;
NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:urlRequest
completionHandler:^(NSData* data, NSURLResponse* response, NSError* requestError) {
options = [self processResponse:response withData:data withRequestError:requestError withError:&requestBlockError];
completionHandler:^(NSData* _Nullable data, NSURLResponse* _Nullable response, NSError* _Nullable requestErr) {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if (requestErr) {
requestBlockError = requestErr;
} else if (httpResponse.statusCode != 200) {
if (data && data.length) {
NSLog(@"Data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
requestBlockError = [self apiError:999200];
} else {
responseData = data;
}

dispatch_semaphore_signal(semaphore);
}];
[dataTask resume];
Expand All @@ -252,32 +263,13 @@ - (NSMutableDictionary*)buildBodyForChatCompletion:(ChatGptRequest*)request
*error = requestBlockError;
return nil;
}
if (!options) {
if (!responseData) {
*error = [self apiError:91111];
return nil;
}

return options;
}

- (NSMutableArray<ResponseOption*>*)processResponse:(NSURLResponse*)response withData:(NSData*)data withRequestError:(NSError*)requestError withError:(NSError**)error
{
if (requestError) {
*error = requestError;
return nil;
}

NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if (httpResponse.statusCode != 200) {
if (data && data.length) {
NSLog(@"Data: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
*error = [self apiError:999200];
return nil;
}

NSError* jsonError = nil;
id parsedJsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
id parsedJsonResponse = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError];
if (jsonError) {
*error = jsonError;
return nil;
Expand All @@ -289,7 +281,13 @@ - (NSMutableDictionary*)buildBodyForChatCompletion:(ChatGptRequest*)request
return nil;
}

return [OpenAiApiRequest processMessageString:responseMessage withError:error];
return responseMessage;
}

- (NSMutableArray<ResponseOption*>*)sendSynchronousRequest:(NSError**)error
{
NSString* openAiMessage = [self sendSynchronousRequestRaw:error];
return [OpenAiApiRequest processMessageString:openAiMessage withError:error];
}

+ (NSMutableArray<ResponseOption*>*)processMessageString:(NSString*)msgString withError:(NSError**)error
Expand Down
2 changes: 2 additions & 0 deletions ios/voicebox/Util/VBMagicEnhancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN

- (void)enhance:(NSString*)text onComplete:(void (^)(NSArray<ResponseOption*>*, NSError*))complete;

- (void)generateSubjectForEmail:(NSString*)emailText onComplete:(void (^)(NSString* _Nullable, NSError* _Nullable))complete;

@end

NS_ASSUME_NONNULL_END
34 changes: 34 additions & 0 deletions ios/voicebox/Util/VBMagicEnhancer.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,35 @@ - (void)enhance:(NSString*)text onComplete:(void (^)(NSArray<ResponseOption*>*,
[self requestAndBuildOptions:request withOriginalText:text withMode:mode onComplete:complete];
}

- (void)generateSubjectForEmail:(NSString*)emailText onComplete:(void (^)(NSString* _Nullable, NSError* _Nullable))complete
{
ChatGptRequest* request = [[ChatGptRequest alloc] init];
request.systemDirective = [self emailSubjectPrompt];

ChatGptMessage* apiMessage = [[ChatGptMessage alloc] init];
apiMessage.roll = kChatGptRollUser;
apiMessage.content = emailText;
request.messages = @[ apiMessage ];

OpenAiApiRequest* req = [self buildApiRequest:request];

if (!request) {
complete(nil, [NSError errorWithDomain:@"net.scosman.voicebox.custom" code:8374834 userInfo:@{ NSLocalizedDescriptionKey : @"Issue generating email subject." }]);
return;
;
}

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSError* err;
NSString* subject = [req sendSynchronousRequestRaw:&err];
if (err) {
complete(nil, err);
} else {
complete(subject, nil);
}
});
}

- (void)requestAndBuildOptions:(id<VBMLProvider>)request withOriginalText:(NSString*)originalText withMode:(MagicEnhancerMode)mode onComplete:(void (^)(NSArray<ResponseOption*>*, NSError*))complete
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
Expand Down Expand Up @@ -297,4 +326,9 @@ - (NSString*)gpt3NextSentancePromptTemplate
return nextSentencePrompt;
}

- (NSString*)emailSubjectPrompt
{
return @"You are a useful assistant which generates friendly email subjects, given an email body.\n\nEvery user message is a user email body, to which you should reply with a suggested subject.\n\n - The suggested subject should be brief. 2 to 8 words is ideal. Never over 12 words.\n - The suggested subject should not try to repeat the email content or all topics, but just give a nice subject to identify the emails from others\n - Your reply should contain only a single suggested subject. It should not contain any prefixes (\"Suggested email subject: \", etc), and formatting (list formatting, etc), alternative suggestions, or descriptions of your sugesstion.";
}

@end
1 change: 1 addition & 0 deletions ios/voicebox/Util/VBResponseOption.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN

@protocol VBMLProvider <NSObject>

- (NSString*)sendSynchronousRequestRaw:(NSError**)error;
- (NSMutableArray<ResponseOption*>*)sendSynchronousRequest:(NSError**)error;

@end
Expand Down

0 comments on commit 41979c1

Please sign in to comment.