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.delete" = "Выдаліць чарнавік";
"status.draft.save" = "Захаваць чарнавік"; "status.draft.save" = "Захаваць чарнавік";
"status.editor.ai-prompt.correct" = "Выправіць тэкст"; "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.emphasize" = "Вылучыць тэкст";
"status.editor.ai-prompt.fit" = "Скараціць тэкст"; "status.editor.ai-prompt.fit" = "Скараціць тэкст";
"status.editor.description.add" = "Дадаць апісанне"; "status.editor.description.add" = "Дадаць апісанне";

View file

@ -417,6 +417,8 @@
"status.draft.delete" = "Elimina l'esborrany"; "status.draft.delete" = "Elimina l'esborrany";
"status.draft.save" = "Desa l'esborrany"; "status.draft.save" = "Desa l'esborrany";
"status.editor.ai-prompt.correct" = "Corregeix el text"; "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.emphasize" = "Amplia text";
"status.editor.ai-prompt.fit" = "Acurta el text"; "status.editor.ai-prompt.fit" = "Acurta el text";
"status.editor.description.add" = "Afegeix una descripció"; "status.editor.description.add" = "Afegeix una descripció";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Entwurf löschen"; "status.draft.delete" = "Entwurf löschen";
"status.draft.save" = "Entwurf sichern"; "status.draft.save" = "Entwurf sichern";
"status.editor.ai-prompt.correct" = "Text korrigieren"; "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.emphasize" = "Text hervorheben";
"status.editor.ai-prompt.fit" = "Text kürzen"; "status.editor.ai-prompt.fit" = "Text kürzen";
"status.editor.description.add" = "Beschreibung hinzufügen"; "status.editor.description.add" = "Beschreibung hinzufügen";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Delete Draft"; "status.draft.delete" = "Delete Draft";
"status.draft.save" = "Save Draft"; "status.draft.save" = "Save Draft";
"status.editor.ai-prompt.correct" = "Correct text"; "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.emphasize" = "Emphasise text";
"status.editor.ai-prompt.fit" = "Shorten text"; "status.editor.ai-prompt.fit" = "Shorten text";
"status.editor.description.add" = "Add description"; "status.editor.description.add" = "Add description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Delete Draft"; "status.draft.delete" = "Delete Draft";
"status.draft.save" = "Save Draft"; "status.draft.save" = "Save Draft";
"status.editor.ai-prompt.correct" = "Correct text"; "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.emphasize" = "Emphasize text";
"status.editor.ai-prompt.fit" = "Shorten text"; "status.editor.ai-prompt.fit" = "Shorten text";
"status.editor.description.add" = "Add description"; "status.editor.description.add" = "Add description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Eliminar borrador"; "status.draft.delete" = "Eliminar borrador";
"status.draft.save" = "Guardar borrador"; "status.draft.save" = "Guardar borrador";
"status.editor.ai-prompt.correct" = "Corregir texto"; "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.emphasize" = "Enfatizar texto";
"status.editor.ai-prompt.fit" = "Acortar texto"; "status.editor.ai-prompt.fit" = "Acortar texto";
"status.editor.description.add" = "Añadir descripción"; "status.editor.description.add" = "Añadir descripción";

View file

@ -412,6 +412,8 @@
"status.draft.delete" = "Ezabatu zirriborroa"; "status.draft.delete" = "Ezabatu zirriborroa";
"status.draft.save" = "Gorde zirriborroa"; "status.draft.save" = "Gorde zirriborroa";
"status.editor.ai-prompt.correct" = "Zuzendu testua"; "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.emphasize" = "Azpimarratu testua";
"status.editor.ai-prompt.fit" = "Laburtu testua"; "status.editor.ai-prompt.fit" = "Laburtu testua";
"status.editor.description.add" = "Gehitu deskribapena"; "status.editor.description.add" = "Gehitu deskribapena";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Supprimer le brouillon"; "status.draft.delete" = "Supprimer le brouillon";
"status.draft.save" = "Enregistrer le brouillon"; "status.draft.save" = "Enregistrer le brouillon";
"status.editor.ai-prompt.correct" = "Corriger le texte"; "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.emphasize" = "Mettre en évidence le texte";
"status.editor.ai-prompt.fit" = "Raccourcir le texte"; "status.editor.ai-prompt.fit" = "Raccourcir le texte";
"status.editor.description.add" = "Ajouter une description"; "status.editor.description.add" = "Ajouter une description";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Cancella la bozza"; "status.draft.delete" = "Cancella la bozza";
"status.draft.save" = "Salva la bozza"; "status.draft.save" = "Salva la bozza";
"status.editor.ai-prompt.correct" = "Correggi il testo"; "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.emphasize" = "Enfatizza il testo";
"status.editor.ai-prompt.fit" = "Accorcia il testo"; "status.editor.ai-prompt.fit" = "Accorcia il testo";
"status.editor.description.add" = "Aggiungi la descrizione"; "status.editor.description.add" = "Aggiungi la descrizione";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "下書きを削除"; "status.draft.delete" = "下書きを削除";
"status.draft.save" = "下書きを保存"; "status.draft.save" = "下書きを保存";
"status.editor.ai-prompt.correct" = "テキストを修正する"; "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.emphasize" = "テキストを強調表示する";
"status.editor.ai-prompt.fit" = "テキストを短くする"; "status.editor.ai-prompt.fit" = "テキストを短くする";
"status.editor.description.add" = "説明文を追加"; "status.editor.description.add" = "説明文を追加";

