I have created chatGPT clone. I have one issue with printing the responses when I type and send it will loop my question and cutoff API response.
code
import SwiftUI
import OpenAISwift
final class ViewModel: ObservableObject{
init(){}
private var client : OpenAISwift?
func setup(){
client = OpenAISwift(authToken: "sk-")
}
func send(text:String,completion:@escaping (String) -> Void ){
self.client?.sendCompletion(with: text,
maxTokens:500,completionHandler: {result in
switch result {
case .success(let model):
let output = model.choices.first?.text ?? ""
completion(output)
case .failure:
break
}
})
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@State var messageText = ""
@State var messages = [String]()
@State private var animating: Bool = false
var body: some View {
VStack(alignment: .leading) {
AnimatedTextView(text: messages.joined(separator: "\n"))
.frame(height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
HStack {
TextField("type here ...", text: $messageText)
Button("Send") {
send()
}
}
}
.onAppear {
viewModel.setup()
}
.padding()
}
private func send() {
guard !messageText.trimmingCharacters(in: .whitespaces).isEmpty else { return }
messages.append(messageText)
viewModel.send(text: messageText) { response in
DispatchQueue.main.async {
self.messages.append("ChatGPT: " + response)
animating = true
}
}
}
}
struct AnimatedTextView: View {
let text: String
@State private var letters: [String] = []
@State private var displayText: String = ""
var body: some View {
Text(displayText)
.font(.system(size: 20))
.frame(height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
.onChange(of: text) { _ in
letters = Array(text).map { String($0) }
loop()
}
}
private func loop() {
let pace = 0.05
if !letters.isEmpty {
DispatchQueue.main.asyncAfter(deadline: .now() + pace) {
displayText += letters.removeFirst()
loop()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First use a StateObject instead of ObservedObject :
struct ContentView: View {
@StateObject var viewModel = ViewModel() // Here
@State var messageText = ""
@State var messages = [String]()
@State var lastMessage: String = "" // save last entered message
@State private var animating: Bool = false
...
AnimatedTextView(text: $lastMessage) // Binding will auto update
...
private func send() {
guard !messageText.trimmingCharacters(in: .whitespaces).isEmpty else { return }
messages.append(messageText)
lastMessage = messageText + "\n" // Save last message
To animate the text you should use Binding in AnimatedTextView and pass only the last message :
struct AnimatedTextView: View {
@Binding var text: String
With this you only send the last question to the AnimatedTextView and update it accordingly.