Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit c064bf5

Browse files
authored
Add JetpackAIServiceRemote (#804)
2 parents 30e9472 + e49f101 commit c064bf5

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _None._
3838

3939
### New Features
4040

41-
_None._
41+
- Add `JetpackAIServiceRemote`
4242

4343
### Bug Fixes
4444

Sources/CoreAPI/HTTPRequestBuilder.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ final class HTTPRequestBuilder {
6868
.append(query: urlComponents.queryItems ?? [])
6969
}
7070

71+
func headers(_ headers: [String: String]) -> Self {
72+
for (key, value) in headers {
73+
self.headers[key] = value
74+
}
75+
return self
76+
}
77+
7178
func header(name: String, value: String?) -> Self {
7279
headers[name] = value
7380
return self

Sources/CoreAPI/WordPressComRestApi.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ open class WordPressComRestApi: NSObject {
309309
return "\(String(describing: oAuthToken)),\(String(describing: userAgent))".hashValue
310310
}
311311

312-
private func requestBuilder(URLString: String) throws -> HTTPRequestBuilder {
312+
func requestBuilder(URLString: String) throws -> HTTPRequestBuilder {
313313
guard let url = URL(string: URLString, relativeTo: baseURL) else {
314314
throw URLError(.badURL)
315315
}
@@ -414,9 +414,9 @@ open class WordPressComRestApi: NSObject {
414414
return await perform(request: builder, fulfilling: progress, decoder: decoder)
415415
}
416416

417-
private func perform<T>(
417+
func perform<T>(
418418
request: HTTPRequestBuilder,
419-
fulfilling progress: Progress?,
419+
fulfilling progress: Progress? = nil,
420420
decoder: @escaping (Data) throws -> T,
421421
taskCreated: ((Int) -> Void)? = nil,
422422
session: URLSession? = nil
@@ -449,7 +449,8 @@ open class WordPressComRestApi: NSObject {
449449

450450
public func upload(
451451
URLString: String,
452-
parameters: [String: AnyObject]?,
452+
parameters: [String: AnyObject]? = nil,
453+
httpHeaders: [String: String]? = nil,
453454
fileParts: [FilePart],
454455
requestEnqueued: RequestEnqueuedBlock? = nil,
455456
fulfilling progress: Progress? = nil
@@ -462,6 +463,7 @@ open class WordPressComRestApi: NSObject {
462463
builder = try requestBuilder(URLString: URLString)
463464
.method(.post)
464465
.body(form: form)
466+
.headers(httpHeaders ?? [:])
465467
} catch {
466468
return .failure(.requestEncodingFailure(underlyingError: error))
467469
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Foundation
2+
3+
public final class JetpackAIServiceRemote: SiteServiceRemoteWordPressComREST {
4+
/// Returns short-lived JWT token (lifetime is in minutes).
5+
public func getAuthorizationToken() async throws -> String {
6+
struct Response: Decodable {
7+
let token: String
8+
}
9+
let path = path(forEndpoint: "sites/\(siteID)/jetpack-openai-query/jwt", withVersion: ._2_0)
10+
let response = await wordPressComRestApi.perform(.post, URLString: path, type: Response.self)
11+
return try response.get().body.token
12+
}
13+
14+
/// - parameter token: Token retrieved using ``JetpackAIServiceRemote/getAuthorizationToken``.
15+
public func transcribeAudio(from fileURL: URL, token: String) async throws -> String {
16+
let path = path(forEndpoint: "jetpack-ai-transcription?feature=voice-to-content", withVersion: ._2_0)
17+
let file = FilePart(parameterName: "audio_file", url: fileURL, fileName: "voice_recording", mimeType: "audio/m4a")
18+
let result = await wordPressComRestApi.upload(URLString: path, httpHeaders: [
19+
"Authorization": "Bearer \(token)"
20+
], fileParts: [file])
21+
guard let body = try result.get().body as? [String: Any],
22+
let text = body["text"] as? String else {
23+
throw URLError(.unknown)
24+
}
25+
return text
26+
}
27+
28+
/// - parameter token: Token retrieved using ``JetpackAIServiceRemote/getAuthorizationToken``.
29+
public func makePostContent(fromPlainText plainText: String, token: String) async throws -> String {
30+
let path = path(forEndpoint: "jetpack-ai-query", withVersion: ._2_0)
31+
let request = JetpackAIQueryRequest(messages: [
32+
.init(role: "jetpack-ai", context: .init(type: "voice-to-content-simple-draft", content: plainText))
33+
], feature: "voice-to-content", stream: false)
34+
let builder = try wordPressComRestApi.requestBuilder(URLString: path)
35+
.method(.post)
36+
.headers(["Authorization": "Bearer \(token)"])
37+
.body(json: request, jsonEncoder: JSONEncoder())
38+
let result = await wordPressComRestApi.perform(request: builder) { data in
39+
try JSONDecoder().decode(JetpackAIQueryResponse.self, from: data)
40+
}
41+
let response = try result.get().body
42+
guard let content = response.choices.first?.message.content else {
43+
throw URLError(.unknown)
44+
}
45+
return content
46+
}
47+
}
48+
49+
private struct JetpackAIQueryRequest: Encodable {
50+
let messages: [Message]
51+
let feature: String
52+
let stream: Bool
53+
54+
struct Message: Encodable {
55+
let role: String
56+
let context: Context
57+
}
58+
59+
struct Context: Codable {
60+
let type: String
61+
let content: String
62+
}
63+
}
64+
65+
private struct JetpackAIQueryResponse: Decodable {
66+
let model: String?
67+
let choices: [Choice]
68+
69+
struct Choice: Codable {
70+
let index: Int
71+
let message: Message
72+
}
73+
74+
struct Message: Codable {
75+
let role: String?
76+
let content: String
77+
}
78+
}

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */; };
2020
0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */; };
2121
0C1C08452B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */; };
22+
0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */; };
2223
0C9CD7992B9A107E0045BE03 /* RemotePostParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */; };
2324
0CB1905E2A2A5E83004D3E80 /* BlazeCampaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */; };
2425
0CB190612A2A6A13004D3E80 /* blaze-campaigns-search.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */; };
@@ -773,6 +774,7 @@
773774
0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteREST+Extended.swift"; sourceTree = "<group>"; };
774775
0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteXMLRPC+Extended.swift"; sourceTree = "<group>"; };
775776
0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
777+
0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackAIServiceRemote.swift; sourceTree = "<group>"; };
776778
0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePostParameters.swift; sourceTree = "<group>"; };
777779
0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaign.swift; sourceTree = "<group>"; };
778780
0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blaze-campaigns-search.json"; sourceTree = "<group>"; };
@@ -1943,6 +1945,7 @@
19431945
730E869E21E44EFD00753E1A /* WordPressComServiceRemote+SiteVerticals.swift */,
19441946
73A2F38921E7F81E00388609 /* WordPressComServiceRemote+SiteVerticalsPrompt.swift */,
19451947
803DE80E28FFA787007D4E9C /* RemoteConfigRemote.swift */,
1948+
0C674E2F2BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift */,
19461949
);
19471950
path = Services;
19481951
sourceTree = "<group>";
@@ -3414,6 +3417,7 @@
34143417
7397F01A220A072500C723F3 /* ActivityServiceRemote_ApiVersion1_0.swift in Sources */,
34153418
4A68E3D429406AA0004AC3DC /* RemoteMenuItem.swift in Sources */,
34163419
8B2F4BEF24ACCC120056C08A /* RemoteReaderCard.swift in Sources */,
3420+
0C674E302BF3A91300F3B3D4 /* JetpackAIServiceRemote.swift in Sources */,
34173421
4A11239C2B1926B7004690CF /* HTTPRequestBuilder.swift in Sources */,
34183422
40E7FEB1220FB3B60032834E /* StatsAnnualAndMostPopularTimeInsight.swift in Sources */,
34193423
3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */,

0 commit comments

Comments
 (0)