Switch to new Chat completion API + Add Tags completion

This commit is contained in:
Thomas Ricouard 2023-03-09 13:46:04 +01:00
parent c36b9083ce
commit 93d9ded447
22 changed files with 93 additions and 32 deletions

View file

@ -423,6 +423,8 @@
"status.draft.delete" = "Выдаліць чарнавік";
"status.draft.save" = "Захаваць чарнавік";
"status.editor.ai-prompt.correct" = "Выправіць тэкст";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Вылучыць тэкст";
"status.editor.ai-prompt.fit" = "Скараціць тэкст";
"status.editor.description.add" = "Дадаць апісанне";

View file

@ -417,6 +417,8 @@
"status.draft.delete" = "Elimina l'esborrany";
"status.draft.save" = "Desa l'esborrany";
"status.editor.ai-prompt.correct" = "Corregeix el text";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Amplia text";
"status.editor.ai-prompt.fit" = "Acurta el text";
"status.editor.description.add" = "Afegeix una descripció";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Entwurf löschen";
"status.draft.save" = "Entwurf sichern";
"status.editor.ai-prompt.correct" = "Text korrigieren";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Text hervorheben";
"status.editor.ai-prompt.fit" = "Text kürzen";
"status.editor.description.add" = "Beschreibung hinzufügen";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Delete Draft";
"status.draft.save" = "Save Draft";
"status.editor.ai-prompt.correct" = "Correct text";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Emphasise text";
"status.editor.ai-prompt.fit" = "Shorten text";
"status.editor.description.add" = "Add description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Delete Draft";
"status.draft.save" = "Save Draft";
"status.editor.ai-prompt.correct" = "Correct text";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Emphasize text";
"status.editor.ai-prompt.fit" = "Shorten text";
"status.editor.description.add" = "Add description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Eliminar borrador";
"status.draft.save" = "Guardar borrador";
"status.editor.ai-prompt.correct" = "Corregir texto";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Enfatizar texto";
"status.editor.ai-prompt.fit" = "Acortar texto";
"status.editor.description.add" = "Añadir descripción";

View file

@ -412,6 +412,8 @@
"status.draft.delete" = "Ezabatu zirriborroa";
"status.draft.save" = "Gorde zirriborroa";
"status.editor.ai-prompt.correct" = "Zuzendu testua";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Azpimarratu testua";
"status.editor.ai-prompt.fit" = "Laburtu testua";
"status.editor.description.add" = "Gehitu deskribapena";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Supprimer le brouillon";
"status.draft.save" = "Enregistrer le brouillon";
"status.editor.ai-prompt.correct" = "Corriger le texte";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Mettre en évidence le texte";
"status.editor.ai-prompt.fit" = "Raccourcir le texte";
"status.editor.description.add" = "Ajouter une description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Cancella la bozza";
"status.draft.save" = "Salva la bozza";
"status.editor.ai-prompt.correct" = "Correggi il testo";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Enfatizza il testo";
"status.editor.ai-prompt.fit" = "Accorcia il testo";
"status.editor.description.add" = "Aggiungi la descrizione";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "下書きを削除";
"status.draft.save" = "下書きを保存";
"status.editor.ai-prompt.correct" = "テキストを修正する";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "テキストを強調表示する";
"status.editor.ai-prompt.fit" = "テキストを短くする";
"status.editor.description.add" = "説明文を追加";

View file

@ -420,6 +420,8 @@
"status.draft.delete" = "삭제";
"status.draft.save" = "임시 보관함에 저장";
"status.editor.ai-prompt.correct" = "맞게 고치기";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "내용 강조하기";
"status.editor.ai-prompt.fit" = "간결하게 바꾸기";
"status.editor.description.add" = "설명 추가";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Slett utkast";
"status.draft.save" = "Arkiver utkast";
"status.editor.ai-prompt.correct" = "Korrekt tekst";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Fremhev tekst";
"status.editor.ai-prompt.fit" = "Forkort tekst";
"status.editor.description.add" = "Legg til beskrivelse";

View file

@ -412,6 +412,8 @@
"status.draft.delete" = "Verwijder concept";
"status.draft.save" = "Bewaar concept";
"status.editor.ai-prompt.correct" = "Corrigeer tekst";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Benadruk tekst";
"status.editor.ai-prompt.fit" = "Kort tekst in";
"status.editor.description.add" = "Voeg omschrijving toe";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Usuń wersję roboczą";
"status.draft.save" = "Zachowaj wersję roboczą";
"status.editor.ai-prompt.correct" = "Popraw tekst";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Wyróżnij tekst";
"status.editor.ai-prompt.fit" = "Skróć tekst";
"status.editor.description.add" = "Dodaj opis";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Excluir Rascunho";
"status.draft.save" = "Salvar Rascunho";
"status.editor.ai-prompt.correct" = "Corrigir texto";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Enfatizar texto";
"status.editor.ai-prompt.fit" = "Diminuir texto";
"status.editor.description.add" = "Adicionar descrição";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Taslağı Sil";
"status.draft.save" = "Taslağı Kaydet";
"status.editor.ai-prompt.correct" = "Yazıyı Düzelt";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Yazıyı Vurgula";
"status.editor.ai-prompt.fit" = "Yazıyı Kısalt";
"status.editor.description.add" = "Açıklama Ekle";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Видалити чернетку";
"status.draft.save" = "Зберегти чернетку";
"status.editor.ai-prompt.correct" = "Виправити текст";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "Надавати виразності";
"status.editor.ai-prompt.fit" = "Підігнати текст";
"status.editor.description.add" = "Додати опис";

View file