View file

@ -420,6 +420,8 @@
"status.draft.delete" = "삭제"; "status.draft.delete" = "삭제";
"status.draft.save" = "임시 보관함에 저장"; "status.draft.save" = "임시 보관함에 저장";
"status.editor.ai-prompt.correct" = "맞게 고치기"; "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.emphasize" = "내용 강조하기";
"status.editor.ai-prompt.fit" = "간결하게 바꾸기"; "status.editor.ai-prompt.fit" = "간결하게 바꾸기";
"status.editor.description.add" = "설명 추가"; "status.editor.description.add" = "설명 추가";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Slett utkast"; "status.draft.delete" = "Slett utkast";
"status.draft.save" = "Arkiver utkast"; "status.draft.save" = "Arkiver utkast";
"status.editor.ai-prompt.correct" = "Korrekt tekst"; "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.emphasize" = "Fremhev tekst";
"status.editor.ai-prompt.fit" = "Forkort tekst"; "status.editor.ai-prompt.fit" = "Forkort tekst";
"status.editor.description.add" = "Legg til beskrivelse"; "status.editor.description.add" = "Legg til beskrivelse";

View file

@ -412,6 +412,8 @@
"status.draft.delete" = "Verwijder concept"; "status.draft.delete" = "Verwijder concept";
"status.draft.save" = "Bewaar concept"; "status.draft.save" = "Bewaar concept";
"status.editor.ai-prompt.correct" = "Corrigeer tekst"; "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.emphasize" = "Benadruk tekst";
"status.editor.ai-prompt.fit" = "Kort tekst in"; "status.editor.ai-prompt.fit" = "Kort tekst in";
"status.editor.description.add" = "Voeg omschrijving toe"; "status.editor.description.add" = "Voeg omschrijving toe";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Usuń wersję roboczą"; "status.draft.delete" = "Usuń wersję roboczą";
"status.draft.save" = "Zachowaj wersję roboczą"; "status.draft.save" = "Zachowaj wersję roboczą";
"status.editor.ai-prompt.correct" = "Popraw tekst"; "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.emphasize" = "Wyróżnij tekst";
"status.editor.ai-prompt.fit" = "Skróć tekst"; "status.editor.ai-prompt.fit" = "Skróć tekst";
"status.editor.description.add" = "Dodaj opis"; "status.editor.description.add" = "Dodaj opis";

View file

@ -418,6 +418,8 @@
"status.draft.delete" = "Excluir Rascunho"; "status.draft.delete" = "Excluir Rascunho";
"status.draft.save" = "Salvar Rascunho"; "status.draft.save" = "Salvar Rascunho";
"status.editor.ai-prompt.correct" = "Corrigir texto"; "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.emphasize" = "Enfatizar texto";
"status.editor.ai-prompt.fit" = "Diminuir texto"; "status.editor.ai-prompt.fit" = "Diminuir texto";
"status.editor.description.add" = "Adicionar descrição"; "status.editor.description.add" = "Adicionar descrição";

View file

@ -414,6 +414,8 @@
"status.draft.delete" = "Taslağı Sil"; "status.draft.delete" = "Taslağı Sil";
"status.draft.save" = "Taslağı Kaydet"; "status.draft.save" = "Taslağı Kaydet";
"status.editor.ai-prompt.correct" = "Yazıyı Düzelt"; "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.emphasize" = "Yazıyı Vurgula";
"status.editor.ai-prompt.fit" = "Yazıyı Kısalt"; "status.editor.ai-prompt.fit" = "Yazıyı Kısalt";
"status.editor.description.add" = "Açıklama Ekle"; "status.editor.description.add" = "Açıklama Ekle";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "Видалити чернетку"; "status.draft.delete" = "Видалити чернетку";
"status.draft.save" = "Зберегти чернетку"; "status.draft.save" = "Зберегти чернетку";
"status.editor.ai-prompt.correct" = "Виправити текст"; "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.emphasize" = "Надавати виразності";
"status.editor.ai-prompt.fit" = "Підігнати текст"; "status.editor.ai-prompt.fit" = "Підігнати текст";
"status.editor.description.add" = "Додати опис"; "status.editor.description.add" = "Додати опис";

