|
| 1 | +// |
| 2 | +// PredictedOutputsView.swift |
| 3 | +// Playground |
| 4 | +// |
| 5 | +// Created by Kevin Hermawan on 11/5/24. |
| 6 | +// |
| 7 | + |
| 8 | +import SwiftUI |
| 9 | +import LLMChatOpenAI |
| 10 | + |
| 11 | +struct PredictedOutputsView: View { |
| 12 | + let provider: ServiceProvider |
| 13 | + |
| 14 | + @Environment(AppViewModel.self) private var viewModel |
| 15 | + @State private var isPreferencesPresented: Bool = false |
| 16 | + |
| 17 | + @State private var prompt: String = "Replace the Username property with an Email property. Respond only with code, and with no markdown formatting." |
| 18 | + @State private var response: String = "" |
| 19 | + @State private var acceptedPredictionTokens: Int = 0 |
| 20 | + @State private var rejectedPredictionTokens: Int = 0 |
| 21 | + @State private var inputTokens: Int = 0 |
| 22 | + @State private var outputTokens: Int = 0 |
| 23 | + @State private var totalTokens: Int = 0 |
| 24 | + |
| 25 | + private let prediction = """ |
| 26 | + /// <summary> |
| 27 | + /// Represents a user with a first name, last name, and username. |
| 28 | + /// </summary> |
| 29 | + public class User |
| 30 | + { |
| 31 | + /// <summary> |
| 32 | + /// Gets or sets the user's first name. |
| 33 | + /// </summary> |
| 34 | + public string FirstName { get; set; } |
| 35 | + |
| 36 | + /// <summary> |
| 37 | + /// Gets or sets the user's last name. |
| 38 | + /// </summary> |
| 39 | + public string LastName { get; set; } |
| 40 | + |
| 41 | + /// <summary> |
| 42 | + /// Gets or sets the user's username. |
| 43 | + /// </summary> |
| 44 | + public string Username { get; set; } |
| 45 | + } |
| 46 | + """ |
| 47 | + |
| 48 | + var body: some View { |
| 49 | + @Bindable var viewModelBindable = viewModel |
| 50 | + |
| 51 | + VStack { |
| 52 | + Form { |
| 53 | + Section("Prompt") { |
| 54 | + TextField("Prompt", text: $prompt) |
| 55 | + } |
| 56 | + |
| 57 | + Section("Prediction") { |
| 58 | + Text(prediction) |
| 59 | + } |
| 60 | + |
| 61 | + Section("Response") { |
| 62 | + Text(response) |
| 63 | + } |
| 64 | + |
| 65 | + Section("Prediction Section") { |
| 66 | + Text("Accepted Prediction Tokens") |
| 67 | + .badge(acceptedPredictionTokens.formatted()) |
| 68 | + |
| 69 | + Text("Rejected Prediction Tokens") |
| 70 | + .badge(rejectedPredictionTokens.formatted()) |
| 71 | + } |
| 72 | + |
| 73 | + UsageSection(inputTokens: inputTokens, outputTokens: outputTokens, totalTokens: totalTokens) |
| 74 | + } |
| 75 | + |
| 76 | + VStack { |
| 77 | + SendButton(stream: viewModel.stream, onSend: onSend, onStream: onStream) |
| 78 | + } |
| 79 | + } |
| 80 | + .toolbar { |
| 81 | + ToolbarItem(placement: .principal) { |
| 82 | + NavigationTitle("Predicted Outputs") |
| 83 | + } |
| 84 | + |
| 85 | + ToolbarItem(placement: .primaryAction) { |
| 86 | + Button("Preferences", systemImage: "gearshape", action: { isPreferencesPresented.toggle() }) |
| 87 | + } |
| 88 | + } |
| 89 | + .sheet(isPresented: $isPreferencesPresented) { |
| 90 | + PreferencesView() |
| 91 | + } |
| 92 | + .onAppear { |
| 93 | + viewModel.setup(for: provider) |
| 94 | + } |
| 95 | + .onDisappear { |
| 96 | + viewModel.selectedModel = "" |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + private func onSend() { |
| 101 | + clear() |
| 102 | + |
| 103 | + let messages = [ |
| 104 | + ChatMessage(role: .user, content: prompt), |
| 105 | + ChatMessage(role: .user, content: prediction) |
| 106 | + ] |
| 107 | + |
| 108 | + let options = ChatOptions( |
| 109 | + prediction: .init(type: .content, content: [.init(type: "text", text: prediction)]), |
| 110 | + temperature: viewModel.temperature |
| 111 | + ) |
| 112 | + |
| 113 | + Task { |
| 114 | + do { |
| 115 | + let completion = try await viewModel.chat.send(model: viewModel.selectedModel, messages: messages, options: options) |
| 116 | + |
| 117 | + if let content = completion.choices.first?.message.content { |
| 118 | + self.response = content |
| 119 | + } |
| 120 | + |
| 121 | + if let usage = completion.usage { |
| 122 | + if let completionTokensDetails = usage.completionTokensDetails { |
| 123 | + self.acceptedPredictionTokens = completionTokensDetails.acceptedPredictionTokens |
| 124 | + self.rejectedPredictionTokens = completionTokensDetails.rejectedPredictionTokens |
| 125 | + } |
| 126 | + |
| 127 | + self.inputTokens = usage.promptTokens |
| 128 | + self.outputTokens = usage.completionTokens |
| 129 | + self.totalTokens = usage.totalTokens |
| 130 | + } |
| 131 | + } catch { |
| 132 | + print(String(describing: error)) |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + private func onStream() { |
| 138 | + clear() |
| 139 | + |
| 140 | + let messages = [ |
| 141 | + ChatMessage(role: .user, content: prompt), |
| 142 | + ChatMessage(role: .user, content: prediction) |
| 143 | + ] |
| 144 | + |
| 145 | + let options = ChatOptions( |
| 146 | + prediction: .init(type: .content, content: prediction), |
| 147 | + temperature: viewModel.temperature |
| 148 | + ) |
| 149 | + |
| 150 | + Task { |
| 151 | + do { |
| 152 | + for try await chunk in viewModel.chat.stream(model: viewModel.selectedModel, messages: messages, options: options) { |
| 153 | + if let content = chunk.choices.first?.delta.content { |
| 154 | + self.response += content |
| 155 | + } |
| 156 | + |
| 157 | + if let usage = chunk.usage { |
| 158 | + if let completionTokensDetails = usage.completionTokensDetails { |
| 159 | + self.acceptedPredictionTokens = completionTokensDetails.acceptedPredictionTokens |
| 160 | + self.rejectedPredictionTokens = completionTokensDetails.rejectedPredictionTokens |
| 161 | + } |
| 162 | + |
| 163 | + self.inputTokens = usage.promptTokens ?? 0 |
| 164 | + self.outputTokens = usage.completionTokens ?? 0 |
| 165 | + self.totalTokens = usage.totalTokens ?? 0 |
| 166 | + } |
| 167 | + } |
| 168 | + } catch { |
| 169 | + print(String(describing: error)) |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + private func clear() { |
| 175 | + response = "" |
| 176 | + acceptedPredictionTokens = 0 |
| 177 | + rejectedPredictionTokens = 0 |
| 178 | + inputTokens = 0 |
| 179 | + outputTokens = 0 |
| 180 | + totalTokens = 0 |
| 181 | + } |
| 182 | +} |
0 commit comments