@ -417,6 +417,8 @@
"status.draft.delete" = "删除草稿";
"status.draft.save" = "保存草稿";
"status.editor.ai-prompt.correct" = "检查拼写和语法";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "使用强调语气";
"status.editor.ai-prompt.fit" = "精简文字";
"status.editor.description.add" = "添加描述";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "刪除草稿";
"status.draft.save" = "儲存草稿";
"status.editor.ai-prompt.correct" = "更正";
"status.editor.ai-prompt.add-tags" = "Add #Tags in place";
"status.editor.ai-prompt.insert-tags" = "Add #Tags after my text";
"status.editor.ai-prompt.emphasize" = "強調";
"status.editor.ai-prompt.fit" = "精簡";
"status.editor.description.add" = "新增描述";

View file

@ -1 +1,3 @@
IceCubesApp does not collect or process any personal information from its users. The app is used to connect to third-party Mastodon servers that may or may not collect personal information and are not covered by this privacy policy. Each third-party Mastodon server comes equipped with its own privacy policy that can be viewed through the app or through that server's website.
When you use the OpenAI feature in the composer, please be aware that your input will be sent to the OpenAI server in order to generate a response. Please refer to the [OpenAI Privacy Policy](https://openai.com/policies/privacy-policy) if you want to know more. Nothing is sent to OpenAI if you don't use this feature. You can also completely disable this button in the app settings.

View file

@ -1,7 +1,11 @@
import Foundation
protocol OpenAIRequest: Encodable {
var path: String { get }
}
public struct OpenAIClient {
private let endpoint: URL = .init(string: "https://api.openai.com/v1/completions")!
private let endpoint: URL = .init(string: "https://api.openai.com/v1/")!
private var APIKey: String {
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
@ -26,58 +30,65 @@ public struct OpenAIClient {
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
public struct ChatRequest: OpenAIRequest {
public struct Message: Encodable {
public let role = "user"
public let content: String
}
let model = "gpt-3.5-turbo"
let messages: [Message]
let temperature: CGFloat
var path: String {
"chat/completions"
}
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
public init(content: String, temperature: CGFloat) {
self.messages = [.init(content: content)]
self.temperature = temperature
self.maxTokens = maxTokens
}
}
public enum Prompt {
case correct(input: String)
case shorten(input: String)
case emphasize(input: String)
case addTags(input: String)
case insertTags(input: String)
var request: Request {
var request: OpenAIRequest {
switch self {
case let .correct(input):
return Request(prompt: "Correct this to standard English:\(input)",
temperature: 0,
maxTokens: 500)
return ChatRequest(content: "Fix the spelling and grammar mistakes in the following text: \(input)", temperature: 0.2)
case let .addTags(input):
return ChatRequest(content: "Replace relevant words with Twitter hashtags in the following text while keeping the input same. Maximum of 5 hashtags: \(input)", temperature: 0.1)
case let .insertTags(input):
return ChatRequest(content: "Return the input with added Twitter hashtags at the end of the input with a maximum of 5 hashtags: \(input)", temperature: 0.2)
case let .shorten(input):
return Request(prompt: "Make a summary of this paragraph:\(input)",
temperature: 0.7,
maxTokens: 100)
return ChatRequest(content: "Make a shorter version of this text: \(input)", temperature: 0.5)
case let .emphasize(input):
return Request(prompt: "Make this paragraph catchy, more fun:\(input)",
temperature: 0.8,
maxTokens: 500)
return ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
}
}
}
public struct Response: Decodable {
public struct Choice: Decodable {
public let text: String
public struct Message: Decodable {
public let role: String
public let content: String
}
public let message: Message?
}
public let id: String
public let object: String
public let model: String
public let choices: [Choice]
public var trimmedText: String {
guard var text = choices.first?.text else {
guard var text = choices.first?.message?.content else {
return ""
}
while text.first?.isNewline == true || text.first?.isWhitespace == true {
@ -92,7 +103,7 @@ public struct OpenAIClient {
public func request(_ prompt: Prompt) async throws -> Response {
do {
let jsonData = try encoder.encode(prompt.request)
var request = URLRequest(url: endpoint)
var request = URLRequest(url: endpoint.appending(path: prompt.request.path))
request.httpMethod = "POST"
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
@ -108,6 +119,8 @@ public struct OpenAIClient {
extension OpenAIClient: Sendable {}
extension OpenAIClient.Prompt: Sendable {}
extension OpenAIClient.Request: Sendable {}
extension OpenAIClient.ChatRequest: Sendable {}
extension OpenAIClient.ChatRequest.Message: Sendable {}
extension OpenAIClient.Response: Sendable {}
extension OpenAIClient.Response.Choice: Sendable {}
extension OpenAIClient.Response.Choice.Message: Sendable {}

View file

@ -3,13 +3,17 @@ import Network
import SwiftUI
enum StatusEditorAIPrompt: CaseIterable {
case correct, fit, emphasize
case correct, fit, emphasize, addTags, insertTags
@ViewBuilder
var label: some View {
switch self {
case .correct:
Label("status.editor.ai-prompt.correct", systemImage: "text.badge.checkmark")
case .addTags:
Label("status.editor.ai-prompt.add-tags", systemImage: "number")
case .insertTags:
Label("status.editor.ai-prompt.insert-tags", systemImage: "number")
case .fit:
Label("status.editor.ai-prompt.fit", systemImage: "text.badge.minus")
case .emphasize:
@ -21,6 +25,10 @@ enum StatusEditorAIPrompt: CaseIterable {
switch self {
case .correct:
return .correct(input: text)
case .addTags:
return .addTags(input: text)
case .insertTags:
return .insertTags(input: text)
case .fit:
return .shorten(input: text)
case .emphasize: