0876vF|B7DVbtk6Bk298HgDu7@F=X(jvYgjNL
zp5cJ%_{{~Au_Aaztwkg9m?5cBH}HHp0rWaS<>cpGgFdG0>G$NAgAy8+voFGIQl-$u
zi?p3GA=|75oG&~-)?-2%4nM_&7AjVWJPf^H|0h~7!vXd%8S0Es
ziQrE)^J3sxrYD_G|MkwdNhx-mnL<}(iYoBr;>bXko1tS{swH7OEnwyV3mHX_RK<9E
z2&ta`=FVB-@zHUB&t;J;b}iWe%M00Uw`4uguVk6FliJjv7@NT~Wi(et;ZV|Iww>R(
z|2pXUzh9qfy=2_LTfIk03p>?=%!E{8@r-Tb1MqA){94rCD={RxT$#1`m)u9f1%mSG
z!Rq@;C%cT=woZeDR+MJ-S#yEJP%+|~TW2H6QMDePn?AQt^ZR_2?)f%*l>@1Zyu
z)P7vEuL99COk$}-F{iJB6gDr@c>H@A8bD)DSPNHaWfHs^yiJ(KuXm4Z@?YT&F%&X)
zn>Kq%itUjU(zJ30Y*Go=;X;l%lZa=FvJx$z@oUKYL~QJ3EzUl@@7E>`1(a^lZejuC
zXvuFkt{m=U6QWP`S0?iC+zsN>a-nZW7#~I*E@CtoG*27DB}0tyH_hed;`;Yxi{auGD3E@^05Hb
zd=xkJAw2P=`e>`(Fz?n|;5WRvvNz=oF5*+W!Xz%rScgKt56cLhZ*5r18kOg}LqEMU
z$jzMgpds26^AZTxhh8I0V1iFB{V`afpN(L=3@6Rr3Kh*hqjlSCze#PPtIv&=Jg8?C
zr15y{-%JQ;P*|3P1S~}}WyQV8A2mzX98}N$D`^S|YMf0764qkv?pUi6>*ngI{N5TQ
zDN4T(fxIu_#PNaORT(_abhAZ!raf#l~5c+nc3&<7wBK3(i{5N)G16>W24U418QWK!YtiXj}AKH
zt@p4e?r4os_j&wJ|JeoF%i%wp`prGjMrWV9dS39^1K*yI@Ww1%9ylo<*UTDOFQjkd
z=htKV8l9o&%y5h|576H!bU3@W%?~dwN-?JkZn9;LZBkdClp-VA6@{eY={d8x{fjz0*jqH|s$Z$VRvSGpbBVTN_d}GBtBUX_U*b8JG~h=h%$7NGUEe+)w@n?
zasSsCqRqV}tpm3s4-qynq98Q}qPB2sR5MdFgTON(e?aTq{u-*CeyADgQoROqChAeU#ED6@A
zSPx?Kx_sc}d_=4U*$=krcyn~%(E6<|reIoH%4!=FH
zA4C&jOD&dUU)`K%v&OEp2S6$vs(1QrG`E{lf#+r(H^PULuoWu#?UTcwR(^}h_&?&M
zYyHKe;8gz92QSjrgt|g(88&IV35FyL=y|J^ja~DIw}E?-vn>~2BA@u_d?2?4C3c9_
zITh=&6wpA*ot8wwel~FC9v&Nl=5aKwBKy{pvFtc98+dYzxNbujnmEFjh(>xBNxcS$
z43nt6rf3?ojA%LjMgM$279|1bHVZuCw0Mh{u5GeI;fX$6)He6|jc`d3FkUHkAOV
zVvt`}1{+tLA9umphb8n1h@&6dv0w6viu7~*-vF(W@p4QSBEPheD+9Zog0Tuc0#tOi^6p6r5Hdh
z9S5TZpNiip+}5FxnNj7g>qn+uO1@xHjs*)1$b6DfVeKy&B8jr5tgCPF0mqkMKwNcK
zysh0SCMdxq<`
zjo!Joq_QCBzobrMPHsBC0tbo62=Y3HQWqe%j{M=dEUVYMpHJ8e?fxz#J+G#+LWCnh
zup)0~oPiH?6Y3$1i*pQ^C0>wB%pu3_tX<<&InAV$xwV2+HEhO`$x1V5MN6L0_&ZCs
z5ptOQRYg`?V9Jjy4!@dn{akT0G5-+KQg?JQ(mGCa3neLgNu%JuN#P#rRrd*Dp=*iW
zk~zgV6fEDtQd3liY;R~?lZ5`O#so0`Q-+B5zmQHq000L66>R#yY52bz{r?G(|38-T
z|AYvNs`USwRxmR37A?x8F09Y^xw*x0fbbu!Att4n^}3jqv80#~|4$$O_g-)em{YV^
UPBAOaV*&(cj7$xSP7?3^FKR$O82|tP
literal 0
HcmV?d00001
diff --git a/Explorer/Package.swift b/Explorer/Package.swift
new file mode 100644
index 0000000..f293613
--- /dev/null
+++ b/Explorer/Package.swift
@@ -0,0 +1,4 @@
+// swift-tools-version: 5.10
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
diff --git a/Package.swift b/Package.swift
index 6f30ac2..d400b9e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -11,7 +11,7 @@ let package = Package(
],
products: [
.library(
- name: "SwiftyGPT",
+ name: "SwiftyGPTChat",
targets: [
"SwiftyGPTChat",
]
From 1168d14d152270887f7a8731c8812cb95c109b69 Mon Sep 17 00:00:00 2001
From: Antonio War
Date: Sat, 27 Apr 2024 21:15:55 +0200
Subject: [PATCH 04/16] feat: add open ai api key to environment variables
---
.../xcschemes/SwiftyGPTChat.xcscheme | 67 +++++++++++++++
.../xcshareddata/xcschemes/Explorer.xcscheme | 85 +++++++++++++++++++
2 files changed, 152 insertions(+)
create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme
create mode 100644 Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme
new file mode 100644
index 0000000..e58709d
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme b/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme
new file mode 100644
index 0000000..d65bd87
--- /dev/null
+++ b/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 74014352a496d36e2b0652fcb5feba5f2c874fd2 Mon Sep 17 00:00:00 2001
From: Antonio War
Date: Sun, 28 Apr 2024 11:56:03 +0200
Subject: [PATCH 05/16] fix: update all object scopes
---
Explorer/Explorer.xcodeproj/project.pbxproj | 24 +++++--
Explorer/Explorer/Chat/Views/ChatView.swift | 70 +++++++++++++++++++
Explorer/Explorer/ContentView.swift | 24 -------
Explorer/Explorer/ExplorerApp.swift | 24 ++++++-
.../Managers/SwiftyGPTChatManager.swift | 8 +--
.../Models/SwiftyGPTChatMessage.swift | 44 ++++++------
.../Models/SwiftyGPTChatRequestBody.swift | 41 ++++++-----
.../Models/SwiftyGPTChatResponse.swift | 2 +-
.../Models/SwiftyGPTChatResponseBody.swift | 16 ++++-
.../Models/SwiftyGPTChatResponseChoice.swift | 22 +++++-
.../Models/SwiftyGPTChatResponseError.swift | 11 ++-
.../Models/SwiftyGPTChatResponseFormat.swift | 4 +-
.../SwiftyGPTChatResponseTokenUsage.swift | 12 +++-
.../Services/SwiftyGPTChatMockService.swift | 14 ++--
.../SwiftyGPTChatNetworkingService.swift | 16 ++---
.../Services/SwiftyGPTChatService.swift | 2 +-
16 files changed, 229 insertions(+), 105 deletions(-)
create mode 100644 Explorer/Explorer/Chat/Views/ChatView.swift
delete mode 100644 Explorer/Explorer/ContentView.swift
diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj
index 8845d97..4be0ff1 100644
--- a/Explorer/Explorer.xcodeproj/project.pbxproj
+++ b/Explorer/Explorer.xcodeproj/project.pbxproj
@@ -8,16 +8,16 @@
/* Begin PBXBuildFile section */
C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */; };
- C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3F2BDD7B980076E6E8 /* ContentView.swift */; };
C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; };
+ C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13399A32BDE461600BAF2B8 /* ChatView.swift */; };
C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */ = {isa = PBXBuildFile; productRef = C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
C1122F3A2BDD7B980076E6E8 /* Explorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Explorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerApp.swift; sourceTree = ""; };
- C1122F3F2BDD7B980076E6E8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ C13399A32BDE461600BAF2B8 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; };
C188BCC92BDD84AB0057B93E /* SwiftyGPT */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyGPT; path = ..; sourceTree = ""; };
/* End PBXFileReference section */
@@ -54,12 +54,28 @@
isa = PBXGroup;
children = (
C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */,
- C1122F3F2BDD7B980076E6E8 /* ContentView.swift */,
+ C13399A52BDE496F00BAF2B8 /* Chat */,
C1122F412BDD7B990076E6E8 /* Assets.xcassets */,
);
path = Explorer;
sourceTree = "";
};
+ C13399A52BDE496F00BAF2B8 /* Chat */ = {
+ isa = PBXGroup;
+ children = (
+ C13399A62BDE497700BAF2B8 /* Views */,
+ );
+ path = Chat;
+ sourceTree = "";
+ };
+ C13399A62BDE497700BAF2B8 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ C13399A32BDE461600BAF2B8 /* ChatView.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -135,7 +151,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */,
+ C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */,
C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Explorer/Explorer/Chat/Views/ChatView.swift b/Explorer/Explorer/Chat/Views/ChatView.swift
new file mode 100644
index 0000000..8d8a19e
--- /dev/null
+++ b/Explorer/Explorer/Chat/Views/ChatView.swift
@@ -0,0 +1,70 @@
+//
+// ChatView.swift
+// Explorer
+//
+// Created by Antonio Guerra on 28/04/24.
+//
+
+import SwiftUI
+import SwiftyGPTChat
+
+struct ChatView: View {
+ private let environment: ExplorerApp.Environment
+ @State var typedMessage: String
+
+ init(environment: ExplorerApp.Environment, typedMessage: String = "") {
+ self.environment = environment
+ self.typedMessage = typedMessage
+ }
+
+ var body: some View {
+ VStack(spacing: .zero) {
+ Spacer()
+ typingView
+ }
+ }
+
+ private var typingView: some View {
+ HStack {
+ TextField("Send a message...", text: $typedMessage)
+ .textFieldStyle(.roundedBorder)
+ Button(action: {}) {
+ Image(systemName: "paperplane")
+ .padding(5)
+ .foregroundStyle(Color.white)
+ .background(Color.accentColor)
+ .clipShape(Circle())
+ }
+ .disabled(typedMessage.isEmpty)
+ }
+ .padding()
+ }
+
+ private var manager: SwiftyGPTChatManager {
+ return SwiftyGPTChatManager(service: service)
+ }
+
+ private var service: any SwiftyGPTChatService {
+ switch environment {
+ case .release, .debug:
+ return SwiftyGPTChatNetworkingService(apiKey: apiKey)
+ case .test, .preview:
+ let choices = [
+ SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop)
+ ]
+ let responseBody = SwiftyGPTChatResponseSuccessBody(id: UUID().uuidString, choices: choices, created: Date().timeIntervalSince1970, model: .gpt3_5_turbo, fingerprint: UUID().uuidString, object: .completion, usage: SwiftyGPTChatResponseTokenUsage(completion: 3, prompt: 3, total: 3))
+ return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.1)
+ }
+ }
+
+ private var apiKey: String {
+ guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else {
+ fatalError("Missing OpenAI API key. Please set the OPEN_AI_API_KEY environment variable.")
+ }
+ return apiKey
+ }
+}
+
+#Preview {
+ ChatView(environment: .preview)
+}
diff --git a/Explorer/Explorer/ContentView.swift b/Explorer/Explorer/ContentView.swift
deleted file mode 100644
index 7ee7f78..0000000
--- a/Explorer/Explorer/ContentView.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// ContentView.swift
-// Explorer
-//
-// Created by Antonio Guerra on 27/04/24.
-//
-
-import SwiftUI
-
-struct ContentView: View {
- var body: some View {
- VStack {
- Image(systemName: "globe")
- .imageScale(.large)
- .foregroundStyle(.tint)
- Text("Hello, world!")
- }
- .padding()
- }
-}
-
-#Preview {
- ContentView()
-}
diff --git a/Explorer/Explorer/ExplorerApp.swift b/Explorer/Explorer/ExplorerApp.swift
index b606f96..aca167e 100644
--- a/Explorer/Explorer/ExplorerApp.swift
+++ b/Explorer/Explorer/ExplorerApp.swift
@@ -6,12 +6,34 @@
//
import SwiftUI
+import SwiftyGPTChat
@main
struct ExplorerApp: App {
var body: some Scene {
WindowGroup {
- ContentView()
+ ChatView(environment: environment)
}
}
+
+ private var environment: Environment {
+ #if DEBUG
+ if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
+ return Environment.test
+ } else if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
+ return Environment.preview
+ } else {
+ return Environment.debug
+ }
+ #else
+ return Environment.release
+ #endif
+ }
+
+ enum Environment {
+ case release
+ case debug
+ case test
+ case preview
+ }
}
diff --git a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift
index d2f6404..36d6533 100644
--- a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift
+++ b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift
@@ -7,14 +7,14 @@
import Foundation
-struct SwiftyGPTChatManager {
- private let service: SwiftyGPTChatService
+public struct SwiftyGPTChatManager {
+ public let service: SwiftyGPTChatService
- init(service: SwiftyGPTChatService) {
+ public init(service: SwiftyGPTChatService) {
self.service = service
}
- func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logitBias: [Int: Int]? = nil, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse {
+ public func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logitBias: [Int: Int]? = nil, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse {
let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logitBias: logitBias, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, responseFormat: responseFormat, seed: seed, temperature: temperature, topP: topP, user: user)
let responseBody = try await service.request(body: requestBody)
return try SwiftyGPTChatResponse(body: responseBody)
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
index 4968661..1561d49 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
@@ -7,7 +7,7 @@
import Foundation
-protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable {
+public protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable {
var role: SwiftyGPTChatRole { get }
}
@@ -41,23 +41,23 @@ public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage {
}
}
-struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage {
- let role: SwiftyGPTChatRole = .user
- let content: String
- let name: String?
+public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage {
+ public let role: SwiftyGPTChatRole = .user
+ public let content: String
+ public let name: String?
- init(content: String, name: String? = nil) {
+ public init(content: String, name: String? = nil) {
self.content = content
self.name = name
}
- init(from decoder: any Decoder) throws {
+ public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.content = try container.decode(String.self, forKey: .content)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
}
- func encode(to encoder: any Encoder) throws {
+ public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(role, forKey: .role)
try container.encode(content, forKey: .content)
@@ -71,25 +71,25 @@ struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage {
}
}
-struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage {
- let role: SwiftyGPTChatRole = .assistant
+public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage {
+ public let role: SwiftyGPTChatRole = .assistant
// TODO: content will be optional once tool_calls parameter will be supported
- let content: String
- let name: String?
+ public let content: String
+ public let name: String?
// TODO: add tool_calls parameter support
- init(content: String, name: String? = nil) {
+ public init(content: String, name: String? = nil) {
self.content = content
self.name = name
}
- init(from decoder: any Decoder) throws {
+ public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.content = try container.decode(String.self, forKey: .content)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
}
- func encode(to encoder: any Encoder) throws {
+ public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.role, forKey: .role)
try container.encode(self.content, forKey: .content)
@@ -103,21 +103,21 @@ struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage {
}
}
-struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage {
- let role: SwiftyGPTChatRole = .tool
- let content: String
+public struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage {
+ public let role: SwiftyGPTChatRole = .tool
+ public let content: String
// TODO: add tool_call_id
- init(content: String) {
+ public init(content: String) {
self.content = content
}
- init(from decoder: any Decoder) throws {
+ public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.content = try container.decode(String.self, forKey: .content)
}
- func encode(to encoder: any Encoder) throws {
+ public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.role, forKey: .role)
try container.encode(self.content, forKey: .content)
@@ -134,7 +134,7 @@ enum SwiftyGPTChatCodableMessage: Equatable, Encodable, Decodable {
case user(SwiftyGPTChatUserMessage)
case assistant(SwiftyGPTChatAssistantMessage)
case tool(SwiftyGPTChatToolMessage)
-
+
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let singleContainer = try decoder.singleValueContainer()
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift
index 9959b5f..20e94a4 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift
@@ -7,27 +7,27 @@
import Foundation
-struct SwiftyGPTChatRequestBody: Encodable {
- let messages: [any SwiftyGPTChatMessage]
- let model: SwiftyGPTChatModel
- let frequencyPenalty: Double?
- let logitBias: [Int: Int]?
- let logprobs: Bool?
- let topLogprobs: Int?
- let maxTokens: Int?
- let n: Int?
- let presencePenalty: Double?
- let responseFormat: SwiftyGPTChatResponseFormat?
- let seed: Int?
+public struct SwiftyGPTChatRequestBody: Encodable {
+ public let messages: [any SwiftyGPTChatMessage]
+ public let model: SwiftyGPTChatModel
+ public let frequencyPenalty: Double?
+ public let logitBias: [Int: Int]?
+ public let logprobs: Bool?
+ public let topLogprobs: Int?
+ public let maxTokens: Int?
+ public let n: Int?
+ public let presencePenalty: Double?
+ public let responseFormat: SwiftyGPTChatResponseFormat?
+ public let seed: Int?
// TODO: add stop parameter support
// TODO: add stream parameter support
- let temperature: Double?
- let topP: Double?
+ public let temperature: Double?
+ public let topP: Double?
// TODO: add tools parameter support
// TODO: add tool_choice parameter support
- let user: String?
+ public let user: String?
- init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) {
+ public init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) {
self.messages = messages
self.model = model
self.frequencyPenalty = frequencyPenalty
@@ -45,10 +45,8 @@ struct SwiftyGPTChatRequestBody: Encodable {
}
var codableMessages: [SwiftyGPTChatCodableMessage] {
- return messages.compactMap {
+ return messages.map {
switch $0 {
- case let message as SwiftyGPTChatSystemMessage:
- return .system(message)
case let message as SwiftyGPTChatUserMessage:
return .user(message)
case let message as SwiftyGPTChatAssistantMessage:
@@ -56,12 +54,13 @@ struct SwiftyGPTChatRequestBody: Encodable {
case let message as SwiftyGPTChatToolMessage:
return .tool(message)
default:
- return .none
+ let message = $0 as! SwiftyGPTChatSystemMessage
+ return .system(message)
}
}
}
- func encode(to encoder: any Encoder) throws {
+ public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(model, forKey: .model)
try container.encode(codableMessages, forKey: .messages)
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift
index 2b55db6..7b78997 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift
@@ -7,7 +7,7 @@
import Foundation
-enum SwiftyGPTChatResponse: Equatable {
+public enum SwiftyGPTChatResponse: Equatable {
case success(body: SwiftyGPTChatResponseSuccessBody)
case failure(body: SwiftyGPTChatResponseFailureBody)
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift
index edc26ba..eaa87c0 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift
@@ -18,6 +18,16 @@ public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Ident
public let object: SwiftyGPTChatResponseObject
public let usage: SwiftyGPTChatResponseTokenUsage
+ public init(id: String, choices: [SwiftyGPTChatResponseChoice], created: TimeInterval, model: SwiftyGPTChatModel, fingerprint: String, object: SwiftyGPTChatResponseObject, usage: SwiftyGPTChatResponseTokenUsage) {
+ self.id = id
+ self.choices = choices
+ self.created = created
+ self.model = model
+ self.fingerprint = fingerprint
+ self.object = object
+ self.usage = usage
+ }
+
enum CodingKeys: String, CodingKey {
case id
case choices
@@ -30,5 +40,9 @@ public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Ident
}
public struct SwiftyGPTChatResponseFailureBody: SwiftyGPTChatResponseBody, Decodable {
- let error: SwiftyGPTChatResponseError
+ public let error: SwiftyGPTChatResponseError
+
+ public init(error: SwiftyGPTChatResponseError) {
+ self.error = error
+ }
}
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift
index 1236b6e..213aa28 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift
@@ -8,11 +8,11 @@
import Foundation
public struct SwiftyGPTChatResponseChoice: Decodable, Equatable {
- let index: Int
+ public let index: Int
let codableMessage: SwiftyGPTChatCodableMessage
- let finishReason: SwiftyGPTChatResponseFinishReason
+ public let finishReason: SwiftyGPTChatResponseFinishReason
- var message: any SwiftyGPTChatMessage {
+ public var message: any SwiftyGPTChatMessage {
switch codableMessage {
case .system(let message):
return message
@@ -25,6 +25,22 @@ public struct SwiftyGPTChatResponseChoice: Decodable, Equatable {
}
}
+ public init(index: Int, message: any SwiftyGPTChatMessage, finishReason: SwiftyGPTChatResponseFinishReason) {
+ self.index = index
+ switch message {
+ case let message as SwiftyGPTChatUserMessage:
+ self.codableMessage = .user(message)
+ case let message as SwiftyGPTChatAssistantMessage:
+ self.codableMessage = .assistant(message)
+ case let message as SwiftyGPTChatToolMessage:
+ self.codableMessage = .tool(message)
+ default:
+ let message = message as! SwiftyGPTChatSystemMessage
+ self.codableMessage = .system(message)
+ }
+ self.finishReason = finishReason
+ }
+
init(index: Int, codableMessage: SwiftyGPTChatCodableMessage, finishReason: SwiftyGPTChatResponseFinishReason) {
self.index = index
self.codableMessage = codableMessage
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
index 611ce8e..31e072d 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
@@ -7,7 +7,12 @@
import Foundation
-struct SwiftyGPTChatResponseError: Decodable, Equatable {
- let type: String
- let message: String
+public struct SwiftyGPTChatResponseError: Decodable, Equatable {
+ public let type: String
+ public let message: String
+
+ public init(type: String, message: String) {
+ self.type = type
+ self.message = message
+ }
}
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift
index 3bd1a8d..d9d28be 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift
@@ -7,7 +7,7 @@
import Foundation
-enum SwiftyGPTChatResponseFormat: String, Encodable {
+public enum SwiftyGPTChatResponseFormat: String, Encodable {
case text = "text"
case json = "json_object"
@@ -15,7 +15,7 @@ enum SwiftyGPTChatResponseFormat: String, Encodable {
case type
}
- func encode(to encoder: any Encoder) throws {
+ public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(rawValue, forKey: .type)
}
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift
index 09c0ed6..2542c25 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift
@@ -8,9 +8,15 @@
import Foundation
public struct SwiftyGPTChatResponseTokenUsage: Decodable, Equatable {
- let completion: Int
- let prompt: Int
- let total: Int
+ public let completion: Int
+ public let prompt: Int
+ public let total: Int
+
+ public init(completion: Int, prompt: Int, total: Int) {
+ self.completion = completion
+ self.prompt = prompt
+ self.total = total
+ }
enum CodingKeys: String, CodingKey {
case completion = "completion_tokens"
diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
index 1a27c91..826c556 100644
--- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
+++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
@@ -7,21 +7,21 @@
import Foundation
-class SwiftyGPTChatMockService: SwiftyGPTChatService {
- private let responseBody: any SwiftyGPTChatResponseBody
- private let duration: TimeInterval
+public class SwiftyGPTChatMockService: SwiftyGPTChatService {
+ let responseBody: any SwiftyGPTChatResponseBody
+ let duration: TimeInterval
- private (set) var requestCallCount: Int = 0
- var requestCalled: Bool {
+ private (set) public var requestCallCount: Int = 0
+ public var requestCalled: Bool {
return requestCallCount > 0
}
- init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) {
+ public init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) {
self.responseBody = responseBody
self.duration = duration
}
- func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody {
+ public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody {
try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000)
requestCallCount += 1
return responseBody
diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift
index e3f57b8..6c87814 100644
--- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift
+++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift
@@ -8,14 +8,14 @@
import Foundation
import SwiftyGPTNetworking
-struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService {
- let client: SwiftyGPTNetworkingClient
- let encoder: JSONEncoder
- let decoder: JSONDecoder
- let apiKey: String
- let organization: String?
+public struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService {
+ public let client: SwiftyGPTNetworkingClient
+ public let encoder: JSONEncoder
+ public let decoder: JSONDecoder
+ public let apiKey: String
+ public let organization: String?
- init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) {
+ public init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) {
self.client = client
self.encoder = encoder
self.decoder = decoder
@@ -23,7 +23,7 @@ struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService {
self.organization = organization
}
- func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody {
+ public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody {
let body = try JSONEncoder().encode(body)
let request = SwiftyGPTChatRequest(apiKey: apiKey, organization: organization, body: body)
let response = try await client.send(request: request)
diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift
index 8989689..12bb1fd 100644
--- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift
+++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift
@@ -7,6 +7,6 @@
import Foundation
-protocol SwiftyGPTChatService {
+public protocol SwiftyGPTChatService {
func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody
}
From 26f8d9b0098db20555da15daa64fff1a48496ec1 Mon Sep 17 00:00:00 2001
From: Antonio War
Date: Sun, 28 Apr 2024 12:59:21 +0200
Subject: [PATCH 06/16] refactor: make SwiftyGPTChatMessage identifiable
---
Explorer/Explorer.xcodeproj/project.pbxproj | 14 ++--
.../Explorer/Chat/{Views => }/ChatView.swift | 49 +++++++++++---
Explorer/Explorer/Chat/MessageView.swift | 65 +++++++++++++++++++
.../Models/SwiftyGPTChatMessage.swift | 7 +-
.../Models/SwiftyGPTChatResponseError.swift | 2 +-
.../Services/SwiftyGPTChatMockService.swift | 2 +-
6 files changed, 117 insertions(+), 22 deletions(-)
rename Explorer/Explorer/Chat/{Views => }/ChatView.swift (56%)
create mode 100644 Explorer/Explorer/Chat/MessageView.swift
diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj
index 4be0ff1..8723bbb 100644
--- a/Explorer/Explorer.xcodeproj/project.pbxproj
+++ b/Explorer/Explorer.xcodeproj/project.pbxproj
@@ -11,6 +11,7 @@
C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; };
C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13399A32BDE461600BAF2B8 /* ChatView.swift */; };
C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */ = {isa = PBXBuildFile; productRef = C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */; };
+ C1FB23572BDE5664008F1C5F /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB23562BDE5664008F1C5F /* MessageView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -19,6 +20,7 @@
C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
C13399A32BDE461600BAF2B8 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; };
C188BCC92BDD84AB0057B93E /* SwiftyGPT */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyGPT; path = ..; sourceTree = ""; };
+ C1FB23562BDE5664008F1C5F /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -61,19 +63,12 @@
sourceTree = "";
};
C13399A52BDE496F00BAF2B8 /* Chat */ = {
- isa = PBXGroup;
- children = (
- C13399A62BDE497700BAF2B8 /* Views */,
- );
- path = Chat;
- sourceTree = "";
- };
- C13399A62BDE497700BAF2B8 /* Views */ = {
isa = PBXGroup;
children = (
C13399A32BDE461600BAF2B8 /* ChatView.swift */,
+ C1FB23562BDE5664008F1C5F /* MessageView.swift */,
);
- path = Views;
+ path = Chat;
sourceTree = "";
};
/* End PBXGroup section */
@@ -151,6 +146,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ C1FB23572BDE5664008F1C5F /* MessageView.swift in Sources */,
C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */,
C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */,
);
diff --git a/Explorer/Explorer/Chat/Views/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift
similarity index 56%
rename from Explorer/Explorer/Chat/Views/ChatView.swift
rename to Explorer/Explorer/Chat/ChatView.swift
index 8d8a19e..ec0a584 100644
--- a/Explorer/Explorer/Chat/Views/ChatView.swift
+++ b/Explorer/Explorer/Chat/ChatView.swift
@@ -10,25 +10,31 @@ import SwiftyGPTChat
struct ChatView: View {
private let environment: ExplorerApp.Environment
- @State var typedMessage: String
+ @State private var messages: [any SwiftyGPTChatMessage]
+ @State private var typedMessage: String
- init(environment: ExplorerApp.Environment, typedMessage: String = "") {
+ init(environment: ExplorerApp.Environment, messages: [any SwiftyGPTChatMessage] = [], typedMessage: String = "") {
self.environment = environment
+ self.messages = messages
self.typedMessage = typedMessage
}
var body: some View {
- VStack(spacing: .zero) {
- Spacer()
- typingView
+ ScrollView {
+ LazyVStack(spacing: .zero) {
+ ForEach(messages, id: \.id) { message in
+ MessageView(message: message)
+ }
+ }
}
+ typingView
}
private var typingView: some View {
HStack {
TextField("Send a message...", text: $typedMessage)
.textFieldStyle(.roundedBorder)
- Button(action: {}) {
+ Button(action: sendMessage ) {
Image(systemName: "paperplane")
.padding(5)
.foregroundStyle(Color.white)
@@ -46,14 +52,14 @@ struct ChatView: View {
private var service: any SwiftyGPTChatService {
switch environment {
- case .release, .debug:
- return SwiftyGPTChatNetworkingService(apiKey: apiKey)
- case .test, .preview:
+// case .release, .debug:
+// return SwiftyGPTChatNetworkingService(apiKey: apiKey)
+ case .release, .debug, .test, .preview:
let choices = [
SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop)
]
let responseBody = SwiftyGPTChatResponseSuccessBody(id: UUID().uuidString, choices: choices, created: Date().timeIntervalSince1970, model: .gpt3_5_turbo, fingerprint: UUID().uuidString, object: .completion, usage: SwiftyGPTChatResponseTokenUsage(completion: 3, prompt: 3, total: 3))
- return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.1)
+ return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.3)
}
}
@@ -63,6 +69,29 @@ struct ChatView: View {
}
return apiKey
}
+
+ private func sendMessage() {
+ let sentMessage = SwiftyGPTChatUserMessage(content: typedMessage)
+ messages.append(sentMessage)
+ typedMessage = ""
+ Task {
+ do {
+ let response = try await manager.send(messages: [sentMessage], model: .gpt3_5_turbo)
+ switch response {
+ case .success(let body):
+ if let receivedMessage = body.choices.first?.message {
+ messages.append(receivedMessage)
+ } else {
+ throw URLError(.badServerResponse)
+ }
+ case .failure(let body):
+ throw body.error
+ }
+ } catch {
+ print(error)
+ }
+ }
+ }
}
#Preview {
diff --git a/Explorer/Explorer/Chat/MessageView.swift b/Explorer/Explorer/Chat/MessageView.swift
new file mode 100644
index 0000000..6c3d771
--- /dev/null
+++ b/Explorer/Explorer/Chat/MessageView.swift
@@ -0,0 +1,65 @@
+//
+// MessageView.swift
+// Explorer
+//
+// Created by Antonio Guerra on 28/04/24.
+//
+
+import SwiftUI
+import SwiftyGPTChat
+
+struct MessageView: View {
+ private var message: any SwiftyGPTChatMessage
+
+ init(message: any SwiftyGPTChatMessage) {
+ self.message = message
+ }
+
+ var body: some View {
+ switch message {
+ case let message as SwiftyGPTChatUserMessage:
+ sentMessageView(content: message.content)
+ case let message as SwiftyGPTChatAssistantMessage:
+ receivedMessageView(content: message.content)
+ case let message as SwiftyGPTChatToolMessage:
+ receivedMessageView(content: message.content)
+ default:
+ let message = message as! SwiftyGPTChatSystemMessage
+ receivedMessageView(content: message.content)
+ }
+ }
+
+ private func sentMessageView(content: String) -> some View {
+ HStack(alignment: .bottom, spacing: 10) {
+ Spacer()
+ Text(content)
+ .padding(10)
+ .foregroundColor(Color.white)
+ .background(Color.accentColor)
+ .cornerRadius(10)
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding()
+ }
+
+ private func receivedMessageView(content: String) -> some View {
+ HStack(alignment: .bottom, spacing: 10) {
+ Text(content)
+ .padding(10)
+ .foregroundColor(Color.black)
+ .background(Color(UIColor.systemGray6))
+ .cornerRadius(10)
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding()
+ }
+}
+
+#Preview("Sent message") {
+ MessageView(message: SwiftyGPTChatUserMessage(content: "Hello, how are you ?"))
+}
+
+#Preview("Received message") {
+ MessageView(message: SwiftyGPTChatSystemMessage(content: "Hello! I'm fine and you ?"))
+}
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
index 1561d49..87eb0d0 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift
@@ -7,11 +7,13 @@
import Foundation
-public protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable {
+public protocol SwiftyGPTChatMessage: Identifiable, Equatable, Encodable, Decodable, Hashable {
+ var id: UUID { get }
var role: SwiftyGPTChatRole { get }
}
public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage {
+ public let id: UUID = UUID()
public let role: SwiftyGPTChatRole = .system
public let content: String
public let name: String?
@@ -42,6 +44,7 @@ public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage {
}
public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage {
+ public let id: UUID = UUID()
public let role: SwiftyGPTChatRole = .user
public let content: String
public let name: String?
@@ -72,6 +75,7 @@ public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage {
}
public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage {
+ public let id: UUID = UUID()
public let role: SwiftyGPTChatRole = .assistant
// TODO: content will be optional once tool_calls parameter will be supported
public let content: String
@@ -104,6 +108,7 @@ public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage {
}
public struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage {
+ public let id: UUID = UUID()
public let role: SwiftyGPTChatRole = .tool
public let content: String
// TODO: add tool_call_id
diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
index 31e072d..83a6845 100644
--- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
+++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift
@@ -7,7 +7,7 @@
import Foundation
-public struct SwiftyGPTChatResponseError: Decodable, Equatable {
+public struct SwiftyGPTChatResponseError: Decodable, Equatable, Error {
public let type: String
public let message: String
diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
index 826c556..c2a431d 100644
--- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
+++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift
@@ -22,7 +22,7 @@ public class SwiftyGPTChatMockService: SwiftyGPTChatService {
}
public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody {
- try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000)
+ try await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))
requestCallCount += 1
return responseBody
}
From 4acb947aa8af12ace6d55f5a256f5cb3a0ca1276 Mon Sep 17 00:00:00 2001
From: Antonio War
Date: Sun, 28 Apr 2024 13:00:01 +0200
Subject: [PATCH 07/16] fix: remove unwanted comment
---
Explorer/Explorer/Chat/ChatView.swift | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Explorer/Explorer/Chat/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift
index ec0a584..af47176 100644
--- a/Explorer/Explorer/Chat/ChatView.swift
+++ b/Explorer/Explorer/Chat/ChatView.swift
@@ -52,9 +52,9 @@ struct ChatView: View {
private var service: any SwiftyGPTChatService {
switch environment {
-// case .release, .debug:
-// return SwiftyGPTChatNetworkingService(apiKey: apiKey)
- case .release, .debug, .test, .preview:
+ case .release, .debug:
+ return SwiftyGPTChatNetworkingService(apiKey: apiKey)
+ case .test, .preview:
let choices = [
SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop)
]
From ea7803dcbde791b0020068f601bfaa2c6ebe32f0 Mon Sep 17 00:00:00 2001
From: Antonio War
Date: Sun, 28 Apr 2024 15:19:02 +0200
Subject: [PATCH 08/16] fix: error description
---
Explorer/Explorer/Chat/ChatView.swift | 2 +-
Explorer/Explorer/ExplorerApp.swift | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/Explorer/Explorer/Chat/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift
index af47176..c7f503d 100644
--- a/Explorer/Explorer/Chat/ChatView.swift
+++ b/Explorer/Explorer/Chat/ChatView.swift
@@ -88,7 +88,7 @@ struct ChatView: View {
throw body.error
}
} catch {
- print(error)
+ messages.append(SwiftyGPTChatSystemMessage(content: "Oops, something went wrong!"))
}
}
}
diff --git a/Explorer/Explorer/ExplorerApp.swift b/Explorer/Explorer/ExplorerApp.swift
index aca167e..98b0539 100644
--- a/Explorer/Explorer/ExplorerApp.swift
+++ b/Explorer/Explorer/ExplorerApp.swift
@@ -12,7 +12,10 @@ import SwiftyGPTChat
struct ExplorerApp: App {
var body: some Scene {
WindowGroup {
- ChatView(environment: environment)
+ NavigationStack {
+ ChatView(environment: environment)
+ .navigationTitle("Chat")
+ }
}
}
From 74a0787cc499231a9d6dddc897175a4f9ca23e45 Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 15:35:50 +0200
Subject: [PATCH 09/16] Update README.md
---
README.md | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 930f695..091fbb6 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,11 @@
- **Modular**: SwiftyGPT is divided into modules, utilizing a different target for each feature exposed by the API. Import only the code that you really need, keeping your project lightweight and efficient.
- **Mockable**: SwiftyGPT employs protocol-oriented programming to guarantee testability and maintainability. It already exposes some mock objects that can be seamlessly utilized in testing or SwiftUI previews, eliminating the need for actual API calls.
+---
+# Explore
+Explore SwiftGPT's capabilities with Explorer app that showcases some of the most popular use cases and provides practical examples of how to utilize it in your projects.
+Whether you're interested in generating text, having conversations, or summarizing documents, Explorer demonstrates the power and versatility of SwiftGPT.
+
---
# Recommendations
@@ -21,8 +26,8 @@ To ensure security and flexibility, we recommend loading your OpenAI API key usi
Here's a simple example of how you can load your OpenAI API key from an environment variable in Swift:
```swift
-guard let apiKey = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] else {
- fatalError("Missing OpenAI API key. Please set the OPENAI_API_KEY environment variable.")
+guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else {
+ fatalError("Missing OpenAI API key. Please set the OPEN_AI_API_KEY environment variable.")
}
```
From 461a9233d4cf9367c1f7be3114606d93520919fa Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 15:43:17 +0200
Subject: [PATCH 10/16] Update README.md
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 091fbb6..767842b 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@
---
# Explore
-Explore SwiftGPT's capabilities with Explorer app that showcases some of the most popular use cases and provides practical examples of how to utilize it in your projects.
-Whether you're interested in generating text, having conversations, or summarizing documents, Explorer demonstrates the power and versatility of SwiftGPT.
+Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
+Explore popular use cases and practical examples for seamless integration into your projects.
---
# Recommendations
@@ -44,7 +44,7 @@ Every donation, no matter how small, makes a big difference. Thank you for consi
---
# Note
-The content of this README.md file has been automatically generated using SwiftyGPT.
+The content of this file has been automatically generated using SwiftyGPT.
---
# License
From e6b89dbd3471ff0ad55372fc29c186b3fc0ea9b1 Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 15:59:20 +0200
Subject: [PATCH 11/16] Update README.md
---
README.md | 31 ++++++++++++++++++++++++++++---
1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 767842b..8c4bfd3 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,37 @@
- **Mockable**: SwiftyGPT employs protocol-oriented programming to guarantee testability and maintainability. It already exposes some mock objects that can be seamlessly utilized in testing or SwiftUI previews, eliminating the need for actual API calls.
---
-# Explore
+# Integration
+Integrating SwiftyGPT into your Swift project is straightforward. Follow these steps to get started:
+
+1. **Install SwiftyGPT**:
+ - If you're using Swift Package Manager (SPM):
+ - Open your Xcode project.
+ - Navigate to "File" > "Swift Packages" > "Add Package Dependency...".
+ - Enter the SwiftyGPT repository URL: `https://github.com/antonio-war/SwiftyGPT`.
+ - Follow the prompts to select the version and add SwiftyGPT to your project.
+ - SwiftyGPT exposes multiple targets, import only the ones that you really need in your project.
+ - If you're using CocoaPods or Carthage, we're sorry, but they are not currently supported.
+2. **Import SwiftyGPT**:
+ - In the files where you want to use SwiftyGPT features, import its modules at the top of the file:
+ ```swift
+ import SwiftyGPTChat
+ ```
+3. **Start Using SwiftyGPT**:
+ - Once SwiftyGPT is imported, you can start using its APIs to interact with GPT models.
+ - Refer to the documentation for guidance on how to use its features for text generation, conversation, summarization, and more.
+5. **Run Your Project**:
+ - Build and run your project to ensure that SwiftyGPT has been integrated successfully.
+ - Test out the functionality you've implemented using SwiftyGPT to ensure everything works as expected.
+That's it! You've successfully integrated SwiftyGPT into your project and can now leverage its powerful features.
+
+---
+# Exploration
Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
Explore popular use cases and practical examples for seamless integration into your projects.
---
-# Recommendations
+# Recommendation
To ensure security and flexibility, we recommend loading your OpenAI API key using environment variables instead of hardcoding it directly into your source code. This approach offers several advantages:
- **Security**: Storing sensitive information like API keys in environment variables helps prevent accidental exposure of credentials, reducing the risk of unauthorized access to your API resources.
@@ -32,7 +57,7 @@ guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else {
```
---
-# Support the Project
+# Support
Your generous donations help sustain and improve this project. Here's why supporting us is important:
1. **Covering API Costs**: Accessing certain features or services may require a paid API key. Your donations help cover the cost of maintaining these subscriptions, ensuring uninterrupted access to essential functionalities.
2. **Development and Maintenance**: Donations enable us to dedicate more time and resources to developing new features, fixing bugs, and maintaining the project's overall health. Your support directly contributes to the project's ongoing improvement and sustainability.
From bfa9c3966ffbef5eb12fb9c19812416566efe726 Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 16:04:55 +0200
Subject: [PATCH 12/16] Update README.md
---
README.md | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 8c4bfd3..13b98cb 100644
--- a/README.md
+++ b/README.md
@@ -38,8 +38,11 @@ That's it! You've successfully integrated SwiftyGPT into your project and can no
---
# Exploration
-Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
-Explore popular use cases and practical examples for seamless integration into your projects.
+Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
+Delve deep into the realm of natural language processing and unleash the full potential of SwiftGPT with these captivating features:
+- **Discover the Full Potential**: Immerse yourself in the boundless capabilities of SwiftGPT as you navigate through the intuitive interface of the Explorer app. Unveil the power of state-of-the-art GPT models and witness firsthand their transformative impact on text generation, conversation, summarization, and more.
+- **Explore Popular Use Cases**: Embark on an exploration of the most popular use cases for SwiftGPT, meticulously curated within the Explorer app. From crafting compelling narratives to engaging in dynamic conversational interactions, delve into a myriad of scenarios where SwiftGPT shines brightest.
+- **Practical Examples for Seamless Integration**: Seamlessly integrate SwiftGPT into your projects with practical examples and step-by-step guidance provided by the Explorer app. Unlock the secrets to effortless integration and harness the unparalleled versatility of SwiftGPT to elevate your applications to new heights of excellence.
---
# Recommendation
From 5ebf6038bbfff2a874496f3534ed2ea0e0e1da67 Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 16:34:17 +0200
Subject: [PATCH 13/16] Update README.md
---
README.md | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/README.md b/README.md
index 13b98cb..d0069ad 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,32 @@ Integrating SwiftyGPT into your Swift project is straightforward. Follow these s
- Test out the functionality you've implemented using SwiftyGPT to ensure everything works as expected.
That's it! You've successfully integrated SwiftyGPT into your project and can now leverage its powerful features.
+---
+# Usage
+The main steps for using SwiftyGPTChat into your project are outlined below, guiding you through the process.
+
+### Service Definition
+First, define a `SwiftyGPTChatService`. You have three options:
+- Use `SwiftyGPTChatNetworkingService` if you want to execute API calls to OpenAI (requires an API Key).
+- Use `SwiftyGPTChatMockService` for mocked responses, ideal for testing or SwiftUI previews.
+- Create your custom instance to suit any of your purposes.
+
+```swift
+ import SwiftyGPTChat
+
+ // Using SwiftyGPTChatNetworkingService
+ let service = SwiftyGPTChatNetworkingService(apiKey: "YOUR_API_KEY")
+
+ // Using SwiftyGPTChatMockService
+ let service = SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.5)
+```
+### Manager creation
+Create a SwiftyGPTChatManager instance using the defined service.
+
+```swift
+ let manager = SwiftyGPTChatManager(service: service)
+```
+
---
# Exploration
Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
From c299f03dc7560eb77f126286ed9b4e79c41f2a0b Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 16:35:25 +0200
Subject: [PATCH 14/16] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d0069ad..c27e2e0 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ That's it! You've successfully integrated SwiftyGPT into your project and can no
# Usage
The main steps for using SwiftyGPTChat into your project are outlined below, guiding you through the process.
-### Service Definition
+### Service definition
First, define a `SwiftyGPTChatService`. You have three options:
- Use `SwiftyGPTChatNetworkingService` if you want to execute API calls to OpenAI (requires an API Key).
- Use `SwiftyGPTChatMockService` for mocked responses, ideal for testing or SwiftUI previews.
From 1e93b2daa5ccac35c1bf0812a4d08307eb77c961 Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 16:48:38 +0200
Subject: [PATCH 15/16] Update README.md
---
README.md | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/README.md b/README.md
index c27e2e0..ff2d062 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,21 @@ Create a SwiftyGPTChatManager instance using the defined service.
```swift
let manager = SwiftyGPTChatManager(service: service)
```
+### Prompt engineering
+Create your prompt using the various message types available.
+```swift
+ let messages: [any SwiftyGPTChatMessage] = [
+ SwiftyGPTChatSystemMessage(content: "You are Victor from Fallout New Vegas"),
+ SwiftyGPTChatUserMessage(content: "What's your name ?")
+ ]
+```
+### Request execution
+Execute the request using the defined messages and a high degree of customization for all available parameters.
+
+```swift
+ let response = try await manager.send(messages: messages, model: .gpt3_5_turbo, frequencyPenalty: 0.5)
+```
---
# Exploration
Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
From 57f9b653316303ec80c9bbc65cf36bd87723420c Mon Sep 17 00:00:00 2001
From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com>
Date: Sun, 28 Apr 2024 16:55:36 +0200
Subject: [PATCH 16/16] Update README.md
---
README.md | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/README.md b/README.md
index ff2d062..5c00a99 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,22 @@ Execute the request using the defined messages and a high degree of customizatio
```swift
let response = try await manager.send(messages: messages, model: .gpt3_5_turbo, frequencyPenalty: 0.5)
```
+### Response handling
+If successful, by default the response message is found within the first choice received. But this may vary based on the type of request you make.
+In case of failure, however, the response body always contains an error describing what went wrong.
+
+```swift
+ switch response {
+ case .success(let body):
+ if let receivedMessage = body.choices.first?.message {
+ messages.append(receivedMessage)
+ } else {
+ print("Oops, there are no available choices!")
+ }
+ case .failure(let body):
+ print(body.error)
+ }
+```
---
# Exploration
Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.