-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
95d4249
commit eec9a25
Showing
6 changed files
with
209 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 0 additions & 23 deletions
23
Playground/OKPlayground.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// | ||
// ChatWithFormatView.swift | ||
// OKPlayground | ||
// | ||
// Created by Michel-Andre Chirita on 30/12/2024. | ||
// | ||
|
||
import Combine | ||
import OllamaKit | ||
import SwiftUI | ||
|
||
struct ChatWithFormatView: View { | ||
|
||
enum ViewState { | ||
case idle | ||
case loading | ||
case error(String) | ||
} | ||
|
||
@Environment(ViewModel.self) private var viewModel | ||
|
||
@State private var model: String? = nil | ||
/// TIP: be sure to include "return as JSON" in your prompt | ||
@State private var prompt = "Lists of the 10 biggest countries in the world with their iso code as id, name and capital, return as JSON" | ||
@State private var cancellables = Set<AnyCancellable>() | ||
@State private var viewState: ViewState = .idle | ||
|
||
@State private var responseItems: [ResponseItem] = [] | ||
|
||
var body: some View { | ||
NavigationStack { | ||
Form { | ||
Section { | ||
Picker("Model", selection: $model) { | ||
ForEach(viewModel.models, id: \.self) { model in | ||
Text(model) | ||
.tag(model as String?) | ||
} | ||
} | ||
|
||
TextField("Prompt", text: $prompt, axis: .vertical) | ||
.lineLimit(5) | ||
} | ||
|
||
Section { | ||
Button("Chat Async", action: actionAsync) | ||
Button("Chat Combine", action: actionCombine) | ||
} | ||
|
||
switch viewState { | ||
case .idle: | ||
EmptyView() | ||
|
||
case .loading: | ||
ProgressView() | ||
.id(UUID()) | ||
|
||
case .error(let error): | ||
Text(error) | ||
.foregroundStyle(.red) | ||
} | ||
|
||
Section("Response") { | ||
ForEach(responseItems) { item in | ||
Text("Country: " + item.country + ", capital: " + item.capital) | ||
} | ||
} | ||
} | ||
.navigationTitle("Chat with Format") | ||
.navigationBarTitleDisplayMode(.inline) | ||
.onAppear { | ||
model = viewModel.models.first | ||
} | ||
} | ||
} | ||
|
||
func actionAsync() { | ||
clearResponse() | ||
|
||
guard let model = model else { return } | ||
let messages = [OKChatRequestData.Message(role: .user, content: prompt)] | ||
var data = OKChatRequestData(model: model, messages: messages, format: getFormat()) | ||
data.options = OKCompletionOptions(temperature: 0) /// TIP: better results with temperature = 0 | ||
self.viewState = .loading | ||
|
||
Task { | ||
do { | ||
var message: String = "" | ||
for try await chunk in viewModel.ollamaKit.chat(data: data) { | ||
if let content = chunk.message?.content { | ||
message.append(content) | ||
} | ||
if chunk.done { | ||
self.viewState = .idle | ||
decodeResponse(message) | ||
} | ||
} | ||
} catch { | ||
print("Error:", error.localizedDescription) | ||
self.viewState = .error(error.localizedDescription) | ||
} | ||
} | ||
} | ||
|
||
func actionCombine() { | ||
clearResponse() | ||
|
||
guard let model = model else { return } | ||
let messages = [OKChatRequestData.Message(role: .user, content: prompt)] | ||
var data = OKChatRequestData(model: model, messages: messages, format: getFormat()) | ||
data.options = OKCompletionOptions(temperature: 0) /// TIP: better results with temperature = 0 | ||
self.viewState = .loading | ||
|
||
var message: String = "" | ||
viewModel.ollamaKit.chat(data: data) | ||
.compactMap { $0.message?.content } | ||
.scan("", { result, nextChunk in | ||
result + nextChunk | ||
}) | ||
.sink { completion in | ||
switch completion { | ||
case .finished: | ||
print("Finished") | ||
decodeResponse(message) | ||
self.viewState = .idle | ||
case .failure(let error): | ||
print("Error:", error.localizedDescription) | ||
self.viewState = .error(error.localizedDescription) | ||
} | ||
} receiveValue: { value in | ||
message = value | ||
} | ||
.store(in: &cancellables) | ||
} | ||
|
||
private func getFormat() -> OKJSONValue { | ||
return | ||
.object(["type": .string("array"), | ||
"items": .object([ | ||
"type" : .string("object"), | ||
"properties": .object([ | ||
"id": .object(["type" : .string("string")]), | ||
"country": .object(["type" : .string("string")]), | ||
"capital": .object(["type" : .string("string")]), | ||
]), | ||
"required": .array([.string("id"), .string("country"), .string("capital")]) | ||
]) | ||
]) | ||
} | ||
|
||
private func decodeResponse(_ content: String) { | ||
do { | ||
guard let data = content.data(using: .utf8) else { return } | ||
let response = try JSONDecoder().decode([ResponseItem].self, from: data) | ||
self.responseItems = response | ||
} catch { | ||
print("Error message: \(error)") | ||
self.viewState = .error(error.localizedDescription) | ||
} | ||
} | ||
|
||
private func clearResponse() { | ||
self.responseItems = [] | ||
} | ||
} | ||
|
||
struct ResponseItem: Identifiable, Codable { | ||
let id: String | ||
let country: String | ||
let capital: String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters