mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 09:41:02 +00:00
Add OpenAI pipeline + some prompts
This commit is contained in:
parent
2fdf5fe239
commit
6b210aec4f
7 changed files with 201 additions and 0 deletions
|
@ -42,6 +42,7 @@
|
||||||
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; };
|
9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; };
|
||||||
9F7335F22967608F00AFF0BA /* AddRemoteTimelineVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */; };
|
9F7335F22967608F00AFF0BA /* AddRemoteTimelineVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */; };
|
||||||
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; };
|
||||||
|
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9FAD85822971BF7200496AB1 /* Secret.plist */; };
|
||||||
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; };
|
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; };
|
||||||
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; };
|
9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; };
|
||||||
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; };
|
9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBFE63C292A715500C250E9 /* IceCubesApp.swift */; };
|
||||||
|
@ -112,6 +113,7 @@
|
||||||
9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; };
|
9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineVIew.swift; sourceTree = "<group>"; };
|
9F7335F12967608F00AFF0BA /* AddRemoteTimelineVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineVIew.swift; sourceTree = "<group>"; };
|
||||||
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
|
9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
9FAD85822971BF7200496AB1 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Secret.plist; sourceTree = "<group>"; };
|
||||||
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
|
9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = "<group>"; };
|
||||||
9FBFE639292A715500C250E9 /* IceCubesApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IceCubesApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
9FBFE639292A715500C250E9 /* IceCubesApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IceCubesApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -254,6 +256,7 @@
|
||||||
9F398AB429360A5800A889F2 /* App */,
|
9F398AB429360A5800A889F2 /* App */,
|
||||||
9FBFE642292A715600C250E9 /* IceCubesApp.entitlements */,
|
9FBFE642292A715600C250E9 /* IceCubesApp.entitlements */,
|
||||||
9F398AB529360A6100A889F2 /* Resources */,
|
9F398AB529360A6100A889F2 /* Resources */,
|
||||||
|
9FAD85822971BF7200496AB1 /* Secret.plist */,
|
||||||
);
|
);
|
||||||
path = IceCubesApp;
|
path = IceCubesApp;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -399,6 +402,7 @@
|
||||||
9F2A542C296B1177009B2D7C /* glass.caf in Resources */,
|
9F2A542C296B1177009B2D7C /* glass.caf in Resources */,
|
||||||
9FD34823293D06E800DB0EE9 /* Assets.xcassets in Resources */,
|
9FD34823293D06E800DB0EE9 /* Assets.xcassets in Resources */,
|
||||||
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
9F24EEB829360C330042359D /* Preview Assets.xcassets in Resources */,
|
||||||
|
9FAD85832971BF7200496AB1 /* Secret.plist in Resources */,
|
||||||
9F2A542E296B1CC0009B2D7C /* glass.wav in Resources */,
|
9F2A542E296B1CC0009B2D7C /* glass.wav in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
8
IceCubesApp/Secret.plist
Normal file
8
IceCubesApp/Secret.plist
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>OPENAI_SECRET</key>
|
||||||
|
<string>NICE_TRY</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
97
Packages/Network/Sources/Network/OpenAIClient.swift
Normal file
97
Packages/Network/Sources/Network/OpenAIClient.swift
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct OpenAIClient {
|
||||||
|
private let endpoint: URL = URL(string: "https://api.openai.com/v1/completions")!
|
||||||
|
|
||||||
|
private var APIKey: String {
|
||||||
|
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
|
||||||
|
let secret = NSDictionary(contentsOfFile: path)
|
||||||
|
return secret?["OPENAI_SECRET"] as? String ?? ""
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private var authorizationHeaderValue: String {
|
||||||
|
"Bearer \(APIKey)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var encoder: JSONEncoder {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
private var decoder: JSONDecoder {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Request: Encodable {
|
||||||
|
let model = "text-davinci-003"
|
||||||
|
let topP: Int = 1
|
||||||
|
let frequencyPenalty: Int = 0
|
||||||
|
let presencePenalty: Int = 0
|
||||||
|
let prompt: String
|
||||||
|
let temperature: Double
|
||||||
|
let maxTokens: Int
|
||||||
|
|
||||||
|
public init(prompt: String, temperature: Double, maxTokens: Int) {
|
||||||
|
self.prompt = prompt
|
||||||
|
self.temperature = temperature
|
||||||
|
self.maxTokens = maxTokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Prompts {
|
||||||
|
case correct(input: String)
|
||||||
|
case shorten(input: String)
|
||||||
|
case emphasize(input: String)
|
||||||
|
|
||||||
|
var request: Request {
|
||||||
|
switch self {
|
||||||
|
case let .correct(input):
|
||||||
|
return Request(prompt: "Correct this to standard English:\(input)",
|
||||||
|
temperature: 0,
|
||||||
|
maxTokens: 500)
|
||||||
|
case let .shorten(input):
|
||||||
|
return Request(prompt: "Make a summary of this paragraph:\(input)",
|
||||||
|
temperature: 0.7,
|
||||||
|
maxTokens: 100)
|
||||||
|
case let .emphasize(input):
|
||||||
|
return Request(prompt: "Make this paragraph catchy, more fun:\(input)",
|
||||||
|
temperature: 0.8,
|
||||||
|
maxTokens: 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Response: Decodable {
|
||||||
|
public struct Choice: Decodable {
|
||||||
|
public let text: String
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let object: String
|
||||||
|
public let model: String
|
||||||
|
public let choices: [Choice]
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
public func request(_ prompt: Prompts) async throws -> Response {
|
||||||
|
do {
|
||||||
|
let jsonData = try encoder.encode(prompt.request)
|
||||||
|
var request = URLRequest(url: endpoint)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
request.httpBody = jsonData
|
||||||
|
let (result, _) = try await URLSession.shared.data(for: request)
|
||||||
|
let response = try decoder.decode(Response.self, from: result)
|
||||||
|
return response
|
||||||
|
} catch let error {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
import Network
|
||||||
|
|
||||||
|
enum StatusEditorAIPrompts: CaseIterable {
|
||||||
|
case correct, fit, emphasize
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var label: some View {
|
||||||
|
switch self {
|
||||||
|
case .correct:
|
||||||
|
Label("Correct text", systemImage: "text.badge.checkmark")
|
||||||
|
case .fit:
|
||||||
|
Label("Shorten text", systemImage: "text.badge.minus")
|
||||||
|
case .emphasize:
|
||||||
|
Label("Emphasize text", systemImage: "text.badge.star")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toRequestPrompt(text: String) -> OpenAIClient.Prompts {
|
||||||
|
switch self {
|
||||||
|
case .correct:
|
||||||
|
return .correct(input: text)
|
||||||
|
case .fit:
|
||||||
|
return .shorten(input: text)
|
||||||
|
case .emphasize:
|
||||||
|
return .emphasize(input: text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ public struct StatusEditorView: View {
|
||||||
@FocusState private var isSpoilerTextFocused: Bool
|
@FocusState private var isSpoilerTextFocused: Bool
|
||||||
|
|
||||||
@State private var isDismissAlertPresented: Bool = false
|
@State private var isDismissAlertPresented: Bool = false
|
||||||
|
@State private var isLoadingAIRequest: Bool = false
|
||||||
|
|
||||||
public init(mode: StatusEditorViewModel.Mode) {
|
public init(mode: StatusEditorViewModel.Mode) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(mode: mode))
|
_viewModel = StateObject(wrappedValue: .init(mode: mode))
|
||||||
|
@ -77,6 +78,10 @@ public struct StatusEditorView: View {
|
||||||
.navigationTitle(viewModel.mode.title)
|
.navigationTitle(viewModel.mode.title)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
AIMenu
|
||||||
|
.disabled(!viewModel.canPost)
|
||||||
|
}
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
Task {
|
Task {
|
||||||
|
@ -176,4 +181,35 @@ public struct StatusEditorView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var AIMenu: some View {
|
||||||
|
Menu {
|
||||||
|
ForEach(StatusEditorAIPrompts.allCases, id: \.self) { prompt in
|
||||||
|
Button {
|
||||||
|
Task {
|
||||||
|
isLoadingAIRequest = true
|
||||||
|
await viewModel.runOpenAI(prompt: prompt.toRequestPrompt(text: viewModel.statusText.string))
|
||||||
|
isLoadingAIRequest = false
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
prompt.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let backup = viewModel.backupStatustext {
|
||||||
|
Button {
|
||||||
|
viewModel.replaceTextWith(text: backup.string)
|
||||||
|
viewModel.backupStatustext = nil
|
||||||
|
} label: {
|
||||||
|
Label("Restore previous text", systemImage: "arrow.uturn.right")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if isLoadingAIRequest {
|
||||||
|
ProgressView()
|
||||||
|
} else {
|
||||||
|
Image(systemName: "faxmachine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
checkEmbed()
|
checkEmbed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Published var backupStatustext: NSAttributedString?
|
||||||
|
|
||||||
@Published var showPoll: Bool = false
|
@Published var showPoll: Bool = false
|
||||||
@Published var pollVotingFrequency = PollVotingFrequency.oneVote
|
@Published var pollVotingFrequency = PollVotingFrequency.oneVote
|
||||||
|
@ -88,6 +89,11 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
statusText = string
|
statusText = string
|
||||||
selectedRange = NSRange(location: inRange.location + text.utf16.count, length: 0)
|
selectedRange = NSRange(location: inRange.location + text.utf16.count, length: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func replaceTextWith(text: String) {
|
||||||
|
statusText = .init(string: text)
|
||||||
|
selectedRange = .init(location: text.utf16.count, length: 0)
|
||||||
|
}
|
||||||
|
|
||||||
private func getPollOptionsForAPI() -> [String] {
|
private func getPollOptionsForAPI() -> [String] {
|
||||||
pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
pollOptions.filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }
|
||||||
|
@ -298,6 +304,20 @@ public class StatusEditorViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - OpenAI Prompt
|
||||||
|
func runOpenAI(prompt: OpenAIClient.Prompts) async {
|
||||||
|
do {
|
||||||
|
let client = OpenAIClient()
|
||||||
|
let response = try await client.request(prompt)
|
||||||
|
if var text = response.choices.first?.text {
|
||||||
|
text.removeFirst()
|
||||||
|
text.removeFirst()
|
||||||
|
backupStatustext = statusText
|
||||||
|
replaceTextWith(text: text)
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Media related function
|
// MARK: - Media related function
|
||||||
|
|
||||||
private func indexOf(container: ImageContainer) -> Int? {
|
private func indexOf(container: ImageContainer) -> Int? {
|
||||||
|
|
6
ci_scripts/ci_pre_xcodebuild.sh
Executable file
6
ci_scripts/ci_pre_xcodebuild.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
cd ../IceCubesApp/
|
||||||
|
plutil -replace OPENAI_SECRET -string $OPENAI_SECRET Secret.plist
|
||||||
|
plutil -p Secret.plist
|
||||||
|
exit 0
|
Loading…
Reference in a new issue