From cb6f875c496b32bbebcc7bb8337be3ba26a721d5 Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 14 May 2024 10:24:33 -0400 Subject: [PATCH 1/4] Add JetpackAIServiceRemote.getJWT --- .../Services/JetpackAIServiceRemote.swift | 13 +++++++++++++ WordPressKit.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 17 insertions(+) create mode 100644 Sources/WordPressKit/Services/JetpackAIServiceRemote.swift diff --git a/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift new file mode 100644 index 00000000..7016315c --- /dev/null +++ b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift @@ -0,0 +1,13 @@ +import Foundation + +public final class JetpackAIServiceRemote: SiteServiceRemoteWordPressComREST { + /// Returns short-lived JWT token (lifetime is in minutes). + public func getJWT() async throws -> String { + struct Response: Decodable { + let token: String + } + let path = path(forEndpoint: "sites/\(siteID)/jetpack-openai-query/jwt", withVersion: ._2_0) + let response = await wordPressComRestApi.perform(.post, URLString: path, type: Response.self) + return try response.get().body.token + } +} diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 1b62cab2..4108b0d8 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */; }; 0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */; }; 0C1C08452B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */; }; + 0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */; }; 0C9CD7992B9A107E0045BE03 /* RemotePostParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */; }; 0CB1905E2A2A5E83004D3E80 /* BlazeCampaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */; }; 0CB190612A2A6A13004D3E80 /* blaze-campaigns-search.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */; }; @@ -773,6 +774,7 @@ 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteREST+Extended.swift"; sourceTree = ""; }; 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteXMLRPC+Extended.swift"; sourceTree = ""; }; 0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAIServiceRemote.swift; sourceTree = ""; }; 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePostParameters.swift; sourceTree = ""; }; 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaign.swift; sourceTree = ""; }; 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-campaigns-search.json"; sourceTree = ""; }; @@ -1943,6 +1945,7 @@ 730E869E21E44EFD00753E1A /* WordPressComServiceRemote+SiteVerticals.swift */, 73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */, 803DE80E28FFA787007D4E9C /* RemoteConfigRemote.swift */, + 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */, ); path = Services; sourceTree = ""; @@ -3414,6 +3417,7 @@ 7397F01A220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift in Sources */, 4A68E3D429406AA0004AC3DC /* RemoteMenuItem.swift in Sources */, 8B2F4BEF24ACCC120056C08A /* RemoteReaderCard.swift in Sources */, + 0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */, 4A11239C2B1926B7004690CF /* HTTPRequestBuilder.swift in Sources */, 40E7FEB1220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift in Sources */, 3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */, From 00b651e26baaed297936523d3009825773d8e49d Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 14 May 2024 17:12:16 -0400 Subject: [PATCH 2/4] Add JetpackAIServiceRemote/transcribeAudio --- Sources/CoreAPI/HTTPRequestBuilder.swift | 7 +++++++ Sources/CoreAPI/WordPressComRestApi.swift | 4 +++- .../Services/JetpackAIServiceRemote.swift | 16 +++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Sources/CoreAPI/HTTPRequestBuilder.swift b/Sources/CoreAPI/HTTPRequestBuilder.swift index c53cf9ca..1cf8170b 100644 --- a/Sources/CoreAPI/HTTPRequestBuilder.swift +++ b/Sources/CoreAPI/HTTPRequestBuilder.swift @@ -68,6 +68,13 @@ final class HTTPRequestBuilder { .append(query: urlComponents.queryItems ?? []) } + func headers(_ headers: [String: String]) -> Self { + for (key, value) in headers { + self.headers[key] = value + } + return self + } + func header(name: String, value: String?) -> Self { headers[name] = value return self diff --git a/Sources/CoreAPI/WordPressComRestApi.swift b/Sources/CoreAPI/WordPressComRestApi.swift index 6983e8c8..d8726018 100644 --- a/Sources/CoreAPI/WordPressComRestApi.swift +++ b/Sources/CoreAPI/WordPressComRestApi.swift @@ -449,7 +449,8 @@ open class WordPressComRestApi: NSObject { public func upload( URLString: String, - parameters: [String: AnyObject]?, + parameters: [String: AnyObject]? = nil, + httpHeaders: [String: String]? = nil, fileParts: [FilePart], requestEnqueued: RequestEnqueuedBlock? = nil, fulfilling progress: Progress? = nil @@ -462,6 +463,7 @@ open class WordPressComRestApi: NSObject { builder = try requestBuilder(URLString: URLString) .method(.post) .body(form: form) + .headers(httpHeaders ?? [:]) } catch { return .failure(.requestEncodingFailure(underlyingError: error)) } diff --git a/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift index 7016315c..65fc69a5 100644 --- a/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift +++ b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift @@ -2,7 +2,7 @@ import Foundation public final class JetpackAIServiceRemote: SiteServiceRemoteWordPressComREST { /// Returns short-lived JWT token (lifetime is in minutes). - public func getJWT() async throws -> String { + public func getAuthorizationToken() async throws -> String { struct Response: Decodable { let token: String } @@ -10,4 +10,18 @@ public final class JetpackAIServiceRemote: SiteServiceRemoteWordPressComREST { let response = await wordPressComRestApi.perform(.post, URLString: path, type: Response.self) return try response.get().body.token } + + /// - parameter token: Token retrieved using ``JetpackAIServiceRemote/getAuthorizationToken``. + public func transcribeAudio(from fileURL: URL, token: String) async throws -> String { + let path = path(forEndpoint: "jetpack-ai-transcription?feature=voice-to-content", withVersion: ._2_0) + let file = FilePart(parameterName: "audio_file", url: fileURL, fileName: "voice_recording", mimeType: "audio/m4a") + let result = await wordPressComRestApi.upload(URLString: path, httpHeaders: [ + "Authorization": "Bearer \(token)" + ], fileParts: [file]) + guard let body = try result.get().body as? [String: Any], + let text = body["text"] as? String else { + throw URLError(.unknown) + } + return text + } } From ade807838898e93e4d3bd7fdee71d6d81d8938ac Mon Sep 17 00:00:00 2001 From: kean Date: Tue, 14 May 2024 18:28:39 -0400 Subject: [PATCH 3/4] Add makePostContent(fromPlainText --- Sources/CoreAPI/WordPressComRestApi.swift | 6 +-- .../Services/JetpackAIServiceRemote.swift | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Sources/CoreAPI/WordPressComRestApi.swift b/Sources/CoreAPI/WordPressComRestApi.swift index d8726018..a8ebeb26 100644 --- a/Sources/CoreAPI/WordPressComRestApi.swift +++ b/Sources/CoreAPI/WordPressComRestApi.swift @@ -309,7 +309,7 @@ open class WordPressComRestApi: NSObject { return "\(String(describing: oAuthToken)),\(String(describing: userAgent))".hashValue } - private func requestBuilder(URLString: String) throws -> HTTPRequestBuilder { + func requestBuilder(URLString: String) throws -> HTTPRequestBuilder { guard let url = URL(string: URLString, relativeTo: baseURL) else { throw URLError(.badURL) } @@ -414,9 +414,9 @@ open class WordPressComRestApi: NSObject { return await perform(request: builder, fulfilling: progress, decoder: decoder) } - private func perform( + func perform( request: HTTPRequestBuilder, - fulfilling progress: Progress?, + fulfilling progress: Progress? = nil, decoder: @escaping (Data) throws -> T, taskCreated: ((Int) -> Void)? = nil, session: URLSession? = nil diff --git a/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift index 65fc69a5..00c2bcc0 100644 --- a/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift +++ b/Sources/WordPressKit/Services/JetpackAIServiceRemote.swift @@ -24,4 +24,55 @@ public final class JetpackAIServiceRemote: SiteServiceRemoteWordPressComREST { } return text } + + /// - parameter token: Token retrieved using ``JetpackAIServiceRemote/getAuthorizationToken``. + public func makePostContent(fromPlainText plainText: String, token: String) async throws -> String { + let path = path(forEndpoint: "jetpack-ai-query", withVersion: ._2_0) + let request = JetpackAIQueryRequest(messages: [ + .init(role: "jetpack-ai", context: .init(type: "voice-to-content-simple-draft", content: plainText)) + ], feature: "voice-to-content", stream: false) + let builder = try wordPressComRestApi.requestBuilder(URLString: path) + .method(.post) + .headers(["Authorization": "Bearer \(token)"]) + .body(json: request, jsonEncoder: JSONEncoder()) + let result = await wordPressComRestApi.perform(request: builder) { data in + try JSONDecoder().decode(JetpackAIQueryResponse.self, from: data) + } + let response = try result.get().body + guard let content = response.choices.first?.message.content else { + throw URLError(.unknown) + } + return content + } +} + +private struct JetpackAIQueryRequest: Encodable { + let messages: [Message] + let feature: String + let stream: Bool + + struct Message: Encodable { + let role: String + let context: Context + } + + struct Context: Codable { + let type: String + let content: String + } +} + +private struct JetpackAIQueryResponse: Decodable { + let model: String? + let choices: [Choice] + + struct Choice: Codable { + let index: Int + let message: Message + } + + struct Message: Codable { + let role: String? + let content: String + } } From e49f101b09113d1a55d4b03cd084279729f292d5 Mon Sep 17 00:00:00 2001 From: kean Date: Wed, 22 May 2024 13:37:54 -0400 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f6cd77..2460575c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ _None._ ### New Features -_None._ +- Add `JetpackAIServiceRemote` ### Bug Fixes