View file

@ -417,6 +417,8 @@
"status.draft.delete" = "删除草稿"; "status.draft.delete" = "删除草稿";
"status.draft.save" = "保存草稿"; "status.draft.save" = "保存草稿";
"status.editor.ai-prompt.correct" = "检查拼写和语法"; "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.emphasize" = "使用强调语气";
"status.editor.ai-prompt.fit" = "精简文字"; "status.editor.ai-prompt.fit" = "精简文字";
"status.editor.description.add" = "添加描述"; "status.editor.description.add" = "添加描述";

View file

@ -419,6 +419,8 @@
"status.draft.delete" = "刪除草稿"; "status.draft.delete" = "刪除草稿";
"status.draft.save" = "儲存草稿"; "status.draft.save" = "儲存草稿";
"status.editor.ai-prompt.correct" = "更正"; "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.emphasize" = "強調";
"status.editor.ai-prompt.fit" = "精簡"; "status.editor.ai-prompt.fit" = "精簡";
"status.editor.description.add" = "新增描述"; "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. 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 import Foundation
protocol OpenAIRequest: Encodable {
var path: String { get }
}
public struct OpenAIClient { 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 { private var APIKey: String {
if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") { if let path = Bundle.main.path(forResource: "Secret", ofType: "plist") {
@ -27,19 +31,24 @@ public struct OpenAIClient {
return decoder return decoder
} }
public struct Request: Encodable { public struct ChatRequest: OpenAIRequest {
let model = "text-davinci-003" public struct Message: Encodable {
let topP: Int = 1 public let role = "user"
let frequencyPenalty: Int = 0 public let content: String
let presencePenalty: Int = 0 }
let prompt: String
let temperature: Double
let maxTokens: Int
public init(prompt: String, temperature: Double, maxTokens: Int) { let model = "gpt-3.5-turbo"
self.prompt = prompt let messages: [Message]
let temperature: CGFloat
var path: String {
"chat/completions"
}
public init(content: String, temperature: CGFloat) {
self.messages = [.init(content: content)]
self.temperature = temperature self.temperature = temperature
self.maxTokens = maxTokens
} }
} }
@ -47,37 +56,39 @@ public struct OpenAIClient {
case correct(input: String) case correct(input: String)
case shorten(input: String) case shorten(input: String)
case emphasize(input: String) case emphasize(input: String)
case addTags(input: String)
case insertTags(input: String)
var request: Request { var request: OpenAIRequest {
switch self { switch self {
case let .correct(input): case let .correct(input):
return Request(prompt: "Correct this to standard English:\(input)", return ChatRequest(content: "Fix the spelling and grammar mistakes in the following text: \(input)", temperature: 0.2)
temperature: 0, case let .addTags(input):
maxTokens: 500) 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): case let .shorten(input):
return Request(prompt: "Make a summary of this paragraph:\(input)", return ChatRequest(content: "Make a shorter version of this text: \(input)", temperature: 0.5)
temperature: 0.7,
maxTokens: 100)
case let .emphasize(input): case let .emphasize(input):
return Request(prompt: "Make this paragraph catchy, more fun:\(input)", return ChatRequest(content: "Make this text catchy, more fun: \(input)", temperature: 1)
temperature: 0.8,
maxTokens: 500)
} }
} }
} }
public struct Response: Decodable { public struct Response: Decodable {
public struct Choice: 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 let choices: [Choice]
public var trimmedText: String { public var trimmedText: String {
guard var text = choices.first?.text else { guard var text = choices.first?.message?.content else {
return "" return ""
} }
while text.first?.isNewline == true || text.first?.isWhitespace == true { while text.first?.isNewline == true || text.first?.isWhitespace == true {
@ -92,7 +103,7 @@ public struct OpenAIClient {
public func request(_ prompt: Prompt) async throws -> Response { public func request(_ prompt: Prompt) async throws -> Response {
do { do {
let jsonData = try encoder.encode(prompt.request) 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.httpMethod = "POST"
request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization") request.setValue(authorizationHeaderValue, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Content-Type")
@ -108,6 +119,8 @@ public struct OpenAIClient {
extension OpenAIClient: Sendable {} extension OpenAIClient: Sendable {}
extension OpenAIClient.Prompt: 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: Sendable {}
extension OpenAIClient.Response.Choice: Sendable {} extension OpenAIClient.Response.Choice: Sendable {}
extension OpenAIClient.Response.Choice.Message: Sendable {}

View file

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